summary refs log tree commit diff
diff options
context:
space:
mode:
authorLaurent Bercot <ska-skaware@skarnet.org>2019-10-09 17:24:15 +0000
committerLaurent Bercot <ska-skaware@skarnet.org>2019-10-09 17:24:15 +0000
commit31694907dde18fc70d7b3e3813e5183d35f0e5db (patch)
tree8a00ba5d8e40180a0c0ac81c26d97506aa8fdde4
parent719337e34b0e3030acab2786c3035bbd3fcceb93 (diff)
downloadexecline-31694907dde18fc70d7b3e3813e5183d35f0e5db.tar.gz
execline-31694907dde18fc70d7b3e3813e5183d35f0e5db.tar.xz
execline-31694907dde18fc70d7b3e3813e5183d35f0e5db.zip
Add --enable-pedantic-posix, posix-cd, prepare for 2.5.3.0
 Also make wait posix-compliant and update doc.
-rw-r--r--.gitignore1
-rw-r--r--INSTALL2
-rw-r--r--NEWS10
-rwxr-xr-xconfigure11
-rw-r--r--doc/cd.html12
-rw-r--r--doc/index.html5
-rw-r--r--doc/posix-cd.html62
-rw-r--r--doc/upgrade.html10
-rw-r--r--doc/wait.html10
-rw-r--r--package/deps.mak3
-rw-r--r--package/info2
-rw-r--r--package/modes1
-rw-r--r--package/targets.mak9
-rw-r--r--src/execline/wait.c54
-rw-r--r--src/posix/deps-exe/posix-cd1
-rw-r--r--src/posix/posix-cd.c155
16 files changed, 324 insertions, 24 deletions
diff --git a/.gitignore b/.gitignore
index 4c9e807..0631586 100644
--- a/.gitignore
+++ b/.gitignore
@@ -42,6 +42,7 @@
 /multisubstitute
 /pipeline
 /piperw
+/posix-cd
 /redirfd
 /runblock
 /shift
diff --git a/INSTALL b/INSTALL
index 0534bbe..4dc76f4 100644
--- a/INSTALL
+++ b/INSTALL
@@ -6,7 +6,7 @@ Build Instructions
 
   - A POSIX-compliant C development environment
   - GNU make version 3.81 or later
-  - skalibs version 2.9.0.0 or later: http://skarnet.org/software/skalibs/
+  - skalibs version 2.9.1.0 or later: http://skarnet.org/software/skalibs/
 
  This software will run on any operating system that implements
 POSIX.1-2008, available at:
diff --git a/NEWS b/NEWS
index 35ce3d4..e0492aa 100644
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,15 @@
 Changelog for execline.
 
+In 2.5.3.0
+----------
+
+ - New configure option: --enable-pedantic-posix. This
+makes the "cd" program a symbolic link to a "posix-cd" program
+which is fully POSIX compliant. This makes distributions unable
+to use the "execline breaks POSIX" pretext to refuse to package
+execline correctly.
+
+
 In 2.5.2.0
 ----------
 
diff --git a/configure b/configure
index 0013e53..ce3939d 100755
--- a/configure
+++ b/configure
@@ -47,6 +47,7 @@ Optional features:
   --enable-absolute-paths       do not rely on PATH to access this package's binaries,
                                   hardcode absolute BINDIR/foobar paths instead [disabled]
   --enable-nsss                 use the nsss library for user information [disabled]
+  --enable-pedantic-posix       use pedantically POSIX-compatible binaries [disabled]
 
 EOF
 exit 0
@@ -152,6 +153,7 @@ allpic=true
 slashpackage=false
 abspath=false
 usensss=false
+pposix=false
 sproot=
 home=
 exthome=
@@ -196,6 +198,9 @@ for arg ; do
     --disable-absolute-paths|--enable-absolute-paths=no) abspath=false ;;
     --enable-nsss|--enable-nsss=yes) usensss=true ;;
     --disable-nsss|--enable-nsss=no) usensss=false ;;
+    --enable-pedantic-posix|--enable-pedantic-posix=yes) pposix=true ;;
+    --disable-pedantic-posix|--enable-pedantic-posix=no) pposix=false ;;
+    --enable-*|--disable-*|--with-*|--without-*|--*dir=*) ;;
     --enable-*|--disable-*|--with-*|--without-*|--*dir=*) ;;
     --host=*|--target=*) target=${arg#*=} ;;
     --build=*) build=${arg#*=} ;;
@@ -455,7 +460,11 @@ else
   echo "LIBNSSS :="
   echo "MAYBEPTHREAD_LIB :="
 fi
-
+if $pposix ; then
+  echo "PEDANTIC_POSIX := 1"
+else
+  echo "PEDANTIC_POSIX :="
+fi
 exec 1>&3 3>&-
 echo "  ... done."
 
diff --git a/doc/cd.html b/doc/cd.html
index c412e5e..fe64c19 100644
--- a/doc/cd.html
+++ b/doc/cd.html
@@ -37,10 +37,14 @@ system call on <em>dir</em>, then execs into <em>prog...</em>.
 
 <h2> Notes </h2>
 
-<p>
-<tt>cd</tt> is a standard shell builtin. Be careful if you want to
-use the <tt>cd</tt> command outside of an <tt>execline</tt> script.
-</p>
+<ul>
+ <li> <tt>cd</tt> is a standard shell builtin. Be careful if you want to
+use the <tt>cd</tt> command outside of an <tt>execline</tt> script </li>
+ <li> When execline has been configured with the <tt>--enable-pedantic-posix</tt>
+option, the <tt>cd</tt> binary is actually a symbolic link to the
+<a href="posix-cd.html">posix-cd</a> binary. </li>
+</ul>
+
 
 </body>
 </html>
diff --git a/doc/index.html b/doc/index.html
index 193f476..0a3643c 100644
--- a/doc/index.html
+++ b/doc/index.html
@@ -51,7 +51,7 @@ shell's syntax, and has no security issues.
  <li> A POSIX-compliant system with a standard C development environment </li>
  <li> GNU make, version 3.81 or later. </li>
  <li> <a href="//skarnet.org/software/skalibs/">skalibs</a> version
-2.9.0.0 or later. It's a build-time requirement. It's also a run-time
+2.9.1.0 or later. It's a build-time requirement. It's also a run-time
 requirement if you link against the shared version of the skalibs
 library. </li>
 </ul>
@@ -66,7 +66,7 @@ library. </li>
 <h3> Download </h3>
 
 <ul>
- <li> The current released version of execline is <a href="execline-2.5.2.0.tar.gz">2.5.2.0</a>. </li>
+ <li> The current released version of execline is <a href="execline-2.5.3.0.tar.gz">2.5.3.0</a>. </li>
  <li> Alternatively, you can checkout a copy of the
 <a href="//git.skarnet.org/cgi-bin/cgit.cgi/execline/">execline
 git repository</a>:
@@ -126,6 +126,7 @@ to your installation: the shebang lines for your system might be something like
 </p>
 <ul>
 <li><a href="cd.html">The <tt>cd</tt> program</a></li>
+<li><a href="posix-cd.html">The <tt>posix-cd</tt> program</a></li>
 <li><a href="umask.html">The <tt>umask</tt> program</a></li>
 <li><a href="emptyenv.html">The <tt>emptyenv</tt> program</a></li>
 <li><a href="envfile.html">The <tt>envfile</tt> program</a></li>
diff --git a/doc/posix-cd.html b/doc/posix-cd.html
new file mode 100644
index 0000000..3a24602
--- /dev/null
+++ b/doc/posix-cd.html
@@ -0,0 +1,62 @@
+<html>
+  <head>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta http-equiv="Content-Language" content="en" />
+ <title>execline: the posix-cd command</title>
+ <meta name="Description" content="execline: the posix-cd command" />
+ <meta name="Keywords" content="execline command cd chdir posix posix-cd" />
+ <!-- <link rel="stylesheet" type="text/css" href="//skarnet.org/default.css" /> -->
+</head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="//skarnet.org/software/">Software</a><br />
+<a href="//skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>posix-cd</tt> program </h1>
+
+<p>
+<tt>posix-cd</tt> changes the current working directory to a
+given directory, then executes a program.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     posix-cd <em>dir</em> <em>prog...</em>
+</pre>
+
+<p>
+<tt>posix-cd</tt> changes the current working directory to <em>dir</em>
+according to the
+<a href="https://pubs.opengroup.org/onlinepubs/9699919799/utilities/cd.html">POSIX
+specification for a <tt>cd</tt> external utility</a>. Then, if <em>prog...</em> is
+not empty, it execs into it.
+</p>
+
+<h2> Notes </h2>
+
+<ul>
+ <li> <tt>posix-cd</tt> is only available when execline has been configured
+with the <tt>--enable-pedantic-posix</tt> option, and in this case, the
+<a href="cd.html">cd</a> binary is a symbolic link to it. </li>
+ <li> <tt>posix-cd</tt> fully conforms to the
+<a href="https://pubs.opengroup.org/onlinepubs/9699919799/utilities/cd.html">POSIX
+specification</a>. When <em>prog...</em> is not empty, the behaviour of a
+<tt>cd</tt> utility is not specified by POSIX, so <tt>posix-cd</tt> extends
+the spec to be actually useful and usable in an execline program with the same
+interface as the regular execline <a href="cd.html">cd</a> command. </li>
+ <li> Nobody ever executes or needs the external version (i.e. not a shell
+builtin) of the POSIX <tt>cd</tt> command. Compared to the regular execline
+<a href="cd.html">cd</a>, <tt>posix-cd</tt> is uselessly bloated and slow.
+The only reason it exists is that some distributions refuse to package
+execline correctly unless it is strictly POSIX-compliant; the
+<tt>--enable-pedantic-posix</tt> configure option is there to appease them. </li> 
+</ul>
+
+
+</body>
+</html>
diff --git a/doc/upgrade.html b/doc/upgrade.html
index 3191685..045803e 100644
--- a/doc/upgrade.html
+++ b/doc/upgrade.html
@@ -18,6 +18,16 @@
 
 <h1> What has changed in execline </h1>
 
+<h2> in 2.5.3.0 </h2>
+
+<ul>
+ <li> <a href="//skarnet.org/software/skalibs/"></a>skalibs</a>
+dependency bumped to 2.9.1.0. </li>
+ <li> <tt>--enable-pedantic-posix</tt> configure option added. </li>
+ <li> <a href="posix-cd.html">posix-cd</a> binary added. </li>
+ <li> <a href="wait.html">wait</a> binary made POSIX-compliant. </li>
+</ul>
+
 <h2> in 2.5.2.0 </h2>
 
 <ul>
diff --git a/doc/wait.html b/doc/wait.html
index a57d28c..a092859 100644
--- a/doc/wait.html
+++ b/doc/wait.html
@@ -60,5 +60,15 @@ exec into <em>prog...</em>. This is the default. </li>
 will print an error message and exit 1. </li>
 </ul>
 
+<h2> Notes </h2>
+
+<ul>
+ <li> For <a href="https://pubs.opengroup.org/onlinepubs/9699919799/utilities/wait.html">POSIX
+compatibility</a>, <tt>wait</tt> also works when it cannot find a block.
+In that case, all its command line is interpreted as <em>pids...</em>
+arguments and it does not execute into a program. Instead, it exits
+with a conforming exit code. </li>
+</ul>
+
 </body>
 </html>
diff --git a/package/deps.mak b/package/deps.mak
index e058e9f..0d4b00f 100644
--- a/package/deps.mak
+++ b/package/deps.mak
@@ -71,6 +71,7 @@ src/libexecline/exlsn_free.o src/libexecline/exlsn_free.lo: src/libexecline/exls
 src/libexecline/exlsn_importas.o src/libexecline/exlsn_importas.lo: src/libexecline/exlsn_importas.c src/include/execline/execline.h src/include-local/exlsn.h
 src/libexecline/exlsn_main.o src/libexecline/exlsn_main.lo: src/libexecline/exlsn_main.c src/include/execline/execline.h src/include-local/exlsn.h
 src/libexecline/exlsn_multidefine.o src/libexecline/exlsn_multidefine.lo: src/libexecline/exlsn_multidefine.c src/include/execline/execline.h src/include-local/exlsn.h
+src/posix/posix-cd.o src/posix/posix-cd.lo: src/posix/posix-cd.c
 
 background: EXTRA_LIBS := ${SPAWN_LIB}
 background: src/execline/background.o ${LIBEXECLINE} -lskarnet
@@ -171,3 +172,5 @@ libexecline.a.xyzzy: src/libexecline/el_execsequence.lo src/libexecline/el_getst
 endif
 libexecline.so.xyzzy: EXTRA_LIBS := -lskarnet
 libexecline.so.xyzzy: src/libexecline/el_execsequence.lo src/libexecline/el_getstrict.lo src/libexecline/el_parse.lo src/libexecline/el_parse_from_buffer.lo src/libexecline/el_parse_from_string.lo src/libexecline/el_popenv.lo src/libexecline/el_pushenv.lo src/libexecline/el_semicolon.lo src/libexecline/el_spawn0.lo src/libexecline/el_spawn1.lo src/libexecline/el_substandrun.lo src/libexecline/el_substandrun_str.lo src/libexecline/el_substitute.lo src/libexecline/el_transform.lo src/libexecline/el_vardupl.lo src/libexecline/exlsn_define.lo src/libexecline/exlsn_elglob.lo src/libexecline/exlsn_importas.lo src/libexecline/exlsn_multidefine.lo src/libexecline/exlsn_exlp.lo src/libexecline/exlsn_main.lo src/libexecline/exlsn_free.lo src/libexecline/exlp.lo
+posix-cd: EXTRA_LIBS :=
+posix-cd: src/posix/posix-cd.o -lskarnet
diff --git a/package/info b/package/info
index c721ad5..4a5dd27 100644
--- a/package/info
+++ b/package/info
@@ -1,4 +1,4 @@
 package=execline
-version=2.5.2.0
+version=2.5.3.0
 category=admin
 package_macro_name=EXECLINE
diff --git a/package/modes b/package/modes
index 7852d06..dbe4972 100644
--- a/package/modes
+++ b/package/modes
@@ -44,3 +44,4 @@ umask			0755
 unexport		0755
 wait			0755
 withstdinas		0755
+posix-cd		0755
diff --git a/package/targets.mak b/package/targets.mak
index c076fa3..824fc26 100644
--- a/package/targets.mak
+++ b/package/targets.mak
@@ -49,3 +49,12 @@ withstdinas
 LIBEXEC_TARGETS :=
 
 LIB_DEFS := EXECLINE=execline
+
+ifeq ($(PEDANTIC_POSIX),1)
+
+BIN_TARGETS += posix-cd
+
+$(DESTDIR)$(bindir)/cd: $(DESTDIR)$(bindir)/posix-cd
+	exec ./tools/install.sh -l posix-cd $(DESTDIR)$(bindir)/cd
+
+endif
diff --git a/src/execline/wait.c b/src/execline/wait.c
index 2fdbe80..2a2ead2 100644
--- a/src/execline/wait.c
+++ b/src/execline/wait.c
@@ -14,36 +14,50 @@
 #define USAGE "wait [ -I | -i ] [ -r | -t timeout ] { pids... }"
 #define dieusage() strerr_dieusage(100, USAGE)
 
-typedef int actfunc_t (pid_t *, unsigned int *) ;
+typedef int actfunc_t (pid_t *, unsigned int *, int *) ;
 typedef actfunc_t *actfunc_t_ref ;
 
-static inline void waitall (void)
+static inline int waitall (void)
 {
+  int wstat = 0 ;
   pid_t r = 1 ;
-  while (r > 0) r = wait(0) ;
-  if (r < 0 && errno != ECHILD) strerr_diefu1sys(111, "wait") ;
+  while (r > 0) r = wait(&wstat) ;
+  if (r < 0)
+  {
+    if (errno != ECHILD) strerr_diefu1sys(111, "wait") ;
+    else return 127 ;
+  }
+  return wait_estatus(wstat) ;
 }
 
-static int waitany (pid_t *dummytab, unsigned int *dummyn)
+static int waitany (pid_t *dummytab, unsigned int *dummyn, int *res)
 {
+  int wstat ;
   pid_t r = 1 ;
-  while (r > 0) r = wait_nohang(0) ;
-  if (!r) return 1 ;
+  while (r > 0) r = wait_nohang(&wstat) ;
+  if (!r) return (*res = wait_estatus(wstat), 1) ;
   if (errno != ECHILD) strerr_diefu1sys(111, "wait") ;
+  *res = 127 ;
   (void)dummytab ;
   (void)dummyn ;
   return 0 ;
 }
 
-static int waitintab (pid_t *tab, unsigned int *n)
+static int waitintab (pid_t *tab, unsigned int *n, int *res)
 {
   unsigned int i = 0 ;
   for (; i < *n ; i++)
   {
-    pid_t r = waitpid(tab[i], 0, WNOHANG) ;
+    int wstat ;
+    pid_t r = waitpid(tab[i], &wstat, WNOHANG) ;
     if (r)
     {
-      if (r < 0 && errno != ECHILD) strerr_diefu1sys(111, "waitpid") ;
+      if (r < 0)
+      {
+        if (errno == ECHILD) *res = 127 ;
+        else strerr_diefu1sys(111, "waitpid") ;
+      }
+      else *res = wait_estatus(wstat) ;
       tab[i--] = tab[--(*n)] ;
     }
   }
@@ -61,15 +75,16 @@ static inline void handle_signals (void)
   }
 }
 
-static inline void mainloop (tain_t *deadline, int insist, actfunc_t_ref f, pid_t *tab, unsigned int *n)
+static inline int mainloop (tain_t *deadline, int insist, actfunc_t_ref f, pid_t *tab, unsigned int *n)
 {
   iopause_fd x = { .events = IOPAUSE_READ } ;
+  int res = 0 ;
   x.fd = selfpipe_init() ;
   if (x.fd < 0) strerr_diefu1sys(111, "create selfpipe") ;
   if (selfpipe_trap(SIGCHLD) < 0) strerr_diefu1sys(111, "trap SIGCHLD") ;
   tain_now_set_stopwatch_g() ;
   tain_add_g(deadline, deadline) ;
-  while ((*f)(tab, n))
+  while ((*f)(tab, n, &res))
   {
     int r = iopause_g(&x, 1, deadline) ;
     if (r < 0) strerr_diefu1sys(111, "iopause") ;
@@ -82,6 +97,7 @@ static inline void mainloop (tain_t *deadline, int insist, actfunc_t_ref f, pid_
     else handle_signals() ;
   }
   selfpipe_finish() ;
+  return res ;
 }
 
 int main (int argc, char const **argv, char const *const *envp)
@@ -90,6 +106,8 @@ int main (int argc, char const **argv, char const *const *envp)
   int argc1 ;
   int hastimeout = 0 ;
   int insist = 0 ;
+  int r ;
+  int hasblock ;
   PROG = "wait" ;
   {
     subgetopt_t l = SUBGETOPT_ZERO ;
@@ -112,8 +130,13 @@ int main (int argc, char const **argv, char const *const *envp)
     else tto = tain_infinite_relative ;
   }
   argc1 = el_semicolon(argv) ;
-  if (argc1 >= argc) strerr_dief1x(100, "unterminated block") ;
-  if (!argc1 && !hastimeout) waitall() ;
+  if (argc1 >= argc)
+  {
+    hasblock = 0 ;
+    argc1 = argc ;
+  }
+  else hasblock = 1 ;
+  if (!argc1 && !hastimeout) r = waitall() ;
   else
   {
     actfunc_t_ref f = argc1 ? &waitintab : &waitany ;
@@ -125,8 +148,9 @@ int main (int argc, char const **argv, char const *const *envp)
       for (; i < n ; i++)
         if (!pid0_scan(argv[i], tab+i)) strerr_dieusage(100, USAGE) ;
     }
-    mainloop(&tto, insist, f, tab, &n) ;
+    r = mainloop(&tto, insist, f, tab, &n) ;
   }
 
+  if (!hasblock) return r ;
   xpathexec0_run(argv + argc1 + 1, envp) ;
 }
diff --git a/src/posix/deps-exe/posix-cd b/src/posix/deps-exe/posix-cd
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/posix/deps-exe/posix-cd
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/posix/posix-cd.c b/src/posix/posix-cd.c
new file mode 100644
index 0000000..8a98981
--- /dev/null
+++ b/src/posix/posix-cd.c
@@ -0,0 +1,155 @@
+/* ISC license. */
+
+#include <sys/stat.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <locale.h>
+
+#include <skalibs/bytestr.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/allreadwrite.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/djbunix.h>
+
+#define USAGE "posix-cd [ -L | -P ] [ - | path ] [ prog... ]"
+#define dieusage() strerr_dieusage(100, USAGE)
+#define dienomem() strerr_diefu1sys(100, "stralloc_catb")
+
+int main (int argc, char const **argv, char const *const *envp)
+{
+  int phy = 0 ;
+  int dopwd = 0 ;
+  char const *where ;
+  int got = 0 ;
+  stralloc sa = STRALLOC_ZERO ;
+  PROG = "posix-cd" ;
+  setlocale(LC_ALL, "") ; /* yeah, as if */
+
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      int opt = subgetopt_r(argc, argv, "LP", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'L' : phy = 0 ; break ;
+        case 'P' : phy = 1 ; break ;
+        default : dieusage() ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+  if (!argc) where = getenv("HOME") ;
+  else
+  {
+    where = *argv++ ;
+    if (!strcmp(where, "-"))
+    {
+      where = getenv("OLDPWD") ;
+      dopwd = 1 ;
+    }
+  }
+  if (!where || !where[0]) dieusage() ;
+
+  if ((where[0] != '/' && (where[0] != '.')) || (where[1] && where[1] != '.'))
+  {
+    char const *cdpath = getenv("CDPATH") ;
+    if (cdpath)
+    {
+      size_t pos = 0 ;
+      size_t len = strlen(cdpath) ;
+      while (pos < len)
+      {
+        struct stat st ;
+        size_t m = byte_chr(cdpath + pos, len - pos, ':') ;
+        sa.len = 0 ;
+        if (m)
+        {
+          if (!stralloc_catb(&sa, cdpath + pos, m)) dienomem() ;
+          if (cdpath[pos + m - 1] != '/' && !stralloc_catb(&sa, "/", 1)) dienomem() ;
+        }
+        else if (!stralloc_catb(&sa, "./", 2)) dienomem() ;
+        if (!stralloc_cats(&sa, where) || !stralloc_0(&sa)) dienomem() ;
+        if (!stat(sa.s, &st) && S_ISDIR(st.st_mode))
+        {
+          got = 1 ;
+          dopwd = 1 ;
+          break ;
+        }
+        pos += m+1 ;
+      }
+    }
+  }
+
+  if (!got && (!stralloc_cats(&sa, where) || !stralloc_0(&sa))) dienomem() ;
+
+  {
+    size_t sabase = sa.len ;
+    if (sagetcwd(&sa) < 0) strerr_diefu1sys(111, "getcwd") ;
+    if (!stralloc_0(&sa)) dienomem() ;
+    if (!pathexec_env("OLDPWD", sa.s + sabase)) dienomem() ;
+    sa.len = sabase ;
+  }
+
+  if (!phy)
+  {
+    char const *x = getenv("PWD") ;
+    if (x && sa.s[0] != '/')
+    {
+      size_t len = strlen(x) ;
+      int doslash = len && x[len-1] != '/' ;
+      if (!stralloc_insertb(&sa, 0, x, len + doslash)) dienomem() ;
+      if (doslash) sa.s[len] = '/' ;
+    }
+    {
+      stralloc tmp = STRALLOC_ZERO ;
+      if (!stralloc_ready(&tmp, sa.len + 2)) dienomem() ;
+      if (!path_canonicalize(tmp.s, sa.s, 1))
+        strerr_diefu4sys(111, "canonicalize ", sa.s, ": problem with ", tmp.s) ;
+      stralloc_free(&sa) ;
+      sa = tmp ;
+    }
+    if (!pathexec_env("PWD", sa.s)) dienomem() ;
+    if (sa.len > PATH_MAX && strlen(where) < PATH_MAX && x && *x)
+    {
+      size_t len = strlen(x) ;
+      int hasslash = x[len-1] == '/' ;
+      if (!strncmp(sa.s, x, len))
+      {
+        if (hasslash || (sa.len > len && sa.s[len] == '/'))
+        {
+          sa.len -= len + !hasslash ;
+          memmove(sa.s, sa.s + len + !hasslash, sa.len) ;
+        }
+      }
+    }
+  }
+
+ /* fking finally */
+
+  if (chdir(sa.s) < 0)
+    strerr_diefu2sys(111, "chdir to ", where) ;
+
+ /* and there's still more nonsense to do afterwards! */
+
+  if (phy)
+  {
+    sa.len = 0 ;
+    if (sagetcwd(&sa) < 0) strerr_diefu1sys(111, "getcwd") ;
+    if (!stralloc_0(&sa)) dienomem() ;
+    if (!pathexec_env("PWD", sa.s)) dienomem() ;
+  }
+  
+  if (dopwd)
+  {
+    sa.s[sa.len - 1] = '\n' ;
+    if (allwrite(1, sa.s, sa.len) < sa.len)
+      strerr_diefu1sys(111, "write to stdout") ;
+  }
+
+  xpathexec0(argv) ;
+}