(* virt-sysprep * Copyright (C) 2012 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *) open Common_utils open Printf open Common_gettext.Gettext open Getopt.OptionName class filesystem_side_effects = object val mutable m_created_file = false val mutable m_changed_file = false method created_file () = m_created_file <- true method get_created_file = m_created_file method changed_file () = m_changed_file <- true method get_changed_file = m_changed_file end class device_side_effects = object end type 'a callback = Guestfs.guestfs -> string -> 'a -> unit type operation = { order : int; name : string; enabled_by_default : bool; heading : string; pod_description : string option; pod_notes : string option; extra_args : extra_arg list; not_enabled_check_args : unit -> unit; perform_on_filesystems : filesystem_side_effects callback option; perform_on_devices : device_side_effects callback option; } and extra_arg = { extra_argspec : Getopt.keys * Getopt.spec * Getopt.doc; extra_pod_argval : string option; extra_pod_description : string; } let defaults = { order = 0; name = ""; enabled_by_default = false; heading = ""; pod_description = None; pod_notes = None; extra_args = []; not_enabled_check_args = (fun () -> ()); perform_on_filesystems = None; perform_on_devices = None; } let all_operations = ref [] let enabled_by_default_operations = ref [] module OperationSet = Set.Make ( struct type t = operation let compare a b = compare a.name b.name end ) type set = OperationSet.t let empty_set = OperationSet.empty let opset_of_oplist li = List.fold_left ( fun acc elem -> OperationSet.add elem acc ) empty_set li let add_to_set name set = let op = List.find (fun { name = n } -> name = n) !all_operations in OperationSet.add op set let add_defaults_to_set set = OperationSet.union set (opset_of_oplist !enabled_by_default_operations) let add_all_to_set set = opset_of_oplist !all_operations let remove_from_set name set = let name_filter = fun { name = n } -> name = n in if List.exists name_filter !all_operations <> true then raise Not_found; OperationSet.diff set (OperationSet.filter name_filter set) let remove_defaults_from_set set = OperationSet.diff set (opset_of_oplist !enabled_by_default_operations) let remove_all_from_set set = empty_set let register_operation op = push_front op all_operations; if op.enabled_by_default then push_front op enabled_by_default_operations let baked = ref false let rec bake () = (* Note we actually want all_operations to be sorted by name, * ignoring the order field. *) let ops = List.sort (fun { name = a } { name = b } -> compare a b) !all_operations in check_no_dupes ops; List.iter check ops; all_operations := ops; baked := true and check_no_dupes ops = ignore ( List.fold_left ( fun opset op -> if OperationSet.mem op opset then error (f_"duplicate operation name (%s)") op.name; add_to_set op.name opset ) empty_set ops ) and check op = let n = String.length op.name in if n = 0 then error (f_"operation name is an empty string"); for i = 0 to n-1 do match String.unsafe_get op.name i with | 'a'..'z' | 'A'..'Z' | '0'..'9' | '-' -> () | c -> error (f_"disallowed character (%c) in operation name") c done; let n = String.length op.heading in if n = 0 then error (f_"operation %s has no heading") op.name; if op.heading.[n-1] = '\n' || op.heading.[n-1] = '.' then error (f_"heading for %s must not end with newline or period") op.name; (match op.pod_description with | None -> () | Some description -> let n = String.length description in if n = 0 then error (f_"operation %s has no POD") op.name; if description.[n-1] = '\n' then error (f_"POD for %s must not end with newline") op.name; ); (match op.pod_notes with | None -> () | Some notes -> let n = String.length notes in if n = 0 then error (f_"operation %s has no POD notes") op.name; if notes.[n-1] = '\n' then error (f_"POD notes for %s must not end with newline") op.name; ) let extra_args () = assert !baked; List.flatten ( List.map (fun { extra_args = extra_args } -> List.map (fun { extra_argspec = argspec } -> argspec) extra_args ) !all_operations ) (* These internal functions are used to generate the man page. *) let dump_pod () = assert !baked; List.iter ( fun op -> printf "=head2 B<%s>\n" op.name; if op.enabled_by_default then printf "*\n"; printf "\n"; printf "%s.\n\n" op.heading; may (printf "%s\n\n") op.pod_description; (match op.pod_notes with | None -> () | Some notes -> printf "=head3 "; printf (f_"Notes on %s") op.name; printf "\n\n"; printf "%s\n\n" notes ) ) !all_operations let dump_pod_options () = assert !baked; let args = List.map ( fun { name = op_name; extra_args = extra_args } -> List.map (fun ea -> op_name, ea) extra_args ) !all_operations in let args = List.flatten args in let args = List.map ( function | (op_name, { extra_argspec = (arg_names, (Getopt.Unit _ | Getopt.Set _ | Getopt.Clear _), _); extra_pod_argval = None; extra_pod_description = pod }) -> List.map ( fun arg_name -> let heading = sprintf "B<%s>" (Getopt.string_of_option_name arg_name) in arg_name, (op_name, heading, pod) ) arg_names | (op_name, { extra_argspec = (arg_names, (Getopt.String _ | Getopt.Set_string _ | Getopt.Int _ | Getopt.Set_int _ | Getopt.Symbol _), _); extra_pod_argval = Some arg_val; extra_pod_description = pod }) -> List.map ( fun arg_name -> let heading = sprintf "B<%s> %s" (Getopt.string_of_option_name arg_name) arg_val in arg_name, (op_name, heading, pod) ) arg_names | _ -> failwith "sysprep_operation.ml: argument type not implemented" ) args in let args = List.flatten args in let args = List.sort (fun (a, _) (b, _) -> Getopt.compare_command_line_args a b) args in List.iter ( fun (arg_name, (op_name, heading, pod)) -> printf "=item %s\n" heading; printf "(see C<%s> below)\n" op_name; printf "\n"; printf "%s\n\n" pod ) args let list_operations () = assert !baked; List.iter ( fun op -> printf "%s %s %s\n" op.name (if op.enabled_by_default then "*" else " ") op.heading ) !all_operations let not_enabled_check_args ?operations () = let enabled_ops = match operations with | None -> !enabled_by_default_operations | Some opset -> (* just the operation names listed *) OperationSet.elements opset in let all_ops = opset_of_oplist !all_operations in let enabled_ops = opset_of_oplist enabled_ops in let disabled_ops = OperationSet.diff all_ops enabled_ops in OperationSet.iter (fun op -> op.not_enabled_check_args ()) disabled_ops let compare_operations { order = o1; name = n1 } { order = o2; name = n2 } = let i = compare o1 o2 in if i <> 0 then i else compare n1 n2 let perform_operations_on_filesystems ?operations g root side_effects = assert !baked; let ops = match operations with | None -> !enabled_by_default_operations | Some opset -> (* just the operation names listed *) OperationSet.elements opset in (* Perform the operations in alphabetical, rather than random order. *) let ops = List.sort compare_operations ops in List.iter ( function | { name = name; perform_on_filesystems = Some fn } -> message (f_"Performing %S ...") name; fn g root side_effects | { perform_on_filesystems = None } -> () ) ops let perform_operations_on_devices ?operations g root side_effects = assert !baked; let ops = match operations with | None -> !enabled_by_default_operations | Some opset -> (* just the operation names listed *) OperationSet.elements opset in (* Perform the operations in alphabetical, rather than random order. *) let ops = List.sort compare_operations ops in List.iter ( function | { name = name; perform_on_devices = Some fn } -> message (f_"Performing %S ...") name; fn g root side_effects | { perform_on_devices = None } -> () ) ops