+ n -= r;
+ }
+ }
+
+ if (r == -1 || close (fd) == -1) {
+ perror (tmpfile);
+ exit (EXIT_FAILURE);
+ }
+
+ unlink (tmpfile);
+
+ return 0;
+}
+
+/* Adapted from
+https://rwmj.wordpress.com/2010/12/15/tip-audit-virtual-machine-for-setuid-files/
+*/
+static char *full_path (const char *dir, const char *name);
+static struct guestfs_stat_list *lstatlist (const char *dir, char **names);
+static struct guestfs_xattr_list *lxattrlist (const char *dir, char **names);
+static int show_file (const char *dir, const char *name, const struct guestfs_stat *stat, const struct guestfs_xattr_list *xattrs);
+
+typedef int (*visitor_function) (const char *dir, const char *name, const struct guestfs_stat *stat, const struct guestfs_xattr_list *xattrs);
+
+static int
+visit (int depth, const char *dir, visitor_function f)
+{
+ /* Call 'f' with the top directory. Note that ordinary recursive
+ * visits will not otherwise do this, so we have to have a special
+ * case.
+ */
+ if (depth == 0) {
+ struct guestfs_stat *stat;
+ struct guestfs_xattr_list *xattrs;
+ int r;
+
+ stat = guestfs_lstat (g, dir);
+ if (stat == NULL)
+ return -1;
+
+ xattrs = guestfs_lgetxattrs (g, dir);
+ if (xattrs == NULL) {
+ guestfs_free_stat (stat);
+ return -1;
+ }
+
+ r = f (dir, NULL, stat, xattrs);
+ guestfs_free_stat (stat);
+ guestfs_free_xattr_list (xattrs);
+
+ if (r == -1)
+ return -1;
+ }
+
+ int ret = -1;
+ char **names = NULL;
+ char *path = NULL;
+ size_t i, xattrp;
+ struct guestfs_stat_list *stats = NULL;
+ struct guestfs_xattr_list *xattrs = NULL;
+
+ names = guestfs_ls (g, dir);
+ if (names == NULL)
+ goto out;
+
+ stats = lstatlist (dir, names);
+ if (stats == NULL)
+ goto out;
+
+ xattrs = lxattrlist (dir, names);
+ if (xattrs == NULL)
+ goto out;
+
+ /* Call function on everything in this directory. */
+ for (i = 0, xattrp = 0; names[i] != NULL; ++i, ++xattrp) {
+ struct guestfs_xattr_list file_xattrs;
+ size_t nr_xattrs;
+
+ assert (stats->len >= i);
+ assert (xattrs->len >= xattrp);
+
+ /* Find the list of extended attributes for this file. */
+ assert (strlen (xattrs->val[xattrp].attrname) == 0);
+
+ if (xattrs->val[xattrp].attrval_len == 0) {
+ fprintf (stderr, _("%s: error getting extended attrs for %s %s\n"),
+ program_name, dir, names[i]);
+ goto out;
+ }
+ /* lxattrlist function made sure attrval was \0-terminated, so we can do */
+ if (sscanf (xattrs->val[xattrp].attrval, "%zu", &nr_xattrs) != 1) {
+ fprintf (stderr, _("%s: error: cannot parse xattr count for %s %s\n"),
+ program_name, dir, names[i]);
+ goto out;
+ }
+
+ file_xattrs.len = nr_xattrs;
+ file_xattrs.val = &xattrs->val[xattrp];
+ xattrp += nr_xattrs;
+
+ /* Call the function. */
+ if (f (dir, names[i], &stats->val[i], &file_xattrs) == -1)
+ goto out;
+
+ /* Recursively call visit, but only on directories. */
+ if (is_dir (stats->val[i].mode)) {
+ path = full_path (dir, names[i]);
+ if (visit (depth + 1, path, f) == -1)
+ goto out;
+ free (path); path = NULL;
+ }
+ }
+
+ ret = 0;
+
+ out:
+ free (path);
+ if (names)
+ free_strings (names);
+ if (stats)
+ guestfs_free_stat_list (stats);
+ if (xattrs)
+ guestfs_free_xattr_list (xattrs);
+ return ret;
+}
+
+static char *
+full_path (const char *dir, const char *name)
+{
+ int r;
+ char *path;
+
+ if (STREQ (dir, "/"))
+ r = asprintf (&path, "/%s", name ? name : "");
+ else if (name)
+ r = asprintf (&path, "%s/%s", dir, name);
+ else
+ r = asprintf (&path, "%s", dir);
+
+ if (r == -1) {
+ perror ("asprintf");
+ exit (EXIT_FAILURE);
+ }
+
+ return path;
+}
+
+/* This calls guestfs_lstatlist, but it splits the names list up so that we
+ * don't overrun the libguestfs protocol limit.
+ */
+#define LSTATLIST_MAX 1000
+
+static struct guestfs_stat_list *
+lstatlist (const char *dir, char **names)
+{
+ size_t len = count_strings (names);
+ char **first;
+ size_t old_len;
+ struct guestfs_stat_list *ret, *stats;
+
+ ret = malloc (sizeof *ret);
+ if (ret == NULL) {
+ perror ("malloc");
+ exit (EXIT_FAILURE);
+ }
+ ret->len = 0;
+ ret->val = NULL;
+
+ while (len > 0) {
+ first = take_strings (names, LSTATLIST_MAX, &names);
+ len = len <= LSTATLIST_MAX ? 0 : len - LSTATLIST_MAX;
+
+ stats = guestfs_lstatlist (g, dir, first);
+ /* Note we don't need to free up the strings because take_strings
+ * does not do a deep copy.
+ */
+ free (first);
+
+ if (stats == NULL) {
+ free (ret);
+ return NULL;