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