Add realpath function

This commit is contained in:
Erik van der Kouwe 2009-12-04 07:52:22 +00:00
parent 150dfbe96d
commit 5427ab41c8
7 changed files with 531 additions and 3 deletions

View file

@ -69,6 +69,8 @@ _PROTOTYPE( int mkstemp, (char *_fmt) );
_PROTOTYPE( char *initstate, (unsigned _seed, char *_state, _PROTOTYPE( char *initstate, (unsigned _seed, char *_state,
size_t _size) ); size_t _size) );
_PROTOTYPE( long random, (void) ); _PROTOTYPE( long random, (void) );
_PROTOTYPE( char *realpath, (const char *file_name,
char *resolved_name) );
_PROTOTYPE( char *setstate, (const char *state) ); _PROTOTYPE( char *setstate, (const char *state) );
_PROTOTYPE( void srandom, (unsigned seed) ); _PROTOTYPE( void srandom, (unsigned seed) );
_PROTOTYPE( int putenv, (char *string) ); _PROTOTYPE( int putenv, (char *string) );

View file

@ -75,6 +75,7 @@ libc_FILES=" \
putenv.c \ putenv.c \
putw.c \ putw.c \
random.c \ random.c \
realpath.c \
rindex.c \ rindex.c \
setenv.c \ setenv.c \
setgroups.c \ setgroups.c \

221
lib/other/realpath.c Normal file
View file

@ -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);
}

20
man/man3/realpath.3 Normal file
View file

@ -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.

View file

@ -8,7 +8,7 @@ OBJ= test1 test2 test3 test4 test5 test6 test7 test8 test9 \
test21 test22 test23 test25 test26 test27 test28 test29 \ test21 test22 test23 test25 test26 test27 test28 test29 \
test30 test31 test32 test34 test35 test36 test37 test38 \ test30 test31 test32 test34 test35 test36 test37 test38 \
test39 t10a t11a t11b test40 t40a t40b t40c t40d t40e t40f test41 \ test39 t10a t11a t11b test40 t40a t40b t40c t40d t40e t40f test41 \
test42 test42 test43
BIGOBJ= test20 test24 BIGOBJ= test20 test24
ROOTOBJ= test11 test33 ROOTOBJ= test11 test33
@ -83,3 +83,4 @@ t40e: t40e.c
t40f: t40f.c t40f: t40f.c
test41: test41.c test41: test41.c
test42: test42.c test42: test42.c
test43: test43.c

View file

@ -12,13 +12,13 @@ badones= # list of tests that failed
# Print test welcome message # Print test welcome message
clr 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 " " echo " "
# Run all the tests, keeping track of who failed. # 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 \ 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 \ 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` do total=`expr $total + 1`
FAIL=0 FAIL=0
if [ $USER = root -a \( $i = 11 -o $i = 33 \) ] if [ $USER = root -a \( $i = 11 -o $i = 33 \) ]

283
test/test43.c Normal file
View file

@ -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 */
}