/* 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;
}
}
}