+ ("mkdir_p", (RErr, [String "path"]), 33, [],
+ [InitBasicFS, TestOutputTrue
+ [["mkdir_p"; "/new/foo/bar"];
+ ["is_dir"; "/new/foo/bar"]];
+ InitBasicFS, TestOutputTrue
+ [["mkdir_p"; "/new/foo/bar"];
+ ["is_dir"; "/new/foo"]];
+ InitBasicFS, TestOutputTrue
+ [["mkdir_p"; "/new/foo/bar"];
+ ["is_dir"; "/new"]]],
+ "create a directory and parents",
+ "\
+Create a directory named C<path>, creating any parent directories
+as necessary. This is like the C<mkdir -p> shell command.");
+
+ ("chmod", (RErr, [Int "mode"; String "path"]), 34, [],
+ [], (* XXX Need stat command to test *)
+ "change file mode",
+ "\
+Change the mode (permissions) of C<path> to C<mode>. Only
+numeric modes are supported.");
+
+ ("chown", (RErr, [Int "owner"; Int "group"; String "path"]), 35, [],
+ [], (* XXX Need stat command to test *)
+ "change file owner and group",
+ "\
+Change the file owner to C<owner> and group to C<group>.
+
+Only numeric uid and gid are supported. If you want to use
+names, you will need to locate and parse the password file
+yourself (Augeas support makes this relatively easy).");
+
+ ("exists", (RBool "existsflag", [String "path"]), 36, [],
+ [InitBasicFS, TestOutputTrue (
+ [["touch"; "/new"];
+ ["exists"; "/new"]]);
+ InitBasicFS, TestOutputTrue (
+ [["mkdir"; "/new"];
+ ["exists"; "/new"]])],
+ "test if file or directory exists",
+ "\
+This returns C<true> if and only if there is a file, directory
+(or anything) with the given C<path> name.
+
+See also C<guestfs_is_file>, C<guestfs_is_dir>, C<guestfs_stat>.");
+
+ ("is_file", (RBool "fileflag", [String "path"]), 37, [],
+ [InitBasicFS, TestOutputTrue (
+ [["touch"; "/new"];
+ ["is_file"; "/new"]]);
+ InitBasicFS, TestOutputFalse (
+ [["mkdir"; "/new"];
+ ["is_file"; "/new"]])],
+ "test if file exists",
+ "\
+This returns C<true> if and only if there is a file
+with the given C<path> name. Note that it returns false for
+other objects like directories.
+
+See also C<guestfs_stat>.");
+
+ ("is_dir", (RBool "dirflag", [String "path"]), 38, [],
+ [InitBasicFS, TestOutputFalse (
+ [["touch"; "/new"];
+ ["is_dir"; "/new"]]);
+ InitBasicFS, TestOutputTrue (
+ [["mkdir"; "/new"];
+ ["is_dir"; "/new"]])],
+ "test if file exists",
+ "\
+This returns C<true> if and only if there is a directory
+with the given C<path> name. Note that it returns false for
+other objects like files.
+
+See also C<guestfs_stat>.");
+
+ ("pvcreate", (RErr, [String "device"]), 39, [],
+ [InitEmpty, TestOutputList (
+ [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ",10 ,20 ,"];
+ ["pvcreate"; "/dev/sda1"];
+ ["pvcreate"; "/dev/sda2"];
+ ["pvcreate"; "/dev/sda3"];
+ ["pvs"]], ["/dev/sda1"; "/dev/sda2"; "/dev/sda3"])],
+ "create an LVM physical volume",
+ "\
+This creates an LVM physical volume on the named C<device>,
+where C<device> should usually be a partition name such
+as C</dev/sda1>.");
+
+ ("vgcreate", (RErr, [String "volgroup"; StringList "physvols"]), 40, [],
+ [InitEmpty, TestOutputList (
+ [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ",10 ,20 ,"];
+ ["pvcreate"; "/dev/sda1"];
+ ["pvcreate"; "/dev/sda2"];
+ ["pvcreate"; "/dev/sda3"];
+ ["vgcreate"; "VG1"; "/dev/sda1 /dev/sda2"];
+ ["vgcreate"; "VG2"; "/dev/sda3"];
+ ["vgs"]], ["VG1"; "VG2"])],
+ "create an LVM volume group",
+ "\
+This creates an LVM volume group called C<volgroup>
+from the non-empty list of physical volumes C<physvols>.");
+
+ ("lvcreate", (RErr, [String "logvol"; String "volgroup"; Int "mbytes"]), 41, [],
+ [InitEmpty, TestOutputList (
+ [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ",10 ,20 ,"];
+ ["pvcreate"; "/dev/sda1"];
+ ["pvcreate"; "/dev/sda2"];
+ ["pvcreate"; "/dev/sda3"];
+ ["vgcreate"; "VG1"; "/dev/sda1 /dev/sda2"];
+ ["vgcreate"; "VG2"; "/dev/sda3"];
+ ["lvcreate"; "LV1"; "VG1"; "50"];
+ ["lvcreate"; "LV2"; "VG1"; "50"];
+ ["lvcreate"; "LV3"; "VG2"; "50"];
+ ["lvcreate"; "LV4"; "VG2"; "50"];
+ ["lvcreate"; "LV5"; "VG2"; "50"];
+ ["lvs"]],
+ ["/dev/VG1/LV1"; "/dev/VG1/LV2";
+ "/dev/VG2/LV3"; "/dev/VG2/LV4"; "/dev/VG2/LV5"])],
+ "create an LVM volume group",
+ "\
+This creates an LVM volume group called C<logvol>
+on the volume group C<volgroup>, with C<size> megabytes.");
+
+ ("mkfs", (RErr, [String "fstype"; String "device"]), 42, [],
+ [InitEmpty, TestOutput (
+ [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ","];
+ ["mkfs"; "ext2"; "/dev/sda1"];
+ ["mount"; "/dev/sda1"; "/"];
+ ["write_file"; "/new"; "new file contents"; "0"];
+ ["cat"; "/new"]], "new file contents")],
+ "make a filesystem",
+ "\
+This creates a filesystem on C<device> (usually a partition
+of LVM logical volume). The filesystem type is C<fstype>, for
+example C<ext3>.");
+
+ ("sfdisk", (RErr, [String "device";
+ Int "cyls"; Int "heads"; Int "sectors";
+ StringList "lines"]), 43, [DangerWillRobinson],
+ [],
+ "create partitions on a block device",
+ "\
+This is a direct interface to the L<sfdisk(8)> program for creating
+partitions on block devices.
+
+C<device> should be a block device, for example C</dev/sda>.
+
+C<cyls>, C<heads> and C<sectors> are the number of cylinders, heads
+and sectors on the device, which are passed directly to sfdisk as
+the I<-C>, I<-H> and I<-S> parameters. If you pass C<0> for any
+of these, then the corresponding parameter is omitted. Usually for
+'large' disks, you can just pass C<0> for these, but for small
+(floppy-sized) disks, sfdisk (or rather, the kernel) cannot work
+out the right geometry and you will need to tell it.
+
+C<lines> is a list of lines that we feed to C<sfdisk>. For more
+information refer to the L<sfdisk(8)> manpage.
+
+To create a single partition occupying the whole disk, you would
+pass C<lines> as a single element list, when the single element being
+the string C<,> (comma).");
+
+ ("write_file", (RErr, [String "path"; String "content"; Int "size"]), 44, [ProtocolLimitWarning],
+ [InitBasicFS, TestOutput (
+ [["write_file"; "/new"; "new file contents"; "0"];
+ ["cat"; "/new"]], "new file contents");
+ InitBasicFS, TestOutput (
+ [["write_file"; "/new"; "\nnew file contents\n"; "0"];
+ ["cat"; "/new"]], "\nnew file contents\n");
+ InitBasicFS, TestOutput (
+ [["write_file"; "/new"; "\n\n"; "0"];
+ ["cat"; "/new"]], "\n\n");
+ InitBasicFS, TestOutput (
+ [["write_file"; "/new"; ""; "0"];
+ ["cat"; "/new"]], "");
+ InitBasicFS, TestOutput (
+ [["write_file"; "/new"; "\n\n\n"; "0"];
+ ["cat"; "/new"]], "\n\n\n");
+ InitBasicFS, TestOutput (
+ [["write_file"; "/new"; "\n"; "0"];
+ ["cat"; "/new"]], "\n")],
+ "create a file",
+ "\
+This call creates a file called C<path>. The contents of the
+file is the string C<content> (which can contain any 8 bit data),
+with length C<size>.
+
+As a special case, if C<size> is C<0>
+then the length is calculated using C<strlen> (so in this case
+the content cannot contain embedded ASCII NULs).");
+
+ ("umount", (RErr, [String "pathordevice"]), 45, [FishAlias "unmount"],
+ [InitEmpty, TestOutputList (
+ [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ","];
+ ["mkfs"; "ext2"; "/dev/sda1"];
+ ["mount"; "/dev/sda1"; "/"];
+ ["mounts"]], ["/dev/sda1"]);
+ InitEmpty, TestOutputList (
+ [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ","];
+ ["mkfs"; "ext2"; "/dev/sda1"];
+ ["mount"; "/dev/sda1"; "/"];
+ ["umount"; "/"];
+ ["mounts"]], [])],
+ "unmount a filesystem",
+ "\
+This unmounts the given filesystem. The filesystem may be
+specified either by its mountpoint (path) or the device which
+contains the filesystem.");
+
+ ("mounts", (RStringList "devices", []), 46, [],
+ [InitBasicFS, TestOutputList (
+ [["mounts"]], ["/dev/sda1"])],
+ "show mounted filesystems",
+ "\
+This returns the list of currently mounted filesystems. It returns
+the list of devices (eg. C</dev/sda1>, C</dev/VG/LV>).
+
+Some internal mounts are not shown.");
+
+ ("umount_all", (RErr, []), 47, [FishAlias "unmount-all"],
+ [InitBasicFS, TestOutputList (
+ [["umount_all"];
+ ["mounts"]], [])],
+ "unmount all filesystems",
+ "\
+This unmounts all mounted filesystems.
+
+Some internal mounts are not unmounted by this call.");
+
+ ("lvm_remove_all", (RErr, []), 48, [DangerWillRobinson],
+ [],
+ "remove all LVM LVs, VGs and PVs",
+ "\
+This command removes all LVM logical volumes, volume groups
+and physical volumes.");
+
+ ("file", (RString "description", [String "path"]), 49, [],
+ [InitBasicFS, TestOutput (
+ [["touch"; "/new"];
+ ["file"; "/new"]], "empty");
+ InitBasicFS, TestOutput (
+ [["write_file"; "/new"; "some content\n"; "0"];
+ ["file"; "/new"]], "ASCII text");
+ InitBasicFS, TestLastFail (
+ [["file"; "/nofile"]])],
+ "determine file type",
+ "\
+This call uses the standard L<file(1)> command to determine
+the type or contents of the file. This also works on devices,
+for example to find out whether a partition contains a filesystem.
+
+The exact command which runs is C<file -bsL path>. Note in
+particular that the filename is not prepended to the output
+(the C<-b> option).");
+
+ ("command", (RString "output", [StringList "arguments"]), 50, [],
+ [], (* XXX how to test? *)
+ "run a command from the guest filesystem",
+ "\
+This call runs a command from the guest filesystem. The
+filesystem must be mounted, and must contain a compatible
+operating system (ie. something Linux, with the same
+or compatible processor architecture).
+
+The single parameter is an argv-style list of arguments.
+The first element is the name of the program to run.
+Subsequent elements are parameters. The list must be
+non-empty (ie. must contain a program name).
+
+The C<$PATH> environment variable will contain at least
+C</usr/bin> and C</bin>. If you require a program from
+another location, you should provide the full path in the
+first parameter.
+
+Shared libraries and data files required by the program
+must be available on filesystems which are mounted in the
+correct places. It is the caller's responsibility to ensure
+all filesystems that are needed are mounted at the right
+locations.");
+
+ ("command_lines", (RStringList "lines", [StringList "arguments"]), 51, [],
+ [], (* XXX how to test? *)
+ "run a command, returning 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.");
+
+ ("tune2fs_l", (RHashtable "superblock", [String "device"]), 55, [],
+ [], (* XXX test *)
+ "get ext2/ext3 superblock details",
+ "\
+This returns the contents of the ext2 or ext3 filesystem superblock
+on C<device>.
+
+It is the same as running C<tune2fs -l device>. See L<tune2fs(8)>
+manpage for more details. The list of fields returned isn't
+clearly defined, and depends on both the version of C<tune2fs>
+that libguestfs was built against, and the filesystem itself.");
+
+ ("blockdev_setro", (RErr, [String "device"]), 56, [],
+ [InitEmpty, TestOutputTrue (
+ [["blockdev_setro"; "/dev/sda"];
+ ["blockdev_getro"; "/dev/sda"]])],
+ "set block device to read-only",
+ "\
+Sets the block device named C<device> to read-only.
+
+This uses the L<blockdev(8)> command.");
+
+ ("blockdev_setrw", (RErr, [String "device"]), 57, [],
+ [InitEmpty, TestOutputFalse (
+ [["blockdev_setrw"; "/dev/sda"];
+ ["blockdev_getro"; "/dev/sda"]])],
+ "set block device to read-write",
+ "\
+Sets the block device named C<device> to read-write.
+
+This uses the L<blockdev(8)> command.");
+
+ ("blockdev_getro", (RBool "ro", [String "device"]), 58, [],
+ [InitEmpty, TestOutputTrue (
+ [["blockdev_setro"; "/dev/sda"];
+ ["blockdev_getro"; "/dev/sda"]])],
+ "is block device set to read-only",
+ "\
+Returns a boolean indicating if the block device is read-only
+(true if read-only, false if not).
+
+This uses the L<blockdev(8)> command.");
+
+ ("blockdev_getss", (RInt "sectorsize", [String "device"]), 59, [],
+ [InitEmpty, TestOutputInt (
+ [["blockdev_getss"; "/dev/sda"]], 512)],
+ "get sectorsize of block device",
+ "\
+This returns the size of sectors on a block device.
+Usually 512, but can be larger for modern devices.
+
+(Note, this is not the size in sectors, use C<guestfs_blockdev_getsz>
+for that).
+
+This uses the L<blockdev(8)> command.");
+
+ ("blockdev_getbsz", (RInt "blocksize", [String "device"]), 60, [],
+ [InitEmpty, TestOutputInt (
+ [["blockdev_getbsz"; "/dev/sda"]], 4096)],
+ "get blocksize of block device",
+ "\
+This returns the block size of a device.
+
+(Note this is different from both I<size in blocks> and
+I<filesystem block size>).
+
+This uses the L<blockdev(8)> command.");
+
+ ("blockdev_setbsz", (RErr, [String "device"; Int "blocksize"]), 61, [],
+ [], (* XXX test *)
+ "set blocksize of block device",
+ "\
+This sets the block size of a device.
+
+(Note this is different from both I<size in blocks> and
+I<filesystem block size>).
+
+This uses the L<blockdev(8)> command.");
+
+ ("blockdev_getsz", (RInt64 "sizeinsectors", [String "device"]), 62, [],
+ [InitEmpty, TestOutputInt (
+ [["blockdev_getsz"; "/dev/sda"]], 1024000)],
+ "get total size of device in 512-byte sectors",
+ "\
+This returns the size of the device in units of 512-byte sectors
+(even if the sectorsize isn't 512 bytes ... weird).
+
+See also C<guestfs_blockdev_getss> for the real sector size of
+the device, and C<guestfs_blockdev_getsize64> for the more
+useful I<size in bytes>.
+
+This uses the L<blockdev(8)> command.");
+
+ ("blockdev_getsize64", (RInt64 "sizeinbytes", [String "device"]), 63, [],
+ [InitEmpty, TestOutputInt (
+ [["blockdev_getsize64"; "/dev/sda"]], 524288000)],
+ "get total size of device in bytes",
+ "\
+This returns the size of the device in bytes.
+
+See also C<guestfs_blockdev_getsz>.
+
+This uses the L<blockdev(8)> command.");
+
+ ("blockdev_flushbufs", (RErr, [String "device"]), 64, [],
+ [InitEmpty, TestRun
+ [["blockdev_flushbufs"; "/dev/sda"]]],
+ "flush device buffers",
+ "\
+This tells the kernel to flush internal buffers associated
+with C<device>.
+
+This uses the L<blockdev(8)> command.");
+
+ ("blockdev_rereadpt", (RErr, [String "device"]), 65, [],
+ [InitEmpty, TestRun
+ [["blockdev_rereadpt"; "/dev/sda"]]],
+ "reread partition table",
+ "\
+Reread the partition table on C<device>.
+
+This uses the L<blockdev(8)> command.");
+
+ ("upload", (RErr, [FileIn "filename"; String "remotefilename"]), 66, [],
+ [],
+ "upload a file from the local machine",
+ "\
+Upload local file C<filename> to C<remotefilename> on the
+filesystem.
+
+C<filename> can also be a named pipe.
+
+See also C<guestfs_download>.");
+
+ ("download", (RErr, [String "remotefilename"; FileOut "filename"]), 67, [],
+ [],
+ "download a file to the local machine",
+ "\
+Download file C<remotefilename> and save it as C<filename>
+on the local machine.
+
+C<filename> can also be a named pipe.
+
+See also C<guestfs_upload>, C<guestfs_cat>.");
+
+]
+
+let all_functions = non_daemon_functions @ daemon_functions
+
+(* In some places we want the functions to be displayed sorted
+ * alphabetically, so this is useful:
+ *)
+let all_functions_sorted =
+ List.sort (fun (n1,_,_,_,_,_,_) (n2,_,_,_,_,_,_) ->
+ compare n1 n2) all_functions
+
+(* Column names and types from LVM PVs/VGs/LVs. *)
+let pv_cols = [
+ "pv_name", `String;
+ "pv_uuid", `UUID;
+ "pv_fmt", `String;
+ "pv_size", `Bytes;
+ "dev_size", `Bytes;
+ "pv_free", `Bytes;
+ "pv_used", `Bytes;
+ "pv_attr", `String (* XXX *);
+ "pv_pe_count", `Int;
+ "pv_pe_alloc_count", `Int;
+ "pv_tags", `String;
+ "pe_start", `Bytes;
+ "pv_mda_count", `Int;
+ "pv_mda_free", `Bytes;
+(* Not in Fedora 10:
+ "pv_mda_size", `Bytes;
+*)
+]
+let vg_cols = [
+ "vg_name", `String;
+ "vg_uuid", `UUID;
+ "vg_fmt", `String;
+ "vg_attr", `String (* XXX *);
+ "vg_size", `Bytes;
+ "vg_free", `Bytes;
+ "vg_sysid", `String;
+ "vg_extent_size", `Bytes;
+ "vg_extent_count", `Int;
+ "vg_free_count", `Int;
+ "max_lv", `Int;
+ "max_pv", `Int;
+ "pv_count", `Int;
+ "lv_count", `Int;
+ "snap_count", `Int;
+ "vg_seqno", `Int;
+ "vg_tags", `String;
+ "vg_mda_count", `Int;
+ "vg_mda_free", `Bytes;
+(* Not in Fedora 10:
+ "vg_mda_size", `Bytes;
+*)
+]
+let lv_cols = [
+ "lv_name", `String;
+ "lv_uuid", `UUID;
+ "lv_attr", `String (* XXX *);
+ "lv_major", `Int;
+ "lv_minor", `Int;
+ "lv_kernel_major", `Int;
+ "lv_kernel_minor", `Int;
+ "lv_size", `Bytes;
+ "seg_count", `Int;
+ "origin", `String;
+ "snap_percent", `OptPercent;
+ "copy_percent", `OptPercent;
+ "move_pv", `String;
+ "lv_tags", `String;
+ "mirror_log", `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.
+ *)
+let failwithf fs = ksprintf failwith fs
+
+let replace_char s c1 c2 =
+ let s2 = String.copy s in
+ let r = ref false in
+ for i = 0 to String.length s2 - 1 do
+ if String.unsafe_get s2 i = c1 then (
+ String.unsafe_set s2 i c2;
+ r := true
+ )
+ done;
+ if not !r then s else s2
+
+let isspace c =
+ c = ' '
+ (* || c = '\f' *) || c = '\n' || c = '\r' || c = '\t' (* || c = '\v' *)
+
+let triml ?(test = isspace) str =
+ let i = ref 0 in
+ let n = ref (String.length str) in
+ while !n > 0 && test str.[!i]; do
+ decr n;
+ incr i
+ done;
+ if !i = 0 then str
+ else String.sub str !i !n
+
+let trimr ?(test = isspace) str =
+ let n = ref (String.length str) in
+ while !n > 0 && test str.[!n-1]; do
+ decr n
+ done;
+ if !n = String.length str then str
+ else String.sub str 0 !n
+
+let trim ?(test = isspace) str =
+ trimr ~test (triml ~test str)
+
+let rec find s sub =
+ let len = String.length s in
+ let sublen = String.length sub in
+ let rec loop i =
+ if i <= len-sublen then (
+ let rec loop2 j =
+ if j < sublen then (
+ if s.[i+j] = sub.[j] then loop2 (j+1)
+ else -1
+ ) else
+ i (* found *)
+ in
+ let r = loop2 0 in
+ if r = -1 then loop (i+1) else r
+ ) else
+ -1 (* not found *)
+ in
+ loop 0
+
+let rec replace_str s s1 s2 =
+ let len = String.length s in
+ let sublen = String.length s1 in
+ let i = find s s1 in
+ if i = -1 then s
+ else (
+ let s' = String.sub s 0 i in
+ let s'' = String.sub s (i+sublen) (len-i-sublen) in
+ s' ^ s2 ^ replace_str s'' s1 s2
+ )
+
+let rec string_split sep str =
+ let len = String.length str in
+ let seplen = String.length sep in
+ let i = find str sep in
+ if i = -1 then [str]
+ else (
+ let s' = String.sub str 0 i in
+ let s'' = String.sub str (i+seplen) (len-i-seplen) in
+ s' :: string_split sep s''
+ )
+
+let rec find_map f = function
+ | [] -> raise Not_found
+ | x :: xs ->
+ match f x with
+ | Some y -> y
+ | None -> find_map f xs
+
+let iteri f xs =
+ let rec loop i = function
+ | [] -> ()
+ | x :: xs -> f i x; loop (i+1) xs
+ in
+ loop 0 xs
+
+let mapi f xs =
+ let rec loop i = function
+ | [] -> []
+ | x :: xs -> let r = f i x in r :: loop (i+1) xs
+ in
+ loop 0 xs
+
+let name_of_argt = function
+ | String n | OptString n | StringList n | Bool n | Int n
+ | FileIn n | FileOut n -> n
+
+let seq_of_test = function
+ | TestRun s | TestOutput (s, _) | TestOutputList (s, _)
+ | TestOutputInt (s, _) | TestOutputTrue s | TestOutputFalse s
+ | TestOutputLength (s, _) | TestOutputStruct (s, _)
+ | TestLastFail s -> s
+
+(* Check function names etc. for consistency. *)
+let check_functions () =
+ let contains_uppercase str =
+ let len = String.length str in
+ let rec loop i =
+ if i >= len then false
+ else (
+ let c = str.[i] in
+ if c >= 'A' && c <= 'Z' then true
+ else loop (i+1)
+ )
+ in
+ loop 0
+ in
+
+ (* Check function names. *)
+ List.iter (
+ fun (name, _, _, _, _, _, _) ->
+ if String.length name >= 7 && String.sub name 0 7 = "guestfs" then
+ failwithf "function name %s does not need 'guestfs' prefix" name;
+ if contains_uppercase name then
+ failwithf "function name %s should not contain uppercase chars" name;
+ if String.contains name '-' then
+ failwithf "function name %s should not contain '-', use '_' instead."
+ name
+ ) all_functions;
+
+ (* Check function parameter/return names. *)
+ List.iter (
+ fun (name, style, _, _, _, _, _) ->
+ let check_arg_ret_name n =
+ if contains_uppercase n then
+ failwithf "%s param/ret %s should not contain uppercase chars"
+ name n;
+ if String.contains n '-' || String.contains n '_' then
+ failwithf "%s param/ret %s should not contain '-' or '_'"
+ name n;
+ if n = "value" then
+ failwithf "%s has a param/ret called 'value', which causes conflicts in the OCaml bindings, use something like 'val' or a more descriptive name" n;
+ if n = "argv" || n = "args" then
+ failwithf "%s has a param/ret called 'argv' or 'args', which will cause some conflicts in the generated code" n
+ in
+
+ (match fst style with
+ | RErr -> ()
+ | RInt n | RInt64 n | RBool n | RConstString n | RString n
+ | RStringList n | RPVList n | RVGList n | RLVList n
+ | RStat n | RStatVFS n
+ | RHashtable n ->
+ check_arg_ret_name n
+ | RIntBool (n,m) ->
+ check_arg_ret_name n;
+ check_arg_ret_name m
+ );
+ List.iter (fun arg -> check_arg_ret_name (name_of_argt arg)) (snd style)
+ ) all_functions;
+
+ (* Check short descriptions. *)
+ List.iter (
+ fun (name, _, _, _, _, shortdesc, _) ->
+ if shortdesc.[0] <> Char.lowercase shortdesc.[0] then
+ failwithf "short description of %s should begin with lowercase." name;
+ let c = shortdesc.[String.length shortdesc-1] in
+ if c = '\n' || c = '.' then
+ failwithf "short description of %s should not end with . or \\n." name
+ ) all_functions;
+
+ (* Check long dscriptions. *)
+ List.iter (
+ fun (name, _, _, _, _, _, longdesc) ->
+ if longdesc.[String.length longdesc-1] = '\n' then
+ failwithf "long description of %s should not end with \\n." name
+ ) all_functions;
+
+ (* Check proc_nrs. *)
+ List.iter (
+ fun (name, _, proc_nr, _, _, _, _) ->
+ if proc_nr <= 0 then
+ failwithf "daemon function %s should have proc_nr > 0" name
+ ) daemon_functions;
+
+ List.iter (
+ fun (name, _, proc_nr, _, _, _, _) ->
+ if proc_nr <> -1 then
+ failwithf "non-daemon function %s should have proc_nr -1" name
+ ) non_daemon_functions;
+
+ let proc_nrs =
+ List.map (fun (name, _, proc_nr, _, _, _, _) -> name, proc_nr)
+ daemon_functions in
+ let proc_nrs =
+ List.sort (fun (_,nr1) (_,nr2) -> compare nr1 nr2) proc_nrs in
+ let rec loop = function
+ | [] -> ()
+ | [_] -> ()
+ | (name1,nr1) :: ((name2,nr2) :: _ as rest) when nr1 < nr2 ->
+ loop rest
+ | (name1,nr1) :: (name2,nr2) :: _ ->
+ failwithf "%s and %s have conflicting procedure numbers (%d, %d)"
+ name1 name2 nr1 nr2
+ in
+ loop proc_nrs;
+
+ (* Check tests. *)
+ List.iter (
+ function
+ (* Ignore functions that have no tests. We generate a
+ * warning when the user does 'make check' instead.
+ *)
+ | name, _, _, _, [], _, _ -> ()
+ | name, _, _, _, tests, _, _ ->
+ let funcs =
+ List.map (
+ fun (_, test) ->
+ match seq_of_test test with
+ | [] ->
+ failwithf "%s has a test containing an empty sequence" name
+ | cmds -> List.map List.hd cmds
+ ) tests in
+ let funcs = List.flatten funcs in
+
+ let tested = List.mem name funcs in
+
+ if not tested then
+ failwithf "function %s has tests but does not test itself" name
+ ) all_functions
+
+(* 'pr' prints to the current output file. *)
+let chan = ref stdout
+let pr fs = ksprintf (output_string !chan) fs
+
+(* Generate a header block in a number of standard styles. *)
+type comment_style = CStyle | HashStyle | OCamlStyle
+type license = GPLv2 | LGPLv2
+
+let generate_header comment license =
+ let c = match comment with
+ | CStyle -> pr "/* "; " *"
+ | HashStyle -> pr "# "; "#"
+ | OCamlStyle -> pr "(* "; " *" in
+ pr "libguestfs generated file\n";
+ pr "%s WARNING: THIS FILE IS GENERATED BY 'src/generator.ml'.\n" c;
+ pr "%s ANY CHANGES YOU MAKE TO THIS FILE WILL BE LOST.\n" c;
+ pr "%s\n" c;
+ pr "%s Copyright (C) 2009 Red Hat Inc.\n" c;
+ pr "%s\n" c;
+ (match license with
+ | GPLv2 ->
+ pr "%s This program is free software; you can redistribute it and/or modify\n" c;
+ pr "%s it under the terms of the GNU General Public License as published by\n" c;
+ pr "%s the Free Software Foundation; either version 2 of the License, or\n" c;
+ pr "%s (at your option) any later version.\n" c;
+ pr "%s\n" c;
+ pr "%s This program is distributed in the hope that it will be useful,\n" c;
+ pr "%s but WITHOUT ANY WARRANTY; without even the implied warranty of\n" c;
+ pr "%s MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" c;
+ pr "%s GNU General Public License for more details.\n" c;
+ pr "%s\n" c;
+ pr "%s You should have received a copy of the GNU General Public License along\n" c;
+ pr "%s with this program; if not, write to the Free Software Foundation, Inc.,\n" c;
+ pr "%s 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n" c;
+
+ | LGPLv2 ->
+ pr "%s This library is free software; you can redistribute it and/or\n" c;
+ pr "%s modify it under the terms of the GNU Lesser General Public\n" c;
+ pr "%s License as published by the Free Software Foundation; either\n" c;
+ pr "%s version 2 of the License, or (at your option) any later version.\n" c;
+ pr "%s\n" c;
+ pr "%s This library is distributed in the hope that it will be useful,\n" c;
+ pr "%s but WITHOUT ANY WARRANTY; without even the implied warranty of\n" c;
+ pr "%s MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n" c;
+ pr "%s Lesser General Public License for more details.\n" c;
+ pr "%s\n" c;
+ pr "%s You should have received a copy of the GNU Lesser General Public\n" c;
+ pr "%s License along with this library; if not, write to the Free Software\n" c;
+ pr "%s Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n" c;
+ );
+ (match comment with
+ | CStyle -> pr " */\n"
+ | HashStyle -> ()
+ | OCamlStyle -> pr " *)\n"
+ );
+ pr "\n"
+
+(* Start of main code generation functions below this line. *)
+
+(* Generate the pod documentation for the C API. *)
+let rec generate_actions_pod () =
+ List.iter (
+ fun (shortname, style, _, flags, _, _, longdesc) ->
+ let name = "guestfs_" ^ shortname in
+ pr "=head2 %s\n\n" name;
+ pr " ";
+ generate_prototype ~extern:false ~handle:"handle" name style;
+ pr "\n\n";
+ pr "%s\n\n" longdesc;
+ (match fst style with
+ | RErr ->
+ pr "This function returns 0 on success or -1 on error.\n\n"
+ | RInt _ ->
+ pr "On error this function returns -1.\n\n"
+ | RInt64 _ ->
+ pr "On error this function returns -1.\n\n"
+ | RBool _ ->
+ pr "This function returns a C truth value on success or -1 on error.\n\n"
+ | RConstString _ ->
+ pr "This function returns a string, or NULL on error.
+The string is owned by the guest handle and must I<not> be freed.\n\n"
+ | RString _ ->
+ pr "This function returns a string, or NULL on error.
+I<The caller must free the returned string after use>.\n\n"
+ | RStringList _ ->
+ pr "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>.\n\n"
+ | RIntBool _ ->
+ 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 *>
+(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 *>
+(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 *>
+(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"
+ | RHashtable _ ->
+ pr "This function returns a NULL-terminated array of
+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"
+ );
+ if List.mem ProtocolLimitWarning flags then
+ pr "%s\n\n" protocol_limit_warning;
+ if List.mem DangerWillRobinson flags then
+ pr "%s\n\n" danger_will_robinson;
+ ) all_functions_sorted
+
+and generate_structs_pod () =
+ (* LVM structs documentation. *)
+ List.iter (
+ fun (typ, cols) ->
+ pr "=head2 guestfs_lvm_%s\n" typ;
+ pr "\n";
+ pr " struct guestfs_lvm_%s {\n" typ;
+ List.iter (
+ function
+ | name, `String -> pr " char *%s;\n" name
+ | name, `UUID ->
+ pr " /* The next field is NOT nul-terminated, be careful when printing it: */\n";
+ pr " char %s[32];\n" name
+ | name, `Bytes -> pr " uint64_t %s;\n" name
+ | name, `Int -> pr " int64_t %s;\n" name
+ | name, `OptPercent ->
+ pr " /* The next field is [0..100] or -1 meaning 'not present': */\n";
+ pr " float %s;\n" name
+ ) cols;
+ pr " \n";
+ pr " struct guestfs_lvm_%s_list {\n" typ;
+ pr " uint32_t len; /* Number of elements in list. */\n";
+ pr " struct guestfs_lvm_%s *val; /* Elements. */\n" typ;
+ pr " };\n";
+ pr " \n";
+ 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]
+
+(* Generate the protocol (XDR) file, 'guestfs_protocol.x' and
+ * indirectly 'guestfs_protocol.h' and 'guestfs_protocol.c'.
+ *
+ * We have to use an underscore instead of a dash because otherwise
+ * rpcgen generates incorrect code.
+ *
+ * This header is NOT exported to clients, but see also generate_structs_h.
+ *)
+and generate_xdr () =
+ generate_header CStyle LGPLv2;
+
+ (* This has to be defined to get around a limitation in Sun's rpcgen. *)
+ pr "typedef string str<>;\n";
+ pr "\n";
+
+ (* LVM internal structures. *)
+ List.iter (
+ function
+ | typ, cols ->
+ pr "struct guestfs_lvm_int_%s {\n" typ;
+ List.iter (function
+ | name, `String -> pr " string %s<>;\n" name
+ | name, `UUID -> pr " opaque %s[32];\n" name
+ | name, `Bytes -> pr " hyper %s;\n" name
+ | name, `Int -> pr " hyper %s;\n" name
+ | name, `OptPercent -> pr " float %s;\n" name
+ ) cols;
+ pr "};\n";
+ pr "\n";
+ pr "typedef struct guestfs_lvm_int_%s guestfs_lvm_int_%s_list<>;\n" typ typ;
+ 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
+
+ (match snd style with
+ | [] -> ()
+ | args ->
+ pr "struct %s_args {\n" name;
+ List.iter (
+ function
+ | String n -> pr " string %s<>;\n" n
+ | OptString n -> pr " str *%s;\n" n
+ | StringList n -> pr " str %s<>;\n" n
+ | Bool n -> pr " bool %s;\n" n
+ | Int n -> pr " int %s;\n" n
+ | FileIn _ | FileOut _ -> ()
+ ) args;
+ pr "};\n\n"
+ );
+ (match fst style with
+ | RErr -> ()
+ | RInt n ->
+ pr "struct %s_ret {\n" name;
+ pr " int %s;\n" n;
+ pr "};\n\n"
+ | RInt64 n ->
+ pr "struct %s_ret {\n" name;
+ pr " hyper %s;\n" n;
+ pr "};\n\n"
+ | RBool n ->
+ pr "struct %s_ret {\n" name;
+ pr " bool %s;\n" n;
+ pr "};\n\n"
+ | RConstString _ ->
+ failwithf "RConstString cannot be returned from a daemon function"
+ | RString n ->
+ pr "struct %s_ret {\n" name;
+ pr " string %s<>;\n" n;
+ pr "};\n\n"
+ | RStringList n ->
+ pr "struct %s_ret {\n" name;
+ pr " str %s<>;\n" n;
+ pr "};\n\n"
+ | RIntBool (n,m) ->
+ pr "struct %s_ret {\n" name;
+ pr " int %s;\n" n;
+ pr " bool %s;\n" m;
+ pr "};\n\n"
+ | RPVList n ->
+ pr "struct %s_ret {\n" name;
+ pr " guestfs_lvm_int_pv_list %s;\n" n;
+ pr "};\n\n"
+ | RVGList n ->
+ pr "struct %s_ret {\n" name;
+ pr " guestfs_lvm_int_vg_list %s;\n" n;
+ pr "};\n\n"
+ | RLVList 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"
+ | RHashtable n ->
+ pr "struct %s_ret {\n" name;
+ pr " str %s<>;\n" n;
+ pr "};\n\n"
+ );
+ ) daemon_functions;
+
+ (* Table of procedure numbers. *)
+ pr "enum guestfs_procedure {\n";
+ List.iter (
+ fun (shortname, _, proc_nr, _, _, _, _) ->
+ pr " GUESTFS_PROC_%s = %d,\n" (String.uppercase shortname) proc_nr
+ ) daemon_functions;
+ pr " GUESTFS_PROC_NR_PROCS\n";
+ pr "};\n";
+ pr "\n";
+
+ (* Having to choose a maximum message size is annoying for several
+ * reasons (it limits what we can do in the API), but it (a) makes
+ * the protocol a lot simpler, and (b) provides a bound on the size
+ * of the daemon which operates in limited memory space. For large
+ * file transfers you should use FTP.
+ *)
+ pr "const GUESTFS_MESSAGE_MAX = %d;\n" (4 * 1024 * 1024);
+ pr "\n";
+
+ (* Message header, etc. *)
+ pr "\
+/* The communication protocol is now documented in the guestfs(3)
+ * manpage.
+ */
+
+const GUESTFS_PROGRAM = 0x2000F5F5;
+const GUESTFS_PROTOCOL_VERSION = 1;
+
+/* These constants must be larger than any possible message length. */
+const GUESTFS_LAUNCH_FLAG = 0xf5f55f5f;
+const GUESTFS_CANCEL_FLAG = 0xffffeeee;
+
+enum guestfs_message_direction {
+ GUESTFS_DIRECTION_CALL = 0, /* client -> daemon */
+ GUESTFS_DIRECTION_REPLY = 1 /* daemon -> client */
+};
+
+enum guestfs_message_status {
+ GUESTFS_STATUS_OK = 0,
+ GUESTFS_STATUS_ERROR = 1
+};
+
+const GUESTFS_ERROR_LEN = 256;
+
+struct guestfs_message_error {
+ string error_message<GUESTFS_ERROR_LEN>;
+};
+
+struct guestfs_message_header {
+ unsigned prog; /* GUESTFS_PROGRAM */
+ unsigned vers; /* GUESTFS_PROTOCOL_VERSION */
+ guestfs_procedure proc; /* GUESTFS_PROC_x */
+ guestfs_message_direction direction;
+ unsigned serial; /* message serial number */
+ guestfs_message_status status;
+};
+
+const GUESTFS_MAX_CHUNK_SIZE = 8192;
+
+struct guestfs_chunk {
+ int cancel; /* if non-zero, transfer is cancelled */
+ /* data size is 0 bytes if the transfer has finished successfully */
+ opaque data<GUESTFS_MAX_CHUNK_SIZE>;
+};
+"
+
+(* Generate the guestfs-structs.h file. *)
+and generate_structs_h () =
+ generate_header CStyle LGPLv2;
+
+ (* This is a public exported header file containing various
+ * structures. The structures are carefully written to have
+ * exactly the same in-memory format as the XDR structures that
+ * we use on the wire to the daemon. The reason for creating
+ * copies of these structures here is just so we don't have to
+ * export the whole of guestfs_protocol.h (which includes much
+ * unrelated and XDR-dependent stuff that we don't want to be
+ * public, or required by clients).
+ *
+ * To reiterate, we will pass these structures to and from the
+ * client with a simple assignment or memcpy, so the format
+ * must be identical to what rpcgen / the RFC defines.
+ *)
+
+ (* guestfs_int_bool structure. *)
+ pr "struct guestfs_int_bool {\n";
+ pr " int32_t i;\n";
+ pr " int32_t b;\n";
+ pr "};\n";
+ pr "\n";
+
+ (* LVM public structures. *)
+ List.iter (
+ function
+ | typ, cols ->
+ pr "struct guestfs_lvm_%s {\n" typ;
+ List.iter (
+ function
+ | name, `String -> pr " char *%s;\n" name
+ | name, `UUID -> pr " char %s[32]; /* this is NOT nul-terminated, be careful when printing */\n" name
+ | name, `Bytes -> pr " uint64_t %s;\n" name
+ | name, `Int -> pr " int64_t %s;\n" name
+ | name, `OptPercent -> pr " float %s; /* [0..100] or -1 */\n" name
+ ) cols;
+ pr "};\n";
+ pr "\n";
+ pr "struct guestfs_lvm_%s_list {\n" typ;
+ pr " uint32_t len;\n";
+ pr " struct guestfs_lvm_%s *val;\n" typ;
+ pr "};\n";
+ pr "\n"
+ ) ["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_header CStyle LGPLv2;
+ List.iter (
+ fun (shortname, style, _, _, _, _, _) ->
+ let name = "guestfs_" ^ shortname in
+ generate_prototype ~single_line:true ~newline:true ~handle:"handle"
+ name style
+ ) all_functions
+
+(* Generate the client-side dispatch stubs. *)
+and generate_client_actions () =
+ generate_header CStyle LGPLv2;
+
+ pr "\
+#include <stdio.h>
+#include <stdlib.h>
+
+#include \"guestfs.h\"
+#include \"guestfs_protocol.h\"
+
+#define error guestfs_error
+#define perrorf guestfs_perrorf
+#define safe_malloc guestfs_safe_malloc
+#define safe_realloc guestfs_safe_realloc
+#define safe_strdup guestfs_safe_strdup
+#define safe_memdup guestfs_safe_memdup
+
+/* Check the return message from a call for validity. */
+static int
+check_reply_header (guestfs_h *g,
+ const struct guestfs_message_header *hdr,
+ int proc_nr, int serial)
+{
+ if (hdr->prog != GUESTFS_PROGRAM) {
+ error (g, \"wrong program (%%d/%%d)\", hdr->prog, GUESTFS_PROGRAM);
+ return -1;
+ }
+ if (hdr->vers != GUESTFS_PROTOCOL_VERSION) {
+ error (g, \"wrong protocol version (%%d/%%d)\",
+ hdr->vers, GUESTFS_PROTOCOL_VERSION);
+ return -1;
+ }
+ if (hdr->direction != GUESTFS_DIRECTION_REPLY) {
+ error (g, \"unexpected message direction (%%d/%%d)\",
+ hdr->direction, GUESTFS_DIRECTION_REPLY);
+ return -1;
+ }
+ if (hdr->proc != proc_nr) {
+ error (g, \"unexpected procedure number (%%d/%%d)\", hdr->proc, proc_nr);
+ return -1;
+ }
+ if (hdr->serial != serial) {
+ error (g, \"unexpected serial (%%d/%%d)\", hdr->serial, serial);
+ return -1;
+ }
+
+ return 0;
+}
+
+/* Check we are in the right state to run a high-level action. */
+static int
+check_state (guestfs_h *g, const char *caller)
+{
+ if (!guestfs_is_ready (g)) {
+ if (guestfs_is_config (g))
+ error (g, \"%%s: call launch() before using this function\",
+ caller);
+ else if (guestfs_is_launching (g))
+ error (g, \"%%s: call wait_ready() before using this function\",
+ caller);
+ else
+ error (g, \"%%s called from the wrong state, %%d != READY\",
+ caller, guestfs_get_state (g));
+ return -1;
+ }
+ return 0;
+}
+
+";
+
+ (* Client-side stubs for each function. *)
+ List.iter (
+ fun (shortname, style, _, _, _, _, _) ->
+ let name = "guestfs_" ^ shortname in
+
+ (* Generate the context struct which stores the high-level
+ * state between callback functions.
+ *)
+ pr "struct %s_ctx {\n" shortname;
+ pr " /* This flag is set by the callbacks, so we know we've done\n";
+ pr " * the callbacks as expected, and in the right sequence.\n";
+ pr " * 0 = not called, 1 = send called,\n";
+ pr " * 1001 = reply called.\n";
+ pr " */\n";
+ pr " int cb_sequence;\n";
+ pr " struct guestfs_message_header hdr;\n";
+ pr " struct guestfs_message_error err;\n";
+ (match fst style with
+ | RErr -> ()
+ | RConstString _ ->
+ failwithf "RConstString cannot be returned from a daemon function"
+ | RInt _ | RInt64 _
+ | RBool _ | RString _ | RStringList _
+ | RIntBool _
+ | RPVList _ | RVGList _ | RLVList _
+ | RStat _ | RStatVFS _
+ | RHashtable _ ->
+ pr " struct %s_ret ret;\n" name
+ );
+ pr "};\n";
+ pr "\n";
+
+ (* Generate the reply callback function. *)
+ pr "static void %s_reply_cb (guestfs_h *g, void *data, XDR *xdr)\n" shortname;
+ pr "{\n";
+ pr " guestfs_main_loop *ml = guestfs_get_main_loop (g);\n";
+ pr " struct %s_ctx *ctx = (struct %s_ctx *) data;\n" shortname shortname;
+ pr "\n";
+ pr " ml->main_loop_quit (ml, g);\n";
+ pr "\n";
+ pr " if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {\n";
+ pr " error (g, \"%%s: failed to parse reply header\", \"%s\");\n" name;
+ pr " return;\n";
+ pr " }\n";
+ pr " if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {\n";
+ pr " if (!xdr_guestfs_message_error (xdr, &ctx->err)) {\n";
+ pr " error (g, \"%%s: failed to parse reply error\", \"%s\");\n"
+ name;
+ pr " return;\n";
+ pr " }\n";
+ pr " goto done;\n";
+ pr " }\n";
+
+ (match fst style with
+ | RErr -> ()
+ | RConstString _ ->
+ failwithf "RConstString cannot be returned from a daemon function"
+ | RInt _ | RInt64 _
+ | RBool _ | RString _ | RStringList _
+ | RIntBool _
+ | RPVList _ | RVGList _ | RLVList _
+ | RStat _ | RStatVFS _
+ | RHashtable _ ->
+ pr " if (!xdr_%s_ret (xdr, &ctx->ret)) {\n" name;
+ pr " error (g, \"%%s: failed to parse reply\", \"%s\");\n" name;
+ pr " return;\n";
+ pr " }\n";
+ );
+
+ pr " done:\n";
+ pr " ctx->cb_sequence = 1001;\n";
+ pr "}\n\n";
+
+ (* Generate the action stub. *)
+ generate_prototype ~extern:false ~semicolon:false ~newline:true
+ ~handle:"g" name style;
+
+ let error_code =
+ match fst style with
+ | RErr | RInt _ | RInt64 _ | RBool _ -> "-1"
+ | RConstString _ ->
+ failwithf "RConstString cannot be returned from a daemon function"
+ | RString _ | RStringList _ | RIntBool _
+ | RPVList _ | RVGList _ | RLVList _
+ | RStat _ | RStatVFS _
+ | RHashtable _ ->
+ "NULL" in
+
+ pr "{\n";
+
+ (match snd style with
+ | [] -> ()
+ | _ -> pr " struct %s_args args;\n" name
+ );
+
+ pr " struct %s_ctx ctx;\n" shortname;
+ pr " guestfs_main_loop *ml = guestfs_get_main_loop (g);\n";
+ pr " int serial;\n";
+ pr "\n";
+ pr " if (check_state (g, \"%s\") == -1) return %s;\n" name error_code;
+ pr "\n";
+ pr " memset (&ctx, 0, sizeof ctx);\n";
+ pr "\n";
+
+ (* Send the main header and arguments. *)
+ (match snd style with
+ | [] ->
+ pr " serial = guestfs__send_sync (g, GUESTFS_PROC_%s, NULL, NULL);\n"
+ (String.uppercase shortname)
+ | args ->
+ List.iter (
+ function
+ | String n ->
+ pr " args.%s = (char *) %s;\n" n n
+ | OptString n ->
+ pr " args.%s = %s ? (char **) &%s : NULL;\n" n n n
+ | StringList n ->
+ pr " args.%s.%s_val = (char **) %s;\n" n n n;
+ pr " for (args.%s.%s_len = 0; %s[args.%s.%s_len]; args.%s.%s_len++) ;\n" n n n n n n n;
+ | Bool n ->
+ pr " args.%s = %s;\n" n n
+ | Int n ->
+ pr " args.%s = %s;\n" n n
+ | FileIn _ | FileOut _ -> ()
+ ) args;
+ pr " serial = guestfs__send_sync (g, GUESTFS_PROC_%s,\n"
+ (String.uppercase shortname);
+ pr " (xdrproc_t) xdr_%s_args, (char *) &args);\n"
+ name;
+ );
+ pr " if (serial == -1)\n";
+ pr " return %s;\n" error_code;
+ pr "\n";
+
+ (* Send any additional files (FileIn) requested. *)
+ List.iter (
+ function
+ | FileIn n ->
+ pr " if (guestfs__send_file_sync (g, %s) == -1)\n" n;
+ pr " return %s;\n" error_code;
+ pr "\n";
+ | _ -> ()
+ ) (snd style);
+
+ (* Wait for the reply from the remote end. *)
+ pr " guestfs__switch_to_receiving (g);\n";
+ pr " ctx.cb_sequence = 0;\n";
+ pr " guestfs_set_reply_callback (g, %s_reply_cb, &ctx);\n" shortname;
+ pr " (void) ml->main_loop_run (ml, g);\n";
+ pr " guestfs_set_reply_callback (g, NULL, NULL);\n";
+ pr " if (ctx.cb_sequence != 1001) {\n";
+ pr " error (g, \"%%s reply failed, see earlier error messages\", \"%s\");\n" name;
+ pr " return %s;\n" error_code;
+ pr " }\n";
+ pr "\n";
+
+ pr " if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_%s, serial) == -1)\n"
+ (String.uppercase shortname);
+ pr " return %s;\n" error_code;
+ pr "\n";
+
+ pr " if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {\n";
+ pr " error (g, \"%%s\", ctx.err.error_message);\n";
+ pr " return %s;\n" error_code;
+ pr " }\n";
+ pr "\n";
+
+ (* Expecting to receive further files (FileOut)? *)
+ List.iter (
+ function
+ | FileOut n ->
+ pr " if (guestfs__receive_file_sync (g, %s) == -1)\n" n;
+ pr " return %s;\n" error_code;
+ pr "\n";
+ | _ -> ()
+ ) (snd style);
+
+ (match fst style with
+ | RErr -> pr " return 0;\n"
+ | RInt n | RInt64 n | RBool n ->
+ pr " return ctx.ret.%s;\n" n
+ | RConstString _ ->
+ failwithf "RConstString cannot be returned from a daemon function"
+ | RString n ->
+ pr " return ctx.ret.%s; /* caller will free */\n" n
+ | RStringList n | RHashtable n ->
+ pr " /* caller will free this, but we need to add a NULL entry */\n";
+ pr " ctx.ret.%s.%s_val =\n" n n;
+ pr " safe_realloc (g, ctx.ret.%s.%s_val,\n" n n;
+ pr " sizeof (char *) * (ctx.ret.%s.%s_len + 1));\n"
+ n n;
+ pr " ctx.ret.%s.%s_val[ctx.ret.%s.%s_len] = NULL;\n" n n n n;
+ pr " return ctx.ret.%s.%s_val;\n" n n
+ | RIntBool _ ->
+ 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 ->
+ pr " /* caller will free this */\n";
+ pr " return safe_memdup (g, &ctx.ret.%s, sizeof (ctx.ret.%s));\n" n n
+ );
+
+ pr "}\n\n"
+ ) daemon_functions
+
+(* Generate daemon/actions.h. *)
+and generate_daemon_actions_h () =
+ generate_header CStyle GPLv2;
+
+ pr "#include \"../src/guestfs_protocol.h\"\n";
+ pr "\n";
+
+ List.iter (
+ fun (name, style, _, _, _, _, _) ->
+ generate_prototype
+ ~single_line:true ~newline:true ~in_daemon:true ~prefix:"do_"
+ name style;
+ ) daemon_functions
+
+(* Generate the server-side stubs. *)
+and generate_daemon_actions () =
+ generate_header CStyle GPLv2;
+
+ pr "#define _GNU_SOURCE // for strchrnul\n";
+ pr "\n";
+ pr "#include <stdio.h>\n";
+ pr "#include <stdlib.h>\n";
+ pr "#include <string.h>\n";
+ pr "#include <inttypes.h>\n";
+ pr "#include <ctype.h>\n";
+ pr "#include <rpc/types.h>\n";
+ pr "#include <rpc/xdr.h>\n";
+ pr "\n";
+ pr "#include \"daemon.h\"\n";
+ pr "#include \"../src/guestfs_protocol.h\"\n";
+ pr "#include \"actions.h\"\n";
+ pr "\n";
+
+ List.iter (
+ fun (name, style, _, _, _, _, _) ->
+ (* Generate server-side stubs. *)
+ pr "static void %s_stub (XDR *xdr_in)\n" name;
+ pr "{\n";
+ let error_code =
+ match fst style with
+ | RErr | RInt _ -> pr " int r;\n"; "-1"
+ | RInt64 _ -> pr " int64_t r;\n"; "-1"
+ | RBool _ -> pr " int r;\n"; "-1"
+ | RConstString _ ->
+ failwithf "RConstString cannot be returned from a daemon function"
+ | RString _ -> pr " char *r;\n"; "NULL"
+ | RStringList _ | RHashtable _ -> pr " char **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"
+ | RStat _ -> pr " guestfs_int_stat *r;\n"; "NULL"
+ | RStatVFS _ -> pr " guestfs_int_statvfs *r;\n"; "NULL" in
+
+ (match snd style with
+ | [] -> ()
+ | args ->
+ pr " struct guestfs_%s_args args;\n" name;
+ List.iter (
+ function
+ | String n
+ | OptString n -> pr " const char *%s;\n" n
+ | StringList n -> pr " char **%s;\n" n
+ | Bool n -> pr " int %s;\n" n
+ | Int n -> pr " int %s;\n" n
+ | FileIn _ | FileOut _ -> ()
+ ) args
+ );
+ pr "\n";
+
+ (match snd style with
+ | [] -> ()
+ | args ->
+ pr " memset (&args, 0, sizeof args);\n";
+ pr "\n";
+ pr " if (!xdr_guestfs_%s_args (xdr_in, &args)) {\n" name;
+ pr " reply_with_error (\"%%s: daemon failed to decode procedure arguments\", \"%s\");\n" name;
+ pr " return;\n";
+ pr " }\n";
+ List.iter (
+ function
+ | String n -> pr " %s = args.%s;\n" n n
+ | OptString n -> pr " %s = args.%s ? *args.%s : NULL;\n" n n n
+ | StringList n ->
+ pr " args.%s.%s_val = realloc (args.%s.%s_val, sizeof (char *) * (args.%s.%s_len+1));\n" n n n n n n;
+ pr " args.%s.%s_val[args.%s.%s_len] = NULL;\n" n n n n;
+ pr " %s = args.%s.%s_val;\n" n n n
+ | Bool n -> pr " %s = args.%s;\n" n n
+ | Int n -> pr " %s = args.%s;\n" n n
+ | FileIn _ | FileOut _ -> ()
+ ) args;
+ pr "\n"
+ );
+
+ (* Don't want to call the impl with any FileIn or FileOut
+ * parameters, since these go "outside" the RPC protocol.
+ *)
+ let argsnofile =
+ List.filter (function FileIn _ | FileOut _ -> false | _ -> true)
+ (snd style) in
+ pr " r = do_%s " name;
+ generate_call_args argsnofile;
+ pr ";\n";
+
+ pr " if (r == %s)\n" error_code;
+ pr " /* do_%s has already called reply_with_error */\n" name;
+ pr " goto done;\n";
+ pr "\n";
+
+ (* If there are any FileOut parameters, then the impl must
+ * send its own reply.
+ *)
+ let no_reply =
+ List.exists (function FileOut _ -> true | _ -> false) (snd style) in
+ if no_reply then
+ pr " /* do_%s has already sent a reply */\n" name
+ else (
+ match fst style with
+ | RErr -> pr " reply (NULL, NULL);\n"
+ | RInt n | RInt64 n | RBool 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
+ | RConstString _ ->
+ failwithf "RConstString cannot be returned from a daemon function"
+ | RString 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 " free (r);\n"
+ | RStringList n | RHashtable n ->
+ pr " struct guestfs_%s_ret ret;\n" name;
+ pr " ret.%s.%s_len = count_strings (r);\n" n n;
+ pr " ret.%s.%s_val = r;\n" n n;
+ pr " reply ((xdrproc_t) &xdr_guestfs_%s_ret, (char *) &ret);\n"
+ name;
+ pr " free_strings (r);\n"
+ | 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 | 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 " xdr_free ((xdrproc_t) xdr_guestfs_%s_ret, (char *) &ret);\n"
+ name
+ );
+
+ (* Free the args. *)
+ (match snd style with
+ | [] ->
+ pr "done: ;\n";
+ | _ ->
+ pr "done:\n";
+ pr " xdr_free ((xdrproc_t) xdr_guestfs_%s_args, (char *) &args);\n"
+ name
+ );
+
+ pr "}\n\n";
+ ) daemon_functions;
+
+ (* Dispatch function. *)
+ pr "void dispatch_incoming_message (XDR *xdr_in)\n";
+ pr "{\n";
+ pr " switch (proc_nr) {\n";
+
+ List.iter (
+ fun (name, style, _, _, _, _, _) ->
+ pr " case GUESTFS_PROC_%s:\n" (String.uppercase name);
+ pr " %s_stub (xdr_in);\n" name;
+ pr " break;\n"
+ ) daemon_functions;
+
+ pr " default:\n";
+ pr " reply_with_error (\"dispatch_incoming_message: unknown procedure number %%d\", proc_nr);\n";
+ pr " }\n";
+ pr "}\n";
+ pr "\n";
+
+ (* LVM columns and tokenization functions. *)
+ (* XXX This generates crap code. We should rethink how we
+ * do this parsing.
+ *)
+ List.iter (
+ function
+ | typ, cols ->
+ pr "static const char *lvm_%s_cols = \"%s\";\n"
+ typ (String.concat "," (List.map fst cols));
+ pr "\n";
+
+ pr "static int lvm_tokenize_%s (char *str, struct guestfs_lvm_int_%s *r)\n" typ typ;
+ pr "{\n";
+ pr " char *tok, *p, *next;\n";
+ pr " int i, j;\n";
+ pr "\n";
+ (*
+ pr " fprintf (stderr, \"%%s: <<%%s>>\\n\", __func__, str);\n";
+ pr "\n";
+ *)
+ pr " if (!str) {\n";
+ pr " fprintf (stderr, \"%%s: failed: passed a NULL string\\n\", __func__);\n";
+ pr " return -1;\n";
+ pr " }\n";
+ pr " if (!*str || isspace (*str)) {\n";
+ pr " fprintf (stderr, \"%%s: failed: passed a empty string or one beginning with whitespace\\n\", __func__);\n";
+ pr " return -1;\n";
+ pr " }\n";
+ pr " tok = str;\n";
+ List.iter (
+ fun (name, coltype) ->
+ pr " if (!tok) {\n";
+ pr " fprintf (stderr, \"%%s: failed: string finished early, around token %%s\\n\", __func__, \"%s\");\n" name;
+ pr " return -1;\n";
+ pr " }\n";
+ pr " p = strchrnul (tok, ',');\n";
+ pr " if (*p) next = p+1; else next = NULL;\n";
+ pr " *p = '\\0';\n";
+ (match coltype with
+ | `String ->
+ pr " r->%s = strdup (tok);\n" name;
+ pr " if (r->%s == NULL) {\n" name;
+ pr " perror (\"strdup\");\n";
+ pr " return -1;\n";
+ pr " }\n"
+ | `UUID ->
+ pr " for (i = j = 0; i < 32; ++j) {\n";
+ pr " if (tok[j] == '\\0') {\n";
+ pr " fprintf (stderr, \"%%s: failed to parse UUID from '%%s'\\n\", __func__, tok);\n";
+ pr " return -1;\n";
+ pr " } else if (tok[j] != '-')\n";
+ pr " r->%s[i++] = tok[j];\n" name;
+ pr " }\n";
+ | `Bytes ->
+ pr " if (sscanf (tok, \"%%\"SCNu64, &r->%s) != 1) {\n" name;
+ pr " fprintf (stderr, \"%%s: failed to parse size '%%s' from token %%s\\n\", __func__, tok, \"%s\");\n" name;
+ pr " return -1;\n";
+ pr " }\n";
+ | `Int ->
+ pr " if (sscanf (tok, \"%%\"SCNi64, &r->%s) != 1) {\n" name;
+ pr " fprintf (stderr, \"%%s: failed to parse int '%%s' from token %%s\\n\", __func__, tok, \"%s\");\n" name;
+ pr " return -1;\n";
+ pr " }\n";
+ | `OptPercent ->
+ pr " if (tok[0] == '\\0')\n";
+ pr " r->%s = -1;\n" name;
+ pr " else if (sscanf (tok, \"%%f\", &r->%s) != 1) {\n" name;
+ pr " fprintf (stderr, \"%%s: failed to parse float '%%s' from token %%s\\n\", __func__, tok, \"%s\");\n" name;
+ pr " return -1;\n";
+ pr " }\n";
+ );
+ pr " tok = next;\n";
+ ) cols;
+
+ pr " if (tok != NULL) {\n";
+ pr " fprintf (stderr, \"%%s: failed: extra tokens at end of string\\n\", __func__);\n";
+ pr " return -1;\n";
+ pr " }\n";
+ pr " return 0;\n";
+ pr "}\n";
+ pr "\n";
+
+ pr "guestfs_lvm_int_%s_list *\n" typ;
+ pr "parse_command_line_%ss (void)\n" typ;
+ pr "{\n";
+ pr " char *out, *err;\n";
+ pr " char *p, *pend;\n";
+ pr " int r, i;\n";
+ pr " guestfs_lvm_int_%s_list *ret;\n" typ;
+ pr " void *newp;\n";
+ pr "\n";
+ pr " ret = malloc (sizeof *ret);\n";
+ pr " if (!ret) {\n";
+ pr " reply_with_perror (\"malloc\");\n";
+ pr " return NULL;\n";
+ pr " }\n";
+ pr "\n";
+ pr " ret->guestfs_lvm_int_%s_list_len = 0;\n" typ;
+ pr " ret->guestfs_lvm_int_%s_list_val = NULL;\n" typ;
+ pr "\n";
+ pr " r = command (&out, &err,\n";
+ pr " \"/sbin/lvm\", \"%ss\",\n" typ;
+ pr " \"-o\", lvm_%s_cols, \"--unbuffered\", \"--noheadings\",\n" typ;
+ pr " \"--nosuffix\", \"--separator\", \",\", \"--units\", \"b\", NULL);\n";
+ pr " if (r == -1) {\n";
+ pr " reply_with_error (\"%%s\", err);\n";
+ pr " free (out);\n";
+ pr " free (err);\n";
+ pr " free (ret);\n";
+ pr " return NULL;\n";
+ pr " }\n";
+ pr "\n";
+ pr " free (err);\n";
+ pr "\n";
+ pr " /* Tokenize each line of the output. */\n";
+ pr " p = out;\n";
+ pr " i = 0;\n";
+ pr " while (p) {\n";
+ pr " pend = strchr (p, '\\n'); /* Get the next line of output. */\n";
+ pr " if (pend) {\n";
+ pr " *pend = '\\0';\n";
+ pr " pend++;\n";
+ pr " }\n";
+ pr "\n";
+ pr " while (*p && isspace (*p)) /* Skip any leading whitespace. */\n";
+ pr " p++;\n";
+ pr "\n";
+ pr " if (!*p) { /* Empty line? Skip it. */\n";
+ pr " p = pend;\n";
+ pr " continue;\n";
+ pr " }\n";
+ pr "\n";
+ pr " /* Allocate some space to store this next entry. */\n";
+ pr " newp = realloc (ret->guestfs_lvm_int_%s_list_val,\n" typ;
+ pr " sizeof (guestfs_lvm_int_%s) * (i+1));\n" typ;
+ pr " if (newp == NULL) {\n";
+ pr " reply_with_perror (\"realloc\");\n";
+ pr " free (ret->guestfs_lvm_int_%s_list_val);\n" typ;
+ pr " free (ret);\n";
+ pr " free (out);\n";
+ pr " return NULL;\n";
+ pr " }\n";
+ pr " ret->guestfs_lvm_int_%s_list_val = newp;\n" typ;
+ pr "\n";
+ pr " /* Tokenize the next entry. */\n";
+ pr " r = lvm_tokenize_%s (p, &ret->guestfs_lvm_int_%s_list_val[i]);\n" typ typ;
+ pr " if (r == -1) {\n";
+ pr " reply_with_error (\"failed to parse output of '%ss' command\");\n" typ;
+ pr " free (ret->guestfs_lvm_int_%s_list_val);\n" typ;
+ pr " free (ret);\n";
+ pr " free (out);\n";
+ pr " return NULL;\n";
+ pr " }\n";
+ pr "\n";
+ pr " ++i;\n";
+ pr " p = pend;\n";
+ pr " }\n";
+ pr "\n";
+ pr " ret->guestfs_lvm_int_%s_list_len = i;\n" typ;
+ pr "\n";
+ pr " free (out);\n";
+ pr " return ret;\n";
+ pr "}\n"
+
+ ) ["pv", pv_cols; "vg", vg_cols; "lv", lv_cols]
+
+(* Generate the tests. *)
+and generate_tests () =
+ generate_header CStyle GPLv2;
+
+ pr "\
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <fcntl.h>
+
+#include \"guestfs.h\"
+
+static guestfs_h *g;
+static int suppress_error = 0;
+
+static void print_error (guestfs_h *g, void *data, const char *msg)
+{
+ if (!suppress_error)
+ fprintf (stderr, \"%%s\\n\", msg);
+}
+
+static void print_strings (char * const * const argv)
+{
+ int argc;
+
+ for (argc = 0; argv[argc] != NULL; ++argc)
+ printf (\"\\t%%s\\n\", argv[argc]);
+}
+
+/*
+static void print_table (char * const * const argv)
+{
+ int i;
+
+ for (i = 0; argv[i] != NULL; i += 2)
+ printf (\"%%s: %%s\\n\", argv[i], argv[i+1]);
+}
+*/
+
+static void no_test_warnings (void)
+{
+";
+
+ List.iter (
+ function
+ | name, _, _, _, [], _, _ ->
+ pr " fprintf (stderr, \"warning: \\\"guestfs_%s\\\" has no tests\\n\");\n" name
+ | name, _, _, _, tests, _, _ -> ()
+ ) all_functions;
+
+ pr "}\n";
+ pr "\n";
+
+ (* Generate the actual tests. Note that we generate the tests
+ * in reverse order, deliberately, so that (in general) the
+ * newest tests run first. This makes it quicker and easier to
+ * debug them.
+ *)
+ let test_names =
+ List.map (
+ fun (name, _, _, _, tests, _, _) ->
+ mapi (generate_one_test name) tests
+ ) (List.rev all_functions) in
+ let test_names = List.concat test_names in
+ let nr_tests = List.length test_names in
+
+ pr "\
+int main (int argc, char *argv[])
+{
+ char c = 0;
+ int failed = 0;
+ const char *srcdir;
+ int fd;
+ char buf[256];
+ int nr_tests, test_num = 0;
+
+ no_test_warnings ();
+
+ g = guestfs_create ();
+ if (g == NULL) {
+ printf (\"guestfs_create FAILED\\n\");
+ exit (1);
+ }
+
+ guestfs_set_error_handler (g, print_error, NULL);
+
+ srcdir = getenv (\"srcdir\");
+ if (!srcdir) srcdir = \".\";
+ guestfs_set_path (g, srcdir);
+
+ snprintf (buf, sizeof buf, \"%%s/test1.img\", srcdir);
+ fd = open (buf, O_WRONLY|O_CREAT|O_NOCTTY|O_NONBLOCK|O_TRUNC, 0666);
+ if (fd == -1) {
+ perror (buf);
+ exit (1);
+ }
+ if (lseek (fd, %d, SEEK_SET) == -1) {
+ perror (\"lseek\");
+ close (fd);
+ unlink (buf);
+ exit (1);
+ }
+ if (write (fd, &c, 1) == -1) {
+ perror (\"write\");
+ close (fd);
+ unlink (buf);
+ exit (1);
+ }
+ if (close (fd) == -1) {
+ perror (buf);
+ unlink (buf);
+ exit (1);
+ }
+ if (guestfs_add_drive (g, buf) == -1) {
+ printf (\"guestfs_add_drive %%s FAILED\\n\", buf);
+ exit (1);
+ }
+
+ snprintf (buf, sizeof buf, \"%%s/test2.img\", srcdir);
+ fd = open (buf, O_WRONLY|O_CREAT|O_NOCTTY|O_NONBLOCK|O_TRUNC, 0666);
+ if (fd == -1) {
+ perror (buf);
+ exit (1);
+ }
+ if (lseek (fd, %d, SEEK_SET) == -1) {
+ perror (\"lseek\");
+ close (fd);
+ unlink (buf);
+ exit (1);
+ }
+ if (write (fd, &c, 1) == -1) {
+ perror (\"write\");
+ close (fd);
+ unlink (buf);
+ exit (1);
+ }
+ if (close (fd) == -1) {
+ perror (buf);
+ unlink (buf);
+ exit (1);
+ }
+ if (guestfs_add_drive (g, buf) == -1) {
+ printf (\"guestfs_add_drive %%s FAILED\\n\", buf);
+ exit (1);
+ }
+
+ snprintf (buf, sizeof buf, \"%%s/test3.img\", srcdir);
+ fd = open (buf, O_WRONLY|O_CREAT|O_NOCTTY|O_NONBLOCK|O_TRUNC, 0666);
+ if (fd == -1) {
+ perror (buf);
+ exit (1);
+ }
+ if (lseek (fd, %d, SEEK_SET) == -1) {
+ perror (\"lseek\");
+ close (fd);
+ unlink (buf);
+ exit (1);
+ }
+ if (write (fd, &c, 1) == -1) {
+ perror (\"write\");
+ close (fd);
+ unlink (buf);
+ exit (1);
+ }
+ if (close (fd) == -1) {
+ perror (buf);
+ unlink (buf);
+ exit (1);
+ }
+ if (guestfs_add_drive (g, buf) == -1) {
+ printf (\"guestfs_add_drive %%s FAILED\\n\", buf);
+ exit (1);
+ }
+
+ if (guestfs_launch (g) == -1) {
+ printf (\"guestfs_launch FAILED\\n\");
+ exit (1);
+ }
+ if (guestfs_wait_ready (g) == -1) {
+ printf (\"guestfs_wait_ready FAILED\\n\");
+ exit (1);
+ }
+
+ nr_tests = %d;
+
+" (500 * 1024 * 1024) (50 * 1024 * 1024) (10 * 1024 * 1024) nr_tests;
+
+ iteri (
+ fun i test_name ->
+ pr " test_num++;\n";
+ pr " printf (\"%%3d/%%3d %s\\n\", test_num, nr_tests);\n" test_name;
+ pr " if (%s () == -1) {\n" test_name;
+ pr " printf (\"%s FAILED\\n\");\n" test_name;
+ pr " failed++;\n";
+ pr " }\n";
+ ) test_names;
+ pr "\n";
+
+ pr " guestfs_close (g);\n";
+ pr " snprintf (buf, sizeof buf, \"%%s/test1.img\", srcdir);\n";
+ pr " unlink (buf);\n";
+ pr " snprintf (buf, sizeof buf, \"%%s/test2.img\", srcdir);\n";
+ pr " unlink (buf);\n";
+ pr " snprintf (buf, sizeof buf, \"%%s/test3.img\", srcdir);\n";
+ pr " unlink (buf);\n";
+ pr "\n";
+
+ pr " if (failed > 0) {\n";
+ pr " printf (\"***** %%d / %%d tests FAILED *****\\n\", failed, nr_tests);\n";
+ pr " exit (1);\n";
+ pr " }\n";
+ pr "\n";
+
+ pr " exit (0);\n";
+ pr "}\n"
+
+and generate_one_test name i (init, test) =
+ let test_name = sprintf "test_%s_%d" name i in
+
+ pr "static int %s (void)\n" test_name;
+ pr "{\n";
+
+ (match init with
+ | InitNone -> ()
+ | InitEmpty ->
+ pr " /* InitEmpty for %s (%d) */\n" name i;
+ List.iter (generate_test_command_call test_name)
+ [["umount_all"];
+ ["lvm_remove_all"]]
+ | InitBasicFS ->
+ pr " /* InitBasicFS for %s (%d): create ext2 on /dev/sda1 */\n" name i;
+ List.iter (generate_test_command_call test_name)
+ [["umount_all"];
+ ["lvm_remove_all"];
+ ["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ","];
+ ["mkfs"; "ext2"; "/dev/sda1"];
+ ["mount"; "/dev/sda1"; "/"]]
+ | InitBasicFSonLVM ->
+ pr " /* InitBasicFSonLVM for %s (%d): create ext2 on /dev/VG/LV */\n"
+ name i;
+ List.iter (generate_test_command_call test_name)
+ [["umount_all"];
+ ["lvm_remove_all"];
+ ["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ","];
+ ["pvcreate"; "/dev/sda1"];
+ ["vgcreate"; "VG"; "/dev/sda1"];
+ ["lvcreate"; "LV"; "VG"; "8"];
+ ["mkfs"; "ext2"; "/dev/VG/LV"];
+ ["mount"; "/dev/VG/LV"; "/"]]
+ );
+
+ let get_seq_last = function
+ | [] ->
+ failwithf "%s: you cannot use [] (empty list) when expecting a command"
+ test_name
+ | seq ->
+ let seq = List.rev seq in
+ List.rev (List.tl seq), List.hd seq
+ in
+
+ (match test with
+ | TestRun seq ->
+ pr " /* TestRun for %s (%d) */\n" name i;
+ List.iter (generate_test_command_call test_name) seq
+ | TestOutput (seq, expected) ->
+ pr " /* TestOutput for %s (%d) */\n" name i;
+ let seq, last = get_seq_last seq in
+ let test () =
+ pr " if (strcmp (r, \"%s\") != 0) {\n" (c_quote expected);
+ pr " fprintf (stderr, \"%s: expected \\\"%s\\\" but got \\\"%%s\\\"\\n\", r);\n" test_name (c_quote expected);
+ pr " return -1;\n";
+ pr " }\n"
+ in
+ List.iter (generate_test_command_call test_name) seq;
+ generate_test_command_call ~test test_name last
+ | TestOutputList (seq, expected) ->
+ pr " /* TestOutputList for %s (%d) */\n" name i;
+ let seq, last = get_seq_last seq in
+ let test () =
+ iteri (
+ fun i str ->
+ pr " if (!r[%d]) {\n" i;
+ pr " fprintf (stderr, \"%s: short list returned from command\\n\");\n" test_name;
+ pr " print_strings (r);\n";
+ pr " return -1;\n";
+ pr " }\n";
+ pr " if (strcmp (r[%d], \"%s\") != 0) {\n" i (c_quote str);
+ pr " fprintf (stderr, \"%s: expected \\\"%s\\\" but got \\\"%%s\\\"\\n\", r[%d]);\n" test_name (c_quote str) i;
+ pr " return -1;\n";
+ pr " }\n"
+ ) expected;
+ pr " if (r[%d] != NULL) {\n" (List.length expected);
+ pr " fprintf (stderr, \"%s: extra elements returned from command\\n\");\n"
+ test_name;
+ pr " print_strings (r);\n";
+ pr " return -1;\n";
+ pr " }\n"
+ in
+ List.iter (generate_test_command_call test_name) seq;
+ generate_test_command_call ~test test_name last
+ | TestOutputInt (seq, expected) ->
+ pr " /* TestOutputInt for %s (%d) */\n" name i;
+ let seq, last = get_seq_last seq in
+ let test () =
+ pr " if (r != %d) {\n" expected;
+ pr " fprintf (stderr, \"%s: expected %d but got %%d\\n\","
+ test_name expected;
+ pr " (int) r);\n";
+ pr " return -1;\n";
+ pr " }\n"
+ in
+ List.iter (generate_test_command_call test_name) seq;
+ generate_test_command_call ~test test_name last
+ | TestOutputTrue seq ->
+ pr " /* TestOutputTrue for %s (%d) */\n" name i;
+ let seq, last = get_seq_last seq in
+ let test () =
+ pr " if (!r) {\n";
+ pr " fprintf (stderr, \"%s: expected true, got false\\n\");\n"
+ test_name;
+ pr " return -1;\n";
+ pr " }\n"
+ in
+ List.iter (generate_test_command_call test_name) seq;
+ generate_test_command_call ~test test_name last
+ | TestOutputFalse seq ->
+ pr " /* TestOutputFalse for %s (%d) */\n" name i;
+ let seq, last = get_seq_last seq in
+ let test () =
+ pr " if (r) {\n";
+ pr " fprintf (stderr, \"%s: expected false, got true\\n\");\n"
+ test_name;
+ pr " return -1;\n";
+ pr " }\n"
+ in
+ List.iter (generate_test_command_call test_name) seq;
+ generate_test_command_call ~test test_name last
+ | TestOutputLength (seq, expected) ->
+ pr " /* TestOutputLength for %s (%d) */\n" name i;
+ let seq, last = get_seq_last seq in
+ let test () =
+ pr " int j;\n";
+ pr " for (j = 0; j < %d; ++j)\n" expected;
+ pr " if (r[j] == NULL) {\n";
+ pr " fprintf (stderr, \"%s: short list returned\\n\");\n"
+ test_name;
+ pr " print_strings (r);\n";
+ pr " return -1;\n";
+ pr " }\n";
+ pr " if (r[j] != NULL) {\n";
+ pr " fprintf (stderr, \"%s: long list returned\\n\");\n"
+ test_name;
+ pr " print_strings (r);\n";
+ pr " return -1;\n";
+ pr " }\n"
+ 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
+ List.iter (generate_test_command_call test_name) seq;
+ generate_test_command_call test_name ~expect_error:true last
+ );
+
+ pr " return 0;\n";
+ pr "}\n";
+ pr "\n";
+ test_name
+
+(* Generate the code to run a command, leaving the result in 'r'.
+ * If you expect to get an error then you should set expect_error:true.
+ *)
+and generate_test_command_call ?(expect_error = false) ?test test_name cmd =
+ match cmd with
+ | [] -> assert false
+ | name :: args ->
+ (* Look up the command to find out what args/ret it has. *)
+ let style =
+ try
+ let _, style, _, _, _, _, _ =
+ List.find (fun (n, _, _, _, _, _, _) -> n = name) all_functions in
+ style
+ with Not_found ->
+ failwithf "%s: in test, command %s was not found" test_name name in
+
+ if List.length (snd style) <> List.length args then
+ failwithf "%s: in test, wrong number of args given to %s"
+ test_name name;
+
+ pr " {\n";
+
+ List.iter (
+ function
+ | String _, _
+ | OptString _, _
+ | Int _, _
+ | Bool _, _ -> ()
+ | FileIn _, _ | FileOut _, _ -> ()
+ | StringList n, arg ->
+ pr " char *%s[] = {\n" n;
+ let strs = string_split " " arg in
+ List.iter (
+ fun str -> pr " \"%s\",\n" (c_quote str)
+ ) strs;
+ pr " NULL\n";
+ pr " };\n";
+ ) (List.combine (snd style) args);
+
+ let error_code =
+ match fst style with
+ | RErr | RInt _ | RBool _ -> pr " int r;\n"; "-1"
+ | RInt64 _ -> pr " int64_t r;\n"; "-1"
+ | RConstString _ -> pr " const char *r;\n"; "NULL"
+ | RString _ -> pr " char *r;\n"; "NULL"
+ | RStringList _ | RHashtable _ ->
+ pr " char **r;\n";
+ pr " int i;\n";
+ "NULL"
+ | RIntBool _ ->
+ pr " struct guestfs_int_bool *r;\n"; "NULL"
+ | RPVList _ ->
+ pr " struct guestfs_lvm_pv_list *r;\n"; "NULL"
+ | RVGList _ ->
+ pr " struct guestfs_lvm_vg_list *r;\n"; "NULL"
+ | RLVList _ ->
+ 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;
+
+ (* Generate the parameters. *)
+ List.iter (
+ function
+ | String _, arg
+ | FileIn _, arg | FileOut _, arg ->
+ pr ", \"%s\"" (c_quote arg)
+ | OptString _, arg ->
+ if arg = "NULL" then pr ", NULL" else pr ", \"%s\"" (c_quote arg)
+ | StringList n, _ ->
+ pr ", %s" n
+ | Int _, arg ->
+ let i =
+ try int_of_string arg
+ with Failure "int_of_string" ->
+ failwithf "%s: expecting an int, but got '%s'" test_name arg in
+ pr ", %d" i
+ | Bool _, arg ->
+ let b = bool_of_string arg in pr ", %d" (if b then 1 else 0)
+ ) (List.combine (snd style) args);
+
+ pr ");\n";
+ if not expect_error then
+ pr " if (r == %s)\n" error_code
+ else
+ pr " if (r != %s)\n" error_code;
+ pr " return -1;\n";
+
+ (* Insert the test code. *)
+ (match test with
+ | None -> ()
+ | Some f -> f ()
+ );
+
+ (match fst style with
+ | RErr | RInt _ | RInt64 _ | RBool _ | RConstString _ -> ()
+ | RString _ -> pr " free (r);\n"
+ | RStringList _ | RHashtable _ ->
+ pr " for (i = 0; r[i] != NULL; ++i)\n";
+ pr " free (r[i]);\n";
+ pr " free (r);\n"
+ | RIntBool _ ->
+ pr " guestfs_free_int_bool (r);\n"
+ | RPVList _ ->
+ pr " guestfs_free_lvm_pv_list (r);\n"
+ | RVGList _ ->
+ 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"
+
+and c_quote str =
+ let str = replace_str str "\r" "\\r" in
+ let str = replace_str str "\n" "\\n" in
+ let str = replace_str str "\t" "\\t" in
+ str
+
+(* Generate a lot of different functions for guestfish. *)
+and generate_fish_cmds () =
+ generate_header CStyle GPLv2;
+
+ let all_functions =
+ List.filter (
+ fun (_, _, _, flags, _, _, _) -> not (List.mem NotInFish flags)
+ ) all_functions in
+ let all_functions_sorted =
+ List.filter (
+ fun (_, _, _, flags, _, _, _) -> not (List.mem NotInFish flags)
+ ) all_functions_sorted in
+
+ pr "#include <stdio.h>\n";
+ pr "#include <stdlib.h>\n";
+ pr "#include <string.h>\n";
+ pr "#include <inttypes.h>\n";
+ pr "\n";
+ pr "#include <guestfs.h>\n";
+ pr "#include \"fish.h\"\n";
+ pr "\n";
+
+ (* list_commands function, which implements guestfish -h *)
+ pr "void list_commands (void)\n";
+ pr "{\n";
+ pr " printf (\" %%-16s %%s\\n\", \"Command\", \"Description\");\n";
+ pr " list_builtin_commands ();\n";
+ List.iter (
+ fun (name, _, _, flags, _, shortdesc, _) ->
+ let name = replace_char name '_' '-' in
+ pr " printf (\"%%-20s %%s\\n\", \"%s\", \"%s\");\n"
+ name shortdesc
+ ) all_functions_sorted;
+ pr " printf (\" Use -h <cmd> / help <cmd> to show detailed help for a command.\\n\");\n";
+ pr "}\n";
+ pr "\n";
+
+ (* display_command function, which implements guestfish -h cmd *)
+ pr "void display_command (const char *cmd)\n";
+ pr "{\n";
+ List.iter (
+ fun (name, style, _, flags, _, shortdesc, longdesc) ->
+ let name2 = replace_char name '_' '-' in
+ let alias =
+ try find_map (function FishAlias n -> Some n | _ -> None) flags
+ with Not_found -> name in
+ let longdesc = replace_str longdesc "C<guestfs_" "C<" in
+ let synopsis =
+ match snd style with
+ | [] -> name2
+ | args ->
+ sprintf "%s <%s>"
+ name2 (String.concat "> <" (List.map name_of_argt args)) in
+
+ let warnings =
+ if List.mem ProtocolLimitWarning flags then
+ ("\n\n" ^ protocol_limit_warning)
+ else "" in
+
+ (* For DangerWillRobinson commands, we should probably have
+ * guestfish prompt before allowing you to use them (especially
+ * in interactive mode). XXX
+ *)
+ let warnings =
+ warnings ^
+ if List.mem DangerWillRobinson flags then
+ ("\n\n" ^ danger_will_robinson)
+ else "" in
+
+ let describe_alias =
+ if name <> alias then
+ sprintf "\n\nYou can use '%s' as an alias for this command." alias
+ else "" in
+
+ pr " if (";
+ pr "strcasecmp (cmd, \"%s\") == 0" name;
+ if name <> name2 then
+ pr " || strcasecmp (cmd, \"%s\") == 0" name2;
+ if name <> alias then
+ pr " || strcasecmp (cmd, \"%s\") == 0" alias;
+ pr ")\n";
+ pr " pod2text (\"%s - %s\", %S);\n"
+ name2 shortdesc
+ (" " ^ synopsis ^ "\n\n" ^ longdesc ^ warnings ^ describe_alias);
+ pr " else\n"
+ ) all_functions;
+ pr " display_builtin_command (cmd);\n";
+ pr "}\n";
+ pr "\n";
+
+ (* print_{pv,vg,lv}_list functions *)
+ List.iter (
+ function
+ | typ, cols ->
+ pr "static void print_%s (struct guestfs_lvm_%s *%s)\n" typ typ typ;
+ pr "{\n";
+ pr " int i;\n";
+ pr "\n";
+ List.iter (
+ function
+ | name, `String ->
+ pr " printf (\"%s: %%s\\n\", %s->%s);\n" name typ name
+ | name, `UUID ->
+ pr " printf (\"%s: \");\n" name;
+ pr " for (i = 0; i < 32; ++i)\n";
+ pr " printf (\"%%c\", %s->%s[i]);\n" typ name;
+ pr " printf (\"\\n\");\n"
+ | name, `Bytes ->
+ pr " printf (\"%s: %%\" PRIu64 \"\\n\", %s->%s);\n" name typ name
+ | name, `Int ->
+ pr " printf (\"%s: %%\" PRIi64 \"\\n\", %s->%s);\n" name typ name
+ | name, `OptPercent ->
+ pr " if (%s->%s >= 0) printf (\"%s: %%g %%%%\\n\", %s->%s);\n"
+ typ name name typ name;
+ pr " else printf (\"%s: \\n\");\n" name
+ ) cols;
+ pr "}\n";
+ pr "\n";
+ pr "static void print_%s_list (struct guestfs_lvm_%s_list *%ss)\n"
+ typ typ typ;
+ pr "{\n";
+ pr " int i;\n";
+ pr "\n";
+ pr " for (i = 0; i < %ss->len; ++i)\n" typ;
+ pr " print_%s (&%ss->val[i]);\n" typ typ;
+ pr "}\n";
+ 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, _, _, _) ->
+ pr "static int run_%s (const char *cmd, int argc, char *argv[])\n" name;
+ pr "{\n";
+ (match fst style with
+ | RErr
+ | RInt _
+ | RBool _ -> pr " int r;\n"
+ | RInt64 _ -> pr " int64_t r;\n"
+ | RConstString _ -> pr " const char *r;\n"
+ | RString _ -> pr " char *r;\n"
+ | RStringList _ | RHashtable _ -> pr " char **r;\n"
+ | RIntBool _ -> pr " struct guestfs_int_bool *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
+ | String n
+ | OptString n
+ | FileIn n
+ | FileOut n -> pr " const char *%s;\n" n
+ | StringList n -> pr " char **%s;\n" n
+ | Bool n -> pr " int %s;\n" n
+ | Int n -> pr " int %s;\n" n
+ ) (snd style);
+
+ (* Check and convert parameters. *)
+ let argc_expected = List.length (snd style) in
+ pr " if (argc != %d) {\n" argc_expected;
+ pr " fprintf (stderr, \"%%s should have %d parameter(s)\\n\", cmd);\n"
+ argc_expected;
+ pr " fprintf (stderr, \"type 'help %%s' for help on %%s\\n\", cmd, cmd);\n";
+ pr " return -1;\n";
+ pr " }\n";
+ iteri (
+ fun i ->
+ function
+ | String name -> pr " %s = argv[%d];\n" name i
+ | OptString name ->
+ pr " %s = strcmp (argv[%d], \"\") != 0 ? argv[%d] : NULL;\n"
+ name i i
+ | FileIn name ->
+ pr " %s = strcmp (argv[%d], \"-\") != 0 ? argv[%d] : \"/dev/stdin\";\n"
+ name i i
+ | FileOut name ->
+ pr " %s = strcmp (argv[%d], \"-\") != 0 ? argv[%d] : \"/dev/stdout\";\n"
+ name i i
+ | StringList name ->
+ pr " %s = parse_string_list (argv[%d]);\n" name i
+ | Bool name ->
+ pr " %s = is_true (argv[%d]) ? 1 : 0;\n" name i
+ | Int name ->
+ pr " %s = atoi (argv[%d]);\n" name i
+ ) (snd style);
+
+ (* Call C API function. *)
+ let fn =
+ try find_map (function FishAction n -> Some n | _ -> None) flags
+ with Not_found -> sprintf "guestfs_%s" name in
+ pr " r = %s " fn;
+ generate_call_args ~handle:"g" (snd style);
+ pr ";\n";
+
+ (* Check return value for errors and display command results. *)
+ (match fst style with
+ | RErr -> pr " return r;\n"
+ | RInt _ ->
+ pr " if (r == -1) return -1;\n";
+ pr " printf (\"%%d\\n\", r);\n";
+ pr " return 0;\n"
+ | RInt64 _ ->
+ pr " if (r == -1) return -1;\n";
+ pr " printf (\"%%\" PRIi64 \"\\n\", r);\n";
+ pr " return 0;\n"
+ | RBool _ ->
+ pr " if (r == -1) return -1;\n";
+ pr " if (r) printf (\"true\\n\"); else printf (\"false\\n\");\n";
+ pr " return 0;\n"
+ | RConstString _ ->
+ pr " if (r == NULL) return -1;\n";
+ pr " printf (\"%%s\\n\", r);\n";
+ pr " return 0;\n"
+ | RString _ ->
+ pr " if (r == NULL) return -1;\n";
+ pr " printf (\"%%s\\n\", r);\n";
+ pr " free (r);\n";
+ pr " return 0;\n"
+ | RStringList _ ->
+ pr " if (r == NULL) return -1;\n";
+ pr " print_strings (r);\n";
+ pr " free_strings (r);\n";
+ pr " return 0;\n"
+ | RIntBool _ ->
+ pr " if (r == NULL) return -1;\n";
+ pr " printf (\"%%d, %%s\\n\", r->i,\n";
+ pr " r->b ? \"true\" : \"false\");\n";
+ pr " guestfs_free_int_bool (r);\n";
+ pr " return 0;\n"
+ | RPVList _ ->
+ pr " if (r == NULL) return -1;\n";
+ pr " print_pv_list (r);\n";
+ pr " guestfs_free_lvm_pv_list (r);\n";
+ pr " return 0;\n"
+ | RVGList _ ->
+ pr " if (r == NULL) return -1;\n";
+ pr " print_vg_list (r);\n";
+ pr " guestfs_free_lvm_vg_list (r);\n";
+ pr " return 0;\n"
+ | RLVList _ ->
+ pr " if (r == NULL) return -1;\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"
+ | RHashtable _ ->
+ pr " if (r == NULL) return -1;\n";
+ pr " print_table (r);\n";
+ pr " free_strings (r);\n";
+ pr " return 0;\n"
+ );
+ pr "}\n";
+ pr "\n"
+ ) all_functions;
+
+ (* run_action function *)
+ pr "int run_action (const char *cmd, int argc, char *argv[])\n";
+ pr "{\n";
+ List.iter (
+ fun (name, _, _, flags, _, _, _) ->
+ let name2 = replace_char name '_' '-' in
+ let alias =
+ try find_map (function FishAlias n -> Some n | _ -> None) flags
+ with Not_found -> name in
+ pr " if (";
+ pr "strcasecmp (cmd, \"%s\") == 0" name;
+ if name <> name2 then
+ pr " || strcasecmp (cmd, \"%s\") == 0" name2;
+ if name <> alias then
+ pr " || strcasecmp (cmd, \"%s\") == 0" alias;
+ pr ")\n";
+ pr " return run_%s (cmd, argc, argv);\n" name;
+ pr " else\n";
+ ) all_functions;
+ pr " {\n";
+ pr " fprintf (stderr, \"%%s: unknown command\\n\", cmd);\n";
+ pr " return -1;\n";
+ pr " }\n";
+ pr " return 0;\n";
+ pr "}\n";
+ pr "\n"
+
+(* Readline completion for guestfish. *)
+and generate_fish_completion () =
+ generate_header CStyle GPLv2;
+
+ let all_functions =
+ List.filter (
+ fun (_, _, _, flags, _, _, _) -> not (List.mem NotInFish flags)
+ ) all_functions in
+
+ pr "\
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef HAVE_LIBREADLINE
+#include <readline/readline.h>
+#endif
+
+#include \"fish.h\"
+
+#ifdef HAVE_LIBREADLINE
+
+static const char *commands[] = {
+";
+
+ (* Get the commands and sort them, including the aliases. *)
+ let commands =
+ List.map (
+ fun (name, _, _, flags, _, _, _) ->
+ let name2 = replace_char name '_' '-' in
+ let alias =
+ try find_map (function FishAlias n -> Some n | _ -> None) flags
+ with Not_found -> name in
+
+ if name <> alias then [name2; alias] else [name2]
+ ) all_functions in
+ let commands = List.flatten commands in
+ let commands = List.sort compare commands in
+
+ List.iter (pr " \"%s\",\n") commands;
+
+ pr " NULL
+};
+
+static char *
+generator (const char *text, int state)
+{
+ static int index, len;
+ const char *name;
+
+ if (!state) {
+ index = 0;
+ len = strlen (text);
+ }
+
+ while ((name = commands[index]) != NULL) {
+ index++;
+ if (strncasecmp (name, text, len) == 0)
+ return strdup (name);
+ }
+
+ return NULL;
+}
+
+#endif /* HAVE_LIBREADLINE */
+
+char **do_completion (const char *text, int start, int end)
+{
+ char **matches = NULL;
+
+#ifdef HAVE_LIBREADLINE
+ if (start == 0)
+ matches = rl_completion_matches (text, generator);
+#endif
+
+ return matches;
+}
+";
+
+(* Generate the POD documentation for guestfish. *)
+and generate_fish_actions_pod () =
+ let all_functions_sorted =
+ List.filter (
+ fun (_, _, _, flags, _, _, _) -> not (List.mem NotInFish flags)
+ ) all_functions_sorted in
+
+ List.iter (
+ fun (name, style, _, flags, _, _, longdesc) ->
+ let longdesc = replace_str longdesc "C<guestfs_" "C<" in
+ let name = replace_char name '_' '-' in
+ let alias =
+ try find_map (function FishAlias n -> Some n | _ -> None) flags
+ with Not_found -> name in
+
+ pr "=head2 %s" name;
+ if name <> alias then
+ pr " | %s" alias;
+ pr "\n";
+ pr "\n";
+ pr " %s" name;
+ List.iter (
+ function
+ | String n -> pr " %s" n
+ | OptString n -> pr " %s" n
+ | StringList n -> pr " %s,..." n
+ | Bool _ -> pr " true|false"
+ | Int n -> pr " %s" n
+ | FileIn n | FileOut n -> pr " (%s|-)" n
+ ) (snd style);
+ pr "\n";
+ pr "\n";
+ pr "%s\n\n" longdesc;
+
+ if List.exists (function FileIn _ | FileOut _ -> true
+ | _ -> false) (snd style) then
+ pr "Use C<-> instead of a filename to read/write from stdin/stdout.\n\n";
+
+ if List.mem ProtocolLimitWarning flags then
+ pr "%s\n\n" protocol_limit_warning;
+
+ if List.mem DangerWillRobinson flags then
+ pr "%s\n\n" danger_will_robinson
+ ) all_functions_sorted
+
+(* Generate a C function prototype. *)
+and generate_prototype ?(extern = true) ?(static = false) ?(semicolon = true)
+ ?(single_line = false) ?(newline = false) ?(in_daemon = false)
+ ?(prefix = "")
+ ?handle name style =
+ if extern then pr "extern ";
+ if static then pr "static ";
+ (match fst style with
+ | RErr -> pr "int "
+ | RInt _ -> pr "int "
+ | RInt64 _ -> pr "int64_t "
+ | RBool _ -> pr "int "
+ | RConstString _ -> pr "const char *"
+ | RString _ -> pr "char *"
+ | RStringList _ | RHashtable _ -> pr "char **"
+ | RIntBool _ ->
+ if not in_daemon then pr "struct guestfs_int_bool *"
+ else pr "guestfs_%s_ret *" name
+ | RPVList _ ->
+ if not in_daemon then pr "struct guestfs_lvm_pv_list *"
+ else pr "guestfs_lvm_int_pv_list *"
+ | RVGList _ ->
+ if not in_daemon then pr "struct guestfs_lvm_vg_list *"
+ else pr "guestfs_lvm_int_vg_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 "void"
+ else (
+ let comma = ref false in
+ (match handle with
+ | None -> ()
+ | Some handle -> pr "guestfs_h *%s" handle; comma := true
+ );
+ let next () =
+ if !comma then (
+ if single_line then pr ", " else pr ",\n\t\t"
+ );
+ comma := true
+ in
+ List.iter (
+ function
+ | String n
+ | OptString n -> next (); pr "const char *%s" n
+ | StringList n -> next (); pr "char * const* const %s" n
+ | Bool n -> next (); pr "int %s" n
+ | Int n -> next (); pr "int %s" n
+ | FileIn n
+ | FileOut n ->
+ if not in_daemon then (next (); pr "const char *%s" n)
+ ) (snd style);
+ );
+ pr ")";
+ if semicolon then pr ";";
+ if newline then pr "\n"
+
+(* Generate C call arguments, eg "(handle, foo, bar)" *)
+and generate_call_args ?handle args =
+ pr "(";
+ let comma = ref false in
+ (match handle with
+ | None -> ()
+ | Some handle -> pr "%s" handle; comma := true
+ );
+ List.iter (
+ fun arg ->
+ if !comma then pr ", ";
+ comma := true;
+ pr "%s" (name_of_argt arg)
+ ) args;
+ pr ")"
+
+(* Generate the OCaml bindings interface. *)
+and generate_ocaml_mli () =
+ generate_header OCamlStyle LGPLv2;
+
+ pr "\
+(** For API documentation you should refer to the C API
+ in the guestfs(3) manual page. The OCaml API uses almost
+ exactly the same calls. *)
+
+type t
+(** A [guestfs_h] handle. *)
+
+exception Error of string
+(** This exception is raised when there is an error. *)
+
+val create : unit -> t
+
+val close : t -> unit
+(** Handles are closed by the garbage collector when they become
+ unreferenced, but callers can also call this in order to
+ provide predictable cleanup. *)
+
+";
+ generate_ocaml_lvm_structure_decls ();
+
+ generate_ocaml_stat_structure_decls ();
+
+ (* The actions. *)
+ List.iter (
+ fun (name, style, _, _, _, shortdesc, _) ->
+ generate_ocaml_prototype name style;
+ pr "(** %s *)\n" shortdesc;
+ pr "\n"
+ ) all_functions
+
+(* Generate the OCaml bindings implementation. *)
+and generate_ocaml_ml () =
+ generate_header OCamlStyle LGPLv2;
+
+ pr "\
+type t
+exception Error of string
+external create : unit -> t = \"ocaml_guestfs_create\"
+external close : t -> unit = \"ocaml_guestfs_close\"
+
+let () =
+ Callback.register_exception \"ocaml_guestfs_error\" (Error \"\")
+
+";
+
+ generate_ocaml_lvm_structure_decls ();
+
+ generate_ocaml_stat_structure_decls ();
+
+ (* The actions. *)
+ List.iter (
+ fun (name, style, _, _, _, shortdesc, _) ->
+ generate_ocaml_prototype ~is_external:true name style;
+ ) all_functions
+
+(* Generate the OCaml bindings C implementation. *)
+and generate_ocaml_c () =
+ generate_header CStyle LGPLv2;
+
+ pr "\
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <caml/config.h>
+#include <caml/alloc.h>
+#include <caml/callback.h>
+#include <caml/fail.h>
+#include <caml/memory.h>
+#include <caml/mlvalues.h>
+#include <caml/signals.h>
+
+#include <guestfs.h>
+
+#include \"guestfs_c.h\"
+
+/* Copy a hashtable of string pairs into an assoc-list. We return
+ * the list in reverse order, but hashtables aren't supposed to be
+ * ordered anyway.
+ */
+static CAMLprim value
+copy_table (char * const * argv)
+{
+ CAMLparam0 ();
+ CAMLlocal5 (rv, pairv, kv, vv, cons);
+ int i;
+
+ rv = Val_int (0);
+ for (i = 0; argv[i] != NULL; i += 2) {
+ kv = caml_copy_string (argv[i]);
+ vv = caml_copy_string (argv[i+1]);
+ pairv = caml_alloc (2, 0);
+ Store_field (pairv, 0, kv);
+ Store_field (pairv, 1, vv);
+ cons = caml_alloc (2, 0);
+ Store_field (cons, 1, rv);
+ rv = cons;
+ Store_field (cons, 0, pairv);
+ }
+
+ CAMLreturn (rv);
+}
+
+";
+
+ (* LVM struct copy functions. *)
+ List.iter (
+ fun (typ, cols) ->
+ let has_optpercent_col =
+ List.exists (function (_, `OptPercent) -> true | _ -> false) cols in
+
+ pr "static CAMLprim value\n";
+ pr "copy_lvm_%s (const struct guestfs_lvm_%s *%s)\n" typ typ typ;
+ pr "{\n";
+ pr " CAMLparam0 ();\n";
+ if has_optpercent_col then
+ pr " CAMLlocal3 (rv, v, v2);\n"
+ else
+ 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, `String ->
+ pr " v = caml_copy_string (%s->%s);\n" typ name
+ | name, `UUID ->
+ pr " v = caml_alloc_string (32);\n";
+ pr " memcpy (String_val (v), %s->%s, 32);\n" typ name
+ | name, `Bytes
+ | name, `Int ->
+ pr " v = caml_copy_int64 (%s->%s);\n" typ name
+ | name, `OptPercent ->
+ pr " if (%s->%s >= 0) { /* Some %s */\n" typ name name;
+ pr " v2 = caml_copy_double (%s->%s);\n" typ name;
+ pr " v = caml_alloc (1, 0);\n";
+ pr " Store_field (v, 0, v2);\n";
+ pr " } else /* None */\n";
+ pr " v = Val_int (0);\n";
+ );
+ pr " Store_field (rv, %d, v);\n" i
+ ) cols;
+ pr " CAMLreturn (rv);\n";
+ pr "}\n";
+ pr "\n";
+
+ pr "static CAMLprim value\n";
+ pr "copy_lvm_%s_list (const struct guestfs_lvm_%s_list *%ss)\n"
+ typ typ typ;
+ pr "{\n";
+ pr " CAMLparam0 ();\n";
+ pr " CAMLlocal2 (rv, v);\n";
+ pr " int i;\n";
+ pr "\n";
+ pr " if (%ss->len == 0)\n" typ;
+ pr " CAMLreturn (Atom (0));\n";
+ pr " else {\n";
+ pr " rv = caml_alloc (%ss->len, 0);\n" typ;
+ pr " for (i = 0; i < %ss->len; ++i) {\n" typ;
+ pr " v = copy_lvm_%s (&%ss->val[i]);\n" typ typ;
+ pr " caml_modify (&Field (rv, i), v);\n";
+ pr " }\n";
+ pr " CAMLreturn (rv);\n";
+ pr " }\n";
+ pr "}\n";
+ 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 =
+ "gv" :: List.map (fun arg -> name_of_argt arg ^ "v") (snd style) in
+
+ pr "CAMLprim value\n";
+ pr "ocaml_guestfs_%s (value %s" name (List.hd params);
+ List.iter (pr ", value %s") (List.tl params);
+ pr ")\n";
+ pr "{\n";
+
+ (match params with
+ | p1 :: p2 :: p3 :: p4 :: p5 :: rest ->
+ pr " CAMLparam5 (%s);\n" (String.concat ", " [p1; p2; p3; p4; p5]);
+ pr " CAMLxparam%d (%s);\n"
+ (List.length rest) (String.concat ", " rest)
+ | ps ->
+ pr " CAMLparam%d (%s);\n" (List.length ps) (String.concat ", " ps)
+ );
+ pr " CAMLlocal1 (rv);\n";
+ pr "\n";
+
+ pr " guestfs_h *g = Guestfs_val (gv);\n";
+ pr " if (g == NULL)\n";
+ pr " caml_failwith (\"%s: used handle after closing it\");\n" name;
+ pr "\n";
+
+ List.iter (
+ function
+ | String n
+ | FileIn n
+ | FileOut n ->
+ pr " const char *%s = String_val (%sv);\n" n n
+ | OptString n ->
+ pr " const char *%s =\n" n;
+ pr " %sv != Val_int (0) ? String_val (Field (%sv, 0)) : NULL;\n"
+ n n
+ | StringList n ->
+ pr " char **%s = ocaml_guestfs_strings_val (%sv);\n" n n
+ | Bool n ->
+ pr " int %s = Bool_val (%sv);\n" n n
+ | Int n ->
+ pr " int %s = Int_val (%sv);\n" n n
+ ) (snd style);
+ let error_code =
+ match fst style with
+ | RErr -> pr " int r;\n"; "-1"
+ | RInt _ -> pr " int r;\n"; "-1"
+ | RInt64 _ -> pr " int64_t r;\n"; "-1"
+ | RBool _ -> pr " int r;\n"; "-1"
+ | RConstString _ -> pr " const char *r;\n"; "NULL"
+ | RString _ -> pr " char *r;\n"; "NULL"
+ | RStringList _ ->
+ pr " int i;\n";
+ pr " char **r;\n";
+ "NULL"
+ | RIntBool _ ->
+ pr " struct guestfs_int_bool *r;\n"; "NULL"
+ | RPVList _ ->
+ pr " struct guestfs_lvm_pv_list *r;\n"; "NULL"
+ | RVGList _ ->
+ pr " struct guestfs_lvm_vg_list *r;\n"; "NULL"
+ | RLVList _ ->
+ 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"
+ | RHashtable _ ->
+ pr " int i;\n";
+ pr " char **r;\n";
+ "NULL" in
+ pr "\n";
+
+ pr " caml_enter_blocking_section ();\n";
+ pr " r = guestfs_%s " name;
+ generate_call_args ~handle:"g" (snd style);
+ pr ";\n";
+ pr " caml_leave_blocking_section ();\n";
+
+ List.iter (
+ function
+ | StringList n ->
+ pr " ocaml_guestfs_free_strings (%s);\n" n;
+ | String _ | OptString _ | Bool _ | Int _ | FileIn _ | FileOut _ -> ()
+ ) (snd style);
+
+ pr " if (r == %s)\n" error_code;
+ pr " ocaml_guestfs_raise_error (g, \"%s\");\n" name;
+ pr "\n";
+
+ (match fst style with
+ | RErr -> pr " rv = Val_unit;\n"
+ | RInt _ -> pr " rv = Val_int (r);\n"
+ | RInt64 _ ->
+ pr " rv = caml_copy_int64 (r);\n"
+ | RBool _ -> pr " rv = Val_bool (r);\n"
+ | RConstString _ -> pr " rv = caml_copy_string (r);\n"
+ | RString _ ->
+ pr " rv = caml_copy_string (r);\n";
+ pr " free (r);\n"
+ | RStringList _ ->
+ pr " rv = caml_copy_string_array ((const char **) r);\n";
+ pr " for (i = 0; r[i] != NULL; ++i) free (r[i]);\n";
+ pr " free (r);\n"
+ | RIntBool _ ->
+ pr " rv = caml_alloc (2, 0);\n";
+ pr " Store_field (rv, 0, Val_int (r->i));\n";
+ pr " Store_field (rv, 1, Val_bool (r->b));\n";
+ pr " guestfs_free_int_bool (r);\n";
+ | RPVList _ ->
+ pr " rv = copy_lvm_pv_list (r);\n";
+ pr " guestfs_free_lvm_pv_list (r);\n";
+ | RVGList _ ->
+ pr " rv = copy_lvm_vg_list (r);\n";
+ pr " guestfs_free_lvm_vg_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";
+ | RHashtable _ ->
+ pr " rv = copy_table (r);\n";
+ pr " for (i = 0; r[i] != NULL; ++i) free (r[i]);\n";
+ pr " free (r);\n";
+ );
+
+ pr " CAMLreturn (rv);\n";
+ pr "}\n";
+ pr "\n";
+
+ if List.length params > 5 then (
+ pr "CAMLprim value\n";
+ pr "ocaml_guestfs_%s_byte (value *argv, int argn)\n" name;
+ pr "{\n";
+ pr " return ocaml_guestfs_%s (argv[0]" name;
+ iteri (fun i _ -> pr ", argv[%d]" i) (List.tl params);
+ pr ");\n";
+ pr "}\n";
+ pr "\n"
+ )
+ ) all_functions
+
+and generate_ocaml_lvm_structure_decls () =
+ List.iter (
+ fun (typ, cols) ->
+ pr "type lvm_%s = {\n" typ;
+ List.iter (
+ function
+ | name, `String -> pr " %s : string;\n" name
+ | name, `UUID -> pr " %s : string;\n" name
+ | name, `Bytes -> pr " %s : int64;\n" name
+ | name, `Int -> pr " %s : int64;\n" name
+ | name, `OptPercent -> pr " %s : float option;\n" name
+ ) cols;
+ pr "}\n";
+ 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;
+ List.iter (
+ function
+ | String _ | FileIn _ | FileOut _ -> pr "string -> "
+ | OptString _ -> pr "string option -> "
+ | StringList _ -> pr "string array -> "
+ | Bool _ -> pr "bool -> "
+ | Int _ -> pr "int -> "
+ ) (snd style);
+ (match fst style with
+ | RErr -> pr "unit" (* all errors are turned into exceptions *)
+ | RInt _ -> pr "int"
+ | RInt64 _ -> pr "int64"
+ | RBool _ -> pr "bool"
+ | RConstString _ -> pr "string"
+ | RString _ -> pr "string"
+ | RStringList _ -> pr "string array"
+ | RIntBool _ -> pr "int * bool"
+ | RPVList _ -> pr "lvm_pv array"
+ | RVGList _ -> pr "lvm_vg array"
+ | RLVList _ -> pr "lvm_lv array"
+ | RStat _ -> pr "stat"
+ | RStatVFS _ -> pr "statvfs"
+ | RHashtable _ -> pr "(string * string) list"
+ );
+ if is_external then (
+ pr " = ";
+ if List.length (snd style) + 1 > 5 then
+ pr "\"ocaml_guestfs_%s_byte\" " name;
+ pr "\"ocaml_guestfs_%s\"" name
+ );
+ pr "\n"
+
+(* Generate Perl xs code, a sort of crazy variation of C with macros. *)
+and generate_perl_xs () =
+ generate_header CStyle LGPLv2;
+
+ pr "\
+#include \"EXTERN.h\"
+#include \"perl.h\"
+#include \"XSUB.h\"
+
+#include <guestfs.h>
+
+#ifndef PRId64
+#define PRId64 \"lld\"
+#endif
+
+static SV *
+my_newSVll(long long val) {
+#ifdef USE_64_BIT_ALL
+ return newSViv(val);
+#else
+ char buf[100];
+ int len;
+ len = snprintf(buf, 100, \"%%\" PRId64, val);
+ return newSVpv(buf, len);
+#endif
+}
+
+#ifndef PRIu64
+#define PRIu64 \"llu\"
+#endif
+
+static SV *
+my_newSVull(unsigned long long val) {
+#ifdef USE_64_BIT_ALL
+ return newSVuv(val);
+#else
+ char buf[100];
+ int len;
+ len = snprintf(buf, 100, \"%%\" PRIu64, val);
+ return newSVpv(buf, len);
+#endif
+}
+
+/* http://www.perlmonks.org/?node_id=680842 */
+static char **
+XS_unpack_charPtrPtr (SV *arg) {
+ char **ret;
+ AV *av;
+ I32 i;
+
+ if (!arg || !SvOK (arg) || !SvROK (arg) || SvTYPE (SvRV (arg)) != SVt_PVAV) {
+ croak (\"array reference expected\");
+ }
+
+ av = (AV *)SvRV (arg);
+ ret = (char **)malloc (av_len (av) + 1 + 1);
+
+ for (i = 0; i <= av_len (av); i++) {
+ SV **elem = av_fetch (av, i, 0);
+
+ if (!elem || !*elem)
+ croak (\"missing element in list\");
+
+ ret[i] = SvPV_nolen (*elem);
+ }
+
+ ret[i] = NULL;
+
+ return ret;
+}
+
+MODULE = Sys::Guestfs PACKAGE = Sys::Guestfs
+
+guestfs_h *
+_create ()
+ CODE:
+ RETVAL = guestfs_create ();
+ if (!RETVAL)
+ croak (\"could not create guestfs handle\");
+ guestfs_set_error_handler (RETVAL, NULL, NULL);
+ OUTPUT:
+ RETVAL
+
+void
+DESTROY (g)
+ guestfs_h *g;
+ PPCODE:
+ guestfs_close (g);
+
+";
+
+ List.iter (
+ fun (name, style, _, _, _, _, _) ->
+ (match fst style with
+ | RErr -> pr "void\n"
+ | RInt _ -> pr "SV *\n"
+ | RInt64 _ -> pr "SV *\n"
+ | RBool _ -> pr "SV *\n"
+ | RConstString _ -> pr "SV *\n"
+ | RString _ -> pr "SV *\n"
+ | RStringList _
+ | RIntBool _
+ | RPVList _ | RVGList _ | RLVList _
+ | RStat _ | RStatVFS _
+ | RHashtable _ ->
+ pr "void\n" (* all lists returned implictly on the stack *)
+ );
+ (* Call and arguments. *)
+ pr "%s " name;
+ generate_call_args ~handle:"g" (snd style);
+ pr "\n";
+ pr " guestfs_h *g;\n";
+ List.iter (
+ function
+ | String n | FileIn n | FileOut n -> pr " char *%s;\n" n
+ | OptString n -> pr " char *%s;\n" n
+ | StringList n -> pr " char **%s;\n" n
+ | Bool n -> pr " int %s;\n" n
+ | Int n -> pr " int %s;\n" n
+ ) (snd style);
+
+ let do_cleanups () =
+ List.iter (
+ function
+ | String _ | OptString _ | Bool _ | Int _
+ | FileIn _ | FileOut _ -> ()
+ | StringList n -> pr " free (%s);\n" n
+ ) (snd style)
+ in
+
+ (* Code. *)
+ (match fst style with
+ | RErr ->
+ pr "PREINIT:\n";
+ pr " int r;\n";
+ pr " PPCODE:\n";
+ pr " r = guestfs_%s " name;
+ generate_call_args ~handle:"g" (snd style);
+ pr ";\n";
+ do_cleanups ();
+ pr " if (r == -1)\n";
+ pr " croak (\"%s: %%s\", guestfs_last_error (g));\n" name;
+ | RInt n
+ | RBool n ->
+ pr "PREINIT:\n";
+ pr " int %s;\n" n;
+ pr " CODE:\n";
+ pr " %s = guestfs_%s " n name;
+ generate_call_args ~handle:"g" (snd style);
+ pr ";\n";
+ do_cleanups ();
+ pr " if (%s == -1)\n" n;
+ pr " croak (\"%s: %%s\", guestfs_last_error (g));\n" name;
+ pr " RETVAL = newSViv (%s);\n" n;
+ pr " OUTPUT:\n";
+ pr " RETVAL\n"
+ | RInt64 n ->
+ pr "PREINIT:\n";
+ pr " int64_t %s;\n" n;
+ pr " CODE:\n";
+ pr " %s = guestfs_%s " n name;
+ generate_call_args ~handle:"g" (snd style);
+ pr ";\n";
+ do_cleanups ();
+ pr " if (%s == -1)\n" n;
+ pr " croak (\"%s: %%s\", guestfs_last_error (g));\n" name;
+ pr " RETVAL = my_newSVll (%s);\n" n;
+ pr " OUTPUT:\n";
+ pr " RETVAL\n"
+ | RConstString n ->
+ pr "PREINIT:\n";
+ pr " const char *%s;\n" n;
+ pr " CODE:\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 " RETVAL = newSVpv (%s, 0);\n" n;
+ pr " OUTPUT:\n";
+ pr " RETVAL\n"