/* We've detected a file system loop. This is caused by one of * two things: * * 1. Option -L is in effect and we've hit a symbolic link that * points to an ancestor. This is harmless. We won't traverse the * symbolic link. * * 2. We have hit a real cycle in the directory hierarchy. In this * case, we issue a diagnostic message (POSIX requires this) and we * skip that directory entry. */ static void issue_loop_warning (FTSENT * ent) { if (S_ISLNK(ent->fts_statp->st_mode)) { error (0, 0, _("Symbolic link %s is part of a loop in the directory hierarchy; we have already visited the directory to which it points."), safely_quote_err_filename (0, ent->fts_path)); } else { /* We have found an infinite loop. POSIX requires us to * issue a diagnostic. Usually we won't get to here * because when the leaf optimisation is on, it will cause * the subdirectory to be skipped. If /a/b/c/d is a hard * link to /a/b, then the link count of /a/b/c is 2, * because the ".." entry of /a/b/c/d points to /a, not * to /a/b/c. */ error (0, 0, _("File system loop detected; " "%s is part of the same file system loop as %s."), safely_quote_err_filename (0, ent->fts_path), partial_quotearg_n (1, ent->fts_cycle->fts_path, ent->fts_cycle->fts_pathlen, options.err_quoting_style)); } }
/* report_file_err */ static void report_file_err(int exitval, int errno_value, const char *name) { /* It is important that the errno value is passed in as a function * argument before we call safely_quote_err_filename(), because otherwise * we might find that safely_quote_err_filename() changes errno. */ if (state.exit_status < 1) state.exit_status = 1; error (exitval, errno_value, "%s", safely_quote_err_filename(0, name)); }
static void find(char *arg) { char * arglist[2]; FTS *p; FTSENT *ent; state.starting_path_length = strlen(arg); inside_dir(AT_FDCWD); arglist[0] = arg; arglist[1] = NULL; switch (options.symlink_handling) { case SYMLINK_ALWAYS_DEREF: ftsoptions |= FTS_COMFOLLOW|FTS_LOGICAL; break; case SYMLINK_DEREF_ARGSONLY: ftsoptions |= FTS_COMFOLLOW|FTS_PHYSICAL; break; case SYMLINK_NEVER_DEREF: ftsoptions |= FTS_PHYSICAL; break; } if (options.stay_on_filesystem) ftsoptions |= FTS_XDEV; p = fts_open(arglist, ftsoptions, NULL); if (NULL == p) { error (0, errno, _("cannot search %s"), safely_quote_err_filename(0, arg)); } else { while ( (ent=fts_read(p)) != NULL ) { state.have_stat = false; state.have_type = false; state.type = 0; consider_visiting(p, ent); } fts_close(p); p = NULL; } }
/* We've detected a file system loop. This is caused by one of * two things: * * 1. Option -L is in effect and we've hit a symbolic link that * points to an ancestor. This is harmless. We won't traverse the * symbolic link. * * 2. We have hit a real cycle in the directory hierarchy. In this * case, we issue a diagnostic message (POSIX requires this) and we * skip that directory entry. */ static void issue_loop_warning (const char *name, const char *pathname, int level) { struct stat stbuf_link; if (lstat (name, &stbuf_link) != 0) stbuf_link.st_mode = S_IFREG; if (S_ISLNK(stbuf_link.st_mode)) { error (0, 0, _("Symbolic link %s is part of a loop in the directory hierarchy; we have already visited the directory to which it points."), safely_quote_err_filename (0, pathname)); /* XXX: POSIX appears to require that the exit status be non-zero if a * diagnostic is issued. */ } else { int distance = 1 + (dir_curr-level); /* We have found an infinite loop. POSIX requires us to * issue a diagnostic. Usually we won't get to here * because when the leaf optimisation is on, it will cause * the subdirectory to be skipped. If /a/b/c/d is a hard * link to /a/b, then the link count of /a/b/c is 2, * because the ".." entry of /b/b/c/d points to /a, not * to /a/b/c. */ error (0, 0, ngettext ( "Filesystem loop detected; %s has the same device number and inode as " "a directory which is %d level higher in the file system hierarchy", "Filesystem loop detected; %s has the same device number and inode as " "a directory which is %d levels higher in the file system hierarchy", (long)distance), safely_quote_err_filename (0, pathname), distance); } }
/* We stat()ed a directory, chdir()ed into it (we know this * since direction is TraversingDown), stat()ed it again, * and noticed that the device numbers are different. Check * if the file system was recently mounted. * * If it was, it looks like chdir()ing into the directory * caused a file system to be mounted. Maybe automount is * running. Anyway, that's probably OK - but it happens * only when we are moving downward. * * We also allow for the possibility that a similar thing * has happened with the unmounting of a file system. This * is much rarer, as it relies on an automounter timeout * occurring at exactly the wrong moment. */ static enum WdSanityCheckFatality dirchange_is_fatal (const char *specific_what, enum WdSanityCheckFatality isfatal, int silent, struct stat *newinfo) { enum MountPointStateChange transition = get_mount_state (newinfo->st_dev); switch (transition) { case MountPointRecentlyUnmounted: isfatal = NON_FATAL_IF_SANITY_CHECK_FAILS; if (!silent) { error (0, 0, _("WARNING: file system %s has recently been unmounted."), safely_quote_err_filename (0, specific_what)); } break; case MountPointRecentlyMounted: isfatal = NON_FATAL_IF_SANITY_CHECK_FAILS; if (!silent) { error (0, 0, _("WARNING: file system %s has recently been mounted."), safely_quote_err_filename (0, specific_what)); } break; case MountPointStateUnchanged: /* leave isfatal as it is */ break; } return isfatal; }
/* 1) fork to get a child; parent remembers the child pid 2) child execs the command requested 3) parent waits for child; checks for proper pid of child Possible returns: ret errno status(h) status(l) pid x signal# 0177 stopped pid x exit arg 0 term by _exit pid x 0 signal # term by signal -1 EINTR parent got signal -1 other some other kind of error Return true only if the pid matches, status(l) is zero, and the exit arg (status high) is 0. Otherwise return false, possibly printing an error message. */ static bool prep_child_for_exec (bool close_stdin, const struct saved_cwd *wd) { bool ok = true; if (close_stdin) { const char inputfile[] = "/dev/null"; if (close (0) < 0) { error (0, errno, _("Cannot close standard input")); ok = false; } else { if (open (inputfile, O_RDONLY #if defined O_LARGEFILE |O_LARGEFILE #endif ) < 0) { /* This is not entirely fatal, since * executing the child with a closed * stdin is almost as good as executing it * with its stdin attached to /dev/null. */ error (0, errno, "%s", safely_quote_err_filename (0, inputfile)); /* do not set ok=false, it is OK to continue anyway. */ } } } /* Even if DebugSearch is set, don't announce our change of * directory, since we're not going to emit a subsequent * announcement of a call to stat() anyway, as we're about to exec * something. */ if (0 != restore_cwd (wd)) { error (0, errno, _("Failed to change directory")); ok = false; } return ok; }
/* Get the stat information for a file, if it is * not already known. */ int get_statinfo (const char *pathname, const char *name, struct stat *p) { /* Set markers in fields so we have a good idea if the implementation * didn't bother to set them (e.g., NetBSD st_birthtimespec for MS-DOS * files) */ if (!state.have_stat) { set_stat_placeholders(p); if (0 == (*options.xstat) (name, p)) { if (00000 == p->st_mode) { /* Savannah bug #16378. */ error(0, 0, _("Warning: file %s appears to have mode 0000"), quotearg_n_style(0, options.err_quoting_style, name)); } } else { if (!options.ignore_readdir_race || (errno != ENOENT) ) { error (0, errno, "%s", safely_quote_err_filename(0, pathname)); state.exit_status = 1; } return -1; } } state.have_stat = true; state.have_type = true; state.type = p->st_mode; return 0; }
int launch (struct buildcmd_control *ctl, void *usercontext, int argc, char **argv) { pid_t child_pid; static int first_time = 1; struct exec_val *execp = usercontext; /* Make sure output of command doesn't get mixed with find output. */ fflush (stdout); fflush (stderr); /* Make sure to listen for the kids. */ if (first_time) { first_time = 0; signal (SIGCHLD, SIG_DFL); } child_pid = fork (); if (child_pid == -1) error (EXIT_FAILURE, errno, _("cannot fork")); if (child_pid == 0) { /* We are the child. */ assert (NULL != execp->wd_for_exec); if (!prep_child_for_exec (execp->close_stdin, execp->wd_for_exec)) { _exit (1); } else { if (fd_leak_check_is_enabled ()) { complain_about_leaky_fds (); } } if (bc_args_exceed_testing_limit (argv)) errno = E2BIG; else execvp (argv[0], argv); /* TODO: use a pipe to pass back the errno value, like xargs does */ error (0, errno, "%s", safely_quote_err_filename (0, argv[0])); _exit (1); } while (waitpid (child_pid, &(execp->last_child_status), 0) == (pid_t) -1) { if (errno != EINTR) { error (0, errno, _("error waiting for %s"), safely_quote_err_filename (0, argv[0])); state.exit_status = 1; return 0; /* FAIL */ } } if (WIFSIGNALED (execp->last_child_status)) { error (0, 0, _("%s terminated by signal %d"), quotearg_n_style (0, options.err_quoting_style, argv[0]), WTERMSIG (execp->last_child_status)); if (execp->multiple) { /* -exec \; just returns false if the invoked command fails. * -exec {} + returns true if the invoked command fails, but * sets the program exit status. */ state.exit_status = 1; } return 1; /* OK */ } if (0 == WEXITSTATUS (execp->last_child_status)) { return 1; /* OK */ } else { if (execp->multiple) { /* -exec \; just returns false if the invoked command fails. * -exec {} + returns true if the invoked command fails, but * sets the program exit status. */ state.exit_status = 1; } /* The child failed, but this is the exec callback. We * don't want to run the child again in this case anwyay. */ return 1; /* FAIL (but don't try again) */ } }
bool impl_pred_exec (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) { struct exec_val *execp = &pred_ptr->args.exec_vec; char *buf = NULL; const char *target; bool result; const bool local = is_exec_in_local_dir (pred_ptr->pred_func); char *prefix; size_t pfxlen; (void) stat_buf; if (local) { /* For -execdir/-okdir predicates, the parser did not fill in the wd_for_exec member of sturct exec_val. So for those predicates, we do so now. */ if (!record_exec_dir (execp)) { error (EXIT_FAILURE, errno, _("Failed to save working directory in order to " "run a command on %s"), safely_quote_err_filename (0, pathname)); /*NOTREACHED*/ } target = buf = base_name (state.rel_pathname); if ('/' == target[0]) { /* find / execdir ls -d {} \; */ prefix = NULL; pfxlen = 0; } else { prefix = "./"; pfxlen = 2u; } } else { /* For the others (-exec, -ok), the parser should have set wd_for_exec to initial_wd, indicating that the exec should take place from find's initial working directory. */ assert (execp->wd_for_exec == initial_wd); target = pathname; prefix = NULL; pfxlen = 0u; } if (execp->multiple) { /* Push the argument onto the current list. * The command may or may not be run at this point, * depending on the command line length limits. */ bc_push_arg (&execp->ctl, &execp->state, target, strlen (target)+1, prefix, pfxlen, 0); /* remember that there are pending execdirs. */ if (execp->state.todo) state.execdirs_outstanding = true; /* POSIX: If the primary expression is punctuated by a plus * sign, the primary shall always evaluate as true */ result = true; } else { int i; for (i=0; i<execp->num_args; ++i) { bc_do_insert (&execp->ctl, &execp->state, execp->replace_vec[i], strlen (execp->replace_vec[i]), prefix, pfxlen, target, strlen (target), 0); } /* Actually invoke the command. */ bc_do_exec (&execp->ctl, &execp->state); if (WIFEXITED(execp->last_child_status)) { if (0 == WEXITSTATUS(execp->last_child_status)) result = true; /* The child succeeded. */ else result = false; } else { result = false; } } if (buf) { assert (local); free (buf); } return result; }
static bool find (char *arg) { char * arglist[2]; FTS *p; FTSENT *ent; state.starting_path_length = strlen (arg); inside_dir (AT_FDCWD); arglist[0] = arg; arglist[1] = NULL; switch (options.symlink_handling) { case SYMLINK_ALWAYS_DEREF: ftsoptions |= FTS_COMFOLLOW|FTS_LOGICAL; break; case SYMLINK_DEREF_ARGSONLY: ftsoptions |= FTS_COMFOLLOW|FTS_PHYSICAL; break; case SYMLINK_NEVER_DEREF: ftsoptions |= FTS_PHYSICAL; break; } if (options.stay_on_filesystem) ftsoptions |= FTS_XDEV; p = fts_open (arglist, ftsoptions, NULL); if (NULL == p) { error (0, errno, _("cannot search %s"), safely_quote_err_filename (0, arg)); error_severity (EXIT_FAILURE); } else { int level = INT_MIN; while ( (errno=0, ent=fts_read (p)) != NULL ) { if (state.execdirs_outstanding) { /* If we changed level, perform any outstanding * execdirs. If we see a sequence of directory entries * like this: fffdfffdfff, we could build a command line * of 9 files, but this simple-minded implementation * builds a command line for only 3 files at a time * (since fts descends into the directories). */ if ((int)ent->fts_level != level) { show_outstanding_execdirs (stderr); complete_pending_execdirs (); } } level = (int)ent->fts_level; state.already_issued_stat_error_msg = false; state.have_stat = false; state.have_type = !!ent->fts_statp->st_mode; state.type = state.have_type ? ent->fts_statp->st_mode : 0; consider_visiting (p, ent); } /* fts_read returned NULL; distinguish between "finished" and "error". */ if (errno) { error (0, errno, "failed to read file names from file system at or below %s", safely_quote_err_filename (0, arg)); error_severity (EXIT_FAILURE); return false; } if (0 != fts_close (p)) { /* Here we break the abstraction of fts_close a bit, because we * are going to skip the rest of the start points, and return with * nonzero exit status. Hence we need to issue a diagnostic on * stderr. */ error (0, errno, _("failed to restore working directory after searching %s"), arg); error_severity (EXIT_FAILURE); return false; } p = NULL; } return true; }
static void do_fprintf (struct format_val *dest, struct segment *segment, const char *pathname, const struct stat *stat_buf) { char hbuf[LONGEST_HUMAN_READABLE + 1]; const char *cp; switch (segment->segkind) { case KIND_PLAIN: /* Plain text string (no % conversion). */ /* trusted */ checked_fwrite(segment->text, 1, segment->text_len, dest); break; case KIND_STOP: /* Terminate argument and flush output. */ /* trusted */ checked_fwrite (segment->text, 1, segment->text_len, dest); checked_fflush (dest); break; case KIND_FORMAT: switch (segment->format_char[0]) { case 'a': /* atime in `ctime' format. */ /* UNTRUSTED, probably unexploitable */ checked_fprintf (dest, segment->text, ctime_format (get_stat_atime (stat_buf))); break; case 'b': /* size in 512-byte blocks */ /* UNTRUSTED, probably unexploitable */ checked_fprintf (dest, segment->text, human_readable ((uintmax_t) ST_NBLOCKS (*stat_buf), hbuf, human_ceiling, ST_NBLOCKSIZE, 512)); break; case 'c': /* ctime in `ctime' format */ /* UNTRUSTED, probably unexploitable */ checked_fprintf (dest, segment->text, ctime_format (get_stat_ctime (stat_buf))); break; case 'd': /* depth in search tree */ /* UNTRUSTED, probably unexploitable */ checked_fprintf (dest, segment->text, state.curdepth); break; case 'D': /* Device on which file exists (stat.st_dev) */ /* trusted */ checked_fprintf (dest, segment->text, human_readable ((uintmax_t) stat_buf->st_dev, hbuf, human_ceiling, 1, 1)); break; case 'f': /* base name of path */ /* sanitised */ { char *base = base_name (pathname); checked_print_quoted (dest, segment->text, base); free (base); } break; case 'F': /* file system type */ /* trusted */ checked_print_quoted (dest, segment->text, filesystem_type (stat_buf, pathname)); break; case 'g': /* group name */ /* trusted */ /* (well, the actual group is selected by the user but * its name was selected by the system administrator) */ { struct group *g; g = getgrgid (stat_buf->st_gid); if (g) { segment->text[segment->text_len] = 's'; checked_fprintf (dest, segment->text, g->gr_name); break; } else { /* Do nothing. */ /*FALLTHROUGH*/ } } /*FALLTHROUGH*/ /*...sometimes, so 'G' case.*/ case 'G': /* GID number */ /* UNTRUSTED, probably unexploitable */ checked_fprintf (dest, segment->text, human_readable ((uintmax_t) stat_buf->st_gid, hbuf, human_ceiling, 1, 1)); break; case 'h': /* leading directories part of path */ /* sanitised */ { cp = strrchr (pathname, '/'); if (cp == NULL) /* No leading directories. */ { /* If there is no slash in the pathname, we still * print the string because it contains characters * other than just '%s'. The %h expands to ".". */ checked_print_quoted (dest, segment->text, "."); } else { char *s = strdup (pathname); s[cp - pathname] = 0; checked_print_quoted (dest, segment->text, s); free (s); } } break; case 'H': /* ARGV element file was found under */ /* trusted */ { char *s = xmalloc (state.starting_path_length+1); memcpy (s, pathname, state.starting_path_length); s[state.starting_path_length] = 0; checked_fprintf (dest, segment->text, s); free (s); } break; case 'i': /* inode number */ /* UNTRUSTED, but not exploitable I think */ /* POSIX does not guarantee that ino_t is unsigned or even * integral (except as an XSI extension), but we'll work on * fixing that if we ever get a report of a system where * ino_t is indeed a signed integral type or a non-integral * arithmetic type. */ checked_fprintf (dest, segment->text, human_readable ((uintmax_t) stat_buf->st_ino, hbuf, human_ceiling, 1, 1)); break; case 'k': /* size in 1K blocks */ /* UNTRUSTED, but not exploitable I think */ checked_fprintf (dest, segment->text, human_readable ((uintmax_t) ST_NBLOCKS (*stat_buf), hbuf, human_ceiling, ST_NBLOCKSIZE, 1024)); break; case 'l': /* object of symlink */ /* sanitised */ #ifdef S_ISLNK { char *linkname = 0; if (S_ISLNK (stat_buf->st_mode)) { linkname = areadlinkat (state.cwd_dir_fd, state.rel_pathname); if (linkname == NULL) { nonfatal_target_file_error (errno, pathname); state.exit_status = 1; } } if (linkname) { checked_print_quoted (dest, segment->text, linkname); } else { /* We still need to honour the field width etc., so this is * not a no-op. */ checked_print_quoted (dest, segment->text, ""); } free (linkname); } #endif /* S_ISLNK */ break; case 'M': /* mode as 10 chars (eg., "-rwxr-x--x" */ /* UNTRUSTED, probably unexploitable */ { char modestring[16] ; filemodestring (stat_buf, modestring); modestring[10] = '\0'; checked_fprintf (dest, segment->text, modestring); } break; case 'm': /* mode as octal number (perms only) */ /* UNTRUSTED, probably unexploitable */ { /* Output the mode portably using the traditional numbers, even if the host unwisely uses some other numbering scheme. But help the compiler in the common case where the host uses the traditional numbering scheme. */ mode_t m = stat_buf->st_mode; bool traditional_numbering_scheme = (S_ISUID == 04000 && S_ISGID == 02000 && S_ISVTX == 01000 && S_IRUSR == 00400 && S_IWUSR == 00200 && S_IXUSR == 00100 && S_IRGRP == 00040 && S_IWGRP == 00020 && S_IXGRP == 00010 && S_IROTH == 00004 && S_IWOTH == 00002 && S_IXOTH == 00001); checked_fprintf (dest, segment->text, (traditional_numbering_scheme ? m & MODE_ALL : ((m & S_ISUID ? 04000 : 0) | (m & S_ISGID ? 02000 : 0) | (m & S_ISVTX ? 01000 : 0) | (m & S_IRUSR ? 00400 : 0) | (m & S_IWUSR ? 00200 : 0) | (m & S_IXUSR ? 00100 : 0) | (m & S_IRGRP ? 00040 : 0) | (m & S_IWGRP ? 00020 : 0) | (m & S_IXGRP ? 00010 : 0) | (m & S_IROTH ? 00004 : 0) | (m & S_IWOTH ? 00002 : 0) | (m & S_IXOTH ? 00001 : 0)))); } break; case 'n': /* number of links */ /* UNTRUSTED, probably unexploitable */ checked_fprintf (dest, segment->text, human_readable ((uintmax_t) stat_buf->st_nlink, hbuf, human_ceiling, 1, 1)); break; case 'p': /* pathname */ /* sanitised */ checked_print_quoted (dest, segment->text, pathname); break; case 'P': /* pathname with ARGV element stripped */ /* sanitised */ if (state.curdepth > 0) { cp = pathname + state.starting_path_length; if (*cp == '/') /* Move past the slash between the ARGV element and the rest of the pathname. But if the ARGV element ends in a slash, we didn't add another, so we've already skipped past it. */ cp++; } else { cp = ""; } checked_print_quoted (dest, segment->text, cp); break; case 's': /* size in bytes */ /* UNTRUSTED, probably unexploitable */ checked_fprintf (dest, segment->text, human_readable ((uintmax_t) stat_buf->st_size, hbuf, human_ceiling, 1, 1)); break; case 'S': /* sparseness */ /* UNTRUSTED, probably unexploitable */ checked_fprintf (dest, segment->text, file_sparseness (stat_buf)); break; case 't': /* mtime in `ctime' format */ /* UNTRUSTED, probably unexploitable */ checked_fprintf (dest, segment->text, ctime_format (get_stat_mtime (stat_buf))); break; case 'u': /* user name */ /* trusted */ /* (well, the actual user is selected by the user on systems * where chown is not restricted, but the user name was * selected by the system administrator) */ { struct passwd *p; p = getpwuid (stat_buf->st_uid); if (p) { segment->text[segment->text_len] = 's'; checked_fprintf (dest, segment->text, p->pw_name); break; } /* else fallthru */ } /* FALLTHROUGH*/ /* .. to case U */ case 'U': /* UID number */ /* UNTRUSTED, probably unexploitable */ checked_fprintf (dest, segment->text, human_readable ((uintmax_t) stat_buf->st_uid, hbuf, human_ceiling, 1, 1)); break; /* %Y: type of file system entry like `ls -l`: * (d,-,l,s,p,b,c,n) n=nonexistent (symlink) */ case 'Y': /* in case of symlink */ /* trusted */ { #ifdef S_ISLNK if (S_ISLNK (stat_buf->st_mode)) { struct stat sbuf; /* If we would normally follow links, do not do so. * If we would normally not follow links, do so. */ if ((following_links () ? optionp_stat : optionl_stat) (state.rel_pathname, &sbuf) != 0) { if ( errno == ENOENT ) { checked_fprintf (dest, segment->text, "N"); break; } else if ( errno == ELOOP ) { checked_fprintf (dest, segment->text, "L"); break; } else { checked_fprintf (dest, segment->text, "?"); error (0, errno, "%s", safely_quote_err_filename (0, pathname)); /* exit_status = 1; return ; */ break; } } checked_fprintf (dest, segment->text, mode_to_filetype (sbuf.st_mode & S_IFMT)); } #endif /* S_ISLNK */ else { checked_fprintf (dest, segment->text, mode_to_filetype (stat_buf->st_mode & S_IFMT)); } } break; case 'y': /* trusted */ { checked_fprintf (dest, segment->text, mode_to_filetype (stat_buf->st_mode & S_IFMT)); } break; case 'Z': /* SELinux security context */ { security_context_t scontext; int rv = (*options.x_getfilecon) (state.cwd_dir_fd, state.rel_pathname, &scontext); if (rv < 0) { /* If getfilecon fails, there will in the general case still be some text to print. We just make %Z expand to an empty string. */ checked_fprintf (dest, segment->text, ""); error (0, errno, _("getfilecon failed: %s"), safely_quote_err_filename (0, pathname)); state.exit_status = 1; } else { checked_fprintf (dest, segment->text, scontext); freecon (scontext); } } break; case 0: case '%': checked_fprintf (dest, segment->text); break; } /* end of KIND_FORMAT case */ break; } }
/* Move to the parent of a given directory and then call a function, * restoring the cwd. Don't bother changing directory if the * specified directory is a child of "." or is the root directory. */ static void at_top (char *pathname, mode_t mode, ino_t inum, struct stat *pstat, void (*action)(char *pathname, char *basename, int mode, ino_t inum, struct stat *pstat)) { int dirchange; char *parent_dir = dir_name (pathname); char *base = last_component (pathname); state.curdepth = 0; state.starting_path_length = strlen (pathname); if (0 == *base || 0 == strcmp (parent_dir, ".")) { dirchange = 0; base = pathname; } else { enum TraversalDirection direction; enum SafeChdirStatus chdir_status; struct stat st; bool did_stat = false; dirchange = 1; if (0 == strcmp (base, "..")) direction = TraversingUp; else direction = TraversingDown; /* We pass SymlinkFollowOk to safely_chdir(), which allows it to * chdir() into a symbolic link. This is only useful for the * case where the directory we're chdir()ing into is the * basename of a command line argument, for example where * "foo/bar/baz" is specified on the command line. When -P is * in effect (the default), baz will not be followed if it is a * symlink, but if bar is a symlink, it _should_ be followed. * Hence we need the ability to override the policy set by * following_links(). */ chdir_status = safely_chdir (parent_dir, direction, &st, SymlinkFollowOk, &did_stat); if (SafeChdirOK != chdir_status) { const char *what = (SafeChdirFailWouldBeUnableToReturn == chdir_status) ? "." : parent_dir; if (errno) error (0, errno, "%s", safely_quote_err_filename (0, what)); else error (0, 0, _("Failed to safely change directory into %s"), safely_quote_err_filename (0, parent_dir)); /* We can't process this command-line argument. */ state.exit_status = 1; return; } } free (parent_dir); parent_dir = NULL; action (pathname, base, mode, inum, pstat); if (dirchange) { chdir_back (); } }
/* Examine the results of the stat() of a directory from before we * entered or left it, with the results of stat()ing it afterward. If * these are different, the file system tree has been modified while we * were traversing it. That might be an attempt to use a race * condition to persuade find to do something it didn't intend * (e.g. an attempt by an ordinary user to exploit the fact that root * sometimes runs find on the whole file system). However, this can * also happen if automount is running (certainly on Solaris). With * automount, moving into a directory can cause a file system to be * mounted there. * * To cope sensibly with this, we will raise an error if we see the * device number change unless we are chdir()ing into a subdirectory, * and the directory we moved into has been mounted or unmounted "recently". * Here "recently" means since we started "find" or we last re-read * the /etc/mnttab file. * * If the device number does not change but the inode does, that is a * problem. * * If the device number and inode are both the same, we are happy. * * If a file system is (un)mounted as we chdir() into the directory, that * may mean that we're now examining a section of the file system that might * have been excluded from consideration (via -prune or -quit for example). * Hence we print a warning message to indicate that the output of find * might be inconsistent due to the change in the file system. */ static bool wd_sanity_check (const char *thing_to_stat, const char *progname, const char *what, dev_t old_dev, ino_t old_ino, struct stat *newinfo, int parent, int line_no, enum TraversalDirection direction, enum WdSanityCheckFatality isfatal, bool *changed) /* output parameter */ { const char *fstype; char *specific_what = NULL; int silent = 0; const char *current_dir = "."; *changed = false; set_stat_placeholders (newinfo); if ((*options.xstat) (current_dir, newinfo) != 0) fatal_target_file_error (errno, thing_to_stat); if (old_dev != newinfo->st_dev) { *changed = true; specific_what = specific_dirname (what); fstype = filesystem_type (newinfo, current_dir); silent = fs_likely_to_be_automounted (fstype); /* This condition is rare, so once we are here it is * reasonable to perform an expensive computation to * determine if we should continue or fail. */ if (TraversingDown == direction) { #ifdef STAT_MOUNTPOINTS isfatal = dirchange_is_fatal (specific_what,isfatal,silent,newinfo); #else (void) silent; isfatal = RETRY_IF_SANITY_CHECK_FAILS; #endif } switch (isfatal) { case FATAL_IF_SANITY_CHECK_FAILS: { fstype = filesystem_type (newinfo, current_dir); error (EXIT_FAILURE, 0, _("%s%s changed during execution of %s (old device number %ld, new device number %ld, file system type is %s) [ref %ld]"), safely_quote_err_filename (0, specific_what), parent ? "/.." : "", safely_quote_err_filename (1, progname), (long) old_dev, (long) newinfo->st_dev, fstype, (long)line_no); /*NOTREACHED*/ return false; } case NON_FATAL_IF_SANITY_CHECK_FAILS: { /* Since the device has changed under us, the inode number * will almost certainly also be different. However, we have * already decided that this is not a problem. Hence we return * without checking the inode number. */ free (specific_what); return true; } case RETRY_IF_SANITY_CHECK_FAILS: return false; } } /* Device number was the same, check if the inode has changed. */ if (old_ino != newinfo->st_ino) { *changed = true; specific_what = specific_dirname (what); fstype = filesystem_type (newinfo, current_dir); error ((isfatal == FATAL_IF_SANITY_CHECK_FAILS) ? 1 : 0, 0, /* no relevant errno value */ _("%s%s changed during execution of %s " "(old inode number %" PRIuMAX ", new inode number %" PRIuMAX ", file system type is %s) [ref %ld]"), safely_quote_err_filename (0, specific_what), parent ? "/.." : "", safely_quote_err_filename (1, progname), (uintmax_t) old_ino, (uintmax_t) newinfo->st_ino, fstype, (long)line_no); free (specific_what); return false; } return true; }
static void process_dir (char *pathname, char *name, int pathlen, const struct stat *statp, char *parent) { int subdirs_left; /* Number of unexamined subdirs in PATHNAME. */ bool subdirs_unreliable; /* if true, cannot use dir link count as subdir limif (if false, it may STILL be unreliable) */ struct stat stat_buf; size_t dircount = 0u; DIR *dirp; if (statp->st_nlink < 2) { subdirs_unreliable = true; subdirs_left = 0; } else { subdirs_unreliable = false; /* not necessarily right */ subdirs_left = statp->st_nlink - 2; /* Account for name and ".". */ } errno = 0; dirp = opendir_safer (name); if (dirp == NULL) { assert (errno != 0); error (0, errno, "%s", safely_quote_err_filename (0, pathname)); state.exit_status = 1; } else { 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 '/'. */ bool did_stat = false; 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; /* We're about to leave the directory. If there are any * -execdir argument lists which have been built but have not * yet been processed, do them now because they must be done in * the same directory. */ complete_pending_execdirs (); if (strcmp (name, ".")) { enum SafeChdirStatus status = safely_chdir (name, TraversingDown, &stat_buf, SymlinkHandleDefault, &did_stat); switch (status) { case SafeChdirOK: /* If there had been a change but wd_sanity_check() * accepted it, we need to accept that on the * way back up as well, so modify our record * of what we think we should see later. * If there was no change, the assignments are a no-op. * * However, before performing the assignment, we need to * check that we have the stat information. If O_NOFOLLOW * is available, safely_chdir() will not have needed to use * stat(), and so stat_buf will just contain random data. */ if (!did_stat) { /* If there is a link we need to follow it. Hence * the direct call to stat() not through (options.xstat) */ set_stat_placeholders (&stat_buf); if (0 != stat (".", &stat_buf)) break; /* skip the assignment. */ } dir_ids[dir_curr].dev = stat_buf.st_dev; dir_ids[dir_curr].ino = stat_buf.st_ino; break; case SafeChdirFailWouldBeUnableToReturn: error (0, errno, "."); state.exit_status = 1; break; case SafeChdirFailNonexistent: case SafeChdirFailDestUnreadable: case SafeChdirFailStat: case SafeChdirFailNotDir: case SafeChdirFailChdirFailed: error (0, errno, "%s", safely_quote_err_filename (0, pathname)); state.exit_status = 1; return; case SafeChdirFailSymlink: error (0, 0, _("warning: not following the symbolic link %s"), safely_quote_err_filename (0, pathname)); state.exit_status = 1; return; } } while (1) { const char *namep; mode_t mode = 0; const struct dirent *dp; /* We reset errno here to distinguish between end-of-directory and an error */ errno = 0; dp = readdir (dirp); if (NULL == dp) { if (errno) { /* an error occurred, but we are not yet at the end of the directory stream. */ error (0, errno, "%s", safely_quote_err_filename (0, pathname)); continue; } else { break; /* End of the directory stream. */ } } else { namep = dp->d_name; /* Skip "", ".", and "..". "" is returned by at least one buggy implementation: Solaris 2.4 readdir on NFS file systems. */ if (!namep[0] || (namep[0] == '.' && (namep[1] == 0 || (namep[1] == '.' && namep[2] == 0)))) continue; } #if defined HAVE_STRUCT_DIRENT_D_TYPE if (dp->d_type != DT_UNKNOWN) mode = type_to_mode (dp->d_type); #endif /* 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; 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); state.curdepth++; if (!options.no_leaf_check && !subdirs_unreliable) { if (mode && S_ISDIR(mode) && (subdirs_left == 0)) { /* This is a subdirectory, but the number of directories we * have found now exceeds the number we would expect given * the hard link count on the parent. This is likely to be * a bug in the file system driver (e.g. Linux's * /proc file system) or may just be a fact that the OS * doesn't really handle hard links with Unix semantics. * In the latter case, -noleaf should be used routinely. */ error (0, 0, _("WARNING: Hard link count is wrong for %s (saw only st_nlink=%" PRIuMAX " but we already saw %" PRIuMAX " subdirectories): this may be a bug in your file system driver. Automatically turning on find's -noleaf option. Earlier results may have failed to include directories that should have been searched."), safely_quote_err_filename(0, pathname), (uintmax_t) statp->st_nlink, (uintmax_t) dircount); state.exit_status = 1; /* We know the result is wrong, now */ options.no_leaf_check = true; /* Don't make same mistake again */ subdirs_unreliable = 1; subdirs_left = 1; /* band-aid for this iteration. */ } /* Normal case optimization. On normal Unix file systems, 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. */ { int count; count = process_path (cur_path, cur_name, subdirs_left == 0, pathname, mode, D_INO(dp)); subdirs_left -= count; dircount += count; } } else { /* There might be weird (e.g., CD-ROM or MS-DOS) file systems mounted, which don't have Unix-like directory link counts. */ process_path (cur_path, cur_name, false, pathname, mode, D_INO(dp)); } state.curdepth--; } /* We're about to leave the directory. If there are any * -execdir argument lists which have been built but have not * yet been processed, do them now because they must be done in * the same directory. */ complete_pending_execdirs (); if (strcmp (name, ".")) { enum SafeChdirStatus status; /* We could go back and do the next command-line arg instead, maybe using longjmp. */ char const *dir; bool deref = following_links () ? true : false; if ( (state.curdepth>0) && !deref) dir = ".."; else { chdir_back (); dir = parent; } did_stat = false; status = safely_chdir (dir, TraversingUp, &stat_buf, SymlinkHandleDefault, &did_stat); switch (status) { case SafeChdirOK: break; case SafeChdirFailWouldBeUnableToReturn: error (EXIT_FAILURE, errno, "."); return; case SafeChdirFailNonexistent: case SafeChdirFailDestUnreadable: case SafeChdirFailStat: case SafeChdirFailSymlink: case SafeChdirFailNotDir: case SafeChdirFailChdirFailed: error (EXIT_FAILURE, errno, "%s", safely_quote_err_filename (0, pathname)); return; } } free (cur_path); CLOSEDIR (dirp); } if (subdirs_unreliable) { /* Make sure we hasn't used the variable subdirs_left if we knew * we shouldn't do so. */ assert (0 == subdirs_left || options.no_leaf_check); } }
static void consider_visiting(FTS *p, FTSENT *ent) { struct stat statbuf; mode_t mode; int ignore, isdir; if (options.debug_options & DebugSearch) fprintf(stderr, "consider_visiting: fts_info=%-6s, fts_level=%2d, prev_depth=%d " "fts_path=%s, fts_accpath=%s\n", get_fts_info_name(ent->fts_info), (int)ent->fts_level, prev_depth, quotearg_n_style(0, options.err_quoting_style, ent->fts_path), quotearg_n_style(1, options.err_quoting_style, ent->fts_accpath)); if (ent->fts_info == FTS_DP) { left_dir(); } else if (ent->fts_level > prev_depth || ent->fts_level==0) { left_dir(); } inside_dir(p->fts_cwd_fd); prev_depth = ent->fts_level; /* Cope with various error conditions. */ if (ent->fts_info == FTS_ERR || ent->fts_info == FTS_DNR) { error(0, ent->fts_errno, "%s", safely_quote_err_filename(0, ent->fts_path)); error_severity(1); return; } else if (ent->fts_info == FTS_DC) { issue_loop_warning(ent); error_severity(1); return; } else if (ent->fts_info == FTS_SLNONE) { /* fts_read() claims that ent->fts_accpath is a broken symbolic * link. That would be fine, but if this is part of a symbolic * link loop, we diagnose the problem and also ensure that the * eventual return value is nonzero. Note that while the path * we stat is local (fts_accpath), we print the full path name * of the file (fts_path) in the error message. */ if (symlink_loop(ent->fts_accpath)) { error(0, ELOOP, "%s", safely_quote_err_filename(0, ent->fts_path)); error_severity(1); return; } } else if (ent->fts_info == FTS_NS) { if (ent->fts_level == 0) { /* e.g., nonexistent starting point */ error(0, ent->fts_errno, "%s", safely_quote_err_filename(0, ent->fts_path)); error_severity(1); /* remember problem */ return; } else { /* The following if statement fixes Savannah bug #19605 * (failure to diagnose a symbolic link loop) */ if (symlink_loop(ent->fts_accpath)) { error(0, ELOOP, "%s", safely_quote_err_filename(0, ent->fts_path)); error_severity(1); return; } } } /* Cope with the usual cases. */ if (ent->fts_info == FTS_NSOK || ent->fts_info == FTS_NS /* e.g. symlink loop */) { assert (!state.have_stat); assert (!state.have_type); state.type = mode = 0; } else { state.have_stat = true; state.have_type = true; statbuf = *(ent->fts_statp); state.type = mode = statbuf.st_mode; if (00000 == mode) { /* Savannah bug #16378. */ error(0, 0, _("Warning: file %s appears to have mode 0000"), quotearg_n_style(0, options.err_quoting_style, ent->fts_path)); } } if (mode) { if (!digest_mode(mode, ent->fts_path, ent->fts_name, &statbuf, 0)) return; } /* examine this item. */ ignore = 0; isdir = S_ISDIR(mode) || (FTS_D == ent->fts_info) || (FTS_DP == ent->fts_info) || (FTS_DC == ent->fts_info); if (isdir && (ent->fts_info == FTS_NSOK)) { /* This is a directory, but fts did not stat it, so * presumably would not be planning to search its * children. Force a stat of the file so that the * children can be checked. */ fts_set(p, ent, FTS_AGAIN); return; } if (options.maxdepth >= 0) { if (ent->fts_level >= options.maxdepth) { fts_set(p, ent, FTS_SKIP); /* descend no further */ if (ent->fts_level > options.maxdepth) ignore = 1; /* don't even look at this one */ } } if ( (ent->fts_info == FTS_D) && !options.do_dir_first ) { /* this is the preorder visit, but user said -depth */ ignore = 1; } else if ( (ent->fts_info == FTS_DP) && options.do_dir_first ) { /* this is the postorder visit, but user didn't say -depth */ ignore = 1; } else if (ent->fts_level < options.mindepth) { ignore = 1; } if (!ignore) { visit(p, ent, &statbuf); } /* XXX: if we allow a build-up of pending arguments for "-execdir foo {} +" * we need to execute them in the same directory as we found the item. * If we are trying to do "find a -execdir echo {} +", we will need to * echo * a while in the original working directory * b while in a * c while in b (just before leaving b) * * These restrictions are hard to satisfy while using fts(). The reason is * that it doesn't tell us just before we leave a directory. For the moment, * we punt and don't allow the arguments to build up. */ if (state.execdirs_outstanding) { show_outstanding_execdirs(stderr); run_in_dir(p->fts_cwd_fd, complete_execdirs_cb, NULL); } if (ent->fts_info == FTS_DP) { /* we're leaving a directory. */ state.stop_at_current_level = false; } }