From 0be1035c710d95aeca68a10fe9a7b4b740ae7aff Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Thu, 29 Jun 2023 13:33:04 +0100 Subject: [PATCH] generator: Add --chown option for virt-customize Also this updates the common submodule to include the changes. Fixes: https://github.com/rwmjones/guestfs-tools/issues/12 Acked-by: Laszlo Ersek (cherry picked from commit d8e48bff212f9b0558480ffedf8158157360d0d5) --- common | 2 +- generator/customize.ml | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) Submodule common d61cd820..bbb54714: diff --git a/common/mlcustomize/customize-options.pod b/common/mlcustomize/customize-options.pod index 22a96e04..22724600 100644 --- a/common/mlcustomize/customize-options.pod +++ b/common/mlcustomize/customize-options.pod @@ -63,6 +63,30 @@ Change the permissions of C to C. I: C by default would be decimal, unless you prefix it with C<0> to get octal, ie. use C<0700> not C<700>. +=item B<--chown> UID.GID:PATH + +Change the owner user and group ID of a file or directory in the guest. +Note: + +=over 4 + +=item * + +Only numeric UIDs and GIDs will work, and these may not be the same +inside the guest as on the host. + +=item * + +This will not work with Windows guests. + +=back + +For example: + + virt-customize --chown '0.0:/var/log/audit.log' + +See also: I<--upload>. + =item B<--commands-from-file> FILENAME Read the customize commands from a file, one (and its arguments) diff --git a/common/mlcustomize/customize-synopsis.pod b/common/mlcustomize/customize-synopsis.pod index d04f421e..e20b12d4 100644 --- a/common/mlcustomize/customize-synopsis.pod +++ b/common/mlcustomize/customize-synopsis.pod @@ -1,15 +1,15 @@ [--append-line FILE:LINE] [--chmod PERMISSIONS:FILE] - [--commands-from-file FILENAME] [--copy SOURCE:DEST] - [--copy-in LOCALPATH:REMOTEDIR] [--delete PATH] [--edit FILE:EXPR] - [--firstboot SCRIPT] [--firstboot-command 'CMD+ARGS'] - [--firstboot-install PKG,PKG..] [--hostname HOSTNAME] - [--inject-qemu-ga METHOD] [--inject-virtio-win METHOD] - [--install PKG,PKG..] [--link TARGET:LINK[:LINK..]] [--mkdir DIR] - [--move SOURCE:DEST] [--password USER:SELECTOR] - [--root-password SELECTOR] [--run SCRIPT] - [--run-command 'CMD+ARGS'] [--scrub FILE] [--sm-attach SELECTOR] - [--sm-register] [--sm-remove] [--sm-unregister] - [--ssh-inject USER[:SELECTOR]] [--truncate FILE] + [--chown UID.GID:PATH] [--commands-from-file FILENAME] + [--copy SOURCE:DEST] [--copy-in LOCALPATH:REMOTEDIR] + [--delete PATH] [--edit FILE:EXPR] [--firstboot SCRIPT] + [--firstboot-command 'CMD+ARGS'] [--firstboot-install PKG,PKG..] + [--hostname HOSTNAME] [--inject-qemu-ga METHOD] + [--inject-virtio-win METHOD] [--install PKG,PKG..] + [--link TARGET:LINK[:LINK..]] [--mkdir DIR] [--move SOURCE:DEST] + [--password USER:SELECTOR] [--root-password SELECTOR] + [--run SCRIPT] [--run-command 'CMD+ARGS'] [--scrub FILE] + [--sm-attach SELECTOR] [--sm-register] [--sm-remove] + [--sm-unregister] [--ssh-inject USER[:SELECTOR]] [--truncate FILE] [--truncate-recursive PATH] [--timezone TIMEZONE] [--touch FILE] [--uninstall PKG,PKG..] [--update] [--upload FILE:DEST] [--write FILE:CONTENT] [--no-logfile] diff --git a/common/mlcustomize/customize_cmdline.ml b/common/mlcustomize/customize_cmdline.ml index 3c24315d..fd3074ad 100644 --- a/common/mlcustomize/customize_cmdline.ml +++ b/common/mlcustomize/customize_cmdline.ml @@ -41,6 +41,8 @@ and op = [ (* --append-line FILE:LINE *) | `Chmod of string * string (* --chmod PERMISSIONS:FILE *) + | `Chown of string * string + (* --chown UID.GID:PATH *) | `CommandsFromFile of string (* --commands-from-file FILENAME *) | `Copy of string * string @@ -187,6 +189,17 @@ let rec argspec () = s_"Change the permissions of a file" ), Some "PERMISSIONS:FILE", "Change the permissions of C to C.\n\nI: C by default would be decimal, unless you prefix\nit with C<0> to get octal, ie. use C<0700> not C<700>."; + ( + [ L"chown" ], + Getopt.String ( + s_"UID.GID:PATH", + fun s -> + let p = split_string_pair "chown" s in + List.push_front (`Chown p) ops + ), + s_"Change the owner user and group ID of a file or directory" + ), + Some "UID.GID:PATH", "Change the owner user and group ID of a file or directory in the guest.\nNote:\n\n=over 4\n\n=item *\n\nOnly numeric UIDs and GIDs will work, and these may not be the same\ninside the guest as on the host.\n\n=item *\n\nThis will not work with Windows guests.\n\n=back\n\nFor example:\n\n virt-customize --chown '0.0:/var/log/audit.log'\n\nSee also: I<--upload>."; ( [ L"commands-from-file" ], Getopt.String ( diff --git a/common/mlcustomize/customize_cmdline.mli b/common/mlcustomize/customize_cmdline.mli index 0cc166e6..5883bbe0 100644 --- a/common/mlcustomize/customize_cmdline.mli +++ b/common/mlcustomize/customize_cmdline.mli @@ -33,6 +33,8 @@ and op = [ (* --append-line FILE:LINE *) | `Chmod of string * string (* --chmod PERMISSIONS:FILE *) + | `Chown of string * string + (* --chown UID.GID:PATH *) | `CommandsFromFile of string (* --commands-from-file FILENAME *) | `Copy of string * string diff --git a/common/mltools/curl.ml b/common/mltools/curl.ml index 6dba9753..73eed903 100644 --- a/common/mltools/curl.ml +++ b/common/mltools/curl.ml @@ -20,11 +20,13 @@ open Printf open Std_utils open Tools_utils +open Common_gettext.Gettext type t = { curl : string; args : args; tmpdir : string option; + url : string; } and args = (string * string option) list @@ -40,11 +42,17 @@ let args_of_proxy = function | SystemProxy -> [] | ForcedProxy url -> [ "proxy", Some url; "noproxy", Some "" ] -let create ?(curl = "curl") ?(proxy = SystemProxy) ?tmpdir args = +let create ?(curl = "curl") ?(proxy = SystemProxy) ?tmpdir args url = + (* The ["url"] key must not appear in [args]. This was how the + * previous version of this module worked, so lets check there + * are no callers still doing this. + *) + List.iter (function "url", _ -> assert false | _ -> ()) args; + let args = safe_args @ args_of_proxy proxy @ args in - { curl = curl; args = args; tmpdir = tmpdir } + { curl; args; tmpdir; url } -let run { curl; args; tmpdir } = +let run { curl; args; tmpdir; url } = let config_file, chan = Filename.open_temp_file ?temp_dir:tmpdir "guestfscurl" ".conf" in List.iter ( @@ -67,15 +75,16 @@ let run { curl; args; tmpdir } = | c -> output_char chan c done; fprintf chan "\"\n" - ) args; + ) (("url", Some url) :: args); close_out chan; let cmd = sprintf "%s -q --config %s" (quote curl) (quote config_file) in - let lines = external_command ~echo_cmd:false cmd in + let help = sprintf (f_"downloading %s") url in + let lines = external_command ~echo_cmd:false ~help cmd in Unix.unlink config_file; lines -let to_string { curl; args } = +let to_string { curl; args; url } = let b = Buffer.create 128 in bprintf b "%s -q" (quote curl); List.iter ( @@ -85,7 +94,7 @@ let to_string { curl; args } = | "user", Some _ -> bprintf b " --user " | name, Some value -> bprintf b " --%s %s" name (quote value) ) args; - bprintf b "\n"; + bprintf b " %s\n" (quote url); Buffer.contents b let print chan t = output_string chan (to_string t) diff --git a/common/mltools/curl.mli b/common/mltools/curl.mli index a3e98dc6..1606a79a 100644 --- a/common/mltools/curl.mli +++ b/common/mltools/curl.mli @@ -27,13 +27,16 @@ type proxy = | SystemProxy (** Use the system settings. *) | ForcedProxy of string (** The proxy is forced to the specified URL. *) -val create : ?curl:string -> ?proxy:proxy -> ?tmpdir:string -> args -> t +val create : ?curl:string -> ?proxy:proxy -> ?tmpdir:string -> args -> string + -> t (** Create a curl command handle. The curl arguments are a list of key, value pairs corresponding to curl command line parameters, without leading dashes, eg. [("user", Some "user:password")]. + The string parameter is the URL (which is required). + The optional [?curl] parameter controls the name of the curl binary (default ["curl"]). diff --git a/common/mltools/tools_utils.ml b/common/mltools/tools_utils.ml index 8b611e77..23f16c51 100644 --- a/common/mltools/tools_utils.ml +++ b/common/mltools/tools_utils.ml @@ -435,8 +435,12 @@ let create_standard_options argspec ?anon_fun ?(key_opts = false) let getopt = Getopt.create argspec ?anon_fun usage_msg in { getopt; ks; debug_gc } +let external_command_failed help cmd reason = + let help_prefix = match help with None -> "" | Some str -> str ^ ": " in + error "%s%s ‘%s’: %s" help_prefix (s_"external command") cmd reason + (* Run an external command, slurp up the output as a list of lines. *) -let external_command ?(echo_cmd = true) cmd = +let external_command ?(echo_cmd = true) ?help cmd = if echo_cmd then debug "%s" cmd; let chan = Unix.open_process_in cmd in @@ -448,15 +452,18 @@ let external_command ?(echo_cmd = true) cmd = (match stat with | Unix.WEXITED 0 -> () | Unix.WEXITED i -> - error (f_"external command ‘%s’ exited with error %d") cmd i + let reason = sprintf (f_"exited with error %d") i in + external_command_failed help cmd reason | Unix.WSIGNALED i -> - error (f_"external command ‘%s’ killed by signal %d") cmd i + let reason = sprintf (f_"killed by signal %d") i in + external_command_failed help cmd reason | Unix.WSTOPPED i -> - error (f_"external command ‘%s’ stopped by signal %d") cmd i + let reason = sprintf (f_"stopped by signal %d") i in + external_command_failed help cmd reason ); lines -let rec run_commands ?(echo_cmd = true) cmds = +let rec run_commands ?(echo_cmd = true) ?help cmds = let res = Array.make (List.length cmds) 0 in let pids = List.mapi ( @@ -482,21 +489,21 @@ let rec run_commands ?(echo_cmd = true) cmds = let matching_pair = List.hd matching_pair in let idx, _, app, outfd, errfd = matching_pair in pids := new_pids; - res.(idx) <- do_teardown app outfd errfd stat + res.(idx) <- do_teardown help app outfd errfd stat ); done; Array.to_list res -and run_command ?(echo_cmd = true) ?stdout_fd ?stderr_fd args = +and run_command ?(echo_cmd = true) ?help ?stdout_fd ?stderr_fd args = let run_res = do_run args ~echo_cmd ?stdout_fd ?stderr_fd in match run_res with | Either (pid, app, outfd, errfd) -> let _, stat = Unix.waitpid [] pid in - do_teardown app outfd errfd stat + do_teardown help app outfd errfd stat | Or code -> code -and do_run ?(echo_cmd = true) ?stdout_fd ?stderr_fd args = +and do_run ?(echo_cmd = true) ?help ?stdout_fd ?stderr_fd args = let app = List.hd args in let get_fd default = function | None -> @@ -522,16 +529,18 @@ and do_run ?(echo_cmd = true) ?stdout_fd ?stderr_fd args = debug "%s: %s: executable not found" app fn; Or 127 -and do_teardown app outfd errfd exitstat = +and do_teardown help app outfd errfd exitstat = Option.iter Unix.close outfd; Option.iter Unix.close errfd; match exitstat with | Unix.WEXITED i -> - i + i | Unix.WSIGNALED i -> - error (f_"external command ‘%s’ killed by signal %d") app i + let reason = sprintf (f_"killed by signal %d") i in + external_command_failed help app reason | Unix.WSTOPPED i -> - error (f_"external command ‘%s’ stopped by signal %d") app i + let reason = sprintf (f_"stopped by signal %d") i in + external_command_failed help app reason let shell_command ?(echo_cmd = true) cmd = if echo_cmd then diff --git a/common/mltools/tools_utils.mli b/common/mltools/tools_utils.mli index ec900e63..193ba7b6 100644 --- a/common/mltools/tools_utils.mli +++ b/common/mltools/tools_utils.mli @@ -103,13 +103,17 @@ val create_standard_options : Getopt.speclist -> ?anon_fun:Getopt.anon_fun -> ?k Returns a new {!cmdline_options} structure. *) -val external_command : ?echo_cmd:bool -> string -> string list +val external_command : ?echo_cmd:bool -> ?help:string -> string -> string list (** Run an external command, slurp up the output as a list of lines. [echo_cmd] specifies whether to output the full command on verbose - mode, and it's on by default. *) + mode, and it's on by default. -val run_commands : ?echo_cmd:bool -> (string list * Unix.file_descr option * Unix.file_descr option) list -> int list + [help] is an optional string which is printed as a prefix in + case the external command fails, eg as a hint to the user about + what we were trying to do. *) + +val run_commands : ?echo_cmd:bool -> ?help:string -> (string list * Unix.file_descr option * Unix.file_descr option) list -> int list (** Run external commands in parallel without using a shell, and return a list with their exit codes. @@ -126,16 +130,24 @@ val run_commands : ?echo_cmd:bool -> (string list * Unix.file_descr option * Uni end of the execution of the command for which it was specified. [echo_cmd] specifies whether output the full command on verbose - mode, and it's on by default. *) + mode, and it's on by default. -val run_command : ?echo_cmd:bool -> ?stdout_fd:Unix.file_descr -> ?stderr_fd:Unix.file_descr -> string list -> int + [help] is an optional string which is printed as a prefix in + case the external command fails, eg as a hint to the user about + what we were trying to do. *) + +val run_command : ?echo_cmd:bool -> ?help:string -> ?stdout_fd:Unix.file_descr -> ?stderr_fd:Unix.file_descr -> string list -> int (** Run an external command without using a shell, and return its exit code. If [stdout_fd] or [stderr_fd] is specified, the file descriptor is automatically closed after executing the command. [echo_cmd] specifies whether output the full command on verbose - mode, and it's on by default. *) + mode, and it's on by default. + + [help] is an optional string which is printed as a prefix in + case the external command fails, eg as a hint to the user about + what we were trying to do. *) val shell_command : ?echo_cmd:bool -> string -> int (** Run an external shell command, and return its exit code. diff --git a/generator/customize.ml b/generator/customize.ml index aa7ac8e8..8d3dec3e 100644 --- a/generator/customize.ml +++ b/generator/customize.ml @@ -95,6 +95,34 @@ I: C by default would be decimal, unless you prefix it with C<0> to get octal, ie. use C<0700> not C<700>."; }; + { op_name = "chown"; + op_type = StringPair "UID.GID:PATH"; + op_discrim = "`Chown"; + op_shortdesc = "Change the owner user and group ID of a file or directory"; + op_pod_longdesc = "\ +Change the owner user and group ID of a file or directory in the guest. +Note: + +=over 4 + +=item * + +Only numeric UIDs and GIDs will work, and these may not be the same +inside the guest as on the host. + +=item * + +This will not work with Windows guests. + +=back + +For example: + + virt-customize --chown '0.0:/var/log/audit.log' + +See also: I<--upload>."; + }; + { op_name = "commands-from-file"; op_type = StringFn ("FILENAME", "customize_read_from_file"); op_discrim = "`CommandsFromFile";