mirror of
https://github.com/openvswitch/ovs
synced 2025-09-03 07:45:30 +00:00
util: New function follow_symlinks().
It will acquire its first user in an upcoming commit. Signed-off-by: Ben Pfaff <blp@nicira.com>
This commit is contained in:
85
lib/util.c
85
lib/util.c
@@ -24,6 +24,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#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
|
||||
|
@@ -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);
|
||||
|
@@ -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
|
||||
|
@@ -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},
|
||||
};
|
||||
|
||||
|
Reference in New Issue
Block a user