2 * Copyright (C) 2010 Red Hat Inc.
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.
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.
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.
27 (* Temporary directory for shared use by any function in this file.
28 * It is cleaned up when the program exits.
30 let tmpdir = tmpdir ()
32 (* Get the basename of a file, using path conventions which are valid
33 * for libguestfs. So [Filename.basename] won't necessarily work
34 * because it will use host path conventions.
36 let basename pathname =
37 let len = String.length pathname in
39 let i = String.rindex pathname '/' in
40 let r = String.sub pathname (i+1) (len-i-1) in
41 if r = "" then "root" else r
45 (* Get the extension of a file using libguestfs path conventions,
46 * including the leading point (eg. ".txt"). Might return an empty
47 * string if there is no extension.
49 let extension pathname =
50 let len = String.length pathname in
52 let i = String.rindex pathname '.' in
53 let r = String.sub pathname i (len-i) in
58 (* Download a single file. *)
59 let rec download_file ({ model = model } as t) path () =
60 let row = model#get_iter path in
61 let src, pathname = get_pathname t row in
62 debug "download_file %s: showing dialog" pathname;
64 (* Put up the dialog. *)
65 let title = "Download file" in
66 let dlg = GWindow.file_chooser_dialog ~action:`SAVE ~title ~modal:true () in
67 dlg#add_button_stock `CANCEL `CANCEL;
68 dlg#add_select_button_stock `SAVE `SAVE;
69 dlg#set_current_name (basename pathname);
72 | `DELETE_EVENT | `CANCEL ->
75 match dlg#filename with
80 (* Download the file. *)
81 Slave.download_file src pathname localfile
82 (when_downloaded_file t path)
84 and when_downloaded_file ({ model = model } as t) path () =
85 let row = model#get_iter path in
88 (* Download a directory as a tarball. *)
89 let rec download_dir_tarball ({ model = model } as t) format path () =
90 let row = model#get_iter path in
91 let src, pathname = get_pathname t row in
92 debug "download_dir_tarball %s: showing dialog" pathname;
94 (* Put up the dialog. *)
95 let title = "Download directory to tar file" in
96 let dlg = GWindow.file_chooser_dialog ~action:`SAVE ~title ~modal:true () in
97 dlg#add_button_stock `CANCEL `CANCEL;
98 dlg#add_select_button_stock `SAVE `SAVE;
100 let extension = match format with
105 dlg#set_current_name (basename pathname ^ extension);
107 match dlg#run () with
108 | `DELETE_EVENT | `CANCEL ->
111 match dlg#filename with
116 (* Download the directory. *)
117 Slave.download_dir_tarball src pathname format localfile
118 (when_downloaded_dir_tarball t path)
120 and when_downloaded_dir_tarball ({ model = model } as t) path () =
121 let row = model#get_iter path in
124 let rec download_dir_find0 ({ model = model } as t) path () =
125 let row = model#get_iter path in
126 let src, pathname = get_pathname t row in
127 debug "download_dir_find0 %s: showing dialog" pathname;
129 (* Put up the dialog. *)
130 let title = "Download list of filenames" in
131 let dlg = GWindow.file_chooser_dialog ~action:`SAVE ~title ~modal:true () in
132 dlg#add_button_stock `CANCEL `CANCEL;
133 dlg#add_select_button_stock `SAVE `SAVE;
134 dlg#set_current_name (basename pathname ^ ".filenames.txt");
136 (* Notify that the list of strings is \0 separated. *)
138 let hbox = GPack.hbox () in
139 ignore (GMisc.image ~stock:`INFO ~packing:hbox#pack ());
140 let label = GMisc.label
141 ~text:"The list of filenames is saved to a file with zero byte separators, to allow the full range of characters to be used in the names themselves."
142 ~packing:hbox#pack () in
143 label#set_line_wrap true;
145 dlg#set_extra_widget (hbox :> GObj.widget);
147 match dlg#run () with
148 | `DELETE_EVENT | `CANCEL ->
151 match dlg#filename with
156 (* Download the directory. *)
157 Slave.download_dir_find0 src pathname localfile
158 (when_downloaded_dir_find0 t path)
160 and when_downloaded_dir_find0 ({ model = model } as t) path () =
161 let row = model#get_iter path in
164 let has_child_node_equals t row content =
165 try ignore (find_child_node_by_content t row content); true
166 with Not_found -> false
168 (* Calculate disk space used by a directory. *)
169 let rec disk_usage ({ model = model } as t) path () =
170 t.view#expand_row path;
172 let row = model#get_iter path in
173 let src, pathname = get_pathname t row in
174 debug "disk_usage %s" pathname;
176 (* See if this node already has an Info "disk_usage" child node. If
177 * so they don't recreate it.
179 let content = Info "disk_usage" in
180 if not (has_child_node_equals t row content) then (
181 (* Create the child node first. *)
182 let row = model#insert ~parent:row 0 in
183 let hdata = { state=IsLeaf; content=content; visited=false; hiveh=None } in
184 store_hdata t row hdata;
185 model#set ~row ~column:t.name_col "<i>Calculating disk usage ...</i>";
187 Slave.disk_usage src pathname (when_disk_usage t path pathname)
190 and when_disk_usage ({ model = model } as t) path pathname kbytes =
191 let row = model#get_iter path in
193 (* Find the Info "disk_usage" child node added above, and replace the
194 * text in it with the final size.
197 let content = Info "disk_usage" in
198 let row = find_child_node_by_content t row content in
200 sprintf "<b>%s</b>\n<small>Disk usage of %s (%Ld KB)</small>"
201 (human_size_1k kbytes) pathname kbytes in
202 model#set ~row ~column:t.name_col msg
206 (* Display operating system inspection information. *)
207 let display_inspection_data ({ model = model } as t) path () =
208 t.view#expand_row path;
210 let row = model#get_iter path in
211 let src, _ = get_pathname t row in
212 debug "display_inspection_data";
214 (* Should be an OS source, if not ignore. *)
218 (* See if this node already has an Info "inspection_data" child
219 * node. If so they don't recreate it.
221 let content = Info "inspection_data" in
222 if not (has_child_node_equals t row content) then (
223 let row = model#insert ~parent:row 0 in
225 { state=IsLeaf; content=content; visited=false; hiveh=None } in
226 store_hdata t row hdata;
230 sprintf "Type: <b>%s</b>\nDistro: <b>%s</b>\nVersion: <b>%d.%d</b>\nArch.: <b>%s</b>\nPackaging: <b>%s</b>/<b>%s</b>\n%sMountpoints:\n%s"
231 os.insp_type os.insp_distro
232 os.insp_major_version os.insp_minor_version
234 os.insp_package_management os.insp_package_format
235 (match os.insp_windows_systemroot with
238 sprintf "Systemroot: <b>%s</b>\n" (markup_escape path))
242 sprintf "<b>%s</b> on <b>%s</b>"
243 (markup_escape dev) (markup_escape mp))
247 model#set ~row ~column:t.name_col data
250 (* Copy registry key value to clipboard. *)
251 let copy_regvalue ({ model = model } as t) path () =
252 let row = model#get_iter path in
253 let hdata = get_hdata t row in
255 | { content=RegValue value; hiveh = Some h } ->
256 let t, v = Hivex.value_value h value in
257 let v = printable_hivex_value t v in
258 let cb = GData.clipboard Gdk.Atom.clipboard in
261 | _ -> () (* not a registry value row, ignore *)
263 (* View a single file. *)
264 let rec view_file ({ model = model } as t) path opener () =
265 let row = model#get_iter path in
266 let src, pathname = get_pathname t row in
267 debug "view_file %s" pathname;
269 (* Download the file into a temporary directory. *)
270 let ext = extension pathname in
271 let localfile = tmpdir // string_of_int (unique ()) ^ ext in
272 Slave.download_file src pathname localfile
273 (when_downloaded_file_for_view t path opener localfile)
275 and when_downloaded_file_for_view ({ model = model } as t) path
276 opener localfile () =
277 let row = model#get_iter path in
281 sprintf "%s %s" (Filename.quote opener) (Filename.quote localfile) in
282 Slave.run_command cmd Slave.no_callback
284 (* Compute the checksum of a file. *)
285 let rec checksum_file ({ model = model } as t) path csumtype () =
286 let row = model#get_iter path in
287 let src, pathname = get_pathname t row in
288 debug "checksum_file %s" pathname;
290 (* See if this node already has an Info "checksum" child
291 * node. If so they don't recreate it.
293 let content = Info ("checksum:" ^ csumtype) in
294 if not (has_child_node_equals t row content) then (
295 let row = model#insert ~parent:row 0 in
297 { state=IsLeaf; content=content; visited=false; hiveh=None } in
298 store_hdata t row hdata;
299 model#set ~row ~column:t.name_col
300 (sprintf "<i>Calculating %s ...</i>" csumtype);
302 t.view#expand_row path;
304 Slave.checksum_file src pathname csumtype
305 (when_checksum_file t path pathname csumtype)
308 and when_checksum_file ({ model = model } as t) path pathname csumtype checksum=
309 let row = model#get_iter path in
312 (* Find the child node added above, and replace the text. *)
314 let content = Info ("checksum:" ^ csumtype) in
315 let row = find_child_node_by_content t row content in
316 let msg = sprintf "%s: %s" csumtype checksum in
317 model#set ~row ~column:t.name_col msg
321 (* Compute the file information of a file. *)
322 let rec file_information ({ model = model } as t) path () =
323 let row = model#get_iter path in
324 let src, pathname = get_pathname t row in
325 debug "file_information %s" pathname;
327 (* See if this node already has an Info "file_information" child
328 * node. If so they don't recreate it.
330 let content = Info "file_information" in
331 if not (has_child_node_equals t row content) then (
332 let row = model#insert ~parent:row 0 in
334 { state=IsLeaf; content=content; visited=false; hiveh=None } in
335 store_hdata t row hdata;
336 model#set ~row ~column:t.name_col "<i>Calculating file information ...</i>";
338 t.view#expand_row path;
340 Slave.file_information src pathname (when_file_information t path pathname)
343 and when_file_information ({ model = model } as t) path pathname info =
344 let row = model#get_iter path in
347 (* Find the child node added above, and replace the text. *)
349 let content = Info "file_information" in
350 let row = find_child_node_by_content t row content in
351 model#set ~row ~column:t.name_col (markup_escape info)
355 (* Export a registry key/subkey tree as a reg file. This is pretty
356 * effortless with hivexregedit.
358 let download_as_reg ({ model = model } as t) path hivexregedit () =
359 let row = model#get_iter path in
360 let hdata = get_hdata t row in
362 (* Get path to the top of the registry tree. *)
363 let (_, rootkey, _, cachefile), nodes = get_registry_path t row in
364 let regpath = String.concat "\\" (List.rev nodes) in
365 debug "download_as_reg: %s %s %s" cachefile rootkey regpath;
368 (* Put up the dialog. *)
369 let title = "Download as .reg file" in
370 let dlg = GWindow.file_chooser_dialog
371 ~action:`SAVE ~title ~modal:true () in
372 dlg#add_button_stock `CANCEL `CANCEL;
373 dlg#add_select_button_stock `SAVE `SAVE;
374 let name = match nodes with [] -> rootkey | (name::_) -> name in
375 dlg#set_current_name (name ^ ".reg");
377 match dlg#run () with
378 | `DELETE_EVENT | `CANCEL ->
381 match dlg#filename with
386 (* Use hivexregedit to save it. *)
388 sprintf "%s --export --prefix %s %s %s > %s"
389 (Filename.quote hivexregedit)
390 (Filename.quote rootkey) (Filename.quote cachefile)
391 (Filename.quote regpath) (Filename.quote localfile) in
392 Slave.run_command cmd Slave.no_callback
396 | { content=RegKey _ } ->
399 | { content=TopWinReg (src, _, remotefile, cachefile) } ->
400 (* There's a subtle problem here: If the top node has not been
401 * opened, the registry cachefile won't have been downloaded. If
402 * the top node has been opened, the registry might still be
403 * being downloaded as we are running here. Either way we can't
404 * trust the cachefile. Tell the slave thread to download the
405 * file if it's not downloaded already (since the slave thread
406 * runs in a serial loop, this is always race free).
408 cache_registry_file t path src remotefile cachefile do_dialog
410 | _ -> () (* not a registry key, ignore *)