> I'm torn on what would be best.

The more I think about it, the uglier I think readlink/realpath are
altogether. I don't see why they were ever separate utilities.

Attached is a patch for what I mentioned I tried, all flags implemented
in readlink, and realpath is a link handled like [ -> test. See what you
think. I still really don't like the way it screws up sbase-box's usage
message.

-Brad
>From 87614aac68876cf5c04071aa2fadbfb3254a23c1 Mon Sep 17 00:00:00 2001
From: Brad Barden <[email protected]>
Date: Fri, 20 Nov 2015 13:54:43 -0600
Subject: [PATCH 1/2] add realpathm() to libutil

---
 Makefile            |   1 +
 libutil/realpathm.c | 103 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 104 insertions(+)
 create mode 100644 libutil/realpathm.c

diff --git a/Makefile b/Makefile
index c2d8266..c4ca41d 100644
--- a/Makefile
+++ b/Makefile
@@ -59,6 +59,7 @@ LIBUTILSRC =\
 	libutil/parseoffset.c\
 	libutil/putword.c\
 	libutil/reallocarray.c\
+	libutil/realpathm.c\
 	libutil/recurse.c\
 	libutil/rm.c\
 	libutil/sha1.c\
diff --git a/libutil/realpathm.c b/libutil/realpathm.c
new file mode 100644
index 0000000..7636383
--- /dev/null
+++ b/libutil/realpathm.c
@@ -0,0 +1,103 @@
+/* See LICENSE file for copyright and license details. */
+#include <errno.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../util.h"
+
+char *
+realpathm(const char * path, char * resolvedpath) {
+	const char *p = path;
+	char buf1[PATH_MAX] = {0}, buf2[PATH_MAX] = {0},
+			 *a = buf1, *b = buf2, *ret = NULL;
+	int i = 0, m = 0;
+
+	if (!p)
+		errno = EINVAL;
+		goto badpath;
+
+	if (!*p) {
+		errno = ENOENT;
+		goto badpath;
+	}
+
+	if (strnlen(path, PATH_MAX) == PATH_MAX) {
+		errno = ENAMETOOLONG;
+		goto badpath;
+	}
+
+	if (*p == '/') {
+		p++;
+	} else { 
+		if (!realpath(".", a)) {
+			goto badpath;
+		}
+	}
+	strcat(a, "/");
+
+	while (*p) {
+		i = strlen(a);
+		while (*p && *p != '/') {
+			if (i >= PATH_MAX) {
+				errno = ENAMETOOLONG;
+				goto badpath;
+			}
+			a[i++] = *p++;
+		}
+		a[i] = '\0';
+		if (*p)
+			++p;
+		if (i > 2 && !strcmp(&a[i-3], "/..")) {
+			if (m)
+				--m;
+			if (i == 3) {
+				strcpy(a, "/");
+				i = 1;
+			}
+			else {
+				i -= 3;
+				while (i > 0 && a[--i] != '/') ;
+				a[i] = '\0';
+			}
+		} else if (i > 1 && !strcmp(&a[i-2], "/.")) {
+			if (i == 2) {
+				strcpy(a, "/");
+				i = 1;
+			} else {
+				i -= 2;
+				a[i] = '\0';
+			}
+		} else if (a[i] == '/') {
+			if (i > 1)
+				a[--i] = '\0';
+		} else if (m) {
+				++m;
+		} else if (!realpath(a, b)) {
+			if (errno == ENOENT) {
+				m = 1;
+			} else {
+				goto badpath;
+			}
+		} else if (a == buf1) {
+			a = buf2;
+			b = buf1;
+		} else {
+			a = buf1;
+			b = buf2;
+		}
+		if (*p && a[i-1] != '/' && strlcat(a, "/", PATH_MAX) >= PATH_MAX) {
+			errno = ENAMETOOLONG;
+			goto badpath;
+		}
+	}
+	i = strlen(a)-1;
+	if (a[i] == '/')
+		a[i] = '\0';
+	if (!(ret = resolvedpath))
+		ret = emalloc(PATH_MAX);
+	estrlcpy(ret, a, PATH_MAX);
+
+badpath:
+	return ret;
+}
-- 
2.3.6

>From 05ef3f271774b2989273c177ce83d1a320fa8dc5 Mon Sep 17 00:00:00 2001
From: Brad Barden <[email protected]>
Date: Fri, 20 Nov 2015 14:12:23 -0600
Subject: [PATCH 2/2] restore readlink -e and -m flags, add realpath

-e and -m are added back to the readlink utility, now working as
intended

when called as 'realpath', readlink's default behavior is now
readlink -f

realpath is implemented with a link to readlink

man page for readlink is updated and man page for realpath is added
---
 Makefile   |   5 +--
 readlink.1 |  52 +++++++++++++++++++----------
 readlink.c | 111 ++++++++++++++++++++++++++++++++++++++++++++++++-------------
 realpath.1 |  16 +++++++++
 4 files changed, 141 insertions(+), 43 deletions(-)
 create mode 100644 realpath.1

diff --git a/Makefile b/Makefile
index c4ca41d..93babdd 100644
--- a/Makefile
+++ b/Makefile
@@ -190,7 +190,7 @@ $(LIBUTIL): $(LIBUTILOBJ)
 install: all
 	mkdir -p $(DESTDIR)$(PREFIX)/bin
 	cp -f $(BIN) $(DESTDIR)$(PREFIX)/bin
-	cd $(DESTDIR)$(PREFIX)/bin && ln -f test [ && chmod 755 $(BIN)
+	cd $(DESTDIR)$(PREFIX)/bin && ln -f readlink realpath && ln -f test [ && chmod 755 $(BIN)
 	mkdir -p $(DESTDIR)$(MANPREFIX)/man1
 	for m in $(MAN); do sed "s/^\.Os sbase/.Os sbase $(VERSION)/g" < "$$m" > $(DESTDIR)$(MANPREFIX)/man1/"$$m"; done
 	cd $(DESTDIR)$(MANPREFIX)/man1 && chmod 644 $(MAN)
@@ -219,8 +219,9 @@ sbase-box: $(LIB) $(SRC)
 	echo 'int main(int argc, char *argv[]) { char *s = basename(argv[0]);'                               >> build/[email protected]
 	echo 'if(!strcmp(s,"sbase-box")) { argc--; argv++; s = basename(argv[0]); } if(0) ;'                 >> build/[email protected]
 	echo "else if (!strcmp(s, \"[\")) return test_main(argc, argv);"                                     >> build/[email protected]
+	echo "else if (!strcmp(s, \"realpath\")) return readlink_main(argc, argv);"                          >> build/[email protected]
 	for f in $(SRC); do echo "else if(!strcmp(s, \"$${f%.c}\")) return $${f%.c}_main(argc, argv);"; done >> build/[email protected]
-	echo 'else { fputs("[ ", stdout);'                                                                   >> build/[email protected]
+	echo 'else { fputs("[ realpath ", stdout);'                                                          >> build/[email protected]
 	for f in $(SRC); do echo "fputs(\"$${f%.c} \", stdout);"; done                                       >> build/[email protected]
 	echo 'putchar(0xa); }; return 0; }'                                                                  >> build/[email protected]
 	$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -o $@ build/*.c $(LIB)
diff --git a/readlink.1 b/readlink.1
index 46b4cad..3de0855 100644
--- a/readlink.1
+++ b/readlink.1
@@ -1,32 +1,48 @@
-.Dd 2015-11-16
+.Dd 2015-11-14
 .Dt READLINK 1
 .Os sbase
 .Sh NAME
 .Nm readlink
-.Nd print symbolic link target or canonical file name
+.Nd Print symbolic link targets or canonical paths
 .Sh SYNOPSIS
 .Nm
-.Op Fl f
-.Op Fl n
-.Ar path
+.Op Fl e | f | m
+.Op Fl n | z
+.Op Fl q
+.Ar file
+.Op file ...
 .Sh DESCRIPTION
 .Nm
-writes the target of
-.Ar path ,
-if it is a symbolic link, to stdout.
-If not,
+prints the target of each
+.Ar file
+which must be an existing symbolic link. With the
+.Fl e, f,
+or
+.Fl m
+flags,
 .Nm
-exits with a non-zero return value.
+instead prints a canonicalized path.
 .Sh OPTIONS
 .Bl -tag -width Ds
-.It Fl f
-Canonicalize
-.Ar path ,
-which needn't be a symlink,
-by recursively following every symlink in its path components.
-.It Fl n
-Do not print the terminating newline.
+.It Fl e | f | m
+All | All but the last | None of the path components must exist.
+.Fl f
+is identical to
+.Xr realpath 1
+behavior.
+.It Fl n | Fl z
+Nothing | NUL is printed after each resolved path. By default, a newline is
+printed.
+.It Fl q
+Suppress error messages. Exit status will indicate if errors were encountered.
 .El
+.Sh EXIT STATUS
+.Bl -tag -width Ds
+.It 0
+All paths were successfully resolved.
+.It 1
+An error was encountered resolving at least one of the given paths.
 .Sh SEE ALSO
-.Xr readlink 2 ,
+.Xr readlink 3
 .Xr realpath 3
+.Xr realpath 1
diff --git a/readlink.c b/readlink.c
index d059584..731b580 100644
--- a/readlink.c
+++ b/readlink.c
@@ -1,54 +1,119 @@
 /* See LICENSE file for copyright and license details. */
+#include <sys/stat.h>
+#include <errno.h>
+#include <libgen.h>
 #include <limits.h>
-#include <stdio.h>
 #include <stdlib.h>
+#include <stdio.h>
 #include <string.h>
 #include <unistd.h>
 
 #include "util.h"
 
+static int efmflag = 0;
+static int nflag = 0;
+static int qflag = 0;
+static int zflag = 0;
+
 static void
 usage(void)
 {
-	eprintf("usage: %s [-fn] path\n", argv0);
+	eprintf("usage: %s [-e | -f | -m] [-z | -n] [-q] file [file ...]\n", argv0);
 }
 
 int
 main(int argc, char *argv[])
 {
-	char buf[PATH_MAX];
-	ssize_t n;
-	int nflag = 0, fflag = 0;
+	int i, n, e = 0, ret = 0;
+	char rp[PATH_MAX], le, *p;
+	struct stat s;
+
+	if (!strcmp(basename(argv[0]), "realpath"))
+		efmflag = 'f';
+	else
+		efmflag = 'L';
 
 	ARGBEGIN {
+	case 'e':
 	case 'f':
-		fflag = ARGC();
+	case 'm':
+		efmflag = ARGC();
 		break;
 	case 'n':
 		nflag = 1;
 		break;
+	case 'q':
+		qflag = 1;
+		break;
+	case 'z':
+		zflag = 1;
+		break;
 	default:
 		usage();
 	} ARGEND
 
-	if (argc != 1)
+	if (!argc)
 		usage();
 
-	if (strlen(argv[0]) >= PATH_MAX)
-		eprintf("path too long\n");
-
-	if (fflag) {
-		if (!realpath(argv[0], buf))
-			eprintf("realpath %s:", argv[0]);
-	} else {
-		if ((n = readlink(argv[0], buf, PATH_MAX - 1)) < 0)
-			eprintf("readlink %s:", argv[0]);
-		buf[n] = '\0';
+	if (zflag)
+		le = '\0';
+	else if (!nflag)
+		le = '\n';
+	for (i = 0; i < argc; i++) {
+		switch (efmflag) {
+		case 'e':
+			if (!realpath(argv[i], rp)) {
+				if (!qflag)
+					weprintf("realpath '%s':", argv[i]);
+				e = 1;
+			}
+			break;
+		case 'm':
+			if (!realpathm(argv[i], rp)) {
+				if (!qflag)
+					weprintf("realpathm '%s':", argv[i]);
+				e = 1;
+			}
+			break;
+		case 'f':
+			if (!realpathm(argv[i], rp)) {
+				if (!qflag)
+					weprintf("realpathm '%s':", argv[i]);
+				e = 1;
+			} else {
+				p = strrchr(rp, '/');
+				if (p == rp)
+					p = NULL;
+			}
+			if (p) {
+				*p = '\0';
+				if (stat(rp, &s) < 0) {
+					if (!qflag)
+						weprintf("stat '%s':", rp);
+					e = 1;
+					break;
+				} else {
+					*p = '/';
+				}
+			}
+			break;
+		case 'L':
+			if ((n = readlink(argv[i], rp, PATH_MAX-1)) < 0) {
+				if (!qflag)
+					weprintf("readlink '%s':", argv[i]);
+				e = 1;
+			} else {
+				rp[n] = '\0';
+			}
+			break;
+		}
+		if (e) {
+			ret = 1;
+			e = 0;
+		} else {
+			fputs(rp, stdout);
+			putchar(le);
+		}
 	}
-
-	fputs(buf, stdout);
-	if (!nflag)
-		putchar('\n');
-
-	return fshut(stdout, "<stdout>");
+	return ret;
 }
diff --git a/realpath.1 b/realpath.1
new file mode 100644
index 0000000..e1f5f7e
--- /dev/null
+++ b/realpath.1
@@ -0,0 +1,16 @@
+.Dd 2015-11-14
+.Dt REALPATH 1
+.Os sbase
+.Sh NAME
+.Nm realpath
+.Nd Print resolved canonical paths
+.Sh SYNOPSIS
+.Nm
+behaves identically to
+.Xr readlink 1
+with the
+.Fl f
+flag. All other flags are supported.
+.Sh SEE ALSO
+.Xr readlink 1
+.Xr realpath 3
-- 
2.3.6

Reply via email to