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