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, 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 746 static void 747 node_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 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