> 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
