BLHandleAPFSBlessData.c [plain text]
#include <stdint.h>
#include <unistd.h>
#include <errno.h>
#include <paths.h>
#include <apfs/apfs_fsctl.h>
#include <System/sys/snapshot.h>
#include <IOKit/storage/IOMedia.h>
#include <IOKit/IOBSD.h>
#include <APFS/APFS.h>
#include "bless.h"
#include "bless_private.h"
int BLGetAPFSBlessData(BLContextPtr context, const char *mountpoint, uint64_t *words)
{
int ret = 0;
if (fsctl(mountpoint, APFSIOC_GET_BOOTINFO, words, 0) < 0) {
if (errno == ENOENT) {
words[0] = words[1] = 0;
} else {
ret = errno;
}
}
return ret;
}
int BLSetAPFSBlessData(BLContextPtr context, const char *mountpoint, uint64_t *words)
{
return fsctl(mountpoint, APFSIOC_SET_BOOTINFO, words, 0) < 0 ? errno : 0;
}
int BLCreateAPFSVolumeInformationDictionary(BLContextPtr context, const char *mountpoint, CFDictionaryRef *outDict)
{
uint64_t blessWords[2];
int err;
uint32_t i;
uint64_t dirID;
CFMutableDictionaryRef dict = NULL;
CFMutableArrayRef infarray = NULL;
char blesspath[MAXPATHLEN];
err = BLGetAPFSBlessData(context, mountpoint, blessWords);
if (err) {
return 1;
}
infarray = CFArrayCreateMutable(kCFAllocatorDefault, 2, &kCFTypeArrayCallBacks);
dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
for (i = 0; i < 2; i++) {
CFMutableDictionaryRef word =
CFDictionaryCreateMutable(kCFAllocatorDefault,0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFTypeRef val;
dirID = blessWords[i];
blesspath[0] = '\0';
err = BLLookupFileIDOnMount64(context, mountpoint, dirID, blesspath, sizeof blesspath);
val = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &dirID);
CFDictionaryAddValue(word, CFSTR("Directory ID"), val);
CFRelease(val); val = NULL;
val = CFStringCreateWithCString(kCFAllocatorDefault, blesspath, kCFStringEncodingUTF8);
CFDictionaryAddValue(word, CFSTR("Path"), val);
CFRelease(val); val = NULL;
if (strlen(blesspath) == 0 || 0 == strcmp(mountpoint, "/")) {
val = CFStringCreateWithCString(kCFAllocatorDefault, blesspath, kCFStringEncodingUTF8);
} else {
val = CFStringCreateWithCString(kCFAllocatorDefault, blesspath+strlen(mountpoint), kCFStringEncodingUTF8);
}
CFDictionaryAddValue(word, CFSTR("Relative Path"), val);
CFRelease(val); val = NULL;
CFArrayAppendValue(infarray, word);
CFRelease(word); word = NULL;
}
CFDictionaryAddValue(dict, CFSTR("Bless Info"),
infarray);
CFRelease(infarray); infarray = NULL;
*outDict = dict;
return 0;
}
int BLGetAPFSSnapshotBlessData(BLContextPtr context, const char *mountpoint, uuid_string_t snap_uuid)
{
if (!snap_uuid) {
return -1;
}
apfs_snap_name_lookup_t snap_lookup_data = {0};
snap_lookup_data.type = SNAP_LOOKUP_ROOT;
if (fsctl(mountpoint, APFSIOC_SNAP_LOOKUP, (void *)&snap_lookup_data, 0)) {
return -1;
}
if (snap_lookup_data.snap_xid != 0) {
uuid_unparse(snap_lookup_data.snap_uuid, snap_uuid);
} else {
memset(snap_uuid, 0, sizeof(uuid_string_t));
}
return 0;
}
int BLCreateAndSetSnapshotBoot(BLContextPtr context, const char *mountpoint, char *snapName, int nameLen)
{
int ret = 0;
int vol_fd = -1;
uuid_t uuid;
uuid_string_t uuid_str;
io_service_t volMedia = IO_OBJECT_NULL;
io_service_t systemMedia = IO_OBJECT_NULL;
struct statfs sfs;
CFStringRef bsdCF;
char systemDev[64] = _PATH_DEV;
char systemMount[MAXPATHLEN];
int mntsize;
struct statfs *mnts;
int i;
bool mustUnmountSystem = false;
if (statfs(mountpoint, &sfs) < 0) {
ret = errno;
contextprintf(context, kBLLogLevelError, "Couldn't get volume information for volume %s: %s\n", mountpoint, strerror(ret));
goto exit;
}
volMedia = IOServiceGetMatchingService(kIOMasterPortDefault, IOBSDNameMatching(kIOMasterPortDefault, 0,
sfs.f_mntfromname + strlen(_PATH_DEV)));
if (!volMedia) {
ret = ENOENT;
contextprintf(context, kBLLogLevelError, "Couldn't get IOMedia for volume %s\n", mountpoint);
goto exit;
}
if (IOObjectConformsTo(volMedia, "AppleAPFSSnapshot")) {
contextprintf(context, kBLLogLevelVerbose, "Volume %s is mounted from a snapshot\n", mountpoint);
ret = BLAPFSSnapshotToVolume(context, volMedia, &systemMedia);
if (ret) {
contextprintf(context, kBLLogLevelError, "Couldn't not resolve snapshot at %s to volume\n", sfs.f_mntfromname);
goto exit;
}
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(systemMount, mnts[i].f_mntonname, sizeof systemMount);
} else {
ret = BLMountContainerVolume(context, systemDev + strlen(_PATH_DEV), systemMount, sizeof systemMount, false);
if (ret) {
contextprintf(context, kBLLogLevelError, "Couldn't mount system volume from %s\n", systemDev);
goto exit;
}
mustUnmountSystem = true;
}
mountpoint = systemMount;
}
uuid_generate(uuid);
uuid_unparse(uuid, uuid_str);
snprintf(snapName, nameLen, "com.apple.bless.%s", uuid_str);
if ((vol_fd = open(mountpoint, O_RDONLY)) < 0) {
ret = errno;
contextprintf(context, kBLLogLevelError, "Couldn't open volume %s for snapshot creation: %s\n", mountpoint, strerror(ret));
goto exit;
}
if (fs_snapshot_create(vol_fd, snapName, 0) < 0) {
ret = errno;
contextprintf(context, kBLLogLevelError, "Couldn't create snapshot on volume %s: %s\n", mountpoint, strerror(ret));
goto exit;
}
if (fs_snapshot_root(vol_fd, snapName, 0) < 0) {
ret = errno;
contextprintf(context, kBLLogLevelError, "Couldn't root snapshot on volume %s: %s\n", mountpoint, strerror(ret));
goto exit;
}
exit:
if (vol_fd >= 0) close(vol_fd);
if (volMedia) IOObjectRelease(volMedia);
if (systemMedia) IOObjectRelease(systemMedia);
if (mustUnmountSystem) {
BLUnmountContainerVolume(context, systemMount);
}
return ret;
}
int BLSetAPFSSnapshotBlessData(BLContextPtr context, const char *mountpoint, uuid_string_t snap_uuid)
{
apfs_snap_name_lookup_t snap_lookup_data = {0};
int vol_fd, err = 0;
if (!snap_uuid) {
return -1;
}
if ((vol_fd = open(mountpoint, O_RDONLY, 0 )) < 0) {
return -1;
}
if (strlen(snap_uuid)) {
err = uuid_parse(snap_uuid, snap_lookup_data.snap_uuid);
snap_lookup_data.type = SNAP_LOOKUP_BY_UUID;
if (!err) {
err = fsctl(mountpoint, APFSIOC_SNAP_LOOKUP, (void *)&snap_lookup_data, 0);
}
}
if (!err) {
err = fs_snapshot_root(vol_fd, snap_lookup_data.snap_name, 0);
}
close(vol_fd);
return err;
}
CFStringRef BLGetAPFSBlessedVolumeBSDName(BLContextPtr context, const char *mountpoint, char *bless_folder, uuid_string_t vol_uuid)
{
char *slash;
uuid_t c_uuid;
CFStringRef inUUID, bsd_name = NULL;
CFStringRef testUUID;
CFMutableDictionaryRef matching = NULL;
io_service_t service = IO_OBJECT_NULL;
io_service_t dataService = IO_OBJECT_NULL;
io_iterator_t iter = IO_OBJECT_NULL;
CFArrayRef roleArray;
CFStringRef role;
kern_return_t kret;
bool skipVolUUIDCheck;
if (!vol_uuid) {
return NULL;
}
memset(vol_uuid, 0, sizeof(uuid_string_t));
memmove(bless_folder, bless_folder + strlen(mountpoint), strlen(bless_folder) - strlen(mountpoint) + 1);
slash = strchr(bless_folder + 1, '/');
if (slash) *slash = '\0';
if (uuid_parse(bless_folder + 1, c_uuid)) {
return NULL;
}
inUUID = CFStringCreateWithCString(kCFAllocatorDefault, bless_folder + 1, kCFStringEncodingUTF8);
matching = IOServiceMatching(APFS_VOLUME_OBJECT);
kret = IOServiceGetMatchingServices(kIOMasterPortDefault, matching, &iter);
if (kret == KERN_SUCCESS) {
while ((service = IOIteratorNext(iter)) != IO_OBJECT_NULL) {
skipVolUUIDCheck = false;
testUUID = IORegistryEntryCreateCFProperty(service, CFSTR(kAPFSVolGroupUUIDKey), kCFAllocatorDefault, 0);
if (testUUID) {
if (CFEqual(testUUID, inUUID)) {
roleArray = IORegistryEntryCreateCFProperty(service, CFSTR(kAPFSRoleKey), kCFAllocatorDefault, 0);
if (roleArray) {
if (CFArrayGetCount(roleArray) > 0) {
role = CFArrayGetValueAtIndex(roleArray, 0);
if (CFEqual(role, CFSTR("System"))) {
CFRelease(testUUID);
CFRelease(roleArray);
break;
} else {
skipVolUUIDCheck = true;
dataService = service;
IOObjectRetain(dataService);
}
}
CFRelease(roleArray);
}
}
CFRelease(testUUID);
}
if (!skipVolUUIDCheck) {
testUUID = IORegistryEntryCreateCFProperty(service, CFSTR(kIOMediaUUIDKey), kCFAllocatorDefault, 0);
if (testUUID) {
if (CFEqual(testUUID, inUUID)) {
CFRelease(testUUID);
break;
}
CFRelease(testUUID);
}
}
IOObjectRelease(service);
}
IOObjectRelease(iter);
if (service) {
bsd_name = IORegistryEntryCreateCFProperty(service, CFSTR(kIOBSDNameKey), kCFAllocatorDefault, 0);
if (bsd_name) strlcpy(vol_uuid, bless_folder + 1, sizeof(uuid_string_t));
IOObjectRelease(service);
} else {
if (dataService) {
bsd_name = IORegistryEntryCreateCFProperty(dataService, CFSTR(kIOBSDNameKey), kCFAllocatorDefault, 0);
if (bsd_name) strlcpy(vol_uuid, bless_folder + 1, sizeof(uuid_string_t));
IOObjectRelease(dataService);
}
}
}
CFRelease(inUUID);
if (slash) *slash = '/';
return bsd_name;
}