#include <fstab.h>
#include <err.h>
#include <errno.h>
#include <sys/param.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <stdarg.h>
#include <stdio.h>
#include <ctype.h>
#include <dirent.h>
#include <fcntl.h>
#include <paths.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/event.h>
#include <sys/time.h>
#include <signal.h>
#include <TargetConditionals.h>
#include "fsck.h"
#include "../edt_fstab/edt_fstab.h"
#if (TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR)
#include <os/bsd.h>
#define AUTO_BOOT "auto-boot"
#endif
static int argtoi(int flag, char *req, char *str, int base);
static void usage();
static int startdiskcheck(disk_t* disk);
int preen = 0;
int returntosingle = 0;
int hotroot= 0;
int fscks_running = 0;
int ndisks = 0;
int debug = 0;
int force_fsck = 0;
int maximum_running = 0;
int quick_check = 0;
int live_check = 0;
int requested_passno = 0;
int assume_no = 0;
int assume_yes = 0;
disk_t *disklist = NULL;
part_t *badlist = NULL;
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) {
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);
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;
if (signal(SIGINT, SIG_IGN) != SIG_IGN) {
(void)signal(SIGINT, catchsig);
}
if (preen) {
(void)signal(SIGQUIT, catchquit);
}
if (argc) {
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
ret = checkfstab();
if (returntosingle) {
exit(2);
}
exit(ret);
}
#if (TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR)
#include <APFS/APFSConstants.h> // 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);
}
container = strdup(boot_container);
if (!container) {
fprintf(stderr, "fsck: failed to copy boot container\n");
return (EEXIT);
}
if ((rcontainer = blockcheck(container)) != 0) {
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";
error = checkfilesys(&disk, 0);
}
free(container);
return (error);
}
#endif
int checkfstab(void) {
int running_status = 0;
int ret;
#if (TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR)
if (quick_check && (requested_passno == 0)) {
return check_boot_container();
}
#endif
ret = build_disklist ();
if ((ret) || (preen == 0)) {
return ret;
}
if (preen) {
ret = do_diskchecks();
running_status |= ret;
}
if (running_status) {
part_t *part = NULL;
if (badlist == NULL) {
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);
}
int build_disklist(void) {
struct fstab *fsp = NULL;
int passno = 0;
char *name;
int retval;
int running_status = 0;
int starting_passno = ROOT_PASSNO; int ending_passno = NONROOT_PASSNO;
if (requested_passno) {
if (requested_passno == NONROOT_PASSNO) {
starting_passno = NONROOT_PASSNO;
}
else if (requested_passno == ROOT_PASSNO) {
ending_passno = ROOT_PASSNO;
}
}
#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++) {
if (setfsent() == 0) {
fprintf(stderr, "Can't get filesystem checklist: %s\n", strerror(errno));
return EEXIT;
}
while ((fsp = getfsent()) != 0) {
if (fs_checkable(fsp) == 0) {
continue;
}
if ((preen == 0) || (passno == 1 && fsp->fs_passno == 1)) {
if (requested_passno && (fsp->fs_passno != requested_passno)) {
continue; }
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
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;
if ((retval = checkfilesys(&disk, 0)) != 0) {
return (retval);
}
}
else {
fprintf(stderr, "BAD DISK NAME %s\n", fsp->fs_spec);
return EEXIT;
}
}
else if (passno == 2 && fsp->fs_passno > 1) {
if ((name = blockcheck(fsp->fs_spec)) == NULL) {
fprintf(stderr, "BAD DISK NAME %s\n", fsp->fs_spec);
running_status |= 8;
continue;
}
addpart(name, fsp->fs_file, fsp->fs_vfstype);
}
}
if (preen == 0) {
break;
}
}
return running_status;
}
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 ((maximum_running == 0) || (maximum_running > ndisks)) {
maximum_running = ndisks;
}
nextdisk = disklist;
for (fsckno = 0; fsckno < maximum_running; ++fsckno) {
while ((retval = startdiskcheck(nextdisk)) && fscks_running > 0) {
sleep(10);
}
if (retval) {
return (retval);
}
nextdisk = nextdisk->next;
}
while ((pid = wait(&exitstatus)) != -1) {
for (disk = disklist; disk; disk = disk->next) {
if (disk->pid == pid) {
break;
}
}
if (disk == 0) {
printf("Unknown pid %d\n", pid);
continue;
}
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 (retval != 0) {
part_t *temp_part = badlist;
running_status |= retval;
badlist = disk->part;
disk->part = disk->part->next;
if (temp_part) {
badlist->next = temp_part;
}
} else {
part_t *temp_part = disk->part;
disk->part = disk->part->next;
destroy_part (temp_part);
}
disk->pid = 0;
fscks_running--;
if (disk->part == NULL) {
ndisks--;
}
if (nextdisk == NULL) {
if (disk->part) {
while ((retval = startdiskcheck(disk)) && fscks_running > 0) {
sleep(10);
}
if (retval) {
return (retval);
}
}
}
else if (fscks_running < maximum_running && fscks_running < ndisks) {
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;
}
static
int startdiskcheck(disk_t* disk) {
disk->pid = fork();
if (disk->pid < 0) {
perror("fork");
return (8);
}
if (disk->pid == 0) {
exit(checkfilesys(disk, 1));
}
else {
fscks_running++;
}
return (0);
}
int checkfilesys(disk_t *disk, int child) {
#define ARGC_MAX 4
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";
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 (vfstype) {
int exitstatus;
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;
(void)snprintf(execname, sizeof(execname), "%s/fsck_%s", _PATH_SBIN, vfstype);
error = stat (execname, &buf);
if (error != 0) {
fprintf(stderr, "Filesystem cannot be checked \n");
return EEXIT;
}
pid = fork();
switch (pid) {
case -1:
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:
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:
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;
}
}
void catchquit(int sig) {
extern int returntosingle;
printf("returning to single-user after filesystem check\n");
returntosingle = 1;
(void)signal(SIGQUIT, SIG_DFL);
}
void catchsig(int sig) {
exit (12);
}
int fs_checkable(struct fstab *fsp) {
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 ((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 ((strncmp(fsp->fs_spec, DISKARB_LABEL, strlen(DISKARB_LABEL)) == 0)
|| (strncmp(fsp->fs_spec, DISKARB_UUID, strlen(DISKARB_UUID)) == 0)) {
return 0;
}
return 1;
}
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)
#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;
if (stat("/", &stslash) < 0) {
perror("/");
printf("Can't stat root\n");
return (origname);
}
newname = origname;
#if TARGET_OS_IPHONE
if (!strcmp(newname, RAMDISK_FS_SPEC)) {
fprintf(stdout, "Encountered ramdisk definition. Do not stat\n");
return (newname);
}
#endif // TARGET_OS_IPHONE
retry:
error = stat(newname, &stblock);
if (error < 0) {
#if (TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR)
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);
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) {
printf("kevent: registered errors.\n");
error = -1;
close (kq);
kq = -1;
break;
}
error = stat (newname, &stblock);
if (error == 0) {
if (kq >= 0) {
close (kq);
}
break;
}
}
if (error != 0) {
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 (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;
}
return (NULL);
}
char *rawname(char *name) {
static char rawbuf[32];
char *dp;
if ((dp = strrchr(name, '/')) == 0) {
return (0);
}
*dp = 0;
(void)strlcpy(rawbuf, name, sizeof(rawbuf));
*dp = '/';
(void)strlcat(rawbuf, "/r", sizeof(rawbuf));
(void)strlcat(rawbuf, &dp[1], sizeof(rawbuf));
return (rawbuf);
}
char *unrawname(char *name) {
char *dp;
struct stat stb;
size_t length;
if ((dp = strrchr(name, '/')) == 0) {
return (name);
}
if (stat(name, &stb) < 0) {
return (name);
}
if ((stb.st_mode & S_IFMT) != S_IFCHR) {
return (name);
}
if (dp[1] != 'r') {
return (name);
}
length = strlen(&dp[2]);
length++;
memmove(&dp[1], &dp[2], length);
return (name);
}
disk_t *finddisk (char *pathname) {
disk_t *disk;
disk_t **dkp;
char *tmp;
size_t len;
tmp = strrchr(pathname, '/');
if (tmp == NULL) {
tmp = pathname;
}
else {
tmp++;
}
for (; *tmp && !isdigit(*tmp); tmp++) {
continue;
}
for (; *tmp && isdigit(*tmp); tmp++){
continue;
}
len = tmp - pathname;
if (len == 0) {
len = strlen(pathname);
}
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 ((*dkp = (disk_t*)malloc(sizeof(disk_t))) == NULL) {
fprintf(stderr, "out of memory");
exit (8);
}
disk = *dkp;
if ((disk->name = malloc(len + 1)) == NULL) {
fprintf(stderr, "out of memory");
exit (8);
}
(void)strncpy(disk->name, pathname, len);
disk->name[len] = '\0';
disk->part = NULL;
disk->next = NULL;
disk->pid = 0;
ndisks++;
return (disk);
}
void addpart(char *name, char *fsname, char *vfstype) {
disk_t *disk;
part_t *part;
part_t **ppt;
disk = finddisk(name);
ppt = &(disk->part);
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;
}
}
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);
}
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);
}
}
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);
}
void
ignore_single_quit(int sig) {
sleep(1);
(void)signal(SIGQUIT, SIG_IGN);
(void)signal(SIGQUIT, SIG_DFL);
}