1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67#define FOR_find
68#include "toys.h"
69
70GLOBALS(
71 char **filter;
72 struct double_list *argdata;
73 int topdir, xdev, depth;
74 time_t now;
75 long max_bytes;
76 char *start;
77)
78
79struct execdir_data {
80 struct execdir_data *next;
81
82 int namecount;
83 struct double_list *names;
84};
85
86
87struct exec_range {
88 char *next, *prev;
89
90 int dir, plus, arglen, argsize, curly;
91 char **argstart;
92 struct execdir_data exec, *execdir;
93};
94
95
96static int flush_exec(struct dirtree *new, struct exec_range *aa)
97{
98 struct execdir_data *bb = aa->execdir ? aa->execdir : &aa->exec;
99 char **newargs;
100 int rc, revert = 0;
101
102 if (!bb->namecount) return 0;
103
104 dlist_terminate(bb->names);
105
106
107
108 if (TT.topdir != -1) {
109 if (aa->dir && new && new->parent) {
110 revert++;
111 rc = fchdir(new->parent->dirfd);
112 } else rc = fchdir(TT.topdir);
113 if (rc) {
114 perror_msg_raw(revert ? new->name : ".");
115
116 return rc;
117 }
118 }
119
120
121 newargs = xmalloc(sizeof(char *)*(aa->arglen+bb->namecount+1));
122 if (aa->curly < 0) {
123 memcpy(newargs, aa->argstart, sizeof(char *)*aa->arglen);
124 newargs[aa->arglen] = 0;
125 } else {
126 int pos = aa->curly, rest = aa->arglen - aa->curly;
127 struct double_list *dl;
128
129
130 memcpy(newargs, aa->argstart, sizeof(char *)*pos);
131 for (dl = bb->names; dl; dl = dl->next) newargs[pos++] = dl->data;
132 rest = aa->arglen - aa->curly - 1;
133 memcpy(newargs+pos, aa->argstart+aa->curly+1, sizeof(char *)*rest);
134 newargs[pos+rest] = 0;
135 }
136
137 rc = xrun(newargs);
138
139 llist_traverse(bb->names, llist_free_double);
140 bb->names = 0;
141 bb->namecount = 0;
142
143 if (revert) revert = fchdir(TT.topdir);
144
145 return rc;
146}
147
148
149static int compare_numsign(long val, long units, char *str)
150{
151 char sign = 0;
152 long myval;
153
154 if (*str == '+' || *str == '-') sign = *(str++);
155 else if (!isdigit(*str)) error_exit("%s not [+-]N", str);
156 myval = atolx(str);
157 if (units && isdigit(str[strlen(str)-1])) myval *= units;
158
159 if (sign == '+') return val > myval;
160 if (sign == '-') return val < myval;
161 return val == myval;
162}
163
164static void do_print(struct dirtree *new, char c)
165{
166 char *s=dirtree_path(new, 0);
167
168 xprintf("%s%c", s, c);
169 free(s);
170}
171
172
173static void execdir(struct dirtree *new, int flush)
174{
175 struct double_list *dl;
176 struct exec_range *aa;
177 struct execdir_data *bb;
178
179 if (new && TT.topdir == -1) return;
180
181 for (dl = TT.argdata; dl; dl = dl->next) {
182 if (dl->prev != (void *)1) continue;
183 aa = (void *)dl;
184 if (!aa->plus || (new && !aa->dir)) continue;
185
186 if (flush) {
187
188
189
190 toys.exitval |= flush_exec(new, aa);
191
192
193 if ((bb = aa->execdir)) {
194 aa->execdir = bb->next;
195 free(bb);
196 }
197 } else if (aa->dir) {
198
199
200
201 bb = xzalloc(sizeof(struct execdir_data));
202 bb->next = aa->execdir;
203 aa->execdir = bb;
204 }
205 }
206}
207
208
209
210
211static int do_find(struct dirtree *new)
212{
213 int pcount = 0, print = 0, not = 0, active = !!new, test = active, recurse;
214 struct double_list *argdata = TT.argdata;
215 char *s, **ss, *arg;
216
217 recurse = DIRTREE_STATLESS|DIRTREE_COMEAGAIN|
218 (DIRTREE_SYMFOLLOW*!!(toys.optflags&FLAG_L));
219
220
221 if (new) {
222
223 if (new->again&2) {
224 if (!new->parent || errno != ENOENT) {
225 perror_msg("'%s'", s = dirtree_path(new, 0));
226 free(s);
227 }
228 return 0;
229 }
230 if (new->parent) {
231 if (!dirtree_notdotdot(new)) return 0;
232 if (TT.xdev && new->st.st_dev != new->parent->st.st_dev) recurse = 0;
233 } else TT.start = new->name;
234
235 if (S_ISDIR(new->st.st_mode)) {
236
237 if (!new->again) {
238 struct dirtree *n;
239
240 for (n = new->parent; n; n = n->parent) {
241 if (same_file(&n->st, &new->st)) {
242 error_msg("'%s': loop detected", s = dirtree_path(new, 0));
243 free(s);
244
245 return 0;
246 }
247 }
248
249 if (TT.depth) {
250 execdir(new, 0);
251
252 return recurse;
253 }
254
255 } else {
256 execdir(new, 1);
257 recurse = 0;
258 if (!TT.depth) return 0;
259 }
260 }
261 }
262
263
264
265
266
267
268
269 if (TT.filter) for (ss = TT.filter; (s = *ss); ss++) {
270 int check = active && test;
271
272
273
274
275 if (*s != '-') {
276 if (s[1]) goto error;
277
278 if (*s == '!') {
279
280 if (check) not = !not;
281
282
283
284
285 } else if (*s == '(') {
286 if (pcount == sizeof(toybuf)) goto error;
287 toybuf[pcount++] = not+(active<<1);
288 if (!check) active = 0;
289 not = 0;
290
291
292 } else if (*s == ')') {
293 if (--pcount < 0) goto error;
294
295 active = (toybuf[pcount]>>1)&1;
296 if (active && (toybuf[pcount]&1)) test = !test;
297 not = 0;
298 } else goto error;
299
300 continue;
301 } else s++;
302
303 if (!strcmp(s, "xdev")) TT.xdev = 1;
304 else if (!strcmp(s, "delete")) {
305
306 TT.depth = 1;
307 if (new && check)
308 test = !unlinkat(dirtree_parentfd(new), new->name,
309 S_ISDIR(new->st.st_mode) ? AT_REMOVEDIR : 0);
310 } else if (!strcmp(s, "depth") || !strcmp(s, "d")) TT.depth = 1;
311 else if (!strcmp(s, "o") || !strcmp(s, "or")) {
312 if (not) goto error;
313 if (active) {
314 if (!test) test = 1;
315 else active = 0;
316 }
317 } else if (!strcmp(s, "not")) {
318 if (check) not = !not;
319 continue;
320 } else if (!strcmp(s, "true")) {
321 if (check) test = 1;
322 } else if (!strcmp(s, "false")) {
323 if (check) test = 0;
324
325
326 } else if (!strcmp(s, "a") || !strcmp(s, "and") || !strcmp(s, "noleaf")) {
327 if (not) goto error;
328
329 } else if (!strcmp(s, "print") || !strcmp("print0", s)) {
330 print++;
331 if (check) do_print(new, s[5] ? 0 : '\n');
332
333 } else if (!strcmp(s, "empty")) {
334 if (check) {
335
336 if (S_ISDIR(new->st.st_mode)) {
337 int fd = openat(dirtree_parentfd(new), new->name, O_RDONLY);
338 DIR *dfd = fdopendir(fd);
339 struct dirent *de = (void *)1;
340 if (dfd) {
341 while ((de = readdir(dfd)) && isdotdot(de->d_name));
342 closedir(dfd);
343 }
344 if (de) test = 0;
345 } else if (S_ISREG(new->st.st_mode)) {
346 if (new->st.st_size) test = 0;
347 } else test = 0;
348 }
349 } else if (!strcmp(s, "nouser")) {
350 if (check && bufgetpwuid(new->st.st_uid)) test = 0;
351 } else if (!strcmp(s, "nogroup")) {
352 if (check && bufgetgrgid(new->st.st_gid)) test = 0;
353 } else if (!strcmp(s, "prune")) {
354 if (check && S_ISDIR(new->st.st_mode) && !TT.depth) recurse = 0;
355 } else if (!strcmp(s, "executable")) {
356 if (check && faccessat(dirtree_parentfd(new), new->name,X_OK,0)) test = 0;
357 } else if (!strcmp(s, "quit")) {
358 if (check) {
359 execdir(0, 1);
360 xexit();
361 }
362
363
364 } else {
365 arg = *++ss;
366 if (!strcmp(s, "name") || !strcmp(s, "iname")
367 || !strcmp(s, "wholename") || !strcmp(s, "iwholename")
368 || !strcmp(s, "path") || !strcmp(s, "ipath")
369 || !strcmp(s, "lname") || !strcmp(s, "ilname"))
370 {
371 int i = (*s == 'i'), is_path = (s[i] != 'n');
372 char *path = 0, *name = new ? new->name : arg;
373
374
375 if (new && s[i] == 'l')
376 name = path = xreadlinkat(dirtree_parentfd(new), new->name);
377 else if (new && is_path) name = path = dirtree_path(new, 0);
378 if (i) {
379 if ((check || !new) && name) name = strlower(name);
380 if (!new) dlist_add(&TT.argdata, name);
381 else arg = ((struct double_list *)llist_pop(&argdata))->data;
382 }
383
384 if (check) {
385 test = !fnmatch(arg, path ? name : basename(name),
386 FNM_PATHNAME*(!is_path));
387 if (i) free(name);
388 }
389 free(path);
390 } else if (!CFG_TOYBOX_LSM_NONE && !strcmp(s, "context")) {
391 if (check) {
392 char *path = dirtree_path(new, 0), *context;
393
394 if (lsm_get_context(path, &context) != -1) {
395 test = !fnmatch(arg, context, 0);
396 free(context);
397 } else test = 0;
398 free(path);
399 }
400 } else if (!strcmp(s, "perm")) {
401 if (check) {
402 int match_min = *arg == '-', match_any = *arg == '/';
403 mode_t m1 = string_to_mode(arg+(match_min || match_any), 0),
404 m2 = new->st.st_mode & 07777;
405
406 if (match_min || match_any) m2 &= m1;
407 test = match_any ? !m1 || m2 : m1 == m2;
408 }
409 } else if (!strcmp(s, "type")) {
410 if (check) {
411 int types[] = {S_IFBLK, S_IFCHR, S_IFDIR, S_IFLNK, S_IFIFO,
412 S_IFREG, S_IFSOCK}, i;
413
414 for (; *arg; arg++) {
415 if (*arg == ',') continue;
416 i = stridx("bcdlpfs", *arg);
417 if (i<0) error_exit("bad -type '%c'", *arg);
418 if ((new->st.st_mode & S_IFMT) == types[i]) break;
419 }
420 test = *arg;
421 }
422
423 } else if (strchr("acm", *s)
424 && (!strcmp(s+1, "time") || !strcmp(s+1, "min")))
425 {
426 if (check) {
427 time_t thyme = (int []){new->st.st_atime, new->st.st_ctime,
428 new->st.st_mtime}[stridx("acm", *s)];
429 int len = strlen(arg), uu, units = (s[1]=='m') ? 60 : 86400;
430
431 if (len && -1!=(uu = stridx("dhms",tolower(arg[len-1])))) {
432 arg = xstrdup(arg);
433 arg[--len] = 0;
434 units = (int []){86400, 3600, 60, 1}[uu];
435 }
436 test = compare_numsign(TT.now - thyme, units, arg);
437 if (*ss != arg) free(arg);
438 }
439 } else if (!strcmp(s, "size")) {
440 if (check) test = compare_numsign(new->st.st_size, 512, arg);
441 } else if (!strcmp(s, "links")) {
442 if (check) test = compare_numsign(new->st.st_nlink, 0, arg);
443 } else if (!strcmp(s, "inum")) {
444 if (check) test = compare_numsign(new->st.st_ino, 0, arg);
445 } else if (!strcmp(s, "mindepth") || !strcmp(s, "maxdepth")) {
446 if (check) {
447 struct dirtree *dt = new;
448 int i = 0, d = atolx(arg);
449
450 while ((dt = dt->parent)) i++;
451 if (s[1] == 'i') {
452 test = i >= d;
453 if (i == d && not) recurse = 0;
454 } else {
455 test = i <= d;
456 if (i == d && !not) recurse = 0;
457 }
458 }
459 } else if (!strcmp(s, "user") || !strcmp(s, "group")
460 || !strncmp(s, "newer", 5) || !strcmp(s, "samefile"))
461 {
462 int macoff[] = {offsetof(struct stat, st_mtim),
463 offsetof(struct stat, st_atim), offsetof(struct stat, st_ctim)};
464 struct {
465 void *next, *prev;
466 union {
467 uid_t uid;
468 gid_t gid;
469 struct timespec tm;
470 struct dev_ino di;
471 };
472 } *udl;
473 struct stat st;
474
475 if (!new) {
476 if (arg) {
477 udl = xmalloc(sizeof(*udl));
478 dlist_add_nomalloc(&TT.argdata, (void *)udl);
479
480 if (strchr("sn", *s)) {
481 if (*s=='n' && s[5] && (s[7] || !strchr("Bmac", s[5]) || !strchr("tBmac", s[6])))
482 goto error;
483 if (*s=='s' || !s[5] || s[6]!='t') {
484 xstat(arg, &st);
485 if (*s=='s') udl->di.dev = st.st_dev, udl->di.ino = st.st_ino;
486 else udl->tm = *(struct timespec *)(((char *)&st)
487 + macoff[!s[5] ? 0 : stridx("ac", s[6])+1]);
488 } else if (s[6] == 't') {
489 unsigned nano;
490
491 xparsedate(arg, &(udl->tm.tv_sec), &nano, 1);
492 udl->tm.tv_nsec = nano;
493 }
494 } else if (*s == 'u') udl->uid = xgetuid(arg);
495 else udl->gid = xgetgid(arg);
496 }
497 } else {
498 udl = (void *)llist_pop(&argdata);
499 if (check) {
500 if (*s == 'u') test = new->st.st_uid == udl->uid;
501 else if (*s == 'g') test = new->st.st_gid == udl->gid;
502 else if (*s == 's') test = same_dev_ino(&new->st, &udl->di);
503 else {
504 struct timespec *tm = (void *)(((char *)&new->st)
505 + macoff[!s[5] ? 0 : stridx("ac", s[5])+1]);
506
507 if (s[5] == 'B') test = 0;
508 else test = (tm->tv_sec == udl->tm.tv_sec)
509 ? tm->tv_nsec > udl->tm.tv_nsec
510 : tm->tv_sec > udl->tm.tv_sec;
511 }
512 }
513 }
514 } else if (!strcmp(s, "exec") || !strcmp("ok", s)
515 || !strcmp(s, "execdir") || !strcmp(s, "okdir"))
516 {
517 struct exec_range *aa;
518
519 print++;
520
521
522 if (!new) {
523 int len;
524
525
526 if (!arg || !strcmp(arg, ";")) error_exit("'%s' needs 1 arg", s);
527
528 dlist_add_nomalloc(&TT.argdata, (void *)(aa = xzalloc(sizeof(*aa))));
529 aa->argstart = ss;
530 aa->curly = -1;
531
532
533 for (len = 0; ss[len]; len++) {
534 if (!strcmp(ss[len], ";")) break;
535 else if (!strcmp(ss[len], "{}")) {
536 aa->curly = len;
537 if (ss[len+1] && !strcmp(ss[len+1], "+")) {
538 aa->plus++;
539 len++;
540 break;
541 }
542 } else aa->argsize += sizeof(char *) + strlen(ss[len]) + 1;
543 }
544 if (!ss[len]) error_exit("-exec without %s",
545 aa->curly!=-1 ? "\\;" : "{}");
546 ss += len;
547 aa->arglen = len;
548 aa->dir = !!strchr(s, 'd');
549 if (TT.topdir == -1) TT.topdir = xopenro(".");
550
551
552 } else {
553 char *name;
554 struct execdir_data *bb;
555
556
557 aa = (void *)llist_pop(&argdata);
558 ss += aa->arglen;
559
560 if (!check) goto cont;
561
562 name = aa->dir ? xstrdup(new->name) : dirtree_path(new, 0);
563
564 if (*s == 'o') {
565 fprintf(stderr, "[%s] %s", arg, name);
566 if (!(test = yesno(0))) {
567 free(name);
568 goto cont;
569 }
570 }
571
572
573 bb = aa->execdir ? aa->execdir : &aa->exec;
574 dlist_add(&bb->names, name);
575 bb->namecount++;
576
577
578 if (aa->plus) {
579
580
581
582 aa->prev = (void *)1;
583
584
585
586
587 if ((aa->plus += sizeof(char *)+strlen(name)+1) > TT.max_bytes) {
588 aa->plus = 1;
589 toys.exitval |= flush_exec(new, aa);
590 }
591 } else test = !flush_exec(new, aa);
592 }
593
594
595 goto cont;
596 } else if (!strcmp(s, "printf")) {
597 char *fmt, *ff, next[32], buf[64], ch;
598 long ll;
599 int len;
600
601 print++;
602 if (check) for (fmt = arg; *fmt; fmt++) {
603
604 if (*fmt == '\\') {
605 unsigned u;
606
607 if (fmt[1] == 'c') break;
608 if ((u = unescape2(&fmt, 0))<128) putchar(u);
609 else printf("%.*s", (int)wcrtomb(buf, u, 0), buf);
610 fmt--;
611 } else if (*fmt != '%') putchar(*fmt);
612 else if (*++fmt == '%') putchar('%');
613 else {
614 fmt = next_printf(ff = fmt-1, 0);
615 if ((len = fmt-ff)>28) error_exit("bad %.*s", len+1, ff);
616 memcpy(next, ff, len);
617 ff = 0;
618 ch = *fmt;
619
620
621 if (ch == 'i' || ch == 's') {
622 strcpy(next+len, "lld");
623 printf(next, (ch == 'i') ? (long long)new->st.st_ino
624 : (long long)new->st.st_size);
625 } else {
626
627
628 strcpy(next+len, "s");
629 if (ch == 'G') next[len] = 'd', ll = new->st.st_gid;
630 else if (ch == 'm') next[len] = 'o', ll = new->st.st_mode&~S_IFMT;
631 else if (ch == 'U') next[len] = 'd', ll = new->st.st_uid;
632 else if (ch == 'f') ll = (long)new->name;
633 else if (ch == 'g') ll = (long)getgroupname(new->st.st_gid);
634 else if (ch == 'u') ll = (long)getusername(new->st.st_uid);
635 else if (ch == 'l') {
636 ll = (long)(ff = xreadlinkat(dirtree_parentfd(new), new->name));
637 if (!ll) ll = (long)"";
638 } else if (ch == 'M') {
639 mode_to_string(new->st.st_mode, buf);
640 ll = (long)buf;
641 } else if (ch == 'P') {
642 ch = *TT.start;
643 *TT.start = 0;
644 ll = (long)(ff = dirtree_path(new, 0));
645 *TT.start = ch;
646 } else if (ch == 'p') ll = (long)(ff = dirtree_path(new, 0));
647 else if (ch == 'T') {
648 if (*++fmt!='@') error_exit("bad -printf %%T: %%T%c", *fmt);
649 sprintf(buf, "%lld.%ld", (long long)new->st.st_mtim.tv_sec,
650 new->st.st_mtim.tv_nsec);
651 ll = (long)buf;
652 } else if (ch == 'Z') {
653 char *path = dirtree_path(new, 0);
654
655 ll = (lsm_get_context(path, &ff) != -1) ? (long)ff : (long)"?";
656 free(path);
657 } else error_exit("bad -printf %%%c", ch);
658
659 printf(next, ll);
660 free(ff);
661 }
662 }
663 }
664 } else goto error;
665
666
667
668
669 if (!check && !arg) error_exit("'%s' needs 1 arg", s-1);
670 }
671cont:
672
673 if (active && not) test = !test;
674 not = 0;
675 }
676
677 if (new) {
678
679 if (!print && test) do_print(new, '\n');
680
681 if (S_ISDIR(new->st.st_mode)) execdir(new, 0);
682
683 } else dlist_terminate(TT.argdata);
684
685 return recurse;
686
687error:
688 error_exit("bad arg '%s'", *ss);
689}
690
691void find_main(void)
692{
693 int i, len;
694 char **ss = (char *[]){"."};
695
696 TT.topdir = -1;
697 TT.max_bytes = sysconf(_SC_ARG_MAX) - environ_bytes();
698
699
700 for (len = 0; toys.optargs[len]; len++)
701 if (*toys.optargs[len] && strchr("-!(", *toys.optargs[len])) break;
702 TT.filter = toys.optargs+len;
703
704
705 if (len) ss = toys.optargs;
706 else len = 1;
707
708
709 TT.now = time(0);
710 do_find(0);
711
712
713 for (i = 0; i < len; i++)
714 dirtree_flagread(ss[i],
715 DIRTREE_STATLESS|(DIRTREE_SYMFOLLOW*!!(toys.optflags&(FLAG_H|FLAG_L))),
716 do_find);
717
718 execdir(0, 1);
719
720 if (CFG_TOYBOX_FREE) {
721 close(TT.topdir);
722 llist_traverse(TT.argdata, free);
723 }
724}
725