1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 22 /* 23 * Copyright 2008 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 /* Copyright (c) 1988 AT&T */ 28 /* All Rights Reserved */ 29 30 /* 31 * _xftw - file tree walk the uses expanded stat structure 32 * 33 * int _xftw(path, fn, depth) char *path; int (*fn)(); int depth; 34 * 35 * Given a path name, _xftw starts from the file given by that path 36 * name and visits each file and directory in the tree beneath 37 * that file. If a single file has multiple links within the 38 * structure, it will be visited once for each such link. 39 * For each object visited, fn is called with three arguments. 40 * (*fn) (pathname, statp, ftwflag) 41 * The first contains the path name of the object, the second 42 * contains a pointer to a stat buffer which will usually hold 43 * appropriate information for the object and the third will 44 * contain an integer value giving additional information about 45 * 46 * FTW_F The object is a file for which stat was 47 * successful. It does not guarantee that the 48 * file can actually be read. 49 * 50 * FTW_D The object is a directory for which stat and 51 * open for read were both successful. 52 * 53 * FTW_DNR The object is a directory for which stat 54 * succeeded, but which cannot be read. Because 55 * the directory cannot be read, fn will not be 56 * called for any descendants of this directory. 57 * 58 * FTW_NS Stat failed on the object because of lack of 59 * appropriate permission. This indication will 60 * be given for example for each file in a 61 * directory with read but no execute permission. 62 * Because stat failed, it is not possible to 63 * determine whether this object is a file or a 64 * directory. The stat buffer passed to fn will 65 * contain garbage. Stat failure for any reason 66 * other than lack of permission will be 67 * considered an error and will cause _xftw to stop 68 * and return -1 to its caller. 69 * 70 * If fn returns nonzero, _xftw stops and returns the same value 71 * to its caller. If _xftw gets into other trouble along the way, 72 * it returns -1 and leaves an indication of the cause in errno. 73 * 74 * The third argument to _xftw does not limit the depth to which 75 * _xftw will go. Rather, it limits the depth to which _xftw will 76 * go before it starts recycling file descriptors. In general, 77 * it is necessary to use a file descriptor for each level of the 78 * tree, but they can be recycled for deep trees by saving the 79 * position, closing, re-opening, and seeking. In order to descend 80 * to arbitrary depths, _xftw requires 2 file descriptors to be open 81 * during the call to openat(), therefore if the depth argument 82 * is less than 2 _xftw will not use openat(), and it will fail with 83 * ENAMETOOLONG if it descends to a directory that exceeds PATH_MAX. 84 */ 85 86 /* 87 * this interface uses the expanded stat structure and therefore 88 * must have EFT enabled. 89 */ 90 #ifdef _STYPES 91 #undef _STYPES 92 #endif 93 94 #include "lint.h" 95 #include <sys/types.h> 96 #include <sys/stat.h> 97 #include <fcntl.h> 98 #include <sys/param.h> 99 #include <dirent.h> 100 #include <errno.h> 101 #include <ftw.h> 102 #include <string.h> 103 #include <stdlib.h> 104 #include <unistd.h> 105 106 struct Var { 107 int level; 108 int odepth; 109 }; 110 111 static DIR *nocdopendir(const char *, struct Var *); 112 static int nocdstat(const char *, struct stat *, struct Var *, int); 113 static const char *get_unrooted(const char *); 114 static int fwalk(const char *, int (*)(const char *, const struct stat *, int), 115 int, struct Var *); 116 117 int 118 _xftw(int ver __unused, const char *path, 119 int (*fn)(const char *, const struct stat *, int), int depth) 120 { 121 struct Var var; 122 int rc; 123 124 var.level = 0; 125 var.odepth = depth; 126 rc = fwalk(path, fn, depth, &var); 127 return (rc); 128 } 129 130 /* 131 * This is the recursive walker. 132 */ 133 static int 134 fwalk(const char *path, int (*fn)(const char *, const struct stat *, int), 135 int depth, struct Var *vp) 136 { 137 size_t n; 138 int rc; 139 int save_errno; 140 DIR *dirp; 141 char *subpath; 142 struct stat sb; 143 struct dirent *direntp; 144 145 vp->level++; 146 147 /* 148 * Try to get file status. 149 * If unsuccessful, errno will say why. 150 * It's ok to have a symbolic link that points to 151 * non-existing file. In this case, pass FTW_NS 152 * to a function instead of aborting fwalk() right away. 153 */ 154 if (nocdstat(path, &sb, vp, 0) < 0) { 155 #ifdef S_IFLNK 156 save_errno = errno; 157 if ((nocdstat(path, &sb, vp, AT_SYMLINK_NOFOLLOW) != -1) && 158 ((sb.st_mode & S_IFMT) == S_IFLNK)) { 159 errno = save_errno; 160 return (*fn)(path, &sb, FTW_NS); 161 } else { 162 errno = save_errno; 163 } 164 #endif 165 return (errno == EACCES? (*fn)(path, &sb, FTW_NS): -1); 166 } 167 168 /* 169 * The stat succeeded, so we know the object exists. 170 * If not a directory, call the user function and return. 171 */ 172 if ((sb.st_mode & S_IFMT) != S_IFDIR) 173 return ((*fn)(path, &sb, FTW_F)); 174 175 /* 176 * The object was a directory. 177 * 178 * Open a file to read the directory 179 */ 180 dirp = nocdopendir(path, vp); 181 182 /* 183 * Call the user function, telling it whether 184 * the directory can be read. If it can't be read 185 * call the user function or indicate an error, 186 * depending on the reason it couldn't be read. 187 */ 188 if (dirp == NULL) 189 return (errno == EACCES? (*fn)(path, &sb, FTW_DNR): -1); 190 191 /* We could read the directory. Call user function. */ 192 rc = (*fn)(path, &sb, FTW_D); 193 if (rc != 0) { 194 (void) closedir(dirp); 195 return (rc); 196 } 197 198 /* 199 * Read the directory one component at a time. 200 * We must ignore "." and "..", but other than that, 201 * just create a path name and call self to check it out. 202 */ 203 while ((direntp = readdir(dirp)) != NULL) { 204 long here; 205 206 if (strcmp(direntp->d_name, ".") == 0 || 207 strcmp(direntp->d_name, "..") == 0) 208 continue; 209 210 /* Create a prefix to which we will append component names */ 211 n = strlen(path); 212 subpath = malloc(n + strlen(direntp->d_name) + 2); 213 if (subpath == 0) { 214 (void) closedir(dirp); 215 errno = ENOMEM; 216 return (-1); 217 } 218 (void) strcpy(subpath, path); 219 if (subpath[0] != '\0' && subpath[n-1] != '/') 220 subpath[n++] = '/'; 221 222 /* Append component name to the working path */ 223 (void) strlcpy(&subpath[n], direntp->d_name, MAXNAMELEN); 224 225 /* 226 * If we are about to exceed our depth, 227 * remember where we are and close a file. 228 */ 229 if (depth <= 1) { 230 here = telldir(dirp); 231 if (closedir(dirp) < 0) { 232 free(subpath); 233 return (-1); 234 } 235 } 236 237 /* 238 * Do a recursive call to process the file. 239 * (watch this, sports fans) 240 */ 241 rc = fwalk(subpath, fn, depth-1, vp); 242 if (rc != 0) { 243 free(subpath); 244 if (depth > 1) 245 (void) closedir(dirp); 246 return (rc); 247 } 248 249 /* 250 * If we closed the file, try to reopen it. 251 */ 252 if (depth <= 1) { 253 dirp = nocdopendir(path, vp); 254 if (dirp == NULL) { 255 free(subpath); 256 return (-1); 257 } 258 seekdir(dirp, here); 259 } 260 free(subpath); 261 } 262 (void) closedir(dirp); 263 return (0); 264 } 265 266 /* 267 * Open a directory with an arbitrarily long path name. If the original 268 * depth arg >= 2, use openat() to make sure that it doesn't fail with 269 * ENAMETOOLONG. 270 */ 271 static DIR * 272 nocdopendir(const char *path, struct Var *vp) 273 { 274 int fd, cfd; 275 DIR *fdd; 276 char *dirp, *token, *ptr; 277 278 fdd = opendir(path); 279 if ((vp->odepth > 1) && (fdd == NULL) && (errno == ENAMETOOLONG)) { 280 /* 281 * Traverse the path using openat() to get the fd for 282 * fdopendir(). 283 */ 284 if ((dirp = strdup(path)) == NULL) { 285 errno = ENAMETOOLONG; 286 return (NULL); 287 } 288 if ((token = strtok_r(dirp, "/", &ptr)) != NULL) { 289 if ((fd = openat(AT_FDCWD, dirp, O_RDONLY)) < 0) { 290 (void) free(dirp); 291 errno = ENAMETOOLONG; 292 return (NULL); 293 } 294 while ((token = strtok_r(NULL, "/", &ptr)) != NULL) { 295 if ((cfd = openat(fd, token, O_RDONLY)) < 0) { 296 (void) close(fd); 297 (void) free(dirp); 298 errno = ENAMETOOLONG; 299 return (NULL); 300 } 301 (void) close(fd); 302 fd = cfd; 303 } 304 (void) free(dirp); 305 return (fdopendir(fd)); 306 } 307 (void) free(dirp); 308 errno = ENAMETOOLONG; 309 } 310 return (fdd); 311 } 312 313 /* 314 * Stat a file with an arbitrarily long path name. If we aren't doing a 315 * stat on the arg passed to _xftw() and if the original depth arg >= 2, 316 * use openat() to make sure that it doesn't fail with ENAMETOOLONG. 317 */ 318 static int 319 nocdstat(const char *path, struct stat *statp, struct Var *vp, int sym) 320 { 321 int fd, cfd; 322 char *dirp, *token, *ptr; 323 int rc; 324 const char *unrootp; 325 int save_err; 326 327 rc = fstatat(AT_FDCWD, path, statp, sym); 328 if ((vp->level > 1) && (vp->odepth >= 2) && (rc < 0) && 329 (errno == ENAMETOOLONG)) { 330 /* Traverse path using openat() to get fd for fstatat(). */ 331 if ((dirp = strdup(path)) == NULL) { 332 errno = ENAMETOOLONG; 333 return (-1); 334 } 335 if ((token = strtok_r(dirp, "/", &ptr)) != NULL) { 336 if ((fd = openat(AT_FDCWD, dirp, O_RDONLY)) < 0) { 337 (void) free(dirp); 338 errno = ENAMETOOLONG; 339 return (-1); 340 } 341 unrootp = get_unrooted(path); 342 while (((token = strtok_r(NULL, "/", &ptr)) != NULL) && 343 (strcmp(token, unrootp) != 0)) { 344 if ((cfd = openat(fd, token, O_RDONLY)) < 0) { 345 (void) close(fd); 346 (void) free(dirp); 347 errno = ENAMETOOLONG; 348 return (0); 349 } 350 (void) close(fd); 351 fd = cfd; 352 } 353 (void) free(dirp); 354 rc = fstatat(fd, unrootp, statp, sym); 355 save_err = errno; 356 (void) close(fd); 357 errno = save_err; 358 return (rc); 359 } 360 (void) free(dirp); 361 errno = ENAMETOOLONG; 362 } 363 return (rc); 364 } 365 366 /* 367 * Return pointer basename of path. This routine doesn't remove 368 * trailing slashes, but there won't be any. 369 */ 370 static const char * 371 get_unrooted(const char *path) 372 { 373 const char *ptr; 374 375 if (!path || !*path) 376 return (NULL); 377 378 ptr = path + strlen(path); 379 /* find last char in path before any trailing slashes */ 380 while (ptr != path && *--ptr == '/') 381 ; 382 383 if (ptr == path) /* all slashes */ 384 return (ptr); 385 386 while (ptr != path) 387 if (*--ptr == '/') 388 return (++ptr); 389 390 return (ptr); 391 } 392