slave: Use slightly modified event_callback.
[guestfs-browser.git] / utils.ml
1 (* Guestfs Browser.
2  * Copyright (C) 2010 Red Hat Inc.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17  *)
18
19 open ExtString
20 open ExtList
21
22 open Printf
23
24 let (+^) = Int64.add
25 let (-^) = Int64.sub
26 let ( *^ ) = Int64.mul
27 let (/^) = Int64.div
28 let (&^) = Int64.logand
29
30 type ('a, 'b) either = Left of 'a | Right of 'b
31
32 let (//) = Filename.concat
33
34 let verbose = ref false
35 let set_verbose_flag () = verbose := true
36 let verbose () = !verbose
37
38 let debug fs =
39   let f str =
40     if verbose () then (
41       prerr_string Config.package;
42       prerr_string ": tid ";
43       prerr_string (string_of_int (Thread.id (Thread.self ())));
44       prerr_string ": ";
45       prerr_string str;
46       prerr_newline ()
47     )
48   in
49   ksprintf f fs
50
51 let failwith fs =
52   let f str =
53     if verbose () then (prerr_string str; prerr_newline ());
54     raise (Failure str)
55   in
56   ksprintf f fs
57
58 let trace = ref false
59 let set_trace_flag () = trace := true
60 let trace () = !trace
61
62 let connect_uri = ref None
63 let set_connect_uri conn = connect_uri := conn
64 let connect_uri () = !connect_uri
65
66 let utf8_copyright = "\194\169"
67 let utf8_rarrow = "\xe2\x86\x92"
68
69 let pretty_string_of_exn =
70   function
71   | Guestfs.Error str ->
72       "Libguestfs error",
73       sprintf "libguestfs reported an error:
74
75 %s
76
77 To get more information about libguestfs errors, run guestfs-browser
78 with the -x flag on the command line."
79         str
80
81   | Libvirt.Virterror err ->
82       "Libvirt error",
83       sprintf "libvirt reported an error:
84
85 %s
86
87 To get more information about libvirt errors, run guestfs-browser
88 from the command line like this:
89
90 LIBVIRT_DEBUG=1 guestfs-browser"
91         (Libvirt.Virterror.to_string err)
92
93   (* Add more exception types here as we come across them.  Last
94    * case below is the catch-all.
95    *)
96   | exn ->
97       let str = Printexc.to_string exn in
98       debug "pretty_string_of_exn: unhandled exception %s" str;
99       "Error", str
100
101 let human_size i =
102   if i < 1024L then
103     sprintf "%Ld" i
104   else if i < 1024L *^ 1024L then
105     sprintf "%.1f KB" (Int64.to_float i /. 1024.)
106   else if i < 1024L *^ 1024L *^ 1024L then
107     sprintf "%.1f MB" (Int64.to_float i /. 1024. /. 1024.)
108   else if i < 1024L *^ 1024L *^ 1024L *^ 1024L then
109     sprintf "%.1f GB" (Int64.to_float i /. 1024. /. 1024. /. 1024.)
110   else
111     sprintf "%.1f TB" (Int64.to_float i /. 1024. /. 1024. /. 1024. /. 1024.)
112
113 let human_size_1k i =
114   if i < 1024L then
115     sprintf "%Ld KB" i
116   else if i < 1024L *^ 1024L then
117     sprintf "%.1f MB" (Int64.to_float i /. 1024.)
118   else
119     sprintf "%.1f GB" (Int64.to_float i /. 1024. /. 1024.)
120
121 let unique = let i = ref 0 in fun () -> incr i; !i
122
123 let mklabel text =
124   (GMisc.label ~text () :> GObj.widget)
125
126 (* g_markup_escape is not bound by lablgtk2, but we want to provide
127  * extra protection for \0 characters appearing in the string
128  * anyway.
129  *)
130 let markup_escape name =
131   let f = function
132     | '&' -> "&amp;" | '<' -> "&lt;" | '>' -> "&gt;"
133     | '\000' -> "\\0"
134     | c -> String.make 1 c
135   in
136   String.replace_chars f name
137
138 let libguestfs_version_string () =
139   let g = new Guestfs.guestfs () in
140   let v = g#version () in
141   let s =
142     sprintf "%Ld.%Ld.%Ld%s"
143       v.Guestfs.major v.Guestfs.minor v.Guestfs.release v.Guestfs.extra in
144   g#close ();
145   s
146
147 let libvirt_version_string () =
148   let v = fst (Libvirt.get_version ()) in
149   sprintf "%d.%d.%d" (v / 1_000_000) ((v / 1_000) mod 1_000) (v mod 1_000)
150
151 (* File type tests.
152  *
153  * Note these have to be on Linux ABI modes.  We cannot use the
154  * OCaml (ie. host) equivalents here.
155  *)
156 let rec file_type mask mode = Int64.logand mode 0o170000L = mask
157
158 and is_socket mode =       file_type 0o140000L mode
159 and is_symlink mode =      file_type 0o120000L mode
160 and is_regular_file mode = file_type 0o100000L mode
161 and is_block mode =        file_type 0o060000L mode
162 and is_directory mode =    file_type 0o040000L mode
163 and is_char mode =         file_type 0o020000L mode
164 and is_fifo mode =         file_type 0o010000L mode
165
166 and is_suid mode =         test_bit 0o4000L mode
167 and is_sgid mode =         test_bit 0o2000L mode
168 and is_svtx mode =         test_bit 0o1000L mode
169
170 and is_ru mode =           test_bit 0o400L mode
171 and is_wu mode =           test_bit 0o200L mode
172 and is_xu mode =           test_bit 0o100L mode
173 and is_rg mode =           test_bit 0o040L mode
174 and is_wg mode =           test_bit 0o020L mode
175 and is_xg mode =           test_bit 0o010L mode
176 and is_ro mode =           test_bit 0o004L mode
177 and is_wo mode =           test_bit 0o002L mode
178 and is_xo mode =           test_bit 0o001L mode
179
180 and test_bit mask mode = Int64.logand mode mask = mask
181
182 let file_permissions_string mode =
183   let c =
184     if is_socket mode then 's'
185     else if is_symlink mode then 'l'
186     else if is_regular_file mode then '-'
187     else if is_block mode then 'b'
188     else if is_directory mode then 'd'
189     else if is_char mode then 'c'
190     else if is_fifo mode then 'p' else '?' in
191   let ru = if is_ru mode then 'r' else '-' in
192   let wu = if is_wu mode then 'w' else '-' in
193   let xu = if is_xu mode then 'x' else '-' in
194   let rg = if is_rg mode then 'r' else '-' in
195   let wg = if is_wg mode then 'w' else '-' in
196   let xg = if is_xg mode then 'x' else '-' in
197   let ro = if is_ro mode then 'r' else '-' in
198   let wo = if is_wo mode then 'w' else '-' in
199   let xo = if is_xo mode then 'x' else '-' in
200   let str = sprintf "%c%c%c%c%c%c%c%c%c%c" c ru wu xu rg wg xg ro wo xo in
201
202   let suid = is_suid mode in
203   let sgid = is_sgid mode in
204   let svtx = is_svtx mode in
205   if suid then str.[3] <- 's';
206   if sgid then str.[6] <- 's';
207   if svtx then str.[9] <- 't';
208
209   str
210
211 let tmpdir () =
212   let chan = open_in "/dev/urandom" in
213   let data = String.create 16 in
214   really_input chan data 0 (String.length data);
215   close_in chan;
216   let data = Digest.to_hex (Digest.string data) in
217   (* Note this is secure, because if the name already exists, even as a
218    * symlink, mkdir(2) will fail.
219    *)
220   let tmpdir = Filename.temp_dir_name // sprintf "guestfsbrowser%s.tmp" data in
221   Unix.mkdir tmpdir 0o700;
222   at_exit
223     (fun () ->
224        let cmd = sprintf "rm -rf %s" (Filename.quote tmpdir) in
225        ignore (Sys.command cmd));
226   tmpdir
227
228 module CE = CamomileLibraryDefault.Camomile.CharEncoding
229 module UTF8 = CamomileLibraryDefault.Camomile.UTF8
230 module UChar = CamomileLibraryDefault.Camomile.UChar
231 let utf16le = CE.utf16le
232 let utf8 = CE.utf8
233 let recode = CE.recode_string ~in_enc:utf16le ~out_enc:utf8
234
235 let windows_string_to_utf8 str =
236   let str = recode str in
237
238   (* Windows strings include the final \0 so remove this if present. *)
239   let len = UTF8.length str in
240   if len > 0 && UChar.code (UTF8.get str (len-1)) = 0 then
241     String.sub str 0 (UTF8.last str)
242   else
243     str
244
245 (* Best effort convert hive value to printable string. *)
246 let rec printable_hivex_value ?split_long_lines t v =
247   let hex = reg_hex_of_string ?split_long_lines in
248   match t with
249   | Hivex.REG_NONE -> if v = "" then "" else hex v
250   | Hivex.REG_SZ ->
251       (try windows_string_to_utf8 v with _ -> hex v)
252   | Hivex.REG_EXPAND_SZ ->
253       (try windows_string_to_utf8 v with _ -> hex v)
254   | Hivex.REG_BINARY -> hex v
255   | Hivex.REG_DWORD ->
256       (bitmatch Bitstring.bitstring_of_string v with
257        | { i : 32 : littleendian } -> sprintf "%08lx" i
258        | { _ } -> hex v)
259   | Hivex.REG_DWORD_BIG_ENDIAN ->
260       (bitmatch Bitstring.bitstring_of_string v with
261        | { i : 32 : bigendian } -> sprintf "%08lx" i
262        | { _ } -> hex v)
263   | Hivex.REG_LINK -> hex v
264   | Hivex.REG_MULTI_SZ -> (* XXX should be better for this one *)
265       hex v
266   | Hivex.REG_RESOURCE_LIST -> hex v
267   | Hivex.REG_FULL_RESOURCE_DESCRIPTOR -> hex v
268   | Hivex.REG_RESOURCE_REQUIREMENTS_LIST -> hex v
269   | Hivex.REG_QWORD ->
270       (bitmatch Bitstring.bitstring_of_string v with
271        | { i : 64 : littleendian } -> sprintf "%016Lx" i
272        | { _ } -> hex v)
273   | Hivex.REG_UNKNOWN i32 -> hex v
274
275 (* Convert binary data to a hex string.  This includes line breaks. *)
276 and reg_hex_of_string ?(split_long_lines=false) v =
277   let vs = String.explode v in
278   let vs = List.mapi (
279     fun i c ->
280       sprintf "%s%02x"
281         (if split_long_lines && i mod 16 = 0 then "\n" else "")
282         (int_of_char c)
283   ) vs in
284   String.concat "," vs
285
286 let local_file_exists filename =
287   try Unix.access filename [Unix.F_OK]; true
288   with Unix.Unix_error _ -> false
289
290 let basename pathname =
291   let len = String.length pathname in
292   try
293     let i = String.rindex pathname '/' in
294     let r = String.sub pathname (i+1) (len-i-1) in
295     if r = "" then "root" else r
296   with
297     Not_found -> pathname
298
299 let extension pathname =
300   let len = String.length pathname in
301   try
302     let i = String.rindex pathname '.' in
303     let r = String.sub pathname i (len-i) in
304     r
305   with
306     Not_found -> ""