#include <sys/param.h>
#include <sys/mount.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <System/sys/reason.h>
#include <err.h>
#include <os/errno.h>
#include <os/bsd.h>
#include <os/variant_private.h>
#include <fstab.h>
#include <pwd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <TargetConditionals.h>
#include <sysexits.h>
#include <sys/sysctl.h>
#include <APFS/APFS.h>
#include <APFS/APFSConstants.h>
#include <pthread.h>
#include <spawn.h>
#include <crt_externs.h>
#if (TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR)
#include <paths.h>
#include <sys/socket.h>
#include <sys/queue.h>
#include <sys/wait.h>
#include <sys/param.h>
#include <sys/cdefs.h>
#include <copyfile.h>
#include <MediaKit/MKMedia.h>
#include <MediaKit/MKMediaAccess.h>
#include <MediaKit/GPTTypes.h>
#endif
#if TARGET_OS_OSX
#include <paths.h>
#include <IOKit/IOBSD.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/storage/IOMedia.h>
#endif
#include "mount_flags.h"
#include "edt_fstab.h"
#include "pathnames.h"
#include "fsck.h"
#if TARGET_OS_OSX
#define APFS_BOOT_UTIL_PATH "/System/Library/Filesystems/apfs.fs/Contents/Resources/apfs_boot_util"
#define PLATFORM_DATA_VOLUME_MOUNT_POINT "/System/Volumes/Data"
#define BASE_SYSTEM_PATH "/System/Volumes/BaseSystem"
#define RECOVERY_PATH "/System/Volumes/Recovery"
#else
#define APFS_BOOT_UTIL_PATH "/System/Library/Filesystems/apfs.fs/apfs_boot_util"
#define PLATFORM_DATA_VOLUME_MOUNT_POINT "/private/var"
#endif
#define environ (*_NSGetEnviron())
#define COMMAND_OUTPUT_MAX 1024
int debug;
int verbose;
int bootstrap_macos = 0;
int passno = 0;
int checkvfsname __P((const char *, const char **));
char *catopt __P((char *, const char *));
struct statfs *getmntpt __P((const char *));
int hasopt __P((const char *, const char *));
const char
**makevfslist __P((char *));
void mangle __P((char *, int *, const char **));
void prmount __P((struct statfs *));
void usage __P((void));
int run_command(char **command_argv, char *output, int *rc, int *signal_no);
void print_mount(const char **vfslist);
int ismounted(const char *fs_spec, const char *fs_file, long *flags);
int mountfs(const char *vfstype, const char *fs_spec, const char *fs_file, int flags, const char *options, const char *mntopts);
int unmount_location(char *mount_point);
#if (TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR)
char* parse_parameter_for_token(char * opts, char * search_string);
int verify_executable_file_existence (char *path);
int verify_file_existence (char *path);
int _verify_file_flags (char *path, int flags);
int preflight_create_mount_ramdisk (char *mnt_opts, size_t *ramdisk_size, char *template);
char* split_ramdisk_params(char *opts);
int create_mount_ramdisk(struct fstab *fs, int init_flags, char *options);
int construct_apfs_volume(char *mounted_device_name);
int create_partition_table(size_t partition_size, char *device);
int attach_device(size_t device_size , char* deviceOut);
void truncate_whitespace(char* str);
#define RAMDISK_BLCK_OFFSET 34
#define RAMDISK_TMP_MOUNT "/mnt2"
#define RAMDISK_BCK_MOUNT "/.mb"
#define RAMDISK_SIZE_TOK "size="
#define RAMDISK_TPLT_TOK "template="
#define HDIK_PATH "/usr/sbin/hdik"
#endif
extern mountopt_t optnames[];
#if TARGET_OS_OSX
static int booted_rosv(void);
static int booted_apfs(void);
#endif
static int upgrade_mount(const char *mountpt, int init_flags, char *options);
static int ret_errno = 0;
static inline int
errno_or_sysexit(int err, int sysexit)
{
if (sysexit == -1) {
sysexit = sysexit_np(err);
}
return (ret_errno ? err : sysexit);
}
#if (TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR)
#define BINDFS_MOUNT_TYPE "bindfs"
#define PREBOOT_VOL_MOUNTPOINT "/private/preboot"
#define HARDWARE_VOL_MOUNTPOINT "/private/var/hardware"
#define BOOT_MANIFEST_HASH_LEN 256
typedef struct bind_mount {
char *bm_mnt_prefix;
char *bm_mnt_to;
bool bm_mandatory;
} bind_mount_t;
static void
do_bindfs_mount(char *from, const char *to, bool required)
{
struct statfs sfs;
uint32_t mnt_flags = MNT_RDONLY | MNT_NODEV | MNT_NOSUID | MNT_DONTBROWSE;
int err = 0;
if (debug) {
printf("call: mount %s %s %x %s", BINDFS_MOUNT_TYPE, to, mnt_flags, from);
return;
}
if ((statfs(from, &sfs) == 0) &&
(strncmp(sfs.f_fstypename, BINDFS_MOUNT_TYPE, sizeof(BINDFS_MOUNT_TYPE)) == 0)) {
return;
}
err = mount(BINDFS_MOUNT_TYPE, to, mnt_flags, from);
if (err) {
if ((errno == ENOENT) && !required) {
err = 0;
} else {
errx(errno_or_sysexit(errno, -1), "failed to mount %s -> %s - %s(%d)",
from, to, strerror(errno), errno);
}
}
}
static void
setup_preboot_mounts(int pass)
{
int err = 0;
const char *mnt_to;
char mnt_from[MAXPATHLEN], boot_manifest_hash[BOOT_MANIFEST_HASH_LEN];
err = get_boot_manifest_hash(boot_manifest_hash, sizeof(boot_manifest_hash));
if (err) {
errx(errno_or_sysexit(err, -1), "failed to get boot manifest hash - %s",
strerror(err));
}
const bind_mount_t preboot_mnts[] = {
{.bm_mnt_prefix = PREBOOT_VOL_MOUNTPOINT,
.bm_mnt_to = "/usr/standalone/firmware",
.bm_mandatory = true},
{.bm_mnt_prefix = PREBOOT_VOL_MOUNTPOINT,
.bm_mnt_to = "/usr/local/standalone/firmware",
.bm_mandatory = false}
};
const bind_mount_t hw_mnts[] = {
{.bm_mnt_prefix = HARDWARE_VOL_MOUNTPOINT "/Pearl",
.bm_mnt_to = "/System/Library/Pearl/ReferenceFrames",
.bm_mandatory = false},
{.bm_mnt_prefix = HARDWARE_VOL_MOUNTPOINT "/FactoryData",
.bm_mnt_to = "/System/Library/Caches/com.apple.factorydata",
.bm_mandatory = true}
};
if (pass == ROOT_PASSNO) {
for (int i = 0; i < (sizeof(preboot_mnts) / sizeof(preboot_mnts[0])); i++) {
mnt_to = preboot_mnts[i].bm_mnt_to;
snprintf(mnt_from, sizeof(mnt_from), "%s/%s%s",
preboot_mnts[i].bm_mnt_prefix, boot_manifest_hash, mnt_to);
do_bindfs_mount(mnt_from, mnt_to, preboot_mnts[i].bm_mandatory);
}
} else {
for (int i = 0; i < (sizeof(hw_mnts) / sizeof(hw_mnts[0])); i++) {
mnt_to = hw_mnts[i].bm_mnt_to;
snprintf(mnt_from, sizeof(mnt_from), "%s%s",
hw_mnts[i].bm_mnt_prefix, mnt_to);
do_bindfs_mount(mnt_from, mnt_to, hw_mnts[i].bm_mandatory);
}
}
}
#endif
#define MOUNT_PHASE_1 1
#define MOUNT_PHASE_2 2
#define NONFS "nonfs"
static void
bootstrap_apfs(int phase)
{
char * const apfs_util_argv[] = {
APFS_BOOT_UTIL_PATH,
((phase == MOUNT_PHASE_1) ? "1" : ((phase == MOUNT_PHASE_2) ? "2" : NULL)),
NULL,
};
execv(APFS_BOOT_UTIL_PATH, apfs_util_argv);
errx(errno_or_sysexit(errno, -1), "apfs_boot_util exec failed");
}
int
main(argc, argv)
int argc;
char * const argv[];
{
const char **vfslist, *vfstype;
struct fstab *fs;
struct statfs *mntbuf;
int all, ch, init_flags, rval;
char *options, *ep;
int mount_phase = 0;
all = init_flags = 0;
options = NULL;
vfslist = NULL;
vfstype = NULL;
while ((ch = getopt(argc, argv, "headfo:rwt:uvP:")) != EOF)
switch (ch) {
case 'a':
all = 1;
break;
case 'd':
debug = 1;
break;
case 'f':
init_flags |= MNT_FORCE;
break;
case 'o':
if (*optarg) {
options = catopt(options, optarg);
if (strstr(optarg, "union"))
init_flags |= MNT_UNION;
}
break;
case 'r':
init_flags |= MNT_RDONLY;
break;
case 't':
if (vfslist != NULL)
errx(errno_or_sysexit(EINVAL, EX_USAGE),
"only one -t option may be specified.");
vfslist = makevfslist(optarg);
vfstype = optarg;
break;
case 'u':
init_flags |= MNT_UPDATE;
break;
case 'v':
verbose = 1;
break;
case 'w':
init_flags &= ~MNT_RDONLY;
break;
case 'e':
ret_errno = 1;
break;
case 'P':
mount_phase = (int)strtol(optarg, &ep, 10);
if ((ep == optarg) || (*ep) ||
(mount_phase < MOUNT_PHASE_1) ||
(mount_phase > MOUNT_PHASE_2)) {
errx(errno_or_sysexit(EINVAL, EX_USAGE),
"-P flag requires a valid mount phase number");
}
break;
case 'h':
case '?':
default:
usage();
}
argc -= optind;
argv += optind;
#define BADTYPE(type) \
(strcmp(type, FSTAB_RO) && \
strcmp(type, FSTAB_RW) && strcmp(type, FSTAB_RQ))
if (mount_phase != 0) {
#if TARGET_OS_OSX
bootstrap_macos = 1;
#else
if (mount_phase == MOUNT_PHASE_1) {
passno = ROOT_PASSNO;
} else if (mount_phase == MOUNT_PHASE_2) {
passno = NONROOT_PASSNO;
}
verbose = 1;
all = 1;
vfslist = makevfslist(NONFS);
vfstype = NONFS;
#endif
}
rval = 0;
switch (argc) {
case 0:
if (all) {
int err = 0;
long fs_flags = 0;
if ((setfsent() == 0)) {
errx(errno_or_sysexit(errno ? errno : ENXIO, -1),
"mount: can't get filesystem checklist");
}
#if (TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR)
uint32_t os_env;
const char *container_dev = get_boot_container(&os_env);
const char *data_vol_dev = get_data_volume();
if (data_vol_dev) {
fprintf(stdout, "mount: found boot container: %s, data volume: %s env: %u\n",
container_dev, data_vol_dev, os_env);
} else if ((os_env == EDT_OS_ENV_MAIN) &&
(passno == MOUNT_PHASE_2)) {
errx(errno_or_sysexit(errno ? errno : ENXIO, -1),
"mount: missing data volume");
} else {
fprintf(stdout, "mount: data volume missing, but not required in env: %u\n",
os_env);
}
#endif
while ((fs = getfsent()) != NULL) {
int ro_mount = !strcmp(fs->fs_type, FSTAB_RO);
if (BADTYPE(fs->fs_type))
continue;
if (checkvfsname(fs->fs_vfstype, vfslist))
continue;
if (hasopt(fs->fs_mntops, "noauto"))
continue;
if (!strcmp(fs->fs_vfstype, "nfs")) {
if (hasopt(fs->fs_mntops, "net"))
continue;
if (fs->fs_spec == NULL ||
fs->fs_file == NULL ||
ismounted(fs->fs_spec, fs->fs_file, NULL))
continue;
}
if (passno && fs->fs_passno != passno)
continue;
if (ismounted(fs->fs_spec, fs->fs_file, &fs_flags) &&
(!(fs_flags & MNT_RDONLY) || ro_mount))
continue;
#if (TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR)
if (!strcmp(fs->fs_spec, RAMDISK_FS_SPEC)) {
if (verbose) {
fprintf(stdout, "mount: encountered ramdisk\n");
}
rval = create_mount_ramdisk(fs, init_flags, options);
continue;
} else if (fs->fs_passno > ROOT_PASSNO &&
!strcmp(fs->fs_vfstype, EDTVolumeFSType) &&
!strcmp(fs->fs_type, FSTAB_RW)) {
if (!debug && data_vol_dev &&
(os_env == EDT_OS_ENV_MAIN) &&
(strcmp(data_vol_dev, fs->fs_spec) == 0)) {
kern_return_t mig_err = APFSContainerMigrateMediaKeys(container_dev);
if (mig_err) {
fprintf(stderr, "mount: failed to migrate Media Keys, error = %x\n", mig_err);
} else {
fprintf(stdout, "mount: successfully migrated Media Keys\n");
}
}
}
#endif
if ((err = mountfs(fs->fs_vfstype, fs->fs_spec,
fs->fs_file, init_flags, options,
fs->fs_mntops)))
rval = err;
}
endfsent();
#if (TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR)
if (os_env != EDT_OS_ENV_OTHER) {
setup_preboot_mounts(passno);
}
if (os_variant_has_internal_content(APFS_BUNDLE_ID) &&
(mount_phase == MOUNT_PHASE_2)) {
bootstrap_apfs(MOUNT_PHASE_2);
}
#endif
}
else if (bootstrap_macos) {
#if TARGET_OS_OSX
if (mount_phase == MOUNT_PHASE_1) {
if (booted_apfs()) {
bootstrap_apfs(MOUNT_PHASE_1);
} else {
fprintf(stdout, "Not booted from APFS, skipping apfs_boot_util\n");
exit(0);
}
} else if (mount_phase == MOUNT_PHASE_2) {
if (booted_rosv()) {
bootstrap_apfs(MOUNT_PHASE_2);
} else {
rval = upgrade_mount("/", MNT_UPDATE, options);
}
}
#endif
} else {
print_mount(vfslist);
}
exit(rval);
case 1:
if (vfslist != NULL)
usage();
if (init_flags & MNT_UPDATE) {
rval = upgrade_mount (*argv, init_flags, options);
break;
}
if ((fs = getfsfile(*argv)) == NULL &&
(fs = getfsspec(*argv)) == NULL)
errx(errno_or_sysexit(errno , -1),
"%s: unknown special file or file system.",
*argv);
if (BADTYPE(fs->fs_type))
errx(errno_or_sysexit(EINVAL, EX_DATAERR),
"%s has unknown file system type.",
*argv);
if (!strcmp(fs->fs_vfstype, "nfs")) {
if (hasopt(fs->fs_mntops, "net"))
errx(errno_or_sysexit(EINVAL, EX_DATAERR),
"%s is owned by the automounter.",
*argv);
if (ismounted(fs->fs_spec, fs->fs_file, NULL))
errx(errno_or_sysexit(EALREADY, EX_CONFIG),
"%s is already mounted at %s.",
fs->fs_spec, fs->fs_file);
}
#if (TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR)
if (!strcmp(fs->fs_spec, RAMDISK_FS_SPEC)) {
if (verbose) {
fprintf(stdout, "Found a ramdisk entry\n");
}
rval = create_mount_ramdisk(fs, init_flags, options);
break;
}
#endif
rval = mountfs(fs->fs_vfstype, fs->fs_spec, fs->fs_file,
init_flags, options, fs->fs_mntops);
break;
case 2:
if (vfslist == NULL && strchr(argv[0], ':') != NULL) {
vfstype = "nfs";
if (ismounted(argv[0], argv[1], NULL))
errx(errno_or_sysexit(EALREADY, EX_CONFIG),
"%s is already mounted at %s.",
argv[0], argv[1]);
}
if (init_flags & MNT_UPDATE) {
if ((mntbuf = getmntpt(*argv)) == NULL)
errx(errno_or_sysexit(errno ? errno : ENOENT, -1),
"unknown special file or file system %s.",
*argv);
rval = mountfs(mntbuf->f_fstypename, mntbuf->f_mntfromname,
mntbuf->f_mntonname, init_flags, options, 0);
}
else {
if (vfstype == NULL) {
errx (errno_or_sysexit(EINVAL, EX_USAGE),
"You must specify a filesystem type with -t.");
}
rval = mountfs(vfstype,
argv[0], argv[1], init_flags, options, NULL);
}
break;
default:
usage();
}
exit(rval);
}
static int
upgrade_mount (const char *mountpt, int init_flags, char *options) {
struct statfs *mntbuf = NULL;
const char *mntfromname = NULL;
struct fstab *fs = NULL;
if ((mntbuf = getmntpt(mountpt)) == NULL) {
errx(errno_or_sysexit(errno, -1),
"unknown special file or file system %s.",
mountpt);
}
mntfromname = mntbuf->f_mntfromname;
if (strchr(mntfromname, '/') == NULL) {
fs = getfsfile(mntbuf->f_mntonname);
if (fs != NULL)
mntfromname = fs->fs_spec;
}
if (mntbuf->f_flags & MNT_CPROTECT) {
init_flags |= MNT_CPROTECT;
}
return mountfs(mntbuf->f_fstypename, mntfromname,
mntbuf->f_mntonname, init_flags, options, 0);
}
int
hasopt(mntopts, option)
const char *mntopts, *option;
{
int negative, found;
char *opt, *optbuf;
if (option[0] == 'n' && option[1] == 'o') {
negative = 1;
option += 2;
} else
negative = 0;
optbuf = strdup(mntopts);
found = 0;
for (opt = optbuf; (opt = strtok(opt, ",")) != NULL; opt = NULL) {
if (opt[0] == 'n' && opt[1] == 'o') {
if (!strcasecmp(opt + 2, option))
found = negative;
} else if (!strcasecmp(opt, option))
found = !negative;
}
free(optbuf);
return (found);
}
int
ismounted(const char *fs_spec, const char *fs_file, long *flags)
{
int i, mntsize;
struct statfs *mntbuf;
if ((mntsize = getmntinfo(&mntbuf, MNT_NOWAIT)) == 0)
err(errno_or_sysexit(errno , -1), "getmntinfo");
for (i = 0; i < mntsize; i++) {
if (strcmp(mntbuf[i].f_mntfromname, fs_spec))
continue;
if (strcmp(mntbuf[i].f_mntonname, fs_file))
continue;
if (flags)
*flags = mntbuf[i].f_flags;
return 1;
}
return 0;
}
#if TARGET_OS_OSX
static int
booted_apfs(void) {
struct statfs *mntbuf;
if ((mntbuf = getmntpt("/")) == NULL) {
errx(errno_or_sysexit(errno, -1),
"failed to lookup root file system");
}
return (strcmp(mntbuf->f_fstypename, "apfs") == 0);
}
static int
booted_rosv(void) {
uint32_t is_rosp = 0;
size_t rospsize = sizeof(is_rosp);
int err = sysctlbyname ("vfs.generic.apfs.rosp", &is_rosp, &rospsize, NULL, NULL);
if (!err && is_rosp) {
return 1;
}
return 0;
}
#endif
void
print_mount(const char **vfslist)
{
struct statfs *mntbuf;
int mntsize;
if ( (mntsize = getmntinfo(&mntbuf, MNT_NOWAIT)) == 0 )
err(errno_or_sysexit(errno , -1), "getmntinfo");
for (int i = 0; i < mntsize; i++) {
if ( checkvfsname(mntbuf[i].f_fstypename, vfslist) )
continue;
prmount(&mntbuf[i]);
}
}
#if (TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR)
int
verify_executable_file_existence (char *path)
{
return _verify_file_flags(path, F_OK | X_OK);
}
int
verify_file_existence (char *path)
{
return _verify_file_flags(path, F_OK);
}
int
_verify_file_flags (char *path, int flags)
{
if ( access(path, flags) ) {
fprintf(stderr, "Failed access check for %s with issue %s\n", path, strerror(errno));
return errno;
}
return 0;
}
int
preflight_create_mount_ramdisk (char *mnt_opts, size_t *ramdisk_size, char *template)
{
char *special_ramdisk_params;
if ( mnt_opts == NULL ) {
fprintf(stderr, "No mnt_opts provided to ramdisk preflight.\n");
return EINVAL;
}
if ( verify_executable_file_existence(HDIK_PATH) != 0 ) {
fprintf(stderr, "Failed to find executable hdik at location %s \n", HDIK_PATH);
return ENOENT;
}
special_ramdisk_params = split_ramdisk_params(mnt_opts);
if ( special_ramdisk_params == NULL ) {
fprintf(stderr, "Ramdisk fstab not in expected format.\n");
return EINVAL;
}
if ( ramdisk_size ) {
char *ramdisk_size_str = parse_parameter_for_token(special_ramdisk_params, RAMDISK_SIZE_TOK);
if ( ramdisk_size_str != NULL ) {
*ramdisk_size = atoi(ramdisk_size_str);
free(ramdisk_size_str);
}
if ( *ramdisk_size == 0 ) {
fprintf(stderr, "Unexpected ramdisk size %zu\n", *ramdisk_size);
return EINVAL;
}
}
if ( template ) {
char *template_str = parse_parameter_for_token(special_ramdisk_params, RAMDISK_TPLT_TOK);
if (template_str != NULL) {
strlcpy(template, template_str, PATH_MAX);
free(template_str);
}
if ( template == NULL ) {
fprintf(stderr, "Ramdisk template path not found\n");
return EINVAL;
}
}
return 0;
}
#endif
int
run_command(char **command_argv, char *output, int *rc, int *signal_no)
{
int error = -1;
int faulting_errno = 0;
int status = -1;
int internal_result = -1;
pid_t pid;
posix_spawn_file_actions_t actions = NULL;
int output_pipe[2] = {-1, -1};
char *command_out = NULL;
FILE *stream = NULL;
if ( !command_argv ) {
fprintf(stderr, "command_argv is NULL\n");
errno = EINVAL;
goto done;
}
if ( pipe(output_pipe) ) {
fprintf(stderr, "Failed to create pipe for command output: %d (%s)\n", errno, strerror(errno));
goto done;
}
if ( (internal_result = posix_spawn_file_actions_init(&actions)) != 0 ) {
errno = internal_result;
fprintf(stderr, "posix_spawn_file_actions_init failed: %d (%s)\n", errno, strerror(errno));
goto done;
}
if ( (internal_result = posix_spawn_file_actions_addclose(&actions, output_pipe[0])) != 0 ) {
errno = internal_result;
fprintf(stderr, "posix_spawn_file_actions_addclose output_pipe[0] failed: %d (%s)\n", errno, strerror(errno));
goto done;
}
if ( (internal_result = posix_spawn_file_actions_adddup2(&actions, output_pipe[1], STDOUT_FILENO)) != 0 ) {
errno = internal_result;
fprintf(stderr, "posix_spawn_file_actions_adddup2 output_pipe[1] failed: %d (%s)\n", errno, strerror(errno));
goto done;
}
if ( (internal_result = posix_spawn_file_actions_addclose(&actions, output_pipe[1])) != 0 ) {
errno = internal_result;
fprintf(stderr, "posix_spawn_file_actions_addclose output_pipe[1] failed: %d (%s)\n", errno, strerror(errno));
goto done;
}
if ( verbose ) {
fprintf(stdout, "Executing command: ");
for (char **command_segment = command_argv; *command_segment; command_segment++) {
fprintf(stdout, "%s ", *command_segment);
}
fprintf(stdout, "\n");
}
if ( (internal_result = posix_spawn(&pid, command_argv[0], &actions, NULL, command_argv, environ)) != 0 ) {
errno = internal_result;
fprintf(stderr, "posix_spawn failed: %d (%s)\n", errno, strerror(errno));
goto done;
}
close(output_pipe[1]);
output_pipe[1] = -1;
if ( output != NULL ) {
command_out = output;
} else {
command_out = calloc(COMMAND_OUTPUT_MAX, sizeof(char));
if (!command_out) {
fprintf(stderr, "calloc failed: %d (%s)\n", errno, strerror(errno));
goto done;
}
}
stream = fdopen(output_pipe[0], "r");
if ( !stream ) {
fprintf(stderr, "fdopen failed: %d (%s)\n", errno, strerror(errno));
goto done;
}
size_t length;
size_t count = 0;
char *line;
while ( (line = fgetln(stream, &length)) && (count < COMMAND_OUTPUT_MAX - length - 1) ) {
strncat(command_out, line, length);
count += length;
}
if ( ferror(stream) ) {
fprintf(stderr, "fgetln failed: %d (%s)\n", errno, strerror(errno));
goto done;
}
if ( fclose(stream) ) {
fprintf(stderr, "fclose failed: %d (%s)\n", errno, strerror(errno));
stream = NULL;
goto done;
}
stream = NULL;
close(output_pipe[0]);
output_pipe[0] = -1;
while ( waitpid(pid, &status, 0) < 0 ) {
if (errno == EINTR) {
continue;
}
fprintf(stderr, "waitpid failed: %d (%s)\n", errno, strerror(errno));
goto done;
}
if ( verbose ) {
fprintf(stdout, "Command output:\n%s\n", command_out);
}
if ( WIFEXITED(status) ) {
int exit_status = WEXITSTATUS(status);
if (rc) *rc = exit_status;
if (signal_no) *signal_no = 0;
if (exit_status != 0) {
error = 1;
fprintf(stderr, "Command failed: %d\n", exit_status);
goto done;
}
}
if ( WIFSIGNALED(status) ) {
if (rc) *rc = 0;
if (signal_no) *signal_no = WTERMSIG(status);
error = 1;
fprintf(stderr, "Command signaled: %d\n", WTERMSIG(status));
goto done;
}
error = 0;
done:
faulting_errno = errno;
if ( actions ) {
posix_spawn_file_actions_destroy(&actions);
}
if ( stream ) {
fclose(stream);
}
if ( output_pipe[0] >= 0 ) {
close(output_pipe[0]);
}
if ( output_pipe[1] >= 0 ) {
close(output_pipe[1]);
}
if ( !output && command_out ) {
free(command_out);
}
errno = faulting_errno;
return error;
}
#if (TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR)
void
truncate_whitespace(char* str)
{
size_t idx = strcspn(str, " \n");
if ( idx != 0 ) {
str[idx] = '\0';
}
}
int
attach_device(size_t device_size , char* deviceOut)
{
int return_val = -1;
char ram_define [PATH_MAX];
snprintf(ram_define, sizeof(ram_define), "ram://%zu", device_size);
char *command[4] = { HDIK_PATH, "-nomount", ram_define, NULL };
int status = run_command(command, deviceOut, &return_val, NULL);
if ( status == 1 ) {
fprintf(stderr, "Failed to create ramdisk. HDIK returned %d.\n", return_val);
exit(errno_or_sysexit(errno, -1));
} else if (status != 0) {
fprintf(stderr, "Failed to execute command %s\n", command[0]);
exit(errno_or_sysexit(errno, -1));
}
truncate_whitespace(deviceOut);
return return_val;
}
int
create_partition_table(size_t partition_size, char *device)
{
MKStatus err = -1;
MKMediaRef gpt_ref = NULL;
CFMutableArrayRef schemes = NULL;
CFMutableArrayRef partitionArray = NULL;
CFDictionaryRef partition = NULL;
CFMutableDictionaryRef options = NULL;
CFMutableDictionaryRef layout = NULL;
CFMutableDictionaryRef media = NULL;
CFMutableDictionaryRef map = NULL;
layout = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
options = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
if (!layout || !options) {
fprintf(stderr, "Failed to create necessary CFDictionaries\n");
err = errno;
goto done;
}
CFDictionarySetValue(options, kMKMediaPropertyWritableKey, kCFBooleanTrue);
gpt_ref = MKMediaCreateWithPath(kCFAllocatorDefault, device, options, &err);
CFRelease(options);
if (gpt_ref) {
MKStatus mediaErr = 0;
partition = MKCFBuildPartition(PMGPTTYPE, apple_apfs, CFSTR(EDTVolumeFSType), CFSTR(RAMDISK_FS_SPEC), 0, RAMDISK_BLCK_OFFSET, &mediaErr, NULL);
if (!partition) {
fprintf(stderr, "Failed to create partition with err %d\n", mediaErr);
err = mediaErr;
goto done;
}
partitionArray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
if (!partitionArray) {
fprintf(stderr, "Failed to create partitionArray\n");
err = errno;
CFRelease(partition);
goto done;
}
CFArrayAppendValue(partitionArray, partition);
CFRelease(partition);
CFDictionaryAddValue(layout, CFSTR(MK_PARTITIONS_KEY), partitionArray);
CFRelease(partitionArray);
media = MKCFCreateMedia(&schemes, &mediaErr);
if (!media) {
fprintf(stderr, "Failed to create Schemes with error %d\n", mediaErr);
err = mediaErr;
goto done;
}
map = MKCFCreateMap(PMGPTTYPE, schemes, layout, NULL, NULL, NULL, NULL, NULL, gpt_ref, &mediaErr);
if (!map) {
fprintf(stderr, "Failed to create map with error %d\n", mediaErr);
err = mediaErr;
goto done;
}
err = MKCFWriteMedia(media, layout, NULL, NULL, gpt_ref);
if (err) {
fprintf(stderr, "Failed to WriteMedia with error %d\n", err);
goto done;
}
} else {
fprintf(stderr, "Failed to create gpt_ref with error %d\n", err);
goto done;
}
if (verbose) {
fprintf(stderr, "Releasing MediaKit objects\n");
}
err = 0;
done:
if (media) {
MKCFDisposeMedia(media);
}
if (layout) {
CFRelease(layout);
}
if (gpt_ref) {
CFRelease(gpt_ref);
}
return err;
}
int
construct_apfs_volume(char *mounted_device_name)
{
int return_val = -1;
int status = -1;
char *command[5] = { "/sbin/newfs_apfs", "-v", "Var", mounted_device_name, NULL };
status = run_command(command, NULL, &return_val, NULL);
if ( status >= 0 ) {
return return_val;
} else {
fprintf(stderr, "Failed to execute command %s\n", command[0]);
errno_or_sysexit(errno, -1);
}
return -1;
}
int
unmount_location(char *mount_point)
{
int return_val = -1;
int status = -1;
char *command[4] = { "/sbin/umount", "-f", mount_point, NULL };
status = run_command(command, NULL, &return_val, NULL);
if ( status >= 0 ) {
return return_val;
} else {
fprintf(stderr, "Failed to execute command %s\n", command[0]);
return errno_or_sysexit(errno, -1);
}
}
char*
split_ramdisk_params(char *opts)
{
char* opt = NULL;
char* target_str = NULL;
char* size_tok = RAMDISK_SIZE_TOK;
char* tplt_tok = RAMDISK_TPLT_TOK;
char* optbuf = NULL;
size_t size_tok_len = strlen(size_tok);
size_t tplt_tok_len = strlen(tplt_tok);
optbuf = strdup(opts);
for (opt = optbuf; (opt = strtok(opt, ",")) != NULL; opt = NULL) {
size_t opt_len = strlen(opt);
if ( (opt_len > size_tok_len && !strncmp(size_tok, opt, size_tok_len) ) ||
(opt_len > tplt_tok_len && !strncmp(tplt_tok, opt, tplt_tok_len) ) ) {
size_t start_index = opt - optbuf;
target_str = opts + start_index;
opts[start_index - 1 ] = '\0'; break;
}
}
free(optbuf);
return target_str;
}
char*
parse_parameter_for_token(char * opts, char * search_string)
{
char *return_str = NULL;
char *tmp_str = NULL;
char *target_str = strstr(opts, search_string);
size_t len = strlen(search_string);
if ( target_str && strlen(target_str) > len ) {
tmp_str = target_str + len;
size_t idx = strcspn(tmp_str, ",\0");
if ( idx != 0 && (idx < MAXPATHLEN) ) {
return_str = calloc(1, idx+1); strncpy(return_str, tmp_str, idx);
}
}
return return_str;
}
static int _copyfile_status(int what, int stage, copyfile_state_t state, const char * src, const char * dst, void * ctx) {
if (verbose && stage == COPYFILE_START) {
if (what == COPYFILE_RECURSE_FILE) {
fprintf(stderr, "Copying %s -> %s\n", src, dst);
} else if (what == COPYFILE_RECURSE_DIR) {
fprintf(stderr, "Creating %s/\n", dst);
}
}
return COPYFILE_CONTINUE;
}
int
create_mount_ramdisk(struct fstab *fs, int init_flags, char *options)
{
int default_flags = init_flags;
int mount_return = 0;
char ramdisk_partition [PATH_MAX] = { 0 };
char ramdisk_volume [PATH_MAX] = { 0 };
char ramdisk_container [PATH_MAX] = { 0 };
char seed_location [PATH_MAX] = { 0 };
char *mnt_point = RAMDISK_TMP_MOUNT; char *target_dir = fs->fs_file; size_t ram_size = 0;
if ( verify_file_existence(mnt_point) != 0 ) {
if (verbose) {
fprintf(stderr, "Default mount %s is not available. Using backup %s.\n", mnt_point, RAMDISK_BCK_MOUNT);
}
mnt_point = RAMDISK_BCK_MOUNT;
if ( verify_file_existence(mnt_point) != 0 ) {
fprintf(stderr, "Mountpoints not available. Exiting.\n");
return ENOENT;
}
}
if ( preflight_create_mount_ramdisk(fs->fs_mntops, &ram_size, seed_location) != 0 ) {
fprintf(stderr, "Failed ramdisk preflight. Exiting.\n");
return EINVAL;
}
if ( verbose ) {
fprintf(stdout, "Attaching device of size %zu\n", ram_size);
}
if( attach_device(ram_size, ramdisk_partition) != 0 ){
fprintf(stderr, "Failed to attach the ramdisk.\n");
exit(errno_or_sysexit(ECHILD, -1));
}
if ( verbose ) {
fprintf(stdout, "Creating partition table for device %s \n", ramdisk_partition);
}
if ( create_partition_table(ram_size, ramdisk_partition) !=0 ) {
fprintf(stderr, "Failed to create partition table.\n");
exit(errno_or_sysexit(ECHILD, -1));
}
snprintf(ramdisk_container, sizeof(ramdisk_container), "%ss1", ramdisk_partition);
if ( verbose ) {
fprintf(stdout, "Creating apfs volume on partition %s\n", ramdisk_container);
}
if ( construct_apfs_volume(ramdisk_container) != 0 ) {
fprintf(stderr, "Failed to construct the apfs volume on the ramdisk.\n");
exit(errno_or_sysexit(ECHILD, -1));
}
snprintf(ramdisk_volume, sizeof(ramdisk_volume), "%ss1", ramdisk_container);
if ( verify_file_existence(ramdisk_volume) != 0 ) {
fprintf(stderr, "Failed to verify %s with issue %s\n", ramdisk_volume, strerror(errno));
exit(errno_or_sysexit(errno, -1));
}
if ( verbose ) {
fprintf(stdout, "Mounting to tmp location %s\n", mnt_point);
}
mount_return = mountfs(EDTVolumeFSType, ramdisk_volume, mnt_point, default_flags, NULL, fs->fs_mntops);
if ( mount_return > 0 ) {
fprintf(stderr, "Initial mount to %s failed with %d\n", mnt_point, mount_return);
exit(errno_or_sysexit(errno, -1));
}
copyfile_state_t state = copyfile_state_alloc();
copyfile_state_set(state, COPYFILE_STATE_STATUS_CB, _copyfile_status);
if( copyfile(seed_location, mnt_point, state, COPYFILE_ALL | COPYFILE_RECURSIVE) < 0 ) {
fprintf(stderr, "Failed to copy contents from %s to %s with error: %s\n", seed_location, mnt_point, strerror(errno));
exit(errno_or_sysexit(errno, -1));
}
copyfile_state_free(state);
if( unmount_location(mnt_point) != 0 ){
fprintf(stderr, "Failed to unmount device mounted at %s.\n", mnt_point);
exit(errno_or_sysexit(ECHILD, -1));
}
if( verbose ) {
fprintf(stdout, "Mounting apfs volume %s to %s\n", ramdisk_volume, target_dir);
}
mount_return = mountfs(EDTVolumeFSType, ramdisk_volume, target_dir, default_flags, options, fs->fs_mntops);
if ( mount_return > 0 ) {
fprintf(stderr, "Followup mount to %s failed with %d\n", target_dir, mount_return);
exit(errno_or_sysexit(errno, -1));
}
if ( verbose ) {
print_mount(NULL);
}
return mount_return;
}
#endif // (TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR)
int
mountfs(const char *vfstype, const char *fs_spec, const char *fs_file, int flags,
const char *options, const char *mntopts)
{
static const char *edirs[] = {
_PATH_SBIN,
_PATH_USRSBIN,
NULL
};
static const char *bdirs[] = {
_PATH_FSBNDL,
_PATH_USRFSBNDL,
NULL
};
const char *argv[100], **edir, **bdir;
struct statfs sf;
pid_t pid;
int argc, i, status;
char *optbuf, execname[MAXPATHLEN + 1], mntpath[MAXPATHLEN];
if (realpath(fs_file, mntpath) == NULL) {
if ((passno == NONROOT_PASSNO) &&
(!strncmp(mntpath, PLATFORM_DATA_VOLUME_MOUNT_POINT,
MIN(strlen(mntpath), strlen(PLATFORM_DATA_VOLUME_MOUNT_POINT))))) {
if (mkdir(mntpath, S_IRWXU)) {
warn("mkdir %s", mntpath);
return (errno_or_sysexit(errno , -1));
}
} else {
warn("realpath %s", mntpath);
return (errno_or_sysexit(errno , -1));
}
}
fs_file = mntpath;
if (mntopts == NULL)
mntopts = "";
if (options == NULL) {
if (*mntopts == '\0') {
options = "";
} else {
options = mntopts;
mntopts = "";
}
}
optbuf = catopt(strdup(mntopts), options);
if ((strcmp(fs_file, "/") == 0) && !(flags & MNT_UNION))
flags |= MNT_UPDATE;
if (flags & MNT_FORCE)
optbuf = catopt(optbuf, "force");
if (flags & MNT_RDONLY)
optbuf = catopt(optbuf, "ro");
if (flags & MNT_UPDATE)
optbuf = catopt(optbuf, "update");
if (flags & MNT_DONTBROWSE)
optbuf = catopt(optbuf, "nobrowse");
if (flags & MNT_CPROTECT)
optbuf = catopt(optbuf, "protect");
argc = 0;
argv[argc++] = vfstype;
mangle(optbuf, &argc, argv);
argv[argc++] = fs_spec;
argv[argc++] = fs_file;
argv[argc] = NULL;
if (debug) {
(void)printf("exec: mount_%s", vfstype);
for (i = 1; i < argc; i++)
(void)printf(" %s", argv[i]);
(void)printf("\n");
return (0);
}
switch (pid = fork()) {
case -1:
warn("fork");
free(optbuf);
return (errno_or_sysexit(errno, EX_OSERR));
case 0:
edir = edirs;
do {
(void)snprintf(execname, sizeof(execname),
"%s/mount_%s", *edir, vfstype);
argv[0] = execname;
execv(execname, (char * const *)argv);
if (errno != ENOENT)
warn("exec %s for %s", execname, fs_file);
} while (*++edir != NULL);
bdir = bdirs;
do {
(void)snprintf(execname, sizeof(execname),
"%s/%s.fs/%s/mount_%s", *bdir,
vfstype, _PATH_FSBNDLBIN, vfstype);
argv[0] = execname;
execv(execname, (char * const *)argv);
if (errno != ENOENT)
warn("exec %s for %s", execname, fs_file);
} while (*++bdir != NULL);
if (errno == ENOENT) {
warn("exec %s for %s", execname, fs_file);
return (errno_or_sysexit(errno, EX_OSFILE));
}
exit(errno_or_sysexit(errno , -1));
default:
free(optbuf);
if (waitpid(pid, &status, 0) < 0) {
warn("waitpid");
return (errno_or_sysexit(errno , -1));
}
if (WIFEXITED(status)) {
if (WEXITSTATUS(status) != 0) {
warnx("%s failed with %d", fs_file, WEXITSTATUS(status));
return (errno_or_sysexit(EINTR, WEXITSTATUS(status)));
}
} else if (WIFSIGNALED(status)) {
warnx("%s: %s", fs_file, sys_siglist[WTERMSIG(status)]);
return (errno_or_sysexit(EINTR, EX_UNAVAILABLE));
}
if (verbose) {
if (statfs(fs_file, &sf) < 0) {
warn("statfs %s", fs_file);
return (errno_or_sysexit(errno , -1));
}
prmount(&sf);
}
break;
}
return (EX_OK);
}
static bool
is_sealed(const char *mntpt)
{
struct vol_attr {
uint32_t len;
vol_capabilities_attr_t vol_cap;
} vol_attrs = {};
struct attrlist vol_attr_list = {
.bitmapcount = ATTR_BIT_MAP_COUNT,
.volattr = ATTR_VOL_CAPABILITIES
};
if (!getattrlist(mntpt, &vol_attr_list, &vol_attrs, sizeof(vol_attrs), 0) &&
vol_attrs.vol_cap.valid[VOL_CAPABILITIES_FORMAT] & VOL_CAP_FMT_SEALED) {
return (vol_attrs.vol_cap.capabilities[VOL_CAPABILITIES_FORMAT] & VOL_CAP_FMT_SEALED);
}
return false;
}
void
prmount(sfp)
struct statfs *sfp;
{
int flags;
mountopt_t *o;
struct passwd *pw;
(void)printf("%s on %s (%s", sfp->f_mntfromname, sfp->f_mntonname,
sfp->f_fstypename);
if (is_sealed(sfp->f_mntonname))
(void)printf(", sealed");
flags = sfp->f_flags & MNT_VISFLAGMASK;
for (o = optnames; flags && o->o_opt; o++)
if (flags & o->o_opt) {
(void)printf(", %s", o->o_name);
flags &= ~o->o_opt;
}
if (sfp->f_owner) {
(void)printf(", mounted by ");
if ((pw = getpwuid(sfp->f_owner)) != NULL)
(void)printf("%s", pw->pw_name);
else
(void)printf("%d", sfp->f_owner);
}
(void)printf(")\n");
}
struct statfs *
getmntpt(name)
const char *name;
{
struct statfs *mntbuf;
int i, mntsize;
mntsize = getmntinfo(&mntbuf, MNT_NOWAIT);
for (i = 0; i < mntsize; i++) {
if (strcmp(mntbuf[i].f_mntfromname, name) == 0 ||
strcmp(mntbuf[i].f_mntonname, name) == 0)
return (&mntbuf[i]);
}
return (NULL);
}
char *
catopt(s0, s1)
char *s0;
const char *s1;
{
size_t i;
char *cp;
if (s0 && *s0) {
i = strlen(s0) + strlen(s1) + 1 + 1;
if ((cp = malloc(i)) == NULL)
err(errno_or_sysexit(errno, EX_TEMPFAIL),
"failed to allocate memory for arguments");
(void)snprintf(cp, i, "%s,%s", s0, s1);
} else
cp = strdup(s1);
if (s0)
free(s0);
return (cp);
}
void
mangle(options, argcp, argv)
char *options;
int *argcp;
const char **argv;
{
char *p, *s;
int argc;
argc = *argcp;
for (s = options; (p = strsep(&s, ",")) != NULL;)
if (*p != '\0') {
if (*p == '-') {
argv[argc++] = p;
p = strchr(p, '=');
if (p) {
*p = '\0';
argv[argc++] = p+1;
}
} else {
argv[argc++] = "-o";
argv[argc++] = p;
}
}
*argcp = argc;
}
void
usage()
{
(void)fprintf(stderr,
"usage: mount %s %s\n mount %s\n mount %s\n",
"[-dfruvw] [-o options] [-t external_type]",
"special mount_point",
"[-adfruvw] [-t external_type]",
"[-dfruvw] special | mount_point");
exit(errno_or_sysexit(EINVAL, EX_USAGE));
}