From 2154953362265eb53d3d58de47c47e08fb88440f Mon Sep 17 00:00:00 2001 From: Rob Landley Date: Wed, 23 Nov 2022 15:15:06 -0600 Subject: [PATCH] Update relative path plumbing and extend realpath. More tests. --- lib/lib.c | 41 ++++++++++++++++++++++---------- lib/lib.h | 3 ++- tests/readlink.test | 4 +++- tests/realpath.test | 54 ++++++++++++++++++++++++++++--------------- toys/other/readlink.c | 37 +++++++++++++++++++++++++---- toys/posix/ln.c | 6 +++-- 6 files changed, 105 insertions(+), 40 deletions(-) diff --git a/lib/lib.c b/lib/lib.c index 800bcc15..1db31711 100644 --- a/lib/lib.c +++ b/lib/lib.c @@ -1077,30 +1077,47 @@ char *fileunderdir(char *file, char *dir) return rc ? s2 : 0; } -// return (malloced) relative path to get from "from" to "to" -char *relative_path(char *from, char *to) +void *mepcpy(void *to, void *from, unsigned long len) +{ + memcpy(to, from, len); + + return ((char *)to)+len; +} + +// return (malloced) relative path to get between two normalized absolute paths +// normalized: no duplicate / or trailing / or .. or . (symlinks optional) +char *relative_path(char *from, char *to, int abs) { char *s, *ret = 0; int i, j, k; - if (!(from = xabspath(from, 0))) return 0; - if (!(to = xabspath(to, 0))) goto error; + if (abs) { + if (!(from = xabspath(from, 0))) return 0; + if (!(to = xabspath(to, 0))) goto error; + } - // skip common directories from root - for (i = j = 0; from[i] && from[i] == to[i]; i++) if (to[i] == '/') j = i+1; + for (i = j = 0;; i++) { + if (!from[i] || !to[i]) { + if (from[i]=='/' || to[i]=='/' || from[i]==to[i]) j = i; + break; + } + if (from[i] != to[i]) break; + if (from[i] == '/') j = i; + } // count remaining destination directories for (i = j, k = 0; from[i]; i++) if (from[i] == '/') k++; - - if (!k) ret = xstrdup(to+j); + if (!k) ret = xstrdup(to[j] ? to+j : "."); else { - s = ret = xmprintf("%*c%s", 3*k, ' ', to+j); - while (k--) memcpy(s+3*k, "../", 3); + s = ret = xmprintf("%*c%s", 3*k-!!k, ' ', to+j); + for (i = 0; ilink (recursive)" "readlink link" "link\n" "" "" testing "-f link->link (recursive)" \ "readlink -f link 2>/dev/null || echo yes" "yes\n" "" "" -testing "-q notlink" "readlink -q file || echo yes" "yes\n" "" "" +testing "-q notlink" "readlink -q file 2>&1 || echo yes" "yes\n" "" "" testing "-q link" "readlink -q link && echo yes" "link\nyes\n" "" "" testing "-q notfound" "readlink -q notfound || echo yes" "yes\n" "" "" testing "-e found" "readlink -e file" "$APWD/file\n" "" "" @@ -40,6 +40,8 @@ testing "-e notfound" \ testing "-nf ." "readlink -nf ." "$APWD" "" "" # -n means no newline at _end_. I.E. on last argument. toyonly testcmd '-nf multiple args' '-n link link' "link\nlink" '' '' +testcmd '-nz' '-nz link' 'link' '' '' +testcmd '-z' '-z link' 'link\0' '' '' mkdir sub && ln -s . here && diff --git a/tests/realpath.test b/tests/realpath.test index c42fd435..e8267589 100755 --- a/tests/realpath.test +++ b/tests/realpath.test @@ -6,34 +6,50 @@ TOP="$(readlink -f .)" -testcmd '' '.' "$TOP\n" '' '' -#testcmd '-z' '-z . | tr "\0" X' "${TOP}X" '' '' touch file -testcmd 'file' 'file' "$TOP/file\n" '' '' mkdir -p one/two/three +ln -s ./one uno +ln -s one/two dos + +testcmd '' '.' "$TOP\n" '' '' +testcmd 'missing' 'missing' "$TOP/missing\n" '' '' +testcmd 'missing2' 'missing/sub 2>/dev/null || echo err' 'err\n' '' '' +testcmd '-z' '-z . | tr "\0" X' "${TOP}X" '' '' +testcmd 'file' 'file' "$TOP/file\n" '' '' testcmd 'dir' 'one/two/three' "$TOP/one/two/three\n" '' '' -#testcmd '--relative-to' '. --relative-to=one/two/three' '../../..\n' '' '' -#testcmd '--relative-base' 'one one/two one/two/three --relative-base=one/two' \ -# "$TOP/one\n.\nthree\n" '' '' -#testcmd '--relative-base stomps --relative-to' \ -# '--relative-to=.. --relative-base=one/two one' "$TOP/one\n" '' '' +testcmd '--relative-to' '. --relative-to=one/two/three' '../../..\n' '' '' +testcmd '--relative-to2' \ + '-m --relative-to=missing/that/ uno/../dos/linux/../../bingeley/bongeley/beep' \ + '../../one/bingeley/bongeley/beep\n' '' '' +testcmd '--relative-to3' '-m walrus --relative-to walrus' '.\n' '' '' +testcmd '--relative-to4' '"$PWD" --relative-to one' '..\n' '' '' +testcmd '--relative-base' 'one one/two one/two/three --relative-base=one/two' \ + "$TOP/one\n.\nthree\n" '' '' +testcmd '--relative-base stomps --relative-to' \ + '--relative-to=.. --relative-base=one/two one' "$TOP/one\n" '' '' +testcmd '-m with relative-base1' '-m --relative-base wurble wurble/poing' \ + 'poing\n' '' '' +testcmd '-m with relative-base2' '-sm --relative-base wurble .' "$PWD\n" '' '' +testcmd '-m with relative-base3' '-m --relative-base wurble wurble wurble/' \ + '.\n.\n' '' '' testcmd 'missing defaults to -m' 'missing' "$TOP/missing\n" '' '' testcmd 'missing -e' '-e missing 2>/dev/null || echo ok' 'ok\n' '' '' +testcmd '-L' '-L dos/../one' "$TOP/one\n" '' '' # The -s tests use $PWD instead of $TOP because symlinks in path _to_ here # should not be resolved either. The shell exports $PWD: use it. -ln -s ./one uno -#testcmd '-s' '-s uno/two' "$PWD/uno/two\n" '' '' -ln -s one/two dos -#testcmd '-s link/..' '-es dos/three' "$PWD/dos/three\n" '' '' -#testcmd '-s .. eats symlink' '-s dos/..' "$PWD\n" '' '' +testcmd '-s' '-s uno/two' "$PWD/uno/two\n" '' '' +testcmd '-s link/..' '-es dos/three' "$PWD/dos/three\n" '' '' +testcmd '-s .. eats symlink' '-s dos/..' "$PWD\n" '' '' # In toybox this test is consistent with the previous one -#toyonly testing '-s .. eats symlink in $PWD' \ -# 'cd dos && realpath -s ..' "$PWD\n" '' '' +toyonly testing '-s .. eats symlink in $PWD' \ + 'cd dos && realpath -s ..' "$PWD\n" '' '' # Logically -es means the _symlink_ should exist, but match behavior... ln -s missing dangling -#testcmd '-es dangling symlink' '-es dangling 2>/dev/null || echo ok' \ -# 'ok\n' '' '' -#testcmd '-ms' '-ms dangling/../dos/../one/two' "$PWD/one/two\n" '' '' +testcmd '-es dangling symlink' '-es dangling 2>/dev/null || echo ok' \ + 'ok\n' '' '' +testcmd '-ms' '-ms dangling/../dos/../one/two' "$PWD/one/two\n" '' '' ln -s ../two/.. one/two/ichi -#testcmd '-es' '-es one/two/ichi/two/ichi/two' "$PWD/one/two/ichi/two/ichi/two\n" '' '' +testcmd '-es' '-es one/two/ichi/two/ichi/two' "$PWD/one/two/ichi/two/ichi/two\n" '' '' + +rm -rf file one uno dos diff --git a/toys/other/readlink.c b/toys/other/readlink.c index be596c58..4fc3f976 100644 --- a/toys/other/readlink.c +++ b/toys/other/readlink.c @@ -38,7 +38,7 @@ config REALPATH -q Quiet (no error messages) -s Don't expand symlinks -z NUL instead of newline - --relative-base Paths below DIR aren't absolute + --relative-base If path under DIR trim off prefix */ /* TODO @@ -72,22 +72,49 @@ GLOBALS( static char *resolve(char *arg) { int flags = FLAG(e) ? ABS_FILE : FLAG(m) ? 0 : ABS_PATH; - char *s; + char *s, *ss = 0, *dd = 0; if (FLAG(s)) flags |= ABS_KEEP; + else if (FLAG(L)) arg = dd = xabspath(arg, ABS_KEEP); if (!(s = xabspath(arg, flags)) && !FLAG(q)) perror_msg("%s", arg); + free(dd); + + // Trim off this prefix if path under here + + if (TT.relative_base) { + ss = s; + if (strstart(&ss, TT.relative_base) && (!*ss || *ss=='/')) { + if (*ss=='/') ss++; + ss = xstrdup(!*ss ? "." : ss); + } else ss = 0; + } else if (TT.R) ss = relative_path(TT.R, s, 0); + if (ss) { + free(s); + s = ss; + } return s; } +// Resolve command line arguments that can't take part in their own resolution +static char *presolve(char **s) +{ + char *ss = *s; + + if (ss) { + *s = 0; + if (!(*s = resolve(ss))) xexit(); + } + + return ss; +} + // Uses realpath flag context: flags (1 = resolve, 2 = -n) static void do_paths(int flags) { char **arg, *s; - if (TT.R && !(TT.R = resolve(TT.R))) xexit(); - if (TT.relative_base && !(TT.relative_base = resolve(TT.relative_base))) - xexit(); + if (!presolve(&TT.relative_base)) presolve(&TT.R); for (arg = toys.optargs; *arg; arg++) { if (!(s = (flags&1) ? resolve(*arg) : xreadlink(*arg))) toys.exitval = 1; diff --git a/toys/posix/ln.c b/toys/posix/ln.c index 3cd5c7b8..65e1d46a 100644 --- a/toys/posix/ln.c +++ b/toys/posix/ln.c @@ -52,13 +52,15 @@ void ln_main(void) } else buf.st_mode = 0; for (i=0; i