1/* $OpenBSD: nftw.c,v 1.2 2003/07/21 21:15:32 millert Exp $ */ 2 3/* 4 * Copyright (c) 2003 Todd C. Miller <Todd.Miller@courtesan.com> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 * 18 * Sponsored in part by the Defense Advanced Research Projects 19 * Agency (DARPA) and Air Force Research Laboratory, Air Force 20 * Materiel Command, USAF, under agreement number F39502-99-1-0512. 21 */ 22 23#if defined(LIBC_SCCS) && !defined(lint) 24static const char rcsid[] = "$OpenBSD: nftw.c,v 1.2 2003/07/21 21:15:32 millert Exp $"; 25#endif /* LIBC_SCCS and not lint */ 26 27#include <stdio.h> 28#include <sys/cdefs.h> 29#include <sys/types.h> 30#include <sys/stat.h> 31#include <errno.h> 32#include <fts.h> 33#include <ftw.h> 34#include <limits.h> 35#include <fcntl.h> 36#include <stdlib.h> 37#include <string.h> 38#include <unistd.h> 39 40static int 41both_ftw(const char *path, 42 int (*ofn)(const char *, const struct stat *, int), 43 int (*nfn)(const char *, const struct stat *, int, struct FTW *), 44 int nfds, int ftwflags) 45{ 46 const char *paths[2]; 47 struct FTW ftw; 48 FTSENT *cur; 49 FTS *ftsp = NULL; 50 int ftsflags, fnflag, error, postorder, sverrno; 51 int cwd_fd = -1; /* cwd_fd != -1 means call chdir a lot */ 52 53#if __DARWIN_UNIX03 54 /* Macro to skip the mount point itself in UNiX03 mode, in legcy 55 mode the mount point is returned, but we don't decend into it */ 56#define SKIP_MOUNT if ((ftwflags & FTW_MOUNT) \ 57 && cur->fts_statp->st_dev != path_stat.st_dev) { \ 58 continue; \ 59 } 60#else 61#define SKIP_MOUNT 62#endif 63 64 /* XXX - nfds is currently unused */ 65 if (nfds < 1 || nfds > OPEN_MAX) { 66 errno = EINVAL; 67 error = -1; 68 goto done; 69 } 70 71 ftsflags = FTS_COMFOLLOW; 72 if (!(ftwflags & FTW_CHDIR)) 73 ftsflags |= FTS_NOCHDIR; 74 if (ftwflags & FTW_MOUNT) 75 ftsflags |= FTS_XDEV; 76 if (ftwflags & FTW_PHYS) { 77 ftsflags |= FTS_PHYSICAL; 78 } else { 79 ftsflags |= FTS_LOGICAL; 80 } 81 postorder = (ftwflags & FTW_DEPTH) != 0; 82 83 /* We have been requested to change directories, and fts doesn't 84 always do it (never for FTS_LOGICAL, and sometimes not for 85 FTS_PHYSICAL) */ 86 if (ftwflags & FTW_CHDIR) { 87 cwd_fd = open(".", O_RDONLY, 0); 88 if (cwd_fd < 0) { 89 error = -1; 90 goto done; 91 } 92 /* Prevent problems if fts ever starts using chdir when passed 93 FTS_PHYSICAL */ 94 ftsflags |= FTS_NOCHDIR; 95 } 96 97#if __DARWIN_UNIX03 98 struct stat path_stat; 99 100 /* UNIX03 requires us to return -1/errno=ELOOP if path 101 is a looping symlink; fts_open is succesful and fts_read 102 gives us FTS_NS which isn't very useful, in fact we get 103 pretty much the same behaviour for ENAMETOOLONG, ENOENT, 104 ENOTDIR, and EACCES */ 105 { 106 int rc = stat(path, &path_stat); 107 if (rc < 0 108 && (errno == ELOOP || errno == ENAMETOOLONG || errno == ENOENT 109 || errno == ENOTDIR || errno == EACCES)) { 110 error = -1; 111 goto done; 112 } 113 if (rc >= 0 && nfn) { 114 if (!S_ISDIR(path_stat.st_mode)) { 115 errno = ENOTDIR; 116 error = -1; 117 goto done; 118 } 119 } 120 } 121#endif 122 paths[0] = path; 123 paths[1] = NULL; 124 ftsp = fts_open((char * const *)paths, ftsflags, NULL); 125 if (ftsp == NULL) { 126 error = -1; 127 goto done; 128 } 129 error = 0; 130 while ((cur = fts_read(ftsp)) != NULL) { 131 switch (cur->fts_info) { 132 case FTS_D: 133 if (postorder) 134 continue; 135 SKIP_MOUNT; 136 /* we will get FTS_DNR next (this is not an issue for 137 FTS_DP, only FTS_D) */ 138 if (access(cur->fts_path, R_OK) != 0) 139 continue; 140 fnflag = FTW_D; 141 break; 142 case FTS_DNR: 143 fnflag = FTW_DNR; 144 break; 145 case FTS_DP: 146 if (!postorder) 147 continue; 148 SKIP_MOUNT; 149 fnflag = FTW_DP; 150 break; 151 case FTS_F: 152 case FTS_DEFAULT: 153 fnflag = FTW_F; 154 break; 155 case FTS_NS: 156 case FTS_NSOK: 157 fnflag = FTW_NS; 158 break; 159 case FTS_SL: 160 fnflag = FTW_SL; 161 break; 162 case FTS_SLNONE: 163 fnflag = nfn ? FTW_SLN : FTW_SL; 164#if __DARWIN_UNIX03 165 { 166 /* The legacy behaviour did not signal an error 167 on symlink loops unless they ended up causing 168 a directory cycle, but the Unix2003 standard 169 requires ELOOP to end ftw and nftw walks with 170 an error */ 171 struct stat sb; 172 int rc = stat(cur->fts_path, &sb); 173 if (rc < 0 && errno == ELOOP) { 174 error = -1; 175 goto done; 176 } 177 } 178#endif 179 break; 180 case FTS_DC: 181#if __DARWIN_UNIX03 182 /* Unix03 says nftw should break cycles and not return 183 errors in non-physical mode (which is definitly what it 184 says ftw can't do) */ 185 if (nfn && !(ftwflags & FTW_PHYS)) { 186 /* 4489297 - when FTW_DEPTH is set, skip 187 the link also */ 188 if (postorder) 189 continue; 190 fnflag = FTW_D; 191 break; 192 } 193#endif 194 errno = ELOOP; 195 /* FALLTHROUGH */ 196 default: 197 error = -1; 198 goto done; 199 } 200 201 if (cwd_fd >= 0) { 202 char *dir, *free_me = NULL; 203 if (fnflag == FTW_D) { 204 dir = cur->fts_path; 205 } else { 206 /* we could alloc just enough for the directory, 207 and use memmove -- but that is a little more 208 error prone, and not noticable in with all the 209 extra work... */ 210 dir = free_me = strdup(cur->fts_path); 211 dir[cur->fts_pathlen - cur->fts_namelen] = '\0'; 212 } 213 int rc = chdir(dir); 214 if (free_me) { 215 free(free_me); 216 } 217 if (rc < 0) { 218 if(cur->fts_pathlen == cur->fts_namelen && 219 fnflag == FTW_DNR) { 220 /* If cwd_fd is our last FD, fts_read will give us FTS_DNR 221 * and fts_path == fts_name == "." 222 * This check results in the correct errno being returned. 223 */ 224 errno = EMFILE; 225 } 226 error = -1; 227 goto done; 228 } 229 } 230 if (nfn) { 231 ftw.base = cur->fts_pathlen - cur->fts_namelen; 232 ftw.level = cur->fts_level; 233 error = nfn(cur->fts_path, cur->fts_statp, fnflag, &ftw); 234 } else { 235 error = ofn(cur->fts_path, cur->fts_statp, fnflag); 236 } 237 if (cwd_fd >= 0) { 238 if (fchdir(cwd_fd) < 0) { 239 error = -1; 240 goto done; 241 } 242 } 243 244 if (error != 0) 245 break; 246 } 247done: 248 sverrno = errno; 249 if(ftsp != NULL) 250 (void) fts_close(ftsp); 251 if(cwd_fd >= 0) 252 (void) close(cwd_fd); 253 errno = sverrno; 254 return (error); 255} 256 257int 258ftw(const char *path, int (*fn)(const char *, const struct stat *, int), 259 int nfds) 260{ 261 /* The legacy implmentation didn't follow symlinks, but Unix03 262 does - this was likely a bug in the legacy implemtation; JKH 263 thinks we ought change the legacy behaviour, and I agree; anyone 264 who doesn't should replace FTW_PHYS with 265 __DARWIN_UNIX03 ? 0 : FTW_PHYS */ 266 return both_ftw(path, fn, NULL, nfds, FTW_PHYS); 267} 268 269int 270nftw(const char *path, int (*fn)(const char *, const struct stat *, int, 271 struct FTW *), int nfds, int ftwflags) 272{ 273 return both_ftw(path, NULL, fn, nfds, ftwflags); 274} 275