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