Skip to content

Commit 6f4a26b

Browse files
committed
Add a way to produce a *.tar.gz archive from the new pure API
1 parent 890c1fe commit 6f4a26b

File tree

10 files changed

+234
-256
lines changed

10 files changed

+234
-256
lines changed

bin/otar.ml

Lines changed: 52 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -18,60 +18,64 @@ let () = Printexc.record_backtrace true
1818

1919
let ( / ) = Filename.concat
2020

21-
let stream_of_fd fd =
22-
let buf = Bytes.create 0x1000 in
23-
fun () -> match Unix.read fd buf 0 (Bytes.length buf) with
24-
| 0 -> None
25-
| len ->
26-
let str = Bytes.sub_string buf 0 len in
27-
Some str
28-
| exception End_of_file -> None
21+
let contents_of_path path =
22+
let fd = ref `None in
23+
let buf = Bytes.create 0x100 in
24+
let rec dispenser () = match !fd with
25+
| `Closed -> Tar.return (Ok None)
26+
| `None ->
27+
let fd' = Unix.openfile path Unix.[ O_RDONLY; O_CLOEXEC ] 0o644 in
28+
fd := `Active fd';
29+
dispenser ()
30+
| `Active fd' ->
31+
match Unix.read fd' buf 0 (Bytes.length buf) with
32+
| 0 | exception End_of_file ->
33+
Unix.close fd'; fd := `Closed; Tar.return (Ok None)
34+
| len ->
35+
let str = Bytes.sub_string buf 0 len in
36+
Tar.return (Ok (Some str)) in
37+
dispenser
2938

30-
let always x = fun _ -> x
31-
32-
(*
33-
let create_tarball directory oc =
39+
let create_tarball directory fd =
3440
let files = Sys.readdir directory in
3541
let os = match Sys.os_type with
3642
| "Win32" -> Gz.NTFS (* XXX(dinosaure): true? *)
3743
| "Unix" | "Cygwin" | _ -> Gz.Unix in
3844
let mtime = Unix.gettimeofday () in
39-
let out_channel = Tar_gz.of_out_channel ~level:4 ~mtime:(Int32.of_float mtime) os oc in
4045
let hdr = Tar.Header.make ~file_mode:0o755
41-
~mod_time:(Int64.of_float mtime) (Filename.concat directory "") 0L in
42-
(match Tar_gz.write_block ~level:Tar.Header.Ustar hdr out_channel (always None) with
43-
| Ok () -> ()
44-
| Error `Msg msg -> Format.eprintf "Error %s writing block\n%!" msg);
45-
Array.iter begin fun filename ->
46-
let fd = Unix.openfile (directory / filename) Unix.[ O_RDONLY; O_CLOEXEC ] 0o644 in
47-
let stat = Unix.LargeFile.lstat (directory / filename) in
48-
match stat.st_kind with
49-
| Unix.S_REG ->
50-
let stream = stream_of_fd fd in
51-
let file_mode = if stat.Unix.LargeFile.st_perm land 0o111 <> 0 then 0o755 else 0o644 in
52-
let mod_time = Int64.of_float stat.Unix.LargeFile.st_mtime in
53-
let user_id = stat.Unix.LargeFile.st_uid in
54-
let group_id = stat.Unix.LargeFile.st_gid in
55-
let hdr = Tar.Header.make
46+
~mod_time:(Int64.of_float mtime) (Filename.concat directory "") 0L in
47+
let entries = Array.fold_left begin fun acc filename ->
48+
let stat = Unix.LargeFile.stat (directory / filename) in
49+
match stat.st_kind with
50+
| Unix.S_REG ->
51+
let file_mode = if stat.st_perm land 0o111 <> 0 then 0o755 else 0o644 in
52+
let mod_time = Int64.of_float stat.Unix.LargeFile.st_mtime in
53+
let user_id = stat.st_uid in
54+
let group_id = stat.st_gid in
55+
let level = Some Tar.Header.Ustar in
56+
let hdr = Tar.Header.make
5657
~file_mode ~mod_time ~user_id ~group_id
57-
(directory / filename) stat.Unix.LargeFile.st_size in
58-
(match Tar_gz.write_block ~level:Tar.Header.Ustar hdr out_channel stream with
59-
| Ok () -> ()
60-
| Error `Msg msg -> Format.eprintf "Error %s writing block\n%!" msg);
61-
Unix.close fd ;
62-
| _ ->
63-
Format.eprintf "Skipping non-regular file %s\n" (Filename.concat directory filename)
64-
end files ;
65-
Tar_gz.write_end out_channel
58+
(directory / filename) stat.st_size in
59+
(level, hdr, contents_of_path (directory / filename)) :: acc
60+
| _ -> acc end [] files in
61+
let entries = List.to_seq entries in
62+
let entries = Seq.to_dispenser entries in
63+
let entries () = Tar.return (Ok (entries ())) in
64+
let t = Tar.out ~level:Tar.Header.Ustar hdr entries in
65+
let t = Tar_gz.out_gzipped ~level:4 ~mtime:(Int32.of_float mtime) os t in
66+
match Tar_unix.run t fd with
67+
| Ok () -> ()
68+
| Error err ->
69+
Format.eprintf "%s: %a\n%!" Sys.executable_name Tar_unix.pp_error err
6670

6771
let make directory oc =
68-
let oc, oc_close, _gz = match oc with
69-
| None -> stdout, ignore, false
72+
let fd, fd_close = match oc with
73+
| None -> Unix.stdout, ignore
7074
| Some filename ->
71-
let oc = open_out filename in
72-
oc, (fun () -> close_out oc), Filename.extension filename = ".gz" in
73-
create_tarball directory oc ; oc_close ()
74-
*)
75+
let fd = Unix.openfile filename Unix.[ O_TRUNC; O_CREAT; O_WRONLY; O_CLOEXEC ] 0o644 in
76+
fd, (fun () -> Unix.close fd) in
77+
Fun.protect ~finally:fd_close @@ fun () ->
78+
create_tarball directory fd
7579

7680
let sizes = [| "B"; "KiB"; "MiB"; "GiB"; "TiB"; "PiB"; "EiB"; "ZiB"; "YiB" |]
7781

@@ -89,46 +93,31 @@ let list filename =
8993
hdr.Tar.Header.file_name
9094
(Tar.Header.Link.to_string hdr.link_indicator)
9195
(bytes_to_size ~decimals:2) hdr.Tar.Header.file_size ;
92-
(*
93-
(* Alternatively:
94-
let padding = Tar.Header.compute_zero_padding_length hdr in
95-
let data = Int64.to_int hdr.Tar.Header.file_size in
96-
let to_skip = data + padding in *)
97-
Tar_gz.skip ic to_skip ;
98-
go global ()
99-
| Error `Eof -> ()
100-
| Error `Fatal e ->
101-
Format.eprintf "Error listing archive: %a\n%!" Tar.pp_error e;
102-
exit 2
103-
*)
10496
let open Tar in
105-
let to_skip = Header.(Int64.to_int (to_sectors hdr) * length) in
106-
let* _ = seek to_skip in
97+
let* _ = seek (Int64.to_int hdr.Tar.Header.file_size) in
10798
return (Ok ())
10899
in
109100
let fd = Unix.openfile filename [ Unix.O_RDONLY ] 0 in
110-
match Tar_unix.run (Tar_gz.gzipped (Tar.fold go ())) fd with
101+
match Tar_unix.run (Tar_gz.in_gzipped (Tar.fold go ())) fd with
111102
| Ok () -> ()
112103
| Error (`Unix _) ->
113104
Format.eprintf "Some UNIX error occurred.\n%!"
114105
| Error (`Msg e) ->
115106
Format.eprintf "Some error: %s.\n%!" e
116-
| Error `Unexpected_end_of_file ->
107+
| Error (`Unexpected_end_of_file | `Eof) ->
117108
Format.eprintf "Unexpected end of file.\n%!"
118-
| Error `Eof | Error `Gz _ ->
119-
Format.eprintf "Some fatal error occurred.\n%!"
109+
| Error `Gz err ->
110+
Format.eprintf "Some Gzip error occurred: %s.\n%!" err
120111
| Error (`Fatal _) ->
121112
Format.eprintf "Some fatal error occurred.\n%!"
122113

123114
let () = match Sys.argv with
124115
| [| _; "list"; filename; |] when Sys.file_exists filename ->
125116
list filename
126-
(*
127117
| [| _; directory |] when Sys.is_directory directory ->
128118
make directory None
129119
| [| _; directory; output |] when Sys.is_directory directory ->
130120
make directory (Some output)
131-
*)
132121
| _ ->
133122
let cmd = Filename.basename Sys.argv.(0) in
134123
Format.eprintf "%s <directory> [<filename.tar.gz>]\n%s list <filename.tar.gz>\n" cmd cmd

lib/tar.ml

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -795,7 +795,7 @@ let encode_unextended_header ?level header =
795795
let encode_extended_header ?level scope hdr =
796796
let link_indicator, link_indicator_name = match scope with
797797
| `Per_file -> Header.Link.PerFileExtendedHeader, "paxheader"
798-
| `Global ->Header.Link.GlobalExtendedHeader, "pax_global_header"
798+
| `Global -> Header.Link.GlobalExtendedHeader, "pax_global_header"
799799
| _ -> assert false
800800
in
801801
let pax_payload = Header.Extended.marshal hdr in
@@ -825,12 +825,14 @@ type ('a, 'err, 't) t =
825825
| Bind : ('a, 'err, 't) t * ('a -> ('b, 'err, 't) t) -> ('b, 'err, 't) t
826826
| Return : ('a, 'err) result -> ('a, 'err, 't) t
827827
| High : (('a, 'err) result, 't) io -> ('a, 'err, 't) t
828+
| Write : string -> (unit, 'err, 't) t
828829

829830
let ( let* ) x f = Bind (x, f)
830831
let return x = Return x
831832
let really_read n = Really_read n
832833
let read n = Read n
833834
let seek n = Seek n
835+
let write str = Write str
834836

835837
type ('a, 'err, 't) fold = (?global:Header.Extended.t -> Header.t -> 'a -> ('a, 'err, 't) t) -> 'a -> ('a, 'err, 't) t
836838

@@ -859,3 +861,41 @@ let fold f init =
859861
| Error `Eof -> return (Ok acc)
860862
| Error `Fatal _ as e -> return e in
861863
go (decode_state ()) init
864+
865+
let rec writev = function
866+
| [] -> return (Ok ())
867+
| x :: r ->
868+
let* () = write x in
869+
writev r
870+
871+
let rec pipe stream =
872+
let* block = stream () in
873+
match block with
874+
| Some str -> let* () = writev [ str ] in pipe stream
875+
| None -> return (Ok ())
876+
877+
type ('err, 't) content = unit -> (string option, 'err, 't) t
878+
type ('err, 't) entry = Header.compatibility option * Header.t * ('err, 't) content
879+
type ('err, 't) entries = unit -> (('err, 't) entry option, 'err, 't) t
880+
881+
let out ?level hdr entries =
882+
let rec go () =
883+
let* entry = entries () in
884+
match entry with
885+
| None ->
886+
let* () = writev [ Header.zero_block; Header.zero_block ] in
887+
return (Ok ())
888+
| Some (level, hdr, stream) ->
889+
match encode_header ?level hdr with
890+
| Ok sstr ->
891+
let* () = writev sstr in
892+
let* () = pipe stream in
893+
let* () = writev [ Header.zero_padding hdr ] in
894+
go ()
895+
| Error _ as err -> return err in
896+
match encode_header ?level hdr with
897+
| Error _ as err -> return err
898+
| Ok sstr ->
899+
let* () = writev sstr in
900+
let* () = writev [ Header.zero_padding hdr ] in
901+
go ()

lib/tar.mli

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,16 +196,31 @@ type ('a, 'err, 't) t =
196196
| Bind : ('a, 'err, 't) t * ('a -> ('b, 'err, 't) t) -> ('b, 'err, 't) t
197197
| Return : ('a, 'err) result -> ('a, 'err, 't) t
198198
| High : (('a, 'err) result, 't) io -> ('a, 'err, 't) t
199+
| Write : string -> (unit, 'err, 't) t
199200

200201
val really_read : int -> (string, _, _) t
201202
val read : int -> (string, _, _) t
202203
val seek : int -> (unit, _, _) t
203204
val ( let* ) : ('a, 'err, 't) t -> ('a -> ('b, 'err, 't) t) -> ('b, 'err, 't) t
204205
val return : ('a, 'err) result -> ('a, 'err, _) t
206+
val write : string -> (unit, _, _) t
205207

206208
type ('a, 'err, 't) fold = (?global:Header.Extended.t -> Header.t -> 'a -> ('a, 'err, 't) t) -> 'a -> ('a, 'err, 't) t
207209

208210
val fold : ('a, [> `Fatal of error ], 't) fold
209211
(** [fold f] is a [_ t] that reads an archive and executes [f] on each header.
210212
[f] is expected to either read or skip the file contents, or return an
211213
error. *)
214+
215+
type ('err, 't) content = unit -> (string option, 'err, 't) t
216+
type ('err, 't) entry = Header.compatibility option * Header.t * ('err, 't) content
217+
type ('err, 't) entries = unit -> (('err, 't) entry option, 'err, 't) t
218+
219+
val out :
220+
?level:Header.compatibility
221+
-> Header.t
222+
-> ([> `Msg of string ] as 'err, 't) entries
223+
-> (unit, 'err, 't) t
224+
(** [out hdr entries] is a [_ t] that writes [entries] into an archive. [hdr] is
225+
the global header and each entry must come from a {!type:content} stream and
226+
the associated header.*)

0 commit comments

Comments
 (0)