399 lines
15 KiB
Diff
399 lines
15 KiB
Diff
From 0be1035c710d95aeca68a10fe9a7b4b740ae7aff Mon Sep 17 00:00:00 2001
|
||
From: "Richard W.M. Jones" <rjones@redhat.com>
|
||
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 <lersek@redhat.com>
|
||
(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<FILE> to C<PERMISSIONS>.
|
||
I<Note>: C<PERMISSIONS> 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<FILE> to C<PERMISSIONS>.\n\nI<Note>: C<PERMISSIONS> 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 <hidden>"
|
||
| 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<Note>: C<PERMISSIONS> 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";
|