vfs: check userland buffers before reading them.
[haiku.git] / src / add-ons / kernel / file_cache / launch_speedup.cpp
blob562c2b3d820ec1242164092c7d469699a7c70720
1 /*
2 * Copyright 2005, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 */
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.
11 * Note: this module is using private kernel API and is definitely not
12 * meant to be an example on how to write modules.
16 #include "launch_speedup.h"
18 #include <KernelExport.h>
19 #include <Node.h>
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>
29 #include <unistd.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <stdio.h>
33 #include <errno.h>
34 #include <ctype.h>
36 extern dev_t gBootDevice;
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)
42 #define TRACE_CACHE_MODULE
43 #ifdef TRACE_CACHE_MODULE
44 # define TRACE(x) dprintf x
45 #else
46 # define TRACE(x) ;
47 #endif
49 #define VNODE_HASH(mountid, vnodeid) (((uint32)((vnodeid) >> 32) \
50 + (uint32)(vnodeid)) ^ (uint32)(mountid))
52 struct data_part {
53 off_t offset;
54 off_t size;
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;
66 struct NodeHash {
67 typedef node_ref KeyType;
68 typedef node ValueType;
70 size_t HashKey(KeyType key) const
72 return VNODE_HASH(key.device, key.node);
75 size_t Hash(ValueType* value) const
77 return HashKey(value->ref);
80 bool Compare(KeyType key, ValueType* node) const
82 return (node->ref.device == key.device && node->ref.node == key.node);
85 ValueType*& GetLink(ValueType* value) const
87 return value->next;
91 typedef BOpenHashTable<NodeHash> NodeTable;
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();
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;
109 void AddNode(dev_t device, ino_t node);
110 void RemoveNode(dev_t device, ino_t node);
112 void Lock() { mutex_lock(&fLock); }
113 void Unlock() { mutex_unlock(&fLock); }
115 status_t StartWatchingTeam();
116 void StopWatchingTeam();
118 status_t LoadFromDirectory(int fd);
119 status_t Save();
120 void Prefetch();
122 Session *&Next() { return fNext; }
124 private:
125 struct node *_FindNode(dev_t device, ino_t node);
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;
141 class SessionGetter {
142 public:
143 SessionGetter(team_id team, Session **_session);
144 ~SessionGetter();
146 status_t New(const char *name, dev_t device, ino_t node,
147 Session **_session);
148 void Stop();
150 private:
151 Session *fSession;
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;
162 node_ref::node_ref()
164 // part of libbe.so
168 struct PrefetchHash {
169 typedef node_ref KeyType;
170 typedef Session ValueType;
172 size_t HashKey(KeyType key) const
174 return VNODE_HASH(key.device, key.node);
177 size_t Hash(ValueType* value) const
179 return HashKey(value->NodeRef());
182 bool Compare(KeyType key, ValueType* session) const
184 return (session->NodeRef().device == key.device
185 && session->NodeRef().node == key.node);
188 ValueType*& GetLink(ValueType* value) const
190 return value->Next();
194 typedef BOpenHashTable<PrefetchHash> PrefetchTable;
197 struct SessionHash {
198 typedef team_id KeyType;
199 typedef Session ValueType;
201 size_t HashKey(KeyType key) const
203 return key;
206 size_t Hash(ValueType* value) const
208 return HashKey(value->Team());
211 bool Compare(KeyType key, ValueType* session) const
213 return session->Team == key;
216 ValueType*& GetLink(ValueType* value) const
218 return value->Next();
222 typedef BOpenHashTable<SessionHash> SessionTable;
225 static void
226 stop_session(Session *session)
228 if (session == NULL)
229 return;
231 TRACE(("stop_session(%s)\n", session->Name()));
233 if (session->IsWorthSaving())
234 session->Save();
237 RecursiveLocker locker(&sLock);
239 if (session->Team() >= B_OK)
240 sTeamHash->Remove(session);
242 if (session == sMainSession)
243 sMainSession = NULL;
246 delete session;
250 static Session *
251 start_session(team_id team, dev_t device, ino_t node, const char *name,
252 int32 seconds = 30)
254 RecursiveLocker locker(&sLock);
256 Session *session = new Session(team, name, device, node, seconds);
257 if (session == NULL)
258 return NULL;
260 if (session->InitCheck() != B_OK || session->StartWatchingTeam() != B_OK) {
261 delete session;
262 return NULL;
265 // let's see if there is a prefetch session for this session
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;
278 } else {
279 // ToDo: search for session by device/node ID
280 prefetchSession = NULL;
282 if (prefetchSession != NULL) {
283 TRACE(("found prefetch session %s\n", prefetchSession->Name()));
284 prefetchSession->Prefetch();
287 if (team >= B_OK)
288 sTeamHash->Insert(session);
290 session->Lock();
291 return session;
295 static void
296 team_gone(team_id team, void *_session)
298 Session *session = (Session *)_session;
300 session->Lock();
301 stop_session(session);
305 static bool
306 parse_node_ref(const char *string, node_ref &ref, const char **_end = NULL)
308 // parse node ref
309 char *end;
310 ref.device = strtol(string, &end, 0);
311 if (end == NULL || ref.device == 0)
312 return false;
314 ref.node = strtoull(end + 1, &end, 0);
316 if (_end)
317 *_end = end;
318 return true;
322 static struct node *
323 new_node(dev_t device, ino_t id)
325 struct node *node = new ::node;
326 if (node == NULL)
327 return NULL;
329 node->ref.device = device;
330 node->ref.node = id;
331 node->timestamp = system_time();
333 return node;
337 static void
338 load_prefetch_data()
340 DIR *dir = opendir("/etc/launch_cache");
341 if (dir == NULL)
342 return;
344 struct dirent *dirent;
345 while ((dirent = readdir(dir)) != NULL) {
346 if (dirent->d_name[0] == '.')
347 continue;
349 Session *session = new Session(dirent->d_name);
351 if (session->LoadFromDirectory(dirfd(dir)) != B_OK) {
352 delete session;
353 continue;
356 if (session->IsMainSession()) {
357 session->Next() = sMainPrefetchSessions;
358 sMainPrefetchSessions = session;
359 } else {
360 sPrefetchHash->Insert(session);
364 closedir(dir);
368 // #pragma mark -
371 Session::Session(team_id team, const char *name, dev_t device,
372 ino_t node, int32 seconds)
374 fNodes(NULL),
375 fNodeCount(0),
376 fTeam(team),
377 fClosing(false),
378 fIsWatchingTeam(false)
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;
385 strlcpy(fName, name, B_OS_NAME_LENGTH);
386 } else
387 fName[0] = '\0';
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;
395 fActiveUntil = system_time() + seconds * 1000000LL;
396 fTimestamp = system_time();
398 fNodeRef.device = device;
399 fNodeRef.node = node;
401 TRACE(("start session %ld:%Ld \"%s\", system_time: %Ld, active until: %Ld\n",
402 device, node, Name(), system_time(), fActiveUntil));
406 Session::Session(const char *name)
408 fNodeHash(NULL),
409 fNodes(NULL),
410 fClosing(false),
411 fIsWatchingTeam(false)
413 fTeam = -1;
414 fNodeRef.device = -1;
415 fNodeRef.node = -1;
417 if (isdigit(name[0]))
418 parse_node_ref(name, fNodeRef);
420 strlcpy(fName, name, B_OS_NAME_LENGTH);
424 Session::~Session()
426 mutex_destroy(&fLock);
428 // free all nodes
429 struct node *node, *next = NULL;
431 if (fNodeHash) {
432 // ... from the hash
433 node = fNodeHash->Clear(true);
434 } else {
435 // ... from the list
436 node = fNodes;
439 for (; node != NULL; node = next) {
440 next = node->next;
441 free(node);
444 delete fNodeHash;
445 StopWatchingTeam();
449 status_t
450 Session::InitCheck()
452 if (fNodeHash == NULL)
453 return B_NO_MEMORY;
455 return B_OK;
459 node *
460 Session::_FindNode(dev_t device, ino_t node)
462 node_ref key;
463 key.device = device;
464 key.node = node;
466 return fNodeHash->Lookup(key);
470 void
471 Session::AddNode(dev_t device, ino_t id)
473 struct node *node = _FindNode(device, id);
474 if (node != NULL) {
475 node->ref_count++;
476 return;
479 node = new_node(device, id);
480 if (node == NULL)
481 return;
483 fNodeHash->Insert(node);
484 fNodeCount++;
488 void
489 Session::RemoveNode(dev_t device, ino_t id)
491 struct node *node = _FindNode(device, id);
492 if (node != NULL && --node->ref_count <= 0) {
493 fNodeHash->Remove(node);
494 fNodeCount--;
499 status_t
500 Session::StartWatchingTeam()
502 if (Team() < B_OK)
503 return B_OK;
505 status_t status = start_watching_team(Team(), team_gone, this);
506 if (status == B_OK)
507 fIsWatchingTeam = true;
509 return status;
513 void
514 Session::StopWatchingTeam()
516 if (fIsWatchingTeam)
517 stop_watching_team(Team(), team_gone, this);
521 void
522 Session::Prefetch()
524 if (fNodes == NULL || fNodeHash != NULL)
525 return;
527 for (struct node *node = fNodes; node != NULL; node = node->next) {
528 cache_prefetch(node->ref.device, node->ref.node, 0, ~0UL);
533 status_t
534 Session::LoadFromDirectory(int directoryFD)
536 TRACE(("load session %s\n", Name()));
538 int fd = _kern_open(directoryFD, Name(), O_RDONLY, 0);
539 if (fd < B_OK)
540 return fd;
542 struct stat stat;
543 if (fstat(fd, &stat) != 0) {
544 close(fd);
545 return errno;
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;
555 char *buffer = (char *)malloc(stat.st_size);
556 if (buffer == NULL) {
557 close(fd);
558 return B_NO_MEMORY;
561 if (read(fd, buffer, stat.st_size) < stat.st_size) {
562 free(buffer);
563 close(fd);
564 return B_ERROR;
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;
576 line++;
579 free(buffer);
580 close(fd);
581 return B_OK;
585 status_t
586 Session::Save()
588 fClosing = true;
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());
597 int fd = open(name, O_CREAT | O_TRUNC | O_WRONLY, 0644);
598 if (fd < B_OK)
599 return errno;
601 status_t status = B_OK;
602 off_t fileSize = 0;
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)
607 // enlarge file, so that it can be written faster
608 ftruncate(fd, 512 * 1024);
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);
615 ssize_t bytesWritten = write(fd, name, strlen(name));
616 if (bytesWritten < B_OK) {
617 status = bytesWritten;
618 break;
621 fileSize += bytesWritten;
624 ftruncate(fd, fileSize);
625 close(fd);
627 return status;
631 bool
632 Session::IsWorthSaving() const
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;
641 return true;
645 bool
646 Session::IsMainSession() const
648 return fNodeRef.node == -1;
652 // #pragma mark -
655 SessionGetter::SessionGetter(team_id team, Session **_session)
657 RecursiveLocker locker(&sLock);
659 if (sMainSession != NULL)
660 fSession = sMainSession;
661 else
662 fSession = sTeamHash->Lookup(team);
664 if (fSession != NULL) {
665 if (!fSession->IsClosing())
666 fSession->Lock();
667 else
668 fSession = NULL;
671 *_session = fSession;
675 SessionGetter::~SessionGetter()
677 if (fSession != NULL)
678 fSession->Unlock();
682 status_t
683 SessionGetter::New(const char *name, dev_t device, ino_t node,
684 Session **_session)
686 Thread *thread = thread_get_current_thread();
687 fSession = start_session(thread->team->id, device, node, name);
689 if (fSession != NULL) {
690 *_session = fSession;
691 return B_OK;
694 return B_ERROR;
698 void
699 SessionGetter::Stop()
701 if (fSession == sMainSession)
702 sMainSession = NULL;
704 stop_session(fSession);
705 fSession = NULL;
708 // #pragma mark -
711 static void
712 node_opened(struct vnode *vnode, int32 fdType, dev_t device, ino_t parent,
713 ino_t node, const char *name, off_t size)
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;
721 Session *session;
722 SessionGetter getter(team_get_current_team_id(), &session);
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;
730 // create new session for this team
731 getter.New(name, device, node, &session);
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();
739 return;
742 session->AddNode(device, node);
746 static void
747 node_closed(struct vnode *vnode, int32 fdType, dev_t device, ino_t node,
748 int32 accessType)
750 Session *session;
751 SessionGetter getter(team_get_current_team_id(), &session);
753 if (session == NULL)
754 return;
756 if (accessType == FILE_CACHE_NO_IO)
757 session->RemoveNode(device, node);
761 static status_t
762 launch_speedup_control(const char *subsystem, uint32 function,
763 void *buffer, size_t bufferSize)
765 switch (function) {
766 case LAUNCH_SPEEDUP_START_SESSION:
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;
773 if (isdigit(name[0]) || name[0] == '.')
774 return B_BAD_VALUE;
776 sMainSession = start_session(-1, -1, -1, name, 60);
777 sMainSession->Unlock();
778 return B_OK;
781 case LAUNCH_SPEEDUP_STOP_SESSION:
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;
788 // ToDo: this check is not thread-safe
789 if (sMainSession == NULL || strcmp(sMainSession->Name(), name))
790 return B_BAD_VALUE;
792 if (!strcmp(name, "system boot"))
793 dprintf("STOP BOOT %Ld\n", system_time());
795 sMainSession->Lock();
796 stop_session(sMainSession);
797 sMainSession = NULL;
798 return B_OK;
802 return B_BAD_VALUE;
806 static void
807 uninit()
809 unregister_generic_syscall(LAUNCH_SPEEDUP_SYSCALLS, 1);
811 recursive_lock_lock(&sLock);
813 // free all sessions from the hashes
815 Session *session = sTeamHash->Clear(true);
816 while (session != NULL) {
817 Session *next = session->next;
818 delete session;
819 session = next;
821 session = sPrefetchHash->Clear(true);
822 while (session != NULL) {
823 Session *next = session->next;
824 delete session;
825 session = next;
828 // free all sessions from the main prefetch list
830 for (session = sMainPrefetchSessions; session != NULL; ) {
831 sMainPrefetchSessions = session->Next();
832 delete session;
833 session = sMainPrefetchSessions;
836 delete sTeamHash;
837 delete sPrefetchHash;
838 recursive_lock_destroy(&sLock);
842 static status_t
843 init()
845 sTeamHash = new(std::nothrow) SessionTable();
846 if (sTeamHash == NULL || sTeamHash->Init(64) != B_OK)
847 return B_NO_MEMORY;
849 status_t status;
851 sPrefetchHash = new(std::nothrow) PrefetchTable();
852 if (sPrefetchHash == NULL || sPrefetchHash->Init(64) != B_OK) {
853 status = B_NO_MEMORY;
854 goto err1;
857 recursive_lock_init(&sLock, "launch speedup");
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;
866 // read in prefetch knowledge base
868 mkdir("/etc/launch_cache", 0755);
869 load_prefetch_data();
871 // start boot session
873 sMainSession = start_session(-1, -1, -1, "system boot");
874 sMainSession->Unlock();
875 dprintf("START BOOT %Ld\n", system_time());
876 return B_OK;
878 err3:
879 recursive_lock_destroy(&sLock);
880 delete sPrefetchHash;
881 err1:
882 delete sTeamHash;
883 return status;
887 static status_t
888 std_ops(int32 op, ...)
890 switch (op) {
891 case B_MODULE_INIT:
892 return init();
894 case B_MODULE_UNINIT:
895 uninit();
896 return B_OK;
898 default:
899 return B_ERROR;
904 static struct cache_module_info sLaunchSpeedupModule = {
906 CACHE_MODULES_NAME "/launch_speedup/v1",
908 std_ops,
910 node_opened,
911 node_closed,
912 NULL,
916 module_info *modules[] = {
917 (module_info *)&sLaunchSpeedupModule,
918 NULL