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
37extern 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
53struct data_part {
54	off_t		offset;
55	off_t		size;
56};
57
58struct 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
67class 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
116class 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
129static Session *sMainSession;
130static hash_table *sTeamHash;
131static hash_table *sPrefetchHash;
132static Session *sMainPrefetchSessions;
133	// singly-linked list
134static recursive_lock sLock;
135
136
137node_ref::node_ref()
138{
139	// part of libbe.so
140}
141
142
143static int
144node_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
156static uint32
157node_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
169static int
170prefetch_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
183static uint32
184prefetch_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
196static int
197team_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
209static uint32
210team_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
222static void
223stop_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
247static Session *
248start_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
292static void
293team_gone(team_id team, void *_session)
294{
295	Session *session = (Session *)_session;
296
297	session->Lock();
298	stop_session(session);
299}
300
301
302static bool
303parse_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
319static struct node *
320new_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
334static void
335load_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
368Session::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
399Session::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
417Session::~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
447status_t
448Session::InitCheck()
449{
450	if (fNodeHash == NULL)
451		return B_NO_MEMORY;
452
453	return B_OK;
454}
455
456
457node *
458Session::_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
468void
469Session::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
486void
487Session::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
497status_t
498Session::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
511void
512Session::StopWatchingTeam()
513{
514	if (fIsWatchingTeam)
515		stop_watching_team(Team(), team_gone, this);
516}
517
518
519void
520Session::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
531status_t
532Session::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
583status_t
584Session::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
633bool
634Session::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
647bool
648Session::IsMainSession() const
649{
650	return fNodeRef.node == -1;
651}
652
653
654//	#pragma mark -
655
656
657SessionGetter::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
677SessionGetter::~SessionGetter()
678{
679	if (fSession != NULL)
680		fSession->Unlock();
681}
682
683
684status_t
685SessionGetter::New(const char *name, dev_t device, ino_t node,
686	Session **_session)
687{
688	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
700void
701SessionGetter::Stop()
702{
703	if (fSession == sMainSession)
704		sMainSession = NULL;
705
706	stop_session(fSession);
707	fSession = NULL;
708}
709
710//	#pragma mark -
711
712
713static void
714node_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
748static void
749node_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
763static status_t
764launch_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
808static void
809uninit()
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
841static status_t
842init()
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
877err3:
878	recursive_lock_destroy(&sLock);
879	hash_uninit(sPrefetchHash);
880err1:
881	hash_uninit(sTeamHash);
882	return status;
883}
884
885
886static status_t
887std_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
903static 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
915module_info *modules[] = {
916	(module_info *)&sLaunchSpeedupModule,
917	NULL
918};
919