Source file tar_lwt_unix.ml
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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
open Tar.Syntax
type decode_error = [
| `Fatal of Tar.error
| `Unix of Unix.error * string * string
| `Unexpected_end_of_file
| `Msg of string
]
let pp_decode_error ppf = function
| `Fatal err -> Tar.pp_error ppf err
| `Unix (err, fname, arg) ->
Format.fprintf ppf "Unix error %s (function %s, arg %s)"
(Unix.error_message err) fname arg
| `Unexpected_end_of_file ->
Format.fprintf ppf "Unexpected end of file"
| `Msg msg ->
Format.fprintf ppf "Error %s" msg
let safe f a =
let open Lwt.Infix in
Lwt.catch
(fun () -> f a >|= fun r -> Ok r)
(function
| Unix.Unix_error (e, f, a) -> Lwt.return (Error (`Unix (e, f, a)))
| e -> Lwt.reraise e)
let read_complete fd buf len =
let open Lwt_result.Infix in
let rec loop offset =
if offset < len then
safe (Lwt_unix.read fd buf offset) (len - offset) >>= fun read ->
if read = 0 then
Lwt.return (Error `Unexpected_end_of_file)
else
loop (offset + read)
else
Lwt.return (Ok ())
in
loop 0
let seek fd n =
safe (Lwt_unix.lseek fd n) Unix.SEEK_CUR
|> Lwt_result.map ignore
let safe_close fd =
Lwt.catch (fun () -> Lwt_unix.close fd) (fun _ -> Lwt.return_unit)
module High : sig
type t
type 'a s = 'a Lwt.t
external inj : 'a s -> ('a, t) Tar.io = "%identity"
external prj : ('a, t) Tar.io -> 'a s = "%identity"
end = struct
type t
type 'a s = 'a Lwt.t
external inj : 'a -> 'b = "%identity"
external prj : 'a -> 'b = "%identity"
end
type t = High.t
let value v = Tar.High (High.inj v)
let run t fd =
let open Lwt_result.Infix in
let rec run : type a. (a, [> decode_error ] as 'err, t) Tar.t -> (a, 'err) result Lwt.t = function
| Tar.Write str ->
safe (Lwt_unix.write_string fd str 0) (String.length str) >>= fun _write ->
Lwt_result.return ()
| Tar.Read len ->
let b = Bytes.make len '\000' in
safe (Lwt_unix.read fd b 0) len >>= fun read ->
if read = 0 then
Lwt_result.fail `Unexpected_end_of_file
else if len = read then
Lwt_result.return (Bytes.unsafe_to_string b)
else
Lwt_result.return (Bytes.sub_string b 0 read)
| Tar.Really_read len ->
let buf = Bytes.make len '\000' in
read_complete fd buf len >|= fun () ->
Bytes.unsafe_to_string buf
| Tar.Seek len -> seek fd len
| Tar.Return value -> Lwt.return value
| Tar.High value -> High.prj value
| Tar.Bind (x, f) ->
run x >>= fun value -> run (f value) in
run t
let fold f filename init =
let open Lwt_result.Infix in
safe Lwt_unix.(openfile filename [ O_RDONLY ]) 0 >>= fun fd ->
Lwt.finalize
(fun () -> run (Tar.fold f init) fd)
(fun () -> safe_close fd)
let unix_err_to_msg = function
| `Unix (e, f, s) ->
`Msg (Format.sprintf "error %s in function %s %s"
(Unix.error_message e) f s)
let copy ~dst_fd len =
let blen = 65536 in
let rec read_write ~dst_fd len =
if len = 0 then value (Lwt.return (Ok ()))
else
let slen = min blen len in
let* str = Tar.really_read slen in
let* _written = Lwt_result.map_error unix_err_to_msg
(safe (Lwt_unix.write_string dst_fd str 0) slen) |> value in
read_write ~dst_fd (len - slen)
in
read_write ~dst_fd len
let ?(filter = fun _ -> true) ~src dst =
let safe_close fd =
let open Lwt.Infix in
Lwt.catch
(fun () -> Lwt_unix.close fd)
(fun _ -> Lwt.return_unit)
>|= Result.ok in
let f ?global:_ hdr () =
match filter hdr, hdr.Tar.Header.link_indicator with
| true, Tar.Header.Link.Normal ->
let* dst = Lwt_result.map_error
unix_err_to_msg
(safe Lwt_unix.(openfile (Filename.concat dst hdr.Tar.Header.file_name) [ O_WRONLY; O_CREAT ]) hdr.Tar.Header.file_mode)
|> value in
begin try
let* () = copy ~dst_fd:dst (Int64.to_int hdr.Tar.Header.file_size) in
let* () = value (safe_close dst) in
Tar.return (Ok ())
with exn ->
let* () = value (safe_close dst) in
Tar.return (Error (`Exn exn))
end
| _ ->
let* () = Tar.seek (Int64.to_int hdr.Tar.Header.file_size) in
Tar.return (Ok ())
in
fold f src ()
(** Return the header needed for a particular file on disk *)
let ?level file =
let open Lwt_result.Infix in
let level = Tar.Header.compatibility level in
safe Lwt_unix.LargeFile.stat file >>= fun stat ->
let file_mode = stat.Lwt_unix.LargeFile.st_perm in
let user_id = stat.Lwt_unix.LargeFile.st_uid in
let group_id = stat.Lwt_unix.LargeFile.st_gid in
let file_size = stat.Lwt_unix.LargeFile.st_size in
let mod_time = Int64.of_float stat.Lwt_unix.LargeFile.st_mtime in
let link_indicator = Tar.Header.Link.Normal in
let link_name = "" in
(if level = V7 then
Lwt.return (Ok "")
else
Lwt.catch
(fun () -> safe Lwt_unix.getpwuid stat.Lwt_unix.LargeFile.st_uid)
(function
| Not_found ->
Lwt.return (Error (`Msg ("No user entry found for UID")))
| e -> Lwt.reraise e) >|= fun pwent ->
pwent.Lwt_unix.pw_name) >>= fun uname ->
(if level = V7 then
Lwt.return (Ok "")
else
Lwt.catch
(fun () -> safe Lwt_unix.getgrgid stat.Lwt_unix.LargeFile.st_gid)
(function
| Not_found ->
Lwt.return (Error (`Msg ("No group entry found for GID")))
| e -> Lwt.reraise e) >|= fun grent ->
grent.Lwt_unix.gr_name) >>= fun gname ->
let devmajor = if level = Ustar then stat.Lwt_unix.LargeFile.st_dev else 0 in
let devminor = if level = Ustar then stat.Lwt_unix.LargeFile.st_rdev else 0 in
let hdr = Tar.Header.make ~file_mode ~user_id ~group_id ~mod_time ~link_indicator ~link_name
~uname ~gname ~devmajor ~devminor file file_size
in
Lwt.return (Ok hdr)
let write_strings fd datas =
let open Lwt_result.Infix in
Lwt_list.fold_left_s (fun acc d ->
Lwt_result.lift acc >>= fun _written ->
Lwt_result.map_error unix_err_to_msg
(safe (Lwt_unix.write_string fd d 0) (String.length d)))
(Ok 0) datas >|= fun _written ->
()
let ?level fd =
let open Lwt_result.Infix in
Lwt_result.lift (Tar.encode_header ?level header) >>= fun ->
write_strings fd header_strings
let copy ~src_fd ~dst_fd len =
let open Lwt_result.Infix in
let blen = 65536 in
let buffer = Bytes.make blen '\000' in
let rec read_write ~src_fd ~dst_fd len =
if len = 0 then
Lwt.return (Ok ())
else
let l = min blen len in
Lwt_result.map_error
(function
| `Unix _ as e -> unix_err_to_msg e
| `Unexpected_end_of_file ->
`Msg "Unexpected end of file")
(read_complete src_fd buffer l) >>= fun () ->
Lwt_result.map_error unix_err_to_msg
(safe (Lwt_unix.write dst_fd buffer 0) l) >>= fun _written ->
read_write ~src_fd ~dst_fd (len - l)
in
read_write ~src_fd ~dst_fd len
let append_file ?level ? filename fd =
let open Lwt_result.Infix in
(match header with
| None -> header_of_file ?level filename
| Some x -> Lwt.return (Ok x)) >>= fun ->
write_header ?level header fd >>= fun () ->
Lwt_result.map_error unix_err_to_msg
(safe Lwt_unix.(openfile filename [ O_RDONLY ]) 0) >>= fun src ->
Lwt.finalize
(fun () -> copy ~src_fd:src ~dst_fd:fd
(Int64.to_int header.Tar.Header.file_size))
(fun () -> safe_close src)
let ?level fd =
let open Lwt_result.Infix in
Lwt_result.lift (Tar.encode_global_extended_header ?level header) >>= fun ->
write_strings fd header_strings
let write_end fd =
write_strings fd [ Tar.Header.zero_block ; Tar.Header.zero_block ]
let create ?level ?global ?(filter = fun _ -> true) ~src dst =
let open Lwt_result.Infix in
Lwt_result.map_error unix_err_to_msg
(safe Lwt_unix.(openfile dst [ O_WRONLY ; O_CREAT ]) 0o644) >>= fun dst_fd ->
Lwt.finalize
(fun () ->
(match global with
| None -> Lwt.return (Ok ())
| Some hdr -> write_global_extended_header ?level hdr dst_fd) >>= fun () ->
let rec copy_files directory =
safe Lwt_unix.opendir directory >>= fun dir ->
Lwt.finalize
(fun () ->
let rec next () =
try
safe Lwt_unix.readdir dir >>= fun name ->
let filename = Filename.concat directory name in
header_of_file ?level filename >>= fun ->
if filter header then
match header.Tar.Header.link_indicator with
| Normal ->
append_file ?level ~header filename dst_fd >>= fun () ->
next ()
| Directory ->
copy_files filename >>= fun () ->
next ()
| _ -> Lwt.return (Ok ())
else Lwt.return (Ok ())
with End_of_file -> Lwt.return (Ok ())
in
next ())
(fun () ->
Lwt.catch
(fun () -> Lwt_unix.closedir dir)
(fun _ -> Lwt.return_unit))
in
copy_files src >>= fun () ->
write_end dst_fd)
(fun () -> safe_close dst_fd)