1/* 2 * Copyright (c) 2003 Constantin S. Svintsoff <kostik@iclub.nsu.ru> 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 3. The names of the authors may not be used to endorse or promote 13 * products derived from this software without specific prior written 14 * permission. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 */ 28 29#if defined(LIBC_SCCS) && !defined(lint) 30static char sccsid[] = "@(#)realpath.c 8.1 (Berkeley) 2/16/94"; 31#endif /* LIBC_SCCS and not lint */ 32#include <sys/cdefs.h> 33__FBSDID("$FreeBSD: src/lib/libc/stdlib/realpath.c,v 1.20 2003/05/28 08:23:01 fjoe Exp $"); 34 35#include "namespace.h" 36#include <sys/param.h> 37#include <sys/stat.h> 38#include <sys/mount.h> 39 40#include <errno.h> 41#include <stdlib.h> 42#include <string.h> 43#include <unistd.h> 44#include <sys/attr.h> 45#include <sys/vnode.h> 46#include "un-namespace.h" 47 48struct attrs { 49 u_int32_t len; 50 attrreference_t name; 51 dev_t dev; 52 fsobj_type_t type; 53 fsobj_id_t id; 54 char buf[PATH_MAX]; 55}; 56 57#ifndef BUILDING_VARIANT 58__private_extern__ const struct attrlist _rp_alist = { 59 ATTR_BIT_MAP_COUNT, 60 0, 61 ATTR_CMN_NAME | ATTR_CMN_DEVID | ATTR_CMN_OBJTYPE | ATTR_CMN_OBJID, 62 0, 63 0, 64 0, 65 0, 66}; 67#else /* BUILDING_VARIANT */ 68__private_extern__ const struct attrlist _rp_alist; 69#endif /* BUILDING_VARIANT */ 70 71extern char * __private_getcwd(char *, size_t, int); 72 73/* 74 * char *realpath(const char *path, char resolved[PATH_MAX]); 75 * 76 * Find the real name of path, by removing all ".", ".." and symlink 77 * components. Returns (resolved) on success, or (NULL) on failure, 78 * in which case the path which caused trouble is left in (resolved). 79 */ 80char * 81realpath(const char *path, char inresolved[PATH_MAX]) 82{ 83 struct attrs attrs; 84 struct stat sb; 85 char *p, *q, *s; 86 size_t left_len, resolved_len, save_resolved_len; 87 unsigned symlinks; 88 int serrno, slen, useattrs, islink; 89 char left[PATH_MAX], next_token[PATH_MAX], symlink[PATH_MAX]; 90 dev_t dev, lastdev; 91 struct statfs sfs; 92 static dev_t rootdev; 93 static int rootdev_inited = 0; 94 ino_t inode; 95 char *resolved; 96 97 if (path == NULL) { 98 errno = EINVAL; 99 return (NULL); 100 } 101#if __DARWIN_UNIX03 102 if (*path == 0) { 103 errno = ENOENT; 104 return (NULL); 105 } 106#endif /* __DARWIN_UNIX03 */ 107 /* 108 * Extension to the standard; if inresolved == NULL, allocate memory 109 */ 110 if (!inresolved) { 111 if ((resolved = malloc(PATH_MAX)) == NULL) return (NULL); 112 } else { 113 resolved = inresolved; 114 } 115 if (!rootdev_inited) { 116 rootdev_inited = 1; 117 if (stat("/", &sb) < 0) { 118error_return: 119 if (!inresolved) { 120 int e = errno; 121 free(resolved); 122 errno = e; 123 } 124 return (NULL); 125 } 126 rootdev = sb.st_dev; 127 } 128 serrno = errno; 129 symlinks = 0; 130 if (path[0] == '/') { 131 resolved[0] = '/'; 132 resolved[1] = '\0'; 133 if (path[1] == '\0') { 134 return (resolved); 135 } 136 resolved_len = 1; 137 left_len = strlcpy(left, path + 1, sizeof(left)); 138 } else { 139#if !defined(VARIANT_DARWINEXTSN) && __DARWIN_UNIX03 140 /* 4447159: don't use GETPATH, so this will fail if */ 141 /* if parent directories are not readable, as per POSIX */ 142 if (__private_getcwd(resolved, PATH_MAX, 0) == NULL) 143#else /* VARIANT_DARWINEXTSN || !__DARWIN_UNIX03 */ 144 if (__private_getcwd(resolved, PATH_MAX, 1) == NULL) 145#endif /* !VARIANT_DARWINEXTSN && __DARWIN_UNIX03 */ 146 { 147 strlcpy(resolved, ".", PATH_MAX); 148 goto error_return; 149 } 150 resolved_len = strlen(resolved); 151 left_len = strlcpy(left, path, sizeof(left)); 152 } 153 if (left_len >= sizeof(left) || resolved_len >= PATH_MAX) { 154 errno = ENAMETOOLONG; 155 goto error_return; 156 } 157 if (resolved_len > 1) { 158 if (stat(resolved, &sb) < 0) { 159 goto error_return; 160 } 161 lastdev = sb.st_dev; 162 } else 163 lastdev = rootdev; 164 165 /* 166 * Iterate over path components in `left'. 167 */ 168 while (left_len != 0) { 169 /* 170 * Extract the next path component and adjust `left' 171 * and its length. 172 */ 173 p = strchr(left, '/'); 174 s = p ? p : left + left_len; 175 if (s - left >= sizeof(next_token)) { 176 errno = ENAMETOOLONG; 177 goto error_return; 178 } 179 memcpy(next_token, left, s - left); 180 next_token[s - left] = '\0'; 181 left_len -= s - left; 182 if (p != NULL) 183 memmove(left, s + 1, left_len + 1); 184 if (resolved[resolved_len - 1] != '/') { 185 if (resolved_len + 1 >= PATH_MAX) { 186 errno = ENAMETOOLONG; 187 goto error_return; 188 } 189 resolved[resolved_len++] = '/'; 190 resolved[resolved_len] = '\0'; 191 } 192 if (next_token[0] == '\0') 193 continue; 194 else if (strcmp(next_token, ".") == 0) 195 continue; 196 else if (strcmp(next_token, "..") == 0) { 197 /* 198 * Strip the last path component except when we have 199 * single "/" 200 */ 201 if (resolved_len > 1) { 202 resolved[resolved_len - 1] = '\0'; 203 q = strrchr(resolved, '/') + 1; 204 *q = '\0'; 205 resolved_len = q - resolved; 206 } 207 continue; 208 } 209 210 /* 211 * Save resolved_len, so that we can later null out 212 * the the appended next_token, and replace with the 213 * real name (matters on case-insensitive filesystems). 214 */ 215 save_resolved_len = resolved_len; 216 217 /* 218 * Append the next path component and lstat() it. If 219 * lstat() fails we still can return successfully if 220 * there are no more path components left. 221 */ 222 resolved_len = strlcat(resolved, next_token, PATH_MAX); 223 if (resolved_len >= PATH_MAX) { 224 errno = ENAMETOOLONG; 225 goto error_return; 226 } 227 if (getattrlist(resolved, (void *)&_rp_alist, &attrs, sizeof(attrs), FSOPT_NOFOLLOW) == 0) { 228 useattrs = 1; 229 islink = (attrs.type == VLNK); 230 dev = attrs.dev; 231 inode = attrs.id.fid_objno; 232 } else if (errno == ENOTSUP || errno == EINVAL) { 233 if ((useattrs = lstat(resolved, &sb)) == 0) { 234 islink = S_ISLNK(sb.st_mode); 235 dev = sb.st_dev; 236 inode = sb.st_ino; 237 } 238 } else 239 useattrs = -1; 240 if (useattrs < 0) { 241#if !__DARWIN_UNIX03 242 if (errno == ENOENT && p == NULL) { 243 errno = serrno; 244 return (resolved); 245 } 246#endif /* !__DARWIN_UNIX03 */ 247 goto error_return; 248 } 249 if (dev != lastdev) { 250 /* 251 * We have crossed a mountpoint. For volumes like UDF 252 * the getattrlist name may not match the actual 253 * mountpoint, so we just copy the mountpoint directly. 254 * (3703138). However, the mountpoint may not be 255 * accessible, as when chroot-ed, so check first. 256 * There may be a file on the chroot-ed volume with 257 * the same name as the mountpoint, so compare device 258 * and inode numbers. 259 */ 260 lastdev = dev; 261 if (statfs(resolved, &sfs) == 0 && lstat(sfs.f_mntonname, &sb) == 0 && dev == sb.st_dev && inode == sb.st_ino) { 262 /* 263 * However, it's possible that the mountpoint 264 * path matches, even though it isn't the real 265 * path in the chroot-ed environment, so check 266 * that each component of the mountpoint 267 * is a directory (and not a symlink) 268 */ 269 char temp[sizeof(sfs.f_mntonname)]; 270 char *cp; 271 int ok = 1; 272 273 strcpy(temp, sfs.f_mntonname); 274 for(;;) { 275 if ((cp = strrchr(temp, '/')) == NULL) { 276 ok = 0; 277 break; 278 } 279 if (cp <= temp) 280 break; 281 *cp = 0; 282 if (lstat(temp, &sb) < 0 || (sb.st_mode & S_IFMT) != S_IFDIR) { 283 ok = 0; 284 break; 285 } 286 } 287 if (ok) { 288 resolved_len = strlcpy(resolved, sfs.f_mntonname, PATH_MAX); 289 continue; 290 } 291 } 292 /* if we fail, use the other methods. */ 293 } 294 if (islink) { 295 if (symlinks++ > MAXSYMLINKS) { 296 errno = ELOOP; 297 goto error_return; 298 } 299 slen = readlink(resolved, symlink, sizeof(symlink) - 1); 300 if (slen < 0) { 301 goto error_return; 302 } 303 symlink[slen] = '\0'; 304 if (symlink[0] == '/') { 305 resolved[1] = 0; 306 resolved_len = 1; 307 lastdev = rootdev; 308 } else if (resolved_len > 1) { 309 /* Strip the last path component. */ 310 resolved[resolved_len - 1] = '\0'; 311 q = strrchr(resolved, '/') + 1; 312 *q = '\0'; 313 resolved_len = q - resolved; 314 } 315 316 /* 317 * If there are any path components left, then 318 * append them to symlink. The result is placed 319 * in `left'. 320 */ 321 if (p != NULL) { 322 if (symlink[slen - 1] != '/') { 323 if (slen + 1 >= sizeof(symlink)) { 324 errno = ENAMETOOLONG; 325 goto error_return; 326 } 327 symlink[slen] = '/'; 328 symlink[slen + 1] = 0; 329 } 330 left_len = strlcat(symlink, left, sizeof(symlink)); 331 if (left_len >= sizeof(left)) { 332 errno = ENAMETOOLONG; 333 goto error_return; 334 } 335 } 336 left_len = strlcpy(left, symlink, sizeof(left)); 337 } else if (useattrs) { 338 /* 339 * attrs already has the real name. 340 */ 341 342 resolved[save_resolved_len] = '\0'; 343 resolved_len = strlcat(resolved, (const char *)&attrs.name + attrs.name.attr_dataoffset, PATH_MAX); 344 if (resolved_len >= PATH_MAX) { 345 errno = ENAMETOOLONG; 346 goto error_return; 347 } 348 } 349 /* 350 * For the case of useattrs == 0, we could scan the directory 351 * and try to match the inode. There are many problems with 352 * this: (1) the directory may not be readable, (2) for multiple 353 * hard links, we would find the first, but not necessarily 354 * the one specified in the path, (3) we can't try to do 355 * a case-insensitive search to match the right one in (2), 356 * because the underlying filesystem may do things like 357 * decompose composed characters. For most cases, doing 358 * nothing is the right thing when useattrs == 0, so we punt 359 * for now. 360 */ 361 } 362 363 /* 364 * Remove trailing slash except when the resolved pathname 365 * is a single "/". 366 */ 367 if (resolved_len > 1 && resolved[resolved_len - 1] == '/') 368 resolved[resolved_len - 1] = '\0'; 369 return (resolved); 370} 371