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 /*ARGSUSED*/ 118 int 119 _xftw(int ver, const char *path, 120 int (*fn)(const char *, const struct stat *, int), int depth) 121 { 122 struct Var var; 123 int rc; 124 125 var.level = 0; 126 var.odepth = depth; 127 rc = fwalk(path, fn, depth, &var); 128 return (rc); 129 } 130 131 /* 132 * This is the recursive walker. 133 */ 134 static int 135 fwalk(const char *path, int (*fn)(const char *, const struct stat *, int), 136 int depth, struct Var *vp) 137 { 138 size_t n; 139 int rc; 140 int save_errno; 141 DIR *dirp; 142 char *subpath; 143 struct stat sb; 144 struct dirent *direntp; 145 146 vp->level++; 147 148 /* 149 * Try to get file status. 150 * If unsuccessful, errno will say why. 151 * It's ok to have a symbolic link that points to 152 * non-existing file. In this case, pass FTW_NS 153 * to a function instead of aborting fwalk() right away. 154 */ 155 if (nocdstat(path, &sb, vp, 0) < 0) { 156 #ifdef S_IFLNK 157 save_errno = errno; 158 if ((nocdstat(path, &sb, vp, AT_SYMLINK_NOFOLLOW) != -1) && 159 ((sb.st_mode & S_IFMT) == S_IFLNK)) { 160 errno = save_errno; 161 return (*fn)(path, &sb, FTW_NS); 162 } else { 163 errno = save_errno; 164 } 165 #endif 166 return (errno == EACCES? (*fn)(path, &sb, FTW_NS): -1); 167 } 168 169 /* 170 * The stat succeeded, so we know the object exists. 171 * If not a directory, call the user function and return. 172 */ 173 if ((sb.st_mode & S_IFMT) != S_IFDIR) 174 return ((*fn)(path, &sb, FTW_F)); 175 176 /* 177 * The object was a directory. 178 * 179 * Open a file to read the directory 180 */ 181 dirp = nocdopendir(path, vp); 182 183 /* 184 * Call the user function, telling it whether 185 * the directory can be read. If it can't be read 186 * call the user function or indicate an error, 187 * depending on the reason it couldn't be read. 188 */ 189 if (dirp == NULL) 190 return (errno == EACCES? (*fn)(path, &sb, FTW_DNR): -1); 191 192 /* We could read the directory. Call user function. */ 193 rc = (*fn)(path, &sb, FTW_D); 194 if (rc != 0) { 195 (void) closedir(dirp); 196 return (rc); 197 } 198 199 /* 200 * Read the directory one component at a time. 201 * We must ignore "." and "..", but other than that, 202 * just create a path name and call self to check it out. 203 */ 204 while (direntp = readdir(dirp)) { 205 long here; 206 207 if (strcmp(direntp->d_name, ".") == 0 || 208 strcmp(direntp->d_name, "..") == 0) 209 continue; 210 211 /* Create a prefix to which we will append component names */ 212 n = strlen(path); 213 subpath = malloc(n + strlen(direntp->d_name) + 2); 214 if (subpath == 0) { 215 (void) closedir(dirp); 216 errno = ENOMEM; 217 return (-1); 218 } 219 (void) strcpy(subpath, path); 220 if (subpath[0] != '\0' && subpath[n-1] != '/') 221 subpath[n++] = '/'; 222 223 /* Append component name to the working path */ 224 (void) strlcpy(&subpath[n], direntp->d_name, MAXNAMELEN); 225 226 /* 227 * If we are about to exceed our depth, 228 * remember where we are and close a file. 229 */ 230 if (depth <= 1) { 231 here = telldir(dirp); 232 if (closedir(dirp) < 0) { 233 free(subpath); 234 return (-1); 235 } 236 } 237 238 /* 239 * Do a recursive call to process the file. 240 * (watch this, sports fans) 241 */ 242 rc = fwalk(subpath, fn, depth-1, vp); 243 if (rc != 0) { 244 free(subpath); 245 if (depth > 1) 246 (void) closedir(dirp); 247 return (rc); 248 } 249 250 /* 251 * If we closed the file, try to reopen it. 252 */ 253 if (depth <= 1) { 254 dirp = nocdopendir(path, vp); 255 if (dirp == NULL) { 256 free(subpath); 257 return (-1); 258 } 259 seekdir(dirp, here); 260 } 261 free(subpath); 262 } 263 (void) closedir(dirp); 264 return (0); 265 } 266 267 /* 268 * Open a directory with an arbitrarily long path name. If the original 269 * depth arg >= 2, use openat() to make sure that it doesn't fail with 270 * ENAMETOOLONG. 271 */ 272 static DIR * 273 nocdopendir(const char *path, struct Var *vp) 274 { 275 int fd, cfd; 276 DIR *fdd; 277 char *dirp, *token, *ptr; 278 279 fdd = opendir(path); 280 if ((vp->odepth > 1) && (fdd == NULL) && (errno == ENAMETOOLONG)) { 281 /* 282 * Traverse the path using openat() to get the fd for 283 * fdopendir(). 284 */ 285 if ((dirp = strdup(path)) == NULL) { 286 errno = ENAMETOOLONG; 287 return (NULL); 288 } 289 if ((token = strtok_r(dirp, "/", &ptr)) != NULL) { 290 if ((fd = openat(AT_FDCWD, dirp, O_RDONLY)) < 0) { 291 (void) free(dirp); 292 errno = ENAMETOOLONG; 293 return (NULL); 294 } 295 while ((token = strtok_r(NULL, "/", &ptr)) != NULL) { 296 if ((cfd = openat(fd, token, O_RDONLY)) < 0) { 297 (void) close(fd); 298 (void) free(dirp); 299 errno = ENAMETOOLONG; 300 return (NULL); 301 } 302 (void) close(fd); 303 fd = cfd; 304 } 305 (void) free(dirp); 306 return (fdopendir(fd)); 307 } 308 (void) free(dirp); 309 errno = ENAMETOOLONG; 310 } 311 return (fdd); 312 } 313 314 /* 315 * Stat a file with an arbitrarily long path name. If we aren't doing a 316 * stat on the arg passed to _xftw() and if the original depth arg >= 2, 317 * use openat() to make sure that it doesn't fail with ENAMETOOLONG. 318 */ 319 static int 320 nocdstat(const char *path, struct stat *statp, struct Var *vp, int sym) 321 { 322 int fd, cfd; 323 char *dirp, *token, *ptr; 324 int rc; 325 const char *unrootp; 326 int save_err; 327 328 rc = fstatat(AT_FDCWD, path, statp, sym); 329 if ((vp->level > 1) && (vp->odepth >= 2) && (rc < 0) && 330 (errno == ENAMETOOLONG)) { 331 /* Traverse path using openat() to get fd for fstatat(). */ 332 if ((dirp = strdup(path)) == NULL) { 333 errno = ENAMETOOLONG; 334 return (-1); 335 } 336 if ((token = strtok_r(dirp, "/", &ptr)) != NULL) { 337 if ((fd = openat(AT_FDCWD, dirp, O_RDONLY)) < 0) { 338 (void) free(dirp); 339 errno = ENAMETOOLONG; 340 return (-1); 341 } 342 unrootp = get_unrooted(path); 343 while (((token = strtok_r(NULL, "/", &ptr)) != NULL) && 344 (strcmp(token, unrootp) != 0)) { 345 if ((cfd = openat(fd, token, O_RDONLY)) < 0) { 346 (void) close(fd); 347 (void) free(dirp); 348 errno = ENAMETOOLONG; 349 return (0); 350 } 351 (void) close(fd); 352 fd = cfd; 353 } 354 (void) free(dirp); 355 rc = fstatat(fd, unrootp, statp, sym); 356 save_err = errno; 357 (void) close(fd); 358 errno = save_err; 359 return (rc); 360 } 361 (void) free(dirp); 362 errno = ENAMETOOLONG; 363 } 364 return (rc); 365 } 366 367 /* 368 * Return pointer basename of path. This routine doesn't remove 369 * trailing slashes, but there won't be any. 370 */ 371 static const char * 372 get_unrooted(const char *path) 373 { 374 const char *ptr; 375 376 if (!path || !*path) 377 return (NULL); 378 379 ptr = path + strlen(path); 380 /* find last char in path before any trailing slashes */ 381 while (ptr != path && *--ptr == '/') 382 ; 383 384 if (ptr == path) /* all slashes */ 385 return (ptr); 386 387 while (ptr != path) 388 if (*--ptr == '/') 389 return (++ptr); 390 391 return (ptr); 392 } 393