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 add_file(pc, BC_DYLD_SHARED_CACHE_32, 0, true, true); 457 warnx("Added 32-bit shared cache to the low priority batch"); 458 } 459 460 // rdar://9021675 Always warm the shared cache 461 // Try subtypes we know about first. <rdar://problem/16093388> 462 if (0 != add_file(pc, BC_DYLD_SHARED_CACHE_H, -1, true, false)) { 463 add_file(pc, BC_DYLD_SHARED_CACHE, -1, true, false); 464 } 465 } 466 467 error = BC_start(pc); 468 if (error != 0) 469 warnx("could not start cache: %d %s", error, strerror(error)); 470 471 PC_FREE_ZERO(pc); 472 return(error); 473} 474 475/* 476 * Fetch or create a login playlist for the given user and add it to the provided playlist in subsequent batches 477 */ 478static int 479add_playlist_for_preheated_user(struct BC_playlist *pc, bool isCompositeDisk) { 480 481 if (!pc) return 1; 482 483 // These defines match those in warmd 484#define PREHEATED_USER_PLAYLIST_PATH_FMT "/var/db/BootCaches/%s/" 485#define MERGED_PLAYLIST "Merged.playlist" 486#define LOGIN_PLAYLIST "Login.playlist" 487#define DEFAULT_USER "PreheatedUser" 488#define APP_CACHE_PLAYLIST_PREFIX "app." 489#define PLAYLIST_SUFFIX ".playlist" 490#define RUNNING_XATTR_NAME "BC_RUNNING" 491#define I386_XATTR_NAME "BC_I386" 492#define MAX_PLAYLIST_PATH_LENGTH 256 493#define TAL_LOGIN_FOREGROUND_APP_BATCH_NUM 0 494#define TAL_LOGIN_BACKGROUND_APP_BATCH_NUM (TAL_LOGIN_FOREGROUND_APP_BATCH_NUM + 1) 495#define TAL_LOGIN_LOWPRI_DATA_BATCH_NUM (TAL_LOGIN_BACKGROUND_APP_BATCH_NUM + 1) 496#define I386_SHARED_CACHE_NULL_BATCH_NUM (-2) 497#define I386_SHARED_CACHE_LOW_PRIORITY_BATCH_NUM (-1) 498 499 int error, i; 500 int playlist_path_end_idx = 0; 501 char playlist_path[MAX_PLAYLIST_PATH_LENGTH]; 502 char login_user[128] = DEFAULT_USER; 503 struct BC_playlist* user_playlist = NULL; 504 bool already_added_i386_shared_cache = false; 505 506 // Limit the playlist size 507 ssize_t playlist_size = 0; 508 ssize_t max_playlist_size = 0; 509 size_t len = sizeof(max_playlist_size); 510 error = sysctlbyname("hw.memsize", &max_playlist_size, &len, NULL, 0); 511 if(error != 0) { 512 max_playlist_size = (1024 * 1024 * 1024); 513 warnx("sysctlbyname(\"hw.memsize\") failed => %d (%d). Assuming 1GB", error, errno); 514 } 515 max_playlist_size = (max_playlist_size / 2) - (512 * 1024 * 1024); 516 if (max_playlist_size < (512 * 1024 * 1024)) { 517 max_playlist_size = (512 * 1024 * 1024); 518 } 519 520 for (i = 0; i < pc->p_nentries; i++) { 521 playlist_size += pc->p_entries[i].pe_length; 522 } 523 524 if (playlist_size >= max_playlist_size) goto out; 525 526 //rdar://8830944&9209576 Detect FDE user 527 io_registry_entry_t service = IORegistryEntryFromPath(kIOMasterPortDefault, "IODeviceTree:/chosen"); 528 if (service != MACH_PORT_NULL) { 529 530 CFDataRef fde_login_user = IORegistryEntryCreateCFProperty(service, CFSTR(kCSFDEEFILoginUnlockIdentID), 531 kCFAllocatorDefault, kNilOptions); 532 IOObjectRelease(service); 533 if (fde_login_user != NULL) { 534 CFDataGetBytes(fde_login_user, CFRangeMake(0, sizeof(login_user)), (UInt8*)login_user); 535 CFRelease(fde_login_user); 536 } 537 } 538 539 playlist_path_end_idx = snprintf(playlist_path, sizeof(playlist_path), PREHEATED_USER_PLAYLIST_PATH_FMT, login_user); 540 541 // warnx("Reading user playlists from user dir %s", playlist_path); 542 543 // Check for the merged playlist warmd has left 544 strlcpy(playlist_path + playlist_path_end_idx, MERGED_PLAYLIST, sizeof(playlist_path) - playlist_path_end_idx); 545 error = BC_read_playlist(playlist_path, &user_playlist); 546 547 if (error == 0) { 548 int i386_shared_cache_batch_num = I386_SHARED_CACHE_NULL_BATCH_NUM; 549 if (-1 != getxattr(playlist_path, I386_XATTR_NAME, &i386_shared_cache_batch_num, sizeof(i386_shared_cache_batch_num), 0, 0x0)) { 550 if (i386_shared_cache_batch_num != I386_SHARED_CACHE_NULL_BATCH_NUM) { 551 if (i386_shared_cache_batch_num == I386_SHARED_CACHE_LOW_PRIORITY_BATCH_NUM) { 552 add_file(user_playlist, BC_DYLD_SHARED_CACHE_32, 0, true, true); 553 } else { 554 add_file(user_playlist, BC_DYLD_SHARED_CACHE_32, i386_shared_cache_batch_num, true, false); 555 } 556 // warnx("Added 32-bit shared cache to batch %d", i386_shared_cache_batch_num); 557 558 // already_added_i386_shared_cache = true; dead store... 559 } 560 } 561 562 } else if (ENOENT == error) { 563 // No merged playlist for the preheated user. Try to create one from its login and app playlists 564 565 strlcpy(playlist_path + playlist_path_end_idx, LOGIN_PLAYLIST, sizeof(playlist_path) - playlist_path_end_idx); 566 error = BC_read_playlist(playlist_path, &user_playlist); 567 if (0 == error) { 568 569 struct BC_playlist *app_playlist = NULL; 570 struct dirent* direntry; 571 int suffix_length = strlen(PLAYLIST_SUFFIX); 572 int app_prefix_length = strlen(APP_CACHE_PLAYLIST_PREFIX); 573 574 playlist_path[playlist_path_end_idx] = '\0'; 575 DIR* dir = opendir(playlist_path); 576 577 578 // Iterate over the app playlists twice: 579 // The first iteration to add the running apps to the playlist. 580 // The second iteration to add the non-running apps to the low-priority playlist 581 int iteration; 582 for (iteration = 0; iteration < 2; iteration++) { 583 int batch_offset = 0; 584 int flags = 0x0; 585 if (iteration == 0) { 586 // We can't tell the order between apps, so just put them all background to give the login playlist priority 587 batch_offset = TAL_LOGIN_BACKGROUND_APP_BATCH_NUM; 588 if (isCompositeDisk) { 589 //rdar://11294417 All user data is low priority on composite disks 590 flags |= BC_PE_LOWPRIORITY; 591 } 592 } else { 593 // Non-running apps are low-priority 594 batch_offset = TAL_LOGIN_LOWPRI_DATA_BATCH_NUM; 595 flags |= BC_PE_LOWPRIORITY; 596 } 597 598 while ((direntry = readdir(dir)) != NULL) { 599 if (direntry->d_namlen > suffix_length && 0 == strncmp(direntry->d_name + direntry->d_namlen - suffix_length, PLAYLIST_SUFFIX, suffix_length)) { 600 if (0 == strncmp(direntry->d_name, APP_CACHE_PLAYLIST_PREFIX, app_prefix_length)) { 601 602 snprintf(playlist_path + playlist_path_end_idx, sizeof(playlist_path) - playlist_path_end_idx, "/%s", direntry->d_name); 603 604 bool is_running = (-1 != getxattr(playlist_path, RUNNING_XATTR_NAME, NULL, 0, 0, 0x0)); 605 if ((iteration == 0) == is_running) { // First iteration we're looking for running apps; second, for non-running 606 607 if (0 == BC_read_playlist(playlist_path, &app_playlist)) { 608 609 // Adjust the flags and batch for this playlist as necessary 610 for (i = 0; i < app_playlist->p_nentries; i++) { 611 app_playlist->p_entries[i].pe_flags |= flags; 612 app_playlist->p_entries[i].pe_batch += batch_offset; 613 playlist_size += app_playlist->p_entries[i].pe_length; 614 } 615 616 // Make sure we don't oversize the playlist 617 if (playlist_size > max_playlist_size) { 618 // warnx("Login cache maximum size of %ldMB reached", max_playlist_size / (1024 * 0124)); 619 PC_FREE_ZERO(app_playlist); 620 break; 621 } 622 623 if (0 != (error = BC_merge_playlists(user_playlist, app_playlist))) { 624 PC_FREE_ZERO(app_playlist); 625 goto out; 626 } 627 628 if (!already_added_i386_shared_cache) { 629 if (-1 != getxattr(playlist_path, I386_XATTR_NAME, NULL, 0, 0, 0x0)) { 630 add_file(user_playlist, BC_DYLD_SHARED_CACHE_32, batch_offset, true, flags & BC_PE_LOWPRIORITY); 631 already_added_i386_shared_cache = true; 632 } 633 } 634 635 PC_FREE_ZERO(app_playlist); 636 // warnx("Added playlist for app %s", playlist_path); 637 } 638 639 } 640 } 641 } else { 642 playlist_path[playlist_path_end_idx] = '\0'; 643 // warnx("Unknown file in user playlist directory %s: %s", playlist_path, direntry->d_name); 644 } 645 } 646 647 if (playlist_size > max_playlist_size) { 648 break; 649 } 650 651 if (iteration == 0) { 652 rewinddir(dir); 653 } 654 } 655 closedir(dir); 656 dir = NULL; 657 658 659 660 661 } 662 } 663 664 if (error != 0) { 665 goto out; 666 } 667 668 if (! isCompositeDisk) { 669 // Add any files we know are read in every login, but change every login 670 // --- none yet, use add_file --- 671 } 672 673 // Slide the user playlist by the number of batches we have in the boot playlist 674 int batch_max = 0; 675 for (i = 0; i < pc->p_nentries; i++) { 676 if (pc->p_entries[i].pe_batch > batch_max) { 677 batch_max = pc->p_entries[i].pe_batch; 678 } 679 } 680 batch_max ++; 681 for (i = 0; i < user_playlist->p_nentries; i++) { 682 user_playlist->p_entries[i].pe_batch += batch_max; 683 } 684 685 error = BC_merge_playlists(pc, user_playlist); 686 if (error != 0) { 687 if (pc->p_entries) { 688 free(pc->p_entries); 689 } 690 if (pc->p_mounts) { 691 free(pc->p_mounts); 692 } 693 memset(pc, 0, sizeof(*pc)); 694 } 695 696out: 697 PC_FREE_ZERO(user_playlist); 698 return (error); 699} 700 701/* 702 * Add contents of the file to the bootcache 703 */ 704static int 705add_file(struct BC_playlist *pc, const char *fname, const int batch, const bool shared, const bool low_priority) 706{ 707#define MAX_FILESIZE (10ll * 1024 * 1024) 708 int error; 709 int fd = open(fname, O_RDONLY); 710 if (fd == -1) { 711 if (errno != ENOENT && errno != EINVAL) { 712 warnx("Unable to open %s to add it to the boot cache: %d %s", fname, errno, strerror(errno)); 713 } 714 return errno; 715 } 716 717 off_t maxsize = MAX_FILESIZE; 718 if (shared) { 719 maxsize = 0; 720 } 721 722 struct BC_playlist *playlist = NULL; 723 error = BC_playlist_for_filename(fd, fname, maxsize, &playlist); 724 close(fd); 725 if (playlist == NULL) { 726 if (error != ENOENT && error != EINVAL) { 727 warnx("Unable to create playlist for %s: %d %s", fname, error, strerror(error)); 728 } 729 return error; 730 } 731 if(shared || low_priority){ 732 int i; 733 u_int16_t flags = 0x0; 734 if (shared) flags |= BC_PE_SHARED; 735 if (low_priority) flags |= BC_PE_LOWPRIORITY; 736 for (i = 0; i < playlist->p_nentries; i++) { 737 playlist->p_entries[i].pe_flags |= flags; 738 } 739 } 740 741 if(batch > 0){ 742 int i; 743 for (i = 0; i < playlist->p_nentries; i++) { 744 playlist->p_entries[i].pe_batch += batch; 745 } 746 } else if (batch < 0) { 747 int i; 748 int inverse_batch = 0 - batch; 749 for (i = 0; i < pc->p_nentries; i++) { 750 pc->p_entries[i].pe_batch += inverse_batch; 751 } 752 } 753 754 error = BC_merge_playlists(pc, playlist); 755 PC_FREE_ZERO(playlist); 756 757 if (error != 0) { 758 if (pc->p_entries) { 759 free(pc->p_entries); 760 } 761 if (pc->p_mounts) { 762 free(pc->p_mounts); 763 } 764 memset(pc, 0, sizeof(*pc)); 765 } 766 767 return error; 768} 769 770/* 771 * Add contents of a directory to the bootcache. Does not operate recursively. 772 */ 773static int 774add_directory(struct BC_playlist *pc, const char *dname, const int batch, const bool shared){ 775#define MAX_FILES_PER_DIR 10 776 777 DIR *dirp = opendir(dname); 778 if (!dirp) 779 return errno; 780 781 int count = 0; 782 783 struct dirent *dp; 784 while ((dp = readdir(dirp)) != NULL){ 785 if (dp->d_type != DT_REG) 786 continue; 787 788 char fname[MAXPATHLEN]; 789 int ret = snprintf(fname, MAXPATHLEN, "%s/%s", dname, dp->d_name); 790 if (ret < 0 || ret >= MAXPATHLEN) 791 continue; 792 793 add_file(pc, fname, batch, shared, false); 794 if (++count >= MAX_FILES_PER_DIR) break; 795 } 796 797 closedir(dirp); 798 799 return 0; 800} 801 802/* 803 * Add fseventsd-related files to the bootcache. 804 * 805 * We add the 10 newest files of the fsventsd folder to the boot cache 806 */ 807static int 808add_fseventsd_files(struct BC_playlist *pc, const int batch, const bool shared){ 809#define FSEVENTSD_DIR "/.fseventsd" 810#define MAX_NAME_LENGTH 64 811#define NUM_FILES_TO_WARM 10 812 813 DIR *dirp = opendir(FSEVENTSD_DIR); 814 if (!dirp) 815 return errno; 816 817 char newest_files[NUM_FILES_TO_WARM][MAX_NAME_LENGTH]; 818 int i; 819 for (i = 0; i < NUM_FILES_TO_WARM; i++) { 820 newest_files[i][0] = '\0'; 821 } 822 823 // Copy the first 10 files into our array 824 struct dirent *dp; 825 for (i = 0; i < NUM_FILES_TO_WARM && (dp = readdir(dirp)) != NULL; i++) { 826 if (dp->d_type != DT_REG) 827 continue; 828 strlcpy(newest_files[i], dp->d_name, MAX_NAME_LENGTH); 829 } 830 831 // Check if we ran out of directory entries 832 if (dp) { 833 834 //Find the oldest file of the first 10 835 char* oldest_new_file = newest_files[0]; 836 for (i = 1; i < NUM_FILES_TO_WARM; i++) { 837 if (strncmp(newest_files[i], oldest_new_file, MAX_NAME_LENGTH) < 0) { 838 oldest_new_file = newest_files[i]; 839 } 840 } 841 842 // Find any files that are newer than the first 10 843 while ((dp = readdir(dirp)) != NULL){ 844 if (dp->d_type != DT_REG) 845 continue; 846 847 // We want the last 10 files, and they're ordered alphabetically 848 if (strncmp(oldest_new_file, dp->d_name, MAX_NAME_LENGTH) < 0) { 849 strlcpy(oldest_new_file, dp->d_name, MAX_NAME_LENGTH); 850 851 // We replaced the oldest file, find the oldest of the 10 newest again 852 for (i = 0; i < NUM_FILES_TO_WARM; i++) { 853 if (strncmp(newest_files[i], oldest_new_file, MAX_NAME_LENGTH) < 0) { 854 oldest_new_file = newest_files[i]; 855 } 856 } 857 } 858 } 859 } 860 closedir(dirp); 861 862 for (i = 0; i < NUM_FILES_TO_WARM; i++) { 863 if (newest_files[i][0] != '\0') { 864 char fname[128]; 865 int ret = snprintf(fname, 128, "%s/%s", FSEVENTSD_DIR, newest_files[i]); 866 if (ret < 0 || ret >= 128) 867 continue; 868 869 add_file(pc, fname, batch, shared, false); 870 } 871 } 872 873 874 return 0; 875} 876 877/* 878 * Start the cache, optionally passing in the playlist file. 879 */ 880static int 881start_cache(const char *pfname) 882{ 883 struct BC_playlist *pc; 884 int error; 885 886 /* set up to start recording with no playback */ 887 pc = NULL; 888 889 if (pfname == NULL) 890 errx(1, "No playlist provided"); 891 892 /* 893 * TAL App Cache needs a way to record without playback 894 */ 895 if (strcmp(pfname, "nofile")) { 896 897 /* 898 * If we have a playlist, open it and prepare to load it. 899 */ 900 error = BC_read_playlist(pfname, &pc); 901 902 /* 903 * If the playlist is missing or invalid, ignore it. We'll 904 * overwrite it later. 905 */ 906 if ((error != 0) && (error != EINVAL) && (error != ENOENT)) { 907 warnx("could not read playlist %s: %s", pfname, strerror(error)); 908 PC_FREE_ZERO(pc); 909 return(error); 910 } 911 912 /* 913 * Remove the consumed playlist: it'll be replaced once BootCache 914 * completes successfully. 915 */ 916 917 //char prev_path[MAXPATHLEN]; 918 //snprintf(prev_path, sizeof(prev_path), "%s.previous", pfname); 919 //error = rename(pfname, prev_path); // for debugging 920 error = unlink(pfname); 921 if (error != 0 && errno != ENOENT) { 922 warnx("could not unlink playlist %s: %s", pfname, strerror(errno)); 923 } 924 } 925 926 error = BC_start(pc); 927 if (error != 0) 928 warnx("could not start cache: %d %s", error, strerror(error)); 929 930 PC_FREE_ZERO(pc); 931 return(error); 932} 933 934/* 935 * Stop the cache and fetch the history list. Post-process the history list 936 * and save to the named playlist file. 937 */ 938static int 939stop_cache(const char *pfname, int debugging) 940{ 941 struct BC_playlist *pc; 942 struct BC_history *hc; 943 struct BC_statistics *ss; 944 int error; 945 946 /* 947 * Notify whoever cares that BootCache has stopped recording. 948 */ 949 notify_post("com.apple.system.private.bootcache.done"); 950 951 /* 952 * Stop the cache and fetch the history list. 953 */ 954 if ((error = BC_stop(&hc)) != 0) 955 return error; 956 957 if (verbose > 0) { 958 print_statistics(NULL); 959 if (verbose > 1) 960 print_history(hc); 961 } 962 963 /* write history and stats to debug logs if debugging */ 964 if (debugging) { 965 BC_print_history(BC_BOOT_HISTFILE, hc); 966 if ((error = BC_fetch_statistics(&ss)) != 0) 967 errx(1, "could not fetch cache statistics: %d %s", error, strerror(error)); 968 if (ss != NULL) { 969 BC_print_statistics(BC_BOOT_STATFILE, ss); 970 } 971 } 972 973 /* 974 * If we have not been asked to update the history list, we are done 975 * here. 976 */ 977 if (pfname == NULL) { 978 HC_FREE_ZERO(hc); 979 return(0); 980 } 981 982 /* 983 * Convert the history list to playlist format. 984 */ 985 if ((error = BC_convert_history(hc, &pc)) != 0) { 986 if (!debugging) { 987 BC_print_history(BC_BOOT_HISTFILE, hc); 988 } 989 HC_FREE_ZERO(hc); 990 errx(1, "history to playlist conversion failed: %d %s", error, strerror(error)); 991 } 992 993 /* 994 * Sort the playlist into block order and coalesce into the smallest set 995 * of read operations. 996 */ 997 //TODO: Check for SSD and don't sort (and fix coalesceing to work with unsorted lists) 998#ifdef BOOTCACHE_ENTRIES_SORTED_BY_DISK_OFFSET 999 BC_sort_playlist(pc); 1000#endif 1001 BC_coalesce_playlist(pc); 1002 1003 1004#if 0 1005 /* 1006 * Turning off playlist merging: this tends to bloat the cache and 1007 * gradually slow the boot until the hitrate drops far enough to force 1008 * a clean slate. Previous attempts to outsmart the system, such as 1009 * truncating cache growth at 5% per boot, have led to fragile behavior 1010 * when the boot sequence changes. Out of a variety of strategies 1011 * (merging, intersection, voting), a memoryless cache gets 1012 * close-to-optimal performance and recovers most quickly from any 1013 * strangeness at boot time. 1014 */ 1015 int onentries; 1016 struct BC_playlist_entry *opc; 1017 1018 /* 1019 * In order to ensure best possible coverage, we try to merge the 1020 * existing playlist with our new history (provided that the hit 1021 * rate has been good). 1022 * 1023 * We check the number of initiated reads from our statistics against 1024 * the number of entries in the "old" playlist to ensure that the 1025 * filename we've been given matches the playlist that was just run. 1026 * 1027 * XXX we hardcode the "good" threshold here to 85%, should be tunable 1028 */ 1029 opc = NULL; 1030 if (ss == NULL) { 1031 warnx("no statistics, not merging playlists"); 1032 goto nomerge; /* no stats, can't make sane decision */ 1033 } 1034 if (ss->ss_total_extents < 1) { 1035 warnx("no playlist in kernel, not merging"); 1036 goto nomerge; 1037 } 1038 if (BC_read_playlist(pfname, &opc, &onentries) != 0) { 1039 warnx("no old playlist '%s', not merging", pfname); 1040 goto nomerge; /* can't read old playlist, nothing to merge */ 1041 } 1042 if (onentries != ss->ss_total_extents) { 1043 warnx("old playlist does not match in-kernel playlist, not merging"); 1044 goto nomerge; /* old playlist doesn't match in-kernel playlist */ 1045 } 1046 if (((nentries * 100) / onentries) < 105) { 1047 warnx("new playlist not much bigger than old playlist, not merging"); 1048 goto nomerge; /* new playlist has < 5% fewer extents */ 1049 } 1050 if (((ss->ss_spurious_blocks * 100) / (ss->ss_read_blocks + 1)) > 10) { 1051 warnx("old playlist has excess block wastage, not merging"); 1052 goto nomerge; /* old playlist has > 10% block wastage */ 1053 } 1054 if (((ss->ss_hit_blocks * 100) / (ss->ss_requested_blocks + 1)) < 85) { 1055 warnx("old playlist has poor hitrate, not merging"); 1056 goto nomerge; /* old playlist had < 85% hitrate */ 1057 } 1058 BC_merge_playlists(&pc, &nentries, opc, onentries); 1059 1060nomerge: 1061 PC_FREE_ZERO(opc); 1062#endif /* 0 */ 1063 1064 /* 1065 * Securely overwrite the previous playlist. 1066 */ 1067 if ((error = BC_write_playlist(pfname, pc)) != 0) { 1068 errx(1, "could not write playlist: %d %s", error, strerror(error)); 1069 } 1070 1071 return(0); 1072} 1073 1074/* 1075 * Jettison the cache. 1076 */ 1077static int 1078jettison_cache(void) 1079{ 1080 int error; 1081 1082 /* 1083 * Stop the cache and fetch the history list. 1084 */ 1085 if ((error = BC_jettison()) != 0) 1086 return error; 1087 1088 if (verbose > 0) 1089 print_statistics(NULL); 1090 1091 return(0); 1092} 1093 1094 1095/* 1096 * Merge multiple playlist files. 1097 */ 1098static int 1099merge_playlists(const char *pfname, int argc, char *argv[], int* pbatch) 1100{ 1101 struct BC_playlist *pc = NULL, *npc = NULL; 1102 int i, j, error; 1103 1104 if (pfname == NULL) 1105 errx(1, "must specify a playlist file to merge"); 1106 1107 pc = calloc(1, sizeof(*pc)); 1108 if(!pc) errx(1, "could not allocate initial playlist"); 1109 1110 /* 1111 * Read playlists into memory. 1112 */ 1113 for (i = 0; i < argc; i++) { 1114 1115 /* 1116 * Read the next playlist and merge with the current set. 1117 */ 1118 if (BC_read_playlist(argv[i], &npc) != 0) { 1119 error = 1; 1120 goto out; 1121 } 1122 1123 /* 1124 * Force the second and subsequent playlists into the specified batch 1125 */ 1126 if (pbatch && i > 0) 1127 for (j = 0; j < npc->p_nentries; j++) 1128 npc->p_entries[j].pe_batch += *pbatch; 1129 1130 if (BC_merge_playlists(pc, npc)){ 1131 error = 1; 1132 goto out; 1133 } 1134 PC_FREE_ZERO(npc); 1135 } 1136 1137 error = BC_write_playlist(pfname, pc); 1138 1139out: 1140 PC_FREE_ZERO(npc); 1141 PC_FREE_ZERO(pc); 1142 1143 return error; 1144} 1145 1146/* 1147 * Print statistics. 1148 */ 1149static int 1150print_statistics(struct BC_statistics *ss) 1151{ 1152 int error; 1153 1154 if (ss == NULL) { 1155 if ((error = BC_fetch_statistics(&ss)) != 0) { 1156 errx(1, "could not fetch statistics: %d %s", error, strerror(error)); 1157 } 1158 } 1159 1160 return(BC_print_statistics(NULL, ss)); 1161} 1162 1163/* 1164 * Print history entries. 1165 */ 1166static void 1167print_history(struct BC_history *hc) 1168{ 1169 int i; 1170 for (i = 0; i < hc->h_nentries; i++) { 1171 printf("%s %-12llu %-8llu %5u%s%s\n", 1172 uuid_string(hc->h_mounts[hc->h_entries[i].he_mount_idx].hm_uuid), 1173 hc->h_entries[i].he_offset, hc->h_entries[i].he_length, 1174 hc->h_entries[i].he_pid, 1175 hc->h_entries[i].he_flags & BC_HE_HIT ? " hit" : 1176 hc->h_entries[i].he_flags & BC_HE_WRITE ? " write" : 1177 hc->h_entries[i].he_flags & BC_HE_TAG ? " tag" : " miss", 1178 hc->h_entries[i].he_flags & BC_HE_SHARED ? " shared" : ""); 1179 } 1180} 1181 1182/* 1183 * Print a playlist from a file. 1184 */ 1185static int 1186print_playlist(const char *pfname, int source) 1187{ 1188 struct BC_playlist *pc; 1189 struct BC_playlist_mount *pm; 1190 struct BC_playlist_entry *pe; 1191 int i; 1192 u_int64_t size = 0, size_lowpri = 0, size_batch[BC_MAXBATCHES] = {0}; 1193 1194 if (pfname == NULL) 1195 errx(1, "must specify a playlist file to print"); 1196 1197 /* 1198 * Suck in the playlist. 1199 */ 1200 if (BC_read_playlist(pfname, &pc)) 1201 errx(1, "could not read playlist"); 1202 1203 if (source) { 1204 printf("static struct BC_playlist_mount BC_mounts[] = {\n"); 1205 for (i = 0; i < pc->p_nmounts; i++) { 1206 pm = pc->p_mounts + i; 1207 printf(" {\"%s\", 0x%x}%s\n", 1208 uuid_string(pm->pm_uuid), pm->pm_nentries, 1209 (i < (pc->p_nmounts - 1)) ? "," : ""); 1210 } 1211 printf("};\n"); 1212 printf("static struct BC_playlist_entry BC_entries[] = {\n"); 1213 } else { 1214 for (i = 0; i < pc->p_nmounts; i++) { 1215 pm = pc->p_mounts + i; 1216 printf("Mount %s %5d entries\n", 1217 uuid_string(pm->pm_uuid), pm->pm_nentries); 1218 } 1219 } 1220 1221 /* 1222 * Print entries in source or "human-readable" format. 1223 */ 1224 for (i = 0; i < pc->p_nentries; i++) { 1225 pe = pc->p_entries + i; 1226 if (source) { 1227 printf(" {0x%llx, 0x%llx, 0x%x, 0x%x, 0x%x}%s\n", 1228 pe->pe_offset, pe->pe_length, pe->pe_batch, pe->pe_flags, pe->pe_mount_idx, 1229 (i < (pc->p_nentries - 1)) ? "," : ""); 1230 } else { 1231 printf("%s %-12llu %-8llu %d 0x%x\n", 1232 uuid_string(pc->p_mounts[pe->pe_mount_idx].pm_uuid), pe->pe_offset, pe->pe_length, pe->pe_batch, pe->pe_flags); 1233 } 1234 if (pe->pe_flags & BC_PE_LOWPRIORITY) { 1235 size_lowpri += pe->pe_length; 1236 } else { 1237 size += pe->pe_length; 1238 size_batch[pe->pe_batch] += pe->pe_length; 1239 } 1240 } 1241 if (source) { 1242 printf("};\n"); 1243 } else { 1244 printf("%12llu bytes\n", size); 1245 printf("%12llu low-priority bytes\n", size_lowpri); 1246 for (i = 0; i < BC_MAXBATCHES; i++) { 1247 if (size_batch[i] != 0) { 1248 printf("%12llu bytes batch %d\n", size_batch[i], i); 1249 } 1250 } 1251 } 1252 1253 PC_FREE_ZERO(pc); 1254 return(0); 1255} 1256 1257static void get_volume_uuid(const char* volume, uuid_t uuid_out) 1258{ 1259 struct attrlist list = { 1260 .bitmapcount = ATTR_BIT_MAP_COUNT, 1261 .volattr = ATTR_VOL_INFO | ATTR_VOL_UUID, 1262 }; 1263 1264 struct { 1265 uint32_t size; 1266 uuid_t uuid; 1267 } attrBuf = {0}; 1268 1269 if (0 != getattrlist(volume, &list, &attrBuf, sizeof(attrBuf), 0)) { 1270 errx(1, "unable to determine uuid for volume %s", volume); 1271 } 1272 1273 uuid_copy(uuid_out, attrBuf.uuid); 1274} 1275 1276/* 1277 * Read a playlist and write to a file. 1278 */ 1279static int 1280unprint_playlist(const char *pfname) 1281{ 1282 struct BC_playlist *pc; 1283 int nentries, alloced, error, got, i; 1284 uuid_string_t uuid_string; 1285 uuid_t uuid; 1286 int seen_old_style; 1287 int m_nentries; 1288 uint64_t unused; 1289 char buf[128]; 1290 1291 pc = calloc(1, sizeof(*pc)); 1292 if(!pc) errx(1, "could not allocate initial playlist"); 1293 1294 alloced = 0; 1295 nentries = 0; 1296 seen_old_style = 0; 1297 1298 if (pfname == NULL) 1299 errx(1, "must specify a playlist file to create"); 1300 1301 while (NULL != fgets(buf, sizeof(buf), stdin)) { 1302 1303 /* 1304 * Parse mounts 1305 */ 1306 got = sscanf(buf, "Mount %s %u entries ", 1307 uuid_string, &m_nentries); 1308 1309 if (got == 2) { 1310 /* make sure we have space for the next mount */ 1311 if ((pc->p_mounts = realloc(pc->p_mounts, (pc->p_nmounts + 1) * sizeof(*pc->p_mounts))) == NULL) 1312 errx(1, "could not allocate memory for %d mounts", pc->p_nmounts + 1); 1313 1314 pc->p_mounts[pc->p_nmounts].pm_nentries = m_nentries; 1315 uuid_parse(uuid_string, pc->p_mounts[pc->p_nmounts].pm_uuid); 1316 1317 pc->p_nmounts++; 1318 1319 continue; 1320 } 1321 1322 /* 1323 * Parse entries 1324 */ 1325 1326 /* make sure we have space for the next entry */ 1327 if (nentries >= alloced) { 1328 if (alloced == 0) { 1329 alloced = 100; 1330 } else { 1331 alloced = alloced * 2; 1332 } 1333 if ((pc->p_entries = realloc(pc->p_entries, alloced * sizeof(*pc->p_entries))) == NULL) 1334 errx(1, "could not allocate memory for %d entries", alloced); 1335 } 1336 1337 /* read input */ 1338 got = sscanf(buf, "%s %lli %lli %hi %hi", 1339 uuid_string, 1340 &(pc->p_entries + nentries)->pe_offset, 1341 &(pc->p_entries + nentries)->pe_length, 1342 &(pc->p_entries + nentries)->pe_batch, 1343 &(pc->p_entries + nentries)->pe_flags); 1344 if (got == 5) { 1345 uuid_parse(uuid_string, uuid); 1346 for (i = 0; i < pc->p_nmounts; i++) { 1347 if (0 == uuid_compare(uuid, pc->p_mounts[i].pm_uuid)) { 1348 (pc->p_entries + nentries)->pe_mount_idx = i; 1349 break; 1350 } 1351 } 1352 if (i == pc->p_nmounts) { 1353 errx(1, "entry doesn't match any existing mount:\n%s", buf); 1354 } 1355 1356 nentries++; 1357 continue; 1358 1359 } 1360 1361 /* Support old-style format (no UUID, root volume implied) */ 1362 got = sscanf(buf, "%llu %llu %hu", 1363 &(pc->p_entries + nentries)->pe_offset, 1364 &(pc->p_entries + nentries)->pe_length, 1365 &(pc->p_entries + nentries)->pe_batch); 1366 if (got == 3) { 1367 1368 if (! seen_old_style) { 1369 if (pc->p_nmounts > 0) { 1370 errx(1, "Bad entry line (Needs mount UUID):\n%s", buf); 1371 } 1372 warnx("No mount UUID provided, assuuming root volume"); 1373 pc->p_nmounts = 1; 1374 if ((pc->p_mounts = malloc(sizeof(*pc->p_mounts))) == NULL) 1375 errx(1, "could not allocate memory for %d mounts", pc->p_nmounts); 1376 pc->p_mounts[0].pm_nentries = 0; 1377 get_volume_uuid("/", pc->p_mounts[0].pm_uuid); 1378 seen_old_style = 1; 1379 } 1380 1381 (pc->p_entries + nentries)->pe_mount_idx = 0; 1382 (pc->p_entries + nentries)->pe_flags = 0; 1383 nentries++; 1384 1385 continue; 1386 } 1387 1388 /* Ignore the line from print_playlist that has the total size of the cache */ 1389 got = sscanf(buf, "%llu bytes", &unused); 1390 if (got == 1) { 1391 continue; 1392 } 1393 1394 got = sscanf(buf, "%llu low-priority bytes", &unused); 1395 if (got == 1) { 1396 continue; 1397 } 1398 1399 /* Ignore the line from the old-style print_playlist that has the block size of the cache */ 1400 got = sscanf(buf, "%llu-byte blocks", &unused); 1401 if (got == 1) { 1402 continue; 1403 } 1404 1405 /* Ignore the line from theold-style print_playlist that has the total size of the cache */ 1406 got = sscanf(buf, "%llu blocks", &unused); 1407 if (got == 1) { 1408 continue; 1409 } 1410 1411 errx(1, "Bad input line: '%s'", buf); 1412 } 1413 1414 pc->p_nentries = nentries; 1415 if (seen_old_style) 1416 pc->p_mounts[0].pm_nentries = nentries; 1417 1418 if (BC_verify_playlist(pc)) { 1419 errx(1, "Playlist failed verification"); 1420 } 1421 1422 if ((error = BC_write_playlist(pfname, pc)) != 0) 1423 errx(1, "could not create playlist: %d %s", error, strerror(error)); 1424 1425 PC_FREE_ZERO(pc); 1426 return(0); 1427} 1428 1429/* 1430 * Modify the playlist the work with any root mount. 1431 */ 1432static int 1433generalize_playlist(const char *pfname) 1434{ 1435 struct BC_playlist *pc; 1436 1437 if (pfname == NULL) 1438 errx(1, "must specify a playlist file to print"); 1439 1440 /* 1441 * Suck in the playlist. 1442 */ 1443 if (BC_read_playlist(pfname, &pc)) 1444 errx(1, "could not read playlist"); 1445 1446 if (pc->p_nmounts != 1) 1447 errx(1, "Playlist generalization only works on playlists with a single mount"); 1448 1449 uuid_clear(pc->p_mounts[0].pm_uuid); 1450 BC_write_playlist(pfname, pc); 1451 1452 PC_FREE_ZERO(pc); 1453 return(0); 1454} 1455 1456/* 1457 * Read a canned playlist specification from stdin and generate 1458 * a sorted playlist from it, as applied to the given root volume 1459 * (which defaults to "/"). This is used during the OS Install to 1460 * seed a bootcache for first boot from a list of files + offsets. 1461 */ 1462static int 1463generate_playlist(const char *pfname, const char *root) 1464{ 1465 struct BC_playlist *pc; 1466 int nentries, alloced; 1467 long block_size; 1468 1469 if (pfname == NULL) 1470 errx(1, "must specify a playlist file to create"); 1471 1472 if (root == NULL) 1473 root = "/"; 1474 if (strlen(root) >= 512) 1475 errx(1, "root path must be less than 512 characters"); 1476 1477 pc = malloc(sizeof(*pc)); 1478 1479 /* Setup the root volume as the only mount */ 1480 pc->p_nmounts = 1; 1481 if ((pc->p_mounts = malloc(sizeof(*pc->p_mounts))) == NULL) 1482 errx(1, "could not allocate memory for %d mounts", pc->p_nmounts); 1483 pc->p_mounts[0].pm_nentries = 0; 1484 1485 struct statfs statfs_buf; 1486 if (0 != statfs(root, &statfs_buf)) { 1487 warnx("Unable to stafs %s: %d %s", root, errno, strerror(errno)); 1488 // Assume 512-byte block size 1489 block_size = 512; 1490 } else { 1491 block_size = statfs_buf.f_bsize; 1492 } 1493 1494 get_volume_uuid(root, pc->p_mounts[0].pm_uuid); 1495 1496 /* Fill in the entries */ 1497 nentries = 0; 1498 alloced = 4096; /* we know the rough size of the list */ 1499 pc->p_entries = malloc(alloced * sizeof(*pc->p_entries)); 1500 if(!pc->p_entries) errx(1, "could not allocate initial playlist"); 1501 for (;;) { /* go over each line */ 1502 1503 /* read input */ 1504 int64_t offset, count; 1505 int batch; 1506 if(fscanf(stdin, "%lli %lli %i ", &offset, &count, &batch) < 3) break; 1507 1508 /* build the path */ 1509 char path[2048]; 1510 unsigned int path_offset = (unsigned int) strlen(root); 1511 strlcpy(path, root, sizeof(path)); 1512 while(path_offset < 2048) { 1513 int read = fgetc(stdin); 1514 if(read == EOF || read == '\n') { 1515 path[path_offset] = '\0'; 1516 break; 1517 } else { 1518 path[path_offset++] = (char) read; 1519 } 1520 } 1521 if(path_offset == 2048) continue; 1522 1523 /* open and stat the file */ 1524 struct stat sb; 1525 int fd = open(path, O_RDONLY); 1526 if(fd == -1 || fstat(fd, &sb) < 0) { 1527 /* give up on this line */ 1528 if(fd != -1) close(fd); 1529 continue; 1530 } 1531 1532 // Round up to the block size 1533 sb.st_size = (((sb.st_size + (block_size - 1)) / block_size) * block_size); 1534 1535 /* add metadata blocks for file */ 1536 /* TODO: 1537 as a further enhancement, since we know we're going to access this file 1538 by name, it would make sense to add to the bootcache any blocks that 1539 will be used in doing so, including the directory entries of the parent 1540 directories on up the chain. 1541 */ 1542 1543 /* find blocks in the file */ 1544 off_t position; 1545 for(position = offset; position < offset + count && position < sb.st_size; ) { 1546 1547 off_t remaining = (count - (position - offset)); 1548 struct log2phys l2p = { 1549 .l2p_flags = 0, 1550 .l2p_devoffset = position, //As an IN parameter to F_LOG2PHYS_EXT, this is the offset into the file 1551 .l2p_contigbytes = remaining, //As an IN parameter to F_LOG2PHYS_EXT, this is the number of bytes to be queried 1552 }; 1553 1554 int ret = fcntl(fd, F_LOG2PHYS_EXT, &l2p); 1555 if (ret != 0) { 1556 errx(1, "fcntl(%d (%s), F_LOG2PHYS_EXT, &{.offset: %lld, .bytes: %lld}) => %d (errno: %d %s)", 1557 fd, path, l2p.l2p_devoffset, l2p.l2p_contigbytes, ret, errno, strerror(errno)); 1558 break; 1559 } 1560 1561 // l2p.l2p_devoffset; as an OUT parameter from F_LOG2PHYS_EXT, this is the offset in bytes on the disk 1562 // l2p.l2p_contigbytes; as an OUT parameter from F_LOG2PHYS_EXT, this is the number of bytes in the range 1563 1564 position += l2p.l2p_contigbytes; 1565 1566 if (remaining < l2p.l2p_contigbytes ) { 1567 warnx("Invalid size returned for %d from disk (%lld bytes requested, %lld bytes returned)", fd, remaining, l2p.l2p_contigbytes); 1568 break; 1569 } 1570 1571 if (l2p.l2p_contigbytes <= 0) { 1572 //RLOG(INFO, "%"PRIdoff":%"PRIdoff" returned %"PRIdoff":%"PRIdoff"\n", position, remaining, l2p.l2p_devoffset, l2p.l2p_contigbytes); 1573 break; 1574 } 1575 1576 if (l2p.l2p_devoffset + l2p.l2p_contigbytes <= l2p.l2p_devoffset) { 1577 warnx("Invalid block range return for %d from disk (%lld:%lld returned %lld:%lld)\n", fd, position, remaining, l2p.l2p_devoffset, l2p.l2p_contigbytes); 1578 break; 1579 } 1580 1581 /* create the new entry */ 1582 pc->p_entries[nentries].pe_offset = l2p.l2p_devoffset; 1583 pc->p_entries[nentries].pe_length = l2p.l2p_contigbytes; 1584 pc->p_entries[nentries].pe_batch = batch; 1585 pc->p_entries[nentries].pe_flags = 0; 1586 pc->p_entries[nentries].pe_mount_idx = 0; 1587 1588 /* create space for a new entry */ 1589 if(++nentries >= alloced) { 1590 alloced *= 2; 1591 if((pc->p_entries = realloc(pc->p_entries, alloced * sizeof(*pc->p_entries))) == NULL) { 1592 errx(1, "could not allocate memory for %d entries", alloced); 1593 } 1594 } 1595 1596 } 1597 close(fd); 1598 } 1599 if(nentries == 0) 1600 errx(1, "no blocks found for playlist"); 1601 1602 pc->p_nentries = nentries; 1603 pc->p_mounts[0].pm_nentries = nentries; 1604 1605 if (BC_verify_playlist(pc)) { 1606 errx(1, "Playlist failed verification"); 1607 } 1608 1609 /* sort the playlist */ 1610#ifdef BOOTCACHE_ENTRIES_SORTED_BY_DISK_OFFSET 1611 BC_sort_playlist(pc); 1612#endif 1613 BC_coalesce_playlist(pc); 1614 1615 /* write the playlist */ 1616 if (BC_write_playlist(pfname, pc) != 0) 1617 errx(1, "could not create playlist"); 1618 1619 return(0); 1620} 1621 1622 1623/* 1624 * Truncate a playlist to a given number of entries. 1625 */ 1626static int 1627truncate_playlist(const char *pfname, char *larg) 1628{ 1629 struct BC_playlist *pc; 1630 char *cp; 1631 int length, error, i; 1632 1633 if (pfname == NULL) 1634 errx(1, "must specify a playlist file to truncate"); 1635 1636 length = (int) strtol(larg, &cp, 0); 1637 if ((*cp != 0) || (length < 1)) 1638 err(1, "bad truncate length '%s'", larg); 1639 1640 /* 1641 * Suck in the playlist. 1642 */ 1643 if (BC_read_playlist(pfname, &pc)) 1644 errx(1, "could not read playlist to truncate"); 1645 1646 /* 1647 * Make sure the new length is shorter. 1648 */ 1649 if (length > pc->p_nentries) { 1650 warnx("playlist is shorter than specified truncate length"); 1651 } else { 1652 1653 for (i = length; i < pc->p_nentries; i++) { 1654 pc->p_mounts[pc->p_entries[i].pe_mount_idx].pm_nentries--; 1655 } 1656 for (i = 0; i < pc->p_nmounts; i++) { 1657 if (pc->p_mounts[i].pm_nentries == 0) { 1658 memcpy(pc->p_mounts + i, pc->p_mounts + i + 1, pc->p_nmounts - i - 1); 1659 pc->p_nmounts--; 1660 i--; 1661 } 1662 } 1663 pc->p_nentries = length; 1664 1665 /* 1666 * Write a shortened version of the same playlist. 1667 */ 1668 if ((error = BC_write_playlist(pfname, pc)) != 0) 1669 errx(1, "could not truncate playlist: %d %s", error, strerror(error)); 1670 } 1671 PC_FREE_ZERO(pc); 1672 return(0); 1673} 1674 1675