#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <dirent.h>
#include <libgen.h>
#include <unistd.h>
#include <errno.h>
#include <paths.h>
#include <fcntl.h>
#include <sys/param.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/mount.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/storage/IOMedia.h>
#include <APFS/APFS.h>
#include "bless.h"
#include "bless_private.h"
#include "protos.h"
#include <IOKit/IOBSD.h>
#include <Img4Decode.h>
#include <Img4Encode.h>
#include <apfs/apfs_fsctl.h>
#include <apfs/apfs_mount.h>
#include <System/sys/snapshot.h>
#include <arvhelper.h>
enum {
kIsInvisible = 0x4000,
};
static int DeleteHierarchy(char *path, int pathMax);
static int DeleteFileWithPrejudice(const char *path, struct stat *sb);
static int CopyKernelCollectionFiles(BLContextPtr context, const char *systemKCPath, const char *prebootKCPath);
static int GetFilesInDirWithPrefix(const char *directory, const char *prefix, char ***outFileList, int *outNumFiles);
static void FreeFileList(char **list, int num);
static int StringCompare(const void *a, const void *b);
static int CopyKCFile(BLContextPtr context, const char *from, const char *to);
static bool StringHasSuffix(const char *str, const char *suffix);
int BlessPrebootVolume(BLContextPtr context, const char *rootBSD, const char *bootEFISourceLocation,
CFDataRef labelData, CFDataRef labelData2, struct clarg actargs[klast])
{
int ret;
io_service_t rootMedia = IO_OBJECT_NULL;
io_service_t systemMedia = IO_OBJECT_NULL;
CFStringRef bootUUID = NULL;
CFDictionaryRef booterDict = NULL;
char prebootBSD[MAXPATHLEN];
int mntsize;
int i;
struct statfs *mnts;
char systemPath[MAXPATHLEN];
char rootHashPath[MAXPATHLEN];
char snapshotRootPath[MAXPATHLEN];
char snapshotName[MAXPATHLEN];
char kcPath[MAXPATHLEN];
char prebootMountPoint[MAXPATHLEN];
char prebootFolderPath[MAXPATHLEN];
char bootEFIloc[MAXPATHLEN];
char prebootKCPath[MAXPATHLEN];
bool mustUnmount = false;
uint64_t blessIDs[2];
CFDataRef booterData;
struct stat existingStat;
uint16_t role;
struct statfs sfs;
char bridgeSrcVersionPath[MAXPATHLEN];
char bridgeDstVersionPath[MAXPATHLEN];
char prebootDirPath[MAXPATHLEN];
CFDataRef versionData = NULL;
char *pathEnd;
char *pathEnd2;
bool unmountSnapshot = false;
CFStringRef bsdCF;
char systemDev[64] = _PATH_DEV;
bool mustUnmountSystem = false;
bool setIDs = (actargs[knextonly].present == 0);
bool createSnapshot = (actargs[kcreatesnapshot].present != 0);
bool copyBootEFI = (actargs[kbootefi].hasArg == 1);
bool sealedSnapshot = (actargs[klastsealedsnapshot].present != 0);
bool isARV;
int vol_fd = -1;
bool untagRootSnapshot = false;
if (APFSVolumeRole(rootBSD, &role, NULL)) {
ret = 8;
goto exit;
}
if (role == APFS_VOL_ROLE_PREBOOT || role == APFS_VOL_ROLE_RECOVERY) {
ret = 0;
goto exit;
}
rootMedia = IOServiceGetMatchingService(kIOMasterPortDefault, IOBSDNameMatching(kIOMasterPortDefault, 0, rootBSD));
if (!rootMedia) {
ret = 1;
goto exit;
}
if (IOObjectConformsTo(rootMedia, "AppleAPFSSnapshot")) {
blesscontextprintf(context, kBLLogLevelVerbose, "%s is a snapshot device\n", rootBSD);
ret = BLAPFSSnapshotToVolume(context, rootMedia, &systemMedia);
if (ret) {
blesscontextprintf(context, kBLLogLevelError, "Could not resolve snapshot at %s to volume\n", rootBSD);
goto exit;
}
} else {
systemMedia = rootMedia;
IOObjectRetain(systemMedia);
}
ret = GetPrebootBSDForVolumeBSD(context, rootBSD, prebootBSD, sizeof prebootBSD);
if (ret) {
blesscontextprintf(context, kBLLogLevelVerbose, "Could not find preboot BSD for : %s\n", rootBSD);
goto exit;
}
ret = GetMountForBSD(context, prebootBSD, prebootMountPoint, sizeof prebootMountPoint);
if (ret) {
blesscontextprintf(context, kBLLogLevelError, "Error looking up mount points\n");
goto exit;
}
if (!prebootMountPoint[0]) {
ret = BLMountContainerVolume(context, prebootBSD, prebootMountPoint, sizeof prebootMountPoint, false);
if (ret) {
goto exit;
}
mustUnmount = true;
}
ret = GetUUIDFolderPathInPreboot(context, prebootMountPoint, rootBSD, prebootFolderPath, sizeof prebootFolderPath);
if (ret) {
blesscontextprintf(context, kBLLogLevelError, "Error looking up UUID folder in preboot\n");
goto exit;
}
strlcpy(prebootDirPath, prebootFolderPath, sizeof prebootDirPath);
strlcpy(prebootKCPath, prebootFolderPath, sizeof prebootKCPath);
strlcat(prebootKCPath, "/boot" kBL_PATH_KERNELCOLLECTIONS, sizeof prebootKCPath);
strlcat(prebootFolderPath, kBL_PATH_CORESERVICES, sizeof prebootFolderPath);
strlcpy(bridgeDstVersionPath, prebootFolderPath, sizeof bridgeDstVersionPath);
pathEnd = prebootFolderPath + strlen(prebootFolderPath);
if (labelData) {
strlcpy(pathEnd, "/.disk_label", prebootFolderPath + sizeof prebootFolderPath - pathEnd);
ret = WriteLabelFile(context, prebootFolderPath, labelData, 0, kBitmapScale_1x);
if (ret) {
blesscontextprintf(context, kBLLogLevelError, "Couldn't write label file \"%s\"", prebootFolderPath);
ret = 6;
goto exit;
}
*pathEnd = '\0';
}
if (labelData2) {
strlcpy(pathEnd, "/.disk_label_2x", prebootFolderPath + sizeof prebootFolderPath - pathEnd);
ret = WriteLabelFile(context, prebootFolderPath, labelData2, 0, kBitmapScale_2x);
if (ret) {
blesscontextprintf(context, kBLLogLevelError, "Couldn't write label file \"%s\"", prebootFolderPath);
ret = 6;
goto exit;
}
*pathEnd = '\0';
}
if (setIDs) {
ret = BLGetAPFSInodeNum(context, prebootFolderPath, &blessIDs[1]);
if (ret) {
blesscontextprintf(context, kBLLogLevelError, "Preboot folder path \"%s\" doesn't exist.\n", prebootFolderPath);
ret = 6;
goto exit;
}
}
strlcat(prebootFolderPath, "/boot.efi", sizeof prebootFolderPath);
if (ret) goto exit;
if (bootEFISourceLocation) {
snprintf(bootEFIloc, sizeof bootEFIloc, "%s", bootEFISourceLocation);
ret = GetMountForBSD(context, rootBSD, systemPath, sizeof systemPath);
if (ret) {
blesscontextprintf(context, kBLLogLevelError, "Error looking up mount points\n");
goto exit;
}
if (!systemPath[0]) {
blesscontextprintf(context, kBLLogLevelError, "Couldn't find volume mounted from /dev/%s\n", rootBSD);
goto exit;
}
ret = BLIsVolumeARV(context, systemPath, rootBSD, &isARV);
if (ret) goto exit;
snprintf(rootHashPath, sizeof rootHashPath, "%s/usr/standalone/OS.dmg.root_hash", prebootDirPath);
if (IOObjectConformsTo(rootMedia, "AppleAPFSSnapshot")) {
contextprintf(context, kBLLogLevelVerbose, "Volume %s is mounted from a snapshot.\n", systemPath);
bsdCF = IORegistryEntryCreateCFProperty(systemMedia, CFSTR(kIOBSDNameKey), kCFAllocatorDefault, 0);
if (!bsdCF) {
ret = ENOENT;
contextprintf(context, kBLLogLevelError, "No BSD name for system volume media\n");
goto exit;
}
CFStringGetCString(bsdCF, systemDev + strlen(_PATH_DEV), sizeof systemDev - strlen(_PATH_DEV), kCFStringEncodingUTF8);
mntsize = getmntinfo(&mnts, MNT_NOWAIT);
if (!mntsize) {
ret = 5;
goto exit;
}
for (i = 0; i < mntsize; i++) {
if (strcmp(mnts[i].f_mntfromname, systemDev) == 0) break;
}
if (i < mntsize) {
strlcpy(systemPath, mnts[i].f_mntonname, sizeof systemPath);
} else {
ret = BLMountContainerVolume(context, systemDev + strlen(_PATH_DEV), systemPath, sizeof systemPath, false);
if (ret) {
contextprintf(context, kBLLogLevelError, "Couldn't mount system volume from %s\n", systemDev);
goto exit;
}
mustUnmountSystem = true;
}
rootBSD = systemDev + strlen(_PATH_DEV);
}
kcPath[0] = '\0';
if (true == createSnapshot) {
snprintf(kcPath, sizeof kcPath, "%s", systemPath);
}
else if (true == sealedSnapshot) {
ret = GetSnapshotNameFromRootHash(context, rootHashPath, snapshotName, sizeof snapshotName);
if (ret) {
blesscontextprintf(context, kBLLogLevelError, "Error looking snapshot name from roothash %s \n", rootHashPath );
goto exit;
}
blesscontextprintf(context, kBLLogLevelVerbose, "Found root snapshot %s\n", snapshotName);
ret = GetMountForSnapshot(context, snapshotName, rootBSD, snapshotRootPath, sizeof snapshotRootPath);
if (ret) {
blesscontextprintf(context, kBLLogLevelError, "Error looking up mount points for snapshot %s\n", snapshotName);
goto exit;
}
if (!snapshotRootPath[0]) {
ret = BLMountSnapshot(context, rootBSD, snapshotName, snapshotRootPath, sizeof snapshotRootPath);
if (ret) goto exit;
unmountSnapshot = true;
}
blesscontextprintf(context, kBLLogLevelVerbose, "snapshot is mounted at %s\n", snapshotRootPath);
snprintf(kcPath, sizeof kcPath, "%s", snapshotRootPath);
untagRootSnapshot = true;
}
else if (isARV == false) {
if (0 == BLAPFSSnapshotAsRoot(context, systemPath, snapshotName, sizeof snapshotName))
{
blesscontextprintf(context, kBLLogLevelVerbose, "Found root snapshot %s\n", snapshotName);
ret = GetMountForSnapshot(context, snapshotName, rootBSD, snapshotRootPath, sizeof snapshotRootPath);
if (ret) {
blesscontextprintf(context, kBLLogLevelError, "Error looking up mount points for snapshot %s\n", snapshotName);
goto exit;
}
if (!snapshotRootPath[0]) {
ret = BLMountSnapshot(context, rootBSD, snapshotName, snapshotRootPath, sizeof snapshotRootPath);
if (ret) goto exit;
unmountSnapshot = true;
}
blesscontextprintf(context, kBLLogLevelVerbose, "snapshot is mounted at %s\n", snapshotRootPath);
snprintf(kcPath, sizeof kcPath, "%s", snapshotRootPath);
}
else
{
snprintf(kcPath, sizeof kcPath, "%s", systemPath);
}
}
if (kcPath[0]) {
if (copyBootEFI == false) {
snprintf(bootEFIloc, sizeof bootEFIloc, "%s%s", kcPath, bootEFISourceLocation);
copyBootEFI = true;
}
strlcat(kcPath, kBL_PATH_KERNELCOLLECTIONS, sizeof kcPath);
ret = CopyKernelCollectionFiles(context, kcPath, prebootKCPath);
if (ret) {
goto exit;
}
}
if (true == copyBootEFI)
{
blesscontextprintf(context, kBLLogLevelVerbose, "booter path %s\n", bootEFIloc);
ret = BLLoadFile(context, bootEFIloc, 0, &booterData);
if (ret) {
blesscontextprintf(context, kBLLogLevelVerbose, "Could not load booter data from %s\n",
bootEFIloc);
}
if (booterData) {
CFDataRef oldEFIdata = NULL;
ret = lstat(prebootFolderPath, &existingStat);
if (ret == 0 && S_ISREG(existingStat.st_mode)) {
ret = BLLoadFile(context, prebootFolderPath, 0, &oldEFIdata);
}
if ((ret == 0) && oldEFIdata && CFEqual(oldEFIdata, booterData)) {
blesscontextprintf(context, kBLLogLevelVerbose, "boot.efi unchanged at %s. Skipping update...\n",
prebootFolderPath);
} else {
ret = BLCreateFileWithOptions(context, booterData, prebootFolderPath, 0, 0, 0, kTryPreallocate);
if (ret) {
blesscontextprintf(context, kBLLogLevelError, "Could not create boot.efi at %s\n", prebootFolderPath);
ret = 7;
goto exit;
} else {
blesscontextprintf(context, kBLLogLevelVerbose, "boot.efi created successfully at %s\n",
prebootFolderPath);
}
}
if (oldEFIdata) CFRelease(oldEFIdata);
ret = CopyManifests(context, prebootFolderPath, bootEFIloc, systemPath);
if (ret) {
blesscontextprintf(context, kBLLogLevelError, "Couldn't copy img4 manifests for file %s\n", bootEFIloc);
}
} else {
blesscontextprintf(context, kBLLogLevelVerbose, "Could not create boot.efi, no X folder specified\n" );
}
ret = statfs(bootEFIloc, &sfs);
if (ret) {
blesscontextprintf(context, kBLLogLevelError, "Could not get filesystem information for file %s\n", bootEFIloc);
ret = errno;
goto exit;
}
strlcpy(bridgeSrcVersionPath, sfs.f_mntonname, sizeof bridgeSrcVersionPath);
strlcat(bridgeDstVersionPath, "/", sizeof bridgeDstVersionPath);
pathEnd = bridgeSrcVersionPath + strlen(bridgeSrcVersionPath);
pathEnd2 = bridgeDstVersionPath + strlen(bridgeDstVersionPath);
strlcpy(pathEnd, kBL_PATH_BRIDGE_VERSION_BIN, bridgeSrcVersionPath + sizeof bridgeSrcVersionPath - pathEnd);
if (access(bridgeSrcVersionPath, R_OK) < 0) {
ret = ENOENT;
} else {
ret = BLLoadFile(context, bridgeSrcVersionPath, 0, &versionData);
}
if (ret) {
blesscontextprintf(context, kBLLogLevelVerbose, "Could not load version data from %s\n",
bridgeSrcVersionPath);
ret = 0;
}
if (versionData) {
strlcpy(pathEnd2, basename(kBL_PATH_BRIDGE_VERSION_BIN), bridgeDstVersionPath + sizeof bridgeDstVersionPath - pathEnd2);
ret = BLCreateFileWithOptions(context, versionData, bridgeDstVersionPath, 0, 0, 0, 0);
CFRelease(versionData);
if (ret) {
blesscontextprintf(context, kBLLogLevelError, "Couldn't create file at %s\n", bridgeDstVersionPath);
goto exit;
}
}
strlcpy(pathEnd, kBL_PATH_BRIDGE_VERSION_PLIST, bridgeSrcVersionPath + sizeof bridgeSrcVersionPath - pathEnd);
if (access(bridgeSrcVersionPath, R_OK) < 0) {
ret = ENOENT;
} else {
ret = BLLoadFile(context, bridgeSrcVersionPath, 0, &versionData);
}
if (ret) {
blesscontextprintf(context, kBLLogLevelVerbose, "Could not load version data from %s\n",
bridgeSrcVersionPath);
ret = 0;
}
if (versionData) {
strlcpy(pathEnd2, basename(kBL_PATH_BRIDGE_VERSION_PLIST), bridgeDstVersionPath + sizeof bridgeDstVersionPath - pathEnd2);
ret = BLCreateFileWithOptions(context, versionData, bridgeDstVersionPath, 0, 0, 0, 0);
CFRelease(versionData);
if (ret) {
blesscontextprintf(context, kBLLogLevelError, "Couldn't create file at %s\n", bridgeDstVersionPath);
goto exit;
}
}
if (true == untagRootSnapshot)
{
if ((vol_fd = open(systemPath, O_RDONLY)) < 0) {
ret = errno;
blesscontextprintf(context, kBLLogLevelError, "Couldn't open volume %s: %s\n", systemPath, strerror(ret));
goto exit;
}
if (fs_snapshot_root(vol_fd, "", 0) < 0) {
ret = errno;
blesscontextprintf(context, kBLLogLevelError, "Coulnd't revert current boot snapshot on volume %s: %s\n", systemPath, strerror(ret));
goto exit;
}
}
}
} else {
ret = lstat(prebootFolderPath, &existingStat);
if (ret) {
blesscontextprintf(context, kBLLogLevelError, "Could not access boot.efi file at %s\n",
prebootFolderPath);
ret = errno;
goto exit;
}
}
if (setIDs) {
ret = BLGetAPFSInodeNum(context, prebootFolderPath, &blessIDs[0]);
if (ret) {
blesscontextprintf(context, kBLLogLevelError, "Preboot booter path \"%s\" doesn't exist.", prebootFolderPath);
ret = 6;
goto exit;
}
ret = BLSetAPFSBlessData(context, prebootMountPoint, blessIDs);
if (ret) {
blesscontextprintf(context, kBLLogLevelError, "Can't set bless data for preboot volume: %s\n", strerror(errno));
return 2;
}
}
exit:
if (rootMedia) IOObjectRelease(rootMedia);
if (systemMedia) IOObjectRelease(systemMedia);
if (bootUUID) CFRelease(bootUUID);
if (booterDict) CFRelease(booterDict);
if (mustUnmount) {
BLUnmountContainerVolume(context, prebootMountPoint);
}
if (unmountSnapshot) {
BLUnmountContainerVolume(context, snapshotRootPath);
}
if (mustUnmountSystem) {
BLUnmountContainerVolume(context, systemPath);
}
if (vol_fd >= 0) close(vol_fd);
return ret;
}
int GetVolumeUUIDs(BLContextPtr context, const char *volBSD, CFStringRef *volUUID, CFStringRef *groupUUID)
{
int ret = 0;
io_service_t mntMedia = IO_OBJECT_NULL;
io_service_t volMedia = IO_OBJECT_NULL;
mntMedia = IOServiceGetMatchingService(kIOMasterPortDefault, IOBSDNameMatching(kIOMasterPortDefault, 0, volBSD));
if (!mntMedia) {
blesscontextprintf(context, kBLLogLevelError, "No media object for device %s\n", volBSD);
ret = EINVAL;
goto exit;
}
if (IOObjectConformsTo(mntMedia, "AppleAPFSSnapshot")) {
ret = BLAPFSSnapshotToVolume(context, mntMedia, &volMedia);
if (ret) {
blesscontextprintf(context, kBLLogLevelError, "Could not resolve snapshot at %s to volume\n", volBSD);
goto exit;
}
} else {
volMedia = mntMedia;
IOObjectRetain(mntMedia);
}
if (volUUID) *volUUID = IORegistryEntryCreateCFProperty(volMedia, CFSTR(kIOMediaUUIDKey), kCFAllocatorDefault, 0);
if (groupUUID) *groupUUID = IORegistryEntryCreateCFProperty(volMedia, CFSTR(kAPFSVolGroupUUIDKey), kCFAllocatorDefault, 0);
exit:
if (mntMedia) IOObjectRelease(mntMedia);
if (volMedia) IOObjectRelease(volMedia);
return ret;
}
int GetSnapshotNameFromRootHash(BLContextPtr context, const char *rootHashPath, char *snapName, int nameLen)
{
struct stat existingStat;
CFDataRef rootHashData = NULL;
char * rootHashString = NULL;
int ret = 0;
CFErrorRef error = NULL;
ret = lstat(rootHashPath, &existingStat);
if (ret) {
blesscontextprintf(context, kBLLogLevelError, "Could not access roothash file at %s\n",
rootHashPath);
ret = errno;
goto exit;
}
ret = BLLoadFile(context, rootHashPath, 0, &rootHashData);
if (ret) {
blesscontextprintf(context, kBLLogLevelError, "Could not load rootHashData data from %s\n",
rootHashPath);
goto exit;
}
rootHashString = copy_arv_apfs_system_snapshot_name(rootHashData, true, &error);
if (rootHashString == NULL) {
blesscontextprintf(context, kBLLogLevelError, "Could not parse root hash from %s\n",
rootHashPath);
ret = (uint32_t)CFErrorGetCode(error);
goto exit;
}
snprintf(snapName, nameLen,"%s",rootHashString);
exit:
if (rootHashData) CFRelease(rootHashData);
if (error) CFRelease(error);
return ret;
}
int GetMountForSnapshot(BLContextPtr context, const char *snapshotName, const char *bsd, char *mountPoint, int mountPointLen)
{
struct statfs *mnts;
int mntsize;
int i;
char mountname[MAXPATHLEN];
struct statfs sfs;
char rootDev[MAXPATHLEN];
io_service_t rootmedia = IO_OBJECT_NULL;
int ret = 0;
mntsize = getmntinfo(&mnts, MNT_NOWAIT);
if (!mntsize) {
return errno;
}
snprintf(mountname, sizeof mountname, "%s@/dev/%s", snapshotName, bsd);
blesscontextprintf(context, kBLLogLevelVerbose, "is mounted %s\n", mountname);
for (i = 0; i < mntsize; i++) {
if (strcmp(mnts[i].f_mntfromname, mountname) == 0) break;
}
if (i < mntsize) {
strlcpy(mountPoint, mnts[i].f_mntonname, mountPointLen);
} else {
if (statfs("/", &sfs) < 0) {
ret = errno;
}
strlcpy(rootDev, sfs.f_mntfromname + strlen(_PATH_DEV), sizeof rootDev);
rootmedia = IOServiceGetMatchingService(kIOMasterPortDefault, IOBSDNameMatching(kIOMasterPortDefault, 0, rootDev));
if (!rootmedia) {
blesscontextprintf(context, kBLLogLevelError, "No media object for device %s\n", rootDev);
ret = ENOENT;
goto exit;
}
if (IOObjectConformsTo(rootmedia, "AppleAPFSSnapshot")) {
uuid_string_t snapUUIDString = "";
CFStringRef snapUUID = IORegistryEntryCreateCFProperty(rootmedia, CFSTR(kIOMediaUUIDKey), NULL, 0);
if (!snapUUID) {
blesscontextprintf(context, kBLLogLevelError, "No valid volume UUID for device %s", rootDev );
ret = ENOENT;
goto exit;
}
CFStringGetCString(snapUUID, snapUUIDString, sizeof snapUUIDString, kCFStringEncodingUTF8);
CFRelease(snapUUID);
apfs_snap_name_lookup_t lookup = { .type = SNAP_LOOKUP_BY_UUID };
if (uuid_parse(snapUUIDString, lookup.snap_uuid) != 0) {
blesscontextprintf(context, kBLLogLevelError, "could not parse %s: %s", kIOMediaUUIDKey, snapUUIDString);
ret = EINVAL;
goto exit;
} else if (fsctl(sfs.f_mntonname, APFSIOC_SNAP_LOOKUP, &lookup, 0) != 0) {
blesscontextprintf(context, kBLLogLevelError, "could not look up snapshot by UUID: %d (%s)", errno, strerror(errno));
ret = EINVAL;
goto exit;
} else {
if (strcmp(lookup.snap_name, snapshotName) == 0) {
strlcpy(mountPoint, "/", mountPointLen);
}
else
{
*mountPoint = '\0';
}
}
}
else {
*mountPoint ='\0';
}
}
exit:
if (rootmedia) IOObjectRelease(rootmedia);
return ret;
}
int WriteLabelFile(BLContextPtr context, const char *path, CFDataRef labeldata, int doTypeCreator, int scale)
{
int ret;
blesscontextprintf(context, kBLLogLevelVerbose, "Putting scale %d label bitmap in %s\n", scale, path);
ret = BLCreateFile(context, labeldata, path,
0,
doTypeCreator ? kBL_OSTYPE_PPC_TYPE_OFLABEL : 0,
doTypeCreator ? kBL_OSTYPE_PPC_CREATOR_CHRP : 0);
if (ret) {
blesscontextprintf(context, kBLLogLevelError, "Could not write scale %d bitmap label file\n", scale);
} else {
blesscontextprintf(context, kBLLogLevelVerbose, "Scale %d label written\n", scale);
}
if (!ret) {
ret = BLSetFinderFlag(context, path, kIsInvisible, 1);
if (ret) {
blesscontextprintf(context, kBLLogLevelError, "Could not set invisibility for %s\n", path);
} else {
blesscontextprintf(context, kBLLogLevelVerbose, "Invisibility set for %s\n", path);
}
}
return ret;
}
int DeleteFileOrDirectory(const char *path)
{
int ret = 0;
struct stat sb;
char modPath[MAXPATHLEN];
if (stat(path, &sb) < 0) {
ret = errno;
goto exit;
}
if (S_ISDIR(sb.st_mode)) {
strlcpy(modPath, path, sizeof modPath);
ret = DeleteHierarchy(modPath, sizeof modPath);
} else {
ret = DeleteFileWithPrejudice(path, &sb);
}
exit:
return ret;
}
static int DeleteHierarchy(char *path, int pathMax)
{
int ret;
DIR *dp = NULL;
struct dirent *dirent;
struct stat sb;
int endIdx;
dp = opendir(path);
if (!dp) {
ret = errno;
goto exit;
}
readdir(dp);
readdir(dp);
if (path[strlen(path)-1] != '/') strlcat(path, "/", pathMax);
endIdx = strlen(path);
ret = 0;
while ((dirent = readdir(dp)) != NULL) {
if (endIdx + dirent->d_namlen >= pathMax) continue;
strlcpy(path + endIdx, dirent->d_name, pathMax - endIdx);
if (stat(path, &sb) < 0) {
ret = errno;
break;
}
if (S_ISDIR(sb.st_mode)) {
ret = DeleteHierarchy(path, pathMax);
} else {
ret = DeleteFileWithPrejudice(path, &sb);
}
if (ret) break;
}
if (ret) goto exit;
path[endIdx] = '\0';
rmdir(path);
exit:
if (dp) closedir(dp);
return ret;
}
static int DeleteFileWithPrejudice(const char *path, struct stat *sb)
{
if ((sb->st_flags & UF_IMMUTABLE) != 0) {
chflags(path, sb->st_flags & ~UF_IMMUTABLE);
}
return (unlink(path) < 0) ? errno : 0;
}
static int CopyKernelCollectionFiles(BLContextPtr context, const char *systemKCPath, const char *prebootKCPath)
{
int ret;
char **systemList = NULL;
int numSysFiles;
char **prebootList = NULL;
int numPrebootFiles;
int i;
char systemFullPath[MAXPATHLEN];
char *systemPathEnd;
char prebootFullPath[MAXPATHLEN];
char *prebootPathEnd;
ret = GetFilesInDirWithPrefix(systemKCPath, kBL_NAME_BOOTKERNELEXTENSIONS, &systemList, &numSysFiles);
if (ret && ret != ENOENT) {
blesscontextprintf(context, kBLLogLevelError, "Couldn't get system kernel collection files - %s\n", strerror(ret));
goto exit;
}
if (ret) {
blesscontextprintf(context, kBLLogLevelVerbose, "No KernelCollections directory in system volume\n");
systemList = malloc(sizeof(char *));
numSysFiles = 0;
ret = 0;
}
ret = GetFilesInDirWithPrefix(prebootKCPath, kBL_NAME_BOOTKERNELEXTENSIONS, &prebootList, &numPrebootFiles);
if (ret && ret != ENOENT) {
blesscontextprintf(context, kBLLogLevelError, "Couldn't get preboot kernel collection files - %s\n", strerror(ret));
goto exit;
}
if (ret) {
blesscontextprintf(context, kBLLogLevelVerbose, "No KernelCollections directory in preboot volume\n");
prebootList = malloc(sizeof(char *));
numPrebootFiles = 0;
if (numSysFiles > 0) {
if (mkdir(prebootKCPath, 0755) < 0) {
ret = errno;
blesscontextprintf(context, kBLLogLevelError, "Couldn't create kernel collections directory in preboot - %s", strerror(ret));
goto exit;
}
}
ret = 0;
} else {
for (i = 0; i < numPrebootFiles; i++) {
if (StringHasSuffix(prebootList[i], ".im4m")) {
free(prebootList[i]);
memmove(prebootList + i, prebootList + i + 1, (numPrebootFiles - i - 1) * sizeof *prebootList);
numPrebootFiles--;
i--;
}
}
}
strlcpy(prebootFullPath, prebootKCPath, sizeof prebootFullPath);
strlcat(prebootFullPath, "/", sizeof prebootFullPath);
prebootPathEnd = prebootFullPath + strlen(prebootFullPath);
for (i = 0; i < numPrebootFiles; i++) {
strlcpy(prebootPathEnd, prebootList[i], prebootFullPath + sizeof prebootFullPath - prebootPathEnd);
if (unlink(prebootFullPath) < 0) {
ret = errno;
blesscontextprintf(context, kBLLogLevelError, "Couldn't delete file %s - %s\n", prebootFullPath, strerror(ret));
goto exit;
}
blesscontextprintf(context, kBLLogLevelVerbose, "Deleted KC file %s\n", prebootFullPath);
}
strlcpy(systemFullPath, systemKCPath, sizeof systemFullPath);
strlcat(systemFullPath, "/", sizeof systemFullPath);
systemPathEnd = systemFullPath + strlen(systemFullPath);
for (i = 0; i < numSysFiles; i++) {
strlcpy(systemPathEnd, systemList[i], systemFullPath + sizeof systemFullPath - systemPathEnd);
strlcpy(prebootPathEnd, systemList[i], prebootFullPath + sizeof prebootFullPath - prebootPathEnd);
ret = CopyKCFile(context, systemFullPath, prebootFullPath);
if (ret) {
blesscontextprintf(context, kBLLogLevelError, "Error %d copying KC file %s\n", ret, systemFullPath);
goto exit;
}
blesscontextprintf(context, kBLLogLevelVerbose, "Copied KC file %s to preboot\n", systemFullPath);
}
exit:
if (systemList) FreeFileList(systemList, numSysFiles);
if (prebootList) FreeFileList(prebootList, numPrebootFiles);
return ret;
}
static int GetFilesInDirWithPrefix(const char *directory, const char *prefix, char ***outFileList, int *outNumFiles)
{
int ret = 0;
DIR *dp = NULL;
struct dirent *dirent;
struct stat sb;
char path[MAXPATHLEN];
int endIdx;
int prefixLen = strlen(prefix);
char **list = NULL;
int numFiles = 0;
int listSize = 10;
list = malloc(listSize * sizeof(char *));
if (!list) {
ret = errno;
goto exit;
}
dp = opendir(directory);
if (!dp) {
ret = errno;
goto exit;
}
readdir(dp); readdir(dp);
strlcpy(path, directory, sizeof path);
if (path[strlen(path)-1] != '/') strlcat(path, "/", sizeof path);
endIdx = strlen(path);
while ((dirent = readdir(dp)) != NULL) {
if (strncmp(dirent->d_name, prefix, prefixLen) != 0) continue;
if (endIdx + dirent->d_namlen >= sizeof path) continue;
strlcpy(path + endIdx, dirent->d_name, sizeof path - endIdx);
if (stat(path, &sb) < 0) {
ret = errno;
goto exit;
}
if (!S_ISREG(sb.st_mode)) continue;
if (numFiles == listSize) {
listSize *= 2;
list = realloc(list, listSize * sizeof(char *));
if (!list) {
ret = errno;
goto exit;
}
}
list[numFiles++] = strdup(dirent->d_name);
}
qsort(list, numFiles, sizeof list[0], StringCompare);
*outFileList = list;
*outNumFiles = numFiles;
exit:
if (dp) closedir(dp);
if (ret && list) {
FreeFileList(list, numFiles);
}
return ret;
}
static void FreeFileList(char **list, int num)
{
int i;
for (i = 0; i < num; i++) {
free(list[i]);
}
free(list);
}
static int StringCompare(const void *a, const void *b)
{
const char * const *s1 = a;
const char * const *s2 = b;
return strcmp(*s1, *s2);
}
static int CopyKCFile(BLContextPtr context, const char *from, const char *to)
{
int ret = 0;
char *buffer = NULL;
int fdFrom = -1;
int fdTo = -1;
off_t fileSize;
int bytes;
fstore_t preall;
buffer = malloc(0x100000); if (!buffer) {
ret = errno;
blesscontextprintf(context, kBLLogLevelError, "%s\n", strerror(ret));
goto exit;
}
fdFrom = open(from, O_RDONLY);
if (fdFrom < 0) {
ret = errno;
blesscontextprintf(context, kBLLogLevelError, "Couldn't open KC file %s: %s\n", from, strerror(ret));
goto exit;
}
fileSize = lseek(fdFrom, 0, SEEK_END);
if (fileSize < 0) {
ret = errno;
blesscontextprintf(context, kBLLogLevelError, "Couldn't get size of file %s: %s\n", from, strerror(ret));
goto exit;
}
fdTo = open(to, O_WRONLY|O_CREAT|O_TRUNC, 0644);
if (fdTo < 0) {
ret = errno;
blesscontextprintf(context, kBLLogLevelError, "Couldn't create KC file %s: %s\n", to, strerror(ret));
goto exit;
}
preall.fst_length = fileSize;
preall.fst_offset = 0;
preall.fst_flags = F_ALLOCATECONTIG;
preall.fst_posmode = F_PEOFPOSMODE;
if (fcntl(fdTo, F_PREALLOCATE, &preall) < 0) {
if (errno == ENOTSUP) {
blesscontextprintf(context, kBLLogLevelVerbose, "Preallocation not supported on this filesystem for %s\n", to);
} else {
ret = errno;
blesscontextprintf(context, kBLLogLevelError, "Preallocation of %s failed: %s\n", to, strerror(ret));
goto exit;
}
} else {
blesscontextprintf(context, kBLLogLevelVerbose, "0x%08X bytes preallocated for %s\n",
(unsigned int)preall.fst_bytesalloc, to);
}
lseek(fdFrom, 0, SEEK_SET);
while (fileSize > 0) {
bytes = MIN(fileSize, 0x100000);
if ((bytes = read(fdFrom, buffer, bytes)) < 0) {
ret = errno;
blesscontextprintf(context, kBLLogLevelError, "Error reading from %s: %s\n", from, strerror(ret));
goto exit;
}
if (write(fdTo, buffer, bytes) < 0) {
ret = errno;
blesscontextprintf(context, kBLLogLevelError, "Error writing to %s: %s\n", to, strerror(ret));
goto exit;
}
fileSize -= bytes;
}
exit:
if (buffer) free(buffer);
if (fdFrom >= 0) close(fdFrom);
if (fdTo >= 0) close(fdTo);
return ret;
}
static bool StringHasSuffix(const char *str, const char *suffix)
{
const char *cmp;
int strLength;
int suffixLength;
strLength = strlen(str);
suffixLength = strlen(suffix);
if (strLength < suffixLength) return false;
cmp = str + strLength - suffixLength;
return strcmp(cmp, suffix) == 0;
}