mirror of
https://github.com/openvswitch/ovs
synced 2025-09-02 15:25:22 +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 <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include "byte-order.h"
|
#include "byte-order.h"
|
||||||
#include "coverage.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
|
/* Pass a value to this function if it is marked with
|
||||||
* __attribute__((warn_unused_result)) and you genuinely want to ignore
|
* __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 *base_name(const char *file_name);
|
||||||
char *abs_file_name(const char *dir, 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);
|
void ignore(bool x OVS_UNUSED);
|
||||||
int log_2_floor(uint32_t);
|
int log_2_floor(uint32_t);
|
||||||
int log_2_ceil(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([dir/file//], [dir], [file])
|
CHECK_FILE_NAME([dir/file//], [dir], [file])
|
||||||
CHECK_FILE_NAME([///foo], [/], [foo])
|
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[] = {
|
static const struct command commands[] = {
|
||||||
{"ctz", 0, 0, test_ctz},
|
{"ctz", 0, 0, test_ctz},
|
||||||
@@ -275,6 +287,7 @@ static const struct command commands[] = {
|
|||||||
{"bitwise_zero", 0, 0, test_bitwise_zero},
|
{"bitwise_zero", 0, 0, test_bitwise_zero},
|
||||||
{"bitwise_one", 0, 0, test_bitwise_one},
|
{"bitwise_one", 0, 0, test_bitwise_one},
|
||||||
{"bitwise_is_all_zeros", 0, 0, test_bitwise_is_all_zeros},
|
{"bitwise_is_all_zeros", 0, 0, test_bitwise_is_all_zeros},
|
||||||
|
{"follow-symlinks", 1, INT_MAX, test_follow_symlinks},
|
||||||
{NULL, 0, 0, NULL},
|
{NULL, 0, 0, NULL},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user