#include <sys/param.h>
#include <sys/mount.h>
#include <sys/attr.h>
#include <sys/stat.h>
#include <paths.h>
#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/IOBSD.h>
#include <IOKit/storage/IOMedia.h>
#include <APFS/APFS.h>
#include <apfs/apfs_fsctl.h>
#include "bless.h"
#include "bless_private.h"
#include "protos.h"
#ifndef FSOPT_LIST_SNAPSHOT
#define FSOPT_LIST_SNAPSHOT 0x00000040
#endif/* FSOPT_LIST_SNAPSHOT */
static uint16_t RoleNameToValue(CFStringRef roleName);
static int GetSpecialRoledVolumeBSDForContainerDev(BLContextPtr context, const char *containerDev, int specialRole, char *roledVolumeBSD, int roledVolumeLen)
{
int ret = 0;
char containerBSD[MAXPATHLEN];
io_service_t conIOMedia = IO_OBJECT_NULL;
io_service_t volIOMedia = IO_OBJECT_NULL;
io_iterator_t volIter = IO_OBJECT_NULL;
CFArrayRef volRoles = NULL;
CFStringRef roleName = NULL;
CFStringRef volBSDName = NULL;
bool roleMatch = false;
bool found = false;
kern_return_t kret = KERN_SUCCESS;
if (roledVolumeLen < 1) {
contextprintf(context, kBLLogLevelError, "Buffer error\n");
ret = 1;
goto exit;
}
*roledVolumeBSD = '\0';
if (strnlen (containerDev, MAXPATHLEN-1) < 10 ) {
contextprintf(context, kBLLogLevelError, "Container dev format error\n");
ret = 1;
goto exit;
}
sscanf (containerDev, "/dev/%s", containerBSD);
conIOMedia = IOServiceGetMatchingService(kIOMasterPortDefault, IOBSDNameMatching(kIOMasterPortDefault, 0, containerBSD));
if (conIOMedia == IO_OBJECT_NULL) {
contextprintf(context, kBLLogLevelError, "Could not get IOService for %s\n", containerDev);
ret = 2;
goto exit;
}
if (!IOObjectConformsTo(conIOMedia, APFS_MEDIA_OBJECT)) {
contextprintf(context, kBLLogLevelError, "%s is not an APFS container\n", containerDev);
ret = 2;
goto exit;
}
kret = IORegistryEntryCreateIterator (conIOMedia, kIOServicePlane, kIORegistryIterateRecursively, &volIter);
if (kret) {
contextprintf(context, kBLLogLevelError, "Could not get iterator for volumes\n");
ret = 2;
goto exit;
}
while (IO_OBJECT_NULL != (volIOMedia = IOIteratorNext(volIter)))
{
if (IOObjectConformsTo(volIOMedia, APFS_VOLUME_OBJECT))
{
volRoles = IORegistryEntryCreateCFProperty(volIOMedia, CFSTR(kAPFSRoleKey), kCFAllocatorDefault, 0);
if ((volRoles != NULL) && (CFArrayGetCount(volRoles) == 1)) {
roleName = CFArrayGetValueAtIndex(volRoles, 0);
roleMatch = false;
switch (specialRole) {
case APFS_VOL_ROLE_PREBOOT:
if (CFStringCompare(roleName, CFSTR(kAPFSVolumeRolePreBoot), 0) == kCFCompareEqualTo) { roleMatch = true; } break;
case APFS_VOL_ROLE_RECOVERY:
if (CFStringCompare(roleName, CFSTR(kAPFSVolumeRoleRecovery), 0) == kCFCompareEqualTo) { roleMatch = true; } break;
default: break;
}
}
if (volRoles) { CFRelease(volRoles); volRoles = NULL; }
if (roleMatch) {
volBSDName = IORegistryEntryCreateCFProperty(volIOMedia, CFSTR(kIOBSDNameKey), kCFAllocatorDefault, 0);
if (!volBSDName) {
contextprintf(context, kBLLogLevelError, "No BSD name\n");
ret = 2;
goto exit;
}
CFStringGetCString(volBSDName, roledVolumeBSD, roledVolumeLen, kCFStringEncodingUTF8);
CFRelease(volBSDName);
volBSDName = NULL;
found = true;
contextprintf(context, kBLLogLevelVerbose, "Found roled volume\n");
break;
}
}
IOObjectRelease(volIOMedia);
volIOMedia = IO_OBJECT_NULL;
}
exit:;
if (volRoles) CFRelease(volRoles);
if (volBSDName) CFRelease(volBSDName);
if (volIOMedia) IOObjectRelease(volIOMedia);
if (volIter) IOObjectRelease(volIter);
if (conIOMedia) IOObjectRelease(conIOMedia);
contextprintf(context, kBLLogLevelVerbose, "Container %s does%s have a volume%s%s with role 0x%04X\n",
containerDev,
found ? "" : " not",
found ? " " : "",
found ? roledVolumeBSD : "",
specialRole);
return ret;
}
int BLAPFSCreatePhysicalStoreBSDsFromVolumeBSD(BLContextPtr context, const char *volBSD, CFArrayRef *physBSDs)
{
io_service_t volDev;
kern_return_t kret = KERN_SUCCESS;
io_service_t parent;
io_service_t p = IO_OBJECT_NULL;
io_iterator_t psIter;
CFStringRef bsd;
CFMutableArrayRef devs;
volDev = IOServiceGetMatchingService(kIOMasterPortDefault, IOBSDNameMatching(kIOMasterPortDefault, 0, volBSD));
if (volDev == IO_OBJECT_NULL) {
contextprintf(context, kBLLogLevelError, "Could not get IOService for %s\n", volBSD);
return 2;
}
if (!IOObjectConformsTo(volDev, APFS_VOLUME_OBJECT)) {
contextprintf(context, kBLLogLevelError, "%s is not an APFS volume\n", volBSD);
IOObjectRelease(volDev);
return 2;
}
kret = IORegistryEntryGetParentEntry(volDev, kIOServicePlane, &parent);
if (kret) goto badHierarchy;
p = parent;
kret = IORegistryEntryGetParentEntry(p, kIOServicePlane, &parent);
if (kret) goto badHierarchy;
IOObjectRelease(p);
p = parent;
kret = IORegistryEntryGetParentEntry(p, kIOServicePlane, &parent);
if (kret) goto badHierarchy;
IOObjectRelease(p);
p = parent;
devs = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
IORegistryEntryGetParentIterator(p, kIOServicePlane, &psIter);
while ((parent = IOIteratorNext(psIter))) {
if (!IOObjectConformsTo(parent, kIOMediaClass)) {
contextprintf(context, kBLLogLevelError, "IORegistry hierarchy for %s has unexpected type\n", volBSD);
IOObjectRelease(parent);
IOObjectRelease(psIter);
CFRelease(devs);
return 2;
}
bsd = IORegistryEntryCreateCFProperty(parent, CFSTR(kIOBSDNameKey), kCFAllocatorDefault, 0);
CFArrayAppendValue(devs, bsd);
CFRelease(bsd);
IOObjectRelease(parent);
}
IOObjectRelease(psIter);
IOObjectRelease(p);
p = IO_OBJECT_NULL;
*physBSDs = devs;
badHierarchy:
if (kret) {
contextprintf(context, kBLLogLevelError, "Couldn't get physical store IOMedia for %s\n", volBSD);
}
if (p != IO_OBJECT_NULL) IOObjectRelease(p);
IOObjectRelease(volDev);
return kret ? 3 : 0;
}
int BLMountContainerVolume(BLContextPtr context, const char *bsdName, char *mntPoint, int mntPtStrSize, bool readOnly)
{
int ret;
char vartmpLoc[MAXPATHLEN];
char fulldevpath[MNAMELEN];
char *newargv[13];
char *installEnv;
int i;
installEnv = getenv("__OSINSTALL_ENVIRONMENT");
if (installEnv && (atoi(installEnv) > 0 || strcasecmp(installEnv, "yes") == 0 || strcasecmp(installEnv, "true") == 0)) {
strlcpy(vartmpLoc, "/var/tmp/RecoveryTemp", sizeof vartmpLoc);
} else {
if (!confstr(_CS_DARWIN_USER_TEMP_DIR, vartmpLoc, sizeof vartmpLoc)) {
strlcpy(vartmpLoc, "/var/tmp/", sizeof vartmpLoc);
}
}
if (access(vartmpLoc, W_OK) < 0) {
contextprintf(context, kBLLogLevelError, "Temporary directory \"%s\" is not writable.\n", vartmpLoc);
}
realpath(vartmpLoc, fulldevpath);
snprintf(mntPoint, mntPtStrSize, "%s/bless.XXXX", fulldevpath);
if (!mkdtemp(mntPoint)) {
contextprintf(context, kBLLogLevelError, "Can't create mountpoint %s\n", mntPoint);
}
contextprintf(context, kBLLogLevelVerbose, "Mounting at %s\n", mntPoint);
snprintf(fulldevpath, sizeof(fulldevpath), "/dev/%s", bsdName);
newargv[0] = "/sbin/mount";
newargv[1] = "-t";
newargv[2] = "apfs";
newargv[3] = "-o";
newargv[4] = "perm";
newargv[5] = "-o";
newargv[6] = "owners";
newargv[7] = "-o";
newargv[8] = "nobrowse";
i = 9;
if (readOnly) newargv[i++] = "-r";
newargv[i++] = fulldevpath;
newargv[i++] = mntPoint;
newargv[i] = NULL;
contextprintf(context, kBLLogLevelVerbose, "Executing \"%s\"\n", "/sbin/mount");
pid_t p = fork();
if (p == 0) {
setuid(geteuid());
ret = execv("/sbin/mount", newargv);
if (ret == -1) {
contextprintf(context, kBLLogLevelError, "Could not exec %s\n", "/sbin/mount");
}
_exit(1);
}
do {
p = wait(&ret);
} while (p == -1 && errno == EINTR);
contextprintf(context, kBLLogLevelVerbose, "Returned %d\n", ret);
if (p == -1 || ret) {
contextprintf(context, kBLLogLevelError, "%s returned non-0 exit status\n", "/sbin/mount");
rmdir(mntPoint);
return 3;
}
return 0;
}
int BLUnmountContainerVolume(BLContextPtr context, char *mntPoint)
{
int err;
char *newargv[3];
struct statfs sfs;
int fd;
if (statfs(mntPoint, &sfs) < 0) {
return errno;
}
newargv[0] = "/sbin/umount";
newargv[1] = sfs.f_mntfromname;
newargv[2] = NULL;
contextprintf(context, kBLLogLevelVerbose, "Executing \"%s\"\n", "/sbin/umount");
pid_t p = fork();
if (p == 0) {
fd = open("/dev/null", O_RDWR);
dup2(fd, STDOUT_FILENO);
close(fd);
setuid(geteuid());
err = execv("/sbin/umount", newargv);
if (err == -1) {
contextprintf(context, kBLLogLevelError, "Could not exec %s\n", "/sbin/umount");
}
_exit(1);
}
do {
p = wait(&err);
} while (p == -1 && errno == EINTR);
contextprintf(context, kBLLogLevelVerbose, "Returned %d\n", err);
if(p == -1 || err) {
contextprintf(context, kBLLogLevelError, "%s returned non-0 exit status\n", "/sbin/umount");
return 3;
}
rmdir(sfs.f_mntonname);
return 0;
}
int BLMountSnapshot(BLContextPtr context, const char *bsdName, const char *snapName, char *mntPoint, int mntPtStrSize)
{
int ret;
char vartmpLoc[MAXPATHLEN];
char fulldevpath[MNAMELEN];
char snapNameLoc[MNAMELEN];
char *newargv[14];
char *installEnv;
installEnv = getenv("__OSINSTALL_ENVIRONMENT");
if (installEnv && (atoi(installEnv) > 0 || strcasecmp(installEnv, "yes") == 0 || strcasecmp(installEnv, "true") == 0)) {
strlcpy(vartmpLoc, "/var/tmp/RecoveryTemp", sizeof vartmpLoc);
} else {
if (!confstr(_CS_DARWIN_USER_TEMP_DIR, vartmpLoc, sizeof vartmpLoc)) {
strlcpy(vartmpLoc, "/var/tmp/", sizeof vartmpLoc);
}
}
if (access(vartmpLoc, W_OK) < 0) {
contextprintf(context, kBLLogLevelError, "Temporary directory \"%s\" is not writable.\n", vartmpLoc);
}
realpath(vartmpLoc, fulldevpath);
snprintf(mntPoint, mntPtStrSize, "%s/snapshot.XXXX", fulldevpath);
if (!mkdtemp(mntPoint)) {
contextprintf(context, kBLLogLevelError, "Can't create mountpoint %s\n", mntPoint);
}
contextprintf(context, kBLLogLevelVerbose, "Mounting at %s\n", mntPoint);
snprintf(fulldevpath, sizeof(fulldevpath), "/dev/%s", bsdName);
snprintf(snapNameLoc, sizeof(snapNameLoc), "%s", snapName);
newargv[0] = "/sbin/mount_apfs";
newargv[1] = "-s";
newargv[2] = snapNameLoc;
newargv[3] = "-o";
newargv[4] = "perm";
newargv[5] = "-o";
newargv[6] = "owners";
newargv[7] = "-o";
newargv[8] = "nobrowse";
newargv[9] = "-o";
newargv[10] = "rdonly";
newargv[11] = fulldevpath;
newargv[12] = mntPoint;
newargv[13] = NULL;
contextprintf(context, kBLLogLevelVerbose, "Executing \"%s\"\n", "/sbin/mount_apfs");
pid_t p = fork();
if (p == 0) {
setuid(geteuid());
ret = execv("/sbin/mount_apfs", newargv);
if (ret == -1) {
contextprintf(context, kBLLogLevelError, "Could not exec %s\n", "/sbin/mount_apfs");
}
_exit(1);
}
do {
p = wait(&ret);
} while (p == -1 && errno == EINTR);
contextprintf(context, kBLLogLevelVerbose, "Returned %d\n", ret);
if (p == -1 || ret) {
contextprintf(context, kBLLogLevelError, "%s returned non-0 exit status\n", "/sbin/mount_apfs");
rmdir(mntPoint);
return 3;
}
return 0;
}
int BLEnsureSpecialAPFSVolumeUUIDPath(BLContextPtr context, const char *volumeDev, int specialRole, bool useGroupUUID, char *subjectPath, int subjectLen, bool *didMount)
{
int ret;
char specialDevPath[64];
char containerDev[MAXPATHLEN];
struct statfs *mnts;
int mntsize;
int i;
io_service_t service = IO_OBJECT_NULL;
CFStringRef uuidKey = NULL;
CFStringRef uuidVal = NULL;
int len;
strlcpy(specialDevPath, _PATH_DEV, sizeof specialDevPath);
i = -1;
if ((sscanf(volumeDev, "/dev/disk%d", &i) != 1) || (i == -1)) {
ret = 6;
goto exit;
}
snprintf(containerDev, sizeof containerDev, "/dev/disk%d", i);
contextprintf(context, kBLLogLevelVerbose, "Getting path to %s UUID folder on volume with role 0x%04X in container %s\n", useGroupUUID ? "volume group" : "target", specialRole, containerDev);
ret = GetSpecialRoledVolumeBSDForContainerDev(context,
containerDev,
specialRole,
specialDevPath + strlen(_PATH_DEV),
sizeof specialDevPath - strlen(_PATH_DEV));
if (ret) goto exit;
mntsize = getmntinfo(&mnts, MNT_NOWAIT);
if (!mntsize) {
ret = 5;
goto exit;
}
for (i = 0; i < mntsize; i++) {
if (strcmp(mnts[i].f_mntfromname, specialDevPath) == 0) break;
}
if (i < mntsize) {
strlcpy(subjectPath, mnts[i].f_mntonname, subjectLen);
*didMount = false;
} else {
ret = BLMountContainerVolume(context, specialDevPath + strlen(_PATH_DEV), subjectPath, subjectLen, false);
if (ret) goto exit;
*didMount = true;
}
ret = BLGetIOServiceForDeviceName(context, volumeDev + strlen(_PATH_DEV), &service);
if (ret) goto exit;
uuidKey = useGroupUUID ? CFSTR(kAPFSVolGroupUUIDKey) : CFSTR(kIOMediaUUIDKey);
uuidVal = IORegistryEntryCreateCFProperty(service, uuidKey, kCFAllocatorDefault, 0);
if (!uuidVal) {
ret = EINVAL;
goto exit;
}
len = strlen(subjectPath);
if (subjectLen <= len + 1) {
ret = EINVAL;
goto exit;
}
subjectPath[len] = '/';
CFStringGetCString(uuidVal, subjectPath + len + 1, subjectLen - len - 1, kCFStringEncodingUTF8);
contextprintf(context, kBLLogLevelVerbose, "Got subject path %s\n", subjectPath);
exit:
if (service) IOObjectRelease(service);
if (uuidKey) CFRelease(uuidKey);
return ret;
}
int BLRoleForAPFSVolumeDev(BLContextPtr context, const char *volumeDev, uint16_t *role)
{
int ret = 0;
io_service_t volIOMedia = IO_OBJECT_NULL;
CFArrayRef volRoles = NULL;
CFStringRef roleName = NULL;
if (strnlen(volumeDev, MAXPATHLEN-1) < 10 ) {
contextprintf(context, kBLLogLevelError, "Volume dev format error\n");
return 1;
}
volIOMedia = IOServiceGetMatchingService(kIOMasterPortDefault,
IOBSDNameMatching(kIOMasterPortDefault, 0, volumeDev + 5));
if (volIOMedia == IO_OBJECT_NULL) {
contextprintf(context, kBLLogLevelError, "Could not get IOService for %s\n", volumeDev);
return 2;
}
if (IOObjectConformsTo(volIOMedia, "AppleAPFSSnapshot")) {
io_registry_entry_t volMedia;
contextprintf(context, kBLLogLevelVerbose, "%s is a snapshot volume\n", volumeDev);
ret = BLAPFSSnapshotToVolume(context, volIOMedia, &volMedia);
if (ret) {
contextprintf(context, kBLLogLevelError, "Could not resolve snapshot at %s to a volume\n", volumeDev);
IOObjectRelease(volIOMedia);
return ret;
}
IOObjectRelease(volIOMedia);
volIOMedia = volMedia;
}
if (!IOObjectConformsTo(volIOMedia, APFS_VOLUME_OBJECT)) {
contextprintf(context, kBLLogLevelError, "%s is not an APFS volume\n", volumeDev);
IOObjectRelease(volIOMedia);
return 2;
}
volRoles = IORegistryEntryCreateCFProperty(volIOMedia, CFSTR(kAPFSRoleKey), kCFAllocatorDefault, 0);
if (volRoles && CFArrayGetCount(volRoles) > 1) {
contextprintf(context, kBLLogLevelError, "Volume at %s has more than one role\n", volumeDev);
IOObjectRelease(volIOMedia);
return 3;
}
if (!volRoles || CFArrayGetCount(volRoles) == 0) {
*role = APFS_VOL_ROLE_NONE;
} else {
roleName = CFArrayGetValueAtIndex(volRoles, 0);
*role = RoleNameToValue(roleName);
}
if (volRoles) CFRelease(volRoles);
if (volIOMedia) IOObjectRelease(volIOMedia);
return 0;
}
int BLIsDataRoleForAPFSVolumeDev(BLContextPtr context, const char *volumeDev, bool *isDataRole)
{
int ret;
uint16_t role;
ret = BLRoleForAPFSVolumeDev(context, volumeDev, &role);
if (!ret) {
*isDataRole = role == APFS_VOL_ROLE_DATA;
}
return ret;
}
int BLIsVolumeARV(BLContextPtr context, const char *mountPoint, const char *volBSD, bool *arv)
{
int ret;
bool isARV = false;
char rootHashPath[MAXPATHLEN];
struct stat existingStat;
char prebootMountPoint[MAXPATHLEN];
char prebootBSD[MAXPATHLEN];
bool mustUnmount = false;
char prebootFolderPath[MAXPATHLEN];
char volDev[64];
uint16_t role;
struct attrlist attr_list = {
.bitmapcount = ATTR_BIT_MAP_COUNT,
.volattr = ATTR_VOL_INFO | ATTR_VOL_CAPABILITIES,
};
struct {
u_int32_t va_length;
vol_capabilities_attr_t va_capabilities;
} __attribute__((aligned(4), packed)) attr_buf;
snprintf(volDev, sizeof volDev, _PATH_DEV "%s", volBSD);
ret = BLRoleForAPFSVolumeDev(context, volDev, &role);
if (ret) {
contextprintf(context, kBLLogLevelError, "Couldn't get role for volume at %s\n", volDev);
goto exit;
}
if (role != APFS_VOL_ROLE_SYSTEM) {
contextprintf(context, kBLLogLevelVerbose, "Volume does not have system role\n");
goto exit;
}
ret = getattrlist(mountPoint, &attr_list, &attr_buf, sizeof(attr_buf), 0);
if (ret) {
contextprintf(context, kBLLogLevelError, "Couldn't get volume attributes for %s: %s\n", mountPoint, strerror(errno));
goto exit;
}
if ((attr_buf.va_capabilities.capabilities[VOL_CAPABILITIES_FORMAT] & VOL_CAP_FMT_SEALED)) {
contextprintf(context, kBLLogLevelVerbose, "Volume %s is sealable\n", mountPoint);
isARV = true;
}
if (isARV == false) {
ret = GetPrebootBSDForVolumeBSD(context, volBSD, prebootBSD, sizeof prebootBSD);
if (ret) {
contextprintf(context, kBLLogLevelVerbose, "Could not find preboot BSD for : %s\n", volBSD);
goto exit;
}
ret = GetMountForBSD(context, prebootBSD, prebootMountPoint, sizeof prebootMountPoint);
if (ret) {
contextprintf(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, volBSD, prebootFolderPath, sizeof prebootFolderPath);
if (ret) {
contextprintf(context, kBLLogLevelError, "Error looking up UUID folder in preboot\n");
goto exit;
}
snprintf(rootHashPath, sizeof rootHashPath, "%s/usr/standalone/OS.dmg.root_hash", prebootFolderPath);
if (0 == stat(rootHashPath, &existingStat)) {
isARV = true;
contextprintf(context, kBLLogLevelVerbose, "%s exists\n", rootHashPath);
}
}
exit:
*arv = isARV;
if (mustUnmount) {
BLUnmountContainerVolume(context, prebootMountPoint);
}
return ret;
}
int BLAPFSSnapshotToVolume(BLContextPtr context, io_service_t snapshotDev, io_service_t *volumeDev)
{
io_registry_entry_t device;
io_registry_entry_t parent;
bool foundVolume;
CFStringRef bsdCF;
char bsd[64] = "";
if (!volumeDev) return EINVAL;
*volumeDev = IO_OBJECT_NULL;
bsdCF = IORegistryEntryCreateCFProperty(snapshotDev, CFSTR(kIOBSDNameKey), kCFAllocatorDefault, 0);
if (bsdCF) {
CFStringGetCString(bsdCF, bsd, sizeof bsd, kCFStringEncodingUTF8);
CFRelease(bsdCF);
}
if (!IOObjectConformsTo(snapshotDev, "AppleAPFSSnapshot")) {
contextprintf(context, kBLLogLevelError, "%s is not an APFS snapshot\n", bsd);
return EINVAL;
}
device = snapshotDev;
IOObjectRetain(snapshotDev);
foundVolume = false;
while (IORegistryEntryGetParentEntry(device, kIOServicePlane, &parent) == kIOReturnSuccess) {
IOObjectRelease(device);
device = parent;
if (IOObjectConformsTo(device, APFS_VOLUME_OBJECT)) {
foundVolume = true;
break;
}
}
if (!foundVolume) {
contextprintf(context, kBLLogLevelError, "No corresponding volume for APFS snapshot %s\n", bsd);
IOObjectRelease(device);
return 2;
}
*volumeDev = device;
return 0;
}
int BLAPFSSnapshotAsRoot(BLContextPtr context, const char *mountPoint, char *snapName, int nameLen)
{
int ret = 0;
int fd = -1;
struct attrlist attrList;
char attrBuf[4096];
bool found = false;
int count;
int i;
char *entry;
char *cur;
int32_t length;
attribute_set_t *returned;
attrreference_t *nameAttr;
char *name;
fd = open(mountPoint, O_RDONLY);
if (fd < 0) {
ret = errno;
goto exit;
}
memset(&attrList, 0, sizeof(attrList));
attrList.bitmapcount = ATTR_BIT_MAP_COUNT;
attrList.commonattr = ATTR_CMN_RETURNED_ATTRS | ATTR_CMN_NAME | ATTR_CMN_OBJID;
while (!found) {
count = getattrlistbulk(fd, &attrList, attrBuf, sizeof attrBuf, FSOPT_LIST_SNAPSHOT);
if (count < 0) {
ret = errno;
goto exit;
}
if (count == 0) break;
for (i = 0, entry = attrBuf; i < count; i++, entry += length) {
length = *(int32_t *)entry;
cur = entry + 4;
returned = (attribute_set_t *)cur;
cur += sizeof *returned;
if (returned->commonattr & ATTR_CMN_NAME) {
nameAttr = (attrreference_t *)cur;
name = cur + nameAttr->attr_dataoffset;
cur += sizeof *nameAttr;
}
if (returned->commonattr & ATTR_CMN_OBJID) {
uint64_t xid = * ( uint64_t * )cur;
if ((xid & SNAPSHOT_MARKED_AS_ROOT_TO_BIT) == 0) continue;
if (snapName) strlcpy(snapName, name, nameLen);
found = true;
break;
} else {
ret = EINVAL;
goto exit;
}
}
}
if (!found) ret = ENOENT;
exit:
if (fd >= 0) close(fd);
return ret;
}
static uint16_t RoleNameToValue(CFStringRef roleName)
{
if (CFEqual(roleName, CFSTR(kAPFSVolumeRoleNone))) {
return APFS_VOL_ROLE_NONE;
} else if (CFEqual(roleName, CFSTR(kAPFSVolumeRoleSystem))) {
return APFS_VOL_ROLE_SYSTEM;
} else if (CFEqual(roleName, CFSTR(kAPFSVolumeRoleUser))) {
return APFS_VOL_ROLE_USER;
} else if (CFEqual(roleName, CFSTR(kAPFSVolumeRoleRecovery))) {
return APFS_VOL_ROLE_RECOVERY;
} else if (CFEqual(roleName, CFSTR(kAPFSVolumeRoleVM))) {
return APFS_VOL_ROLE_VM;
} else if (CFEqual(roleName, CFSTR(kAPFSVolumeRolePreBoot))) {
return APFS_VOL_ROLE_PREBOOT;
} else if (CFEqual(roleName, CFSTR(kAPFSVolumeRoleInstaller))) {
return APFS_VOL_ROLE_INSTALLER;
} else if (CFEqual(roleName, CFSTR(kAPFSVolumeRoleData))) {
return APFS_VOL_ROLE_DATA;
} else if (CFEqual(roleName, CFSTR(kAPFSVolumeRoleBaseband))) {
return APFS_VOL_ROLE_BASEBAND;
} else if (CFEqual(roleName, CFSTR(kAPFSVolumeRoleXART))) {
return APFS_VOL_ROLE_XART;
} else if (CFEqual(roleName, CFSTR(kAPFSVolumeRoleInternal))) {
return APFS_VOL_ROLE_INTERNAL;
} else if (CFEqual(roleName, CFSTR(kAPFSVolumeRoleBackup))) {
return APFS_VOL_ROLE_BACKUP;
} else if (CFEqual(roleName, CFSTR(kAPFSVolumeRoleUpdate))) {
return APFS_VOL_ROLE_UPDATE;
} else if (CFEqual(roleName, CFSTR(kAPFSVolumeRoleHardware))) {
return APFS_VOL_ROLE_HARDWARE;
} else if (CFEqual(roleName, CFSTR(kAPFSVolumeRoleSideCar))) {
return APFS_VOL_ROLE_SIDECAR;
} else if (CFEqual(roleName, CFSTR(kAPFSVolumeRoleEnterprise))) {
return APFS_VOL_ROLE_ENTERPRISE;
} else if (CFEqual(roleName, CFSTR(kAPFSVolumeRoleIDiags))) {
return APFS_VOL_ROLE_IDIAGS;
}
return -1U;
}
int GetPrebootBSDForVolumeBSD(BLContextPtr context, const char *volBSD, char *prebootBSD, int prebootBSDLen)
{
int ret;
CFDictionaryRef booterDict;
CFArrayRef prebootVols;
CFStringRef prebootVol;
ret = BLCreateBooterInformationDictionary(context, volBSD, &booterDict);
if (ret) {
contextprintf(context, kBLLogLevelError, "Could not get boot information for device %s\n", volBSD);
goto exit;
}
prebootVols = CFDictionaryGetValue(booterDict, kBLAPFSPrebootVolumesKey);
if (!prebootVols || !CFArrayGetCount(prebootVols)) {
contextprintf(context, kBLLogLevelError, "No preboot volume associated with device %s\n", volBSD);
ret = EINVAL;
goto exit;
}
prebootVol = CFArrayGetValueAtIndex(prebootVols, 0);
if (CFGetTypeID(prebootVol) != CFStringGetTypeID()) {
contextprintf(context, kBLLogLevelError, "Badly formed registry entry for preboot volume\n");
ret = EILSEQ;
goto exit;
}
CFStringGetCString(prebootVol, prebootBSD, prebootBSDLen, kCFStringEncodingUTF8);
exit:
return ret;
}
int GetMountForBSD(BLContextPtr context, const char *bsd, char *mountPoint, int mountPointLen)
{
struct statfs *mnts;
int mntsize;
int i;
mntsize = getmntinfo(&mnts, MNT_NOWAIT);
if (!mntsize) {
return errno;
}
for (i = 0; i < mntsize; i++) {
if (strcmp(mnts[i].f_mntfromname + strlen(_PATH_DEV), bsd) == 0) break;
}
if (i < mntsize) {
strlcpy(mountPoint, mnts[i].f_mntonname, mountPointLen);
} else {
*mountPoint = '\0';
}
return 0;
}
int GetUUIDFolderPathInPreboot(BLContextPtr context, const char *prebootMountPoint, const char *rootBSD, char *prebootDirPath, int len)
{
char prebootFolderPath[MAXPATHLEN];
char *pathEnd;
size_t pathFreeSpace;
int ret = 0;
CFStringRef bootUUID = NULL;
struct stat existingStat;
io_service_t rootMedia = IO_OBJECT_NULL;
io_service_t systemMedia = IO_OBJECT_NULL;
rootMedia = IOServiceGetMatchingService(kIOMasterPortDefault, IOBSDNameMatching(kIOMasterPortDefault, 0, rootBSD));
if (!rootMedia) {
ret = 1;
goto exit;
}
if (IOObjectConformsTo(rootMedia, "AppleAPFSSnapshot")) {
contextprintf(context, kBLLogLevelVerbose, "%s is a snapshot device\n", rootBSD);
ret = BLAPFSSnapshotToVolume(context, rootMedia, &systemMedia);
if (ret) {
contextprintf(context, kBLLogLevelError, "Could not resolve snapshot at %s to volume\n", rootBSD);
goto exit;
}
} else {
systemMedia = rootMedia;
IOObjectRetain(systemMedia);
}
snprintf(prebootFolderPath, sizeof prebootFolderPath, "%s/", prebootMountPoint);
pathEnd = prebootFolderPath + strlen(prebootFolderPath);
pathFreeSpace = sizeof prebootFolderPath - strlen(prebootFolderPath);
bootUUID = IORegistryEntryCreateCFProperty(systemMedia, CFSTR(kIOMediaUUIDKey), kCFAllocatorDefault, 0);
if (!bootUUID) {
contextprintf(context, kBLLogLevelError, "No valid volume UUID for device %s\n", rootBSD);
ret = 2;
goto exit;
}
CFStringGetCString(bootUUID, pathEnd, pathFreeSpace, kCFStringEncodingUTF8);
if (stat(prebootFolderPath, &existingStat) < 0 || !S_ISDIR(existingStat.st_mode)) {
CFRelease(bootUUID);
bootUUID = NULL;
} else {
contextprintf(context, kBLLogLevelVerbose, "Found system volume UUID path in preboot for %s\n", rootBSD);
}
if (!bootUUID) {
bootUUID = IORegistryEntryCreateCFProperty(systemMedia, CFSTR(kAPFSVolGroupUUIDKey), kCFAllocatorDefault, 0);
if (bootUUID) CFStringGetCString(bootUUID, pathEnd, pathFreeSpace, kCFStringEncodingUTF8);
if (!bootUUID || stat(prebootFolderPath, &existingStat) < 0 || !S_ISDIR(existingStat.st_mode)) {
contextprintf(context, kBLLogLevelError, "No valid group or volume UUID folder for %s\n", rootBSD);
ret = 2;
goto exit;
} else {
contextprintf(context, kBLLogLevelVerbose, "Found volume group UUID path in preboot for %s\n", rootBSD);
}
}
exit:
if (0 == ret) {
snprintf(prebootDirPath, len, "%s", prebootFolderPath);
}
if (rootMedia) IOObjectRelease(rootMedia);
if (systemMedia) IOObjectRelease(systemMedia);
if (bootUUID) CFRelease(bootUUID);
return ret;
}