Example #1
0
/* Take a "mode" indicator and fill in the files of 'state'.
 */
int
digest_mode(mode_t mode,
	    const char *pathname,
	    const char *name,
	    struct stat *pstat,
	    boolean leaf)
{
  /* If we know the type of the directory entry, and it is not a
   * symbolic link, we may be able to avoid a stat() or lstat() call.
   */
  if (mode)
    {
      if (S_ISLNK(mode) && following_links())
	{
	  /* mode is wrong because we should have followed the symlink. */
	  if (get_statinfo(pathname, name, pstat) != 0)
	    return 0;
	  mode = state.type = pstat->st_mode;
	  state.have_type = true;
	}
      else
	{
	  state.have_type = true;
	  pstat->st_mode = state.type = mode;
	}
    }
  else
    {
      /* Mode is not yet known; may have to stat the file unless we 
       * can deduce that it is not a directory (which is all we need to 
       * know at this stage)
       */
      if (leaf)
	{
	  state.have_stat = false;
	  state.have_type = false;;
	  state.type = 0;
	}
      else
	{
	  if (get_statinfo(pathname, name, pstat) != 0)
	    return 0;
	  
	  /* If -L is in effect and we are dealing with a symlink,
	   * st_mode is the mode of the pointed-to file, while mode is
	   * the mode of the directory entry (S_IFLNK).  Hence now
	   * that we have the stat information, override "mode".
	   */
	  state.type = pstat->st_mode;
	  state.have_type = true;
	}
    }

  /* success. */
  return 1;
}
Example #2
0
/* 
 * Return true if NAME corresponds to a file which forms part of a 
 * symbolic link loop.  The command 
 *      rm -f a b; ln -s a b; ln -s b a 
 * produces such a loop.
 */
static boolean 
symlink_loop(const char *name)
{
  struct stat stbuf;
  int rv;
  if (following_links())
    rv = stat(name, &stbuf);
  else
    rv = lstat(name, &stbuf);
  return (0 != rv) && (ELOOP == errno);
}
Example #3
0
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;
    }
}
Example #4
0
/* Safely change working directory to the specified subdirectory.  If
 * we are not allowed to follow symbolic links, we use open() with
 * O_NOFOLLOW, followed by fchdir().  This ensures that we don't
 * follow symbolic links (of course, we do follow them if the -L
 * option is in effect).
 */
static enum SafeChdirStatus
safely_chdir_nofollow (const char *dest,
		       enum TraversalDirection direction,
		       struct stat *statbuf_dest,
		       enum ChdirSymlinkHandling symlink_follow_option,
		       bool *did_stat)
{
  int extraflags, fd;

  (void) direction;
  (void) statbuf_dest;

  extraflags = 0;
  *did_stat = false;

  switch (symlink_follow_option)
    {
    case SymlinkFollowOk:
      extraflags = 0;
      break;

    case SymlinkHandleDefault:
      if (following_links ())
	extraflags = 0;
      else
	extraflags = O_NOFOLLOW; /* ... which may still be 0. */
      break;
    }

  errno = 0;
  fd = open (dest, O_RDONLY
#if defined O_LARGEFILE
	    |O_LARGEFILE
#endif
#if defined O_CLOEXEC
	    |O_CLOEXEC
#endif
	    |extraflags);
  if (fd < 0)
    {
      switch (errno)
	{
	case ELOOP:
	  return SafeChdirFailSymlink; /* This is why we use O_NOFOLLOW */
	case ENOENT:
	  return SafeChdirFailNonexistent;
	default:
	  return SafeChdirFailDestUnreadable;
	}
    }

  errno = 0;
  if (0 == fchdir (fd))
    {
      close (fd);
      return SafeChdirOK;
    }
  else
    {
      int saved_errno = errno;
      close (fd);
      errno = saved_errno;

      switch (errno)
	{
	case ENOTDIR:
	  return SafeChdirFailNotDir;

	case EACCES:
	case EBADF:		/* Shouldn't happen */
	case EINTR:
	case EIO:
	default:
	  return SafeChdirFailChdirFailed;
	}
    }
}
Example #5
0
/* Safely perform a change in directory.  We do this by calling
 * lstat() on the subdirectory, using chdir() to move into it, and
 * then lstat()ing ".".  We compare the results of the two stat calls
 * to see if they are consistent.  If not, we sound the alarm.
 *
 * If following_links() is true, we do follow symbolic links.
 */
static enum SafeChdirStatus
safely_chdir_lstat (const char *dest,
		    enum TraversalDirection direction,
		    struct stat *statbuf_dest,
		    enum ChdirSymlinkHandling symlink_follow_option,
		    bool *did_stat)
{
  struct stat statbuf_arrived;
  int rv, dotfd=-1;
  int saved_errno;		/* specific_dirname() changes errno. */
  bool rv_set = false;
  bool statflag = false;
  int tries = 0;
  enum WdSanityCheckFatality isfatal = RETRY_IF_SANITY_CHECK_FAILS;

  saved_errno = errno = 0;

  dotfd = open_cloexec (".", O_RDONLY
#if defined O_LARGEFILE
			|O_LARGEFILE
#endif
			);

  /* We jump back to here if wd_sanity_check()
   * recoverably triggers an alert.
   */
 retry:
  ++tries;

  if (dotfd >= 0)
    {
      /* Stat the directory we're going to. */
      set_stat_placeholders (statbuf_dest);
      if (0 == options.xstat (dest, statbuf_dest))
	{
	  statflag = true;

#ifdef S_ISLNK
	  /* symlink_follow_option might be set to SymlinkFollowOk, which
	   * would allow us 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().
	   */
	  if (!following_links () && S_ISLNK(statbuf_dest->st_mode))
	    {
	      /* We're not supposed to be following links, but this is
	       * a link.  Check symlink_follow_option to see if we should
	       * make a special exception.
	       */
	      if (symlink_follow_option == SymlinkFollowOk)
		{
		  /* We need to re-stat() the file so that the
		   * sanity check can pass.
		   */
		  if (0 != stat (dest, statbuf_dest))
		    {
		      rv = SafeChdirFailNonexistent;
		      rv_set = true;
		      saved_errno = errno;
		      goto fail;
		    }
		  statflag = true;
		}
	      else
		{
		  /* Not following symlinks, so the attempt to
		   * chdir() into a symlink should be prevented.
		   */
		  rv = SafeChdirFailSymlink;
		  rv_set = true;
		  saved_errno = 0;	/* silence the error message */
		  goto fail;
		}
	    }
#endif
#ifdef S_ISDIR
	  /* Although the immediately following chdir() would detect
	   * the fact that this is not a directory for us, this would
	   * result in an extra system call that fails.  Anybody
	   * examining the system-call trace should ideally not be
	   * concerned that something is actually failing.
	   */
	  if (!S_ISDIR(statbuf_dest->st_mode))
	    {
	      rv = SafeChdirFailNotDir;
	      rv_set = true;
	      saved_errno = 0;	/* silence the error message */
	      goto fail;
	    }
#endif

	  if (options.debug_options & DebugSearch)
	    fprintf (stderr, "safely_chdir(): chdir(\"%s\")\n", dest);

	  if (0 == chdir (dest))
	    {
	      /* check we ended up where we wanted to go */
	      bool changed = false;
	      if (!wd_sanity_check (".", program_name, ".",
				    statbuf_dest->st_dev,
				    statbuf_dest->st_ino,
				    &statbuf_arrived,
				    0, __LINE__, direction,
				    isfatal,
				    &changed))
		{
		  /* Only allow one failure. */
		  if (RETRY_IF_SANITY_CHECK_FAILS == isfatal)
		    {
		      if (0 == fchdir (dotfd))
			{
			  isfatal = FATAL_IF_SANITY_CHECK_FAILS;
			  goto retry;
			}
		      else
			{
			  /* Failed to return to original directory,
			   * but we know that the current working
			   * directory is not the one that we intend
			   * to be in.  Since fchdir() failed, we
			   * can't recover from this and so this error
			   * is fatal.
			   */
			  error (EXIT_FAILURE, errno,
				 _("failed to return to parent directory"));
			}
		    }
		  else
		    {
		      /* XXX: not sure what to use as an excuse here. */
		      rv = SafeChdirFailNonexistent;
		      rv_set = true;
		      saved_errno = 0;
		      goto fail;
		    }
		}

	      close (dotfd);
	      return SafeChdirOK;
	    }
	  else
	    {
	      saved_errno = errno;
	      if (ENOENT == saved_errno)
		{
		  rv = SafeChdirFailNonexistent;
		  rv_set = true;
		  if (options.ignore_readdir_race)
		    errno = 0;	/* don't issue err msg */
		}
	      else if (ENOTDIR == saved_errno)
		{
		  /* This can happen if the we stat a directory,
		   * and then file system activity changes it into
		   * a non-directory.
		   */
		  saved_errno = 0;	/* don't issue err msg */
		  rv = SafeChdirFailNotDir;
		  rv_set = true;
		}
	      else
		{
		  rv = SafeChdirFailChdirFailed;
		  rv_set = true;
		}
	      goto fail;
	    }
	}
      else
	{
	  saved_errno = errno;
	  rv = SafeChdirFailStat;
	  rv_set = true;

	  if ( (ENOENT == saved_errno) || (0 == state.curdepth))
	    saved_errno = 0;	/* don't issue err msg */
	  goto fail;
	}
    }
  else
    {
      /* We do not have read permissions on "." */
      rv = SafeChdirFailWouldBeUnableToReturn;
      rv_set = true;
      goto fail;
    }

  /* This is the success path, so we clear errno.  The caller probably
   * won't be calling error() anyway.
   */
  saved_errno = 0;

  /* We use the same exit path for success or failure.
   * which has occurred is recorded in RV.
   */
 fail:
  /* We do not call error() as this would result in a duplicate error
   * message when the caller does the same thing.
   */
  if (saved_errno)
    errno = saved_errno;

  if (dotfd >= 0)
    {
      close (dotfd);
      dotfd = -1;
    }

  *did_stat = statflag;
  assert (rv_set);
  return rv;
}
Example #6
0
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);
    }
}