From fee0c9631470e8eb10df25544f6661d0beb43a7a Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Mon, 30 Jul 2012 11:36:06 -0700 Subject: [PATCH] util: New function follow_symlinks(). It will acquire its first user in an upcoming commit. Signed-off-by: Ben Pfaff --- lib/util.c | 85 ++++++++++++++++++++++++++++++++++++++++ lib/util.h | 3 ++ tests/file_name.at | 97 ++++++++++++++++++++++++++++++++++++++++++++++ tests/test-util.c | 13 +++++++ 4 files changed, 198 insertions(+) diff --git a/lib/util.c b/lib/util.c index cbcf693e..cd99142c 100644 --- a/lib/util.c +++ b/lib/util.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include "byte-order.h" #include "coverage.h" @@ -647,6 +648,90 @@ abs_file_name(const char *dir, const char *file_name) } } +/* Like readlink(), but returns the link name as a null-terminated string in + * allocated memory that the caller must eventually free (with free()). + * Returns NULL on error, in which case errno is set appropriately. */ +char * +xreadlink(const char *filename) +{ + size_t size; + + for (size = 64; ; size *= 2) { + char *buf = xmalloc(size); + ssize_t retval = readlink(filename, buf, size); + int error = errno; + + if (retval >= 0 && retval < size) { + buf[retval] = '\0'; + return buf; + } + + free(buf); + if (retval < 0) { + errno = error; + return NULL; + } + } +} + +/* Returns a version of 'filename' with symlinks in the final component + * dereferenced. This differs from realpath() in that: + * + * - 'filename' need not exist. + * + * - If 'filename' does exist as a symlink, its referent need not exist. + * + * - Only symlinks in the final component of 'filename' are dereferenced. + * + * The caller must eventually free the returned string (with free()). */ +char * +follow_symlinks(const char *filename) +{ + struct stat s; + char *fn; + int i; + + fn = xstrdup(filename); + for (i = 0; i < 10; i++) { + char *linkname; + char *next_fn; + + if (lstat(fn, &s) != 0 || !S_ISLNK(s.st_mode)) { + return fn; + } + + linkname = xreadlink(fn); + if (!linkname) { + VLOG_WARN("%s: readlink failed (%s)", filename, strerror(errno)); + return fn; + } + + if (linkname[0] == '/') { + /* Target of symlink is absolute so use it raw. */ + next_fn = linkname; + } else { + /* Target of symlink is relative so add to 'fn''s directory. */ + char *dir = dir_name(fn); + + if (!strcmp(dir, ".")) { + next_fn = linkname; + } else { + char *separator = dir[strlen(dir) - 1] == '/' ? "" : "/"; + next_fn = xasprintf("%s%s%s", dir, separator, linkname); + free(linkname); + } + + free(dir); + } + + free(fn); + fn = next_fn; + } + + VLOG_WARN("%s: too many levels of symlinks", filename); + free(fn); + return xstrdup(filename); +} /* Pass a value to this function if it is marked with * __attribute__((warn_unused_result)) and you genuinely want to ignore diff --git a/lib/util.h b/lib/util.h index 809ae9c5..60ec737e 100644 --- a/lib/util.h +++ b/lib/util.h @@ -219,6 +219,9 @@ char *dir_name(const char *file_name); char *base_name(const char *file_name); char *abs_file_name(const char *dir, const char *file_name); +char *xreadlink(const char *filename); +char *follow_symlinks(const char *filename); + void ignore(bool x OVS_UNUSED); int log_2_floor(uint32_t); int log_2_ceil(uint32_t); diff --git a/tests/file_name.at b/tests/file_name.at index e0b43dca..aee4070c 100644 --- a/tests/file_name.at +++ b/tests/file_name.at @@ -24,3 +24,100 @@ CHECK_FILE_NAME([dir/file], [dir], [file]) CHECK_FILE_NAME([dir/file/], [dir], [file]) CHECK_FILE_NAME([dir/file//], [dir], [file]) CHECK_FILE_NAME([///foo], [/], [foo]) + +AT_BANNER([test follow_symlinks function]) + +m4_define([CHECK_FOLLOW], + [echo "check $1 -> $2" + AT_CHECK_UNQUOTED([test-util follow-symlinks "$1"], [0], [$2 +]) + echo]) + +AT_SETUP([follow_symlinks - relative symlinks]) +: > target +ln -s target source +AT_SKIP_IF([test ! -h source]) +CHECK_FOLLOW([source], [target]) + +mkdir dir +ln -s target2 dir/source2 +CHECK_FOLLOW([dir/source2], [dir/target2]) + +mkdir dir/dir2 +ln -s dir/b a +ln -s c dir/b +ln -s dir2/d dir/c +CHECK_FOLLOW([a], [dir/dir2/d]) +AT_CLEANUP + +AT_SETUP([follow_symlinks - absolute symlinks]) +: > target +ln -s "`pwd`/target" source +AT_SKIP_IF([test ! -h source]) +CHECK_FOLLOW([source], [`pwd`/target]) + +mkdir dir +ln -s "`pwd`/dir/target2" dir/source2 +CHECK_FOLLOW([dir/source2], [`pwd`/dir/target2]) + +mkdir dir/dir2 +ln -s "`pwd`/dir/b" a +ln -s "`pwd`/dir/c" dir/b +ln -s "`pwd`/dir/dir2/d" dir/c +CHECK_FOLLOW([a], [`pwd`/dir/dir2/d]) +AT_CLEANUP + +AT_SETUP([follow_symlinks - symlinks to directories]) +mkdir target +ln -s target source +AT_SKIP_IF([test ! -h source]) +ln -s target/ source2 +CHECK_FOLLOW([source], [target]) +CHECK_FOLLOW([source2], [target/]) + +# follow_symlinks() doesn't expand symlinks in the middle of a name. +: > source/x +CHECK_FOLLOW([source/x], [source/x]) +AT_CLEANUP + +AT_SETUP([follow_symlinks - nonexistent targets]) +ln -s target source +AT_SKIP_IF([test ! -h source]) +CHECK_FOLLOW([source], [target]) +CHECK_FOLLOW([target], [target]) +CHECK_FOLLOW([target], [target]) +AT_CLEANUP + +AT_SETUP([follow_symlinks - regular files]) +touch x +CHECK_FOLLOW([x], [x]) +AT_CLEANUP + +AT_SETUP([follow_symlinks - device targets]) +AT_SKIP_IF([test ! -e /dev/null]) +AT_SKIP_IF([test ! -e /dev/full]) +ln -s /dev/null x +ln -s /dev/full y +CHECK_FOLLOW([x], [/dev/null]) +CHECK_FOLLOW([y], [/dev/full]) +AT_CLEANUP + +AT_SETUP([follow_symlinks - nonexistent files]) +CHECK_FOLLOW([nonexistent], [nonexistent]) +CHECK_FOLLOW([a/b/c], [a/b/c]) +CHECK_FOLLOW([/a/b/c], [/a/b/c]) +CHECK_FOLLOW([//a/b/c], [//a/b/c]) +AT_CLEANUP + +AT_SETUP([follow_symlinks - symlink loop]) +ln -s a b +AT_SKIP_IF([test ! -h b]) +ln -s b a +AT_SKIP_IF([test ! -h a]) + +AT_CHECK([test-util follow-symlinks a], [0], [a +], [stderr]) +AT_CHECK([sed 's/^[[^|]]*|//' stderr], [0], + [00001|util|WARN|a: too many levels of symlinks +]) +AT_CLEANUP diff --git a/tests/test-util.c b/tests/test-util.c index 56c5b28f..71a7f218 100644 --- a/tests/test-util.c +++ b/tests/test-util.c @@ -267,6 +267,18 @@ test_bitwise_is_all_zeros(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) } } } + +static void +test_follow_symlinks(int argc, char *argv[]) +{ + int i; + + for (i = 1; i < argc; i++) { + char *target = follow_symlinks(argv[i]); + puts(target); + free(target); + } +} static const struct command commands[] = { {"ctz", 0, 0, test_ctz}, @@ -275,6 +287,7 @@ static const struct command commands[] = { {"bitwise_zero", 0, 0, test_bitwise_zero}, {"bitwise_one", 0, 0, test_bitwise_one}, {"bitwise_is_all_zeros", 0, 0, test_bitwise_is_all_zeros}, + {"follow-symlinks", 1, INT_MAX, test_follow_symlinks}, {NULL, 0, 0, NULL}, }; -- 2.30.2