1/* $OpenBSD: realpath3.c,v 1.4 2019/07/16 17:48:56 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 /* 81 * POSIX demands ENOTDIR for non directories ending in a "/". 82 */ 83 if (!S_ISDIR(sb.st_mode) && strchr(path, '/') != NULL && 84 path[strlen(path) - 1] == '/') { 85 errno = ENOTDIR; 86 return (NULL); 87 } 88 89 serrno = errno; 90 91 if (resolved == NULL) { 92 resolved = malloc(PATH_MAX); 93 if (resolved == NULL) 94 return (NULL); 95 mem_allocated = 1; 96 } else 97 mem_allocated = 0; 98 99 symlinks = 0; 100 if (path[0] == '/') { 101 resolved[0] = '/'; 102 resolved[1] = '\0'; 103 if (path[1] == '\0') 104 return (resolved); 105 resolved_len = 1; 106 left_len = strlcpy(left, path + 1, sizeof(left)); 107 } else { 108 if (getcwd(resolved, PATH_MAX) == NULL) { 109 if (mem_allocated) 110 free(resolved); 111 else 112 strlcpy(resolved, ".", PATH_MAX); 113 return (NULL); 114 } 115 resolved_len = strlen(resolved); 116 left_len = strlcpy(left, path, sizeof(left)); 117 } 118 if (left_len >= sizeof(left)) { 119 errno = ENAMETOOLONG; 120 goto err; 121 } 122 123 /* 124 * Iterate over path components in `left'. 125 */ 126 while (left_len != 0) { 127 /* 128 * Extract the next path component and adjust `left' 129 * and its length. 130 */ 131 p = strchr(left, '/'); 132 133 next_token_len = p ? (size_t) (p - left) : left_len; 134 memcpy(next_token, left, next_token_len); 135 next_token[next_token_len] = '\0'; 136 137 if (p != NULL) { 138 left_len -= next_token_len + 1; 139 memmove(left, p + 1, left_len + 1); 140 } else { 141 left[0] = '\0'; 142 left_len = 0; 143 } 144 145 if (resolved[resolved_len - 1] != '/') { 146 if (resolved_len + 1 >= PATH_MAX) { 147 errno = ENAMETOOLONG; 148 goto err; 149 } 150 resolved[resolved_len++] = '/'; 151 resolved[resolved_len] = '\0'; 152 } 153 if (next_token[0] == '\0') 154 continue; 155 else if (strcmp(next_token, ".") == 0) 156 continue; 157 else if (strcmp(next_token, "..") == 0) { 158 /* 159 * Strip the last path component except when we have 160 * single "/" 161 */ 162 if (resolved_len > 1) { 163 resolved[resolved_len - 1] = '\0'; 164 q = strrchr(resolved, '/') + 1; 165 *q = '\0'; 166 resolved_len = q - resolved; 167 } 168 continue; 169 } 170 171 /* 172 * Append the next path component and readlink() it. If 173 * readlink() fails we still can return successfully if 174 * it exists but isn't a symlink, or if there are no more 175 * path components left. 176 */ 177 resolved_len = strlcat(resolved, next_token, PATH_MAX); 178 if (resolved_len >= PATH_MAX) { 179 errno = ENAMETOOLONG; 180 goto err; 181 } 182 slen = readlink(resolved, symlink, sizeof(symlink)); 183 if (slen < 0) { 184 switch (errno) { 185 case EINVAL: 186 /* not a symlink, continue to next component */ 187 continue; 188 case ENOENT: 189 if (p == NULL) { 190 errno = serrno; 191 return (resolved); 192 } 193 /* FALLTHROUGH */ 194 default: 195 goto err; 196 } 197 } else if (slen == 0) { 198 errno = EINVAL; 199 goto err; 200 } else if (slen == sizeof(symlink)) { 201 errno = ENAMETOOLONG; 202 goto err; 203 } else { 204 if (symlinks++ > SYMLOOP_MAX) { 205 errno = ELOOP; 206 goto err; 207 } 208 209 symlink[slen] = '\0'; 210 if (symlink[0] == '/') { 211 resolved[1] = 0; 212 resolved_len = 1; 213 } else { 214 /* Strip the last path component. */ 215 q = strrchr(resolved, '/') + 1; 216 *q = '\0'; 217 resolved_len = q - resolved; 218 } 219 220 /* 221 * If there are any path components left, then 222 * append them to symlink. The result is placed 223 * in `left'. 224 */ 225 if (p != NULL) { 226 if (symlink[slen - 1] != '/') { 227 if (slen + 1 >= sizeof(symlink)) { 228 errno = ENAMETOOLONG; 229 goto err; 230 } 231 symlink[slen] = '/'; 232 symlink[slen + 1] = 0; 233 } 234 left_len = strlcat(symlink, left, sizeof(symlink)); 235 if (left_len >= sizeof(symlink)) { 236 errno = ENAMETOOLONG; 237 goto err; 238 } 239 } 240 left_len = strlcpy(left, symlink, sizeof(left)); 241 } 242 } 243 244 /* 245 * Remove trailing slash except when the resolved pathname 246 * is a single "/". 247 */ 248 if (resolved_len > 1 && resolved[resolved_len - 1] == '/') 249 resolved[resolved_len - 1] = '\0'; 250 return (resolved); 251 252err: 253 if (mem_allocated) 254 free(resolved); 255 return (NULL); 256} 257