/* find - look for files satisfying a predicate Author: E. Baalbergen */ /* Original author: Erik Baalbergen; POSIX compliant version: Bert Laverman */ #include #include #include #include #include #include #include #include #include #include #include #include #include /*######################## DEFINITIONS ##############################*/ #ifdef S_IFLNK #define LSTAT lstat #else #define LSTAT stat #endif #define SHELL "/bin/sh" #define MAXARG 256 /* maximum length for an argv for -exec */ #define BSIZE 512 /* POSIX wants 512 byte blocks */ #define SECS_PER_DAY (24L*60L*60L) /* check your planet */ #define OP_NAME 1 /* match name */ #define OP_PERM 2 /* check file permission bits */ #define OP_TYPE 3 /* check file type bits */ #define OP_LINKS 4 /* check link count */ #define OP_USER 5 /* check owner */ #define OP_GROUP 6 /* check group ownership */ #define OP_SIZE 7 /* check size, blocks or bytes */ #define OP_SIZEC 8 /* this is a fake for -size with 'c' */ #define OP_INUM 9 /* compare inode number */ #define OP_ATIME 10 /* check last access time */ #define OP_CTIME 11 /* check creation time */ #define OP_MTIME 12 /* check last modification time */ #define OP_EXEC 13 /* execute command */ #define OP_OK 14 /* execute with confirmation */ #define OP_PRINT 15 /* print name */ #define OP_PRINT0 16 /* print name null terminated */ #define OP_NEWER 17 /* compare modification times */ #define OP_CNEWER 18 /* compare modification times */ #define OP_AND 19 /* logical and (short circuit) */ #define OP_OR 20 /* logical or (short circuit) */ #define OP_XDEV 21 /* do not cross file-system boundaries */ #define OP_DEPTH 22 /* descend directory before testing */ #define OP_PRUNE 23 /* don't descend into current directory */ #define OP_NOUSER 24 /* check validity of user id */ #define OP_NOGROUP 25 /* check validity of group id */ #define LPAR 26 /* left parenthesis */ #define RPAR 27 /* right parenthesis */ #define NOT 28 /* logical not */ /* Some return values: */ #define EOI -1 /* end of expression */ #define NONE 0 /* not a valid predicate */ /* For -perm with symbolic modes: */ #define ISWHO(c) ((c == 'u') || (c == 'g') || (c == 'o') || (c == 'a')) #define ISOPER(c) ((c == '-') || (c == '=') || (c == '+')) #define ISMODE(c) ((c == 'r') || (c == 'w') || (c == 'x') || \ (c == 's') || (c == 't')) #define MUSER 1 #define MGROUP 2 #define MOTHERS 4 struct exec { int e_cnt; char *e_vec[MAXARG]; }; struct node { int n_type; /* any OP_ or NOT */ union { char *n_str; struct { long n_val; int n_sign; } n_int; struct exec *n_exec; struct { struct node *n_left, *n_right; } n_opnd; } n_info; }; struct oper { char *op_str; int op_val; } ops[] = { { "name", OP_NAME }, { "perm", OP_PERM }, { "type", OP_TYPE }, { "links", OP_LINKS }, { "user", OP_USER }, { "group", OP_GROUP }, { "size", OP_SIZE }, { "inum", OP_INUM }, { "atime", OP_ATIME }, { "ctime", OP_CTIME }, { "mtime", OP_MTIME }, { "exec", OP_EXEC }, { "ok", OP_OK }, { "print", OP_PRINT }, { "print0", OP_PRINT0 }, { "newer", OP_NEWER }, { "cnewer", OP_CNEWER }, { "a", OP_AND }, { "o", OP_OR }, { "xdev", OP_XDEV }, { "depth", OP_DEPTH }, { "prune", OP_PRUNE }, { "nouser", OP_NOUSER }, { "nogroup", OP_NOGROUP }, { 0, 0 } }; char **ipp; /* pointer to next argument during parsing */ char *prog; /* program name (== argv [0]) */ char *epath; /* value of PATH environment string */ long current_time; /* for computing age */ int tty; /* fd for /dev/tty when using -ok */ int xdev_flag = 0; /* cross device boundaries? */ int devnr; /* device nr of first inode */ int depth_flag = 0; /* descend before check? */ int prune_here; /* This is Baaaad! Don't ever do this again! */ int um; /* current umask() */ int needprint = 1; /* implicit -print needed? */ /* The prototypes: */ _PROTOTYPE(int main, (int argc, char **argv)); _PROTOTYPE(char *Malloc, (int n)); _PROTOTYPE(char *Salloc, (char *s)); _PROTOTYPE(void find, (char *path, struct node * pred, char *last)); _PROTOTYPE(int check, (char *path, struct stat * st, struct node * n, char *last)); _PROTOTYPE(int ichk, (long val, struct node * n)); _PROTOTYPE(int lex, (char *str)); _PROTOTYPE(struct node * newnode, (int t)); _PROTOTYPE(int isnumber, (char *str, int base, int sign)); _PROTOTYPE(void number, (char *str, int base, long *pl, int *ps)); _PROTOTYPE(void fmode, (char *str, long *pl, int *ps)); _PROTOTYPE(struct node * expr, (int t)); _PROTOTYPE(struct node * primary, (int t)); _PROTOTYPE(struct node * secondary, (int t)); _PROTOTYPE(void checkarg, (char *arg)); _PROTOTYPE(struct node * simple, (int t)); _PROTOTYPE(void nonfatal, (char *s1, char *s2)); _PROTOTYPE(void fatal, (char *s1, char *s2)); _PROTOTYPE(int smatch, (char *s, char *t)); _PROTOTYPE(char *find_bin, (char *s)); _PROTOTYPE(int execute, (int op, struct exec * e, char *path)); _PROTOTYPE(void domode, (int op, int *mode, int bits)); /* Malloc: a certified malloc */ char *Malloc(n) int n; { char *m; if ((m = (char *) malloc(n)) == (char *) NULL) fatal("out of memory", ""); return m; } /* Salloc: allocate space for a string */ char *Salloc(s) char *s; { return strcpy(Malloc(strlen(s) + 1), s); } /* Main: the main body */ int main(argc, argv) int argc; char *argv[]; { char **pathlist, *path, *last; int pathcnt = 0, i; struct node *pred; prog = *argv++; /* set program name (for diagnostics) */ if ((epath = getenv("PATH")) == (char *) NULL) fatal("Can't get path from environment", ""); (void) umask(um = umask(0)); /* non-destructive get-umask :-) */ time(¤t_time); /* get current time */ pathlist= argv; while (--argc > 0 && lex(*argv) == NONE) { /* find paths */ pathcnt++; argv++; } if (pathcnt == 0) /* there must be at least one path */ fatal("Usage: path-list [predicate-list]", ""); ipp = argv; /* prepare for parsing */ if (argc != 0) { /* If there is anything to parse, */ pred = expr(lex(*ipp)); /* then do so */ if (lex(*++ipp) != EOI) /* Make sure there's nothing left */ fatal("syntax error: garbage at end of predicate", ""); } else /* No predicate list */ pred = (struct node *) NULL; for (i = 0; i < pathcnt; i++) { if (xdev_flag) xdev_flag = 2; path = pathlist[i]; if ((last = strrchr(path, '/')) == NULL) last = path; else last++; find(path, pred, last); } return 0; } void find(path, pred, last) char *path, *last; struct node *pred; { char spath[PATH_MAX]; register char *send = spath, *p; struct stat st; DIR *dp; struct dirent *de; if (path[1] == '\0' && *path == '/') { *send++ = '/'; *send = '\0'; } else while (*send++ = *path++) { } if (LSTAT(spath, &st) == -1) nonfatal("can't get status of ", spath); else { switch (xdev_flag) { case 0: break; case 1: if (st.st_dev != devnr) return; break; case 2: /* set current device number */ xdev_flag = 1; devnr = st.st_dev; break; } prune_here = 0; if (!depth_flag && check(spath, &st, pred, last) && needprint) printf("%s\n", spath); if (!prune_here && (st.st_mode & S_IFMT) == S_IFDIR) { if ((dp = opendir(spath)) == NULL) { nonfatal("can't read directory ", spath); perror( "Error" ); return; } send[-1] = '/'; while ((de = readdir(dp)) != NULL) { p = de->d_name; if ((de->d_name[0] != '.') || ((de->d_name[1]) && ((de->d_name[1] != '.') || (de->d_name[2])))) { strcpy(send, de->d_name); find(spath, pred, send); } } closedir(dp); } if (depth_flag) { send[-1] = '\0'; if (check(spath, &st, pred, last) && needprint) printf("%s\n", spath); } } } int check(path, st, n, last) char *path, *last; register struct stat *st; register struct node *n; { if (n == (struct node *) NULL) return 1; switch (n->n_type) { case OP_AND: return check(path, st, n->n_info.n_opnd.n_left, last) && check(path, st, n->n_info.n_opnd.n_right, last); case OP_OR: return check(path, st, n->n_info.n_opnd.n_left, last) || check(path, st, n->n_info.n_opnd.n_right, last); case NOT: return !check(path, st, n->n_info.n_opnd.n_left, last); case OP_NAME: return smatch(last, n->n_info.n_str); case OP_PERM: if (n->n_info.n_int.n_sign < 0) return(st->st_mode & (int) n->n_info.n_int.n_val) == (int) n->n_info.n_int.n_val; return(st->st_mode & 07777) == (int) n->n_info.n_int.n_val; case OP_NEWER: return st->st_mtime > n->n_info.n_int.n_val; case OP_CNEWER: return st->st_ctime > n->n_info.n_int.n_val; case OP_TYPE: return(st->st_mode & S_IFMT) == (mode_t) n->n_info.n_int.n_val; case OP_LINKS: return ichk((long) (st->st_nlink), n); case OP_USER: return st->st_uid == n->n_info.n_int.n_val; case OP_GROUP: return st->st_gid == n->n_info.n_int.n_val; case OP_SIZE: return ichk((st->st_size == 0) ? 0L : (long) ((st->st_size - 1) / BSIZE + 1), n); case OP_SIZEC: return ichk((long) st->st_size, n); case OP_INUM: return ichk((long) (st->st_ino), n); case OP_ATIME: return ichk(st->st_atime, n); case OP_CTIME: return ichk(st->st_ctime, n); case OP_MTIME: return ichk(st->st_mtime, n); case OP_EXEC: case OP_OK: return execute(n->n_type, n->n_info.n_exec, path); case OP_PRINT: printf("%s\n", path); return 1; case OP_PRINT0: printf("%s", path); putchar(0); return 1; case OP_XDEV: case OP_DEPTH: return 1; case OP_PRUNE: prune_here = 1; return 1; case OP_NOUSER: return(getpwuid(st->st_uid) == (struct passwd *) NULL); case OP_NOGROUP: return(getgrgid(st->st_gid) == (struct group *) NULL); } fatal("ILLEGAL NODE", ""); return 0; /* Never reached */ } int ichk(val, n) long val; struct node *n; { switch (n->n_info.n_int.n_sign) { case 0: return val == n->n_info.n_int.n_val; case 1: return val > n->n_info.n_int.n_val; case -1: return val < n->n_info.n_int.n_val; } fatal("internal: bad n_sign", ""); return 0; /* Never reached */ } int lex(str) char *str; { if (str == (char *) NULL) return EOI; if (*str == '-') { register struct oper *op; str++; for (op = ops; op->op_str; op++) if (strcmp(str, op->op_str) == 0) break; return op->op_val; } if (str[1] == 0) { switch (*str) { case '(': return LPAR; case ')': return RPAR; case '!': return NOT; } } return NONE; } struct node * newnode(t) int t; { struct node *n = (struct node *) Malloc(sizeof(struct node)); n->n_type = t; return n; } /*########################### PARSER ###################################*/ /* Grammar: * expr : primary | primary OR expr; * primary : secondary | secondary AND primary | secondary primary; * secondary : NOT secondary | LPAR expr RPAR | simple; * simple : -OP args... */ /* Isnumber checks correct number syntax. A sign is allowed, but the '+' * only if the number is to be in decimal. */ int isnumber(str, base, sign) register char *str; int base; int sign; { if (sign && ((*str == '-') || ((base == 8) && (*str == '+')))) str++; while ((*str >= '0') && (*str < ('0' + base))) str++; return(*str == '\0' ? 1 : 0); } /* Convert a string to an integer, storing sign info in *ps. */ void number(str, base, pl, ps) char *str; int base; long *pl; int *ps; { int up = '0' + base - 1; long val = 0; *ps = ((*str == '-' || *str == '+') ? ((*str++ == '-') ? -1 : 1) : 0); while (*str >= '0' && *str <= up) val = base * val + *str++ - '0'; if (*str) fatal("syntax error: illegal numeric value", ""); *pl = val; } void domode(op, mode, bits) int op; int *mode; int bits; { switch (op) { case '-': *mode &= ~bits; break; /* clear bits */ case '=': *mode |= bits; break; /* set bits */ case '+': *mode |= (bits & ~um); /* set, but take umask in account */ } } void fmode(str, pl, ps) char *str; long *pl; int *ps; { int m = 0, w, op; char *p = str; if (*p == '-') { *ps = -1; p++; } else *ps = 0; while (*p) { w = 0; if (ISOPER(*p)) w = MUSER | MGROUP | MOTHERS; else if (!ISWHO(*p)) fatal("u, g, o, or a expected: ", p); else { while (ISWHO(*p)) { switch (*p) { case 'u': w |= MUSER; break; case 'g': w |= MGROUP; break; case 'o': w |= MOTHERS; break; case 'a': w = MUSER | MGROUP | MOTHERS; } p++; } if (!ISOPER(*p)) fatal("-, + or = expected: ", p); } op = *p++; while (ISMODE(*p)) { switch (*p) { case 'r': if (w & MUSER) domode(op, &m, S_IRUSR); if (w & MGROUP) domode(op, &m, S_IRGRP); if (w & MOTHERS) domode(op, &m, S_IROTH); break; case 'w': if (w & MUSER) domode(op, &m, S_IWUSR); if (w & MGROUP) domode(op, &m, S_IWGRP); if (w & MOTHERS) domode(op, &m, S_IWOTH); break; case 'x': if (w & MUSER) domode(op, &m, S_IXUSR); if (w & MGROUP) domode(op, &m, S_IXGRP); if (w & MOTHERS) domode(op, &m, S_IXOTH); break; case 's': if (w & MUSER) domode(op, &m, S_ISUID); if (w & MGROUP) domode(op, &m, S_ISGID); break; case 't': domode(op, &m, S_ISVTX); } p++; } if (*p) { if (*p == ',') p++; else fatal("garbage at end of mode string: ", p); } } *pl = m; } struct node * expr(t) int t; { struct node *nd, *p, *nd2; nd = primary(t); if ((t = lex(*++ipp)) == OP_OR) { nd2 = expr(lex(*++ipp)); p = newnode(OP_OR); p->n_info.n_opnd.n_left = nd; p->n_info.n_opnd.n_right = nd2; return p; } ipp--; return nd; } struct node * primary(t) int t; { struct node *nd, *p, *nd2; nd = secondary(t); if ((t = lex(*++ipp)) != OP_AND) { ipp--; if (t == EOI || t == RPAR || t == OP_OR) return nd; } nd2 = primary(lex(*++ipp)); p = newnode(OP_AND); p->n_info.n_opnd.n_left = nd; p->n_info.n_opnd.n_right = nd2; return p; } struct node * secondary(t) int t; { struct node *n, *p; if (t == LPAR) { n = expr(lex(*++ipp)); if (lex(*++ipp) != RPAR) fatal("syntax error, ) expected", ""); return n; } if (t == NOT) { n = secondary(lex(*++ipp)); p = newnode(NOT); p->n_info.n_opnd.n_left = n; return p; } return simple(t); } void checkarg(arg) char *arg; { if (arg == 0) fatal("syntax error, argument expected", ""); } struct node * simple(t) int t; { struct node *p = newnode(t); struct exec *e; struct stat est; struct passwd *pw; struct group *gr; long l; int i; switch (t) { case OP_TYPE: checkarg(*++ipp); switch (**ipp) { case 'b': p->n_info.n_int.n_val = S_IFBLK; break; case 'c': p->n_info.n_int.n_val = S_IFCHR; break; case 'd': p->n_info.n_int.n_val = S_IFDIR; break; case 'f': p->n_info.n_int.n_val = S_IFREG; break; case 'p': p->n_info.n_int.n_val = S_IFIFO; break; case 's': p->n_info.n_int.n_val = ~0; break; case 'l': #ifdef S_IFLNK p->n_info.n_int.n_val = S_IFLNK; #else p->n_info.n_int.n_val = ~0; /* Always unequal. */ #endif break; default: fatal("-type needs b, c, d, f, p, s or l", ""); } break; case OP_USER: checkarg(*++ipp); if (((pw = getpwnam(*ipp)) == NULL) && isnumber(*ipp, 10, 0)) number(*ipp, 10, &(p->n_info.n_int.n_val), &(p->n_info.n_int.n_sign)); else { if (pw == NULL) fatal("unknown user: ", *ipp); p->n_info.n_int.n_val = pw->pw_uid; p->n_info.n_int.n_sign = 0; } break; case OP_GROUP: checkarg(*++ipp); if (((gr = getgrnam(*ipp)) == NULL) && isnumber(*ipp, 10, 0)) number(*ipp, 10, &(p->n_info.n_int.n_val), &(p->n_info.n_int.n_sign)); else { if (gr == NULL) fatal("unknown group: ", *ipp); p->n_info.n_int.n_val = gr->gr_gid; p->n_info.n_int.n_sign = 0; } break; case OP_SIZE: checkarg(*++ipp); i = strlen(*ipp) - 1; if ((*ipp)[i] == 'c') { p->n_type = OP_SIZEC; /* Count in bytes i.s.o. blocks */ (*ipp)[i] = '\0'; } number(*ipp, 10, &(p->n_info.n_int.n_val), &(p->n_info.n_int.n_sign)); break; case OP_LINKS: case OP_INUM: checkarg(*++ipp); number(*ipp, 10, &(p->n_info.n_int.n_val), &(p->n_info.n_int.n_sign)); break; case OP_PERM: checkarg(*++ipp); if (isnumber(*ipp, 8, 1)) number(*ipp, 8, &(p->n_info.n_int.n_val), &(p->n_info.n_int.n_sign)); else fmode(*ipp, &(p->n_info.n_int.n_val), &(p->n_info.n_int.n_sign)); break; case OP_ATIME: case OP_CTIME: case OP_MTIME: checkarg(*++ipp); number(*ipp, 10, &l, &(p->n_info.n_int.n_sign)); p->n_info.n_int.n_val = current_time - l * SECS_PER_DAY; /* More than n days old means less than the absolute time */ p->n_info.n_int.n_sign *= -1; break; case OP_EXEC: case OP_OK: checkarg(*++ipp); e = (struct exec *) Malloc(sizeof(struct exec)); e->e_cnt = 2; e->e_vec[0] = SHELL; p->n_info.n_exec = e; while (*ipp) { if (**ipp == ';' && (*ipp)[1] == '\0') { e->e_vec[e->e_cnt] = 0; break; } e->e_vec[(e->e_cnt)++] = (**ipp == '{' && (*ipp)[1] == '}' && (*ipp)[2] == '\0') ? (char *) (-1) : *ipp; ipp++; } if (*ipp == 0) fatal("-exec/-ok: ; missing", ""); if ((e->e_vec[1] = find_bin(e->e_vec[2])) == 0) fatal("can't find program ", e->e_vec[2]); if (t == OP_OK) if ((tty = open("/dev/tty", O_RDWR)) < 0) fatal("can't open /dev/tty", ""); break; case OP_CNEWER: case OP_NEWER: checkarg(*++ipp); if (LSTAT(*ipp, &est) == -1) fatal("-newer: can't get status of ", *ipp); p->n_info.n_int.n_val = est.st_mtime; break; case OP_NAME: checkarg(*++ipp); p->n_info.n_str = *ipp; break; case OP_XDEV: xdev_flag = 1; break; case OP_DEPTH: depth_flag = 1; break; case OP_PRUNE: case OP_PRINT: case OP_PRINT0: case OP_NOUSER: case OP_NOGROUP: break; default: fatal("syntax error, operator expected", ""); } if ((t == OP_PRINT) || (t == OP_PRINT0) || (t == OP_EXEC) || (t == OP_OK)) needprint = 0; return p; } /*######################## DIAGNOSTICS ##############################*/ void nonfatal(s1, s2) char *s1, *s2; { fprintf(stderr, "%s: %s%s\n", prog, s1, s2); } void fatal(s1, s2) char *s1, *s2; { nonfatal(s1, s2); exit(1); } /*################### SMATCH #########################*/ /* Don't try to understand the following one... */ int smatch(s, t) /* shell-like matching */ char *s, *t; { register n; if (*t == '\0') return *s == '\0'; if (*t == '*') { ++t; do if (smatch(s, t)) return 1; while (*s++ != '\0'); return 0; } if (*s == '\0') return 0; if (*t == '\\') return (*s == *++t) ? smatch(++s, ++t) : 0; if (*t == '?') return smatch(++s, ++t); if (*t == '[') { while (*++t != ']') { if (*t == '\\') ++t; if (*(t + 1) != '-') if (*t == *s) { while (*++t != ']') if (*t == '\\') ++t; return smatch(++s, ++t); } else continue; if (*(t + 2) == ']') return(*s == *t || *s == '-'); n = (*(t + 2) == '\\') ? 3 : 2; if (*s >= *t && *s <= *(t + n)) { while (*++t != ']') if (*t == '\\') ++t; return smatch(++s, ++t); } t += n; } return 0; } return(*s == *t) ? smatch(++s, ++t) : 0; } /*####################### EXECUTE ###########################*/ /* Do -exec or -ok */ char * find_bin(s) char *s; { char *f, *l, buf[PATH_MAX]; if (*s == '/') /* absolute path name */ return(access(s, 1) == 0) ? s : 0; l = f = epath; for (;;) { if (*l == ':' || *l == 0) { if (l == f) { if (access(s, 1) == 0) return Salloc(s); f++; } else { register char *p = buf, *q = s; while (f != l) *p++ = *f++; f++; *p++ = '/'; while (*p++ = *q++) { } if (access(buf, 1) == 0) return Salloc(buf); } if (*l == 0) break; } l++; } return 0; } int execute(op, e, path) int op; struct exec *e; char *path; { int s, pid; char *argv[MAXARG]; register char **p, **q; for (p = e->e_vec, q = argv; *p;) /* replace the {}s */ if ((*q++ = *p++) == (char *) -1) q[-1] = path; *q = '\0'; if (op == OP_OK) { char answer[10]; for (p = &argv[2]; *p; p++) { write(tty, *p, strlen(*p)); write(tty, " ", 1); } write(tty, "? ", 2); if (read(tty, answer, 10) < 2 || *answer != 'y') return 0; } if ((pid = fork()) == -1) fatal("can't fork", ""); if (pid == 0) { register i = 3; while (close(i++) == 0) { } /* wow!!! */ execv(argv[1], &argv[2]); /* binary itself? */ execv(argv[0], &argv[1]); /* shell command? */ fatal("exec failure: ", argv[1]); /* none of them! */ exit(127); } return wait(&s) == pid && s == 0; }