xref: /haiku/src/add-ons/kernel/file_cache/launch_speedup.cpp (revision b55a57da7173b9af0432bd3e148d03f06161d036)
1 /*
2  * Copyright 2005, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 /** This module memorizes all opened files for a certain session. A session
7  *	can be the start of an application or the boot process.
8  *	When a session is started, it will prefetch all files from an earlier
9  *	session in order to speed up the launching or booting process.
10  *
11  *	Note: this module is using private kernel API and is definitely not
12  *		meant to be an example on how to write modules.
13  */
14 
15 
16 #include "launch_speedup.h"
17 
18 #include <KernelExport.h>
19 #include <Node.h>
20 
21 #include <util/kernel_cpp.h>
22 #include <util/khash.h>
23 #include <util/AutoLock.h>
24 #include <thread.h>
25 #include <team.h>
26 #include <file_cache.h>
27 #include <generic_syscall.h>
28 #include <syscalls.h>
29 
30 #include <unistd.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <stdio.h>
34 #include <errno.h>
35 #include <ctype.h>
36 
37 extern dev_t gBootDevice;
38 
39 
40 // ToDo: combine the last 3-5 sessions to their intersection
41 // ToDo: maybe ignore sessions if the node count is < 3 (without system libs)
42 
43 #define TRACE_CACHE_MODULE
44 #ifdef TRACE_CACHE_MODULE
45 #	define TRACE(x) dprintf x
46 #else
47 #	define TRACE(x) ;
48 #endif
49 
50 #define VNODE_HASH(mountid, vnodeid) (((uint32)((vnodeid) >> 32) \
51 	+ (uint32)(vnodeid)) ^ (uint32)(mountid))
52 
53 struct data_part {
54 	off_t		offset;
55 	off_t		size;
56 };
57 
58 struct node {
59 	struct node	*next;
60 	node_ref	ref;
61 	int32		ref_count;
62 	bigtime_t	timestamp;
63 	data_part	parts[5];
64 	size_t		part_count;
65 };
66 
67 class Session {
68 	public:
69 		Session(team_id team, const char *name, dev_t device,
70 			ino_t node, int32 seconds);
71 		Session(const char *name);
72 		~Session();
73 
74 		status_t InitCheck();
75 		team_id Team() const { return fTeam; }
76 		const char *Name() const { return fName; }
77 		const node_ref &NodeRef() const { return fNodeRef; }
78 		bool IsActive() const { return fActiveUntil >= system_time(); }
79 		bool IsClosing() const { return fClosing; }
80 		bool IsMainSession() const;
81 		bool IsWorthSaving() const;
82 
83 		void AddNode(dev_t device, ino_t node);
84 		void RemoveNode(dev_t device, ino_t node);
85 
86 		void Lock() { mutex_lock(&fLock); }
87 		void Unlock() { mutex_unlock(&fLock); }
88 
89 		status_t StartWatchingTeam();
90 		void StopWatchingTeam();
91 
92 		status_t LoadFromDirectory(int fd);
93 		status_t Save();
94 		void Prefetch();
95 
96 		Session *&Next() { return fNext; }
97 		static uint32 NextOffset() { return offsetof(Session, fNext); }
98 
99 	private:
100 		struct node *_FindNode(dev_t device, ino_t node);
101 
102 		Session		*fNext;
103 		char		fName[B_OS_NAME_LENGTH];
104 		mutex		fLock;
105 		hash_table	*fNodeHash;
106 		struct node	*fNodes;
107 		int32		fNodeCount;
108 		team_id		fTeam;
109 		node_ref	fNodeRef;
110 		bigtime_t	fActiveUntil;
111 		bigtime_t	fTimestamp;
112 		bool		fClosing;
113 		bool		fIsWatchingTeam;
114 };
115 
116 class SessionGetter {
117 	public:
118 		SessionGetter(team_id team, Session **_session);
119 		~SessionGetter();
120 
121 		status_t New(const char *name, dev_t device, ino_t node,
122 					Session **_session);
123 		void Stop();
124 
125 	private:
126 		Session	*fSession;
127 };
128 
129 static Session *sMainSession;
130 static hash_table *sTeamHash;
131 static hash_table *sPrefetchHash;
132 static Session *sMainPrefetchSessions;
133 	// singly-linked list
134 static recursive_lock sLock;
135 
136 
137 node_ref::node_ref()
138 {
139 	// part of libbe.so
140 }
141 
142 
143 static int
144 node_compare(void *_node, const void *_key)
145 {
146 	struct node *node = (struct node *)_node;
147 	const struct node_ref *key = (node_ref *)_key;
148 
149 	if (node->ref.device == key->device && node->ref.node == key->node)
150 		return 0;
151 
152 	return -1;
153 }
154 
155 
156 static uint32
157 node_hash(void *_node, const void *_key, uint32 range)
158 {
159 	struct node *node = (struct node *)_node;
160 	const struct node_ref *key = (node_ref *)_key;
161 
162 	if (node != NULL)
163 		return VNODE_HASH(node->ref.device, node->ref.node) % range;
164 
165 	return VNODE_HASH(key->device, key->node) % range;
166 }
167 
168 
169 static int
170 prefetch_compare(void *_session, const void *_key)
171 {
172 	Session *session = (Session *)_session;
173 	const struct node_ref *key = (node_ref *)_key;
174 
175 	if (session->NodeRef().device == key->device
176 		&& session->NodeRef().node == key->node)
177 		return 0;
178 
179 	return -1;
180 }
181 
182 
183 static uint32
184 prefetch_hash(void *_session, const void *_key, uint32 range)
185 {
186 	Session *session = (Session *)_session;
187 	const struct node_ref *key = (node_ref *)_key;
188 
189 	if (session != NULL)
190 		return VNODE_HASH(session->NodeRef().device, session->NodeRef().node) % range;
191 
192 	return VNODE_HASH(key->device, key->node) % range;
193 }
194 
195 
196 static int
197 team_compare(void *_session, const void *_key)
198 {
199 	Session *session = (Session *)_session;
200 	const team_id *team = (const team_id *)_key;
201 
202 	if (session->Team() == *team)
203 		return 0;
204 
205 	return -1;
206 }
207 
208 
209 static uint32
210 team_hash(void *_session, const void *_key, uint32 range)
211 {
212 	Session *session = (Session *)_session;
213 	const team_id *team = (const team_id *)_key;
214 
215 	if (session != NULL)
216 		return session->Team() % range;
217 
218 	return *team % range;
219 }
220 
221 
222 static void
223 stop_session(Session *session)
224 {
225 	if (session == NULL)
226 		return;
227 
228 	TRACE(("stop_session(%s)\n", session->Name()));
229 
230 	if (session->IsWorthSaving())
231 		session->Save();
232 
233 	{
234 		RecursiveLocker locker(&sLock);
235 
236 		if (session->Team() >= B_OK)
237 			hash_remove(sTeamHash, session);
238 
239 		if (session == sMainSession)
240 			sMainSession = NULL;
241 	}
242 
243 	delete session;
244 }
245 
246 
247 static Session *
248 start_session(team_id team, dev_t device, ino_t node, const char *name,
249 	int32 seconds = 30)
250 {
251 	RecursiveLocker locker(&sLock);
252 
253 	Session *session = new Session(team, name, device, node, seconds);
254 	if (session == NULL)
255 		return NULL;
256 
257 	if (session->InitCheck() != B_OK || session->StartWatchingTeam() != B_OK) {
258 		delete session;
259 		return NULL;
260 	}
261 
262 	// let's see if there is a prefetch session for this session
263 
264 	Session *prefetchSession;
265 	if (session->IsMainSession()) {
266 		// search for session by name
267 		for (prefetchSession = sMainPrefetchSessions;
268 				prefetchSession != NULL;
269 				prefetchSession = prefetchSession->Next()) {
270 			if (!strcmp(prefetchSession->Name(), name)) {
271 				// found session!
272 				break;
273 			}
274 		}
275 	} else {
276 		// ToDo: search for session by device/node ID
277 		prefetchSession = NULL;
278 	}
279 	if (prefetchSession != NULL) {
280 		TRACE(("found prefetch session %s\n", prefetchSession->Name()));
281 		prefetchSession->Prefetch();
282 	}
283 
284 	if (team >= B_OK)
285 		hash_insert(sTeamHash, session);
286 
287 	session->Lock();
288 	return session;
289 }
290 
291 
292 static void
293 team_gone(team_id team, void *_session)
294 {
295 	Session *session = (Session *)_session;
296 
297 	session->Lock();
298 	stop_session(session);
299 }
300 
301 
302 static bool
303 parse_node_ref(const char *string, node_ref &ref, const char **_end = NULL)
304 {
305 	// parse node ref
306 	char *end;
307 	ref.device = strtol(string, &end, 0);
308 	if (end == NULL || ref.device == 0)
309 		return false;
310 
311 	ref.node = strtoull(end + 1, &end, 0);
312 
313 	if (_end)
314 		*_end = end;
315 	return true;
316 }
317 
318 
319 static struct node *
320 new_node(dev_t device, ino_t id)
321 {
322 	struct node *node = new ::node;
323 	if (node == NULL)
324 		return NULL;
325 
326 	node->ref.device = device;
327 	node->ref.node = id;
328 	node->timestamp = system_time();
329 
330 	return node;
331 }
332 
333 
334 static void
335 load_prefetch_data()
336 {
337 	DIR *dir = opendir("/etc/launch_cache");
338 	if (dir == NULL)
339 		return;
340 
341 	struct dirent *dirent;
342 	while ((dirent = readdir(dir)) != NULL) {
343 		if (dirent->d_name[0] == '.')
344 			continue;
345 
346 		Session *session = new Session(dirent->d_name);
347 
348 		if (session->LoadFromDirectory(dirfd(dir)) != B_OK) {
349 			delete session;
350 			continue;
351 		}
352 
353 		if (session->IsMainSession()) {
354 			session->Next() = sMainPrefetchSessions;
355 			sMainPrefetchSessions = session;
356 		} else {
357 			hash_insert(sPrefetchHash, session);
358 		}
359 	}
360 
361 	closedir(dir);
362 }
363 
364 
365 //	#pragma mark -
366 
367 
368 Session::Session(team_id team, const char *name, dev_t device,
369 	ino_t node, int32 seconds)
370 	:
371 	fNodes(NULL),
372 	fNodeCount(0),
373 	fTeam(team),
374 	fClosing(false),
375 	fIsWatchingTeam(false)
376 {
377 	if (name != NULL) {
378 		size_t length = strlen(name) + 1;
379 		if (length > B_OS_NAME_LENGTH)
380 			name += length - B_OS_NAME_LENGTH;
381 
382 		strlcpy(fName, name, B_OS_NAME_LENGTH);
383 	} else
384 		fName[0] = '\0';
385 
386 	mutex_init(&fLock, "launch speedup session");
387 	fNodeHash = hash_init(64, 0, &node_compare, &node_hash);
388 	fActiveUntil = system_time() + seconds * 1000000LL;
389 	fTimestamp = system_time();
390 
391 	fNodeRef.device = device;
392 	fNodeRef.node = node;
393 
394 	TRACE(("start session %ld:%Ld \"%s\", system_time: %Ld, active until: %Ld\n",
395 		device, node, Name(), system_time(), fActiveUntil));
396 }
397 
398 
399 Session::Session(const char *name)
400 	:
401 	fNodeHash(NULL),
402 	fNodes(NULL),
403 	fClosing(false),
404 	fIsWatchingTeam(false)
405 {
406 	fTeam = -1;
407 	fNodeRef.device = -1;
408 	fNodeRef.node = -1;
409 
410 	if (isdigit(name[0]))
411 		parse_node_ref(name, fNodeRef);
412 
413 	strlcpy(fName, name, B_OS_NAME_LENGTH);
414 }
415 
416 
417 Session::~Session()
418 {
419 	mutex_destroy(&fLock);
420 
421 	// free all nodes
422 
423 	if (fNodeHash) {
424 		// ... from the hash
425 		uint32 cookie = 0;
426 		struct node *node;
427 		while ((node = (struct node *)hash_remove_first(fNodeHash, &cookie)) != NULL) {
428 			//TRACE(("  node %ld:%Ld\n", node->ref.device, node->ref.node));
429 			free(node);
430 		}
431 
432 		hash_uninit(fNodeHash);
433 	} else {
434 		// ... from the list
435 		struct node *node = fNodes, *next = NULL;
436 
437 		for (; node != NULL; node = next) {
438 			next = node->next;
439 			free(node);
440 		}
441 	}
442 
443 	StopWatchingTeam();
444 }
445 
446 
447 status_t
448 Session::InitCheck()
449 {
450 	if (fNodeHash == NULL)
451 		return B_NO_MEMORY;
452 
453 	return B_OK;
454 }
455 
456 
457 node *
458 Session::_FindNode(dev_t device, ino_t node)
459 {
460 	node_ref key;
461 	key.device = device;
462 	key.node = node;
463 
464 	return (struct node *)hash_lookup(fNodeHash, &key);
465 }
466 
467 
468 void
469 Session::AddNode(dev_t device, ino_t id)
470 {
471 	struct node *node = _FindNode(device, id);
472 	if (node != NULL) {
473 		node->ref_count++;
474 		return;
475 	}
476 
477 	node = new_node(device, id);
478 	if (node == NULL)
479 		return;
480 
481 	hash_insert(fNodeHash, node);
482 	fNodeCount++;
483 }
484 
485 
486 void
487 Session::RemoveNode(dev_t device, ino_t id)
488 {
489 	struct node *node = _FindNode(device, id);
490 	if (node != NULL && --node->ref_count <= 0) {
491 		hash_remove(fNodeHash, node);
492 		fNodeCount--;
493 	}
494 }
495 
496 
497 status_t
498 Session::StartWatchingTeam()
499 {
500 	if (Team() < B_OK)
501 		return B_OK;
502 
503 	status_t status = start_watching_team(Team(), team_gone, this);
504 	if (status == B_OK)
505 		fIsWatchingTeam = true;
506 
507 	return status;
508 }
509 
510 
511 void
512 Session::StopWatchingTeam()
513 {
514 	if (fIsWatchingTeam)
515 		stop_watching_team(Team(), team_gone, this);
516 }
517 
518 
519 void
520 Session::Prefetch()
521 {
522 	if (fNodes == NULL || fNodeHash != NULL)
523 		return;
524 
525 	for (struct node *node = fNodes; node != NULL; node = node->next) {
526 		cache_prefetch(node->ref.device, node->ref.node, 0, ~0UL);
527 	}
528 }
529 
530 
531 status_t
532 Session::LoadFromDirectory(int directoryFD)
533 {
534 	TRACE(("load session %s\n", Name()));
535 
536 	int fd = _kern_open(directoryFD, Name(), O_RDONLY, 0);
537 	if (fd < B_OK)
538 		return fd;
539 
540 	struct stat stat;
541 	if (fstat(fd, &stat) != 0) {
542 		close(fd);
543 		return errno;
544 	}
545 
546 	if (stat.st_size > 32768) {
547 		// for safety reasons
548 		// ToDo: make a bit larger later
549 		close(fd);
550 		return B_BAD_DATA;
551 	}
552 
553 	char *buffer = (char *)malloc(stat.st_size);
554 	if (buffer == NULL) {
555 		close(fd);
556 		return B_NO_MEMORY;
557 	}
558 
559 	if (read(fd, buffer, stat.st_size) < stat.st_size) {
560 		free(buffer);
561 		close(fd);
562 		return B_ERROR;
563 	}
564 
565 	const char *line = buffer;
566 	node_ref nodeRef;
567 	while (parse_node_ref(line, nodeRef, &line)) {
568 		struct node *node = new_node(nodeRef.device, nodeRef.node);
569 		if (node != NULL) {
570 			// note: this reverses the order of the nodes in the file
571 			node->next = fNodes;
572 			fNodes = node;
573 		}
574 		line++;
575 	}
576 
577 	free(buffer);
578 	close(fd);
579 	return B_OK;
580 }
581 
582 
583 status_t
584 Session::Save()
585 {
586 	fClosing = true;
587 
588 	char name[B_OS_NAME_LENGTH + 25];
589 	if (!IsMainSession()) {
590 		snprintf(name, sizeof(name), "/etc/launch_cache/%ld:%Ld %s",
591 			fNodeRef.device, fNodeRef.node, Name());
592 	} else
593 		snprintf(name, sizeof(name), "/etc/launch_cache/%s", Name());
594 
595 	int fd = open(name, O_CREAT | O_TRUNC | O_WRONLY, 0644);
596 	if (fd < B_OK)
597 		return errno;
598 
599 	status_t status = B_OK;
600 	off_t fileSize = 0;
601 
602 	// ToDo: order nodes by timestamp... (should improve launch speed)
603 	// ToDo: test which parts of a file have been read (and save that as well)
604 
605 	// enlarge file, so that it can be written faster
606 	ftruncate(fd, 512 * 1024);
607 
608 	struct hash_iterator iterator;
609 	struct node *node;
610 
611 	hash_open(fNodeHash, &iterator);
612 	while ((node = (struct node *)hash_next(fNodeHash, &iterator)) != NULL) {
613 		snprintf(name, sizeof(name), "%ld:%Ld\n", node->ref.device, node->ref.node);
614 
615 		ssize_t bytesWritten = write(fd, name, strlen(name));
616 		if (bytesWritten < B_OK) {
617 			status = bytesWritten;
618 			break;
619 		}
620 
621 		fileSize += bytesWritten;
622 	}
623 
624 	hash_close(fNodeHash, &iterator, false);
625 
626 	ftruncate(fd, fileSize);
627 	close(fd);
628 
629 	return status;
630 }
631 
632 
633 bool
634 Session::IsWorthSaving() const
635 {
636 	// ToDo: sort out entries with only very few nodes, and those that load
637 	//	instantly, anyway
638 	if (fNodeCount < 5 || system_time() - fTimestamp < 400000) {
639 		// sort anything out that opens less than 5 files, or needs less
640 		// than 0.4 seconds to load an run
641 		return false;
642 	}
643 	return true;
644 }
645 
646 
647 bool
648 Session::IsMainSession() const
649 {
650 	return fNodeRef.node == -1;
651 }
652 
653 
654 //	#pragma mark -
655 
656 
657 SessionGetter::SessionGetter(team_id team, Session **_session)
658 {
659 	RecursiveLocker locker(&sLock);
660 
661 	if (sMainSession != NULL)
662 		fSession = sMainSession;
663 	else
664 		fSession = (Session *)hash_lookup(sTeamHash, &team);
665 
666 	if (fSession != NULL) {
667 		if (!fSession->IsClosing())
668 			fSession->Lock();
669 		else
670 			fSession = NULL;
671 	}
672 
673 	*_session = fSession;
674 }
675 
676 
677 SessionGetter::~SessionGetter()
678 {
679 	if (fSession != NULL)
680 		fSession->Unlock();
681 }
682 
683 
684 status_t
685 SessionGetter::New(const char *name, dev_t device, ino_t node,
686 	Session **_session)
687 {
688 	struct thread *thread = thread_get_current_thread();
689 	fSession = start_session(thread->team->id, device, node, name);
690 
691 	if (fSession != NULL) {
692 		*_session = fSession;
693 		return B_OK;
694 	}
695 
696 	return B_ERROR;
697 }
698 
699 
700 void
701 SessionGetter::Stop()
702 {
703 	if (fSession == sMainSession)
704 		sMainSession = NULL;
705 
706 	stop_session(fSession);
707 	fSession = NULL;
708 }
709 
710 //	#pragma mark -
711 
712 
713 static void
714 node_opened(struct vnode *vnode, int32 fdType, dev_t device, ino_t parent,
715 	ino_t node, const char *name, off_t size)
716 {
717 	if (device < gBootDevice) {
718 		// we ignore any access to rootfs, pipefs, and devfs
719 		// ToDo: if we can ever move the boot device on the fly, this will break
720 		return;
721 	}
722 
723 	Session *session;
724 	SessionGetter getter(team_get_current_team_id(), &session);
725 
726 	if (session == NULL) {
727 		char buffer[B_FILE_NAME_LENGTH];
728 		if (name == NULL
729 			&& vfs_get_vnode_name(vnode, buffer, sizeof(buffer)) == B_OK)
730 			name = buffer;
731 
732 		// create new session for this team
733 		getter.New(name, device, node, &session);
734 	}
735 
736 	if (session == NULL || !session->IsActive()) {
737 		if (sMainSession != NULL) {
738 			// ToDo: this opens a race condition with the "stop session" syscall
739 			getter.Stop();
740 		}
741 		return;
742 	}
743 
744 	session->AddNode(device, node);
745 }
746 
747 
748 static void
749 node_closed(struct vnode *vnode, int32 fdType, dev_t device, ino_t node,
750 	int32 accessType)
751 {
752 	Session *session;
753 	SessionGetter getter(team_get_current_team_id(), &session);
754 
755 	if (session == NULL)
756 		return;
757 
758 	if (accessType == FILE_CACHE_NO_IO)
759 		session->RemoveNode(device, node);
760 }
761 
762 
763 static status_t
764 launch_speedup_control(const char *subsystem, uint32 function,
765 	void *buffer, size_t bufferSize)
766 {
767 	switch (function) {
768 		case LAUNCH_SPEEDUP_START_SESSION:
769 		{
770 			char name[B_OS_NAME_LENGTH];
771 			if (!IS_USER_ADDRESS(buffer)
772 				|| user_strlcpy(name, (const char *)buffer, B_OS_NAME_LENGTH) < B_OK)
773 				return B_BAD_ADDRESS;
774 
775 			if (isdigit(name[0]) || name[0] == '.')
776 				return B_BAD_VALUE;
777 
778 			sMainSession = start_session(-1, -1, -1, name, 60);
779 			sMainSession->Unlock();
780 			return B_OK;
781 		}
782 
783 		case LAUNCH_SPEEDUP_STOP_SESSION:
784 		{
785 			char name[B_OS_NAME_LENGTH];
786 			if (!IS_USER_ADDRESS(buffer)
787 				|| user_strlcpy(name, (const char *)buffer, B_OS_NAME_LENGTH) < B_OK)
788 				return B_BAD_ADDRESS;
789 
790 			// ToDo: this check is not thread-safe
791 			if (sMainSession == NULL || strcmp(sMainSession->Name(), name))
792 				return B_BAD_VALUE;
793 
794 			if (!strcmp(name, "system boot"))
795 				dprintf("STOP BOOT %Ld\n", system_time());
796 
797 			sMainSession->Lock();
798 			stop_session(sMainSession);
799 			sMainSession = NULL;
800 			return B_OK;
801 		}
802 	}
803 
804 	return B_BAD_VALUE;
805 }
806 
807 
808 static void
809 uninit()
810 {
811 	unregister_generic_syscall(LAUNCH_SPEEDUP_SYSCALLS, 1);
812 
813 	recursive_lock_lock(&sLock);
814 
815 	// free all sessions from the hashes
816 
817 	uint32 cookie = 0;
818 	Session *session;
819 	while ((session = (Session *)hash_remove_first(sTeamHash, &cookie)) != NULL) {
820 		delete session;
821 	}
822 	cookie = 0;
823 	while ((session = (Session *)hash_remove_first(sPrefetchHash, &cookie)) != NULL) {
824 		delete session;
825 	}
826 
827 	// free all sessions from the main prefetch list
828 
829 	for (session = sMainPrefetchSessions; session != NULL; ) {
830 		sMainPrefetchSessions = session->Next();
831 		delete session;
832 		session = sMainPrefetchSessions;
833 	}
834 
835 	hash_uninit(sTeamHash);
836 	hash_uninit(sPrefetchHash);
837 	recursive_lock_destroy(&sLock);
838 }
839 
840 
841 static status_t
842 init()
843 {
844 	sTeamHash = hash_init(64, Session::NextOffset(), &team_compare, &team_hash);
845 	if (sTeamHash == NULL)
846 		return B_NO_MEMORY;
847 
848 	status_t status;
849 
850 	sPrefetchHash = hash_init(64, Session::NextOffset(), &prefetch_compare, &prefetch_hash);
851 	if (sPrefetchHash == NULL) {
852 		status = B_NO_MEMORY;
853 		goto err1;
854 	}
855 
856 	recursive_lock_init(&sLock, "launch speedup");
857 
858 	// register kernel syscalls
859 	if (register_generic_syscall(LAUNCH_SPEEDUP_SYSCALLS,
860 			launch_speedup_control, 1, 0) != B_OK) {
861 		status = B_ERROR;
862 		goto err3;
863 	}
864 
865 	// read in prefetch knowledge base
866 
867 	mkdir("/etc/launch_cache", 0755);
868 	load_prefetch_data();
869 
870 	// start boot session
871 
872 	sMainSession = start_session(-1, -1, -1, "system boot");
873 	sMainSession->Unlock();
874 	dprintf("START BOOT %Ld\n", system_time());
875 	return B_OK;
876 
877 err3:
878 	recursive_lock_destroy(&sLock);
879 	hash_uninit(sPrefetchHash);
880 err1:
881 	hash_uninit(sTeamHash);
882 	return status;
883 }
884 
885 
886 static status_t
887 std_ops(int32 op, ...)
888 {
889 	switch (op) {
890 		case B_MODULE_INIT:
891 			return init();
892 
893 		case B_MODULE_UNINIT:
894 			uninit();
895 			return B_OK;
896 
897 		default:
898 			return B_ERROR;
899 	}
900 }
901 
902 
903 static struct cache_module_info sLaunchSpeedupModule = {
904 	{
905 		CACHE_MODULES_NAME "/launch_speedup/v1",
906 		0,
907 		std_ops,
908 	},
909 	node_opened,
910 	node_closed,
911 	NULL,
912 };
913 
914 
915 module_info *modules[] = {
916 	(module_info *)&sLaunchSpeedupModule,
917 	NULL
918 };
919