]> Zhao Yanbai Git Server - minix.git/commitdiff
Add realpath function
authorErik van der Kouwe <erik@minix3.org>
Fri, 4 Dec 2009 07:52:22 +0000 (07:52 +0000)
committerErik van der Kouwe <erik@minix3.org>
Fri, 4 Dec 2009 07:52:22 +0000 (07:52 +0000)
include/stdlib.h
lib/other/Makefile.in
lib/other/realpath.c [new file with mode: 0644]
man/man3/realpath.3 [new file with mode: 0644]
test/Makefile
test/run
test/test43.c [new file with mode: 0644]

index d170623ee3562f080b2201007409fe37f8611b4c..3b3adab961bd84f8aa57b755f6b05c09a4c03977 100644 (file)
@@ -69,6 +69,8 @@ _PROTOTYPE( int mkstemp, (char *_fmt)                                 );
 _PROTOTYPE( char *initstate, (unsigned _seed, char *_state,
                                                        size_t _size)   );
 _PROTOTYPE( long random, (void)                                                );
+_PROTOTYPE( char *realpath, (const char *file_name, 
+                                                 char *resolved_name)   );
 _PROTOTYPE( char *setstate, (const char *state)                                );
 _PROTOTYPE( void srandom, (unsigned seed)                              );
 _PROTOTYPE( int putenv, (char *string)                                 );
index 344a34a81133bdda873bac94fe8a63c944e04249..6d84c857038041d34038fd5a3272c66f240ff4bd 100644 (file)
@@ -75,6 +75,7 @@ libc_FILES=" \
        putenv.c \
        putw.c \
        random.c \
+       realpath.c \
        rindex.c \
        setenv.c \
        setgroups.c \
diff --git a/lib/other/realpath.c b/lib/other/realpath.c
new file mode 100644 (file)
index 0000000..a2c25c1
--- /dev/null
@@ -0,0 +1,221 @@
+/*     realpath() - resolve absolute path        Author: Erik van der Kouwe
+ *                                            4 December 2009
+ *
+ * Based on this specification:
+ * http://www.opengroup.org/onlinepubs/000095399/functions/realpath.html 
+ */
+#include <errno.h>
+#include <limits.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+static char *append_path_component(char *path, const char *component, 
+       size_t component_length);
+static char *process_path_component(const char *component, 
+       size_t component_length, char *resolved_name, int last_part, int max_depth);
+static char *realpath_recurse(const char *file_name, char *resolved_name, 
+       int max_depth);
+static char *remove_last_path_component(char *path);
+
+static char *append_path_component(char *path, const char *component, 
+       size_t component_length)
+{
+       size_t path_length, slash_length;
+
+       /* insert or remove a slash? */
+       path_length = strlen(path);
+       slash_length = 
+               ((path[path_length - 1] == '/') ? 0 : 1) + 
+               ((component[0] == '/') ? 0 : 1) - 1;
+
+       /* check whether this fits */
+       if (path_length + slash_length + component_length >= PATH_MAX)
+       {
+               errno = ENAMETOOLONG;
+               return NULL;
+       }
+
+       /* insert slash if needed */
+       if (slash_length > 0)
+               path[path_length] = '/';
+
+       /* copy the bytes */
+       memcpy(path + path_length + slash_length, component, component_length);
+       path[path_length + slash_length + component_length] = 0;
+
+       return path;
+}
+
+static char *process_path_component(const char *component, 
+       size_t component_length, char *resolved_name, int last_part, int max_depth)
+{
+       char readlink_buffer[PATH_MAX + 1];
+       ssize_t readlink_buffer_length;
+       struct stat stat_buffer;
+
+       /* handle zero-length components */
+       if (!component_length)
+       {
+               if (last_part)
+                       return resolved_name;
+               else
+               {
+                       errno = ENOENT;
+                       return NULL;
+               }
+       }
+
+       /* ignore current directory components */
+       if (component_length == 1 && component[0] == '.')
+               return resolved_name;
+
+       /* process parent directory components */
+       if (component_length == 2 && component[0] == '.' && component[1] == '.')
+               return remove_last_path_component(resolved_name);
+
+       /* not a special case, so just add the component */
+       if (!append_path_component(resolved_name, component, component_length))
+               return NULL;
+
+       /* stat partially resolved file */
+       if (lstat(resolved_name, &stat_buffer) < 0)
+       {
+               if (last_part && errno == ENOENT)
+                       return resolved_name;
+               else
+                       return NULL;
+       }
+
+       if (S_ISLNK(stat_buffer.st_mode))
+       {
+               /* resolve symbolic link */
+               readlink_buffer_length = readlink(resolved_name, readlink_buffer, 
+                       sizeof(readlink_buffer) - 1);
+               if (readlink_buffer_length < 0)
+                       return NULL;
+
+               readlink_buffer[readlink_buffer_length] = 0;
+               
+               /* recurse to resolve path in link */
+               remove_last_path_component(resolved_name);
+               if (!realpath_recurse(readlink_buffer, resolved_name, max_depth - 1))
+                       return NULL;
+
+               /* stat symlink target */
+               if (lstat(resolved_name, &stat_buffer) < 0)
+               {
+                       if (last_part && errno == ENOENT)
+                               return resolved_name;
+                       else
+                               return NULL;
+               }
+       }
+       
+       /* non-directories may appear only as the last component */
+       if (!last_part && !S_ISDIR(stat_buffer.st_mode))
+       {
+               errno = ENOTDIR;
+               return NULL;
+       }
+       
+       return resolved_name;
+}
+
+static char *realpath_recurse(const char *file_name, char *resolved_name, 
+       int max_depth)
+{
+       const char *file_name_component;
+
+       /* avoid infinite recursion */
+       if (max_depth <= 0)
+       {
+               errno = ELOOP;
+               return NULL;
+       }
+
+       /* relative to root or to current? */
+       if (file_name[0] == '/')
+       {
+               /* relative to root */
+               resolved_name[0] = '/';
+               resolved_name[1] = '\0';
+               file_name++;
+       }
+
+       /* process the path component by component */
+       while (*file_name)
+       {
+               /* extract a slash-delimited component */
+               file_name_component = file_name;
+               while (*file_name && *file_name != '/')
+                       file_name++;
+
+               /* check length of component */
+               if (file_name - file_name_component > PATH_MAX)
+               {
+                       errno = ENAMETOOLONG;
+                       return NULL;
+               }
+
+               /* add the component to the current result */
+               if (!process_path_component(
+                       file_name_component, 
+                       file_name - file_name_component,
+                       resolved_name,
+                       !*file_name,
+                       max_depth))
+                       return NULL;
+
+               /* skip the slash */
+               if (*file_name == '/')
+                       file_name++;
+       }
+
+       return resolved_name;
+}
+
+static char *remove_last_path_component(char *path)
+{
+       char *current, *slash;
+
+       /* find the last slash */
+       slash = NULL;
+       for (current = path; *current; current++)
+               if (*current == '/')
+                       slash = current;
+
+       /* truncate after the last slash, but do not remove the root */
+       if (slash > path)
+               *slash = 0;
+       else if (slash == path)
+               slash[1] = 0;
+
+       return path;
+}
+
+char *realpath(const char *file_name, char *resolved_name)
+{
+       /* check parameters */
+       if (!file_name || !resolved_name)
+       {
+               errno = EINVAL;
+               return NULL;
+       }
+
+       if (strlen(file_name) > PATH_MAX)
+       {
+               errno = ENAMETOOLONG;
+               return NULL;
+       }
+
+       /* basis to resolve against: root or CWD */
+       if (file_name[0] == '/')
+               *resolved_name = 0;
+       else if (!getcwd(resolved_name, PATH_MAX))
+               return NULL;
+
+       /* do the actual work */
+       return realpath_recurse(file_name, resolved_name, SYMLOOP_MAX);
+}
diff --git a/man/man3/realpath.3 b/man/man3/realpath.3
new file mode 100644 (file)
index 0000000..d5cebbf
--- /dev/null
@@ -0,0 +1,20 @@
+.TH REALPATH 3  "December 3, 2009"
+.UC 4
+.SH NAME
+realpath \- resolve a pathname
+.SH SYNOPSIS
+.nf
+.ft B
+#include <stdlib.h>
+
+char *realpath(const char *\fIfile_name\fP, char *\fIresolved_name\fP);
+.fi
+.SH DESCRIPTION
+realpath finds an absolute path to \fIfile_name\fP which does not
+contain . and .. components or symbolic links. The absolute path is stored
+in \fIresolved_name\fP, which is expected to provide storage for at least
+MAX_PATH bytes.
+.SH "RETURN VALUE
+If the function succeeds, a pointer to \fIresolved_name\fP is returned.
+If the function fails, NULL is returned and errno is set to indicate the
+cause of the failure.
index 6262d8d897de1a3f4a720dc4cadebf4d19d0f0ba..eaab7e3f20bb797cba98a212c4b6bfe5a9767485 100644 (file)
@@ -8,7 +8,7 @@ OBJ=    test1  test2  test3  test4  test5  test6  test7  test8  test9  \
               test21 test22 test23        test25 test26 test27 test28 test29 \
        test30 test31 test32        test34 test35 test36 test37 test38 \
        test39 t10a t11a t11b test40 t40a t40b t40c t40d t40e t40f test41 \
-       test42
+       test42 test43
 
 BIGOBJ=  test20 test24
 ROOTOBJ= test11 test33
@@ -83,3 +83,4 @@ t40e: t40e.c
 t40f: t40f.c
 test41: test41.c
 test42: test42.c
+test43: test43.c
index 7fba9a7fcbeb6aa17abc146af945faf66f0cf32f..4e9297df705fc93d2e3188e430248317a57cff81 100755 (executable)
--- a/test/run
+++ b/test/run
@@ -12,13 +12,13 @@ badones=                    # list of tests that failed
 
 # Print test welcome message
 clr
-echo "Running POSIX compliance test suite. There are 44 tests in total."
+echo "Running POSIX compliance test suite. There are 45 tests in total."
 echo " "
 
 # Run all the tests, keeping track of who failed.
 for i in  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 \
          21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 \
-         41 42 sh1.sh sh2.sh
+         41 42 43 sh1.sh sh2.sh
 do total=`expr $total + 1`
    FAIL=0
    if [ $USER = root -a \( $i = 11 -o $i = 33 \) ]
diff --git a/test/test43.c b/test/test43.c
new file mode 100644 (file)
index 0000000..35e07fd
--- /dev/null
@@ -0,0 +1,283 @@
+/* Tests for MINIX3 realpath(3) - by Erik van der Kouwe */
+#define _POSIX_SOURCE 1
+#include <assert.h>
+#include <dirent.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#define MAX_ERROR 4
+
+static int errct = 0;
+static const char *executable, *subtest;
+
+#define ERR (e(__LINE__))
+
+static void e(int n)
+{
+       printf("File %s, error line %d, errno %d: %s\n",
+               subtest, n, errno, strerror(errno));
+
+       if (errct++ > MAX_ERROR) 
+       {
+               printf("Too many errors; test aborted\n");
+               exit(1);
+       }
+}
+
+static void quit(void)
+{
+       if (errct == 0) 
+       {
+               printf("ok\n");
+               exit(0);
+       } 
+       else 
+       {
+               printf("%d errors\n", errct);
+               exit(1);
+       }
+}
+
+static char *remove_last_path_component(char *path)
+{
+       char *current, *last;
+
+       assert(path);
+
+       /* find last slash */
+       last = NULL;
+       for (current = path; *current; current++)
+               if (*current == '/')
+                       last = current;
+
+       /* check path component */
+       if (last)
+       {
+               if (strcmp(last + 1, ".") == 0) ERR;
+               if (strcmp(last + 1, "..") == 0) ERR;
+       }
+
+       /* if only root path slash, we are done */
+       if (last <= path)
+               return NULL;
+
+       /* chop off last path component */
+       *last = 0;
+       return path;
+}
+
+static void check_realpath(const char *path, int expected_errno)
+{
+       char buffer[PATH_MAX + 1], *resolved_path;
+       struct stat statbuf[2];
+
+       assert(path);
+       
+       /* run realpath */
+       subtest = path;
+       resolved_path = realpath(path, buffer);
+
+       /* do we get errors when expected? */
+       if (expected_errno)
+       {
+               if (errno != expected_errno) ERR;
+               if (resolved_path) ERR;
+               subtest = NULL;
+               return;
+       }
+
+       /* do we get success when expected? */
+       if (!resolved_path)
+       {
+               ERR;
+               subtest = NULL;
+               return;
+       }
+       errno = 0;
+
+       /* do the paths point to the same file? */
+       if (stat(path,          &statbuf[0]) < 0) { ERR; return; }
+       if (stat(resolved_path, &statbuf[1]) < 0) { ERR; return; }
+       if (statbuf[0].st_dev != statbuf[1].st_dev) ERR;
+       if (statbuf[0].st_ino != statbuf[1].st_ino) ERR;
+
+       /* is the path absolute? */
+       if (resolved_path[0] != '/') ERR;
+
+       /* is each path element allowable? */
+       while (remove_last_path_component(resolved_path))
+       {
+               /* not a symlink? */
+               if (lstat(resolved_path, &statbuf[1]) < 0) { ERR; return; }
+               if ((statbuf[1].st_mode & S_IFMT) != S_IFDIR) ERR;
+       }
+       subtest = NULL;
+}
+
+static void check_realpath_step_by_step(const char *path, int expected_errno)
+{
+       char buffer[PATH_MAX + 1];
+       const char *path_current;
+
+       assert(path);
+       assert(strlen(path) < sizeof(buffer));
+
+       /* check the absolute path */
+       check_realpath(path, expected_errno);
+
+       /* try with different CWDs */
+       for (path_current = path; *path_current; path_current++)
+               if (path_current[0] == '/' && path_current[1])
+               {
+                       /* set CWD */
+                       memcpy(buffer, path, path_current - path + 1);
+                       buffer[path_current - path + 1] = 0;
+                       if (chdir(buffer) < 0) { ERR; continue; }
+
+                       /* perform test */
+                       check_realpath(path_current + 1, expected_errno);
+               }
+}
+
+static char *pathncat(char *buffer, size_t size, const char *path1, const char *path2)
+{
+       size_t len1, len2, lenslash;
+
+       assert(buffer);
+       assert(path1);
+       assert(path2);
+
+       /* check whether it fits */
+       len1 = strlen(path1);
+       len2 = strlen(path2);
+       lenslash = (len1 > 0 && path1[len1 - 1] == '/') ? 0 : 1;
+       if (len1 >= size || /* check individual components to avoid overflow */
+               len2 >= size || 
+               len1 + len2 + lenslash >= size)
+               return NULL;
+
+       /* perform the copy */
+       memcpy(buffer, path1, len1);
+       if (lenslash)
+               buffer[len1] = '/';
+
+       memcpy(buffer + len1 + lenslash, path2, len2 + 1);
+       return buffer;
+}
+
+static void check_realpath_recurse(const char *path, int depth)
+{
+       DIR *dir;
+       struct dirent *dirent;
+       char pathsub[PATH_MAX + 1];
+
+       /* check with the path itself */
+       check_realpath_step_by_step(path, 0);
+
+       /* don't go too deep */
+       if (depth < 1)
+               return;
+
+       /* loop through subdirectories (including . and ..) */
+       if (!(dir = opendir(path))) 
+       {
+               if (errno != ENOTDIR)
+                       ERR;
+               return;
+       }
+       while (dirent = readdir(dir))
+       {
+               /* build path */
+               if (!pathncat(pathsub, sizeof(pathsub), path, dirent->d_name))
+               {
+                       ERR;
+                       continue;
+               }
+
+               /* check path */
+               check_realpath_recurse(pathsub, depth - 1);
+       }
+       if (closedir(dir) < 0) ERR;
+}
+
+#define PATH_DEPTH 3
+#define L(x) "/t43_link_" #x ".tmp"
+
+static char basepath[PATH_MAX + 1];
+
+static char *addbasepath(char *buffer, const char *path)
+{
+       size_t basepathlen, pathlen;
+       int slashlen;
+       
+       /* assumption: both start with slash and neither end with it */
+       assert(basepath[0] == '/');
+       assert(basepath[strlen(basepath) - 1] != '/');
+       assert(buffer);
+       assert(path);
+       assert(path[0] == '/');
+       
+       /* check result length */
+       basepathlen = strlen(basepath);
+       pathlen = strlen(path);
+       if (basepathlen + pathlen > PATH_MAX)
+       {
+               printf("path too long\n");
+               exit(-1);
+       }
+
+       /* concatenate base path and path */
+       memcpy(buffer, basepath, basepathlen);
+       memcpy(buffer + basepathlen, path, pathlen + 1);
+       return buffer;
+}
+
+static void cleanup(int silent)
+{
+       char buffer[PATH_MAX + 1];
+
+       if (unlink(addbasepath(buffer, L(1))) < 0 && !silent) ERR;
+       if (unlink(addbasepath(buffer, L(2))) < 0 && !silent) ERR;
+       if (unlink(addbasepath(buffer, L(3))) < 0 && !silent) ERR;
+       if (unlink(addbasepath(buffer, L(4))) < 0 && !silent) ERR;
+       if (unlink(addbasepath(buffer, L(5))) < 0 && !silent) ERR;
+}
+
+int main(int argc, char **argv)
+{
+       char buffer1[PATH_MAX + 1], buffer2[PATH_MAX + 1];
+
+       /* initialize */
+       printf("Test 43 ");
+       fflush(stdout);
+       executable = argv[0];
+       getcwd(basepath, sizeof(basepath));
+       cleanup(1);
+
+       /* prepare some symlinks to make it more difficult */
+       if (symlink("/",      addbasepath(buffer1, L(1))) < 0) ERR;
+       if (symlink(basepath, addbasepath(buffer1, L(2))) < 0) ERR;
+
+       /* perform some tests */
+       check_realpath_recurse(basepath, PATH_DEPTH);
+
+       /* now try with recursive symlinks */
+       if (symlink(addbasepath(buffer1, L(3)), addbasepath(buffer2, L(3))) < 0) ERR;
+       if (symlink(addbasepath(buffer1, L(5)), addbasepath(buffer2, L(4))) < 0) ERR;
+       if (symlink(addbasepath(buffer1, L(4)), addbasepath(buffer2, L(5))) < 0) ERR;
+       check_realpath_step_by_step(addbasepath(buffer1, L(3)), ELOOP);
+       check_realpath_step_by_step(addbasepath(buffer1, L(4)), ELOOP);
+       check_realpath_step_by_step(addbasepath(buffer1, L(5)), ELOOP);
+
+       /* delete the symlinks */
+       cleanup(0);
+
+       /* done */
+       quit();
+       return(-1);     /* impossible */
+}