+/* If we just ran out of file descriptors, release a file descriptor
+ in the directory chain somewhere leading from DIR->parent->parent
+ up through the root. Return true if successful, false (preserving
+ errno == EMFILE) otherwise.
+
+ Do not release DIR's file descriptor, or DIR's parent, as other
+ code assumes that they work. On some operating systems, another
+ process can claim file descriptor resources as we release them, and
+ some calls or their emulations require multiple file descriptors,
+ so callers should not give up if a single release doesn't work. */
+
+static bool
+open_failure_recover (struct tar_stat_info const *dir)
+{
+ if (errno == EMFILE && dir && dir->parent)
+ {
+ struct tar_stat_info *p;
+ for (p = dir->parent->parent; p; p = p->parent)
+ if (0 < p->fd && (! p->parent || p->parent->fd <= 0))
+ {
+ tar_stat_close (p);
+ return true;
+ }
+ errno = EMFILE;
+ }
+
+ return false;
+}
+
+/* Return the directory entries of ST, in a dynamically allocated buffer,
+ each entry followed by '\0' and the last followed by an extra '\0'.
+ Return null on failure, setting errno. */
+char *
+get_directory_entries (struct tar_stat_info *st)
+{
+ DIR *dirstream;
+ while (! (dirstream = fdopendir (st->fd)) && open_failure_recover (st))
+ continue;
+
+ if (! dirstream)
+ return 0;
+ else
+ {
+ char *entries = streamsavedir (dirstream);
+ int streamsavedir_errno = errno;
+
+ int fd = dirfd (dirstream);
+ if (fd < 0)
+ {
+ /* The dirent.h implementation doesn't use file descriptors
+ for directory streams, so open the directory again. */
+ char const *name = st->orig_file_name;
+ if (closedir (dirstream) != 0)
+ close_diag (name);
+ dirstream = 0;
+ fd = subfile_open (st->parent,
+ st->parent ? last_component (name) : name,
+ open_searchdir_flags);
+ if (fd < 0)
+ fd = - errno;
+ else
+ {
+ struct stat dirst;
+ if (! (fstat (fd, &dirst) == 0
+ && st->stat.st_ino == dirst.st_ino
+ && st->stat.st_dev == dirst.st_dev))
+ {
+ close (fd);
+ fd = - IMPOSTOR_ERRNO;
+ }
+ }
+ }
+
+ st->fd = fd;
+ st->dirstream = dirstream;
+ errno = streamsavedir_errno;
+ return entries;
+ }
+}
+
+/* Dump the directory ST. Return true if successful, false (emitting
+ diagnostics) otherwise. Get ST's entries, recurse through its
+ subdirectories, and clean up file descriptors afterwards. */