On Sat, Aug 26, 2017 at 04:00:11PM -0700, Pádraig Brady wrote:
> If we were to provide this I think chdir is a fine name.
> It's a pity we have to have this heavy weight chaining of execs like this
> to set each process attribute. This is more general though, than adding
> --chdir to other "context" type programs like chroot etc.
> Note GNU chroot has a --skip-chdir which nearly does what you want.
> I.E. you could os.chdir("/chroot/build") before calling "chroot --skip-dir 
> ...",
> however that's currently restricted for greater protection to when
> you're not actually changing the root directory.  I suppose we could relax
> the check to verify that getcwd() was _below_ the specified chroot path.
> Of course none of this helps with jexec, lxc etc.

Indeed; I really want something composable rather than playing
whack-a-mole with every possible wrapper command.

> Another option to consider is to add --chdir to env(1) since they
> would often be used together and could be done more efficiently with a single 
> exec?

That's a good idea, and I agree they're often used together.  I'd
already slipped and implemented chdir(1), but I think that suggestion
may be better: it results in a much shorter patch, and it avoids the
problem with some shells (e.g. zsh) implementing their own "chdir"
built-ins with different semantics.

I've attached both patches; my preference following Pádraig's suggestion
is the second one ("env: add --chdir option"), but I'd be happy either
way.

If either of these requires a copyright assignment and I don't have one
on file for coreutils, then I'd appreciate it if a maintainer could get
the ball rolling on the necessary paperwork.

Thanks,

-- 
Colin Watson                                       [cjwat...@debian.org]
>From ebe4e313994059ef0c3b98c1c86b0137538e9604 Mon Sep 17 00:00:00 2001
From: Colin Watson <cjwat...@debian.org>
Date: Sun, 27 Aug 2017 00:14:06 +0100
Subject: [PATCH] chdir: new command to run command with changed CWD

* AUTHORS: Add my name.
* NEWS: Add new binary.
* README: Likewise.
* build-aux/gen-lists-of-programs.sh: Likewise.
* scripts/git-hooks/commit-msg: Likewise.
* doc/coreutils.texi (chdir invocation): Document the new program.
* man/chdir.x: New template.
* man/local.mk: Reference new template.
* po/POTFILES.in: Add src/chdir.c.
* src/.gitignore: Ignore chdir.
* src/chdir.c: New file.
* src/local.mk (src_chdir_LDADD): New variable.
* tests/misc/chdir.sh: New file.
* tests/local.mk (all_tests): Add tests/misc/chdir.sh.
* tests/misc/help-version.sh: Handle expected chdir exit code and setup
requirements.
* tests/misc/invalid-opt.pl: Handle expected chdir exit code.
---
 AUTHORS                            |   1 +
 NEWS                               |   4 ++
 README                             |   6 +-
 build-aux/gen-lists-of-programs.sh |   1 +
 doc/coreutils.texi                 |  63 ++++++++++++++++--
 man/chdir.x                        |   6 ++
 man/local.mk                       |   1 +
 po/POTFILES.in                     |   1 +
 scripts/git-hooks/commit-msg       |   6 +-
 src/.gitignore                     |   1 +
 src/chdir.c                        | 132 +++++++++++++++++++++++++++++++++++++
 src/local.mk                       |   1 +
 tests/local.mk                     |   1 +
 tests/misc/chdir.sh                |  35 ++++++++++
 tests/misc/help-version.sh         |   2 +
 tests/misc/invalid-opt.pl          |   1 +
 16 files changed, 252 insertions(+), 10 deletions(-)
 create mode 100644 man/chdir.x
 create mode 100644 src/chdir.c
 create mode 100644 tests/misc/chdir.sh

diff --git a/AUTHORS b/AUTHORS
index 93d547b..fbf92b8 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -8,6 +8,7 @@ base64: Simon Josefsson
 basename: David MacKenzie
 cat: Torbjorn Granlund, Richard M. Stallman
 chcon: Russell Coker, Jim Meyering
+chdir: Colin Watson
 chgrp: David MacKenzie, Jim Meyering
 chmod: David MacKenzie, Jim Meyering
 chown: David MacKenzie, Jim Meyering
diff --git a/NEWS b/NEWS
index d37195e..c7b49e6 100644
--- a/NEWS
+++ b/NEWS
@@ -75,6 +75,10 @@ GNU coreutils NEWS                                    -*- outline -*-
   expr now returns number of characters matched (instead of incorrect
   number of bytes matched) with 'match'/':' operators on multibyte strings.
 
+** New programs
+
+  chdir: Run command or shell with changed working directory.
+
 ** New features
 
   expand and unexpand now support specifying an offset for tab stops
diff --git a/README b/README
index 5cb90d3..5cfd228 100644
--- a/README
+++ b/README
@@ -7,9 +7,9 @@ arbitrary limits.
 
 The programs that can be built with this package are:
 
-  [ arch b2sum base32 base64 basename cat chcon chgrp chmod chown chroot cksum
-  comm coreutils cp csplit cut date dd df dir dircolors dirname du echo env
-  expand expr factor false fmt fold groups head hostid hostname id install
+  [ arch b2sum base32 base64 basename cat chcon chdir chgrp chmod chown chroot
+  cksum comm coreutils cp csplit cut date dd df dir dircolors dirname du echo
+  env expand expr factor false fmt fold groups head hostid hostname id install
   join kill link ln logname ls md5sum mkdir mkfifo mknod mktemp mv nice nl
   nohup nproc numfmt od paste pathchk pinky pr printenv printf ptx pwd
   readlink realpath rm rmdir runcon seq sha1sum sha224sum sha256sum sha384sum
diff --git a/build-aux/gen-lists-of-programs.sh b/build-aux/gen-lists-of-programs.sh
index cdbcd0a..59ac692 100755
--- a/build-aux/gen-lists-of-programs.sh
+++ b/build-aux/gen-lists-of-programs.sh
@@ -48,6 +48,7 @@ normal_progs='
     basename
     cat
     chcon
+    chdir
     chgrp
     chmod
     chown
diff --git a/doc/coreutils.texi b/doc/coreutils.texi
index 8f1cb4c..8c1d5eb 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -43,6 +43,7 @@
 * basename: (coreutils)basename invocation.     Strip directory and suffix.
 * cat: (coreutils)cat invocation.               Concatenate and write files.
 * chcon: (coreutils)chcon invocation.           Change SELinux CTX of files.
+* chdir: (coreutils)chdir invocation.           Specify the working directory.
 * chgrp: (coreutils)chgrp invocation.           Change file groups.
 * chmod: (coreutils)chmod invocation.           Change access permissions.
 * chown: (coreutils)chown invocation.           Change file owners and groups.
@@ -204,7 +205,7 @@ Free Documentation License''.
 * User information::             id logname whoami groups users who
 * System context::               date arch nproc uname hostname hostid uptime
 * SELinux context::              chcon runcon
-* Modified command invocation::  chroot env nice nohup stdbuf timeout
+* Modified command invocation::  chdir chroot env nice nohup stdbuf timeout
 * Process control::              kill
 * Delaying::                     sleep
 * Numeric operations::           factor numfmt seq
@@ -446,6 +447,7 @@ SELinux context
 
 Modified command invocation
 
+* chdir invocation::             Run a command in a different working directory
 * chroot invocation::            Run a command with a different root directory
 * env invocation::               Run a command in a modified environment
 * nice invocation::              Run a command with modified niceness
@@ -813,9 +815,10 @@ However, some of the programs documented here do produce
 other exit status values and a few associate different
 meanings with the values @samp{0} and @samp{1}.
 Here are some of the exceptions:
-@command{chroot}, @command{env}, @command{expr}, @command{nice},
-@command{nohup}, @command{numfmt}, @command{printenv}, @command{sort},
-@command{stdbuf}, @command{test}, @command{timeout}, @command{tty}.
+@command{chdir}, @command{chroot}, @command{env}, @command{expr},
+@command{nice}, @command{nohup}, @command{numfmt}, @command{printenv},
+@command{sort}, @command{stdbuf}, @command{test}, @command{timeout},
+@command{tty}.
 
 
 @node Backup options
@@ -16648,6 +16651,7 @@ different than the current one: a modified environment, as a different
 user, etc.
 
 @menu
+* chdir invocation::            Modify the working directory.
 * chroot invocation::           Modify the root directory.
 * env invocation::              Modify environment variables.
 * nice invocation::             Modify niceness.
@@ -16657,6 +16661,57 @@ user, etc.
 @end menu
 
 
+@node chdir invocation
+@section @command{chdir}: Run a command in a different working directory
+
+@pindex chdir
+@cindex running a program in a specified working directory
+@cindex working directory, running a program in a specified
+
+@command{chdir} runs a command with a specified working directory.  Synopses:
+
+@example
+chdir @var{option} @var{newdir} [@var{command} [@var{args}]@dots{}]
+chdir @var{option}
+@end example
+
+This command differs from the shell built-in @command{cd} in that it starts
+@var{command} as a subprocess rather than altering the shell's own working
+directory; this allows it to be chained with other commands that run commands
+in a different context.  For example:
+
+@example
+# Run 'true' with /chroot as its root directory and /srv as its working
+# directory.
+chroot /chroot chdir /srv true
+# Run 'true' with /build as its working directory and FOO=bar in its
+# environment.
+chdir /build env FOO=bar true
+@end example
+
+If @var{command} is not specified, the default is the value of the @env{SHELL}
+environment variable or @command{/bin/sh} if not set, invoked with the
+@option{-i} option.
+@var{command} must not be a special built-in utility (@pxref{Special built-in
+utilities}).
+
+@c zsh has chdir as a synonym for cd
+@mayConflictWithShellBuiltIn{chdir}
+
+The only options are @option{--help} and @option{--version}.  @xref{Common
+options}.  Options must precede operands.
+
+@cindex exit status of @command{chdir}
+Exit status:
+
+@display
+125 if @command{chdir} itself fails
+126 if @var{command} is found but cannot be invoked
+127 if @var{command} cannot be found
+the exit status of @var{command} otherwise
+@end display
+
+
 @node chroot invocation
 @section @command{chroot}: Run a command with a different root directory
 
diff --git a/man/chdir.x b/man/chdir.x
new file mode 100644
index 0000000..ee591f0
--- /dev/null
+++ b/man/chdir.x
@@ -0,0 +1,6 @@
+[NAME]
+chdir \- run command or interactive shell with changed working directory
+[DESCRIPTION]
+.\" Add any additional description here
+[SEE ALSO]
+chdir(2)
diff --git a/man/local.mk b/man/local.mk
index 4b98bfd..e7ab51a 100644
--- a/man/local.mk
+++ b/man/local.mk
@@ -66,6 +66,7 @@ man/base64.1:    src/base64$(EXEEXT)
 man/basename.1:  src/basename$(EXEEXT)
 man/cat.1:       src/cat$(EXEEXT)
 man/chcon.1:     src/chcon$(EXEEXT)
+man/chdir.1:     src/chdir$(EXEEXT)
 man/chgrp.1:     src/chgrp$(EXEEXT)
 man/chmod.1:     src/chmod$(EXEEXT)
 man/chown.1:     src/chown$(EXEEXT)
diff --git a/po/POTFILES.in b/po/POTFILES.in
index e4fcdbf..9dd9d93 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -37,6 +37,7 @@ src/base64.c
 src/basename.c
 src/cat.c
 src/chcon.c
+src/chdir.c
 src/chgrp.c
 src/chmod.c
 src/chown-core.c
diff --git a/scripts/git-hooks/commit-msg b/scripts/git-hooks/commit-msg
index 6aedf80..a1d4d44 100755
--- a/scripts/git-hooks/commit-msg
+++ b/scripts/git-hooks/commit-msg
@@ -14,9 +14,9 @@ $editor = "vi" if $? != 0 or $editor =~ /^\s*\z/;
 # Keywords allowed before the colon on the first line of a commit message:
 # program names and a few general category names.
 my @valid = qw(
-    arch b2sum base32 base64 basename cat chcon chgrp chmod chown chroot cksum
-    comm cp csplit cut date dd df dir dircolors dirname du echo env expand
-    expr factor false fmt fold groups head hostid hostname id install
+    arch b2sum base32 base64 basename cat chcon chdir chgrp chmod chown chroot
+    cksum comm cp csplit cut date dd df dir dircolors dirname du echo env
+    expand expr factor false fmt fold groups head hostid hostname id install
     join kill link ln logname ls md5sum mkdir mkfifo mknod mktemp
     mv nice nl nohup nproc numfmt od paste pathchk pinky pr printenv printf
     ptx pwd readlink realpath rm rmdir runcon seq sha1sum sha224sum sha256sum
diff --git a/src/.gitignore b/src/.gitignore
index 70ab2ca..e3c4072 100644
--- a/src/.gitignore
+++ b/src/.gitignore
@@ -8,6 +8,7 @@ base64
 basename
 cat
 chcon
+chdir
 chgrp
 chmod
 chown
diff --git a/src/chdir.c b/src/chdir.c
new file mode 100644
index 0000000..07210cd
--- /dev/null
+++ b/src/chdir.c
@@ -0,0 +1,132 @@
+/* chdir -- run command or shell with changed working directory
+   Copyright (C) 2017 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+/* Written by Colin Watson.  */
+
+#include <config.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <grp.h>
+
+#include "system.h"
+#include "die.h"
+#include "error.h"
+#include "quote.h"
+
+/* The official name of this program (e.g., no 'g' prefix).  */
+#define PROGRAM_NAME "chdir"
+
+#define AUTHORS proper_name ("Colin Watson")
+
+static struct option const long_opts[] =
+{
+  {GETOPT_HELP_OPTION_DECL},
+  {GETOPT_VERSION_OPTION_DECL},
+  {NULL, 0, NULL, 0}
+};
+
+void
+usage (int status)
+{
+  if (status != EXIT_SUCCESS)
+    emit_try_help ();
+  else
+    {
+      printf (_("\
+Usage: %s [OPTION] NEWDIR [COMMAND [ARG]...]\n\
+  or:  %s OPTION\n\
+"), program_name, program_name);
+
+      fputs (_("\
+Run COMMAND with working directory set to NEWDIR.\n\
+\n\
+"), stdout);
+
+      fputs (HELP_OPTION_DESCRIPTION, stdout);
+      fputs (VERSION_OPTION_DESCRIPTION, stdout);
+      fputs (_("\
+\n\
+If no command is given, run '\"$SHELL\" -i' (default: '/bin/sh -i').\n\
+"), stdout);
+      emit_ancillary_info (PROGRAM_NAME);
+    }
+  exit (status);
+}
+
+int
+main (int argc, char **argv)
+{
+  int c;
+
+  initialize_main (&argc, &argv);
+  set_program_name (argv[0]);
+  setlocale (LC_ALL, "");
+  bindtextdomain (PACKAGE, LOCALEDIR);
+  textdomain (PACKAGE);
+
+  initialize_exit_failure (EXIT_CANCELED);
+  atexit (close_stdout);
+
+  while ((c = getopt_long (argc, argv, "+", long_opts, NULL)) != -1)
+    {
+      switch (c)
+        {
+        case_GETOPT_HELP_CHAR;
+
+        case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
+
+        default:
+          usage (EXIT_CANCELED);
+        }
+    }
+
+  if (argc <= optind)
+    {
+      error (0, 0, _("missing operand"));
+      usage (EXIT_CANCELED);
+    }
+
+  char const *newdir = argv[optind];
+
+  if (chdir (newdir) != 0)
+    die (EXIT_CANCELED, errno, _("cannot change directory to %s"),
+         quoteaf (newdir));
+
+  if (argc == optind + 1)
+    {
+      /* No command.  Run an interactive shell.  */
+      char *shell = getenv ("SHELL");
+      if (shell == NULL)
+        shell = bad_cast ("/bin/sh");
+      argv[0] = shell;
+      argv[1] = bad_cast ("-i");
+      argv[2] = NULL;
+    }
+  else
+    {
+      /* The following arguments give the command.  */
+      argv += optind + 1;
+    }
+
+  /* Execute the given command.  */
+  execvp (argv[0], argv);
+
+  int exit_status = errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE;
+  error (0, errno, _("failed to run command %s"), quote (argv[0]));
+  return exit_status;
+}
diff --git a/src/local.mk b/src/local.mk
index 1cb6859..ae27d01 100644
--- a/src/local.mk
+++ b/src/local.mk
@@ -100,6 +100,7 @@ src_base32_LDADD = $(LDADD)
 src_basename_LDADD = $(LDADD)
 src_cat_LDADD = $(LDADD)
 src_chcon_LDADD = $(LDADD)
+src_chdir_LDADD = $(LDADD)
 src_chgrp_LDADD = $(LDADD)
 src_chmod_LDADD = $(LDADD)
 src_chown_LDADD = $(LDADD)
diff --git a/tests/local.mk b/tests/local.mk
index fd4713d..abbbaaa 100644
--- a/tests/local.mk
+++ b/tests/local.mk
@@ -275,6 +275,7 @@ all_tests =					\
   tests/misc/base64.pl				\
   tests/misc/basename.pl			\
   tests/misc/close-stdout.sh			\
+  tests/misc/chdir.sh				\
   tests/misc/chroot-fail.sh			\
   tests/misc/comm.pl				\
   tests/misc/csplit.sh				\
diff --git a/tests/misc/chdir.sh b/tests/misc/chdir.sh
new file mode 100644
index 0000000..0b6bd01
--- /dev/null
+++ b/tests/misc/chdir.sh
@@ -0,0 +1,35 @@
+#!/bin/sh
+# Verify behavior of chdir.
+
+# Copyright (C) 2017 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ chdir pwd
+
+# Verify exact status of internal failure.
+returns_ 125 chdir || fail=1 # missing argument
+returns_ 125 chdir --- / true || fail=1 # unknown option
+
+returns_ 2 chdir / sh -c 'exit 2' || fail=1 # exit status propagation
+returns_ 126 chdir / . || fail=1 # invalid command
+returns_ 127 chdir / no_such || fail=1 # no such command
+
+# Ensure we change directory.
+curdir=$(chdir /bin env pwd) || fail=1
+test "$curdir" = /bin || fail=1
+
+Exit $fail
diff --git a/tests/misc/help-version.sh b/tests/misc/help-version.sh
index b864e8c..77d74a7 100755
--- a/tests/misc/help-version.sh
+++ b/tests/misc/help-version.sh
@@ -22,6 +22,7 @@
 # Terminate any background processes
 cleanup_() { kill $pid 2>/dev/null && wait $pid; }
 
+expected_failure_status_chdir=125
 expected_failure_status_chroot=125
 expected_failure_status_env=125
 expected_failure_status_nice=125
@@ -188,6 +189,7 @@ expr_setup () { args=foo; }
 # Punt, in case GNU 'id' hasn't been installed yet.
 groups_setup () { args=--version; }
 
+chdir_setup () { args="/ true"; }
 pathchk_setup () { args=$tmp_in; }
 yes_setup () { args=--version; }
 logname_setup () { args=--version; }
diff --git a/tests/misc/invalid-opt.pl b/tests/misc/invalid-opt.pl
index cca7811..c7437ed 100755
--- a/tests/misc/invalid-opt.pl
+++ b/tests/misc/invalid-opt.pl
@@ -28,6 +28,7 @@ my %exit_status =
     dir => 2,
     vdir => 2,
     test => 2,
+    chdir => 125,
     chroot => 125,
     echo => 0,
     env => 125,
-- 
2.7.4

>From 0a74257b9fb7e02587b6c5553d8bc1b536249a7e Mon Sep 17 00:00:00 2001
From: Colin Watson <cjwat...@debian.org>
Date: Sun, 27 Aug 2017 08:16:21 +0100
Subject: [PATCH] env: add --chdir option

This is useful when chaining with other commands that run commands in a
different context.

* NEWS (New features): Document the new option.
* doc/coreutils.texi (env invocation): Likewise.
* src/env.c (usage): Likewise.
(main): Implement the new option.
* tests/misc/env.sh: Test the new option.
---
 NEWS               |  3 +++
 doc/coreutils.texi | 17 +++++++++++++++++
 src/env.c          | 20 ++++++++++++++++++++
 tests/misc/env.sh  | 10 +++++++++-
 4 files changed, 49 insertions(+), 1 deletion(-)

diff --git a/NEWS b/NEWS
index d37195e..60d5ca5 100644
--- a/NEWS
+++ b/NEWS
@@ -86,6 +86,9 @@ GNU coreutils NEWS                                    -*- outline -*-
 
   expr supports multibyte strings for all string operations.
 
+  env now has a --chdir option to change to a new working directory before
+  executing the subsidiary program.
+
 ** Improvements
 
   mv --verbose now distinguishes rename and copy operations.
diff --git a/doc/coreutils.texi b/doc/coreutils.texi
index 8f1cb4c..f127517 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -16909,6 +16909,23 @@ environment.
 @opindex --ignore-environment
 Start with an empty environment, ignoring the inherited environment.
 
+@item --chdir=@var{dir}
+@opindex --chdir
+Change the working directory to @var{dir} before invoking @var{command}.
+This differs from the shell built-in @command{cd} in that it starts
+@var{command} as a subprocess rather than altering the shell's own working
+directory; this allows it to be chained with other commands that run commands
+in a different context.  For example:
+
+@example
+# Run 'true' with /chroot as its root directory and /srv as its working
+# directory.
+chroot /chroot env --chdir=/srv true
+# Run 'true' with /build as its working directory, FOO=bar in its
+# environment, and a time limit of five seconds.
+env --chdir=/build FOO=bar timeout 5 true
+@end example
+
 @end table
 
 @cindex exit status of @command{env}
diff --git a/src/env.c b/src/env.c
index 63d5c2c..291fbb4 100644
--- a/src/env.c
+++ b/src/env.c
@@ -33,11 +33,19 @@
   proper_name ("Richard Mlynarik"), \
   proper_name ("David MacKenzie")
 
+/* For long options that have no equivalent short option, use a
+   non-character as a pseudo short option, starting with CHAR_MAX + 1.  */
+enum
+{
+  CHDIR = CHAR_MAX + 1
+};
+
 static struct option const longopts[] =
 {
   {"ignore-environment", no_argument, NULL, 'i'},
   {"null", no_argument, NULL, '0'},
   {"unset", required_argument, NULL, 'u'},
+  {"chdir", required_argument, NULL, CHDIR},
   {GETOPT_HELP_OPTION_DECL},
   {GETOPT_VERSION_OPTION_DECL},
   {NULL, 0, NULL, 0}
@@ -63,6 +71,7 @@ Set each NAME to VALUE in the environment and run COMMAND.\n\
   -i, --ignore-environment  start with an empty environment\n\
   -0, --null           end each output line with NUL, not newline\n\
   -u, --unset=NAME     remove variable from the environment\n\
+      --chdir=DIR      change working directory to DIR\n\
 "), stdout);
       fputs (HELP_OPTION_DESCRIPTION, stdout);
       fputs (VERSION_OPTION_DESCRIPTION, stdout);
@@ -81,6 +90,7 @@ main (int argc, char **argv)
   int optc;
   bool ignore_environment = false;
   bool opt_nul_terminate_output = false;
+  char const *newdir = NULL;
 
   initialize_main (&argc, &argv);
   set_program_name (argv[0]);
@@ -103,6 +113,9 @@ main (int argc, char **argv)
         case '0':
           opt_nul_terminate_output = true;
           break;
+	case CHDIR:
+	  newdir = optarg;
+	  break;
         case_GETOPT_HELP_CHAR;
         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
         default:
@@ -154,6 +167,13 @@ main (int argc, char **argv)
       usage (EXIT_CANCELED);
     }
 
+  if (newdir)
+    {
+      if (chdir (newdir) != 0)
+	die (EXIT_CANCELED, errno, _("cannot change directory to %s"),
+	     quoteaf (newdir));
+    }
+
   execvp (argv[optind], &argv[optind]);
 
   int exit_status = errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE;
diff --git a/tests/misc/env.sh b/tests/misc/env.sh
index f2f6ba8..c409630 100755
--- a/tests/misc/env.sh
+++ b/tests/misc/env.sh
@@ -18,7 +18,7 @@
 
 
 . "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
-print_ver_ env
+print_ver_ env pwd
 
 # A simple shebang program to call "echo" from symlinks like "./-u" or "./--".
 echo "#!$abs_top_builddir/src/echo simple_echo" > simple_echo \
@@ -150,4 +150,12 @@ test "x$(sh -c '\c=d echo fail')" = xpass && #dash 0.5.4 fails so check first
 returns_ 125 env -u a=b true || fail=1
 returns_ 125 env -u '' true || fail=1
 
+# Verify changing directory.
+mkdir empty || framework_failure_
+returns_ 125 env --chdir=empty/nonexistent true || fail=1
+curdir=$(env pwd) || fail=1
+test "$curdir" = "$(pwd)" || fail=1
+curdir=$(env --chdir=empty pwd) || fail=1
+test "$curdir" = "$(cd empty && pwd)" || fail=1
+
 Exit $fail
-- 
2.7.4

Reply via email to