Add 'readdir' call.
authorRichard W.M. Jones <rjones@redhat.com>
Wed, 1 Jul 2009 19:56:58 +0000 (20:56 +0100)
committerRichard W.M. Jones <rjones@redhat.com>
Thu, 2 Jul 2009 09:11:55 +0000 (10:11 +0100)
This adds a readdir call (mostly intended for programs).  The
return value is a list of guestfs_dirent structures.

This adds the new types 'struct guestfs_dirent' and
'struct guestfs_dirent_list', along with all the code to
return these in the different language bindings.

Also includes additional tests for OCaml and Perl bindings
to test this.

36 files changed:
.gitignore
capitests/tests.c
daemon/Makefile.am
daemon/actions.h
daemon/readdir.c [new file with mode: 0644]
daemon/stubs.c
fish/cmds.c
fish/completion.c
guestfish-actions.pod
guestfs-actions.pod
guestfs-structs.pod
java/Makefile.am
java/com/redhat/et/libguestfs/Dirent.java [new file with mode: 0644]
java/com/redhat/et/libguestfs/GuestFS.java
java/com_redhat_et_libguestfs_GuestFS.c
ocaml/Makefile.am
ocaml/guestfs.ml
ocaml/guestfs.mli
ocaml/guestfs_c_actions.c
ocaml/t/guestfs_060_readdir.ml [new file with mode: 0644]
perl/Guestfs.xs
perl/lib/Sys/Guestfs.pm
perl/t/060-readdir.t [new file with mode: 0644]
python/guestfs-py.c
python/guestfs.py
ruby/ext/guestfs/_guestfs.c
src/MAX_PROC_NR
src/generator.ml
src/guestfs-actions.c
src/guestfs-actions.h
src/guestfs-structs.h
src/guestfs.c
src/guestfs.h
src/guestfs_protocol.c
src/guestfs_protocol.h
src/guestfs_protocol.x

index 4d1da44..6c58e6f 100644 (file)
@@ -93,6 +93,7 @@ ocaml/examples/lvs
 ocaml/t/guestfs_005_load
 ocaml/t/guestfs_010_launch
 ocaml/t/guestfs_050_lvcreate
+ocaml/t/guestfs_060_readdir
 perl/Guestfs.c
 perl/Guestfs.bs
 perl/Makefile-pl
index df49dfe..9124ea9 100644 (file)
@@ -157,6 +157,7 @@ static void no_test_warnings (void)
   fprintf (stderr, "warning: \"guestfs_df_h\" has no tests\n");
   fprintf (stderr, "warning: \"guestfs_mount_loop\" has no tests\n");
   fprintf (stderr, "warning: \"guestfs_umask\" has no tests\n");
+  fprintf (stderr, "warning: \"guestfs_readdir\" has no tests\n");
 }
 
 static int test_mknod_c_0_skip (void)
index 8884c6b..8b909c5 100644 (file)
@@ -51,6 +51,7 @@ guestfsd_SOURCES = \
        ntfs.c \
        pingdaemon.c \
        proto.c \
+       readdir.c \
        scrub.c \
        sfdisk.c \
        sleep.c \
index ad44d53..9eaf87b 100644 (file)
@@ -158,3 +158,4 @@ extern int do_mkfifo (int mode, char *path);
 extern int do_mknod_b (int mode, int devmajor, int devminor, char *path);
 extern int do_mknod_c (int mode, int devmajor, int devminor, char *path);
 extern int do_umask (int mask);
+extern guestfs_int_dirent_list *do_readdir (char *dir);
diff --git a/daemon/readdir.c b/daemon/readdir.c
new file mode 100644 (file)
index 0000000..cea6fdd
--- /dev/null
@@ -0,0 +1,105 @@
+/* libguestfs - the guestfsd daemon
+ * Copyright (C) 2009 Red Hat Inc. 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <dirent.h>
+
+#include "daemon.h"
+#include "actions.h"
+
+guestfs_int_dirent_list *
+do_readdir (char *path)
+{
+  guestfs_int_dirent_list *ret;
+  guestfs_int_dirent v;
+  DIR *dir;
+  struct dirent *d;
+  int i;
+
+  NEED_ROOT (NULL);
+  ABS_PATH (path, NULL);
+
+  ret = malloc (sizeof *ret);
+  if (ret == NULL) {
+    reply_with_perror ("malloc");
+    return NULL;
+  }
+
+  ret->guestfs_int_dirent_list_len = 0;
+  ret->guestfs_int_dirent_list_val = NULL;
+
+  CHROOT_IN;
+  dir = opendir (path);
+  CHROOT_OUT;
+
+  if (dir == NULL) {
+    reply_with_perror ("opendir: %s", path);
+    free (ret);
+    return NULL;
+  }
+
+  i = 0;
+  while ((d = readdir (dir)) != NULL) {
+    guestfs_int_dirent *p;
+
+    p = realloc (ret->guestfs_int_dirent_list_val,
+                sizeof (guestfs_int_dirent) * (i+1));
+    v.name = strdup (d->d_name);
+    if (!p || !v.name) {
+      reply_with_perror ("allocate");
+      free (ret->guestfs_int_dirent_list_val);
+      free (ret);
+      closedir (dir);
+      return NULL;
+    }
+    ret->guestfs_int_dirent_list_val = p;
+
+    v.ino = d->d_ino;
+    switch (d->d_type) {
+    case DT_BLK: v.ftyp = 'b'; break;
+    case DT_CHR: v.ftyp = 'c'; break;
+    case DT_DIR: v.ftyp = 'd'; break;
+    case DT_FIFO: v.ftyp = 'f'; break;
+    case DT_LNK: v.ftyp = 'l'; break;
+    case DT_REG: v.ftyp = 'r'; break;
+    case DT_SOCK: v.ftyp = 's'; break;
+    case DT_UNKNOWN: v.ftyp = 'u'; break;
+    default: v.ftyp = '?'; break;
+    }
+
+    ret->guestfs_int_dirent_list_val[i] = v;
+
+    i++;
+  }
+
+  ret->guestfs_int_dirent_list_len = i;
+
+  if (closedir (dir) == -1) {
+    reply_with_perror ("closedir");
+    free (ret->guestfs_int_dirent_list_val);
+    free (ret);
+    return NULL;
+  }
+
+  return ret;
+}
index 033f42c..9c2bfef 100644 (file)
@@ -3477,6 +3477,33 @@ done:
   xdr_free ((xdrproc_t) xdr_guestfs_umask_args, (char *) &args);
 }
 
+static void readdir_stub (XDR *xdr_in)
+{
+  guestfs_int_dirent_list *r;
+  struct guestfs_readdir_args args;
+  char *dir;
+
+  memset (&args, 0, sizeof args);
+
+  if (!xdr_guestfs_readdir_args (xdr_in, &args)) {
+    reply_with_error ("%s: daemon failed to decode procedure arguments", "readdir");
+    return;
+  }
+  dir = args.dir;
+
+  r = do_readdir (dir);
+  if (r == NULL)
+    /* do_readdir has already called reply_with_error */
+    goto done;
+
+  struct guestfs_readdir_ret ret;
+  ret.entries = *r;
+  reply ((xdrproc_t) xdr_guestfs_readdir_ret, (char *) &ret);
+  xdr_free ((xdrproc_t) xdr_guestfs_readdir_ret, (char *) &ret);
+done:
+  xdr_free ((xdrproc_t) xdr_guestfs_readdir_args, (char *) &args);
+}
+
 void dispatch_incoming_message (XDR *xdr_in)
 {
   switch (proc_nr) {
@@ -3891,6 +3918,9 @@ void dispatch_incoming_message (XDR *xdr_in)
     case GUESTFS_PROC_UMASK:
       umask_stub (xdr_in);
       break;
+    case GUESTFS_PROC_READDIR:
+      readdir_stub (xdr_in);
+      break;
     default:
       reply_with_error ("dispatch_incoming_message: unknown procedure number %d, set LIBGUESTFS_PATH to point to the matching libguestfs appliance directory", proc_nr);
   }
index fe59737..9b8dfbb 100644 (file)
@@ -139,6 +139,7 @@ void list_commands (void)
   printf ("%-20s %s\n", "pvs", "list the LVM physical volumes (PVs)");
   printf ("%-20s %s\n", "pvs-full", "list the LVM physical volumes (PVs)");
   printf ("%-20s %s\n", "read-lines", "read file as lines");
+  printf ("%-20s %s\n", "readdir", "read directories entries");
   printf ("%-20s %s\n", "resize2fs", "resize an ext2/ext3 filesystem");
   printf ("%-20s %s\n", "rm", "remove a file");
   printf ("%-20s %s\n", "rm-rf", "remove a file or directory recursively");
@@ -676,6 +677,9 @@ void display_command (const char *cmd)
   if (strcasecmp (cmd, "umask") == 0)
     pod2text ("umask - set file mode creation mask (umask)", " umask <mask>\n\nThis function sets the mask used for creating new files and\ndevice nodes to C<mask & 0777>.\n\nTypical umask values would be C<022> which creates new files\nwith permissions like \"-rw-r--r--\" or \"-rwxr-xr-x\", and\nC<002> which creates new files with permissions like\n\"-rw-rw-r--\" or \"-rwxrwxr-x\".\n\nThe default umask is C<022>.  This is important because it\nmeans that directories and device nodes will be created with\nC<0644> or C<0755> mode even if you specify C<0777>.\n\nSee also L<umask(2)>, C<mknod>, C<mkdir>.\n\nThis call returns the previous umask.");
   else
+  if (strcasecmp (cmd, "readdir") == 0)
+    pod2text ("readdir - read directories entries", " readdir <dir>\n\nThis returns the list of directory entries in directory C<dir>.\n\nAll entries in the directory are returned, including C<.> and\nC<..>.  The entries are I<not> sorted, but returned in the same\norder as the underlying filesystem.\n\nThis function is primarily intended for use by programs.  To\nget a simple list of names, use C<ls>.  To get a printable\ndirectory for human consumption, use C<ll>.");
+  else
     display_builtin_command (cmd);
 }
 
@@ -813,6 +817,21 @@ static void print_statvfs (struct guestfs_statvfs *statvfs)
   printf ("namemax: %" PRIi64 "\n", statvfs->namemax);
 }
 
+static void print_dirent (struct guestfs_dirent *dirent)
+{
+  printf ("ino: %" PRIi64 "\n", dirent->ino);
+  printf ("ftyp: %c\n", dirent->ftyp);
+  printf ("name: %s\n", dirent->name);
+}
+
+static void print_dirent_list (struct guestfs_dirent_list *dirents)
+{
+  int i;
+
+  for (i = 0; i < dirents->len; ++i)
+    print_dirent (&dirents->val[i]);
+}
+
 static int run_launch (const char *cmd, int argc, char *argv[])
 {
   int r;
@@ -3329,6 +3348,23 @@ static int run_umask (const char *cmd, int argc, char *argv[])
   return 0;
 }
 
+static int run_readdir (const char *cmd, int argc, char *argv[])
+{
+  struct guestfs_dirent_list *r;
+  const char *dir;
+  if (argc != 1) {
+    fprintf (stderr, "%s should have 1 parameter(s)\n", cmd);
+    fprintf (stderr, "type 'help %s' for help on %s\n", cmd, cmd);
+    return -1;
+  }
+  dir = argv[0];
+  r = guestfs_readdir (g, dir);
+  if (r == NULL) return -1;
+  print_dirent_list (r);
+  guestfs_free_dirent_list (r);
+  return 0;
+}
+
 int run_action (const char *cmd, int argc, char *argv[])
 {
   if (strcasecmp (cmd, "launch") == 0 || strcasecmp (cmd, "run") == 0)
@@ -3811,6 +3847,9 @@ int run_action (const char *cmd, int argc, char *argv[])
   if (strcasecmp (cmd, "umask") == 0)
     return run_umask (cmd, argc, argv);
   else
+  if (strcasecmp (cmd, "readdir") == 0)
+    return run_readdir (cmd, argc, argv);
+  else
     {
       fprintf (stderr, "%s: unknown command\n", cmd);
       return -1;
index 2ddae2e..b506559 100644 (file)
@@ -207,6 +207,7 @@ static const char *const commands[] = {
   "mknod-b",
   "mknod-c",
   "umask",
+  "readdir",
   NULL
 };
 
index e727120..e635e54 100644 (file)
@@ -1235,6 +1235,20 @@ Note that this function cannot correctly handle binary files
 as end of line).  For those you need to use the C<read-file>
 function which has a more complex interface.
 
+=head2 readdir
+
+ readdir dir
+
+This returns the list of directory entries in directory C<dir>.
+
+All entries in the directory are returned, including C<.> and
+C<..>.  The entries are I<not> sorted, but returned in the same
+order as the underlying filesystem.
+
+This function is primarily intended for use by programs.  To
+get a simple list of names, use C<ls>.  To get a printable
+directory for human consumption, use C<ll>.
+
 =head2 resize2fs
 
  resize2fs device
index 5f66318..5821e0e 100644 (file)
@@ -1647,6 +1647,26 @@ This function returns a NULL-terminated array of strings
 (like L<environ(3)>), or NULL if there was an error.
 I<The caller must free the strings and the array after use>.
 
+=head2 guestfs_readdir
+
+ struct guestfs_dirent_list *guestfs_readdir (guestfs_h *handle,
+               const char *dir);
+
+This returns the list of directory entries in directory C<dir>.
+
+All entries in the directory are returned, including C<.> and
+C<..>.  The entries are I<not> sorted, but returned in the same
+order as the underlying filesystem.
+
+This function is primarily intended for use by programs.  To
+get a simple list of names, use C<guestfs_ls>.  To get a printable
+directory for human consumption, use C<guestfs_ll>.
+
+This function returns a C<struct guestfs_dirent_list *>
+(see E<lt>guestfs-structs.hE<gt>),
+or NULL if there was an error.
+I<The caller must call C<guestfs_free_dirent_list> after use>.
+
 =head2 guestfs_resize2fs
 
  int guestfs_resize2fs (guestfs_h *handle,
index 31b9ff2..dd6b0ed 100644 (file)
  
  void guestfs_free_lvm_lv_list (struct guestfs_free_lvm_lv_list *);
 
+=head2 guestfs_stat
+
+ struct guestfs_stat {
+   int64_t dev;
+   int64_t ino;
+   int64_t mode;
+   int64_t nlink;
+   int64_t uid;
+   int64_t gid;
+   int64_t rdev;
+   int64_t size;
+   int64_t blksize;
+   int64_t blocks;
+   int64_t atime;
+   int64_t mtime;
+   int64_t ctime;
+ };
+
+=head2 guestfs_statvfs
+
+ struct guestfs_statvfs {
+   int64_t bsize;
+   int64_t frsize;
+   int64_t blocks;
+   int64_t bfree;
+   int64_t bavail;
+   int64_t files;
+   int64_t ffree;
+   int64_t favail;
+   int64_t fsid;
+   int64_t flag;
+   int64_t namemax;
+ };
+
+=head2 guestfs_dirent
+
+ struct guestfs_dirent {
+   int64_t ino;
+   char ftyp;
+   char *name;
+ };
+
+ struct guestfs_dirent_list {
+   uint32_t len; /* Number of elements in list. */
+   struct guestfs_dirent *val; /* Elements. */
+ };
+ void guestfs_free_dirent_list (struct guestfs_free_dirent_list *);
+
index 633ecda..ea90a48 100644 (file)
@@ -25,6 +25,7 @@ java_sources = \
        $(CPTH)/LV.java \
        $(CPTH)/Stat.java \
        $(CPTH)/StatVFS.java \
+       $(CPTH)/Dirent.java \
        $(CPTH)/GuestFS.java
 
 java_tests = \
diff --git a/java/com/redhat/et/libguestfs/Dirent.java b/java/com/redhat/et/libguestfs/Dirent.java
new file mode 100644 (file)
index 0000000..f6ef3d8
--- /dev/null
@@ -0,0 +1,34 @@
+/* libguestfs generated file
+ * WARNING: THIS FILE IS GENERATED BY 'src/generator.ml'.
+ * ANY CHANGES YOU MAKE TO THIS FILE WILL BE LOST.
+ *
+ * Copyright (C) 2009 Red Hat Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package com.redhat.et.libguestfs;
+
+/**
+ * Libguestfs Dirent structure.
+ *
+ * @author rjones
+ * @see GuestFS
+ */
+public class Dirent {
+  public long ino;
+  public char ftyp;
+  public String name;
+}
index cc94a98..e8f36ff 100644 (file)
@@ -29,6 +29,7 @@ import com.redhat.et.libguestfs.LV;
 import com.redhat.et.libguestfs.Stat;
 import com.redhat.et.libguestfs.StatVFS;
 import com.redhat.et.libguestfs.IntBool;
+import com.redhat.et.libguestfs.Dirent;
 
 /**
  * The GuestFS object is a libguestfs handle.
@@ -4054,4 +4055,30 @@ public HashMap<String,String> test0rhashtableerr ()
   private native int _umask (long g, int mask)
     throws LibGuestFSException;
 
+  /**
+   * read directories entries
+   * <p>
+   * This returns the list of directory entries in directory
+   * "dir".
+   * <p>
+   * All entries in the directory are returned, including "."
+   * and "..". The entries are *not* sorted, but returned in
+   * the same order as the underlying filesystem.
+   * <p>
+   * This function is primarily intended for use by programs.
+   * To get a simple list of names, use "g.ls". To get a
+   * printable directory for human consumption, use "g.ll".
+   * <p>
+   * @throws LibGuestFSException
+   */
+  public Dirent[] readdir (String dir)
+    throws LibGuestFSException
+  {
+    if (g == 0)
+      throw new LibGuestFSException ("readdir: handle is closed");
+    return _readdir (g, dir);
+  }
+  private native Dirent[] _readdir (long g, String dir)
+    throws LibGuestFSException;
+
 }
index 91095a4..52137ec 100644 (file)
@@ -4635,3 +4635,39 @@ Java_com_redhat_et_libguestfs_GuestFS__1umask
   return (jint) r;
 }
 
+JNIEXPORT jobjectArray JNICALL
+Java_com_redhat_et_libguestfs_GuestFS__1readdir
+  (JNIEnv *env, jobject obj, jlong jg, jstring jdir)
+{
+  guestfs_h *g = (guestfs_h *) (long) jg;
+  jobjectArray jr;
+  jclass cl;
+  jfieldID fl;
+  jobject jfl;
+  struct guestfs_dirent_list *r;
+  const char *dir;
+  int i;
+
+  dir = (*env)->GetStringUTFChars (env, jdir, NULL);
+  r = guestfs_readdir (g, dir);
+  (*env)->ReleaseStringUTFChars (env, jdir, dir);
+  if (r == NULL) {
+    throw_exception (env, guestfs_last_error (g));
+    return NULL;
+  }
+  cl = (*env)->FindClass (env, "com/redhat/et/libguestfs/Dirent");
+  jr = (*env)->NewObjectArray (env, r->len, cl, NULL);
+  for (i = 0; i < r->len; ++i) {
+    jfl = (*env)->AllocObject (env, cl);
+    fl = (*env)->GetFieldID (env, cl, "ino", "J");
+    (*env)->SetLongField (env, jfl, fl, r->val[i].ino);
+    fl = (*env)->GetFieldID (env, cl, "ftyp", "J");
+    (*env)->SetLongField (env, jfl, fl, r->val[i].ftyp);
+    fl = (*env)->GetFieldID (env, cl, "name", "Ljava/lang/String;");
+    (*env)->SetObjectField (env, jfl, fl, (*env)->NewStringUTF (env, r->val[i].name));
+    (*env)->SetObjectArrayElement (env, jfl, i, jfl);
+  }
+  guestfs_free_dirent_list (r);
+  return jr;
+}
+
index cb4ad34..bed1f92 100644 (file)
@@ -50,9 +50,11 @@ TESTS_ENVIRONMENT = \
        $(VG)
 
 TESTS = run-bindtests \
-       t/guestfs_005_load t/guestfs_010_launch t/guestfs_050_lvcreate
+       t/guestfs_005_load t/guestfs_010_launch t/guestfs_050_lvcreate \
+       t/guestfs_060_readdir
 noinst_DATA += bindtests \
-       t/guestfs_005_load t/guestfs_010_launch t/guestfs_050_lvcreate
+       t/guestfs_005_load t/guestfs_010_launch t/guestfs_050_lvcreate \
+       t/guestfs_060_readdir
 
 bindtests: bindtests.ml mlguestfs.cmxa
        $(OCAMLFIND) ocamlopt -cclib -L$(top_builddir)/src/.libs -I . unix.cmxa mlguestfs.cmxa $< -o $@
@@ -66,6 +68,9 @@ t/guestfs_010_launch: t/guestfs_010_launch.ml mlguestfs.cmxa
 t/guestfs_050_lvcreate: t/guestfs_050_lvcreate.ml mlguestfs.cmxa
        $(OCAMLFIND) ocamlopt -cclib -L$(top_builddir)/src/.libs -I . unix.cmxa mlguestfs.cmxa $< -o $@
 
+t/guestfs_060_readdir: t/guestfs_060_readdir.ml mlguestfs.cmxa
+       $(OCAMLFIND) ocamlopt -cclib -L$(top_builddir)/src/.libs -I . unix.cmxa mlguestfs.cmxa $< -o $@
+
 .mli.cmi:
        $(OCAMLFIND) ocamlc -c $<
 .ml.cmo:
index 0bf458c..0d3349f 100644 (file)
@@ -115,6 +115,12 @@ type statvfs = {
   namemax : int64;
 }
 
+type dirent = {
+  ino : int64;
+  ftyp : char;
+  name : string;
+}
+
 external test0 : t -> string -> string option -> string array -> bool -> int -> string -> string -> unit = "ocaml_guestfs_test0_byte" "ocaml_guestfs_test0"
 external test0rint : t -> string -> int = "ocaml_guestfs_test0rint"
 external test0rinterr : t -> int = "ocaml_guestfs_test0rinterr"
@@ -306,3 +312,4 @@ external mkfifo : t -> int -> string -> unit = "ocaml_guestfs_mkfifo"
 external mknod_b : t -> int -> int -> int -> string -> unit = "ocaml_guestfs_mknod_b"
 external mknod_c : t -> int -> int -> int -> string -> unit = "ocaml_guestfs_mknod_c"
 external umask : t -> int -> int = "ocaml_guestfs_umask"
+external readdir : t -> string -> dirent array = "ocaml_guestfs_readdir"
index 5cab588..d15335e 100644 (file)
@@ -124,6 +124,12 @@ type statvfs = {
   namemax : int64;
 }
 
+type dirent = {
+  ino : int64;
+  ftyp : char;
+  name : string;
+}
+
 val test0 : t -> string -> string option -> string array -> bool -> int -> string -> string -> unit
 (** internal test function - do not use *)
 
@@ -697,3 +703,6 @@ val mknod_c : t -> int -> int -> int -> string -> unit
 val umask : t -> int -> int
 (** set file mode creation mask (umask) *)
 
+val readdir : t -> string -> dirent array
+(** read directories entries *)
+
index 6db7239..498b4b1 100644 (file)
@@ -328,6 +328,41 @@ copy_statvfs (const struct guestfs_statvfs *statvfs)
   CAMLreturn (rv);
 }
 
+static CAMLprim value
+copy_dirent (const struct guestfs_dirent *dirent)
+{
+  CAMLparam0 ();
+  CAMLlocal2 (rv, v);
+
+  rv = caml_alloc (3, 0);
+  v = caml_copy_int64 (dirent->ino);
+  Store_field (rv, 0, v);
+  v = Val_int (dirent->ftyp);
+  Store_field (rv, 1, v);
+  v = caml_copy_string (dirent->name);
+  Store_field (rv, 2, v);
+  CAMLreturn (rv);
+}
+
+static CAMLprim value
+copy_dirent_list (const struct guestfs_dirent_list *dirents)
+{
+  CAMLparam0 ();
+  CAMLlocal2 (rv, v);
+  int i;
+
+  if (dirents->len == 0)
+    CAMLreturn (Atom (0));
+  else {
+    rv = caml_alloc (dirents->len, 0);
+    for (i = 0; i < dirents->len; ++i) {
+      v = copy_dirent (&dirents->val[i]);
+      caml_modify (&Field (rv, i), v);
+    }
+    CAMLreturn (rv);
+  }
+}
+
 CAMLprim value
 ocaml_guestfs_test0 (value gv, value strv, value optstrv, value strlistv, value bv, value integerv, value fileinv, value fileoutv)
 {
@@ -4902,3 +4937,27 @@ ocaml_guestfs_umask (value gv, value maskv)
   CAMLreturn (rv);
 }
 
+CAMLprim value
+ocaml_guestfs_readdir (value gv, value dirv)
+{
+  CAMLparam2 (gv, dirv);
+  CAMLlocal1 (rv);
+
+  guestfs_h *g = Guestfs_val (gv);
+  if (g == NULL)
+    caml_failwith ("readdir: used handle after closing it");
+
+  const char *dir = String_val (dirv);
+  struct guestfs_dirent_list *r;
+
+  caml_enter_blocking_section ();
+  r = guestfs_readdir (g, dir);
+  caml_leave_blocking_section ();
+  if (r == NULL)
+    ocaml_guestfs_raise_error (g, "readdir");
+
+  rv = copy_dirent_list (r);
+  guestfs_free_dirent_list (r);
+  CAMLreturn (rv);
+}
+
diff --git a/ocaml/t/guestfs_060_readdir.ml b/ocaml/t/guestfs_060_readdir.ml
new file mode 100644 (file)
index 0000000..f333810
--- /dev/null
@@ -0,0 +1,54 @@
+(* libguestfs OCaml bindings
+ * Copyright (C) 2009 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *)
+
+open Unix
+
+let () =
+  let g = Guestfs.create () in
+
+  let fd = openfile "test.img" [O_WRONLY;O_CREAT;O_NOCTTY;O_TRUNC] 0o666 in
+  ftruncate fd (10 * 1024 * 1024);
+  close fd;
+
+  Guestfs.add_drive g "test.img";
+  Guestfs.launch g;
+  Guestfs.wait_ready g;
+
+  Guestfs.sfdisk g "/dev/sda" 0 0 0 [|","|];
+  Guestfs.mkfs g "ext2" "/dev/sda1";
+  Guestfs.mount g "/dev/sda1" "/";
+  Guestfs.mkdir g "/p";
+  Guestfs.touch g "/q";
+
+  let dirs = Guestfs.readdir g "/" in
+  let dirs = Array.to_list dirs in
+  let cmp { Guestfs.name = n1 } { Guestfs.name = n2 } = compare n1 n2 in
+  let dirs = List.sort cmp dirs in
+  let dirs = List.map (
+    fun { Guestfs.name = name; Guestfs.ftyp = ftyp } -> (name, ftyp)
+  ) dirs in
+
+  if dirs <> [ ".", 'd';
+              "..", 'd';
+              "lost+found", 'd';
+              "p", 'd';
+              "q", 'r' ] then
+    failwith "Guestfs.readdir returned incorrect result";
+
+  Guestfs.close g;
+  unlink "test.img"
index 8f4ab5f..e1cd84a 100644 (file)
@@ -356,7 +356,7 @@ PREINIT:
         (void) hv_store (hv, "pe_start", 8, my_newSVull (valout->val[i].pe_start), 0);
         (void) hv_store (hv, "pv_mda_count", 12, my_newSVll (valout->val[i].pv_mda_count), 0);
         (void) hv_store (hv, "pv_mda_free", 11, my_newSVull (valout->val[i].pv_mda_free), 0);
-        PUSHs (sv_2mortal ((SV *) hv));
+        PUSHs (sv_2mortal (newRV ((SV *) hv)));
       }
       guestfs_free_lvm_pv_list (valout);
 
@@ -388,7 +388,7 @@ PREINIT:
         (void) hv_store (hv, "pe_start", 8, my_newSVull (valout->val[i].pe_start), 0);
         (void) hv_store (hv, "pv_mda_count", 12, my_newSVll (valout->val[i].pv_mda_count), 0);
         (void) hv_store (hv, "pv_mda_free", 11, my_newSVull (valout->val[i].pv_mda_free), 0);
-        PUSHs (sv_2mortal ((SV *) hv));
+        PUSHs (sv_2mortal (newRV ((SV *) hv)));
       }
       guestfs_free_lvm_pv_list (valout);
 
@@ -426,7 +426,7 @@ PREINIT:
         (void) hv_store (hv, "vg_tags", 7, newSVpv (valout->val[i].vg_tags, 0), 0);
         (void) hv_store (hv, "vg_mda_count", 12, my_newSVll (valout->val[i].vg_mda_count), 0);
         (void) hv_store (hv, "vg_mda_free", 11, my_newSVull (valout->val[i].vg_mda_free), 0);
-        PUSHs (sv_2mortal ((SV *) hv));
+        PUSHs (sv_2mortal (newRV ((SV *) hv)));
       }
       guestfs_free_lvm_vg_list (valout);
 
@@ -463,7 +463,7 @@ PREINIT:
         (void) hv_store (hv, "vg_tags", 7, newSVpv (valout->val[i].vg_tags, 0), 0);
         (void) hv_store (hv, "vg_mda_count", 12, my_newSVll (valout->val[i].vg_mda_count), 0);
         (void) hv_store (hv, "vg_mda_free", 11, my_newSVull (valout->val[i].vg_mda_free), 0);
-        PUSHs (sv_2mortal ((SV *) hv));
+        PUSHs (sv_2mortal (newRV ((SV *) hv)));
       }
       guestfs_free_lvm_vg_list (valout);
 
@@ -498,7 +498,7 @@ PREINIT:
         (void) hv_store (hv, "lv_tags", 7, newSVpv (valout->val[i].lv_tags, 0), 0);
         (void) hv_store (hv, "mirror_log", 10, newSVpv (valout->val[i].mirror_log, 0), 0);
         (void) hv_store (hv, "modules", 7, newSVpv (valout->val[i].modules, 0), 0);
-        PUSHs (sv_2mortal ((SV *) hv));
+        PUSHs (sv_2mortal (newRV ((SV *) hv)));
       }
       guestfs_free_lvm_lv_list (valout);
 
@@ -532,7 +532,7 @@ PREINIT:
         (void) hv_store (hv, "lv_tags", 7, newSVpv (valout->val[i].lv_tags, 0), 0);
         (void) hv_store (hv, "mirror_log", 10, newSVpv (valout->val[i].mirror_log, 0), 0);
         (void) hv_store (hv, "modules", 7, newSVpv (valout->val[i].modules, 0), 0);
-        PUSHs (sv_2mortal ((SV *) hv));
+        PUSHs (sv_2mortal (newRV ((SV *) hv)));
       }
       guestfs_free_lvm_lv_list (valout);
 
@@ -1185,7 +1185,7 @@ PREINIT:
         (void) hv_store (hv, "pe_start", 8, my_newSVull (physvols->val[i].pe_start), 0);
         (void) hv_store (hv, "pv_mda_count", 12, my_newSVll (physvols->val[i].pv_mda_count), 0);
         (void) hv_store (hv, "pv_mda_free", 11, my_newSVull (physvols->val[i].pv_mda_free), 0);
-        PUSHs (sv_2mortal ((SV *) hv));
+        PUSHs (sv_2mortal (newRV ((SV *) hv)));
       }
       guestfs_free_lvm_pv_list (physvols);
 
@@ -1222,7 +1222,7 @@ PREINIT:
         (void) hv_store (hv, "vg_tags", 7, newSVpv (volgroups->val[i].vg_tags, 0), 0);
         (void) hv_store (hv, "vg_mda_count", 12, my_newSVll (volgroups->val[i].vg_mda_count), 0);
         (void) hv_store (hv, "vg_mda_free", 11, my_newSVull (volgroups->val[i].vg_mda_free), 0);
-        PUSHs (sv_2mortal ((SV *) hv));
+        PUSHs (sv_2mortal (newRV ((SV *) hv)));
       }
       guestfs_free_lvm_vg_list (volgroups);
 
@@ -1256,7 +1256,7 @@ PREINIT:
         (void) hv_store (hv, "lv_tags", 7, newSVpv (logvols->val[i].lv_tags, 0), 0);
         (void) hv_store (hv, "mirror_log", 10, newSVpv (logvols->val[i].mirror_log, 0), 0);
         (void) hv_store (hv, "modules", 7, newSVpv (logvols->val[i].modules, 0), 0);
-        PUSHs (sv_2mortal ((SV *) hv));
+        PUSHs (sv_2mortal (newRV ((SV *) hv)));
       }
       guestfs_free_lvm_lv_list (logvols);
 
@@ -2970,3 +2970,25 @@ PREINIT:
  OUTPUT:
       RETVAL
 
+void
+readdir (g, dir)
+      guestfs_h *g;
+      char *dir;
+PREINIT:
+      struct guestfs_dirent_list *entries;
+      int i;
+      HV *hv;
+ PPCODE:
+      entries = guestfs_readdir (g, dir);
+      if (entries == NULL)
+        croak ("readdir: %s", guestfs_last_error (g));
+      EXTEND (SP, entries->len);
+      for (i = 0; i < entries->len; ++i) {
+        hv = newHV ();
+        (void) hv_store (hv, "ino", 3, my_newSVull (entries->val[i].ino), 0);
+        (void) hv_store (hv, "ftyp", 4, newSVpv (&entries->val[i].ftyp, 1), 0);
+        (void) hv_store (hv, "name", 4, newSVpv (entries->val[i].name, 0), 0);
+        PUSHs (newRV (sv_2mortal ((SV *) hv)));
+      }
+      guestfs_free_dirent_list (entries);
+
index cd26bed..e624d55 100644 (file)
@@ -1118,6 +1118,18 @@ Note that this function cannot correctly handle binary files
 as end of line).  For those you need to use the C<$h-E<gt>read_file>
 function which has a more complex interface.
 
+=item @entries = $h->readdir ($dir);
+
+This returns the list of directory entries in directory C<dir>.
+
+All entries in the directory are returned, including C<.> and
+C<..>.  The entries are I<not> sorted, but returned in the same
+order as the underlying filesystem.
+
+This function is primarily intended for use by programs.  To
+get a simple list of names, use C<$h-E<gt>ls>.  To get a printable
+directory for human consumption, use C<$h-E<gt>ll>.
+
 =item $h->resize2fs ($device);
 
 This resizes an ext2 or ext3 filesystem to match the size of
diff --git a/perl/t/060-readdir.t b/perl/t/060-readdir.t
new file mode 100644 (file)
index 0000000..17dfd24
--- /dev/null
@@ -0,0 +1,63 @@
+# libguestfs Perl bindings -*- perl -*-
+# Copyright (C) 2009 Red Hat Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+use strict;
+use warnings;
+use Test::More tests => 13;
+
+use Sys::Guestfs;
+
+my $h = Sys::Guestfs->new ();
+ok ($h);
+open FILE, ">test.img";
+truncate FILE, 10*1024*1024;
+close FILE;
+ok (1);
+
+$h->add_drive ("test.img");
+ok (1);
+
+$h->launch ();
+ok (1);
+$h->wait_ready ();
+ok (1);
+
+$h->sfdisk ("/dev/sda", 0, 0, 0, [","]);
+ok (1);
+$h->mkfs ("ext2", "/dev/sda1");
+ok (1);
+$h->mount ("/dev/sda1", "/");
+ok (1);
+$h->mkdir ("/p");
+ok (1);
+$h->touch ("/q");
+ok (1);
+
+my @dirs = $h->readdir ("/");
+@dirs = sort { $a->{name} cmp $b->{name} } @dirs;
+foreach (@dirs) {
+  print "$_->{name} $_->{ino} $_->{ftyp}\n";
+}
+ok (1);
+
+$h->sync ();
+ok (1);
+
+undef $h;
+ok (1);
+
+unlink ("test.img");
index 98a14a0..44b446a 100644 (file)
@@ -389,6 +389,33 @@ put_statvfs (struct guestfs_statvfs *statvfs)
 };
 
 static PyObject *
+put_dirent (struct guestfs_dirent *dirent)
+{
+  PyObject *dict;
+
+  dict = PyDict_New ();
+  PyDict_SetItemString (dict, "ino",
+                        PyLong_FromLongLong (dirent->ino));
+  PyDict_SetItemString (dict, "ftyp",
+                        PyString_FromStringAndSize (&dirent->ftyp, 1));
+  PyDict_SetItemString (dict, "name",
+                        PyString_FromString (dirent->name));
+  return dict;
+};
+
+static PyObject *
+put_dirent_list (struct guestfs_dirent_list *dirents)
+{
+  PyObject *list;
+  int i;
+
+  list = PyList_New (dirents->len);
+  for (i = 0; i < dirents->len; ++i)
+    PyList_SetItem (list, i, put_dirent (&dirents->val[i]));
+  return list;
+};
+
+static PyObject *
 py_guestfs_test0 (PyObject *self, PyObject *args)
 {
   PyObject *py_g;
@@ -5187,6 +5214,31 @@ py_guestfs_umask (PyObject *self, PyObject *args)
   return py_r;
 }
 
+static PyObject *
+py_guestfs_readdir (PyObject *self, PyObject *args)
+{
+  PyObject *py_g;
+  guestfs_h *g;
+  PyObject *py_r;
+  struct guestfs_dirent_list *r;
+  const char *dir;
+
+  if (!PyArg_ParseTuple (args, (char *) "Os:guestfs_readdir",
+                         &py_g, &dir))
+    return NULL;
+  g = get_handle (py_g);
+
+  r = guestfs_readdir (g, dir);
+  if (r == NULL) {
+    PyErr_SetString (PyExc_RuntimeError, guestfs_last_error (g));
+    return NULL;
+  }
+
+  py_r = put_dirent_list (r);
+  guestfs_free_dirent_list (r);
+  return py_r;
+}
+
 static PyMethodDef methods[] = {
   { (char *) "create", py_guestfs_create, METH_VARARGS, NULL },
   { (char *) "close", py_guestfs_close, METH_VARARGS, NULL },
@@ -5381,6 +5433,7 @@ static PyMethodDef methods[] = {
   { (char *) "mknod_b", py_guestfs_mknod_b, METH_VARARGS, NULL },
   { (char *) "mknod_c", py_guestfs_mknod_c, METH_VARARGS, NULL },
   { (char *) "umask", py_guestfs_umask, METH_VARARGS, NULL },
+  { (char *) "readdir", py_guestfs_readdir, METH_VARARGS, NULL },
   { NULL, NULL, 0, NULL }
 };
 
index ad44df7..784c567 100644 (file)
@@ -1958,3 +1958,20 @@ class GuestFS:
         """
         return libguestfsmod.umask (self._o, mask)
 
+    def readdir (self, dir):
+        u"""This returns the list of directory entries in directory
+        "dir".
+        
+        All entries in the directory are returned, including "."
+        and "..". The entries are *not* sorted, but returned in
+        the same order as the underlying filesystem.
+        
+        This function is primarily intended for use by programs.
+        To get a simple list of names, use "g.ls". To get a
+        printable directory for human consumption, use "g.ll".
+        
+        This function returns a list of directory entries. Each
+        directory entry is represented as a dictionary.
+        """
+        return libguestfsmod.readdir (self._o, dir)
+
index 606e9a2..38776b6 100644 (file)
@@ -4865,6 +4865,38 @@ static VALUE ruby_guestfs_umask (VALUE gv, VALUE maskv)
   return INT2NUM (r);
 }
 
+static VALUE ruby_guestfs_readdir (VALUE gv, VALUE dirv)
+{
+  guestfs_h *g;
+  Data_Get_Struct (gv, guestfs_h, g);
+  if (!g)
+    rb_raise (rb_eArgError, "%s: used handle after closing it", "readdir");
+
+  Check_Type (dirv, T_STRING);
+  const char *dir = StringValueCStr (dirv);
+  if (!dir)
+    rb_raise (rb_eTypeError, "expected string for parameter %s of %s",
+              "dir", "readdir");
+
+  struct guestfs_dirent_list *r;
+
+  r = guestfs_readdir (g, dir);
+  if (r == NULL)
+    rb_raise (e_Error, "%s", guestfs_last_error (g));
+
+  VALUE rv = rb_ary_new2 (r->len);
+  int i;
+  for (i = 0; i < r->len; ++i) {
+    VALUE hv = rb_hash_new ();
+    rb_hash_aset (rv, rb_str_new2 ("ino"), ULL2NUM (r->val[i].ino));
+    rb_hash_aset (rv, rb_str_new2 ("ftyp"), ULL2NUM (r->val[i].ftyp));
+    rb_hash_aset (rv, rb_str_new2 ("name"), rb_str_new2 (r->val[i].name));
+    rb_ary_push (rv, hv);
+  }
+  guestfs_free_dirent_list (r);
+  return rv;
+}
+
 /* Initialize the module. */
 void Init__guestfs ()
 {
@@ -5257,4 +5289,6 @@ void Init__guestfs ()
         ruby_guestfs_mknod_c, 4);
   rb_define_method (c_guestfs, "umask",
         ruby_guestfs_umask, 1);
+  rb_define_method (c_guestfs, "readdir",
+        ruby_guestfs_readdir, 1);
 }
index 065fd3e..93e7803 100644 (file)
@@ -1 +1 @@
-137
+138
index 69fd706..df5ff7e 100755 (executable)
@@ -83,6 +83,8 @@ and ret =
      * inefficient.  Keys should be unique.  NULLs are not permitted.
      *)
   | RHashtable of string
+    (* List of directory entries (the result of readdir(3)). *)
+  | RDirentList of string
 
 and args = argt list   (* Function parameters, guestfs handle is implicit. *)
 
@@ -2751,6 +2753,20 @@ See also L<umask(2)>, C<guestfs_mknod>, C<guestfs_mkdir>.
 
 This call returns the previous umask.");
 
+  ("readdir", (RDirentList "entries", [String "dir"]), 138, [],
+   [],
+   "read directories entries",
+   "\
+This returns the list of directory entries in directory C<dir>.
+
+All entries in the directory are returned, including C<.> and
+C<..>.  The entries are I<not> sorted, but returned in the same
+order as the underlying filesystem.
+
+This function is primarily intended for use by programs.  To
+get a simple list of names, use C<guestfs_ls>.  To get a printable
+directory for human consumption, use C<guestfs_ll>.");
+
 ]
 
 let all_functions = non_daemon_functions @ daemon_functions
@@ -2858,6 +2874,13 @@ let statvfs_cols = [
   "namemax", `Int;
 ]
 
+(* Column names in dirent structure. *)
+let dirent_cols = [
+  "ino", `Int;
+  "ftyp", `Char; (* 'b' 'c' 'd' 'f' (FIFO) 'l' 'r' (regular file) 's' 'u' '?' *)
+  "name", `String;
+]
+
 (* Used for testing language bindings. *)
 type callt =
   | CallString of string
@@ -3042,7 +3065,8 @@ let check_functions () =
        | RInt n | RInt64 n | RBool n | RConstString n | RString n
        | RStringList n | RPVList n | RVGList n | RLVList n
        | RStat n | RStatVFS n
-       | RHashtable n ->
+       | RHashtable n
+       | RDirentList n ->
           check_arg_ret_name n
        | RIntBool (n,m) ->
           check_arg_ret_name n;
@@ -3247,6 +3271,11 @@ strings, or NULL if there was an error.
 The array of strings will always have length C<2n+1>, where
 C<n> keys and values alternate, followed by the trailing NULL entry.
 I<The caller must free the strings and the array after use>.\n\n"
+        | RDirentList _ ->
+            pr "This function returns a C<struct guestfs_dirent_list *>
+(see E<lt>guestfs-structs.hE<gt>),
+or NULL if there was an error.
+I<The caller must call C<guestfs_free_dirent_list> after use>.\n\n"
        );
        if List.mem ProtocolLimitWarning flags then
          pr "%s\n\n" protocol_limit_warning;
@@ -3283,7 +3312,41 @@ and generate_structs_pod () =
       pr " void guestfs_free_lvm_%s_list (struct guestfs_free_lvm_%s_list *);\n"
        typ typ;
       pr "\n"
-  ) ["pv", pv_cols; "vg", vg_cols; "lv", lv_cols]
+  ) ["pv", pv_cols; "vg", vg_cols; "lv", lv_cols];
+
+  (* Stat *)
+  List.iter (
+    fun (typ, cols) ->
+      pr "=head2 guestfs_%s\n" typ;
+      pr "\n";
+      pr " struct guestfs_%s {\n" typ;
+      List.iter (
+       function
+       | name, `Int -> pr "   int64_t %s;\n" name
+      ) cols;
+      pr " };\n";
+      pr "\n";
+  ) [ "stat", stat_cols; "statvfs", statvfs_cols ];
+
+  (* DirentList *)
+  pr "=head2 guestfs_dirent\n";
+  pr "\n";
+  pr " struct guestfs_dirent {\n";
+  List.iter (
+    function
+    | name, `String -> pr "   char *%s;\n" name
+    | name, `Int -> pr "   int64_t %s;\n" name
+    | name, `Char -> pr "   char %s;\n" name
+  ) dirent_cols;
+  pr " };\n";
+  pr "\n";
+  pr " struct guestfs_dirent_list {\n";
+  pr "   uint32_t len; /* Number of elements in list. */\n";
+  pr "   struct guestfs_dirent *val; /* Elements. */\n";
+  pr " };\n";
+  pr " \n";
+  pr " void guestfs_free_dirent_list (struct guestfs_free_dirent_list *);\n";
+  pr "\n"
 
 (* Generate the protocol (XDR) file, 'guestfs_protocol.x' and
  * indirectly 'guestfs_protocol.h' and 'guestfs_protocol.c'.
@@ -3330,6 +3393,18 @@ and generate_xdr () =
        pr "\n";
   ) ["stat", stat_cols; "statvfs", statvfs_cols];
 
+  (* Dirent structures. *)
+  pr "struct guestfs_int_dirent {\n";
+  List.iter (function
+            | name, `Int -> pr "  hyper %s;\n" name
+            | name, `Char -> pr "  char %s;\n" name
+            | name, `String -> pr "  string %s<>;\n" name
+           ) dirent_cols;
+  pr "};\n";
+  pr "\n";
+  pr "typedef struct guestfs_int_dirent guestfs_int_dirent_list<>;\n";
+  pr "\n";
+
   List.iter (
     fun (shortname, style, _, _, _, _, _) ->
       let name = "guestfs_" ^ shortname in
@@ -3402,6 +3477,10 @@ and generate_xdr () =
           pr "struct %s_ret {\n" name;
           pr "  str %s<>;\n" n;
           pr "};\n\n"
+       | RDirentList n ->
+          pr "struct %s_ret {\n" name;
+          pr "  guestfs_int_dirent_list %s;\n" n;
+          pr "};\n\n"
       );
   ) daemon_functions;
 
@@ -3529,7 +3608,23 @@ and generate_structs_h () =
        ) cols;
        pr "};\n";
        pr "\n"
-  ) ["stat", stat_cols; "statvfs", statvfs_cols]
+  ) ["stat", stat_cols; "statvfs", statvfs_cols];
+
+  (* Dirent structures. *)
+  pr "struct guestfs_dirent {\n";
+  List.iter (
+    function
+    | name, `Int -> pr "  int64_t %s;\n" name
+    | name, `Char -> pr "  char %s;\n" name
+    | name, `String -> pr "  char *%s;\n" name
+  ) dirent_cols;
+  pr "};\n";
+  pr "\n";
+  pr "struct guestfs_dirent_list {\n";
+  pr "  uint32_t len;\n";
+  pr "  struct guestfs_dirent *val;\n";
+  pr "};\n";
+  pr "\n"
 
 (* Generate the guestfs-actions.h file. *)
 and generate_actions_h () =
@@ -3637,7 +3732,8 @@ check_state (guestfs_h *g, const char *caller)
        | RIntBool _
        | RPVList _ | RVGList _ | RLVList _
        | RStat _ | RStatVFS _
-       | RHashtable _ ->
+       | RHashtable _
+       | RDirentList _ ->
           pr "  struct %s_ret ret;\n" name
       );
       pr "};\n";
@@ -3680,7 +3776,8 @@ check_state (guestfs_h *g, const char *caller)
        | RIntBool _
        | RPVList _ | RVGList _ | RLVList _
        | RStat _ | RStatVFS _
-       | RHashtable _ ->
+       | RHashtable _
+       | RDirentList _ ->
            pr "  if (!xdr_%s_ret (xdr, &ctx->ret)) {\n" name;
            pr "    error (g, \"%%s: failed to parse reply\", \"%s\");\n" name;
            pr "    return;\n";
@@ -3703,7 +3800,8 @@ check_state (guestfs_h *g, const char *caller)
        | RString _ | RStringList _ | RIntBool _
        | RPVList _ | RVGList _ | RLVList _
        | RStat _ | RStatVFS _
-       | RHashtable _ ->
+       | RHashtable _
+       | RDirentList _ ->
            "NULL" in
 
       pr "{\n";
@@ -3839,7 +3937,8 @@ check_state (guestfs_h *g, const char *caller)
           pr "  /* caller with free this */\n";
           pr "  return safe_memdup (g, &ctx.ret, sizeof (ctx.ret));\n"
        | RPVList n | RVGList n | RLVList n
-       | RStat n | RStatVFS n ->
+       | RStat n | RStatVFS n
+       | RDirentList n ->
           pr "  /* caller will free this */\n";
           pr "  return safe_memdup (g, &ctx.ret.%s, sizeof (ctx.ret.%s));\n" n n
       );
@@ -3899,7 +3998,8 @@ and generate_daemon_actions () =
        | RVGList _ -> pr "  guestfs_lvm_int_vg_list *r;\n"; "NULL"
        | RLVList _ -> pr "  guestfs_lvm_int_lv_list *r;\n"; "NULL"
        | RStat _ -> pr "  guestfs_int_stat *r;\n"; "NULL"
-       | RStatVFS _ -> pr "  guestfs_int_statvfs *r;\n"; "NULL" in
+       | RStatVFS _ -> pr "  guestfs_int_statvfs *r;\n"; "NULL"
+       | RDirentList _ -> pr "  guestfs_int_dirent_list *r;\n"; "NULL" in
 
       (match snd style with
        | [] -> ()
@@ -4000,7 +4100,8 @@ and generate_daemon_actions () =
              name;
            pr "  xdr_free ((xdrproc_t) xdr_guestfs_%s_ret, (char *) r);\n" name
        | RPVList n | RVGList n | RLVList n
-       | RStat n | RStatVFS n ->
+       | RStat n | RStatVFS n
+       | RDirentList n ->
            pr "  struct guestfs_%s_ret ret;\n" name;
            pr "  ret.%s = *r;\n" n;
            pr "  reply ((xdrproc_t) xdr_guestfs_%s_ret, (char *) &ret);\n"
@@ -4781,7 +4882,9 @@ and generate_test_command_call ?(expect_error = false) ?test test_name cmd =
        | RStat _ ->
            pr "    struct guestfs_stat *r;\n"; "NULL"
        | RStatVFS _ ->
-           pr "    struct guestfs_statvfs *r;\n"; "NULL" in
+           pr "    struct guestfs_statvfs *r;\n"; "NULL"
+       | RDirentList _ ->
+           pr "    struct guestfs_dirent_list *r;\n"; "NULL" in
 
       pr "    suppress_error = %d;\n" (if expect_error then 1 else 0);
       pr "    r = guestfs_%s (g" name;
@@ -4837,6 +4940,8 @@ and generate_test_command_call ?(expect_error = false) ?test test_name cmd =
           pr "    guestfs_free_lvm_lv_list (r);\n"
        | RStat _ | RStatVFS _ ->
           pr "    free (r);\n"
+       | RDirentList _ ->
+          pr "    guestfs_free_dirent_list (r);\n"
       );
 
       pr "  }\n"
@@ -4992,6 +5097,29 @@ and generate_fish_cmds () =
        pr "\n";
   ) ["stat", stat_cols; "statvfs", statvfs_cols];
 
+  (* print_dirent_list function *)
+  pr "static void print_dirent (struct guestfs_dirent *dirent)\n";
+  pr "{\n";
+  List.iter (
+    function
+    | name, `String ->
+       pr "  printf (\"%s: %%s\\n\", dirent->%s);\n" name name
+    | name, `Int ->
+       pr "  printf (\"%s: %%\" PRIi64 \"\\n\", dirent->%s);\n" name name
+    | name, `Char ->
+       pr "  printf (\"%s: %%c\\n\", dirent->%s);\n" name name
+  ) dirent_cols;
+  pr "}\n";
+  pr "\n";
+  pr "static void print_dirent_list (struct guestfs_dirent_list *dirents)\n";
+  pr "{\n";
+  pr "  int i;\n";
+  pr "\n";
+  pr "  for (i = 0; i < dirents->len; ++i)\n";
+  pr "    print_dirent (&dirents->val[i]);\n";
+  pr "}\n";
+  pr "\n";
+
   (* run_<action> actions *)
   List.iter (
     fun (name, style, _, flags, _, _, _) ->
@@ -5011,6 +5139,7 @@ and generate_fish_cmds () =
        | RLVList _ -> pr "  struct guestfs_lvm_lv_list *r;\n"
        | RStat _ -> pr "  struct guestfs_stat *r;\n"
        | RStatVFS _ -> pr "  struct guestfs_statvfs *r;\n"
+       | RDirentList _ -> pr "  struct guestfs_dirent_list *r;\n"
       );
       List.iter (
        function
@@ -5125,6 +5254,11 @@ and generate_fish_cmds () =
           pr "  print_table (r);\n";
           pr "  free_strings (r);\n";
           pr "  return 0;\n"
+       | RDirentList _ ->
+          pr "  if (r == NULL) return -1;\n";
+          pr "  print_dirent_list (r);\n";
+          pr "  guestfs_free_dirent_list (r);\n";
+          pr "  return 0;\n"
       );
       pr "}\n";
       pr "\n"
@@ -5335,6 +5469,9 @@ and generate_prototype ?(extern = true) ?(static = false) ?(semicolon = true)
    | RStatVFS _ ->
        if not in_daemon then pr "struct guestfs_statvfs *"
        else pr "guestfs_int_statvfs *"
+   | RDirentList _ ->
+       if not in_daemon then pr "struct guestfs_dirent_list *"
+       else pr "guestfs_int_dirent_list *"
   );
   pr "%s%s (" prefix name;
   if handle = None && List.length (snd style) = 0 then
@@ -5416,6 +5553,8 @@ val close : t -> unit
 
   generate_ocaml_stat_structure_decls ();
 
+  generate_ocaml_dirent_structure_decls ();
+
   (* The actions. *)
   List.iter (
     fun (name, style, _, _, _, shortdesc, _) ->
@@ -5443,6 +5582,8 @@ let () =
 
   generate_ocaml_stat_structure_decls ();
 
+  generate_ocaml_dirent_structure_decls ();
+
   (* The actions. *)
   List.iter (
     fun (name, style, _, _, _, shortdesc, _) ->
@@ -5585,6 +5726,50 @@ copy_table (char * const * argv)
       pr "\n";
   ) ["stat", stat_cols; "statvfs", statvfs_cols];
 
+  (* Dirent copy functions. *)
+  pr "static CAMLprim value\n";
+  pr "copy_dirent (const struct guestfs_dirent *dirent)\n";
+  pr "{\n";
+  pr "  CAMLparam0 ();\n";
+  pr "  CAMLlocal2 (rv, v);\n";
+  pr "\n";
+  pr "  rv = caml_alloc (%d, 0);\n" (List.length dirent_cols);
+  iteri (
+    fun i col ->
+      (match col with
+       | name, `String ->
+          pr "  v = caml_copy_string (dirent->%s);\n" name
+       | name, `Int ->
+          pr "  v = caml_copy_int64 (dirent->%s);\n" name
+       | name, `Char ->
+          pr "  v = Val_int (dirent->%s);\n" name
+      );
+      pr "  Store_field (rv, %d, v);\n" i
+  ) dirent_cols;
+  pr "  CAMLreturn (rv);\n";
+  pr "}\n";
+  pr "\n";
+
+  pr "static CAMLprim value\n";
+  pr "copy_dirent_list (const struct guestfs_dirent_list *dirents)\n";
+  pr "{\n";
+  pr "  CAMLparam0 ();\n";
+  pr "  CAMLlocal2 (rv, v);\n";
+  pr "  int i;\n";
+  pr "\n";
+  pr "  if (dirents->len == 0)\n";
+  pr "    CAMLreturn (Atom (0));\n";
+  pr "  else {\n";
+  pr "    rv = caml_alloc (dirents->len, 0);\n";
+  pr "    for (i = 0; i < dirents->len; ++i) {\n";
+  pr "      v = copy_dirent (&dirents->val[i]);\n";
+  pr "      caml_modify (&Field (rv, i), v);\n";
+  pr "    }\n";
+  pr "    CAMLreturn (rv);\n";
+  pr "  }\n";
+  pr "}\n";
+  pr "\n";
+
   (* The wrappers. *)
   List.iter (
     fun (name, style, _, _, _, _, _) ->
@@ -5659,7 +5844,9 @@ copy_table (char * const * argv)
        | RHashtable _ ->
            pr "  int i;\n";
            pr "  char **r;\n";
-           "NULL" in
+           "NULL"
+       | RDirentList _ ->
+           pr "  struct guestfs_dirent_list *r;\n"; "NULL" in
       pr "\n";
 
       pr "  caml_enter_blocking_section ();\n";
@@ -5717,6 +5904,9 @@ copy_table (char * const * argv)
           pr "  rv = copy_table (r);\n";
           pr "  for (i = 0; r[i] != NULL; ++i) free (r[i]);\n";
           pr "  free (r);\n";
+       | RDirentList _ ->
+          pr "  rv = copy_dirent_list (r);\n";
+          pr "  guestfs_free_dirent_list (r);\n";
       );
 
       pr "  CAMLreturn (rv);\n";
@@ -5763,6 +5953,17 @@ and generate_ocaml_stat_structure_decls () =
       pr "\n"
   ) ["stat", stat_cols; "statvfs", statvfs_cols]
 
+and generate_ocaml_dirent_structure_decls () =
+  pr "type dirent = {\n";
+  List.iter (
+    function
+    | name, `Int -> pr "  %s : int64;\n" name
+    | name, `Char -> pr "  %s : char;\n" name
+    | name, `String -> pr "  %s : string;\n" name
+  ) dirent_cols;
+  pr "}\n";
+  pr "\n"
+
 and generate_ocaml_prototype ?(is_external = false) name style =
   if is_external then pr "external " else pr "val ";
   pr "%s : t -> " name;
@@ -5789,6 +5990,7 @@ and generate_ocaml_prototype ?(is_external = false) name style =
    | RStat _ -> pr "stat"
    | RStatVFS _ -> pr "statvfs"
    | RHashtable _ -> pr "(string * string) list"
+   | RDirentList _ -> pr "dirent array"
   );
   if is_external then (
     pr " = ";
@@ -5905,7 +6107,8 @@ DESTROY (g)
        | RIntBool _
        | RPVList _ | RVGList _ | RLVList _
        | RStat _ | RStatVFS _
-       | RHashtable _ ->
+       | RHashtable _
+       | RDirentList _ ->
           pr "void\n" (* all lists returned implictly on the stack *)
       );
       (* Call and arguments. *)
@@ -6046,6 +6249,9 @@ DESTROY (g)
        | RStatVFS n ->
           generate_perl_stat_code
             "statvfs" statvfs_cols name style n do_cleanups
+       | RDirentList n ->
+          generate_perl_dirent_code
+            "dirent" dirent_cols name style n do_cleanups
       );
 
       pr "\n"
@@ -6084,7 +6290,7 @@ and generate_perl_lvm_code typ cols name style n do_cleanups =
        pr "        (void) hv_store (hv, \"%s\", %d, newSVnv (%s->val[i].%s), 0);\n"
          name (String.length name) n name
   ) cols;
-  pr "        PUSHs (sv_2mortal ((SV *) hv));\n";
+  pr "        PUSHs (sv_2mortal (newRV ((SV *) hv)));\n";
   pr "      }\n";
   pr "      guestfs_free_lvm_%s_list (%s);\n" typ n
 
@@ -6106,6 +6312,37 @@ and generate_perl_stat_code typ cols name style n do_cleanups =
   ) cols;
   pr "      free (%s);\n" n
 
+and generate_perl_dirent_code typ cols name style n do_cleanups =
+  pr "PREINIT:\n";
+  pr "      struct guestfs_%s_list *%s;\n" typ n;
+  pr "      int i;\n";
+  pr "      HV *hv;\n";
+  pr " PPCODE:\n";
+  pr "      %s = guestfs_%s " n name;
+  generate_call_args ~handle:"g" (snd style);
+  pr ";\n";
+  do_cleanups ();
+  pr "      if (%s == NULL)\n" n;
+  pr "        croak (\"%s: %%s\", guestfs_last_error (g));\n" name;
+  pr "      EXTEND (SP, %s->len);\n" n;
+  pr "      for (i = 0; i < %s->len; ++i) {\n" n;
+  pr "        hv = newHV ();\n";
+  List.iter (
+    function
+    | name, `String ->
+       pr "        (void) hv_store (hv, \"%s\", %d, newSVpv (%s->val[i].%s, 0), 0);\n"
+         name (String.length name) n name
+    | name, `Int ->
+       pr "        (void) hv_store (hv, \"%s\", %d, my_newSVull (%s->val[i].%s), 0);\n"
+         name (String.length name) n name
+    | name, `Char ->
+       pr "        (void) hv_store (hv, \"%s\", %d, newSVpv (&%s->val[i].%s, 1), 0);\n"
+         name (String.length name) n name
+  ) cols;
+  pr "        PUSHs (newRV (sv_2mortal ((SV *) hv)));\n";
+  pr "      }\n";
+  pr "      guestfs_free_%s_list (%s);\n" typ n
+
 (* Generate Sys/Guestfs.pm. *)
 and generate_perl_pm () =
   generate_header HashStyle LGPLv2;
@@ -6239,7 +6476,8 @@ and generate_perl_prototype name style =
    | RStringList n
    | RPVList n
    | RVGList n
-   | RLVList n -> pr "@%s = " n
+   | RLVList n
+   | RDirentList n -> pr "@%s = " n
    | RStat n
    | RStatVFS n
    | RHashtable n -> pr "%%%s = " n
@@ -6475,6 +6713,42 @@ py_guestfs_close (PyObject *self, PyObject *args)
       pr "\n";
   ) ["stat", stat_cols; "statvfs", statvfs_cols];
 
+  (* Dirent structures, turned into Python dictionaries. *)
+  pr "static PyObject *\n";
+  pr "put_dirent (struct guestfs_dirent *dirent)\n";
+  pr "{\n";
+  pr "  PyObject *dict;\n";
+  pr "\n";
+  pr "  dict = PyDict_New ();\n";
+  List.iter (
+    function
+    | name, `Int ->
+       pr "  PyDict_SetItemString (dict, \"%s\",\n" name;
+       pr "                        PyLong_FromLongLong (dirent->%s));\n" name
+    | name, `Char ->
+       pr "  PyDict_SetItemString (dict, \"%s\",\n" name;
+       pr "                        PyString_FromStringAndSize (&dirent->%s, 1));\n" name
+    | name, `String ->
+       pr "  PyDict_SetItemString (dict, \"%s\",\n" name;
+       pr "                        PyString_FromString (dirent->%s));\n" name
+  ) dirent_cols;
+  pr "  return dict;\n";
+  pr "};\n";
+  pr "\n";
+
+  pr "static PyObject *\n";
+  pr "put_dirent_list (struct guestfs_dirent_list *dirents)\n";
+  pr "{\n";
+  pr "  PyObject *list;\n";
+  pr "  int i;\n";
+  pr "\n";
+  pr "  list = PyList_New (dirents->len);\n";
+  pr "  for (i = 0; i < dirents->len; ++i)\n";
+  pr "    PyList_SetItem (list, i, put_dirent (&dirents->val[i]));\n";
+  pr "  return list;\n";
+  pr "};\n";
+  pr "\n";
+
   (* Python wrapper functions. *)
   List.iter (
     fun (name, style, _, _, _, _, _) ->
@@ -6498,7 +6772,8 @@ py_guestfs_close (PyObject *self, PyObject *args)
        | RVGList n -> pr "  struct guestfs_lvm_vg_list *r;\n"; "NULL"
        | RLVList n -> pr "  struct guestfs_lvm_lv_list *r;\n"; "NULL"
        | RStat n -> pr "  struct guestfs_stat *r;\n"; "NULL"
-       | RStatVFS n -> pr "  struct guestfs_statvfs *r;\n"; "NULL" in
+       | RStatVFS n -> pr "  struct guestfs_statvfs *r;\n"; "NULL"
+       | RDirentList n -> pr "  struct guestfs_dirent_list *r;\n"; "NULL" in
 
       List.iter (
        function
@@ -6602,6 +6877,9 @@ py_guestfs_close (PyObject *self, PyObject *args)
        | RHashtable n ->
           pr "  py_r = put_table (r);\n";
           pr "  free_strings (r);\n"
+       | RDirentList n ->
+          pr "  py_r = put_dirent_list (r);\n";
+          pr "  guestfs_free_dirent_list (r);\n"
       );
 
       pr "  return py_r;\n";
@@ -6729,7 +7007,9 @@ class GuestFS:
          | RStatVFS _ ->
              doc ^ "\n\nThis function returns a dictionary, with keys matching the various fields in the statvfs structure."
          | RHashtable _ ->
-             doc ^ "\n\nThis function returns a dictionary." in
+             doc ^ "\n\nThis function returns a dictionary."
+         | RDirentList _ ->
+             doc ^ "\n\nThis function returns a list of directory entries.  Each directory entry is represented as a dictionary." in
        let doc =
          if List.mem ProtocolLimitWarning flags then
            doc ^ "\n\n" ^ protocol_limit_warning
@@ -6894,7 +7174,8 @@ static VALUE ruby_guestfs_close (VALUE gv)
        | RVGList n -> pr "  struct guestfs_lvm_vg_list *r;\n"; "NULL"
        | RLVList n -> pr "  struct guestfs_lvm_lv_list *r;\n"; "NULL"
        | RStat n -> pr "  struct guestfs_stat *r;\n"; "NULL"
-       | RStatVFS n -> pr "  struct guestfs_statvfs *r;\n"; "NULL" in
+       | RStatVFS n -> pr "  struct guestfs_statvfs *r;\n"; "NULL"
+       | RDirentList n -> pr "  struct guestfs_dirent_list *r;\n"; "NULL" in
       pr "\n";
 
       pr "  r = guestfs_%s " name;
@@ -6975,6 +7256,8 @@ static VALUE ruby_guestfs_close (VALUE gv)
           pr "  }\n";
           pr "  free (r);\n";
           pr "  return rv;\n"
+       | RDirentList n ->
+          generate_ruby_dirent_code "dirent" dirent_cols
       );
 
       pr "}\n";
@@ -7025,6 +7308,24 @@ and generate_ruby_lvm_code typ cols =
   pr "  guestfs_free_lvm_%s_list (r);\n" typ;
   pr "  return rv;\n"
 
+(* Ruby code to return a dirent struct list. *)
+and generate_ruby_dirent_code typ cols =
+  pr "  VALUE rv = rb_ary_new2 (r->len);\n";
+  pr "  int i;\n";
+  pr "  for (i = 0; i < r->len; ++i) {\n";
+  pr "    VALUE hv = rb_hash_new ();\n";
+  List.iter (
+    function
+    | name, `String ->
+       pr "    rb_hash_aset (rv, rb_str_new2 (\"%s\"), rb_str_new2 (r->val[i].%s));\n" name name
+    | name, (`Char|`Int) ->
+       pr "    rb_hash_aset (rv, rb_str_new2 (\"%s\"), ULL2NUM (r->val[i].%s));\n" name name
+  ) cols;
+  pr "    rb_ary_push (rv, hv);\n";
+  pr "  }\n";
+  pr "  guestfs_free_%s_list (r);\n" typ;
+  pr "  return rv;\n"
+
 (* Generate Java bindings GuestFS.java file. *)
 and generate_java_java () =
   generate_header CStyle LGPLv2;
@@ -7040,6 +7341,7 @@ import com.redhat.et.libguestfs.LV;
 import com.redhat.et.libguestfs.Stat;
 import com.redhat.et.libguestfs.StatVFS;
 import com.redhat.et.libguestfs.IntBool;
+import com.redhat.et.libguestfs.Dirent;
 
 /**
  * The GuestFS object is a libguestfs handle.
@@ -7163,6 +7465,7 @@ and generate_java_prototype ?(public=false) ?(privat=false) ?(native=false)
    | RStat _ -> pr "Stat ";
    | RStatVFS _ -> pr "StatVFS ";
    | RHashtable _ -> pr "HashMap<String,String> ";
+   | RDirentList _ -> pr "Dirent[] ";
   );
 
   if native then pr "_%s " name else pr "%s " name;
@@ -7218,6 +7521,7 @@ public class %s {
     | name, `UUID -> pr "  public String %s;\n" name
     | name, `Bytes
     | name, `Int -> pr "  public long %s;\n" name
+    | name, `Char -> pr "  public char %s;\n" name
     | name, `OptPercent ->
        pr "  /* The next field is [0..100] or -1 meaning 'not present': */\n";
        pr "  public float %s;\n" name
@@ -7284,7 +7588,7 @@ Java_com_redhat_et_libguestfs_GuestFS__1close
        | RConstString _ | RString _ -> pr "jstring ";
        | RIntBool _ | RStat _ | RStatVFS _ | RHashtable _ ->
           pr "jobject ";
-       | RStringList _ | RPVList _ | RVGList _ | RLVList _ ->
+       | RStringList _ | RPVList _ | RVGList _ | RLVList _ | RDirentList _ ->
           pr "jobjectArray ";
       );
       pr "JNICALL\n";
@@ -7358,7 +7662,13 @@ Java_com_redhat_et_libguestfs_GuestFS__1close
            pr "  jfieldID fl;\n";
            pr "  jobject jfl;\n";
            pr "  struct guestfs_lvm_lv_list *r;\n"; "NULL", "NULL"
-       | RHashtable _ -> pr "  char **r;\n"; "NULL", "NULL" in
+       | RHashtable _ -> pr "  char **r;\n"; "NULL", "NULL"
+       | RDirentList _ ->
+           pr "  jobjectArray jr;\n";
+           pr "  jclass cl;\n";
+           pr "  jfieldID fl;\n";
+           pr "  jobject jfl;\n";
+           pr "  struct guestfs_dirent_list *r;\n"; "NULL", "NULL" in
       List.iter (
        function
        | String n
@@ -7376,7 +7686,8 @@ Java_com_redhat_et_libguestfs_GuestFS__1close
 
       let needs_i =
        (match fst style with
-        | RStringList _ | RPVList _ | RVGList _ | RLVList _ -> true
+        | RStringList _ | RPVList _ | RVGList _ | RLVList _
+        | RDirentList _ -> true
         | RErr | RBool _ | RInt _ | RInt64 _ | RConstString _
         | RString _ | RIntBool _ | RStat _ | RStatVFS _
         | RHashtable _ -> false) ||
@@ -7510,6 +7821,8 @@ Java_com_redhat_et_libguestfs_GuestFS__1close
           (* XXX *)
           pr "  throw_exception (env, \"%s: internal error: please let us know how to make a Java HashMap from JNI bindings!\");\n" name;
           pr "  return NULL;\n"
+       | RDirentList _ ->
+          generate_java_dirent_return "dirent" "Dirent" dirent_cols
       );
 
       pr "}\n";
@@ -7546,6 +7859,25 @@ and generate_java_lvm_return typ jtyp cols =
   pr "  guestfs_free_lvm_%s_list (r);\n" typ;
   pr "  return jr;\n"
 
+and generate_java_dirent_return typ jtyp cols =
+  pr "  cl = (*env)->FindClass (env, \"com/redhat/et/libguestfs/%s\");\n" jtyp;
+  pr "  jr = (*env)->NewObjectArray (env, r->len, cl, NULL);\n";
+  pr "  for (i = 0; i < r->len; ++i) {\n";
+  pr "    jfl = (*env)->AllocObject (env, cl);\n";
+  List.iter (
+    function
+    | name, `String ->
+       pr "    fl = (*env)->GetFieldID (env, cl, \"%s\", \"Ljava/lang/String;\");\n" name;
+       pr "    (*env)->SetObjectField (env, jfl, fl, (*env)->NewStringUTF (env, r->val[i].%s));\n" name;
+    | name, (`Char|`Int) ->
+       pr "    fl = (*env)->GetFieldID (env, cl, \"%s\", \"J\");\n" name;
+       pr "    (*env)->SetLongField (env, jfl, fl, r->val[i].%s);\n" name;
+  ) cols;
+  pr "    (*env)->SetObjectArrayElement (env, jfl, i, jfl);\n";
+  pr "  }\n";
+  pr "  guestfs_free_%s_list (r);\n" typ;
+  pr "  return jr;\n"
+
 and generate_haskell_hs () =
   generate_header HaskellStyle LGPLv2;
 
@@ -7567,7 +7899,8 @@ and generate_haskell_hs () =
     | RLVList _, _
     | RStat _, _
     | RStatVFS _, _
-    | RHashtable _, _ -> false in
+    | RHashtable _, _
+    | RDirentList _, _ -> false in
 
   pr "\
 {-# INCLUDE <guestfs.h> #-}
@@ -7678,7 +8011,7 @@ last_error h = do
             pr "      fail err\n";
         | RConstString _ | RString _ | RStringList _ | RIntBool _
         | RPVList _ | RVGList _ | RLVList _ | RStat _ | RStatVFS _
-        | RHashtable _ ->
+        | RHashtable _ | RDirentList _ ->
             pr "  if (r == nullPtr)\n";
             pr "    then do\n";
             pr "      err <- last_error h\n";
@@ -7702,7 +8035,8 @@ last_error h = do
         | RLVList _
         | RStat _
         | RStatVFS _
-        | RHashtable _ ->
+        | RHashtable _
+        | RDirentList _ ->
             pr "    else return ()\n" (* XXXXXXXXXXXXXXXXXXXX *)
        );
        pr "\n";
@@ -7744,6 +8078,7 @@ and generate_haskell_prototype ~handle ?(hs = false) style =
    | RStat _ -> pr "Stat"
    | RStatVFS _ -> pr "StatVFS"
    | RHashtable _ -> pr "Hashtable"
+   | RDirentList _ -> pr "[Dirent]"
   );
   pr ")"
 
@@ -7904,6 +8239,15 @@ print_strings (char * const* const argv)
             pr "  }\n";
             pr "  strs[n*2] = NULL;\n";
             pr "  return strs;\n"
+        | RDirentList _ ->
+            pr "  struct guestfs_dirent_list *r;\n";
+            pr "  int i;\n";
+            pr "  r = malloc (sizeof (struct guestfs_dirent_list));\n";
+            pr "  sscanf (val, \"%%d\", &r->len);\n";
+            pr "  r->val = calloc (r->len, sizeof (struct guestfs_dirent));\n";
+            pr "  for (i = 0; i < r->len; ++i)\n";
+            pr "    r->val[i].ino = i;\n";
+            pr "  return r;\n"
        );
        pr "}\n";
        pr "\n"
@@ -7919,7 +8263,8 @@ print_strings (char * const* const argv)
         | RConstString _
         | RString _ | RStringList _ | RIntBool _
         | RPVList _ | RVGList _ | RLVList _ | RStat _ | RStatVFS _
-        | RHashtable _ ->
+        | RHashtable _
+        | RDirentList _ ->
             pr "  return NULL;\n"
        );
        pr "}\n";
@@ -8344,6 +8689,10 @@ Run it from the top source directory using the command
   generate_java_struct "StatVFS" statvfs_cols;
   close ();
 
+  let close = output_to "java/com/redhat/et/libguestfs/Dirent.java" in
+  generate_java_struct "Dirent" dirent_cols;
+  close ();
+
   let close = output_to "java/com_redhat_et_libguestfs_GuestFS.c" in
   generate_java_c ();
   close ();
index a46b339..189eebe 100644 (file)
@@ -12560,3 +12560,96 @@ int guestfs_umask (guestfs_h *g,
   return ctx.ret.oldmask;
 }
 
+struct readdir_ctx {
+  /* This flag is set by the callbacks, so we know we've done
+   * the callbacks as expected, and in the right sequence.
+   * 0 = not called, 1 = reply_cb called.
+   */
+  int cb_sequence;
+  struct guestfs_message_header hdr;
+  struct guestfs_message_error err;
+  struct guestfs_readdir_ret ret;
+};
+
+static void readdir_reply_cb (guestfs_h *g, void *data, XDR *xdr)
+{
+  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct readdir_ctx *ctx = (struct readdir_ctx *) data;
+
+  /* This should definitely not happen. */
+  if (ctx->cb_sequence != 0) {
+    ctx->cb_sequence = 9999;
+    error (g, "%s: internal error: reply callback called twice", "guestfs_readdir");
+    return;
+  }
+
+  ml->main_loop_quit (ml, g);
+
+  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_readdir");
+    return;
+  }
+  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_readdir");
+      return;
+    }
+    goto done;
+  }
+  if (!xdr_guestfs_readdir_ret (xdr, &ctx->ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_readdir");
+    return;
+  }
+ done:
+  ctx->cb_sequence = 1;
+}
+
+struct guestfs_dirent_list *guestfs_readdir (guestfs_h *g,
+               const char *dir)
+{
+  struct guestfs_readdir_args args;
+  struct readdir_ctx ctx;
+  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  int serial;
+
+  if (check_state (g, "guestfs_readdir") == -1) return NULL;
+  guestfs_set_busy (g);
+
+  memset (&ctx, 0, sizeof ctx);
+
+  args.dir = (char *) dir;
+  serial = guestfs__send_sync (g, GUESTFS_PROC_READDIR,
+        (xdrproc_t) xdr_guestfs_readdir_args, (char *) &args);
+  if (serial == -1) {
+    guestfs_end_busy (g);
+    return NULL;
+  }
+
+  guestfs__switch_to_receiving (g);
+  ctx.cb_sequence = 0;
+  guestfs_set_reply_callback (g, readdir_reply_cb, &ctx);
+  (void) ml->main_loop_run (ml, g);
+  guestfs_set_reply_callback (g, NULL, NULL);
+  if (ctx.cb_sequence != 1) {
+    error (g, "%s reply failed, see earlier error messages", "guestfs_readdir");
+    guestfs_end_busy (g);
+    return NULL;
+  }
+
+  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_READDIR, serial) == -1) {
+    guestfs_end_busy (g);
+    return NULL;
+  }
+
+  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", ctx.err.error_message);
+    free (ctx.err.error_message);
+    guestfs_end_busy (g);
+    return NULL;
+  }
+
+  guestfs_end_busy (g);
+  /* caller will free this */
+  return safe_memdup (g, &ctx.ret.entries, sizeof (ctx.ret.entries));
+}
+
index 5300d15..4f58949 100644 (file)
@@ -210,3 +210,4 @@ extern int guestfs_mkfifo (guestfs_h *handle, int mode, const char *path);
 extern int guestfs_mknod_b (guestfs_h *handle, int mode, int devmajor, int devminor, const char *path);
 extern int guestfs_mknod_c (guestfs_h *handle, int mode, int devmajor, int devminor, const char *path);
 extern int guestfs_umask (guestfs_h *handle, int mask);
+extern struct guestfs_dirent_list *guestfs_readdir (guestfs_h *handle, const char *dir);
index 76ac819..44f0ce0 100644 (file)
@@ -127,3 +127,14 @@ struct guestfs_statvfs {
   int64_t namemax;
 };
 
+struct guestfs_dirent {
+  int64_t ino;
+  char ftyp;
+  char *name;
+};
+
+struct guestfs_dirent_list {
+  uint32_t len;
+  struct guestfs_dirent *val;
+};
+
index 2d4db66..9cdb2dd 100644 (file)
@@ -1473,6 +1473,13 @@ guestfs_free_lvm_lv_list (struct guestfs_lvm_lv_list *x)
   free (x);
 }
 
+void
+guestfs_free_dirent_list (struct guestfs_dirent_list *x)
+{
+  xdr_free ((xdrproc_t) xdr_guestfs_int_dirent_list, (char *) x);
+  free (x);
+}
+
 /* We don't know if stdout_event or sock_read_event will be the
  * first to receive EOF if the qemu process dies.  This function
  * has the common cleanup code for both.
index b5ed0f7..201d60c 100644 (file)
@@ -57,6 +57,7 @@ extern void guestfs_free_int_bool (struct guestfs_int_bool *);
 extern void guestfs_free_lvm_pv_list (struct guestfs_lvm_pv_list *);
 extern void guestfs_free_lvm_vg_list (struct guestfs_lvm_vg_list *);
 extern void guestfs_free_lvm_lv_list (struct guestfs_lvm_lv_list *);
+extern void guestfs_free_dirent_list (struct guestfs_dirent_list *);
 
 /* Low-level event API. */
 typedef void (*guestfs_send_cb) (guestfs_h *g, void *data);
index ef0b805..69ea4ae 100644 (file)
@@ -238,6 +238,31 @@ xdr_guestfs_int_statvfs (XDR *xdrs, guestfs_int_statvfs *objp)
 }
 
 bool_t
+xdr_guestfs_int_dirent (XDR *xdrs, guestfs_int_dirent *objp)
+{
+       register int32_t *buf;
+
+        if (!xdr_quad_t (xdrs, &objp->ino))
+                return FALSE;
+        if (!xdr_char (xdrs, &objp->ftyp))
+                return FALSE;
+        if (!xdr_string (xdrs, &objp->name, ~0))
+                return FALSE;
+       return TRUE;
+}
+
+bool_t
+xdr_guestfs_int_dirent_list (XDR *xdrs, guestfs_int_dirent_list *objp)
+{
+       register int32_t *buf;
+
+        if (!xdr_array (xdrs, (char **)&objp->guestfs_int_dirent_list_val, (u_int *) &objp->guestfs_int_dirent_list_len, ~0,
+               sizeof (guestfs_int_dirent), (xdrproc_t) xdr_guestfs_int_dirent))
+                return FALSE;
+       return TRUE;
+}
+
+bool_t
 xdr_guestfs_mount_args (XDR *xdrs, guestfs_mount_args *objp)
 {
        register int32_t *buf;
@@ -2435,6 +2460,26 @@ xdr_guestfs_umask_ret (XDR *xdrs, guestfs_umask_ret *objp)
 }
 
 bool_t
+xdr_guestfs_readdir_args (XDR *xdrs, guestfs_readdir_args *objp)
+{
+       register int32_t *buf;
+
+        if (!xdr_string (xdrs, &objp->dir, ~0))
+                return FALSE;
+       return TRUE;
+}
+
+bool_t
+xdr_guestfs_readdir_ret (XDR *xdrs, guestfs_readdir_ret *objp)
+{
+       register int32_t *buf;
+
+        if (!xdr_guestfs_int_dirent_list (xdrs, &objp->entries))
+                return FALSE;
+       return TRUE;
+}
+
+bool_t
 xdr_guestfs_procedure (XDR *xdrs, guestfs_procedure *objp)
 {
        register int32_t *buf;
index 3b508b2..9ce9af9 100644 (file)
@@ -124,6 +124,18 @@ struct guestfs_int_statvfs {
 };
 typedef struct guestfs_int_statvfs guestfs_int_statvfs;
 
+struct guestfs_int_dirent {
+       quad_t ino;
+       char ftyp;
+       char *name;
+};
+typedef struct guestfs_int_dirent guestfs_int_dirent;
+
+typedef struct {
+       u_int guestfs_int_dirent_list_len;
+       guestfs_int_dirent *guestfs_int_dirent_list_val;
+} guestfs_int_dirent_list;
+
 struct guestfs_mount_args {
        char *device;
        char *mountpoint;
@@ -1187,6 +1199,16 @@ struct guestfs_umask_ret {
 };
 typedef struct guestfs_umask_ret guestfs_umask_ret;
 
+struct guestfs_readdir_args {
+       char *dir;
+};
+typedef struct guestfs_readdir_args guestfs_readdir_args;
+
+struct guestfs_readdir_ret {
+       guestfs_int_dirent_list entries;
+};
+typedef struct guestfs_readdir_ret guestfs_readdir_ret;
+
 enum guestfs_procedure {
        GUESTFS_PROC_MOUNT = 1,
        GUESTFS_PROC_SYNC = 2,
@@ -1325,7 +1347,8 @@ enum guestfs_procedure {
        GUESTFS_PROC_MKNOD_B = 135,
        GUESTFS_PROC_MKNOD_C = 136,
        GUESTFS_PROC_UMASK = 137,
-       GUESTFS_PROC_NR_PROCS = 137 + 1,
+       GUESTFS_PROC_READDIR = 138,
+       GUESTFS_PROC_NR_PROCS = 138 + 1,
 };
 typedef enum guestfs_procedure guestfs_procedure;
 #define GUESTFS_MESSAGE_MAX 4194304
@@ -1384,6 +1407,8 @@ extern  bool_t xdr_guestfs_lvm_int_lv (XDR *, guestfs_lvm_int_lv*);
 extern  bool_t xdr_guestfs_lvm_int_lv_list (XDR *, guestfs_lvm_int_lv_list*);
 extern  bool_t xdr_guestfs_int_stat (XDR *, guestfs_int_stat*);
 extern  bool_t xdr_guestfs_int_statvfs (XDR *, guestfs_int_statvfs*);
+extern  bool_t xdr_guestfs_int_dirent (XDR *, guestfs_int_dirent*);
+extern  bool_t xdr_guestfs_int_dirent_list (XDR *, guestfs_int_dirent_list*);
 extern  bool_t xdr_guestfs_mount_args (XDR *, guestfs_mount_args*);
 extern  bool_t xdr_guestfs_touch_args (XDR *, guestfs_touch_args*);
 extern  bool_t xdr_guestfs_cat_args (XDR *, guestfs_cat_args*);
@@ -1567,6 +1592,8 @@ extern  bool_t xdr_guestfs_mknod_b_args (XDR *, guestfs_mknod_b_args*);
 extern  bool_t xdr_guestfs_mknod_c_args (XDR *, guestfs_mknod_c_args*);
 extern  bool_t xdr_guestfs_umask_args (XDR *, guestfs_umask_args*);
 extern  bool_t xdr_guestfs_umask_ret (XDR *, guestfs_umask_ret*);
+extern  bool_t xdr_guestfs_readdir_args (XDR *, guestfs_readdir_args*);
+extern  bool_t xdr_guestfs_readdir_ret (XDR *, guestfs_readdir_ret*);
 extern  bool_t xdr_guestfs_procedure (XDR *, guestfs_procedure*);
 extern  bool_t xdr_guestfs_message_direction (XDR *, guestfs_message_direction*);
 extern  bool_t xdr_guestfs_message_status (XDR *, guestfs_message_status*);
@@ -1584,6 +1611,8 @@ extern bool_t xdr_guestfs_lvm_int_lv ();
 extern bool_t xdr_guestfs_lvm_int_lv_list ();
 extern bool_t xdr_guestfs_int_stat ();
 extern bool_t xdr_guestfs_int_statvfs ();
+extern bool_t xdr_guestfs_int_dirent ();
+extern bool_t xdr_guestfs_int_dirent_list ();
 extern bool_t xdr_guestfs_mount_args ();
 extern bool_t xdr_guestfs_touch_args ();
 extern bool_t xdr_guestfs_cat_args ();
@@ -1767,6 +1796,8 @@ extern bool_t xdr_guestfs_mknod_b_args ();
 extern bool_t xdr_guestfs_mknod_c_args ();
 extern bool_t xdr_guestfs_umask_args ();
 extern bool_t xdr_guestfs_umask_ret ();
+extern bool_t xdr_guestfs_readdir_args ();
+extern bool_t xdr_guestfs_readdir_ret ();
 extern bool_t xdr_guestfs_procedure ();
 extern bool_t xdr_guestfs_message_direction ();
 extern bool_t xdr_guestfs_message_status ();
index bcc0138..0fe1dea 100644 (file)
@@ -115,6 +115,14 @@ struct guestfs_int_statvfs {
   hyper namemax;
 };
 
+struct guestfs_int_dirent {
+  hyper ino;
+  char ftyp;
+  string name<>;
+};
+
+typedef struct guestfs_int_dirent guestfs_int_dirent_list<>;
+
 struct guestfs_mount_args {
   string device<>;
   string mountpoint<>;
@@ -911,6 +919,14 @@ struct guestfs_umask_ret {
   int oldmask;
 };
 
+struct guestfs_readdir_args {
+  string dir<>;
+};
+
+struct guestfs_readdir_ret {
+  guestfs_int_dirent_list entries;
+};
+
 enum guestfs_procedure {
   GUESTFS_PROC_MOUNT = 1,
   GUESTFS_PROC_SYNC = 2,
@@ -1049,6 +1065,7 @@ enum guestfs_procedure {
   GUESTFS_PROC_MKNOD_B = 135,
   GUESTFS_PROC_MKNOD_C = 136,
   GUESTFS_PROC_UMASK = 137,
+  GUESTFS_PROC_READDIR = 138,
   GUESTFS_PROC_NR_PROCS
 };