WARNING: This server is unstable and will be retired in the next days. If you want to keep this forum available, please request immediately a migration on the Nabble Support forum. Forums that don't receive any migration request will be deleted forever.

 « Return to Thread: backport of patches for upstream bugs #19593 and #27563

backport of patches for upstream bugs #19593 and #27563

by Bugzilla from kdudka@redhat.com :: Rate this Message:

| View in Thread

Hi James,

On Fri May 7 2010 10:41:03 James Youngman wrote:
> If you'd like to supply a backported "format-patch" patch against the
> 4.4.x tree, I can apply that too.

the patches are attached.  Thanks in advance for considering them!

Kamil

[0001-Add-a-test-which-checks-CWD-for-find-execdir.patch]

From 7bd13a0685ab119ea5a5cd8da890a1656877900e Mon Sep 17 00:00:00 2001
From: Kamil Dudka <kdudka@...>
Date: Mon, 10 May 2010 15:36:08 +0200
Subject: [PATCH 1/5] Add a test which checks $CWD for find -execdir {} +/;

* find/testsuite/find.gnu/execdir-multiple.exp: New test; verifies
that for -execdir +, all the execs occur with the correct working
directory.
* find/testsuite/find.gnu/execdir-multiple.xo: Expected output for
this test.
* find/testsuite/Makefile.am (EXTRA_DIST_EXP): Add the new test.
(EXTRA_DIST_XO): Add the expected output file.
* find/testsuite/config/unix.exp (mkdir): Create proc "mkdir"
which creates a directory.
* find/testsuite/find.gnu/execdir-pwd1.exp: New test.
* find/testsuite/Makefile.am (EXTRA_DIST_EXP): Add
execdir-pwd1.exp.

Signed-off-by: Kamil Dudka <kdudka@...>
---
 ChangeLog                                    |   16 +++++++
 find/testsuite/Makefile.am                   |    3 +
 find/testsuite/config/unix.exp               |    9 ++++
 find/testsuite/find.gnu/execdir-multiple.exp |   55 ++++++++++++++++++++++++++
 find/testsuite/find.gnu/execdir-multiple.xo  |   24 +++++++++++
 find/testsuite/find.gnu/execdir-pwd1.exp     |   20 +++++++++
 6 files changed, 127 insertions(+), 0 deletions(-)
 create mode 100644 find/testsuite/find.gnu/execdir-multiple.exp
 create mode 100644 find/testsuite/find.gnu/execdir-multiple.xo
 create mode 100644 find/testsuite/find.gnu/execdir-pwd1.exp

diff --git a/ChangeLog b/ChangeLog
index 3c9f62e..460ec1c 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,19 @@
+2010-04-10  James Youngman  <jay@...>
+
+ Add a test which checks $CWD for find -execdir {} +/;
+ * find/testsuite/find.gnu/execdir-multiple.exp: New test; verifies
+ that for -execdir +, all the execs occur with the correct working
+ directory.
+ * find/testsuite/find.gnu/execdir-multiple.xo: Expected output for
+ this test.
+ * find/testsuite/Makefile.am (EXTRA_DIST_EXP): Add the new test.
+ (EXTRA_DIST_XO): Add the expected output file.
+ * find/testsuite/config/unix.exp (mkdir): Create proc "mkdir"
+ which creates a directory.
+ * find/testsuite/find.gnu/execdir-pwd1.exp: New test.
+ * find/testsuite/Makefile.am (EXTRA_DIST_EXP): Add
+ execdir-pwd1.exp.
+
 2010-04-06  James Youngman  <jay@...>
 
  * po/nl.po: Updated Dutch translation.
diff --git a/find/testsuite/Makefile.am b/find/testsuite/Makefile.am
index dca30e6..1447132 100644
--- a/find/testsuite/Makefile.am
+++ b/find/testsuite/Makefile.am
@@ -14,6 +14,7 @@ find.gnu/depth.xo \
 find.gnu/depth-d.xo \
 find.gnu/empty.xo \
 find.gnu/execdir-hier.xo   \
+find.gnu/execdir-multiple.xo \
 find.gnu/execdir-one.xo   \
 find.gnu/execdir-root-only.xo   \
 find.gnu/exec-many-rtn-failure.xo   \
@@ -114,8 +115,10 @@ find.gnu/depth-d.exp \
 find.gnu/empty.exp \
 find.gnu/execdir-hier.exp \
 find.gnu/execdir-in-unreadable.exp \
+find.gnu/execdir-multiple.exp \
 find.gnu/execdir-one.exp \
 find.gnu/execdir-pwd.exp \
+find.gnu/execdir-pwd1.exp \
 find.gnu/execdir-root-only.exp \
 find.gnu/exec-many-rtn-failure.exp  \
 find.gnu/exec-many-rtn-success.exp  \
diff --git a/find/testsuite/config/unix.exp b/find/testsuite/config/unix.exp
index 087aeb2..968eb9d 100644
--- a/find/testsuite/config/unix.exp
+++ b/find/testsuite/config/unix.exp
@@ -259,6 +259,15 @@ proc touch args {
     }
 }
 
+proc mkdir { dirname } {
+    # Not all versions of Tcl offer 'file mkdir'.
+    set failed [ catch "file mkdir $dirname" result ]
+    if $failed {
+ # Fall back on the external command.
+ send_log "file mkdir does not work, falling back on exec mkdir\n"
+ exec mkdir "$dirname"
+    }
+}
 
 
 proc safe_path [ ] {
diff --git a/find/testsuite/find.gnu/execdir-multiple.exp b/find/testsuite/find.gnu/execdir-multiple.exp
new file mode 100644
index 0000000..6d4bd66
--- /dev/null
+++ b/find/testsuite/find.gnu/execdir-multiple.exp
@@ -0,0 +1,55 @@
+# tests for -execdir ... \+
+
+# Create 4 empty files in each of 6 directories.
+# Also create a shell script in each of those 6 directories.
+# Run a find command which runs the shell script for each empty file.
+# Check to make sure that each file is mentioned exactly once, and that
+# the command was run with the correct working directory.
+#
+# The output is a sequence of lines of this form:
+#
+# cwd ./basename
+#
+# cmd is the basename of the current directory at the time the command
+# is run by -execidr.   ./basename is the name of the file that was matched
+# (that is, it's the value passed in {}).
+
+# $body is the body of a shell script we use for testing.
+# It prints a series of lines of the form described above.
+# One line is printed for each command-line argument.
+set body {#! /bin/sh
+set -e
+here=`pwd`
+d=`basename $here`
+
+for arg;
+do
+  echo "$d" "$arg"
+done | LC_ALL=C sort
+}
+
+
+if { [ safe_path ] } {
+    global SKIP_OLD
+
+    exec rm -rf tmp
+    mkdir tmp
+
+    # Put a copy of our shell script in each
+    # directory, plus some files.
+    foreach dir { a b c d e f } {
+ mkdir "tmp/$dir"
+ set script_name "tmp/$dir/runme"
+ set f [open "$script_name" "w" 0700 ]
+ puts $f "$body"
+ close $f
+ foreach item { one two three four } {
+    touch "tmp/$dir/$item"
+ }
+    }
+
+    set SKIP_OLD 1
+    find_start p {tmp -type f -empty -execdir sh ./runme  \{\} + } ""
+    set SKIP_OLD 0
+    exec rm -rf tmp
+}
diff --git a/find/testsuite/find.gnu/execdir-multiple.xo b/find/testsuite/find.gnu/execdir-multiple.xo
new file mode 100644
index 0000000..a4f93d9
--- /dev/null
+++ b/find/testsuite/find.gnu/execdir-multiple.xo
@@ -0,0 +1,24 @@
+a ./one
+a ./two
+a ./three
+a ./four
+b ./one
+b ./two
+b ./three
+b ./four
+c ./one
+c ./two
+c ./three
+c ./four
+d ./one
+d ./two
+d ./three
+d ./four
+e ./one
+e ./two
+e ./three
+e ./four
+f ./one
+f ./two
+f ./three
+f ./four
diff --git a/find/testsuite/find.gnu/execdir-pwd1.exp b/find/testsuite/find.gnu/execdir-pwd1.exp
new file mode 100644
index 0000000..e9863ac
--- /dev/null
+++ b/find/testsuite/find.gnu/execdir-pwd1.exp
@@ -0,0 +1,20 @@
+# tests for working directory of -execdir {} \;
+if { [ safe_path ] } {
+    global SKIP_OLD
+
+    exec rm -rf tmp
+    exec mkdir tmp
+
+    # Create an empty shell script.
+    exec touch    tmp/foo
+    exec chmod +x tmp/foo
+
+    # The -execdir should find the "foo" in the current directory.
+    # If not, the find command is probably executing the command
+    # built up by -execdir in the wrong directory.
+
+    set SKIP_OLD 1
+    find_start p {tmp -name foo -execdir sh ./foo  \{\} \; } ""
+    set SKIP_OLD 0
+    exec rm -rf tmp
+}
--
1.6.6.1



[0003-Fix-Savannah-bug-19593-execdir-.-has-suboptimal-perf.patch]

From 1758fea3ee7112c69ca8ea264c5f8ca9d5879473 Mon Sep 17 00:00:00 2001
From: Kamil Dudka <kdudka@...>
Date: Mon, 10 May 2010 16:15:17 +0200
Subject: [PATCH 3/5] Fix Savannah bug #19593, -execdir .... {} + has suboptimal performance

* find/ftsfind.c (consider_visiting): Don't call
complete_pending_execdirs for every file we visit.
(find): Instead, call complete_pending_execdirs every time we
see a file which isn't at the same nesting level as the previous
file we saw.  This is an improvement but not optimal (since
descending into a subdirectory will cause us to issue an exec
before we've finished with the current directory).
* NEWS: Mention this change.

Signed-off-by: Kamil Dudka <kdudka@...>
---
 ChangeLog      |   10 ++++++++++
 NEWS           |   13 +++++++++++++
 find/ftsfind.c |   37 +++++++++++++++++++------------------
 3 files changed, 42 insertions(+), 18 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 8a8c7a8..1c55b65 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,15 @@
 2010-04-10  James Youngman  <jay@...>
 
+ Fix Savannah bug #19593, -execdir .... {} + has suboptimal performance
+ * find/ftsfind.c (consider_visiting): Don't call
+ complete_pending_execdirs for every file we visit.
+ (find): Instead, call complete_pending_execdirs every time we
+ see a file which isn't at the same nesting level as the previous
+ file we saw.  This is an improvement but not optimal (since
+ descending into a subdirectory will cause us to issue an exec
+ before we've finished with the current directory).
+ * NEWS: Mention this change.
+
         Exec predicates now store which directory they want to run in.
         * lib/dircallback.c (run_in_dirfd): New name for old run_in_dir
         function.
diff --git a/NEWS b/NEWS
index 853fade..ce0ad6e 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,16 @@
+** Bug Fixes
+
+#19593: -execdir .... {} + has suboptimal performance (see below)
+
+** Performance changes
+
+The find program will once again build argument lists longer than 1
+with "-execdir ...+".  The upper limit of 1 argument for execdir was
+introduced as a workaround in findutils-4.3.4.   The limit is now
+removed, but find still does not issue the maximum possible number of
+arguments, since an exec will occur each time find encounters a
+subdirectory (if at least one argument is pending).
+
 GNU findutils NEWS - User visible changes. -*- outline -*- (allout)
 
 * Major changes in release 4.4.3-git, YYYY-MM-DD
diff --git a/find/ftsfind.c b/find/ftsfind.c
index cc54c9f..26392ce 100644
--- a/find/ftsfind.c
+++ b/find/ftsfind.c
@@ -518,24 +518,6 @@ consider_visiting(FTS *p, FTSENT *ent)
       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);
-      complete_pending_execdirs ();
-    }
-
   if (ent->fts_info == FTS_DP)
     {
       /* we're leaving a directory. */
@@ -585,8 +567,27 @@ find(char *arg)
     }
   else
     {
+      int level = INT_MIN;
+
       while ( (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.have_stat = false;
   state.have_type = false;
   state.type = 0;
--
1.6.6.1



[0002-Exec-predicates-now-store-which-directory-they-want-.patch]

From 93e76f306931097a28e2a5fe81bef03204d689d3 Mon Sep 17 00:00:00 2001
From: Kamil Dudka <kdudka@...>
Date: Mon, 10 May 2010 16:11:41 +0200
Subject: [PATCH 2/5] Exec predicates now store which directory they want to run in.

* lib/dircallback.c (run_in_dirfd): New name for old run_in_dir
function.
(run_in_dir): Like the old function of the same name, but now
takes an argument const struct saved_cwd *.
* lib/dircallback.h: Update declarations of run_in_dirfd and
run_in_dir.
* find/util.c: Include dircallback.h, xalloc.h, save-cwd.h.
(do_complete_pending_execdirs): Remove dir_fd parameter, since the
per-predicate data structures now indicate what directory they
need to be run in.  Instead of calling bc_do_exec directly, use a
callback 'exec_cb' that uses run_in_dir (which now takes a
saved_cwd* parameter instead of a file descriptor).
(do_exec): Called by do_complete_pending_execdirs, and simply uses
run_in_dir to call exec_cb, restoring the working directory
afterward.
(record_initial_cwd): New function, initialises the global
variable initial_wd.
(cleanup_initial_cwd): New function, cleans up the global variable
initial_wd.
(cleanup): Call cleanup_initial_cwd.
(get_start_dirfd): Remove.
(is_exec_in_local_dir): New function; true for predicates -execdir
and -okdir.
* find/pred.c: Include save-cwd.h.
(record_exec_dir): New function, sets the value of
execp->wd_for_exec if needed.
(new_impl_pred_exec): Remove the obsolete dir_fd parameter.  Call
record_exec_dir.
(pred_exec): Don't pass the dir_fd parameter.
(pred_execdir): Likewise.
(pred_ok): Likewise.
(pred_okdir): Likewise.
(can_access): Call run_in_dirfd rather than run_in_dir (the
function was renamed).
(prep_child_for_exec): Remove dir_fd parameter; don't fchdir to
that.  Call restore_cwd instead (passing a saved_cwd* parameter
which replaced dir_fd).
(launch): Remove references to execp->use_current_dir.
(launch): Change references to execp->dir_fd to execp->wd_for_exec.
* find/parser.c: Correct indentiation of declaration of
insert_exec_ok and remove the obsolete dir_fd parameter.
(parse_exec): Don't pass the dir_fd parameter to insert_exec_ok.
(parse_execdir): Likewise.
(parse_ok): Likewise.
(parse_okdir): Likewise.
(insert_exec_ok): Remove obsolete dir_fd paramter.  Initialise
execp->wd_for_exec, either to NULL (for -*dir) or to the
initial_wd.
* find/ftsfind.c: Remove get_current_dirfd.  Remove
complete_execdirs_cb.
(consider_visiting): Call complete_pending_execdirs directly.
(main): Call record_initial_cwd to record the initial working
directory, early on.  Don't initialise starting_dir or
starting_desc, they have been removed.
* find/finddata.c: Include save-cwd.h.  Remove starting_dir and
starting_desc. Add new global variable initial_wd.  It is a struct
saved_wd* and represents find's initial working directory.
* find/find.c: Include save-cwd.h.
(main): Call record_initial_cwd in order to initialise the
global variable initial_wd  Don't set starting_desc and
starting_dir, since those variables have been removed.
(safely_chdir): Don't pass an fd to complete_pending_execdirs.
(chdir_back): Remove the safety check (since we are using fchdir
and in any case no longer have all the data that the existing
wd_sanity_check function wants).
(do_process_top_dir): Don't pass an fd to
complete_pending_execdirs.
(process_dir): Likewise.
* find/defs.h (struct exec_val): Remove use_current_dir and
dir_fd.  Replace with wd_for_exec, which is a struct saved_wd*.
(get_start_dirfd): Remove prototype.
(get_current_dirfd): Remove prototype.
(complete_pending_execdirs): No longer takes dir_fd parameter.
(record_initial_cwd): Add prototype.
(is_exec_in_local_dir): Add prototype.
(options): Declare.
(initial_wd): Add declaration.  It is a struct saved_wd* and
represents find's initial working directory.
(starting_dir): Remove declaration of global variable.
(starting_desc): Remove declaration of global variable.
* import-gnulib.config (modules): Import module save-cwd.

Signed-off-by: Kamil Dudka <kdudka@...>
---
 ChangeLog            |   85 ++++++++++++++++++++++++++++++++++++++++++++++
 find/defs.h          |   14 ++++----
 find/find.c          |   69 +++++--------------------------------
 find/finddata.c      |   12 +-----
 find/ftsfind.c       |   49 ++-------------------------
 find/parser.c        |   35 ++++++------------
 find/pred.c          |   92 +++++++++++++++++++++++++++++++++-----------------
 find/util.c          |   89 +++++++++++++++++++++++++++++++++++++++---------
 import-gnulib.config |    1 +
 lib/dircallback.c    |   36 +++++++++++++++++++-
 lib/dircallback.h    |    5 ++-
 lib/listfile.c       |    2 +-
 12 files changed, 293 insertions(+), 196 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 460ec1c..8a8c7a8 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,90 @@
 2010-04-10  James Youngman  <jay@...>
 
+        Exec predicates now store which directory they want to run in.
+        * lib/dircallback.c (run_in_dirfd): New name for old run_in_dir
+        function.
+        (run_in_dir): Like the old function of the same name, but now
+        takes an argument const struct saved_cwd *.
+        * lib/dircallback.h: Update declarations of run_in_dirfd and
+        run_in_dir.
+        * find/util.c: Include dircallback.h, xalloc.h, save-cwd.h.
+        (do_complete_pending_execdirs): Remove dir_fd parameter, since the
+        per-predicate data structures now indicate what directory they
+        need to be run in.  Instead of calling bc_do_exec directly, use a
+        callback 'exec_cb' that uses run_in_dir (which now takes a
+        saved_cwd* parameter instead of a file descriptor).
+        (do_exec): Called by do_complete_pending_execdirs, and simply uses
+        run_in_dir to call exec_cb, restoring the working directory
+        afterward.
+        (record_initial_cwd): New function, initialises the global
+        variable initial_wd.
+        (cleanup_initial_cwd): New function, cleans up the global variable
+        initial_wd.
+        (cleanup): Call cleanup_initial_cwd.
+        (get_start_dirfd): Remove.
+        (is_exec_in_local_dir): New function; true for predicates -execdir
+        and -okdir.
+        * find/pred.c: Include save-cwd.h.
+        (record_exec_dir): New function, sets the value of
+        execp->wd_for_exec if needed.
+        (new_impl_pred_exec): Remove the obsolete dir_fd parameter.  Call
+        record_exec_dir.
+        (pred_exec): Don't pass the dir_fd parameter.
+        (pred_execdir): Likewise.
+        (pred_ok): Likewise.
+        (pred_okdir): Likewise.
+        (can_access): Call run_in_dirfd rather than run_in_dir (the
+        function was renamed).
+        (prep_child_for_exec): Remove dir_fd parameter; don't fchdir to
+        that.  Call restore_cwd instead (passing a saved_cwd* parameter
+        which replaced dir_fd).
+        (launch): Remove references to execp->use_current_dir.
+        (launch): Change references to execp->dir_fd to execp->wd_for_exec.
+        * find/parser.c: Correct indentiation of declaration of
+        insert_exec_ok and remove the obsolete dir_fd parameter.
+        (parse_exec): Don't pass the dir_fd parameter to insert_exec_ok.
+        (parse_execdir): Likewise.
+        (parse_ok): Likewise.
+        (parse_okdir): Likewise.
+        (insert_exec_ok): Remove obsolete dir_fd paramter.  Initialise
+        execp->wd_for_exec, either to NULL (for -*dir) or to the
+        initial_wd.
+        * find/ftsfind.c: Remove get_current_dirfd.  Remove
+        complete_execdirs_cb.
+        (consider_visiting): Call complete_pending_execdirs directly.
+        (main): Call record_initial_cwd to record the initial working
+        directory, early on.  Don't initialise starting_dir or
+        starting_desc, they have been removed.
+        * find/finddata.c: Include save-cwd.h.  Remove starting_dir and
+        starting_desc. Add new global variable initial_wd.  It is a struct
+        saved_wd* and represents find's initial working directory.
+        * find/find.c: Include save-cwd.h.
+        (main): Call record_initial_cwd in order to initialise the
+        global variable initial_wd  Don't set starting_desc and
+        starting_dir, since those variables have been removed.
+        (safely_chdir): Don't pass an fd to complete_pending_execdirs.
+        (chdir_back): Remove the safety check (since we are using fchdir
+        and in any case no longer have all the data that the existing
+        wd_sanity_check function wants).
+        (do_process_top_dir): Don't pass an fd to
+        complete_pending_execdirs.
+        (process_dir): Likewise.
+        * find/defs.h (struct exec_val): Remove use_current_dir and
+        dir_fd.  Replace with wd_for_exec, which is a struct saved_wd*.
+        (get_start_dirfd): Remove prototype.
+        (get_current_dirfd): Remove prototype.
+        (complete_pending_execdirs): No longer takes dir_fd parameter.
+        (record_initial_cwd): Add prototype.
+        (is_exec_in_local_dir): Add prototype.
+        (options): Declare.
+        (initial_wd): Add declaration.  It is a struct saved_wd* and
+        represents find's initial working directory.
+        (starting_dir): Remove declaration of global variable.
+        (starting_desc): Remove declaration of global variable.
+        * import-gnulib.config (modules): Import module save-cwd.
+
+2010-04-10  James Youngman  <jay@...>
+
  Add a test which checks $CWD for find -execdir {} +/;
  * find/testsuite/find.gnu/execdir-multiple.exp: New test; verifies
  that for -execdir +, all the execs occur with the correct working
diff --git a/find/defs.h b/find/defs.h
index 4539fd9..6115b3c 100644
--- a/find/defs.h
+++ b/find/defs.h
@@ -195,9 +195,8 @@ struct exec_val
   struct buildcmd_state   state;
   char **replace_vec; /* Command arguments (for ";" style) */
   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 dir_fd; /* The directory to do the exec in. */
+  struct saved_cwd *wd_for_exec;/* What directory to perform the exec in. */
 };
 
 /* The format string for a -printf or -fprintf is chopped into one or
@@ -335,8 +334,6 @@ struct predicate
 
 /* find.c, ftsfind.c */
 boolean is_fts_enabled(int *ftsoptions);
-int get_start_dirfd(void);
-int get_current_dirfd(void);
 
 /* find library function declarations.  */
 
@@ -493,8 +490,11 @@ struct predicate *insert_primary_withpred PARAMS((const struct parser_table *ent
 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(int dir_fd); /* Passing dir_fd is an unpleasant CodeSmell. */
+void complete_pending_execdirs(void);
 const char *safely_quote_err_filename (int n, char const *arg);
+void record_initial_cwd (void);
+boolean is_exec_in_local_dir(const PRED_FUNC pred_func);
+
 void fatal_file_error(const char *name) ATTRIBUTE_NORETURN;
 void nonfatal_file_error(const char *name);
 
@@ -654,10 +654,10 @@ struct state
 };
 
 /* finddata.c */
+extern struct options options;
 extern struct state state;
-extern char const *starting_dir;
-extern int starting_desc;
 extern char *program_name;
+extern struct saved_cwd *initial_wd;
 
 
 #endif
diff --git a/find/find.c b/find/find.c
index 171988f..badcf3c 100644
--- a/find/find.c
+++ b/find/find.c
@@ -52,6 +52,7 @@
 #include "quotearg.h"
 #include "xgetcwd.h"
 #include "error.h"
+#include "save-cwd.h"
 
 #ifdef HAVE_LOCALE_H
 #include <locale.h>
@@ -131,6 +132,8 @@ main (int argc, char **argv)
   program_name = argv[0];
   state.exit_status = 0;
 
+  record_initial_cwd ();
+
   /* Set the option defaults before we do the locale
    * initialisation as check_nofollow() needs to be executed in the
    * POSIX locale.
@@ -183,23 +186,6 @@ main (int argc, char **argv)
     }
   
 
-  starting_desc = open (".", O_RDONLY
-#if defined O_LARGEFILE
- |O_LARGEFILE
-#endif
- );
-  if (0 <= starting_desc && fchdir (starting_desc) != 0)
-    {
-      close (starting_desc);
-      starting_desc = -1;
-    }
-
-  if (starting_desc < 0)
-    {
-      starting_dir = xgetcwd ();
-      if (! starting_dir)
- error (1, errno, _("cannot get current directory"));
-    }
   set_stat_placeholders(&starting_stat_buf);
   if ((*options.xstat) (".", &starting_stat_buf) != 0)
     error (1, errno, _("cannot stat current directory"));
@@ -876,7 +862,7 @@ safely_chdir(const char *dest,
    * processed, do them now because they must be done in the same
    * directory.
    */
-  complete_pending_execdirs(get_current_dirfd());
+  complete_pending_execdirs ();
 
 #if !defined(O_NOFOLLOW)
   options.open_nofollow_available = false;
@@ -911,45 +897,10 @@ safely_chdir(const char *dest,
 static void
 chdir_back (void)
 {
-  struct stat stat_buf;
-  boolean dummy;
-  
-  if (starting_desc < 0)
-    {
-      if (options.debug_options & DebugSearch)
- fprintf(stderr, "chdir_back(): chdir(\"%s\")\n", starting_dir);
-      
-#ifdef STAT_MOUNTPOINTS
-      /* We will need the mounted device list.  Get it now if we don't
-       * already have it.
-       */
-      if (NULL == mounted_devices)
- init_mounted_dev_list(1);
-#endif
-      
-      if (chdir (starting_dir) != 0)
- fatal_file_error(starting_dir);
-
-      wd_sanity_check(starting_dir,
-      program_name,
-      starting_dir,
-      starting_stat_buf.st_dev,
-      starting_stat_buf.st_ino,
-      &stat_buf, 0, __LINE__,
-      TraversingUp,
-      FATAL_IF_SANITY_CHECK_FAILS,
-      &dummy);
-    }
-  else
-    {
-      if (options.debug_options & DebugSearch)
- fprintf(stderr, "chdir_back(): chdir(<starting-point>)\n");
+  if (options.debug_options & DebugSearch)
+    fprintf (stderr, "chdir_back(): chdir to start point\n");
 
-      if (fchdir (starting_desc) != 0)
- {
-  fatal_file_error(starting_dir);
- }
-    }
+  restore_cwd (initial_wd);
 }
 
 /* Move to the parent of a given directory and then call a function,
@@ -1038,7 +989,7 @@ static void do_process_top_dir(char *pathname,
   (void) pstat;
   
   process_path (pathname, base, false, ".", mode);
-  complete_pending_execdirs(get_current_dirfd());
+  complete_pending_execdirs ();
 }
 
 static void do_process_predicate(char *pathname,
@@ -1326,7 +1277,7 @@ process_dir (char *pathname, char *name, int pathlen, const struct stat *statp,
        * yet been processed, do them now because they must be done in
        * the same directory.
        */
-      complete_pending_execdirs(get_current_dirfd());
+      complete_pending_execdirs ();
       
       if (strcmp (name, "."))
  {
@@ -1464,7 +1415,7 @@ process_dir (char *pathname, char *name, int pathlen, const struct stat *statp,
        * yet been processed, do them now because they must be done in
        * the same directory.
        */
-      complete_pending_execdirs(get_current_dirfd());
+      complete_pending_execdirs ();
 
       if (strcmp (name, "."))
  {
diff --git a/find/finddata.c b/find/finddata.c
index 373eb38..dcc8261 100644
--- a/find/finddata.c
+++ b/find/finddata.c
@@ -19,6 +19,7 @@
 #include <config.h>
 
 #include "defs.h"
+#include "save-cwd.h"
 
 
 /* Name this program was run with. */
@@ -26,13 +27,4 @@ char *program_name;
 
 struct options options;
 struct state state;
-
-/* The full path of the initial working directory, or "." if
-   STARTING_DESC is nonnegative.  */
-char const *starting_dir = ".";
-
-/* A file descriptor open to the initial working directory.
-   Doing it this way allows us to work when the i.w.d. has
-   unreadable parents.  */
-int starting_desc;
-
+struct saved_cwd *initial_wd = NULL;
diff --git a/find/ftsfind.c b/find/ftsfind.c
index b59d896..cc54c9f 100644
--- a/find/ftsfind.c
+++ b/find/ftsfind.c
@@ -98,24 +98,6 @@ static int ftsoptions = FTS_NOSTAT|FTS_TIGHT_CYCLE_CHECK;
 static int prev_depth = INT_MIN; /* fts_level can be < 0 */
 static int curr_fd = -1;
 
-int get_current_dirfd(void)
-{
-  if (ftsoptions & FTS_CWDFD)
-    {
-      assert (curr_fd != -1);
-      assert ( (AT_FDCWD == curr_fd) || (curr_fd >= 0) );
-      
-      if (AT_FDCWD == curr_fd)
- return starting_desc;
-      else
- return curr_fd;
-    }
-  else
-    {
-      return AT_FDCWD;
-    }
-}
-
 static void left_dir(void)
 {
   if (ftsoptions & FTS_CWDFD)
@@ -324,15 +306,6 @@ symlink_loop(const char *name)
 }
 
   
-static int
-complete_execdirs_cb(void *context)
-{
-  (void) context;
-  /* By the tme this callback is called, the current directory is correct. */
-  complete_pending_execdirs(AT_FDCWD);
-  return 0;
-}
-
 static void
 show_outstanding_execdirs(FILE *fp)
 {
@@ -560,7 +533,7 @@ consider_visiting(FTS *p, FTSENT *ent)
   if (state.execdirs_outstanding)
     {
       show_outstanding_execdirs(stderr);
-      run_in_dir(p->fts_cwd_fd, complete_execdirs_cb, NULL);
+      complete_pending_execdirs ();
     }
 
   if (ent->fts_info == FTS_DP)
@@ -664,6 +637,8 @@ main (int argc, char **argv)
   state.execdirs_outstanding = false;
   state.cwd_dir_fd = AT_FDCWD;
 
+  record_initial_cwd ();
+
   /* Set the option defaults before we do the locale initialisation as
    * check_nofollow() needs to be executed in the POSIX locale.
    */
@@ -713,24 +688,6 @@ main (int argc, char **argv)
     }
   
 
-  starting_desc = open (".", O_RDONLY
-#if defined O_LARGEFILE
- |O_LARGEFILE
-#endif
- );
-  if (0 <= starting_desc && fchdir (starting_desc) != 0)
-    {
-      close (starting_desc);
-      starting_desc = -1;
-    }
-  if (starting_desc < 0)
-    {
-      starting_dir = xgetcwd ();
-      if (! starting_dir)
- error (1, errno, _("cannot get current directory"));
-    }
-
-
   process_all_startpoints(argc-end_of_leading_options, argv+end_of_leading_options);
   
   /* If "-exec ... {} +" has been used, there may be some
diff --git a/find/parser.c b/find/parser.c
index 08758ee..4344c56 100644
--- a/find/parser.c
+++ b/find/parser.c
@@ -177,7 +177,6 @@ static struct segment **make_segment PARAMS((struct segment **segment,
      struct predicate *pred));
 static boolean insert_exec_ok PARAMS((const char *action,
       const struct parser_table *entry,
-      int dir_fd,
       char *argv[],
       int *arg_ptr));
 static boolean get_comp_type PARAMS((const char **str,
@@ -844,13 +843,13 @@ parse_empty (const struct parser_table* entry, char **argv, int *arg_ptr)
 static boolean
 parse_exec (const struct parser_table* entry, char **argv, int *arg_ptr)
 {
-  return insert_exec_ok ("-exec", entry, get_start_dirfd(), argv, arg_ptr);
+  return insert_exec_ok ("-exec", entry, argv, arg_ptr);
 }
 
 static boolean
 parse_execdir (const struct parser_table* entry, char **argv, int *arg_ptr)
 {
-  return insert_exec_ok ("-execdir", entry, -1, argv, arg_ptr);
+  return insert_exec_ok ("-execdir", entry, argv, arg_ptr);
 }
 
 static boolean
@@ -1734,13 +1733,13 @@ parse_nowarn (const struct parser_table* entry, char **argv, int *arg_ptr)
 static boolean
 parse_ok (const struct parser_table* entry, char **argv, int *arg_ptr)
 {
-  return insert_exec_ok ("-ok", entry, get_start_dirfd(), argv, arg_ptr);
+  return insert_exec_ok ("-ok", entry, argv, arg_ptr);
 }
 
 static boolean
 parse_okdir (const struct parser_table* entry, char **argv, int *arg_ptr)
 {
-  return insert_exec_ok ("-okdir", entry, -1, argv, arg_ptr);
+  return insert_exec_ok ("-okdir", entry, argv, arg_ptr);
 }
 
 boolean
@@ -3083,11 +3082,10 @@ check_path_safety(const char *action, char **argv)
 
 /* handles both exec and ok predicate */
 static boolean
-new_insert_exec_ok (const char *action,
-    const struct parser_table *entry,
-    int dir_fd,
-    char **argv,
-    int *arg_ptr)
+insert_exec_ok (const char *action,
+ const struct parser_table *entry,
+ char **argv,
+ int *arg_ptr)
 {
   int start, end; /* Indexes in ARGV of start & end of cmd. */
   int i; /* Index into cmd args */
@@ -3108,6 +3106,7 @@ new_insert_exec_ok (const char *action,
   our_pred->need_type = our_pred->need_stat = false;
 
   execp = &our_pred->args.exec_vec;
+  execp->wd_for_exec = NULL;
 
   if ((func != pred_okdir) && (func != pred_ok))
     {
@@ -3127,13 +3126,14 @@ new_insert_exec_ok (const char *action,
 
   if ((func == pred_execdir) || (func == pred_okdir))
     {
+      execp->wd_for_exec = NULL;
       options.ignore_readdir_race = false;
       check_path_safety(action, argv);
-      execp->use_current_dir = true;
     }
   else
     {
-      execp->use_current_dir = false;
+      assert (NULL != initial_wd);
+      execp->wd_for_exec = initial_wd;
     }
 
   our_pred->args.exec_vec.multiple = 0;
@@ -3286,17 +3286,6 @@ new_insert_exec_ok (const char *action,
 
 
 
-static boolean
-insert_exec_ok (const char *action,
- const struct parser_table *entry,
- int dir_fd,
- char **argv,
- int *arg_ptr)
-{
-  return new_insert_exec_ok(action, entry, dir_fd, argv, arg_ptr);
-}
-
-
 
 /* Get a timestamp and comparison type.
 
diff --git a/find/pred.c b/find/pred.c
index b1f48a0..3d74f7a 100644
--- a/find/pred.c
+++ b/find/pred.c
@@ -47,6 +47,7 @@
 #include "dircallback.h"
 #include "error.h"
 #include "verify.h"
+#include "save-cwd.h"
 
 #if ENABLE_NLS
 # include <libintl.h>
@@ -499,8 +500,30 @@ pred_empty (const char *pathname, struct stat *stat_buf, struct predicate *pred_
     return (false);
 }
 
+
 static boolean
-new_impl_pred_exec (int dir_fd, const char *pathname,
+record_exec_dir (struct exec_val *execp)
+{
+  if (!execp->wd_for_exec)
+    {
+      /* working directory not already known, so must be a *dir variant,
+ and this must be the first arg we added.   However, this may
+ be -execdir foo {} \; (i.e. not multiple).  */
+      assert (!execp->state.todo);
+
+      /* Record the WD. */
+      execp->wd_for_exec = xmalloc (sizeof (*execp->wd_for_exec));
+      execp->wd_for_exec->name = NULL;
+      execp->wd_for_exec->desc = openat (state.cwd_dir_fd, ".", O_RDONLY);
+      if (execp->wd_for_exec->desc < 0)
+ return false;
+    }
+  return true;
+}
+
+
+static boolean
+new_impl_pred_exec (const char *pathname,
     struct stat *stat_buf,
     struct predicate *pred_ptr,
     const char *prefix, size_t pfxlen)
@@ -509,7 +532,32 @@ new_impl_pred_exec (int dir_fd, const char *pathname,
   size_t len = strlen(pathname);
 
   (void) stat_buf;
-  execp->dir_fd = dir_fd;
+
+  if (is_exec_in_local_dir (pred_ptr->pred_func))
+    {
+      /* 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*/
+ }
+    }
+  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);
+    }
+
   if (execp->multiple)
     {
       /* Push the argument onto the current list.
@@ -555,8 +603,7 @@ new_impl_pred_exec (int dir_fd, const char *pathname,
 boolean
 pred_exec (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr)
 {
-  return new_impl_pred_exec(get_start_dirfd(),
-    pathname, stat_buf, pred_ptr, NULL, 0);
+  return new_impl_pred_exec(pathname, stat_buf, pred_ptr, NULL, 0);
 }
 
 boolean
@@ -564,8 +611,7 @@ pred_execdir (const char *pathname, struct stat *stat_buf, struct predicate *pre
 {
    const char *prefix = (state.rel_pathname[0] == '/') ? NULL : "./";
    (void) &pathname;
-   return new_impl_pred_exec (get_current_dirfd(),
-      state.rel_pathname, stat_buf, pred_ptr,
+   return new_impl_pred_exec (state.rel_pathname, stat_buf, pred_ptr,
       prefix, (prefix ? 2 : 0));
 }
 
@@ -1433,8 +1479,7 @@ boolean
 pred_ok (const 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 (get_start_dirfd(),
-       pathname, stat_buf, pred_ptr, NULL, 0);
+    return new_impl_pred_exec (pathname, stat_buf, pred_ptr, NULL, 0);
   else
     return false;
 }
@@ -1444,8 +1489,7 @@ pred_okdir (const char *pathname, struct stat *stat_buf, struct predicate *pred_
 {
   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 (get_current_dirfd(),
-       state.rel_pathname, stat_buf, pred_ptr,
+    return new_impl_pred_exec (state.rel_pathname, stat_buf, pred_ptr,
        prefix, (prefix ? 2 : 0));
   else
     return false;
@@ -1546,7 +1590,7 @@ can_access(int access_type)
   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);
+  return 0 == run_in_dirfd (state.cwd_dir_fd, access_callback, &args);
 }
 
 
@@ -1865,7 +1909,7 @@ pred_xtype (const char *pathname, struct stat *stat_buf, struct predicate *pred_
 
 
 static boolean
-prep_child_for_exec (boolean close_stdin, int dir_fd)
+prep_child_for_exec (boolean close_stdin, const struct saved_cwd *wd)
 {
   boolean ok = true;
   if (close_stdin)
@@ -1901,17 +1945,10 @@ prep_child_for_exec (boolean close_stdin, int dir_fd)
    * announcement of a call to stat() anyway, as we're about to exec
    * something.
    */
-  if (dir_fd != AT_FDCWD)
+  if (0 != restore_cwd (wd))
     {
-      assert (dir_fd >= 0);
-      if (0 != fchdir(dir_fd))
- {
-  /* If we cannot execute our command in the correct directory,
-   * we should not execute it at all.
-   */
-  error(0, errno, _("Failed to change directory"));
-  ok = false;
- }
+      error (0, errno, _("Failed to change directory"));
+      ok = false;
     }
   return ok;
 }
@@ -1927,13 +1964,6 @@ launch (const struct buildcmd_control *ctl,
   static int first_time = 1;
   const struct exec_val *execp = buildstate->usercontext;
 
-  if (!execp->use_current_dir)
-    {
-      assert (starting_desc >= 0);
-      assert (execp->dir_fd == starting_desc);
-    }
-
-
   /* Null terminate the arg list.  */
   bc_push_arg (ctl, buildstate, (char *) NULL, 0, NULL, 0, false);
 
@@ -1954,8 +1984,8 @@ launch (const struct buildcmd_control *ctl,
   if (child_pid == 0)
     {
       /* We are the child. */
-      assert (starting_desc >= 0);
-      if (!prep_child_for_exec(execp->close_stdin, execp->dir_fd))
+      assert (NULL != execp->wd_for_exec);
+      if (!prep_child_for_exec (execp->close_stdin, execp->wd_for_exec))
  {
   _exit(1);
  }
diff --git a/find/util.c b/find/util.c
index cc9a3eb..56bf488 100644
--- a/find/util.c
+++ b/find/util.c
@@ -36,6 +36,9 @@
 #include "error.h"
 #include "verify.h"
 #include "openat.h"
+#include "dircallback.h"
+#include "xalloc.h"
+#include "save-cwd.h"
 
 #if ENABLE_NLS
 # include <libintl.h>
@@ -290,6 +293,26 @@ check_nofollow(void)
 #endif
 
 
+static int
+exec_cb (void *context)
+{
+  struct exec_val *execp = context;
+  launch (&execp->ctl, &execp->state);
+  return 0;
+}
+
+static void
+do_exec (struct exec_val *execp)
+{
+  run_in_dir (execp->wd_for_exec, exec_cb, execp);
+  if (execp->wd_for_exec != initial_wd)
+    {
+      free_cwd (execp->wd_for_exec);
+      free (execp->wd_for_exec);
+      execp->wd_for_exec = NULL;
+    }
+}
+
 
 /* Examine the predicate list for instances of -execdir or -okdir
  * which have been terminated with '+' (build argument list) rather
@@ -297,14 +320,14 @@ check_nofollow(void)
  * have no effect if there are no arguments waiting).
  */
 static void
-do_complete_pending_execdirs(struct predicate *p, int dir_fd)
+do_complete_pending_execdirs(struct predicate *p)
 {
   if (NULL == p)
     return;
   
   assert (state.execdirs_outstanding);
   
-  do_complete_pending_execdirs(p->pred_left, dir_fd);
+  do_complete_pending_execdirs(p->pred_left);
   
   if (pred_is(p, pred_execdir) || pred_is(p, pred_okdir))
     {
@@ -319,25 +342,24 @@ do_complete_pending_execdirs(struct predicate *p, int dir_fd)
   if (execp->state.todo)
     {
       /* There are not-yet-executed arguments. */
-      launch (&execp->ctl, &execp->state);
+      do_exec (execp);
     }
  }
     }
 
-  do_complete_pending_execdirs(p->pred_right, dir_fd);
+  do_complete_pending_execdirs(p->pred_right);
 }
 
 void
-complete_pending_execdirs(int dir_fd)
+complete_pending_execdirs (void)
 {
   if (state.execdirs_outstanding)
     {
-      do_complete_pending_execdirs(get_eval_tree(), dir_fd);
+      do_complete_pending_execdirs(get_eval_tree());
       state.execdirs_outstanding = false;
     }
 }
 
-
 
 /* Examine the predicate list for instances of -exec which have been
  * terminated with '+' (build argument list) rather than ';' (singles
@@ -373,6 +395,37 @@ complete_pending_execs(struct predicate *p)
 
   complete_pending_execs(p->pred_right);
 }
+
+void
+record_initial_cwd (void)
+{
+  initial_wd = xmalloc (sizeof (*initial_wd));
+  if (0 != save_cwd (initial_wd))
+    {
+      error (EXIT_FAILURE, errno,
+     _("failed to save initial working directory"));
+    }
+}
+
+static void
+cleanup_initial_cwd (void)
+{
+  if (0 == restore_cwd (initial_wd))
+    {
+      free_cwd (initial_wd);
+      free (initial_wd);
+      initial_wd = NULL;
+    }
+  else
+    {
+      /* since we may already be in atexit, die with _exit(). */
+      error (0, errno,
+     _("failed to restore initial working directory"));
+      _exit (EXIT_FAILURE);
+    }
+}
+
+
 
 static void
 traverse_tree(struct predicate *tree,
@@ -432,9 +485,11 @@ cleanup(void)
   if (eval_tree)
     {
       traverse_tree(eval_tree, complete_pending_execs);
-      complete_pending_execdirs(get_current_dirfd());
+      complete_pending_execdirs ();
       traverse_tree(eval_tree, flush_and_close_output_files);
     }
+
+  cleanup_initial_cwd ();
 }
 
 /* Savannah bug #16378 manifests as an assertion failure in pred_type()
@@ -970,15 +1025,6 @@ set_option_defaults(struct options *p)
 }
 
 
-/* get_start_dirfd
- *
- * Returns the fd for the directory we started in.
- */
-int get_start_dirfd(void)
-{
-  return starting_desc;
-}
-
 /* apply_predicate
  *
  */
@@ -1004,6 +1050,15 @@ apply_predicate(const char *pathname, struct stat *stat_buf, struct predicate *p
     }
 }
 
+/* is_exec_in_local_dir
+ *
+ */
+bool
+is_exec_in_local_dir (const PRED_FUNC pred_func)
+{
+  return pred_execdir == pred_func || pred_okdir == pred_func;
+}
+
 
 /* safely_quote_err_filename
  *
diff --git a/import-gnulib.config b/import-gnulib.config
index f2e8998..b1f0851 100644
--- a/import-gnulib.config
+++ b/import-gnulib.config
@@ -67,6 +67,7 @@ quotearg
 realloc
 regex
 rpmatch
+save-cwd
 savedir
 stat-macros
 stat-time
diff --git a/lib/dircallback.c b/lib/dircallback.c
index 5dbf3b3..f96fccc 100644
--- a/lib/dircallback.c
+++ b/lib/dircallback.c
@@ -54,7 +54,41 @@
 
 
 int
-run_in_dir (int dir_fd, int (*callback)(void*), void *usercontext)
+run_in_dir (const struct saved_cwd *there,
+    int (*callback)(void*), void *usercontext)
+{
+  int err = -1;
+  int saved_errno = 0;
+  struct saved_cwd here;
+  if (0 == save_cwd (&here))
+    {
+      if (0 == restore_cwd (there))
+ {
+  err = callback(usercontext);
+  saved_errno = (err < 0 ? errno : 0);
+ }
+      else
+ {
+  openat_restore_fail (errno);
+ }
+
+      if (restore_cwd (&here) != 0)
+ openat_restore_fail (errno);
+
+      free_cwd (&here);
+    }
+  else
+    {
+      openat_save_fail (errno);
+    }
+  if (saved_errno)
+    errno = saved_errno;
+  return err;
+}
+
+
+int
+run_in_dirfd (int dir_fd, int (*callback)(void*), void *usercontext)
 {
   if (dir_fd == AT_FDCWD)
     {
diff --git a/lib/dircallback.h b/lib/dircallback.h
index 41ea282..3234113 100644
--- a/lib/dircallback.h
+++ b/lib/dircallback.h
@@ -19,6 +19,9 @@
 #if !defined DIRCALLBACK_H
 # define DIRCALLBACK_H
 
-int run_in_dir (int dir_fd, int (*callback)(void*), void *usercontext);
+struct saved_cwd;
+
+int run_in_dirfd (int fd, int (*callback)(void*), void *usercontext);
+int run_in_dir (struct saved_cwd*, int (*callback)(void*), void *usercontext);
 
 #endif
diff --git a/lib/listfile.c b/lib/listfile.c
index ca9eae2..b5bee54 100644
--- a/lib/listfile.c
+++ b/lib/listfile.c
@@ -424,7 +424,7 @@ get_link_name_at (const char *name, int dir_fd, char *relname)
   args.result = NULL;
   args.name = name;
   args.relname = relname;
-  if (0 == run_in_dir(dir_fd, get_link_name_cb, &args))
+  if (0 == run_in_dirfd (dir_fd, get_link_name_cb, &args))
     return args.result;
   else
     return NULL;
--
1.6.6.1



[0004-Fix-Savannah-bug-27563-L-breaks-execdir.patch]

From 556abd539c0708d9bc2cb1e25872664c17b1a2aa Mon Sep 17 00:00:00 2001
From: Kamil Dudka <kdudka@...>
Date: Mon, 10 May 2010 16:22:44 +0200
Subject: [PATCH 4/5] Fix Savannah bug #27563, -L breaks -execdir.

* find/pred.c (mdir_name): New function, taken from newer gnulib.
(initialise_wd_for_exec): New function, factoring out part of the body
of record_exec_dir.
(record_exec_dir): If state.rel_pathname contains a /, extract the
directory part and initialise execp->wd_for_exec to point at that
directory.
(impl_pred_exec): Rename new_impl_pred_exec to impl_pred_exec.
Drop the prefix and pfxlen parameters.  Compute the base name of
the target and pass that to the bc_push_arg function instead of
state.rel_pathname.  Deal with state.rel_pathname being an
absolute path (e.g. find / -execdir...).  Introduce a new
variable, result, allowing us to free the buffer used for the base
name in the return path.
(pred_exec): Don't pass the prefix and the prefix length any more.
(pred_execdir): Likewise.
(pred_ok): Likewise.
(pred_okdir): Likewise.

Signed-off-by: Kamil Dudka <kdudka@...>
---
 ChangeLog   |   21 +++++++++
 NEWS        |    2 +
 find/pred.c |  133 ++++++++++++++++++++++++++++++++++++++++++++++++-----------
 3 files changed, 132 insertions(+), 24 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 1c55b65..49743e3 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,24 @@
+2010-04-11  James Youngman  <jay@...>
+
+ Fix Savannah bug #27563, -L breaks -execdir.
+ * find/pred.c (mdir_name): New function, taken from newer gnulib.
+ (initialise_wd_for_exec): New function, factoring out part of the body
+ of record_exec_dir.
+ (record_exec_dir): If state.rel_pathname contains a /, extract the
+ directory part and initialise execp->wd_for_exec to point at that
+ directory.
+ (impl_pred_exec): Rename new_impl_pred_exec to impl_pred_exec.
+ Drop the prefix and pfxlen parameters.  Compute the base name of
+ the target and pass that to the bc_push_arg function instead of
+ state.rel_pathname.  Deal with state.rel_pathname being an
+ absolute path (e.g. find / -execdir...).  Introduce a new
+ variable, result, allowing us to free the buffer used for the base
+ name in the return path.
+ (pred_exec): Don't pass the prefix and the prefix length any more.
+ (pred_execdir): Likewise.
+ (pred_ok): Likewise.
+ (pred_okdir): Likewise.
+
 2010-04-10  James Youngman  <jay@...>
 
  Fix Savannah bug #19593, -execdir .... {} + has suboptimal performance
diff --git a/NEWS b/NEWS
index ce0ad6e..5351995 100644
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,7 @@
 ** Bug Fixes
 
+#27563: -L breaks -execdir
+
 #19593: -execdir .... {} + has suboptimal performance (see below)
 
 ** Performance changes
diff --git a/find/pred.c b/find/pred.c
index 3d74f7a..d98c4dc 100644
--- a/find/pred.c
+++ b/find/pred.c
@@ -501,6 +501,57 @@ pred_empty (const char *pathname, struct stat *stat_buf, struct predicate *pred_
 }
 
 
+/* In general, we can't use the builtin `dirname' function if available,
+   since it has different meanings in different environments.
+   In some environments the builtin `dirname' modifies its argument.
+
+   Return the leading directories part of FILE, allocated with malloc.
+   Works properly even if there are trailing slashes (by effectively
+   ignoring them).  Return NULL on failure.
+
+   If lstat (FILE) would succeed, then { chdir (dir_name (FILE));
+   lstat (base_name (FILE)); } will access the same file.  Likewise,
+   if the sequence { chdir (dir_name (FILE));
+   rename (base_name (FILE), "foo"); } succeeds, you have renamed FILE
+   to "foo" in the same directory FILE was in.  */
+
+static char *
+mdir_name (char const *file)
+{
+  size_t length = dir_len (file);
+  bool append_dot = (length == 0
+                     || (FILE_SYSTEM_DRIVE_PREFIX_CAN_BE_RELATIVE
+                         && length == FILE_SYSTEM_PREFIX_LEN (file)
+                         && file[2] != '\0' && ! ISSLASH (file[2])));
+  char *dir = malloc (length + append_dot + 1);
+  if (!dir)
+    return NULL;
+  memcpy (dir, file, length);
+  if (append_dot)
+    dir[length++] = '.';
+  dir[length] = '\0';
+  return dir;
+}
+
+
+/* Initialise exec->wd_for_exec.
+
+   We save in exec->wd_for_exec the directory whose path relative to
+   cwd_df is dir.
+ */
+static boolean
+initialise_wd_for_exec (struct exec_val *execp, int cwd_fd, const char *dir)
+{
+  execp->wd_for_exec = xmalloc (sizeof (*execp->wd_for_exec));
+  execp->wd_for_exec->name = NULL;
+  execp->wd_for_exec->desc = openat (cwd_fd, dir, O_RDONLY);
+  if (execp->wd_for_exec->desc < 0)
+    return false;
+
+  return true;
+}
+
+
 static boolean
 record_exec_dir (struct exec_val *execp)
 {
@@ -511,29 +562,46 @@ record_exec_dir (struct exec_val *execp)
  be -execdir foo {} \; (i.e. not multiple).  */
       assert (!execp->state.todo);
 
-      /* Record the WD. */
-      execp->wd_for_exec = xmalloc (sizeof (*execp->wd_for_exec));
-      execp->wd_for_exec->name = NULL;
-      execp->wd_for_exec->desc = openat (state.cwd_dir_fd, ".", O_RDONLY);
-      if (execp->wd_for_exec->desc < 0)
- return false;
+      /* Record the WD. If we're using -L or fts chooses to do so for
+ any other reason, state.cwd_dir_fd may in fact not be the
+ directory containing the target file.  When this happens,
+ rel_path will contain directory components (since it is the
+ path from state.cwd_dir_fd to the target file).
+
+ We deal with this by extracting any directory part and using
+ that to adjust what goes into execp->wd_for_exec.
+      */
+      if (strchr (state.rel_pathname, '/'))
+ {
+  char *dir = mdir_name (state.rel_pathname);
+  bool result = initialise_wd_for_exec (execp, state.cwd_dir_fd, dir);
+  free (dir);
+  return result;
+ }
+      else
+ {
+  return initialise_wd_for_exec (execp, state.cwd_dir_fd, ".");
+ }
     }
   return true;
 }
 
 
 static boolean
-new_impl_pred_exec (const char *pathname,
-    struct stat *stat_buf,
-    struct predicate *pred_ptr,
-    const char *prefix, size_t pfxlen)
+impl_pred_exec (const char *pathname,
+ struct stat *stat_buf,
+ struct predicate *pred_ptr)
 {
   struct exec_val *execp = &pred_ptr->args.exec_vec;
-  size_t len = strlen(pathname);
+  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 (is_exec_in_local_dir (pred_ptr->pred_func))
+  if (local)
     {
       /* For -execdir/-okdir predicates, the parser did not fill in
  the wd_for_exec member of sturct exec_val.  So for those
@@ -547,6 +615,18 @@ new_impl_pred_exec (const char *pathname,
  safely_quote_err_filename (0, pathname));
   /*NOTREACHED*/
  }
+      target = base_name (state.rel_pathname);
+      if ('/' == target[0])
+ {
+  /* find / execdir ls -d {} \; */
+  prefix = NULL;
+  pfxlen = 0;
+ }
+      else
+ {
+  prefix = "./";
+  pfxlen = 2u;
+ }
     }
   else
     {
@@ -556,6 +636,9 @@ new_impl_pred_exec (const char *pathname,
  working directory.
       */
       assert (execp->wd_for_exec == initial_wd);
+      target = (char *) pathname;
+      prefix = NULL;
+      pfxlen = 0u;
     }
 
   if (execp->multiple)
@@ -566,7 +649,7 @@ new_impl_pred_exec (const char *pathname,
        */
       bc_push_arg(&execp->ctl,
   &execp->state,
-  pathname, len+1,
+  target, strlen (target)+1,
   prefix, pfxlen,
   0);
 
@@ -576,7 +659,7 @@ new_impl_pred_exec (const char *pathname,
       /* POSIX: If the primary expression is punctuated by a plus
        * sign, the primary shall always evaluate as true
        */
-      return true;
+      result = true;
     }
   else
     {
@@ -589,30 +672,34 @@ new_impl_pred_exec (const char *pathname,
        execp->replace_vec[i],
        strlen(execp->replace_vec[i]),
        prefix, pfxlen,
-       pathname, len,
+       target, strlen (target),
        0);
  }
 
       /* Actually invoke the command. */
-      return  execp->ctl.exec_callback(&execp->ctl,
+      result = execp->ctl.exec_callback(&execp->ctl,
  &execp->state);
     }
+  if (target != pathname)
+    {
+      assert (local);
+      free (target);
+    }
+  return result;
 }
 
 
 boolean
 pred_exec (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr)
 {
-  return new_impl_pred_exec(pathname, stat_buf, pred_ptr, NULL, 0);
+  return impl_pred_exec(pathname, stat_buf, pred_ptr);
 }
 
 boolean
 pred_execdir (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr)
 {
-   const char *prefix = (state.rel_pathname[0] == '/') ? NULL : "./";
    (void) &pathname;
-   return new_impl_pred_exec (state.rel_pathname, stat_buf, pred_ptr,
-      prefix, (prefix ? 2 : 0));
+   return impl_pred_exec (state.rel_pathname, stat_buf, pred_ptr);
 }
 
 boolean
@@ -1479,7 +1566,7 @@ boolean
 pred_ok (const 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 impl_pred_exec (pathname, stat_buf, pred_ptr);
   else
     return false;
 }
@@ -1487,10 +1574,8 @@ pred_ok (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr
 boolean
 pred_okdir (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr)
 {
-  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,
-       prefix, (prefix ? 2 : 0));
+    return impl_pred_exec (state.rel_pathname, stat_buf, pred_ptr);
   else
     return false;
 }
--
1.6.6.1



[0005-Add-a-test-case-for-Savannah-bug-27563-L-breaks-exec.patch]

From d1e9ad40d23b3814f33df8ef62c34784e00e5a9c Mon Sep 17 00:00:00 2001
From: Kamil Dudka <kdudka@...>
Date: Mon, 10 May 2010 16:30:56 +0200
Subject: [PATCH 5/5] Add a test case for Savannah bug 27563 (-L breaks -execdir).

* find/testsuite/Makefile.am (EXTRA_DIST_EXP): Add
find.gnu/sv-bug-27563-execdir.exp and
find.posix/sv-bug-27563-exec.exp.
(EXTRA_DIST_XO): Add find.gnu/sv-bug-27563-execdir.xo and
find.posix/sv-bug-27563-exec.xo.
* find/testsuite/find.gnu/sv-bug-27563-execdir.exp: New test.
* find/testsuite/find.posix/sv-bug-27563-exec.exp: New test.
* find/testsuite/find.gnu/sv-bug-27563-execdir.xo: Expected output.
* find/testsuite/find.posix/sv-bug-27563-exec.xo: Expected output.

Signed-off-by: Kamil Dudka <kdudka@...>
---
 ChangeLog                                        |   11 +++++++++++
 find/testsuite/Makefile.am                       |    4 ++++
 find/testsuite/find.gnu/sv-bug-27563-execdir.exp |    6 ++++++
 find/testsuite/find.gnu/sv-bug-27563-execdir.xo  |    1 +
 find/testsuite/find.posix/sv-bug-27563-exec.exp  |    6 ++++++
 find/testsuite/find.posix/sv-bug-27563-exec.xo   |    1 +
 6 files changed, 29 insertions(+), 0 deletions(-)
 create mode 100644 find/testsuite/find.gnu/sv-bug-27563-execdir.exp
 create mode 100644 find/testsuite/find.gnu/sv-bug-27563-execdir.xo
 create mode 100644 find/testsuite/find.posix/sv-bug-27563-exec.exp
 create mode 100644 find/testsuite/find.posix/sv-bug-27563-exec.xo

diff --git a/ChangeLog b/ChangeLog
index 49743e3..724371a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,16 @@
 2010-04-11  James Youngman  <jay@...>
 
+ Add a test case for Savannah bug 27563 (-L breaks -execdir).
+ * find/testsuite/Makefile.am (EXTRA_DIST_EXP): Add
+ find.gnu/sv-bug-27563-execdir.exp and
+ find.posix/sv-bug-27563-exec.exp.
+ (EXTRA_DIST_XO): Add find.gnu/sv-bug-27563-execdir.xo and
+ find.posix/sv-bug-27563-exec.xo.
+ * find/testsuite/find.gnu/sv-bug-27563-execdir.exp: New test.
+ * find/testsuite/find.posix/sv-bug-27563-exec.exp: New test.
+ * find/testsuite/find.gnu/sv-bug-27563-execdir.xo: Expected output.
+ * find/testsuite/find.posix/sv-bug-27563-exec.xo: Expected output.
+
  Fix Savannah bug #27563, -L breaks -execdir.
  * find/pred.c (mdir_name): New function, taken from newer gnulib.
  (initialise_wd_for_exec): New function, factoring out part of the body
diff --git a/find/testsuite/Makefile.am b/find/testsuite/Makefile.am
index 1447132..2e661d9 100644
--- a/find/testsuite/Makefile.am
+++ b/find/testsuite/Makefile.am
@@ -63,6 +63,7 @@ find.gnu/samefile-same.xo \
 find.gnu/samefile-symlink.xo \
 find.gnu/sv-bug-17782.xo \
 find.gnu/sv-bug-18222.xo \
+find.gnu/sv-bug-27563-execdir.xo \
 find.gnu/true.xo \
 find.gnu/wholename.xo \
 find.gnu/xtype-symlink.xo \
@@ -78,6 +79,7 @@ find.posix/grouping.xo \
 find.posix/links.xo \
 find.posix/sv-bug-11175.xo \
 find.posix/sv-bug-12181.xo \
+find.posix/sv-bug-27563-exec.xo \
 find.posix/depth1.xo \
 find.posix/mtime0.xo \
 find.posix/sizes.xo \
@@ -179,6 +181,7 @@ find.gnu/sv-bug-17490.exp \
 find.gnu/sv-bug-17782.exp \
 find.gnu/sv-bug-18222.exp \
 find.gnu/sv-bug-24169.exp \
+find.gnu/sv-bug-25359-execdir.exp \
 find.gnu/quit.exp \
 find.gnu/used-invarg.exp \
 find.gnu/used-missing.exp \
@@ -199,6 +202,7 @@ find.posix/links.exp \
 find.posix/mtime0.exp \
 find.posix/sv-bug-11175.exp \
 find.posix/sv-bug-12181.exp \
+find.posix/sv-bug-25359-exec.exp \
 find.posix/depth1.exp \
 find.posix/sizes.exp \
 find.posix/name.exp \
diff --git a/find/testsuite/find.gnu/sv-bug-27563-execdir.exp b/find/testsuite/find.gnu/sv-bug-27563-execdir.exp
new file mode 100644
index 0000000..c67fc88
--- /dev/null
+++ b/find/testsuite/find.gnu/sv-bug-27563-execdir.exp
@@ -0,0 +1,6 @@
+# tests for Savannah bug 27563 (result of find -L -exec ls {} \;)
+exec rm -rf tmp
+exec mkdir tmp
+exec touch tmp/yyyy
+find_start p {-L tmp -name yyyy -execdir ls \{\} \; }
+exec rm -rf tmp
diff --git a/find/testsuite/find.gnu/sv-bug-27563-execdir.xo b/find/testsuite/find.gnu/sv-bug-27563-execdir.xo
new file mode 100644
index 0000000..285260b
--- /dev/null
+++ b/find/testsuite/find.gnu/sv-bug-27563-execdir.xo
@@ -0,0 +1 @@
+./yyyy
diff --git a/find/testsuite/find.posix/sv-bug-27563-exec.exp b/find/testsuite/find.posix/sv-bug-27563-exec.exp
new file mode 100644
index 0000000..d18b0c1
--- /dev/null
+++ b/find/testsuite/find.posix/sv-bug-27563-exec.exp
@@ -0,0 +1,6 @@
+# tests for Savannah bug 27563 (result of find -L -exec ls {} \;)
+exec rm -rf tmp
+exec mkdir tmp
+exec touch tmp/yyyy
+find_start p {-L tmp -name yyyy -exec ls \{\} \; }
+exec rm -rf tmp
diff --git a/find/testsuite/find.posix/sv-bug-27563-exec.xo b/find/testsuite/find.posix/sv-bug-27563-exec.xo
new file mode 100644
index 0000000..cd491dd
--- /dev/null
+++ b/find/testsuite/find.posix/sv-bug-27563-exec.xo
@@ -0,0 +1 @@
+tmp/yyyy
--
1.6.6.1



_______________________________________________
Findutils-patches mailing list
Findutils-patches@...
http://lists.gnu.org/mailman/listinfo/findutils-patches

 « Return to Thread: backport of patches for upstream bugs #19593 and #27563