/* * realpath.c -- canonicalize pathname by removing symlinks * Copyright (C) 1993 Rick Sladkey <jrs@world.std.com> * Copyright (C) 2000-2006 Erik Andersen <andersen@uclibc.org> * * Licensed under the LGPL v2.1, see the file COPYING.LIB in this tarball. */ #ifdef HAVE_CONFIG_H #include <config.h> #endif #include <sys/types.h> #include <unistd.h> #include <stdio.h> #include <string.h> #include <limits.h> /* for PATH_MAX */ #include <sys/param.h> /* for MAXPATHLEN */ #include <errno.h> #include <stdlib.h> #include <sys/stat.h> /* for S_IFLNK */ #ifndef PATH_MAX #ifdef _POSIX_VERSION #define PATH_MAX _POSIX_PATH_MAX #else #ifdef MAXPATHLEN #define PATH_MAX MAXPATHLEN #else #define PATH_MAX 1024 #endif #endif #endif #define MAX_READLINKS 32 char *realpath(const char *path, char got_path[]) { char copy_path[PATH_MAX]; char *max_path, *new_path, *allocated_path; size_t path_len; int readlinks = 0; #ifdef S_IFLNK int link_len; #endif if (path == NULL) { __set_errno(EINVAL); return NULL; } if (*path == '\0') { __set_errno(ENOENT); return NULL; } /* Make a copy of the source path since we may need to modify it. */ path_len = strlen(path); if (path_len >= PATH_MAX - 2) { __set_errno(ENAMETOOLONG); return NULL; } /* Copy so that path is at the end of copy_path[] */ strcpy(copy_path + (PATH_MAX-1) - path_len, path); path = copy_path + (PATH_MAX-1) - path_len; allocated_path = got_path ? NULL : (got_path = malloc(PATH_MAX)); max_path = got_path + PATH_MAX - 2; /* points to last non-NUL char */ new_path = got_path; if (*path != '/') { /* If it's a relative pathname use getcwd for starters. */ if (!getcwd(new_path, PATH_MAX - 1)) goto err; new_path += strlen(new_path); if (new_path[-1] != '/') *new_path++ = '/'; } else { *new_path++ = '/'; path++; } /* Expand each slash-separated pathname component. */ while (*path != '\0') { /* Ignore stray "/". */ if (*path == '/') { path++; continue; } if (*path == '.') { /* Ignore ".". */ if (path[1] == '\0' || path[1] == '/') { path++; continue; } if (path[1] == '.') { if (path[2] == '\0' || path[2] == '/') { path += 2; /* Ignore ".." at root. */ if (new_path == got_path + 1) continue; /* Handle ".." by backing up. */ while ((--new_path)[-1] != '/'); continue; } } } /* Safely copy the next pathname component. */ while (*path != '\0' && *path != '/') { if (new_path > max_path) { __set_errno(ENAMETOOLONG); err: free(allocated_path); return NULL; } *new_path++ = *path++; } #ifdef S_IFLNK /* Protect against infinite loops. */ if (readlinks++ > MAX_READLINKS) { __set_errno(ELOOP); goto err; } path_len = strlen(path); /* See if last (so far) pathname component is a symlink. */ *new_path = '\0'; { int sv_errno = errno; link_len = readlink(got_path, copy_path, PATH_MAX - 1); if (link_len < 0) { /* EINVAL means the file exists but isn't a symlink. */ if (errno != EINVAL) { goto err; } } else { /* Safe sex check. */ if (path_len + link_len >= PATH_MAX - 2) { __set_errno(ENAMETOOLONG); goto err; } /* Note: readlink doesn't add the null byte. */ /* copy_path[link_len] = '\0'; - we don't need it too */ if (*copy_path == '/') /* Start over for an absolute symlink. */ new_path = got_path; else /* Otherwise back up over this component. */ while (*(--new_path) != '/'); /* Prepend symlink contents to path. */ memmove(copy_path + (PATH_MAX-1) - link_len - path_len, copy_path, link_len); path = copy_path + (PATH_MAX-1) - link_len - path_len; } __set_errno(sv_errno); } #endif /* S_IFLNK */ *new_path++ = '/'; } /* Delete trailing slash but don't whomp a lone slash. */ if (new_path != got_path + 1 && new_path[-1] == '/') new_path--; /* Make sure it's null terminated. */ *new_path = '\0'; return got_path; }