Eric Blake
2023-Feb-15 23:03 UTC
[Libguestfs] [libnbd PATCH v3 10/29] lib/utils: add unit tests for async-signal-safe execvpe()
On Wed, Feb 15, 2023 at 03:11:39PM +0100, Laszlo Ersek wrote:> Don't try to test async-signal-safety, but strive to exercise as many as > possible paths through nbd_internal_execvpe_init() and > nbd_internal_fork_safe_execvpe(). > > Signed-off-by: Laszlo Ersek <lersek at redhat.com> > --- > lib/test-fork-safe-execvpe.c | 117 +++++++++ > lib/Makefile.am | 10 + > lib/test-fork-safe-execvpe.sh | 270 ++++++++++++++++++++ > .gitignore | 1 + > 4 files changed, 398 insertions(+) > > diff --git a/lib/test-fork-safe-execvpe.c b/lib/test-fork-safe-execvpe.c > new file mode 100644 > index 000000000000..09b91102b613 > --- /dev/null > +++ b/lib/test-fork-safe-execvpe.c> + > +/* This is a perror() replacement that makes the error message more > + * machine-readable, for a select few error numbers. Do not use it as a general > + * error() replacement, only upon nbd_internal_execvpe_init() and > + * nbd_internal_fork_safe_execvpe() failure. > + */ > +static void > +xperror (const char *s)Handy for the .sh counterpart that reads expected output.> + > +int > +main (int argc, char **argv) > +{ > + struct execvpe ctx; > + const char *prog_file; > + string_vector prog_argv; > + size_t i; > + > + if (argc < 3) { > + fprintf (stderr, "%1$s: usage: %1$s program-to-exec argv0 ...\n", argv[0]); > + return EXIT_FAILURE; > + } > + > + prog_file = argv[1]; > + > + /* For the argv of the program to execute, we need to drop our argv[0] (= our > + * own name) and argv[1] (= the program we need to execute), and to tack on a > + * terminating null pointer. Note that "argc" does not include the terminating > + * NULL. > + */ > + prog_argv = (string_vector)empty_vector; > + if (string_vector_reserve (&prog_argv, argc - 2 + 1) == -1) { > + perror ("string_vector_reserve"); > + return EXIT_FAILURE; > + } > + > + for (i = 2; i < argc; ++i) > + (void)string_vector_append (&prog_argv, argv[i]); > + (void)string_vector_append (&prog_argv, NULL);I don't know if I would have used the cast-to-void. But it doesn't hurt (this is a unit test and you are documenting that we are basically ignoring append errors as unlikely), and at this point it's more effort to rip than out than leave well enough alone.> + > + if (nbd_internal_execvpe_init (&ctx, prog_file, prog_argv.len) == -1) { > + xperror ("nbd_internal_execvpe_init"); > + goto reset_prog_argv; > + } > + > + /* Print out the generated candidates. */ > + for (i = 0; i < ctx.pathnames.len; ++i) > + (void)fprintf (stdout, "%s\n", ctx.pathnames.ptr[i]);Peering into the internals. Naughty for normal code, but perfectly acceptable for the unit test.> + > + if (fflush (stdout) == EOF) { > + perror ("fflush"); > + goto uninit_execvpe; > + } > + > + (void)nbd_internal_fork_safe_execvpe (&ctx, &prog_argv, environ); > + xperror ("nbd_internal_fork_safe_execvpe"); > + /* fall through */ > + > +uninit_execvpe: > + nbd_internal_execvpe_uninit (&ctx); > + > +reset_prog_argv: > + string_vector_reset (&prog_argv); > + > + return EXIT_FAILURE; > +}> +++ b/lib/test-fork-safe-execvpe.sh> + > +set -e > + > +# Determine the absolute pathname of the execvpe helper binary. The "realpath" > +# utility is not in POSIX, but Linux, FreeBSD and OpenBSD all have it.not in POSIX yet, but see https://www.austingroupbugs.net/view.php?id=1457> +bname=$(basename -- "$0" .sh) > +dname=$(dirname -- "$0") > +execvpe=$(realpath -- "$dname/$bname") > + > +# This is an elaborate way to control the PATH variable around the $execvpe > +# helper binary as narrowly as possible. > +# > +# If $1 is "_", then the $execvpe helper binary is invoked with PATH unset. > +# Otherwise, the binary is invoked with PATH set to $1. > +# > +# $2 and onward are passed to $execvpe; note that $2 becomes *both* > +# "program-to-exec" for the helper *and* argv[0] for the program executed by the > +# helper. > +# > +# The command itself (including the PATH setting) is written to "cmd" (for error > +# reporting purposes only); the standard output and error are saved in "out" and > +# "err" respectively; the exit status is written to "status". This function > +# should never fail; if it does, then that's a bug in this unit test script, or > +# the disk is full etc. > +run()Looks nice.> + > +# Create a temporary directory and change the working directory to it. > +tmpd=$(mktemp -d) > +trap 'rm -r -- "$tmpd"' EXITOur testsuite has cleanup_fn in functions.sh to make this sort of cleanup work more robust (more than just on EXIT).> +cd "$tmpd" > + > +# If the "file" parameter of execvpe() is an empty string, then we must fail -- > +# in nbd_internal_execvpe_init() -- regardless of PATH. > +run _ ""; init_fail ENOENT > +run "" ""; init_fail ENOENT > +run . ""; init_fail ENOENT > + > +# Create subdirectories for triggering non-fatal internal error conditions of > +# execvpe(). (Almost) every subdirectory will contain one entry, called "f". > +# > +# Create a directory that's empty. > +mkdir empty > + > +# Create a directory with a named pipe (FIFO) in it. > +mkdir fifo > +mkfifo fifo/f > + > +# Create a directory with a non-executable file in it. > +mkdir nxregf > +touch nxregf/f > + > +# Create a symlink loop. > +ln -s symlink symlinkThere are two places where ELOOP could matter: both in PATH (symlink/f can't resolve, regardless of the binary name we are looking up) and in the binary (./symlink or PATH=. symlink). Looking ahead, I'm seeing your tests of symlink as a directory name, but not as a binary name. But I don't know if adding it would increase coverage of your code, so much as coverage of the underlying execve() call.> + > +# Create a directory with a (most likely) binary executable in it. > +mkdir bin > +expr_pathname=$(command -p -v expr) > +cp -- "$expr_pathname" bin/f > + > +# Create a directory with an executable shell script that does not contain a > +# shebang (#!). The script will print $0 and $1, and not depend on PATH for it. > +mkdir sh > +printf "command -p printf '%%s %%s\\\\n' \"\$0\" \"\$1\"\\n" >sh/f > +chmod u+x sh/fBased on your reactions to my comments on 09/29 about handling empty PATH= the same as PATH=., and on whether to allow the child process to chdir() and/or deal with a PATH element or binary name such as "+s" that could impact /bin/sh's behavior, there may be other cases you want to add to the testsuite.> + > +# In the tests below, invoke each "f" such that the "file" parameter of > +# execvpe() contain a <slash> character. > +# > +# Therefore, PATH does not matter. Set it to the empty string. (Which in this > +# implementation would cause nbd_internal_execvpe_init() to fail with ENOENT, if > +# the "file" parameter didn't contain a <slash>.) > +run "" empty/f; execve_fail empty/f ENOENT > +run "" fifo/f; execve_fail fifo/f EACCES > +run "" nxregf/f; execve_fail nxregf/f EACCES > +run "" nxregf/f/; execve_fail nxregf/f/ ENOTDIR > +run "" symlink/f; execve_fail symlink/f ELOOP > + > +# This runs "expr 1 + 1". > +run "" bin/f 1 + 1; success bin/f,2 > + > +# This triggers the ENOEXEC branch in nbd_internal_fork_safe_execvpe(). > +# nbd_internal_fork_safe_execvpe() will first try > +# > +# execve("sh/f", {"sh/f", "arg1", NULL}, envp) > +# > +# hitting ENOEXEC. Then it will successfully call > +# > +# execve("/bin/sh", {"sh/f", "sh/f", "arg1", NULL}, envp) > +# > +# The shell script will get "sh/f" for $0 and "arg1" for $1, and print those > +# out. > +run "" sh/f arg1; success sh/f,"sh/f arg1" > + > +# In the tests below, the "file" parameter of execvpe() will not contain a > +# <slash> character. > +# > +# Show that PATH matters that way -- first, trigger an ENOENT in > +# nbd_internal_execvpe_init() by setting PATH to the empty string. > +run "" expr 1 + 1; init_fail ENOENTAnother spot that might need tweaking based on your reaction to my comments on the previous patch.> + > +# Fall back to confstr(_CS_PATH) in nbd_internal_execvpe_init(), by unsetting > +# PATH. Verify the generated candidates by invoking "getconf PATH" here, and > +# appending "/expr" to each prefix. > +expected_output=$( > + path=$(command -p getconf PATH)We may have to check that getconf works, or skip the test if it doesn't (POSIX says it should exist, but not all the world is POSIX). Can be done as a followup if CI shows a failure.> + IFS=: > + for p in $path; do > + printf '%s/%s\n' $p expr > + done > + command -p expr 1 + 1 > +) > +run _ expr 1 + 1; success "${expected_output//$'\n'/,}" > + > +# Continue with tests where the "file" parameter of execvpe() does not contain a > +# <slash> character, but now set PATH to explicit prefix lists. > +# > +# Show that, if the last candidate fails execve() with an error number that > +# would not be fatal otherwise, we do get that error number. > +run empty:fifo:nxregf:symlink f > +execve_fail empty/f,fifo/f,nxregf/f,symlink/f ELOOP > + > +# Put a single prefix in PATH, such that it lead to a successful execution. Thisleads> +# exercises two things at the same time: (a) that nbd_internal_execvpe_init() > +# produces *one* candidate (i.e., that no <colon> is seen), and (b) that > +# nbd_internal_fork_safe_execvpe() succeeds for the *last* candidate. Repeat the > +# test with "expr" (called "f" under "bin") and the shell script (called "f" > +# under "sh", triggering the ENOEXEC branch). > +run bin f 1 + 1; success bin/f,2 > +run sh f arg1; success sh/f,"sh/f arg1" > + > +# Demonstrate that the order of candidates matters. The first invocation finds > +# "expr" (called "f" under "bin"), the second invocation finds the shell script > +# under "sh" (triggering the ENOEXEC branch). > +run empty:bin:sh f 1 + 1; success empty/f,bin/f,sh/f,2 > +run empty:sh:bin f arg1; success empty/f,sh/f,bin/f,"sh/f arg1" > + > +# Check the expansion of zero-length prefixes in PATH to ".", plus the > +# (non-)insertion of the "/" separator. > +run a/: f; execve_fail a/f,./f ENOENT > +run :a/ f; execve_fail ./f,a/f ENOENT > +run : f; execve_fail ./f,./f ENOENT > +pushd bin > +run : f 1 + 1; success ./f,./f,2 > +popd > +run :a/:::b/: f; execve_fail ./f,a/f,./f,./f,b/f,./f ENOENTOverall a nice unit test. Reviewed-by: Eric Blake <eblake at redhat.com> -- Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3266 Virtualization: qemu.org | libvirt.org
Eric Blake
2023-Feb-15 23:33 UTC
[Libguestfs] [libnbd PATCH v3 10/29] lib/utils: add unit tests for async-signal-safe execvpe()
On Wed, Feb 15, 2023 at 05:03:48PM -0600, Eric Blake wrote:> > + > > +# Create subdirectories for triggering non-fatal internal error conditions of > > +# execvpe(). (Almost) every subdirectory will contain one entry, called "f". > > +# > > +# Create a directory that's empty. > > +mkdir empty > > + > > +# Create a directory with a named pipe (FIFO) in it. > > +mkdir fifo > > +mkfifo fifo/f > > + > > +# Create a directory with a non-executable file in it. > > +mkdir nxregf > > +touch nxregf/f > > + > > +# Create a symlink loop. > > +ln -s symlink symlinkAnother interesting thing you might want to add to the test: mkdir -p subdir/f then show that PATH=...:subdir:... does not get tripped up by subdir/f/ having the execute bit set (aka search bit since it's a directory) but not being an executable. -- Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3266 Virtualization: qemu.org | libvirt.org
Laszlo Ersek
2023-Feb-21 12:53 UTC
[Libguestfs] [libnbd PATCH v3 10/29] lib/utils: add unit tests for async-signal-safe execvpe()
On 2/16/23 00:03, Eric Blake wrote:> On Wed, Feb 15, 2023 at 03:11:39PM +0100, Laszlo Ersek wrote: >> Don't try to test async-signal-safety, but strive to exercise as many as >> possible paths through nbd_internal_execvpe_init() and >> nbd_internal_fork_safe_execvpe(). >> >> Signed-off-by: Laszlo Ersek <lersek at redhat.com> >> --- >> lib/test-fork-safe-execvpe.c | 117 +++++++++ >> lib/Makefile.am | 10 + >> lib/test-fork-safe-execvpe.sh | 270 ++++++++++++++++++++ >> .gitignore | 1 + >> 4 files changed, 398 insertions(+) >> >> diff --git a/lib/test-fork-safe-execvpe.c b/lib/test-fork-safe-execvpe.c >> new file mode 100644 >> index 000000000000..09b91102b613 >> --- /dev/null >> +++ b/lib/test-fork-safe-execvpe.c > >> + >> +/* This is a perror() replacement that makes the error message more >> + * machine-readable, for a select few error numbers. Do not use it as a general >> + * error() replacement, only upon nbd_internal_execvpe_init() and >> + * nbd_internal_fork_safe_execvpe() failure. >> + */ >> +static void >> +xperror (const char *s) > > Handy for the .sh counterpart that reads expected output. > >> + >> +int >> +main (int argc, char **argv) >> +{ >> + struct execvpe ctx; >> + const char *prog_file; >> + string_vector prog_argv; >> + size_t i; >> + >> + if (argc < 3) { >> + fprintf (stderr, "%1$s: usage: %1$s program-to-exec argv0 ...\n", argv[0]); >> + return EXIT_FAILURE; >> + } >> + >> + prog_file = argv[1]; >> + >> + /* For the argv of the program to execute, we need to drop our argv[0] (= our >> + * own name) and argv[1] (= the program we need to execute), and to tack on a >> + * terminating null pointer. Note that "argc" does not include the terminating >> + * NULL. >> + */ >> + prog_argv = (string_vector)empty_vector; >> + if (string_vector_reserve (&prog_argv, argc - 2 + 1) == -1) { >> + perror ("string_vector_reserve"); >> + return EXIT_FAILURE; >> + } >> + >> + for (i = 2; i < argc; ++i) >> + (void)string_vector_append (&prog_argv, argv[i]); >> + (void)string_vector_append (&prog_argv, NULL); > > I don't know if I would have used the cast-to-void. But it doesn't > hurt (this is a unit test and you are documenting that we are > basically ignoring append errors as unlikely), and at this point it's > more effort to rip than out than leave well enough alone. >s/unlikely/impossible/, per successful string_vector_reserve() above.>> + >> + if (nbd_internal_execvpe_init (&ctx, prog_file, prog_argv.len) == -1) { >> + xperror ("nbd_internal_execvpe_init"); >> + goto reset_prog_argv; >> + } >> + >> + /* Print out the generated candidates. */ >> + for (i = 0; i < ctx.pathnames.len; ++i) >> + (void)fprintf (stdout, "%s\n", ctx.pathnames.ptr[i]); > > Peering into the internals. Naughty for normal code, but perfectly > acceptable for the unit test.Well put :)> >> + >> + if (fflush (stdout) == EOF) { >> + perror ("fflush"); >> + goto uninit_execvpe; >> + } >> + >> + (void)nbd_internal_fork_safe_execvpe (&ctx, &prog_argv, environ); >> + xperror ("nbd_internal_fork_safe_execvpe"); >> + /* fall through */ >> + >> +uninit_execvpe: >> + nbd_internal_execvpe_uninit (&ctx); >> + >> +reset_prog_argv: >> + string_vector_reset (&prog_argv); >> + >> + return EXIT_FAILURE; >> +} > >> +++ b/lib/test-fork-safe-execvpe.sh > >> + >> +set -e >> + >> +# Determine the absolute pathname of the execvpe helper binary. The "realpath" >> +# utility is not in POSIX, but Linux, FreeBSD and OpenBSD all have it. > > not in POSIX yet, but see https://www.austingroupbugs.net/view.php?id=1457Very cool! Thanks!> >> +bname=$(basename -- "$0" .sh) >> +dname=$(dirname -- "$0") >> +execvpe=$(realpath -- "$dname/$bname") >> + >> +# This is an elaborate way to control the PATH variable around the $execvpe >> +# helper binary as narrowly as possible. >> +# >> +# If $1 is "_", then the $execvpe helper binary is invoked with PATH unset. >> +# Otherwise, the binary is invoked with PATH set to $1. >> +# >> +# $2 and onward are passed to $execvpe; note that $2 becomes *both* >> +# "program-to-exec" for the helper *and* argv[0] for the program executed by the >> +# helper. >> +# >> +# The command itself (including the PATH setting) is written to "cmd" (for error >> +# reporting purposes only); the standard output and error are saved in "out" and >> +# "err" respectively; the exit status is written to "status". This function >> +# should never fail; if it does, then that's a bug in this unit test script, or >> +# the disk is full etc. >> +run() > > Looks nice. > >> + >> +# Create a temporary directory and change the working directory to it. >> +tmpd=$(mktemp -d) >> +trap 'rm -r -- "$tmpd"' EXIT > > Our testsuite has cleanup_fn in functions.sh to make this sort of > cleanup work more robust (more than just on EXIT).(1) cleanup_fn is in "tests/functions.sh", but this unit test is in lib/. Hm... still, I see lots of . ../tests/functions.sh lines across various test cases. So, based on that, and for uniformity's sake, I can use cleanup_fn here, indeed. (2) But, what do you mean by "more than just on EXIT"? In "functions.sh", I see: trap _run_cleanup_hooks INT QUIT TERM EXIT ERR>From these, INT, TERM, EXIT and ERR should *already* be covered by justEXIT; ERR in particular because of "set -e" in this script. And regarding QUIT, I disagree that the cleanup handlers should run upon QUIT. QUIT ("Terminal quit signal") is a signal specifically designed for interactive abnormal termination, without cleaning up, in practice even including a core dump -- that is, for debugging purposes. I do use Ctrl-\ on occasion, for debugging. https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap01.html#tag_17_04 Any temporary file created by the implementation shall be removed by the implementation upon a utility's successful exit, exit because of errors, or before termination by any of the SIGHUP, SIGINT, or SIGTERM signals, unless specified otherwise by the utility description. Receipt of the SIGQUIT signal should generally cause termination (unless in some debugging mode) that would bypass any attempted recovery actions.> >> +cd "$tmpd" >> + >> +# If the "file" parameter of execvpe() is an empty string, then we must fail -- >> +# in nbd_internal_execvpe_init() -- regardless of PATH. >> +run _ ""; init_fail ENOENT >> +run "" ""; init_fail ENOENT >> +run . ""; init_fail ENOENT >> + >> +# Create subdirectories for triggering non-fatal internal error conditions of >> +# execvpe(). (Almost) every subdirectory will contain one entry, called "f". >> +# >> +# Create a directory that's empty. >> +mkdir empty >> + >> +# Create a directory with a named pipe (FIFO) in it. >> +mkdir fifo >> +mkfifo fifo/f >> + >> +# Create a directory with a non-executable file in it. >> +mkdir nxregf >> +touch nxregf/f >> + >> +# Create a symlink loop. >> +ln -s symlink symlink > > There are two places where ELOOP could matter: both in PATH (symlink/f > can't resolve, regardless of the binary name we are looking up) and in > the binary (./symlink or PATH=. symlink). Looking ahead, I'm seeing > your tests of symlink as a directory name, but not as a binary name. > But I don't know if adding it would increase coverage of your code, so > much as coverage of the underlying execve() call.Exactly -- what particular internal step of execve() leads to execve() returning -1/ELOOP doesn't matter here.> >> + >> +# Create a directory with a (most likely) binary executable in it. >> +mkdir bin >> +expr_pathname=$(command -p -v expr) >> +cp -- "$expr_pathname" bin/f >> + >> +# Create a directory with an executable shell script that does not contain a >> +# shebang (#!). The script will print $0 and $1, and not depend on PATH for it. >> +mkdir sh >> +printf "command -p printf '%%s %%s\\\\n' \"\$0\" \"\$1\"\\n" >sh/f >> +chmod u+x sh/f > > Based on your reactions to my comments on 09/29 about handling empty > PATH= the same as PATH=., and on whether to allow the child process to > chdir() and/or deal with a PATH element or binary name such as "+s" > that could impact /bin/sh's behavior, there may be other cases you > want to add to the testsuite.I agree, and that's another reason why I wouldn't like to modify the behavior on PATH='' :) Writing the present patch was the single most time consuming / difficult step across the entire series.> >> + >> +# In the tests below, invoke each "f" such that the "file" parameter of >> +# execvpe() contain a <slash> character. >> +# >> +# Therefore, PATH does not matter. Set it to the empty string. (Which in this >> +# implementation would cause nbd_internal_execvpe_init() to fail with ENOENT, if >> +# the "file" parameter didn't contain a <slash>.) >> +run "" empty/f; execve_fail empty/f ENOENT >> +run "" fifo/f; execve_fail fifo/f EACCES >> +run "" nxregf/f; execve_fail nxregf/f EACCES >> +run "" nxregf/f/; execve_fail nxregf/f/ ENOTDIR >> +run "" symlink/f; execve_fail symlink/f ELOOP >> + >> +# This runs "expr 1 + 1". >> +run "" bin/f 1 + 1; success bin/f,2 >> + >> +# This triggers the ENOEXEC branch in nbd_internal_fork_safe_execvpe(). >> +# nbd_internal_fork_safe_execvpe() will first try >> +# >> +# execve("sh/f", {"sh/f", "arg1", NULL}, envp) >> +# >> +# hitting ENOEXEC. Then it will successfully call >> +# >> +# execve("/bin/sh", {"sh/f", "sh/f", "arg1", NULL}, envp) >> +# >> +# The shell script will get "sh/f" for $0 and "arg1" for $1, and print those >> +# out. >> +run "" sh/f arg1; success sh/f,"sh/f arg1" >> + >> +# In the tests below, the "file" parameter of execvpe() will not contain a >> +# <slash> character. >> +# >> +# Show that PATH matters that way -- first, trigger an ENOENT in >> +# nbd_internal_execvpe_init() by setting PATH to the empty string. >> +run "" expr 1 + 1; init_fail ENOENT > > Another spot that might need tweaking based on your reaction to my > comments on the previous patch. > >> + >> +# Fall back to confstr(_CS_PATH) in nbd_internal_execvpe_init(), by unsetting >> +# PATH. Verify the generated candidates by invoking "getconf PATH" here, and >> +# appending "/expr" to each prefix. >> +expected_output=$( >> + path=$(command -p getconf PATH) > > We may have to check that getconf works, or skip the test if it > doesn't (POSIX says it should exist, but not all the world is POSIX). > Can be done as a followup if CI shows a failure.Hm, good point! Our README file says, under requirements: "Linux, FreeBSD or OpenBSD". Both BSDs seem to know about "getconf": https://man.openbsd.org/getconf.1 https://man.freebsd.org/cgi/man.cgi?query=getconf Let's hope CI will be happy. :)> >> + IFS=: >> + for p in $path; do >> + printf '%s/%s\n' $p expr >> + done >> + command -p expr 1 + 1 >> +) >> +run _ expr 1 + 1; success "${expected_output//$'\n'/,}" >> + >> +# Continue with tests where the "file" parameter of execvpe() does not contain a >> +# <slash> character, but now set PATH to explicit prefix lists. >> +# >> +# Show that, if the last candidate fails execve() with an error number that >> +# would not be fatal otherwise, we do get that error number. >> +run empty:fifo:nxregf:symlink f >> +execve_fail empty/f,fifo/f,nxregf/f,symlink/f ELOOP >> + >> +# Put a single prefix in PATH, such that it lead to a successful execution. This > > leadsI disagree (as a non-native speaker); I meant to express a singular third person imperative, with subjunctive mood: https://en.wikipedia.org/wiki/English_subjunctive> >> +# exercises two things at the same time: (a) that nbd_internal_execvpe_init() >> +# produces *one* candidate (i.e., that no <colon> is seen), and (b) that >> +# nbd_internal_fork_safe_execvpe() succeeds for the *last* candidate. Repeat the >> +# test with "expr" (called "f" under "bin") and the shell script (called "f" >> +# under "sh", triggering the ENOEXEC branch). >> +run bin f 1 + 1; success bin/f,2 >> +run sh f arg1; success sh/f,"sh/f arg1" >> + >> +# Demonstrate that the order of candidates matters. The first invocation finds >> +# "expr" (called "f" under "bin"), the second invocation finds the shell script >> +# under "sh" (triggering the ENOEXEC branch). >> +run empty:bin:sh f 1 + 1; success empty/f,bin/f,sh/f,2 >> +run empty:sh:bin f arg1; success empty/f,sh/f,bin/f,"sh/f arg1" >> + >> +# Check the expansion of zero-length prefixes in PATH to ".", plus the >> +# (non-)insertion of the "/" separator. >> +run a/: f; execve_fail a/f,./f ENOENT >> +run :a/ f; execve_fail ./f,a/f ENOENT >> +run : f; execve_fail ./f,./f ENOENT >> +pushd bin >> +run : f 1 + 1; success ./f,./f,2 >> +popd >> +run :a/:::b/: f; execve_fail ./f,a/f,./f,./f,b/f,./f ENOENT > > Overall a nice unit test. > > Reviewed-by: Eric Blake <eblake at redhat.com> >Thanks! Laszlo