From 94eb1c4dc6556932e1a12a0ce7734512ac95985e Mon Sep 17 00:00:00 2001 From: Ron Yorston Date: Sat, 31 Jul 2021 13:51:57 +0100 Subject: [PATCH] libbb: better coreutils compatibility for realpath Add some tests which coreutils realpath pass but BusyBox realpath fails (bar one). Adjust xmalloc_realpath_coreutils() so the tests pass: - Expand symbolic links before testing whether the last path component exists. - When the link target is a relative path canonicalize it by passing it through xmalloc_realpath_coreutils() as already happens for absolute paths. - Ignore trailing slashes when finding the last path component and correctly handle the case where the only slash is at the start of the path. This requires ignoring superfluous leading slashes. - Undo all changes to the path so error messages from the caller show the original filename. function old new delta xmalloc_realpath_coreutils 214 313 +99 Signed-off-by: Ron Yorston Signed-off-by: Denys Vlasenko --- include/libbb.h | 2 +- libbb/xreadlink.c | 75 ++++++++++++++++++++++++---------------- testsuite/realpath.tests | 45 ++++++++++++++++++++++++ 3 files changed, 91 insertions(+), 31 deletions(-) create mode 100755 testsuite/realpath.tests diff --git a/include/libbb.h b/include/libbb.h index b72576f28..296417dae 100644 --- a/include/libbb.h +++ b/include/libbb.h @@ -578,7 +578,7 @@ DIR *xopendir(const char *path) FAST_FUNC; DIR *warn_opendir(const char *path) FAST_FUNC; char *xmalloc_realpath(const char *path) FAST_FUNC RETURNS_MALLOC; -char *xmalloc_realpath_coreutils(const char *path) FAST_FUNC RETURNS_MALLOC; +char *xmalloc_realpath_coreutils(char *path) FAST_FUNC RETURNS_MALLOC; char *xmalloc_readlink(const char *path) FAST_FUNC RETURNS_MALLOC; char *xmalloc_readlink_or_warn(const char *path) FAST_FUNC RETURNS_MALLOC; /* !RETURNS_MALLOC: it's a realloc-like function */ diff --git a/libbb/xreadlink.c b/libbb/xreadlink.c index a18dd0748..2682f6975 100644 --- a/libbb/xreadlink.c +++ b/libbb/xreadlink.c @@ -123,7 +123,7 @@ char* FAST_FUNC xmalloc_realpath(const char *path) #endif } -char* FAST_FUNC xmalloc_realpath_coreutils(const char *path) +char* FAST_FUNC xmalloc_realpath_coreutils(char *path) { char *buf; @@ -137,32 +137,19 @@ char* FAST_FUNC xmalloc_realpath_coreutils(const char *path) * (the directory must exist). */ if (!buf && errno == ENOENT) { - char *last_slash = strrchr(path, '/'); - if (last_slash) { - *last_slash++ = '\0'; - buf = xmalloc_realpath(path); - if (buf) { - unsigned len = strlen(buf); - buf = xrealloc(buf, len + strlen(last_slash) + 2); - buf[len++] = '/'; - strcpy(buf + len, last_slash); - } - } else { - char *target = xmalloc_readlink(path); - if (target) { - char *cwd; - if (target[0] == '/') { - /* - * $ ln -s /bin/qwe symlink # note: /bin is a link to /usr/bin - * $ readlink -f symlink - * /usr/bin/qwe/target_does_not_exist - * $ realpath symlink - * /usr/bin/qwe/target_does_not_exist - */ - buf = xmalloc_realpath_coreutils(target); - free(target); - return buf; - } + char *target, c, *last_slash; + size_t i; + + target = xmalloc_readlink(path); + if (target) { + /* + * $ ln -s /bin/qwe symlink # note: /bin is a link to /usr/bin + * $ readlink -f symlink + * /usr/bin/qwe + * $ realpath symlink + * /usr/bin/qwe + */ + if (target[0] != '/') { /* * $ ln -s target_does_not_exist symlink * $ readlink -f symlink @@ -170,13 +157,41 @@ char* FAST_FUNC xmalloc_realpath_coreutils(const char *path) * $ realpath symlink * /CURDIR/target_does_not_exist */ - cwd = xrealloc_getcwd_or_warn(NULL); - buf = concat_path_file(cwd, target); + char *cwd = xrealloc_getcwd_or_warn(NULL); + char *tmp = concat_path_file(cwd, target); free(cwd); free(target); - return buf; + target = tmp; + } + buf = xmalloc_realpath_coreutils(target); + free(target); + return buf; + } + + /* ignore leading and trailing slashes */ + while (path[0] == '/' && path[1] == '/') + ++path; + i = strlen(path) - 1; + while (i > 0 && path[i] == '/') + i--; + c = path[i + 1]; + path[i + 1] = '\0'; + + last_slash = strrchr(path, '/'); + if (last_slash == path) + buf = xstrdup(path); + else if (last_slash) { + *last_slash = '\0'; + buf = xmalloc_realpath(path); + *last_slash++ = '/'; + if (buf) { + unsigned len = strlen(buf); + buf = xrealloc(buf, len + strlen(last_slash) + 2); + buf[len++] = '/'; + strcpy(buf + len, last_slash); } } + path[i + 1] = c; } return buf; diff --git a/testsuite/realpath.tests b/testsuite/realpath.tests new file mode 100755 index 000000000..0e68e0279 --- /dev/null +++ b/testsuite/realpath.tests @@ -0,0 +1,45 @@ +#!/bin/sh + +# Realpath tests. +# Copyright 2006 by Natanael Copa +# Copyright 2021 by Ron Yorston +# Licensed under GPLv2, see file LICENSE in this source tree. + +. ./testing.sh + +unset LC_ALL +unset LC_MESSAGES +unset LANG +unset LANGUAGE + +TESTDIR=realpath_testdir +TESTLINK1="link1" +TESTLINK2="link2" + +# create the dir and test files +mkdir -p "./$TESTDIR" +ln -s "./$TESTDIR/not_file" "./$TESTLINK1" +ln -s "./$TESTDIR/not_file/not_dir" "./$TESTLINK2" + +# shell's $PWD may leave symlinks unresolved. +# "pwd" may be a built-in and have the same problem. +# External pwd _can't_ have that problem (current dir on Unix is physical). +pwd=`which pwd` +pwd=`$pwd` +testing "realpath on non-existent absolute path 1" "realpath /not_file" "/not_file\n" "" "" +testing "realpath on non-existent absolute path 2" "realpath /not_file/" "/not_file\n" "" "" +testing "realpath on non-existent absolute path 3" "realpath //not_file" "/not_file\n" "" "" +testing "realpath on non-existent absolute path 4" "realpath /not_dir/not_file 2>&1" "realpath: /not_dir/not_file: No such file or directory\n" "" "" + +testing "realpath on non-existent local file 1" "realpath $TESTDIR/not_file" "$pwd/$TESTDIR/not_file\n" "" "" +testing "realpath on non-existent local file 2" "realpath $TESTDIR/not_dir/not_file 2>&1" "realpath: $TESTDIR/not_dir/not_file: No such file or directory\n" "" "" + +testing "realpath on link to non-existent file 1" "realpath $TESTLINK1" "$pwd/$TESTDIR/not_file\n" "" "" +testing "realpath on link to non-existent file 2" "realpath $TESTLINK2 2>&1" "realpath: $TESTLINK2: No such file or directory\n" "" "" +testing "realpath on link to non-existent file 3" "realpath ./$TESTLINK1" "$pwd/$TESTDIR/not_file\n" "" "" +testing "realpath on link to non-existent file 4" "realpath ./$TESTLINK2 2>&1" "realpath: ./$TESTLINK2: No such file or directory\n" "" "" + +# clean up +rm -r "$TESTLINK1" "$TESTLINK2" "$TESTDIR" + +exit $((FAILCOUNT <= 255 ? FAILCOUNT : 255))