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
36extern 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
52struct data_part {
53	off_t		offset;
54	off_t		size;
55};
56
57struct 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
66struct 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
91typedef BOpenHashTable<NodeHash> NodeTable;
92
93class 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
141class 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
154static Session *sMainSession;
155static SessionTable *sTeamHash;
156static PrefetchTable *sPrefetchHash;
157static Session *sMainPrefetchSessions;
158	// singly-linked list
159static recursive_lock sLock;
160
161
162node_ref::node_ref()
163{
164	// part of libbe.so
165}
166
167
168struct 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
194typedef BOpenHashTable<PrefetchHash> PrefetchTable;
195
196
197struct 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
222typedef BOpenHashTable<SessionHash> SessionTable;
223
224
225static void
226stop_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
250static Session *
251start_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
295static void
296team_gone(team_id team, void *_session)
297{
298	Session *session = (Session *)_session;
299
300	session->Lock();
301	stop_session(session);
302}
303
304
305static bool
306parse_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
322static struct node *
323new_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
337static void
338load_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
371Session::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:%Ld \"%s\", system_time: %Ld, active until: %Ld\n",
402		device, node, Name(), system_time(), fActiveUntil));
403}
404
405
406Session::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
424Session::~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
449status_t
450Session::InitCheck()
451{
452	if (fNodeHash == NULL)
453		return B_NO_MEMORY;
454
455	return B_OK;
456}
457
458
459node *
460Session::_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
470void
471Session::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
488void
489Session::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
499status_t
500Session::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
513void
514Session::StopWatchingTeam()
515{
516	if (fIsWatchingTeam)
517		stop_watching_team(Team(), team_gone, this);
518}
519
520
521void
522Session::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
533status_t
534Session::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
585status_t
586Session::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:%Ld %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:%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	ftruncate(fd, fileSize);
625	close(fd);
626
627	return status;
628}
629
630
631bool
632Session::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
645bool
646Session::IsMainSession() const
647{
648	return fNodeRef.node == -1;
649}
650
651
652//	#pragma mark -
653
654
655SessionGetter::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
675SessionGetter::~SessionGetter()
676{
677	if (fSession != NULL)
678		fSession->Unlock();
679}
680
681
682status_t
683SessionGetter::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
698void
699SessionGetter::Stop()
700{
701	if (fSession == sMainSession)
702		sMainSession = NULL;
703
704	stop_session(fSession);
705	fSession = NULL;
706}
707
708//	#pragma mark -
709
710
711static void
712node_opened(struct vnode *vnode, int32 fdType, 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
746static void
747node_closed(struct vnode *vnode, int32 fdType, 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
761static status_t
762launch_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 %Ld\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
806static void
807uninit()
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
842static status_t
843init()
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 %Ld\n", system_time());
876	return B_OK;
877
878err3:
879	recursive_lock_destroy(&sLock);
880	delete sPrefetchHash;
881err1:
882	delete sTeamHash;
883	return status;
884}
885
886
887static status_t
888std_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
904static 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
916module_info *modules[] = {
917	(module_info *)&sLaunchSpeedupModule,
918	NULL
919};
920