| /* 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; |
| char *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); |
| 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; |
| TT.negate = no; |
| } else if (!strncmp(p, "opts=", 5)) { |
| p+=5; |
| TT.arr_flag[n] = is_no_prefix(&p) ?FLAG_WITH_NO_PRFX :FLAG_WITHOUT_NO_PRFX; |
| TT.negate = no; |
| } 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) { |
| for (j=0;j<i;j++) free(args[i]); |
| free(args); |
| return; |
| } else { |
| if ((pid = fork()) < 0) { |
| perror_msg(args[0]); |
| for (j=0;j<i;j++) free(args[i]); |
| free(args); |
| 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; |
| |
| errno = 0; |
| if (!c_list) return 0; |
| while ((pid = wait(&status))) { |
| temp = c_list; |
| prev = temp; |
| 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; |
| return; //if CFG_TOYBOX_FREE is not set, exit. |
| } |
| |
| 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; |
| } |
| } |
| } |