/* * Copyright (c) 2010-2020 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, 1986, 1993 * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fsck.h" #include "../edt_fstab/edt_fstab.h" #if (TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR) #include #define AUTO_BOOT "auto-boot" #endif /* Local Static Functions */ static int argtoi(int flag, char *req, char *str, int base); static void usage(); static int startdiskcheck(disk_t* disk); /* Global Variables */ int preen = 0; /* We're checking all fs'es in 'preen' mode */ int returntosingle = 0; /* Return a non-zero status to prevent multi-user start up */ int hotroot= 0; /* We're checking / (slash or root) */ int fscks_running = 0; /* Number of currently running fs checks */ int ndisks = 0; /* Total number of disks observed */ int debug = 0; /* Output Debugging info */ int force_fsck = 0; /* Force an fsck even if the underlying FS is clean */ int maximum_running = 0; /* Maximum number of sub-processes we'll allow to be spawned */ int quick_check = 0; /* Do a quick check. Quick check returns clean, dirty, or fail */ int live_check = 0; /* Do a live check. This allows fsck to run on a partition which is mounted RW */ int requested_passno = 0; /* only check the filesystems with the specified passno */ /* * The two following globals are mutually exclusive; you cannot assume "yes" and "no." * The last one observed will be the one that is honored. e.g. fsck -fdnyny will result * in assume_yes == 1, and assume_no == 0; */ int assume_no = 0; /* If set, assume a "no" response to repair requests */ int assume_yes = 0; /* If set, assume a "yes" response to repair requests. */ disk_t *disklist = NULL; /* Disk struct with embedded list to enum. all disks */ part_t *badlist = NULL; /* List of partitions which may have had errors */ static int argtoi(int flag, char *req, char *str, int base) { char *cp; int ret; ret = (int)strtol(str, &cp, base); if (cp == str || *cp) errx(EEXIT, "-%c flag requires a %s", flag, req); return (ret); } static void usage(void) { fprintf(stderr, "fsck usage: fsck [-fdnypqL] [-l number]\n"); } #if DEBUG void debug_args (void); void dump_part (part_t* part); void dump_disk (disk_t* disk); void dump_fsp (struct fstab *fsp); void debug_args (void) { if (debug) { printf("debug %d\n", debug); } if (force_fsck) { printf("force_fsck %d\n", force_fsck); } if (assume_no) { printf("assume_no: %d\n", assume_no); } if (assume_yes) { printf("assume_yes: %d\n", assume_yes); } if (preen) { printf("preen: %d\n", preen); } if (quick_check) { printf("quick check %d\n", quick_check); } printf("maximum_running %d\n", maximum_running); } void dump_fsp (struct fstab *fsp) { fprintf (stderr, "**********dumping fstab entry %p**********\n", fsp); fprintf (stderr, "fstab->fs_spec: %s\n", fsp->fs_spec); fprintf (stderr, "fstab->fs_file: %s\n", fsp->fs_file); fprintf (stderr, "fstab->fs_vfstype: %s\n", fsp->fs_vfstype); fprintf (stderr, "fstab->fs_mntops: %s\n", fsp->fs_mntops); fprintf (stderr, "fstab->fs_type: %s\n", fsp->fs_type); fprintf (stderr, "fstab->fs_freq: %d\n", fsp->fs_freq); fprintf (stderr, "fstab->fs_passno: %d\n", fsp->fs_passno); fprintf (stderr, "********** finished dumping fstab entry %p**********\n\n\n", fsp); } void dump_disk (disk_t* disk) { part_t *part; fprintf (stderr, "**********dumping disk entry %p**********\n", disk); fprintf (stderr, "disk->name: %s\n", disk->name); fprintf (stderr, "disk->next: %p\n", disk->next); fprintf (stderr, "disk->part: %p\n", disk->part); fprintf (stderr, "disk->pid: %d\n\n", disk->pid); part = disk->part; if (part) { fprintf(stderr, "dumping partition entries now... \n"); } while (part) { dump_part (part); part = part->next; } fprintf (stderr, "**********done dumping disk entry %p**********\n\n\n", disk); } void dump_part (part_t* part) { fprintf (stderr, "**********dumping partition entry %p**********\n", part); fprintf (stderr, "part->next: %p\n", part->next); fprintf (stderr, "part->name: %s\n", part->name); fprintf (stderr, "part->fsname: %s\n", part->fsname); fprintf (stderr, "part->vfstype: %s\n\n", part->vfstype); fprintf (stderr, "**********done dumping partition entry %p**********\n\n\n", part); } #endif int main (int argc, char** argv) { /* for getopt */ extern char *optarg; extern int optind; int ch; int ret; sync(); while ((ch = getopt(argc, argv, "dfpR:qnNyYl:L")) != EOF) { switch (ch) { case 'd': debug++; break; case 'l': maximum_running = argtoi('l', "number", optarg, 10); break; case 'f': force_fsck++; break; case 'R': requested_passno = argtoi('R', "number", optarg, 10); /* only allowed to specify 1 or 2 as argument here */ if ((requested_passno < ROOT_PASSNO) || (requested_passno > NONROOT_PASSNO)) { usage(); exit(EINVAL); } break; case 'N': case 'n': assume_no = 1; assume_yes = 0; break; case 'p': preen++; break; case 'q': quick_check = 1; break; case 'Y': case 'y': assume_yes = 1; assume_no = 0; break; case 'L': live_check = 1; break; default: errx(EEXIT, "%c option?", ch); break; } } argc -= optind; argv += optind; /* Install our signal handlers */ if (signal(SIGINT, SIG_IGN) != SIG_IGN) { (void)signal(SIGINT, catchsig); } if (preen) { (void)signal(SIGQUIT, catchquit); } if (argc) { /* We do not support any extra arguments at this time */ ret = EINVAL; usage(); exit(ret); } #if (TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR) char arg_val[16]; if (os_parse_boot_arg_string(AUTO_BOOT, arg_val, sizeof(arg_val))) { if (strcmp(arg_val, "false")) { fprintf(stderr, "warning: auto-boot is set to %s\n", arg_val); } } #endif /* (TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR) */ /* * checkfstab does the bulk of work for fsck. It will scan through the * fstab and iterate through the devices, as needed */ ret = checkfstab(); /* Return a non-zero return status so that we'll stay in single-user */ if (returntosingle) { exit(2); } /* Return the error value obtained from checking all filesystems in checkfstab */ exit(ret); } #if (TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR) #include // EDT_OS_ENV_MAIN static int check_boot_container(void) { int error = 0; uint32_t os_env = 0; const char *boot_container = get_boot_container(&os_env); char *rcontainer = NULL; char *container = NULL; if ((os_env != EDT_OS_ENV_MAIN) && (os_env != EDT_OS_ENV_DIAGS)) { fprintf(stdout, "fsck: not booting main or diagnostic OS. Skipping fsck on OS container\n"); return (0); } if (!boot_container) { fprintf(stderr, "fsck: failed to get boot container\n"); return (EEXIT); } /* get a non-const copy */ container = strdup(boot_container); if (!container) { fprintf(stderr, "fsck: failed to copy boot container\n"); return (EEXIT); } /* Take the special device name, and do some cursory checks */ if ((rcontainer = blockcheck(container)) != 0) { /* Construct a temporary disk_t for checkfilesys */ disk_t disk; part_t part; disk.name = NULL; disk.next = NULL; disk.part = ∂ disk.pid = 0; part.next = NULL; part.name = rcontainer; part.vfstype = "apfs"; /* Run the filesystem check against the filesystem in question */ error = checkfilesys(&disk, 0); } free(container); return (error); } #endif /* * This is now the guts of fsck. * * This function will iterate over all of the elements in the fstab and run * fsck-like binaries on all of the filesystems in question if able. The root filesystem * is checked first, and then non-root filesystems are checked in order. */ int checkfstab(void) { int running_status = 0; int ret; /* * fsck boot-task (fsck -q): * iOS - fsck_apfs -q will quick-check the container and volumes. * So no need to obtain and check the fstab entries from EDT, * just check the container. * OSX - See comment in build_disklist(). In short - during early boot * getfsent() will only return a synthetic entry for the root volume ("/") * and additional fstab entries. An invalid entry will fail boot. * To avoid this we require passing the "-R 1" flag to only check * the root volume, which per fsck_apfs behaviour will quick-check the container and * the root volume. We dont need to check the other volumes for boot * (except perhaps for the VM and Data volumes but those are mounted earlier anyway). */ #if (TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR) if (quick_check && (requested_passno == 0)) { return check_boot_container(); } #endif ret = build_disklist (); /* * If we encountered any errors or if 'preen' was off, * then we must have scanned everything. Either way, return. */ if ((ret) || (preen == 0)) { return ret; } if (preen) { /* Otherwise, see if we need to do a cursory fsck against the FS. */ ret = do_diskchecks(); running_status |= ret; } if (running_status) { part_t *part = NULL; if (badlist == NULL) { /* If there were no disk problems, then return the status */ return (running_status); } fprintf(stderr, "THE FOLLOWING FILE SYSTEM%s HAD AN %s\n\t", badlist->next ? "S" : "", "UNEXPECTED INCONSISTENCY:"); for (part = badlist; part; part = part->next) { fprintf(stderr, "%s (%s)%s", part->name, part->fsname, part->next ? ", " : "\n"); } return (running_status); } endfsent(); return (0); } /* * This function builds up the list of disks that fsck will need to * process and check. * * If we're not in 'preen' mode, then we'll go ahead and do the full * check on all of them now. * * If we ARE in 'preen' mode, then we'll just check the root fs, and log * all of the other ones that we encounter by scanning through the fstab * for checking a bit later on. See notes below for checking '/' at boot. */ int build_disklist(void) { struct fstab *fsp = NULL; int passno = 0; char *name; int retval; int running_status = 0; int starting_passno = ROOT_PASSNO; //1 int ending_passno = NONROOT_PASSNO; //2 if (requested_passno) { if (requested_passno == NONROOT_PASSNO) { starting_passno = NONROOT_PASSNO; } else if (requested_passno == ROOT_PASSNO) { ending_passno = ROOT_PASSNO; } } /* * We may need to iterate over the elements in the fstab in non-sequential order. * Thus, we take up to two passes to check all fstab fsck-eligible FSes. The first * pass should focus on the root filesystem, which can be inferred from the fsp->fs_passno * field. The library code used to fill in the fsp structure will specify an * fsp->fs_passno == 1 for the root. All other filesystems should get fsp->fs_passno == 2. * (See fstab manpage for more info.) * * However, note that with the addition of the -R argument to this utility, we might "skip" * one of these two passes. By passing in -R 1 or -R 2, the executor of this utility is * specifying that they only want 'fsck' to run on either the root filesystem (passno == 1) * or the non-root filesystems (passno == 2). */ #if DEBUG fprintf(stderr, "fsck: iterating fstab - starting passno %d, ending passno %d\n", starting_passno, ending_passno); #endif for (passno = starting_passno; passno <= ending_passno; passno++) { /* Open or reset the fstab entry */ if (setfsent() == 0) { fprintf(stderr, "Can't get filesystem checklist: %s\n", strerror(errno)); return EEXIT; } /* Iterate through the fs entries returned from fstab */ while ((fsp = getfsent()) != 0) { /* * Determine if the filesystem is worth checking. Ignore it if it * is not checkable. */ if (fs_checkable(fsp) == 0) { continue; } /* * 'preen' mode is a holdover from the BSD days of long ago. It is semi- * equivalent to a fsck -q, except that it skips filesystems who say that they * are cleanly unmounted and fsck -q will actually call into fsck_hfs to do a * journaling check. * * If preen is off, then we will wind up checking everything in order, so * go ahead and just check this item now. However, if requested_passno is set, then * the caller is asking us to explicitly only check partitions with a certain passno * identifier. * * Otherwise, only work on the root filesystem in the first pass. We can * tell that the fsp represents the root filesystem if fsp->fs_passno == 1. * * NOTE: On Mac OSX, LibInfo, part of Libsystem is in charge of vending us a valid * fstab entry when we're running 'fsck -q' in early boot to ensure the validity of the * boot disk. Since it's before the volume is mounted read-write, getfsent() will probe * the Mach port for directory services. Since it's not up yet, it will determine the * underlying /dev/disk entry for '/' and mechanically construct a fstab entry for / here. * It correctly fills in the passno field below, which will allow us to fork/exec in order * to call fsck_XXX as necessary. * * Once we're booted to multi-user, this block of code shouldn't ever really check anything * unless it's a valid fstab entry because the synthesized fstab entries don't supply a passno * field. Also, they would have to be valid /dev/disk fstab entries as opposed to * UUID or LABEL ones. * * on iOS the above is not true; we rely on the EDT for all fstab entries. */ if ((preen == 0) || (passno == 1 && fsp->fs_passno == 1)) { /* * If the caller specified a -R argument for us to examine only a certain * range of passno fields AND that value does not match our current passno, * then let the loop march on. */ if (requested_passno && (fsp->fs_passno != requested_passno)) { continue; //skip to the next fsent entry. } /* Take the special device name, and do some cursory checks. */ if ((name = blockcheck(fsp->fs_spec)) != 0) { #if TARGET_OS_IPHONE if (!strcmp(name, RAMDISK_FS_SPEC)) { fprintf(stdout, "Encountered ramdisk definition for location %s - will be created during mount.\n", fsp->fs_file); continue; } #endif // TARGET_OS_IPHONE /* Construct a temporary disk_t for checkfilesys */ disk_t disk; part_t part; disk.name = NULL; disk.next = NULL; disk.part = ∂ disk.pid = 0; part.next = NULL; part.name = name; part.vfstype = fsp->fs_vfstype; /* Run the filesystem check against the filesystem in question */ if ((retval = checkfilesys(&disk, 0)) != 0) { return (retval); } } else { fprintf(stderr, "BAD DISK NAME %s\n", fsp->fs_spec); /* * If we get here, then blockcheck failed (returned NULL). * Presumably, we couldn't stat the disk device. In this case, * just bail out because we couldn't even find all of the * entries in the fstab. */ return EEXIT; } } /* * If we get here, then preen must be ON and we're checking a * non-root filesystem. So we must be on the 2nd pass, and * the passno of the element returned from fstab will be > 1. */ else if (passno == 2 && fsp->fs_passno > 1) { /* * If the block device checks tell us the name is bad, mark it in the status * field and continue */ if ((name = blockcheck(fsp->fs_spec)) == NULL) { fprintf(stderr, "BAD DISK NAME %s\n", fsp->fs_spec); running_status |= 8; continue; } /* * Since we haven't actually done anything yet, add this partition * to the list of devices to check later on. */ addpart(name, fsp->fs_file, fsp->fs_vfstype); } } /* If we're not in preen mode, then we scanned everything already. Just bail out */ if (preen == 0) { break; } } return running_status; } /* * This function only runs if we're operating in 'preen' mode. If so, * then iterate over our list of non-root filesystems and fork/exec 'fsck_XXX' * on them to actually do the checking. Spawn up to 'maximum_running' processes. * If 'maximum_running' was not set, then default to the number of disk devices * that we encounter. */ int do_diskchecks(void) { int fsckno = 0; int pid = 0; int exitstatus = 0; int retval = 0; int running_status = 0; disk_t *disk = NULL; disk_t *nextdisk = NULL; /* * If we were not specified a maximum number of FS's to check at once, * or the max exceeded the number of disks we observed, then clip it to * the maximum number of disks. */ if ((maximum_running == 0) || (maximum_running > ndisks)) { maximum_running = ndisks; } nextdisk = disklist; /* Start as many fscks as we will allow */ for (fsckno = 0; fsckno < maximum_running; ++fsckno) { /* * Run the disk check against the disk devices we have seen. * 'fscks_running' is increased for each disk we actually visit. * If we hit an error then sleep for 10 seconds and just try again. */ while ((retval = startdiskcheck(nextdisk)) && fscks_running > 0) { sleep(10); } if (retval) { return (retval); } nextdisk = nextdisk->next; } /* * Once we get limited by 'maximum_running' as to the maximum * number of processes we can spawn at a time, wait until one of our * child processes exits before spawning another one. */ while ((pid = wait(&exitstatus)) != -1) { for (disk = disklist; disk; disk = disk->next) { if (disk->pid == pid) { break; } } if (disk == 0) { /* Couldn't find a new disk to check */ printf("Unknown pid %d\n", pid); continue; } /* Check the WIFEXITED macros */ if (WIFEXITED(exitstatus)) { retval = WEXITSTATUS(exitstatus); } else { retval = 0; } if (WIFSIGNALED(exitstatus)) { printf("%s (%s): EXITED WITH SIGNAL %d\n", disk->part->name, disk->part->fsname, WTERMSIG(exitstatus)); retval = 8; } /* If it hit an error, OR in the value into our running total */ if (retval != 0) { part_t *temp_part = badlist; /* Add the bad partition to the bad partition list */ running_status |= retval; badlist = disk->part; disk->part = disk->part->next; if (temp_part) { badlist->next = temp_part; } } else { /* Just move to the next partition */ part_t *temp_part = disk->part; disk->part = disk->part->next; destroy_part (temp_part); } /* * Reset the pid to 0 since this partition was checked. * Decrease the number of running processes. Decrease the * number of disks if we finish one off. */ disk->pid = 0; fscks_running--; if (disk->part == NULL) { ndisks--; } if (nextdisk == NULL) { if (disk->part) { /* Start the next partition up */ while ((retval = startdiskcheck(disk)) && fscks_running > 0) { sleep(10); } if (retval) { return (retval); } } } else if (fscks_running < maximum_running && fscks_running < ndisks) { /* If there's more room to go, then find the next valid disk */ for ( ;; ) { if ((nextdisk = nextdisk->next) == NULL) { nextdisk = disklist; } if (nextdisk->part != NULL && nextdisk->pid == 0) { break; } } while ((retval = startdiskcheck(nextdisk)) && fscks_running > 0) { sleep(10); } if (retval) { return (retval); } } } return running_status; } /* * fork/exec in order to spawn a process that will eventually * wait4() on the fsck_XXX process. * * Note: The number of forks/execs here is slightly complicated. * We call fork twice, and exec once. The reason we need three total * processes is that the first will continue on as the main line of execution. * This first fork() will create the second process which calls checkfilesys(). * In checkfilesys() we will call fork again, followed by an exec. Observe that * the second process created here will *immediately* call wait4 on the third * process, fsck_XXX. This is so that we can track error dialogs and exit statuses * and tie them to the specific instance of fsck_XXX that created them. Otherwise, * if we just called fork a bunch of times and waited on the first one to finish, * it would be difficult to tell which process exited first, and whether or not the * exit status is meaningful. * * Also note that after we get our result from checkfilesys(), we immediately exit, * so that this process doesn't linger and accidentally continue on. */ static int startdiskcheck(disk_t* disk) { /* * Split this process into the one that will go * call fsck_XX and the one that won't */ disk->pid = fork(); if (disk->pid < 0) { perror("fork"); return (8); } if (disk->pid == 0) { /* * Call checkfilesys. Note the exit() call. Also note that * we pass 1 to checkfilesys since we are a child process */ exit(checkfilesys(disk, 1)); } else { fscks_running++; } return (0); } /* * Call fork/exec in order to spawn instance of fsck_XXX for the filesystem * of the specified vfstype. This will actually spawn the process that does the * checking of the filesystem in question. */ int checkfilesys(disk_t *disk, int child) { #define ARGC_MAX 4 /* cmd-name, options, device, NULL-termination */ part_t *part = disk->part; const char *argv[ARGC_MAX]; int argc; int error = 0; struct stat buf; pid_t pid; int status = 0; char options[] = "-pdfnyql"; /* constant strings are not on the stack */ char progname[NAME_MAX]; char execname[MAXPATHLEN + 1]; char* filesys = part->name; char* vfstype = part->vfstype; if (preen && child) { (void)signal(SIGQUIT, ignore_single_quit); } #if TARGET_OS_IPHONE if (!strcmp(filesys, RAMDISK_FS_SPEC)) { fprintf(stdout, "No need to check filesys for ramdisk, does not exist yet.\n"); return 0; } #endif // TARGET_OS_IPHONE /* * If there was a vfstype specified, then we can go ahead and fork/exec * the child fsck process if the fsck_XXX binary exists. */ if (vfstype) { int exitstatus; /* * Not all options are currently supported by all 5 fsck_* binaries. * Specifically: * udf does not support debug, quick or live * msdos does not support quick or live * exfat does not support live * apfs does not support preen * When the System invokes fsck it is during boot (one of launchd's boot-tasks). * This task is run with the quick and live options. * On iOS we can assume all partitions are APFS or HFS. * On OSX we run this only against the System volume which will always be HFS or APFS */ bzero(options, sizeof(options)); snprintf(options, sizeof(options), "-%s%s%s%s%s%s%s", (preen) ? "p" : "", (debug) ? "d" : "", (force_fsck) ? "f" : "", (assume_no) ? "n" : "", (assume_yes) ? "y" : "", (quick_check) ? "q" : "", (live_check) ? "l" : "" ); argc = 0; snprintf(progname, sizeof(progname), "fsck_%s", vfstype); argv[argc++] = progname; if (strlen(options) > 1) { argv[argc++] = options; } argv[argc++] = filesys; argv[argc] = NULL; /* Create the string to the fsck binary */ (void)snprintf(execname, sizeof(execname), "%s/fsck_%s", _PATH_SBIN, vfstype); /* Check that the binary exists */ error = stat (execname, &buf); if (error != 0) { fprintf(stderr, "Filesystem cannot be checked \n"); return EEXIT; } pid = fork(); switch (pid) { case -1: /* The fork failed. */ fprintf(stderr, "fork failed for %s \n", filesys); if (preen) { fprintf(stderr, "\n%s: UNEXPECTED INCONSISTENCY; RUN fsck MANUALLY.\n", filesys); exit(EEXIT); } status = EEXIT; break; case 0: /* The child */ if (preen) { (void)signal(SIGQUIT, ignore_single_quit); } #if DEBUG printf("exec: %s", execname); for (int i = 1; i < argc; i++) { printf(" %s", argv[i]); } printf("\n"); exit(0); #endif execv(execname, (char * const *)argv); fprintf(stderr, "error attempting to exec %s\n", execname); _exit(8); break; default: /* The parent; child is process "pid" */ waitpid(pid, &exitstatus, 0); if (WIFEXITED(exitstatus)) { status = WEXITSTATUS(exitstatus); } else { status = 0; } if (WIFSIGNALED(exitstatus)) { printf("%s (%s) EXITED WITH SIGNAL %d\n", filesys, vfstype, WTERMSIG(exitstatus)); status = 8; } break; } return status; } else { fprintf(stderr, "Filesystem cannot be checked \n"); return EEXIT; } } /* * When preening, allow a single quit to signal * a special exit after filesystem checks complete * so that reboot sequence may be interrupted. */ void catchquit(int sig) { extern int returntosingle; printf("returning to single-user after filesystem check\n"); returntosingle = 1; (void)signal(SIGQUIT, SIG_DFL); } /* Quit if we catch a signal here. Emit 12 */ void catchsig(int sig) { exit (12); } /* * Determine whether a filesystem should be checked. * * Zero indicates that no check should be performed. */ int fs_checkable(struct fstab *fsp) { /* * APFS, HFS, MSDOS, exfat, and UDF are allowed for now. */ if (strcmp(fsp->fs_vfstype, "apfs") && strcmp(fsp->fs_vfstype, "hfs") && strcmp(fsp->fs_vfstype, "msdos") && strcmp(fsp->fs_vfstype, "exfat") && strcmp(fsp->fs_vfstype, "udf")) { return 0; } /* if not RW and not RO (SW or XX?), ignore it */ if ((strcmp(fsp->fs_type, FSTAB_RW) && strcmp(fsp->fs_type, FSTAB_RO)) || fsp->fs_passno == 0) { return 0; } #define DISKARB_LABEL "LABEL=" #define DISKARB_UUID "UUID=" /* If LABEL or UUID specified, ignore it */ if ((strncmp(fsp->fs_spec, DISKARB_LABEL, strlen(DISKARB_LABEL)) == 0) || (strncmp(fsp->fs_spec, DISKARB_UUID, strlen(DISKARB_UUID)) == 0)) { return 0; } /* Otherwise, it looks fine. Go ahead and check! */ return 1; } /* * Do some cursory checks on the pathname provided to ensure that it's really a block * device. If it is, then generate the raw device name and vend it out. */ char *blockcheck (char *origname) { struct stat stslash; struct stat stblock; struct stat stchar; char *newname; char *raw; int retried = 0; int error = 0; #if (TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR) /* Variables for setting up the kqueue listener*/ #define TIMEOUT_SEC 30l struct kevent kev; struct kevent results; struct timespec ts; int slashdev_fd; int kq = -1; int ct; time_t end; time_t now; #endif // (TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR) hotroot = 0; /* Try to get device info for '/' */ if (stat("/", &stslash) < 0) { perror("/"); /* If we can't get any info on root, then bail out */ printf("Can't stat root\n"); return (origname); } newname = origname; #if TARGET_OS_IPHONE if (!strcmp(newname, RAMDISK_FS_SPEC)) { // Keyword ramdisk fprintf(stdout, "Encountered ramdisk definition. Do not stat\n"); return (newname); } #endif // TARGET_OS_IPHONE retry: /* Poke the block device argument */ error = stat(newname, &stblock); if (error < 0) { #if (TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR) /* * If the device node is not present, set up * a kqueue and wait for up to 30 seconds for it to be * published. */ kq = kqueue(); if (kq < 0) { printf("kqueue: could not create kqueue: %d\n", errno); printf("Can't stat %s\n", newname); return NULL; } slashdev_fd = open(_PATH_DEV, O_RDONLY); EV_SET(&kev, slashdev_fd, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR, NOTE_WRITE, 0, NULL); ct = kevent(kq, &kev, 1, NULL, 0, NULL); if (ct != 0) { printf("kevent() failed to register: %d\n", errno); printf("Can't stat %s\n", newname); /* If we can't register the kqueue, bail out */ close (kq); kq = -1; return NULL; } now = time(NULL); end = now + TIMEOUT_SEC; ts.tv_nsec = 0; while ((now = time(NULL)) < end) { ts.tv_sec = end - now; ct = kevent(kq, NULL, 0, &results, 1, &ts); if (results.flags & EV_ERROR) { /* If we register any errors, bail out */ printf("kevent: registered errors.\n"); error = -1; close (kq); kq = -1; break; } error = stat (newname, &stblock); if (error == 0) { /* found the item. continue on */ if (kq >= 0) { close (kq); } break; } } if (error != 0) { /* Time out. bail out */ if (kq >= 0) { close(kq); } printf("fsck timed out. Can't stat %s\n", newname); return NULL; } #else //(TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR) perror(newname); printf("Can't stat %s\n", newname); return (NULL); #endif // (TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR) } if ((stblock.st_mode & S_IFMT) == S_IFBLK) { /* * If the block device we're checking is the same as '/' then * update hotroot global for debugging. */ if (stslash.st_dev == stblock.st_rdev) { hotroot++; } raw = rawname(newname); if (stat(raw, &stchar) < 0) { perror(raw); printf("Can't stat %s\n", raw); return (origname); } if ((stchar.st_mode & S_IFMT) == S_IFCHR) { return (raw); } else { printf("%s is not a character device\n", raw); return (origname); } } else if ((stblock.st_mode & S_IFMT) == S_IFCHR && !retried) { newname = unrawname(newname); retried++; goto retry; } /* * Not a block or character device, return NULL and * let the user decide what to do. */ return (NULL); } /* * Generate a raw disk device pathname from a normal one. * * For input /dev/disk1s2, generate /dev/rdisk1s2 */ char *rawname(char *name) { static char rawbuf[32]; char *dp; /* * Search for the last '/' in the pathname. * If it's not there, then bail out */ if ((dp = strrchr(name, '/')) == 0) { return (0); } /* * Insert a NULL in the place of the final '/' so that we can * copy everything BEFORE that last '/' into a separate buffer. */ *dp = 0; (void)strlcpy(rawbuf, name, sizeof(rawbuf)); *dp = '/'; /* Now add an /r to our buffer, then copy everything after the final / */ (void)strlcat(rawbuf, "/r", sizeof(rawbuf)); (void)strlcat(rawbuf, &dp[1], sizeof(rawbuf)); return (rawbuf); } /* * Generate a regular disk device name from a raw one. * * For input /dev/rdisk1s2, generate /dev/disk1s2 */ char *unrawname(char *name) { char *dp; struct stat stb; size_t length; /* Find the last '/' in the pathname */ if ((dp = strrchr(name, '/')) == 0) { return (name); } /* Stat the disk device argument */ if (stat(name, &stb) < 0) { return (name); } /* If it's not a character device, error out */ if ((stb.st_mode & S_IFMT) != S_IFCHR) { return (name); } /* If it's not a real raw name, then error out */ if (dp[1] != 'r') { return (name); } length = strlen(&dp[2]); length++; /* to account for trailing NULL */ memmove(&dp[1], &dp[2], length); return (name); } /* * Given a pathname to a disk device, generate the relevant disk_t for that * disk device. It is assumed that this function will be called for each item in the * fstab that needs to get checked. */ disk_t *finddisk (char *pathname) { disk_t *disk; disk_t **dkp; char *tmp; size_t len; /* * Find the disk name. It is assumed that the disk name ends with the * first run of digit(s) in the last component of the path. */ tmp = strrchr(pathname, '/'); /* Find the last component of the path */ if (tmp == NULL) { tmp = pathname; } else { tmp++; } for (; *tmp && !isdigit(*tmp); tmp++) { /* Skip non-digits */ continue; } for (; *tmp && isdigit(*tmp); tmp++){ /* Skip to end of consecutive digits */ continue; } len = tmp - pathname; if (len == 0) { len = strlen(pathname); } /* Iterate through all known disks to see if this item was already seen before */ for (disk = disklist, dkp = &disklist; disk; dkp = &disk->next, disk = disk->next) { if ((strncmp(disk->name, pathname, len) == 0) && (disk->name[len] == 0)) { return (disk); } } /* If not, then allocate a new structure and add it to the end of the list */ if ((*dkp = (disk_t*)malloc(sizeof(disk_t))) == NULL) { fprintf(stderr, "out of memory"); exit (8); } /* Make 'disk' point to the newly allocated structure */ disk = *dkp; if ((disk->name = malloc(len + 1)) == NULL) { fprintf(stderr, "out of memory"); exit (8); } /* copy the name into place */ (void)strncpy(disk->name, pathname, len); disk->name[len] = '\0'; /* Initialize 'part' and 'next' to NULL for now */ disk->part = NULL; disk->next = NULL; disk->pid = 0; /* Increase total number of disks observed */ ndisks++; /* Bubble out either the newly created disk_t or the one we found */ return (disk); } /* * Add this partition to the list of devices to check. */ void addpart(char *name, char *fsname, char *vfstype) { disk_t *disk; part_t *part; part_t **ppt; /* Find the disk_t that corresponds to our element */ disk = finddisk(name); ppt = &(disk->part); /* * Now iterate through all of the partitions of that disk. * If we see our partition name already in there, then it means the entry * was in the fstab more than once, which is bad. */ for (part = disk->part; part; ppt = &part->next, part = part->next) { if (strcmp(part->name, name) == 0) { printf("%s in fstab more than once!\n", name); return; } } /* Hopefully we get here. Allocate a new partition structure for the disk */ if ((*ppt = (part_t*)malloc(sizeof(part_t))) == NULL) { fprintf(stderr, "out of memory"); exit (8); } part = *ppt; if ((part->name = strdup(name)) == NULL) { fprintf(stderr, "out of memory"); exit (8); } /* Add the name & vfs info to the partition struct */ if ((part->fsname = strdup(fsname)) == NULL) { fprintf(stderr, "out of memory"); exit (8); } part->next = NULL; part->vfstype = strdup(vfstype); if (part->vfstype == NULL) { fprintf(stderr, "out of memory"); exit (8); } } /* * Free the partition and its fields. */ void destroy_part (part_t *part) { if (part->name) { free (part->name); } if (part->fsname) { free (part->fsname); } if (part->vfstype) { free (part->vfstype); } free (part); } /* * Ignore a single quit signal; wait and flush just in case. * Used by child processes in preen mode. */ void ignore_single_quit(int sig) { sleep(1); (void)signal(SIGQUIT, SIG_IGN); (void)signal(SIGQUIT, SIG_DFL); }