find.c revision a873444aa3e57d05c639c849a41a2add146f0309
1/* find.c - Search directories for matching files. 2 * 3 * Copyright 2014 Rob Landley <rob@landley.net> 4 * 5 * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/find.c 6 * Our "unspecified" behavior for no paths is to use "." 7 * Parentheses can only stack 4096 deep 8 9USE_FIND(NEWTOY(find, "^HL", TOYFLAG_USR|TOYFLAG_BIN)) 10 11config FIND 12 bool "find" 13 default n 14 help 15 usage: find [-HL] [DIR...] [<options>] 16 17 Search directories for matching files. 18 (Default is to search "." match all and display matches.) 19 20 -H Follow command line symlinks 21 -L Follow all symlinks 22 23 Match filters: 24 -name <pattern> filename (with wildcards) 25 -path <pattern> path name (with wildcards) 26 -nouser belongs to unknown user 27 -nogroup belongs to unknown group 28 -xdev do not cross into new filesystems 29 -prune do not descend into children 30 -perm MODE permissons (prefixed with - means at least these) 31 -iname PATTERN case insensitive filename 32 -links N hardlink count 33 -user UNAME belongs to user 34 -group GROUP belongs to group 35 -size N[c] 36 -atime N 37 -ctime N 38 -type [bcdflps] type (block, char, dir, file, symlink, pipe, socket) 39 -mtime N last modified N (24 hour) days ago 40 41 Numbers N may be prefixed by a - (less than) or + (greater than) 42 43 Combine matches with: 44 !, -a, -o, ( ) not, and, or, group expressions 45 46 47 Actions: 48 -exec 49 -print 50 -print0 51*/ 52 53// find . ! \( -name blah -print \) 54// find . -o 55 56#define FOR_find 57#include "toys.h" 58 59GLOBALS( 60 char **filter; 61 struct double_list *argdata; 62 int xdev, depth; 63 time_t now; 64) 65 66// Return numeric value with explicit sign 67static int compare_numsign(long val, long units, char *str) 68{ 69 char sign = 0; 70 long myval; 71 72 if (*str == '+' || *str == '-') sign = *(str++); 73 else if (!isdigit(*str)) error_exit("%s not [+-]N", str); 74 myval = atolx(str); 75 if (units && isdigit(str[strlen(str)-1])) myval *= units; 76 77 if (sign == '+') return val > myval; 78 if (sign == '-') return val < myval; 79 return val == myval; 80} 81 82static void do_print(struct dirtree *new, char c) 83{ 84 char *s=dirtree_path(new, 0); 85 86 xprintf("%s%c", s, c); 87 free(s); 88} 89 90void todo_store_argument(void) 91{ 92 error_exit("NOP"); 93} 94 95// pending issues: 96// old false -a ! new false does not yield true. 97// 98// -user -group -newer evaluate once and save result (where?) 99// add -print if no action (-exec, -ok, -print) 100// find . -print -xdev (should xdev before print) 101 102// Call this with 0 for first pass argument parsing, syntax checking. 103static int do_find(struct dirtree *new) 104{ 105 int pcount = 0, print = 0, not = 0, active = !!new, test = active, recurse; 106 char *s, **ss; 107 108 recurse = DIRTREE_RECURSE|((toys.optflags&FLAG_L) ? DIRTREE_SYMFOLLOW : 0); 109 110 // skip . and .. below topdir, handle -xdev and -depth 111 if (active) { 112 if (new->parent) { 113 if (!dirtree_notdotdot(new)) return 0; 114 if (TT.xdev && new->st.st_dev != new->parent->st.st_dev) return 0; 115 } 116 if (TT.depth && S_ISDIR(new->st.st_dev) && new->data != -1) 117 return DIRTREE_COMEAGAIN; 118 } 119 120 // pcount: parentheses stack depth (using toybuf bytes, 4096 max depth) 121 // test: result of most recent test 122 // active: if 0 don't perform tests 123 // not: a pending ! applies to this test (only set if performing tests) 124 // print: saw one of print/ok/exec, no need for default -print 125 126 if (TT.filter) for (ss = TT.filter; *ss; ss++) { 127 int check = active && test; 128 129 s = *ss; 130 131 // handle ! ( ) using toybuf as a stack 132 if (*s != '-') { 133 if (s[1]) goto error; 134 135 if (*s == '!') { 136 // Don't invert if we're not making a decision 137 if (check) not = !not; 138 139 // Save old "not" and "active" on toybuf stack. 140 // Deactivate this parenthetical if !test 141 // Note: test value should never change while !active 142 } else if (*s == '(') { 143 if (pcount == sizeof(toybuf)) goto error; 144 toybuf[pcount++] = not+(active<<1); 145 if (!check) active = 0; 146 not = 0; 147 148 // Pop status, apply deferred not to test 149 } else if (*s == ')') { 150 if (--pcount < 0) goto error; 151 // Pop active state, apply deferred not (which was only set if checking) 152 active = (toybuf[pcount]>>1)&1; 153 if (active && (toybuf[pcount]&1)) test = !test; 154 not = 0; 155 } else goto error; 156 157 continue; 158 } else s++; 159 160 if (!strcmp(s, "xdev")) TT.xdev = 1; 161 else if (!strcmp(s, "depth")) TT.depth = 1; 162 else if (!strcmp(s, "o") || !strcmp(s, "or")) { 163 if (not) goto error; 164 if (active) { 165 if (!test) test = 1; 166 else active = 0; // decision has been made until next ")" 167 } 168 169 // Mostly ignore NOP argument 170 } else if (!strcmp(s, "a") || !strcmp(s, "and")) { 171 if (not) goto error; 172 173 } else if (!strcmp(s, "print") || !strcmp("print0", s)) { 174 print++; 175 if (check) do_print(new, s[5] ? 0 : '\n'); 176 177 } else if (!strcmp(s, "nouser")) { 178 if (check) if (getpwuid(new->st.st_uid)) test = 0; 179 } else if (!strcmp(s, "nogroup")) { 180 if (check) if (getgrgid(new->st.st_gid)) test = 0; 181 } else if (!strcmp(s, "prune")) { 182 if (check && S_ISDIR(new->st.st_dev) && !TT.depth) recurse = 0; 183 184 // Remaining filters take an argument 185 } else { 186 187 if (!strcmp(s, "name") || !strcmp(s, "iname")) { 188 if (check) { 189 if (*s == 'i') todo_store_argument(); 190// if (!new) { 191// } else { 192// name = xstrdup(name); 193// while ( 194 test = !fnmatch(ss[1], new->name, 0); 195 } 196 } else if (!strcmp(s, "path")) { 197 if (check) { 198 char *path = dirtree_path(new, 0); 199 int len = strlen(ss[1]); 200 201 if (strncmp(path, ss[1], len) || (ss[1][len] && ss[1][len] != '/')) 202 test = 0; 203 free(s); 204 } 205 } else if (!strcmp(s, "perm")) { 206 if (check) { 207 char *m = ss[1]; 208 mode_t m1 = string_to_mode(m+(*m == '-'), 0), 209 m2 = new->st.st_dev & 07777; 210 211 if (*m != '-') m2 &= m1; 212 test = m1 == m2; 213 } 214 } else if (!strcmp(s, "type")) { 215 if (check) { 216 char c = stridx("bcdlpfs", *ss[1]); 217 int types[] = {S_IFBLK, S_IFCHR, S_IFDIR, S_IFLNK, S_IFIFO, 218 S_IFREG, S_IFSOCK}; 219 220 if ((new->st.st_dev & S_IFMT) != types[c]) test = 0; 221 } 222 223 } else if (!strcmp(s, "atime")) { 224 if (check) 225 test = compare_numsign(TT.now - new->st.st_atime, 86400, ss[1]); 226 } else if (!strcmp(s, "ctime")) { 227 if (check) 228 test = compare_numsign(TT.now - new->st.st_ctime, 86400, ss[1]); 229 } else if (!strcmp(s, "mtime")) { 230 if (check) 231 test = compare_numsign(TT.now - new->st.st_mtime, 86400, ss[1]); 232 } else if (!strcmp(s, "size")) { 233 if (check) 234 test = compare_numsign(new->st.st_size, 512, ss[1]); 235 } else if (!strcmp(s, "links")) { 236 if (check) test = compare_numsign(new->st.st_nlink, 0, ss[1]); 237 } else if (!strcmp(s, "user")) { 238 todo_store_argument(); 239 } else if (!strcmp(s, "group")) { 240 todo_store_argument(); 241 } else if (!strcmp(s, "newer")) { 242 todo_store_argument(); 243 } else if (!strcmp(s, "exec") || !strcmp("ok", s)) { 244 print++; 245 if (check) error_exit("implement exec/ok"); 246 } else goto error; 247 248 // This test can go at the end because we do a syntax checking 249 // pass first. Putting it here gets the error message (-unknown 250 // vs -known noarg) right. 251 if (!*++ss) error_exit("'%s' needs 1 arg", --s); 252 } 253 254 // Apply pending "!" to result 255 if (active && not) test = !test; 256 not = 0; 257 } 258 259 // If there was no action, print 260 if (!print && test && new) do_print(new, '\n'); 261 262 return recurse; 263 264error: 265 error_exit("bad arg '%s'", *ss); 266} 267 268void find_main(void) 269{ 270 int i, len; 271 char **ss = toys.optargs; 272 273 // Distinguish paths from filters 274 for (len = 0; toys.optargs[len]; len++) 275 if (strchr("-!(", *toys.optargs[len])) break; 276 TT.filter = toys.optargs+len; 277 278 // use "." if no paths 279 if (!*ss) { 280 ss = (char *[]){"."}; 281 len = 1; 282 } 283 284 // first pass argument parsing, verify args match up, handle "evaluate once" 285 TT.now = time(0); 286 do_find(0); 287 288 // Loop through paths 289 for (i = 0; i < len; i++) { 290 struct dirtree *new; 291 292 new = dirtree_add_node(0, ss[i], toys.optflags&(FLAG_H|FLAG_L)); 293 if (new) dirtree_handle_callback(new, do_find); 294 } 295} 296