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