File: utils.ml

package info (click to toggle)
libguestfs 1%3A1.54.1-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 98,892 kB
  • sloc: ansic: 379,443; ml: 38,771; sh: 10,329; java: 9,631; cs: 6,377; haskell: 5,729; makefile: 5,178; python: 3,821; perl: 2,467; erlang: 2,461; ruby: 349; xml: 275; pascal: 257; javascript: 157; cpp: 10
file content (240 lines) | stat: -rw-r--r-- 7,647 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
(* libguestfs
 * Copyright (C) 2009-2023 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
 *)

(* Please read generator/README first. *)

(* Useful functions.
 * Note we don't want to use any external OCaml libraries which
 * makes this a bit harder than it should be.
 *)

open Std_utils

open Unix
open Printf

open Types

let errcode_of_ret = function
  | RConstOptString _ ->
      `CannotReturnError
  | RErr | RInt _ | RBool _ | RInt64 _ ->
      `ErrorIsMinusOne
  | RConstString _
  | RString _ | RBufferOut _
  | RStringList _ | RHashtable _
  | RStruct _ | RStructList _ ->
      `ErrorIsNULL

let string_of_errcode = function
  | `ErrorIsMinusOne -> "-1"
  | `ErrorIsNULL -> "NULL"

(* Generate a uuidgen-compatible UUID (used in tests).  However to
 * avoid having the UUID change every time we rebuild the tests,
 * generate it as a function of the contents of the [actions*.ml]
 * files.
 *
 * Originally I thought uuidgen was using RFC 4122, but it doesn't
 * appear to.
 *
 * Note that the format must be 01234567-0123-0123-0123-0123456789ab
 *)
let stable_uuid =
  let cmd = "cat generator/actions*.ml" in
  let chan = open_process_in cmd in
  let s = Digest.channel chan (-1) in
  (match close_process_in chan with
  | WEXITED 0 -> ()
  | WEXITED i ->
    failwithf "command exited with non-zero status (%d)" i
  | WSIGNALED i | WSTOPPED i ->
    failwithf "command signalled or stopped with non-zero status (%d)" i
  );

  let s = Digest.to_hex s in

  (* In util-linux <= 2.19, mkswap -U cannot handle the first byte of
   * the UUID being zero, so we artificially rewrite such UUIDs.
   * http://article.gmane.org/gmane.linux.utilities.util-linux-ng/4273
   *)
  let s =
    if s.[0] = '0' && s.[1] = '0' then
      "1" ^ String.sub s 1 (String.length s - 1)
    else s in

  String.sub s 0 8 ^ "-"
  ^ String.sub s 8 4 ^ "-"
  ^ String.sub s 12 4 ^ "-"
  ^ String.sub s 16 4 ^ "-"
  ^ String.sub s 20 12

type rstructs_used_t = RStructOnly | RStructListOnly | RStructAndList

(* Returns a list of RStruct/RStructList structs that are returned
 * by any function.  Each element of returned list is a pair:
 *
 * (structname, RStructOnly)
 *    == there exists function which returns RStruct (_, structname)
 * (structname, RStructListOnly)
 *    == there exists function which returns RStructList (_, structname)
 * (structname, RStructAndList)
 *    == there are functions returning both RStruct (_, structname)
 *                                      and RStructList (_, structname)
 *)
let rstructs_used_by functions =
  (* ||| is a "logical OR" for rstructs_used_t *)
  let (|||) a b =
    match a, b with
    | RStructAndList, _
    | _, RStructAndList -> RStructAndList
    | RStructOnly, RStructListOnly
    | RStructListOnly, RStructOnly -> RStructAndList
    | RStructOnly, RStructOnly -> RStructOnly
    | RStructListOnly, RStructListOnly -> RStructListOnly
  in

  let h = Hashtbl.create 13 in

  (* if elem->oldv exists, update entry using ||| operator,
   * else just add elem->newv to the hash
   *)
  let update elem newv =
    try  let oldv = Hashtbl.find h elem in
         Hashtbl.replace h elem (newv ||| oldv)
    with Not_found -> Hashtbl.add h elem newv
  in

  List.iter (
    fun { style = ret, _, _ } ->
      match ret with
      | RStruct (_, structname) -> update structname RStructOnly
      | RStructList (_, structname) -> update structname RStructListOnly
      | _ -> ()
  ) functions;

  (* return key->values as a list of (key,value) *)
  Hashtbl.fold (fun key value xs -> (key, value) :: xs) h []

let files_equal n1 n2 =
  let cmd = sprintf "cmp -s %s %s" (Filename.quote n1) (Filename.quote n2) in
  match Sys.command cmd with
  | 0 -> true
  | 1 -> false
  | i -> failwithf "%s: failed with error code %d" cmd i

let name_of_argt = function
  | String (_, n) | StringList (_, n)
  | OptString n
  | Bool n | Int n | Int64 n
  | BufferIn n | Pointer (_, n) -> n

let name_of_optargt = function
  | OBool n | OInt n | OInt64 n | OString n | OStringList n -> n

let seq_of_test = function
  | TestRun s
  | TestResult (s, _)
  | TestResultString (s, _)
  | TestResultDevice (s, _)
  | TestResultTrue s
  | TestResultFalse s
  | TestLastFail s
  | TestRunOrUnsupported s -> s

let c_quote str =
  let str = String.replace str "\\" "\\\\" in
  let str = String.replace str "\r" "\\r" in
  let str = String.replace str "\n" "\\n" in
  let str = String.replace str "\t" "\\t" in
  let str = String.replace str "\000" "\\0" in
  let str = String.replace str "\"" "\\\"" in
  str

let html_escape text =
  let text = String.replace text "&" "&amp;" in
  let text = String.replace text "<" "&lt;" in
  let text = String.replace text ">" "&gt;" in
  text

(* Used to memoize the result of pod2text. *)
type pod2text_memo_key = int option * bool * bool * string * string
                         (* width,    trim, discard, name,   longdesc *)
type pod2text_memo_value = string list (* list of lines of POD file *)
let run_pod2text (width, trim, discard, name, longdesc) =
  let cmd =
    match width with
    | Some width ->
        sprintf "pod2text -w %d" width
    | None ->
        "pod2text" in
  let chan_out, chan_in = open_process cmd in
  output_string chan_in "=encoding utf8\n\n";
  output_string chan_in (sprintf "=head1 %s\n\n%s\n" name longdesc);
  close_out chan_in;
  let lines = ref [] in
  (try while true do lines := input_line chan_out :: !lines done
   with End_of_file -> ());
  let lines = List.rev !lines in
  (match close_process (chan_out, chan_in) with
   | WEXITED 0 -> ()
   | WEXITED i ->
       failwithf "pod2text: process exited with non-zero status (%d)" i
   | WSIGNALED i | WSTOPPED i ->
       failwithf "pod2text: process signalled or stopped by signal %d" i
  );
  let lines =
    if discard then (* discard the first line of output *) List.tl lines
    else lines in
  let lines =
    if trim then List.map String.triml lines
    else lines in
  lines
let pod2text_memo : (pod2text_memo_key, pod2text_memo_value) Memoized_cache.t =
  Memoized_cache.create ~version:2 "pod2text" run_pod2text

let memos_atexit = ref false
let memos_save () =
  Memoized_cache.save pod2text_memo

(* Useful if you need the longdesc POD text as plain text.  Returns a
 * list of lines.
 *
 * Because this is very slow (the slowest part of autogeneration),
 * we memoize the results.
 *)
let pod2text ?width ?(trim = true) ?(discard = true) name longdesc =
  let key : pod2text_memo_key = width, trim, discard, name, longdesc in
  if not (!memos_atexit) then (
    at_exit memos_save;
    memos_atexit := true;
  );
  Memoized_cache.find pod2text_memo key

(* Compare two actions (for sorting). *)
let action_compare { name = n1 } { name = n2 } = compare n1 n2

let args_of_optargs optargs =
  List.map (
    function
    | OBool n -> Bool n
    | OInt n -> Int n
    | OInt64 n -> Int64 n
    | OString n -> String (PlainString, n)
    | OStringList n -> StringList (PlainString, n)
  ) optargs