223 lines
5 KiB
C
223 lines
5 KiB
C
/* 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);
|
|
}
|