Just move the compress and decompress functions around.
[virt-df.git] / diskzip / diskzip.ml
1 (* 'diskzip' command for intelligently compressing disk images.
2    (C) Copyright 2007 Richard W.M. Jones, Red Hat Inc.
3    http://libvirt.org/
4
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 2 of the License, or
8    (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program; if not, write to the Free Software
17    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18  *)
19
20 open Unix
21 open Printf
22
23 open Diskzip_gettext.Gettext
24
25 type output = File of string | Dir of string
26 type extcompress = BZip2 | GZip | External of string
27
28 let rec main () =
29   (* Program name changes behaviour. *)
30   let compressing =
31     let name = Sys.argv.(0) in
32     let name = Filename.basename name in       (* just the executable name *)
33     let name = Filename.chop_extension name in (* remove .opt or .exe *)
34     let name = String.lowercase name in
35     match name with
36     | "diskzcat" -> false
37     | "diskzip" -> true
38     | name ->
39         eprintf
40           (f_"diskzip: unknown executable name '%s', assuming 'diskzip'\n")
41           name in
42   let compressing = ref compressing in
43
44   (* Command line argument parsing. *)
45   let version () =
46     printf "diskzip\n"; (* XXX version XXX *)
47     exit 0
48   in
49
50   let output = ref None in
51   let set_output path =
52     if !output <> None then (
53       prerr_endline (s_"diskzip: '-o' option cannot appear more than once");
54       exit 2
55     );
56     try
57       let statbuf = stat path in
58       if statbuf.st_kind = S_DIR then
59         output := Some (Dir path)
60       else
61         output := Some (File path)
62     with
63     (* No such file or directory, assume it's a file output. *)
64     | Unix_error (ENOENT, _, _) -> output := Some (File path)
65   in
66
67   (* By default we don't use any external compression program. *)
68   let extcompress = ref None in
69   let set_extcompress t () =
70     if !extcompress <> None then (
71       prerr_endline (s_"diskzip: '-z' or '-j' cannot appear more than once");
72       exit 2
73     );
74     extcompress := Some t
75   in
76
77   let force = ref false in
78
79   let argspec = Arg.align [
80     "-d", Arg.Clear compressing,
81       " " ^ s_ "Uncompress (default: depends on executable name)";
82     "--debug", Arg.Set Diskimage.debug,
83       " " ^ s_ "Debug mode (default: false)";
84     "-f", Arg.Set force,
85       " " ^ s_"Force compress even if stdout looks like a tty";
86     "-j", Arg.Unit (set_extcompress BZip2),
87       " " ^ s_"Pipe the output/input through bzip2";
88     "-o", Arg.String set_output,
89       "path " ^ s_"Set the output filename or directory name";
90     "-p", Arg.String (fun prog -> set_extcompress (External prog) ()),
91       "prog " ^ s_"Pipe the output/input through external program";
92     "--version", Arg.Unit version,
93       " " ^ s_"Display version and exit";
94     "-z", Arg.Unit (set_extcompress GZip),
95       " " ^ s_"Pipe the output/input through gzip";
96   ] in
97
98   let args = ref [] in
99   let anon_fun str = args := str :: !args in
100   let usage_msg = s_"diskzip: Intelligently compress disk images
101
102 SUMMARY
103   diskzip [-options] disk.img [disk.img ...] > output.dz
104   diskzcat [-options] output.dz > disk.img
105
106 OPTIONS" in
107
108   Arg.parse argspec anon_fun usage_msg;
109
110   (* Turn refs back into normal values. *)
111   let compressing = !compressing in
112   let extcompress = !extcompress in
113   let output = !output in
114   let force = !force in
115   let args = !args in
116
117   (* Check the arguments make sense. *)
118   if compressing && output <> None then (
119     prerr_endline (s_"diskzip: '-o' option cannot be used when compressing");
120     exit 2
121   );
122   if compressing && args = [] then (
123     prerr_endline (s_"diskzip: no input");
124     exit 2
125   );
126   if compressing && not force && isatty stdout then (
127     prerr_endline (s_"diskzip: compressed data not written to a terminal, use '-f' to force");
128     exit 2
129   );
130
131   (* Run the compression or decompression functions. *)
132   if compressing then
133     go_compress extcompress args
134   else
135     go_decompress ?output extcompress args
136
137 (* Do compression. *)
138 and go_compress extcompress images =
139   (* Create a Diskimage machine description from the requested images.  This
140    * also checks that everything we need is readable.
141    *)
142   let machine =
143     Diskimage.open_machine "diskzip" (List.map (fun n -> (n,n)) images) in
144
145   (* Scan the images for filesystems. *)
146   let machine = Diskimage.scan_machine machine in
147
148   (* Redirect output through external pipe if asked. *)
149   (match extcompress with
150    | None -> ()
151    | Some prog ->
152        let prog, progargs =
153          match prog with
154          | BZip2 -> "bzip2", [|"bzip2"; "-c"|]
155          | GZip -> "gzip", [|"gzip"; "-c"|]
156          | External prog -> "sh", [|"sh"; "-c"; prog |] in
157        let rfd, wfd = pipe () in
158        let pid = fork () in
159        if pid = 0 then (                (* child *)
160          close wfd;
161          dup2 rfd stdin;
162          close rfd;
163          execvp prog progargs
164        ) else (                         (* parent *)
165          close rfd;
166          dup2 wfd stdout;
167          close wfd
168        )
169   )
170
171
172
173
174
175
176
177
178
179
180 and go_decompress ?output extcompress args =
181   (* Read the input, which may be a single named file, or a series of
182    * files (we just concatenate them).  We may have to feed the input
183    * through an external program.
184    *)
185   let () =
186     match args with
187     | [] -> ()                          (* Reading from stdin. *)
188     | [file] ->                         (* Read the named file. *)
189         let fd = openfile file [O_RDONLY] 0 in
190         dup2 fd stdin;
191         close fd
192     | files ->                          (* Concatenate files. *)
193         let rfd, wfd = pipe () in
194         let pid = fork () in
195         if pid = 0 then (               (* child *)
196           close rfd;
197           dup2 wfd stdout;
198           close wfd;
199           execvp "cat" (Array.of_list ("cat" :: "--" :: files))
200         ) else (                        (* parent *)
201           close wfd;
202           dup2 rfd stdin;
203           close rfd
204         )
205   in
206   (match extcompress with
207    | None -> ()
208    | Some prog ->
209        let prog, progargs =
210          match prog with
211          | BZip2 -> "bzip2", [|"bzip2"; "-cd"|]
212          | GZip -> "gzip", [|"gzip"; "-cd"|]
213          | External prog -> "sh", [|"sh"; "-c"; prog |] in
214        let rfd, wfd = pipe () in
215        let pid = fork () in
216        if pid = 0 then (                (* child *)
217          close rfd;
218          dup2 wfd stdout;
219          close wfd;
220          execvp prog progargs
221        ) else (                         (* parent *)
222          close wfd;
223          dup2 rfd stdin;
224          close rfd
225        )
226   )
227
228 (*
229   let header = read_header () in
230   XXX
231
232 *)
233
234
235
236
237
238
239
240
241 (*
242 (* Since we have the wonderful pa_bitmatch, might as well use it to
243  * define a robust binary format for the compressed files.
244  *)
245 and write_header ... =
246   let bs = BITSTRING {
247     0xD152 : 16; 0x01 : 8; 0x00 : 8;    (* file magic, version 1.0 *)
248     nr_disks : 8;                       (* number of disks being packed *)
249     
250
251
252
253   } in
254   
255 and read_header () =
256   (* Diskzip headers are limited to overall max size of 1024 bytes. *)
257   let bs = Bitmatch.bitstring_of_file_descr_max stdin 1024 in
258
259   bitmatch bs with
260   | { 0xD152 : 16;                      (* file magic *)
261       0x01 : 8; (_ as minor) : 8;       (* major, minor versions *)
262     } ->
263
264   (* Is this a later version (major != 1)? *)
265   | { 0xD152 : 16;                      (* file magic *)
266       (_ as major) : 8; (_ as minor) : 8 } when major <> 1 ->
267       eprintf (f_"diskzip: archive version %d.%d, this program only understands version 1.x")
268         major minor;
269       exit 1
270
271   (* If it looks like gzip or bzip2, exit with an informative error. *)
272   | { 0o37 : 8; 0o213 : 8 } ->          (* gzip *)
273       prerr_endline (s_"diskzip: This looks like a gzip archive. Did you mean to pass the '-z' option?");
274       exit 1
275   | { "BZh" : 24 : string } ->          (* bzip2 *)
276       prerr_endline (s_"diskzip: This looks like a bzip2 archive. Did you mean to pass the '-j' option?");
277       exit 1
278
279   (* If it looks like a disk image (MBR), give an error. *)
280   | { _ : 4080 : bitstring; 0x55 : 8; 0xAA : 8 } ->
281       prerr_endline (s_"diskzip: This looks like a disk image. Did you mean to compress it?");
282       exit 1
283
284   | { _ } ->
285       prerr_endline (s_"diskzip: Not a diskzip archive.");
286       exit 1
287 *)
288
289 let () = main ()