realpath3.c revision 1.3
1/* $OpenBSD: realpath3.c,v 1.3 2019/07/15 16:05:04 bluhm 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 <sys/stat.h> 31 32#include <errno.h> 33#include <stdlib.h> 34#include <string.h> 35#include <unistd.h> 36#include <limits.h> 37 38/* 39 * The OpenBSD 6.4 libc version of realpath(3), preserved to validate 40 * an implementation of realpath(2). 41 * As our kernel realpath(2) is heading towards to POSIX compliance, 42 * some details in this version have changed. 43 */ 44 45/* 46 * char *realpath(const char *path, char resolved[PATH_MAX]); 47 * 48 * Find the real name of path, by removing all ".", ".." and symlink 49 * components. Returns (resolved) on success, or (NULL) on failure, 50 * in which case the path which caused trouble is left in (resolved). 51 */ 52char * 53realpath3(const char *path, char *resolved) 54{ 55 const char *p; 56 char *q; 57 size_t left_len, resolved_len, next_token_len; 58 unsigned symlinks; 59 int serrno, mem_allocated; 60 ssize_t slen; 61 char left[PATH_MAX], next_token[PATH_MAX], symlink[PATH_MAX]; 62 struct stat sb; 63 64 if (path == NULL) { 65 errno = EINVAL; 66 return (NULL); 67 } 68 69 if (path[0] == '\0') { 70 errno = ENOENT; 71 return (NULL); 72 } 73 74 /* 75 * POSIX demands ENOENT for non existing file. 76 */ 77 if (stat(path, &sb) == -1) 78 return (NULL); 79 80 serrno = errno; 81 82 if (resolved == NULL) { 83 resolved = malloc(PATH_MAX); 84 if (resolved == NULL) 85 return (NULL); 86 mem_allocated = 1; 87 } else 88 mem_allocated = 0; 89 90 symlinks = 0; 91 if (path[0] == '/') { 92 resolved[0] = '/'; 93 resolved[1] = '\0'; 94 if (path[1] == '\0') 95 return (resolved); 96 resolved_len = 1; 97 left_len = strlcpy(left, path + 1, sizeof(left)); 98 } else { 99 if (getcwd(resolved, PATH_MAX) == NULL) { 100 if (mem_allocated) 101 free(resolved); 102 else 103 strlcpy(resolved, ".", PATH_MAX); 104 return (NULL); 105 } 106 resolved_len = strlen(resolved); 107 left_len = strlcpy(left, path, sizeof(left)); 108 } 109 if (left_len >= sizeof(left)) { 110 errno = ENAMETOOLONG; 111 goto err; 112 } 113 114 /* 115 * Iterate over path components in `left'. 116 */ 117 while (left_len != 0) { 118 /* 119 * Extract the next path component and adjust `left' 120 * and its length. 121 */ 122 p = strchr(left, '/'); 123 124 next_token_len = p ? (size_t) (p - left) : left_len; 125 memcpy(next_token, left, next_token_len); 126 next_token[next_token_len] = '\0'; 127 128 if (p != NULL) { 129 left_len -= next_token_len + 1; 130 memmove(left, p + 1, left_len + 1); 131 } else { 132 left[0] = '\0'; 133 left_len = 0; 134 } 135 136 if (resolved[resolved_len - 1] != '/') { 137 if (resolved_len + 1 >= PATH_MAX) { 138 errno = ENAMETOOLONG; 139 goto err; 140 } 141 resolved[resolved_len++] = '/'; 142 resolved[resolved_len] = '\0'; 143 } 144 if (next_token[0] == '\0') 145 continue; 146 else if (strcmp(next_token, ".") == 0) 147 continue; 148 else if (strcmp(next_token, "..") == 0) { 149 /* 150 * Strip the last path component except when we have 151 * single "/" 152 */ 153 if (resolved_len > 1) { 154 resolved[resolved_len - 1] = '\0'; 155 q = strrchr(resolved, '/') + 1; 156 *q = '\0'; 157 resolved_len = q - resolved; 158 } 159 continue; 160 } 161 162 /* 163 * Append the next path component and readlink() it. If 164 * readlink() fails we still can return successfully if 165 * it exists but isn't a symlink, or if there are no more 166 * path components left. 167 */ 168 resolved_len = strlcat(resolved, next_token, PATH_MAX); 169 if (resolved_len >= PATH_MAX) { 170 errno = ENAMETOOLONG; 171 goto err; 172 } 173 slen = readlink(resolved, symlink, sizeof(symlink)); 174 if (slen < 0) { 175 switch (errno) { 176 case EINVAL: 177 /* not a symlink, continue to next component */ 178 continue; 179 case ENOENT: 180 if (p == NULL) { 181 errno = serrno; 182 return (resolved); 183 } 184 /* FALLTHROUGH */ 185 default: 186 goto err; 187 } 188 } else if (slen == 0) { 189 errno = EINVAL; 190 goto err; 191 } else if (slen == sizeof(symlink)) { 192 errno = ENAMETOOLONG; 193 goto err; 194 } else { 195 if (symlinks++ > SYMLOOP_MAX) { 196 errno = ELOOP; 197 goto err; 198 } 199 200 symlink[slen] = '\0'; 201 if (symlink[0] == '/') { 202 resolved[1] = 0; 203 resolved_len = 1; 204 } else { 205 /* Strip the last path component. */ 206 q = strrchr(resolved, '/') + 1; 207 *q = '\0'; 208 resolved_len = q - resolved; 209 } 210 211 /* 212 * If there are any path components left, then 213 * append them to symlink. The result is placed 214 * in `left'. 215 */ 216 if (p != NULL) { 217 if (symlink[slen - 1] != '/') { 218 if (slen + 1 >= sizeof(symlink)) { 219 errno = ENAMETOOLONG; 220 goto err; 221 } 222 symlink[slen] = '/'; 223 symlink[slen + 1] = 0; 224 } 225 left_len = strlcat(symlink, left, sizeof(symlink)); 226 if (left_len >= sizeof(symlink)) { 227 errno = ENAMETOOLONG; 228 goto err; 229 } 230 } 231 left_len = strlcpy(left, symlink, sizeof(left)); 232 } 233 } 234 235 /* 236 * POSIX demands ENOTDIR for non directories ending in a "/". 237 */ 238 if (strchr(path, '/') != NULL && path[strlen(path) - 1] == '/') { 239 if (stat(resolved, &sb) != -1 && !S_ISDIR(sb.st_mode)) { 240 errno = ENOTDIR; 241 goto err; 242 } 243 } 244 245 /* 246 * Remove trailing slash except when the resolved pathname 247 * is a single "/". 248 */ 249 if (resolved_len > 1 && resolved[resolved_len - 1] == '/') 250 resolved[resolved_len - 1] = '\0'; 251 return (resolved); 252 253err: 254 if (mem_allocated) 255 free(resolved); 256 return (NULL); 257} 258