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 <rmy@pobox.com>
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
This commit is contained in:
Ron Yorston 2021-07-31 13:51:57 +01:00 committed by Denys Vlasenko
parent 86ba007b84
commit 94eb1c4dc6
3 changed files with 91 additions and 31 deletions

View File

@ -578,7 +578,7 @@ DIR *xopendir(const char *path) FAST_FUNC;
DIR *warn_opendir(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(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(const char *path) FAST_FUNC RETURNS_MALLOC;
char *xmalloc_readlink_or_warn(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 */ /* !RETURNS_MALLOC: it's a realloc-like function */

View File

@ -123,7 +123,7 @@ char* FAST_FUNC xmalloc_realpath(const char *path)
#endif #endif
} }
char* FAST_FUNC xmalloc_realpath_coreutils(const char *path) char* FAST_FUNC xmalloc_realpath_coreutils(char *path)
{ {
char *buf; char *buf;
@ -137,32 +137,19 @@ char* FAST_FUNC xmalloc_realpath_coreutils(const char *path)
* (the directory must exist). * (the directory must exist).
*/ */
if (!buf && errno == ENOENT) { if (!buf && errno == ENOENT) {
char *last_slash = strrchr(path, '/'); char *target, c, *last_slash;
if (last_slash) { size_t i;
*last_slash++ = '\0';
buf = xmalloc_realpath(path); target = xmalloc_readlink(path);
if (buf) { if (target) {
unsigned len = strlen(buf); /*
buf = xrealloc(buf, len + strlen(last_slash) + 2); * $ ln -s /bin/qwe symlink # note: /bin is a link to /usr/bin
buf[len++] = '/'; * $ readlink -f symlink
strcpy(buf + len, last_slash); * /usr/bin/qwe
} * $ realpath symlink
} else { * /usr/bin/qwe
char *target = xmalloc_readlink(path); */
if (target) { if (target[0] != '/') {
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;
}
/* /*
* $ ln -s target_does_not_exist symlink * $ ln -s target_does_not_exist symlink
* $ readlink -f symlink * $ readlink -f symlink
@ -170,13 +157,41 @@ char* FAST_FUNC xmalloc_realpath_coreutils(const char *path)
* $ realpath symlink * $ realpath symlink
* /CURDIR/target_does_not_exist * /CURDIR/target_does_not_exist
*/ */
cwd = xrealloc_getcwd_or_warn(NULL); char *cwd = xrealloc_getcwd_or_warn(NULL);
buf = concat_path_file(cwd, target); char *tmp = concat_path_file(cwd, target);
free(cwd); free(cwd);
free(target); 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; return buf;

45
testsuite/realpath.tests Executable file
View File

@ -0,0 +1,45 @@
#!/bin/sh
# Realpath tests.
# Copyright 2006 by Natanael Copa <n@tanael.org>
# Copyright 2021 by Ron Yorston <rmy@pobox.com>
# 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))