util: New function follow_symlinks().
authorBen Pfaff <blp@nicira.com>
Mon, 30 Jul 2012 18:36:06 +0000 (11:36 -0700)
committerBen Pfaff <blp@nicira.com>
Wed, 1 Aug 2012 17:55:57 +0000 (10:55 -0700)
It will acquire its first user in an upcoming commit.

Signed-off-by: Ben Pfaff <blp@nicira.com>
lib/util.c
lib/util.h
tests/file_name.at
tests/test-util.c

index cbcf693..cd99142 100644 (file)
@@ -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
index 809ae9c..60ec737 100644 (file)
@@ -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);
index e0b43dc..aee4070 100644 (file)
@@ -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
index 56c5b28..71a7f21 100644 (file)
@@ -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);
+    }
+}
 \f
 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},
 };