[PATCH v2] exec: Print absolute paths for command location queries

Kerin Millar <[email protected]> Sun, 7 Jun 2026 01:13:26 +0100
Newsgroups org.kernel.vger.dash
Message-ID <[email protected]>
POSIX requires that, when reporting the location of a utility found by
searching PATH (or one named with a slash), the pathname written by
type, command -v and command -V be absolute:

    Executable utilities, regular built-in utilities, command_names
    including a <slash> character, and any implementation-provided
    functions that are found using the PATH variable [...] shall be
    written as absolute pathnames.

Presently, dash fails to conform in this regard. Address this issue by
prefixing relative pathnames with the logical current working directory,
provided that it still resolves to the current directory, and with the
physical current working directory otherwise. If the current working
directory cannot be determined, print the relative pathname instead.

Link: https://lore.kernel.org/dash/[email protected]/
Reported-by: Christoph Anton Mitterer <[email protected]>
Suggested-by: Harald van Dijk <[email protected]>
Signed-off-by: Kerin Millar <[email protected]>
---
 src/cd.c   | 40 ++++++++++++++++++++++++++++++++++++----
 src/cd.h   |  1 +
 src/exec.c | 30 ++++++++++++++++++++++++++----
 3 files changed, 63 insertions(+), 8 deletions(-)

diff --git a/src/cd.c b/src/cd.c
index bcd1484..9766cfb 100644
--- a/src/cd.c
+++ b/src/cd.c
@@ -261,12 +261,11 @@ updatepwd(const char *dir)
 
 
 /*
- * Find out what the current directory is. If we already know the current
- * directory, this routine returns immediately.
+ * Find out what the physical current directory is.
  */
-inline
+
 STATIC char *
-getpwd()
+getphyscwd()
 {
 #ifdef __GLIBC__
 	char *dir = getcwd(0, 0);
@@ -280,6 +279,39 @@ getpwd()
 		return savestr(buf);
 #endif
 
+	return NULL;
+}
+
+/*
+ * Return the logical current directory if it still resolves to the physical
+ * current directory.  Otherwise, return the physical current directory, or
+ * NULL if it cannot be determined.
+ */
+
+char *
+getlogcwd()
+{
+	struct stat64 st1, st2;
+
+	if (*curdir == '/' && !stat64(curdir, &st1) && !stat64(dotdir, &st2) &&
+	    st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino)
+		return savestr(curdir);
+
+	return getphyscwd();
+}
+
+/*
+ * Find out what the current directory is.
+ */
+
+STATIC char *
+getpwd()
+{
+	char *dir = getphyscwd();
+
+	if (dir)
+		return dir;
+
 	sh_warnx("getcwd() failed: %s", strerror(errno));
 	return nullstr;
 }
diff --git a/src/cd.h b/src/cd.h
index 8763161..d22bf84 100644
--- a/src/cd.h
+++ b/src/cd.h
@@ -32,4 +32,5 @@
 
 int	cdcmd(int, char **);
 int	pwdcmd(int, char **);
+char	*getlogcwd(void);
 void	setpwd(const char *, int);
diff --git a/src/exec.c b/src/exec.c
index 6fe0fed..903b63d 100644
--- a/src/exec.c
+++ b/src/exec.c
@@ -58,6 +58,7 @@
 #include "redir.h"
 #include "eval.h"
 #include "exec.h"
+#include "cd.h"
 #include "builtins.h"
 #include "var.h"
 #include "options.h"
@@ -779,6 +780,28 @@ typecmd(int argc, char **argv)
 	return err;
 }
 
+/*
+ * Print the location of a command, as reported by type, command -v and
+ * command -V.
+ */
+
+static void printpath(struct output *out, const char *p)
+{
+	char *pwd;
+
+	if (*p != '/') {
+		pwd = getlogcwd();
+		if (pwd) {
+			outstr(pwd, out);
+			/* Do not append another slash to / or //. */
+			if (pwd[strlen(pwd) - 1] != '/')
+				outc('/', out);
+			free(pwd);
+		}
+	}
+	outstr(p, out);
+}
+
 static int describe_command(struct output *out, char *command,
                             const char *path, int verbose)
 {
@@ -840,12 +863,11 @@ static int describe_command(struct output *out, char *command,
 		}
 		if (verbose) {
 			outfmt(
-				out, " is%s %s",
-				cmdp ? " a tracked alias for" : nullstr, p
+				out, " is%s ",
+				cmdp ? " a tracked alias for" : nullstr
 			);
-		} else {
-			outstr(p, out);
 		}
+		printpath(out, p);
 		break;
 	}
 
-- 
2.53.0