1 /* find -- search for files in a directory hierarchy (fts version)
2 Copyright (C) 1990-2022 Free Software Foundation, Inc.
3
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17
18 /* This file was written by James Youngman, based on oldfind.c.
19
20 GNU find was written by Eric Decker <cire@soe.ucsc.edu>,
21 with enhancements by David MacKenzie <djm@gnu.org>,
22 Jay Plett <jay@silence.princeton.nj.us>,
23 and Tim Wood <axolotl!tim@toad.com>.
24 The idea for -print0 and xargs -0 came from
25 Dan Bernstein <brnstnd@kramden.acf.nyu.edu>.
26 */
27
28 /* config.h must always be included first. */
29 #include <config.h>
30
31
32 /* system headers. */
33 #include <assert.h>
34 #include <errno.h>
35 #include <fcntl.h>
36 #include <inttypes.h>
37 #include <sys/stat.h>
38 #include <unistd.h>
39
40 /* gnulib headers. */
41 #include "argv-iter.h"
42 #include "cloexec.h"
43 #include "closeout.h"
44 #include "error.h"
45 #include "fts_.h"
46 #include "intprops.h"
47 #include "progname.h"
48 #include "quotearg.h"
49 #include "same-inode.h"
50 #include "save-cwd.h"
51 #include "xgetcwd.h"
52 #include "xalloc.h"
53
54 /* find headers. */
55 #include "defs.h"
56 #include "die.h"
57 #include "dircallback.h"
58 #include "fdleak.h"
59 #include "unused-result.h"
60 #include "system.h"
61
62
63 #undef STAT_MOUNTPOINTS
64
65
66 /* FTS_TIGHT_CYCLE_CHECK tries to work around Savannah bug #17877
67 * (but actually using it doesn't fix the bug).
68 */
69 static int ftsoptions = FTS_NOSTAT|FTS_TIGHT_CYCLE_CHECK|FTS_CWDFD|FTS_VERBATIM;
70
71 static int prev_depth = INT_MIN; /* fts_level can be < 0 */
72 static int curr_fd = -1;
73
74
75 static bool find (char *arg) __attribute_warn_unused_result__;
76 static bool process_all_startpoints (int argc, char *argv[]) __attribute_warn_unused_result__;
77
78
79
80 static void
81 left_dir (void)
82 {
83 if (ftsoptions & FTS_CWDFD)
84 {
85 if (curr_fd >= 0)
86 {
87 close (curr_fd);
88 curr_fd = -1;
89 }
90 }
91 else
92 {
93 /* do nothing. */
94 }
95 }
96
97 /*
98 * Signal that we are now inside a directory pointed to by dir_fd.
99 * The caller can't tell if this is the first time this happens, so
100 * we have to be careful not to call dup() more than once
101 */
102 static void
103 inside_dir (int dir_fd)
104 {
105 if (ftsoptions & FTS_CWDFD)
106 {
107 assert (dir_fd == AT_FDCWD || dir_fd >= 0);
108
109 state.cwd_dir_fd = dir_fd;
110 if (curr_fd < 0)
111 {
112 if (AT_FDCWD == dir_fd)
113 {
114 curr_fd = AT_FDCWD;
115 }
116 else if (dir_fd >= 0)
117 {
118 curr_fd = dup_cloexec (dir_fd);
119 }
120 else
121 {
122 /* curr_fd is invalid, but dir_fd is also invalid.
123 * This should not have happened.
124 */
125 assert (curr_fd >= 0 || dir_fd >= 0);
126 }
127 }
128 }
129 else
130 {
131 /* FTS_CWDFD is not in use. We can always assume that
132 * AT_FDCWD refers to the directory we are currently searching.
133 *
134 * Therefore there is nothing to do.
135 */
136 }
137 }
138
139
140
141 #ifdef STAT_MOUNTPOINTS
142 static void init_mounted_dev_list (void);
143 #endif
144
145 #define HANDLECASE(N) case N: return #N;
146
147 static const char *
148 get_fts_info_name (int info)
149 {
150 static char buf[1 + INT_BUFSIZE_BOUND (info) + 1];
151 switch (info)
152 {
153 HANDLECASE(FTS_D);
154 HANDLECASE(FTS_DC);
155 HANDLECASE(FTS_DEFAULT);
156 HANDLECASE(FTS_DNR);
157 HANDLECASE(FTS_DOT);
158 HANDLECASE(FTS_DP);
159 HANDLECASE(FTS_ERR);
160 HANDLECASE(FTS_F);
161 HANDLECASE(FTS_INIT);
162 HANDLECASE(FTS_NS);
163 HANDLECASE(FTS_NSOK);
164 HANDLECASE(FTS_SL);
165 HANDLECASE(FTS_SLNONE);
166 HANDLECASE(FTS_W);
167 default:
168 sprintf (buf, "[%d]", info);
169 return buf;
170 }
171 }
172
173 static void
174 visit (FTS *p, FTSENT *ent, struct stat *pstat)
175 {
176 struct predicate *eval_tree;
177
178 state.have_stat = (ent->fts_info != FTS_NS) && (ent->fts_info != FTS_NSOK);
179 state.rel_pathname = ent->fts_accpath;
180 state.cwd_dir_fd = p->fts_cwd_fd;
181
182 /* Apply the predicates to this path. */
183 eval_tree = get_eval_tree ();
184 apply_predicate (ent->fts_path, pstat, eval_tree);
185
186 /* Deal with any side effects of applying the predicates. */
187 if (state.stop_at_current_level)
188 {
189 fts_set (p, ent, FTS_SKIP);
190 }
191 }
192
193 static const char*
194 partial_quotearg_n (int n, char *s, size_t len, enum quoting_style style)
195 {
196 if (0 == len)
197 {
198 return quotearg_n_style (n, style, "");
199 }
200 else
201 {
202 char saved;
203 const char *result;
204
205 saved = s[len];
206 s[len] = 0;
207 result = quotearg_n_style (n, style, s);
208 s[len] = saved;
209 return result;
210 }
211 }
212
213
214 /* We've detected a file system loop. This is caused by one of
215 * two things:
216 *
217 * 1. Option -L is in effect and we've hit a symbolic link that
218 * points to an ancestor. This is harmless. We won't traverse the
219 * symbolic link.
220 *
221 * 2. We have hit a real cycle in the directory hierarchy. In this
222 * case, we issue a diagnostic message (POSIX requires this) and we
223 * skip that directory entry.
224 */
225 static void
226 issue_loop_warning (FTSENT * ent)
227 {
228 if (S_ISLNK(ent->fts_statp->st_mode))
229 {
230 error (0, 0,
231 _("Symbolic link %s is part of a loop in the directory hierarchy; we have already visited the directory to which it points."),
232 safely_quote_err_filename (0, ent->fts_path));
233 }
234 else
235 {
236 /* We have found an infinite loop. POSIX requires us to
237 * issue a diagnostic. Usually we won't get to here
238 * because when the leaf optimisation is on, it will cause
239 * the subdirectory to be skipped. If /a/b/c/d is a hard
240 * link to /a/b, then the link count of /a/b/c is 2,
241 * because the ".." entry of /a/b/c/d points to /a, not
242 * to /a/b/c.
243 */
244 error (0, 0,
245 _("File system loop detected; "
246 "%s is part of the same file system loop as %s."),
247 safely_quote_err_filename (0, ent->fts_path),
248 partial_quotearg_n (1,
249 ent->fts_cycle->fts_path,
250 ent->fts_cycle->fts_pathlen,
251 options.err_quoting_style));
252 }
253 }
254
255 /*
256 * Return true if NAME corresponds to a file which forms part of a
257 * symbolic link loop. The command
258 * rm -f a b; ln -s a b; ln -s b a
259 * produces such a loop.
260 */
261 static bool
262 symlink_loop (const char *name)
263 {
264 struct stat stbuf;
265 const int rv = options.xstat (name, &stbuf);
266 return (0 != rv) && (ELOOP == errno);
267 }
268
269
270 static void
271 consider_visiting (FTS *p, FTSENT *ent)
272 {
273 struct stat statbuf;
274 mode_t mode;
275 int ignore, isdir;
276
277 if (options.debug_options & DebugSearch)
278 fprintf (stderr,
279 "consider_visiting (early): %s: "
280 "fts_info=%-6s, fts_level=%2d, prev_depth=%d "
281 "fts_path=%s, fts_accpath=%s\n",
282 quotearg_n_style (0, options.err_quoting_style, ent->fts_path),
283 get_fts_info_name (ent->fts_info),
284 (int)ent->fts_level, prev_depth,
285 quotearg_n_style (1, options.err_quoting_style, ent->fts_path),
286 quotearg_n_style (2, options.err_quoting_style, ent->fts_accpath));
287
288 if (ent->fts_info == FTS_DP)
289 {
290 left_dir ();
291 }
292 else if (ent->fts_level > prev_depth || ent->fts_level==0)
293 {
294 left_dir ();
295 }
296 inside_dir (p->fts_cwd_fd);
297 prev_depth = ent->fts_level;
298
299 statbuf.st_ino = ent->fts_statp->st_ino;
300
301 /* Cope with various error conditions. */
302 if (ent->fts_info == FTS_ERR)
303 {
304 nonfatal_target_file_error (ent->fts_errno, ent->fts_path);
305 return;
306 }
307 if (ent->fts_info == FTS_DNR)
308 {
309 nonfatal_target_file_error (ent->fts_errno, ent->fts_path);
310 if (options.do_dir_first)
311 {
312 /* Return for unreadable directories without -depth.
313 * With -depth, the directory itself has to be processed, yet the
314 * error message above has to be output.
315 */
316 return;
317 }
318 }
319 else if (ent->fts_info == FTS_DC)
320 {
321 issue_loop_warning (ent);
322 state.exit_status = EXIT_FAILURE;
323 return;
324 }
325 else if (ent->fts_info == FTS_SLNONE)
326 {
327 /* fts_read() claims that ent->fts_accpath is a broken symbolic
328 * link. That would be fine, but if this is part of a symbolic
329 * link loop, we diagnose the problem and also ensure that the
330 * eventual return value is nonzero. Note that while the path
331 * we stat is local (fts_accpath), we print the full path name
332 * of the file (fts_path) in the error message.
333 */
334 if (symlink_loop (ent->fts_accpath))
335 {
336 nonfatal_target_file_error (ELOOP, ent->fts_path);
337 return;
338 }
339 }
340 else if (ent->fts_info == FTS_NS)
341 {
342 if (ent->fts_level == 0)
343 {
344 /* e.g., nonexistent starting point */
345 nonfatal_target_file_error (ent->fts_errno, ent->fts_path);
346 return;
347 }
348 else
349 {
350 /* The following if statement fixes Savannah bug #19605
351 * (failure to diagnose a symbolic link loop)
352 */
353 if (symlink_loop (ent->fts_accpath))
354 {
355 nonfatal_target_file_error (ELOOP, ent->fts_path);
356 return;
357 }
358 else
359 {
360 nonfatal_target_file_error (ent->fts_errno, ent->fts_path);
361 /* Continue despite the error, as file name without stat info
362 * might be better than not even processing the file name. This
363 * can lead to repeated error messages later on, though, if a
364 * predicate requires stat information.
365 *
366 * Not printing an error message here would be even more wrong,
367 * though, as this could cause the contents of a directory to be
368 * silently ignored, as the directory wouldn't be identified as
369 * such.
370 */
371 }
372
373 }
374 }
375
376 /* Cope with the usual cases. */
377 if (ent->fts_info == FTS_NSOK
378 || ent->fts_info == FTS_NS /* e.g. symlink loop */)
379 {
380 assert (!state.have_stat);
381 assert (ent->fts_info == FTS_NSOK || state.type == 0);
382 mode = state.type;
383 }
384 else
385 {
386 state.have_stat = true;
387 state.have_type = true;
388 statbuf = *(ent->fts_statp);
389 state.type = mode = statbuf.st_mode;
390
391 if (00000 == mode)
392 {
393 /* Savannah bug #16378. */
394 error (0, 0, _("WARNING: file %s appears to have mode 0000"),
395 quotearg_n_style (0, options.err_quoting_style, ent->fts_path));
396 }
397 }
398
399 /* update state.curdepth before calling digest_mode(), because digest_mode
400 * may call following_links().
401 */
402 state.curdepth = ent->fts_level;
403 if (mode)
404 {
405 if (!digest_mode (&mode, ent->fts_path, ent->fts_name, &statbuf, 0))
406 return;
407 }
408
409 /* examine this item. */
410 ignore = 0;
411 isdir = S_ISDIR(mode)
412 || (FTS_D == ent->fts_info)
413 || (FTS_DP == ent->fts_info)
414 || (FTS_DC == ent->fts_info);
415
416 if (isdir && (ent->fts_info == FTS_NSOK))
417 {
418 /* This is a directory, but fts did not stat it, so
419 * presumably would not be planning to search its
420 * children. Force a stat of the file so that the
421 * children can be checked.
422 */
423 fts_set (p, ent, FTS_AGAIN);
424 return;
425 }
426
427 if (options.maxdepth >= 0)
428 {
429 if (ent->fts_level >= options.maxdepth)
430 {
431 fts_set (p, ent, FTS_SKIP); /* descend no further */
432
433 if (ent->fts_level > options.maxdepth)
434 ignore = 1; /* don't even look at this one */
435 }
436 }
437
438 if ( (ent->fts_info == FTS_D) && !options.do_dir_first )
439 {
440 /* this is the preorder visit, but user said -depth */
441 ignore = 1;
442 }
443 else if ( (ent->fts_info == FTS_DP) && options.do_dir_first )
444 {
445 /* this is the postorder visit, but user didn't say -depth */
446 ignore = 1;
447 }
448 else if (ent->fts_level < options.mindepth)
449 {
450 ignore = 1;
451 }
452
453 if (options.debug_options & DebugSearch)
454 fprintf (stderr,
455 "consider_visiting (late): %s: "
456 "fts_info=%-6s, isdir=%d ignore=%d have_stat=%d have_type=%d \n",
457 quotearg_n_style (0, options.err_quoting_style, ent->fts_path),
458 get_fts_info_name (ent->fts_info),
459 isdir, ignore, state.have_stat, state.have_type);
460
461 if (!ignore)
462 {
463 visit (p, ent, &statbuf);
464 }
465
466 if (ent->fts_info == FTS_DP)
467 {
468 /* we're leaving a directory. */
469 state.stop_at_current_level = false;
470 }
471 }
472
473
474
475 static bool
476 find (char *arg)
477 {
478 char * arglist[2];
479 FTS *p;
480 FTSENT *ent;
481
482 state.starting_path_length = strlen (arg);
483 inside_dir (AT_FDCWD);
484
485 arglist[0] = arg;
486 arglist[1] = NULL;
487
488 switch (options.symlink_handling)
489 {
490 case SYMLINK_ALWAYS_DEREF:
491 ftsoptions |= FTS_COMFOLLOW|FTS_LOGICAL;
492 break;
493
494 case SYMLINK_DEREF_ARGSONLY:
495 ftsoptions |= FTS_COMFOLLOW|FTS_PHYSICAL;
496 break;
497
498 case SYMLINK_NEVER_DEREF:
499 ftsoptions |= FTS_PHYSICAL;
500 break;
501 }
502
503 if (options.stay_on_filesystem)
504 ftsoptions |= FTS_XDEV;
505
506 p = fts_open (arglist, ftsoptions, NULL);
507 if (NULL == p)
508 {
509 error (0, errno, _("cannot search %s"),
510 safely_quote_err_filename (0, arg));
511 state.exit_status = EXIT_FAILURE;
512 }
513 else
514 {
515 int level = INT_MIN;
516
517 while ( (errno=0, ent=fts_read (p)) != NULL )
518 {
519 if (state.execdirs_outstanding && ((int)ent->fts_level != level))
520 {
521 /* If we changed level, perform any outstanding
522 * execdirs. If we see a sequence of directory entries
523 * like this: fffdfffdfff, we could build a command line
524 * of 9 files, but this simple-minded implementation
525 * builds a command line for only 3 files at a time
526 * (since fts descends into the directories).
527 */
528 complete_pending_execdirs ();
529 }
530 level = (int)ent->fts_level;
531
532 state.already_issued_stat_error_msg = false;
533 state.have_stat = false;
534 state.have_type = !!ent->fts_statp->st_mode;
535 state.type = state.have_type ? ent->fts_statp->st_mode : 0;
536 consider_visiting (p, ent);
537 }
538 /* fts_read returned NULL; distinguish between "finished" and "error". */
539 if (errno)
540 {
541 error (0, errno,
542 "failed to read file names from file system at or below %s",
543 safely_quote_err_filename (0, arg));
544 state.exit_status = EXIT_FAILURE;
545 return false;
546 }
547
548 if (0 != fts_close (p))
549 {
550 /* Here we break the abstraction of fts_close a bit, because we
551 * are going to skip the rest of the start points, and return with
552 * nonzero exit status. Hence we need to issue a diagnostic on
553 * stderr. */
554 error (0, errno,
555 _("failed to restore working directory after searching %s"),
556 arg);
557 state.exit_status = EXIT_FAILURE;
558 return false;
559 }
560 p = NULL;
561 }
562 return true;
563 }
564
565
566 static bool
567 process_all_startpoints (int argc, char *argv[])
568 {
569 /* Did the user pass starting points on the command line? */
570 bool argv_starting_points = 0 < argc && !looks_like_expression (argv[0], true);
571
572 FILE *stream = NULL;
573 char const* files0_filename_quoted = NULL;
574
575 struct argv_iterator *ai;
576 if (options.files0_from)
577 {
578 /* Option -files0-from must not be combined with passing starting points
579 * on the command line. */
580 if (argv_starting_points)
581 {
582 error (0, 0, _("extra operand %s"), safely_quote_err_filename (0, argv[0]));
583 die (EXIT_FAILURE, 0, "%s",
584 _("file operands cannot be combined with -files0-from"));
585 }
586
587 if (0 == strcmp (options.files0_from, "-"))
588 {
589 /* Option -files0-from with argument "-" (=stdin) must not be combined
590 * with the -ok, -okdir actions: getting the user confirmation would
591 * mess with stdin. */
592 if (options.ok_prompt_stdin)
593 {
594 die (EXIT_FAILURE, 0, "%s\n",
595 _("option -files0-from reading from standard input"
596 " cannot be combined with -ok, -okdir"));
597 }
598 files0_filename_quoted = safely_quote_err_filename (0, _("(standard input)"));
599 stream = stdin;
600 }
601 else
602 {
603 files0_filename_quoted = safely_quote_err_filename (0, options.files0_from);
604 stream = fopen (options.files0_from, "r");
605 if (stream == NULL)
606 die (EXIT_FAILURE, errno, _("cannot open %s for reading"),
607 files0_filename_quoted);
608
609 const int fd = fileno (stream);
610 assert (fd >= 0);
611 if (options.ok_prompt_stdin)
612 {
613 /* Check if the given file is associated to the same stream as
614 * standard input - which is not allowed with -ok, -okdir. This
615 * is the case with special device names symlinks for stdin like
616 * $ find -files0-from /dev/stdin -ok
617 * or when the given FILE is also associated to stdin:
618 * $ find -files0-from FILE -ok < FILE
619 */
620 struct stat sb1, sb2;
621 if (fstat (fd, &sb1) == 0 && fstat (STDIN_FILENO, &sb2) == 0
622 && SAME_INODE (sb1, sb2))
623 {
624 die (EXIT_FAILURE, 0, "%s: %s\n",
625 _("option -files0-from: standard input must not refer"
626 " to the same file when combined with -ok, -okdir"),
627 files0_filename_quoted);
628 }
629 }
630 set_cloexec_flag (fd, true);
631 }
632 ai = argv_iter_init_stream (stream);
633 }
634 else
635 {
636 if (!argv_starting_points)
637 {
638 /* If no starting points are given on the comman line, then
639 * fall back to processing the current directory, i.e., ".".
640 * We use a temporary variable here because some actions modify
641 * the path temporarily. Hence if we use a string constant,
642 * we get a coredump. The best example of this is if we say
643 * "find -printf %H" (note, not "find . -printf %H").
644 */
645 char defaultpath[2] = ".";
646 return find (defaultpath);
647 }
648
649 /* Process the starting point(s) from the command line. */
650 ai = argv_iter_init_argv (argv);
651 }
652
653 if (!ai)
654 xalloc_die ();
655
656 bool ok = true;
657 while (true)
658 {
659 enum argv_iter_err ai_err;
660 char *file_name = argv_iter (ai, &ai_err);
661 if (!file_name)
662 {
663 switch (ai_err)
664 {
665 case AI_ERR_EOF:
666 goto argv_iter_done;
667 case AI_ERR_READ: /* may only happen with -files0-from */
668 error (0, errno, _("%s: read error"), files0_filename_quoted);
669 state.exit_status = EXIT_FAILURE;
670 ok = false;
671 goto argv_iter_done;
672 case AI_ERR_MEM:
673 xalloc_die ();
674 default:
675 assert (!"unexpected error code from argv_iter");
676 }
677 }
678 /* Report and skip any empty file names before invoking fts.
679 This works around a glitch in fts, which fails immediately
680 (without looking at the other file names) when given an empty
681 file name. */
682 if (!file_name[0])
683 {
684 /* Diagnose a zero-length file name. When it's one
685 among many, knowing the record number may help. */
686 if (options.files0_from == NULL)
687 error (0, ENOENT, "%s", safely_quote_err_filename (0, file_name));
688 else
689 {
690 /* Using the standard 'filename:line-number:' prefix here is
691 not totally appropriate, since NUL is the separator, not NL,
692 but it might be better than nothing. */
693 unsigned long int file_number = argv_iter_n_args (ai);
694 error (0, 0, "%s:%lu: %s", files0_filename_quoted, file_number,
695 _("invalid zero-length file name"));
696 }
697 state.exit_status = EXIT_FAILURE;
698 ok = false;
699 continue;
700 }
701
702 /* Terminate loop when processing the start points from command line,
703 and reaching the first expression. */
704 if (!options.files0_from && looks_like_expression (file_name, true))
705 break;
706
707 state.starting_path_length = strlen (file_name); /* TODO: is this redundant? */
708 if (!find (file_name))
709 {
710 ok = false;
711 goto argv_iter_done;
712 }
713 }
714 argv_iter_done:
715
716 argv_iter_free (ai);
717
718 if (ok && options.files0_from && (ferror (stream) || fclose (stream) != 0))
719 die (EXIT_FAILURE, 0, _("error reading %s"), files0_filename_quoted);
720
721 return ok;
722 }
723
724
725
726
727 int
728 main (int argc, char **argv)
729 {
730 int end_of_leading_options = 0; /* First arg after any -H/-L etc. */
731 struct predicate *eval_tree;
732
733 if (argv[0])
734 set_program_name (argv[0]);
735 else
736 set_program_name ("find");
737
738 record_initial_cwd ();
739
740 state.already_issued_stat_error_msg = false;
741 state.exit_status = EXIT_SUCCESS;
742 state.execdirs_outstanding = false;
743 state.cwd_dir_fd = AT_FDCWD;
744
745 if (fd_leak_check_is_enabled ())
746 {
747 remember_non_cloexec_fds ();
748 }
749
750 state.shared_files = sharefile_init ("w");
751 if (NULL == state.shared_files)
752 {
753 die (EXIT_FAILURE, errno,
754 _("Failed to initialize shared-file hash table"));
755 }
756
757 /* Set the option defaults before we do the locale initialisation as
758 * check_nofollow() needs to be executed in the POSIX locale.
759 */
760 set_option_defaults (&options);
761
762 #ifdef HAVE_SETLOCALE
763 setlocale (LC_ALL, "");
764 #endif
765
766 bindtextdomain (PACKAGE, LOCALEDIR);
767 textdomain (PACKAGE);
768 if (atexit (close_stdout))
769 {
770 die (EXIT_FAILURE, errno, _("The atexit library function failed"));
771 }
772
773 /* Check for -P, -H or -L options. Also -D and -O, which are
774 * both GNU extensions.
775 */
776 end_of_leading_options = process_leading_options (argc, argv);
777
778 if (options.debug_options & DebugStat)
779 options.xstat = debug_stat;
780
781
782 if (options.debug_options & DebugTime)
783 fprintf (stderr, "cur_day_start = %s", ctime (&options.cur_day_start.tv_sec));
784
785
786 /* We are now processing the part of the "find" command line
787 * after the -H/-L options (if any).
788 */
789 eval_tree = build_expression_tree (argc, argv, end_of_leading_options);
790
791 /* safely_chdir() needs to check that it has ended up in the right place.
792 * To avoid bailing out when something gets automounted, it checks if
793 * the target directory appears to have had a directory mounted on it as
794 * we chdir()ed. The problem with this is that in order to notice that
795 * a file system was mounted, we would need to lstat() all the mount points.
796 * That strategy loses if our machine is a client of a dead NFS server.
797 *
798 * Hence if safely_chdir() and wd_sanity_check() can manage without needing
799 * to know the mounted device list, we do that.
800 */
801 if (!options.open_nofollow_available)
802 {
803 #ifdef STAT_MOUNTPOINTS
804 init_mounted_dev_list ();
805 #endif
806 }
807
808
809 /* process_all_startpoints processes the starting points named on
810 * the command line. A false return value from it means that we
811 * failed to restore the original context. That means it would not
812 * be safe to call cleanup() since we might complete an execdir in
813 * the wrong directory for example.
814 */
815 if (process_all_startpoints (argc-end_of_leading_options,
816 argv+end_of_leading_options))
817 {
818 /* If "-exec ... {} +" has been used, there may be some
819 * partially-full command lines which have been built,
820 * but which are not yet complete. Execute those now.
821 */
822 show_success_rates (eval_tree);
823 cleanup ();
824 }
825 return state.exit_status;
826 }
827
828 bool
829 is_fts_enabled (int *fts_options)
830 {
831 /* this version of find (i.e. this main()) uses fts. */
832 *fts_options = ftsoptions;
833 return true;
834 }