personalize.m   [plain text]

//
// personalize.m
//

#import <string.h>
#import <stdlib.h>
#import <stdbool.h>
#import <unistd.h>
#import <paths.h>
#import <sys/param.h>
#import <sys/mount.h>
#import <sys/stat.h>
#import <CoreFoundation/CoreFoundation.h>
#import <Foundation/Foundation.h>
#import <OSPersonalization/OSPersonalization.h>
#import <APFS/APFS.h>
#import <Bom/Bom.h>
#import <IOKit/storage/IOMedia.h>
#import "bless.h"
#import "bless_private.h"
#import "protos.h"

#define EX_BADNETWORK 129
#define EX_SERVERREFUSED 130
#define EX_OTHERPERSONALIZE 131

static int HandleSpecialVolume(BLContextPtr context, const char *rootPath, const char *volumeDev, int role, bool useGroupUUID);
static int CopyRootToDir(const char *rootPath, const char *volumePath);

int PersonalizeOSVolume(BLContextPtr context, const char *volumePath, const char *prFile, bool suppressACPrompt)
{
__block int ret = 0;
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
OSPersonalizationController *pc;
char prebootMount[MAXPATHLEN];
NSURL *vURL;
NSURL *pURL = nil;
char *installEnv;
NSURL *rootsURL;
dispatch_semaphore_t wait = dispatch_semaphore_create(0);
NSDictionary *prOptions;
NSDictionary *restoreOptions = nil;
NSMutableDictionary *options;
char tmpLoc[MAXPATHLEN];
__block NSArray<OSPersonalizedManifestRootType> *types;
const char *pRoot;
struct statfs sfs;
bool mustCleanupRoots = false;
bool mustUnmountPreboot = false;
bool dummyBool;
struct stat sb;
bool useGroup = false;
bool isDataVol = false;
bool isARV = false;

// OSPersonalization is weak-linked, so check to make sure it's present
if ([OSPersonalizationController class] == nil) {
return ENOTSUP;
}
pc = [OSPersonalizationController sharedController];
if (statfs(volumePath, &sfs) < 0) {
ret = errno;
goto exit;
}
vURL = [NSURL fileURLWithFileSystemRepresentation:volumePath isDirectory:YES relativeToURL:nil];
if ([pc personalizationRequiredForVolumeAtMountPoint:vURL]) {
blesscontextprintf(context, kBLLogLevelVerbose, "Personalization required for volume at %s\n", volumePath);
installEnv = getenv("__OSINSTALL_ENVIRONMENT");
if (installEnv && (atoi(installEnv) > 0 || strcasecmp(installEnv, "yes") == 0 || strcasecmp(installEnv, "true") == 0)) {
strlcpy(tmpLoc, "/var/tmp/RecoveryTemp", sizeof tmpLoc);
} else {
if (!confstr(_CS_DARWIN_USER_TEMP_DIR, tmpLoc, sizeof tmpLoc)) {
// We couldn't get our path in /var/folders, so just try /var/tmp.
strlcpy(tmpLoc, "/var/tmp/", sizeof tmpLoc);
}
}

if (strcmp(sfs.f_fstypename, "apfs") == 0) {
ret = BLEnsureSpecialAPFSVolumeUUIDPath(context, sfs.f_mntfromname,
APFS_VOL_ROLE_PREBOOT, false,
prebootMount, sizeof prebootMount,
&mustUnmountPreboot);
if (ret) goto exit;
if (stat(prebootMount, &sb) < 0 || !S_ISDIR(sb.st_mode)) {
// Volume UUID didn't work. Let's try group UUID.
ret = BLEnsureSpecialAPFSVolumeUUIDPath(context, sfs.f_mntfromname,
APFS_VOL_ROLE_PREBOOT, true,
prebootMount, sizeof prebootMount,
&dummyBool);
if (ret) goto exit;
if (dummyBool) mustUnmountPreboot = true;
useGroup = true;
}
pURL = [NSURL fileURLWithFileSystemRepresentation:prebootMount isDirectory:YES relativeToURL:nil];
}
if ([pc volumeHasBeenPersonalized:vURL prebootFolder:pURL]) {
// This is already done. Let's get out of here.
blesscontextprintf(context, kBLLogLevelVerbose, "OSP reports volume is already personalized\n");
ret = 0;
goto exit;
}

rootsURL = [[NSURL fileURLWithFileSystemRepresentation:tmpLoc isDirectory:YES relativeToURL:nil] URLByAppendingPathComponent:@"roots"];

options = [NSMutableDictionary dictionary];
[options setObject:[NSNumber numberWithBool:YES] forKey:OSPersonalizationOptionUseRunningDeviceIdentity];
if (suppressACPrompt) [options setObject:[NSNumber numberWithBool:NO] forKey:OSPersonalizationOptionShowUI];
if (prFile) {
prOptions = [NSDictionary dictionaryWithContentsOfFile:[NSString stringWithUTF8String:prFile]];
if (prOptions) {
restoreOptions = [prOptions objectForKey:@"RestoreOptions"];
if (!restoreOptions) restoreOptions = prOptions;
}
}
if (restoreOptions) [options addEntriesFromDictionary:restoreOptions];
if ([pc networkAvailableForPersonalizationWithOptions:options]) {
[pc personalizeVolumeAtMountPointForInstall:vURL outputDirectory:rootsURL options:options completionHandler:^(NSArray<OSPersonalizedManifestRootType> * _Nonnull manifestTypes, NSError * _Nullable error) {
if (error) {
if (OSPErrorIsNetworkingRelated(error)) {
ret = EX_BADNETWORK;
} else if ([error.domain isEqualToString:OSPErrorDomain] && error.code == OSPTATSUDeclinedAuthorizationError) {
ret = EX_SERVERREFUSED;
} else {
ret = EX_OTHERPERSONALIZE;
}
} else {
ret = 0;
}
if (!ret) types = manifestTypes;
dispatch_semaphore_signal(wait);
}];
dispatch_semaphore_wait(wait, DISPATCH_TIME_FOREVER);
if (ret) goto exit;
mustCleanupRoots = true;
if (strcmp(sfs.f_fstypename, "apfs") == 0) {
ret = BLIsDataRoleForAPFSVolumeDev(context, sfs.f_mntfromname, &isDataVol);
if (ret) goto exit;
ret = BLIsVolumeARV(context, sfs.f_mntonname, sfs.f_mntfromname + strlen(_PATH_DEV), &isARV);
if (ret) goto exit;
}
if (!isDataVol && !isARV) {
ret = CopyRootToDir([[rootsURL URLByAppendingPathComponent:OSPersonalizedManifestRootTypeBoot] fileSystemRepresentation],
volumePath);
if (ret) goto exit;
}
if (strcmp(sfs.f_fstypename, "apfs") == 0) {
pRoot = [[rootsURL URLByAppendingPathComponent:OSPersonalizedManifestRootTypePreboot] fileSystemRepresentation];
if (access(pRoot, R_OK) < 0) goto exit;
ret = HandleSpecialVolume(context, pRoot, sfs.f_mntfromname, APFS_VOL_ROLE_PREBOOT, useGroup);
if (ret) goto exit;
pRoot = [[rootsURL URLByAppendingPathComponent:OSPersonalizedManifestRootTypeRecoveryBoot] fileSystemRepresentation];
if (access(pRoot, R_OK) < 0) goto exit;
ret = HandleSpecialVolume(context, pRoot, sfs.f_mntfromname, APFS_VOL_ROLE_RECOVERY, useGroup);
if (ret) goto exit;
}
} else {
ret = EX_BADNETWORK;
goto exit;
}
} else {
blesscontextprintf(context, kBLLogLevelVerbose, "Personalization not required for volume at %s\n", volumePath);
}

exit:
if (mustUnmountPreboot) {
BLUnmountContainerVolume(context, prebootMount);
}
if (!ret && mustCleanupRoots) {
DeleteFileOrDirectory([rootsURL fileSystemRepresentation]);
}
dispatch_release(wait);
[pool release];
return ret;
}

static int HandleSpecialVolume(BLContextPtr context, const char *rootPath, const char *volumeDev, int role, bool useGroupUUID)
{
int ret;
char specialMountPointPath[MAXPATHLEN];
bool mustUnmount;

ret = BLEnsureSpecialAPFSVolumeUUIDPath(context, volumeDev,
role, useGroupUUID,
specialMountPointPath, sizeof specialMountPointPath,
&mustUnmount);
if (ret) goto exit;

ret = CopyRootToDir(rootPath, specialMountPointPath);
if (ret) goto exit;

exit:
if (mustUnmount) BLUnmountContainerVolume(context, specialMountPointPath);
return ret;
}

static int CopyRootToDir(const char *rootPath, const char *volumePath)
{
int ret;
BOMCopier copier;

copier = BOMCopierNew();
if (!copier) return ENOMEM;
ret = BOMCopierCopy(copier, rootPath, volumePath);
BOMCopierFree(copier);
return ret;
}