1 /* $OpenBSD: realpath.c,v 1.22 2017/12/24 01:50:50 millert Exp $ */ 2 /* 3 * Copyright (c) 2003 Constantin S. Svintsoff <kostik@iclub.nsu.ru> 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. The names of the authors may not be used to endorse or promote 14 * products derived from this software without specific prior written 15 * permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 * SUCH DAMAGE. 28 */ 29 30 #include <errno.h> 31 #include <stdlib.h> 32 #include <string.h> 33 #include <unistd.h> 34 #include <limits.h> 35 36 /* A slightly modified copy of this file exists in libexec/ld.so */ 37 38 /* 39 * char *realpath(const char *path, char resolved[PATH_MAX]); 40 * 41 * Find the real name of path, by removing all ".", ".." and symlink 42 * components. Returns (resolved) on success, or (NULL) on failure, 43 * in which case the path which caused trouble is left in (resolved). 44 */ 45 char * 46 realpath(const char *path, char *resolved) 47 { 48 const char *p; 49 char *q; 50 size_t left_len, resolved_len, next_token_len; 51 unsigned symlinks; 52 int serrno, mem_allocated; 53 ssize_t slen; 54 char left[PATH_MAX], next_token[PATH_MAX], symlink[PATH_MAX]; 55 56 if (path == NULL) { 57 errno = EINVAL; 58 return (NULL); 59 } 60 61 if (path[0] == '\0') { 62 errno = ENOENT; 63 return (NULL); 64 } 65 66 serrno = errno; 67 68 if (resolved == NULL) { 69 resolved = malloc(PATH_MAX); 70 if (resolved == NULL) 71 return (NULL); 72 mem_allocated = 1; 73 } else 74 mem_allocated = 0; 75 76 symlinks = 0; 77 if (path[0] == '/') { 78 resolved[0] = '/'; 79 resolved[1] = '\0'; 80 if (path[1] == '\0') 81 return (resolved); 82 resolved_len = 1; 83 left_len = strlcpy(left, path + 1, sizeof(left)); 84 } else { 85 if (getcwd(resolved, PATH_MAX) == NULL) { 86 if (mem_allocated) 87 free(resolved); 88 else 89 strlcpy(resolved, ".", PATH_MAX); 90 return (NULL); 91 } 92 resolved_len = strlen(resolved); 93 left_len = strlcpy(left, path, sizeof(left)); 94 } 95 if (left_len >= sizeof(left)) { 96 errno = ENAMETOOLONG; 97 goto err; 98 } 99 100 /* 101 * Iterate over path components in `left'. 102 */ 103 while (left_len != 0) { 104 /* 105 * Extract the next path component and adjust `left' 106 * and its length. 107 */ 108 p = strchr(left, '/'); 109 110 next_token_len = p ? (size_t) (p - left) : left_len; 111 memcpy(next_token, left, next_token_len); 112 next_token[next_token_len] = '\0'; 113 114 if (p != NULL) { 115 left_len -= next_token_len + 1; 116 memmove(left, p + 1, left_len + 1); 117 } else { 118 left[0] = '\0'; 119 left_len = 0; 120 } 121 122 if (resolved[resolved_len - 1] != '/') { 123 if (resolved_len + 1 >= PATH_MAX) { 124 errno = ENAMETOOLONG; 125 goto err; 126 } 127 resolved[resolved_len++] = '/'; 128 resolved[resolved_len] = '\0'; 129 } 130 if (next_token[0] == '\0') 131 continue; 132 else if (strcmp(next_token, ".") == 0) 133 continue; 134 else if (strcmp(next_token, "..") == 0) { 135 /* 136 * Strip the last path component except when we have 137 * single "/" 138 */ 139 if (resolved_len > 1) { 140 resolved[resolved_len - 1] = '\0'; 141 q = strrchr(resolved, '/') + 1; 142 *q = '\0'; 143 resolved_len = q - resolved; 144 } 145 continue; 146 } 147 148 /* 149 * Append the next path component and readlink() it. If 150 * readlink() fails we still can return successfully if 151 * it exists but isn't a symlink, or if there are no more 152 * path components left. 153 */ 154 resolved_len = strlcat(resolved, next_token, PATH_MAX); 155 if (resolved_len >= PATH_MAX) { 156 errno = ENAMETOOLONG; 157 goto err; 158 } 159 slen = readlink(resolved, symlink, sizeof(symlink)); 160 if (slen < 0) { 161 switch (errno) { 162 case EINVAL: 163 /* not a symlink, continue to next component */ 164 continue; 165 case ENOENT: 166 if (p == NULL) { 167 errno = serrno; 168 return (resolved); 169 } 170 /* FALLTHROUGH */ 171 default: 172 goto err; 173 } 174 } else if (slen == 0) { 175 errno = EINVAL; 176 goto err; 177 } else if (slen == sizeof(symlink)) { 178 errno = ENAMETOOLONG; 179 goto err; 180 } else { 181 if (symlinks++ > SYMLOOP_MAX) { 182 errno = ELOOP; 183 goto err; 184 } 185 186 symlink[slen] = '\0'; 187 if (symlink[0] == '/') { 188 resolved[1] = 0; 189 resolved_len = 1; 190 } else { 191 /* Strip the last path component. */ 192 q = strrchr(resolved, '/') + 1; 193 *q = '\0'; 194 resolved_len = q - resolved; 195 } 196 197 /* 198 * If there are any path components left, then 199 * append them to symlink. The result is placed 200 * in `left'. 201 */ 202 if (p != NULL) { 203 if (symlink[slen - 1] != '/') { 204 if (slen + 1 >= sizeof(symlink)) { 205 errno = ENAMETOOLONG; 206 goto err; 207 } 208 symlink[slen] = '/'; 209 symlink[slen + 1] = 0; 210 } 211 left_len = strlcat(symlink, left, sizeof(symlink)); 212 if (left_len >= sizeof(symlink)) { 213 errno = ENAMETOOLONG; 214 goto err; 215 } 216 } 217 left_len = strlcpy(left, symlink, sizeof(left)); 218 } 219 } 220 221 /* 222 * Remove trailing slash except when the resolved pathname 223 * is a single "/". 224 */ 225 if (resolved_len > 1 && resolved[resolved_len - 1] == '/') 226 resolved[resolved_len - 1] = '\0'; 227 return (resolved); 228 229 err: 230 if (mem_allocated) 231 free(resolved); 232 return (NULL); 233 } 234