diff options
36 files changed, 1797 insertions, 678 deletions
@@ -177,6 +177,7 @@ ocaml/t/guestfs_010_launch ocaml/t/guestfs_050_lvcreate ocaml/t/guestfs_060_readdir ocaml/t/guestfs_070_threads +ocaml/t/guestfs_080_optargs ocaml/t/guestfs_400_progress perl/bindtests.pl perl/blib diff --git a/configure.ac b/configure.ac index 455ffd18..94e65b47 100644 --- a/configure.ac +++ b/configure.ac @@ -266,7 +266,7 @@ the --with-qemu option. fi fi -dnl Set drive interface used by the guestfs_add_drive{,_ro} calls +dnl Set default drive interface used by the guestfs_add_drive_opts call dnl ('-drive ...,if=...' option to qemu). dnl dnl If you encounter performance problems with virtio (RHBZ#509383) diff --git a/examples/hello.c b/examples/hello.c index b4d5d8c6..b3d36e63 100644 --- a/examples/hello.c +++ b/examples/hello.c @@ -24,11 +24,14 @@ main (int argc, char *argv[]) if (!(g = guestfs_create ())) exit (EXIT_FAILURE); - if (guestfs_add_drive (g, argv[1]) == -1) exit (EXIT_FAILURE); + if (guestfs_add_drive_opts (g, argv[1], + GUESTFS_ADD_DRIVE_OPTS_FORMAT, "raw", + -1) == -1) + exit (EXIT_FAILURE); if (guestfs_launch (g) == -1) exit (EXIT_FAILURE); - if (guestfs_mount (g, argv[2], "/") == -1) exit (EXIT_FAILURE); + if (guestfs_mount_options (g, "", argv[2], "/") == -1) exit (EXIT_FAILURE); if (guestfs_touch (g, "/hello") == -1) exit (EXIT_FAILURE); diff --git a/examples/to-xml.c b/examples/to-xml.c index 537ae915..45994cb3 100644 --- a/examples/to-xml.c +++ b/examples/to-xml.c @@ -48,7 +48,9 @@ main (int argc, char *argv[]) } for (i = 1; i < argc; ++i) - CALL (guestfs_add_drive (g, argv[i]), -1); + CALL (guestfs_add_drive_opts (g, argv[i], + GUESTFS_ADD_DRIVE_OPTS_FORMAT, "raw", + -1), -1); CALL (guestfs_launch (g), -1); diff --git a/fish/guestfish.pod b/fish/guestfish.pod index fc32d0a9..c92953b6 100644 --- a/fish/guestfish.pod +++ b/fish/guestfish.pod @@ -381,6 +381,21 @@ must be escaped with a backslash. command "/bin/echo 'foo bar'" command "/bin/echo \'foo\'" +=head1 OPTIONAL ARGUMENTS + +Some commands take optional arguments. These arguments appear in this +documentation as C<[argname:..]>. You can use them as in these +examples: + + add-drive-opts filename + + add-drive-opts filename readonly:true + + add-drive-opts filename format:qcow2 readonly:false + +Each optional argument can appear at most once. All optional +arguments must appear after the required ones. + =head1 NUMBERS This section applies to all commands which can take integers diff --git a/generator/generator_actions.ml b/generator/generator_actions.ml index c9890a67..695a73d7 100644 --- a/generator/generator_actions.ml +++ b/generator/generator_actions.ml @@ -58,7 +58,7 @@ let test_all_rets = [ ] let test_functions = [ - ("test0", (RErr, test_all_args), -1, [NotInFish; NotInDocs], + ("test0", (RErr, test_all_args, []), -1, [NotInFish; NotInDocs], [], "internal test function - do not use", "\ @@ -72,7 +72,7 @@ You probably don't want to call this function."); ] @ List.flatten ( List.map ( fun (name, ret) -> - [(name, (ret, [String "val"]), -1, [NotInFish; NotInDocs], + [(name, (ret, [String "val"], []), -1, [NotInFish; NotInDocs], [], "internal test function - do not use", "\ @@ -83,7 +83,7 @@ return type correctly. It converts string C<val> to the return type. You probably don't want to call this function."); - (name ^ "err", (ret, []), -1, [NotInFish; NotInDocs], + (name ^ "err", (ret, [], []), -1, [NotInFish; NotInDocs], [], "internal test function - do not use", "\ @@ -103,7 +103,7 @@ You probably don't want to call this function.")] *) let non_daemon_functions = test_functions @ [ - ("launch", (RErr, []), -1, [FishAlias "run"], + ("launch", (RErr, [], []), -1, [FishAlias "run"], [], "launch the qemu subprocess", "\ @@ -113,7 +113,7 @@ using L<qemu(1)>. You should call this after configuring the handle (eg. adding drives) but before performing any actions."); - ("wait_ready", (RErr, []), -1, [NotInFish], + ("wait_ready", (RErr, [], []), -1, [NotInFish], [], "wait until the qemu subprocess launches (no op)", "\ @@ -128,44 +128,28 @@ If you see any calls to this function in code then you can just remove them, unless you want to retain compatibility with older versions of the API."); - ("kill_subprocess", (RErr, []), -1, [], + ("kill_subprocess", (RErr, [], []), -1, [], [], "kill the qemu subprocess", "\ This kills the qemu subprocess. You should never need to call this."); - ("add_drive", (RErr, [String "filename"]), -1, [FishAlias "add"], + ("add_drive", (RErr, [String "filename"], []), -1, [], [], "add an image to examine or modify", "\ -This function adds a virtual machine disk image C<filename> to the -guest. The first time you call this function, the disk appears as IDE -disk 0 (C</dev/sda>) in the guest, the second time as C</dev/sdb>, and -so on. +This function is the equivalent of calling C<guestfs_add_drive_opts> +with no optional parameters, so the disk is added writable, with +the format being detected automatically. -You don't necessarily need to be root when using libguestfs. However -you obviously do need sufficient permissions to access the filename -for whatever operations you want to perform (ie. read access if you -just want to read the image or write access if you want to modify the -image). +Automatic detection of the format opens you up to a potential +security hole when dealing with untrusted raw-format images. +See CVE-2010-3851 and RHBZ#642934. Specifying the format closes +this security hole. Therefore you should think about replacing +calls to this function with calls to C<guestfs_add_drive_opts>, +and specifying the format."); -This is equivalent to the qemu parameter -C<-drive file=filename,cache=off,if=...>. - -C<cache=off> is omitted in cases where it is not supported by -the underlying filesystem. - -C<if=...> is set at compile time by the configuration option -C<./configure --with-drive-if=...>. In the rare case where you -might need to change this at run time, use C<guestfs_add_drive_with_if> -or C<guestfs_add_drive_ro_with_if>. - -Note that this call checks for the existence of C<filename>. This -stops you from specifying other types of drive which are supported -by qemu such as C<nbd:> and C<http:> URLs. To specify those, use -the general C<guestfs_config> call instead."); - - ("add_cdrom", (RErr, [String "filename"]), -1, [FishAlias "cdrom"], + ("add_cdrom", (RErr, [String "filename"], []), -1, [DeprecatedBy "add_drive_opts"], [], "add a CD-ROM disk image to examine", "\ @@ -192,33 +176,16 @@ should probably use C<guestfs_add_drive_ro> instead. =back"); - ("add_drive_ro", (RErr, [String "filename"]), -1, [FishAlias "add-ro"], + ("add_drive_ro", (RErr, [String "filename"], []), -1, [FishAlias "add-ro"], [], "add a drive in snapshot mode (read-only)", "\ -This adds a drive in snapshot mode, making it effectively -read-only. - -Note that writes to the device are allowed, and will be seen for -the duration of the guestfs handle, but they are written -to a temporary file which is discarded as soon as the guestfs -handle is closed. We don't currently have any method to enable -changes to be committed, although qemu can support this. - -This is equivalent to the qemu parameter -C<-drive file=filename,snapshot=on,if=...>. - -C<if=...> is set at compile time by the configuration option -C<./configure --with-drive-if=...>. In the rare case where you -might need to change this at run time, use C<guestfs_add_drive_with_if> -or C<guestfs_add_drive_ro_with_if>. - -Note that this call checks for the existence of C<filename>. This -stops you from specifying other types of drive which are supported -by qemu such as C<nbd:> and C<http:> URLs. To specify those, use -the general C<guestfs_config> call instead."); +This function is the equivalent of calling C<guestfs_add_drive_opts> +with the optional parameter C<GUESTFS_ADD_DRIVE_OPTS_READONLY> set to 1, +so the disk is added read-only, with the format being detected +automatically."); - ("config", (RErr, [String "qemuparam"; OptString "qemuvalue"]), -1, [], + ("config", (RErr, [String "qemuparam"; OptString "qemuvalue"], []), -1, [], [], "add qemu parameters", "\ @@ -231,7 +198,7 @@ The first character of C<param> string must be a C<-> (dash). C<value> can be NULL."); - ("set_qemu", (RErr, [OptString "qemu"]), -1, [FishAlias "qemu"], + ("set_qemu", (RErr, [OptString "qemu"], []), -1, [FishAlias "qemu"], [], "set the qemu binary", "\ @@ -253,7 +220,7 @@ so you might see inconsistent results. Using the environment variable C<LIBGUESTFS_QEMU> is safest of all since that picks the qemu binary at the same time as the handle is created."); - ("get_qemu", (RConstString "qemu", []), -1, [], + ("get_qemu", (RConstString "qemu", [], []), -1, [], [InitNone, Always, TestRun ( [["get_qemu"]])], "get the qemu binary", @@ -263,7 +230,7 @@ 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, [OptString "searchpath"]), -1, [FishAlias "path"], + ("set_path", (RErr, [OptString "searchpath"], []), -1, [FishAlias "path"], [], "set the search path", "\ @@ -274,7 +241,7 @@ C<LIBGUESTFS_PATH> environment variable. Setting C<path> to C<NULL> restores the default path."); - ("get_path", (RConstString "path", []), -1, [], + ("get_path", (RConstString "path", [], []), -1, [], [InitNone, Always, TestRun ( [["get_path"]])], "get the search path", @@ -284,7 +251,7 @@ Return the current search path. This is always non-NULL. If it wasn't set already, then this will return the default path."); - ("set_append", (RErr, [OptString "append"]), -1, [FishAlias "append"], + ("set_append", (RErr, [OptString "append"], []), -1, [FishAlias "append"], [], "add options to kernel command line", "\ @@ -297,7 +264,7 @@ C<LIBGUESTFS_APPEND> environment variable. Setting C<append> to C<NULL> means I<no> additional options are passed (libguestfs always adds a few of its own)."); - ("get_append", (RConstOptString "append", []), -1, [], + ("get_append", (RConstOptString "append", [], []), -1, [], (* This cannot be tested with the current framework. The * function can return NULL in normal operations, which the * test framework interprets as an error. @@ -310,7 +277,7 @@ guest kernel command line. If C<NULL> then no options are added."); - ("set_autosync", (RErr, [Bool "autosync"]), -1, [FishAlias "autosync"], + ("set_autosync", (RErr, [Bool "autosync"], []), -1, [FishAlias "autosync"], [], "set autosync mode", "\ @@ -322,14 +289,14 @@ C<guestfs_sync> when the handle is closed This is disabled by default (except in guestfish where it is enabled by default)."); - ("get_autosync", (RBool "autosync", []), -1, [], + ("get_autosync", (RBool "autosync", [], []), -1, [], [InitNone, Always, TestRun ( [["get_autosync"]])], "get autosync mode", "\ Get the autosync flag."); - ("set_verbose", (RErr, [Bool "verbose"]), -1, [FishAlias "verbose"], + ("set_verbose", (RErr, [Bool "verbose"], []), -1, [FishAlias "verbose"], [], "set verbose mode", "\ @@ -338,13 +305,13 @@ If C<verbose> is true, this turns on verbose messages (to C<stderr>). Verbose messages are disabled unless the environment variable C<LIBGUESTFS_DEBUG> is defined and set to C<1>."); - ("get_verbose", (RBool "verbose", []), -1, [], + ("get_verbose", (RBool "verbose", [], []), -1, [], [], "get verbose mode", "\ This returns the verbose messages flag."); - ("is_ready", (RBool "ready", []), -1, [], + ("is_ready", (RBool "ready", [], []), -1, [], [InitNone, Always, TestOutputTrue ( [["is_ready"]])], "is ready to accept commands", @@ -354,7 +321,7 @@ This returns true iff this handle is ready to accept commands For more information on states, see L<guestfs(3)>."); - ("is_config", (RBool "config", []), -1, [], + ("is_config", (RBool "config", [], []), -1, [], [InitNone, Always, TestOutputFalse ( [["is_config"]])], "is in configuration state", @@ -364,7 +331,7 @@ This returns true iff this handle is being configured For more information on states, see L<guestfs(3)>."); - ("is_launching", (RBool "launching", []), -1, [], + ("is_launching", (RBool "launching", [], []), -1, [], [InitNone, Always, TestOutputFalse ( [["is_launching"]])], "is launching subprocess", @@ -374,7 +341,7 @@ This returns true iff this handle is launching the subprocess For more information on states, see L<guestfs(3)>."); - ("is_busy", (RBool "busy", []), -1, [], + ("is_busy", (RBool "busy", [], []), -1, [], [InitNone, Always, TestOutputFalse ( [["is_busy"]])], "is busy processing a command", @@ -384,7 +351,7 @@ This returns true iff this handle is busy processing a command For more information on states, see L<guestfs(3)>."); - ("get_state", (RInt "state", []), -1, [], + ("get_state", (RInt "state", [], []), -1, [], [], "get the current state", "\ @@ -393,7 +360,7 @@ only useful for printing debug and internal error messages. For more information on states, see L<guestfs(3)>."); - ("set_memsize", (RErr, [Int "memsize"]), -1, [FishAlias "memsize"], + ("set_memsize", (RErr, [Int "memsize"], []), -1, [FishAlias "memsize"], [InitNone, Always, TestOutputInt ( [["set_memsize"; "500"]; ["get_memsize"]], 500)], @@ -410,7 +377,7 @@ created. For more information on the architecture of libguestfs, see L<guestfs(3)>."); - ("get_memsize", (RInt "memsize", []), -1, [], + ("get_memsize", (RInt "memsize", [], []), -1, [], [InitNone, Always, TestOutputIntOp ( [["get_memsize"]], ">=", 256)], "get memory allocated to the qemu subprocess", @@ -425,7 +392,7 @@ then this returns the compiled-in default value for memsize. For more information on the architecture of libguestfs, see L<guestfs(3)>."); - ("get_pid", (RInt "pid", []), -1, [FishAlias "pid"], + ("get_pid", (RInt "pid", [], []), -1, [FishAlias "pid"], [InitNone, Always, TestOutputIntOp ( [["get_pid"]], ">=", 1)], "get PID of qemu subprocess", @@ -435,7 +402,7 @@ qemu subprocess, then this will return an error. This is an internal call used for debugging and testing."); - ("version", (RStruct ("version", "version"), []), -1, [], + ("version", (RStruct ("version", "version"), [], []), -1, [], [InitNone, Always, TestOutputStruct ( [["version"]], [CompareWithInt ("major", 1)])], "get the library version number", @@ -471,7 +438,7 @@ features from later versions into earlier versions, making this an unreliable way to test for features. Use C<guestfs_available> instead."); - ("set_selinux", (RErr, [Bool "selinux"]), -1, [FishAlias "selinux"], + ("set_selinux", (RErr, [Bool "selinux"], []), -1, [FishAlias "selinux"], [InitNone, Always, TestOutputTrue ( [["set_selinux"; "true"]; ["get_selinux"]])], @@ -486,7 +453,7 @@ Permissive mode (C<enforcing=0>). For more information on the architecture of libguestfs, see L<guestfs(3)>."); - ("get_selinux", (RBool "selinux", []), -1, [], + ("get_selinux", (RBool "selinux", [], []), -1, [], [], "get SELinux enabled flag", "\ @@ -496,7 +463,7 @@ is passed to the appliance at boot time. See C<guestfs_set_selinux>. For more information on the architecture of libguestfs, see L<guestfs(3)>."); - ("set_trace", (RErr, [Bool "trace"]), -1, [FishAlias "trace"], + ("set_trace", (RErr, [Bool "trace"], []), -1, [FishAlias "trace"], [InitNone, Always, TestOutputFalse ( [["set_trace"; "false"]; ["get_trace"]])], @@ -516,13 +483,13 @@ the external ltrace(1) command. Command traces are disabled unless the environment variable C<LIBGUESTFS_TRACE> is defined and set to C<1>."); - ("get_trace", (RBool "trace", []), -1, [], + ("get_trace", (RBool "trace", [], []), -1, [], [], "get command trace enabled flag", "\ Return the command trace flag."); - ("set_direct", (RErr, [Bool "direct"]), -1, [FishAlias "direct"], + ("set_direct", (RErr, [Bool "direct"], []), -1, [FishAlias "direct"], [InitNone, Always, TestOutputFalse ( [["set_direct"; "false"]; ["get_direct"]])], @@ -541,13 +508,13 @@ are doing. The default is disabled."); - ("get_direct", (RBool "direct", []), -1, [], + ("get_direct", (RBool "direct", [], []), -1, [], [], "get direct appliance mode flag", "\ Return the direct appliance mode flag."); - ("set_recovery_proc", (RErr, [Bool "recoveryproc"]), -1, [FishAlias "recovery-proc"], + ("set_recovery_proc", (RErr, [Bool "recoveryproc"], []), -1, [FishAlias "recovery-proc"], [InitNone, Always, TestOutputTrue ( [["set_recovery_proc"; "true"]; ["get_recovery_proc"]])], @@ -567,27 +534,27 @@ if the main process will fork itself into the background thinks that the main program has disappeared and so kills qemu, which is not very helpful."); - ("get_recovery_proc", (RBool "recoveryproc", []), -1, [], + ("get_recovery_proc", (RBool "recoveryproc", [], []), -1, [], [], "get recovery process enabled flag", "\ Return the recovery process enabled flag."); - ("add_drive_with_if", (RErr, [String "filename"; String "iface"]), -1, [], + ("add_drive_with_if", (RErr, [String "filename"; String "iface"], []), -1, [DeprecatedBy "add_drive_opts"], [], "add a drive specifying the QEMU block emulation to use", "\ This is the same as C<guestfs_add_drive> but it allows you to specify the QEMU interface emulation to use at run time."); - ("add_drive_ro_with_if", (RErr, [String "filename"; String "iface"]), -1, [], + ("add_drive_ro_with_if", (RErr, [String "filename"; String "iface"], []), -1, [DeprecatedBy "add_drive_opts"], [], "add a drive read-only specifying the QEMU block emulation to use", "\ This is the same as C<guestfs_add_drive_ro> but it allows you to specify the QEMU interface emulation to use at run time."); - ("file_architecture", (RString "arch", [Pathname "filename"]), -1, [], + ("file_architecture", (RString "arch", [Pathname "filename"], []), -1, [], [InitISOFS, Always, TestOutput ( [["file_architecture"; "/bin-i586-dynamic"]], "i386"); InitISOFS, Always, TestOutput ( @@ -715,7 +682,7 @@ initrd or kernel module(s) instead. =back"); - ("inspect_os", (RStringList "roots", []), -1, [], + ("inspect_os", (RStringList "roots", [], []), -1, [], [], "inspect disk and return list of operating systems found", "\ @@ -751,7 +718,7 @@ Please read L<guestfs(3)/INSPECTION> for more details. See also C<guestfs_list_filesystems>."); - ("inspect_get_type", (RString "name", [Device "root"]), -1, [], + ("inspect_get_type", (RString "name", [Device "root"], []), -1, [], [], "get type of inspected operating system", "\ @@ -782,7 +749,7 @@ The caller should be prepared to handle any string. Please read L<guestfs(3)/INSPECTION> for more details."); - ("inspect_get_arch", (RString "arch", [Device "root"]), -1, [], + ("inspect_get_arch", (RString "arch", [Device "root"], []), -1, [], [], "get architecture of inspected operating system", "\ @@ -798,7 +765,7 @@ string C<unknown> is returned. Please read L<guestfs(3)/INSPECTION> for more details."); - ("inspect_get_distro", (RString "distro", [Device "root"]), -1, [], + ("inspect_get_distro", (RString "distro", [Device "root"], []), -1, [], [], "get distro of inspected operating system", "\ @@ -844,7 +811,7 @@ The caller should be prepared to handle any string. Please read L<guestfs(3)/INSPECTION> for more details."); - ("inspect_get_major_version", (RInt "major", [Device "root"]), -1, [], + ("inspect_get_major_version", (RInt "major", [Device "root"], []), -1, [], [], "get major version of inspected operating system", "\ @@ -865,7 +832,7 @@ If the version could not be determined, then C<0> is returned. Please read L<guestfs(3)/INSPECTION> for more details."); - ("inspect_get_minor_version", (RInt "minor", [Device "root"]), -1, [], + ("inspect_get_minor_version", (RInt "minor", [Device "root"], []), -1, [], [], "get minor version of inspected operating system", "\ @@ -880,7 +847,7 @@ If the version could not be determined, then C<0> is returned. Please read L<guestfs(3)/INSPECTION> for more details. See also C<guestfs_inspect_get_major_version>."); - ("inspect_get_product_name", (RString "product", [Device "root"]), -1, [], + ("inspect_get_product_name", (RString "product", [Device "root"], []), -1, [], [], "get product name of inspected operating system", "\ @@ -897,7 +864,7 @@ string C<unknown> is returned. Please read L<guestfs(3)/INSPECTION> for more details."); - ("inspect_get_mountpoints", (RHashtable "mountpoints", [Device "root"]), -1, [], + ("inspect_get_mountpoints", (RHashtable "mountpoints", [Device "root"], []), -1, [], [], "get mountpoints of inspected operating system", "\ @@ -920,7 +887,7 @@ returned in this list. Please read L<guestfs(3)/INSPECTION> for more details. See also C<guestfs_inspect_get_filesystems>."); - ("inspect_get_filesystems", (RStringList "filesystems", [Device "root"]), -1, [], + ("inspect_get_filesystems", (RStringList "filesystems", [Device "root"], []), -1, [], [], "get filesystems associated with inspected operating system", "\ @@ -938,7 +905,7 @@ for a filesystem to be shared between operating systems. Please read L<guestfs(3)/INSPECTION> for more details. See also C<guestfs_inspect_get_mountpoints>."); - ("set_network", (RErr, [Bool "network"]), -1, [FishAlias "network"], + ("set_network", (RErr, [Bool "network"], []), -1, [FishAlias "network"], [], "set enable network flag", "\ @@ -951,13 +918,13 @@ This affects whether commands are able to access the network You must call this before calling C<guestfs_launch>, otherwise it has no effect."); - ("get_network", (RBool "network", []), -1, [], + ("get_network", (RBool "network", [], []), -1, [], [], "get enable network flag", "\ This returns the enable network flag."); - ("list_filesystems", (RHashtable "fses", []), -1, [], + ("list_filesystems", (RHashtable "fses", [], []), -1, [], [], "list filesystems", "\ @@ -990,6 +957,51 @@ be mountable but require special options. Filesystems may not all belong to a single logical operating system (use C<guestfs_inspect_os> to look for OSes)."); + ("add_drive_opts", (RErr, [String "filename"], [Bool "readonly"; String "format"; String "iface"]), -1, [FishAlias "add"], + [], + "add an image to examine or modify", + "\ +This function adds a virtual machine disk image C<filename> to +libguestfs. The first time you call this function, the disk +appears as C</dev/sda>, the second time as C</dev/sdb>, and +so on. + +You don't necessarily need to be root when using libguestfs. However +you obviously do need sufficient permissions to access the filename +for whatever operations you want to perform (ie. read access if you +just want to read the image or write access if you want to modify the +image). + +This call checks that C<filename> exists. + +The optional arguments are: + +=over 4 + +=item C<readonly> + +If true then the image is treated as read-only. Writes are still +allowed, but they are stored in a temporary snapshot overlay which +is discarded at the end. The disk that you add is not modified. + +=item C<format> + +This forces the image format. If you omit this (or use C<guestfs_add_drive> +or C<guestfs_add_drive_ro>) then the format is automatically detected. +Possible formats include C<raw> and C<qcow2>. + +Automatic detection of the format opens you up to a potential +security hole when dealing with untrusted raw-format images. +See CVE-2010-3851 and RHBZ#642934. Specifying the format closes +this security hole. + +=item C<iface> + +This rarely-used option lets you emulate the behaviour of the +deprecated C<guestfs_add_drive_with_if> call (q.v.) + +=back"); + ] (* daemon_functions are any functions which cause some action @@ -997,7 +1009,7 @@ not all belong to a single logical operating system *) let daemon_functions = [ - ("mount", (RErr, [Device "device"; String "mountpoint"]), 1, [], + ("mount", (RErr, [Device "device"; String "mountpoint"], []), 1, [], [InitEmpty, Always, TestOutput ( [["part_disk"; "/dev/sda"; "mbr"]; ["mkfs"; "ext2"; "/dev/sda1"]; @@ -1030,7 +1042,7 @@ C<guestfs_mount> in any code that needs performance, and instead use C<guestfs_mount_options> (use an empty string for the first parameter if you don't want any options)."); - ("sync", (RErr, []), 2, [], + ("sync", (RErr, [], []), 2, [], [ InitEmpty, Always, TestRun [["sync"]]], "sync disks, writes are flushed through to the disk image", "\ @@ -1040,7 +1052,7 @@ underlying disk image. You should always call this if you have modified a disk image, before closing the handle."); - ("touch", (RErr, [Pathname "path"]), 3, [], + ("touch", (RErr, [Pathname "path"], []), 3, [], [InitBasicFS, Always, TestOutputTrue ( [["touch"; "/new"]; ["exists"; "/new"]])], @@ -1053,7 +1065,7 @@ to create a new zero-length file. This command only works on regular files, and will fail on other file types such as directories, symbolic links, block special etc."); - ("cat", (RString "content", [Pathname "path"]), 4, [ProtocolLimitWarning], + ("cat", (RString "content", [Pathname "path"], []), 4, [ProtocolLimitWarning], [InitISOFS, Always, TestOutput ( [["cat"; "/known-2"]], "abcdef\n")], "list the contents of a file", @@ -1065,7 +1077,7 @@ Note that this function cannot correctly handle binary files as end of string). For those you need to use the C<guestfs_read_file> or C<guestfs_download> functions which have a more complex interface."); - ("ll", (RString "listing", [Pathname "directory"]), 5, [], + ("ll", (RString "listing", [Pathname "directory"], []), 5, [], [], (* XXX Tricky to test because it depends on the exact format * of the 'ls -l' command, which changes between F10 and F11. *) @@ -1077,7 +1089,7 @@ there is no cwd) in the format of 'ls -la'. This command is mostly useful for interactive sessions. It is I<not> intended that you try to parse the output string."); - ("ls", (RStringList "listing", [Pathname "directory"]), 6, [], + ("ls", (RStringList "listing", [Pathname "directory"], []), 6, [], [InitBasicFS, Always, TestOutputList ( [["touch"; "/new"]; ["touch"; "/newer"]; @@ -1092,7 +1104,7 @@ hidden files are shown. This command is mostly useful for interactive sessions. Programs should probably use C<guestfs_readdir> instead."); - ("list_devices", (RStringList "devices", []), 7, [], + ("list_devices", (RStringList "devices", [], []), 7, [], [InitEmpty, Always, TestOutputListOfDevices ( [["list_devices"]], ["/dev/sda"; "/dev/sdb"; "/dev/sdc"; "/dev/sdd"])], "list the block devices", @@ -1103,7 +1115,7 @@ The full block device names are returned, eg. C</dev/sda>. See also C<guestfs_list_filesystems>."); - ("list_partitions", (RStringList "partitions", []), 8, [], + ("list_partitions", (RStringList "partitions", [], []), 8, [], [InitBasicFS, Always, TestOutputListOfDevices ( [["list_partitions"]], ["/dev/sda1"]); InitEmpty, Always, TestOutputListOfDevices ( @@ -1120,7 +1132,7 @@ call C<guestfs_lvs>. See also C<guestfs_list_filesystems>."); - ("pvs", (RStringList "physvols", []), 9, [Optional "lvm2"], + ("pvs", (RStringList "physvols", [], []), 9, [Optional "lvm2"], [InitBasicFSonLVM, Always, TestOutputListOfDevices ( [["pvs"]], ["/dev/sda1"]); InitEmpty, Always, TestOutputListOfDevices ( @@ -1139,7 +1151,7 @@ PVs (eg. C</dev/sda2>). See also C<guestfs_pvs_full>."); - ("vgs", (RStringList "volgroups", []), 10, [Optional "lvm2"], + ("vgs", (RStringList "volgroups", [], []), 10, [Optional "lvm2"], [InitBasicFSonLVM, Always, TestOutputList ( [["vgs"]], ["VG"]); InitEmpty, Always, TestOutputList ( @@ -1160,7 +1172,7 @@ detected (eg. C<VolGroup00>). See also C<guestfs_vgs_full>."); - ("lvs", (RStringList "logvols", []), 11, [Optional "lvm2"], + ("lvs", (RStringList "logvols", [], []), 11, [Optional "lvm2"], [InitBasicFSonLVM, Always, TestOutputList ( [["lvs"]], ["/dev/VG/LV"]); InitEmpty, Always, TestOutputList ( @@ -1184,28 +1196,28 @@ This returns a list of the logical volume device names See also C<guestfs_lvs_full>, C<guestfs_list_filesystems>."); - ("pvs_full", (RStructList ("physvols", "lvm_pv"), []), 12, [Optional "lvm2"], + ("pvs_full", (RStructList ("physvols", "lvm_pv"), [], []), 12, [Optional "lvm2"], [], (* XXX how to test? *) "list the LVM physical volumes (PVs)", "\ List all the physical volumes detected. This is the equivalent of the L<pvs(8)> command. The \"full\" version includes all fields."); - ("vgs_full", (RStructList ("volgroups", "lvm_vg"), []), 13, [Optional "lvm2"], + ("vgs_full", (RStructList ("volgroups", "lvm_vg"), [], []), 13, [Optional "lvm2"], [], (* XXX how to test? *) "list the LVM volume groups (VGs)", "\ List all the volumes groups detected. This is the equivalent of the L<vgs(8)> command. The \"full\" version includes all fields."); - ("lvs_full", (RStructList ("logvols", "lvm_lv"), []), 14, [Optional "lvm2"], + ("lvs_full", (RStructList ("logvols", "lvm_lv"), [], []), 14, [Optional "lvm2"], [], (* XXX how to test? *) "list the LVM logical volumes (LVs)", "\ List all the logical volumes detected. This is the equivalent of the L<lvs(8)> command. The \"full\" version includes all fields."); - ("read_lines", (RStringList "lines", [Pathname "path"]), 15, [], + ("read_lines", (RStringList "lines", [Pathname "path"], []), 15, [], [InitISOFS, Always, TestOutputList ( [["read_lines"; "/known-4"]], ["abc"; "def"; "ghi"]); InitISOFS, Always, TestOutputList ( @@ -1222,7 +1234,7 @@ Note that this function cannot correctly handle binary files as end of line). For those you need to use the C<guestfs_read_file> function which has a more complex interface."); - ("aug_init", (RErr, [Pathname "root"; Int "flags"]), 16, [Optional "augeas"], + ("aug_init", (RErr, [Pathname "root"; Int "flags"], []), 16, [Optional "augeas"], [], (* XXX Augeas code needs tests. *) "create a new Augeas handle", "\ @@ -1273,7 +1285,7 @@ To close the handle, you can call C<guestfs_aug_close>. To find out more about Augeas, see L<http://augeas.net/>."); - ("aug_close", (RErr, []), 26, [Optional "augeas"], + ("aug_close", (RErr, [], []), 26, [Optional "augeas"], [], (* XXX Augeas code needs tests. *) "close the current Augeas handle", "\ @@ -1282,7 +1294,7 @@ used by it. After calling this, you have to call C<guestfs_aug_init> again before you can use any other Augeas functions."); - ("aug_defvar", (RInt "nrnodes", [String "name"; OptString "expr"]), 17, [Optional "augeas"], + ("aug_defvar", (RInt "nrnodes", [String "name"; OptString "expr"], []), 17, [Optional "augeas"], [], (* XXX Augeas code needs tests. *) "define an Augeas variable", "\ @@ -1293,7 +1305,7 @@ undefined. On success this returns the number of nodes in C<expr>, or C<0> if C<expr> evaluates to something which is not a nodeset."); - ("aug_defnode", (RStruct ("nrnodescreated", "int_bool"), [String "name"; String "expr"; String "val"]), 18, [Optional "augeas"], + ("aug_defnode", (RStruct ("nrnodescreated", "int_bool"), [String "name"; String "expr"; String "val"], []), 18, [Optional "augeas"], [], (* XXX Augeas code needs tests. *) "define an Augeas node", "\ @@ -1308,14 +1320,14 @@ On success this returns a pair containing the number of nodes in the nodeset, and a boolean flag if a node was created."); - ("aug_get", (RString "val", [String "augpath"]), 19, [Optional "augeas"], + ("aug_get", (RString "val", [String "augpath"], []), 19, [Optional "augeas"], [], (* XXX Augeas code needs tests. *) "look up the value of an Augeas path", "\ Look up the value associated with C<path>. If C<path> matches exactly one node, the C<value> is returned."); - ("aug_set", (RErr, [String "augpath"; String "val"]), 20, [Optional "augeas"], + ("aug_set", (RErr, [String "augpath"; String "val"], []), 20, [Optional "augeas"], [], (* XXX Augeas code needs tests. *) "set Augeas path to value", "\ @@ -1326,7 +1338,7 @@ the value to NULL. Due to an oversight in the libguestfs API you cannot do that with this call. Instead you must use the C<guestfs_aug_clear> call."); - ("aug_insert", (RErr, [String "augpath"; String "label"; Bool "before"]), 21, [Optional "augeas"], + ("aug_insert", (RErr, [String "augpath"; String "label"; Bool "before"], []), 21, [Optional "augeas"], [], (* XXX Augeas code needs tests. *) "insert a sibling Augeas node", "\ @@ -1338,7 +1350,7 @@ C<path> must match exactly one existing node in the tree, and C<label> must be a label, ie. not contain C</>, C<*> or end with a bracketed index C<[N]>."); - ("aug_rm", (RInt "nrnodes", [String "augpath"]), 22, [Optional "augeas"], + ("aug_rm", (RInt "nrnodes", [String "augpath"], []), 22, [Optional "augeas"], [], (* XXX Augeas code needs tests. *) "remove an Augeas path", "\ @@ -1346,14 +1358,14 @@ Remove C<path> and all of its children. On success this returns the number of entries which were removed."); - ("aug_mv", (RErr, [String "src"; String "dest"]), 23, [Optional "augeas"], + ("aug_mv", (RErr, [String "src"; String "dest"], []), 23, [Optional "augeas"], [], (* XXX Augeas code needs tests. *) "move Augeas node", "\ Move the node C<src> to C<dest>. C<src> must match exactly one node. C<dest> is overwritten if it exists."); - ("aug_match", (RStringList "matches", [String "augpath"]), 24, [Optional "augeas"], + ("aug_match", (RStringList "matches", [String "augpath"], []), 24, [Optional "augeas"], [], (* XXX Augeas code needs tests. *) "return Augeas nodes which match augpath", "\ @@ -1361,7 +1373,7 @@ Returns a list of paths which match the path expression C<path>. The returned paths are sufficiently qualified so that they match exactly one node in the current tree."); - ("aug_save", (RErr, []), 25, [Optional "augeas"], + ("aug_save", (RErr, [], []), 25, [Optional "augeas"], [], (* XXX Augeas code needs tests. *) "write all pending Augeas changes to disk", "\ @@ -1370,7 +1382,7 @@ This writes all pending changes to disk. The flags which were passed to C<guestfs_aug_init> affect exactly how files are saved."); - ("aug_load", (RErr, []), 27, [Optional "augeas"], + ("aug_load", (RErr, [], []), 27, [Optional "augeas"], [], (* XXX Augeas code needs tests. *) "load files into the tree", "\ @@ -1379,14 +1391,14 @@ Load files into the tree. See C<aug_load> in the Augeas documentation for the full gory details."); - ("aug_ls", (RStringList "matches", [String "augpath"]), 28, [Optional "augeas"], + ("aug_ls", (RStringList "matches", [String "augpath"], []), 28, [Optional "augeas"], [], (* XXX Augeas code needs tests. *) "list Augeas nodes under augpath", "\ This is just a shortcut for listing C<guestfs_aug_match> C<path/*> and sorting the resulting nodes into alphabetical order."); - ("rm", (RErr, [Pathname "path"]), 29, [], + ("rm", (RErr, [Pathname "path"], []), 29, [], [InitBasicFS, Always, TestRun [["touch"; "/new"]; ["rm"; "/new"]]; @@ -1399,7 +1411,7 @@ C<path/*> and sorting the resulting nodes into alphabetical order."); "\ Remove the single file C<path>."); - ("rmdir", (RErr, [Pathname "path"]), 30, [], + ("rmdir", (RErr, [Pathname "path"], []), 30, [], [InitBasicFS, Always, TestRun [["mkdir"; "/new"]; ["rmdir"; "/new"]]; @@ -1412,7 +1424,7 @@ Remove the single file C<path>."); "\ Remove the single directory C<path>."); - ("rm_rf", (RErr, [Pathname "path"]), 31, [], + ("rm_rf", (RErr, [Pathname "path"], []), 31, [], [InitBasicFS, Always, TestOutputFalse [["mkdir"; "/new"]; ["mkdir"; "/new/foo"]; @@ -1425,7 +1437,7 @@ Remove the file or directory C<path>, recursively removing the contents if its a directory. This is like the C<rm -rf> shell command."); - ("mkdir", (RErr, [Pathname "path"]), 32, [], + ("mkdir", (RErr, [Pathname "path"], []), 32, [], [InitBasicFS, Always, TestOutputTrue [["mkdir"; "/new"]; ["is_dir"; "/new"]]; @@ -1435,7 +1447,7 @@ command."); "\ Create a directory named C<path>."); - ("mkdir_p", (RErr, [Pathname "path"]), 33, [], + ("mkdir_p", (RErr, [Pathname "path"], []), 33, [], [InitBasicFS, Always, TestOutputTrue [["mkdir_p"; "/new/foo/bar"]; ["is_dir"; "/new/foo/bar"]]; @@ -1457,7 +1469,7 @@ Create a directory named C<path>."); 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"; Pathname "path"]), 34, [], + ("chmod", (RErr, [Int "mode"; Pathname "path"], []), 34, [], [], (* XXX Need stat command to test *) "change file mode", "\ @@ -1470,7 +1482,7 @@ C<0> to get octal, ie. use C<0700> not C<700>. The mode actually set is affected by the umask."); - ("chown", (RErr, [Int "owner"; Int "group"; Pathname "path"]), 35, [], + ("chown", (RErr, [Int "owner"; Int "group"; Pathname "path"], []), 35, [], [], (* XXX Need stat command to test *) "change file owner and group", "\ @@ -1480,7 +1492,7 @@ 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", [Pathname "path"]), 36, [], + ("exists", (RBool "existsflag", [Pathname "path"], []), 36, [], [InitISOFS, Always, TestOutputTrue ( [["exists"; "/empty"]]); InitISOFS, Always, TestOutputTrue ( @@ -1492,7 +1504,7 @@ This returns C<true> if and only if there is a file, directory See also C<guestfs_is_file>, C<guestfs_is_dir>, C<guestfs_stat>."); - ("is_file", (RBool "fileflag", [Pathname "path"]), 37, [], + ("is_file", (RBool "fileflag", [Pathname "path"], []), 37, [], [InitISOFS, Always, TestOutputTrue ( [["is_file"; "/known-1"]]); InitISOFS, Always, TestOutputFalse ( @@ -1505,7 +1517,7 @@ other objects like directories. See also C<guestfs_stat>."); - ("is_dir", (RBool "dirflag", [Pathname "path"]), 38, [], + ("is_dir", (RBool "dirflag", [Pathname "path"], []), 38, [], [InitISOFS, Always, TestOutputFalse ( [["is_dir"; "/known-3"]]); InitISOFS, Always, TestOutputTrue ( @@ -1518,7 +1530,7 @@ other objects like files. See also C<guestfs_stat>."); - ("pvcreate", (RErr, [Device "device"]), 39, [Optional "lvm2"], + ("pvcreate", (RErr, [Device "device"], []), 39, [Optional "lvm2"], [InitEmpty, Always, TestOutputListOfDevices ( [["sfdiskM"; "/dev/sda"; ",100 ,200 ,"]; ["pvcreate"; "/dev/sda1"]; @@ -1531,7 +1543,7 @@ 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"; DeviceList "physvols"]), 40, [Optional "lvm2"], + ("vgcreate", (RErr, [String "volgroup"; DeviceList "physvols"], []), 40, [Optional "lvm2"], [InitEmpty, Always, TestOutputList ( [["sfdiskM"; "/dev/sda"; ",100 ,200 ,"]; ["pvcreate"; "/dev/sda1"]; @@ -1545,7 +1557,7 @@ as C</dev/sda1>."); 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, [Optional "lvm2"], + ("lvcreate", (RErr, [String "logvol"; String "volgroup"; Int "mbytes"], []), 41, [Optional "lvm2"], [InitEmpty, Always, TestOutputList ( [["sfdiskM"; "/dev/sda"; ",100 ,200 ,"]; ["pvcreate"; "/dev/sda1"]; @@ -1566,7 +1578,7 @@ from the non-empty list of physical volumes C<physvols>."); This creates an LVM logical volume called C<logvol> on the volume group C<volgroup>, with C<size> megabytes."); - ("mkfs", (RErr, [String "fstype"; Device "device"]), 42, [], + ("mkfs", (RErr, [String "fstype"; Device "device"], []), 42, [], [InitEmpty, Always, TestOutput ( [["part_disk"; "/dev/sda"; "mbr"]; ["mkfs"; "ext2"; "/dev/sda1"]; @@ -1581,7 +1593,7 @@ example C<ext3>."); ("sfdisk", (RErr, [Device "device"; Int "cyls"; Int "heads"; Int "sectors"; - StringList "lines"]), 43, [DangerWillRobinson], + StringList "lines"], []), 43, [DangerWillRobinson], [], "create partitions on a block device", "\ @@ -1608,7 +1620,7 @@ the string C<,> (comma). See also: C<guestfs_sfdisk_l>, C<guestfs_sfdisk_N>, C<guestfs_part_init>"); - ("write_file", (RErr, [Pathname "path"; String "content"; Int "size"]), 44, [ProtocolLimitWarning; DeprecatedBy "write"], + ("write_file", (RErr, [Pathname "path"; String "content"; Int "size"], []), 44, [ProtocolLimitWarning; DeprecatedBy "write"], (* Regression test for RHBZ#597135. *) [InitBasicFS, Always, TestLastFail [["write_file"; "/new"; "abc"; "10000"]]], @@ -1625,7 +1637,7 @@ the content cannot contain embedded ASCII NULs). I<NB.> Owing to a bug, writing content containing ASCII NUL characters does I<not> work, even if the length is specified."); - ("umount", (RErr, [String "pathordevice"]), 45, [FishAlias "unmount"], + ("umount", (RErr, [String "pathordevice"], []), 45, [FishAlias "unmount"], [InitEmpty, Always, TestOutputListOfDevices ( [["part_disk"; "/dev/sda"; "mbr"]; ["mkfs"; "ext2"; "/dev/sda1"]; @@ -1643,7 +1655,7 @@ 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, [], + ("mounts", (RStringList "devices", [], []), 46, [], [InitBasicFS, Always, TestOutputListOfDevices ( [["mounts"]], ["/dev/sda1"])], "show mounted filesystems", @@ -1655,7 +1667,7 @@ Some internal mounts are not shown. See also: C<guestfs_mountpoints>"); - ("umount_all", (RErr, []), 47, [FishAlias "unmount-all"], + ("umount_all", (RErr, [], []), 47, [FishAlias "unmount-all"], [InitBasicFS, Always, TestOutputList ( [["umount_all"]; ["mounts"]], []); @@ -1679,14 +1691,14 @@ This unmounts all mounted filesystems. Some internal mounts are not unmounted by this call."); - ("lvm_remove_all", (RErr, []), 48, [DangerWillRobinson; Optional "lvm2"], + ("lvm_remove_all", (RErr, [], []), 48, [DangerWillRobinson; Optional "lvm2"], [], "remove all LVM LVs, VGs and PVs", "\ This command removes all LVM logical volumes, volume groups and physical volumes."); - ("file", (RString "description", [Dev_or_Path "path"]), 49, [], + ("file", (RString "description", [Dev_or_Path "path"], []), 49, [], [InitISOFS, Always, TestOutput ( [["file"; "/empty"]], "empty"); InitISOFS, Always, TestOutput ( @@ -1719,7 +1731,7 @@ this command only works for the content of regular files. For other file types (directory, symbolic link etc) it will just return the string C<directory> etc."); - ("command", (RString "output", [StringList "arguments"]), 50, [ProtocolLimitWarning], + ("command", (RString "output", [StringList "arguments"], []), 50, [ProtocolLimitWarning], [InitBasicFS, Always, TestOutput ( [["upload"; "test-command"; "/test-command"]; ["chmod"; "0o755"; "/test-command"]; @@ -1800,7 +1812,7 @@ 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, [ProtocolLimitWarning], + ("command_lines", (RStringList "lines", [StringList "arguments"], []), 51, [ProtocolLimitWarning], [InitBasicFS, Always, TestOutputList ( [["upload"; "test-command"; "/test-command"]; ["chmod"; "0o755"; "/test-command"]; @@ -1852,7 +1864,7 @@ result into a list of lines. See also: C<guestfs_sh_lines>"); - ("stat", (RStruct ("statbuf", "stat"), [Pathname "path"]), 52, [], + ("stat", (RStruct ("statbuf", "stat"), [Pathname "path"], []), 52, [], [InitISOFS, Always, TestOutputStruct ( [["stat"; "/empty"]], [CompareWithInt ("size", 0)])], "get file information", @@ -1861,7 +1873,7 @@ Returns file information for the given C<path>. This is the same as the C<stat(2)> system call."); - ("lstat", (RStruct ("statbuf", "stat"), [Pathname "path"]), 53, [], + ("lstat", (RStruct ("statbuf", "stat"), [Pathname "path"], []), 53, [], [InitISOFS, Always, TestOutputStruct ( [["lstat"; "/empty"]], [CompareWithInt ("size", 0)])], "get file information for a symbolic link", @@ -1874,7 +1886,7 @@ refers to. This is the same as the C<lstat(2)> system call."); - ("statvfs", (RStruct ("statbuf", "statvfs"), [Pathname "path"]), 54, [], + ("statvfs", (RStruct ("statbuf", "statvfs"), [Pathname "path"], []), 54, [], [InitISOFS, Always, TestOutputStruct ( [["statvfs"; "/"]], [CompareWithInt ("namemax", 255)])], "get file system statistics", @@ -1885,7 +1897,7 @@ C<path> should be a file or directory in the mounted file system This is the same as the C<statvfs(2)> system call."); - ("tune2fs_l", (RHashtable "superblock", [Device "device"]), 55, [], + ("tune2fs_l", (RHashtable "superblock", [Device "device"], []), 55, [], [], (* XXX test *) "get ext2/ext3/ext4 superblock details", "\ @@ -1897,7 +1909,7 @@ 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, [Device "device"]), 56, [], + ("blockdev_setro", (RErr, [Device "device"], []), 56, [], [InitEmpty, Always, TestOutputTrue ( [["blockdev_setro"; "/dev/sda"]; ["blockdev_getro"; "/dev/sda"]])], @@ -1907,7 +1919,7 @@ Sets the block device named C<device> to read-only. This uses the L<blockdev(8)> command."); - ("blockdev_setrw", (RErr, [Device "device"]), 57, [], + ("blockdev_setrw", (RErr, [Device "device"], []), 57, [], [InitEmpty, Always, TestOutputFalse ( [["blockdev_setrw"; "/dev/sda"]; ["blockdev_getro"; "/dev/sda"]])], @@ -1917,7 +1929,7 @@ Sets the block device named C<device> to read-write. This uses the L<blockdev(8)> command."); - ("blockdev_getro", (RBool "ro", [Device "device"]), 58, [], + ("blockdev_getro", (RBool "ro", [Device "device"], []), 58, [], [InitEmpty, Always, TestOutputTrue ( [["blockdev_setro"; "/dev/sda"]; ["blockdev_getro"; "/dev/sda"]])], @@ -1928,7 +1940,7 @@ Returns a boolean indicating if the block device is read-only This uses the L<blockdev(8)> command."); - ("blockdev_getss", (RInt "sectorsize", [Device "device"]), 59, [], + ("blockdev_getss", (RInt "sectorsize", [Device "device"], []), 59, [], [InitEmpty, Always, TestOutputInt ( [["blockdev_getss"; "/dev/sda"]], 512)], "get sectorsize of block device", @@ -1941,7 +1953,7 @@ for that). This uses the L<blockdev(8)> command."); - ("blockdev_getbsz", (RInt "blocksize", [Device "device"]), 60, [], + ("blockdev_getbsz", (RInt "blocksize", [Device "device"], []), 60, [], [InitEmpty, Always, TestOutputInt ( [["blockdev_getbsz"; "/dev/sda"]], 4096)], "get blocksize of block device", @@ -1953,7 +1965,7 @@ I<filesystem block size>). This uses the L<blockdev(8)> command."); - ("blockdev_setbsz", (RErr, [Device "device"; Int "blocksize"]), 61, [], + ("blockdev_setbsz", (RErr, [Device "device"; Int "blocksize"], []), 61, [], [], (* XXX test *) "set blocksize of block device", "\ @@ -1964,7 +1976,7 @@ I<filesystem block size>). This uses the L<blockdev(8)> command."); - ("blockdev_getsz", (RInt64 "sizeinsectors", [Device "device"]), 62, [], + ("blockdev_getsz", (RInt64 "sizeinsectors", [Device "device"], []), 62, [], [InitEmpty, Always, TestOutputInt ( [["blockdev_getsz"; "/dev/sda"]], 1024000)], "get total size of device in 512-byte sectors", @@ -1978,7 +1990,7 @@ useful I<size in bytes>. This uses the L<blockdev(8)> command."); - ("blockdev_getsize64", (RInt64 "sizeinbytes", [Device "device"]), 63, [], + ("blockdev_getsize64", (RInt64 "sizeinbytes", [Device "device"], []), 63, [], [InitEmpty, Always, TestOutputInt ( [["blockdev_getsize64"; "/dev/sda"]], 524288000)], "get total size of device in bytes", @@ -1989,7 +2001,7 @@ See also C<guestfs_blockdev_getsz>. This uses the L<blockdev(8)> command."); - ("blockdev_flushbufs", (RErr, [Device "device"]), 64, [], + ("blockdev_flushbufs", (RErr, [Device "device"], []), 64, [], [InitEmpty, Always, TestRun [["blockdev_flushbufs"; "/dev/sda"]]], "flush device buffers", @@ -1999,7 +2011,7 @@ with C<device>. This uses the L<blockdev(8)> command."); - ("blockdev_rereadpt", (RErr, [Device "device"]), 65, [], + ("blockdev_rereadpt", (RErr, [Device "device"], []), 65, [], [InitEmpty, Always, TestRun [["blockdev_rereadpt"; "/dev/sda"]]], "reread partition table", @@ -2008,7 +2020,7 @@ Reread the partition table on C<device>. This uses the L<blockdev(8)> command."); - ("upload", (RErr, [FileIn "filename"; Dev_or_Path "remotefilename"]), 66, [], + ("upload", (RErr, [FileIn "filename"; Dev_or_Path "remotefilename"], []), 66, [], [InitBasicFS, Always, TestOutput ( (* Pick a file from cwd which isn't likely to change. *) [["upload"; "../COPYING.LIB"; "/COPYING.LIB"]; @@ -2023,7 +2035,7 @@ C<filename> can also be a named pipe. See also C<guestfs_download>."); - ("download", (RErr, [Dev_or_Path "remotefilename"; FileOut "filename"]), 67, [Progress], + ("download", (RErr, [Dev_or_Path "remotefilename"; FileOut "filename"], []), 67, [Progress], [InitBasicFS, Always, TestOutput ( (* Pick a file from cwd which isn't likely to change. *) [["upload"; "../COPYING.LIB"; "/COPYING.LIB"]; @@ -2040,7 +2052,7 @@ C<filename> can also be a named pipe. See also C<guestfs_upload>, C<guestfs_cat>."); - ("checksum", (RString "checksum", [String "csumtype"; Pathname "path"]), 68, [], + ("checksum", (RString "checksum", [String "csumtype"; Pathname "path"], []), 68, [], [InitISOFS, Always, TestOutput ( [["checksum"; "crc"; "/known-3"]], "2891671662"); InitISOFS, Always, TestLastFail ( @@ -2107,7 +2119,7 @@ To get the checksum for a device, use C<guestfs_checksum_device>. To get the checksums for many files, use C<guestfs_checksums_out>."); - ("tar_in", (RErr, [FileIn "tarfile"; Pathname "directory"]), 69, [], + ("tar_in", (RErr, [FileIn "tarfile"; Pathname "directory"], []), 69, [], [InitBasicFS, Always, TestOutput ( [["tar_in"; "../images/helloworld.tar"; "/"]; ["cat"; "/hello"]], "hello\n")], @@ -2119,7 +2131,7 @@ I<uncompressed> tar file) into C<directory>. To upload a compressed tarball, use C<guestfs_tgz_in> or C<guestfs_txz_in>."); - ("tar_out", (RErr, [String "directory"; FileOut "tarfile"]), 70, [], + ("tar_out", (RErr, [String "directory"; FileOut "tarfile"], []), 70, [], [], "pack directory into tarfile", "\ @@ -2129,7 +2141,7 @@ it to local file C<tarfile>. To download a compressed tarball, use C<guestfs_tgz_out> or C<guestfs_txz_out>."); - ("tgz_in", (RErr, [FileIn "tarball"; Pathname "directory"]), 71, [], + ("tgz_in", (RErr, [FileIn "tarball"; Pathname "directory"], []), 71, [], [InitBasicFS, Always, TestOutput ( [["tgz_in"; "../images/helloworld.tar.gz"; "/"]; ["cat"; "/hello"]], "hello\n")], @@ -2140,7 +2152,7 @@ I<gzip compressed> tar file) into C<directory>. To upload an uncompressed tarball, use C<guestfs_tar_in>."); - ("tgz_out", (RErr, [Pathname "directory"; FileOut "tarball"]), 72, [], + ("tgz_out", (RErr, [Pathname "directory"; FileOut "tarball"], []), 72, [], [], "pack directory into compressed tarball", "\ @@ -2149,7 +2161,7 @@ it to local file C<tarball>. To download an uncompressed tarball, use C<guestfs_tar_out>."); - ("mount_ro", (RErr, [Device "device"; String "mountpoint"]), 73, [], + ("mount_ro", (RErr, [Device "device"; String "mountpoint"], []), 73, [], [InitBasicFS, Always, TestLastFail ( [["umount"; "/"]; ["mount_ro"; "/dev/sda1"; "/"]; @@ -2164,7 +2176,7 @@ To download an uncompressed tarball, use C<guestfs_tar_out>."); This is the same as the C<guestfs_mount> command, but it mounts the filesystem with the read-only (I<-o ro>) flag."); - ("mount_options", (RErr, [String "options"; Device "device"; String "mountpoint"]), 74, [], + ("mount_options", (RErr, [String "options"; Device "device"; String "mountpoint"], []), 74, [], [], "mount a guest disk with mount options", "\ @@ -2176,7 +2188,7 @@ If the C<options> parameter is an empty string, then no options are passed (all options default to whatever the filesystem uses)."); - ("mount_vfs", (RErr, [String "options"; String "vfstype"; Device "device"; String "mountpoint"]), 75, [], + ("mount_vfs", (RErr, [String "options"; String "vfstype"; Device "device"; String "mountpoint"], []), 75, [], [], "mount a guest disk with mount options and vfstype", "\ @@ -2184,7 +2196,7 @@ This is the same as the C<guestfs_mount> command, but it allows you to set both the mount options and the vfstype as for the L<mount(8)> I<-o> and I<-t> flags."); - ("debug", (RString "result", [String "subcmd"; StringList "extraargs"]), 76, [], + ("debug", (RString "result", [String "subcmd"; StringList "extraargs"], []), 76, [], [], "debugging and internals", "\ @@ -2196,7 +2208,7 @@ There is no comprehensive help for this command. You have to look at the file C<daemon/debug.c> in the libguestfs source to find out what you can do."); - ("lvremove", (RErr, [Device "device"]), 77, [Optional "lvm2"], + ("lvremove", (RErr, [Device "device"], []), 77, [Optional "lvm2"], [InitEmpty, Always, TestOutputList ( [["part_disk"; "/dev/sda"; "mbr"]; ["pvcreate"; "/dev/sda1"]; @@ -2229,7 +2241,7 @@ the path to the LV, such as C</dev/VG/LV>. You can also remove all LVs in a volume group by specifying the VG name, C</dev/VG>."); - ("vgremove", (RErr, [String "vgname"]), 78, [Optional "lvm2"], + ("vgremove", (RErr, [String "vgname"], []), 78, [Optional "lvm2"], [InitEmpty, Always, TestOutputList ( [["part_disk"; "/dev/sda"; "mbr"]; ["pvcreate"; "/dev/sda1"]; @@ -2253,7 +2265,7 @@ Remove an LVM volume group C<vgname>, (for example C<VG>). This also forcibly removes all logical volumes in the volume group (if any)."); - ("pvremove", (RErr, [Device "device"]), 79, [Optional "lvm2"], + ("pvremove", (RErr, [Device "device"], []), 79, [Optional "lvm2"], [InitEmpty, Always, TestOutputListOfDevices ( [["part_disk"; "/dev/sda"; "mbr"]; ["pvcreate"; "/dev/sda1"]; @@ -2290,7 +2302,7 @@ The implementation uses the C<pvremove> command which refuses to wipe physical volumes that contain any volume groups, so you have to remove those first."); - ("set_e2label", (RErr, [Device "device"; String "label"]), 80, [], + ("set_e2label", (RErr, [Device "device"; String "label"], []), 80, [], [InitBasicFS, Always, TestOutput ( [["set_e2label"; "/dev/sda1"; "testlabel"]; ["get_e2label"; "/dev/sda1"]], "testlabel")], @@ -2303,14 +2315,14 @@ C<device> to C<label>. Filesystem labels are limited to You can use either C<guestfs_tune2fs_l> or C<guestfs_get_e2label> to return the existing label on a filesystem."); - ("get_e2label", (RString "label", [Device "device"]), 81, [DeprecatedBy "vfs_label"], + ("get_e2label", (RString "label", [Device "device"], []), 81, [DeprecatedBy "vfs_label"], [], "get the ext2/3/4 filesystem label", "\ This returns the ext2/3/4 filesystem label of the filesystem on C<device>."); - ("set_e2uuid", (RErr, [Device "device"; String "uuid"]), 82, [], + ("set_e2uuid", (RErr, [Device "device"; String "uuid"], []), 82, [], (let uuid = uuidgen () in [InitBasicFS, Always, TestOutput ( [["set_e2uuid"; "/dev/sda1"; uuid]; @@ -2333,7 +2345,7 @@ L<tune2fs(8)> manpage. You can use either C<guestfs_tune2fs_l> or C<guestfs_get_e2uuid> to return the existing UUID of a filesystem."); - ("get_e2uuid", (RString "uuid", [Device "device"]), 83, [DeprecatedBy "vfs_uuid"], + ("get_e2uuid", (RString "uuid", [Device "device"], []), 83, [DeprecatedBy "vfs_uuid"], (* Regression test for RHBZ#597112. *) (let uuid = uuidgen () in [InitBasicFS, Always, TestOutput ( @@ -2345,7 +2357,7 @@ to return the existing UUID of a filesystem."); This returns the ext2/3/4 filesystem UUID of the filesystem on C<device>."); - ("fsck", (RInt "status", [String "fstype"; Device "device"]), 84, [FishOutput FishOutputHexadecimal], + ("fsck", (RInt "status", [String "fstype"; Device "device"], []), 84, [FishOutput FishOutputHexadecimal], [InitBasicFS, Always, TestOutputInt ( [["umount"; "/dev/sda1"]; ["fsck"; "ext2"; "/dev/sda1"]], 0); @@ -2383,7 +2395,7 @@ Checking or repairing NTFS volumes is not supported This command is entirely equivalent to running C<fsck -a -t fstype device>."); - ("zero", (RErr, [Device "device"]), 85, [Progress], + ("zero", (RErr, [Device "device"], []), 85, [Progress], [InitBasicFS, Always, TestOutput ( [["umount"; "/dev/sda1"]; ["zero"; "/dev/sda1"]; @@ -2398,7 +2410,7 @@ any partition tables, filesystem superblocks and so on. See also: C<guestfs_zero_device>, C<guestfs_scrub_device>."); - ("grub_install", (RErr, [Pathname "root"; Device "device"]), 86, [], + ("grub_install", (RErr, [Pathname "root"; Device "device"], []), 86, [], (* See: * https://bugzilla.redhat.com/show_bug.cgi?id=484986 * https://bugzilla.redhat.com/show_bug.cgi?id=479760 @@ -2424,7 +2436,7 @@ a file containing: replacing C</dev/vda> with the name of the installation device."); - ("cp", (RErr, [Pathname "src"; Pathname "dest"]), 87, [], + ("cp", (RErr, [Pathname "src"; Pathname "dest"], []), 87, [], [InitBasicFS, Always, TestOutput ( [["write"; "/old"; "file content"]; ["cp"; "/old"; "/new"]; @@ -2443,7 +2455,7 @@ replacing C</dev/vda> with the name of the installation device."); This copies a file from C<src> to C<dest> where C<dest> is either a destination filename or destination directory."); - ("cp_a", (RErr, [Pathname "src"; Pathname "dest"]), 88, [], + ("cp_a", (RErr, [Pathname "src"; Pathname "dest"], []), 88, [], [InitBasicFS, Always, TestOutput ( [["mkdir"; "/olddir"]; ["mkdir"; "/newdir"]; @@ -2455,7 +2467,7 @@ either a destination filename or destination directory."); This copies a file or directory from C<src> to C<dest> recursively using the C<cp -a> command."); - ("mv", (RErr, [Pathname "src"; Pathname "dest"]), 89, [], + ("mv", (RErr, [Pathname "src"; Pathname "dest"], []), 89, [], [InitBasicFS, Always, TestOutput ( [["write"; "/old"; "file content"]; ["mv"; "/old"; "/new"]; @@ -2469,7 +2481,7 @@ recursively using the C<cp -a> command."); This moves a file from C<src> to C<dest> where C<dest> is either a destination filename or destination directory."); - ("drop_caches", (RErr, [Int "whattodrop"]), 90, [], + ("drop_caches", (RErr, [Int "whattodrop"], []), 90, [], [InitEmpty, Always, TestRun ( [["drop_caches"; "3"]])], "drop kernel page cache, dentries and inodes", @@ -2484,7 +2496,7 @@ Setting C<whattodrop> to 3 should drop everything. This automatically calls L<sync(2)> before the operation, so that the maximum guest memory is freed."); - ("dmesg", (RString "kmsgs", []), 91, [], + ("dmesg", (RString "kmsgs", [], []), 91, [], [InitEmpty, Always, TestRun ( [["dmesg"]])], "return kernel messages", @@ -2498,7 +2510,7 @@ verbose messages with C<guestfs_set_verbose> or by setting the environment variable C<LIBGUESTFS_DEBUG=1> before running the program."); - ("ping_daemon", (RErr, []), 92, [], + ("ping_daemon", (RErr, [], []), 92, [], [InitEmpty, Always, TestRun ( [["ping_daemon"]])], "ping the guest daemon", @@ -2508,7 +2520,7 @@ the qemu subprocess. Calling this function checks that the daemon responds to the ping message, without affecting the daemon or attached block device(s) in any other way."); - ("equal", (RBool "equality", [Pathname "file1"; Pathname "file2"]), 93, [], + ("equal", (RBool "equality", [Pathname "file1"; Pathname "file2"], []), 93, [], [InitBasicFS, Always, TestOutputTrue ( [["write"; "/file1"; "contents of a file"]; ["cp"; "/file1"; "/file2"]; @@ -2526,7 +2538,7 @@ true if their content is exactly equal, or false otherwise. The external L<cmp(1)> program is used for the comparison."); - ("strings", (RStringList "stringsout", [Pathname "path"]), 94, [ProtocolLimitWarning], + ("strings", (RStringList "stringsout", [Pathname "path"], []), 94, [ProtocolLimitWarning], [InitISOFS, Always, TestOutputList ( [["strings"; "/known-5"]], ["abcdefghi"; "jklmnopqr"]); InitISOFS, Always, TestOutputList ( @@ -2539,7 +2551,7 @@ The external L<cmp(1)> program is used for the comparison."); This runs the L<strings(1)> command on a file and returns the list of printable strings found."); - ("strings_e", (RStringList "stringsout", [String "encoding"; Pathname "path"]), 95, [ProtocolLimitWarning], + ("strings_e", (RStringList "stringsout", [String "encoding"; Pathname "path"], []), 95, [ProtocolLimitWarning], [InitISOFS, Always, TestOutputList ( [["strings_e"; "b"; "/known-5"]], []); InitBasicFS, Always, TestOutputList ( @@ -2586,7 +2598,7 @@ This is useful for examining binaries in Windows guests. The returned strings are transcoded to UTF-8."); - ("hexdump", (RString "dump", [Pathname "path"]), 96, [ProtocolLimitWarning], + ("hexdump", (RString "dump", [Pathname "path"], []), 96, [ProtocolLimitWarning], [InitISOFS, Always, TestOutput ( [["hexdump"; "/known-4"]], "00000000 61 62 63 0a 64 65 66 0a 67 68 69 |abc.def.ghi|\n0000000b\n"); (* Test for RHBZ#501888c2 regression which caused large hexdump @@ -2602,7 +2614,7 @@ The returned strings are transcoded to UTF-8."); This runs C<hexdump -C> on the given C<path>. The result is the human-readable, canonical hex dump of the file."); - ("zerofree", (RErr, [Device "device"]), 97, [Optional "zerofree"], + ("zerofree", (RErr, [Device "device"], []), 97, [Optional "zerofree"], [InitNone, Always, TestOutput ( [["part_disk"; "/dev/sda"; "mbr"]; ["mkfs"; "ext3"; "/dev/sda1"]; @@ -2625,7 +2637,7 @@ mounted. It is possible that using this program can damage the filesystem or data on the filesystem."); - ("pvresize", (RErr, [Device "device"]), 98, [Optional "lvm2"], + ("pvresize", (RErr, [Device "device"], []), 98, [Optional "lvm2"], [], "resize an LVM physical volume", "\ @@ -2634,7 +2646,7 @@ volume to match the new size of the underlying device."); ("sfdisk_N", (RErr, [Device "device"; Int "partnum"; Int "cyls"; Int "heads"; Int "sectors"; - String "line"]), 99, [DangerWillRobinson], + String "line"], []), 99, [DangerWillRobinson], [], "modify a single partition on a block device", "\ @@ -2646,7 +2658,7 @@ pass C<0> for the cyls/heads/sectors parameters. See also: C<guestfs_part_add>"); - ("sfdisk_l", (RString "partitions", [Device "device"]), 100, [], + ("sfdisk_l", (RString "partitions", [Device "device"], []), 100, [], [], "display the partition table", "\ @@ -2656,7 +2668,7 @@ not intended to be parsed. See also: C<guestfs_part_list>"); - ("sfdisk_kernel_geometry", (RString "partitions", [Device "device"]), 101, [], + ("sfdisk_kernel_geometry", (RString "partitions", [Device "device"], []), 101, [], [], "display the kernel geometry", "\ @@ -2665,7 +2677,7 @@ This displays the kernel's idea of the geometry of C<device>. The result is in human-readable format, and not designed to be parsed."); - ("sfdisk_disk_geometry", (RString "partitions", [Device "device"]), 102, [], + ("sfdisk_disk_geometry", (RString "partitions", [Device "device"], []), 102, [], [], "display the disk geometry from the partition table", "\ @@ -2677,7 +2689,7 @@ kernel's idea of the geometry (see C<guestfs_sfdisk_kernel_geometry>). The result is in human-readable format, and not designed to be parsed."); - ("vg_activate_all", (RErr, [Bool "activate"]), 103, [Optional "lvm2"], + ("vg_activate_all", (RErr, [Bool "activate"], []), 103, [Optional "lvm2"], [], "activate or deactivate all volume groups", "\ @@ -2689,7 +2701,7 @@ then those devices disappear. This command is the same as running C<vgchange -a y|n>"); - ("vg_activate", (RErr, [Bool "activate"; StringList "volgroups"]), 104, [Optional "lvm2"], + ("vg_activate", (RErr, [Bool "activate"; StringList "volgroups"], []), 104, [Optional "lvm2"], [], "activate or deactivate some volume groups", "\ @@ -2704,7 +2716,7 @@ This command is the same as running C<vgchange -a y|n volgroups...> Note that if C<volgroups> is an empty list then B<all> volume groups are activated or deactivated."); - ("lvresize", (RErr, [Device "device"; Int "mbytes"]), 105, [Optional "lvm2"], + ("lvresize", (RErr, [Device "device"; Int "mbytes"], []), 105, [Optional "lvm2"], [InitNone, Always, TestOutput ( [["part_disk"; "/dev/sda"; "mbr"]; ["pvcreate"; "/dev/sda1"]; @@ -2732,7 +2744,7 @@ This resizes (expands or shrinks) an existing LVM logical volume to C<mbytes>. When reducing, data in the reduced part is lost."); - ("resize2fs", (RErr, [Device "device"]), 106, [], + ("resize2fs", (RErr, [Device "device"], []), 106, [], [], (* lvresize tests this *) "resize an ext2, ext3 or ext4 filesystem", "\ @@ -2745,7 +2757,7 @@ C<resize2fs> sometimes gives an error about this and sometimes not. In any case, it is always safe to call C<guestfs_e2fsck_f> before calling this function."); - ("find", (RStringList "names", [Pathname "directory"]), 107, [ProtocolLimitWarning], + ("find", (RStringList "names", [Pathname "directory"], []), 107, [ProtocolLimitWarning], [InitBasicFS, Always, TestOutputList ( [["find"; "/"]], ["lost+found"]); InitBasicFS, Always, TestOutputList ( @@ -2786,7 +2798,7 @@ The returned list is sorted. See also C<guestfs_find0>."); - ("e2fsck_f", (RErr, [Device "device"]), 108, [], + ("e2fsck_f", (RErr, [Device "device"], []), 108, [], [], (* lvresize tests this *) "check an ext2/ext3 filesystem", "\ @@ -2797,14 +2809,14 @@ even if the filesystem appears to be clean (C<-f>). This command is only needed because of C<guestfs_resize2fs> (q.v.). Normally you should use C<guestfs_fsck>."); - ("sleep", (RErr, [Int "secs"]), 109, [], + ("sleep", (RErr, [Int "secs"], []), 109, [], [InitNone, Always, TestRun ( [["sleep"; "1"]])], "sleep for some seconds", "\ Sleep for C<secs> seconds."); - ("ntfs_3g_probe", (RInt "status", [Bool "rw"; Device "device"]), 110, [Optional "ntfs3g"], + ("ntfs_3g_probe", (RInt "status", [Bool "rw"; Device "device"], []), 110, [Optional "ntfs3g"], [InitNone, Always, TestOutputInt ( [["part_disk"; "/dev/sda"; "mbr"]; ["mkfs"; "ntfs"; "/dev/sda1"]; @@ -2827,7 +2839,7 @@ The return value is an integer which C<0> if the operation would succeed, or some non-zero value documented in the L<ntfs-3g.probe(8)> manual page."); - ("sh", (RString "output", [String "command"]), 111, [], + ("sh", (RString "output", [String "command"], []), 111, [], [], (* XXX needs tests *) "run a command via the shell", "\ @@ -2844,7 +2856,7 @@ and so on. All the provisos about C<guestfs_command> apply to this call."); - ("sh_lines", (RStringList "lines", [String "command"]), 112, [], + ("sh_lines", (RStringList "lines", [String "command"], []), 112, [], [], (* XXX needs tests *) "run a command via the shell returning lines", "\ @@ -2853,7 +2865,7 @@ into a list of lines. See also: C<guestfs_command_lines>"); - ("glob_expand", (RStringList "paths", [Pathname "pattern"]), 113, [], + ("glob_expand", (RStringList "paths", [Pathname "pattern"], []), 113, [], (* Use Pathname here, and hence ABS_PATH (pattern,... in generated * code in stubs.c, since all valid glob patterns must start with "/". * There is no concept of "cwd" in libguestfs, hence no "."-relative names. @@ -2886,7 +2898,7 @@ It is just a wrapper around the C L<glob(3)> function with flags C<GLOB_MARK|GLOB_BRACE>. See that manual page for more details."); - ("scrub_device", (RErr, [Device "device"]), 114, [DangerWillRobinson; Optional "scrub"], + ("scrub_device", (RErr, [Device "device"], []), 114, [DangerWillRobinson; Optional "scrub"], [InitNone, Always, TestRun ( (* use /dev/sdc because it's smaller *) [["scrub_device"; "/dev/sdc"]])], "scrub (securely wipe) a device", @@ -2897,7 +2909,7 @@ more difficult. It is an interface to the L<scrub(1)> program. See that manual page for more details."); - ("scrub_file", (RErr, [Pathname "file"]), 115, [Optional "scrub"], + ("scrub_file", (RErr, [Pathname "file"], []), 115, [Optional "scrub"], [InitBasicFS, Always, TestRun ( [["write"; "/file"; "content"]; ["scrub_file"; "/file"]])], @@ -2911,7 +2923,7 @@ The file is I<removed> after scrubbing. It is an interface to the L<scrub(1)> program. See that manual page for more details."); - ("scrub_freespace", (RErr, [Pathname "dir"]), 116, [Optional "scrub"], + ("scrub_freespace", (RErr, [Pathname "dir"], []), 116, [Optional "scrub"], [], (* XXX needs testing *) "scrub (securely wipe) free space", "\ @@ -2924,7 +2936,7 @@ containing C<dir>. It is an interface to the L<scrub(1)> program. See that manual page for more details."); - ("mkdtemp", (RString "dir", [Pathname "template"]), 117, [], + ("mkdtemp", (RString "dir", [Pathname "template"], []), 117, [], [InitBasicFS, Always, TestRun ( [["mkdir"; "/tmp"]; ["mkdtemp"; "/tmp/tmpXXXXXX"]])], @@ -2949,7 +2961,7 @@ directory and its contents after use. See also: L<mkdtemp(3)>"); - ("wc_l", (RInt "lines", [Pathname "path"]), 118, [], + ("wc_l", (RInt "lines", [Pathname "path"], []), 118, [], [InitISOFS, Always, TestOutputInt ( [["wc_l"; "/10klines"]], 10000); (* Test for RHBZ#579608, absolute symbolic links. *) @@ -2960,7 +2972,7 @@ See also: L<mkdtemp(3)>"); This command counts the lines in a file, using the C<wc -l> external command."); - ("wc_w", (RInt "words", [Pathname "path"]), 119, [], + ("wc_w", (RInt "words", [Pathname "path"], []), 119, [], [InitISOFS, Always, TestOutputInt ( [["wc_w"; "/10klines"]], 10000)], "count words in a file", @@ -2968,7 +2980,7 @@ C<wc -l> external command."); This command counts the words in a file, using the C<wc -w> external command."); - ("wc_c", (RInt "chars", [Pathname "path"]), 120, [], + ("wc_c", (RInt "chars", [Pathname "path"], []), 120, [], [InitISOFS, Always, TestOutputInt ( [["wc_c"; "/100kallspaces"]], 102400)], "count characters in a file", @@ -2976,7 +2988,7 @@ C<wc -w> external command."); This command counts the characters in a file, using the C<wc -c> external command."); - ("head", (RStringList "lines", [Pathname "path"]), 121, [ProtocolLimitWarning], + ("head", (RStringList "lines", [Pathname "path"], []), 121, [ProtocolLimitWarning], [InitISOFS, Always, TestOutputList ( [["head"; "/10klines"]], ["0abcdefghijklmnopqrstuvwxyz";"1abcdefghijklmnopqrstuvwxyz";"2abcdefghijklmnopqrstuvwxyz";"3abcdefghijklmnopqrstuvwxyz";"4abcdefghijklmnopqrstuvwxyz";"5abcdefghijklmnopqrstuvwxyz";"6abcdefghijklmnopqrstuvwxyz";"7abcdefghijklmnopqrstuvwxyz";"8abcdefghijklmnopqrstuvwxyz";"9abcdefghijklmnopqrstuvwxyz"]); (* Test for RHBZ#579608, absolute symbolic links. *) @@ -2987,7 +2999,7 @@ C<wc -c> external command."); This command returns up to the first 10 lines of a file as a list of strings."); - ("head_n", (RStringList "lines", [Int "nrlines"; Pathname "path"]), 122, [ProtocolLimitWarning], + ("head_n", (RStringList "lines", [Int "nrlines"; Pathname "path"], []), 122, [ProtocolLimitWarning], [InitISOFS, Always, TestOutputList ( [["head_n"; "3"; "/10klines"]], ["0abcdefghijklmnopqrstuvwxyz";"1abcdefghijklmnopqrstuvwxyz";"2abcdefghijklmnopqrstuvwxyz"]); InitISOFS, Always, TestOutputList ( @@ -3004,7 +3016,7 @@ from the file C<path>, excluding the last C<nrlines> lines. If the parameter C<nrlines> is zero, this returns an empty list."); - ("tail", (RStringList "lines", [Pathname "path"]), 123, [ProtocolLimitWarning], + ("tail", (RStringList "lines", [Pathname "path"], []), 123, [ProtocolLimitWarning], [InitISOFS, Always, TestOutputList ( [["tail"; "/10klines"]], ["9990abcdefghijklmnopqrstuvwxyz";"9991abcdefghijklmnopqrstuvwxyz";"9992abcdefghijklmnopqrstuvwxyz";"9993abcdefghijklmnopqrstuvwxyz";"9994abcdefghijklmnopqrstuvwxyz";"9995abcdefghijklmnopqrstuvwxyz";"9996abcdefghijklmnopqrstuvwxyz";"9997abcdefghijklmnopqrstuvwxyz";"9998abcdefghijklmnopqrstuvwxyz";"9999abcdefghijklmnopqrstuvwxyz"])], "return last 10 lines of a file", @@ -3012,7 +3024,7 @@ If the parameter C<nrlines> is zero, this returns an empty list."); This command returns up to the last 10 lines of a file as a list of strings."); - ("tail_n", (RStringList "lines", [Int "nrlines"; Pathname "path"]), 124, [ProtocolLimitWarning], + ("tail_n", (RStringList "lines", [Int "nrlines"; Pathname "path"], []), 124, [ProtocolLimitWarning], [InitISOFS, Always, TestOutputList ( [["tail_n"; "3"; "/10klines"]], ["9997abcdefghijklmnopqrstuvwxyz";"9998abcdefghijklmnopqrstuvwxyz";"9999abcdefghijklmnopqrstuvwxyz"]); InitISOFS, Always, TestOutputList ( @@ -3029,7 +3041,7 @@ from the file C<path>, starting with the C<-nrlines>th line. If the parameter C<nrlines> is zero, this returns an empty list."); - ("df", (RString "output", []), 125, [], + ("df", (RString "output", [], []), 125, [], [], (* XXX Tricky to test because it depends on the exact format * of the 'df' command and other imponderables. *) @@ -3041,7 +3053,7 @@ This command is mostly useful for interactive sessions. It is I<not> intended that you try to parse the output string. Use C<statvfs> from programs."); - ("df_h", (RString "output", []), 126, [], + ("df_h", (RString "output", [], []), 126, [], [], (* XXX Tricky to test because it depends on the exact format * of the 'df' command and other imponderables. *) @@ -3054,7 +3066,7 @@ This command is mostly useful for interactive sessions. It is I<not> intended that you try to parse the output string. Use C<statvfs> from programs."); - ("du", (RInt64 "sizekb", [Pathname "path"]), 127, [], + ("du", (RInt64 "sizekb", [Pathname "path"], []), 127, [], [InitISOFS, Always, TestOutputInt ( [["du"; "/directory"]], 2 (* ISO fs blocksize is 2K *))], "estimate file space usage", @@ -3069,7 +3081,7 @@ subdirectories (recursively). The result is the estimated size in I<kilobytes> (ie. units of 1024 bytes)."); - ("initrd_list", (RStringList "filenames", [Pathname "path"]), 128, [], + ("initrd_list", (RStringList "filenames", [Pathname "path"], []), 128, [], [InitISOFS, Always, TestOutputList ( [["initrd_list"; "/initrd"]], ["empty";"known-1";"known-2";"known-3";"known-4"; "known-5"])], "list files in an initrd", @@ -3084,7 +3096,7 @@ Old Linux kernels (2.4 and earlier) used a compressed ext2 filesystem as initrd. We I<only> support the newer initramfs format (compressed cpio files)."); - ("mount_loop", (RErr, [Pathname "file"; Pathname "mountpoint"]), 129, [], + ("mount_loop", (RErr, [Pathname "file"; Pathname "mountpoint"], []), 129, [], [], "mount a file using the loop device", "\ @@ -3092,7 +3104,7 @@ This command lets you mount C<file> (a filesystem image in a file) on a mount point. It is entirely equivalent to the command C<mount -o loop file mountpoint>."); - ("mkswap", (RErr, [Device "device"]), 130, [], + ("mkswap", (RErr, [Device "device"], []), 130, [], [InitEmpty, Always, TestRun ( [["part_disk"; "/dev/sda"; "mbr"]; ["mkswap"; "/dev/sda1"]])], @@ -3100,7 +3112,7 @@ the command C<mount -o loop file mountpoint>."); "\ Create a swap partition on C<device>."); - ("mkswap_L", (RErr, [String "label"; Device "device"]), 131, [], + ("mkswap_L", (RErr, [String "label"; Device "device"], []), 131, [], [InitEmpty, Always, TestRun ( [["part_disk"; "/dev/sda"; "mbr"]; ["mkswap_L"; "hello"; "/dev/sda1"]])], @@ -3112,7 +3124,7 @@ Note that you cannot attach a swap label to a block device (eg. C</dev/sda>), just to a partition. This appears to be a limitation of the kernel or swap tools."); - ("mkswap_U", (RErr, [String "uuid"; Device "device"]), 132, [Optional "linuxfsuuid"], + ("mkswap_U", (RErr, [String "uuid"; Device "device"], []), 132, [Optional "linuxfsuuid"], (let uuid = uuidgen () in [InitEmpty, Always, TestRun ( [["part_disk"; "/dev/sda"; "mbr"]; @@ -3121,7 +3133,7 @@ a limitation of the kernel or swap tools."); "\ Create a swap partition on C<device> with UUID C<uuid>."); - ("mknod", (RErr, [Int "mode"; Int "devmajor"; Int "devminor"; Pathname "path"]), 133, [Optional "mknod"], + ("mknod", (RErr, [Int "mode"; Int "devmajor"; Int "devminor"; Pathname "path"], []), 133, [Optional "mknod"], [InitBasicFS, Always, TestOutputStruct ( [["mknod"; "0o10777"; "0"; "0"; "/node"]; (* NB: default umask 022 means 0777 -> 0755 in these tests *) @@ -3149,7 +3161,7 @@ in the appropriate constant for you. The mode actually set is affected by the umask."); - ("mkfifo", (RErr, [Int "mode"; Pathname "path"]), 134, [Optional "mknod"], + ("mkfifo", (RErr, [Int "mode"; Pathname "path"], []), 134, [Optional "mknod"], [InitBasicFS, Always, TestOutputStruct ( [["mkfifo"; "0o777"; "/node"]; ["stat"; "/node"]], [CompareWithInt ("mode", 0o10755)])], @@ -3161,7 +3173,7 @@ C<guestfs_mknod>. The mode actually set is affected by the umask."); - ("mknod_b", (RErr, [Int "mode"; Int "devmajor"; Int "devminor"; Pathname "path"]), 135, [Optional "mknod"], + ("mknod_b", (RErr, [Int "mode"; Int "devmajor"; Int "devminor"; Pathname "path"], []), 135, [Optional "mknod"], [InitBasicFS, Always, TestOutputStruct ( [["mknod_b"; "0o777"; "99"; "66"; "/node"]; ["stat"; "/node"]], [CompareWithInt ("mode", 0o60755)])], @@ -3173,7 +3185,7 @@ It is just a convenient wrapper around C<guestfs_mknod>. The mode actually set is affected by the umask."); - ("mknod_c", (RErr, [Int "mode"; Int "devmajor"; Int "devminor"; Pathname "path"]), 136, [Optional "mknod"], + ("mknod_c", (RErr, [Int "mode"; Int "devmajor"; Int "devminor"; Pathname "path"], []), 136, [Optional "mknod"], [InitBasicFS, Always, TestOutputStruct ( [["mknod_c"; "0o777"; "99"; "66"; "/node"]; ["stat"; "/node"]], [CompareWithInt ("mode", 0o20755)])], @@ -3185,7 +3197,7 @@ It is just a convenient wrapper around C<guestfs_mknod>. The mode actually set is affected by the umask."); - ("umask", (RInt "oldmask", [Int "mask"]), 137, [FishOutput FishOutputOctal], + ("umask", (RInt "oldmask", [Int "mask"], []), 137, [FishOutput FishOutputOctal], [InitEmpty, Always, TestOutputInt ( [["umask"; "0o22"]], 0o22)], "set file mode creation mask (umask)", @@ -3207,7 +3219,7 @@ L<umask(2)>, C<guestfs_mknod>, C<guestfs_mkdir>. This call returns the previous umask."); - ("readdir", (RStructList ("entries", "dirent"), [Pathname "dir"]), 138, [], + ("readdir", (RStructList ("entries", "dirent"), [Pathname "dir"], []), 138, [], [], "read directories entries", "\ @@ -3265,7 +3277,7 @@ This function is primarily intended for use by programs. To get a simple list of names, use C<guestfs_ls>. To get a printable directory for human consumption, use C<guestfs_ll>."); - ("sfdiskM", (RErr, [Device "device"; StringList "lines"]), 139, [DangerWillRobinson], + ("sfdiskM", (RErr, [Device "device"; StringList "lines"], []), 139, [DangerWillRobinson], [], "create partitions on a block device", "\ @@ -3278,7 +3290,7 @@ were rarely if ever used anyway. See also: C<guestfs_sfdisk>, the L<sfdisk(8)> manpage and C<guestfs_part_disk>"); - ("zfile", (RString "description", [String "meth"; Pathname "path"]), 140, [DeprecatedBy "file"], + ("zfile", (RString "description", [String "meth"; Pathname "path"], []), 140, [DeprecatedBy "file"], [], "determine file type inside a compressed file", "\ @@ -3290,7 +3302,7 @@ C<method> must be one of C<gzip>, C<compress> or C<bzip2>. Since 1.0.63, use C<guestfs_file> instead which can now process compressed files."); - ("getxattrs", (RStructList ("xattrs", "xattr"), [Pathname "path"]), 141, [Optional "linuxxattrs"], + ("getxattrs", (RStructList ("xattrs", "xattr"), [Pathname "path"], []), 141, [Optional "linuxxattrs"], [], "list extended attributes of a file or directory", "\ @@ -3302,7 +3314,7 @@ L<listxattr(2)> and L<getxattr(2)> calls. See also: C<guestfs_lgetxattrs>, L<attr(5)>."); - ("lgetxattrs", (RStructList ("xattrs", "xattr"), [Pathname "path"]), 142, [Optional "linuxxattrs"], + ("lgetxattrs", (RStructList ("xattrs", "xattr"), [Pathname "path"], []), 142, [Optional "linuxxattrs"], [], "list extended attributes of a file or directory", "\ @@ -3312,7 +3324,7 @@ of the link itself."); ("setxattr", (RErr, [String "xattr"; String "val"; Int "vallen"; (* will be BufferIn *) - Pathname "path"]), 143, [Optional "linuxxattrs"], + Pathname "path"], []), 143, [Optional "linuxxattrs"], [], "set extended attribute of a file or directory", "\ @@ -3324,7 +3336,7 @@ See also: C<guestfs_lsetxattr>, L<attr(5)>."); ("lsetxattr", (RErr, [String "xattr"; String "val"; Int "vallen"; (* will be BufferIn *) - Pathname "path"]), 144, [Optional "linuxxattrs"], + Pathname "path"], []), 144, [Optional "linuxxattrs"], [], "set extended attribute of a file or directory", "\ @@ -3332,7 +3344,7 @@ This is the same as C<guestfs_setxattr>, but if C<path> is a symbolic link, then it sets an extended attribute of the link itself."); - ("removexattr", (RErr, [String "xattr"; Pathname "path"]), 145, [Optional "linuxxattrs"], + ("removexattr", (RErr, [String "xattr"; Pathname "path"], []), 145, [Optional "linuxxattrs"], [], "remove extended attribute of a file or directory", "\ @@ -3341,7 +3353,7 @@ of the file C<path>. See also: C<guestfs_lremovexattr>, L<attr(5)>."); - ("lremovexattr", (RErr, [String "xattr"; Pathname "path"]), 146, [Optional "linuxxattrs"], + ("lremovexattr", (RErr, [String "xattr"; Pathname "path"], []), 146, [Optional "linuxxattrs"], [], "remove extended attribute of a file or directory", "\ @@ -3349,7 +3361,7 @@ This is the same as C<guestfs_removexattr>, but if C<path> is a symbolic link, then it removes an extended attribute of the link itself."); - ("mountpoints", (RHashtable "mps", []), 147, [], + ("mountpoints", (RHashtable "mps", [], []), 147, [], [], "show mountpoints", "\ @@ -3357,7 +3369,7 @@ This call is similar to C<guestfs_mounts>. That call returns a list of devices. This one returns a hash table (map) of device name to directory where the device is mounted."); - ("mkmountpoint", (RErr, [String "exemptpath"]), 148, [], + ("mkmountpoint", (RErr, [String "exemptpath"], []), 148, [], (* This is a special case: while you would expect a parameter * of type "Pathname", that doesn't work, because it implies * NEED_ROOT in the generated calling code in stubs.c, and @@ -3390,7 +3402,7 @@ in guestfish: The inner filesystem is now unpacked under the /ext3 mountpoint."); - ("rmmountpoint", (RErr, [String "exemptpath"]), 149, [], + ("rmmountpoint", (RErr, [String "exemptpath"], []), 149, [], [], "remove a mountpoint", "\ @@ -3398,7 +3410,7 @@ This calls removes a mountpoint that was previously created with C<guestfs_mkmountpoint>. See C<guestfs_mkmountpoint> for full details."); - ("read_file", (RBufferOut "content", [Pathname "path"]), 150, [ProtocolLimitWarning], + ("read_file", (RBufferOut "content", [Pathname "path"], []), 150, [ProtocolLimitWarning], [InitISOFS, Always, TestOutputBuffer ( [["read_file"; "/known-4"]], "abc\ndef\nghi"); (* Test various near large, large and too large files (RHBZ#589039). *) @@ -3424,7 +3436,7 @@ handle files that contain embedded ASCII NUL characters. However unlike C<guestfs_download>, this function is limited in the total size of file that can be handled."); - ("grep", (RStringList "lines", [String "regex"; Pathname "path"]), 151, [ProtocolLimitWarning], + ("grep", (RStringList "lines", [String "regex"; Pathname "path"], []), 151, [ProtocolLimitWarning], [InitISOFS, Always, TestOutputList ( [["grep"; "abc"; "/test-grep.txt"]], ["abc"; "abc123"]); InitISOFS, Always, TestOutputList ( @@ -3437,7 +3449,7 @@ in the total size of file that can be handled."); This calls the external C<grep> program and returns the matching lines."); - ("egrep", (RStringList "lines", [String "regex"; Pathname "path"]), 152, [ProtocolLimitWarning], + ("egrep", (RStringList "lines", [String "regex"; Pathname "path"], []), 152, [ProtocolLimitWarning], [InitISOFS, Always, TestOutputList ( [["egrep"; "abc"; "/test-grep.txt"]], ["abc"; "abc123"])], "return lines matching a pattern", @@ -3445,7 +3457,7 @@ matching lines."); This calls the external C<egrep> program and returns the matching lines."); - ("fgrep", (RStringList "lines", [String "pattern"; Pathname "path"]), 153, [ProtocolLimitWarning], + ("fgrep", (RStringList "lines", [String "pattern"; Pathname "path"], []), 153, [ProtocolLimitWarning], [InitISOFS, Always, TestOutputList ( [["fgrep"; "abc"; "/test-grep.txt"]], ["abc"; "abc123"])], "return lines matching a pattern", @@ -3453,7 +3465,7 @@ matching lines."); This calls the external C<fgrep> program and returns the matching lines."); - ("grepi", (RStringList "lines", [String "regex"; Pathname "path"]), 154, [ProtocolLimitWarning], + ("grepi", (RStringList "lines", [String "regex"; Pathname "path"], []), 154, [ProtocolLimitWarning], [InitISOFS, Always, TestOutputList ( [["grepi"; "abc"; "/test-grep.txt"]], ["abc"; "abc123"; "ABC"])], "return lines matching a pattern", @@ -3461,7 +3473,7 @@ matching lines."); This calls the external C<grep -i> program and returns the matching lines."); - ("egrepi", (RStringList "lines", [String "regex"; Pathname "path"]), 155, [ProtocolLimitWarning], + ("egrepi", (RStringList "lines", [String "regex"; Pathname "path"], []), 155, [ProtocolLimitWarning], [InitISOFS, Always, TestOutputList ( [["egrepi"; "abc"; "/test-grep.txt"]], ["abc"; "abc123"; "ABC"])], "return lines matching a pattern", @@ -3469,7 +3481,7 @@ matching lines."); This calls the external C<egrep -i> program and returns the matching lines."); - ("fgrepi", (RStringList "lines", [String "pattern"; Pathname "path"]), 156, [ProtocolLimitWarning], + ("fgrepi", (RStringList "lines", [String "pattern"; Pathname "path"], []), 156, [ProtocolLimitWarning], [InitISOFS, Always, TestOutputList ( [["fgrepi"; "abc"; "/test-grep.txt"]], ["abc"; "abc123"; "ABC"])], "return lines matching a pattern", @@ -3477,7 +3489,7 @@ matching lines."); This calls the external C<fgrep -i> program and returns the matching lines."); - ("zgrep", (RStringList "lines", [String "regex"; Pathname "path"]), 157, [ProtocolLimitWarning], + ("zgrep", (RStringList "lines", [String "regex"; Pathname "path"], []), 157, [ProtocolLimitWarning], [InitISOFS, Always, TestOutputList ( [["zgrep"; "abc"; "/test-grep.txt.gz"]], ["abc"; "abc123"])], "return lines matching a pattern", @@ -3485,7 +3497,7 @@ matching lines."); This calls the external C<zgrep> program and returns the matching lines."); - ("zegrep", (RStringList "lines", [String "regex"; Pathname "path"]), 158, [ProtocolLimitWarning], + ("zegrep", (RStringList "lines", [String "regex"; Pathname "path"], []), 158, [ProtocolLimitWarning], [InitISOFS, Always, TestOutputList ( [["zegrep"; "abc"; "/test-grep.txt.gz"]], ["abc"; "abc123"])], "return lines matching a pattern", @@ -3493,7 +3505,7 @@ matching lines."); This calls the external C<zegrep> program and returns the matching lines."); - ("zfgrep", (RStringList "lines", [String "pattern"; Pathname "path"]), 159, [ProtocolLimitWarning], + ("zfgrep", (RStringList "lines", [String "pattern"; Pathname "path"], []), 159, [ProtocolLimitWarning], [InitISOFS, Always, TestOutputList ( [["zfgrep"; "abc"; "/test-grep.txt.gz"]], ["abc"; "abc123"])], "return lines matching a pattern", @@ -3501,7 +3513,7 @@ matching lines."); This calls the external C<zfgrep> program and returns the matching lines."); - ("zgrepi", (RStringList "lines", [String "regex"; Pathname "path"]), 160, [ProtocolLimitWarning], + ("zgrepi", (RStringList "lines", [String "regex"; Pathname "path"], []), 160, [ProtocolLimitWarning], [InitISOFS, Always, TestOutputList ( [["zgrepi"; "abc"; "/test-grep.txt.gz"]], ["abc"; "abc123"; "ABC"])], "return lines matching a pattern", @@ -3509,7 +3521,7 @@ matching lines."); This calls the external C<zgrep -i> program and returns the matching lines."); - ("zegrepi", (RStringList "lines", [String "regex"; Pathname "path"]), 161, [ProtocolLimitWarning], + ("zegrepi", (RStringList "lines", [String "regex"; Pathname "path"], []), 161, [ProtocolLimitWarning], [InitISOFS, Always, TestOutputList ( [["zegrepi"; "abc"; "/test-grep.txt.gz"]], ["abc"; "abc123"; "ABC"])], "return lines matching a pattern", @@ -3517,7 +3529,7 @@ matching lines."); This calls the external C<zegrep -i> program and returns the matching lines."); - ("zfgrepi", (RStringList "lines", [String "pattern"; Pathname "path"]), 162, [ProtocolLimitWarning], + ("zfgrepi", (RStringList "lines", [String "pattern"; Pathname "path"], []), 162, [ProtocolLimitWarning], [InitISOFS, Always, TestOutputList ( [["zfgrepi"; "abc"; "/test-grep.txt.gz"]], ["abc"; "abc123"; "ABC"])], "return lines matching a pattern", @@ -3525,7 +3537,7 @@ matching lines."); This calls the external C<zfgrep -i> program and returns the matching lines."); - ("realpath", (RString "rpath", [Pathname "path"]), 163, [Optional "realpath"], + ("realpath", (RString "rpath", [Pathname "path"], []), 163, [Optional "realpath"], [InitISOFS, Always, TestOutput ( [["realpath"; "/../directory"]], "/directory")], "canonicalized absolute pathname", @@ -3533,7 +3545,7 @@ matching lines."); Return the canonicalized absolute pathname of C<path>. The returned path has no C<.>, C<..> or symbolic link path elements."); - ("ln", (RErr, [String "target"; Pathname "linkname"]), 164, [], + ("ln", (RErr, [String "target"; Pathname "linkname"], []), 164, [], [InitBasicFS, Always, TestOutputStruct ( [["touch"; "/a"]; ["ln"; "/a"; "/b"]; @@ -3542,7 +3554,7 @@ returned path has no C<.>, C<..> or symbolic link path elements."); "\ This command creates a hard link using the C<ln> command."); - ("ln_f", (RErr, [String "target"; Pathname "linkname"]), 165, [], + ("ln_f", (RErr, [String "target"; Pathname "linkname"], []), 165, [], [InitBasicFS, Always, TestOutputStruct ( [["touch"; "/a"]; ["touch"; "/b"]; @@ -3553,7 +3565,7 @@ This command creates a hard link using the C<ln> command."); This command creates a hard link using the C<ln -f> command. The C<-f> option removes the link (C<linkname>) if it exists already."); - ("ln_s", (RErr, [String "target"; Pathname "linkname"]), 166, [], + ("ln_s", (RErr, [String "target"; Pathname "linkname"], []), 166, [], [InitBasicFS, Always, TestOutputStruct ( [["touch"; "/a"]; ["ln_s"; "a"; "/b"]; @@ -3562,7 +3574,7 @@ The C<-f> option removes the link (C<linkname>) if it exists already."); "\ This command creates a symbolic link using the C<ln -s> command."); - ("ln_sf", (RErr, [String "target"; Pathname "linkname"]), 167, [], + ("ln_sf", (RErr, [String "target"; Pathname "linkname"], []), 167, [], [InitBasicFS, Always, TestOutput ( [["mkdir_p"; "/a/b"]; ["touch"; "/a/b/c"]; @@ -3573,13 +3585,13 @@ This command creates a symbolic link using the C<ln -s> command."); This command creates a symbolic link using the C<ln -sf> command, The C<-f> option removes the link (C<linkname>) if it exists already."); - ("readlink", (RString "link", [Pathname "path"]), 168, [], + ("readlink", (RString "link", [Pathname "path"], []), 168, [], [] (* XXX tested above *), "read the target of a symbolic link", "\ This command reads the target of a symbolic link."); - ("fallocate", (RErr, [Pathname "path"; Int "len"]), 169, [DeprecatedBy "fallocate64"], + ("fallocate", (RErr, [Pathname "path"; Int "len"], []), 169, [DeprecatedBy "fallocate64"], [InitBasicFS, Always, TestOutputStruct ( [["fallocate"; "/a"; "1000000"]; ["stat"; "/a"]], [CompareWithInt ("size", 1_000_000)])], @@ -3593,7 +3605,7 @@ Do not confuse this with the guestfish-specific C<alloc> command which allocates a file in the host and attaches it as a device."); - ("swapon_device", (RErr, [Device "device"]), 170, [], + ("swapon_device", (RErr, [Device "device"], []), 170, [], [InitPartition, Always, TestRun ( [["mkswap"; "/dev/sda1"]; ["swapon_device"; "/dev/sda1"]; @@ -3612,7 +3624,7 @@ the guest doesn't want you to trash. You also risk leaking information about the host to the guest this way. Instead, attach a new host device to the guest and swap on that."); - ("swapoff_device", (RErr, [Device "device"]), 171, [], + ("swapoff_device", (RErr, [Device "device"], []), 171, [], [], (* XXX tested by swapon_device *) "disable swap on device", "\ @@ -3620,7 +3632,7 @@ This command disables the libguestfs appliance swap device or partition named C<device>. See C<guestfs_swapon_device>."); - ("swapon_file", (RErr, [Pathname "file"]), 172, [], + ("swapon_file", (RErr, [Pathname "file"], []), 172, [], [InitBasicFS, Always, TestRun ( [["fallocate"; "/swap"; "8388608"]; ["mkswap_file"; "/swap"]; @@ -3631,13 +3643,13 @@ See C<guestfs_swapon_device>."); This command enables swap to a file. See C<guestfs_swapon_device> for other notes."); - ("swapoff_file", (RErr, [Pathname "file"]), 173, [], + ("swapoff_file", (RErr, [Pathname "file"], []), 173, [], [], (* XXX tested by swapon_file *) "disable swap on file", "\ This command disables the libguestfs appliance swap on file."); - ("swapon_label", (RErr, [String "label"]), 174, [], + ("swapon_label", (RErr, [String "label"], []), 174, [], [InitEmpty, Always, TestRun ( [["part_disk"; "/dev/sdb"; "mbr"]; ["mkswap_L"; "swapit"; "/dev/sdb1"]; @@ -3650,14 +3662,14 @@ This command disables the libguestfs appliance swap on file."); This command enables swap to a labeled swap partition. See C<guestfs_swapon_device> for other notes."); - ("swapoff_label", (RErr, [String "label"]), 175, [], + ("swapoff_label", (RErr, [String "label"], []), 175, [], [], (* XXX tested by swapon_label *) "disable swap on labeled swap partition", "\ This command disables the libguestfs appliance swap on labeled swap partition."); - ("swapon_uuid", (RErr, [String "uuid"]), 176, [Optional "linuxfsuuid"], + ("swapon_uuid", (RErr, [String "uuid"], []), 176, [Optional "linuxfsuuid"], (let uuid = uuidgen () in [InitEmpty, Always, TestRun ( [["mkswap_U"; uuid; "/dev/sdb"]; @@ -3668,14 +3680,14 @@ labeled swap partition."); This command enables swap to a swap partition with the given UUID. See C<guestfs_swapon_device> for other notes."); - ("swapoff_uuid", (RErr, [String "uuid"]), 177, [Optional "linuxfsuuid"], + ("swapoff_uuid", (RErr, [String "uuid"], []), 177, [Optional "linuxfsuuid"], [], (* XXX tested by swapon_uuid *) "disable swap on swap partition by UUID", "\ This command disables the libguestfs appliance swap partition with the given UUID."); - ("mkswap_file", (RErr, [Pathname "path"]), 178, [], + ("mkswap_file", (RErr, [Pathname "path"], []), 178, [], [InitBasicFS, Always, TestRun ( [["fallocate"; "/swap"; "8388608"]; ["mkswap_file"; "/swap"]])], @@ -3686,7 +3698,7 @@ Create a swap file. This command just writes a swap file signature to an existing file. To create the file itself, use something like C<guestfs_fallocate>."); - ("inotify_init", (RErr, [Int "maxevents"]), 179, [Optional "inotify"], + ("inotify_init", (RErr, [Int "maxevents"], []), 179, [Optional "inotify"], [InitISOFS, Always, TestRun ( [["inotify_init"; "0"]])], "create an inotify handle", @@ -3727,7 +3739,7 @@ as exposed by the Linux kernel, which is roughly what we expose via libguestfs. Note that there is one global inotify handle per libguestfs instance."); - ("inotify_add_watch", (RInt64 "wd", [Pathname "path"; Int "mask"]), 180, [Optional "inotify"], + ("inotify_add_watch", (RInt64 "wd", [Pathname "path"; Int "mask"], []), 180, [Optional "inotify"], [InitBasicFS, Always, TestOutputList ( [["inotify_init"; "0"]; ["inotify_add_watch"; "/"; "1073741823"]; @@ -3746,14 +3758,14 @@ Note for non-C or non-Linux callers: the inotify events are defined by the Linux kernel ABI and are listed in C</usr/include/sys/inotify.h>."); - ("inotify_rm_watch", (RErr, [Int(*XXX64*) "wd"]), 181, [Optional "inotify"], + ("inotify_rm_watch", (RErr, [Int(*XXX64*) "wd"], []), 181, [Optional "inotify"], [], "remove an inotify watch", "\ Remove a previously defined inotify watch. See C<guestfs_inotify_add_watch>."); - ("inotify_read", (RStructList ("events", "inotify_event"), []), 182, [Optional "inotify"], + ("inotify_read", (RStructList ("events", "inotify_event"), [], []), 182, [Optional "inotify"], [], "return list of inotify events", "\ @@ -3768,7 +3780,7 @@ returns an empty list. The reason is that the call will read events up to the maximum appliance-to-host message size and leave remaining events in the queue."); - ("inotify_files", (RStringList "paths", []), 183, [Optional "inotify"], + ("inotify_files", (RStringList "paths", [], []), 183, [Optional "inotify"], [], "return list of watched files that had events", "\ @@ -3776,7 +3788,7 @@ This function is a helpful wrapper around C<guestfs_inotify_read> which just returns a list of pathnames of objects that were touched. The returned pathnames are sorted and deduplicated."); - ("inotify_close", (RErr, []), 184, [Optional "inotify"], + ("inotify_close", (RErr, [], []), 184, [Optional "inotify"], [], "close the inotify handle", "\ @@ -3784,7 +3796,7 @@ This closes the inotify handle which was previously opened by inotify_init. It removes all watches, throws away any pending events, and deallocates all resources."); - ("setcon", (RErr, [String "context"]), 185, [Optional "selinux"], + ("setcon", (RErr, [String "context"], []), 185, [Optional "selinux"], [], "set SELinux security context", "\ @@ -3793,7 +3805,7 @@ to the string C<context>. See the documentation about SELINUX in L<guestfs(3)>."); - ("getcon", (RString "context", []), 186, [Optional "selinux"], + ("getcon", (RString "context", [], []), 186, [Optional "selinux"], [], "get SELinux security context", "\ @@ -3802,7 +3814,7 @@ This gets the SELinux security context of the daemon. See the documentation about SELINUX in L<guestfs(3)>, and C<guestfs_setcon>"); - ("mkfs_b", (RErr, [String "fstype"; Int "blocksize"; Device "device"]), 187, [], + ("mkfs_b", (RErr, [String "fstype"; Int "blocksize"; Device "device"], []), 187, [], [InitEmpty, Always, TestOutput ( [["part_disk"; "/dev/sda"; "mbr"]; ["mkfs_b"; "ext2"; "4096"; "/dev/sda1"]; @@ -3831,7 +3843,7 @@ are C<1024>, C<2048> or C<4096> only. For VFAT and NTFS the C<blocksize> parameter is treated as the requested cluster size."); - ("mke2journal", (RErr, [Int "blocksize"; Device "device"]), 188, [], + ("mke2journal", (RErr, [Int "blocksize"; Device "device"], []), 188, [], [InitEmpty, Always, TestOutput ( [["sfdiskM"; "/dev/sda"; ",100 ,"]; ["mke2journal"; "4096"; "/dev/sda1"]; @@ -3846,7 +3858,7 @@ to the command: mke2fs -O journal_dev -b blocksize device"); - ("mke2journal_L", (RErr, [Int "blocksize"; String "label"; Device "device"]), 189, [], + ("mke2journal_L", (RErr, [Int "blocksize"; String "label"; Device "device"], []), 189, [], [InitEmpty, Always, TestOutput ( [["sfdiskM"; "/dev/sda"; ",100 ,"]; ["mke2journal_L"; "4096"; "JOURNAL"; "/dev/sda1"]; @@ -3858,7 +3870,7 @@ to the command: "\ This creates an ext2 external journal on C<device> with label C<label>."); - ("mke2journal_U", (RErr, [Int "blocksize"; String "uuid"; Device "device"]), 190, [Optional "linuxfsuuid"], + ("mke2journal_U", (RErr, [Int "blocksize"; String "uuid"; Device "device"], []), 190, [Optional "linuxfsuuid"], (let uuid = uuidgen () in [InitEmpty, Always, TestOutput ( [["sfdiskM"; "/dev/sda"; ",100 ,"]; @@ -3871,7 +3883,7 @@ This creates an ext2 external journal on C<device> with label C<label>."); "\ This creates an ext2 external journal on C<device> with UUID C<uuid>."); - ("mke2fs_J", (RErr, [String "fstype"; Int "blocksize"; Device "device"; Device "journal"]), 191, [], + ("mke2fs_J", (RErr, [String "fstype"; Int "blocksize"; Device "device"; Device "journal"], []), 191, [], [], "make ext2/3/4 filesystem with external journal", "\ @@ -3883,7 +3895,7 @@ to the command: See also C<guestfs_mke2journal>."); - ("mke2fs_JL", (RErr, [String "fstype"; Int "blocksize"; Device "device"; String "label"]), 192, [], + ("mke2fs_JL", (RErr, [String "fstype"; Int "blocksize"; Device "device"; String "label"], []), 192, [], [], "make ext2/3/4 filesystem with external journal", "\ @@ -3892,7 +3904,7 @@ an external journal on the journal labeled C<label>. See also C<guestfs_mke2journal_L>."); - ("mke2fs_JU", (RErr, [String "fstype"; Int "blocksize"; Device "device"; String "uuid"]), 193, [Optional "linuxfsuuid"], + ("mke2fs_JU", (RErr, [String "fstype"; Int "blocksize"; Device "device"; String "uuid"], []), 193, [Optional "linuxfsuuid"], [], "make ext2/3/4 filesystem with external journal", "\ @@ -3901,7 +3913,7 @@ an external journal on the journal with UUID C<uuid>. See also C<guestfs_mke2journal_U>."); - ("modprobe", (RErr, [String "modulename"]), 194, [Optional "linuxmodules"], + ("modprobe", (RErr, [String "modulename"], []), 194, [Optional "linuxmodules"], [InitNone, Always, TestRun [["modprobe"; "fat"]]], "load a kernel module", "\ @@ -3910,7 +3922,7 @@ This loads a kernel module in the appliance. The kernel module must have been whitelisted when libguestfs was built (see C<appliance/kmod.whitelist.in> in the source)."); - ("echo_daemon", (RString "output", [StringList "words"]), 195, [], + ("echo_daemon", (RString "output", [StringList "words"], []), 195, [], [InitNone, Always, TestOutput ( [["echo_daemon"; "This is a test"]], "This is a test" )], @@ -3923,7 +3935,7 @@ You can use this command to test the connection through to the daemon. See also C<guestfs_ping_daemon>."); - ("find0", (RErr, [Pathname "directory"; FileOut "files"]), 196, [], + ("find0", (RErr, [Pathname "directory"; FileOut "files"], []), 196, [], [], (* There is a regression test for this. *) "find all files and directories, returning NUL-separated list", "\ @@ -3956,7 +3968,7 @@ The result list is not sorted. =back"); - ("case_sensitive_path", (RString "rpath", [Pathname "path"]), 197, [], + ("case_sensitive_path", (RString "rpath", [Pathname "path"], []), 197, [], [InitISOFS, Always, TestOutput ( [["case_sensitive_path"; "/DIRECTORY"]], "/directory"); InitISOFS, Always, TestOutput ( @@ -4014,7 +4026,7 @@ This function does not handle drive names, backslashes etc. See also C<guestfs_realpath>."); - ("vfs_type", (RString "fstype", [Device "device"]), 198, [], + ("vfs_type", (RString "fstype", [Device "device"], []), 198, [], [InitBasicFS, Always, TestOutput ( [["vfs_type"; "/dev/sda1"]], "ext2")], "get the Linux VFS type corresponding to a mounted device", @@ -4027,7 +4039,7 @@ VFS module which would be used to mount this filesystem if you mounted it without specifying the filesystem type. For example a string such as C<ext3> or C<ntfs>."); - ("truncate", (RErr, [Pathname "path"]), 199, [], + ("truncate", (RErr, [Pathname "path"], []), 199, [], [InitBasicFS, Always, TestOutputStruct ( [["write"; "/test"; "some stuff so size is not zero"]; ["truncate"; "/test"]; @@ -4037,7 +4049,7 @@ For example a string such as C<ext3> or C<ntfs>."); This command truncates C<path> to a zero-length file. The file must exist already."); - ("truncate_size", (RErr, [Pathname "path"; Int64 "size"]), 200, [], + ("truncate_size", (RErr, [Pathname "path"; Int64 "size"], []), 200, [], [InitBasicFS, Always, TestOutputStruct ( [["touch"; "/test"]; ["truncate_size"; "/test"; "1000"]; @@ -4053,7 +4065,7 @@ This creates a sparse file (ie. disk blocks are not allocated for the file until you write to it). To create a non-sparse file of zeroes, use C<guestfs_fallocate64> instead."); - ("utimens", (RErr, [Pathname "path"; Int64 "atsecs"; Int64 "atnsecs"; Int64 "mtsecs"; Int64 "mtnsecs"]), 201, [], + ("utimens", (RErr, [Pathname "path"; Int64 "atsecs"; Int64 "atnsecs"; Int64 "mtsecs"; Int64 "mtnsecs"], []), 201, [], [InitBasicFS, Always, TestOutputStruct ( [["touch"; "/test"]; ["utimens"; "/test"; "12345"; "67890"; "9876"; "5432"]; @@ -4077,7 +4089,7 @@ If the C<*nsecs> field contains the special value C<-2> then the corresponding timestamp is left unchanged. (The C<*secs> field is ignored in this case)."); - ("mkdir_mode", (RErr, [Pathname "path"; Int "mode"]), 202, [], + ("mkdir_mode", (RErr, [Pathname "path"; Int "mode"], []), 202, [], [InitBasicFS, Always, TestOutputStruct ( [["mkdir_mode"; "/test"; "0o111"]; ["stat"; "/test"]], [CompareWithInt ("mode", 0o40111)])], @@ -4092,7 +4104,7 @@ interpret the mode in other ways. See also C<guestfs_mkdir>, C<guestfs_umask>"); - ("lchown", (RErr, [Int "owner"; Int "group"; Pathname "path"]), 203, [], + ("lchown", (RErr, [Int "owner"; Int "group"; Pathname "path"], []), 203, [], [], (* XXX *) "change file owner and group", "\ @@ -4104,7 +4116,7 @@ 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)."); - ("lstatlist", (RStructList ("statbufs", "stat"), [Pathname "path"; StringList "names"]), 204, [], + ("lstatlist", (RStructList ("statbufs", "stat"), [Pathname "path"; StringList "names"], []), 204, [], [], (* XXX *) "lstat on multiple files", "\ @@ -4125,7 +4137,7 @@ might cause the protocol message size to be exceeded, causing this call to fail. The caller must split up such requests into smaller groups of names."); - ("lxattrlist", (RStructList ("xattrs", "xattr"), [Pathname "path"; StringList "names"]), 205, [Optional "linuxxattrs"], + ("lxattrlist", (RStructList ("xattrs", "xattr"), [Pathname "path"; StringList "names"], []), 205, [Optional "linuxxattrs"], [], (* XXX *) "lgetxattr on multiple files", "\ @@ -4151,7 +4163,7 @@ might cause the protocol message size to be exceeded, causing this call to fail. The caller must split up such requests into smaller groups of names."); - ("readlinklist", (RStringList "links", [Pathname "path"; StringList "names"]), 206, [], + ("readlinklist", (RStringList "links", [Pathname "path"; StringList "names"], []), 206, [], [], (* XXX *) "readlink on multiple files", "\ @@ -4177,7 +4189,7 @@ message size to be exceeded, causing this call to fail. The caller must split up such requests into smaller groups of names."); - ("pread", (RBufferOut "content", [Pathname "path"; Int "count"; Int64 "offset"]), 207, [ProtocolLimitWarning], + ("pread", (RBufferOut "content", [Pathname "path"; Int "count"; Int64 "offset"], []), 207, [ProtocolLimitWarning], [InitISOFS, Always, TestOutputBuffer ( [["pread"; "/known-4"; "1"; "3"]], "\n"); InitISOFS, Always, TestOutputBuffer ( @@ -4192,7 +4204,7 @@ see the L<pread(2)> system call. See also C<guestfs_pwrite>, C<guestfs_pread_device>."); - ("part_init", (RErr, [Device "device"; String "parttype"]), 208, [], + ("part_init", (RErr, [Device "device"; String "parttype"], []), 208, [], [InitEmpty, Always, TestRun ( [["part_init"; "/dev/sda"; "gpt"]])], "create an empty partition table", @@ -4264,7 +4276,7 @@ Sun disk labels. =back"); - ("part_add", (RErr, [Device "device"; String "prlogex"; Int64 "startsect"; Int64 "endsect"]), 209, [], + ("part_add", (RErr, [Device "device"; String "prlogex"; Int64 "startsect"; Int64 "endsect"], []), 209, [], [InitEmpty, Always, TestRun ( [["part_init"; "/dev/sda"; "mbr"]; ["part_add"; "/dev/sda"; "primary"; "1"; "-1"]]); @@ -4295,7 +4307,7 @@ backwards from the end of the disk (C<-1> is the last sector). Creating a partition which covers the whole disk is not so easy. Use C<guestfs_part_disk> to do that."); - ("part_disk", (RErr, [Device "device"; String "parttype"]), 210, [DangerWillRobinson], + ("part_disk", (RErr, [Device "device"; String "parttype"], []), 210, [DangerWillRobinson], [InitEmpty, Always, TestRun ( [["part_disk"; "/dev/sda"; "mbr"]]); InitEmpty, Always, TestRun ( @@ -4309,7 +4321,7 @@ covering the whole disk. C<parttype> is the partition table type, usually C<mbr> or C<gpt>, but other possible values are described in C<guestfs_part_init>."); - ("part_set_bootable", (RErr, [Device "device"; Int "partnum"; Bool "bootable"]), 211, [], + ("part_set_bootable", (RErr, [Device "device"; Int "partnum"; Bool "bootable"], []), 211, [], [InitEmpty, Always, TestRun ( [["part_disk"; "/dev/sda"; "mbr"]; ["part_set_bootable"; "/dev/sda"; "1"; "true"]])], @@ -4322,7 +4334,7 @@ The bootable flag is used by some operating systems (notably Windows) to determine which partition to boot from. It is by no means universally recognized."); - ("part_set_name", (RErr, [Device "device"; Int "partnum"; String "name"]), 212, [], + ("part_set_name", (RErr, [Device "device"; Int "partnum"; String "name"], []), 212, [], [InitEmpty, Always, TestRun ( [["part_disk"; "/dev/sda"; "gpt"]; ["part_set_name"; "/dev/sda"; "1"; "thepartname"]])], @@ -4334,7 +4346,7 @@ device C<device>. Note that partitions are numbered from 1. The partition name can only be set on certain types of partition table. This works on C<gpt> but not on C<mbr> partitions."); - ("part_list", (RStructList ("partitions", "partition"), [Device "device"]), 213, [], + ("part_list", (RStructList ("partitions", "partition"), [Device "device"], []), 213, [], [], (* XXX Add a regression test for this. *) "list partitions on a device", "\ @@ -4364,7 +4376,7 @@ Size of the partition in bytes. =back"); - ("part_get_parttype", (RString "parttype", [Device "device"]), 214, [], + ("part_get_parttype", (RString "parttype", [Device "device"], []), 214, [], [InitEmpty, Always, TestOutput ( [["part_disk"; "/dev/sda"; "gpt"]; ["part_get_parttype"; "/dev/sda"]], "gpt")], @@ -4378,7 +4390,7 @@ partition table), C<gpt> (a GPT/EFI-style partition table). Other values are possible, although unusual. See C<guestfs_part_init> for a full list."); - ("fill", (RErr, [Int "c"; Int "len"; Pathname "path"]), 215, [Progress], + ("fill", (RErr, [Int "c"; Int "len"; Pathname "path"], []), 215, [Progress], [InitBasicFS, Always, TestOutputBuffer ( [["fill"; "0x63"; "10"; "/test"]; ["read_file"; "/test"]], "cccccccccc")], @@ -4393,7 +4405,7 @@ much more efficient to use C<guestfs_truncate_size>. To create a file with a pattern of repeating bytes use C<guestfs_fill_pattern>."); - ("available", (RErr, [StringList "groups"]), 216, [], + ("available", (RErr, [StringList "groups"], []), 216, [], [InitNone, Always, TestRun [["available"; ""]]], "test availability of some parts of the API", "\ @@ -4454,7 +4466,7 @@ See also C<guestfs_version>. =back"); - ("dd", (RErr, [Dev_or_Path "src"; Dev_or_Path "dest"]), 217, [], + ("dd", (RErr, [Dev_or_Path "src"; Dev_or_Path "dest"], []), 217, [], [InitBasicFS, Always, TestOutputBuffer ( [["write"; "/src"; "hello, world"]; ["dd"; "/src"; "/dest"]; @@ -4470,7 +4482,7 @@ If the destination is a device, it must be as large or larger than the source file or device, otherwise the copy will fail. This command cannot do partial copies (see C<guestfs_copy_size>)."); - ("filesize", (RInt64 "size", [Pathname "file"]), 218, [], + ("filesize", (RInt64 "size", [Pathname "file"], []), 218, [], [InitBasicFS, Always, TestOutputInt ( [["write"; "/file"; "hello, world"]; ["filesize"; "/file"]], 12)], @@ -4482,7 +4494,7 @@ To get other stats about a file, use C<guestfs_stat>, C<guestfs_lstat>, C<guestfs_is_dir>, C<guestfs_is_file> etc. To get the size of block devices, use C<guestfs_blockdev_getsize64>."); - ("lvrename", (RErr, [String "logvol"; String "newlogvol"]), 219, [], + ("lvrename", (RErr, [String "logvol"; String "newlogvol"], []), 219, [], [InitBasicFSonLVM, Always, TestOutputList ( [["lvrename"; "/dev/VG/LV"; "/dev/VG/LV2"]; ["lvs"]], ["/dev/VG/LV2"])], @@ -4490,7 +4502,7 @@ To get the size of block devices, use C<guestfs_blockdev_getsize64>."); "\ Rename a logical volume C<logvol> with the new name C<newlogvol>."); - ("vgrename", (RErr, [String "volgroup"; String "newvolgroup"]), 220, [], + ("vgrename", (RErr, [String "volgroup"; String "newvolgroup"], []), 220, [], [InitBasicFSonLVM, Always, TestOutputList ( [["umount"; "/"]; ["vg_activate"; "false"; "VG"]; @@ -4502,7 +4514,7 @@ Rename a logical volume C<logvol> with the new name C<newlogvol>."); "\ Rename a volume group C<volgroup> with the new name C<newvolgroup>."); - ("initrd_cat", (RBufferOut "content", [Pathname "initrdpath"; String "filename"]), 221, [ProtocolLimitWarning], + ("initrd_cat", (RBufferOut "content", [Pathname "initrdpath"; String "filename"], []), 221, [ProtocolLimitWarning], [InitISOFS, Always, TestOutputBuffer ( [["initrd_cat"; "/initrd"; "known-4"]], "abc\ndef\nghi")], "list the contents of a single file in an initrd", @@ -4519,25 +4531,25 @@ contained in a Linux initrd or initramfs image: See also C<guestfs_initrd_list>."); - ("pvuuid", (RString "uuid", [Device "device"]), 222, [], + ("pvuuid", (RString "uuid", [Device "device"], []), 222, [], [], "get the UUID of a physical volume", "\ This command returns the UUID of the LVM PV C<device>."); - ("vguuid", (RString "uuid", [String "vgname"]), 223, [], + ("vguuid", (RString "uuid", [String "vgname"], []), 223, [], [], "get the UUID of a volume group", "\ This command returns the UUID of the LVM VG named C<vgname>."); - ("lvuuid", (RString "uuid", [Device "device"]), 224, [], + ("lvuuid", (RString "uuid", [Device "device"], []), 224, [], [], "get the UUID of a logical volume", "\ This command returns the UUID of the LVM LV C<device>."); - ("vgpvuuids", (RStringList "uuids", [String "vgname"]), 225, [], + ("vgpvuuids", (RStringList "uuids", [String "vgname"], []), 225, [], [], "get the PV UUIDs containing the volume group", "\ @@ -4549,7 +4561,7 @@ calls to associate physical volumes and volume groups. See also C<guestfs_vglvuuids>."); - ("vglvuuids", (RStringList "uuids", [String "vgname"]), 226, [], + ("vglvuuids", (RStringList "uuids", [String "vgname"], []), 226, [], [], "get the LV UUIDs of all LVs in the volume group", "\ @@ -4561,7 +4573,7 @@ calls to associate logical volumes and volume groups. See also C<guestfs_vgpvuuids>."); - ("copy_size", (RErr, [Dev_or_Path "src"; Dev_or_Path "dest"; Int64 "size"]), 227, [Progress], + ("copy_size", (RErr, [Dev_or_Path "src"; Dev_or_Path "dest"; Int64 "size"], []), 227, [Progress], [InitBasicFS, Always, TestOutputBuffer ( [["write"; "/src"; "hello, world"]; ["copy_size"; "/src"; "/dest"; "5"]; @@ -4574,7 +4586,7 @@ or file C<src> to another destination device or file C<dest>. Note this will fail if the source is too short or if the destination is not large enough."); - ("zero_device", (RErr, [Device "device"]), 228, [DangerWillRobinson; Progress], + ("zero_device", (RErr, [Device "device"], []), 228, [DangerWillRobinson; Progress], [InitBasicFSonLVM, Always, TestRun ( [["zero_device"; "/dev/VG/LV"]])], "write zeroes to an entire device", @@ -4583,7 +4595,7 @@ This command writes zeroes over the entire C<device>. Compare with C<guestfs_zero> which just zeroes the first few blocks of a device."); - ("txz_in", (RErr, [FileIn "tarball"; Pathname "directory"]), 229, [Optional "xz"], + ("txz_in", (RErr, [FileIn "tarball"; Pathname "directory"], []), 229, [Optional "xz"], [InitBasicFS, Always, TestOutput ( [["txz_in"; "../images/helloworld.tar.xz"; "/"]; ["cat"; "/hello"]], "hello\n")], @@ -4592,14 +4604,14 @@ a device."); This command uploads and unpacks local file C<tarball> (an I<xz compressed> tar file) into C<directory>."); - ("txz_out", (RErr, [Pathname "directory"; FileOut "tarball"]), 230, [Optional "xz"], + ("txz_out", (RErr, [Pathname "directory"; FileOut "tarball"], []), 230, [Optional "xz"], [], "pack directory into compressed tarball", "\ This command packs the contents of C<directory> and downloads it to local file C<tarball> (as an xz compressed tar archive)."); - ("ntfsresize", (RErr, [Device "device"]), 231, [Optional "ntfsprogs"], + ("ntfsresize", (RErr, [Device "device"], []), 231, [Optional "ntfsprogs"], [], "resize an NTFS filesystem", "\ @@ -4607,7 +4619,7 @@ This command resizes an NTFS filesystem, expanding or shrinking it to the size of the underlying device. See also L<ntfsresize(8)>."); - ("vgscan", (RErr, []), 232, [], + ("vgscan", (RErr, [], []), 232, [], [InitEmpty, Always, TestRun ( [["vgscan"]])], "rescan for LVM physical volumes, volume groups and logical volumes", @@ -4615,7 +4627,7 @@ See also L<ntfsresize(8)>."); This rescans all block devices and rebuilds the list of LVM physical volumes, volume groups and logical volumes."); - ("part_del", (RErr, [Device "device"; Int "partnum"]), 233, [], + ("part_del", (RErr, [Device "device"; Int "partnum"], []), 233, [], [InitEmpty, Always, TestRun ( [["part_init"; "/dev/sda"; "mbr"]; ["part_add"; "/dev/sda"; "primary"; "1"; "-1"]; @@ -4628,7 +4640,7 @@ Note that in the case of MBR partitioning, deleting an extended partition also deletes any logical partitions it contains."); - ("part_get_bootable", (RBool "bootable", [Device "device"; Int "partnum"]), 234, [], + ("part_get_bootable", (RBool "bootable", [Device "device"; Int "partnum"], []), 234, [], [InitEmpty, Always, TestOutputTrue ( [["part_init"; "/dev/sda"; "mbr"]; ["part_add"; "/dev/sda"; "primary"; "1"; "-1"]; @@ -4641,7 +4653,7 @@ C<device> has the bootable flag set. See also C<guestfs_part_set_bootable>."); - ("part_get_mbr_id", (RInt "idbyte", [Device "device"; Int "partnum"]), 235, [FishOutput FishOutputHexadecimal], + ("part_get_mbr_id", (RInt "idbyte", [Device "device"; Int "partnum"], []), 235, [FishOutput FishOutputHexadecimal], [InitEmpty, Always, TestOutputInt ( [["part_init"; "/dev/sda"; "mbr"]; ["part_add"; "/dev/sda"; "primary"; "1"; "-1"]; @@ -4656,7 +4668,7 @@ Note that only MBR (old DOS-style) partitions have type bytes. You will get undefined results for other partition table types (see C<guestfs_part_get_parttype>)."); - ("part_set_mbr_id", (RErr, [Device "device"; Int "partnum"; Int "idbyte"]), 236, [], + ("part_set_mbr_id", (RErr, [Device "device"; Int "partnum"; Int "idbyte"], []), 236, [], [], (* tested by part_get_mbr_id *) "set the MBR type byte (ID byte) of a partition", "\ @@ -4670,7 +4682,7 @@ Note that only MBR (old DOS-style) partitions have type bytes. You will get undefined results for other partition table types (see C<guestfs_part_get_parttype>)."); - ("checksum_device", (RString "checksum", [String "csumtype"; Device "device"]), 237, [], + ("checksum_device", (RString "checksum", [String "csumtype"; Device "device"], []), 237, [], [InitISOFS, Always, TestOutputFileMD5 ( [["checksum_device"; "md5"; "/dev/sdd"]], "../images/test.iso")], @@ -4680,7 +4692,7 @@ This call computes the MD5, SHAx or CRC checksum of the contents of the device named C<device>. For the types of checksums supported see the C<guestfs_checksum> command."); - ("lvresize_free", (RErr, [Device "lv"; Int "percent"]), 238, [Optional "lvm2"], + ("lvresize_free", (RErr, [Device "lv"; Int "percent"], []), 238, [Optional "lvm2"], [InitNone, Always, TestRun ( [["part_disk"; "/dev/sda"; "mbr"]; ["pvcreate"; "/dev/sda1"]; @@ -4695,14 +4707,14 @@ you would call this with pc = 100 which expands the logical volume as much as possible, using all remaining free space in the volume group."); - ("aug_clear", (RErr, [String "augpath"]), 239, [Optional "augeas"], + ("aug_clear", (RErr, [String "augpath"], []), 239, [Optional "augeas"], [], (* XXX Augeas code needs tests. *) "clear Augeas path", "\ Set the value associated with C<path> to C<NULL>. This is the same as the L<augtool(1)> C<clear> command."); - ("get_umask", (RInt "mask", []), 240, [FishOutput FishOutputOctal], + ("get_umask", (RInt "mask", [], []), 240, [FishOutput FishOutputOctal], [InitEmpty, Always, TestOutputInt ( [["get_umask"]], 0o22)], "get the current umask", @@ -4710,7 +4722,7 @@ is the same as the L<augtool(1)> C<clear> command."); Return the current umask. By default the umask is C<022> unless it has been set by calling C<guestfs_umask>."); - ("debug_upload", (RErr, [FileIn "filename"; String "tmpname"; Int "mode"]), 241, [], + ("debug_upload", (RErr, [FileIn "filename"; String "tmpname"; Int "mode"], []), 241, [], [], "upload a file to the appliance (internal use only)", "\ @@ -4721,7 +4733,7 @@ There is no comprehensive help for this command. You have to look at the file C<daemon/debug.c> in the libguestfs source to find out what it is for."); - ("base64_in", (RErr, [FileIn "base64file"; Pathname "filename"]), 242, [], + ("base64_in", (RErr, [FileIn "base64file"; Pathname "filename"], []), 242, [], [InitBasicFS, Always, TestOutput ( [["base64_in"; "../images/hello.b64"; "/hello"]; ["cat"; "/hello"]], "hello\n")], @@ -4730,14 +4742,14 @@ to find out what it is for."); This command uploads base64-encoded data from C<base64file> to C<filename>."); - ("base64_out", (RErr, [Pathname "filename"; FileOut "base64file"]), 243, [], + ("base64_out", (RErr, [Pathname "filename"; FileOut "base64file"], []), 243, [], [], "download file and encode as base64", "\ This command downloads the contents of C<filename>, writing it out to local file C<base64file> encoded as base64."); - ("checksums_out", (RErr, [String "csumtype"; Pathname "directory"; FileOut "sumsfile"]), 244, [], + ("checksums_out", (RErr, [String "csumtype"; Pathname "directory"; FileOut "sumsfile"], []), 244, [], [], "compute MD5, SHAx or CRC checksum of files in a directory", "\ @@ -4753,7 +4765,7 @@ filename is not printable, coreutils uses a special backslash syntax. For more information, see the GNU coreutils info file."); - ("fill_pattern", (RErr, [String "pattern"; Int "len"; Pathname "path"]), 245, [Progress], + ("fill_pattern", (RErr, [String "pattern"; Int "len"; Pathname "path"], []), 245, [Progress], [InitBasicFS, Always, TestOutputBuffer ( [["fill_pattern"; "abcdefghijklmnopqrstuvwxyz"; "28"; "/test"]; ["read_file"; "/test"]], "abcdefghijklmnopqrstuvwxyzab")], @@ -4764,7 +4776,7 @@ a new file of length C<len> containing the repeating pattern of bytes in C<pattern>. The pattern is truncated if necessary to ensure the length of the file is exactly C<len> bytes."); - ("write", (RErr, [Pathname "path"; BufferIn "content"]), 246, [ProtocolLimitWarning], + ("write", (RErr, [Pathname "path"; BufferIn "content"], []), 246, [ProtocolLimitWarning], [InitBasicFS, Always, TestOutput ( [["write"; "/new"; "new file contents"]; ["cat"; "/new"]], "new file contents"); @@ -4788,7 +4800,7 @@ to ensure the length of the file is exactly C<len> bytes."); This call creates a file called C<path>. The content of the file is the string C<content> (which can contain any 8 bit data)."); - ("pwrite", (RInt "nbytes", [Pathname "path"; BufferIn "content"; Int64 "offset"]), 247, [ProtocolLimitWarning], + ("pwrite", (RInt "nbytes", [Pathname "path"; BufferIn "content"; Int64 "offset"], []), 247, [ProtocolLimitWarning], [InitBasicFS, Always, TestOutput ( [["write"; "/new"; "new file contents"]; ["pwrite"; "/new"; "data"; "4"]; @@ -4814,28 +4826,28 @@ unlikely for regular files in ordinary circumstances. See also C<guestfs_pread>, C<guestfs_pwrite_device>."); - ("resize2fs_size", (RErr, [Device "device"; Int64 "size"]), 248, [], + ("resize2fs_size", (RErr, [Device "device"; Int64 "size"], []), 248, [], [], "resize an ext2, ext3 or ext4 filesystem (with size)", "\ This command is the same as C<guestfs_resize2fs> except that it allows you to specify the new size (in bytes) explicitly."); - ("pvresize_size", (RErr, [Device "device"; Int64 "size"]), 249, [Optional "lvm2"], + ("pvresize_size", (RErr, [Device "device"; Int64 "size"], []), 249, [Optional "lvm2"], [], "resize an LVM physical volume (with size)", "\ This command is the same as C<guestfs_pvresize> except that it allows you to specify the new size (in bytes) explicitly."); - ("ntfsresize_size", (RErr, [Device "device"; Int64 "size"]), 250, [Optional "ntfsprogs"], + ("ntfsresize_size", (RErr, [Device "device"; Int64 "size"], []), 250, [Optional "ntfsprogs"], [], "resize an NTFS filesystem (with size)", "\ This command is the same as C<guestfs_ntfsresize> except that it allows you to specify the new size (in bytes) explicitly."); - ("available_all_groups", (RStringList "groups", []), 251, [], + ("available_all_groups", (RStringList "groups", [], []), 251, [], [InitNone, Always, TestRun [["available_all_groups"]]], "return a list of all optional groups", "\ @@ -4847,7 +4859,7 @@ returned list. See also C<guestfs_available> and L<guestfs(3)/AVAILABILITY>."); - ("fallocate64", (RErr, [Pathname "path"; Int64 "len"]), 252, [], + ("fallocate64", (RErr, [Pathname "path"; Int64 "len"], []), 252, [], [InitBasicFS, Always, TestOutputStruct ( [["fallocate64"; "/a"; "1000000"]; ["stat"; "/a"]], [CompareWithInt ("size", 1_000_000)])], @@ -4869,7 +4881,7 @@ Do not confuse this with the guestfish-specific C<alloc> and C<sparse> commands which create a file in the host and attach it as a device."); - ("vfs_label", (RString "label", [Device "device"]), 253, [], + ("vfs_label", (RString "label", [Device "device"], []), 253, [], [InitBasicFS, Always, TestOutput ( [["set_e2label"; "/dev/sda1"; "LTEST"]; ["vfs_label"; "/dev/sda1"]], "LTEST")], @@ -4882,7 +4894,7 @@ If the filesystem is unlabeled, this returns the empty string. To find a filesystem from the label, use C<guestfs_findfs_label>."); - ("vfs_uuid", (RString "uuid", [Device "device"]), 254, [], + ("vfs_uuid", (RString "uuid", [Device "device"], []), 254, [], (let uuid = uuidgen () in [InitBasicFS, Always, TestOutput ( [["set_e2uuid"; "/dev/sda1"; uuid]; @@ -4896,7 +4908,7 @@ If the filesystem does not have a UUID, this returns the empty string. To find a filesystem from the UUID, use C<guestfs_findfs_uuid>."); - ("lvm_set_filter", (RErr, [DeviceList "devices"]), 255, [Optional "lvm2"], + ("lvm_set_filter", (RErr, [DeviceList "devices"], []), 255, [Optional "lvm2"], (* Can't be tested with the current framework because * the VG is being used by the mounted filesystem, so * the vgchange -an command we do first will fail. @@ -4927,7 +4939,7 @@ You cannot use this if any VG is currently in use (eg. contains a mounted filesystem), even if you are not filtering out that VG."); - ("lvm_clear_filter", (RErr, []), 256, [], + ("lvm_clear_filter", (RErr, [], []), 256, [], [], (* see note on lvm_set_filter *) "clear LVM device filter", "\ @@ -4937,7 +4949,7 @@ will be able to see every block device. This command also clears the LVM cache and performs a volume group scan."); - ("luks_open", (RErr, [Device "device"; Key "key"; String "mapname"]), 257, [Optional "luks"], + ("luks_open", (RErr, [Device "device"; Key "key"; String "mapname"], []), 257, [Optional "luks"], [], "open a LUKS-encrypted block device", "\ @@ -4957,14 +4969,14 @@ If this block device contains LVM volume groups, then calling C<guestfs_vgscan> followed by C<guestfs_vg_activate_all> will make them visible."); - ("luks_open_ro", (RErr, [Device "device"; Key "key"; String "mapname"]), 258, [Optional "luks"], + ("luks_open_ro", (RErr, [Device "device"; Key "key"; String "mapname"], []), 258, [Optional "luks"], [], "open a LUKS-encrypted block device read-only", "\ This is the same as C<guestfs_luks_open> except that a read-only mapping is created."); - ("luks_close", (RErr, [Device "device"]), 259, [Optional "luks"], + ("luks_close", (RErr, [Device "device"], []), 259, [Optional "luks"], [], "close a LUKS device", "\ @@ -4974,7 +4986,7 @@ C<device> parameter must be the name of the LUKS mapping device (ie. C</dev/mapper/mapname>) and I<not> the name of the underlying block device."); - ("luks_format", (RErr, [Device "device"; Key "key"; Int "keyslot"]), 260, [Optional "luks"; DangerWillRobinson], + ("luks_format", (RErr, [Device "device"; Key "key"; Int "keyslot"], []), 260, [Optional "luks"; DangerWillRobinson], [], "format a block device as a LUKS encrypted device", "\ @@ -4983,14 +4995,14 @@ the device as a LUKS encrypted device. C<key> is the initial key, which is added to key slot C<slot>. (LUKS supports 8 key slots, numbered 0-7)."); - ("luks_format_cipher", (RErr, [Device "device"; Key "key"; Int "keyslot"; String "cipher"]), 261, [Optional "luks"; DangerWillRobinson], + ("luks_format_cipher", (RErr, [Device "device"; Key "key"; Int "keyslot"; String "cipher"], []), 261, [Optional "luks"; DangerWillRobinson], [], "format a block device as a LUKS encrypted device", "\ This command is the same as C<guestfs_luks_format> but it also allows you to set the C<cipher> used."); - ("luks_add_key", (RErr, [Device "device"; Key "key"; Key "newkey"; Int "keyslot"]), 262, [Optional "luks"], + ("luks_add_key", (RErr, [Device "device"; Key "key"; Key "newkey"; Int "keyslot"], []), 262, [Optional "luks"], [], "add a key on a LUKS encrypted device", "\ @@ -5003,7 +5015,7 @@ Note that if C<keyslot> already contains a key, then this command will fail. You have to use C<guestfs_luks_kill_slot> first to remove that key."); - ("luks_kill_slot", (RErr, [Device "device"; Key "key"; Int "keyslot"]), 263, [Optional "luks"], + ("luks_kill_slot", (RErr, [Device "device"; Key "key"; Int "keyslot"], []), 263, [Optional "luks"], [], "remove a key from a LUKS encrypted device", "\ @@ -5011,7 +5023,7 @@ This command deletes the key in key slot C<keyslot> from the encrypted LUKS device C<device>. C<key> must be one of the I<other> keys."); - ("is_lv", (RBool "lvflag", [Device "device"]), 264, [Optional "lvm2"], + ("is_lv", (RBool "lvflag", [Device "device"], []), 264, [Optional "lvm2"], [InitBasicFSonLVM, IfAvailable "lvm2", TestOutputTrue ( [["is_lv"; "/dev/VG/LV"]]); InitBasicFSonLVM, IfAvailable "lvm2", TestOutputFalse ( @@ -5021,7 +5033,7 @@ I<other> keys."); This command tests whether C<device> is a logical volume, and returns true iff this is the case."); - ("findfs_uuid", (RString "device", [String "uuid"]), 265, [], + ("findfs_uuid", (RString "device", [String "uuid"], []), 265, [], [], "find a filesystem by UUID", "\ @@ -5031,7 +5043,7 @@ filesystem can be found. To find the UUID of a filesystem, use C<guestfs_vfs_uuid>."); - ("findfs_label", (RString "device", [String "label"]), 266, [], + ("findfs_label", (RString "device", [String "label"], []), 266, [], [], "find a filesystem by label", "\ @@ -5041,7 +5053,7 @@ filesystem can be found. To find the label of a filesystem, use C<guestfs_vfs_label>."); - ("is_chardev", (RBool "flag", [Pathname "path"]), 267, [], + ("is_chardev", (RBool "flag", [Pathname "path"], []), 267, [], [InitISOFS, Always, TestOutputFalse ( [["is_chardev"; "/directory"]]); InitBasicFS, Always, TestOutputTrue ( @@ -5054,7 +5066,7 @@ with the given C<path> name. See also C<guestfs_stat>."); - ("is_blockdev", (RBool "flag", [Pathname "path"]), 268, [], + ("is_blockdev", (RBool "flag", [Pathname "path"], []), 268, [], [InitISOFS, Always, TestOutputFalse ( [["is_blockdev"; "/directory"]]); InitBasicFS, Always, TestOutputTrue ( @@ -5067,7 +5079,7 @@ with the given C<path> name. See also C<guestfs_stat>."); - ("is_fifo", (RBool "flag", [Pathname "path"]), 269, [], + ("is_fifo", (RBool "flag", [Pathname "path"], []), 269, [], [InitISOFS, Always, TestOutputFalse ( [["is_fifo"; "/directory"]]); InitBasicFS, Always, TestOutputTrue ( @@ -5080,7 +5092,7 @@ with the given C<path> name. See also C<guestfs_stat>."); - ("is_symlink", (RBool "flag", [Pathname "path"]), 270, [], + ("is_symlink", (RBool "flag", [Pathname "path"], []), 270, [], [InitISOFS, Always, TestOutputFalse ( [["is_symlink"; "/directory"]]); InitISOFS, Always, TestOutputTrue ( @@ -5092,7 +5104,7 @@ with the given C<path> name. See also C<guestfs_stat>."); - ("is_socket", (RBool "flag", [Pathname "path"]), 271, [], + ("is_socket", (RBool "flag", [Pathname "path"], []), 271, [], (* XXX Need a positive test for sockets. *) [InitISOFS, Always, TestOutputFalse ( [["is_socket"; "/directory"]])], @@ -5103,7 +5115,7 @@ with the given C<path> name. See also C<guestfs_stat>."); - ("part_to_dev", (RString "device", [Device "partition"]), 272, [], + ("part_to_dev", (RString "device", [Device "partition"], []), 272, [], [InitPartition, Always, TestOutputDevice ( [["part_to_dev"; "/dev/sda1"]], "/dev/sda"); InitEmpty, Always, TestLastFail ( @@ -5117,7 +5129,7 @@ removes the partition number, returning the device name The named partition must exist, for example as a string returned from C<guestfs_list_partitions>."); - ("upload_offset", (RErr, [FileIn "filename"; Dev_or_Path "remotefilename"; Int64 "offset"]), 273, [], + ("upload_offset", (RErr, [FileIn "filename"; Dev_or_Path "remotefilename"; Int64 "offset"], []), 273, [], (let md5 = Digest.to_hex (Digest.file "COPYING.LIB") in [InitBasicFS, Always, TestOutput ( [["upload_offset"; "../COPYING.LIB"; "/COPYING.LIB"; "0"]; @@ -5141,7 +5153,7 @@ error occurs. See also C<guestfs_upload>, C<guestfs_pwrite>."); - ("download_offset", (RErr, [Dev_or_Path "remotefilename"; FileOut "filename"; Int64 "offset"; Int64 "size"]), 274, [Progress], + ("download_offset", (RErr, [Dev_or_Path "remotefilename"; FileOut "filename"; Int64 "offset"; Int64 "size"], []), 274, [Progress], (let md5 = Digest.to_hex (Digest.file "COPYING.LIB") in let offset = string_of_int 100 in let size = string_of_int ((Unix.stat "COPYING.LIB").Unix.st_size - 100) in @@ -5166,7 +5178,7 @@ error occurs. See also C<guestfs_download>, C<guestfs_pread>."); - ("pwrite_device", (RInt "nbytes", [Device "device"; BufferIn "content"; Int64 "offset"]), 275, [ProtocolLimitWarning], + ("pwrite_device", (RInt "nbytes", [Device "device"; BufferIn "content"; Int64 "offset"], []), 275, [ProtocolLimitWarning], [InitPartition, Always, TestOutputList ( [["pwrite_device"; "/dev/sda"; "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"; "446"]; ["blockdev_rereadpt"; "/dev/sda"]; @@ -5183,7 +5195,7 @@ probably impossible with standard Linux kernels). See also C<guestfs_pwrite>."); - ("pread_device", (RBufferOut "content", [Device "device"; Int "count"; Int64 "offset"]), 276, [ProtocolLimitWarning], + ("pread_device", (RBufferOut "content", [Device "device"; Int "count"; Int64 "offset"], []), 276, [ProtocolLimitWarning], [InitEmpty, Always, TestOutputBuffer ( [["pread_device"; "/dev/sdd"; "8"; "32768"]], "\001CD001\001\000")], "read part of a device", @@ -5217,16 +5229,16 @@ let max_proc_nr = (* Non-API meta-commands available only in guestfish. * - * Note (1): args/return value, proc_nr and tests fields are all - * meaningless. The only fields which are actually used are the - * shortname, FishAlias flags, shortdesc and longdesc. + * Note (1): style, proc_nr and tests fields are all meaningless. + * The only fields which are actually used are the shortname, + * FishAlias flags, shortdesc and longdesc. * * Note (2): to refer to other commands, use L</shortname>. * * Note (3): keep this list sorted by shortname. *) let fish_commands = [ - ("alloc", (RErr,[]), -1, [FishAlias "allocate"], [], + ("alloc", (RErr,[], []), -1, [FishAlias "allocate"], [], "allocate and add a disk file", " alloc filename size @@ -5240,7 +5252,7 @@ Size can be specified using standard suffixes, eg. C<1M>. To create a sparse file, use L</sparse> instead. To create a prepared disk image, see L</PREPARED DISK IMAGES>."); - ("copy_in", (RErr,[]), -1, [], [], + ("copy_in", (RErr,[], []), -1, [], [], "copy local files or directories into an image", " copy-in local [local ...] /remotedir @@ -5253,7 +5265,7 @@ Multiple local files and directories can be specified, but the last parameter must always be a remote directory. Wildcards cannot be used."); - ("copy_out", (RErr,[]), -1, [], [], + ("copy_out", (RErr,[], []), -1, [], [], "copy remote files or directories out of an image", " copy-out remote [remote ...] localdir @@ -5274,13 +5286,13 @@ them with the help of L</glob> like this: glob copy-out /home/* ."); - ("echo", (RErr,[]), -1, [], [], + ("echo", (RErr,[], []), -1, [], [], "display a line of text", " echo [params ...] This echos the parameters to the terminal."); - ("edit", (RErr,[]), -1, [FishAlias "vi"; FishAlias "emacs"], [], + ("edit", (RErr,[], []), -1, [FishAlias "vi"; FishAlias "emacs"], [], "edit a file", " edit filename @@ -5291,7 +5303,7 @@ The editor is C<$EDITOR>. However if you use the alternate commands C<vi> or C<emacs> you will get those corresponding editors."); - ("glob", (RErr,[]), -1, [], [], + ("glob", (RErr,[], []), -1, [], [], "expand wildcards in command", " glob command args... @@ -5300,7 +5312,7 @@ repeatedly on each matching path. See L</WILDCARDS AND GLOBBING>."); - ("hexedit", (RErr,[]), -1, [], [], + ("hexedit", (RErr,[], []), -1, [], [], "edit with a hex editor", " hexedit <filename|device> hexedit <filename|device> <max> @@ -5336,7 +5348,7 @@ environment variable. See also L</hexdump>."); - ("lcd", (RErr,[]), -1, [], [], + ("lcd", (RErr,[], []), -1, [], [], "change working directory", " lcd directory @@ -5345,13 +5357,13 @@ itself. Note that C<!cd> won't do what you might expect."); - ("man", (RErr,[]), -1, [FishAlias "manual"], [], + ("man", (RErr,[], []), -1, [FishAlias "manual"], [], "open the manual", " man Opens the manual page for guestfish."); - ("more", (RErr,[]), -1, [FishAlias "less"], [], + ("more", (RErr,[], []), -1, [FishAlias "less"], [], "view a file", " more filename @@ -5362,7 +5374,7 @@ This is used to view a file. The default viewer is C<$PAGER>. However if you use the alternate command C<less> you will get the C<less> command specifically."); - ("reopen", (RErr,[]), -1, [], [], + ("reopen", (RErr,[], []), -1, [], [], "close and reopen libguestfs handle", " reopen @@ -5370,7 +5382,7 @@ Close and reopen the libguestfs handle. It is not necessary to use this normally, because the handle is closed properly when guestfish exits. However this is occasionally useful for testing."); - ("sparse", (RErr,[]), -1, [], [], + ("sparse", (RErr,[], []), -1, [], [], "create a sparse disk image and add", " sparse filename size @@ -5387,7 +5399,7 @@ For more advanced image creation, see L<qemu-img(1)> utility. Size can be specified using standard suffixes, eg. C<1M>."); - ("supported", (RErr,[]), -1, [], [], + ("supported", (RErr,[], []), -1, [], [], "list supported groups of commands", " supported @@ -5397,7 +5409,7 @@ supported by this build of the libguestfs appliance. See also L<guestfs(3)/AVAILABILITY>."); - ("time", (RErr,[]), -1, [], [], + ("time", (RErr,[], []), -1, [], [], "print elapsed time taken to run a command", " time command args... diff --git a/generator/generator_bindtests.ml b/generator/generator_bindtests.ml index 66a5d551..adc10dcf 100644 --- a/generator/generator_bindtests.ml +++ b/generator/generator_bindtests.ml @@ -69,7 +69,7 @@ print_strings (char *const *argv) | test0 :: tests -> test0, tests in let () = - let (name, style, _, _, _, _, _) = test0 in + let (name, (ret, args, _ as style), _, _, _, _, _) = test0 in generate_prototype ~extern:false ~semicolon:false ~newline:true ~handle:"g" ~prefix:"guestfs__" name style; pr "{\n"; @@ -93,7 +93,7 @@ print_strings (char *const *argv) | Bool n -> pr " printf (\"%%s\\n\", %s ? \"true\" : \"false\");\n" n | Int n -> pr " printf (\"%%d\\n\", %s);\n" n | Int64 n -> pr " printf (\"%%\" PRIi64 \"\\n\", %s);\n" n - ) (snd style); + ) args; pr " /* Java changes stdout line buffering so we need this: */\n"; pr " fflush (stdout);\n"; pr " return 0;\n"; @@ -101,13 +101,13 @@ print_strings (char *const *argv) pr "\n" in List.iter ( - fun (name, style, _, _, _, _, _) -> + fun (name, (ret, args, _ as style), _, _, _, _, _) -> if String.sub name (String.length name - 3) 3 <> "err" then ( pr "/* Test normal return. */\n"; generate_prototype ~extern:false ~semicolon:false ~newline:true ~handle:"g" ~prefix:"guestfs__" name style; pr "{\n"; - (match fst style with + (match ret with | RErr -> pr " return 0;\n" | RInt _ -> @@ -174,7 +174,7 @@ print_strings (char *const *argv) ~handle:"g" ~prefix:"guestfs__" name style; pr "{\n"; pr " error (g, \"error\");\n"; - (match fst style with + (match ret with | RErr | RInt _ | RInt64 _ | RBool _ -> pr " return -1;\n" | RConstString _ | RConstOptString _ diff --git a/generator/generator_c.ml b/generator/generator_c.ml index ca0907d5..0a9060b6 100644 --- a/generator/generator_c.ml +++ b/generator/generator_c.ml @@ -30,15 +30,19 @@ open Generator_structs (* Generate C API. *) +type optarg_proto = Dots | VA | Argv + (* Generate a C function prototype. *) let rec generate_prototype ?(extern = true) ?(static = false) ?(semicolon = true) ?(single_line = false) ?(newline = false) ?(in_daemon = false) - ?(prefix = "") - ?handle name style = + ?(prefix = "") ?(suffix = "") + ?handle + ?(optarg_proto = Dots) + name (ret, args, optargs) = if extern then pr "extern "; if static then pr "static "; - (match fst style with + (match ret with | RErr -> pr "int " | RInt _ -> pr "int " | RInt64 _ -> pr "int64_t " @@ -53,10 +57,10 @@ let rec generate_prototype ?(extern = true) ?(static = false) if not in_daemon then pr "struct guestfs_%s_list *" typ else pr "guestfs_int_%s_list *" typ ); - let is_RBufferOut = match fst style with RBufferOut _ -> true | _ -> false in - pr "%s%s (" prefix name; - if handle = None && List.length (snd style) = 0 && not is_RBufferOut then - pr "void" + let is_RBufferOut = match ret with RBufferOut _ -> true | _ -> false in + pr "%s%s%s (" prefix name suffix; + if handle = None && args = [] && optargs = [] && not is_RBufferOut then + pr "void" else ( let comma = ref false in (match handle with @@ -92,15 +96,22 @@ let rec generate_prototype ?(extern = true) ?(static = false) pr "const char *%s" n; next (); pr "size_t %s_size" n - ) (snd style); + ) args; if is_RBufferOut then (next (); pr "size_t *size_r"); + if optargs <> [] then ( + next (); + match optarg_proto with + | Dots -> pr "..." + | VA -> pr "va_list args" + | Argv -> pr "const struct guestfs_%s_argv *optargs" name + ); ); pr ")"; if semicolon then pr ";"; if newline then pr "\n" (* Generate C call arguments, eg "(handle, foo, bar)" *) -and generate_c_call_args ?handle style = +and generate_c_call_args ?handle (ret, args, optargs) = pr "("; let comma = ref false in let next () = @@ -119,28 +130,56 @@ and generate_c_call_args ?handle style = | arg -> next (); pr "%s" (name_of_argt arg) - ) (snd style); + ) args; (* For RBufferOut calls, add implicit &size parameter. *) - (match fst style with + (match ret with | RBufferOut _ -> next (); pr "&size" | _ -> () ); + (* For calls with optional arguments, add implicit optargs parameter. *) + if optargs <> [] then ( + next (); + pr "optargs" + ); pr ")" (* Generate the pod documentation for the C API. *) and generate_actions_pod () = List.iter ( - fun (shortname, style, _, flags, _, _, longdesc) -> + fun (shortname, (ret, args, optargs as style), _, flags, _, _, longdesc) -> if not (List.mem NotInDocs flags) then ( let name = "guestfs_" ^ shortname in pr "=head2 %s\n\n" name; pr " "; generate_prototype ~extern:false ~handle:"g" name style; pr "\n\n"; + + let uc_shortname = String.uppercase shortname in + if optargs <> [] then ( + pr "You may supply a list of optional arguments to this call.\n"; + pr "Use zero or more of the following pairs of parameters,\n"; + pr "and terminate the list with C<-1> on its own.\n"; + pr "See L</CALLS WITH OPTIONAL ARGUMENTS>.\n\n"; + List.iter ( + fun argt -> + let n = name_of_argt argt in + let uc_n = String.uppercase n in + pr " GUESTFS_%s_%s, " uc_shortname uc_n; + match argt with + | Bool n -> pr "int %s,\n" n + | Int n -> pr "int %s,\n" n + | Int64 n -> pr "int64_t %s,\n" n + | String n -> pr "const char *%s,\n" n + | _ -> assert false + ) optargs; + pr "\n"; + ); + pr "%s\n\n" longdesc; - (match fst style with + let ret, args, optargs = style in + (match ret with | RErr -> pr "This function returns 0 on success or -1 on error.\n\n" | RInt _ -> @@ -189,13 +228,34 @@ I<The caller must free the returned buffer after use>.\n\n" pr "%s\n\n" protocol_limit_warning; if List.mem DangerWillRobinson flags then pr "%s\n\n" danger_will_robinson; - if List.exists (function Key _ -> true | _ -> false) (snd style) then + if List.exists (function Key _ -> true | _ -> false) (args@optargs) then pr "This function takes a key or passphrase parameter which could contain sensitive material. Read the section L</KEYS AND PASSPHRASES> for more information.\n\n"; - match deprecation_notice flags with - | None -> () - | Some txt -> pr "%s\n\n" txt + (match deprecation_notice flags with + | None -> () + | Some txt -> pr "%s\n\n" txt + ); + + (* Handling of optional argument variants. *) + if optargs <> [] then ( + pr "=head2 %s_va\n\n" name; + pr " "; + generate_prototype ~extern:false ~handle:"g" + ~prefix:"guestfs_" ~suffix:"_va" ~optarg_proto:VA + shortname style; + pr "\n\n"; + pr "This is the \"va_list variant\" of L</%s>.\n\n" name; + pr "See L</CALLS WITH OPTIONAL ARGUMENTS>.\n\n"; + pr "=head2 %s_argv\n\n" name; + pr " "; + generate_prototype ~extern:false ~handle:"g" + ~prefix:"guestfs_" ~suffix:"_argv" ~optarg_proto:Argv + shortname style; + pr "\n\n"; + pr "This is the \"argv variant\" of L</%s>.\n\n" name; + pr "See L</CALLS WITH OPTIONAL ARGUMENTS>.\n\n"; + ); ) ) all_functions_sorted @@ -305,9 +365,7 @@ and generate_structs_h () = and generate_actions_h () = generate_header CStyle LGPLv2plus; List.iter ( - fun (shortname, style, _, flags, _, _, _) -> - let name = "guestfs_" ^ shortname in - + fun (shortname, (ret, args, optargs as style), _, flags, _, _, _) -> let deprecated = List.exists (function DeprecatedBy _ -> true | _ -> false) flags in let test0 = @@ -318,7 +376,41 @@ and generate_actions_h () = pr "#define LIBGUESTFS_HAVE_%s 1\n" (String.uppercase shortname); generate_prototype ~single_line:true ~newline:true ~handle:"g" - name style + ~prefix:"guestfs_" shortname style; + + if optargs <> [] then ( + generate_prototype ~single_line:true ~newline:true ~handle:"g" + ~prefix:"guestfs_" ~suffix:"_va" ~optarg_proto:VA + shortname style; + + pr "struct guestfs_%s_argv {\n" shortname; + pr " uint64_t bitmask;\n"; + iteri ( + fun i argt -> + let c_type = + match argt with + | Bool n -> "int " + | Int n -> "int64_t " + | Int64 n -> "int " + | String n -> "const char *" + | _ -> assert false (* checked in generator_checks *) in + let uc_shortname = String.uppercase shortname in + let n = name_of_argt argt in + let uc_n = String.uppercase n in + pr "#define GUESTFS_%s_%s %d\n" uc_shortname uc_n i; + pr "#define GUESTFS_%s_%s_BITMASK (UINT64_C(1)<<%d)\n" uc_shortname uc_n i; + pr "/* The field below is only valid in this struct if the\n"; + pr " * GUESTFS_%s_%s_BITMASK bit is set\n" uc_shortname uc_n; + pr " * in the bitmask above, otherwise the contents are ignored.\n"; + pr " */\n"; + pr " %s%s;\n" c_type n + ) optargs; + pr "};\n"; + + generate_prototype ~single_line:true ~newline:true ~handle:"g" + ~prefix:"guestfs_" ~suffix:"_argv" ~optarg_proto:Argv + shortname style; + ); ) all_functions_sorted (* Generate the guestfs-internal-actions.h file. *) @@ -326,9 +418,9 @@ and generate_internal_actions_h () = generate_header CStyle LGPLv2plus; List.iter ( fun (shortname, style, _, _, _, _, _) -> - let name = "guestfs__" ^ shortname in generate_prototype ~single_line:true ~newline:true ~handle:"g" - name style + ~prefix:"guestfs__" ~optarg_proto:Argv + shortname style ) non_daemon_functions (* Generate the client-side dispatch stubs. *) @@ -408,7 +500,7 @@ check_state (guestfs_h *g, const char *caller) (* Generate code to check String-like parameters are not passed in * as NULL (returning an error if they are). *) - let check_null_strings shortname style = + let check_null_strings shortname (ret, args, optargs) = let pr_newline = ref false in List.iter ( function @@ -426,7 +518,7 @@ check_state (guestfs_h *g, const char *caller) pr " if (%s == NULL) {\n" n; pr " error (g, \"%%s: %%s: parameter cannot be NULL\",\n"; pr " \"%s\", \"%s\");\n" shortname n; - pr " return %s;\n" (error_code_of (fst style)); + pr " return %s;\n" (error_code_of ret); pr " }\n"; pr_newline := true @@ -437,25 +529,60 @@ check_state (guestfs_h *g, const char *caller) | Bool _ | Int _ | Int64 _ -> () - ) (snd style); + ) args; + + (* For optional arguments. *) + List.iter ( + function + | String n -> + pr " if ((optargs->bitmask & GUESTFS_%s_%s_BITMASK) &&\n" + (String.uppercase shortname) (String.uppercase n); + pr " optargs->%s == NULL) {\n" n; + pr " error (g, \"%%s: %%s: optional parameter cannot be NULL\",\n"; + pr " \"%s\", \"%s\");\n" shortname n; + pr " return %s;\n" (error_code_of ret); + pr " }\n"; + pr_newline := true + + (* not applicable *) + | Bool _ | Int _ | Int64 _ -> () + + | _ -> assert false + ) optargs; if !pr_newline then pr "\n"; in + (* Generate code to reject optargs we don't know about. *) + let reject_unknown_optargs shortname = function + | _, _, [] -> () + | ret, _, optargs -> + let len = List.length optargs in + let mask = Int64.lognot (Int64.pred (Int64.shift_left 1L len)) in + pr " if (optargs->bitmask & UINT64_C(0x%Lx)) {\n" mask; + pr " error (g, \"%%s: unknown option in guestfs_%%s_argv->bitmask (this can happen if a program is compiled against a newer version of libguestfs, then dynamically linked to an older version)\",\n"; + pr " \"%s\", \"%s\");\n" shortname shortname; + pr " return %s;\n" (error_code_of ret); + pr " }\n"; + pr "\n"; + in + (* Generate code to generate guestfish call traces. *) - let trace_call shortname style = + let trace_call shortname (ret, args, optargs) = pr " if (guestfs__get_trace (g)) {\n"; let needs_i = List.exists (function | StringList _ | DeviceList _ -> true - | _ -> false) (snd style) in + | _ -> false) args in if needs_i then ( pr " size_t i;\n"; pr "\n" ); pr " fprintf (stderr, \"%s\");\n" shortname; + + (* Required arguments. *) List.iter ( function | String n (* strings *) @@ -486,7 +613,29 @@ check_state (guestfs_h *g, const char *caller) pr " fprintf (stderr, \" %%d\", %s);\n" n | Int64 n -> pr " fprintf (stderr, \" %%\" PRIi64, %s);\n" n - ) (snd style); + ) args; + + (* Optional arguments. *) + List.iter ( + fun argt -> + let n = name_of_argt argt in + let uc_shortname = String.uppercase shortname in + let uc_n = String.uppercase n in + pr " if (optargs->bitmask & GUESTFS_%s_%s_BITMASK)\n" + uc_shortname uc_n; + (match argt with + | String n -> + pr " fprintf (stderr, \" \\\"%%s:%%s\\\"\", \"%s\", optargs->%s);\n" n n + | Bool n -> + pr " fprintf (stderr, \" \\\"%%s:%%s\\\"\", \"%s\", optargs->%s ? \"true\" : \"false\");\n" n n + | Int n -> + pr " fprintf (stderr, \" \\\"%%s:%%d\\\"\", \"%s\", optargs->%s);\n" n n + | Int64 n -> + pr " fprintf (stderr, \" \\\"%%s:%%\" PRIi64 \"\\\"\", \"%s\", optargs->%s);\n" n n + | _ -> assert false + ); + ) optargs; + pr " fputc ('\\n', stderr);\n"; pr " }\n"; pr "\n"; @@ -494,13 +643,18 @@ check_state (guestfs_h *g, const char *caller) (* For non-daemon functions, generate a wrapper around each function. *) List.iter ( - fun (shortname, style, _, _, _, _, _) -> - let name = "guestfs_" ^ shortname in - - generate_prototype ~extern:false ~semicolon:false ~newline:true - ~handle:"g" name style; + fun (shortname, (_, _, optargs as style), _, _, _, _, _) -> + if optargs = [] then + generate_prototype ~extern:false ~semicolon:false ~newline:true + ~handle:"g" ~prefix:"guestfs_" + shortname style + else + generate_prototype ~extern:false ~semicolon:false ~newline:true + ~handle:"g" ~prefix:"guestfs_" ~suffix:"_argv" ~optarg_proto:Argv + shortname style; pr "{\n"; check_null_strings shortname style; + reject_unknown_optargs shortname style; trace_call shortname style; pr " return guestfs__%s " shortname; generate_c_call_args ~handle:"g" style; @@ -511,17 +665,24 @@ check_state (guestfs_h *g, const char *caller) (* Client-side stubs for each function. *) List.iter ( - fun (shortname, style, _, _, _, _, _) -> + fun (shortname, (ret, args, optargs as style), _, _, _, _, _) -> + if optargs <> [] then + failwithf "optargs not yet implemented for daemon functions"; + let name = "guestfs_" ^ shortname in - let error_code = error_code_of (fst style) in + let error_code = error_code_of ret in (* Generate the action stub. *) - generate_prototype ~extern:false ~semicolon:false ~newline:true - ~handle:"g" name style; + if optargs = [] then + generate_prototype ~extern:false ~semicolon:false ~newline:true + ~handle:"g" name style + else + generate_prototype ~extern:false ~semicolon:false ~newline:true + ~handle:"g" ~suffix:"_argv" ~optarg_proto:Argv name style; pr "{\n"; - (match snd style with + (match args with | [] -> () | _ -> pr " struct %s_args args;\n" name ); @@ -529,7 +690,7 @@ check_state (guestfs_h *g, const char *caller) pr " guestfs_message_header hdr;\n"; pr " guestfs_message_error err;\n"; let has_ret = - match fst style with + match ret with | RErr -> false | RConstString _ | RConstOptString _ -> failwithf "RConstString|RConstOptString cannot be used by daemon functions" @@ -544,6 +705,7 @@ check_state (guestfs_h *g, const char *caller) pr " int r;\n"; pr "\n"; check_null_strings shortname style; + reject_unknown_optargs shortname style; trace_call shortname style; pr " if (check_state (g, \"%s\") == -1) return %s;\n" shortname error_code; @@ -551,7 +713,7 @@ check_state (guestfs_h *g, const char *caller) pr "\n"; (* Send the main header and arguments. *) - (match snd style with + (match args with | [] -> pr " serial = guestfs___send (g, GUESTFS_PROC_%s, NULL, NULL);\n" (String.uppercase shortname) @@ -609,7 +771,7 @@ check_state (guestfs_h *g, const char *caller) need_read_reply_label := true; pr "\n"; | _ -> () - ) (snd style); + ) args; (* Wait for the reply from the remote end. *) if !need_read_reply_label then pr " read_reply:\n"; @@ -655,11 +817,11 @@ check_state (guestfs_h *g, const char *caller) pr " }\n"; pr "\n"; | _ -> () - ) (snd style); + ) args; pr " guestfs___end_busy (g);\n"; - (match fst style with + (match ret with | RErr -> pr " return 0;\n" | RInt n | RInt64 n | RBool n -> pr " return ret.%s;\n" n @@ -728,6 +890,95 @@ check_state (guestfs_h *g, const char *caller) ) structs; + (* Functions which have optional arguments have two generated variants. *) + List.iter ( + function + | shortname, (ret, args, (_::_ as optargs) as style), _, _, _, _, _ -> + let uc_shortname = String.uppercase shortname in + + (* Get the name of the last regular argument. *) + let last_arg = + match args with + | [] -> "g" + | args -> name_of_argt (List.hd (List.rev args)) in + + let rerrcode, rtype = + match ret with + | RErr | RInt _ | RBool _ -> "-1", "int " + | RInt64 _ -> "-1", "int64_t " + | RConstString _ | RConstOptString _ -> "NULL", "const char *" + | RString _ | RBufferOut _ -> "NULL", "char *" + | RStringList _ | RHashtable _ -> "NULL", "char **" + | RStruct (_, typ) -> "NULL", sprintf "struct guestfs_%s *" typ + | RStructList (_, typ) -> + "NULL", sprintf "struct guestfs_%s_list *" typ in + + (* The regular variable args function, just calls the _va variant. *) + generate_prototype ~extern:false ~semicolon:false ~newline:true + ~handle:"g" ~prefix:"guestfs_" shortname style; + pr "{\n"; + pr " va_list optargs;\n"; + pr "\n"; + pr " va_start (optargs, %s);\n" last_arg; + pr " %sr = guestfs_%s_va " rtype shortname; + generate_c_call_args ~handle:"g" style; + pr ";\n"; + pr " va_end (optargs);\n"; + pr "\n"; + pr " return r;\n"; + pr "}\n\n"; + + generate_prototype ~extern:false ~semicolon:false ~newline:true + ~handle:"g" ~prefix:"guestfs_" ~suffix:"_va" ~optarg_proto:VA + shortname style; + pr "{\n"; + pr " struct guestfs_%s_argv optargs_s;\n" shortname; + pr " struct guestfs_%s_argv *optargs = &optargs_s;\n" shortname; + pr " int i;\n"; + pr "\n"; + pr " optargs_s.bitmask = 0;\n"; + pr "\n"; + pr " while ((i = va_arg (args, int)) >= 0) {\n"; + pr " switch (i) {\n"; + + List.iter ( + fun argt -> + let n = name_of_argt argt in + let uc_n = String.uppercase n in + pr " case GUESTFS_%s_%s:\n" uc_shortname uc_n; + pr " optargs_s.%s = va_arg (args, " n; + (match argt with + | Bool _ | Int _ -> pr "int" + | Int64 _ -> pr "int64_t" + | String _ -> pr "const char *" + | _ -> assert false + ); + pr ");\n"; + pr " break;\n"; + ) optargs; + + pr " default:\n"; + pr " error (g, \"%%s: unknown option %%d (this can happen if a program is compiled against a newer version of libguestfs, then dynamically linked to an older version)\",\n"; + pr " \"%s\", i);\n" shortname; + pr " return %s;\n" rerrcode; + pr " }\n"; + pr "\n"; + pr " uint64_t i_mask = UINT64_C(1) << i;\n"; + pr " if (optargs_s.bitmask & i_mask) {\n"; + pr " error (g, \"%%s: same optional argument specified more than once\",\n"; + pr " \"%s\");\n" shortname; + pr " return %s;\n" rerrcode; + pr " }\n"; + pr " optargs_s.bitmask |= i_mask;\n"; + pr " }\n"; + pr "\n"; + pr " return guestfs_%s_argv " shortname; + generate_c_call_args ~handle:"g" style; + pr ";\n"; + pr "}\n\n" + | _ -> () + ) all_functions_sorted + (* Generate the linker script which controls the visibility of * symbols in the public ABI and ensures no other symbols get * exported accidentally. @@ -761,8 +1012,16 @@ and generate_linker_script () = "guestfs_tmpdir"; ] in let functions = - List.map (fun (name, _, _, _, _, _, _) -> "guestfs_" ^ name) - all_functions in + List.flatten ( + List.map ( + function + | name, (_, _, []), _, _, _, _, _ -> ["guestfs_" ^ name] + | name, (_, _, _), _, _, _, _, _ -> + ["guestfs_" ^ name; + "guestfs_" ^ name ^ "_va"; + "guestfs_" ^ name ^ "_argv"] + ) all_functions + ) in let structs = List.concat ( List.map (fun (typ, _) -> diff --git a/generator/generator_capitests.ml b/generator/generator_capitests.ml index 594c8676..b41e2889 100644 --- a/generator/generator_capitests.ml +++ b/generator/generator_capitests.ml @@ -720,6 +720,13 @@ and generate_test_command_call ?(expect_error = false) ?test test_name cmd = with Not_found -> failwithf "%s: in test, command %s was not found" test_name name in + (* If the call has optional args, fold them all together. We cannot + * test partial optional args yet. + *) + let style = + let ret, args, optargs = style in + ret, args@optargs in + if List.length (snd style) <> List.length args then failwithf "%s: in test, wrong number of args given to %s" test_name name; diff --git a/generator/generator_checks.ml b/generator/generator_checks.ml index 98649e26..34740470 100644 --- a/generator/generator_checks.ml +++ b/generator/generator_checks.ml @@ -101,7 +101,9 @@ let () = failwithf "%s has param/ret using reserved word %s" name n; in - (match fst style with + let ret, args, optargs = style in + + (match ret with | RErr -> () | RInt n | RInt64 n | RBool n | RConstString n | RConstOptString n | RString n @@ -109,7 +111,22 @@ let () = | RHashtable n | RBufferOut n -> check_arg_ret_name n ); - List.iter (fun arg -> check_arg_ret_name (name_of_argt arg)) (snd style) + List.iter (fun arg -> check_arg_ret_name (name_of_argt arg)) args; + List.iter (fun arg -> check_arg_ret_name (name_of_argt arg)) optargs; + ) all_functions; + + (* Check only certain types allowed in optargs. *) + List.iter ( + fun (name, (_, _, optargs), _, _, _, _, _) -> + if List.length optargs > 64 then + failwithf "maximum of 64 optional args allowed for %s" name; + + List.iter ( + function + | Bool _ | Int _ | Int64 _ | String _ -> () + | _ -> + failwithf "optional args of %s can only have type Bool|Int|Int64|String" name + ) optargs ) all_functions; (* Check short descriptions. *) diff --git a/generator/generator_csharp.ml b/generator/generator_csharp.ml index 1b2672b5..e178945a 100644 --- a/generator/generator_csharp.ml +++ b/generator/generator_csharp.ml @@ -44,7 +44,8 @@ let rec generate_csharp () = // The second issue is that some calls are known to be incorrect and // can cause Mono to segfault. Particularly: calls which pass or // return string[], or return any structure value. This is because -// we haven't worked out the correct way to do this from C#. +// we haven't worked out the correct way to do this from C#. Also +// we don't handle functions that take optional arguments at all. // // The third issue is that when compiling you get a lot of warnings. // We are not sure whether the warnings are important or not. @@ -134,9 +135,9 @@ namespace Guestfs (* Generate C# function bindings. *) List.iter ( - fun (name, style, _, _, _, shortdesc, _) -> + fun (name, (ret, args, optargs), _, _, _, shortdesc, _) -> let rec csharp_return_type () = - match fst style with + match ret with | RErr -> "void" | RBool n -> "bool" | RInt n -> "int" @@ -151,7 +152,7 @@ namespace Guestfs | RStructList (_,n) -> sprintf "_%s[]" n and c_return_type () = - match fst style with + match ret with | RErr | RBool _ | RInt _ -> "int" @@ -166,7 +167,7 @@ namespace Guestfs | RStructList (_,n) -> sprintf "_%s[]" n and c_error_comparison () = - match fst style with + match ret with | RErr | RBool _ | RInt _ @@ -198,7 +199,7 @@ namespace Guestfs pr ", int %s" n | Int64 n -> pr ", long %s" n - ) (snd style); + ) args; pr ");\n" and generate_public_prototype () = @@ -223,12 +224,12 @@ namespace Guestfs next (); pr "int %s" n | Int64 n -> next (); pr "long %s" n - ) (snd style); + ) args; pr ")\n" and generate_call () = pr "guestfs_%s (_handle" name; - List.iter (fun arg -> pr ", %s" (name_of_argt arg)) (snd style); + List.iter (fun arg -> pr ", %s" (name_of_argt arg)) args; pr ");\n"; in @@ -245,7 +246,7 @@ namespace Guestfs generate_call (); pr " if (r %s)\n" (c_error_comparison ()); pr " throw new Error (guestfs_last_error (_handle));\n"; - (match fst style with + (match ret with | RErr -> () | RBool _ -> pr " return r != 0 ? true : false;\n" diff --git a/generator/generator_daemon.ml b/generator/generator_daemon.ml index 4ac2a6e9..9d9fb0c1 100644 --- a/generator/generator_daemon.ml +++ b/generator/generator_daemon.ml @@ -63,12 +63,15 @@ and generate_daemon_actions () = pr "\n"; List.iter ( - fun (name, style, _, _, _, _, _) -> + fun (name, (ret, args, optargs), _, _, _, _, _) -> + if optargs <> [] then + failwithf "optional arguments not supported in the daemon yet"; + (* Generate server-side stubs. *) pr "static void %s_stub (XDR *xdr_in)\n" name; pr "{\n"; let error_code = - match fst style with + match ret with | RErr | RInt _ -> pr " int r;\n"; "-1" | RInt64 _ -> pr " int64_t r;\n"; "-1" | RBool _ -> pr " int r;\n"; "-1" @@ -83,7 +86,7 @@ and generate_daemon_actions () = pr " char *r;\n"; "NULL" in - (match snd style with + (match args with | [] -> () | args -> pr " struct guestfs_%s_args args;\n" name; @@ -107,9 +110,9 @@ and generate_daemon_actions () = pr "\n"; let is_filein = - List.exists (function FileIn _ -> true | _ -> false) (snd style) in + List.exists (function FileIn _ -> true | _ -> false) args in - (match snd style with + (match args with | [] -> () | args -> pr " memset (&args, 0, sizeof args);\n"; @@ -176,7 +179,7 @@ and generate_daemon_actions () = ); (* this is used at least for do_equal *) - if List.exists (function Pathname _ -> true | _ -> false) (snd style) then ( + if List.exists (function Pathname _ -> true | _ -> false) args then ( (* Emit NEED_ROOT just once, even when there are two or more Pathname args *) pr " NEED_ROOT (%s, goto done);\n" @@ -187,13 +190,12 @@ and generate_daemon_actions () = * parameters, since these go "outside" the RPC protocol. *) let args' = - List.filter (function FileIn _ | FileOut _ -> false | _ -> true) - (snd style) in + List.filter (function FileIn _ | FileOut _ -> false | _ -> true) args in pr " r = do_%s " name; - generate_c_call_args (fst style, args'); + generate_c_call_args (ret, args', optargs); pr ";\n"; - (match fst style with + (match ret with | RErr | RInt _ | RInt64 _ | RBool _ | RConstString _ | RConstOptString _ | RString _ | RStringList _ | RHashtable _ @@ -216,11 +218,11 @@ and generate_daemon_actions () = * send its own reply. *) let no_reply = - List.exists (function FileOut _ -> true | _ -> false) (snd style) in + List.exists (function FileOut _ -> true | _ -> false) args in if no_reply then pr " /* do_%s has already sent a reply */\n" name else ( - match fst style with + match ret with | RErr -> pr " reply (NULL, NULL);\n" | RInt n | RInt64 n | RBool n -> pr " struct guestfs_%s_ret ret;\n" name; @@ -267,7 +269,7 @@ and generate_daemon_actions () = (* Free the args. *) pr "done:\n"; - (match snd style with + (match args with | [] -> () | _ -> pr " xdr_free ((xdrproc_t) xdr_guestfs_%s_args, (char *) &args);\n" @@ -283,7 +285,7 @@ and generate_daemon_actions () = pr " switch (proc_nr) {\n"; List.iter ( - fun (name, style, _, _, _, _, _) -> + fun (name, _, _, _, _, _, _) -> pr " case GUESTFS_PROC_%s:\n" (String.uppercase name); pr " %s_stub (xdr_in);\n" name; pr " break;\n" diff --git a/generator/generator_fish.ml b/generator/generator_fish.ml index 749c661e..fdb4eff3 100644 --- a/generator/generator_fish.ml +++ b/generator/generator_fish.ml @@ -84,7 +84,7 @@ let generate_fish_cmds () = pr "{\n"; List.iter ( - fun (name, style, _, flags, _, shortdesc, longdesc) -> + fun (name, _, _, flags, _, shortdesc, longdesc) -> let name2 = replace_char name '_' '-' in let aliases = filter_map (function FishAlias n -> Some n | _ -> None) flags in @@ -113,21 +113,25 @@ let generate_fish_cmds () = ) fish_commands; List.iter ( - fun (name, style, _, flags, _, shortdesc, longdesc) -> + fun (name, (_, args, optargs), _, flags, _, shortdesc, longdesc) -> let name2 = replace_char name '_' '-' in let aliases = filter_map (function FishAlias n -> Some n | _ -> None) flags in let longdesc = replace_str longdesc "C<guestfs_" "C<" in let synopsis = - match snd style with + match args with | [] -> name2 | args -> let args = List.filter (function Key _ -> false | _ -> true) args in - sprintf "%s %s" - name2 (String.concat " " (List.map name_of_argt args)) in + sprintf "%s%s%s" + name2 + (String.concat "" + (List.map (fun arg -> " " ^ name_of_argt arg) args)) + (String.concat "" + (List.map (fun arg -> sprintf " [%s:..]" (name_of_argt arg)) optargs)) in let warnings = - if List.exists (function Key _ -> true | _ -> false) (snd style) then + if List.exists (function Key _ -> true | _ -> false) args then "\n\nThis command has one or more key or passphrase parameters. Guestfish will prompt for these separately." else "" in @@ -274,10 +278,10 @@ Guestfish will prompt for these separately." (* run_<action> actions *) List.iter ( - fun (name, style, _, flags, _, _, _) -> + fun (name, (ret, args, optargs as style), _, flags, _, _, _) -> pr "static int run_%s (const char *cmd, size_t argc, char *argv[])\n" name; pr "{\n"; - (match fst style with + (match ret with | RErr | RInt _ | RBool _ -> pr " int r;\n" @@ -308,26 +312,45 @@ Guestfish will prompt for these separately." | Bool n -> pr " int %s;\n" n | Int n -> pr " int %s;\n" n | Int64 n -> pr " int64_t %s;\n" n - ) (snd style); + ) args; + + if optargs <> [] then ( + pr " struct guestfs_%s_argv optargs_s = { .bitmask = 0 };\n" name; + pr " struct guestfs_%s_argv *optargs = &optargs_s;\n" name + ); + + if args <> [] || optargs <> [] then + pr " size_t i = 0;\n"; + + pr "\n"; (* Check and convert parameters. *) - let argc_expected = + let argc_minimum, argc_maximum = let args_no_keys = - List.filter (function Key _ -> false | _ -> true) (snd style) in - List.length args_no_keys in - pr " if (argc != %d) {\n" argc_expected; - pr " fprintf (stderr, _(\"%%s should have %%d parameter(s)\\n\"), cmd, %d);\n" - argc_expected; + List.filter (function Key _ -> false | _ -> true) args in + let argc_minimum = List.length args_no_keys in + let argc_maximum = argc_minimum + List.length optargs in + argc_minimum, argc_maximum in + + if argc_minimum = argc_maximum then ( + pr " if (argc != %d) {\n" argc_minimum; + pr " fprintf (stderr, _(\"%%s should have %%d parameter(s)\\n\"), cmd, %d);\n" + argc_minimum; + ) else ( + pr " if (argc < %d || argc > %d) {\n" argc_minimum argc_maximum; + pr " fprintf (stderr, _(\"%%s should have %%d-%%d parameter(s)\\n\"), cmd, %d, %d);\n" + argc_minimum argc_maximum; + ); pr " fprintf (stderr, _(\"type 'help %%s' for help on %%s\\n\"), cmd, cmd);\n"; pr " return -1;\n"; pr " }\n"; - let parse_integer fn fntyp rtyp range name = + let parse_integer expr fn fntyp rtyp range name = pr " {\n"; pr " strtol_error xerr;\n"; pr " %s r;\n" fntyp; pr "\n"; - pr " xerr = %s (argv[i++], NULL, 0, &r, xstrtol_suffixes);\n" fn; + pr " xerr = %s (%s, NULL, 0, &r, xstrtol_suffixes);\n" fn expr; pr " if (xerr != LONGINT_OK) {\n"; pr " fprintf (stderr,\n"; pr " _(\"%%s: %%s: invalid integer parameter (%%s returned %%d)\\n\"),\n"; @@ -349,9 +372,6 @@ Guestfish will prompt for these separately." pr " }\n"; in - if snd style <> [] then - pr " size_t i = 0;\n"; - List.iter ( function | Device name @@ -389,13 +409,67 @@ Guestfish will prompt for these separately." and comment = "The Int type in the generator is a signed 31 bit int." in Some (min, max, comment) in - parse_integer "xstrtoll" "long long" "int" range name + parse_integer "argv[i++]" "xstrtoll" "long long" "int" range name | Int64 name -> - parse_integer "xstrtoll" "long long" "int64_t" None name - ) (snd style); + parse_integer "argv[i++]" "xstrtoll" "long long" "int64_t" None name + ) args; + + (* Optional arguments are prefixed with <argname>:<value> and + * may be missing, so we need to parse those until the end of + * the argument list. + *) + if optargs <> [] then ( + let uc_name = String.uppercase name in + pr "\n"; + pr " for (; i < argc; ++i) {\n"; + pr " uint64_t this_mask;\n"; + pr " const char *this_arg;\n"; + List.iter ( + fun argt -> + let n = name_of_argt argt in + let uc_n = String.uppercase n in + let len = String.length n in + pr " if (STRPREFIX (argv[i], \"%s:\")) {\n" n; + (match argt with + | Bool n -> + pr " optargs_s.%s = is_true (&argv[i][%d]) ? 1 : 0;\n" + n (len+1); + | Int n -> + let range = + let min = "(-(2LL<<30))" + and max = "((2LL<<30)-1)" + and comment = + "The Int type in the generator is a signed 31 bit int." in + Some (min, max, comment) in + let expr = sprintf "&argv[i][%d]" (len+1) in + parse_integer expr "xstrtoll" "long long" "int" range name + | Int64 n -> + let expr = sprintf "&argv[i][%d]" (len+1) in + parse_integer expr "xstrtoll" "long long" "int64_t" None name + | String n -> + pr " optargs_s.%s = &argv[i][%d];\n" n (len+1); + | _ -> assert false + ); + pr " this_mask = GUESTFS_%s_%s_BITMASK;\n" uc_name uc_n; + pr " this_arg = \"%s\";\n" n; + pr " }\n"; + ) optargs; + + pr " if (optargs_s.bitmask & this_mask) {\n"; + pr " fprintf (stderr, _(\"%%s: optional argument %%s given twice\\n\"),\n"; + pr " cmd, this_arg);\n"; + pr " return -1;\n"; + pr " }\n"; + pr " optargs_s.bitmask |= this_mask;\n"; + pr " }\n"; + pr "\n"; + ); (* Call C API function. *) - pr " r = guestfs_%s " name; + if optargs = [] then + pr " r = guestfs_%s " name + else + pr " r = guestfs_%s_argv " name; generate_c_call_args ~handle:"g" style; pr ";\n"; @@ -412,7 +486,7 @@ Guestfish will prompt for these separately." pr " free_file_in (%s);\n" name | StringList name | DeviceList name -> pr " free_strings (%s);\n" name - ) (snd style); + ) args; (* Any output flags? *) let fish_output = @@ -426,7 +500,7 @@ Guestfish will prompt for these separately." failwithf "%s: more than one FishOutput flag is not allowed" name in (* Check return value for errors and display command results. *) - (match fst style with + (match ret with | RErr -> pr " return r;\n" | RInt _ -> pr " if (r == -1) return -1;\n"; @@ -637,7 +711,7 @@ and generate_fish_actions_pod () = let rex = Str.regexp "C<guestfs_\\([^>]+\\)>" in List.iter ( - fun (name, style, _, flags, _, _, longdesc) -> + fun (name, (_, args, optargs), _, flags, _, _, longdesc) -> let longdesc = Str.global_substitute rex ( fun s -> @@ -668,19 +742,27 @@ and generate_fish_actions_pod () = | FileIn n | FileOut n -> pr " (%s|-)" n | BufferIn n -> pr " %s" n | Key _ -> () (* keys are entered at a prompt *) - ) (snd style); + ) args; + List.iter ( + function + | Bool n | Int n | Int64 n | String n -> pr " [%s:..]" n + | _ -> assert false + ) optargs; pr "\n"; pr "\n"; pr "%s\n\n" longdesc; if List.exists (function FileIn _ | FileOut _ -> true - | _ -> false) (snd style) then + | _ -> false) args then pr "Use C<-> instead of a filename to read/write from stdin/stdout.\n\n"; - if List.exists (function Key _ -> true | _ -> false) (snd style) then + if List.exists (function Key _ -> true | _ -> false) args then pr "This command has one or more key or passphrase parameters. Guestfish will prompt for these separately.\n\n"; + if optargs <> [] then + pr "This command has one or more optional arguments. See L</OPTIONAL ARGUMENTS>.\n\n"; + if List.mem ProtocolLimitWarning flags then pr "%s\n\n" protocol_limit_warning; @@ -695,7 +777,7 @@ Guestfish will prompt for these separately.\n\n"; (* Generate documentation for guestfish-only commands. *) and generate_fish_commands_pod () = List.iter ( - fun (name, style, _, flags, _, _, longdesc) -> + fun (name, _, _, flags, _, _, longdesc) -> let name = replace_char name '_' '-' in let aliases = filter_map (function FishAlias n -> Some n | _ -> None) flags in diff --git a/generator/generator_haskell.ml b/generator/generator_haskell.ml index a125cbd5..b49e385b 100644 --- a/generator/generator_haskell.ml +++ b/generator/generator_haskell.ml @@ -36,18 +36,19 @@ let rec generate_haskell_hs () = *) let can_generate style = match style with - | RErr, _ - | RInt _, _ - | RInt64 _, _ -> true - | RBool _, _ - | RConstString _, _ - | RConstOptString _, _ - | RString _, _ - | RStringList _, _ - | RStruct _, _ - | RStructList _, _ - | RHashtable _, _ - | RBufferOut _, _ -> false in + | _, _, (_::_) -> false (* no optional args yet *) + | RErr, _, [] + | RInt _, _, [] + | RInt64 _, _, [] -> true + | RBool _, _, [] + | RConstString _, _, [] + | RConstOptString _, _, [] + | RString _, _, [] + | RStringList _, _, [] + | RStruct _, _, [] + | RStructList _, _, [] + | RHashtable _, _, [] + | RBufferOut _, _, [] -> false in pr "\ {-# INCLUDE <guestfs.h> #-} @@ -123,7 +124,7 @@ last_error h = do (* Generate wrappers for each foreign function. *) List.iter ( - fun (name, style, _, _, _, _, _) -> + fun (name, (ret, args, optargs as style), _, _, _, _, _) -> if can_generate style then ( pr "foreign import ccall unsafe \"guestfs_%s\" c_%s\n" name name; pr " :: "; @@ -134,7 +135,7 @@ last_error h = do generate_haskell_prototype ~handle:"GuestfsH" ~hs:true style; pr "\n"; pr "%s %s = do\n" name - (String.concat " " ("h" :: List.map name_of_argt (snd style))); + (String.concat " " ("h" :: List.map name_of_argt args)); pr " r <- "; (* Convert pointer arguments using with* functions. *) List.iter ( @@ -148,7 +149,7 @@ last_error h = do | OptString n -> pr "maybeWith withCString %s $ \\%s -> " n n | StringList n | DeviceList n -> pr "withMany withCString %s $ \\%s -> withArray0 nullPtr %s $ \\%s -> " n n n n | Bool _ | Int _ | Int64 _ -> () - ) (snd style); + ) args; (* Convert integer arguments. *) let args = List.map ( @@ -162,10 +163,10 @@ last_error h = do | StringList n | DeviceList n | Key n -> n | BufferIn n -> sprintf "%s (fromIntegral %s_size)" n n - ) (snd style) in + ) args in pr "withForeignPtr h (\\p -> c_%s %s)\n" name (String.concat " " ("p" :: args)); - (match fst style with + (match ret with | RErr | RInt _ | RInt64 _ | RBool _ -> pr " if (r == -1)\n"; pr " then do\n"; @@ -179,7 +180,7 @@ last_error h = do pr " err <- last_error h\n"; pr " fail err\n"; ); - (match fst style with + (match ret with | RErr -> pr " else return ()\n" | RInt _ -> @@ -202,7 +203,7 @@ last_error h = do ) ) all_functions -and generate_haskell_prototype ~handle ?(hs = false) style = +and generate_haskell_prototype ~handle ?(hs = false) (ret, args, optargs) = pr "%s -> " handle; let string = if hs then "String" else "CString" in let int = if hs then "Int" else "CInt" in @@ -225,9 +226,9 @@ and generate_haskell_prototype ~handle ?(hs = false) style = | FileOut _ -> pr "%s" string ); pr " -> "; - ) (snd style); + ) args; pr "IO ("; - (match fst style with + (match ret with | RErr -> if not hs then pr "CInt" | RInt _ -> pr "%s" int | RInt64 _ -> pr "%s" int64 diff --git a/generator/generator_java.ml b/generator/generator_java.ml index 2ccb1b6c..b5517408 100644 --- a/generator/generator_java.ml +++ b/generator/generator_java.ml @@ -100,10 +100,14 @@ public class GuestFS { "; List.iter ( - fun (name, style, _, flags, _, shortdesc, longdesc) -> + fun (name, (ret, args, optargs as style), _, flags, _, shortdesc, longdesc) -> if not (List.mem NotInDocs flags); then ( let doc = replace_str longdesc "C<guestfs_" "C<g." in let doc = + if optargs <> [] then + doc ^ "\n\nOptional arguments are supplied in the final Map<String,Object> parameter, which is a hash of the argument name to its value (cast to Object). Pass an empty Map for no optional arguments." + else doc in + let doc = if List.mem ProtocolLimitWarning flags then doc ^ "\n\n" ^ protocol_limit_warning else doc in @@ -138,9 +142,9 @@ public class GuestFS { pr " throw new LibGuestFSException (\"%s: handle is closed\");\n" name; pr " "; - if fst style <> RErr then pr "return "; + if ret <> RErr then pr "return "; pr "_%s " name; - generate_java_call_args ~handle:"g" (snd style); + generate_java_call_args ~handle:"g" style; pr ";\n"; pr " }\n"; pr " "; @@ -152,19 +156,20 @@ public class GuestFS { pr "}\n" (* Generate Java call arguments, eg "(handle, foo, bar)" *) -and generate_java_call_args ~handle args = +and generate_java_call_args ~handle (_, args, optargs) = pr "(%s" handle; List.iter (fun arg -> pr ", %s" (name_of_argt arg)) args; + if optargs <> [] then pr ", optargs"; pr ")" and generate_java_prototype ?(public=false) ?(privat=false) ?(native=false) - ?(semicolon=true) name style = + ?(semicolon=true) name (ret, args, optargs) = if privat then pr "private "; if public then pr "public "; if native then pr "native "; (* return type *) - (match fst style with + (match ret with | RErr -> pr "void "; | RInt _ -> pr "int "; | RInt64 _ -> pr "long "; @@ -214,7 +219,13 @@ and generate_java_prototype ?(public=false) ?(privat=false) ?(native=false) pr "int %s" n | Int64 n -> pr "long %s" n - ) (snd style); + ) args; + + if optargs <> [] then ( + if !needs_comma then pr ", "; + needs_comma := true; + pr "HashMap optargs" + ); pr ")\n"; pr " throws LibGuestFSException"; @@ -299,9 +310,9 @@ Java_com_redhat_et_libguestfs_GuestFS__1close "; List.iter ( - fun (name, style, _, _, _, _, _) -> + fun (name, (ret, args, optargs as style), _, _, _, _, _) -> pr "JNIEXPORT "; - (match fst style with + (match ret with | RErr -> pr "void "; | RInt _ -> pr "jint "; | RInt64 _ -> pr "jlong "; @@ -338,12 +349,14 @@ Java_com_redhat_et_libguestfs_GuestFS__1close pr ", jint j%s" n | Int64 n -> pr ", jlong j%s" n - ) (snd style); + ) args; + if optargs <> [] then + pr ", jobject joptargs"; pr ")\n"; pr "{\n"; pr " guestfs_h *g = (guestfs_h *) (long) jg;\n"; let error_code, no_ret = - match fst style with + match ret with | RErr -> pr " int r;\n"; "-1", "" | RBool _ | RInt _ -> pr " int r;\n"; "-1", "0" @@ -397,10 +410,10 @@ Java_com_redhat_et_libguestfs_GuestFS__1close pr " int %s;\n" n | Int64 n -> pr " int64_t %s;\n" n - ) (snd style); + ) args; let needs_i = - (match fst style with + (match ret with | RStringList _ | RStructList _ -> true | RErr | RBool _ | RInt _ | RInt64 _ | RConstString _ | RConstOptString _ @@ -408,7 +421,7 @@ Java_com_redhat_et_libguestfs_GuestFS__1close List.exists (function | StringList _ -> true | DeviceList _ -> true - | _ -> false) (snd style) in + | _ -> false) args in if needs_i then pr " size_t i;\n"; @@ -445,10 +458,20 @@ Java_com_redhat_et_libguestfs_GuestFS__1close | Int n | Int64 n -> pr " %s = j%s;\n" n n - ) (snd style); + ) args; + + if optargs <> [] then ( + (* XXX *) + pr " throw_exception (env, \"%s: internal error: please let us know how to read a Java HashMap parameter from JNI bindings!\");\n" name; + pr " return NULL;\n"; + pr " /*\n"; + ); (* Make the call. *) - pr " r = guestfs_%s " name; + if optargs = [] then + pr " r = guestfs_%s " name + else + pr " r = guestfs_%s_argv " name; generate_c_call_args ~handle:"g" style; pr ";\n"; @@ -477,7 +500,7 @@ Java_com_redhat_et_libguestfs_GuestFS__1close | Bool n | Int n | Int64 n -> () - ) (snd style); + ) args; (* Check for errors. *) pr " if (r == %s) {\n" error_code; @@ -486,7 +509,7 @@ Java_com_redhat_et_libguestfs_GuestFS__1close pr " }\n"; (* Return value. *) - (match fst style with + (match ret with | RErr -> () | RInt _ -> pr " return (jint) r;\n" | RBool _ -> pr " return (jboolean) r;\n" @@ -528,6 +551,9 @@ Java_com_redhat_et_libguestfs_GuestFS__1close pr " return jr;\n" ); + if optargs <> [] then + pr " */\n"; + pr "}\n"; pr "\n" ) all_functions diff --git a/generator/generator_ocaml.ml b/generator/generator_ocaml.ml index 9db69dcf..888a1527 100644 --- a/generator/generator_ocaml.ml +++ b/generator/generator_ocaml.ml @@ -97,8 +97,8 @@ val clear_progress_callback : t -> unit (** {2 Object-oriented API} This is an alternate way of calling the API using an object-oriented - style, so you can use [g#add_drive filename] instead of - [Guestfs.add_drive g filename]. Apart from the different style, + style, so you can use [g#add_drive_opts filename] instead of + [Guestfs.add_drive_opts g filename]. Apart from the different style, it offers exactly the same functionality. Calling [new guestfs ()] creates both the object and the handle. @@ -122,7 +122,7 @@ class guestfs : unit -> object List.iter ( function - | name, ((_, []) as style), _, _, _, _, _ -> + | name, ((_, [], []) as style), _, _, _, _, _ -> pr " method %s : unit -> " name; generate_ocaml_function_type style; pr "\n" @@ -182,7 +182,7 @@ class guestfs () = List.iter ( function - | name, (_, []), _, _, _, _, _ -> (* no params? add explicit unit *) + | name, (_, [], []), _, _, _, _, _ -> (* no params? add explicit unit *) pr " method %s () = %s g\n" name name | name, _, _, _, _, _, _ -> pr " method %s = %s g\n" name name @@ -323,18 +323,25 @@ copy_table (char * const * argv) (* The wrappers. *) List.iter ( - fun (name, style, _, _, _, _, _) -> + fun (name, (ret, args, optargs as style), _, _, _, _, _) -> pr "/* Automatically generated wrapper for function\n"; pr " * "; generate_ocaml_prototype name style; pr " */\n"; pr "\n"; + (* If we run into this situation, we'll need to change the + * bindings a little. + *) + if args = [] && optargs <> [] then + failwithf "ocaml bindings don't support args = [], optargs <> []"; + let params = - "gv" :: List.map (fun arg -> name_of_argt arg ^ "v") (snd style) in + "gv" :: + List.map (fun arg -> name_of_argt arg ^ "v") (optargs @ args) in let needs_extra_vs = - match fst style with RConstOptString _ -> true | _ -> false in + match ret with RConstOptString _ -> true | _ -> false in pr "/* Emit prototype to appease gcc's -Wmissing-prototypes. */\n"; pr "CAMLprim value ocaml_guestfs_%s (value %s" name (List.hd params); @@ -393,9 +400,35 @@ copy_table (char * const * argv) pr " int %s = Int_val (%sv);\n" n n | Int64 n -> pr " int64_t %s = Int64_val (%sv);\n" n n - ) (snd style); + ) args; + + (* Optional arguments. *) + if optargs <> [] then ( + pr " struct guestfs_%s_argv optargs_s = { .bitmask = 0 };\n" name; + pr " struct guestfs_%s_argv *optargs = &optargs_s;\n" name; + let uc_name = String.uppercase name in + List.iter ( + fun argt -> + let n = name_of_argt argt in + let uc_n = String.uppercase n in + pr " if (%sv != Val_int (0)) {\n" n; + pr " optargs_s.bitmask |= GUESTFS_%s_%s_BITMASK;\n" uc_name uc_n; + pr " optargs_s.%s = " n; + (match argt with + | Bool _ -> pr "Bool_val (Field (%sv, 0))" n + | Int _ -> pr "Int_val (Field (%sv, 0))" n + | Int64 _ -> pr "Int64_val (Field (%sv, 0))" n + | String _ -> + pr "guestfs_safe_strdup (g, String_val (Field (%sv, 0)))" n + | _ -> assert false + ); + pr ";\n"; + pr " }\n"; + ) optargs + ); + let error_code = - match fst style with + match ret with | RErr -> pr " int r;\n"; "-1" | RInt _ -> pr " int r;\n"; "-1" | RInt64 _ -> pr " int64_t r;\n"; "-1" @@ -422,7 +455,10 @@ copy_table (char * const * argv) pr "\n"; pr " caml_enter_blocking_section ();\n"; - pr " r = guestfs_%s " name; + if optargs = [] then + pr " r = guestfs_%s " name + else + pr " r = guestfs_%s_argv " name; generate_c_call_args ~handle:"g" style; pr ";\n"; pr " caml_leave_blocking_section ();\n"; @@ -436,13 +472,23 @@ copy_table (char * const * argv) | StringList n | DeviceList n -> pr " ocaml_guestfs_free_strings (%s);\n" n; | Bool _ | Int _ | Int64 _ -> () - ) (snd style); + ) args; + List.iter ( + function + | String n -> + pr " if (%sv != Val_int (0))\n" n; + pr " free ((char *) optargs_s.%s);\n" n + | Bool _ | Int _ | Int64 _ + | Pathname _ | Device _ | Dev_or_Path _ | OptString _ + | FileIn _ | FileOut _ | BufferIn _ | Key _ + | StringList _ | DeviceList _ -> () + ) optargs; pr " if (r == %s)\n" error_code; pr " ocaml_guestfs_raise_error (g, \"%s\");\n" name; pr "\n"; - (match fst style with + (match ret with | RErr -> pr " rv = Val_unit;\n" | RInt _ -> pr " rv = Val_int (r);\n" | RInt64 _ -> @@ -522,13 +568,22 @@ and generate_ocaml_prototype ?(is_external = false) name style = generate_ocaml_function_type style; if is_external then ( pr " = "; - if List.length (snd style) + 1 > 5 then + let _, args, optargs = style in + if List.length args + List.length optargs + 1 > 5 then pr "\"ocaml_guestfs_%s_byte\" " name; pr "\"ocaml_guestfs_%s\"" name ); pr "\n" -and generate_ocaml_function_type style = +and generate_ocaml_function_type (ret, args, optargs) = + List.iter ( + function + | Bool n -> pr "?%s:bool -> " n + | Int n -> pr "?%s:int -> " n + | Int64 n -> pr "?%s:int64 -> " n + | String n -> pr "?%s:string -> " n + | _ -> assert false + ) optargs; List.iter ( function | Pathname _ | Device _ | Dev_or_Path _ | String _ | FileIn _ | FileOut _ @@ -538,8 +593,8 @@ and generate_ocaml_function_type style = | Bool _ -> pr "bool -> " | Int _ -> pr "int -> " | Int64 _ -> pr "int64 -> " - ) (snd style); - (match fst style with + ) args; + (match ret with | RErr -> pr "unit" (* all errors are turned into exceptions *) | RInt _ -> pr "int" | RInt64 _ -> pr "int64" diff --git a/generator/generator_perl.ml b/generator/generator_perl.ml index 020f1b2e..96b8dd18 100644 --- a/generator/generator_perl.ml +++ b/generator/generator_perl.ml @@ -199,8 +199,8 @@ clear_progress_callback (g) "; List.iter ( - fun (name, style, _, _, _, _, _) -> - (match fst style with + fun (name, (ret, args, optargs as style), _, _, _, _, _) -> + (match ret with | RErr -> pr "void\n" | RInt _ -> pr "SV *\n" | RInt64 _ -> pr "SV *\n" @@ -218,7 +218,9 @@ clear_progress_callback (g) pr "%s (g" name; List.iter ( fun arg -> pr ", %s" (name_of_argt arg) - ) (snd style); + ) args; + if optargs <> [] then + pr ", ..."; pr ")\n"; pr " guestfs_h *g;\n"; iteri ( @@ -240,11 +242,11 @@ clear_progress_callback (g) | Bool n -> pr " int %s;\n" n | Int n -> pr " int %s;\n" n | Int64 n -> pr " int64_t %s;\n" n - ) (snd style); + ) args; (* PREINIT section (local variable declarations). *) pr "PREINIT:\n"; - (match fst style with + (match ret with | RErr -> pr " int r;\n"; | RInt _ @@ -272,11 +274,17 @@ clear_progress_callback (g) pr " size_t size;\n"; ); + if optargs <> [] then ( + pr " struct guestfs_%s_argv optargs_s = { .bitmask = 0 };\n" name; + pr " struct guestfs_%s_argv *optargs = &optargs_s;\n" name; + pr " size_t items_i;\n"; + ); + (* CODE or PPCODE section. PPCODE is used where we are * returning void, or where we push the return value on the stack * ourselves. Using CODE means we will manipulate RETVAL. *) - (match fst style with + (match ret with | RErr -> pr " PPCODE:\n"; | RInt n @@ -299,8 +307,52 @@ clear_progress_callback (g) pr " PPCODE:\n"; ); + (* For optional arguments, convert these from the XSUB "items" + * variable by hand. + *) + if optargs <> [] then ( + let uc_name = String.uppercase name in + let skip = List.length args + 1 in + pr " if (((items - %d) & 1) != 0)\n" skip; + pr " croak (\"expecting an even number of extra parameters\");\n"; + pr " for (items_i = %d; items_i < items; items_i += 2) {\n" skip; + pr " uint64_t this_mask;\n"; + pr " const char *this_arg;\n"; + pr "\n"; + pr " this_arg = SvPV_nolen (ST (items_i));\n"; + pr " "; + List.iter ( + fun argt -> + let n = name_of_argt argt in + let uc_n = String.uppercase n in + pr "if (strcmp (this_arg, \"%s\") == 0) {\n" n; + pr " optargs_s.%s = " n; + (match argt with + | Bool _ + | Int _ + | Int64 _ -> pr "SvIV (ST (items_i+1))" + | String _ -> pr "SvPV_nolen (ST (items_i+1))" + | _ -> assert false + ); + pr ";\n"; + pr " this_mask = GUESTFS_%s_%s_BITMASK;\n" uc_name uc_n; + pr " }\n"; + pr " else "; + ) optargs; + pr "croak (\"unknown optional argument '%%s'\", this_arg);\n"; + pr " if (optargs_s.bitmask & this_mask)\n"; + pr " croak (\"optional argument '%%s' given twice\",\n"; + pr " this_arg);\n"; + pr " optargs_s.bitmask |= this_mask;\n"; + pr " }\n"; + pr "\n"; + ); + (* The call to the C function. *) - pr " r = guestfs_%s " name; + if optargs = [] then + pr " r = guestfs_%s " name + else + pr " r = guestfs_%s_argv " name; generate_c_call_args ~handle:"g" style; pr ";\n"; @@ -312,10 +364,10 @@ clear_progress_callback (g) | FileIn _ | FileOut _ | BufferIn _ | Key _ -> () | StringList n | DeviceList n -> pr " free (%s);\n" n - ) (snd style); + ) args; (* Check return value for errors and return it if necessary. *) - (match fst style with + (match ret with | RErr -> pr " if (r == -1)\n"; pr " croak (\"%%s\", guestfs_last_error (g));\n"; @@ -469,9 +521,9 @@ Sys::Guestfs - Perl bindings for libguestfs use Sys::Guestfs; my $h = Sys::Guestfs->new (); - $h->add_drive ('guest.img'); + $h->add_drive_opts ('guest.img', format => 'raw'); $h->launch (); - $h->mount ('/dev/sda1', '/'); + $h->mount_options ('', '/dev/sda1', '/'); $h->touch ('/hello'); $h->sync (); @@ -675,8 +727,8 @@ L<Sys::Guestfs::Lib(3)>. =cut " copyright_years -and generate_perl_prototype name style = - (match fst style with +and generate_perl_prototype name (ret, args, optargs) = + (match ret with | RErr -> () | RBool n | RInt n @@ -703,5 +755,12 @@ and generate_perl_prototype name style = pr "$%s" n | StringList n | DeviceList n -> pr "\\@%s" n - ) (snd style); + ) args; + List.iter ( + fun arg -> + if !comma then pr " [, " else pr "["; + comma := true; + let n = name_of_argt arg in + pr "%s => $%s]" n n + ) optargs; pr ");" diff --git a/generator/generator_php.ml b/generator/generator_php.ml index 13ef4c00..b71d5c35 100644 --- a/generator/generator_php.ml +++ b/generator/generator_php.ml @@ -52,7 +52,7 @@ PHP_FUNCTION (guestfs_last_error); "; List.iter ( - fun (shortname, style, _, _, _, _, _) -> + fun (shortname, _, _, _, _, _, _) -> pr "PHP_FUNCTION (guestfs_%s);\n" shortname ) all_functions_sorted; @@ -112,7 +112,7 @@ static function_entry guestfs_php_functions[] = { "; List.iter ( - fun (shortname, style, _, _, _, _, _) -> + fun (shortname, _, _, _, _, _, _) -> pr " PHP_FE (guestfs_%s, NULL)\n" shortname ) all_functions_sorted; @@ -180,7 +180,7 @@ PHP_FUNCTION (guestfs_last_error) (* Now generate the PHP bindings for each action. *) List.iter ( - fun (shortname, style, _, _, _, _, _) -> + fun (shortname, (ret, args, optargs as style), _, _, _, _, _) -> pr "PHP_FUNCTION (guestfs_%s)\n" shortname; pr "{\n"; pr " zval *z_g;\n"; @@ -202,7 +202,28 @@ PHP_FUNCTION (guestfs_last_error) pr " zend_bool %s;\n" n | Int n | Int64 n -> pr " long %s;\n" n - ) (snd style); + ) args; + + if optargs <> [] then ( + pr " struct guestfs_%s_argv optargs_s = { .bitmask = 0 };\n" shortname; + pr " struct guestfs_%s_argv *optargs = &optargs_s;\n" shortname; + + (* XXX Ugh PHP doesn't have proper optional arguments, so we + * have to use sentinel values. + *) + (* Since we don't know if PHP types will exactly match structure + * types, declare some local variables here. + *) + List.iter ( + function + | Bool n -> pr " zend_bool optargs_t_%s = -1;\n" n + | Int n | Int64 n -> pr " long optargs_t_%s = -1;\n" n + | String n -> + pr " char *optargs_t_%s = NULL;\n" n; + pr " int optargs_t_%s_size = -1;\n" n + | _ -> assert false + ) optargs + ); pr "\n"; @@ -216,9 +237,23 @@ PHP_FUNCTION (guestfs_last_error) | StringList n | DeviceList n -> "a" | Bool n -> "b" | Int n | Int64 n -> "l" - ) (snd style) + ) args ) in + let param_string = + if optargs <> [] then + param_string ^ "|" ^ + String.concat "" ( + List.map ( + function + | Bool _ -> "b" + | Int _ | Int64 _ -> "l" + | String _ -> "s" + | _ -> assert false + ) optargs + ) + else param_string in + pr " if (zend_parse_parameters (ZEND_NUM_ARGS() TSRMLS_CC, \"r%s\",\n" param_string; pr " &z_g"; @@ -234,7 +269,15 @@ PHP_FUNCTION (guestfs_last_error) pr ", &%s" n | Int n | Int64 n -> pr ", &%s" n - ) (snd style); + ) args; + List.iter ( + function + | Bool n | Int n | Int64 n -> + pr ", &optargs_t_%s" n + | String n -> + pr ", &optargs_t_%s, &optargs_t_%s_size" n n + | _ -> assert false + ) optargs; pr ") == FAILURE) {\n"; pr " RETURN_FALSE;\n"; pr " }\n"; @@ -288,11 +331,34 @@ PHP_FUNCTION (guestfs_last_error) pr " }\n"; pr "\n" | Bool n | Int n | Int64 n -> () - ) (snd style); + ) args; + + (* Optional arguments. *) + if optargs <> [] then ( + let uc_shortname = String.uppercase shortname in + List.iter ( + fun argt -> + let n = name_of_argt argt in + let uc_n = String.uppercase n in + pr " if (optargs_t_%s != " n; + (match argt with + | Bool _ -> pr "((zend_bool)-1)" + | Int _ | Int64 _ -> pr "-1" + | String _ -> pr "NULL" + | _ -> assert false + ); + pr ") {\n"; + pr " optargs_s.%s = optargs_t_%s;\n" n n; + pr " optargs_s.bitmask |= GUESTFS_%s_%s_BITMASK;\n" + uc_shortname uc_n; + pr " }\n" + ) optargs; + pr "\n" + ); (* Return value. *) let error_code = - match fst style with + match ret with | RErr -> pr " int r;\n"; "-1" | RBool _ | RInt _ -> pr " int r;\n"; "-1" @@ -315,7 +381,10 @@ PHP_FUNCTION (guestfs_last_error) "NULL" in (* Call the function. *) - pr " r = guestfs_%s " shortname; + if optargs = [] then + pr " r = guestfs_%s " shortname + else + pr " r = guestfs_%s_argv " shortname; generate_c_call_args ~handle:"g" style; pr ";\n"; pr "\n"; @@ -338,7 +407,7 @@ PHP_FUNCTION (guestfs_last_error) pr " }\n"; pr "\n" | Bool n | Int n | Int64 n -> () - ) (snd style); + ) args; (* Check for errors. *) pr " if (r == %s) {\n" error_code; @@ -347,7 +416,7 @@ PHP_FUNCTION (guestfs_last_error) pr "\n"; (* Convert the return value. *) - (match fst style with + (match ret with | RErr -> pr " RETURN_TRUE;\n" | RBool _ -> diff --git a/generator/generator_python.ml b/generator/generator_python.ml index 2a6034d4..f85f5d6c 100644 --- a/generator/generator_python.ml +++ b/generator/generator_python.ml @@ -279,7 +279,7 @@ py_guestfs_close (PyObject *self, PyObject *args) (* Python wrapper functions. *) List.iter ( - fun (name, style, _, _, _, _, _) -> + fun (name, (ret, args, optargs as style), _, _, _, _, _) -> pr "static PyObject *\n"; pr "py_guestfs_%s (PyObject *self, PyObject *args)\n" name; pr "{\n"; @@ -288,8 +288,13 @@ py_guestfs_close (PyObject *self, PyObject *args) pr " guestfs_h *g;\n"; pr " PyObject *py_r;\n"; + if optargs <> [] then ( + pr " struct guestfs_%s_argv optargs_s;\n" name; + pr " struct guestfs_%s_argv *optargs = &optargs_s;\n" name; + ); + let error_code = - match fst style with + match ret with | RErr | RInt _ | RBool _ -> pr " int r;\n"; "-1" | RInt64 _ -> pr " int64_t r;\n"; "-1" | RConstString _ | RConstOptString _ -> @@ -319,11 +324,33 @@ py_guestfs_close (PyObject *self, PyObject *args) | Bool n -> pr " int %s;\n" n | Int n -> pr " int %s;\n" n | Int64 n -> pr " long long %s;\n" n - ) (snd style); + ) args; + + if optargs <> [] then ( + (* XXX This is horrible. We have to use sentinel values on the + * Python side to denote values not set. + *) + (* Since we don't know if Python types will exactly match + * structure types, declare some local variables here. + *) + List.iter ( + function + | Bool n + | Int n -> pr " int optargs_t_%s = -1;\n" n + | Int64 n -> pr " long long optargs_t_%s = -1;\n" n + | String n -> pr " const char *optargs_t_%s = NULL;\n" n + | _ -> assert false + ) optargs + ); pr "\n"; - (* Convert the parameters. *) + if optargs <> [] then ( + pr " optargs_s.bitmask = 0;\n"; + pr "\n" + ); + + (* Convert the required parameters. *) pr " if (!PyArg_ParseTuple (args, (char *) \"O"; List.iter ( function @@ -337,7 +364,19 @@ py_guestfs_close (PyObject *self, PyObject *args) * emulate C's int/long/long long in Python? *) | BufferIn _ -> pr "s#" - ) (snd style); + ) args; + + (* Optional parameters. *) + if optargs <> [] then ( + List.iter ( + function + | Bool _ | Int _ -> pr "i" + | Int64 _ -> pr "L" + | String _ -> pr "z" (* because we use None to mean not set *) + | _ -> assert false + ) optargs; + ); + pr ":guestfs_%s\",\n" name; pr " &py_g"; List.iter ( @@ -350,7 +389,13 @@ py_guestfs_close (PyObject *self, PyObject *args) | Int n -> pr ", &%s" n | Int64 n -> pr ", &%s" n | BufferIn n -> pr ", &%s, &%s_size" n n - ) (snd style); + ) args; + + List.iter ( + function + | Bool n | Int n | Int64 n | String n -> pr ", &optargs_t_%s" n + | _ -> assert false + ) optargs; pr "))\n"; pr " return NULL;\n"; @@ -364,11 +409,34 @@ py_guestfs_close (PyObject *self, PyObject *args) | StringList n | DeviceList n -> pr " %s = get_string_list (py_%s);\n" n n; pr " if (!%s) return NULL;\n" n - ) (snd style); + ) args; pr "\n"; - pr " r = guestfs_%s " name; + if optargs <> [] then ( + let uc_name = String.uppercase name in + List.iter ( + fun argt -> + let n = name_of_argt argt in + let uc_n = String.uppercase n in + pr " if (optargs_t_%s != " n; + (match argt with + | Bool _ | Int _ | Int64 _ -> pr "-1" + | String _ -> pr "NULL" + | _ -> assert false + ); + pr ") {\n"; + pr " optargs_s.%s = optargs_t_%s;\n" n n; + pr " optargs_s.bitmask |= GUESTFS_%s_%s_BITMASK;\n" uc_name uc_n; + pr " }\n" + ) optargs; + pr "\n" + ); + + if optargs = [] then + pr " r = guestfs_%s " name + else + pr " r = guestfs_%s_argv " name; generate_c_call_args ~handle:"g" style; pr ";\n"; @@ -379,7 +447,7 @@ py_guestfs_close (PyObject *self, PyObject *args) | BufferIn _ -> () | StringList n | DeviceList n -> pr " free (%s);\n" n - ) (snd style); + ) args; pr " if (r == %s) {\n" error_code; pr " PyErr_SetString (PyExc_RuntimeError, guestfs_last_error (g));\n"; @@ -387,7 +455,7 @@ py_guestfs_close (PyObject *self, PyObject *args) pr " }\n"; pr "\n"; - (match fst style with + (match ret with | RErr -> pr " Py_INCREF (Py_None);\n"; pr " py_r = Py_None;\n" @@ -462,7 +530,7 @@ u\"\"\"Python bindings for libguestfs import guestfs g = guestfs.GuestFS () -g.add_drive (\"guest.img\") +g.add_drive_opts (\"guest.img\", format=\"raw\") g.launch () parts = g.list_partitions () @@ -492,10 +560,10 @@ RuntimeError exceptions. To create a guestfs handle you usually have to perform the following sequence of calls: -# Create the handle, call add_drive at least once, and possibly +# Create the handle, call add_drive* at least once, and possibly # several times if the guest has multiple block devices: g = guestfs.GuestFS () -g.add_drive (\"guest.img\") +g.add_drive_opts (\"guest.img\", format=\"raw\") # Launch the qemu subprocess and wait for it to become ready: g.launch () @@ -520,15 +588,21 @@ class GuestFS: "; List.iter ( - fun (name, style, _, flags, _, _, longdesc) -> - pr " def %s " name; - generate_py_call_args ~handle:"self" (snd style); - pr ":\n"; + fun (name, (ret, args, optargs), _, flags, _, _, longdesc) -> + pr " def %s (self" name; + List.iter (fun arg -> pr ", %s" (name_of_argt arg)) args; + List.iter ( + function + | Bool n | Int n | Int64 n -> pr ", %s=-1" n + | String n -> pr ", %s=None" n + | _ -> assert false + ) optargs; + pr "):\n"; if not (List.mem NotInDocs flags) then ( let doc = replace_str longdesc "C<guestfs_" "C<g." in let doc = - match fst style with + match ret with | RErr | RInt _ | RInt64 _ | RBool _ | RConstOptString _ | RConstString _ | RString _ | RBufferOut _ -> doc @@ -557,14 +631,7 @@ class GuestFS: let doc = String.concat "\n " doc in pr " u\"\"\"%s\"\"\"\n" doc; ); - pr " return libguestfsmod.%s " name; - generate_py_call_args ~handle:"self._o" (snd style); - pr "\n"; - pr "\n"; + pr " return libguestfsmod.%s (self._o" name; + List.iter (fun arg -> pr ", %s" (name_of_argt arg)) (args@optargs); + pr ")\n\n"; ) all_functions - -(* Generate Python call arguments, eg "(handle, foo, bar)" *) -and generate_py_call_args ~handle args = - pr "(%s" handle; - List.iter (fun arg -> pr ", %s" (name_of_argt arg)) args; - pr ")" diff --git a/generator/generator_ruby.ml b/generator/generator_ruby.ml index 4ee34752..d0a7cd20 100644 --- a/generator/generator_ruby.ml +++ b/generator/generator_ruby.ml @@ -89,9 +89,18 @@ static VALUE ruby_guestfs_close (VALUE gv) "; List.iter ( - fun (name, style, _, _, _, _, _) -> + fun (name, (ret, args, optargs as style), _, _, _, _, _) -> pr "static VALUE ruby_guestfs_%s (VALUE gv" name; - List.iter (fun arg -> pr ", VALUE %sv" (name_of_argt arg)) (snd style); + List.iter (fun arg -> pr ", VALUE %sv" (name_of_argt arg)) args; + (* XXX This makes the hash mandatory, meaning that you have + * to specify {} for no arguments. We could make it so this + * can be omitted. However that is a load of hassle because + * you have to completely change the way that arguments are + * passed in. See: + * http://www.redhat.com/archives/libvir-list/2008-April/msg00004.html + *) + if optargs <> [] then + pr ", VALUE optargsv"; pr ")\n"; pr "{\n"; pr " guestfs_h *g;\n"; @@ -139,11 +148,42 @@ static VALUE ruby_guestfs_close (VALUE gv) pr " int %s = NUM2INT (%sv);\n" n n | Int64 n -> pr " long long %s = NUM2LL (%sv);\n" n n - ) (snd style); + ) args; pr "\n"; + (* Optional arguments are passed in a final hash parameter. *) + if optargs <> [] then ( + let uc_name = String.uppercase name in + pr " Check_Type (optargsv, T_HASH);\n"; + pr " struct guestfs_%s_argv optargs_s = { .bitmask = 0 };\n" name; + pr " struct guestfs_%s_argv *optargs = &optargs_s;\n" name; + pr " VALUE v;\n"; + List.iter ( + fun argt -> + let n = name_of_argt argt in + let uc_n = String.uppercase n in + pr " v = rb_hash_lookup (optargsv, ID2SYM (rb_intern (\"%s\")));\n" n; + pr " if (v != Qnil) {\n"; + (match argt with + | Bool n -> + pr " optargs_s.%s = RTEST (v);\n" n; + | Int n -> + pr " optargs_s.%s = NUM2INT (v);\n" n; + | Int64 n -> + pr " optargs_s.%s = NUM2LL (v);\n" n; + | String _ -> + pr " Check_Type (v, T_STRING);\n"; + pr " optargs_s.%s = StringValueCStr (v);\n" n + | _ -> assert false + ); + pr " optargs_s.bitmask |= GUESTFS_%s_%s_BITMASK;\n" uc_name uc_n; + pr " }\n"; + ) optargs; + pr "\n"; + ); + let error_code = - match fst style with + match ret with | RErr | RInt _ | RBool _ -> pr " int r;\n"; "-1" | RInt64 _ -> pr " int64_t r;\n"; "-1" | RConstString _ | RConstOptString _ -> @@ -159,7 +199,10 @@ static VALUE ruby_guestfs_close (VALUE gv) "NULL" in pr "\n"; - pr " r = guestfs_%s " name; + if optargs = [] then + pr " r = guestfs_%s " name + else + pr " r = guestfs_%s_argv " name; generate_c_call_args ~handle:"g" style; pr ";\n"; @@ -170,13 +213,13 @@ static VALUE ruby_guestfs_close (VALUE gv) | BufferIn _ -> () | StringList n | DeviceList n -> pr " free (%s);\n" n - ) (snd style); + ) args; pr " if (r == %s)\n" error_code; pr " rb_raise (e_Error, \"%%s\", guestfs_last_error (g));\n"; pr "\n"; - (match fst style with + (match ret with | RErr -> pr " return Qnil;\n" | RInt _ | RBool _ -> @@ -248,9 +291,10 @@ void Init__guestfs () "; (* Define the rest of the methods. *) List.iter ( - fun (name, style, _, _, _, _, _) -> + fun (name, (_, args, optargs), _, _, _, _, _) -> + let nr_args = List.length args + if optargs <> [] then 1 else 0 in pr " rb_define_method (c_guestfs, \"%s\",\n" name; - pr " ruby_guestfs_%s, %d);\n" name (List.length (snd style)) + pr " ruby_guestfs_%s, %d);\n" name nr_args ) all_functions; pr "}\n" diff --git a/generator/generator_types.ml b/generator/generator_types.ml index 03805cf7..262fb205 100644 --- a/generator/generator_types.ml +++ b/generator/generator_types.ml @@ -20,7 +20,45 @@ (* Types used to describe the API. *) -type style = ret * args +type style = ret * args * args + (* The [style] is a tuple which describes the return value and + * arguments of a function. + * + * [ret] is the return value, one of the [R*] values below. + * + * The second element is the list of required arguments, a list of + * [argt]s from the list below, eg. [Bool], [String] etc. Each has + * a name so that for example [Int "foo"] corresponds in the C + * bindings to an [int foo] parameter. + * + * The third element is the list of optional arguments. These are + * mapped to optional arguments in the language binding, eg. in + * Perl to: + * $g->fn (required1, required2, opt1 => val, opt2 => val); + * As the name suggests these are optional, and the function can + * tell which optional parameters were supplied by the caller. + * + * Only [Bool], [Int], [Int64], [String] may currently appear in + * the optional argument list (we may permit more types in future). + * + * ABI and API considerations + * -------------------------- + * + * The return type and required arguments may not be changed after + * these have been published in a stable version of libguestfs, + * because doing so would break the ABI. + * + * If a published function has one or more optional arguments, + * then the call can be extended without breaking the ABI or API. + * This is in fact the only way to change an existing function. + * There are limitations on this: + * + * (1) you may _only_ add extra elements at the end of the list + * (2) you may _not_ rearrange or rename or remove existing elements + * (3) you may _not_ add optional arguments to a function which did + * not have any before (since this breaks the C ABI). + *) + and ret = (* "RErr" as a return value means an int used as a simple error * indication, ie. 0 or -1. @@ -108,13 +146,6 @@ and ret = and args = argt list (* Function parameters, guestfs handle is implicit. *) - (* Note in future we should allow a "variable args" parameter as - * the final parameter, to allow commands like - * chmod mode file [file(s)...] - * This is not implemented yet, but many commands (such as chmod) - * are currently defined with the argument order keeping this future - * possibility in mind. - *) and argt = | Bool of string (* boolean *) | Int of string (* int (smallish ints, signed, <= 31 bits) *) diff --git a/generator/generator_utils.ml b/generator/generator_utils.ml index b7401db0..85112271 100644 --- a/generator/generator_utils.ml +++ b/generator/generator_utils.ml @@ -83,8 +83,8 @@ let rstructs_used_by functions = in List.iter ( - fun (_, style, _, _, _, _, _) -> - match fst style with + fun (_, (ret, _, _), _, _, _, _, _) -> + match ret with | RStruct (_, structname) -> update structname RStructOnly | RStructList (_, structname) -> update structname RStructListOnly | _ -> () diff --git a/generator/generator_xdr.ml b/generator/generator_xdr.ml index 7f2e6eb9..49421499 100644 --- a/generator/generator_xdr.ml +++ b/generator/generator_xdr.ml @@ -64,10 +64,13 @@ let generate_xdr () = ) structs; List.iter ( - fun (shortname, style, _, _, _, _, _) -> + fun (shortname, (ret, args, optargs), _, _, _, _, _) -> + if optargs <> [] then + failwithf "optional arguments not supported in XDR yet"; + let name = "guestfs_" ^ shortname in - (match snd style with + (match args with | [] -> () | args -> pr "struct %s_args {\n" name; @@ -86,7 +89,7 @@ let generate_xdr () = ) args; pr "};\n\n" ); - (match fst style with + (match ret with | RErr -> () | RInt n -> pr "struct %s_ret {\n" name; diff --git a/ocaml/Makefile.am b/ocaml/Makefile.am index e80d44a3..77c5be96 100644 --- a/ocaml/Makefile.am +++ b/ocaml/Makefile.am @@ -65,10 +65,12 @@ TESTS_ENVIRONMENT = \ TESTS = run-bindtests \ t/guestfs_005_load t/guestfs_010_launch t/guestfs_050_lvcreate \ t/guestfs_060_readdir t/guestfs_070_threads \ + t/guestfs_080_optargs \ t/guestfs_400_progress noinst_DATA += bindtests \ t/guestfs_005_load t/guestfs_010_launch t/guestfs_050_lvcreate \ t/guestfs_060_readdir t/guestfs_070_threads \ + t/guestfs_080_optargs \ t/guestfs_400_progress bindtests: bindtests.cmx mlguestfs.cmxa @@ -95,6 +97,10 @@ t/guestfs_070_threads: t/guestfs_070_threads.cmx mlguestfs.cmxa mkdir -p t $(OCAMLFIND) ocamlopt -cclib -L$(top_builddir)/src/.libs -I . -package unix,threads -thread -linkpkg mlguestfs.cmxa $< -o $@ +t/guestfs_080_optargs: t/guestfs_080_optargs.cmx mlguestfs.cmxa + mkdir -p t + $(OCAMLFIND) ocamlopt -cclib -L$(top_builddir)/src/.libs -I . -package xml-light,unix -linkpkg mlguestfs.cmxa $< -o $@ + t/guestfs_400_progress: t/guestfs_400_progress.cmx mlguestfs.cmxa mkdir -p t $(OCAMLFIND) ocamlopt -cclib -L$(top_builddir)/src/.libs -I . -package xml-light,unix -linkpkg mlguestfs.cmxa $< -o $@ diff --git a/ocaml/examples/lvs.ml b/ocaml/examples/lvs.ml index 74998b9f..5db1089e 100644 --- a/ocaml/examples/lvs.ml +++ b/ocaml/examples/lvs.ml @@ -9,7 +9,7 @@ let () = ); let h = Guestfs.create () in - Guestfs.add_drive h Sys.argv.(1); + Guestfs.add_drive_opts h ~format:"raw" Sys.argv.(1); Guestfs.launch h; let pvs = Guestfs.pvs h in diff --git a/ocaml/t/guestfs_080_optargs.ml b/ocaml/t/guestfs_080_optargs.ml new file mode 100644 index 00000000..2b377843 --- /dev/null +++ b/ocaml/t/guestfs_080_optargs.ml @@ -0,0 +1,30 @@ +(* libguestfs OCaml bindings + * Copyright (C) 2010 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + *) + +open Unix + +let () = + let g = Guestfs.create () in + + Guestfs.add_drive_opts g "/dev/null"; + Guestfs.add_drive_opts g ~readonly:true "/dev/null"; + Guestfs.add_drive_opts g ~readonly:true ~format:"raw" "/dev/null"; + Guestfs.add_drive_opts g ~iface:"virtio" ~readonly:true ~format:"raw" + "/dev/null"; + + Guestfs.close g diff --git a/perl/examples/lvs.pl b/perl/examples/lvs.pl index 1a840171..1755c899 100755 --- a/perl/examples/lvs.pl +++ b/perl/examples/lvs.pl @@ -10,7 +10,7 @@ die "Usage: lvs.pl guest.img\n" if @ARGV != 1 || ! -f $ARGV[0]; print "Creating the libguestfs handle\n"; my $h = Sys::Guestfs->new (); -$h->add_drive ($ARGV[0]); +$h->add_drive_opts ($ARGV[0], format => "raw"); print "Launching, this can take a few seconds\n"; $h->launch (); diff --git a/perl/t/070-optargs.t b/perl/t/070-optargs.t new file mode 100644 index 00000000..81873959 --- /dev/null +++ b/perl/t/070-optargs.t @@ -0,0 +1,34 @@ +# libguestfs Perl bindings -*- perl -*- +# Copyright (C) 2010 Red Hat Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +use strict; +use warnings; +use Test::More tests => 4; + +use Sys::Guestfs; + +my $h = Sys::Guestfs->new (); +ok ($h); + +$h->add_drive_opts ("/dev/null"); +ok (1); + +$h->add_drive_opts ("/dev/null", readonly => 1); +ok (1); + +$h->add_drive_opts ("/dev/null", format => "qcow2", readonly => 0); +ok (1); diff --git a/php/extension/guestfs_php_004.phpt b/php/extension/guestfs_php_004.phpt new file mode 100644 index 00000000..08e53a37 --- /dev/null +++ b/php/extension/guestfs_php_004.phpt @@ -0,0 +1,33 @@ +--TEST-- +Check function with optional arguments. +--FILE-- +<?php + +// See comment in php/run-php-tests.sh. +//putenv ('LIBGUESTFS_DEBUG=1'); + +$g = guestfs_create (); +if ($g == false) { + echo ("Failed to create guestfs_php handle.\n"); + exit; +} +if (guestfs_add_drive_opts ($g, "/dev/null") == false) { + echo ("Failed add_drive_opts, no optional arguments.\n"); + exit; +} +if (guestfs_add_drive_opts ($g, "/dev/null", 0) == false) { + echo ("Failed add_drive_opts, one optional argument.\n"); + exit; +} +if (guestfs_add_drive_opts ($g, "/dev/null", 1) == false) { + echo ("Failed add_drive_opts, one optional argument.\n"); + exit; +} +if (guestfs_add_drive_opts ($g, "/dev/null", 1, "qcow2") == false) { + echo ("Failed add_drive_opts, two optional arguments.\n"); + exit; +} +echo ("Completed tests OK.\n"); +?> +--EXPECT-- +Completed tests OK. diff --git a/python/t/060-optargs.py b/python/t/060-optargs.py new file mode 100644 index 00000000..78d07c8b --- /dev/null +++ b/python/t/060-optargs.py @@ -0,0 +1,25 @@ +# libguestfs Python bindings +# Copyright (C) 2010 Red Hat Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +import os +import guestfs + +g = guestfs.GuestFS() +g.add_drive_opts ("/dev/null") +g.add_drive_opts ("/dev/null", readonly = 1) +g.add_drive_opts ("/dev/null", iface = "virtio", format = "qcow2") +del g diff --git a/regressions/rhbz501893.c b/regressions/rhbz501893.c index fcda5cd3..aa752716 100644 --- a/regressions/rhbz501893.c +++ b/regressions/rhbz501893.c @@ -39,6 +39,12 @@ main (int argc, char *argv[]) assert (guestfs_add_drive (g, NULL) == -1); assert (guestfs_config (g, NULL, NULL) == -1); + /* This optional argument must not be NULL. */ + + assert (guestfs_add_drive_opts (g, "/dev/null", + GUESTFS_ADD_DRIVE_OPTS_FORMAT, NULL, + -1) == -1); + /* These can be safely set to NULL, should be no error. */ assert (guestfs_set_path (g, NULL) == 0); diff --git a/regressions/test-lvm-mapping.pl b/regressions/test-lvm-mapping.pl index 8a38232e..7af2842e 100755 --- a/regressions/test-lvm-mapping.pl +++ b/regressions/test-lvm-mapping.pl @@ -34,7 +34,7 @@ my $g = Sys::Guestfs->new (); #$g->set_verbose (1); #$g->set_trace (1); -$g->add_drive ($testimg); +$g->add_drive_opts ($testimg, format => "raw"); $g->launch (); # Create an arrangement of PVs, VGs and LVs. diff --git a/ruby/tests/tc_060_optargs.rb b/ruby/tests/tc_060_optargs.rb new file mode 100644 index 00000000..232286a2 --- /dev/null +++ b/ruby/tests/tc_060_optargs.rb @@ -0,0 +1,33 @@ +# libguestfs Ruby bindings -*- ruby -*- +# Copyright (C) 2010 Red Hat Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +require 'test/unit' +$:.unshift(File::join(File::dirname(__FILE__), "..", "lib")) +$:.unshift(File::join(File::dirname(__FILE__), "..", "ext", "guestfs")) +require 'guestfs' + +class TestLoad < Test::Unit::TestCase + def test_optargs + g = Guestfs::create() + + g.add_drive_opts("/dev/null", {}) + g.add_drive_opts("/dev/null", :readonly => 1) + g.add_drive_opts("/dev/null", :readonly => 1, :iface => "virtio") + g.add_drive_opts("/dev/null", + :readonly => 1, :iface => "virtio", :format => "qcow2") + end +end diff --git a/src/guestfs.pod b/src/guestfs.pod index d034c8e3..c1e595c7 100644 --- a/src/guestfs.pod +++ b/src/guestfs.pod @@ -124,7 +124,22 @@ disk, an actual block device, or simply an empty file of zeroes that you have created through L<posix_fallocate(3)>. Libguestfs lets you do useful things to all of these. -You can add a disk read-only using L</guestfs_add_drive_ro>, in which +The call you should use in modern code for adding drives is +L</guestfs_add_drive_opts>. To add a disk image, allowing writes, and +specifying that the format is raw, do: + + guestfs_add_drive_opts (g, filename, + GUESTFS_ADD_DRIVE_OPTS_FORMAT, "raw", + -1); + +You can add a disk read-only using: + + guestfs_add_drive_opts (g, filename, + GUESTFS_ADD_DRIVE_OPTS_FORMAT, "raw", + GUESTFS_ADD_DRIVE_OPTS_READONLY, 1, + -1); + +or by calling the older function L</guestfs_add_drive_ro>. In either case libguestfs won't modify the file. Be extremely cautious if the disk image is in use, eg. if it is being @@ -835,7 +850,8 @@ L</MULTIPLE HANDLES AND MULTIPLE THREADS> below. Create a connection handle. -You have to call L</guestfs_add_drive> on the handle at least once. +You have to call L</guestfs_add_drive_opts> (or one of the equivalent +calls) on the handle at least once. This function returns a non-NULL pointer to a handle on success or NULL on error. @@ -1050,6 +1066,101 @@ package versioning: Requires: libguestfs >= 1.0.80 +=head1 CALLS WITH OPTIONAL ARGUMENTS + +A recent feature of the API is the introduction of calls which take +optional arguments. In C these are declared 3 ways. The main way is +as a call which takes variable arguments (ie. C<...>), as in this +example: + + int guestfs_add_drive_opts (guestfs_h *g, const char *filename, ...); + +Call this with a list of optional arguments, terminated by C<-1>. +So to call with no optional arguments specified: + + guestfs_add_drive_opts (g, filename, -1); + +With a single optional argument: + + guestfs_add_drive_opts (g, filename, + GUESTFS_ADD_DRIVE_OPTS_FORMAT, "qcow2", + -1); + +With two: + + guestfs_add_drive_opts (g, filename, + GUESTFS_ADD_DRIVE_OPTS_FORMAT, "qcow2", + GUESTFS_ADD_DRIVE_OPTS_READONLY, 1, + -1); + +and so forth. Don't forget the terminating C<-1> otherwise +Bad Things will happen! + +=head2 USING va_list FOR OPTIONAL ARGUMENTS + +The second variant has the same name with the suffix C<_va>, which +works the same way but takes a C<va_list>. See the C manual for +details. For the example function, this is declared: + + int guestfs_add_drive_opts_va (guestfs_h *g, const char *filename, + va_list args); + +=head2 CONSTRUCTING OPTIONAL ARGUMENTS + +The third variant is useful where you need to construct these +calls. You pass in a structure where you fill in the optional +fields. The structure has a bitmask as the first element which +you must set to indicate which fields you have filled in. For +our example function the structure and call are declared: + + struct guestfs_add_drive_opts_argv { + uint64_t bitmask; + int readonly; + const char *format; + /* ... */ + }; + int guestfs_add_drive_opts_argv (guestfs_h *g, const char *filename, + const struct guestfs_add_drive_opts_argv *optargs); + +You could call it like this: + + struct guestfs_add_drive_opts_argv optargs = { + .bitmask = GUESTFS_ADD_DRIVE_OPTS_READONLY_BITMASK | + GUESTFS_ADD_DRIVE_OPTS_FORMAT_BITMASK, + .readonly = 1, + .format = "qcow2" + }; + + guestfs_add_drive_opts_argv (g, filename, &optargs); + +Notes: + +=over 4 + +=item * + +The C<_BITMASK> suffix on each option name when specifying the +bitmask. + +=item * + +You do not need to fill in all fields of the structure. + +=item * + +There must be a one-to-one correspondence between fields of the +structure that are filled in, and bits set in the bitmask. + +=back + +=head2 OPTIONAL ARGUMENTS IN OTHER LANGUAGES + +In other languages, optional arguments are expressed in the +way that is natural for that language. We refer you to the +language-specific documentation for more details on that. + +For guestfish, see L<guestfish(1)/OPTIONAL ARGUMENTS>. + =begin html <!-- old anchor for the next section --> diff --git a/src/launch.c b/src/launch.c index f9d8329f..7f2f74ca 100644 --- a/src/launch.c +++ b/src/launch.c @@ -61,6 +61,7 @@ #include <arpa/inet.h> #include <netinet/in.h> +#include "c-ctype.h" #include "glthread/lock.h" #include "guestfs.h" @@ -131,64 +132,108 @@ guestfs__config (guestfs_h *g, return 0; } -int -guestfs__add_drive_with_if (guestfs_h *g, const char *filename, - const char *drive_if) +/* cache=off improves reliability in the event of a host crash. + * + * However this option causes qemu to try to open the file with + * O_DIRECT. This fails on some filesystem types (notably tmpfs). + * So we check if we can open the file with or without O_DIRECT, + * and use cache=off (or not) accordingly. + */ +static int +test_cache_off (guestfs_h *g, const char *filename) { - size_t len = strlen (filename) + 64; - char buf[len]; - - if (strchr (filename, ',') != NULL) { - error (g, _("filename cannot contain ',' (comma) character")); - return -1; + int fd = open (filename, O_RDONLY|O_DIRECT); + if (fd >= 0) { + close (fd); + return 1; } - /* cache=off improves reliability in the event of a host crash. - * - * However this option causes qemu to try to open the file with - * O_DIRECT. This fails on some filesystem types (notably tmpfs). - * So we check if we can open the file with or without O_DIRECT, - * and use cache=off (or not) accordingly. - * - * This test also checks for the presence of the file, which - * is a documented semantic of this interface. - */ - int fd = open (filename, O_RDONLY|O_DIRECT); + fd = open (filename, O_RDONLY); if (fd >= 0) { close (fd); - snprintf (buf, len, "file=%s,cache=off,if=%s", filename, drive_if); - } else { - fd = open (filename, O_RDONLY); - if (fd >= 0) { - close (fd); - snprintf (buf, len, "file=%s,if=%s", filename, drive_if); - } else { - perrorf (g, "%s", filename); - return -1; - } + return 0; } - return guestfs__config (g, "-drive", buf); + perrorf (g, "%s", filename); + return -1; +} + +/* Check string parameter matches ^[-_[:alnum:]]+$ (in C locale). */ +static int +valid_format_iface (const char *str) +{ + size_t len = strlen (str); + + if (len == 0) + return 0; + + while (len > 0) { + char c = *str++; + len--; + if (c != '-' && c != '_' && !c_isalnum (c)) + return 0; + } + return 1; } int -guestfs__add_drive_ro_with_if (guestfs_h *g, const char *filename, - const char *drive_if) +guestfs__add_drive_opts (guestfs_h *g, const char *filename, + const struct guestfs_add_drive_opts_argv *optargs) { + int readonly; + const char *format; + const char *iface; + if (strchr (filename, ',') != NULL) { error (g, _("filename cannot contain ',' (comma) character")); return -1; } - if (access (filename, F_OK) == -1) { - perrorf (g, "%s", filename); + readonly = optargs->bitmask & GUESTFS_ADD_DRIVE_OPTS_READONLY_BITMASK + ? optargs->readonly : 0; + format = optargs->bitmask & GUESTFS_ADD_DRIVE_OPTS_FORMAT_BITMASK + ? optargs->format : NULL; + iface = optargs->bitmask & GUESTFS_ADD_DRIVE_OPTS_IFACE_BITMASK + ? optargs->iface : DRIVE_IF; + + if (format && !valid_format_iface (format)) { + error (g, _("%s parameter is empty or contains disallowed characters"), + "format"); return -1; } + if (!valid_format_iface (iface)) { + error (g, _("%s parameter is empty or contains disallowed characters"), + "iface"); + return -1; + } + + /* For writable files, see if we can use cache=off. This also + * checks for the existence of the file. For readonly we have + * to do the check explicitly. + */ + int use_cache_off = readonly ? 0 : test_cache_off (g, filename); + if (use_cache_off == -1) + return -1; + + if (readonly) { + if (access (filename, F_OK) == -1) { + perrorf (g, "%s", filename); + return -1; + } + } - size_t len = strlen (filename) + 64; + /* Construct the final -drive parameter. */ + size_t len = 64 + strlen (filename) + strlen (iface); + if (format) len += strlen (format); char buf[len]; - snprintf (buf, len, "file=%s,snapshot=on,if=%s", filename, drive_if); + snprintf (buf, len, "file=%s%s%s%s%s,if=%s", + filename, + readonly ? ",snapshot=on" : "", + use_cache_off ? ",cache=off" : "", + format ? ",format=" : "", + format ? format : "", + iface); return guestfs__config (g, "-drive", buf); } @@ -196,13 +241,48 @@ guestfs__add_drive_ro_with_if (guestfs_h *g, const char *filename, int guestfs__add_drive (guestfs_h *g, const char *filename) { - return guestfs__add_drive_with_if (g, filename, DRIVE_IF); + struct guestfs_add_drive_opts_argv optargs = { + .bitmask = 0, + }; + + return guestfs__add_drive_opts (g, filename, &optargs); } int guestfs__add_drive_ro (guestfs_h *g, const char *filename) { - return guestfs__add_drive_ro_with_if (g, filename, DRIVE_IF); + struct guestfs_add_drive_opts_argv optargs = { + .bitmask = GUESTFS_ADD_DRIVE_OPTS_READONLY_BITMASK, + .readonly = 1, + }; + + return guestfs__add_drive_opts (g, filename, &optargs); +} + +int +guestfs__add_drive_with_if (guestfs_h *g, const char *filename, + const char *iface) +{ + struct guestfs_add_drive_opts_argv optargs = { + .bitmask = GUESTFS_ADD_DRIVE_OPTS_IFACE_BITMASK, + .iface = iface, + }; + + return guestfs__add_drive_opts (g, filename, &optargs); +} + +int +guestfs__add_drive_ro_with_if (guestfs_h *g, const char *filename, + const char *iface) +{ + struct guestfs_add_drive_opts_argv optargs = { + .bitmask = GUESTFS_ADD_DRIVE_OPTS_IFACE_BITMASK + | GUESTFS_ADD_DRIVE_OPTS_READONLY_BITMASK, + .iface = iface, + .readonly = 1, + }; + + return guestfs__add_drive_opts (g, filename, &optargs); } int diff --git a/test-tool/test-tool.c b/test-tool/test-tool.c index 7b49dbec..c21906f3 100644 --- a/test-tool/test-tool.c +++ b/test-tool/test-tool.c @@ -182,13 +182,18 @@ main (int argc, char *argv[]) _("libguestfs-test-tool: failed to create libguestfs handle\n")); exit (EXIT_FAILURE); } - if (guestfs_add_drive (g, tmpf) == -1) { + if (guestfs_add_drive_opts (g, tmpf, + GUESTFS_ADD_DRIVE_OPTS_FORMAT, "raw", + -1) == -1) { fprintf (stderr, _("libguestfs-test-tool: failed to add drive '%s'\n"), tmpf); exit (EXIT_FAILURE); } - if (guestfs_add_drive (g, isof) == -1) { + if (guestfs_add_drive_opts (g, isof, + GUESTFS_ADD_DRIVE_OPTS_FORMAT, "raw", + GUESTFS_ADD_DRIVE_OPTS_READONLY, 1, + -1) == -1) { fprintf (stderr, _("libguestfs-test-tool: failed to add drive '%s'\n"), isof); |