From a1e1592ac7028659d09eb3fa6c8a2227cba9f2a9 Mon Sep 17 00:00:00 2001
From: Kurtis Rader <krader@skepticism.us>
Date: Wed, 8 Jan 2020 19:14:31 -0800
Subject: [PATCH] Fix handling of skipped directories

The bug in `path_opentype()` fixed by this commit may affect other
scenarios but we know it affects autoloaded functions. Hence the unit
test for that scenario.

Fixes #1454

(cherry picked from commit 3bc58164494eecc180e2bad966d7753bfdd1e295)
---
 src/cmd/ksh93/sh/path.c              | 16 +++++++++-------
 src/cmd/ksh93/tests/autoload.sh      | 15 +++++++++++++++
 src/cmd/ksh93/tests/data/skipped_dir | 15 +++++++++++++++
 src/cmd/ksh93/tests/meson.build      |  1 +
 4 files changed, 40 insertions(+), 7 deletions(-)
 create mode 100644 src/cmd/ksh93/tests/autoload.sh
 create mode 100644 src/cmd/ksh93/tests/data/skipped_dir

diff --git a/src/cmd/ksh93/sh/path.c b/src/cmd/ksh93/sh/path.c
index 69382f9..b7869b3 100644
--- a/src/cmd/ksh93/sh/path.c
+++ b/src/cmd/ksh93/sh/path.c
@@ -475,28 +475,30 @@ Pathcomp_t *path_get(Shell_t *shp, const char *name) {
 //
 static_fn int path_opentype(Shell_t *shp, const char *name, Pathcomp_t *pp, int fun) {
     int fd = -1;
-    struct stat statb;
-    Pathcomp_t *oldpp;
 
     if (!pp && !shp->pathlist) path_init(shp);
     if (!fun && strchr(name, '/') && sh_isoption(shp, SH_RESTRICTED)) {
         errormsg(SH_DICT, ERROR_exit(1), e_restricted, name);
         __builtin_unreachable();
     }
+
+    // The structure of this loop is slightly odd. It's a consequence of how path_nextcomp() works.
+    Pathcomp_t *next_pp = pp;
     do {
-        pp = path_nextcomp(shp, oldpp = pp, name, 0);
-        while (oldpp && (oldpp->flags & PATH_SKIP)) oldpp = oldpp->next;
-        if (fun && (!oldpp || !(oldpp->flags & PATH_FPATH))) continue;
+        pp = next_pp;
+        next_pp = path_nextcomp(shp, pp, name, NULL);
+        if (pp && (pp->flags & PATH_SKIP)) continue;
+        if (fun && (!pp || !(pp->flags & PATH_FPATH))) continue;
         fd = sh_open(path_relative(shp, stkptr(shp->stk, PATH_OFFSET)), O_RDONLY | O_CLOEXEC, 0);
+        struct stat statb;
         if (fd >= 0 && (fstat(fd, &statb) < 0 || S_ISDIR(statb.st_mode))) {
             errno = EISDIR;
             sh_close(fd);
             fd = -1;
         }
-    } while (fd < 0 && pp);
+    } while (fd < 0 && next_pp);
 
     assert(fd < 0 || sh_iovalidfd(shp, fd));
-
     if (fd >= 0 && (fd = sh_iomovefd(shp, fd)) > 0) {
         (void)fcntl(fd, F_SETFD, FD_CLOEXEC);
         shp->fdstatus[fd] |= IOCLEX;
diff --git a/src/cmd/ksh93/tests/autoload.sh b/src/cmd/ksh93/tests/autoload.sh
new file mode 100644
index 0000000..6aaa206
--- /dev/null
+++ b/src/cmd/ksh93/tests/autoload.sh
@@ -0,0 +1,15 @@
+# Verify the behavior of autoloaded functions.
+
+# ====================
+# Verify that directories in the path search list which should be skipped (e.g., because they don't
+# exist) interacts correctly with autoloaded functions.
+#
+# See https://github.com/att/ast/issues/1454
+expect=$"Func cd called with |$TEST_DIR/usr|\n$TEST_DIR/usr"
+actual=$($SHELL "$TEST_ROOT/data/skipped_dir")
+actual_status=$?
+expect_status=0
+[[ $actual_status == $expect_status ]] ||
+    log_error "autoload function skipped dir test wrong status" "$expect_status" "$actual_status"
+[[ $actual == $expect ]] ||
+    log_error "autoload function skipped dir test wrong output" "$expect" "$actual"
diff --git a/src/cmd/ksh93/tests/data/skipped_dir b/src/cmd/ksh93/tests/data/skipped_dir
new file mode 100644
index 0000000..b8eeddc
--- /dev/null
+++ b/src/cmd/ksh93/tests/data/skipped_dir
@@ -0,0 +1,15 @@
+# See https://github.com/att/ast/issues/1454
+
+mkdir -p "$TEST_DIR/usr/bin"
+print '#!/bin/sh' >"$TEST_DIR/usr/bin/cd"
+print 'builtin cd "$@"' >>"$TEST_DIR/usr/bin/cd"
+prefix="$TEST_DIR/ksh.$$"
+
+FPATH="$prefix/bad:$prefix/functions"
+mkdir -p "$prefix/functions"
+print 'function cd { echo "Func cd called with |$*|"; command cd "$@"; }' >"$prefix/functions/cd"
+typeset -fu cd
+
+PATH="/arglebargle:$PATH:$TEST_DIR/usr/bin:$TEST_DIR/bin"
+cd "$TEST_DIR/usr"
+pwd
diff --git a/src/cmd/ksh93/tests/meson.build b/src/cmd/ksh93/tests/meson.build
index 6a07d7c..26f2d43 100644
--- a/src/cmd/ksh93/tests/meson.build
+++ b/src/cmd/ksh93/tests/meson.build
@@ -48,6 +48,7 @@ all_tests = [
     ['arrays'],
     ['arrays2'],
     ['attributes'],
+    ['autoload'],
     ['basic', 90],
     ['case'],
     ['comvar'],
-- 
1.8.3.1

