mount.c   [plain text]


/*
 * Copyright (c) 1999-2019 Apple Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */
/*-
 * Copyright (c) 1980, 1989, 1993, 1994
 *	The Regents of the University of California.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */


#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 <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>
#if (TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR)
#include <pthread.h>
#include <paths.h>
#include <spawn.h>
#include <sys/socket.h>
#include <sys/queue.h>
#include <sys/wait.h>
#include <sys/param.h>
#include <sys/cdefs.h>
#include <crt_externs.h>

/* Some APFS specific goop */
#include <copyfile.h>
#include <os/variant_private.h>
#include <APFS/APFS.h>
#include <MediaKit/MKMedia.h>
#include <MediaKit/MKMediaAccess.h>
#include <MediaKit/GPTTypes.h>

#endif

//get the mount flags (shared with vsdbutil)
#include "../mount_flags_dir/mount_flags.h"
#include "../edt_fstab/edt_fstab.h"
#include "pathnames.h"
#include "fsck.h"

#define PLATFORM_DATA_VOLUME_MOUNT_POINT "/System/Volumes/Data"
#define APFS_UTIL_PATH   "/System/Library/Filesystems/apfs.fs/Contents/Resources/apfs.util"

#define environ (*_NSGetEnviron())
#define COMMAND_OUTPUT_MAX 1024

int debug;
int verbose;
int bootstrap_macos = 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 **));
int mountfs __P((const char *, const char *, const char *,
            int, const char *, const char *));
void    prmount __P((struct statfs *));
void    usage __P((void));
void    print_mount(const char **vfslist);
int     ismounted(const char *fs_spec, const char *fs_file, long *flags);

#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 unmount_location(char *mount_point);
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);
int validate_system_devt (void);
dev_t get_devt_for_path (const char *pathname);
void truncate_whitespace(char* str);
int run_command(char **command_argv, char *output, int *rc, int *signal_no);

#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 /* (TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR) */

//pull in the optnames array from the mount_flags.c file
extern mountopt_t optnames[];
static int booted_rosv(void);
static int upgrade_mount (const char *mountpt, int init_flags, char *options);
#if TARGET_OS_OSX
static int booted_apfs(void);
#endif

/*
 * Map a POSIX error code to a representative sysexits(3) code. Can be disabled
 * to exit with errno error code by passing -e as a command line argument to mount
 */
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);
}

dev_t get_devt_for_path(const char *pathname)
{
	struct stat statbuf;
	int flag = 0;

	int err = fstatat(AT_FDCWD, pathname, &statbuf, flag);
	if (err) {
		return 0;
	}
	return statbuf.st_dev;
}

/* Assert that the dev_t for "/" is the same as for "/Applications" */
int validate_system_devt (void)
{
	dev_t system = get_devt_for_path ("/");
	if (system == 0) {
		return 0;
	}

	dev_t data = get_devt_for_path ("/Applications");
	if (data == 0) {
		return 0;
	}

	if (memcmp(&system, &data, sizeof(dev_t)) == 0) {
		return 0;
	}

	return 1;
}

/*
 * mount phases to be used during boot to perform the following operations:
 * first phase:
 *      TARGET_OS_OSX: perform firmlink stitching (ROSV config)
 *      TARGET_OS_IPHONE: mount System and xART volumes (if present)
 *
 * second phase:
 *      TARGET_OS_OSX: upgrade System volume to RW or upgrade Data volume to RW (ROSV config)
 *      TARGET_OS_IPHONE: mount remaining volumes
 */
#define MOUNT_PHASE_1      1       /* first phase */
#define MOUNT_PHASE_2      2       /* second phase */

#define NONFS   "nonfs"

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;
    int passno = 0;
    char *options, *ep;
    int mount_phase = 0;

    all = init_flags = 0;
    options = NULL;
    vfslist = NULL;
    vfstype = NULL;

#if (TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR)
#ifndef MOUNT_INTERNAL
    if (os_variant_has_internal_content(APFS_BUNDLE_ID)) {
        struct stat stats = {0};
        if ((stat(MOUNT_INTERNAL_PATH, &stats) == 0)) {
            char **argv_copy = malloc((argc + 1) * sizeof(*argv_copy));
            if (!argv_copy) err(errno_or_sysexit(errno, EX_TEMPFAIL), NULL);

            argv_copy[0] = MOUNT_INTERNAL_PATH;
            for (int i = 1; i < argc; ++i) {
                argv_copy[i] = argv[i];
            }
            argv_copy[argc] = NULL;

            execv(MOUNT_INTERNAL_PATH, argv_copy);
            errx(errno_or_sysexit(errno, EX_OSERR), "mount_internal exec failed");
        }
    }
#endif
#endif /* (TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR) */

    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':
            /* only allowed to specify 1 or 2 as argument here */
            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();
            /* NOTREACHED */
        }
    argc -= optind;
    argv += optind;

#define BADTYPE(type)                            \
    (strcmp(type, FSTAB_RO) &&                   \
        strcmp(type, FSTAB_RW) && strcmp(type, FSTAB_RQ))

// mount boot tasks
#if TARGET_OS_OSX
    if (mount_phase == MOUNT_PHASE_1) {
        /* apfs.util -B */
        char * const apfs_util_argv[] = {
            APFS_UTIL_PATH,
            "-B",
            NULL,
        };

        if (booted_apfs()) {
            execv(APFS_UTIL_PATH, apfs_util_argv);
            errx(errno_or_sysexit(errno, -1), "apfs.util exec failed");
        } else {
            fprintf(stdout, "Not booted from APFS, skipping apfs.util\n");
            exit(0);
        }
    } else if (mount_phase == MOUNT_PHASE_2) {
        bootstrap_macos = 1;
    }
#else /* !TARGET_OS_OSX */
    if (mount_phase != 0) {
        if (mount_phase == MOUNT_PHASE_1) {
            /* mount -vat -nonfs -R 1 */
            passno = 1;
        } else if (mount_phase == MOUNT_PHASE_2) {
            /* mount -vat -nonfs -R 2 */
            passno = 2;
        }
        verbose = 1;
        all = 1;
        vfslist = makevfslist(NONFS);
        vfstype = NONFS;
    }
#endif /* !TARGET_OS_OSX */

    rval = 0;
    switch (argc) {
    case 0:
        /*
         * Note - mount should never be called with "-a" on OSX
         *        as per fstab(5) - you may insert entries with UUID=,LABEL=
         *        and mount(8) has no knowledge of these entries
         */
        if (all) {
            int err = 0;
            long fs_flags = 0;

            if ((setup_fsent() == 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();
            /*
             * It is a given that the boot container must be present
             * in order to locate the data volume.
             *
             * The data volume is required if it is present in the EDT.
             * This is usually the case when booting the main OS (EDT_OS_ENV_MAIN),
             * however there are some exceptions.
             *
             * When the data volume is required,
             * we defer checking it is present until the second mount-phase,
             * when the data volume is actually mounted, in order to allow
             * MobileObliteration the opportunity to fix the container or fail gracefully.
             */
            if (data_vol_dev) {
                fprintf(stdout, "mount: found boot container: %s, data volume: %s\n",
                        container_dev, data_vol_dev);
            } else if (!needs_data_volume()) {
                fprintf(stdout, "mount: data volume missing, but not required\n");
            } else if ((os_env == EDT_OS_ENV_MAIN) &&
                       (passno == MOUNT_PHASE_2)) {
                errx(errno_or_sysexit(errno ? errno : ENXIO, -1),
                     "mount: missing data volume");
            }
#endif

            while ((fs = get_fsent()) != 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;
                    /* check if already mounted */
                    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;
                /*
                 * We already have a read-only root, skip it.
                 * This is needed to prevent trying to mount-upgrade
                 * the APFS system volume when rooting from a locker
                 */
                if (!strcmp(fs->fs_file, "/") && ro_mount)
                    continue;
                /*
                 * Check if already mounted:
                 *  1) If mounted RW this is either an attempt to
                 *      downgrade (RW -> RO) or someone else already
                 *      mounted this volume as RW.
                 *  2) If mounted RO and not upgrading to RW nothing
                 *      nothing need to be done so we should skip this entry.
                 * Skip this entry in both cases (basically only keep going
                 * if this is an acctual mount RW upgrade).
                 */
                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, "apfs") &&
                           !strcmp(fs->fs_type, FSTAB_RW)) {

                    /*
                     * Perform media keys migration if this is the data volume
                     * of the main OS environment
                     */
                    if (container_dev &&
                        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 /* (TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR) */

                if ((err = mountfs(fs->fs_vfstype, fs->fs_spec,
                                   fs->fs_file, init_flags, options,
                                   fs->fs_mntops)))
                    rval = err;
            }
            end_fsent();
        }
        else if (bootstrap_macos) {
            /*
             * We centralize the logic for dealing with a read-only system volume here.
             * If it is not set up, then default to the old logic of a `mount -uw /`
             * Otherwise prepare the data volume for upgrading to writable.
             */
            const char *mount_point;

            /*
             * set to MNT_UPDATE and ignore MNT_RDONLY bit
             *  Note: We can safely do this boot-task even if we are
             *      already mounted RW (e.g. boot from single user mode).
             *      In that case this will effectively be a no-op. We
             *      only need to avoid downgrading to RO.
             */
            init_flags = MNT_UPDATE;

            /* Check to see if we are running in a ROSV config */
            if (booted_rosv()) {
                /* upgrade mount the data volume writable */
                mount_point = PLATFORM_DATA_VOLUME_MOUNT_POINT;
                init_flags |= MNT_DONTBROWSE;
            }
            else {
                /* upgrade mount "/" read-write */
                mount_point = "/";
            }

            rval = upgrade_mount (mount_point, init_flags, options);

			/* Assert that the unified dev_t "lie" was instantiated */
			if (rval == 0)
			{
				if (validate_system_devt()) {
					/*
					 * If the two mount points don't unify, then
					 * exit with a string that launchd can propagate
					 * on and resolve.
					 */
					abort_with_reason(OS_REASON_LIBSYSTEM, 0, "UNEXPECTED: macOS mount-2 APFS dev_t not unified", 0);
				}
			}
        } 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 = get_fsfile(*argv)) == NULL &&
            (fs = get_fsspec(*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 -t flag has not been specified, and spec contains a ':'
         * then assume that an NFS filesystem is being specified.
         */
        if (vfslist == NULL && strchr(argv[0], ':') != NULL) {
            vfstype = "nfs";
            /* check if already mounted */
            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 we have both a devnode and a pathname, and an update mount was requested,
         * then figure out where the devnode is mounted.  We will need to run
         * an update mount on its path.  It wouldn't make sense to do an
         * update mount on a path other than the one it's already using.
         *
         * XXX: Should we implement the same workaround for updating the
         * root file system at boot time?
         */
        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 update mount not requested, then go with the vfstype and arguments
             * specified.  If no vfstype specified, then error out.
             */
            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();
        /* NOTREACHED */
    }

    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);
    }

    /*
     * Handle the special case of upgrading the root file
     * system from read-only to read/write.  The root file
     * system was originally mounted with a "mount from" name
     * of "root_device".  The getfsfile("/") returns non-
     * NULL at this point, with fs_spec set to the true
     * path to the root device (regardless of what either the real
     * or synthesized /etc/fstab contained).
     */
    mntfromname = mntbuf->f_mntfromname;
    if (strchr(mntfromname, '/') == NULL) {
        fs = get_fsfile(mntbuf->f_mntonname);
        if (fs != NULL)
            mntfromname = fs->fs_spec;
    }

    /* Do the update mount */
    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);
}
#endif

static int
booted_rosv (void) {
    /* use sysctl to query kernel */
    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;
}

// prints currently mounted filesystems
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;
}

/*
 * Helper function that posix_spawn a child process
 * as defined by command_argv[0].
 * If `output` is non-null, then the command's stdout will be read
 * into that buffer.
 * If `rc` is non-null, then the command's return code will be set
 * there.
 * If `signal_no` is non-null, then if the command is signaled, the
 * signal number will be set there.
 *
 *
 * This function returns
 *  -1, if there's an internal error. errno will be set
 *   0, if command exit normally with 0 as return code
 *   1, if command exit abnormally or with a non-zero return code
 */
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 out our side of the pipe
    close(output_pipe[1]);
    output_pipe[1] = -1;

    // If caller specified the output buffer, we'll use that
    // Otherwise allocate a buffer and capture the output ourselves for verbose logging
    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:
    // we don't care much about the errno set by the clean up routine
    // so save the errno here and return to caller
    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;
}

// Helper function that truncates whitespaces
void
truncate_whitespace(char* str)
{
    size_t idx = strcspn(str, " \n");
    if ( idx != 0 ) {
        str[idx] = '\0';
    }
}

// Creates a new unmounted ramdisk of size device_size
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;
}

// Creates the partition table directly through MediaKit.
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;
}

// Triggers newfs_apfs for the target device
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);
    }

    // shouldn't reach here. This is to satisfy the compiler
    return -1;
}

// unmounts device at location
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]);
        errno_or_sysexit(errno, -1);
    }

    // shouldn't reach here. This is to satisfy the compiler
    return -1;
}

// The mnt_opts for fstab are standard across the different
// mount_fs implementations. To create and mount an ephemeral
// filesystem, it is necessary to provide additional non-standard
// values in filesystem definition - mainly size and location of
// the seed files.
// The fstab definition for a ramdisk fs requires two new parameters:
// 'size=%zu' and 'template=%s'. To keep the fstab structure
// consistent with that of other filesystem types, these
// parameters are appended at the end of the mnt_opts string.
// It is necessary to split the mnt_opts into two strings, the
// standard mountfs parameters that are used in the fs-specifnc mount
// and the ramdisk definition parameters.
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 original into two strings.
            break;
        }
    }
    free(optbuf);
    return target_str;
}

// returns string for the parameter after the '=' in the search_string
// part of the special ramdisk parameters
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); //for null terminator
            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;
}


// returns 0 upon success and a valid sysexit or errno code upon failure
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; // intermediate
    char    *target_dir                      =  fs->fs_file; // target
    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));
    }

    // Mount volume to RAMDISK_TMP_MOUNT
    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));
    }

    // ditto contents of RAMDISK_TMP_MOUNT to /private/var
    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);

    // unount RAMDISK_TMP_MOUNT
    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));
    }

    // Verify contents in stdout
    if ( verbose ) {
        print_mount(NULL);
    }

    return mount_return;
}
#endif // (TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR)

// returns 0 upon success and a valid sysexit or errno code upon failure
int
mountfs(vfstype, spec, name, flags, options, mntopts)
    const char *vfstype, *spec, *name, *options, *mntopts;
    int flags;
{
    /* List of directories containing mount_xxx subcommands. */
    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(name, mntpath) == NULL) {
        warn("realpath %s", mntpath);
        return (errno_or_sysexit(errno , -1));
    }

    name = mntpath;

    if (mntopts == NULL)
        mntopts = "";
    if (options == NULL) {
        if (*mntopts == '\0') {
            options = "";
        } else {
            options = mntopts;
            mntopts = "";
        }
    }
    optbuf = catopt(strdup(mntopts), options);

    if ((strcmp(name, "/") == 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");

    argc = 0;
    argv[argc++] = vfstype;
    mangle(optbuf, &argc, argv);
    argv[argc++] = spec;
    argv[argc++] = name;
    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:                /* Error. */
        warn("fork");
        free(optbuf);
        return (errno_or_sysexit(errno, EX_OSERR));
    case 0:                    /* Child. */
        /* Go find an executable. */
        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, name);
        } while (*++edir != NULL);

        bdir = bdirs;
        do {
            /* Special case file system bundle executable path */
            (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, name);
        } while (*++bdir != NULL);

        if (errno == ENOENT) {
            warn("exec %s for %s", execname, name);
            return (errno_or_sysexit(errno, EX_OSFILE));
        }
        exit(errno_or_sysexit(errno , -1));
        /* NOTREACHED */
    default:                /* Parent. */
        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", name, WEXITSTATUS(status));
                return (errno_or_sysexit(EINTR, WEXITSTATUS(status)));
            }
        } else if (WIFSIGNALED(status)) {
            warnx("%s: %s", name, sys_siglist[WTERMSIG(status)]);
            return (errno_or_sysexit(EINTR, EX_UNAVAILABLE));
        }

        if (verbose) {
            if (statfs(name, &sf) < 0) {
                warn("statfs %s", name);
                return (errno_or_sysexit(errno , -1));
            }
            prmount(&sf);
        }
        break;
    }

    return (EX_OK);
}

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);

    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));
}