? change.txt
? execdir-pwd.patch
? lib/dircallback.c
? lib/dircallback.h
Index: find/defs.h
===================================================================
RCS file: /cvsroot/findutils/findutils/find/defs.h,v
retrieving revision 1.65
diff -u -p -r1.65 defs.h
--- find/defs.h	28 Mar 2007 10:23:23 -0000	1.65
+++ find/defs.h	14 Apr 2007 13:58:40 -0000
@@ -65,6 +65,8 @@ extern int errno;
 
 #include "regex.h"
 #include "timespec.h"
+#include "buildcmd.h"
+
 
 #ifndef S_IFLNK
 #define lstat stat
@@ -236,11 +238,8 @@ struct time_val
 };
 
     
-#include "buildcmd.h"
-
 struct exec_val
 {
-  /* new-style */
   boolean multiple;		/* -exec {} \+ denotes multiple argument. */
   struct buildcmd_control ctl;
   struct buildcmd_state   state;
@@ -248,6 +247,7 @@ struct exec_val
   int num_args;
   boolean use_current_dir;      /* If nonzero, don't chdir to start dir */
   boolean close_stdin;		/* If true, close stdin in the child. */
+  int dirfd;			/* The directory to do the exec in. */
 };
 
 /* The format string for a -printf or -fprintf is chopped into one or
@@ -376,8 +376,8 @@ struct predicate
 
 /* find.c, ftsfind.c */
 boolean is_fts_enabled();
-
-
+int get_start_dirfd(void);
+int get_current_dirfd(void);
 
 /* find library function declarations.  */
 
@@ -550,13 +550,13 @@ struct predicate *insert_primary_withpre
 void usage PARAMS((FILE *fp, int status, char *msg));
 extern boolean check_nofollow(void);
 void complete_pending_execs(struct predicate *p);
-void complete_pending_execdirs(struct predicate *p);
+void complete_pending_execdirs(struct predicate *p, int dirfd);
 int process_leading_options PARAMS((int argc, char *argv[]));
 void set_option_defaults PARAMS((struct options *p));
 
 
 /* find.c. */
-int get_info PARAMS((const char *pathname, const char *name, struct stat *p, struct predicate *pred_ptr));
+int get_info PARAMS((const char *pathname, struct stat *p, struct predicate *pred_ptr));
 int following_links PARAMS((void));
 int digest_mode PARAMS((mode_t mode, const char *pathname, const char *name, struct stat *pstat, boolean leaf));
 boolean default_prints PARAMS((struct predicate *pred));
@@ -659,6 +659,10 @@ struct state
   /* The file being operated on, relative to the current directory.
      Used for stat, readlink, remove, and opendir.  */
   char *rel_pathname;
+  /* The directory fd to which rel_pathname is relative.  Thsi is relevant
+   * when we're navigating the hierarchy with fts() and using FTS_CWDFD.
+   */
+  int cwd_dir_fd;
 
   /* Length of starting path. */
   int starting_path_length;
Index: find/find.c
===================================================================
RCS file: /cvsroot/findutils/findutils/find/find.c,v
retrieving revision 1.110
diff -u -p -r1.110 find.c
--- find/find.c	28 Mar 2007 10:23:23 -0000	1.110
+++ find/find.c	14 Apr 2007 13:58:41 -0000
@@ -42,12 +42,14 @@
 #else
 #include <sys/file.h>
 #endif
+#include <openat.h>
 
 #include "../gnulib/lib/xalloc.h"
 #include "../gnulib/lib/human.h"
 #include "../gnulib/lib/canonicalize.h"
-#include "closeout.h"
 #include <modetype.h>
+
+#include "closeout.h"
 #include "savedirinfo.h"
 #include "buildcmd.h"
 #include "dirname.h"
@@ -117,6 +119,12 @@ enum WdSanityCheckFatality
     NON_FATAL_IF_SANITY_CHECK_FAILS
   };
 
+
+int get_current_dirfd(void)
+{
+  return AT_FDCWD;
+}
+
 
 int
 main (int argc, char **argv)
@@ -151,6 +159,11 @@ main (int argc, char **argv)
   fprintf (stderr, "cur_day_start = %s", ctime (&options.cur_day_start));
 #endif /* DEBUG */
 
+  /* state.cwd_dir_fd has to be initialised before we call build_expression_tree()
+   * because command-line parsing may lead us to stat some files.
+   */
+  state.cwd_dir_fd = AT_FDCWD;
+  
   /* We are now processing the part of the "find" command line 
    * after the -H/-L options (if any).
    */
@@ -175,12 +188,14 @@ main (int argc, char **argv)
     }
   
 
-  starting_desc = open (".", O_RDONLY);
+  starting_desc = open (".", O_RDONLY|O_LARGEFILE);
   if (0 <= starting_desc && fchdir (starting_desc) != 0)
     {
       close (starting_desc);
       starting_desc = -1;
     }
+  assert(starting_desc >= 0);
+
   if (starting_desc < 0)
     {
       starting_dir = xgetcwd ();
@@ -189,7 +204,7 @@ main (int argc, char **argv)
     }
   set_stat_placeholders(&starting_stat_buf);
   if ((*options.xstat) (".", &starting_stat_buf) != 0)
-    error (1, errno, _("cannot get current directory"));
+    error (1, errno, _("cannot stat current directory"));
 
   /* If no paths are given, default to ".".  */
   for (i = end_of_leading_options; i < argc && !looks_like_expression(argv[i], true); i++)
@@ -533,7 +548,8 @@ enum SafeChdirStatus
     SafeChdirFailStat,
     SafeChdirFailWouldBeUnableToReturn,
     SafeChdirFailChdirFailed,
-    SafeChdirFailNonexistent
+    SafeChdirFailNonexistent,
+    SafeChdirFailDestUnreadable
   };
 
 /* Safely perform a change in directory.  We do this by calling
@@ -560,7 +576,7 @@ safely_chdir_lstat(const char *dest,
   
   saved_errno = errno = 0;
 
-  dotfd = open(".", O_RDONLY);
+  dotfd = open(".", O_RDONLY|O_LARGEFILE);
 
   /* We jump back to here if wd_sanity_check()
    * recoverably triggers an alert.
@@ -680,7 +696,7 @@ safely_chdir_lstat(const char *dest,
 		      goto fail;
 		    }
 		}
-	      
+
 	      close(dotfd);
 	      return SafeChdirOK;
 	    }
@@ -794,7 +810,7 @@ safely_chdir_nofollow(const char *dest,
     }
   
   errno = 0;
-  fd = open(dest, O_RDONLY|extraflags);
+  fd = open(dest, O_RDONLY|O_LARGEFILE|extraflags);
   if (fd < 0)
     {
       switch (errno)
@@ -804,7 +820,7 @@ safely_chdir_nofollow(const char *dest,
 	case ENOENT:
 	  return SafeChdirFailNonexistent;
 	default:
-	  return SafeChdirFailChdirFailed;
+	  return SafeChdirFailDestUnreadable;
 	}
     }
   
@@ -843,18 +859,23 @@ safely_chdir(const char *dest,
 	     enum ChdirSymlinkHandling symlink_follow_option,
 	     boolean *did_stat)
 {
+  enum SafeChdirStatus result;
+  
   /* We're about to leave a 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(get_eval_tree());
+  complete_pending_execdirs(get_eval_tree(), get_current_dirfd());
 
-#if defined(O_NOFOLLOW)
-  if (options.open_nofollow_available)
-    return safely_chdir_nofollow(dest, direction, statbuf_dest, symlink_follow_option, did_stat);
+#if !defined(O_NOFOLLOW)
+  options.open_nofollow_available = false;
 #endif
-  return safely_chdir_lstat(dest, direction, statbuf_dest, symlink_follow_option, did_stat);
+  if (options.open_nofollow_available)
+    result = safely_chdir_nofollow(dest, direction, statbuf_dest, symlink_follow_option, did_stat);
+  else
+    result = safely_chdir_lstat(dest, direction, statbuf_dest, symlink_follow_option, did_stat);
+  return result;
 }
 
 
@@ -898,7 +919,9 @@ chdir_back (void)
 	fprintf(stderr, "chdir_back(): chdir(<starting-point>)\n");
 
       if (fchdir (starting_desc) != 0)
-	error (1, errno, "%s", starting_dir);
+	{
+	  error (1, errno, "%s", starting_dir);
+	}
     }
 }
 
@@ -987,7 +1010,7 @@ static void do_process_top_dir(char *pat
   (void) pstat;
   
   process_path (pathname, base, false, ".", mode);
-  complete_pending_execdirs(get_eval_tree());
+  complete_pending_execdirs(get_eval_tree(), get_current_dirfd());
 }
 
 static void do_process_predicate(char *pathname,
@@ -997,7 +1020,7 @@ static void do_process_predicate(char *p
 {
   (void) mode;
   
-  state.rel_pathname = base;
+  state.rel_pathname = base;	/* cwd_dir_fd was already set by safely_chdir */
   apply_predicate (pathname, pstat, get_eval_tree());
 }
 
@@ -1262,7 +1285,7 @@ process_dir (char *pathname, char *name,
        * yet been processed, do them now because they must be done in
        * the same directory.
        */
-      complete_pending_execdirs(get_eval_tree());
+      complete_pending_execdirs(get_eval_tree(), get_current_dirfd());
       
       if (strcmp (name, "."))
 	{
@@ -1301,6 +1324,7 @@ process_dir (char *pathname, char *name,
 	      break;
 	      
 	    case SafeChdirFailNonexistent:
+	    case SafeChdirFailDestUnreadable:
 	    case SafeChdirFailStat:
 	    case SafeChdirFailNotDir:
 	    case SafeChdirFailChdirFailed:
@@ -1391,7 +1415,7 @@ process_dir (char *pathname, char *name,
        * yet been processed, do them now because they must be done in
        * the same directory.
        */
-      complete_pending_execdirs(get_eval_tree()); 
+      complete_pending_execdirs(get_eval_tree(), get_current_dirfd()); 
 
       if (strcmp (name, "."))
 	{
@@ -1423,6 +1447,7 @@ process_dir (char *pathname, char *name,
 	      return;
 	      
 	    case SafeChdirFailNonexistent:
+	    case SafeChdirFailDestUnreadable:
 	    case SafeChdirFailStat:
 	    case SafeChdirFailSymlink:
 	    case SafeChdirFailNotDir:
Index: find/ftsfind.c
===================================================================
RCS file: /cvsroot/findutils/findutils/find/ftsfind.c,v
retrieving revision 1.21
diff -u -p -r1.21 ftsfind.c
--- find/ftsfind.c	24 Feb 2007 14:47:26 -0000	1.21
+++ find/ftsfind.c	14 Apr 2007 13:58:41 -0000
@@ -44,6 +44,10 @@
 #include <sys/file.h>
 #endif
 
+#include <unistd.h>
+#if defined(HAVE_FCNTL_H)
+#include <fcntl.h>
+#endif
 
 #include "../gnulib/lib/xalloc.h"
 #include "closeout.h"
@@ -51,6 +55,7 @@
 #include "quotearg.h"
 #include "quote.h"
 #include "fts_.h"
+#include "openat.h"
 
 #ifdef HAVE_LOCALE_H
 #include <locale.h>
@@ -72,6 +77,75 @@
 #endif
 
 
+static void set_close_on_exec(int fd)
+{
+#if defined(F_GETFD) && defined(FD_CLOEXEC)
+  int flags;
+  flags = fcntl(fd, F_GETFD);
+  if (flags >= 0)
+    {
+      flags |= FD_CLOEXEC;
+      fcntl(fd, F_SETFD, flags);
+    }
+#endif
+}
+
+
+static int curr_fd = -1;
+
+int get_current_dirfd(void)
+{
+  assert(curr_fd != -1);
+  assert( (AT_FDCWD == curr_fd) || (curr_fd >= 0) );
+  
+  if (AT_FDCWD == curr_fd)
+    return starting_desc;
+  else
+    return curr_fd;
+}
+
+static void left_dir(void)
+{
+  if (curr_fd >= 0)
+    {
+      close(curr_fd);
+      curr_fd = -1;
+    }
+}
+
+/*
+ * Signal that we are now inside a directory pointed to by dirfd.
+ * The caller can't tell if this is the first time this happens, so 
+ * we have to be careful not to call dup() more than once 
+ */
+static void inside_dir(int dirfd)
+{
+  assert(dirfd == AT_FDCWD || dirfd >= 0);
+
+  state.cwd_dir_fd = dirfd;
+  if (curr_fd < 0)
+    {
+      if (AT_FDCWD == dirfd)
+	{
+	  curr_fd = AT_FDCWD;
+	}
+      else if (dirfd >= 0)
+	{
+	  curr_fd = dup(dirfd);
+	  set_close_on_exec(curr_fd);
+	}
+      else 
+	{
+	  /* curr_fd is invalid, but dirfd is also invalid.
+	   * This should not have happened.
+	   */
+	  assert(curr_fd >= 0 || dirfd >= 0);
+	}
+    }
+}
+
+
+
 #ifdef STAT_MOUNTPOINTS
 static void init_mounted_dev_list(void);
 #endif
@@ -126,6 +200,7 @@ visit(FTS *p, FTSENT *ent, struct stat *
   state.curdepth = ent->fts_level;
   state.have_stat = (ent->fts_info != FTS_NS) && (ent->fts_info != FTS_NSOK);
   state.rel_pathname = ent->fts_accpath;
+  state.cwd_dir_fd   = p->fts_cwd_fd;
 
   /* Apply the predicates to this path. */
   eval_tree = get_eval_tree();
@@ -234,9 +309,16 @@ consider_visiting(FTS *p, FTSENT *ent)
             (int)ent->fts_level,
 	    quotearg_n_style(0, locale_quoting_style, ent->fts_path));
 
+  if (ent->fts_info == FTS_DP)
+    {
+      complete_pending_execdirs(get_eval_tree(), get_current_dirfd());
+      left_dir();
+    }
+  inside_dir(p->fts_cwd_fd);
+
+  
   /* Cope with various error conditions. */
   if (ent->fts_info == FTS_ERR
-      || ent->fts_info == FTS_NS
       || ent->fts_info == FTS_DNR)
     {
       error(0, ent->fts_errno, ent->fts_path);
@@ -336,15 +418,14 @@ consider_visiting(FTS *p, FTSENT *ent)
       visit(p, ent, &statbuf);
     }
 
-
   if (ent->fts_info == FTS_DP)
     {
       /* we're leaving a directory. */
       state.stop_at_current_level = false;
-      complete_pending_execdirs(get_eval_tree());
     }
 }
 
+
 
 static void
 find(char *arg)
@@ -356,11 +437,12 @@ find(char *arg)
   
 
   state.starting_path_length = strlen(arg);
+  inside_dir(AT_FDCWD);
 
   arglist[0] = arg;
   arglist[1] = NULL;
   
-  ftsoptions = FTS_NOSTAT;
+  ftsoptions = FTS_NOSTAT|FTS_CWDFD;
 
   /* Try to work around Savannah bug #17877 (but actually this change
    * doesn't fix the bug).
@@ -399,7 +481,6 @@ find(char *arg)
 	  state.have_stat = false;
 	  state.have_type = false;
 	  state.type = 0;
-	  
 	  consider_visiting(p, ent);
 	}
       fts_close(p);
@@ -444,7 +525,7 @@ main (int argc, char **argv)
 
   program_name = argv[0];
   state.exit_status = 0;
-
+  state.cwd_dir_fd = AT_FDCWD;
 
   /* Set the option defaults before we do the the locale
    * initialisation as check_nofollow() needs to be executed in the
@@ -496,7 +577,7 @@ main (int argc, char **argv)
     }
   
 
-  starting_desc = open (".", O_RDONLY);
+  starting_desc = open (".", O_RDONLY|O_LARGEFILE);
   if (0 <= starting_desc && fchdir (starting_desc) != 0)
     {
       close (starting_desc);
Index: find/parser.c
===================================================================
RCS file: /cvsroot/findutils/findutils/find/parser.c,v
retrieving revision 1.106
diff -u -p -r1.106 parser.c
--- find/parser.c	28 Mar 2007 10:23:23 -0000	1.106
+++ find/parser.c	14 Apr 2007 13:58:42 -0000
@@ -166,7 +166,7 @@ static boolean insert_fprintf PARAMS((FI
 static struct segment **make_segment PARAMS((struct segment **segment, char *format, int len,
 					     int kind, char format_char, char aux_format_char,
 					     struct predicate *pred));
-static boolean insert_exec_ok PARAMS((const char *action, const struct parser_table *entry, char *argv[], int *arg_ptr));
+static boolean insert_exec_ok PARAMS((const char *action, const struct parser_table *entry, int dirfd, char *argv[], int *arg_ptr));
 static boolean get_comp_type PARAMS((char **str, enum comparison_type *comp_type));
 static boolean get_relative_timestamp PARAMS((char *str, struct time_val *tval, time_t origin, double sec_per_unit, const char *overflowmessage));
 static boolean get_num PARAMS((char *str, uintmax_t *num, enum comparison_type *comp_type));
@@ -721,13 +721,13 @@ parse_empty (const struct parser_table* 
 static boolean
 parse_exec (const struct parser_table* entry, char **argv, int *arg_ptr)
 {
-  return insert_exec_ok ("-exec", entry, argv, arg_ptr);
+  return insert_exec_ok ("-exec", entry, get_start_dirfd(), argv, arg_ptr);
 }
 
 static boolean
 parse_execdir (const struct parser_table* entry, char **argv, int *arg_ptr)
 {
-  return insert_exec_ok ("-execdir", entry, argv, arg_ptr);
+  return insert_exec_ok ("-execdir", entry, -1, argv, arg_ptr);
 }
 
 static boolean
@@ -1482,13 +1482,13 @@ parse_nowarn (const struct parser_table*
 static boolean
 parse_ok (const struct parser_table* entry, char **argv, int *arg_ptr)
 {
-  return insert_exec_ok ("-ok", entry, argv, arg_ptr);
+  return insert_exec_ok ("-ok", entry, get_start_dirfd(), argv, arg_ptr);
 }
 
 static boolean
 parse_okdir (const struct parser_table* entry, char **argv, int *arg_ptr)
 {
-  return insert_exec_ok ("-okdir", entry, argv, arg_ptr);
+  return insert_exec_ok ("-okdir", entry, -1, argv, arg_ptr);
 }
 
 boolean
@@ -2578,6 +2578,7 @@ check_path_safety(const char *action, ch
 static boolean
 new_insert_exec_ok (const char *action,
 		    const struct parser_table *entry,
+		    int dirfd,
 		    char **argv,
 		    int *arg_ptr)
 {
@@ -2773,9 +2774,9 @@ new_insert_exec_ok (const char *action,
 
 
 static boolean
-insert_exec_ok (const char *action, const struct parser_table *entry, char **argv, int *arg_ptr)
+insert_exec_ok (const char *action, const struct parser_table *entry, int dirfd, char **argv, int *arg_ptr)
 {
-  return new_insert_exec_ok(action, entry, argv, arg_ptr);
+  return new_insert_exec_ok(action, entry, dirfd, argv, arg_ptr);
 }
 
 
Index: find/pred.c
===================================================================
RCS file: /cvsroot/findutils/findutils/find/pred.c,v
retrieving revision 1.82
diff -u -p -r1.82 pred.c
--- find/pred.c	28 Mar 2007 10:23:23 -0000	1.82
+++ find/pred.c	14 Apr 2007 13:58:42 -0000
@@ -30,6 +30,7 @@
 #include <assert.h>
 #include <fcntl.h>
 #include <locale.h>
+#include <openat.h>
 #include "xalloc.h"
 #include "dirname.h"
 #include "human.h"
@@ -41,6 +42,7 @@
 #include "yesno.h"
 #include "listfile.h"
 #include "stat-time.h"
+#include "dircallback.h"
 
 #if ENABLE_NLS
 # include <libintl.h>
@@ -305,7 +307,7 @@ pred_and (char *pathname, struct stat *s
 					    pred_ptr->pred_left))
     {
       /* Check whether we need a stat here. */
-      if (get_info(pathname, state.rel_pathname, stat_buf, pred_ptr) != 0)
+      if (get_info(pathname, stat_buf, pred_ptr) != 0)
 	    return false;
       return ((*pred_ptr->pred_right->pred_func) (pathname, stat_buf,  
 						  pred_ptr->pred_right));
@@ -363,7 +365,7 @@ pred_comma (char *pathname, struct stat 
 				       pred_ptr->pred_left);
   /* Check whether we need a stat here. */
   /* TODO: what about need_type? */
-  if (get_info(pathname, state.rel_pathname, stat_buf, pred_ptr) != 0)
+  if (get_info(pathname, stat_buf, pred_ptr) != 0)
     return false;
   return ((*pred_ptr->pred_right->pred_func) (pathname, stat_buf,
 					      pred_ptr->pred_right));
@@ -383,7 +385,10 @@ pred_delete (char *pathname, struct stat
   (void) stat_buf;
   if (strcmp (state.rel_pathname, "."))
     {
-      if (0 != remove (state.rel_pathname))
+      int flags=0;
+      if (S_ISDIR(stat_buf->st_mode))
+	flags |= AT_REMOVEDIR;
+      if (0 != unlinkat(state.cwd_dir_fd, state.rel_pathname, flags))
 	{
 	  error (0, errno, "cannot delete %s", pathname);
 	  return false;
@@ -406,12 +411,19 @@ pred_empty (char *pathname, struct stat 
   
   if (S_ISDIR (stat_buf->st_mode))
     {
+      int fd;
       DIR *d;
       struct dirent *dp;
       boolean empty = true;
 
       errno = 0;
-      d = opendir (state.rel_pathname);
+      if ((fd = openat(state.cwd_dir_fd, state.rel_pathname, O_RDONLY|O_LARGEFILE)) < 0)
+	{
+	  error (0, errno, "%s", pathname);
+	  state.exit_status = 1;
+	  return false;
+	}
+      d = fdopendir (fd);
       if (d == NULL)
 	{
 	  error (0, errno, "%s", pathname);
@@ -443,7 +455,8 @@ pred_empty (char *pathname, struct stat 
 }
 
 static boolean
-new_impl_pred_exec (const char *pathname, struct stat *stat_buf,
+new_impl_pred_exec (int dirfd, const char *pathname,
+		    struct stat *stat_buf,
 		    struct predicate *pred_ptr,
 		    const char *prefix, size_t pfxlen)
 {
@@ -451,7 +464,7 @@ new_impl_pred_exec (const char *pathname
   size_t len = strlen(pathname);
 
   (void) stat_buf;
-  
+  execp->dirfd = dirfd;
   if (execp->multiple)
     {
       /* Push the argument onto the current list. 
@@ -494,7 +507,8 @@ new_impl_pred_exec (const char *pathname
 boolean
 pred_exec (char *pathname, struct stat *stat_buf, struct predicate *pred_ptr)
 {
-  return new_impl_pred_exec(pathname, stat_buf, pred_ptr, NULL, 0);
+  return new_impl_pred_exec(get_start_dirfd(),
+			    pathname, stat_buf, pred_ptr, NULL, 0);
 }
 
 boolean
@@ -502,7 +516,8 @@ pred_execdir (char *pathname, struct sta
 {
    const char *prefix = (state.rel_pathname[0] == '/') ? NULL : "./";
    (void) &pathname;
-   return new_impl_pred_exec (state.rel_pathname, stat_buf, pred_ptr,
+   return new_impl_pred_exec (get_current_dirfd(),
+			      state.rel_pathname, stat_buf, pred_ptr,
 			      prefix, (prefix ? 2 : 0));
 }
 
@@ -520,7 +535,7 @@ pred_false (char *pathname, struct stat 
 boolean
 pred_fls (char *pathname, struct stat *stat_buf, struct predicate *pred_ptr)
 {
-  list_file (pathname, state.rel_pathname, stat_buf, options.start_time.tv_sec,
+  list_file (pathname, state.cwd_dir_fd, state.rel_pathname, stat_buf, options.start_time.tv_sec,
 	     options.output_block_size,
 	     pred_ptr->literal_control_chars, pred_ptr->args.stream);
   return true;
@@ -730,7 +745,7 @@ do_fprintf(FILE *fp,
 
 	    if (S_ISLNK (stat_buf->st_mode))
 	      {
-		linkname = get_link_name (pathname, state.rel_pathname);
+		linkname = get_link_name_at (pathname, state.cwd_dir_fd, state.rel_pathname);
 		if (linkname == 0)
 		  state.exit_status = 1;
 	      }
@@ -740,7 +755,12 @@ do_fprintf(FILE *fp,
 		free (linkname);
 	      }
 	    else
-	      print_quoted (fp, qopts, ttyflag, segment->text, "");
+	      {
+		/* We still need to honour the field width etc., so this is
+		 * not a no-op.
+		 */
+		print_quoted (fp, qopts, ttyflag, segment->text, "");
+	      }
 	  }
 #endif				/* S_ISLNK */
 	  break;
@@ -1114,7 +1134,7 @@ match_lname (char *pathname, struct stat
 #ifdef S_ISLNK
   if (S_ISLNK (stat_buf->st_mode))
     {
-      char *linkname = get_link_name (pathname, state.rel_pathname);
+      char *linkname = get_link_name_at (pathname, state.cwd_dir_fd, state.rel_pathname);
       if (linkname)
 	{
 	  if (fnmatch (pred_ptr->args.str, linkname,
@@ -1130,7 +1150,8 @@ match_lname (char *pathname, struct stat
 boolean
 pred_ls (char *pathname, struct stat *stat_buf, struct predicate *pred_ptr)
 {
-  list_file (pathname, state.rel_pathname, stat_buf, options.start_time.tv_sec,
+  list_file (pathname, state.cwd_dir_fd, state.rel_pathname,
+	     stat_buf, options.start_time.tv_sec,
 	     options.output_block_size,
 	     pred_ptr->literal_control_chars,
 	     stdout);
@@ -1172,7 +1193,7 @@ pred_negate (char *pathname, struct stat
 {
   /* Check whether we need a stat here. */
   /* TODO: what about need_type? */
-  if (get_info(pathname, state.rel_pathname, stat_buf, pred_ptr) != 0)
+  if (get_info(pathname, stat_buf, pred_ptr) != 0)
     return false;
   return (!(*pred_ptr->pred_right->pred_func) (pathname, stat_buf,
 					      pred_ptr->pred_right));
@@ -1275,7 +1296,8 @@ boolean
 pred_ok (char *pathname, struct stat *stat_buf, struct predicate *pred_ptr)
 {
   if (is_ok(pred_ptr->args.exec_vec.replace_vec[0], pathname))
-    return new_impl_pred_exec (pathname, stat_buf, pred_ptr, NULL, 0);
+    return new_impl_pred_exec (get_start_dirfd(),
+			       pathname, stat_buf, pred_ptr, NULL, 0);
   else
     return false;
 }
@@ -1285,7 +1307,8 @@ pred_okdir (char *pathname, struct stat 
 {
   const char *prefix = (state.rel_pathname[0] == '/') ? NULL : "./";
   if (is_ok(pred_ptr->args.exec_vec.replace_vec[0], pathname))
-    return new_impl_pred_exec (state.rel_pathname, stat_buf, pred_ptr, 
+    return new_impl_pred_exec (get_current_dirfd(),
+			       state.rel_pathname, stat_buf, pred_ptr, 
 			       prefix, (prefix ? 2 : 0));
   else
     return false;
@@ -1307,7 +1330,7 @@ pred_or (char *pathname, struct stat *st
       || !(*pred_ptr->pred_left->pred_func) (pathname, stat_buf,
 					     pred_ptr->pred_left))
     {
-      if (get_info(pathname, state.rel_pathname, stat_buf, pred_ptr) != 0)
+      if (get_info(pathname, stat_buf, pred_ptr) != 0)
 	return false;
       return ((*pred_ptr->pred_right->pred_func) (pathname, stat_buf,
 						  pred_ptr->pred_right));
@@ -1365,6 +1388,35 @@ pred_perm (char *pathname, struct stat *
 }
 
 
+struct access_check_args
+{
+  const char *filename;
+  int access_type;
+  int cb_errno;
+};
+
+
+static int
+access_callback(void *context)
+{
+  int rv;
+  struct access_check_args *args = context;
+  if ((rv = access(args->filename, args->access_type)) < 0)
+    args->cb_errno = errno;
+  return rv;
+}
+
+static int
+can_access(int access_type)
+{
+  struct access_check_args args;
+  args.filename = state.rel_pathname;
+  args.access_type = access_type;
+  args.cb_errno = 0;
+  return 0 == run_in_dir(state.cwd_dir_fd, access_callback, &args);
+}
+
+
 boolean
 pred_executable (char *pathname, struct stat *stat_buf, struct predicate *pred_ptr)
 {
@@ -1372,7 +1424,7 @@ pred_executable (char *pathname, struct 
   (void) stat_buf;
   (void) pred_ptr;
   
-  return 0 == access(state.rel_pathname, X_OK);
+  return can_access(X_OK);
 }
 
 boolean
@@ -1382,7 +1434,7 @@ pred_readable (char *pathname, struct st
   (void) stat_buf;
   (void) pred_ptr;
   
-  return 0 == access(state.rel_pathname, R_OK);
+  return can_access(R_OK);
 }
 
 boolean
@@ -1392,7 +1444,7 @@ pred_writable (char *pathname, struct st
   (void) stat_buf;
   (void) pred_ptr;
   
-  return 0 == access(state.rel_pathname, W_OK);
+  return can_access(W_OK);
 }
 
 boolean
@@ -1666,25 +1718,52 @@ pred_xtype (char *pathname, struct stat 
     Otherwise return false, possibly printing an error message. */
 
 
-static void
-prep_child_for_exec (boolean close_stdin)
+static boolean
+prep_child_for_exec (boolean close_stdin, int dirfd)
 {
+  boolean ok = true;
   if (close_stdin)
     {
       const char inputfile[] = "/dev/null";
-      /* fprintf(stderr, "attaching stdin to /dev/null\n"); */
       
-      close(0);
-      if (open(inputfile, O_RDONLY) < 0)
+      if (close(0) < 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, _("Cannot close standard input"));
+	  ok = false;
+	}
+      else 
+	{
+	  if (open(inputfile, O_RDONLY|O_LARGEFILE) < 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", 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 (dirfd != AT_FDCWD)
+    {
+      assert(dirfd >= 0);
+      if (0 != fchdir(dirfd))
+	{
+	  /* If we cannot execute our command in the correct directory,
+	   * we should not execute it at all.
 	   */
-	  error (0, errno, "%s", inputfile);
+	  error(0, errno, _("Failed to change directory"));
+	  ok = false;
 	}
     }
+  return ok;
 }
 
 
@@ -1697,7 +1776,14 @@ launch (const struct buildcmd_control *c
   pid_t child_pid;
   static int first_time = 1;
   const struct exec_val *execp = buildstate->usercontext;
+
+  if (!execp->use_current_dir)
+    {
+      assert(starting_desc >= 0);
+      assert(execp->dirfd == starting_desc);
+    }
   
+	
   /* Null terminate the arg list.  */
   bc_push_arg (ctl, buildstate, (char *) NULL, 0, NULL, 0, false); 
   
@@ -1717,27 +1803,11 @@ launch (const struct buildcmd_control *c
     error (1, errno, _("cannot fork"));
   if (child_pid == 0)
     {
-      /* We be the child. */
-      prep_child_for_exec(execp->close_stdin);
-
-      /* For -exec and -ok, change directory back to the starting directory.
-       * for -execdir and -okdir, stay in the directory we are searching
-       * (the latter is more secure).
-       */
-      if (!execp->use_current_dir)
+      /* We are the child. */
+      assert(starting_desc >= 0);
+      if (!prep_child_for_exec(execp->close_stdin, execp->dirfd))
 	{
-	  /* 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 (starting_desc < 0
-	      ? chdir (starting_dir) != 0
-	      : fchdir (starting_desc) != 0)
-	    {
-	      error (0, errno, "%s", starting_dir);
-	      _exit (1);
-	    }
+	  _exit(1);
 	}
       
       execvp (buildstate->cmd_argv[0], buildstate->cmd_argv);
Index: find/util.c
===================================================================
RCS file: /cvsroot/findutils/findutils/find/util.c,v
retrieving revision 1.33
diff -u -p -r1.33 util.c
--- find/util.c	28 Mar 2007 10:23:23 -0000	1.33
+++ find/util.c	14 Apr 2007 13:58:42 -0000
@@ -51,6 +51,7 @@
 #include <string.h>
 #include <limits.h>
 #include <assert.h>
+#include <openat.h>
 
 
 struct debug_option_assoc
@@ -215,23 +216,23 @@ get_statinfo (const char *pathname, cons
  */
 int
 get_info (const char *pathname,
-	  const char *name,
 	  struct stat *p,
 	  struct predicate *pred_ptr)
 {
+  boolean todo = false;
+  
   /* If we need the full stat info, or we need the type info but don't 
    * already have it, stat the file now.
    */
-  (void) name;
   if (pred_ptr->need_stat)
-    {
-      return get_statinfo(pathname, state.rel_pathname, p);
-    }
-  if ((pred_ptr->need_type && (0 == state.have_type)))
-    {
-      return get_statinfo(pathname, state.rel_pathname, p);
-    }
-  return 0;
+    todo = true;
+  else if ((pred_ptr->need_type && (0 == state.have_type)))
+    todo = true;
+  
+  if (todo)
+    return get_statinfo(pathname, state.rel_pathname, p);
+  else
+    return 0;
 }
 
 /* Determine if we can use O_NOFOLLOW.
@@ -273,12 +274,12 @@ check_nofollow(void)
  * have no effect if there are no arguments waiting).
  */
 void
-complete_pending_execdirs(struct predicate *p)
+complete_pending_execdirs(struct predicate *p, int dirfd)
 {
   if (NULL == p)
     return;
   
-  complete_pending_execdirs(p->pred_left);
+  complete_pending_execdirs(p->pred_left, dirfd);
   
   if (p->pred_func == pred_execdir || p->pred_func == pred_okdir)
     {
@@ -298,7 +299,7 @@ complete_pending_execdirs(struct predica
 	}
     }
 
-  complete_pending_execdirs(p->pred_right);
+  complete_pending_execdirs(p->pred_right, dirfd);
 }
 
 
@@ -346,7 +347,7 @@ cleanup(void)
   if (eval_tree)
     {
       complete_pending_execs(eval_tree);
-      complete_pending_execdirs(eval_tree);
+      complete_pending_execdirs(eval_tree, get_current_dirfd());
     }
 }
 
@@ -364,7 +365,7 @@ fallback_stat(const char *name, struct s
     case ENOTDIR:
       if (options.debug_options & DebugStat)
 	fprintf(stderr, "fallback_stat(): stat(%s) failed; falling back on lstat()\n", name);
-      return lstat(name, p);
+      return fstatat(state.cwd_dir_fd, name, p, AT_SYMLINK_NOFOLLOW);
 
     case EACCES:
     case EIO:
@@ -392,14 +393,14 @@ fallback_stat(const char *name, struct s
 int 
 optionh_stat(const char *name, struct stat *p)
 {
+  set_stat_placeholders(p);
   if (0 == state.curdepth) 
     {
       /* This file is from the command line; deference the link (if it
        * is a link).  
        */
       int rv;
-      set_stat_placeholders(p);
-      rv = stat(name, p);
+      rv = fstatat(state.cwd_dir_fd, name, p, 0);
       if (0 == rv)
 	return 0;		/* success */
       else
@@ -409,7 +410,7 @@ optionh_stat(const char *name, struct st
     {
       /* Not a file on the command line; do not dereference the link.
        */
-      return lstat(name, p);
+      return fstatat(state.cwd_dir_fd, name, p, AT_SYMLINK_NOFOLLOW);
     }
 }
 
@@ -422,7 +423,7 @@ optionl_stat(const char *name, struct st
 {
   int rv;
   set_stat_placeholders(p);
-  rv = stat(name, p);
+  rv = fstatat(state.cwd_dir_fd, name, p, 0);
   if (0 == rv)
     return 0;			/* normal case. */
   else
@@ -437,7 +438,7 @@ int 
 optionp_stat(const char *name, struct stat *p)
 {
   set_stat_placeholders(p);
-  return lstat(name, p);
+  return fstatat(state.cwd_dir_fd, name, p, AT_SYMLINK_NOFOLLOW);
 }
 
 
@@ -814,3 +815,13 @@ set_option_defaults(struct options *p)
 
   set_follow_state(SYMLINK_NEVER_DEREF); /* The default is equivalent to -P. */
 }
+
+
+/* get_start_dirfd
+ *
+ * Returns the fd for the directory we started in.
+ */
+int get_start_dirfd(void)
+{
+  return starting_desc;
+}
Index: find/testsuite/find.gnu/execdir-pwd.exp
===================================================================
RCS file: /cvsroot/findutils/findutils/find/testsuite/find.gnu/execdir-pwd.exp,v
retrieving revision 1.2
diff -u -p -r1.2 execdir-pwd.exp
--- find/testsuite/find.gnu/execdir-pwd.exp	22 Jan 2007 10:56:47 -0000	1.2
+++ find/testsuite/find.gnu/execdir-pwd.exp	14 Apr 2007 13:58:42 -0000
@@ -2,11 +2,6 @@
 if { [ safe_path ] } {
     global SKIP_OLD
 
-    # For the moment we expect this test to fail for ftsfind..
-    send_log "This test is expected to fail for ftsfind but not oldfind\n"
-    send_log "The PRMS number refers to http://savannah.gnu.org/bugs/?18466\n"
-    setup_xfail "*-*-*" "18466"
-
     exec rm -rf tmp
     exec mkdir tmp
 
@@ -19,9 +14,7 @@ if { [ safe_path ] } {
     # built up by -execdir in the wrong directory.
 
     set SKIP_OLD 1
-    find_start xp {tmp -name foo -execdir sh ./foo  \{\} + } "" 
+    find_start p {tmp -name foo -execdir sh ./foo  \{\} + } "" 
     set SKIP_OLD 0
     exec rm -rf tmp
-
-    clear_xfail
 }
Index: lib/Makefile.am
===================================================================
RCS file: /cvsroot/findutils/findutils/lib/Makefile.am,v
retrieving revision 1.25
diff -u -p -r1.25 Makefile.am
--- lib/Makefile.am	28 Feb 2007 23:55:47 -0000	1.25
+++ lib/Makefile.am	14 Apr 2007 13:58:42 -0000
@@ -27,8 +27,10 @@ IMPORT_GNULIB_CONFIG = $(top_srcdir)/imp
 INCLUDES = -I../gnulib/lib -I$(top_srcdir)/gnulib/lib 
 LDADD = ../gnulib/lib/libgnulib.a @INTLLIBS@
 
-libfind_a_SOURCES += modetype.h nextelem.h printquoted.h listfile.h regextype.h
-libfind_a_SOURCES += listfile.c nextelem.c extendbuf.c buildcmd.c savedirinfo.c forcefindlib.c qmark.c printquoted.c regextype.c
+libfind_a_SOURCES += modetype.h nextelem.h printquoted.h listfile.h \
+	regextype.h dircallback.h
+libfind_a_SOURCES += listfile.c nextelem.c extendbuf.c buildcmd.c savedirinfo.c \
+	forcefindlib.c qmark.c printquoted.c regextype.c dircallback.c
 
 EXTRA_DIST += waitpid.c forcefindlib.c
 
Index: lib/buildcmd.c
===================================================================
RCS file: /cvsroot/findutils/findutils/lib/buildcmd.c,v
retrieving revision 1.18
diff -u -p -r1.18 buildcmd.c
--- lib/buildcmd.c	25 Feb 2007 12:05:51 -0000	1.18
+++ lib/buildcmd.c	14 Apr 2007 13:58:42 -0000
@@ -89,6 +89,7 @@
 
 #include <xalloc.h>
 #include <error.h>
+#include <openat.h>
 
 #include "buildcmd.h"
 
@@ -181,7 +182,7 @@ bc_do_insert (const struct buildcmd_cont
   *p++ = '\0';
   
   bc_push_arg (ctl, state,
-               insertbuf, p - insertbuf,
+	       insertbuf, p - insertbuf,
                NULL, 0,
                initial_args);
 }
@@ -233,7 +234,9 @@ bc_push_arg (const struct buildcmd_contr
              int initial_args)
 {
   if (!initial_args)
-    state->todo = 1;
+    {
+      state->todo = 1;
+    }
   
   if (arg)
     {
@@ -502,6 +505,7 @@ bc_init_state(const struct buildcmd_cont
   
   state->cmd_argv_chars = state->cmd_initial_argv_chars = 0;
   state->todo = 0;
+  state->dirfd = -1;
   state->usercontext = context;
 }
 
@@ -512,5 +516,6 @@ bc_clear_args(const struct buildcmd_cont
   state->cmd_argc = ctl->initial_argc;
   state->cmd_argv_chars = state->cmd_initial_argv_chars;
   state->todo = 0;
+  state->dirfd = -1;
 }
 
Index: lib/buildcmd.h
===================================================================
RCS file: /cvsroot/findutils/findutils/lib/buildcmd.h,v
retrieving revision 1.8
diff -u -p -r1.8 buildcmd.h
--- lib/buildcmd.h	5 Aug 2006 18:02:54 -0000	1.8
+++ lib/buildcmd.h	14 Apr 2007 13:58:42 -0000
@@ -47,6 +47,9 @@ struct buildcmd_state
 
   /* to-do flag. */
   int todo;
+
+  /* Directory in which to perform the exec. */
+  int dirfd;
 };
 
 struct buildcmd_control
@@ -119,8 +122,8 @@ extern void bc_push_arg (const struct bu
 			 int initial_args);
 
 extern void  bc_init_state(const struct buildcmd_control *ctl,
-					 struct buildcmd_state *state,
-					 void *usercontext);
+			   struct buildcmd_state *state,
+			   void *usercontext);
 extern enum BC_INIT_STATUS bc_init_controlinfo(struct buildcmd_control *ctl);
 extern size_t bc_get_arg_max(void);
 extern void bc_use_sensible_arg_max(struct buildcmd_control *ctl);
Index: lib/listfile.c
===================================================================
RCS file: /cvsroot/findutils/findutils/lib/listfile.c,v
retrieving revision 1.18
diff -u -p -r1.18 listfile.c
--- lib/listfile.c	29 Dec 2006 19:32:10 -0000	1.18
+++ lib/listfile.c	14 Apr 2007 13:58:42 -0000
@@ -47,6 +47,8 @@ char *alloca ();
 #include <grp.h>
 #include <time.h>
 #include <errno.h>
+#include <fcntl.h>
+#include <openat.h>
 #include "human.h"
 #include "xalloc.h"
 #include "pathmax.h"
@@ -85,6 +87,31 @@ extern int errno;
 #define HAVE_MAJOR
 #endif
 
+
+
+
+
+#ifdef HAVE_LOCALE_H
+#include <locale.h>
+#endif
+
+#if ENABLE_NLS
+# include <libintl.h>
+# define _(Text) gettext (Text)
+#else
+# define _(Text) Text
+#define textdomain(Domain)
+#define bindtextdomain(Package, Directory)
+#endif
+#ifdef gettext_noop
+# define N_(String) gettext_noop (String)
+#else
+/* See locate.c for explanation as to why not use (String) */
+# define N_(String) String
+#endif
+
+
+
 #ifdef STAT_MACROS_BROKEN
 #undef S_ISCHR
 #undef S_ISBLK
@@ -202,8 +229,9 @@ file_blocksize(const struct stat *p)
 
 void
 list_file (char *name,
+	   int dirfd, 
 	   char *relname,
-	   struct stat *statp,
+	   const struct stat *statp,
 	   time_t current_time,
 	   int output_block_size,
 	   int literal_control_chars,
@@ -316,7 +344,7 @@ list_file (char *name,
 #ifdef S_ISLNK
   if (S_ISLNK (statp->st_mode))
     {
-      char *linkname = get_link_name (name, relname);
+      char *linkname = get_link_name_at (name, dirfd, relname);
 
       if (linkname)
 	{
@@ -396,7 +424,7 @@ static void print_name (register char *p
 }
 
 #ifdef S_ISLNK
-char *
+static char *
 get_link_name (const char *name, char *relname)
 {
   register char *linkname;
@@ -417,4 +445,34 @@ get_link_name (const char *name, char *r
   linkname[linklen] = '\0';
   return linkname;
 }
+
+struct link_name_args
+{
+  const char *name;
+  char *relname;
+  char *result;
+};
+
+static int
+get_link_name_cb(void *context)
+{
+  struct link_name_args *args = context;
+  args->result = get_link_name(args->name, args->relname);
+  return 0;
+}
+
+char *
+get_link_name_at (const char *name, int dirfd, char *relname)
+{
+  struct link_name_args args;
+  args.result = NULL;
+  args.name = name;
+  args.relname = relname;
+  if (0 == run_in_dir(dirfd, get_link_name_cb, &args))
+    return args.result;
+  else
+    return NULL;
+}
+
+
 #endif
Index: lib/listfile.h
===================================================================
RCS file: /cvsroot/findutils/findutils/lib/listfile.h,v
retrieving revision 1.4
diff -u -p -r1.4 listfile.h
--- lib/listfile.h	29 Dec 2006 19:32:10 -0000	1.4
+++ lib/listfile.h	14 Apr 2007 13:58:42 -0000
@@ -21,9 +21,9 @@
 #if !defined LISTFILE_H
 # define LISTFILE_H
 
-void list_file (char *name, char *relname, struct stat *statp, time_t current_time, int output_block_size, int literal_control_chars, FILE *stream);
+void list_file (char *name, int dir_fd, char *relname, const struct stat *statp, time_t current_time, int output_block_size, int literal_control_chars, FILE *stream);
 
-char * get_link_name (const char *name, char *relname);
+char * get_link_name_at (const char *name, int dirfd, char *relname);
 
 size_t file_blocksize(const struct stat *p);
 
