/* fsck.c - check and repair a Linux filesystem * * Copyright 2013 Sandeep Sharma <sandeep.jack2756@gmail.com> * Copyright 2013 Kyungwan Han <asura321@gmail.com> USE_FSCK(NEWTOY(fsck, "?t:ANPRTVsC#", TOYFLAG_USR|TOYFLAG_BIN)) config FSCK bool "fsck" default n help usage: fsck [-ANPRTV] [-C FD] [-t FSTYPE] [FS_OPTS] [BLOCKDEV]... Check and repair filesystems -A Walk /etc/fstab and check all filesystems -N Don't execute, just show what would be done -P With -A, check filesystems in parallel -R With -A, skip the root filesystem -T Don't show title on startup -V Verbose -C n Write status information to specified filedescriptor -t TYPE List of filesystem types to check */ #define FOR_fsck #include "toys.h" #include <mntent.h> #define FLAG_WITHOUT_NO_PRFX 1 #define FLAG_WITH_NO_PRFX 2 #define FLAG_DONE 1 GLOBALS( int fd_num; char *t_list; struct double_list *devices; int *arr_flag; char **arr_type; int negate; int sum_status; int nr_run; int sig_num; long max_nr_run; ) struct f_sys_info { char *device, *mountpt, *type, *opts; int passno, flag; struct f_sys_info *next; }; struct child_list { struct child_list *next; pid_t pid; char *prog_name, *dev_name; }; static struct f_sys_info *filesys_info = NULL; //fstab entry list static struct child_list *c_list = NULL; //fsck.type child list. static void kill_all(void) { struct child_list *child; for (child = c_list; child; child = child->next) kill(child->pid, SIGTERM); _exit(0); } static long strtol_range(char *str, int min, int max) { char *endptr = NULL; errno = 0; long ret_value = strtol(str, &endptr, 10); if(errno) perror_exit("Invalid num %s", str); else if(endptr && (*endptr != '\0' || endptr == str)) perror_exit("Not a valid num %s", str); if(ret_value >= min && ret_value <= max) return ret_value; else perror_exit("Number %s is not in valid [%d-%d] Range", str, min, max); } //create fstab entries list. static struct f_sys_info* create_db(struct mntent *f_info) { struct f_sys_info *temp = filesys_info; if (temp) { while (temp->next) temp = temp->next; temp->next = xzalloc(sizeof(struct f_sys_info)); temp = temp->next; } else filesys_info = temp = xzalloc(sizeof(struct f_sys_info)); temp->device = xstrdup(f_info->mnt_fsname); temp->mountpt = xstrdup(f_info->mnt_dir); if (strchr(f_info->mnt_type, ',')) temp->type = xstrdup("auto"); else temp->type = xstrdup(f_info->mnt_type); temp->opts = xstrdup(f_info->mnt_opts); temp->passno = f_info->mnt_passno; return temp; } //is we have 'no' or ! before type. static int is_no_prefix(char **p) { int no = 0; if ((*p[0] == 'n' && *(*p + 1) == 'o')) no = 2; else if (*p[0] == '!') no = 1; *p += no; return ((no) ? 1 :0); } static void fix_tlist(void) { char *p, *s = TT.t_list; int n = 1, no; while ((s = strchr(s, ','))) { s++; n++; } TT.arr_flag = xzalloc((n + 1) * sizeof(char)); TT.arr_type = xzalloc((n + 1) * sizeof(char *)); s = TT.t_list; n = 0; while ((p = strsep(&s, ","))) { no = is_no_prefix(&p); if (!strcmp(p, "loop")) TT.arr_flag[n] = no ? FLAG_WITH_NO_PRFX :FLAG_WITHOUT_NO_PRFX; else if (!strncmp(p, "opts=", 5)) { p+=5; TT.arr_flag[n] = is_no_prefix(&p) ?FLAG_WITH_NO_PRFX :FLAG_WITHOUT_NO_PRFX; } else { if (!n) TT.negate = no; if (n && TT.negate != no) error_exit("either all or none of the filesystem" " types passed to -t must be prefixed with 'no' or '!'"); } TT.arr_type[n++] = p; } } //ignore these types... static int ignore_type(char *type) { int i = 0; char *str; char *ignored_types[] = { "ignore","iso9660", "nfs","proc", "sw","swap", "tmpfs","devpts",NULL }; while ((str = ignored_types[i++])) { if (!strcmp(str, type)) return 1; } return 0; } // return true if has to ignore the filesystem. static int to_be_ignored(struct f_sys_info *finfo) { int i, ret = 0, type_present = 0; if (!finfo->passno) return 1; //Ignore with pass num = 0 if (TT.arr_type) { for (i = 0; TT.arr_type[i]; i++) { if (!TT.arr_flag[i]) { //it is type of filesys. type_present = 2; if (!strcmp(TT.arr_type[i], finfo->type)) ret = 0; else ret = 1; } else if (TT.arr_flag[i] == FLAG_WITH_NO_PRFX) { //it is option of filesys if (hasmntopt((const struct mntent *)finfo, TT.arr_type[i])) return 1; } else { //FLAG_WITHOUT_NO_PRFX if (!hasmntopt((const struct mntent *)finfo, TT.arr_type[i])) return 1; } } } if (ignore_type(finfo->type)) return 1; if (TT.arr_type && type_present != 2) return 0; return ((TT.negate) ? !ret : ret); } // find type and execute corresponding fsck.type prog. static void do_fsck(struct f_sys_info *finfo) { struct child_list *child; char **args; char *type; pid_t pid; int i = 1, j = 0; if (strcmp(finfo->type, "auto")) type = finfo->type; else if (TT.t_list && (TT.t_list[0] != 'n' || TT.t_list[1] != 'o' || TT.t_list[0] != '!') && strncmp(TT.t_list, "opts=", 5) && strncmp(TT.t_list , "loop", 4) && !TT.arr_type[1]) type = TT.t_list; //one file sys at cmdline else type = "auto"; args = xzalloc((toys.optc + 2 + 1 + 1) * sizeof(char*)); //+1, for NULL, +1 if -C args[0] = xmprintf("fsck.%s", type); if(toys.optflags & FLAG_C) args[i++] = xmprintf("%s %d","-C", TT.fd_num); while(toys.optargs[j]) { if(*toys.optargs[j]) args[i++] = xstrdup(toys.optargs[j]); j++; } args[i] = finfo->device; TT.nr_run++; if ((toys.optflags & FLAG_V) || (toys.optflags & FLAG_N)) { printf("[%s (%d) -- %s]", args[0], TT.nr_run, finfo->mountpt ? finfo->mountpt : finfo->device); for (i = 0; args[i]; i++) xprintf(" %s", args[i]); xputc('\n'); } if (toys.optflags & FLAG_N) return; else { if ((pid = fork()) < 0) { perror_msg(args[0]); return; } if (!pid) xexec(args); //child, executes fsck.type } child = xzalloc(sizeof(struct child_list)); //Parent, add to child list. child->dev_name = xstrdup(finfo->device); child->prog_name = args[0]; child->pid = pid; if (c_list) { child->next = c_list; c_list = child; } else { c_list = child; child->next =NULL; } } // for_all = 1; wait for all child to exit // for_all = 0; wait for any one to exit static int wait_for(int for_all) { pid_t pid; int status = 0, child_exited; struct child_list *prev, *temp = c_list; prev = temp; errno = 0; if (!c_list) return 0; while ((pid = wait(&status))) { if (TT.sig_num) kill_all(); child_exited = 0; if (pid < 0) { if (errno == EINTR) continue; else if (errno == ECHILD) break; //No child to wait, break and return status. else perror_exit("option arg Invalid\n"); //paranoid. } while (temp) { if (temp->pid == pid) { child_exited = 1; break; } prev = temp; temp = temp->next; } if (child_exited) { if (WIFEXITED(status)) TT.sum_status |= WEXITSTATUS(status); else if (WIFSIGNALED(status)) { TT.sum_status |= 4; //Uncorrected. if (WTERMSIG(status) != SIGINT) perror_msg("child Term. by sig: %d\n",(WTERMSIG(status))); TT.sum_status |= 8; //Operatinal error } else { TT.sum_status |= 4; //Uncorrected. perror_msg("%s %s: status is %x, should never happen\n", temp->prog_name, temp->dev_name, status); } TT.nr_run--; if (prev == temp) c_list = c_list->next; //first node else prev->next = temp->next; free(temp->prog_name); free(temp->dev_name); free(temp); if (!for_all) break; } } return TT.sum_status; } //scan all the fstab entries or -t matches with fstab. static int scan_all(void) { struct f_sys_info *finfo = filesys_info; int ret = 0, passno; if (toys.optflags & FLAG_V) xprintf("Checking all filesystem\n"); while (finfo) { if (to_be_ignored(finfo)) finfo->flag |= FLAG_DONE; finfo = finfo->next; } finfo = filesys_info; if (!(toys.optflags & FLAG_P)) { while (finfo) { if (!strcmp(finfo->mountpt, "/")) { // man says: check / in parallel with others if -P is absent. if ((toys.optflags & FLAG_R) || to_be_ignored(finfo)) { finfo->flag |= FLAG_DONE; break; } else { do_fsck(finfo); finfo->flag |= FLAG_DONE; if (TT.sig_num) kill_all(); if ((ret |= wait_for(1)) > 4) return ret; //destruction in filesys. break; } } finfo = finfo->next; } } if (toys.optflags & FLAG_R) { // with -PR we choose to skip root. for (finfo = filesys_info; finfo; finfo = finfo->next) { if(!strcmp(finfo->mountpt, "/")) finfo->flag |= FLAG_DONE; } } passno = 1; while (1) { for (finfo = filesys_info; finfo; finfo = finfo->next) if (!finfo->flag) break; if (!finfo) break; for (finfo = filesys_info; finfo; finfo = finfo->next) { if (finfo->flag) continue; if (finfo->passno == passno) { do_fsck(finfo); finfo->flag |= FLAG_DONE; if ((toys.optflags & FLAG_s) || (TT.nr_run && (TT.nr_run >= TT.max_nr_run))) ret |= wait_for(0); } } if (TT.sig_num) kill_all(); ret |= wait_for(1); passno++; } return ret; } void record_sig_num(int sig) { TT.sig_num = sig; } void fsck_main(void) { struct mntent mt; struct double_list *dev; struct f_sys_info *finfo; FILE *fp; char *tmp, **arg = toys.optargs; sigatexit(record_sig_num); while (*arg) { if ((**arg == '/') || strchr(*arg, '=')) { dlist_add(&TT.devices, xstrdup(*arg)); **arg = '\0'; } arg++; } if (toys.optflags & FLAG_t) fix_tlist(); if (!(tmp = getenv("FSTAB_FILE"))) tmp = "/etc/fstab"; if (!(fp = setmntent(tmp, "r"))) perror_exit("setmntent failed:"); while (getmntent_r(fp, &mt, toybuf, 4096)) create_db(&mt); endmntent(fp); if (!(toys.optflags & FLAG_T)) xprintf("fsck ----- (Toybox)\n"); if ((tmp = getenv("FSCK_MAX_INST"))) TT.max_nr_run = strtol_range(tmp, 0, INT_MAX); if (!TT.devices || (toys.optflags & FLAG_A)) { toys.exitval = scan_all(); if (CFG_TOYBOX_FREE) goto free_all; } dev = TT.devices; dev->prev->next = NULL; //break double list to traverse. for (; dev; dev = dev->next) { for (finfo = filesys_info; finfo; finfo = finfo->next) if (!strcmp(finfo->device, dev->data) || !strcmp(finfo->mountpt, dev->data)) break; if (!finfo) { //if not present, fill def values. mt.mnt_fsname = dev->data; mt.mnt_dir = ""; mt.mnt_type = "auto"; mt.mnt_opts = ""; mt.mnt_passno = -1; finfo = create_db(&mt); } do_fsck(finfo); finfo->flag |= FLAG_DONE; if ((toys.optflags & FLAG_s) || (TT.nr_run && (TT.nr_run >= TT.max_nr_run))) toys.exitval |= wait_for(0); } if (TT.sig_num) kill_all(); toys.exitval |= wait_for(1); finfo = filesys_info; free_all: if (CFG_TOYBOX_FREE) { struct f_sys_info *finfo, *temp; llist_traverse(TT.devices, llist_free_double); free(TT.arr_type); free(TT.arr_flag); for (finfo = filesys_info; finfo;) { temp = finfo->next; free(finfo->device); free(finfo->mountpt); free(finfo->type); free(finfo->opts); free(finfo); finfo = temp; } } }