xref: /haiku/src/add-ons/kernel/file_cache/launch_speedup.cpp (revision fef6144999c2fa611f59ee6ffe6dd7999501385c)
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, mount_id device,
70 			vnode_id 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(mount_id device, vnode_id node);
84 		void RemoveNode(mount_id device, vnode_id 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(mount_id device, vnode_id 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, mount_id device, vnode_id 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, mount_id device, vnode_id 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(mount_id device, vnode_id 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(dir->fd) != 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, mount_id device,
369 	vnode_id 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 (fLock.sem < B_OK)
451 		return fLock.sem;
452 
453 	if (fNodeHash == NULL)
454 		return B_NO_MEMORY;
455 
456 	return B_OK;
457 }
458 
459 
460 node *
461 Session::_FindNode(mount_id device, vnode_id node)
462 {
463 	node_ref key;
464 	key.device = device;
465 	key.node = node;
466 
467 	return (struct node *)hash_lookup(fNodeHash, &key);
468 }
469 
470 
471 void
472 Session::AddNode(mount_id device, vnode_id id)
473 {
474 	struct node *node = _FindNode(device, id);
475 	if (node != NULL) {
476 		node->ref_count++;
477 		return;
478 	}
479 
480 	node = new_node(device, id);
481 	if (node == NULL)
482 		return;
483 
484 	hash_insert(fNodeHash, node);
485 	fNodeCount++;
486 }
487 
488 
489 void
490 Session::RemoveNode(mount_id device, vnode_id id)
491 {
492 	struct node *node = _FindNode(device, id);
493 	if (node != NULL && --node->ref_count <= 0) {
494 		hash_remove(fNodeHash, node);
495 		fNodeCount--;
496 	}
497 }
498 
499 
500 status_t
501 Session::StartWatchingTeam()
502 {
503 	if (Team() < B_OK)
504 		return B_OK;
505 
506 	status_t status = start_watching_team(Team(), team_gone, this);
507 	if (status == B_OK)
508 		fIsWatchingTeam = true;
509 
510 	return status;
511 }
512 
513 
514 void
515 Session::StopWatchingTeam()
516 {
517 	if (fIsWatchingTeam)
518 		stop_watching_team(Team(), team_gone, this);
519 }
520 
521 
522 void
523 Session::Prefetch()
524 {
525 	if (fNodes == NULL || fNodeHash != NULL)
526 		return;
527 
528 	for (struct node *node = fNodes; node != NULL; node = node->next) {
529 		cache_prefetch(node->ref.device, node->ref.node, 0, ~0UL);
530 	}
531 }
532 
533 
534 status_t
535 Session::LoadFromDirectory(int directoryFD)
536 {
537 	TRACE(("load session %s\n", Name()));
538 
539 	int fd = _kern_open(directoryFD, Name(), O_RDONLY, 0);
540 	if (fd < B_OK)
541 		return fd;
542 
543 	struct stat stat;
544 	if (fstat(fd, &stat) != 0) {
545 		close(fd);
546 		return errno;
547 	}
548 
549 	if (stat.st_size > 32768) {
550 		// for safety reasons
551 		// ToDo: make a bit larger later
552 		close(fd);
553 		return B_BAD_DATA;
554 	}
555 
556 	char *buffer = (char *)malloc(stat.st_size);
557 	if (buffer == NULL) {
558 		close(fd);
559 		return B_NO_MEMORY;
560 	}
561 
562 	if (read(fd, buffer, stat.st_size) < stat.st_size) {
563 		free(buffer);
564 		close(fd);
565 		return B_ERROR;
566 	}
567 
568 	const char *line = buffer;
569 	node_ref nodeRef;
570 	while (parse_node_ref(line, nodeRef, &line)) {
571 		struct node *node = new_node(nodeRef.device, nodeRef.node);
572 		if (node != NULL) {
573 			// note: this reverses the order of the nodes in the file
574 			node->next = fNodes;
575 			fNodes = node;
576 		}
577 		line++;
578 	}
579 
580 	free(buffer);
581 	close(fd);
582 	return B_OK;
583 }
584 
585 
586 status_t
587 Session::Save()
588 {
589 	fClosing = true;
590 
591 	char name[B_OS_NAME_LENGTH + 25];
592 	if (!IsMainSession()) {
593 		snprintf(name, sizeof(name), "/etc/launch_cache/%ld:%Ld %s",
594 			fNodeRef.device, fNodeRef.node, Name());
595 	} else
596 		snprintf(name, sizeof(name), "/etc/launch_cache/%s", Name());
597 
598 	int fd = open(name, O_CREAT | O_TRUNC | O_WRONLY, 0644);
599 	if (fd < B_OK)
600 		return errno;
601 
602 	status_t status = B_OK;
603 	off_t fileSize = 0;
604 
605 	// ToDo: order nodes by timestamp... (should improve launch speed)
606 	// ToDo: test which parts of a file have been read (and save that as well)
607 
608 	// enlarge file, so that it can be written faster
609 	ftruncate(fd, 512 * 1024);
610 
611 	struct hash_iterator iterator;
612 	struct node *node;
613 
614 	hash_open(fNodeHash, &iterator);
615 	while ((node = (struct node *)hash_next(fNodeHash, &iterator)) != NULL) {
616 		snprintf(name, sizeof(name), "%ld:%Ld\n", node->ref.device, node->ref.node);
617 
618 		ssize_t bytesWritten = write(fd, name, strlen(name));
619 		if (bytesWritten < B_OK) {
620 			status = bytesWritten;
621 			break;
622 		}
623 
624 		fileSize += bytesWritten;
625 	}
626 
627 	hash_close(fNodeHash, &iterator, false);
628 
629 	ftruncate(fd, fileSize);
630 	close(fd);
631 
632 	return status;
633 }
634 
635 
636 bool
637 Session::IsWorthSaving() const
638 {
639 	// ToDo: sort out entries with only very few nodes, and those that load
640 	//	instantly, anyway
641 	if (fNodeCount < 5 || system_time() - fTimestamp < 400000) {
642 		// sort anything out that opens less than 5 files, or needs less
643 		// than 0.4 seconds to load an run
644 		return false;
645 	}
646 	return true;
647 }
648 
649 
650 bool
651 Session::IsMainSession() const
652 {
653 	return fNodeRef.node == -1;
654 }
655 
656 
657 //	#pragma mark -
658 
659 
660 SessionGetter::SessionGetter(team_id team, Session **_session)
661 {
662 	RecursiveLocker locker(&sLock);
663 
664 	if (sMainSession != NULL)
665 		fSession = sMainSession;
666 	else
667 		fSession = (Session *)hash_lookup(sTeamHash, &team);
668 
669 	if (fSession != NULL) {
670 		if (!fSession->IsClosing())
671 			fSession->Lock();
672 		else
673 			fSession = NULL;
674 	}
675 
676 	*_session = fSession;
677 }
678 
679 
680 SessionGetter::~SessionGetter()
681 {
682 	if (fSession != NULL)
683 		fSession->Unlock();
684 }
685 
686 
687 status_t
688 SessionGetter::New(const char *name, mount_id device, vnode_id node,
689 	Session **_session)
690 {
691 	struct thread *thread = thread_get_current_thread();
692 	fSession = start_session(thread->team->id, device, node, name);
693 
694 	if (fSession != NULL) {
695 		*_session = fSession;
696 		return B_OK;
697 	}
698 
699 	return B_ERROR;
700 }
701 
702 
703 void
704 SessionGetter::Stop()
705 {
706 	if (fSession == sMainSession)
707 		sMainSession = NULL;
708 
709 	stop_session(fSession);
710 	fSession = NULL;
711 }
712 
713 //	#pragma mark -
714 
715 
716 static void
717 node_opened(void *vnode, int32 fdType, mount_id device, vnode_id parent,
718 	vnode_id node, const char *name, off_t size)
719 {
720 	if (device < gBootDevice) {
721 		// we ignore any access to rootfs, pipefs, and devfs
722 		// ToDo: if we can ever move the boot device on the fly, this will break
723 		return;
724 	}
725 
726 	Session *session;
727 	SessionGetter getter(team_get_current_team_id(), &session);
728 
729 	if (session == NULL) {
730 		char buffer[B_FILE_NAME_LENGTH];
731 		if (name == NULL
732 			&& vfs_get_vnode_name(vnode, buffer, sizeof(buffer)) == B_OK)
733 			name = buffer;
734 
735 		// create new session for this team
736 		getter.New(name, device, node, &session);
737 	}
738 
739 	if (session == NULL || !session->IsActive()) {
740 		if (sMainSession != NULL) {
741 			// ToDo: this opens a race condition with the "stop session" syscall
742 			getter.Stop();
743 		}
744 		return;
745 	}
746 
747 	session->AddNode(device, node);
748 }
749 
750 
751 static void
752 node_closed(void *vnode, int32 fdType, mount_id device, vnode_id node, int32 accessType)
753 {
754 	Session *session;
755 	SessionGetter getter(team_get_current_team_id(), &session);
756 
757 	if (session == NULL)
758 		return;
759 
760 	if (accessType == FILE_CACHE_NO_IO)
761 		session->RemoveNode(device, node);
762 }
763 
764 
765 static status_t
766 launch_speedup_control(const char *subsystem, uint32 function,
767 	void *buffer, size_t bufferSize)
768 {
769 	switch (function) {
770 		case LAUNCH_SPEEDUP_START_SESSION:
771 		{
772 			char name[B_OS_NAME_LENGTH];
773 			if (!IS_USER_ADDRESS(buffer)
774 				|| user_strlcpy(name, (const char *)buffer, B_OS_NAME_LENGTH) < B_OK)
775 				return B_BAD_ADDRESS;
776 
777 			if (isdigit(name[0]) || name[0] == '.')
778 				return B_BAD_VALUE;
779 
780 			sMainSession = start_session(-1, -1, -1, name, 60);
781 			sMainSession->Unlock();
782 			return B_OK;
783 		}
784 
785 		case LAUNCH_SPEEDUP_STOP_SESSION:
786 		{
787 			char name[B_OS_NAME_LENGTH];
788 			if (!IS_USER_ADDRESS(buffer)
789 				|| user_strlcpy(name, (const char *)buffer, B_OS_NAME_LENGTH) < B_OK)
790 				return B_BAD_ADDRESS;
791 
792 			// ToDo: this check is not thread-safe
793 			if (sMainSession == NULL || strcmp(sMainSession->Name(), name))
794 				return B_BAD_VALUE;
795 
796 			sMainSession->Lock();
797 			stop_session(sMainSession);
798 			sMainSession = NULL;
799 			return B_OK;
800 		}
801 	}
802 
803 	return B_BAD_VALUE;
804 }
805 
806 
807 static void
808 uninit()
809 {
810 	unregister_generic_syscall(LAUNCH_SPEEDUP_SYSCALLS, 1);
811 
812 	recursive_lock_lock(&sLock);
813 
814 	// free all sessions from the hashes
815 
816 	uint32 cookie = 0;
817 	Session *session;
818 	while ((session = (Session *)hash_remove_first(sTeamHash, &cookie)) != NULL) {
819 		delete session;
820 	}
821 	cookie = 0;
822 	while ((session = (Session *)hash_remove_first(sPrefetchHash, &cookie)) != NULL) {
823 		delete session;
824 	}
825 
826 	// free all sessions from the main prefetch list
827 
828 	for (session = sMainPrefetchSessions; session != NULL; ) {
829 		sMainPrefetchSessions = session->Next();
830 		delete session;
831 		session = sMainPrefetchSessions;
832 	}
833 
834 	hash_uninit(sTeamHash);
835 	hash_uninit(sPrefetchHash);
836 	recursive_lock_destroy(&sLock);
837 }
838 
839 
840 static status_t
841 init()
842 {
843 	sTeamHash = hash_init(64, Session::NextOffset(), &team_compare, &team_hash);
844 	if (sTeamHash == NULL)
845 		return B_NO_MEMORY;
846 
847 	status_t status;
848 
849 	sPrefetchHash = hash_init(64, Session::NextOffset(), &prefetch_compare, &prefetch_hash);
850 	if (sPrefetchHash == NULL) {
851 		status = B_NO_MEMORY;
852 		goto err1;
853 	}
854 
855 	if (recursive_lock_init(&sLock, "launch speedup") < B_OK) {
856 		status = sLock.sem;
857 		goto err2;
858 	}
859 
860 	// register kernel syscalls
861 	if (register_generic_syscall(LAUNCH_SPEEDUP_SYSCALLS,
862 			launch_speedup_control, 1, 0) != B_OK) {
863 		status = B_ERROR;
864 		goto err3;
865 	}
866 
867 	// read in prefetch knowledge base
868 
869 	mkdir("/etc/launch_cache", 0755);
870 	load_prefetch_data();
871 
872 	// start boot session
873 
874 	sMainSession = start_session(-1, -1, -1, "system boot");
875 	sMainSession->Unlock();
876 	return B_OK;
877 
878 err3:
879 	recursive_lock_destroy(&sLock);
880 err2:
881 	hash_uninit(sPrefetchHash);
882 err1:
883 	hash_uninit(sTeamHash);
884 	return status;
885 }
886 
887 
888 static status_t
889 std_ops(int32 op, ...)
890 {
891 	switch (op) {
892 		case B_MODULE_INIT:
893 			return init();
894 
895 		case B_MODULE_UNINIT:
896 			uninit();
897 			return B_OK;
898 
899 		default:
900 			return B_ERROR;
901 	}
902 }
903 
904 
905 static struct cache_module_info sLaunchSpeedupModule = {
906 	{
907 		CACHE_MODULES_NAME "/launch_speedup/v1",
908 		0,
909 		std_ops,
910 	},
911 	node_opened,
912 	node_closed,
913 	NULL,
914 };
915 
916 
917 module_info *modules[] = {
918 	(module_info *)&sLaunchSpeedupModule,
919 	NULL
920 };
921