Added stat, lstat, statvfs and associated stat structures.
authorRichard Jones <rjones@redhat.com>
Wed, 15 Apr 2009 09:44:27 +0000 (10:44 +0100)
committerRichard Jones <rjones@redhat.com>
Wed, 15 Apr 2009 09:44:27 +0000 (10:44 +0100)
daemon/Makefile.am
daemon/stat.c [new file with mode: 0644]
src/generator.ml

index 1c52f7a..520ad1e 100644 (file)
@@ -31,6 +31,7 @@ guestfsd_SOURCES = \
        lvm.c \
        mount.c \
        proto.c \
        lvm.c \
        mount.c \
        proto.c \
+       stat.c \
        stubs.c \
        sync.c \
        ../src/guestfs_protocol.h \
        stubs.c \
        sync.c \
        ../src/guestfs_protocol.h \
diff --git a/daemon/stat.c b/daemon/stat.c
new file mode 100644 (file)
index 0000000..743dc6e
--- /dev/null
@@ -0,0 +1,155 @@
+/* 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 <sys/types.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <unistd.h>
+
+#include "../src/guestfs_protocol.h"
+#include "daemon.h"
+#include "actions.h"
+
+guestfs_int_stat *
+do_stat (const char *path)
+{
+  int r;
+  guestfs_int_stat *ret;
+  struct stat statbuf;
+
+  NEED_ROOT (NULL);
+  ABS_PATH (path, NULL);
+
+  CHROOT_IN;
+  r = stat (path, &statbuf);
+  CHROOT_OUT;
+
+  if (r == -1) {
+    reply_with_perror ("stat");
+    return NULL;
+  }
+
+  ret = malloc (sizeof *ret);
+  if (ret == NULL) {
+    reply_with_perror ("malloc");
+    return NULL;
+  }
+
+  ret->dev = statbuf.st_dev;
+  ret->ino = statbuf.st_ino;
+  ret->mode = statbuf.st_mode;
+  ret->nlink = statbuf.st_nlink;
+  ret->uid = statbuf.st_uid;
+  ret->gid = statbuf.st_gid;
+  ret->rdev = statbuf.st_rdev;
+  ret->size = statbuf.st_size;
+  ret->blksize = statbuf.st_blksize;
+  ret->blocks = statbuf.st_blocks;
+  ret->atime = statbuf.st_atime;
+  ret->mtime = statbuf.st_mtime;
+  ret->ctime = statbuf.st_ctime;
+
+  return ret;
+}
+
+guestfs_int_stat *
+do_lstat (const char *path)
+{
+  int r;
+  guestfs_int_stat *ret;
+  struct stat statbuf;
+
+  NEED_ROOT (NULL);
+  ABS_PATH (path, NULL);
+
+  CHROOT_IN;
+  r = lstat (path, &statbuf);
+  CHROOT_OUT;
+
+  if (r == -1) {
+    reply_with_perror ("stat");
+    return NULL;
+  }
+
+  ret = malloc (sizeof *ret);
+  if (ret == NULL) {
+    reply_with_perror ("malloc");
+    return NULL;
+  }
+
+  ret->dev = statbuf.st_dev;
+  ret->ino = statbuf.st_ino;
+  ret->mode = statbuf.st_mode;
+  ret->nlink = statbuf.st_nlink;
+  ret->uid = statbuf.st_uid;
+  ret->gid = statbuf.st_gid;
+  ret->rdev = statbuf.st_rdev;
+  ret->size = statbuf.st_size;
+  ret->blksize = statbuf.st_blksize;
+  ret->blocks = statbuf.st_blocks;
+  ret->atime = statbuf.st_atime;
+  ret->mtime = statbuf.st_mtime;
+  ret->ctime = statbuf.st_ctime;
+
+  return ret;
+}
+
+guestfs_int_statvfs *
+do_statvfs (const char *path)
+{
+  int r;
+  guestfs_int_statvfs *ret;
+  struct statvfs statbuf;
+
+  NEED_ROOT (NULL);
+  ABS_PATH (path, NULL);
+
+  CHROOT_IN;
+  r = statvfs (path, &statbuf);
+  CHROOT_OUT;
+
+  if (r == -1) {
+    reply_with_perror ("statvfs");
+    return NULL;
+  }
+
+  ret = malloc (sizeof *ret);
+  if (ret == NULL) {
+    reply_with_perror ("malloc");
+    return NULL;
+  }
+
+  ret->bsize = statbuf.f_bsize;
+  ret->frsize = statbuf.f_frsize;
+  ret->blocks = statbuf.f_blocks;
+  ret->bfree = statbuf.f_bfree;
+  ret->bavail = statbuf.f_bavail;
+  ret->files = statbuf.f_files;
+  ret->ffree = statbuf.f_ffree;
+  ret->favail = statbuf.f_favail;
+  ret->fsid = statbuf.f_fsid;
+  ret->flag = statbuf.f_flag;
+  ret->namemax = statbuf.f_namemax;
+
+  return ret;
+}
index 6ef8e1b..c32b0ed 100755 (executable)
@@ -65,6 +65,9 @@ and ret =
   | RPVList of string
   | RVGList of string
   | RLVList of string
   | RPVList of string
   | RVGList of string
   | RLVList of string
+    (* Stat buffers. *)
+  | RStat of string
+  | RStatVFS of string
 and args = argt list   (* Function parameters, guestfs handle is implicit. *)
 
     (* Note in future we should allow a "variable args" parameter as
 and args = argt list   (* Function parameters, guestfs handle is implicit. *)
 
     (* Note in future we should allow a "variable args" parameter as
@@ -107,7 +110,7 @@ can easily destroy all your data>."
  * the virtual machine and block devices are reused between tests.
  * So don't try testing kill_subprocess :-x
  *
  * the virtual machine and block devices are reused between tests.
  * So don't try testing kill_subprocess :-x
  *
- * Between each test we umount-all and lvm-remove-all.
+ * Between each test we umount-all and lvm-remove-all (except InitNone).
  *
  * Don't assume anything about the previous contents of the block
  * devices.  Use 'Init*' to create some initial scenarios.
  *
  * Don't assume anything about the previous contents of the block
  * devices.  Use 'Init*' to create some initial scenarios.
@@ -141,11 +144,21 @@ and test =
      * content).
      *)
   | TestOutputLength of seq * int
      * content).
      *)
   | TestOutputLength of seq * int
+    (* Run the command sequence and expect the output of the final
+     * command to be a structure.
+     *)
+  | TestOutputStruct of seq * test_field_compare list
     (* Run the command sequence and expect the final command (only)
      * to fail.
      *)
   | TestLastFail of seq
 
     (* Run the command sequence and expect the final command (only)
      * to fail.
      *)
   | TestLastFail of seq
 
+and test_field_compare =
+  | CompareWithInt of string * int
+  | CompareWithString of string * string
+  | CompareFieldsIntEq of string * string
+  | CompareFieldsStrEq of string * string
+
 (* Some initial scenarios for testing. *)
 and test_init =
     (* Do nothing, block devices could contain random stuff including
 (* Some initial scenarios for testing. *)
 and test_init =
     (* Do nothing, block devices could contain random stuff including
@@ -475,24 +488,21 @@ This returns a list of the logical volume device names
 See also C<guestfs_lvs_full>.");
 
   ("pvs_full", (RPVList "physvols", []), 12, [],
 See also C<guestfs_lvs_full>.");
 
   ("pvs_full", (RPVList "physvols", []), 12, [],
-   [InitBasicFSonLVM, TestOutputLength (
-      [["pvs"]], 1)],
+   [], (* XXX how to test? *)
    "list the LVM physical volumes (PVs)",
    "\
 List all the physical volumes detected.  This is the equivalent
 of the L<pvs(8)> command.  The \"full\" version includes all fields.");
 
   ("vgs_full", (RVGList "volgroups", []), 13, [],
    "list the LVM physical volumes (PVs)",
    "\
 List all the physical volumes detected.  This is the equivalent
 of the L<pvs(8)> command.  The \"full\" version includes all fields.");
 
   ("vgs_full", (RVGList "volgroups", []), 13, [],
-   [InitBasicFSonLVM, TestOutputLength (
-      [["pvs"]], 1)],
+   [], (* XXX how to test? *)
    "list the LVM volume groups (VGs)",
    "\
 List all the volumes groups detected.  This is the equivalent
 of the L<vgs(8)> command.  The \"full\" version includes all fields.");
 
   ("lvs_full", (RLVList "logvols", []), 14, [],
    "list the LVM volume groups (VGs)",
    "\
 List all the volumes groups detected.  This is the equivalent
 of the L<vgs(8)> command.  The \"full\" version includes all fields.");
 
   ("lvs_full", (RLVList "logvols", []), 14, [],
-   [InitBasicFSonLVM, TestOutputLength (
-      [["pvs"]], 1)],
+   [], (* XXX how to test? *)
    "list the LVM logical volumes (LVs)",
    "\
 List all the logical volumes detected.  This is the equivalent
    "list the LVM logical volumes (LVs)",
    "\
 List all the logical volumes detected.  This is the equivalent
@@ -1001,6 +1011,43 @@ locations.");
 This is the same as C<guestfs_command>, but splits the
 result into a list of lines.");
 
 This is the same as C<guestfs_command>, but splits the
 result into a list of lines.");
 
+  ("stat", (RStat "statbuf", [String "path"]), 52, [],
+   [InitBasicFS, TestOutputStruct (
+      [["touch"; "/new"];
+       ["stat"; "/new"]], [CompareWithInt ("size", 0)])],
+   "get file information",
+   "\
+Returns file information for the given C<path>.
+
+This is the same as the C<stat(2)> system call.");
+
+  ("lstat", (RStat "statbuf", [String "path"]), 53, [],
+   [InitBasicFS, TestOutputStruct (
+      [["touch"; "/new"];
+       ["lstat"; "/new"]], [CompareWithInt ("size", 0)])],
+   "get file information for a symbolic link",
+   "\
+Returns file information for the given C<path>.
+
+This is the same as C<guestfs_stat> except that if C<path>
+is a symbolic link, then the link is stat-ed, not the file it
+refers to.
+
+This is the same as the C<lstat(2)> system call.");
+
+  ("statvfs", (RStatVFS "statbuf", [String "path"]), 54, [],
+   [InitBasicFS, TestOutputStruct (
+      [["statvfs"; "/"]], [CompareWithInt ("bfree", 487702);
+                          CompareWithInt ("blocks", 490020);
+                          CompareWithInt ("bsize", 1024)])],
+   "get file system statistics",
+   "\
+Returns file system statistics for any mounted file system.
+C<path> should be a file or directory in the mounted file system
+(typically it is the mount point itself, but it doesn't need to be).
+
+This is the same as the C<statvfs(2)> system call.");
+
 ]
 
 let all_functions = non_daemon_functions @ daemon_functions
 ]
 
 let all_functions = non_daemon_functions @ daemon_functions
@@ -1075,6 +1122,39 @@ let lv_cols = [
   "modules", `String;
 ]
 
   "modules", `String;
 ]
 
+(* Column names and types from stat structures.
+ * NB. Can't use things like 'st_atime' because glibc header files
+ * define some of these as macros.  Ugh.
+ *)
+let stat_cols = [
+  "dev", `Int;
+  "ino", `Int;
+  "mode", `Int;
+  "nlink", `Int;
+  "uid", `Int;
+  "gid", `Int;
+  "rdev", `Int;
+  "size", `Int;
+  "blksize", `Int;
+  "blocks", `Int;
+  "atime", `Int;
+  "mtime", `Int;
+  "ctime", `Int;
+]
+let statvfs_cols = [
+  "bsize", `Int;
+  "frsize", `Int;
+  "blocks", `Int;
+  "bfree", `Int;
+  "bavail", `Int;
+  "files", `Int;
+  "ffree", `Int;
+  "favail", `Int;
+  "fsid", `Int;
+  "flag", `Int;
+  "namemax", `Int;
+]
+
 (* Useful functions.
  * Note we don't want to use any external OCaml libraries which
  * makes this a bit harder than it should be.
 (* Useful functions.
  * Note we don't want to use any external OCaml libraries which
  * makes this a bit harder than it should be.
@@ -1203,7 +1283,8 @@ let check_functions () =
       (match fst style with
        | RErr -> ()
        | RInt n | RBool n | RConstString n | RString n
       (match fst style with
        | RErr -> ()
        | RInt n | RBool n | RConstString n | RString n
-       | RStringList n | RPVList n | RVGList n | RLVList n ->
+       | RStringList n | RPVList n | RVGList n | RLVList n
+       | RStat n | RStatVFS n ->
           check_arg_ret_name n
        | RIntBool (n,m) ->
           check_arg_ret_name n;
           check_arg_ret_name n
        | RIntBool (n,m) ->
           check_arg_ret_name n;
@@ -1345,17 +1426,34 @@ I<The caller must free the returned string after use>.\n\n"
 (like L<environ(3)>), or NULL if there was an error.
 I<The caller must free the strings and the array after use>.\n\n"
        | RIntBool _ ->
 (like L<environ(3)>), or NULL if there was an error.
 I<The caller must free the strings and the array after use>.\n\n"
        | RIntBool _ ->
-          pr "This function returns a C<struct guestfs_int_bool *>.
+          pr "This function returns a C<struct guestfs_int_bool *>,
+or NULL if there was an error.
 I<The caller must call C<guestfs_free_int_bool> after use>.\n\n"
        | RPVList _ ->
 I<The caller must call C<guestfs_free_int_bool> after use>.\n\n"
        | RPVList _ ->
-          pr "This function returns a C<struct guestfs_lvm_pv_list *>.
+          pr "This function returns a C<struct guestfs_lvm_pv_list *>
+(see E<lt>guestfs-structs.hE<gt>),
+or NULL if there was an error.
 I<The caller must call C<guestfs_free_lvm_pv_list> after use>.\n\n"
        | RVGList _ ->
 I<The caller must call C<guestfs_free_lvm_pv_list> after use>.\n\n"
        | RVGList _ ->
-          pr "This function returns a C<struct guestfs_lvm_vg_list *>.
+          pr "This function returns a C<struct guestfs_lvm_vg_list *>
+(see E<lt>guestfs-structs.hE<gt>),
+or NULL if there was an error.
 I<The caller must call C<guestfs_free_lvm_vg_list> after use>.\n\n"
        | RLVList _ ->
 I<The caller must call C<guestfs_free_lvm_vg_list> after use>.\n\n"
        | RLVList _ ->
-          pr "This function returns a C<struct guestfs_lvm_lv_list *>.
+          pr "This function returns a C<struct guestfs_lvm_lv_list *>
+(see E<lt>guestfs-structs.hE<gt>),
+or NULL if there was an error.
 I<The caller must call C<guestfs_free_lvm_lv_list> after use>.\n\n"
 I<The caller must call C<guestfs_free_lvm_lv_list> after use>.\n\n"
+       | RStat _ ->
+          pr "This function returns a C<struct guestfs_stat *>
+(see L<stat(2)> and E<lt>guestfs-structs.hE<gt>),
+or NULL if there was an error.
+I<The caller must call C<free> after use>.\n\n"
+       | RStatVFS _ ->
+          pr "This function returns a C<struct guestfs_statvfs *>
+(see L<statvfs(2)> and E<lt>guestfs-structs.hE<gt>),
+or NULL if there was an error.
+I<The caller must call C<free> after use>.\n\n"
       );
       if List.mem ProtocolLimitWarning flags then
        pr "%s\n\n" protocol_limit_warning;
       );
       if List.mem ProtocolLimitWarning flags then
        pr "%s\n\n" protocol_limit_warning;
@@ -1426,6 +1524,18 @@ and generate_xdr () =
        pr "\n";
   ) ["pv", pv_cols; "vg", vg_cols; "lv", lv_cols];
 
        pr "\n";
   ) ["pv", pv_cols; "vg", vg_cols; "lv", lv_cols];
 
+  (* Stat internal structures. *)
+  List.iter (
+    function
+    | typ, cols ->
+       pr "struct guestfs_int_%s {\n" typ;
+       List.iter (function
+                  | name, `Int -> pr "  hyper %s;\n" name
+                 ) cols;
+       pr "};\n";
+       pr "\n";
+  ) ["stat", stat_cols; "statvfs", statvfs_cols];
+
   List.iter (
     fun (shortname, style, _, _, _, _, _) ->
       let name = "guestfs_" ^ shortname in
   List.iter (
     fun (shortname, style, _, _, _, _, _) ->
       let name = "guestfs_" ^ shortname in
@@ -1481,6 +1591,14 @@ and generate_xdr () =
           pr "struct %s_ret {\n" name;
           pr "  guestfs_lvm_int_lv_list %s;\n" n;
           pr "};\n\n"
           pr "struct %s_ret {\n" name;
           pr "  guestfs_lvm_int_lv_list %s;\n" n;
           pr "};\n\n"
+       | RStat n ->
+          pr "struct %s_ret {\n" name;
+          pr "  guestfs_int_stat %s;\n" n;
+          pr "};\n\n";
+       | RStatVFS n ->
+          pr "struct %s_ret {\n" name;
+          pr "  guestfs_int_statvfs %s;\n" n;
+          pr "};\n\n";
       );
   ) daemon_functions;
 
       );
   ) daemon_functions;
 
@@ -1579,7 +1697,20 @@ and generate_structs_h () =
        pr "  struct guestfs_lvm_%s *val;\n" typ;
        pr "};\n";
        pr "\n"
        pr "  struct guestfs_lvm_%s *val;\n" typ;
        pr "};\n";
        pr "\n"
-  ) ["pv", pv_cols; "vg", vg_cols; "lv", lv_cols]
+  ) ["pv", pv_cols; "vg", vg_cols; "lv", lv_cols];
+
+  (* Stat structures. *)
+  List.iter (
+    function
+    | typ, cols ->
+       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]
 
 (* Generate the guestfs-actions.h file. *)
 and generate_actions_h () =
 
 (* Generate the guestfs-actions.h file. *)
 and generate_actions_h () =
@@ -1612,7 +1743,8 @@ and generate_client_actions () =
        | RInt _
        | RBool _ | RString _ | RStringList _
        | RIntBool _
        | RInt _
        | RBool _ | RString _ | RStringList _
        | RIntBool _
-       | RPVList _ | RVGList _ | RLVList _ ->
+       | RPVList _ | RVGList _ | RLVList _
+       | RStat _ | RStatVFS _ ->
           pr "  struct %s_ret ret;\n" name
       );
       pr "};\n\n";
           pr "  struct %s_ret ret;\n" name
       );
       pr "};\n\n";
@@ -1641,7 +1773,8 @@ and generate_client_actions () =
        | RInt _
        | RBool _ | RString _ | RStringList _
        | RIntBool _
        | RInt _
        | RBool _ | RString _ | RStringList _
        | RIntBool _
-       | RPVList _ | RVGList _ | RLVList _ ->
+       | RPVList _ | RVGList _ | RLVList _
+       | RStat _ | RStatVFS _ ->
            pr "  if (!xdr_%s_ret (xdr, &rv->ret)) {\n" name;
            pr "    error (g, \"%s: failed to parse reply\");\n" name;
            pr "    return;\n";
            pr "  if (!xdr_%s_ret (xdr, &rv->ret)) {\n" name;
            pr "    error (g, \"%s: failed to parse reply\");\n" name;
            pr "    return;\n";
@@ -1663,7 +1796,8 @@ and generate_client_actions () =
        | RConstString _ ->
            failwithf "RConstString cannot be returned from a daemon function"
        | RString _ | RStringList _ | RIntBool _
        | RConstString _ ->
            failwithf "RConstString cannot be returned from a daemon function"
        | RString _ | RStringList _ | RIntBool _
-       | RPVList _ | RVGList _ | RLVList _ ->
+       | RPVList _ | RVGList _ | RLVList _
+       | RStat _ | RStatVFS _ ->
            "NULL" in
 
       pr "{\n";
            "NULL" in
 
       pr "{\n";
@@ -1765,6 +1899,12 @@ and generate_client_actions () =
        | RLVList n ->
           pr "  /* caller will free this */\n";
           pr "  return safe_memdup (g, &rv.ret.%s, sizeof (rv.ret.%s));\n" n n
        | RLVList n ->
           pr "  /* caller will free this */\n";
           pr "  return safe_memdup (g, &rv.ret.%s, sizeof (rv.ret.%s));\n" n n
+       | RStat n ->
+          pr "  /* caller will free this */\n";
+          pr "  return safe_memdup (g, &rv.ret.%s, sizeof (rv.ret.%s));\n" n n
+       | RStatVFS n ->
+          pr "  /* caller will free this */\n";
+          pr "  return safe_memdup (g, &rv.ret.%s, sizeof (rv.ret.%s));\n" n n
       );
 
       pr "}\n\n"
       );
 
       pr "}\n\n"
@@ -1819,7 +1959,9 @@ and generate_daemon_actions () =
        | RIntBool _ -> pr "  guestfs_%s_ret *r;\n" name; "NULL"
        | RPVList _ -> pr "  guestfs_lvm_int_pv_list *r;\n"; "NULL"
        | RVGList _ -> pr "  guestfs_lvm_int_vg_list *r;\n"; "NULL"
        | RIntBool _ -> pr "  guestfs_%s_ret *r;\n" name; "NULL"
        | RPVList _ -> pr "  guestfs_lvm_int_pv_list *r;\n"; "NULL"
        | RVGList _ -> pr "  guestfs_lvm_int_vg_list *r;\n"; "NULL"
-       | RLVList _ -> pr "  guestfs_lvm_int_lv_list *r;\n"; "NULL" in
+       | 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
 
       (match snd style with
        | [] -> ()
 
       (match snd style with
        | [] -> ()
@@ -1894,17 +2036,7 @@ and generate_daemon_actions () =
        | RIntBool _ ->
           pr "  reply ((xdrproc_t) xdr_guestfs_%s_ret, (char *) r);\n" name;
           pr "  xdr_free ((xdrproc_t) xdr_guestfs_%s_ret, (char *) r);\n" name
        | RIntBool _ ->
           pr "  reply ((xdrproc_t) xdr_guestfs_%s_ret, (char *) r);\n" name;
           pr "  xdr_free ((xdrproc_t) xdr_guestfs_%s_ret, (char *) r);\n" name
-       | RPVList 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" name;
-          pr "  xdr_free ((xdrproc_t) xdr_guestfs_%s_ret, (char *) &ret);\n" name
-       | RVGList 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" name;
-          pr "  xdr_free ((xdrproc_t) xdr_guestfs_%s_ret, (char *) &ret);\n" name
-       | RLVList n ->
+       | RPVList n | RVGList n | RLVList n | RStat n | RStatVFS 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" name;
           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" name;
@@ -2156,6 +2288,7 @@ int main (int argc, char *argv[])
   const char *srcdir;
   int fd;
   char buf[256];
   const char *srcdir;
   int fd;
   char buf[256];
+  int nr_tests;
 
   g = guestfs_create ();
   if (g == NULL) {
 
   g = guestfs_create ();
   if (g == NULL) {
@@ -2262,11 +2395,12 @@ int main (int argc, char *argv[])
     exit (1);
   }
 
     exit (1);
   }
 
-" (500 * 1024 * 1024) (50 * 1024 * 1024) (10 * 1024 * 1024);
+  nr_tests = %d;
+" (500 * 1024 * 1024) (50 * 1024 * 1024) (10 * 1024 * 1024) nr_tests;
 
   iteri (
     fun i test_name ->
 
   iteri (
     fun i test_name ->
-      pr "  printf (\"%3d/%3d %s\\n\");\n" (i+1) nr_tests test_name;
+      pr "  printf (\"%3d/%%3d %s\\n\", nr_tests);\n" (i+1) test_name;
       pr "  if (%s () == -1) {\n" test_name;
       pr "    printf (\"%s FAILED\\n\");\n" test_name;
       pr "    failed++;\n";
       pr "  if (%s () == -1) {\n" test_name;
       pr "    printf (\"%s FAILED\\n\");\n" test_name;
       pr "    failed++;\n";
@@ -2284,8 +2418,7 @@ int main (int argc, char *argv[])
   pr "\n";
 
   pr "  if (failed > 0) {\n";
   pr "\n";
 
   pr "  if (failed > 0) {\n";
-  pr "    printf (\"***** %%d / %d tests FAILED *****\\n\", failed);\n"
-    nr_tests;
+  pr "    printf (\"***** %%d / %%d tests FAILED *****\\n\", failed, nr_tests);\n";
   pr "    exit (1);\n";
   pr "  }\n";
   pr "\n";
   pr "    exit (1);\n";
   pr "  }\n";
   pr "\n";
@@ -2434,6 +2567,44 @@ and generate_one_test name i (init, test) =
        in
        List.iter (generate_test_command_call test_name) seq;
        generate_test_command_call ~test test_name last
        in
        List.iter (generate_test_command_call test_name) seq;
        generate_test_command_call ~test test_name last
+   | TestOutputStruct (seq, checks) ->
+       pr "  /* TestOutputStruct for %s (%d) */\n" name i;
+       let seq, last = get_seq_last seq in
+       let test () =
+        List.iter (
+          function
+          | CompareWithInt (field, expected) ->
+              pr "    if (r->%s != %d) {\n" field expected;
+              pr "      fprintf (stderr, \"%s: %s was %%d, expected %d\\n\",\n"
+                test_name field expected;
+              pr "               (int) r->%s);\n" field;
+              pr "      return -1;\n";
+              pr "    }\n"
+          | CompareWithString (field, expected) ->
+              pr "    if (strcmp (r->%s, \"%s\") != 0) {\n" field expected;
+              pr "      fprintf (stderr, \"%s: %s was \"%%s\", expected \"%s\"\\n\",\n"
+                test_name field expected;
+              pr "               r->%s);\n" field;
+              pr "      return -1;\n";
+              pr "    }\n"
+          | CompareFieldsIntEq (field1, field2) ->
+              pr "    if (r->%s != r->%s) {\n" field1 field2;
+              pr "      fprintf (stderr, \"%s: %s (%%d) <> %s (%%d)\\n\",\n"
+                test_name field1 field2;
+              pr "               (int) r->%s, (int) r->%s);\n" field1 field2;
+              pr "      return -1;\n";
+              pr "    }\n"
+          | CompareFieldsStrEq (field1, field2) ->
+              pr "    if (strcmp (r->%s, r->%s) != 0) {\n" field1 field2;
+              pr "      fprintf (stderr, \"%s: %s (\"%%s\") <> %s (\"%%s\")\\n\",\n"
+                test_name field1 field2;
+              pr "               r->%s, r->%s);\n" field1 field2;
+              pr "      return -1;\n";
+              pr "    }\n"
+        ) checks
+       in
+       List.iter (generate_test_command_call test_name) seq;
+       generate_test_command_call ~test test_name last
    | TestLastFail seq ->
        pr "  /* TestLastFail for %s (%d) */\n" name i;
        let seq, last = get_seq_last seq in
    | TestLastFail seq ->
        pr "  /* TestLastFail for %s (%d) */\n" name i;
        let seq, last = get_seq_last seq in
@@ -2494,17 +2665,17 @@ and generate_test_command_call ?(expect_error = false) ?test test_name cmd =
            pr "    int i;\n";
            "NULL"
        | RIntBool _ ->
            pr "    int i;\n";
            "NULL"
        | RIntBool _ ->
-           pr "    struct guestfs_int_bool *r;\n";
-           "NULL"
+           pr "    struct guestfs_int_bool *r;\n"; "NULL"
        | RPVList _ ->
        | RPVList _ ->
-           pr "    struct guestfs_lvm_pv_list *r;\n";
-           "NULL"
+           pr "    struct guestfs_lvm_pv_list *r;\n"; "NULL"
        | RVGList _ ->
        | RVGList _ ->
-           pr "    struct guestfs_lvm_vg_list *r;\n";
-           "NULL"
+           pr "    struct guestfs_lvm_vg_list *r;\n"; "NULL"
        | RLVList _ ->
        | RLVList _ ->
-           pr "    struct guestfs_lvm_lv_list *r;\n";
-           "NULL" in
+           pr "    struct guestfs_lvm_lv_list *r;\n"; "NULL"
+       | RStat _ ->
+           pr "    struct guestfs_stat *r;\n"; "NULL"
+       | RStatVFS _ ->
+           pr "    struct guestfs_statvfs *r;\n"; "NULL" in
 
       pr "    suppress_error = %d;\n" (if expect_error then 1 else 0);
       pr "    r = guestfs_%s (g" name;
 
       pr "    suppress_error = %d;\n" (if expect_error then 1 else 0);
       pr "    r = guestfs_%s (g" name;
@@ -2555,6 +2726,8 @@ and generate_test_command_call ?(expect_error = false) ?test test_name cmd =
           pr "    guestfs_free_lvm_vg_list (r);\n"
        | RLVList _ ->
           pr "    guestfs_free_lvm_lv_list (r);\n"
           pr "    guestfs_free_lvm_vg_list (r);\n"
        | RLVList _ ->
           pr "    guestfs_free_lvm_lv_list (r);\n"
+       | RStat _ | RStatVFS _ ->
+          pr "    free (r);\n"
       );
 
       pr "  }\n"
       );
 
       pr "  }\n"
@@ -2694,6 +2867,21 @@ and generate_fish_cmds () =
        pr "\n";
   ) ["pv", pv_cols; "vg", vg_cols; "lv", lv_cols];
 
        pr "\n";
   ) ["pv", pv_cols; "vg", vg_cols; "lv", lv_cols];
 
+  (* print_{stat,statvfs} functions *)
+  List.iter (
+    function
+    | typ, cols ->
+       pr "static void print_%s (struct guestfs_%s *%s)\n" typ typ typ;
+       pr "{\n";
+       List.iter (
+         function
+         | name, `Int ->
+             pr "  printf (\"%s: %%\" PRIi64 \"\\n\", %s->%s);\n" name typ name
+       ) cols;
+       pr "}\n";
+       pr "\n";
+  ) ["stat", stat_cols; "statvfs", statvfs_cols];
+
   (* run_<action> actions *)
   List.iter (
     fun (name, style, _, flags, _, _, _) ->
   (* run_<action> actions *)
   List.iter (
     fun (name, style, _, flags, _, _, _) ->
@@ -2710,6 +2898,8 @@ and generate_fish_cmds () =
        | RPVList _ -> pr "  struct guestfs_lvm_pv_list *r;\n"
        | RVGList _ -> pr "  struct guestfs_lvm_vg_list *r;\n"
        | RLVList _ -> pr "  struct guestfs_lvm_lv_list *r;\n"
        | RPVList _ -> pr "  struct guestfs_lvm_pv_list *r;\n"
        | RVGList _ -> pr "  struct guestfs_lvm_vg_list *r;\n"
        | RLVList _ -> pr "  struct guestfs_lvm_lv_list *r;\n"
+       | RStat _ -> pr "  struct guestfs_stat *r;\n"
+       | RStatVFS _ -> pr "  struct guestfs_statvfs *r;\n"
       );
       List.iter (
        function
       );
       List.iter (
        function
@@ -2797,6 +2987,16 @@ and generate_fish_cmds () =
           pr "  print_lv_list (r);\n";
           pr "  guestfs_free_lvm_lv_list (r);\n";
           pr "  return 0;\n"
           pr "  print_lv_list (r);\n";
           pr "  guestfs_free_lvm_lv_list (r);\n";
           pr "  return 0;\n"
+       | RStat _ ->
+          pr "  if (r == NULL) return -1;\n";
+          pr "  print_stat (r);\n";
+          pr "  free (r);\n";
+          pr "  return 0;\n"
+       | RStatVFS _ ->
+          pr "  if (r == NULL) return -1;\n";
+          pr "  print_statvfs (r);\n";
+          pr "  free (r);\n";
+          pr "  return 0;\n"
       );
       pr "}\n";
       pr "\n"
       );
       pr "}\n";
       pr "\n"
@@ -2976,6 +3176,12 @@ and generate_prototype ?(extern = true) ?(static = false) ?(semicolon = true)
    | RLVList _ ->
        if not in_daemon then pr "struct guestfs_lvm_lv_list *"
        else pr "guestfs_lvm_int_lv_list *"
    | RLVList _ ->
        if not in_daemon then pr "struct guestfs_lvm_lv_list *"
        else pr "guestfs_lvm_int_lv_list *"
+   | RStat _ ->
+       if not in_daemon then pr "struct guestfs_stat *"
+       else pr "guestfs_int_stat *"
+   | RStatVFS _ ->
+       if not in_daemon then pr "struct guestfs_statvfs *"
+       else pr "guestfs_int_statvfs *"
   );
   pr "%s%s (" prefix name;
   if handle = None && List.length (snd style) = 0 then
   );
   pr "%s%s (" prefix name;
   if handle = None && List.length (snd style) = 0 then
@@ -3051,6 +3257,8 @@ val close : t -> unit
 ";
   generate_ocaml_lvm_structure_decls ();
 
 ";
   generate_ocaml_lvm_structure_decls ();
 
+  generate_ocaml_stat_structure_decls ();
+
   (* The actions. *)
   List.iter (
     fun (name, style, _, _, _, shortdesc, _) ->
   (* The actions. *)
   List.iter (
     fun (name, style, _, _, _, shortdesc, _) ->
@@ -3076,6 +3284,8 @@ let () =
 
   generate_ocaml_lvm_structure_decls ();
 
 
   generate_ocaml_lvm_structure_decls ();
 
+  generate_ocaml_stat_structure_decls ();
+
   (* The actions. *)
   List.iter (
     fun (name, style, _, _, _, shortdesc, _) ->
   (* The actions. *)
   List.iter (
     fun (name, style, _, _, _, shortdesc, _) ->
@@ -3166,6 +3376,30 @@ and generate_ocaml_c () =
       pr "\n";
   ) ["pv", pv_cols; "vg", vg_cols; "lv", lv_cols];
 
       pr "\n";
   ) ["pv", pv_cols; "vg", vg_cols; "lv", lv_cols];
 
+  (* Stat copy functions. *)
+  List.iter (
+    fun (typ, cols) ->
+      pr "static CAMLprim value\n";
+      pr "copy_%s (const struct guestfs_%s *%s)\n" typ typ typ;
+      pr "{\n";
+      pr "  CAMLparam0 ();\n";
+      pr "  CAMLlocal2 (rv, v);\n";
+      pr "\n";
+      pr "  rv = caml_alloc (%d, 0);\n" (List.length cols);
+      iteri (
+       fun i col ->
+         (match col with
+          | name, `Int ->
+              pr "  v = caml_copy_int64 (%s->%s);\n" typ name
+         );
+         pr "  Store_field (rv, %d, v);\n" i
+      ) cols;
+      pr "  CAMLreturn (rv);\n";
+      pr "}\n";
+      pr "\n";
+  ) ["stat", stat_cols; "statvfs", statvfs_cols];
+
+  (* The wrappers. *)
   List.iter (
     fun (name, style, _, _, _, _, _) ->
       let params =
   List.iter (
     fun (name, style, _, _, _, _, _) ->
       let params =
@@ -3220,17 +3454,17 @@ and generate_ocaml_c () =
            pr "  char **r;\n";
            "NULL"
        | RIntBool _ ->
            pr "  char **r;\n";
            "NULL"
        | RIntBool _ ->
-           pr "  struct guestfs_int_bool *r;\n";
-           "NULL"
+           pr "  struct guestfs_int_bool *r;\n"; "NULL"
        | RPVList _ ->
        | RPVList _ ->
-           pr "  struct guestfs_lvm_pv_list *r;\n";
-           "NULL"
+           pr "  struct guestfs_lvm_pv_list *r;\n"; "NULL"
        | RVGList _ ->
        | RVGList _ ->
-           pr "  struct guestfs_lvm_vg_list *r;\n";
-           "NULL"
+           pr "  struct guestfs_lvm_vg_list *r;\n"; "NULL"
        | RLVList _ ->
        | RLVList _ ->
-           pr "  struct guestfs_lvm_lv_list *r;\n";
-           "NULL" in
+           pr "  struct guestfs_lvm_lv_list *r;\n"; "NULL"
+       | RStat _ ->
+           pr "  struct guestfs_stat *r;\n"; "NULL"
+       | RStatVFS _ ->
+           pr "  struct guestfs_statvfs *r;\n"; "NULL" in
       pr "\n";
 
       pr "  caml_enter_blocking_section ();\n";
       pr "\n";
 
       pr "  caml_enter_blocking_section ();\n";
@@ -3276,6 +3510,12 @@ and generate_ocaml_c () =
        | RLVList _ ->
           pr "  rv = copy_lvm_lv_list (r);\n";
           pr "  guestfs_free_lvm_lv_list (r);\n";
        | RLVList _ ->
           pr "  rv = copy_lvm_lv_list (r);\n";
           pr "  guestfs_free_lvm_lv_list (r);\n";
+       | RStat _ ->
+          pr "  rv = copy_stat (r);\n";
+          pr "  free (r);\n";
+       | RStatVFS _ ->
+          pr "  rv = copy_statvfs (r);\n";
+          pr "  free (r);\n";
       );
 
       pr "  CAMLreturn (rv);\n";
       );
 
       pr "  CAMLreturn (rv);\n";
@@ -3310,6 +3550,18 @@ and generate_ocaml_lvm_structure_decls () =
       pr "\n"
   ) ["pv", pv_cols; "vg", vg_cols; "lv", lv_cols]
 
       pr "\n"
   ) ["pv", pv_cols; "vg", vg_cols; "lv", lv_cols]
 
+and generate_ocaml_stat_structure_decls () =
+  List.iter (
+    fun (typ, cols) ->
+      pr "type %s = {\n" typ;
+      List.iter (
+       function
+       | name, `Int -> pr "  %s : int64;\n" name
+      ) cols;
+      pr "}\n";
+      pr "\n"
+  ) ["stat", stat_cols; "statvfs", statvfs_cols]
+
 and generate_ocaml_prototype ?(is_external = false) name style =
   if is_external then pr "external " else pr "val ";
   pr "%s : t -> " name;
 and generate_ocaml_prototype ?(is_external = false) name style =
   if is_external then pr "external " else pr "val ";
   pr "%s : t -> " name;
@@ -3332,6 +3584,8 @@ and generate_ocaml_prototype ?(is_external = false) name style =
    | RPVList _ -> pr "lvm_pv array"
    | RVGList _ -> pr "lvm_vg array"
    | RLVList _ -> pr "lvm_lv array"
    | RPVList _ -> pr "lvm_pv array"
    | RVGList _ -> pr "lvm_vg array"
    | RLVList _ -> pr "lvm_lv array"
+   | RStat _ -> pr "stat"
+   | RStatVFS _ -> pr "statvfs"
   );
   if is_external then (
     pr " = ";
   );
   if is_external then (
     pr " = ";
@@ -3442,7 +3696,8 @@ DESTROY (g)
        | RString _ -> pr "SV *\n"
        | RStringList _
        | RIntBool _
        | RString _ -> pr "SV *\n"
        | RStringList _
        | RIntBool _
-       | RPVList _ | RVGList _ | RLVList _ ->
+       | RPVList _ | RVGList _ | RLVList _
+       | RStat _ | RStatVFS _ ->
           pr "void\n" (* all lists returned implictly on the stack *)
       );
       (* Call and arguments. *)
           pr "void\n" (* all lists returned implictly on the stack *)
       );
       (* Call and arguments. *)
@@ -3556,11 +3811,16 @@ DESTROY (g)
           pr "      PUSHs (sv_2mortal (newSViv (r->b)));\n";
           pr "      guestfs_free_int_bool (r);\n";
        | RPVList n ->
           pr "      PUSHs (sv_2mortal (newSViv (r->b)));\n";
           pr "      guestfs_free_int_bool (r);\n";
        | RPVList n ->
-          generate_perl_lvm_code "pv" pv_cols name style n do_cleanups;
+          generate_perl_lvm_code "pv" pv_cols name style n do_cleanups
        | RVGList n ->
        | RVGList n ->
-          generate_perl_lvm_code "vg" vg_cols name style n do_cleanups;
+          generate_perl_lvm_code "vg" vg_cols name style n do_cleanups
        | RLVList n ->
        | RLVList n ->
-          generate_perl_lvm_code "lv" lv_cols name style n do_cleanups;
+          generate_perl_lvm_code "lv" lv_cols name style n do_cleanups
+       | RStat n ->
+          generate_perl_stat_code "stat" stat_cols name style n do_cleanups
+       | RStatVFS n ->
+          generate_perl_stat_code
+            "statvfs" statvfs_cols name style n do_cleanups
       );
 
       pr "\n"
       );
 
       pr "\n"
@@ -3603,6 +3863,24 @@ and generate_perl_lvm_code typ cols name style n do_cleanups =
   pr "      }\n";
   pr "      guestfs_free_lvm_%s_list (%s);\n" typ n
 
   pr "      }\n";
   pr "      guestfs_free_lvm_%s_list (%s);\n" typ n
 
+and generate_perl_stat_code typ cols name style n do_cleanups =
+  pr "PREINIT:\n";
+  pr "      struct guestfs_%s *%s;\n" typ n;
+  pr " PPCODE:\n";
+  pr "      %s = guestfs_%s " n name;
+  generate_call_args ~handle:"g" style;
+  pr ";\n";
+  do_cleanups ();
+  pr "      if (%s == NULL)\n" n;
+  pr "        croak (\"%s: %%s\", guestfs_last_error (g));\n" name;
+  pr "      EXTEND (SP, %d);\n" (List.length cols);
+  List.iter (
+    function
+    | name, `Int ->
+       pr "      PUSHs (sv_2mortal (my_newSVll (%s->%s)));\n" n name
+  ) cols;
+  pr "      free (%s);\n" n
+
 (* Generate Sys/Guestfs.pm. *)
 and generate_perl_pm () =
   generate_header HashStyle LGPLv2;
 (* Generate Sys/Guestfs.pm. *)
 and generate_perl_pm () =
   generate_header HashStyle LGPLv2;
@@ -3734,6 +4012,8 @@ and generate_perl_prototype name style =
    | RPVList n
    | RVGList n
    | RLVList n -> pr "@%s = " n
    | RPVList n
    | RVGList n
    | RLVList n -> pr "@%s = " n
+   | RStat n
+   | RStatVFS n -> pr "%%%s = " n
   );
   pr "$h->%s (" name;
   let comma = ref false in
   );
   pr "$h->%s (" name;
   let comma = ref false in
@@ -3925,6 +4205,27 @@ py_guestfs_close (PyObject *self, PyObject *args)
       pr "\n"
   ) ["pv", pv_cols; "vg", vg_cols; "lv", lv_cols];
 
       pr "\n"
   ) ["pv", pv_cols; "vg", vg_cols; "lv", lv_cols];
 
+  (* Stat structures, turned into Python dictionaries. *)
+  List.iter (
+    fun (typ, cols) ->
+      pr "static PyObject *\n";
+      pr "put_%s (struct guestfs_%s *%s)\n" typ typ typ;
+      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 (%s->%s));\n"
+             typ name
+      ) cols;
+      pr "  return dict;\n";
+      pr "};\n";
+      pr "\n";
+  ) ["stat", stat_cols; "statvfs", statvfs_cols];
+
   (* Python wrapper functions. *)
   List.iter (
     fun (name, style, _, _, _, _, _) ->
   (* Python wrapper functions. *)
   List.iter (
     fun (name, style, _, _, _, _, _) ->
@@ -3945,7 +4246,9 @@ py_guestfs_close (PyObject *self, PyObject *args)
        | RIntBool _ -> pr "  struct guestfs_int_bool *r;\n"; "NULL"
        | RPVList n -> pr "  struct guestfs_lvm_pv_list *r;\n"; "NULL"
        | RVGList n -> pr "  struct guestfs_lvm_vg_list *r;\n"; "NULL"
        | RIntBool _ -> pr "  struct guestfs_int_bool *r;\n"; "NULL"
        | RPVList n -> pr "  struct guestfs_lvm_pv_list *r;\n"; "NULL"
        | RVGList n -> pr "  struct guestfs_lvm_vg_list *r;\n"; "NULL"
-       | RLVList n -> pr "  struct guestfs_lvm_lv_list *r;\n"; "NULL" in
+       | 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
 
       List.iter (
        function
 
       List.iter (
        function
@@ -4039,6 +4342,12 @@ py_guestfs_close (PyObject *self, PyObject *args)
        | RLVList n ->
           pr "  py_r = put_lvm_lv_list (r);\n";
           pr "  guestfs_free_lvm_lv_list (r);\n"
        | RLVList n ->
           pr "  py_r = put_lvm_lv_list (r);\n";
           pr "  guestfs_free_lvm_lv_list (r);\n"
+       | RStat n ->
+          pr "  py_r = put_stat (r);\n";
+          pr "  free (r);\n"
+       | RStatVFS n ->
+          pr "  py_r = put_statvfs (r);\n";
+          pr "  free (r);\n"
       );
 
       pr "  return py_r;\n";
       );
 
       pr "  return py_r;\n";