#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"
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 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 [-fdnypq] [-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, "dfpqnNyYl:")) != EOF) {
switch (ch) {
case 'd':
debug++;
break;
case 'l':
maximum_running = argtoi('l', "number", optarg, 10);
break;
case 'f':
force_fsck++;
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;
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);
}
ret = checkfstab();
if (returntosingle) {
exit(2);
}
exit(ret);
}
int checkfstab(void) {
int running_status = 0;
int ret;
if (setfsent() == 0) {
fprintf(stderr, "Can't open checklist file: %s\n", _PATH_FSTAB);
return EEXIT;
}
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);
}
(void)endfsent();
return (0);
}
int build_disklist(void) {
struct fstab *fsp = NULL;
int passno = 0;
char *name;
int retval;
int running_status = 0;
for (passno = 1; passno <= 2; passno++) {
if (setfsent() == 0) {
fprintf(stderr, "Can't open checklist file: %s\n", _PATH_FSTAB);
return EEXIT;
}
while ((fsp = getfsent()) != 0) {
if (fs_checkable(fsp) == 0) {
continue;
}
if (preen == 0 || (passno == 1 && fsp->fs_passno == 1)) {
if ((name = blockcheck(fsp->fs_spec)) != 0) {
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[] = "-pdfnyq";
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 (vfstype) {
int exitstatus;
bzero(options, sizeof(options));
snprintf(options, sizeof(options), "-%s%s%s%s%s%s",
(preen) ? "p" : "",
(debug) ? "d" : "",
(force_fsck) ? "f" : "",
(assume_no) ? "n" : "",
(assume_yes) ? "y" : "",
(quick_check) ? "q" : ""
);
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);
}
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, "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_EMBEDDED
#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
hotroot = 0;
if (stat("/", &stslash) < 0) {
perror("/");
printf("Can't stat root\n");
return (origname);
}
newname = origname;
retry:
error = stat(newname, &stblock);
if (error < 0) {
#if TARGET_OS_EMBEDDED
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
perror(newname);
printf("Can't stat %s\n", newname);
return (NULL);
#endif
}
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;
int 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 = malloc(strlen(name) + 1)) == NULL) {
fprintf(stderr, "out of memory");
exit (8);
}
(void)strcpy(part->name, name);
if ((part->fsname = malloc(strlen(fsname) + 1)) == NULL) {
fprintf(stderr, "out of memory");
exit (8);
}
(void)strcpy(part->fsname, fsname);
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);
}