diff mbox series

exec.c: Fix exit status for command -v on non-executable files

Message ID CA+z1gfX9ZexoQC8tm9V0hV_Vo3kMxxDw5GJdeNTy_DFGu_CMKg@mail.gmail.com (mailing list archive)
State Superseded
Delegated to: Herbert Xu
Headers show
Series exec.c: Fix exit status for command -v on non-executable files | expand

Commit Message

Nicola Lamacchia May 30, 2022, 5:02 p.m. UTC
Hello,

the following patches `command -v` to make it abide by POSIX
specifications[1] as previously described by orbea in this mailing
list[2]. Behavior comparison at the bottom[5].

Please note that POSIX does not specify the exit status in this
case[1]: "Otherwise, no output shall be written and the exit status
shall reflect that the name was not found." Although it requires the
exit status to be 127 in case of command not found[3].

Also note that in order for a command to be considered "found" when
using the PATH variable, it has to be an executable file[4]: "The list
shall be searched from beginning to end, applying the filename to each
prefix, until an executable file with the specified name and
appropriate execution permissions is found."

Therefore an exit status of 127 seems to be suitable in this case.
Which would leave the exit status 126 to be used when the user tries
to execute a file while not having the right permissions to do so.

---
---

Cheers

-- Nicola

[1]: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/command.html
[2]: https://www.mail-archive.com/dash@vger.kernel.org/msg01968.html
[3]: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_08_02
[4]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03
[5]:
Old behavior:

$ cd /tmp
$ mkdir -p test1 test2 test3
$ umask 0000
$ touch test1/foo test2/foo test3/foo
$ chmod +x test2/foo
$ PATH=./test1
$ command -v foo
./test1/foo
$ echo $?
0
$ foo
dash: 9: foo: Permission denied
$ echo $?
126
$ test1/foo
dash: 11: test1/foo: Permission denied
$ echo $?
126
$ PATH=./test2
$ command -v foo
./test2/foo
$ echo $?
0
$ foo
$ echo $?
0
$ PATH=./test1:./test2:./test3
$ command -v foo # returns the non-executable file
./test1/foo
$ echo $?
0
$ /bin/chmod +x test1/foo test3/foo
$ command -v foo # returns the cached command
./test1/foo
$ echo $?
0
$ foo # uses the executable file (./test2/foo)
$ echo $?
0
$ /bin/rm test1/foo test2/foo test3/foo
$ /bin/rmdir test1 test2 test3 --ignore-fail-on-non-empty

Patched behavior:

$ cd /tmp
$ mkdir -p test1 test2 test3
$ umask 0000
$ touch test1/foo test2/foo test3/foo
$ chmod +x test2/foo
$ PATH=./test1
$ command -v foo
$ echo $?
127
$ foo
dash: 9: foo: not found
$ echo $?
127
$ test1/foo
dash: 11: test1/foo: Permission denied
$ echo $?
126
$ PATH=./test2
$ command -v foo
./test2/foo
$ echo $?
0
$ foo
$ echo $?
0
$ PATH=./test1:./test2:./test3
$ command -v foo
./test2/foo
$ echo $?
0
$ /bin/chmod +x test1/foo test3/foo
$ command -v foo # returns the cached command
./test2/foo
$ echo $?
0
$ foo
$ echo $?
0
$ /bin/rm test1/foo test2/foo test3/foo
$ /bin/rmdir test1 test2 test3 --ignore-fail-on-non-empty
diff mbox series

Patch

diff --git a/src/exec.c b/src/exec.c
index 87354d4..facaf6b 100644
--- a/src/exec.c
+++ b/src/exec.c
@@ -445,8 +445,8 @@  loop:
                                e = errno;
                        goto loop;
                }
-               e = EACCES;     /* if we fail, this will be the error */
-               if (!S_ISREG(statb.st_mode))
+               e = ENOENT;     /* if we fail, this will be the error */
+               if (!S_ISREG(statb.st_mode) || access(fullname, F_OK|X_OK) != 0)
                        continue;
                if (lpathopt) {         /* this is a %func directory */
                        stalloc(len);
@@ -832,6 +832,9 @@  describe_command(out, command, path, verbose)
                        } while (--j >= 0);
                        p = stackblock();
                }
+               if (access(p, X_OK) != 0) {
+                       goto not_found;
+               }
                if (verbose) {
                        outfmt(
                                out, " is%s %s",
@@ -864,15 +867,18 @@  describe_command(out, command, path, verbose)
                break;

        default:
-               if (verbose) {
-                       outstr(": not found\n", out);
-               }
-               return 127;
+               goto not_found;
        }

 out:
        outc('\n', out);
        return 0;
+
+not_found:
+       if (verbose) {
+               outstr(": not found\n", out);
+       }
+       return 127;
 }

 int