224 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			224 lines
		
	
	
		
			5.0 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);
 | |
| }
 | 
