/* Return 1 if FILE is an unwritable non-symlink, 0 if it is writable or some other type of file, -1 and set errno if there is some problem in determining the answer. Use FULL_NAME only if necessary. Set *BUF to the file status. This is to avoid calling euidaccess when FILE is a symlink. */ static int write_protected_non_symlink (int fd_cwd, char const *file, char const *full_name, struct stat *buf) { if (can_write_any_file ()) return 0; if (cache_fstatat (fd_cwd, file, buf, AT_SYMLINK_NOFOLLOW) != 0) return -1; if (S_ISLNK (buf->st_mode)) return 0; /* Here, we know FILE is not a symbolic link. */ /* In order to be reentrant -- i.e., to avoid changing the working directory, and at the same time to be able to deal with alternate access control mechanisms (ACLs, xattr-style attributes) and arbitrarily deep trees -- we need a function like eaccessat, i.e., like Solaris' eaccess, but fd-relative, in the spirit of openat. */ /* In the absence of a native eaccessat function, here are some of the implementation choices [#4 and #5 were suggested by Paul Eggert]: 1) call openat with O_WRONLY|O_NOCTTY Disadvantage: may create the file and doesn't work for directory, may mistakenly report `unwritable' for EROFS or ACLs even though perm bits say the file is writable. 2) fake eaccessat (save_cwd, fchdir, call euidaccess, restore_cwd) Disadvantage: changes working directory (not reentrant) and can't work if save_cwd fails. 3) if (euidaccess (full_name, W_OK) == 0) Disadvantage: doesn't work if full_name is too long. Inefficient for very deep trees (O(Depth^2)). 4) If the full pathname is sufficiently short (say, less than PATH_MAX or 8192 bytes, whichever is shorter): use method (3) (i.e., euidaccess (full_name, W_OK)); Otherwise: vfork, fchdir in the child, run euidaccess in the child, then the child exits with a status that tells the parent whether euidaccess succeeded. This avoids the O(N**2) algorithm of method (3), and it also avoids the failure-due-to-too-long-file-names of method (3), but it's fast in the normal shallow case. It also avoids the lack-of-reentrancy and the save_cwd problems. Disadvantage; it uses a process slot for very-long file names, and would be very slow for hierarchies with many such files. 5) If the full file name is sufficiently short (say, less than PATH_MAX or 8192 bytes, whichever is shorter): use method (3) (i.e., euidaccess (full_name, W_OK)); Otherwise: look just at the file bits. Perhaps issue a warning the first time this occurs. This is like (4), except for the "Otherwise" case where it isn't as "perfect" as (4) but is considerably faster. It conforms to current POSIX, and is uniformly better than what Solaris and FreeBSD do (they mess up with long file names). */ { /* This implements #1: on decent systems, either faccessat is native or /proc/self/fd allows us to skip a chdir. */ if (!openat_needs_fchdir () && faccessat (fd_cwd, file, W_OK, AT_EACCESS) == 0) return 0; /* This implements #5: */ size_t file_name_len = strlen (full_name); if (MIN (PATH_MAX, 8192) <= file_name_len) return ! euidaccess_stat (buf, W_OK); if (euidaccess (full_name, W_OK) == 0) return 0; if (errno == EACCES) { errno = 0; return 1; } /* Perhaps some other process has removed the file, or perhaps this is a buggy NFS client. */ return -1; } }
FTS * fts_open (char * const *argv, register int options, int (*compar) (FTSENT const **, FTSENT const **)) { register FTS *sp; register FTSENT *p, *root; register size_t nitems; FTSENT *parent = NULL; FTSENT *tmp = NULL; /* pacify gcc */ size_t len; bool defer_stat; /* Options check. */ if (options & ~FTS_OPTIONMASK) { __set_errno (EINVAL); return (NULL); } if ((options & FTS_NOCHDIR) && (options & FTS_CWDFD)) { __set_errno (EINVAL); return (NULL); } if ( ! (options & (FTS_LOGICAL | FTS_PHYSICAL))) { __set_errno (EINVAL); return (NULL); } /* Allocate/initialize the stream */ if ((sp = malloc(sizeof(FTS))) == NULL) return (NULL); memset(sp, 0, sizeof(FTS)); sp->fts_compar = compar; sp->fts_options = options; /* Logical walks turn on NOCHDIR; symbolic links are too hard. */ if (ISSET(FTS_LOGICAL)) { SET(FTS_NOCHDIR); CLR(FTS_CWDFD); } /* Initialize fts_cwd_fd. */ sp->fts_cwd_fd = AT_FDCWD; if ( ISSET(FTS_CWDFD) && ! HAVE_OPENAT_SUPPORT) { /* While it isn't technically necessary to open "." this early, doing it here saves us the trouble of ensuring later (where it'd be messier) that "." can in fact be opened. If not, revert to FTS_NOCHDIR mode. */ int fd = open (".", O_RDONLY); if (fd < 0) { /* Even if `.' is unreadable, don't revert to FTS_NOCHDIR mode on systems like Linux+PROC_FS, where our openat emulation is good enough. Note: on a system that emulates openat via /proc, this technique can still fail, but only in extreme conditions, e.g., when the working directory cannot be saved (i.e. save_cwd fails) -- and that happens on Linux only when "." is unreadable and the CWD would be longer than PATH_MAX. FIXME: once Linux kernel openat support is well established, replace the above open call and this entire if/else block with the body of the if-block below. */ if ( openat_needs_fchdir ()) { SET(FTS_NOCHDIR); CLR(FTS_CWDFD); } } else { close (fd); } } /* * Start out with 1K of file name space, and enough, in any case, * to hold the user's file names. */ #ifndef MAXPATHLEN # define MAXPATHLEN 1024 #endif { size_t maxarglen = fts_maxarglen(argv); if (! fts_palloc(sp, MAX(maxarglen, MAXPATHLEN))) goto mem1; } /* Allocate/initialize root's parent. */ if (*argv != NULL) { if ((parent = fts_alloc(sp, "", 0)) == NULL) goto mem2; parent->fts_level = FTS_ROOTPARENTLEVEL; } /* The classic fts implementation would call fts_stat with a new entry for each iteration of the loop below. If the comparison function is not specified or if the FTS_DEFER_STAT option is in effect, don't stat any entry in this loop. This is an attempt to minimize the interval between the initial stat/lstat/fstatat and the point at which a directory argument is first opened. This matters for any directory command line argument that resides on a file system without genuine i-nodes. If you specify FTS_DEFER_STAT along with a comparison function, that function must not access any data via the fts_statp pointer. */ defer_stat = (compar == NULL || ISSET(FTS_DEFER_STAT)); /* Allocate/initialize root(s). */ for (root = NULL, nitems = 0; *argv != NULL; ++argv, ++nitems) { /* Don't allow zero-length file names. */ if ((len = strlen(*argv)) == 0) { __set_errno (ENOENT); goto mem3; } if ((p = fts_alloc(sp, *argv, len)) == NULL) goto mem3; p->fts_level = FTS_ROOTLEVEL; p->fts_parent = parent; p->fts_accpath = p->fts_name; /* Even when defer_stat is true, be sure to stat the first command line argument, since fts_read (at least with FTS_XDEV) requires that. */ if (defer_stat && root != NULL) { p->fts_info = FTS_NSOK; fts_set_stat_required(p, true); } else { p->fts_info = fts_stat(sp, p, false); } /* * If comparison routine supplied, traverse in sorted * order; otherwise traverse in the order specified. */ if (compar) { p->fts_link = root; root = p; } else { p->fts_link = NULL; if (root == NULL) tmp = root = p; else { tmp->fts_link = p; tmp = p; } } } if (compar && nitems > 1) root = fts_sort(sp, root, nitems); /* * Allocate a dummy pointer and make fts_read think that we've just * finished the node before the root(s); set p->fts_info to FTS_INIT * so that everything about the "current" node is ignored. */ if ((sp->fts_cur = fts_alloc(sp, "", 0)) == NULL) goto mem3; sp->fts_cur->fts_link = root; sp->fts_cur->fts_info = FTS_INIT; if (! setup_dir (sp)) goto mem3; /* * If using chdir(2), grab a file descriptor pointing to dot to ensure * that we can get back here; this could be avoided for some file names, * but almost certainly not worth the effort. Slashes, symbolic links, * and ".." are all fairly nasty problems. Note, if we can't get the * descriptor we run anyway, just more slowly. */ if (!ISSET(FTS_NOCHDIR) && !ISSET(FTS_CWDFD) && (sp->fts_rfd = diropen (sp, ".")) < 0) SET(FTS_NOCHDIR); i_ring_init (&sp->fts_fd_ring, -1); return (sp); mem3: fts_lfree(root); free(parent); mem2: free(sp->fts_path); mem1: free(sp); return (NULL); }