source: trunk/essentials/sys-apps/findutils/find/find.c@ 3175

Last change on this file since 3175 was 3174, checked in by bird, 19 years ago

silly!

File size: 38.9 KB
RevLine 
[3170]1/* find -- search for files in a directory hierarchy
2 Copyright (C) 1990, 91, 92, 93, 94, 2000,
3 2003, 2004, 2005 Free Software Foundation, Inc.
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2, or (at your option)
8 any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
18 USA.*/
19
20/* GNU find was written by Eric Decker <[email protected]>,
21 with enhancements by David MacKenzie <[email protected]>,
22 Jay Plett <[email protected]>,
23 and Tim Wood <[email protected]>.
24 The idea for -print0 and xargs -0 came from
25 Dan Bernstein <[email protected]>.
26 Improvements have been made by James Youngman <[email protected]>.
27*/
28
29
30#include "defs.h"
31
32#define USE_SAFE_CHDIR 1
33#undef STAT_MOUNTPOINTS
34
35
36#include <errno.h>
37#include <assert.h>
38
39
40#ifdef HAVE_FCNTL_H
41#include <fcntl.h>
42#else
43#include <sys/file.h>
44#endif
45
46#include "../gnulib/lib/xalloc.h"
47#include "../gnulib/lib/human.h"
48#include "../gnulib/lib/canonicalize.h"
49#include "closeout.h"
50#include <modetype.h>
51#include "savedirinfo.h"
52#include "buildcmd.h"
53#include "dirname.h"
54#include "quote.h"
55#include "quotearg.h"
56
57#ifdef HAVE_LOCALE_H
58#include <locale.h>
59#endif
60
61#if ENABLE_NLS
62# include <libintl.h>
63# define _(Text) gettext (Text)
64#else
65# define _(Text) Text
66#define textdomain(Domain)
67#define bindtextdomain(Package, Directory)
68#endif
69#ifdef gettext_noop
70# define N_(String) gettext_noop (String)
71#else
72/* See locate.c for explanation as to why not use (String) */
73# define N_(String) String
74#endif
75
76#define apply_predicate(pathname, stat_buf_ptr, node) \
77 (*(node)->pred_func)((pathname), (stat_buf_ptr), (node))
78
79#ifdef STAT_MOUNTPOINTS
80static void init_mounted_dev_list(int mandatory);
81#endif
82
83static void process_top_path PARAMS((char *pathname, mode_t mode));
84static int process_path PARAMS((char *pathname, char *name, boolean leaf, char *parent, mode_t type));
85static void process_dir PARAMS((char *pathname, char *name, int pathlen, struct stat *statp, char *parent));
86
87
88
89/* Name this program was run with. */
90char *program_name;
91
92/* A file descriptor open to the initial working directory.
93 Doing it this way allows us to work when the i.w.d. has
94 unreadable parents. */
95int starting_desc;
96
97/* The stat buffer of the initial working directory. */
98static struct stat starting_stat_buf;
99
100enum ChdirSymlinkHandling
101 {
102 SymlinkHandleDefault, /* Normally the right choice */
103 SymlinkFollowOk /* see comment in process_top_path() */
104 };
105
106
107enum TraversalDirection
108 {
109 TraversingUp,
110 TraversingDown
111 };
112
113enum WdSanityCheckFatality
114 {
115 FATAL_IF_SANITY_CHECK_FAILS,
116 RETRY_IF_SANITY_CHECK_FAILS,
117 NON_FATAL_IF_SANITY_CHECK_FAILS
118 };
119
120
121
122int
123main (int argc, char **argv)
124{
125 int i;
126 int end_of_leading_options = 0; /* First arg after any -H/-L etc. */
127 struct predicate *eval_tree;
128
129 program_name = argv[0];
130 state.exit_status = 0;
131
132 /* Set the option defaults before we do the the locale
133 * initialisation as check_nofollow() needs to be executed in the
134 * POSIX locale.
135 */
136 set_option_defaults(&options);
137
138#ifdef HAVE_SETLOCALE
139 setlocale (LC_ALL, "");
140#endif
141 bindtextdomain (PACKAGE, LOCALEDIR);
142 textdomain (PACKAGE);
143 atexit (close_stdout);
144
145 /* Check for -P, -H or -L options. */
146 end_of_leading_options = process_leading_options(argc, argv);
147
148 if (options.debug_options & DebugStat)
149 options.xstat = debug_stat;
150
[3174]151#ifdef DEBUG
[3170]152/* fprintf (stderr, "cur_day_start = %s", ctime (&p->cur_day_start)); - what's "p"? */
153#endif /* DEBUG */
154
155 /* We are now processing the part of the "find" command line
156 * after the -H/-L options (if any).
157 */
158 eval_tree = build_expression_tree(argc, argv, end_of_leading_options);
159
160
161 /* safely_chdir() needs to check that it has ended up in the right place.
162 * To avoid bailing out when something gets automounted, it checks if
163 * the target directory appears to have had a directory mounted on it as
164 * we chdir()ed. The problem with this is that in order to notice that
165 * a filesystem was mounted, we would need to lstat() all the mount points.
166 * That strategy loses if our machine is a client of a dead NFS server.
167 *
168 * Hence if safely_chdir() and wd_sanity_check() can manage without needing
169 * to know the mounted device list, we do that.
170 */
171 if (!options.open_nofollow_available)
172 {
173#ifdef STAT_MOUNTPOINTS
174 init_mounted_dev_list(0);
175#endif
176 }
177
178
179 starting_desc = open (".", O_RDONLY);
180 if (0 <= starting_desc && fchdir (starting_desc) != 0)
181 {
182 close (starting_desc);
183 starting_desc = -1;
184 }
185 if (starting_desc < 0)
186 {
187 starting_dir = xgetcwd ();
188 if (! starting_dir)
189 error (1, errno, _("cannot get current directory"));
190 }
191 if ((*options.xstat) (".", &starting_stat_buf) != 0)
192 error (1, errno, _("cannot get current directory"));
193
194 /* If no paths are given, default to ".". */
195 for (i = end_of_leading_options; i < argc && !looks_like_expression(argv[i], true); i++)
196 {
197 process_top_path (argv[i], 0);
198 }
199
200 /* If there were no path arguments, default to ".". */
201 if (i == end_of_leading_options)
202 {
203 /*
204 * We use a temporary variable here because some actions modify
205 * the path temporarily. Hence if we use a string constant,
206 * we get a coredump. The best example of this is if we say
207 * "find -printf %H" (note, not "find . -printf %H").
208 */
209 char defaultpath[2] = ".";
210 process_top_path (defaultpath, 0);
211 }
212
213 /* If "-exec ... {} +" has been used, there may be some
214 * partially-full command lines which have been built,
215 * but which are not yet complete. Execute those now.
216 */
217 cleanup();
218 return state.exit_status;
219}
220
221
222boolean is_fts_enabled()
223{
224 /* this version of find (i.e. this main()) does not use fts. */
225 return false;
226}
227
228
229
230static char *
231specific_dirname(const char *dir)
232{
233 char dirbuf[1024];
234
235 if (0 == strcmp(".", dir))
236 {
237 /* OK, what's '.'? */
238 if (NULL != getcwd(dirbuf, sizeof(dirbuf)))
239 {
240 return strdup(dirbuf);
241 }
242 else
243 {
244 return strdup(dir);
245 }
246 }
247 else
248 {
249 char *result = canonicalize_filename_mode(dir, CAN_EXISTING);
250 if (NULL == result)
251 return strdup(dir);
252 else
253 return result;
254 }
255}
256
257
258
259
260/* Return non-zero if FS is the name of a filesystem that is likely to
261 * be automounted
262 */
263static int
264fs_likely_to_be_automounted(const char *fs)
265{
266 return ( (0==strcmp(fs, "nfs")) || (0==strcmp(fs, "autofs")) || (0==strcmp(fs, "subfs")));
267}
268
269
270
271#ifdef STAT_MOUNTPOINTS
272static dev_t *mounted_devices = NULL;
273static size_t num_mounted_devices = 0u;
274
275
276static void
277init_mounted_dev_list(int mandatory)
278{
279 assert(NULL == mounted_devices);
280 assert(0 == num_mounted_devices);
281 mounted_devices = get_mounted_devices(&num_mounted_devices);
282 if (mandatory && (NULL == mounted_devices))
283 {
284 error(1, 0, "Cannot read list of mounted devices.");
285 }
286}
287
288static void
289refresh_mounted_dev_list(void)
290{
291 if (mounted_devices)
292 {
293 free(mounted_devices);
294 mounted_devices = 0;
295 }
296 num_mounted_devices = 0u;
297 init_mounted_dev_list(1);
298}
299
300
301/* Search for device DEV in the array LIST, which is of size N. */
302static int
303dev_present(dev_t dev, const dev_t *list, size_t n)
304{
305 if (list)
306 {
307 while (n-- > 0u)
308 {
309 if ( (*list++) == dev )
310 return 1;
311 }
312 }
313 return 0;
314}
315
316enum MountPointStateChange
317 {
318 MountPointRecentlyMounted,
319 MountPointRecentlyUnmounted,
320 MountPointStateUnchanged
321 };
322
323
324
325static enum MountPointStateChange
326get_mount_state(dev_t newdev)
327{
328 int new_is_present, new_was_present;
329
330 new_was_present = dev_present(newdev, mounted_devices, num_mounted_devices);
331 refresh_mounted_dev_list();
332 new_is_present = dev_present(newdev, mounted_devices, num_mounted_devices);
333
334 if (new_was_present == new_is_present)
335 return MountPointStateUnchanged;
336 else if (new_is_present)
337 return MountPointRecentlyMounted;
338 else
339 return MountPointRecentlyUnmounted;
340}
341
342
343
344/* We stat()ed a directory, chdir()ed into it (we know this
345 * since direction is TraversingDown), stat()ed it again,
346 * and noticed that the device numbers are different. Check
347 * if the filesystem was recently mounted.
348 *
349 * If it was, it looks like chdir()ing into the directory
350 * caused a filesystem to be mounted. Maybe automount is
351 * running. Anyway, that's probably OK - but it happens
352 * only when we are moving downward.
353 *
354 * We also allow for the possibility that a similar thing
355 * has happened with the unmounting of a filesystem. This
356 * is much rarer, as it relies on an automounter timeout
357 * occurring at exactly the wrong moment.
358 */
359static enum WdSanityCheckFatality
360dirchange_is_fatal(const char *specific_what,
361 enum WdSanityCheckFatality isfatal,
362 int silent,
363 struct stat *newinfo)
364{
365 enum MountPointStateChange transition = get_mount_state(newinfo->st_dev);
366 switch (transition)
367 {
368 case MountPointRecentlyUnmounted:
369 isfatal = NON_FATAL_IF_SANITY_CHECK_FAILS;
370 if (!silent)
371 {
372 error (0, 0,
373 _("Warning: filesystem %s has recently been unmounted."),
374 specific_what);
375 }
376 break;
377
378 case MountPointRecentlyMounted:
379 isfatal = NON_FATAL_IF_SANITY_CHECK_FAILS;
380 if (!silent)
381 {
382 error (0, 0,
383 _("Warning: filesystem %s has recently been mounted."),
384 specific_what);
385 }
386 break;
387
388 case MountPointStateUnchanged:
389 /* leave isfatal as it is */
390 break;
391 }
392
393 return isfatal;
394}
395
396
397#endif
398
399
400
401/* Examine the results of the stat() of a directory from before we
402 * entered or left it, with the results of stat()ing it afterward. If
403 * these are different, the filesystem tree has been modified while we
404 * were traversing it. That might be an attempt to use a race
405 * condition to persuade find to do something it didn't intend
406 * (e.g. an attempt by an ordinary user to exploit the fact that root
407 * sometimes runs find on the whole filesystem). However, this can
408 * also happen if automount is running (certainly on Solaris). With
409 * automount, moving into a directory can cause a filesystem to be
410 * mounted there.
411 *
412 * To cope sensibly with this, we will raise an error if we see the
413 * device number change unless we are chdir()ing into a subdirectory,
414 * and the directory we moved into has been mounted or unmounted "recently".
415 * Here "recently" means since we started "find" or we last re-read
416 * the /etc/mnttab file.
417 *
418 * If the device number does not change but the inode does, that is a
419 * problem.
420 *
421 * If the device number and inode are both the same, we are happy.
422 *
423 * If a filesystem is (un)mounted as we chdir() into the directory, that
424 * may mean that we're now examining a section of the filesystem that might
425 * have been excluded from consideration (via -prune or -quit for example).
426 * Hence we print a warning message to indicate that the output of find
427 * might be inconsistent due to the change in the filesystem.
428 */
429static boolean
430wd_sanity_check(const char *thing_to_stat,
431 const char *progname,
432 const char *what,
433 dev_t old_dev,
434 ino_t old_ino,
435 struct stat *newinfo,
436 int parent,
437 int line_no,
438 enum TraversalDirection direction,
439 enum WdSanityCheckFatality isfatal,
440 boolean *changed) /* output parameter */
441{
442 const char *fstype;
443 char *specific_what = NULL;
444 int silent = 0;
445 const char *current_dir = ".";
446
447 *changed = false;
448
449 if ((*options.xstat) (current_dir, newinfo) != 0)
450 error (1, errno, "%s", thing_to_stat);
451
452 if (old_dev != newinfo->st_dev)
453 {
454 *changed = true;
455 specific_what = specific_dirname(what);
456 fstype = filesystem_type(newinfo, current_dir);
457 silent = fs_likely_to_be_automounted(fstype);
458
459 /* This condition is rare, so once we are here it is
460 * reasonable to perform an expensive computation to
461 * determine if we should continue or fail.
462 */
463 if (TraversingDown == direction)
464 {
465#ifdef STAT_MOUNTPOINTS
466 isfatal = dirchange_is_fatal(specific_what,isfatal,silent,newinfo);
467#else
468 isfatal = RETRY_IF_SANITY_CHECK_FAILS;
469#endif
470 }
471
472 switch (isfatal)
473 {
474 case FATAL_IF_SANITY_CHECK_FAILS:
475 {
476 fstype = filesystem_type(newinfo, current_dir);
477 error (1, 0,
478 _("%s%s changed during execution of %s (old device number %ld, new device number %ld, filesystem type is %s) [ref %ld]"),
479 specific_what,
480 parent ? "/.." : "",
481 progname,
482 (long) old_dev,
483 (long) newinfo->st_dev,
484 fstype,
485 line_no);
486 /*NOTREACHED*/
487 return false;
488 }
489
490 case NON_FATAL_IF_SANITY_CHECK_FAILS:
491 {
492 /* Since the device has changed under us, the inode number
493 * will almost certainly also be different. However, we have
494 * already decided that this is not a problem. Hence we return
495 * without checking the inode number.
496 */
497 free(specific_what);
498 return true;
499 }
500
501 case RETRY_IF_SANITY_CHECK_FAILS:
502 return false;
503 }
504 }
505
506 /* Device number was the same, check if the inode has changed. */
507 if (old_ino != newinfo->st_ino)
508 {
509 *changed = true;
510 specific_what = specific_dirname(what);
511 fstype = filesystem_type(newinfo, current_dir);
512
513 error ((isfatal == FATAL_IF_SANITY_CHECK_FAILS) ? 1 : 0,
514 0, /* no relevant errno value */
515 _("%s%s changed during execution of %s (old inode number %ld, new inode number %ld, filesystem type is %s) [ref %ld]"),
516 specific_what,
517 parent ? "/.." : "",
518 progname,
519 (long) old_ino,
520 (long) newinfo->st_ino,
521 fstype,
522 line_no);
523 free(specific_what);
524 return false;
525 }
526
527 return true;
528}
529
530
531enum SafeChdirStatus
532 {
533 SafeChdirOK,
534 SafeChdirFailSymlink,
535 SafeChdirFailNotDir,
536 SafeChdirFailStat,
537 SafeChdirFailWouldBeUnableToReturn,
538 SafeChdirFailChdirFailed,
539 SafeChdirFailNonexistent
540 };
541
542/* Safely perform a change in directory. We do this by calling
543 * lstat() on the subdirectory, using chdir() to move into it, and
544 * then lstat()ing ".". We compare the results of the two stat calls
545 * to see if they are consistent. If not, we sound the alarm.
546 *
547 * If following_links() is true, we do follow symbolic links.
548 */
549static enum SafeChdirStatus
550safely_chdir_lstat(const char *dest,
551 enum TraversalDirection direction,
552 struct stat *statbuf_dest,
553 enum ChdirSymlinkHandling symlink_follow_option,
554 boolean *did_stat)
555{
556 struct stat statbuf_arrived;
557 int rv, dotfd=-1;
558 int saved_errno; /* specific_dirname() changes errno. */
559 boolean rv_set = false;
560 boolean statflag = false;
561 int tries = 0;
562 enum WdSanityCheckFatality isfatal = RETRY_IF_SANITY_CHECK_FAILS;
563
564 saved_errno = errno = 0;
565
566 dotfd = open(".", O_RDONLY);
567
568 /* We jump back to here if wd_sanity_check()
569 * recoverably triggers an alert.
570 */
571 retry:
572 ++tries;
573
574 if (dotfd >= 0)
575 {
576 /* Stat the directory we're going to. */
577 if (0 == options.xstat(dest, statbuf_dest))
578 {
579 statflag = true;
580
581#ifdef S_ISLNK
582 /* symlink_follow_option might be set to SymlinkFollowOk, which
583 * would allow us to chdir() into a symbolic link. This is
584 * only useful for the case where the directory we're
585 * chdir()ing into is the basename of a command line
586 * argument, for example where "foo/bar/baz" is specified on
587 * the command line. When -P is in effect (the default),
588 * baz will not be followed if it is a symlink, but if bar
589 * is a symlink, it _should_ be followed. Hence we need the
590 * ability to override the policy set by following_links().
591 */
592 if (!following_links() && S_ISLNK(statbuf_dest->st_mode))
593 {
594 /* We're not supposed to be following links, but this is
595 * a link. Check symlink_follow_option to see if we should
596 * make a special exception.
597 */
598 if (symlink_follow_option == SymlinkFollowOk)
599 {
600 /* We need to re-stat() the file so that the
601 * sanity check can pass.
602 */
603 if (0 != stat(dest, statbuf_dest))
604 {
605 rv = SafeChdirFailNonexistent;
606 rv_set = true;
607 saved_errno = errno;
608 goto fail;
609 }
610 statflag = true;
611 }
612 else
613 {
614 /* Not following symlinks, so the attempt to
615 * chdir() into a symlink should be prevented.
616 */
617 rv = SafeChdirFailSymlink;
618 rv_set = true;
619 saved_errno = 0; /* silence the error message */
620 goto fail;
621 }
622 }
623#endif
624#ifdef S_ISDIR
625 /* Although the immediately following chdir() would detect
626 * the fact that this is not a directory for us, this would
627 * result in an extra system call that fails. Anybody
628 * examining the system-call trace should ideally not be
629 * concerned that something is actually failing.
630 */
631 if (!S_ISDIR(statbuf_dest->st_mode))
632 {
633 rv = SafeChdirFailNotDir;
634 rv_set = true;
635 saved_errno = 0; /* silence the error message */
636 goto fail;
637 }
638#endif
639
640 if (options.debug_options & DebugSearch)
641 fprintf(stderr, "safely_chdir(): chdir(\"%s\")\n", dest);
642
643 if (0 == chdir(dest))
644 {
645 /* check we ended up where we wanted to go */
646 boolean changed = false;
647 if (!wd_sanity_check(".", program_name, ".",
648 statbuf_dest->st_dev,
649 statbuf_dest->st_ino,
650 &statbuf_arrived,
651 0, __LINE__, direction,
652 isfatal,
653 &changed))
654 {
655 /* Only allow one failure. */
656 if (RETRY_IF_SANITY_CHECK_FAILS == isfatal)
657 {
658 if (0 == fchdir(dotfd))
659 {
660 isfatal = FATAL_IF_SANITY_CHECK_FAILS;
661 goto retry;
662 }
663 else
664 {
665 /* Failed to return to original directory,
666 * but we know that the current working
667 * directory is not the one that we intend
668 * to be in. Since fchdir() failed, we
669 * can't recover from this and so this error
670 * is fatal.
671 */
672 error(1, errno,
673 "failed to return to parent directory");
674 }
675 }
676 else
677 {
678 /* XXX: not sure what to use as an excuse here. */
679 rv = SafeChdirFailNonexistent;
680 rv_set = true;
681 saved_errno = 0;
682 goto fail;
683 }
684 }
685
686 close(dotfd);
687 return SafeChdirOK;
688 }
689 else
690 {
691 saved_errno = errno;
692 if (ENOENT == saved_errno)
693 {
694 rv = SafeChdirFailNonexistent;
695 rv_set = true;
696 if (options.ignore_readdir_race)
697 errno = 0; /* don't issue err msg */
698 }