summaryrefslogtreecommitdiff
path: root/find/find.c
blob: 361f2c416c026c318fc685a716cdef0005466ef8 (plain)
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
/* find -- search for files in a directory hierarchy
   Copyright (C) 1990, 91, 92, 93, 94 Free Software Foundation, Inc.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */

/* GNU find was written by Eric Decker <cire@cisco.com>,
   with enhancements by David MacKenzie <djm@gnu.ai.mit.edu>,
   Jay Plett <jay@silence.princeton.nj.us>,
   and Tim Wood <axolotl!tim@toad.com>.
   The idea for -print0 and xargs -0 came from
   Dan Bernstein <brnstnd@kramden.acf.nyu.edu>.  */

#include <config.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#else
#include <sys/file.h>
#endif
#include "defs.h"
#include "modetype.h"

#ifndef S_IFLNK
#define lstat stat
#endif

int lstat ();
int stat ();

#define apply_predicate(pathname, stat_buf_ptr, node)	\
  (*(node)->pred_func)((pathname), (stat_buf_ptr), (node))

static void process_top_path P_((char *pathname));
static int process_path P_((char *pathname, char *name, boolean leaf, char *parent));
static void process_dir P_((char *pathname, char *name, int pathlen, struct stat *statp, char *parent));
static boolean no_side_effects P_((struct predicate *pred));

/* Name this program was run with. */
char *program_name;

/* All predicates for each path to process. */
struct predicate *predicates;

/* The last predicate allocated. */
struct predicate *last_pred;

/* The root of the evaluation tree. */
static struct predicate *eval_tree;

/* If true, process directory before contents.  True unless -depth given. */
boolean do_dir_first;

/* If >=0, don't descend more than this many levels of subdirectories. */
int maxdepth;

/* If >=0, don't process files above this level. */
int mindepth;

/* Current depth; 0 means current path is a command line arg. */
int curdepth;

/* Seconds between 00:00 1/1/70 and either one day before now
   (the default), or the start of today (if -daystart is given). */
time_t cur_day_start;

/* If true, cur_day_start has been adjusted to the start of the day. */
boolean full_days;

/* If true, do not assume that files in directories with nlink == 2
   are non-directories. */
boolean no_leaf_check;

/* If true, don't cross filesystem boundaries. */
boolean stay_on_filesystem;

/* If true, don't descend past current directory.
   Can be set by -prune, -maxdepth, and -xdev/-mount. */
boolean stop_at_current_level;

#ifndef HAVE_FCHDIR
/* The full path of the initial working directory.  */
char *starting_dir;
#else
/* A file descriptor open to the initial working directory.
   Doing it this way allows us to work when the i.w.d. has
   unreadable parents.  */
int starting_desc;
#endif

/* If true, we have called stat on the current path. */
boolean have_stat;

/* The file being operated on, relative to the current directory.
   Used for stat, readlink, and opendir.  */
char *rel_pathname;

/* Length of current path. */
int path_length;

/* true if following symlinks.  Should be consistent with xstat.  */
boolean dereference;

/* Pointer to the function used to stat files. */
int (*xstat) ();

/* Status value to return to system. */
int exit_status;

#ifdef DEBUG_STAT
static int
debug_stat (file, bufp)
     char *file;
     struct stat *bufp;
{
  fprintf (stderr, "debug_stat (%s)\n", file);
  return lstat (file, bufp);
}
#endif /* DEBUG_STAT */

void
main (argc, argv)
     int argc;
     char *argv[];
{
  int i;
  PFB parse_function;		/* Pointer to who is to do the parsing. */
  struct predicate *cur_pred;
  char *predicate_name;		/* Name of predicate being parsed. */

  program_name = argv[0];

  predicates = NULL;
  last_pred = NULL;
  do_dir_first = true;
  maxdepth = mindepth = -1;
  cur_day_start = time ((time_t *) 0) - DAYSECS;
  full_days = false;
  no_leaf_check = false;
  stay_on_filesystem = false;
  exit_status = 0;
  dereference = false;
#ifdef DEBUG_STAT
  xstat = debug_stat;
#else /* !DEBUG_STAT */
  xstat = lstat;
#endif /* !DEBUG_STAT */

#ifdef DEBUG
  printf ("cur_day_start = %s", ctime (&cur_day_start));
#endif /* DEBUG */

  /* Find where in ARGV the predicates begin. */
  for (i = 1; i < argc && strchr ("-!(),", argv[i][0]) == NULL; i++)
    /* Do nothing. */ ;

  /* Enclose the expression in `( ... )' so a default -print will
     apply to the whole expression. */
  parse_open (argv, &argc);
  /* Build the input order list. */
  while (i < argc)
    {
      if (strchr ("-!(),", argv[i][0]) == NULL)
	usage ("paths must precede expression");
      predicate_name = argv[i];
      parse_function = find_parser (predicate_name);
      if (parse_function == NULL)
	error (1, 0, "invalid predicate `%s'", predicate_name);
      i++;
      if (!(*parse_function) (argv, &i))
	{
	  if (argv[i] == NULL)
	    error (1, 0, "missing argument to `%s'", predicate_name);
	  else
	    error (1, 0, "invalid argument `%s' to `%s'",
		   argv[i], predicate_name);
	}
    }
  if (predicates->pred_next == NULL)
    {
      /* No predicates that do something other than set a global variable
	 were given; remove the unneeded initial `(' and add `-print'. */
      cur_pred = predicates;
      predicates = last_pred = predicates->pred_next;
      free ((char *) cur_pred);
      parse_print (argv, &argc);
    }
  else if (!no_side_effects (predicates->pred_next))
    {
      /* One or more predicates that produce output were given;
	 remove the unneeded initial `('. */
      cur_pred = predicates;
      predicates = predicates->pred_next;
      free ((char *) cur_pred);
    }
  else
    {
      /* `( user-supplied-expression ) -print'. */
      parse_close (argv, &argc);
      parse_print (argv, &argc);
    }

#ifdef	DEBUG
  printf ("Predicate List:\n");
  print_list (predicates);
#endif /* DEBUG */

  /* Done parsing the predicates.  Build the evaluation tree. */
  cur_pred = predicates;
  eval_tree = get_expr (&cur_pred, NO_PREC);
#ifdef	DEBUG
  printf ("Eval Tree:\n");
  print_tree (eval_tree, 0);
#endif /* DEBUG */

  /* Rearrange the eval tree in optimal-predicate order. */
  opt_expr (&eval_tree);

  /* Determine the point, if any, at which to stat the file. */
  mark_stat (eval_tree);

#ifdef DEBUG
  printf ("Optimized Eval Tree:\n");
  print_tree (eval_tree, 0);
#endif /* DEBUG */

#ifndef HAVE_FCHDIR
  starting_dir = xgetcwd ();
  if (starting_dir == NULL)
    error (1, errno, "cannot get current directory");
#else
  starting_desc = open (".", O_RDONLY);
  if (starting_desc < 0)
    error (1, errno, "cannot open current directory");
#endif

  /* If no paths are given, default to ".".  */
  for (i = 1; i < argc && strchr ("-!(),", argv[i][0]) == NULL; i++)
    process_top_path (argv[i]);
  if (i == 1)
    process_top_path (".");

  exit (exit_status);
}

/* Descend PATHNAME, which is a command-line argument.  */

static void
process_top_path (pathname)
     char *pathname;
{
  struct stat stat_buf;

  curdepth = 0;
  path_length = strlen (pathname);

  /* We stat each pathname given on the command-line twice --
     once here and once in process_path.  It's not too bad, though,
     since the kernel can read the stat information out of its inode
     cache the second time.  */
  if ((*xstat) (pathname, &stat_buf) == 0 && S_ISDIR (stat_buf.st_mode))
    {
      if (chdir (pathname) < 0)
	{
	  error (0, errno, "%s", pathname);
	  exit_status = 1;
	  return;
	}
      process_path (pathname, ".", false, ".");
#ifndef HAVE_FCHDIR
      if (chdir (starting_dir) < 0)
	error (1, errno, "%s", starting_dir);
#else
      if (fchdir (starting_desc))
	error (1, errno, "cannot return to starting directory");
#endif
    }
  else
    process_path (pathname, pathname, false, ".");
}

/* Info on each directory in the current tree branch, to avoid
   getting stuck in symbolic link loops.  */
struct dir_id
{
  ino_t ino;
  dev_t dev;
};
static struct dir_id *dir_ids = NULL;
/* Entries allocated in `dir_ids'.  */
static int dir_alloc = 0;
/* Index in `dir_ids' of directory currently being searched.
   This is always the last valid entry.  */
static int dir_curr = -1;
/* (Arbitrary) number of entries to grow `dir_ids' by.  */
#define DIR_ALLOC_STEP 32

/* Recursively descend path PATHNAME, applying the predicates.
   LEAF is true if PATHNAME is known to be in a directory that has no
   more unexamined subdirectories, and therefore it is not a directory.
   Knowing this allows us to avoid calling stat as long as possible for
   leaf files.

   NAME is PATHNAME relative to the current directory.  We access NAME
   but print PATHNAME.

   PARENT is the path of the parent of NAME, relative to find's
   starting directory.

   Return nonzero iff PATHNAME is a directory. */

static int
process_path (pathname, name, leaf, parent)
     char *pathname;
     char *name;
     boolean leaf;
     char *parent;
{
  struct stat stat_buf;
  static dev_t root_dev;	/* Device ID of current argument pathname. */
  int i;

  /* Assume it is a non-directory initially. */
  stat_buf.st_mode = 0;

  rel_pathname = name;

  if (leaf)
    have_stat = false;
  else
    {
      if ((*xstat) (name, &stat_buf) != 0)
	{
	  error (0, errno, "%s", pathname);
	  exit_status = 1;
	  return 0;
	}
      have_stat = true;
    }

  if (!S_ISDIR (stat_buf.st_mode))
    {
      if (curdepth >= mindepth)
	apply_predicate (pathname, &stat_buf, eval_tree);
      return 0;
    }

  /* From here on, we're working on a directory.  */

  stop_at_current_level = maxdepth >= 0 && curdepth >= maxdepth;

  /* If we've already seen this directory on this branch,
     don't descend it again.  */
  for (i = 0; i <= dir_curr; i++)
    if (stat_buf.st_ino == dir_ids[i].ino &&
	stat_buf.st_dev == dir_ids[i].dev)
      stop_at_current_level = true;

  if (dir_alloc <= ++dir_curr)
    {
      dir_alloc += DIR_ALLOC_STEP;
      dir_ids = (struct dir_id *)
	xrealloc ((char *) dir_ids, dir_alloc * sizeof (struct dir_id));
    }
  dir_ids[dir_curr].ino = stat_buf.st_ino;
  dir_ids[dir_curr].dev = stat_buf.st_dev;

  if (stay_on_filesystem)
    {
      if (curdepth == 0)
	root_dev = stat_buf.st_dev;
      else if (stat_buf.st_dev != root_dev)
	stop_at_current_level = true;
    }

  if (do_dir_first && curdepth >= mindepth)
    apply_predicate (pathname, &stat_buf, eval_tree);

  if (stop_at_current_level == false)
    /* Scan directory on disk. */
    process_dir (pathname, name, strlen (pathname), &stat_buf, parent);

  if (do_dir_first == false && curdepth >= mindepth)
    apply_predicate (pathname, &stat_buf, eval_tree);

  dir_curr--;

  return 1;
}

/* Scan directory PATHNAME and recurse through process_path for each entry.

   PATHLEN is the length of PATHNAME.

   NAME is PATHNAME relative to the current directory.

   STATP is the results of *xstat on it.

   PARENT is the path of the parent of NAME, relative to find's
   starting directory.  */

static void
process_dir (pathname, name, pathlen, statp, parent)
     char *pathname;
     char *name;
     int pathlen;
     struct stat *statp;
     char *parent;
{
  char *name_space;		/* Names of files in PATHNAME. */
  int subdirs_left;		/* Number of unexamined subdirs in PATHNAME. */

  subdirs_left = statp->st_nlink - 2; /* Account for name and ".". */

  errno = 0;
  /* On some systems (VAX 4.3BSD+NFS), NFS mount points have st_size < 0.  */
  name_space = savedir (name, statp->st_size > 0 ? statp->st_size : 512);
  if (name_space == NULL)
    {
      if (errno)
	{
	  error (0, errno, "%s", pathname);
	  exit_status = 1;
	}
      else
	error (1, 0, "virtual memory exhausted");
    }
  else
    {
      register char *namep;	/* Current point in `name_space'. */
      char *cur_path;		/* Full path of each file to process. */
      char *cur_name;		/* Base name of each file to process. */
      unsigned cur_path_size;	/* Bytes allocated for `cur_path'. */
      register unsigned file_len; /* Length of each path to process. */
      register unsigned pathname_len; /* PATHLEN plus trailing '/'. */

      if (pathname[pathlen - 1] == '/')
	pathname_len = pathlen + 1; /* For '\0'; already have '/'. */
      else
	pathname_len = pathlen + 2; /* For '/' and '\0'. */
      cur_path_size = 0;
      cur_path = NULL;

      if (strcmp (name, ".") && chdir (name) < 0)
	{
	  error (0, errno, "%s", pathname);
	  exit_status = 1;
	  return;
	}

      for (namep = name_space; *namep; namep += file_len - pathname_len + 1)
	{
	  /* Append this directory entry's name to the path being searched. */
	  file_len = pathname_len + strlen (namep);
	  if (file_len > cur_path_size)
	    {
	      while (file_len > cur_path_size)
		cur_path_size += 1024;
	      if (cur_path)
		free (cur_path);
	      cur_path = xmalloc (cur_path_size);
	      strcpy (cur_path, pathname);
	      cur_path[pathname_len - 2] = '/';
	    }
	  cur_name = cur_path + pathname_len - 1;
	  strcpy (cur_name, namep);

	  curdepth++;
	  if (!no_leaf_check)
	    /* Normal case optimization.
	       On normal Unix filesystems, a directory that has no
	       subdirectories has two links: its name, and ".".  Any
	       additional links are to the ".." entries of its
	       subdirectories.  Once we have processed as many
	       subdirectories as there are additional links, we know
	       that the rest of the entries are non-directories --
	       in other words, leaf files. */
	    subdirs_left -= process_path (cur_path, cur_name,
					  subdirs_left == 0, pathname);
	  else
	    /* There might be weird (e.g., CD-ROM or MS-DOS) filesystems
	       mounted, which don't have Unix-like directory link counts. */
	    process_path (cur_path, cur_name, false, pathname);
	  curdepth--;
	}

      if (strcmp (name, "."))
	{
	  if (!dereference)
	    {
	      if (chdir ("..") < 0)
		/* We could go back and do the next command-line arg instead,
		   maybe using longjmp.  */
		error (1, errno, "%s", parent);
	    }
	  else
	    {
#ifndef HAVE_FCHDIR
	      if (chdir (starting_dir) || chdir (parent))
		error (1, errno, "%s", parent);
#else
	      if (fchdir (starting_desc) || chdir (parent))
		error (1, errno, "%s", parent);
#endif
	    }
	}

      if (cur_path)
	free (cur_path);
      free (name_space);
    }
}

/* Return true if there are no side effects in any of the predicates in
   predicate list PRED, false if there are any. */

static boolean
no_side_effects (pred)
     struct predicate *pred;
{
  while (pred != NULL)
    {
      if (pred->side_effects)
	return (false);
      pred = pred->pred_next;
    }
  return (true);
}