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, mount_id device, 70 vnode_id 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(mount_id device, vnode_id node); 84 void RemoveNode(mount_id device, vnode_id 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(mount_id device, vnode_id 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, mount_id device, vnode_id 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, mount_id device, vnode_id 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(mount_id device, vnode_id 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(dir->fd) != 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, mount_id device, 369 vnode_id 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 (fLock.sem < B_OK) 451 return fLock.sem; 452 453 if (fNodeHash == NULL) 454 return B_NO_MEMORY; 455 456 return B_OK; 457 } 458 459 460 node * 461 Session::_FindNode(mount_id device, vnode_id node) 462 { 463 node_ref key; 464 key.device = device; 465 key.node = node; 466 467 return (struct node *)hash_lookup(fNodeHash, &key); 468 } 469 470 471 void 472 Session::AddNode(mount_id device, vnode_id id) 473 { 474 struct node *node = _FindNode(device, id); 475 if (node != NULL) { 476 node->ref_count++; 477 return; 478 } 479 480 node = new_node(device, id); 481 if (node == NULL) 482 return; 483 484 hash_insert(fNodeHash, node); 485 fNodeCount++; 486 } 487 488 489 void 490 Session::RemoveNode(mount_id device, vnode_id id) 491 { 492 struct node *node = _FindNode(device, id); 493 if (node != NULL && --node->ref_count <= 0) { 494 hash_remove(fNodeHash, node); 495 fNodeCount--; 496 } 497 } 498 499 500 status_t 501 Session::StartWatchingTeam() 502 { 503 if (Team() < B_OK) 504 return B_OK; 505 506 status_t status = start_watching_team(Team(), team_gone, this); 507 if (status == B_OK) 508 fIsWatchingTeam = true; 509 510 return status; 511 } 512 513 514 void 515 Session::StopWatchingTeam() 516 { 517 if (fIsWatchingTeam) 518 stop_watching_team(Team(), team_gone, this); 519 } 520 521 522 void 523 Session::Prefetch() 524 { 525 if (fNodes == NULL || fNodeHash != NULL) 526 return; 527 528 for (struct node *node = fNodes; node != NULL; node = node->next) { 529 cache_prefetch(node->ref.device, node->ref.node, 0, ~0UL); 530 } 531 } 532 533 534 status_t 535 Session::LoadFromDirectory(int directoryFD) 536 { 537 TRACE(("load session %s\n", Name())); 538 539 int fd = _kern_open(directoryFD, Name(), O_RDONLY, 0); 540 if (fd < B_OK) 541 return fd; 542 543 struct stat stat; 544 if (fstat(fd, &stat) != 0) { 545 close(fd); 546 return errno; 547 } 548 549 if (stat.st_size > 32768) { 550 // for safety reasons 551 // ToDo: make a bit larger later 552 close(fd); 553 return B_BAD_DATA; 554 } 555 556 char *buffer = (char *)malloc(stat.st_size); 557 if (buffer == NULL) { 558 close(fd); 559 return B_NO_MEMORY; 560 } 561 562 if (read(fd, buffer, stat.st_size) < stat.st_size) { 563 free(buffer); 564 close(fd); 565 return B_ERROR; 566 } 567 568 const char *line = buffer; 569 node_ref nodeRef; 570 while (parse_node_ref(line, nodeRef, &line)) { 571 struct node *node = new_node(nodeRef.device, nodeRef.node); 572 if (node != NULL) { 573 // note: this reverses the order of the nodes in the file 574 node->next = fNodes; 575 fNodes = node; 576 } 577 line++; 578 } 579 580 free(buffer); 581 close(fd); 582 return B_OK; 583 } 584 585 586 status_t 587 Session::Save() 588 { 589 fClosing = true; 590 591 char name[B_OS_NAME_LENGTH + 25]; 592 if (!IsMainSession()) { 593 snprintf(name, sizeof(name), "/etc/launch_cache/%ld:%Ld %s", 594 fNodeRef.device, fNodeRef.node, Name()); 595 } else 596 snprintf(name, sizeof(name), "/etc/launch_cache/%s", Name()); 597 598 int fd = open(name, O_CREAT | O_TRUNC | O_WRONLY, 0644); 599 if (fd < B_OK) 600 return errno; 601 602 status_t status = B_OK; 603 off_t fileSize = 0; 604 605 // ToDo: order nodes by timestamp... (should improve launch speed) 606 // ToDo: test which parts of a file have been read (and save that as well) 607 608 // enlarge file, so that it can be written faster 609 ftruncate(fd, 512 * 1024); 610 611 struct hash_iterator iterator; 612 struct node *node; 613 614 hash_open(fNodeHash, &iterator); 615 while ((node = (struct node *)hash_next(fNodeHash, &iterator)) != NULL) { 616 snprintf(name, sizeof(name), "%ld:%Ld\n", node->ref.device, node->ref.node); 617 618 ssize_t bytesWritten = write(fd, name, strlen(name)); 619 if (bytesWritten < B_OK) { 620 status = bytesWritten; 621 break; 622 } 623 624 fileSize += bytesWritten; 625 } 626 627 hash_close(fNodeHash, &iterator, false); 628 629 ftruncate(fd, fileSize); 630 close(fd); 631 632 return status; 633 } 634 635 636 bool 637 Session::IsWorthSaving() const 638 { 639 // ToDo: sort out entries with only very few nodes, and those that load 640 // instantly, anyway 641 if (fNodeCount < 5 || system_time() - fTimestamp < 400000) { 642 // sort anything out that opens less than 5 files, or needs less 643 // than 0.4 seconds to load an run 644 return false; 645 } 646 return true; 647 } 648 649 650 bool 651 Session::IsMainSession() const 652 { 653 return fNodeRef.node == -1; 654 } 655 656 657 // #pragma mark - 658 659 660 SessionGetter::SessionGetter(team_id team, Session **_session) 661 { 662 RecursiveLocker locker(&sLock); 663 664 if (sMainSession != NULL) 665 fSession = sMainSession; 666 else 667 fSession = (Session *)hash_lookup(sTeamHash, &team); 668 669 if (fSession != NULL) { 670 if (!fSession->IsClosing()) 671 fSession->Lock(); 672 else 673 fSession = NULL; 674 } 675 676 *_session = fSession; 677 } 678 679 680 SessionGetter::~SessionGetter() 681 { 682 if (fSession != NULL) 683 fSession->Unlock(); 684 } 685 686 687 status_t 688 SessionGetter::New(const char *name, mount_id device, vnode_id node, 689 Session **_session) 690 { 691 struct thread *thread = thread_get_current_thread(); 692 fSession = start_session(thread->team->id, device, node, name); 693 694 if (fSession != NULL) { 695 *_session = fSession; 696 return B_OK; 697 } 698 699 return B_ERROR; 700 } 701 702 703 void 704 SessionGetter::Stop() 705 { 706 if (fSession == sMainSession) 707 sMainSession = NULL; 708 709 stop_session(fSession); 710 fSession = NULL; 711 } 712 713 // #pragma mark - 714 715 716 static void 717 node_opened(void *vnode, int32 fdType, mount_id device, vnode_id parent, 718 vnode_id node, const char *name, off_t size) 719 { 720 if (device < gBootDevice) { 721 // we ignore any access to rootfs, pipefs, and devfs 722 // ToDo: if we can ever move the boot device on the fly, this will break 723 return; 724 } 725 726 Session *session; 727 SessionGetter getter(team_get_current_team_id(), &session); 728 729 if (session == NULL) { 730 char buffer[B_FILE_NAME_LENGTH]; 731 if (name == NULL 732 && vfs_get_vnode_name(vnode, buffer, sizeof(buffer)) == B_OK) 733 name = buffer; 734 735 // create new session for this team 736 getter.New(name, device, node, &session); 737 } 738 739 if (session == NULL || !session->IsActive()) { 740 if (sMainSession != NULL) { 741 // ToDo: this opens a race condition with the "stop session" syscall 742 getter.Stop(); 743 } 744 return; 745 } 746 747 session->AddNode(device, node); 748 } 749 750 751 static void 752 node_closed(void *vnode, int32 fdType, mount_id device, vnode_id node, int32 accessType) 753 { 754 Session *session; 755 SessionGetter getter(team_get_current_team_id(), &session); 756 757 if (session == NULL) 758 return; 759 760 if (accessType == FILE_CACHE_NO_IO) 761 session->RemoveNode(device, node); 762 } 763 764 765 static status_t 766 launch_speedup_control(const char *subsystem, uint32 function, 767 void *buffer, size_t bufferSize) 768 { 769 switch (function) { 770 case LAUNCH_SPEEDUP_START_SESSION: 771 { 772 char name[B_OS_NAME_LENGTH]; 773 if (!IS_USER_ADDRESS(buffer) 774 || user_strlcpy(name, (const char *)buffer, B_OS_NAME_LENGTH) < B_OK) 775 return B_BAD_ADDRESS; 776 777 if (isdigit(name[0]) || name[0] == '.') 778 return B_BAD_VALUE; 779 780 sMainSession = start_session(-1, -1, -1, name, 60); 781 sMainSession->Unlock(); 782 return B_OK; 783 } 784 785 case LAUNCH_SPEEDUP_STOP_SESSION: 786 { 787 char name[B_OS_NAME_LENGTH]; 788 if (!IS_USER_ADDRESS(buffer) 789 || user_strlcpy(name, (const char *)buffer, B_OS_NAME_LENGTH) < B_OK) 790 return B_BAD_ADDRESS; 791 792 // ToDo: this check is not thread-safe 793 if (sMainSession == NULL || strcmp(sMainSession->Name(), name)) 794 return B_BAD_VALUE; 795 796 sMainSession->Lock(); 797 stop_session(sMainSession); 798 sMainSession = NULL; 799 return B_OK; 800 } 801 } 802 803 return B_BAD_VALUE; 804 } 805 806 807 static void 808 uninit() 809 { 810 unregister_generic_syscall(LAUNCH_SPEEDUP_SYSCALLS, 1); 811 812 recursive_lock_lock(&sLock); 813 814 // free all sessions from the hashes 815 816 uint32 cookie = 0; 817 Session *session; 818 while ((session = (Session *)hash_remove_first(sTeamHash, &cookie)) != NULL) { 819 delete session; 820 } 821 cookie = 0; 822 while ((session = (Session *)hash_remove_first(sPrefetchHash, &cookie)) != NULL) { 823 delete session; 824 } 825 826 // free all sessions from the main prefetch list 827 828 for (session = sMainPrefetchSessions; session != NULL; ) { 829 sMainPrefetchSessions = session->Next(); 830 delete session; 831 session = sMainPrefetchSessions; 832 } 833 834 hash_uninit(sTeamHash); 835 hash_uninit(sPrefetchHash); 836 recursive_lock_destroy(&sLock); 837 } 838 839 840 static status_t 841 init() 842 { 843 sTeamHash = hash_init(64, Session::NextOffset(), &team_compare, &team_hash); 844 if (sTeamHash == NULL) 845 return B_NO_MEMORY; 846 847 status_t status; 848 849 sPrefetchHash = hash_init(64, Session::NextOffset(), &prefetch_compare, &prefetch_hash); 850 if (sPrefetchHash == NULL) { 851 status = B_NO_MEMORY; 852 goto err1; 853 } 854 855 if (recursive_lock_init(&sLock, "launch speedup") < B_OK) { 856 status = sLock.sem; 857 goto err2; 858 } 859 860 // register kernel syscalls 861 if (register_generic_syscall(LAUNCH_SPEEDUP_SYSCALLS, 862 launch_speedup_control, 1, 0) != B_OK) { 863 status = B_ERROR; 864 goto err3; 865 } 866 867 // read in prefetch knowledge base 868 869 mkdir("/etc/launch_cache", 0755); 870 load_prefetch_data(); 871 872 // start boot session 873 874 sMainSession = start_session(-1, -1, -1, "system boot"); 875 sMainSession->Unlock(); 876 return B_OK; 877 878 err3: 879 recursive_lock_destroy(&sLock); 880 err2: 881 hash_uninit(sPrefetchHash); 882 err1: 883 hash_uninit(sTeamHash); 884 return status; 885 } 886 887 888 static status_t 889 std_ops(int32 op, ...) 890 { 891 switch (op) { 892 case B_MODULE_INIT: 893 return init(); 894 895 case B_MODULE_UNINIT: 896 uninit(); 897 return B_OK; 898 899 default: 900 return B_ERROR; 901 } 902 } 903 904 905 static struct cache_module_info sLaunchSpeedupModule = { 906 { 907 CACHE_MODULES_NAME "/launch_speedup/v1", 908 0, 909 std_ops, 910 }, 911 node_opened, 912 node_closed, 913 NULL, 914 }; 915 916 917 module_info *modules[] = { 918 (module_info *)&sLaunchSpeedupModule, 919 NULL 920 }; 921