X-Git-Url: http://git.annexia.org/?p=libguestfs.git;a=blobdiff_plain;f=src%2Fgenerator.ml;h=04445ee6c7177731bfe3e799ae13ff4d0fed7103;hp=06a638e62e8bd45654b720257e39844dae66f342;hb=36f9dac1a2530b575dab9226f6ddd85e6e8c8590;hpb=99f68f259f92eee884c6c7396f61b9c16e2bf354 diff --git a/src/generator.ml b/src/generator.ml index 06a638e..04445ee 100755 --- a/src/generator.ml +++ b/src/generator.ml @@ -33,6 +33,7 @@ *) #load "unix.cma";; +#load "str.cma";; open Printf @@ -43,9 +44,15 @@ and ret = *) | RErr (* "RInt" as a return value means an int which is -1 for error - * or any value >= 0 on success. + * or any value >= 0 on success. Only use this for smallish + * positive ints (0 <= i < 2^30). *) | RInt of string + (* "RInt64" is the same as RInt, but is guaranteed to be able + * to return a full 64 bit value, _except_ that -1 means error + * (so -1 cannot be a valid, non-error return value). + *) + | RInt64 of string (* "RBool" is a bool return value which can be true/false or * -1 for error. *) @@ -65,6 +72,18 @@ and ret = | RPVList of string | RVGList of string | RLVList of string + (* Stat buffers. *) + | RStat of string + | RStatVFS of string + (* Key-value pairs of untyped strings. Turns into a hashtable or + * dictionary in languages which support it. DON'T use this as a + * general "bucket" for results. Prefer a stronger typed return + * value if one is available, or write a custom struct. Don't use + * this if the list could potentially be very long, since it is + * inefficient. Keys should be unique. NULLs are not permitted. + *) + | RHashtable of string + and args = argt list (* Function parameters, guestfs handle is implicit. *) (* Note in future we should allow a "variable args" parameter as @@ -77,77 +96,117 @@ and args = argt list (* Function parameters, guestfs handle is implicit. *) and argt = | String of string (* const char *name, cannot be NULL *) | OptString of string (* const char *name, may be NULL *) + | StringList of string(* list of strings (each string cannot be NULL) *) | Bool of string (* boolean *) | Int of string (* int (smallish ints, signed, <= 31 bits) *) + (* These are treated as filenames (simple string parameters) in + * the C API and bindings. But in the RPC protocol, we transfer + * the actual file content up to or down from the daemon. + * FileIn: local machine -> daemon (in request) + * FileOut: daemon -> local machine (in reply) + * In guestfish (only), the special name "-" means read from + * stdin or write to stdout. + *) + | FileIn of string + | FileOut of string type flags = | ProtocolLimitWarning (* display warning about protocol size limits *) + | DangerWillRobinson (* flags particularly dangerous commands *) | FishAlias of string (* provide an alias for this cmd in guestfish *) | FishAction of string (* call this function in guestfish *) | NotInFish (* do not export via guestfish *) +let protocol_limit_warning = + "Because of the message protocol, there is a transfer limit +of somewhere between 2MB and 4MB. To transfer large files you should use +FTP." + +let danger_will_robinson = + "B." + (* You can supply zero or as many tests as you want per API call. * - * Note that the test environment has 3 block devices, of size 10M, 20M - * and 30M (respectively /dev/sda, /dev/sdb, /dev/sdc). To run the - * tests in a reasonable amount of time, the virtual machine and - * block devices are reused between tests. So don't try testing - * kill_subprocess :-x + * Note that the test environment has 3 block devices, of size 500MB, + * 50MB and 10MB (respectively /dev/sda, /dev/sdb, /dev/sdc). + * Note for partitioning purposes, the 500MB device has 63 cylinders. + * + * To be able to run the tests in a reasonable amount of time, + * the virtual machine and block devices are reused between tests. + * So don't try testing kill_subprocess :-x + * + * Between each test we umount-all and lvm-remove-all (except InitNone). * * Don't assume anything about the previous contents of the block * devices. Use 'Init*' to create some initial scenarios. *) -type tests = test list +type tests = (test_init * test) list and test = (* Run the command sequence and just expect nothing to fail. *) - | TestRun of test_init * seq + | TestRun of seq (* Run the command sequence and expect the output of the final * command to be the string. *) - | TestOutput of test_init * seq * string + | TestOutput of seq * string (* Run the command sequence and expect the output of the final * command to be the list of strings. *) - | TestOutputList of test_init * seq * string list + | TestOutputList of seq * string list (* Run the command sequence and expect the output of the final * command to be the integer. *) - | TestOutputInt of test_init * seq * int + | TestOutputInt of seq * int (* Run the command sequence and expect the output of the final * command to be a true value (!= 0 or != NULL). *) - | TestOutputTrue of test_init * seq + | TestOutputTrue of seq (* Run the command sequence and expect the output of the final * command to be a false value (== 0 or == NULL, but not an error). *) - | TestOutputFalse of test_init * seq + | TestOutputFalse of seq (* Run the command sequence and expect the output of the final * command to be a list of the given length (but don't care about * content). *) - | TestOutputLength of test_init * seq * int + | TestOutputLength of seq * int + (* Run the command sequence and expect the output of the final + * command to be a structure. + *) + | TestOutputStruct of seq * test_field_compare list (* Run the command sequence and expect the final command (only) * to fail. *) - | TestLastFail of test_init * seq + | TestLastFail of seq + +and test_field_compare = + | CompareWithInt of string * int + | CompareWithString of string * string + | CompareFieldsIntEq of string * string + | CompareFieldsStrEq of string * string (* Some initial scenarios for testing. *) and test_init = - (* Do nothing, block devices could contain random stuff. *) + (* Do nothing, block devices could contain random stuff including + * LVM PVs, and some filesystems might be mounted. This is usually + * a bad idea. + *) | InitNone + (* Block devices are empty and no filesystems are mounted. *) + | InitEmpty (* /dev/sda contains a single partition /dev/sda1, which is formatted * as ext2, empty [except for lost+found] and mounted on /. * /dev/sdb and /dev/sdc may have random content. * No LVM. *) - | InitEmpty + | InitBasicFS (* /dev/sda: * /dev/sda1 (is a PV): - * /dev/VG/LV: + * /dev/VG/LV (size 8MB): * formatted as ext2, empty [except for lost+found], mounted on / * /dev/sdb and /dev/sdc may have random content. *) - | InitEmptyLVM + | InitBasicFSonLVM (* Sequence of commands for testing. *) and seq = cmd list @@ -226,6 +285,32 @@ The first character of C string must be a C<-> (dash). C can be NULL."); + ("set_qemu", (RErr, [String "qemu"]), -1, [FishAlias "qemu"], + [], + "set the qemu binary", + "\ +Set the qemu binary that we will use. + +The default is chosen when the library was compiled by the +configure script. + +You can also override this by setting the C +environment variable. + +The string C is stashed in the libguestfs handle, so the caller +must make sure it remains valid for the lifetime of the handle. + +Setting C to C restores the default qemu binary."); + + ("get_qemu", (RConstString "qemu", []), -1, [], + [], + "get the qemu binary", + "\ +Return the current qemu binary. + +This is always non-NULL. If it wasn't set already, then this will +return the default qemu binary name."); + ("set_path", (RErr, [String "path"]), -1, [FishAlias "path"], [], "set the search path", @@ -254,8 +339,12 @@ return the default path."); "set autosync mode", "\ If C is true, this enables autosync. Libguestfs will make a -best effort attempt to run C when the handle is closed -(also if the program exits without closing handles)."); +best effort attempt to run C followed by +C when the handle is closed +(also if the program exits without closing handles). + +This is disabled by default (except in guestfish where it is +enabled by default)."); ("get_autosync", (RBool "autosync", []), -1, [], [], @@ -276,14 +365,77 @@ C is defined and set to C<1>."); [], "get verbose mode", "\ -This returns the verbose messages flag.") +This returns the verbose messages flag."); + + ("is_ready", (RBool "ready", []), -1, [], + [], + "is ready to accept commands", + "\ +This returns true iff this handle is ready to accept commands +(in the C state). + +For more information on states, see L."); + + ("is_config", (RBool "config", []), -1, [], + [], + "is in configuration state", + "\ +This returns true iff this handle is being configured +(in the C state). + +For more information on states, see L."); + + ("is_launching", (RBool "launching", []), -1, [], + [], + "is launching subprocess", + "\ +This returns true iff this handle is launching the subprocess +(in the C state). + +For more information on states, see L."); + + ("is_busy", (RBool "busy", []), -1, [], + [], + "is busy processing a command", + "\ +This returns true iff this handle is busy processing a command +(in the C state). + +For more information on states, see L."); + + ("get_state", (RInt "state", []), -1, [], + [], + "get the current state", + "\ +This returns the current state as an opaque integer. This is +only useful for printing debug and internal error messages. + +For more information on states, see L."); + + ("set_busy", (RErr, []), -1, [NotInFish], + [], + "set state to busy", + "\ +This sets the state to C. This is only used when implementing +actions using the low-level API. + +For more information on states, see L."); + + ("set_ready", (RErr, []), -1, [NotInFish], + [], + "set state to ready", + "\ +This sets the state to C. This is only used when implementing +actions using the low-level API. + +For more information on states, see L."); + ] let daemon_functions = [ ("mount", (RErr, [String "device"; String "mountpoint"]), 1, [], - [TestOutput ( - InitNone, - [["sfdisk"]; + [InitEmpty, TestOutput ( + [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ","]; ["mkfs"; "ext2"; "/dev/sda1"]; ["mount"; "/dev/sda1"; "/"]; ["write_file"; "/new"; "new file contents"; "0"]; @@ -308,7 +460,7 @@ The filesystem options C and C are set with this call, in order to improve reliability."); ("sync", (RErr, []), 2, [], - [ TestRun (InitNone, [["sync"]])], + [ InitEmpty, TestRun [["sync"]]], "sync disks, writes are flushed through to the disk image", "\ This syncs the disk, so that any writes are flushed through to the @@ -318,8 +470,7 @@ You should always call this if you have modified a disk image, before closing the handle."); ("touch", (RErr, [String "path"]), 3, [], - [TestOutputTrue ( - InitEmpty, + [InitBasicFS, TestOutputTrue ( [["touch"; "/new"]; ["exists"; "/new"]])], "update file timestamps or create a new file", @@ -329,8 +480,7 @@ update the timestamps on a file, or, if the file does not exist, to create a new zero-length file."); ("cat", (RString "content", [String "path"]), 4, [ProtocolLimitWarning], - [TestOutput ( - InitEmpty, + [InitBasicFS, TestOutput ( [["write_file"; "/new"; "new file contents"; "0"]; ["cat"; "/new"]], "new file contents")], "list the contents of a file", @@ -339,7 +489,7 @@ Return the contents of the file named C. Note that this function cannot correctly handle binary files (specifically, files containing C<\\0> character which is treated -as end of string). For those you need to use the C +as end of string). For those you need to use the C function which has a more complex interface."); ("ll", (RString "listing", [String "directory"]), 5, [], @@ -355,8 +505,7 @@ This command is mostly useful for interactive sessions. It is I intended that you try to parse the output string."); ("ls", (RStringList "listing", [String "directory"]), 6, [], - [TestOutputList ( - InitEmpty, + [InitBasicFS, TestOutputList ( [["touch"; "/new"]; ["touch"; "/newer"]; ["touch"; "/newest"]; @@ -371,8 +520,7 @@ This command is mostly useful for interactive sessions. Programs should probably use C instead."); ("list_devices", (RStringList "devices", []), 7, [], - [TestOutputList ( - InitNone, + [InitEmpty, TestOutputList ( [["list_devices"]], ["/dev/sda"; "/dev/sdb"; "/dev/sdc"])], "list the block devices", "\ @@ -381,12 +529,10 @@ List all the block devices. The full block device names are returned, eg. C"); ("list_partitions", (RStringList "partitions", []), 8, [], - [TestOutputList ( - InitEmpty, + [InitBasicFS, TestOutputList ( [["list_partitions"]], ["/dev/sda1"]); - TestOutputList ( - InitEmpty, - [["sfdisk"]; + InitEmpty, TestOutputList ( + [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ",10 ,20 ,"]; ["list_partitions"]], ["/dev/sda1"; "/dev/sda2"; "/dev/sda3"])], "list the partitions", "\ @@ -398,12 +544,10 @@ This does not return logical volumes. For that you will need to call C."); ("pvs", (RStringList "physvols", []), 9, [], - [TestOutputList ( - InitEmptyLVM, + [InitBasicFSonLVM, TestOutputList ( [["pvs"]], ["/dev/sda1"]); - TestOutputList ( - InitNone, - [["sfdisk"]; + InitEmpty, TestOutputList ( + [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ",10 ,20 ,"]; ["pvcreate"; "/dev/sda1"]; ["pvcreate"; "/dev/sda2"]; ["pvcreate"; "/dev/sda3"]; @@ -419,12 +563,10 @@ PVs (eg. C). See also C."); ("vgs", (RStringList "volgroups", []), 10, [], - [TestOutputList ( - InitEmptyLVM, + [InitBasicFSonLVM, TestOutputList ( [["vgs"]], ["VG"]); - TestOutputList ( - InitNone, - [["sfdisk"]; + InitEmpty, TestOutputList ( + [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ",10 ,20 ,"]; ["pvcreate"; "/dev/sda1"]; ["pvcreate"; "/dev/sda2"]; ["pvcreate"; "/dev/sda3"]; @@ -442,21 +584,19 @@ detected (eg. C). See also C."); ("lvs", (RStringList "logvols", []), 11, [], - [TestOutputList ( - InitEmptyLVM, + [InitBasicFSonLVM, TestOutputList ( [["lvs"]], ["/dev/VG/LV"]); - TestOutputList ( - InitNone, - [["sfdisk"]; + 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"; "5000"]; - ["lvcreate"; "LV2"; "VG1"; "5000"]; - ["lvcreate"; "LV3"; "VG2"; "5000"]; - ["lvs"]], ["LV1"; "LV2"; "LV3"])], + ["lvcreate"; "LV1"; "VG1"; "50"]; + ["lvcreate"; "LV2"; "VG1"; "50"]; + ["lvcreate"; "LV3"; "VG2"; "50"]; + ["lvs"]], ["/dev/VG1/LV1"; "/dev/VG1/LV2"; "/dev/VG2/LV3"])], "list the LVM logical volumes (LVs)", "\ List all the logical volumes detected. This is the equivalent @@ -468,39 +608,31 @@ This returns a list of the logical volume device names See also C."); ("pvs_full", (RPVList "physvols", []), 12, [], - [TestOutputLength ( - InitEmptyLVM, - [["pvs"]], 1)], + [], (* XXX how to test? *) "list the LVM physical volumes (PVs)", "\ List all the physical volumes detected. This is the equivalent of the L command. The \"full\" version includes all fields."); ("vgs_full", (RVGList "volgroups", []), 13, [], - [TestOutputLength ( - InitEmptyLVM, - [["pvs"]], 1)], + [], (* XXX how to test? *) "list the LVM volume groups (VGs)", "\ List all the volumes groups detected. This is the equivalent of the L command. The \"full\" version includes all fields."); ("lvs_full", (RLVList "logvols", []), 14, [], - [TestOutputLength ( - InitEmptyLVM, - [["pvs"]], 1)], + [], (* XXX how to test? *) "list the LVM logical volumes (LVs)", "\ List all the logical volumes detected. This is the equivalent of the L command. The \"full\" version includes all fields."); ("read_lines", (RStringList "lines", [String "path"]), 15, [], - [TestOutputList ( - InitEmpty, + [InitBasicFS, TestOutputList ( [["write_file"; "/new"; "line1\r\nline2\nline3"; "0"]; ["read_lines"; "/new"]], ["line1"; "line2"; "line3"]); - TestOutputList ( - InitEmpty, + InitBasicFS, TestOutputList ( [["write_file"; "/new"; ""; "0"]; ["read_lines"; "/new"]], [])], "read file as lines", @@ -675,45 +807,38 @@ This is just a shortcut for listing C C and sorting the resulting nodes into alphabetical order."); ("rm", (RErr, [String "path"]), 29, [], - [TestRun ( - InitEmpty, + [InitBasicFS, TestRun [["touch"; "/new"]; - ["rm"; "/new"]]); - TestLastFail ( - InitEmpty, - [["rm"; "/new"]]); - TestLastFail ( - InitEmpty, + ["rm"; "/new"]]; + InitBasicFS, TestLastFail + [["rm"; "/new"]]; + InitBasicFS, TestLastFail [["mkdir"; "/new"]; - ["rm"; "/new"]])], + ["rm"; "/new"]]], "remove a file", "\ Remove the single file C."); ("rmdir", (RErr, [String "path"]), 30, [], - [TestRun ( - InitEmpty, + [InitBasicFS, TestRun [["mkdir"; "/new"]; - ["rmdir"; "/new"]]); - TestLastFail ( - InitEmpty, - [["rmdir"; "/new"]]); - TestLastFail ( - InitEmpty, + ["rmdir"; "/new"]]; + InitBasicFS, TestLastFail + [["rmdir"; "/new"]]; + InitBasicFS, TestLastFail [["touch"; "/new"]; - ["rmdir"; "/new"]])], + ["rmdir"; "/new"]]], "remove a directory", "\ Remove the single directory C."); ("rm_rf", (RErr, [String "path"]), 31, [], - [TestOutputFalse ( - InitEmpty, + [InitBasicFS, TestOutputFalse [["mkdir"; "/new"]; ["mkdir"; "/new/foo"]; ["touch"; "/new/foo/bar"]; ["rm_rf"; "/new"]; - ["exists"; "/new"]])], + ["exists"; "/new"]]], "remove a file or directory recursively", "\ Remove the file or directory C, recursively removing the @@ -721,27 +846,25 @@ contents if its a directory. This is like the C shell command."); ("mkdir", (RErr, [String "path"]), 32, [], - [TestOutputTrue ( - InitEmpty, + [InitBasicFS, TestOutputTrue [["mkdir"; "/new"]; - ["is_dir"; "/new"]])], + ["is_dir"; "/new"]]; + InitBasicFS, TestLastFail + [["mkdir"; "/new/foo/bar"]]], "create a directory", "\ Create a directory named C."); ("mkdir_p", (RErr, [String "path"]), 33, [], - [TestOutputTrue ( - InitEmpty, + [InitBasicFS, TestOutputTrue [["mkdir_p"; "/new/foo/bar"]; - ["is_dir"; "/new/foo/bar"]]); - TestOutputTrue ( - InitEmpty, + ["is_dir"; "/new/foo/bar"]]; + InitBasicFS, TestOutputTrue [["mkdir_p"; "/new/foo/bar"]; - ["is_dir"; "/new/foo"]]); - TestOutputTrue ( - InitEmpty, + ["is_dir"; "/new/foo"]]; + InitBasicFS, TestOutputTrue [["mkdir_p"; "/new/foo/bar"]; - ["is_dir"; "/new"]])], + ["is_dir"; "/new"]]], "create a directory and parents", "\ Create a directory named C, creating any parent directories @@ -763,2090 +886,5417 @@ Change the file owner to C and group to C. 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)."); -] -let all_functions = non_daemon_functions @ daemon_functions + ("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 if and only if there is a file, directory +(or anything) with the given C name. -(* 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 +See also C, C, C."); -(* 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; -] + ("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 if and only if there is a file +with the given C name. Note that it returns false for +other objects like directories. -(* 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 +See also C."); -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 + ("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 if and only if there is a directory +with the given C name. Note that it returns false for +other objects like files. -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 +See also C."); -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 - ) + ("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, +where C should usually be a partition name such +as C."); -let rec find_map f = function - | [] -> raise Not_found - | x :: xs -> - match f x with - | Some y -> y - | None -> find_map f xs + ("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 +from the non-empty list of physical volumes C."); -let iteri f xs = - let rec loop i = function - | [] -> () - | x :: xs -> f i x; loop (i+1) xs - in - loop 0 xs + ("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 +on the volume group C, with C megabytes."); -let name_of_argt = function String n | OptString n | Bool n | Int n -> n + ("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 (usually a partition +of LVM logical volume). The filesystem type is C, for +example C."); -(* 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 + ("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 program for creating +partitions on block devices. - (* 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; +C should be a block device, for example C. - (* 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 - in +C, C and C 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. - (match fst style with - | RErr -> () - | RInt n | RBool n | RConstString n | RString n - | RStringList n | RPVList n | RVGList n | RLVList 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; +C is a list of lines that we feed to C. For more +information refer to the L manpage. - (* 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; +To create a single partition occupying the whole disk, you would +pass C as a single element list, when the single element being +the string C<,> (comma)."); - (* 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; + ("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. The contents of the +file is the string C (which can contain any 8 bit data), +with length C. - 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; +As a special case, if C is C<0> +then the length is calculated using C (so in this case +the content cannot contain embedded ASCII NULs)."); - 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 + ("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, C). + +Some internal mounts are not shown."); + + ("umount_all", (RErr, []), 47, [FishAlias "unmount-all"], + [InitBasicFS, TestOutputList ( + [["umount_all"]; + ["mounts"]], []); + (* check that umount_all can unmount nested mounts correctly: *) + InitEmpty, TestOutputList ( + [["sfdisk"; "/dev/sda"; "0"; "0"; "0"; ",10 ,20 ,"]; + ["mkfs"; "ext2"; "/dev/sda1"]; + ["mkfs"; "ext2"; "/dev/sda2"]; + ["mkfs"; "ext2"; "/dev/sda3"]; + ["mount"; "/dev/sda1"; "/"]; + ["mkdir"; "/mp1"]; + ["mount"; "/dev/sda2"; "/mp1"]; + ["mkdir"; "/mp1/mp2"]; + ["mount"; "/dev/sda3"; "/mp1/mp2"]; + ["mkdir"; "/mp1/mp2/mp3"]; + ["umount_all"]; + ["mounts"]], [])], + "unmount all filesystems", + "\ +This unmounts all mounted filesystems. -(* 'pr' prints to the current output file. *) -let chan = ref stdout -let pr fs = ksprintf (output_string !chan) fs +Some internal mounts are not unmounted by this call."); -(* Generate a header block in a number of standard styles. *) -type comment_style = CStyle | HashStyle | OCamlStyle -type license = GPLv2 | LGPLv2 + ("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."); -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; + ("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 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. - | 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" +The exact command which runs is C. Note in +particular that the filename is not prepended to the output +(the C<-b> option)."); -(* Start of main code generation functions below this line. *) + ("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 and C. 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, but splits the +result into a list of lines."); -(* 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" - | 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 be freed.\n\n" - | RString _ -> - pr "This function returns a string or NULL on error. -I.\n\n" - | RStringList _ -> - pr "This function returns a NULL-terminated array of strings -(like L), or NULL if there was an error. -I.\n\n" - | RIntBool _ -> - pr "This function returns a C. -I after use>.\n\n" - | RPVList _ -> - pr "This function returns a C. -I after use>.\n\n" - | RVGList _ -> - pr "This function returns a C. -I after use>.\n\n" - | RLVList _ -> - pr "This function returns a C. -I after use>.\n\n" - ); - if List.mem ProtocolLimitWarning flags then - pr "Because of the message protocol, there is a transfer limit -of somewhere between 2MB and 4MB. To transfer large files you should use -FTP.\n\n"; - ) all_functions_sorted + ("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. -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] +This is the same as the C system call."); -(* 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; + ("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. - (* This has to be defined to get around a limitation in Sun's rpcgen. *) - pr "typedef string str<>;\n"; - pr "\n"; +This is the same as C except that if C +is a symbolic link, then the link is stat-ed, not the file it +refers to. - (* 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]; +This is the same as the C system call."); - List.iter ( - fun (shortname, style, _, _, _, _, _) -> - let name = "guestfs_" ^ shortname in + ("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 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). - (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 - | Bool n -> pr " bool %s;\n" n - | Int n -> pr " int %s;\n" n - ) 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" - | 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" - ); - ) daemon_functions; +This is the same as the C system call."); - (* 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_dummy\n"; (* so we don't have a "hanging comma" *) - pr "};\n"; - pr "\n"; + ("tune2fs_l", (RHashtable "superblock", [String "device"]), 55, [], + [], (* XXX test *) + "get ext2/ext3/ext4 superblock details", + "\ +This returns the contents of the ext2, ext3 or ext4 filesystem +superblock on C. + +It is the same as running C. See L +manpage for more details. The list of fields returned isn't +clearly defined, and depends on both the version of C +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 to read-only. - (* 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"; +This uses the L command."); - (* Message header, etc. *) - pr "\ -const GUESTFS_PROGRAM = 0x2000F5F5; -const GUESTFS_PROTOCOL_VERSION = 1; + ("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 to read-write. -enum guestfs_message_direction { - GUESTFS_DIRECTION_CALL = 0, /* client -> daemon */ - GUESTFS_DIRECTION_REPLY = 1 /* daemon -> client */ -}; +This uses the L command."); -enum guestfs_message_status { - GUESTFS_STATUS_OK = 0, - GUESTFS_STATUS_ERROR = 1 -}; + ("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). -const GUESTFS_ERROR_LEN = 256; +This uses the L command."); -struct guestfs_message_error { - string error; /* error message */ -}; + ("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. -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; -}; -" +(Note, this is not the size in sectors, use C +for that). -(* Generate the guestfs-structs.h file. *) -and generate_structs_h () = - generate_header CStyle LGPLv2; +This uses the L command."); - (* 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. - *) + ("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. - (* guestfs_int_bool structure. *) - pr "struct guestfs_int_bool {\n"; - pr " int32_t i;\n"; - pr " int32_t b;\n"; - pr "};\n"; - pr "\n"; +(Note this is different from both I and +I). - (* 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] +This uses the L command."); -(* 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 + ("blockdev_setbsz", (RErr, [String "device"; Int "blocksize"]), 61, [], + [], (* XXX test *) + "set blocksize of block device", + "\ +This sets the block size of a device. -(* Generate the client-side dispatch stubs. *) -and generate_client_actions () = - generate_header CStyle LGPLv2; +(Note this is different from both I and +I). - (* Client-side stubs for each function. *) - List.iter ( - fun (shortname, style, _, _, _, _, _) -> - let name = "guestfs_" ^ shortname in +This uses the L command."); - (* Generate the return value struct. *) - pr "struct %s_rv {\n" shortname; - pr " int cb_done; /* flag to indicate callback was called */\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 _ - | RBool _ | RString _ | RStringList _ - | RIntBool _ - | RPVList _ | RVGList _ | RLVList _ -> - pr " struct %s_ret ret;\n" name - ); - pr "};\n\n"; + ("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). - (* Generate the callback function. *) - pr "static void %s_cb (guestfs_h *g, void *data, XDR *xdr)\n" shortname; - pr "{\n"; - pr " struct %s_rv *rv = (struct %s_rv *) data;\n" shortname shortname; - pr "\n"; - pr " if (!xdr_guestfs_message_header (xdr, &rv->hdr)) {\n"; - pr " error (g, \"%s: failed to parse reply header\");\n" name; - pr " return;\n"; - pr " }\n"; - pr " if (rv->hdr.status == GUESTFS_STATUS_ERROR) {\n"; - pr " if (!xdr_guestfs_message_error (xdr, &rv->err)) {\n"; - pr " error (g, \"%s: failed to parse reply error\");\n" name; - pr " return;\n"; - pr " }\n"; - pr " goto done;\n"; - pr " }\n"; +See also C for the real sector size of +the device, and C for the more +useful I. - (match fst style with - | RErr -> () - | RConstString _ -> - failwithf "RConstString cannot be returned from a daemon function" - | RInt _ - | RBool _ | RString _ | RStringList _ - | RIntBool _ - | RPVList _ | RVGList _ | RLVList _ -> - pr " if (!xdr_%s_ret (xdr, &rv->ret)) {\n" name; - pr " error (g, \"%s: failed to parse reply\");\n" name; - pr " return;\n"; - pr " }\n"; - ); +This uses the L command."); - pr " done:\n"; - pr " rv->cb_done = 1;\n"; - pr " main_loop.main_loop_quit (g);\n"; - pr "}\n\n"; + ("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. - (* Generate the action stub. *) - generate_prototype ~extern:false ~semicolon:false ~newline:true - ~handle:"g" name style; +See also C. - let error_code = - match fst style with - | RErr | RInt _ | RBool _ -> "-1" - | RConstString _ -> - failwithf "RConstString cannot be returned from a daemon function" - | RString _ | RStringList _ | RIntBool _ - | RPVList _ | RVGList _ | RLVList _ -> - "NULL" in +This uses the L command."); - pr "{\n"; + ("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. - (match snd style with - | [] -> () - | _ -> pr " struct %s_args args;\n" name - ); +This uses the L command."); - pr " struct %s_rv rv;\n" shortname; - pr " int serial;\n"; - pr "\n"; - pr " if (g->state != READY) {\n"; - pr " error (g, \"%s called from the wrong state, %%d != READY\",\n" - name; - pr " g->state);\n"; - pr " return %s;\n" error_code; - pr " }\n"; - pr "\n"; - pr " memset (&rv, 0, sizeof rv);\n"; - pr "\n"; + ("blockdev_rereadpt", (RErr, [String "device"]), 65, [], + [InitEmpty, TestRun + [["blockdev_rereadpt"; "/dev/sda"]]], + "reread partition table", + "\ +Reread the partition table on C. - (match snd style with - | [] -> - pr " serial = dispatch (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 - | Bool n -> - pr " args.%s = %s;\n" n n - | Int n -> - pr " args.%s = %s;\n" n n - ) args; - pr " serial = dispatch (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"; +This uses the L command."); - pr " rv.cb_done = 0;\n"; - pr " g->reply_cb_internal = %s_cb;\n" shortname; - pr " g->reply_cb_internal_data = &rv;\n"; - pr " main_loop.main_loop_run (g);\n"; - pr " g->reply_cb_internal = NULL;\n"; - pr " g->reply_cb_internal_data = NULL;\n"; - pr " if (!rv.cb_done) {\n"; - pr " error (g, \"%s failed, see earlier error messages\");\n" name; - pr " return %s;\n" error_code; - pr " }\n"; - pr "\n"; + ("upload", (RErr, [FileIn "filename"; String "remotefilename"]), 66, [], + [InitBasicFS, TestOutput ( + (* Pick a file from cwd which isn't likely to change. *) + [["upload"; "COPYING.LIB"; "/COPYING.LIB"]; + ["checksum"; "md5"; "/COPYING.LIB"]], "e3eda01d9815f8d24aae2dbd89b68b06")], + "upload a file from the local machine", + "\ +Upload local file C to C on the +filesystem. - pr " if (check_reply_header (g, &rv.hdr, GUESTFS_PROC_%s, serial) == -1)\n" - (String.uppercase shortname); - pr " return %s;\n" error_code; - pr "\n"; +C can also be a named pipe. - pr " if (rv.hdr.status == GUESTFS_STATUS_ERROR) {\n"; - pr " error (g, \"%%s\", rv.err.error);\n"; - pr " return %s;\n" error_code; - pr " }\n"; - pr "\n"; +See also C."); - (match fst style with - | RErr -> pr " return 0;\n" - | RInt n - | RBool n -> pr " return rv.ret.%s;\n" n - | RConstString _ -> - failwithf "RConstString cannot be returned from a daemon function" - | RString n -> - pr " return rv.ret.%s; /* caller will free */\n" n - | RStringList n -> - pr " /* caller will free this, but we need to add a NULL entry */\n"; - pr " rv.ret.%s.%s_val =" n n; - pr " safe_realloc (g, rv.ret.%s.%s_val,\n" n n; - pr " sizeof (char *) * (rv.ret.%s.%s_len + 1));\n" - n n; - pr " rv.ret.%s.%s_val[rv.ret.%s.%s_len] = NULL;\n" n n n n; - pr " return rv.ret.%s.%s_val;\n" n n - | RIntBool _ -> - pr " /* caller with free this */\n"; - pr " return safe_memdup (g, &rv.ret, sizeof (rv.ret));\n" - | RPVList n -> - pr " /* caller will free this */\n"; - pr " return safe_memdup (g, &rv.ret.%s, sizeof (rv.ret.%s));\n" n n - | RVGList n -> - pr " /* caller will free this */\n"; - pr " return safe_memdup (g, &rv.ret.%s, sizeof (rv.ret.%s));\n" n n - | RLVList n -> - pr " /* caller will free this */\n"; - pr " return safe_memdup (g, &rv.ret.%s, sizeof (rv.ret.%s));\n" n n - ); + ("download", (RErr, [String "remotefilename"; FileOut "filename"]), 67, [], + [InitBasicFS, TestOutput ( + (* Pick a file from cwd which isn't likely to change. *) + [["upload"; "COPYING.LIB"; "/COPYING.LIB"]; + ["download"; "/COPYING.LIB"; "testdownload.tmp"]; + ["upload"; "testdownload.tmp"; "/upload"]; + ["checksum"; "md5"; "/upload"]], "e3eda01d9815f8d24aae2dbd89b68b06")], + "download a file to the local machine", + "\ +Download file C and save it as C +on the local machine. + +C can also be a named pipe. + +See also C, C."); + + ("checksum", (RString "checksum", [String "csumtype"; String "path"]), 68, [], + [InitBasicFS, TestOutput ( + [["write_file"; "/new"; "test\n"; "0"]; + ["checksum"; "crc"; "/new"]], "935282863"); + InitBasicFS, TestLastFail ( + [["checksum"; "crc"; "/new"]]); + InitBasicFS, TestOutput ( + [["write_file"; "/new"; "test\n"; "0"]; + ["checksum"; "md5"; "/new"]], "d8e8fca2dc0f896fd7cb4cb0031ba249"); + InitBasicFS, TestOutput ( + [["write_file"; "/new"; "test\n"; "0"]; + ["checksum"; "sha1"; "/new"]], "4e1243bd22c66e76c2ba9eddc1f91394e57f9f83"); + InitBasicFS, TestOutput ( + [["write_file"; "/new"; "test\n"; "0"]; + ["checksum"; "sha224"; "/new"]], "52f1bf093f4b7588726035c176c0cdb4376cfea53819f1395ac9e6ec"); + InitBasicFS, TestOutput ( + [["write_file"; "/new"; "test\n"; "0"]; + ["checksum"; "sha256"; "/new"]], "f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2"); + InitBasicFS, TestOutput ( + [["write_file"; "/new"; "test\n"; "0"]; + ["checksum"; "sha384"; "/new"]], "109bb6b5b6d5547c1ce03c7a8bd7d8f80c1cb0957f50c4f7fda04692079917e4f9cad52b878f3d8234e1a170b154b72d"); + InitBasicFS, TestOutput ( + [["write_file"; "/new"; "test\n"; "0"]; + ["checksum"; "sha512"; "/new"]], "0e3e75234abc68f4378a86b3f4b32a198ba301845b0cd6e50106e874345700cc6663a86c1ea125dc5e92be17c98f9a0f85ca9d5f595db2012f7cc3571945c123")], + "compute MD5, SHAx or CRC checksum of file", + "\ +This call computes the MD5, SHAx or CRC checksum of the +file named C. - pr "}\n\n" - ) daemon_functions +The type of checksum to compute is given by the C +parameter which must have one of the following values: -(* Generate daemon/actions.h. *) -and generate_daemon_actions_h () = - generate_header CStyle GPLv2; +=over 4 - pr "#include \"../src/guestfs_protocol.h\"\n"; - pr "\n"; +=item C - List.iter ( - fun (name, style, _, _, _, _, _) -> - generate_prototype - ~single_line:true ~newline:true ~in_daemon:true ~prefix:"do_" - name style; - ) daemon_functions +Compute the cyclic redundancy check (CRC) specified by POSIX +for the C command. -(* Generate the server-side stubs. *) -and generate_daemon_actions () = - generate_header CStyle GPLv2; +=item C - pr "#define _GNU_SOURCE // for strchrnul\n"; - pr "\n"; - pr "#include \n"; - pr "#include \n"; - pr "#include \n"; - pr "#include \n"; - pr "#include \n"; - pr "#include \n"; - pr "#include \n"; - pr "\n"; - pr "#include \"daemon.h\"\n"; - pr "#include \"../src/guestfs_protocol.h\"\n"; - pr "#include \"actions.h\"\n"; - pr "\n"; +Compute the MD5 hash (using the C program). - 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" - | RBool _ -> pr " int r;\n"; "-1" - | RConstString _ -> - failwithf "RConstString cannot be returned from a daemon function" - | RString _ -> pr " char *r;\n"; "NULL" - | RStringList _ -> 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" in +=item C - (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 - | Bool n -> pr " int %s;\n" n - | Int n -> pr " int %s;\n" n - ) args - ); - pr "\n"; +Compute the SHA1 hash (using the C program). - (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 - | Bool n -> pr " %s = args.%s;\n" n n - | Int n -> pr " %s = args.%s;\n" n n - ) args; - pr "\n" - ); +=item C - pr " r = do_%s " name; - generate_call_args style; - pr ";\n"; +Compute the SHA224 hash (using the C program). - pr " if (r == %s)\n" error_code; - pr " /* do_%s has already called reply_with_error, so just return */\n" name; - pr " return;\n"; - pr "\n"; +=item C - (match fst style with - | RErr -> pr " reply (NULL, NULL);\n" - | RInt 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 - | 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 -> - 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 -> - pr " struct guestfs_%s_ret ret;\n" name; - pr " ret.%s = *r;\n" n; - pr " reply ((xdrproc_t) xdr_guestfs_%s_ret, (char *) &ret);\n" name; - pr " xdr_free ((xdrproc_t) xdr_guestfs_%s_ret, (char *) &ret);\n" name - | RVGList n -> - pr " struct guestfs_%s_ret ret;\n" name; - pr " ret.%s = *r;\n" n; - pr " reply ((xdrproc_t) xdr_guestfs_%s_ret, (char *) &ret);\n" name; - pr " xdr_free ((xdrproc_t) xdr_guestfs_%s_ret, (char *) &ret);\n" name - | RLVList n -> - 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 - ); +Compute the SHA256 hash (using the C program). - pr "}\n\n"; - ) daemon_functions; +=item C - (* Dispatch function. *) - pr "void dispatch_incoming_message (XDR *xdr_in)\n"; - pr "{\n"; - pr " switch (proc_nr) {\n"; +Compute the SHA384 hash (using the C program). - 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; +=item C - pr " default:\n"; - pr " reply_with_error (\"dispatch_incoming_message: unknown procedure number %%d\", proc_nr);\n"; - pr " }\n"; - pr "}\n"; - pr "\n"; +Compute the SHA512 hash (using the C program). - (* 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"; +=back - 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; +The checksum is returned as a printable string."); - 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"; + ("tar_in", (RErr, [FileIn "tarfile"; String "directory"]), 69, [], + [InitBasicFS, TestOutput ( + [["tar_in"; "images/helloworld.tar"; "/"]; + ["cat"; "/hello"]], "hello\n")], + "unpack tarfile to directory", + "\ +This command uploads and unpacks local file C (an +I tar file) into C. - 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 " 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" +To upload a compressed tarball, use C."); + + ("tar_out", (RErr, [String "directory"; FileOut "tarfile"]), 70, [], + [], + "pack directory into tarfile", + "\ +This command packs the contents of C and downloads +it to local file C. + +To download a compressed tarball, use C."); + + ("tgz_in", (RErr, [FileIn "tarball"; String "directory"]), 71, [], + [InitBasicFS, TestOutput ( + [["tgz_in"; "images/helloworld.tar.gz"; "/"]; + ["cat"; "/hello"]], "hello\n")], + "unpack compressed tarball to directory", + "\ +This command uploads and unpacks local file C (a +I tar file) into C. + +To upload an uncompressed tarball, use C."); + + ("tgz_out", (RErr, [String "directory"; FileOut "tarball"]), 72, [], + [], + "pack directory into compressed tarball", + "\ +This command packs the contents of C and downloads +it to local file C. + +To download an uncompressed tarball, use C."); + + ("mount_ro", (RErr, [String "device"; String "mountpoint"]), 73, [], + [InitBasicFS, TestLastFail ( + [["umount"; "/"]; + ["mount_ro"; "/dev/sda1"; "/"]; + ["touch"; "/new"]]); + InitBasicFS, TestOutput ( + [["write_file"; "/new"; "data"; "0"]; + ["umount"; "/"]; + ["mount_ro"; "/dev/sda1"; "/"]; + ["cat"; "/new"]], "data")], + "mount a guest disk, read-only", + "\ +This is the same as the C command, but it +mounts the filesystem with the read-only (I<-o ro>) flag."); + + ("mount_options", (RErr, [String "options"; String "device"; String "mountpoint"]), 74, [], + [], + "mount a guest disk with mount options", + "\ +This is the same as the C command, but it +allows you to set the mount options as for the +L I<-o> flag."); + + ("mount_vfs", (RErr, [String "options"; String "vfstype"; String "device"; String "mountpoint"]), 75, [], + [], + "mount a guest disk with mount options and vfstype", + "\ +This is the same as the C command, but it +allows you to set both the mount options and the vfstype +as for the L I<-o> and I<-t> flags."); + + ("debug", (RString "result", [String "subcmd"; StringList "extraargs"]), 76, [], + [], + "debugging and internals", + "\ +The C command exposes some internals of +C (the guestfs daemon) that runs inside the +qemu subprocess. + +There is no comprehensive help for this command. You have +to look at the file C in the libguestfs source +to find out what you can do."); + + ("lvremove", (RErr, [String "device"]), 77, [], + [InitEmpty, TestOutputList ( + [["pvcreate"; "/dev/sda"]; + ["vgcreate"; "VG"; "/dev/sda"]; + ["lvcreate"; "LV1"; "VG"; "50"]; + ["lvcreate"; "LV2"; "VG"; "50"]; + ["lvremove"; "/dev/VG/LV1"]; + ["lvs"]], ["/dev/VG/LV2"]); + InitEmpty, TestOutputList ( + [["pvcreate"; "/dev/sda"]; + ["vgcreate"; "VG"; "/dev/sda"]; + ["lvcreate"; "LV1"; "VG"; "50"]; + ["lvcreate"; "LV2"; "VG"; "50"]; + ["lvremove"; "/dev/VG"]; + ["lvs"]], []); + InitEmpty, TestOutputList ( + [["pvcreate"; "/dev/sda"]; + ["vgcreate"; "VG"; "/dev/sda"]; + ["lvcreate"; "LV1"; "VG"; "50"]; + ["lvcreate"; "LV2"; "VG"; "50"]; + ["lvremove"; "/dev/VG"]; + ["vgs"]], ["VG"])], + "remove an LVM logical volume", + "\ +Remove an LVM logical volume C, where C is +the path to the LV, such as C. + +You can also remove all LVs in a volume group by specifying +the VG name, C."); + + ("vgremove", (RErr, [String "vgname"]), 78, [], + [InitEmpty, TestOutputList ( + [["pvcreate"; "/dev/sda"]; + ["vgcreate"; "VG"; "/dev/sda"]; + ["lvcreate"; "LV1"; "VG"; "50"]; + ["lvcreate"; "LV2"; "VG"; "50"]; + ["vgremove"; "VG"]; + ["lvs"]], []); + InitEmpty, TestOutputList ( + [["pvcreate"; "/dev/sda"]; + ["vgcreate"; "VG"; "/dev/sda"]; + ["lvcreate"; "LV1"; "VG"; "50"]; + ["lvcreate"; "LV2"; "VG"; "50"]; + ["vgremove"; "VG"]; + ["vgs"]], [])], + "remove an LVM volume group", + "\ +Remove an LVM volume group C, (for example C). + +This also forcibly removes all logical volumes in the volume +group (if any)."); + + ("pvremove", (RErr, [String "device"]), 79, [], + [InitEmpty, TestOutputList ( + [["pvcreate"; "/dev/sda"]; + ["vgcreate"; "VG"; "/dev/sda"]; + ["lvcreate"; "LV1"; "VG"; "50"]; + ["lvcreate"; "LV2"; "VG"; "50"]; + ["vgremove"; "VG"]; + ["pvremove"; "/dev/sda"]; + ["lvs"]], []); + InitEmpty, TestOutputList ( + [["pvcreate"; "/dev/sda"]; + ["vgcreate"; "VG"; "/dev/sda"]; + ["lvcreate"; "LV1"; "VG"; "50"]; + ["lvcreate"; "LV2"; "VG"; "50"]; + ["vgremove"; "VG"]; + ["pvremove"; "/dev/sda"]; + ["vgs"]], []); + InitEmpty, TestOutputList ( + [["pvcreate"; "/dev/sda"]; + ["vgcreate"; "VG"; "/dev/sda"]; + ["lvcreate"; "LV1"; "VG"; "50"]; + ["lvcreate"; "LV2"; "VG"; "50"]; + ["vgremove"; "VG"]; + ["pvremove"; "/dev/sda"]; + ["pvs"]], [])], + "remove an LVM physical volume", + "\ +This wipes a physical volume C so that LVM will no longer +recognise it. + +The implementation uses the C command which refuses to +wipe physical volumes that contain any volume groups, so you have +to remove those first."); + + ("set_e2label", (RErr, [String "device"; String "label"]), 80, [], + [InitBasicFS, TestOutput ( + [["set_e2label"; "/dev/sda1"; "testlabel"]; + ["get_e2label"; "/dev/sda1"]], "testlabel")], + "set the ext2/3/4 filesystem label", + "\ +This sets the ext2/3/4 filesystem label of the filesystem on +C to C