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
open Eio
module Monad = struct
type 'a t = 'a
let (>>=) a f = f a
let return = Fun.id
end
module Reader = struct
type in_channel = Flow.source
type 'a t = 'a
let really_read f b = Flow.read_exact f b
let skip f (n: int) =
let buffer_size = 32768 in
let buffer = Cstruct.create buffer_size in
let rec loop (n: int) =
if n <= 0 then ()
else
let amount = min n buffer_size in
let block = Cstruct.sub buffer 0 amount in
really_read f block;
loop (n - amount) in
loop n
end
let really_read = Reader.really_read
module Writer = struct
type out_channel = Flow.sink
type 'a t = 'a
let really_write f b = Flow.write f [ b ]
end
let really_write = Writer.really_write
let copy_n ifd ofd n =
let block_size = 32768 in
let buffer = Cstruct.create block_size in
let rec loop remaining =
if remaining = 0L then () else begin
let this = Int64.(to_int (min (of_int block_size) remaining)) in
let block = Cstruct.sub buffer 0 this in
really_read ifd block;
really_write ofd block;
loop (Int64.(sub remaining (of_int this)))
end in
loop n
module HR = Tar.HeaderReader(Monad)(Reader)
module HW = Tar.HeaderWriter(Monad)(Writer)
let ?level ic =
match HR.read ?level (ic :> Flow.source) with
| Error `Eof -> None
| Ok hdrs -> Some hdrs
let stat path =
Eio.Path.with_open_in path @@ fun f ->
Eio.File.stat f
(** Return the header needed for a particular file on disk *)
let ?level ?getpwuid ?getgrgid filepath : Tar.Header.t =
let level = match level with None -> Tar.Header.V7 | Some level -> level in
let stat = stat filepath in
let pwent = Option.map (fun f -> f stat.uid) getpwuid in
let grent = Option.map (fun f -> f stat.gid) getgrgid in
let uname = if level = V7 then Some "" else pwent in
let gname = if level = V7 then Some "" else grent in
let file_mode = stat.perm in
let user_id = stat.uid |> Int64.to_int in
let group_id = stat.gid |> Int64.to_int in
let file_size = stat.size |> Optint.Int63.to_int64 in
let mod_time = Int64.of_float stat.mtime in
let link_indicator = Tar.Header.Link.Normal in
let link_name = "" in
let devmajor = if level = Ustar then stat.dev |> Int64.to_int else 0 in
let devminor = if level = Ustar then stat.rdev |> Int64.to_int else 0 in
Tar.Header.make ~file_mode ~user_id ~group_id ~mod_time ~link_indicator ~link_name
?uname ?gname ~devmajor ~devminor (snd filepath) file_size
let write_block ?level (: Tar.Header.t) (body: #Flow.sink -> unit) sink =
HW.write ?level header (sink :> Flow.sink);
body sink;
really_write sink (Tar.Header.zero_padding header)
let write_end sink =
really_write sink Tar.Header.zero_block;
really_write sink Tar.Header.zero_block
(** Utility functions for operating over whole tar archives *)
module Archive = struct
(** Read the next header, apply the function 'f' to the fd and the header. The function
should leave the fd positioned immediately after the datablock. Finally the function
skips past the zero padding to the next header *)
let with_next_file src (f: Eio.Flow.source -> Tar.Header.t -> 'a) =
match get_next_header src with
| Some hdr ->
let result = f src hdr in
Reader.skip src (Tar.Header.compute_zero_padding_length hdr);
Some result
| None ->
None
(** List the contents of a tar *)
let list ?level fd =
let rec loop acc =
match get_next_header ?level (fd :> Flow.source) with
| None -> List.rev acc
| Some hdr ->
Reader.skip fd (Int64.to_int hdr.Tar.Header.file_size);
Reader.skip fd (Tar.Header.compute_zero_padding_length hdr);
loop (hdr :: acc) in
loop []
(** Extract the contents of a tar to directory 'dest' *)
let dest ifd =
let rec loop () =
match get_next_header ifd with
| None -> ()
| Some hdr ->
let filename = dest hdr.Tar.Header.file_name in
Eio.Path.(with_open_out ~create:(`Exclusive 0) filename) @@ fun ofd ->
copy_n ifd ofd hdr.Tar.Header.file_size;
Reader.skip ifd (Tar.Header.compute_zero_padding_length hdr);
loop ()
in
loop ()
let transform ?level f (ifd : #Flow.source) (ofd : #Flow.sink) =
let rec loop () =
match get_next_header ifd with
| None -> ()
| Some ->
let = f header' in
let body = fun _ -> copy_n ifd ofd header.Tar.Header.file_size in
write_block ?level header body ofd;
Reader.skip ifd (Tar.Header.compute_zero_padding_length header');
loop ()
in
loop ();
write_end ofd
(** Create a tar on file descriptor fd from the filename list
'files' *)
let create ?getpwuid ?getgrgid files ofd =
let file filename =
let stat = stat filename in
if stat.kind <> `Regular_file then
()
else begin
let hdr = header_of_file ?getpwuid ?getgrgid filename in
write_block hdr (fun ofd ->
Eio.Path.with_open_in filename @@ fun ifd ->
copy_n ifd ofd hdr.Tar.Header.file_size
) ofd
end in
List.iter file files;
write_end ofd
end