1/* 2 * Block device "smart readahead" cache control utility. 3 * 4 * Provides the user-space interface to the BootCache cache; 5 * loading the playlist, reading and presorting the history list 6 * into a new playlist, gathering performance statistics. 7 * 8 */ 9 10#include <sys/param.h> 11#include <sys/mman.h> 12#include <sys/stat.h> 13#include <sys/sysctl.h> 14#include <sys/attr.h> 15#include <sys/syscall.h> 16#include <CoreFoundation/CoreFoundation.h> 17#include <IOKit/IOKitLib.h> 18#include <IOKit/storage/CoreStorage/CoreStorageCryptoIDs.h> 19#include <dirent.h> 20#include <sys/xattr.h> 21#include <sys/mount.h> 22 23#include <err.h> 24#include <errno.h> 25#include <fcntl.h> 26#include <notify.h> 27#include <stdio.h> 28#include <stdlib.h> 29#include <string.h> 30#include <time.h> 31#include <unistd.h> 32 33#include "BootCache.h" 34 35// --- For composite disk checking 36#include <IOKit/storage/CoreStorage/CoreStorageUserLib.h> 37#include <IOKit/storage/IOMedia.h> 38 39static int usage(const char *reason); 40static int do_boot_cache(); 41static int start_cache(const char *pfname); 42static int stop_cache(const char *pfname, int debugging); 43static int jettison_cache(void); 44static int merge_playlists(const char *pfname, int argc, char *argv[], int* pbatch); 45static int print_statistics(struct BC_statistics *ss); 46static void print_history(struct BC_history *he); 47static int print_playlist(const char *pfname, int source); 48static int unprint_playlist(const char *pfname); 49static int generalize_playlist(const char *pfname); 50static int generate_playlist(const char *pfname, const char *root); 51static int truncate_playlist(const char *pfname, char *larg); 52static int add_file(struct BC_playlist *pc, const char *fname, const int batch, const bool shared, const bool low_priority); 53static int add_directory(struct BC_playlist *pc, const char *fname, const int batch, const bool shared); 54static int add_fseventsd_files(struct BC_playlist *pc, const int batch, const bool shared); 55static int add_playlist_for_preheated_user(struct BC_playlist *pc, bool isCompositeDisk); 56static int add_logical_playlist(const char *playlist, struct BC_playlist *pc, int batch, int shared); 57 58static int verbose; 59static char *myname; 60 61int 62main(int argc, char *argv[]) 63{ 64 int ch, cflag, batch; 65 int* pbatch; 66 char *pfname; 67 68 myname = argv[0]; 69 pfname = NULL; 70 pbatch = NULL; 71 cflag = 0; 72 while ((ch = getopt(argc, argv, "b:cvf:t:")) != -1) { 73 switch(ch) { 74 case 'b': 75 usage("blocksize is no longer required, ignoring"); 76 case 'c': 77 cflag++; 78 break; 79 case 'v': 80 verbose++; 81 break; 82 case 'f': 83 pfname = optarg; 84 break; 85 case 't': 86 if ((sscanf(optarg, "%d", &batch)) != 1) 87 usage("bad batch number"); 88 else 89 pbatch = &batch; 90 break; 91 case '?': 92 default: 93 return(usage(NULL)); 94 break; 95 } 96 } 97 argc -= optind; 98 argv += optind; 99 100 if (argc < 1) 101 return(usage("missing command")); 102 /* documented interface */ 103 if (!strcmp(argv[0], "start")) { 104 if (pfname) 105 return(start_cache(pfname)); 106 else 107 return(do_boot_cache()); 108 } 109 if (!strcmp(argv[0], "stop")) 110 return(stop_cache(pfname, 0)); 111 if (!strcmp(argv[0], "mount")) 112 return(BC_notify_mount()); 113 if (!strcmp(argv[0], "jettison")) 114 return(jettison_cache()); 115 if (!strcmp(argv[0], "tag")) 116 return(BC_tag_history()); 117 if (!strcmp(argv[0], "statistics")) 118 return(print_statistics(NULL)); 119 if (!strcmp(argv[0], "merge")) 120 return(merge_playlists(pfname, argc - 1, argv + 1, pbatch)); 121 if (!strcmp(argv[0], "print")) 122 return(print_playlist(pfname, cflag)); 123 if (!strcmp(argv[0], "unprint")) 124 return(unprint_playlist(pfname)); 125 if (!strcmp(argv[0], "generalize")) 126 return(generalize_playlist(pfname)); 127 if (!strcmp(argv[0], "generate")) 128 return(generate_playlist(pfname, argc < 2 ? NULL : argv[1])); 129 if (!strcmp(argv[0], "truncate")) { 130 if (argc < 2) 131 return(usage("missing truncate length")); 132 return(truncate_playlist(pfname, argv[1])); 133 } 134 135 if (!strcmp(argv[0], "unload")) { 136 if (BC_unload()) { 137 warnx("could not unload cache"); 138 return(1); 139 } else { 140 return(0); 141 } 142 } 143 return(usage("invalid command")); 144} 145 146/* 147 * Suggest better usage habits. 148 */ 149static int 150usage(const char *reason) 151{ 152 if (reason != NULL) 153 warnx("%s", reason); 154 fprintf(stderr, "Usage: %s [-vvv] [-f <playlistfile>] start\n", myname); 155 fprintf(stderr, " Start recording history and play back <playlistfile>.\n"); 156 fprintf(stderr, " %s [-vvv] [-f <playlistfile>] stop\n", myname); 157 fprintf(stderr, " Stop recording history and write the playlist to <playlistfile>.\n"); 158 fprintf(stderr, " %s mount\n", myname); 159 fprintf(stderr, " Notify the boot cache of a new mount.\n"); 160 fprintf(stderr, " %s [-vvv] jettison\n", myname); 161 fprintf(stderr, " Jettison the cache.\n"); 162 fprintf(stderr, " %s statistics\n", myname); 163 fprintf(stderr, " Print statistics for the currently-active cache.\n"); 164 fprintf(stderr, " %s tag\n", myname); 165 fprintf(stderr, " Insert the end-prefetch tag.\n"); 166 fprintf(stderr, " %s [-vvv] [-t batchnum] -f <playlistfile> merge <playlistfile1> [<playlistfile2>...]\n", myname); 167 fprintf(stderr, " Merge <playlistfile1>... into <playlistfile>.\n"); 168 fprintf(stderr, " Playlist files after the first will be offset <batchnum> batches, if provided\n"); 169 fprintf(stderr, " %s [-c] -f <playlistfile> print\n", myname); 170 fprintf(stderr, " Print the contents of <playlistfile>.\n"); 171 fprintf(stderr, " %s -f <playlistfile> unprint\n", myname); 172 fprintf(stderr, " Read a playlist from standard input and write to <playlistfile>.\n"); 173 fprintf(stderr, " %s -f <playlistfile> generate [<volume>]\n", myname); 174 fprintf(stderr, " Generate a playlist from standard input data for <volume> and write to <playlistfile>.\n"); 175 fprintf(stderr, " %s -f <playlistfile> truncate <count>\n", myname); 176 fprintf(stderr, " Truncate <playlistfile> to <count> entries.\n"); 177 fprintf(stderr, " %s -f <playlistfile> generalize\n", myname); 178 fprintf(stderr, " Modify <playlistfile> to apply to any root volume.\n"); 179 fflush(stderr); 180 return(1); 181} 182 183/* 184 * Return a user-readable string for a given uuid 185 * 186 * Returns a pointer to a static buffer, which is 187 * racy, so this should only be used for debugging purposes 188 */ 189static inline const char* uuid_string(uuid_t uuid) 190{ 191 /* Racy, but used for debug output so who cares */ 192 static uuid_string_t uuidString; 193 uuid_unparse(uuid, uuidString); 194 return (char*)uuidString; 195} 196 197static int add_logical_playlist(const char *playlist, struct BC_playlist *pc, int batch, int shared) 198{ 199 char filename[MAXPATHLEN]; 200 201 FILE *lpl = fopen(playlist, "r"); 202 if (lpl == NULL){ 203 if (errno != ENOENT && errno != EINVAL) { 204 warnx("Could not read playlist %s: %s", playlist, strerror(errno)); 205 } 206 return -1; 207 } 208 209 while (fgets(filename, sizeof filename, lpl)){ 210 int len = strlen(filename); 211 if (len < 3) 212 continue; 213 214 if (filename[len - 1] == '\n') 215 filename[len - 1] = '\0'; 216 217 if (filename[len - 2] == '/'){ 218 filename[len - 2] = '\0'; 219 int error; 220 if ((error = add_directory(pc, filename, batch, shared)) 221 && error != ENOENT && error != EINVAL) 222 warnx("Error adding files in directory %s: %s", filename, strerror(errno)); 223 } else { 224 add_file(pc, filename, batch, shared, false); 225 } 226 } 227 fclose(lpl); 228 229 return 0; 230} 231 232/* 233 * Return whether the root disk is a composite disk. 234 */ 235static bool 236isRootCPDK() 237{ 238 //*********************************************// 239 // Get the bsd device name for the root volume // 240 //*********************************************// 241 242 char* bsdDevPath = NULL; 243 244 struct statfs buffer; 245 bzero (&buffer, sizeof (buffer)); 246 if (0 == statfs("/", &buffer)) { 247 248 if (strlen(buffer.f_mntfromname) >= 9) { /* /dev/disk */ 249 250 bsdDevPath = &(buffer.f_mntfromname[5]); 251 252 } 253 } 254 255 if (NULL == bsdDevPath) { 256 // No bsd device? Assume not composite 257 return false; 258 } 259 260 //*************************************************************// 261 // Get the CoreStorage Logical Volume UUID from the bsd device // 262 //*************************************************************// 263 264 CFStringRef lvUUID = NULL; 265 266 mach_port_t masterPort; 267 if (KERN_SUCCESS == IOMasterPort(bootstrap_port, &masterPort)) { 268 269 io_registry_entry_t diskObj = IOServiceGetMatchingService(masterPort, IOBSDNameMatching(masterPort, 0, bsdDevPath)); 270 if (IO_OBJECT_NULL != diskObj) { 271 272 if (IOObjectConformsTo(diskObj, kCoreStorageLogicalClassName)) { 273 274 CFTypeRef cfRef = IORegistryEntryCreateCFProperty(diskObj, CFSTR(kIOMediaUUIDKey), kCFAllocatorDefault, 0); 275 if (NULL != cfRef) { 276 277 if (CFGetTypeID(cfRef) == CFStringGetTypeID()) { 278 279 lvUUID = CFStringCreateCopy(NULL, cfRef); 280 // fprintf(outfile, "%6f CoreStorage Logical Volume UUID is %p %s\n", CFAbsoluteTimeGetCurrent() - starttime, lvUUID, CFStringGetCStringPtr(lvUUID, 0)); fflush(outfile); 281 282 } 283 284 CFRelease(cfRef); 285 } 286 287 } else { 288 //Common case for non-CoreStorage filesystems 289 } 290 291 IOObjectRelease(diskObj); 292 } 293 294 } 295 296 if (NULL == lvUUID) { 297 // Not composite if it's not CoreStorage 298 return false; 299 } 300 301 //****************************************************************************************// 302 // Get the CoreStorage Logical Volume Group UUID from the CoreStorage Logical Volume UUID // 303 //****************************************************************************************// 304 305 CFStringRef lvgUUID = NULL; 306 307 CFMutableDictionaryRef propLV = CoreStorageCopyVolumeProperties ((CoreStorageLogicalRef)lvUUID); 308 CFRelease(lvUUID); 309 if (NULL != propLV) { 310 311 lvgUUID = CFDictionaryGetValue(propLV, CFSTR(kCoreStorageLogicalGroupUUIDKey)); 312 if (NULL != lvgUUID) { 313 314 lvgUUID = CFStringCreateCopy(NULL, lvgUUID); 315 // fprintf(outfile, "%6f CoreStorage Logical Volume Group UUID is %p %s\n", CFAbsoluteTimeGetCurrent() - starttime, lvgUUID, CFStringGetCStringPtr(lvgUUID, 0)); fflush(outfile); 316 317 } 318 319 CFRelease(propLV); 320 } 321 322 if (NULL == lvgUUID) { 323 // Can't get the group? Assume not composite 324 return false; 325 } 326 327 //**************************************************************************************************// 328 // Check if the Core Storage Group Type of the CoreStorage Logical Volume Group is a Composite Disk // 329 //**************************************************************************************************// 330 331 bool isCompositeDisk = false; 332 333 CFMutableDictionaryRef lvgProperties = CoreStorageCopyLVGProperties ((CoreStorageGroupRef)lvgUUID); 334 CFRelease(lvgUUID); 335 if (NULL != lvgProperties) { 336 337 CFStringRef groupType = CFDictionaryGetValue(lvgProperties, CFSTR(kCoreStorageGroupTypeKey)); 338 if (NULL != groupType) { 339 340 isCompositeDisk = (kCFCompareEqualTo == CFStringCompare(groupType, CFSTR(kCoreStorageGroupTypeCPDK), 0x0)); 341 342 } 343 344 CFRelease(lvgProperties); 345 } 346 347 if (isCompositeDisk) { 348 // fprintf(outfile, "%6f is cpdk\n", CFAbsoluteTimeGetCurrent() - starttime); fflush(outfile); 349 } else { 350 // fprintf(outfile, "%6f is not cpdk\n", CFAbsoluteTimeGetCurrent() - starttime); fflush(outfile); 351 } 352 353 return isCompositeDisk; 354} 355 356/* 357 * Kick off the boot cache. 358 */ 359static int 360do_boot_cache() 361{ 362 struct BC_playlist *pc; 363 int error; 364 const char* pfname = BC_ROOT_PLAYLIST; 365 366 if ((error = BC_test()) != 0) { 367 return error; 368 } 369 370 bool isCompositeDisk = isRootCPDK(); 371 372 /* set up to start recording with no playback */ 373 pc = NULL; 374 375 /* 376 * If we have a playlist, open it and prepare to load it. 377 */ 378 error = BC_read_playlist(pfname, &pc); 379 380 /* 381 * If the playlist is missing or invalid, ignore it. We'll 382 * overwrite it later. 383 */ 384 if ((error != 0) && (error != EINVAL) && (error != ENOENT)) { 385 warnx("could not read playlist %s: %s", pfname, strerror(error)); 386 return error; 387 } 388 389 int last_shutdown_was_clean = 0; 390 size_t wasCleanSz = sizeof(wasCleanSz); 391 error = sysctlbyname("vfs.generic.root_unmounted_cleanly", &last_shutdown_was_clean, &wasCleanSz, NULL, 0); 392 if (error != 0) { 393 warnx("Unable to check for hard shutdown"); 394 } 395 396 if (pc) { 397 398 /* 399 * Remove the consumed playlist: it'll be replaced once BootCache 400 * completes successfully. 401 */ 402 403 //char prev_path[MAXPATHLEN]; 404 //snprintf(prev_path, sizeof(prev_path), "%s.previous", pfname); 405 //error = rename(pfname, prev_path); // for debugging 406 error = unlink(pfname); 407 if (error != 0 && errno != ENOENT) 408 errx(1, "could not unlink bootcache playlist %s: %d %s", pfname, errno, strerror(errno)); 409 410 if (! last_shutdown_was_clean) { 411 struct timeval start_time = {0,0}, end_time = {0,0}; 412 (void)gettimeofday(&start_time, NULL); // can't fail 413 414 // Make sure the unlink hits disk in case we panic rdar://8947415 415 int fd = open("/var/db/", O_RDONLY); 416 if (fd != -1) { 417 if (-1 != fcntl(fd, F_FULLFSYNC)) { 418 fprintf(stderr, "Synced /var/db/\n"); 419 } else { 420 warnx("Unable to sync /var/db/: %d %s", errno, strerror(errno)); 421 } 422 close(fd); 423 } else { 424 warnx("Unable to open /var/db/: %d %s", errno, strerror(errno)); 425 } 426 427 (void)gettimeofday(&end_time, NULL); // can't fail 428 timersub(&end_time, &start_time, &end_time); 429 // warnx("Took %dus to sync /var/db/\n", end_time.tv_usec); 430 } 431 432 if (! isCompositeDisk) { 433 // rdar://9424845 Add any files we know are read in every boot, but change every boot 434 add_fseventsd_files(pc, 0, false); 435 add_logical_playlist(BC_ROOT_EXTRA_LOGICAL_PLAYLIST, pc, 0, false); 436 add_logical_playlist(BC_LOGIN_EXTRA_LOGICAL_PLAYLIST, pc, 2, false); 437 } 438 } else { 439 if (last_shutdown_was_clean) { 440 pc = calloc(1, sizeof(*pc)); 441 442 // No Root playlist: we're during an install so use our premade logical playlists 443 444 if (! isCompositeDisk) { 445 add_logical_playlist(BC_ROOT_LOGICAL_PLAYLIST, pc, 0, false); 446 add_logical_playlist(BC_LOGIN_LOGICAL_PLAYLIST, pc, 2, false); 447 } 448 } 449 } 450 451 // If we don't have a playlist here, we don't want to play back anything 452 if (pc) { 453 // Add the login playlist of user we expect to log in to the boot playlist 454 if (0 != add_playlist_for_preheated_user(pc, isCompositeDisk)) { 455 // Unable to add user playlist, add 32-bit shared cache to low-priority playlist 456#if defined __x86_64__ 457 add_file(pc, BC_DYLD_SHARED_CACHE_32, 0, true, true); 458 warnx("Added 32-bit shared cache to the low priority batch"); 459#endif 460 } 461 462 // rdar://9021675 Always warm the shared cache 463 add_file(pc, BC_DYLD_SHARED_CACHE, -1, true, false); 464 } 465 466 error = BC_start(pc); 467 if (error != 0) 468 warnx("could not start cache: %d %s", error, strerror(error)); 469 470 PC_FREE_ZERO(pc); 471 return(error); 472} 473 474/* 475 * Fetch or create a login playlist for the given user and add it to the provided playlist in subsequent batches 476 */ 477static int 478add_playlist_for_preheated_user(struct BC_playlist *pc, bool isCompositeDisk) { 479 480 if (!pc) return 1; 481 482 // These defines match those in warmd 483#define PREHEATED_USER_PLAYLIST_PATH_FMT "/var/db/BootCaches/%s/" 484#define MERGED_PLAYLIST "Merged.playlist" 485#define LOGIN_PLAYLIST "Login.playlist" 486#define DEFAULT_USER "PreheatedUser" 487#define APP_CACHE_PLAYLIST_PREFIX "app." 488#define PLAYLIST_SUFFIX ".playlist" 489#define RUNNING_XATTR_NAME "BC_RUNNING" 490#define I386_XATTR_NAME "BC_I386" 491#define MAX_PLAYLIST_PATH_LENGTH 256 492#define TAL_LOGIN_FOREGROUND_APP_BATCH_NUM 0 493#define TAL_LOGIN_BACKGROUND_APP_BATCH_NUM (TAL_LOGIN_FOREGROUND_APP_BATCH_NUM + 1) 494#define TAL_LOGIN_LOWPRI_DATA_BATCH_NUM (TAL_LOGIN_BACKGROUND_APP_BATCH_NUM + 1) 495#define I386_SHARED_CACHE_NULL_BATCH_NUM (-2) 496#define I386_SHARED_CACHE_LOW_PRIORITY_BATCH_NUM (-1) 497 498 int error, i; 499 int playlist_path_end_idx = 0; 500 char playlist_path[MAX_PLAYLIST_PATH_LENGTH]; 501 char login_user[128] = DEFAULT_USER; 502 struct BC_playlist* user_playlist = NULL; 503#if defined __x86_64__ 504 bool already_added_i386_shared_cache = false; 505#endif 506 507 // Limit the playlist size 508 ssize_t playlist_size = 0; 509 ssize_t max_playlist_size = 0; 510 size_t len = sizeof(max_playlist_size); 511 error = sysctlbyname("hw.memsize", &max_playlist_size, &len, NULL, 0); 512 if(error != 0) { 513 max_playlist_size = (1024 * 1024 * 1024); 514 warnx("sysctlbyname(\"hw.memsize\") failed => %d (%d). Assuming 1GB", error, errno); 515 } 516 max_playlist_size = (max_playlist_size / 2) - (512 * 1024 * 1024); 517 if (max_playlist_size < (512 * 1024 * 1024)) { 518 max_playlist_size = (512 * 1024 * 1024); 519 } 520 521 for (i = 0; i < pc->p_nentries; i++) { 522 playlist_size += pc->p_entries[i].pe_length; 523 } 524 525 if (playlist_size >= max_playlist_size) goto out; 526 527 //rdar://8830944&9209576 Detect FDE user 528 io_registry_entry_t service = IORegistryEntryFromPath(kIOMasterPortDefault, "IODeviceTree:/chosen"); 529 if (service != MACH_PORT_NULL) { 530 531 CFDataRef fde_login_user = IORegistryEntryCreateCFProperty(service, CFSTR(kCSFDEEFILoginUnlockIdentID), 532 kCFAllocatorDefault, kNilOptions); 533 IOObjectRelease(service); 534 if (fde_login_user != NULL) { 535 CFDataGetBytes(fde_login_user, CFRangeMake(0, sizeof(login_user)), (UInt8*)login_user); 536 CFRelease(fde_login_user); 537 } 538 } 539 540 playlist_path_end_idx = snprintf(playlist_path, sizeof(playlist_path), PREHEATED_USER_PLAYLIST_PATH_FMT, login_user); 541 542 // warnx("Reading user playlists from user dir %s", playlist_path); 543 544 // Check for the merged playlist warmd has left 545 strlcpy(playlist_path + playlist_path_end_idx, MERGED_PLAYLIST, sizeof(playlist_path) - playlist_path_end_idx); 546 error = BC_read_playlist(playlist_path, &user_playlist); 547 548 if (error == 0) { 549#if defined __x86_64__ 550 int i386_shared_cache_batch_num = I386_SHARED_CACHE_NULL_BATCH_NUM; 551 if (-1 != getxattr(playlist_path, I386_XATTR_NAME, &i386_shared_cache_batch_num, sizeof(i386_shared_cache_batch_num), 0, 0x0)) { 552 if (i386_shared_cache_batch_num != I386_SHARED_CACHE_NULL_BATCH_NUM) { 553 if (i386_shared_cache_batch_num == I386_SHARED_CACHE_LOW_PRIORITY_BATCH_NUM) { 554 add_file(user_playlist, BC_DYLD_SHARED_CACHE_32, 0, true, true); 555 } else { 556 add_file(user_playlist, BC_DYLD_SHARED_CACHE_32, i386_shared_cache_batch_num, true, false); 557 } 558 // warnx("Added 32-bit shared cache to batch %d", i386_shared_cache_batch_num); 559 560 // already_added_i386_shared_cache = true; dead store... 561 } 562 } 563#endif 564 565 } else if (ENOENT == error) { 566 // No merged playlist for the preheated user. Try to create one from its login and app playlists 567 568 strlcpy(playlist_path + playlist_path_end_idx, LOGIN_PLAYLIST, sizeof(playlist_path) - playlist_path_end_idx); 569 error = BC_read_playlist(playlist_path, &user_playlist); 570 if (0 == error) { 571 572 struct BC_playlist *app_playlist = NULL; 573 struct dirent* direntry; 574 int suffix_length = strlen(PLAYLIST_SUFFIX); 575 int app_prefix_length = strlen(APP_CACHE_PLAYLIST_PREFIX); 576 577 playlist_path[playlist_path_end_idx] = '\0'; 578 DIR* dir = opendir(playlist_path); 579 580 581 // Iterate over the app playlists twice: 582 // The first iteration to add the running apps to the playlist. 583 // The second iteration to add the non-running apps to the low-priority playlist 584 int iteration; 585 for (iteration = 0; iteration < 2; iteration++) { 586 int batch_offset = 0; 587 int flags = 0x0; 588 if (iteration == 0) { 589 // We can't tell the order between apps, so just put them all background to give the login playlist priority 590 batch_offset = TAL_LOGIN_BACKGROUND_APP_BATCH_NUM; 591 if (isCompositeDisk) { 592 //rdar://11294417 All user data is low priority on composite disks 593 flags |= BC_PE_LOWPRIORITY; 594 } 595 } else { 596 // Non-running apps are low-priority 597 batch_offset = TAL_LOGIN_LOWPRI_DATA_BATCH_NUM; 598 flags |= BC_PE_LOWPRIORITY; 599 } 600 601 while ((direntry = readdir(dir)) != NULL) { 602 if (direntry->d_namlen > suffix_length && 0 == strncmp(direntry->d_name + direntry->d_namlen - suffix_length, PLAYLIST_SUFFIX, suffix_length)) { 603 if (0 == strncmp(direntry->d_name, APP_CACHE_PLAYLIST_PREFIX, app_prefix_length)) { 604 605 snprintf(playlist_path + playlist_path_end_idx, sizeof(playlist_path) - playlist_path_end_idx, "/%s", direntry->d_name); 606 607 bool is_running = (-1 != getxattr(playlist_path, RUNNING_XATTR_NAME, NULL, 0, 0, 0x0)); 608 if ((iteration == 0) == is_running) { // First iteration we're looking for running apps; second, for non-running 609 610 if (0 == BC_read_playlist(playlist_path, &app_playlist)) { 611 612 // Adjust the flags and batch for this playlist as necessary 613 for (i = 0; i < app_playlist->p_nentries; i++) { 614 app_playlist->p_entries[i].pe_flags |= flags; 615 app_playlist->p_entries[i].pe_batch += batch_offset; 616 playlist_size += app_playlist->p_entries[i].pe_length; 617 } 618 619 // Make sure we don't oversize the playlist 620 if (playlist_size > max_playlist_size) { 621 // warnx("Login cache maximum size of %ldMB reached", max_playlist_size / (1024 * 0124)); 622 PC_FREE_ZERO(app_playlist); 623 break; 624 } 625 626 if (0 != (error = BC_merge_playlists(user_playlist, app_playlist))) { 627 PC_FREE_ZERO(app_playlist); 628 goto out; 629 } 630 631#if defined __x86_64__ 632 if (!already_added_i386_shared_cache) { 633 if (-1 != getxattr(playlist_path, I386_XATTR_NAME, NULL, 0, 0, 0x0)) { 634 add_file(user_playlist, BC_DYLD_SHARED_CACHE_32, batch_offset, true, flags & BC_PE_LOWPRIORITY); 635 already_added_i386_shared_cache = true; 636 } 637 } 638#endif 639 640 PC_FREE_ZERO(app_playlist); 641 // warnx("Added playlist for app %s", playlist_path); 642 } 643 644 } 645 } 646 } else { 647 playlist_path[playlist_path_end_idx] = '\0'; 648 // warnx("Unknown file in user playlist directory %s: %s", playlist_path, direntry->d_name); 649 } 650 } 651 652 if (playlist_size > max_playlist_size) { 653 break; 654 } 655 656 if (iteration == 0) { 657 rewinddir(dir); 658 } 659 } 660 closedir(dir); 661 dir = NULL; 662 663 664 665 666 } 667 } 668 669 if (error != 0) { 670 goto out; 671 } 672 673 if (! isCompositeDisk) { 674 // Add any files we know are read in every login, but change every login 675 // --- none yet, use add_file --- 676 } 677 678 // Slide the user playlist by the number of batches we have in the boot playlist 679 int batch_max = 0; 680 for (i = 0; i < pc->p_nentries; i++) { 681 if (pc->p_entries[i].pe_batch > batch_max) { 682 batch_max = pc->p_entries[i].pe_batch; 683 } 684 } 685 batch_max ++; 686 for (i = 0; i < user_playlist->p_nentries; i++) { 687 user_playlist->p_entries[i].pe_batch += batch_max; 688 } 689 690 error = BC_merge_playlists(pc, user_playlist); 691 if (error != 0) { 692 if (pc->p_entries) { 693 free(pc->p_entries); 694 } 695 if (pc->p_mounts) { 696 free(pc->p_mounts); 697 } 698 memset(pc, 0, sizeof(*pc)); 699 } 700 701out: 702 PC_FREE_ZERO(user_playlist); 703 return (error); 704} 705 706/* 707 * Add contents of the file to the bootcache 708 */ 709static int 710add_file(struct BC_playlist *pc, const char *fname, const int batch, const bool shared, const bool low_priority) 711{ 712#define MAX_FILESIZE (10ll * 1024 * 1024) 713 int error; 714 int fd = open(fname, O_RDONLY); 715 if (fd == -1) { 716 if (errno != ENOENT && errno != EINVAL) { 717 warnx("Unable to open %s to add it to the boot cache: %d %s", fname, errno, strerror(errno)); 718 } 719 return errno; 720 } 721 722 off_t maxsize = MAX_FILESIZE; 723 if (shared) { 724 maxsize = 0; 725 } 726 727 struct BC_playlist *playlist = NULL; 728 error = BC_playlist_for_filename(fd, fname, maxsize, &playlist); 729 close(fd); 730 if (playlist == NULL) { 731 if (error != ENOENT && error != EINVAL) { 732 warnx("Unable to create playlist for %s: %d %s", fname, error, strerror(error)); 733 } 734 return error; 735 } 736 if(shared || low_priority){ 737 int i; 738 u_int16_t flags = 0x0; 739 if (shared) flags |= BC_PE_SHARED; 740 if (low_priority) flags |= BC_PE_LOWPRIORITY; 741 for (i = 0; i < playlist->p_nentries; i++) { 742 playlist->p_entries[i].pe_flags |= flags; 743 } 744 } 745 746 if(batch > 0){ 747 int i; 748 for (i = 0; i < playlist->p_nentries; i++) { 749 playlist->p_entries[i].pe_batch += batch; 750 } 751 } else if (batch < 0) { 752 int i; 753 int inverse_batch = 0 - batch; 754 for (i = 0; i < pc->p_nentries; i++) { 755 pc->p_entries[i].pe_batch += inverse_batch; 756 } 757 } 758 759 error = BC_merge_playlists(pc, playlist); 760 PC_FREE_ZERO(playlist); 761 762 if (error != 0) { 763 if (pc->p_entries) { 764 free(pc->p_entries); 765 } 766 if (pc->p_mounts) { 767 free(pc->p_mounts); 768 } 769 memset(pc, 0, sizeof(*pc)); 770 } 771 772 return error; 773} 774 775/* 776 * Add contents of a directory to the bootcache. Does not operate recursively. 777 */ 778static int 779add_directory(struct BC_playlist *pc, const char *dname, const int batch, const bool shared){ 780#define MAX_FILES_PER_DIR 10 781 782 DIR *dirp = opendir(dname); 783 if (!dirp) 784 return errno; 785 786 int count = 0; 787 788 struct dirent *dp; 789 while ((dp = readdir(dirp)) != NULL){ 790 if (dp->d_type != DT_REG) 791 continue; 792 793 char fname[MAXPATHLEN]; 794 int ret = snprintf(fname, MAXPATHLEN, "%s/%s", dname, dp->d_name); 795 if (ret < 0 || ret >= MAXPATHLEN) 796 continue; 797 798 add_file(pc, fname, batch, shared, false); 799 if (++count >= MAX_FILES_PER_DIR) break; 800 } 801 802 closedir(dirp); 803 804 return 0; 805} 806 807/* 808 * Add fseventsd-related files to the bootcache. 809 * 810 * We add the 10 newest files of the fsventsd folder to the boot cache 811 */ 812static int 813add_fseventsd_files(struct BC_playlist *pc, const int batch, const bool shared){ 814#define FSEVENTSD_DIR "/.fseventsd" 815#define MAX_NAME_LENGTH 64 816#define NUM_FILES_TO_WARM 10 817 818 DIR *dirp = opendir(FSEVENTSD_DIR); 819 if (!dirp) 820 return errno; 821 822 char newest_files[NUM_FILES_TO_WARM][MAX_NAME_LENGTH]; 823 int i; 824 for (i = 0; i < NUM_FILES_TO_WARM; i++) { 825 newest_files[i][0] = '\0'; 826 } 827 828 // Copy the first 10 files into our array 829 struct dirent *dp; 830 for (i = 0; i < NUM_FILES_TO_WARM && (dp = readdir(dirp)) != NULL; i++) { 831 if (dp->d_type != DT_REG) 832 continue; 833 strlcpy(newest_files[i], dp->d_name, MAX_NAME_LENGTH); 834 } 835 836 // Check if we ran out of directory entries 837 if (dp) { 838 839 //Find the oldest file of the first 10 840 char* oldest_new_file = newest_files[0]; 841 for (i = 1; i < NUM_FILES_TO_WARM; i++) { 842 if (strncmp(newest_files[i], oldest_new_file, MAX_NAME_LENGTH) < 0) { 843 oldest_new_file = newest_files[i]; 844 } 845 } 846 847 // Find any files that are newer than the first 10 848 while ((dp = readdir(dirp)) != NULL){ 849 if (dp->d_type != DT_REG) 850 continue; 851 852 // We want the last 10 files, and they're ordered alphabetically 853 if (strncmp(oldest_new_file, dp->d_name, MAX_NAME_LENGTH) < 0) { 854 strlcpy(oldest_new_file, dp->d_name, MAX_NAME_LENGTH); 855 856 // We replaced the oldest file, find the oldest of the 10 newest again 857 for (i = 0; i < NUM_FILES_TO_WARM; i++) { 858 if (strncmp(newest_files[i], oldest_new_file, MAX_NAME_LENGTH) < 0) { 859 oldest_new_file = newest_files[i]; 860 } 861 } 862 } 863 } 864 } 865 closedir(dirp); 866 867 for (i = 0; i < NUM_FILES_TO_WARM; i++) { 868 if (newest_files[i][0] != '\0') { 869 char fname[128]; 870 int ret = snprintf(fname, 128, "%s/%s", FSEVENTSD_DIR, newest_files[i]); 871 if (ret < 0 || ret >= 128) 872 continue; 873 874 add_file(pc, fname, batch, shared, false); 875 } 876 } 877 878 879 return 0; 880} 881 882/* 883 * Start the cache, optionally passing in the playlist file. 884 */ 885static int 886start_cache(const char *pfname) 887{ 888 struct BC_playlist *pc; 889 int error; 890 891 /* set up to start recording with no playback */ 892 pc = NULL; 893 894 if (pfname == NULL) 895 errx(1, "No playlist provided"); 896 897 /* 898 * TAL App Cache needs a way to record without playback 899 */ 900 if (strcmp(pfname, "nofile")) { 901 902 /* 903 * If we have a playlist, open it and prepare to load it. 904 */ 905 error = BC_read_playlist(pfname, &pc); 906 907 /* 908 * If the playlist is missing or invalid, ignore it. We'll 909 * overwrite it later. 910 */ 911 if ((error != 0) && (error != EINVAL) && (error != ENOENT)) { 912 warnx("could not read playlist %s: %s", pfname, strerror(error)); 913 PC_FREE_ZERO(pc); 914 return(error); 915 } 916 917 /* 918 * Remove the consumed playlist: it'll be replaced once BootCache 919 * completes successfully. 920 */ 921 922 //char prev_path[MAXPATHLEN]; 923 //snprintf(prev_path, sizeof(prev_path), "%s.previous", pfname); 924 //error = rename(pfname, prev_path); // for debugging 925 error = unlink(pfname); 926 if (error != 0 && errno != ENOENT) { 927 warnx("could not unlink playlist %s: %s", pfname, strerror(errno)); 928 } 929 } 930 931 error = BC_start(pc); 932 if (error != 0) 933 warnx("could not start cache: %d %s", error, strerror(error)); 934 935 PC_FREE_ZERO(pc); 936 return(error); 937} 938 939/* 940 * Stop the cache and fetch the history list. Post-process the history list 941 * and save to the named playlist file. 942 */ 943static int 944stop_cache(const char *pfname, int debugging) 945{ 946 struct BC_playlist *pc; 947 struct BC_history *hc; 948 struct BC_statistics *ss; 949 int error; 950 951 /* 952 * Notify whoever cares that BootCache has stopped recording. 953 */ 954 notify_post("com.apple.system.private.bootcache.done"); 955 956 /* 957 * Stop the cache and fetch the history list. 958 */ 959 if ((error = BC_stop(&hc)) != 0) 960 return error; 961 962 if (verbose > 0) { 963 print_statistics(NULL); 964 if (verbose > 1) 965 print_history(hc); 966 } 967 968 /* write history and stats to debug logs if debugging */ 969 if (debugging) { 970 BC_print_history(BC_BOOT_HISTFILE, hc); 971 if ((error = BC_fetch_statistics(&ss)) != 0) 972 errx(1, "could not fetch cache statistics: %d %s", error, strerror(error)); 973 if (ss != NULL) { 974 BC_print_statistics(BC_BOOT_STATFILE, ss); 975 } 976 } 977 978 /* 979 * If we have not been asked to update the history list, we are done 980 * here. 981 */ 982 if (pfname == NULL) { 983 HC_FREE_ZERO(hc); 984 return(0); 985 } 986 987 /* 988 * Convert the history list to playlist format. 989 */ 990 if ((error = BC_convert_history(hc, &pc)) != 0) { 991 if (!debugging) { 992 BC_print_history(BC_BOOT_HISTFILE, hc); 993 } 994 HC_FREE_ZERO(hc); 995 errx(1, "history to playlist conversion failed: %d %s", error, strerror(error)); 996 } 997 998 /* 999 * Sort the playlist into block order and coalesce into the smallest set 1000 * of read operations. 1001 */ 1002 //TODO: Check for SSD and don't sort (and fix coalesceing to work with unsorted lists) 1003#ifdef BOOTCACHE_ENTRIES_SORTED_BY_DISK_OFFSET 1004 BC_sort_playlist(pc); 1005#endif 1006 BC_coalesce_playlist(pc); 1007 1008 1009#if 0 1010 /* 1011 * Turning off playlist merging: this tends to bloat the cache and 1012 * gradually slow the boot until the hitrate drops far enough to force 1013 * a clean slate. Previous attempts to outsmart the system, such as 1014 * truncating cache growth at 5% per boot, have led to fragile behavior 1015 * when the boot sequence changes. Out of a variety of strategies 1016 * (merging, intersection, voting), a memoryless cache gets 1017 * close-to-optimal performance and recovers most quickly from any 1018 * strangeness at boot time. 1019 */ 1020 int onentries; 1021 struct BC_playlist_entry *opc; 1022 1023 /* 1024 * In order to ensure best possible coverage, we try to merge the 1025 * existing playlist with our new history (provided that the hit 1026 * rate has been good). 1027 * 1028 * We check the number of initiated reads from our statistics against 1029 * the number of entries in the "old" playlist to ensure that the 1030 * filename we've been given matches the playlist that was just run. 1031 * 1032 * XXX we hardcode the "good" threshold here to 85%, should be tunable 1033 */ 1034 opc = NULL; 1035 if (ss == NULL) { 1036 warnx("no statistics, not merging playlists"); 1037 goto nomerge; /* no stats, can't make sane decision */ 1038 } 1039 if (ss->ss_total_extents < 1) { 1040 warnx("no playlist in kernel, not merging"); 1041 goto nomerge; 1042 } 1043 if (BC_read_playlist(pfname, &opc, &onentries) != 0) { 1044 warnx("no old playlist '%s', not merging", pfname); 1045 goto nomerge; /* can't read old playlist, nothing to merge */ 1046 } 1047 if (onentries != ss->ss_total_extents) { 1048 warnx("old playlist does not match in-kernel playlist, not merging"); 1049 goto nomerge; /* old playlist doesn't match in-kernel playlist */ 1050 } 1051 if (((nentries * 100) / onentries) < 105) { 1052 warnx("new playlist not much bigger than old playlist, not merging"); 1053 goto nomerge; /* new playlist has < 5% fewer extents */ 1054 } 1055 if (((ss->ss_spurious_blocks * 100) / (ss->ss_read_blocks + 1)) > 10) { 1056 warnx("old playlist has excess block wastage, not merging"); 1057 goto nomerge; /* old playlist has > 10% block wastage */ 1058 } 1059 if (((ss->ss_hit_blocks * 100) / (ss->ss_requested_blocks + 1)) < 85) { 1060 warnx("old playlist has poor hitrate, not merging"); 1061 goto nomerge; /* old playlist had < 85% hitrate */ 1062 } 1063 BC_merge_playlists(&pc, &nentries, opc, onentries); 1064 1065nomerge: 1066 PC_FREE_ZERO(opc); 1067#endif /* 0 */ 1068 1069 /* 1070 * Securely overwrite the previous playlist. 1071 */ 1072 if ((error = BC_write_playlist(pfname, pc)) != 0) { 1073 errx(1, "could not write playlist: %d %s", error, strerror(error)); 1074 } 1075 1076 return(0); 1077} 1078 1079/* 1080 * Jettison the cache. 1081 */ 1082static int 1083jettison_cache(void) 1084{ 1085 int error; 1086 1087 /* 1088 * Stop the cache and fetch the history list. 1089 */ 1090 if ((error = BC_jettison()) != 0) 1091 return error; 1092 1093 if (verbose > 0) 1094 print_statistics(NULL); 1095 1096 return(0); 1097} 1098 1099 1100/* 1101 * Merge multiple playlist files. 1102 */ 1103static int 1104merge_playlists(const char *pfname, int argc, char *argv[], int* pbatch) 1105{ 1106 struct BC_playlist *pc = NULL, *npc = NULL; 1107 int i, j, error; 1108 1109 if (pfname == NULL) 1110 errx(1, "must specify a playlist file to merge"); 1111 1112 pc = calloc(1, sizeof(*pc)); 1113 if(!pc) errx(1, "could not allocate initial playlist"); 1114 1115 /* 1116 * Read playlists into memory. 1117 */ 1118 for (i = 0; i < argc; i++) { 1119 1120 /* 1121 * Read the next playlist and merge with the current set. 1122 */ 1123 if (BC_read_playlist(argv[i], &npc) != 0) { 1124 error = 1; 1125 goto out; 1126 } 1127 1128 /* 1129 * Force the second and subsequent playlists into the specified batch 1130 */ 1131 if (pbatch && i > 0) 1132 for (j = 0; j < npc->p_nentries; j++) 1133 npc->p_entries[j].pe_batch += *pbatch; 1134 1135 if (BC_merge_playlists(pc, npc)){ 1136 error = 1; 1137 goto out; 1138 } 1139 PC_FREE_ZERO(npc); 1140 } 1141 1142 error = BC_write_playlist(pfname, pc); 1143 1144out: 1145 PC_FREE_ZERO(npc); 1146 PC_FREE_ZERO(pc); 1147 1148 return error; 1149} 1150 1151/* 1152 * Print statistics. 1153 */ 1154static int 1155print_statistics(struct BC_statistics *ss) 1156{ 1157 int error; 1158 1159 if (ss == NULL) { 1160 if ((error = BC_fetch_statistics(&ss)) != 0) { 1161 errx(1, "could not fetch statistics: %d %s", error, strerror(error)); 1162 } 1163 } 1164 1165 return(BC_print_statistics(NULL, ss)); 1166} 1167 1168/* 1169 * Print history entries. 1170 */ 1171static void 1172print_history(struct BC_history *hc) 1173{ 1174 int i; 1175 for (i = 0; i < hc->h_nentries; i++) { 1176 printf("%s %-12llu %-8llu %5u%s%s\n", 1177 uuid_string(hc->h_mounts[hc->h_entries[i].he_mount_idx].hm_uuid), 1178 hc->h_entries[i].he_offset, hc->h_entries[i].he_length, 1179 hc->h_entries[i].he_pid, 1180 hc->h_entries[i].he_flags & BC_HE_HIT ? " hit" : 1181 hc->h_entries[i].he_flags & BC_HE_WRITE ? " write" : 1182 hc->h_entries[i].he_flags & BC_HE_TAG ? " tag" : " miss", 1183 hc->h_entries[i].he_flags & BC_HE_SHARED ? " shared" : ""); 1184 } 1185} 1186 1187/* 1188 * Print a playlist from a file. 1189 */ 1190static int 1191print_playlist(const char *pfname, int source) 1192{ 1193 struct BC_playlist *pc; 1194 struct BC_playlist_mount *pm; 1195 struct BC_playlist_entry *pe; 1196 int i; 1197 u_int64_t size = 0, size_lowpri = 0, size_batch[BC_MAXBATCHES] = {0}; 1198 1199 if (pfname == NULL) 1200 errx(1, "must specify a playlist file to print"); 1201 1202 /* 1203 * Suck in the playlist. 1204 */ 1205 if (BC_read_playlist(pfname, &pc)) 1206 errx(1, "could not read playlist"); 1207 1208 if (source) { 1209 printf("static struct BC_playlist_mount BC_mounts[] = {\n"); 1210 for (i = 0; i < pc->p_nmounts; i++) { 1211 pm = pc->p_mounts + i; 1212 printf(" {\"%s\", 0x%x}%s\n", 1213 uuid_string(pm->pm_uuid), pm->pm_nentries, 1214 (i < (pc->p_nmounts - 1)) ? "," : ""); 1215 } 1216 printf("};\n"); 1217 printf("static struct BC_playlist_entry BC_entries[] = {\n"); 1218 } else { 1219 for (i = 0; i < pc->p_nmounts; i++) { 1220 pm = pc->p_mounts + i; 1221 printf("Mount %s %5d entries\n", 1222 uuid_string(pm->pm_uuid), pm->pm_nentries); 1223 } 1224 } 1225 1226 /* 1227 * Print entries in source or "human-readable" format. 1228 */ 1229 for (i = 0; i < pc->p_nentries; i++) { 1230 pe = pc->p_entries + i; 1231 if (source) { 1232 printf(" {0x%llx, 0x%llx, 0x%x, 0x%x, 0x%x}%s\n", 1233 pe->pe_offset, pe->pe_length, pe->pe_batch, pe->pe_flags, pe->pe_mount_idx, 1234 (i < (pc->p_nentries - 1)) ? "," : ""); 1235 } else { 1236 printf("%s %-12llu %-8llu %d 0x%x\n", 1237 uuid_string(pc->p_mounts[pe->pe_mount_idx].pm_uuid), pe->pe_offset, pe->pe_length, pe->pe_batch, pe->pe_flags); 1238 } 1239 if (pe->pe_flags & BC_PE_LOWPRIORITY) { 1240 size_lowpri += pe->pe_length; 1241 } else { 1242 size += pe->pe_length; 1243 size_batch[pe->pe_batch] += pe->pe_length; 1244 } 1245 } 1246 if (source) { 1247 printf("};\n"); 1248 } else { 1249 printf("%12llu bytes\n", size); 1250 printf("%12llu low-priority bytes\n", size_lowpri); 1251 for (i = 0; i < BC_MAXBATCHES; i++) { 1252 if (size_batch[i] != 0) { 1253 printf("%12llu bytes batch %d\n", size_batch[i], i); 1254 } 1255 } 1256 } 1257 1258 PC_FREE_ZERO(pc); 1259 return(0); 1260} 1261 1262static void get_volume_uuid(const char* volume, uuid_t uuid_out) 1263{ 1264 struct attrlist list = { 1265 .bitmapcount = ATTR_BIT_MAP_COUNT, 1266 .volattr = ATTR_VOL_INFO | ATTR_VOL_UUID, 1267 }; 1268 1269 struct { 1270 uint32_t size; 1271 uuid_t uuid; 1272 } attrBuf = {0}; 1273 1274 if (0 != getattrlist(volume, &list, &attrBuf, sizeof(attrBuf), 0)) { 1275 errx(1, "unable to determine uuid for volume %s", volume); 1276 } 1277 1278 uuid_copy(uuid_out, attrBuf.uuid); 1279} 1280 1281/* 1282 * Read a playlist and write to a file. 1283 */ 1284static int 1285unprint_playlist(const char *pfname) 1286{ 1287 struct BC_playlist *pc; 1288 int nentries, alloced, error, got, i; 1289 uuid_string_t uuid_string; 1290 uuid_t uuid; 1291 int seen_old_style; 1292 int m_nentries; 1293 uint64_t unused; 1294 char buf[128]; 1295 1296 pc = calloc(1, sizeof(*pc)); 1297 if(!pc) errx(1, "could not allocate initial playlist"); 1298 1299 alloced = 0; 1300 nentries = 0; 1301 seen_old_style = 0; 1302 1303 if (pfname == NULL) 1304 errx(1, "must specify a playlist file to create"); 1305 1306 while (NULL != fgets(buf, sizeof(buf), stdin)) { 1307 1308 /* 1309 * Parse mounts 1310 */ 1311 got = sscanf(buf, "Mount %s %u entries ", 1312 uuid_string, &m_nentries); 1313 1314 if (got == 2) { 1315 /* make sure we have space for the next mount */ 1316 if ((pc->p_mounts = realloc(pc->p_mounts, (pc->p_nmounts + 1) * sizeof(*pc->p_mounts))) == NULL) 1317 errx(1, "could not allocate memory for %d mounts", pc->p_nmounts + 1); 1318 1319 pc->p_mounts[pc->p_nmounts].pm_nentries = m_nentries; 1320 uuid_parse(uuid_string, pc->p_mounts[pc->p_nmounts].pm_uuid); 1321 1322 pc->p_nmounts++; 1323 1324 continue; 1325 } 1326 1327 /* 1328 * Parse entries 1329 */ 1330 1331 /* make sure we have space for the next entry */ 1332 if (nentries >= alloced) { 1333 if (alloced == 0) { 1334 alloced = 100; 1335 } else { 1336 alloced = alloced * 2; 1337 } 1338 if ((pc->p_entries = realloc(pc->p_entries, alloced * sizeof(*pc->p_entries))) == NULL) 1339 errx(1, "could not allocate memory for %d entries", alloced); 1340 } 1341 1342 /* read input */ 1343 got = sscanf(buf, "%s %lli %lli %hi %hi", 1344 uuid_string, 1345 &(pc->p_entries + nentries)->pe_offset, 1346 &(pc->p_entries + nentries)->pe_length, 1347 &(pc->p_entries + nentries)->pe_batch, 1348 &(pc->p_entries + nentries)->pe_flags); 1349 if (got == 5) { 1350 uuid_parse(uuid_string, uuid); 1351 for (i = 0; i < pc->p_nmounts; i++) { 1352 if (0 == uuid_compare(uuid, pc->p_mounts[i].pm_uuid)) { 1353 (pc->p_entries + nentries)->pe_mount_idx = i; 1354 break; 1355 } 1356 } 1357 if (i == pc->p_nmounts) { 1358 errx(1, "entry doesn't match any existing mount:\n%s", buf); 1359 } 1360 1361 nentries++; 1362 continue; 1363 1364 } 1365 1366 /* Support old-style format (no UUID, root volume implied) */ 1367 got = sscanf(buf, "%llu %llu %hu", 1368 &(pc->p_entries + nentries)->pe_offset, 1369 &(pc->p_entries + nentries)->pe_length, 1370 &(pc->p_entries + nentries)->pe_batch); 1371 if (got == 3) { 1372 1373 if (! seen_old_style) { 1374 if (pc->p_nmounts > 0) { 1375 errx(1, "Bad entry line (Needs mount UUID):\n%s", buf); 1376 } 1377 warnx("No mount UUID provided, assuuming root volume"); 1378 pc->p_nmounts = 1; 1379 if ((pc->p_mounts = malloc(sizeof(*pc->p_mounts))) == NULL) 1380 errx(1, "could not allocate memory for %d mounts", pc->p_nmounts); 1381 pc->p_mounts[0].pm_nentries = 0; 1382 get_volume_uuid("/", pc->p_mounts[0].pm_uuid); 1383 seen_old_style = 1; 1384 } 1385 1386 (pc->p_entries + nentries)->pe_mount_idx = 0; 1387 (pc->p_entries + nentries)->pe_flags = 0; 1388 nentries++; 1389 1390 continue; 1391 } 1392 1393 /* Ignore the line from print_playlist that has the total size of the cache */ 1394 got = sscanf(buf, "%llu bytes", &unused); 1395 if (got == 1) { 1396 continue; 1397 } 1398 1399 got = sscanf(buf, "%llu low-priority bytes", &unused); 1400 if (got == 1) { 1401 continue; 1402 } 1403 1404 /* Ignore the line from the old-style print_playlist that has the block size of the cache */ 1405 got = sscanf(buf, "%llu-byte blocks", &unused); 1406 if (got == 1) { 1407 continue; 1408 } 1409 1410 /* Ignore the line from theold-style print_playlist that has the total size of the cache */ 1411 got = sscanf(buf, "%llu blocks", &unused); 1412 if (got == 1) { 1413 continue; 1414 } 1415 1416 errx(1, "Bad input line: '%s'", buf); 1417 } 1418 1419 pc->p_nentries = nentries; 1420 if (seen_old_style) 1421 pc->p_mounts[0].pm_nentries = nentries; 1422 1423 if (BC_verify_playlist(pc)) { 1424 errx(1, "Playlist failed verification"); 1425 } 1426 1427 if ((error = BC_write_playlist(pfname, pc)) != 0) 1428 errx(1, "could not create playlist: %d %s", error, strerror(error)); 1429 1430 PC_FREE_ZERO(pc); 1431 return(0); 1432} 1433 1434/* 1435 * Modify the playlist the work with any root mount. 1436 */ 1437static int 1438generalize_playlist(const char *pfname) 1439{ 1440 struct BC_playlist *pc; 1441 1442 if (pfname == NULL) 1443 errx(1, "must specify a playlist file to print"); 1444 1445 /* 1446 * Suck in the playlist. 1447 */ 1448 if (BC_read_playlist(pfname, &pc)) 1449 errx(1, "could not read playlist"); 1450 1451 if (pc->p_nmounts != 1) 1452 errx(1, "Playlist generalization only works on playlists with a single mount"); 1453 1454 uuid_clear(pc->p_mounts[0].pm_uuid); 1455 BC_write_playlist(pfname, pc); 1456 1457 PC_FREE_ZERO(pc); 1458 return(0); 1459} 1460 1461/* 1462 * Read a canned playlist specification from stdin and generate 1463 * a sorted playlist from it, as applied to the given root volume 1464 * (which defaults to "/"). This is used during the OS Install to 1465 * seed a bootcache for first boot from a list of files + offsets. 1466 */ 1467static int 1468generate_playlist(const char *pfname, const char *root) 1469{ 1470 struct BC_playlist *pc; 1471 int nentries, alloced; 1472 long block_size; 1473 1474 if (pfname == NULL) 1475 errx(1, "must specify a playlist file to create"); 1476 1477 if (root == NULL) 1478 root = "/"; 1479 if (strlen(root) >= 512) 1480 errx(1, "root path must be less than 512 characters"); 1481 1482 pc = malloc(sizeof(*pc)); 1483 1484 /* Setup the root volume as the only mount */ 1485 pc->p_nmounts = 1; 1486 if ((pc->p_mounts = malloc(sizeof(*pc->p_mounts))) == NULL) 1487 errx(1, "could not allocate memory for %d mounts", pc->p_nmounts); 1488 pc->p_mounts[0].pm_nentries = 0; 1489 1490 struct statfs statfs_buf; 1491 if (0 != statfs(root, &statfs_buf)) { 1492 warnx("Unable to stafs %s: %d %s", root, errno, strerror(errno)); 1493 // Assume 512-byte block size 1494 block_size = 512; 1495 } else { 1496 block_size = statfs_buf.f_bsize; 1497 } 1498 1499 get_volume_uuid(root, pc->p_mounts[0].pm_uuid); 1500 1501 /* Fill in the entries */ 1502 nentries = 0; 1503 alloced = 4096; /* we know the rough size of the list */ 1504 pc->p_entries = malloc(alloced * sizeof(*pc->p_entries)); 1505 if(!pc->p_entries) errx(1, "could not allocate initial playlist"); 1506 for (;;) { /* go over each line */ 1507 1508 /* read input */ 1509 int64_t offset, count; 1510 int batch; 1511 if(fscanf(stdin, "%lli %lli %i ", &offset, &count, &batch) < 3) break; 1512 1513 /* build the path */ 1514 char path[2048]; 1515 unsigned int path_offset = (unsigned int) strlen(root); 1516 strlcpy(path, root, sizeof(path)); 1517 while(path_offset < 2048) { 1518 int read = fgetc(stdin); 1519 if(read == EOF || read == '\n') { 1520 path[path_offset] = '\0'; 1521 break; 1522 } else { 1523 path[path_offset++] = (char) read; 1524 } 1525 } 1526 if(path_offset == 2048) continue; 1527 1528 /* open and stat the file */ 1529 struct stat sb; 1530 int fd = open(path, O_RDONLY); 1531 if(fd == -1 || fstat(fd, &sb) < 0) { 1532 /* give up on this line */ 1533 if(fd != -1) close(fd); 1534 continue; 1535 } 1536 1537 // Round up to the block size 1538 sb.st_size = (((sb.st_size + (block_size - 1)) / block_size) * block_size); 1539 1540 /* add metadata blocks for file */ 1541 /* TODO: 1542 as a further enhancement, since we know we're going to access this file 1543 by name, it would make sense to add to the bootcache any blocks that 1544 will be used in doing so, including the directory entries of the parent 1545 directories on up the chain. 1546 */ 1547 1548 /* find blocks in the file */ 1549 off_t position; 1550 for(position = offset; position < offset + count && position < sb.st_size; ) { 1551 1552 off_t remaining = (count - (position - offset)); 1553 struct log2phys l2p = { 1554 .l2p_flags = 0, 1555 .l2p_devoffset = position, //As an IN parameter to F_LOG2PHYS_EXT, this is the offset into the file 1556 .l2p_contigbytes = remaining, //As an IN parameter to F_LOG2PHYS_EXT, this is the number of bytes to be queried 1557 }; 1558 1559 int ret = fcntl(fd, F_LOG2PHYS_EXT, &l2p); 1560 if (ret != 0) { 1561 errx(1, "fcntl(%d (%s), F_LOG2PHYS_EXT, &{.offset: %lld, .bytes: %lld}) => %d (errno: %d %s)", 1562 fd, path, l2p.l2p_devoffset, l2p.l2p_contigbytes, ret, errno, strerror(errno)); 1563 break; 1564 } 1565 1566 // l2p.l2p_devoffset; as an OUT parameter from F_LOG2PHYS_EXT, this is the offset in bytes on the disk 1567 // l2p.l2p_contigbytes; as an OUT parameter from F_LOG2PHYS_EXT, this is the number of bytes in the range 1568 1569 position += l2p.l2p_contigbytes; 1570 1571 if (remaining < l2p.l2p_contigbytes ) { 1572 warnx("Invalid size returned for %d from disk (%lld bytes requested, %lld bytes returned)", fd, remaining, l2p.l2p_contigbytes); 1573 break; 1574 } 1575 1576 if (l2p.l2p_contigbytes <= 0) { 1577 //RLOG(INFO, "%"PRIdoff":%"PRIdoff" returned %"PRIdoff":%"PRIdoff"\n", position, remaining, l2p.l2p_devoffset, l2p.l2p_contigbytes); 1578 break; 1579 } 1580 1581 if (l2p.l2p_devoffset + l2p.l2p_contigbytes <= l2p.l2p_devoffset) { 1582 warnx("Invalid block range return for %d from disk (%lld:%lld returned %lld:%lld)\n", fd, position, remaining, l2p.l2p_devoffset, l2p.l2p_contigbytes); 1583 break; 1584 } 1585 1586 /* create the new entry */ 1587 pc->p_entries[nentries].pe_offset = l2p.l2p_devoffset; 1588 pc->p_entries[nentries].pe_length = l2p.l2p_contigbytes; 1589 pc->p_entries[nentries].pe_batch = batch; 1590 pc->p_entries[nentries].pe_flags = 0; 1591 pc->p_entries[nentries].pe_mount_idx = 0; 1592 1593 /* create space for a new entry */ 1594 if(++nentries >= alloced) { 1595 alloced *= 2; 1596 if((pc->p_entries = realloc(pc->p_entries, alloced * sizeof(*pc->p_entries))) == NULL) { 1597 errx(1, "could not allocate memory for %d entries", alloced); 1598 } 1599 } 1600 1601 } 1602 close(fd); 1603 } 1604 if(nentries == 0) 1605 errx(1, "no blocks found for playlist"); 1606 1607 pc->p_nentries = nentries; 1608 pc->p_mounts[0].pm_nentries = nentries; 1609 1610 if (BC_verify_playlist(pc)) { 1611 errx(1, "Playlist failed verification"); 1612 } 1613 1614 /* sort the playlist */ 1615#ifdef BOOTCACHE_ENTRIES_SORTED_BY_DISK_OFFSET 1616 BC_sort_playlist(pc); 1617#endif 1618 BC_coalesce_playlist(pc); 1619 1620 /* write the playlist */ 1621 if (BC_write_playlist(pfname, pc) != 0) 1622 errx(1, "could not create playlist"); 1623 1624 return(0); 1625} 1626 1627 1628/* 1629 * Truncate a playlist to a given number of entries. 1630 */ 1631static int 1632truncate_playlist(const char *pfname, char *larg) 1633{ 1634 struct BC_playlist *pc; 1635 char *cp; 1636 int length, error, i; 1637 1638 if (pfname == NULL) 1639 errx(1, "must specify a playlist file to truncate"); 1640 1641 length = (int) strtol(larg, &cp, 0); 1642 if ((*cp != 0) || (length < 1)) 1643 err(1, "bad truncate length '%s'", larg); 1644 1645 /* 1646 * Suck in the playlist. 1647 */ 1648 if (BC_read_playlist(pfname, &pc)) 1649 errx(1, "could not read playlist to truncate"); 1650 1651 /* 1652 * Make sure the new length is shorter. 1653 */ 1654 if (length > pc->p_nentries) { 1655 warnx("playlist is shorter than specified truncate length"); 1656 } else { 1657 1658 for (i = length; i < pc->p_nentries; i++) { 1659 pc->p_mounts[pc->p_entries[i].pe_mount_idx].pm_nentries--; 1660 } 1661 for (i = 0; i < pc->p_nmounts; i++) { 1662 if (pc->p_mounts[i].pm_nentries == 0) { 1663 memcpy(pc->p_mounts + i, pc->p_mounts + i + 1, pc->p_nmounts - i - 1); 1664 pc->p_nmounts--; 1665 i--; 1666 } 1667 } 1668 pc->p_nentries = length; 1669 1670 /* 1671 * Write a shortened version of the same playlist. 1672 */ 1673 if ((error = BC_write_playlist(pfname, pc)) != 0) 1674 errx(1, "could not truncate playlist: %d %s", error, strerror(error)); 1675 } 1676 PC_FREE_ZERO(pc); 1677 return(0); 1678} 1679 1680