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

Last change on this file was 3170, checked in by bird, 18 years ago

findutils 4.3.2

File size: 13.1 KB
Line 
1/* find -- search for files in a directory hierarchy (fts version)
2 Copyright (C) 1990, 91, 92, 93, 94, 2000,
3 2003, 2004, 2005, 2006 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/* This file was written by James Youngman, based on find.c.
21
22 GNU find was written by Eric Decker <cire@cisco.com>,
23 with enhancements by David MacKenzie <djm@gnu.org>,
24 Jay Plett <jay@silence.princeton.nj.us>,
25 and Tim Wood <axolotl!tim@toad.com>.
26 The idea for -print0 and xargs -0 came from
27 Dan Bernstein <brnstnd@kramden.acf.nyu.edu>.
28*/
29
30
31#include "defs.h"
32
33
34#define USE_SAFE_CHDIR 1
35#undef STAT_MOUNTPOINTS
36
37
38#include <errno.h>
39#include <assert.h>
40
41#ifdef HAVE_FCNTL_H
42#include <fcntl.h>
43#else
44#include <sys/file.h>
45#endif
46
47
48#include "../gnulib/lib/xalloc.h"
49#include "closeout.h"
50#include <modetype.h>
51#include "quotearg.h"
52#include "quote.h"
53#include "fts_.h"
54
55#ifdef HAVE_LOCALE_H
56#include <locale.h>
57#endif
58
59#if ENABLE_NLS
60# include <libintl.h>
61# define _(Text) gettext (Text)
62#else
63# define _(Text) Text
64#define textdomain(Domain)
65#define bindtextdomain(Package, Directory)
66#endif
67#ifdef gettext_noop
68# define N_(String) gettext_noop (String)
69#else
70/* See locate.c for explanation as to why not use (String) */
71# define N_(String) String
72#endif
73
74
75#ifdef STAT_MOUNTPOINTS
76static void init_mounted_dev_list(void);
77#endif
78
79
80/* We have encountered an error which shoudl affect the exit status.
81 * This is normally used to change the exit status from 0 to 1.
82 * However, if the exit status is already 2 for example, we don't want to
83 * reduce it to 1.
84 */
85static void
86error_severity(int level)
87{
88 if (state.exit_status < level)
89 state.exit_status = level;
90}
91
92
93
94#define STRINGIFY(X) #X
95#define HANDLECASE(N) case N: return #N;
96
97static char *
98get_fts_info_name(int info)
99{
100 static char buf[10];
101 switch (info)
102 {
103 HANDLECASE(FTS_D);
104 HANDLECASE(FTS_DC);
105 HANDLECASE(FTS_DEFAULT);
106 HANDLECASE(FTS_DNR);
107 HANDLECASE(FTS_DOT);
108 HANDLECASE(FTS_DP);
109 HANDLECASE(FTS_ERR);
110 HANDLECASE(FTS_F);
111 HANDLECASE(FTS_INIT);
112 HANDLECASE(FTS_NS);
113 HANDLECASE(FTS_NSOK);
114 HANDLECASE(FTS_SL);
115 HANDLECASE(FTS_SLNONE);
116 HANDLECASE(FTS_W);
117 default:
118 sprintf(buf, "[%d]", info);
119 return buf;
120 }
121}
122
123
124static void
125visit(FTS *p, FTSENT *ent, struct stat *pstat)
126{
127 struct predicate *eval_tree;
128
129 state.curdepth = ent->fts_level;
130 state.have_stat = (ent->fts_info != FTS_NS) && (ent->fts_info != FTS_NSOK);
131 state.rel_pathname = ent->fts_accpath;
132
133 /* Apply the predicates to this path. */
134 eval_tree = get_eval_tree();
135 (*(eval_tree)->pred_func)(ent->fts_path, pstat, eval_tree);
136
137 /* Deal with any side effects of applying the predicates. */
138 if (state.stop_at_current_level)
139 {
140 fts_set(p, ent, FTS_SKIP);
141 }
142}
143
144
145static const char*
146partial_quotearg_n(int n, char *s, size_t len, enum quoting_style style)
147{
148 if (0 == len)
149 {
150 return quotearg_n_style(n, style, "");
151 }
152 else
153 {
154 char saved;
155 const char *result;
156
157 saved = s[len];
158 s[len] = 0;
159 result = quotearg_n_style(n, style, s);
160 s[len] = saved;
161 return result;
162 }
163}
164
165
166
167/* We've detected a filesystem loop. This is caused by one of
168 * two things:
169 *
170 * 1. Option -L is in effect and we've hit a symbolic link that
171 * points to an ancestor. This is harmless. We won't traverse the
172 * symbolic link.
173 *
174 * 2. We have hit a real cycle in the directory hierarchy. In this
175 * case, we issue a diagnostic message (POSIX requires this) and we
176 * skip that directory entry.
177 */
178static void
179issue_loop_warning(FTSENT * ent)
180{
181 if (S_ISLNK(ent->fts_statp->st_mode))
182 {
183 error(0, 0,
184 _("Symbolic link %s is part of a loop in the directory hierarchy; we have already visited the directory to which it points."),
185 quotearg_n_style(0, locale_quoting_style, ent->fts_path));
186 }
187 else
188 {
189 /* We have found an infinite loop. POSIX requires us to
190 * issue a diagnostic. Usually we won't get to here
191 * because when the leaf optimisation is on, it will cause
192 * the subdirectory to be skipped. If /a/b/c/d is a hard
193 * link to /a/b, then the link count of /a/b/c is 2,
194 * because the ".." entry of /b/b/c/d points to /a, not
195 * to /a/b/c.
196 */
197 error(0, 0,
198 _("Filesystem loop detected; "
199 "%s is part of the same filesystem loop as %s."),
200 quotearg_n_style(0, locale_quoting_style, ent->fts_path),
201 partial_quotearg_n(1,
202 ent->fts_cycle->fts_path,
203 ent->fts_cycle->fts_pathlen,
204 locale_quoting_style));
205 }
206}
207
208
209/*
210 * Return true if NAME corresponds to a file which forms part of a
211 * symbolic link loop. The command
212 * rm -f a b; ln -s a b; ln -s b a
213 * produces such a loop.
214 */
215static boolean
216symlink_loop(const char *name)
217{
218 struct stat stbuf;
219 int rv;
220 if (following_links())
221 rv = stat(name, &stbuf);
222 else
223 rv = lstat(name, &stbuf);
224 return (0 != rv) && (ELOOP == errno);
225}
226
227
228
229static void
230consider_visiting(FTS *p, FTSENT *ent)
231{
232 struct stat statbuf;
233 mode_t mode;
234 int ignore, isdir;
235
236 if (options.debug_options & DebugSearch)
237 fprintf(stderr,
238 "consider_visiting: fts_info=%-6s, fts_level=%2d, "
239 "fts_path=%s\n",
240 get_fts_info_name(ent->fts_info),
241 (int)ent->fts_level,
242 quotearg_n_style(0, locale_quoting_style, ent->fts_path));
243
244 /* Cope with various error conditions. */
245 if (ent->fts_info == FTS_ERR
246 || ent->fts_info == FTS_NS
247 || ent->fts_info == FTS_DNR)
248 {
249 error(0, ent->fts_errno, ent->fts_path);
250 error_severity(1);
251 return;
252 }
253 else if (ent->fts_info == FTS_DC)
254 {
255 issue_loop_warning(ent);
256 error_severity(1);
257 return;
258 }
259 else if (ent->fts_info == FTS_SLNONE)
260 {
261 /* fts_read() claims that ent->fts_accpath is a broken symbolic
262 * link. That would be fine, but if this is part of a symbolic
263 * link loop, we diagnose the problem and also ensure that the
264 * eventual return value is nonzero. Note that while the path
265 * we stat is local (fts_accpath), we print the fill path name
266 * of the file (fts_path) in the error message.
267 */
268 if (symlink_loop(ent->fts_accpath))
269 {
270 error(0, ELOOP, ent->fts_path);
271 error_severity(1);
272 return;
273 }
274 }
275
276 /* Not an error, cope with the usual cases. */
277 if (ent->fts_info == FTS_NSOK)
278 {
279 assert(!state.have_stat);
280 assert(!state.have_type);
281 state.type = mode = 0;
282 }
283 else
284 {
285 state.have_stat = true;
286 state.have_type = true;
287 statbuf = *(ent->fts_statp);
288 state.type = mode = statbuf.st_mode;
289 }
290
291 if (mode)
292 {
293 if (!digest_mode(mode, ent->fts_path, ent->fts_name, &statbuf, 0))
294 return;
295 }
296
297 /* examine this item. */
298 ignore = 0;
299 isdir = S_ISDIR(statbuf.st_mode)
300 || (FTS_D == ent->fts_info)
301 || (FTS_DP == ent->fts_info)
302 || (FTS_DC == ent->fts_info);
303
304 if (isdir && (ent->fts_info == FTS_NSOK))
305 {
306 /* This is a directory, but fts did not stat it, so
307 * presumably would not be planning to search its
308 * children. Force a stat of the file so that the
309 * children can be checked.
310 */
311 fts_set(p, ent, FTS_AGAIN);
312 return;
313 }
314
315 if (options.maxdepth >= 0)
316 {
317 if (ent->fts_level >= options.maxdepth)
318 {
319 fts_set(p, ent, FTS_SKIP); /* descend no further */
320
321 if (ent->fts_level > options.maxdepth)
322 ignore = 1; /* don't even look at this one */
323 }
324 }
325
326 if ( (ent->fts_info == FTS_D) && !options.do_dir_first )
327 {
328 /* this is the preorder visit, but user said -depth */
329 ignore = 1;
330 }
331 else if ( (ent->fts_info == FTS_DP) && options.do_dir_first )
332 {
333 /* this is the postorder visit, but user didn't say -depth */
334 ignore = 1;
335 }
336 else if (ent->fts_level < options.mindepth)
337 {
338 ignore = 1;
339 }
340
341 if (!ignore)
342 {
343 visit(p, ent, &statbuf);
344 }
345
346
347 if (ent->fts_info == FTS_DP)
348 {
349 /* we're leaving a directory. */
350 state.stop_at_current_level = false;
351 complete_pending_execdirs(get_eval_tree());
352 }
353}
354
355
356
357static void
358find(char *arg)
359{
360 char * arglist[2];
361 int ftsoptions;
362 FTS *p;
363 FTSENT *ent;
364
365
366 state.starting_path_length = strlen(arg);
367
368 arglist[0] = arg;
369 arglist[1] = NULL;
370
371 ftsoptions = FTS_NOSTAT;
372
373 /* Work around Savannah bug #17877, which manifests on systems which
374 * use the same inode number for more than one file (smbfs, FAT,
375 * sometimes some FUSE-based ones). This happens for unreferenced
376 * files. Fix suggested by Jim Meyering.
377 */
378 ftsoptions |= FTS_TIGHT_CYCLE_CHECK;
379
380 switch (options.symlink_handling)
381 {
382 case SYMLINK_ALWAYS_DEREF:
383 ftsoptions |= FTS_COMFOLLOW|FTS_LOGICAL;
384 break;
385
386 case SYMLINK_DEREF_ARGSONLY:
387 ftsoptions |= FTS_COMFOLLOW|FTS_PHYSICAL;
388 break;
389
390 case SYMLINK_NEVER_DEREF:
391 ftsoptions |= FTS_PHYSICAL;
392 break;
393 }
394
395 if (options.stay_on_filesystem)
396 ftsoptions |= FTS_XDEV;
397
398 p = fts_open(arglist, ftsoptions, NULL);
399 if (NULL == p)
400 {
401 error (0, errno,
402 _("cannot search %s"),
403 quotearg_n_style(0, locale_quoting_style, arg));
404 }
405 else
406 {
407 while ( (ent=fts_read(p)) != NULL )
408 {
409 state.have_stat = false;
410 state.have_type = false;
411 state.type = 0;
412
413 consider_visiting(p, ent);
414 }
415 fts_close(p);
416 p = NULL;
417 }
418}
419
420
421static void
422process_all_startpoints(int argc, char *argv[])
423{
424 int i;
425
426 /* figure out how many start points there are */
427 for (i = 0; i < argc && !looks_like_expression(argv[i], true); i++)
428 {
429 state.starting_path_length = strlen(argv[i]);
430 find(argv[i]);
431 }
432
433 if (i == 0)
434 {
435 /*
436 * We use a temporary variable here because some actions modify
437 * the path temporarily. Hence if we use a string constant,
438 * we get a coredump. The best example of this is if we say
439 * "find -printf %H" (note, not "find . -printf %H").
440 */
441 char defaultpath[2] = ".";
442 find(defaultpath);
443 }
444}
445
446
447
448
449
450int
451main (int argc, char **argv)
452{
453 int end_of_leading_options = 0; /* First arg after any -H/-L etc. */
454 struct predicate *eval_tree;
455
456 program_name = argv[0];
457 state.exit_status = 0;
458
459
460 /* Set the option defaults before we do the the locale
461 * initialisation as check_nofollow() needs to be executed in the
462 * POSIX locale.
463 */
464 set_option_defaults(&options);
465
466#ifdef HAVE_SETLOCALE
467 setlocale (LC_ALL, "");
468#endif
469
470 bindtextdomain (PACKAGE, LOCALEDIR);
471 textdomain (PACKAGE);
472 atexit (close_stdout);
473
474 /* Check for -P, -H or -L options. Also -D and -O, which are
475 * both GNU extensions.
476 */
477 end_of_leading_options = process_leading_options(argc, argv);
478
479 if (options.debug_options & DebugStat)
480 options.xstat = debug_stat;
481
482#ifdef DEBUG
483 fprintf (stderr, "cur_day_start = %s", ctime (&options.cur_day_start));
484#endif /* DEBUG */
485
486
487 /* We are now processing the part of the "find" command line
488 * after the -H/-L options (if any).
489 */
490 eval_tree = build_expression_tree(argc, argv, end_of_leading_options);
491
492 /* safely_chdir() needs to check that it has ended up in the right place.
493 * To avoid bailing out when something gets automounted, it checks if
494 * the target directory appears to have had a directory mounted on it as
495 * we chdir()ed. The problem with this is that in order to notice that
496 * a filesystem was mounted, we would need to lstat() all the mount points.
497 * That strategy loses if our machine is a client of a dead NFS server.
498 *
499 * Hence if safely_chdir() and wd_sanity_check() can manage without needing
500 * to know the mounted device list, we do that.
501 */
502 if (!options.open_nofollow_available)
503 {
504#ifdef STAT_MOUNTPOINTS
505 init_mounted_dev_list();
506#endif
507 }
508
509
510 starting_desc = open (".", O_RDONLY);
511 if (0 <= starting_desc && fchdir (starting_desc) != 0)
512 {
513 close (starting_desc);
514 starting_desc = -1;
515 }
516 if (starting_desc < 0)
517 {
518 starting_dir = xgetcwd ();
519 if (! starting_dir)
520 error (1, errno, _("cannot get current directory"));
521 }
522
523
524 process_all_startpoints(argc-end_of_leading_options, argv+end_of_leading_options);
525
526 /* If "-exec ... {} +" has been used, there may be some
527 * partially-full command lines which have been built,
528 * but which are not yet complete. Execute those now.
529 */
530 cleanup();
531 return state.exit_status;
532}
533
534boolean is_fts_enabled()
535{
536 /* this version of find (i.e. this main()) uses fts. */
537 return true;
538}
Note: See TracBrowser for help on using the repository browser.