Tue Jun 18 17:56:44 1996 Roland McGrath <roland@delasyd.gnu.ai.mit.edu>

* stdlib/test-canon.c: New test program contributed by David Mosberger.
	* stdlib/Makefile (tests): Add test-canon.
	* stdlib/canonicalize.c: Rewritten by David Mosberger.
This commit is contained in:
Roland McGrath 1996-06-18 22:01:27 +00:00
parent 1c719dd82c
commit 394f4f179e
3 changed files with 299 additions and 139 deletions

View File

@ -45,7 +45,8 @@ routines := \
rpmatch strfmon rpmatch strfmon
distribute := exit.h grouping.h distribute := exit.h grouping.h
tests := tst-strtol tst-strtod testmb testrand testsort testdiv tests := tst-strtol tst-strtod testmb testrand testsort testdiv \
test-canon
# Several mpn functions from GNU MP are used by the strtod function. # Several mpn functions from GNU MP are used by the strtod function.

View File

@ -17,174 +17,152 @@ License along with the GNU C Library; see the file COPYING.LIB. If
not, write to the Free Software Foundation, Inc., 675 Mass Ave, not, write to the Free Software Foundation, Inc., 675 Mass Ave,
Cambridge, MA 02139, USA. */ Cambridge, MA 02139, USA. */
#include <assert.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
#include <limits.h> #include <limits.h>
#include <sys/param.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <errno.h> #include <errno.h>
/* Return the canonical absolute name of file NAME. The last file name /* Return the canonical absolute name of file NAME. A canonical name
component need not exist, and may be a symlink to a nonexistent file. does not contain any `.', `..' components nor any repeated path
If RESOLVED is null, the result is malloc'd; otherwise, if the canonical separators ('/') or symlinks. All path components must exist. If
name is PATH_MAX chars or more, returns null with `errno' set to RESOLVED is null, the result is malloc'd; otherwise, if the
ENAMETOOLONG; if the name fits in fewer than PATH_MAX chars, returns the canonical name is PATH_MAX chars or more, returns null with `errno'
name in RESOLVED. */ set to ENAMETOOLONG; if the name fits in fewer than PATH_MAX chars,
returns the name in RESOLVED. If the name cannot be resolved and
RESOLVED is non-NULL, it contains the path of the first component
that cannot be resolved. If the path can be resolved, RESOLVED
holds the same value as the value returned. */
static char * static char *
canonicalize (const char *name, char *resolved) canonicalize (const char *name, char *resolved)
{ {
struct stat st; char *rpath, *dest, *extra_buf = NULL;
const char *p; const char *start, *end, *rpath_limit;
long int path_max; long int path_max;
char *result, *dir, *end; int num_links = 0;
size_t namelen;
if (! resolved)
path_max = 0;
else
{
#ifdef PATH_MAX #ifdef PATH_MAX
path_max = PATH_MAX; path_max = PATH_MAX;
#else #else
path_max = pathconf (name, _PC_PATH_MAX); path_max = pathconf (name, _PC_PATH_MAX);
if (path_max <= 0) if (path_max <= 0)
path_max = 1024; path_max = 1024;
#endif #endif
}
p = strrchr (name, '/'); rpath = resolved;
if (!p) rpath_limit = rpath + path_max;
if (!resolved)
rpath = malloc (path_max);
strcpy (rpath, "/");
if (name[0] != '/' && !getcwd (rpath, path_max))
goto error;
dest = rpath + strlen (rpath);
for (start = end = name; *start; start = end)
{ {
dir = (char *) "."; struct stat st;
p = name; int n;
}
else /* skip sequence of multiple path-separators: */
{ while (*start == '/') ++start;
if (p++ == name)
dir = (char *) "/"; /* find end of path component: */
else for (end = start; *end && *end != '/'; ++end);
if (end - start == 0)
break;
else if (strncmp (start, ".", end - start) == 0)
/* nothing */;
else if (strncmp (start, "..", end - start) == 0) {
/* back up to previous component, ignore if at root already: */
if (dest > rpath + 1)
while ((--dest)[-1] != '/');
} else
{ {
dir = __alloca (p - name); size_t new_size;
memcpy (dir, name, p - name - 1);
dir[p - name] = '\0';
}
}
result = __canonicalize_directory_name_internal (dir, resolved, path_max); if (dest[-1] != '/')
if (!result) *dest++ = '/';
return NULL;
/* Reconstruct the file name in the canonicalized directory. */ if (dest + (end - start) >= rpath_limit)
namelen = strlen (name);
end = strchr (result, '\0');
if (resolved)
{
/* Make sure the name is not too long. */
if (end - result + namelen > path_max)
{
errno = ENAMETOOLONG;
return NULL;
}
}
else
{
/* The name is dynamically allocated. Extend it. */
char *new = realloc (result, end - result + namelen + 1);
if (! new)
{
free (result);
return NULL;
}
end = new + (end - result);
result = new;
}
memcpy (end, name, namelen + 1);
while (__lstat (result, &st) == 0 && S_ISLNK (st.st_mode))
{
/* The file is a symlink. Read its contents. */
ssize_t n;
read_link_contents:
n = readlink (result, end,
resolved ? result + path_max - end : namelen + 1);
if (n < 0)
/* Error reading the link contents. */
return NULL;
if (end[0] == '/')
{
/* Absolute symlink. */
if (resolved ? (end + n < result + path_max) : (n < namelen + 1))
{ {
/* It fit in our buffer, so we have the whole thing. */ if (resolved)
memcpy (result, end, n);
result[n] = '\0';
}
else if (resolved)
{
/* It didn't fit in the remainder of the buffer. Either it
fits in the entire buffer, or it doesn't. Copy back the
unresolved name onto the canonical directory and try once
more. */
memcpy (end, name, namelen + 1);
n = readlink (result, result, path_max);
if (n < 0)
return NULL;
if (n == path_max)
{ {
errno = ENAMETOOLONG; errno = ENAMETOOLONG;
return NULL; goto error;
} }
result[n] = '\0'; new_size = rpath_limit - rpath;
if (end - start + 1 > path_max)
new_size += end - start + 1;
else
new_size += path_max;
rpath = realloc (rpath, new_size);
rpath_limit = rpath + new_size;
if (!rpath)
return NULL;
}
memcpy (dest, start, end - start);
dest += end - start;
*dest = '\0';
if (__lstat (rpath, &st) < 0)
goto error;
if (S_ISLNK (st.st_mode))
{
char * buf = __alloca(path_max);
if (++num_links > MAXSYMLINKS)
{
errno = ELOOP;
goto error;
}
n = readlink (rpath, buf, path_max);
if (n < 0)
goto error;
buf[n] = '\0';
if (!extra_buf)
extra_buf = __alloca (path_max);
if (n + strlen (end) >= path_max)
{
errno = ENAMETOOLONG;
goto error;
}
/* careful here, end may be a pointer into extra_buf... */
strcat (buf, end);
strcpy (extra_buf, buf);
name = end = extra_buf;
if (buf[0] == '/')
dest = rpath + 1; /* it's an absolute symlink */
else
/* back up to previous component, ignore if at root already: */
if (dest > rpath + 1)
while ((--dest)[-1] != '/');
} }
else else
/* Try again with a bigger buffer. */ num_links = 0;
goto extend_buffer;
/* Check the resolved name for being a symlink too. */
continue;
} }
if (resolved)
{
if (end + n == result + path_max)
{
/* The link contents we read fill the buffer completely.
There may be even more to read, and there is certainly no
space for the null terminator. */
errno = ENAMETOOLONG;
return NULL;
}
}
else if (n == namelen + 1)
extend_buffer:
{
/* The name buffer is dynamically allocated. Extend it. */
char *new;
/* Copy back the unresolved name onto the canonical directory. */
memcpy (end, name, namelen + 1);
/* Make more space for readlink. */
namelen *= 2;
new = realloc (result, end - result + namelen + 1);
if (! new)
{
free (result);
return NULL;
}
end = new + (end - result);
result = new;
goto read_link_contents;
}
/* Terminate the string; readlink does not. */
end[n] = '\0';
} }
if (dest > rpath + 1 && dest[-1] == '/')
--dest;
*dest = '\0';
return rpath;
return result; error:
if (!resolved)
free (rpath);
return NULL;
} }
weak_alias (canonicalize, realpath) weak_alias (canonicalize, realpath)

181
stdlib/test-canon.c Normal file
View File

@ -0,0 +1,181 @@
/* Test program for returning the canonical absolute name of a given file.
Copyright (C) 1996 Free Software Foundation, Inc.
This file is part of the GNU C Library.
Contributed by David Mosberger <davidm@azstarnet.com>.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with the GNU C Library; see the file COPYING.LIB. If
not, write to the Free Software Foundation, Inc., 675 Mass Ave,
Cambridge, MA 02139, USA. */
/* This file must be run from within a directory called "stdlib". */
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/param.h>
static char cwd[1024];
static size_t cwd_len;
struct {
const char * name;
const char * value;
} symlinks[] = {
{"SYMLINK_LOOP", "SYMLINK_LOOP"},
{"SYMLINK_1", "."},
{"SYMLINK_2", "//////./../../etc"},
{"SYMLINK_3", "SYMLINK_1"},
{"SYMLINK_4", "SYMLINK_2"},
{"SYMLINK_5", "doesNotExist"},
};
struct {
const char * in, * out, * resolved;
int errno;
} tests[] = {
/* 0 */
{"/", "/"},
{"/////////////////////////////////", "/"},
{"/.././.././.././..///", "/"},
{"/etc", "/etc"},
{"/etc/../etc", "/etc"},
/* 5 */
{"/doesNotExist/../etc", 0, "/doesNotExist", ENOENT},
{"./././././././././.", "."},
{"/etc/.//doesNotExist", 0, "/etc/doesNotExist", ENOENT},
{"./doesExist", "./doesExist"},
{"./doesExist/", "./doesExist"},
/* 10 */
{"./doesExist/../doesExist", "./doesExist"},
{"foobar", 0, "./foobar", ENOENT},
{".", "."},
{"./foobar", 0, "./foobar", ENOENT},
{"SYMLINK_LOOP", 0, "./SYMLINK_LOOP", ELOOP},
/* 15 */
{"./SYMLINK_LOOP", 0, "./SYMLINK_LOOP", ELOOP},
{"SYMLINK_1", "."},
{"SYMLINK_1/foobar", 0, "./foobar", ENOENT},
{"SYMLINK_2", "/etc"},
{"SYMLINK_3", "."},
/* 20 */
{"SYMLINK_4", "/etc"},
{"../stdlib/SYMLINK_1", "."},
{"../stdlib/SYMLINK_2", "/etc"},
{"../stdlib/SYMLINK_3", "."},
{"../stdlib/SYMLINK_4", "/etc"},
/* 25 */
{"./SYMLINK_5", 0, "./doesNotExist", ENOENT},
{"SYMLINK_5", 0, "./doesNotExist", ENOENT},
{"SYMLINK_5/foobar", 0, "./doesNotExist", ENOENT},
{"doesExist/../../stdlib/doesExist", "./doesExist"},
{"doesExist/.././../stdlib/.", "."}
};
int
check_path (const char * result, const char * expected)
{
int good;
if (!result)
return (expected == NULL);
if (!expected)
return 0;
if (expected[0] == '.' && (expected[1] == '/' || expected[1] == '\0'))
good = (strncmp (result, cwd, cwd_len) == 0
&& strcmp (result + cwd_len, expected + 1) == 0);
else
good = (strcmp (expected, result) == 0);
return good;
}
void
main (int argc, char ** argv)
{
char * result;
int fd, i, errors = 0;
char buf[PATH_MAX];
getcwd (cwd, sizeof(buf));
cwd_len = strlen (cwd);
for (i = 0; i < sizeof (symlinks) / sizeof (symlinks[0]); ++i)
symlink (symlinks[i].value, symlinks[i].name);
fd = open("doesExist", O_CREAT | O_EXCL, 0777);
for (i = 0; i < sizeof (tests) / sizeof (tests[0]); ++i)
{
buf[0] = '\0';
result = realpath (tests[i].in, buf);
if (!check_path (result, tests[i].out))
{
printf ("%s: flunked test %d (expected `%s', got `%s')\n",
argv[0], i, tests[i].out ? tests[i].out : "NULL",
result ? result : "NULL");
++errors;
continue;
}
if (!check_path (buf, tests[i].out ? tests[i].out : tests[i].resolved))
{
printf ("%s: flunked test %d (expected resolved `%s', got `%s')\n",
argv[0], i, tests[i].out ? tests[i].out : tests[i].resolved,
buf);
++errors;
continue;
}
if (!tests[i].out && errno != tests[i].errno)
{
printf ("%s: flunked test %d (expected errno %d, got %d)\n",
argv[0], i, tests[i].errno, errno);
++errors;
continue;
}
}
getcwd (buf, sizeof(buf));
if (strcmp (buf, cwd))
{
printf ("%s: current working directory changed from %s to %s\n",
argv[0], cwd, buf);
++errors;
}
if (fd >= 0)
unlink("doesExist");
for (i = 0; i < sizeof (symlinks) / sizeof (symlinks[0]); ++i)
unlink (symlinks[i].name);
if (errors == 0)
{
puts ("No errors.");
exit (EXIT_SUCCESS);
}
else
{
printf ("%d errors.\n", errors);
exit (EXIT_FAILURE);
}
}