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 \
+       stat.c \
        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
+    (* 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
@@ -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
  *
- * 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.
@@ -141,11 +144,21 @@ and test =
      * 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
 
+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
@@ -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, [],
-   [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, [],
-   [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, [],
-   [InitBasicFSonLVM, TestOutputLength (
-      [["pvs"]], 1)],
+   [], (* XXX how to test? *)
    "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.");
 
+  ("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
@@ -1075,6 +1122,39 @@ let lv_cols = [
   "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.
@@ -1203,7 +1283,8 @@ let check_functions () =
       (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;
@@ -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 _ ->
-          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 _ ->
-          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 _ ->
-          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 _ ->
-          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"
+       | 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;
@@ -1426,6 +1524,18 @@ and generate_xdr () =
        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
@@ -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"
+       | 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;
 
@@ -1579,7 +1697,20 @@ and generate_structs_h () =
        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 () =
@@ -1612,7 +1743,8 @@ and generate_client_actions () =
        | RInt _
        | RBool _ | RString _ | RStringList _
        | RIntBool _
-       | RPVList _ | RVGList _ | RLVList _ ->
+       | RPVList _ | RVGList _ | RLVList _
+       | RStat _ | RStatVFS _ ->
           pr "  struct %s_ret ret;\n" name
       );
       pr "};\n\n";
@@ -1641,7 +1773,8 @@ and generate_client_actions () =
        | 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";
@@ -1663,7 +1796,8 @@ and generate_client_actions () =
        | 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";
@@ -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
+       | 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"
@@ -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"
-       | 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
        | [] -> ()
@@ -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
-       | 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;
@@ -2156,6 +2288,7 @@ int main (int argc, char *argv[])
   const char *srcdir;
   int fd;
   char buf[256];
+  int nr_tests;
 
   g = guestfs_create ();
   if (g == NULL) {
@@ -2262,11 +2395,12 @@ int main (int argc, char *argv[])
     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 ->
-      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";
@@ -2284,8 +2418,7 @@ int main (int argc, char *argv[])
   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";
@@ -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
+   | 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
@@ -2494,17 +2665,17 @@ and generate_test_command_call ?(expect_error = false) ?test test_name cmd =
            pr "    int i;\n";
            "NULL"
        | RIntBool _ ->
-           pr "    struct guestfs_int_bool *r;\n";
-           "NULL"
+           pr "    struct guestfs_int_bool *r;\n"; "NULL"
        | RPVList _ ->
-           pr "    struct guestfs_lvm_pv_list *r;\n";
-           "NULL"
+           pr "    struct guestfs_lvm_pv_list *r;\n"; "NULL"
        | RVGList _ ->
-           pr "    struct guestfs_lvm_vg_list *r;\n";
-           "NULL"
+           pr "    struct guestfs_lvm_vg_list *r;\n"; "NULL"
        | 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;
@@ -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"
+       | RStat _ | RStatVFS _ ->
+          pr "    free (r);\n"
       );
 
       pr "  }\n"
@@ -2694,6 +2867,21 @@ and generate_fish_cmds () =
        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, _, _, _) ->
@@ -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"
+       | RStat _ -> pr "  struct guestfs_stat *r;\n"
+       | RStatVFS _ -> pr "  struct guestfs_statvfs *r;\n"
       );
       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"
+       | 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"
@@ -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 *"
+   | 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
@@ -3051,6 +3257,8 @@ val close : t -> unit
 ";
   generate_ocaml_lvm_structure_decls ();
 
+  generate_ocaml_stat_structure_decls ();
+
   (* The actions. *)
   List.iter (
     fun (name, style, _, _, _, shortdesc, _) ->
@@ -3076,6 +3284,8 @@ let () =
 
   generate_ocaml_lvm_structure_decls ();
 
+  generate_ocaml_stat_structure_decls ();
+
   (* 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];
 
+  (* 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 =
@@ -3220,17 +3454,17 @@ and generate_ocaml_c () =
            pr "  char **r;\n";
            "NULL"
        | RIntBool _ ->
-           pr "  struct guestfs_int_bool *r;\n";
-           "NULL"
+           pr "  struct guestfs_int_bool *r;\n"; "NULL"
        | RPVList _ ->
-           pr "  struct guestfs_lvm_pv_list *r;\n";
-           "NULL"
+           pr "  struct guestfs_lvm_pv_list *r;\n"; "NULL"
        | RVGList _ ->
-           pr "  struct guestfs_lvm_vg_list *r;\n";
-           "NULL"
+           pr "  struct guestfs_lvm_vg_list *r;\n"; "NULL"
        | 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";
@@ -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";
+       | 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";
@@ -3310,6 +3550,18 @@ and generate_ocaml_lvm_structure_decls () =
       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;
@@ -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"
+   | RStat _ -> pr "stat"
+   | RStatVFS _ -> pr "statvfs"
   );
   if is_external then (
     pr " = ";
@@ -3442,7 +3696,8 @@ DESTROY (g)
        | 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. *)
@@ -3556,11 +3811,16 @@ DESTROY (g)
           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 ->
-          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 ->
-          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"
@@ -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
 
+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;
@@ -3734,6 +4012,8 @@ and generate_perl_prototype name style =
    | 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
@@ -3925,6 +4205,27 @@ py_guestfs_close (PyObject *self, PyObject *args)
       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, _, _, _, _, _) ->
@@ -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"
-       | 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
@@ -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"
+       | 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";