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