--- /dev/null
+/* 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);
+}
--- /dev/null
+/* 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 */
+}