diff --git a/dune-project b/dune-project index a02724b..1e905b2 100644 --- a/dune-project +++ b/dune-project @@ -64,12 +64,14 @@ (depends (ocaml (>= 4.08.0)) (cstruct (>= 1.9.0)) - lwt + (lwt (>= 5.6.0)) (mirage-block (>= 2.0.0)) + (mirage-clock (>= 4.0.0)) (mirage-kv (>= 5.0.0)) ptime (tar (= :version)) (mirage-block-unix (and :with-test (>= 2.5.0))) + (mirage-clock-unix :with-test) (ounit2 :with-test) (ounit2-lwt :with-test) (tar-unix (and :with-test (= :version))) diff --git a/lib/tar.ml b/lib/tar.ml index 2bcccc4..3345c7c 100644 --- a/lib/tar.ml +++ b/lib/tar.ml @@ -209,9 +209,7 @@ module Header = struct let length = 8 + 1 + (String.length k) + 1 + (String.length v) + 1 in Printf.sprintf "%08d %s=%s\n" length k v ) pairs) in - let buffer = Cstruct.create (String.length txt) in - Cstruct.blit_from_string txt 0 buffer 0 (String.length txt); - buffer + Cstruct.of_string txt let unmarshal (c: Cstruct.t) : t = (* "%d %s=%s\n", , , with constraints that @@ -314,12 +312,7 @@ module Header = struct let length = 512 (** A blank header block (two of these in series mark the end of the tar) *) - let zero_block = - let buf = Cstruct.create length in - for i = 0 to Cstruct.length buf - 1 do - Cstruct.set_uint8 buf i 0 - done; - buf + let zero_block = Cstruct.create length (** [allzeroes buf] is true if [buf] contains only zero bytes *) let allzeroes buf = @@ -624,7 +617,6 @@ module HeaderWriter(Async: ASYNC)(Writer: WRITER with type 'a t = 'a Async.t) = let write_unextended ?level header fd = let level = Header.get_level level in let buffer = Cstruct.create Header.length in - Cstruct.memset buffer 0; let blank = {Header.file_name = longlink; file_mode = 0; user_id = 0; group_id = 0; mod_time = 0L; file_size = 0L; link_indicator = Header.Link.LongLink; link_name = ""; uname = "root"; gname = "root"; devmajor = 0; devminor = 0; extended = None} in ( if (String.length header.Header.link_name > Header.sizeof_hdr_link_name || String.length header.Header.file_name > Header.sizeof_hdr_file_name) && level = Header.GNU then begin ( if String.length header.Header.link_name > Header.sizeof_hdr_link_name then begin @@ -633,9 +625,7 @@ module HeaderWriter(Async: ASYNC)(Writer: WRITER with type 'a t = 'a Async.t) = Header.imarshal ~level buffer 'K' blank; really_write fd buffer >>= fun () -> - let text = header.Header.link_name ^ "\000" in - let payload = Cstruct.create (String.length text) in - Cstruct.blit_from_string text 0 payload 0 (String.length text); + let payload = Cstruct.of_string (header.Header.link_name ^ "\000") in really_write fd payload >>= fun () -> really_write fd (Header.zero_padding blank) @@ -647,9 +637,7 @@ module HeaderWriter(Async: ASYNC)(Writer: WRITER with type 'a t = 'a Async.t) = Header.imarshal ~level buffer 'L' blank; really_write fd buffer >>= fun () -> - let text = header.Header.file_name ^ "\000" in - let payload = Cstruct.create (String.length text) in - Cstruct.blit_from_string text 0 payload 0 (String.length text); + let payload = Cstruct.of_string (header.Header.file_name ^ "\000") in really_write fd payload >>= fun () -> really_write fd (Header.zero_padding blank) diff --git a/lib_test/dune b/lib_test/dune index 557f8fc..6ebb07e 100644 --- a/lib_test/dune +++ b/lib_test/dune @@ -4,6 +4,7 @@ (libraries mirage-block-unix mirage-block + mirage-clock-unix ounit2 ounit2-lwt lwt diff --git a/lib_test/parse_test.ml b/lib_test/parse_test.ml index dd55652..a927a63 100644 --- a/lib_test/parse_test.ml +++ b/lib_test/parse_test.ml @@ -23,29 +23,17 @@ let convert_path os path = close_in ch; line -let win32_openfile path flags perms = - Unix.openfile (convert_path `Windows path) flags perms - module Unix = struct include Unix - let openfile = if Sys.win32 then win32_openfile else openfile + let openfile path = + if Sys.win32 then openfile (convert_path `Windows path) else openfile path + let stat path = + if Sys.win32 then stat (convert_path `Windows path) else stat path + let truncate path = + if Sys.win32 then truncate (convert_path `Windows path) else truncate path end -exception Cstruct_differ - -let cstruct_equal a b = - let check_contents a b = - try - for i = 0 to Cstruct.length a - 1 do - let a' = Cstruct.get_char a i in - let b' = Cstruct.get_char b i in - if a' <> b' then raise Cstruct_differ - done; - true - with _ -> false in - (Cstruct.length a = (Cstruct.length b)) && (check_contents a b) - let header _test_ctxt = (* check header marshalling and unmarshalling *) let h = Tar.Header.make ~file_mode:5 ~user_id:1001 ~group_id:1002 ~mod_time:55L ~link_name:"" "hello" 1234L in @@ -55,7 +43,7 @@ let header _test_ctxt = let c' = Cstruct.create Tar.Header.length in for i = 0 to Tar.Header.length - 1 do Cstruct.set_uint8 c' i 0 done; Tar.Header.marshal c' h; - assert_equal ~printer:(fun x -> String.escaped (Cstruct.to_string x)) ~cmp:cstruct_equal c c'; + assert_equal ~printer:(fun x -> String.escaped (Cstruct.to_string x)) ~cmp:Cstruct.equal c c'; let printer = function | None -> "None" | Some x -> "Some " ^ (Tar.Header.to_detailed_string x) in @@ -64,7 +52,7 @@ let header _test_ctxt = let set_difference a b = List.filter (fun a -> not(List.mem a b)) a -let with_tar ?(level:Tar.Header.compatibility option) ?files test_ctxt f = +let with_tar ?(level:Tar.Header.compatibility option) ?files ?(block_size = 512) test_ctxt f = let format = match level with | None -> "" | Some format -> "--format=" ^ match format with @@ -76,7 +64,8 @@ let with_tar ?(level:Tar.Header.compatibility option) ?files test_ctxt f = let tar_filename, ch = bracket_tmpfile ~prefix:"tar-test" ~suffix:".tar" test_ctxt in close_out ch; let tar_filename = if Sys.win32 then convert_path `Unix tar_filename else tar_filename in - let cmdline = Printf.sprintf "tar -cf %s %s %s" tar_filename format (String.concat " " files) in + let tar_block_size = block_size / 512 in + let cmdline = Printf.sprintf "tar -cf %s -b %d %s %s" tar_filename tar_block_size format (String.concat " " files) in begin match Unix.system cmdline with | Unix.WEXITED 0 -> () | Unix.WEXITED n -> failwith (Printf.sprintf "%s: exited with %d" cmdline n) @@ -166,91 +155,103 @@ let can_transform_tar test_ctxt = module Block4096 = struct include Block - let get_info b = - Block.get_info b - >>= fun info -> - let size_sectors = Int64.(div (add info.size_sectors 7L) 8L) in - Lwt.return { info with Mirage_block.sector_size = 4096; size_sectors } - - let read b ofs bufs = - Block.get_info b - >>= fun info -> - let len = List.fold_left (+) 0 (List.map Cstruct.length bufs) in - let requested_end = Int64.(add (mul ofs 4096L) (of_int len)) in - let end_of_file = Int64.(mul info.size_sectors (of_int info.sector_size)) in - let need_to_trim = max 0L (Int64.sub requested_end end_of_file) |> Int64.to_int in - let need_to_keep = len - need_to_trim in - let rec trimmed len = function - | [] -> [] - | b :: bs -> - let b' = Cstruct.length b in - for _ = 0 to b' do Cstruct.set_uint8 b 0 0 done; - let to_drop = max 0 (len + b' - need_to_keep) in - let to_keep = max 0 (b' - to_drop) in - Cstruct.sub b 0 to_keep :: (trimmed (len + b') bs) in - let trimmed = (trimmed 0 bufs) in - Block.read b (Int64.mul ofs 8L) trimmed + let block_size = 4096 let connect name = let name = if Sys.win32 then convert_path `Windows name else name in - connect name + connect ~prefered_sector_size:(Some 4096) name end module type BLOCK = sig include Mirage_block.S val connect: string -> t Lwt.t + val block_size : int end module B = struct include Block + let block_size = 512 + let connect name = let name = if Sys.win32 then convert_path `Windows name else name in - connect name + connect ~prefered_sector_size:(Some 512) name end module Test(B: BLOCK) = struct + let add_data_to_tar ?(level:Tar.Header.compatibility option) ?files test_ctxt f = + let f tar_filename files = + let size = Unix.(stat tar_filename).st_size in + Unix.truncate tar_filename (size + (min (2 * B.block_size) 4096)); + B.connect tar_filename >>= fun b -> + let module KV_RW = Tar_mirage.Make_KV_RW(Pclock)(B) in + KV_RW.connect b >>= fun t -> + KV_RW.set t (Mirage_kv.Key.v "barf") "foobar" >>= fun x -> + Result.iter_error (fun e -> + failwith (Fmt.to_to_string KV_RW.pp_write_error e)) + x; + let files = "barf" :: files in + f tar_filename files + in + with_tar ?level ?files ~block_size:B.block_size test_ctxt f + + let write_with_full_archive ?(level:Tar.Header.compatibility option) ?files test_ctxt = + let f tar_filename files = + B.connect tar_filename >>= fun b -> + let module KV_RW = Tar_mirage.Make_KV_RW(Pclock)(B) in + KV_RW.connect b >>= fun t -> + KV_RW.set t (Mirage_kv.Key.v "barf") "foobar" >>= function + | Error `No_space -> Lwt.return () + | _ -> failwith "expected `No_space" + in + with_tar ?level ?files test_ctxt f + + let check_tar tar_filename files = + B.connect tar_filename >>= fun b -> + let module KV_RO = Tar_mirage.Make_KV_RO(B) in + KV_RO.connect b >>= fun k -> + Lwt_list.iter_s + (fun file -> + let size = + if file = "barf" then 6L else Unix.LargeFile.((stat file).st_size) + in + let read_file key ofs len = + if key = "barf" then String.sub "foobar" ofs len else + let fd = Unix.openfile key [ Unix.O_RDONLY ] 0 in + Fun.protect + (fun () -> + let (_: int) = Unix.lseek fd ofs Unix.SEEK_SET in + let buf = Bytes.make len '\000' in + let len' = Unix.read fd buf 0 len in + assert_equal ~printer:string_of_int len len'; + Bytes.to_string buf + ) ~finally:(fun () -> Unix.close fd) in + let read_tar key = + KV_RO.get k key >>= function + | Error e -> Fmt.failwith "KV_RO.read (%a) %a" Mirage_kv.Key.pp key KV_RO.pp_error e + | Ok buf -> Lwt.return buf in + (* Read whole file *) + let value = read_file file 0 (Int64.to_int size) in + read_tar (Mirage_kv.Key.v file) >>= fun value' -> + assert_equal ~printer:(fun x -> x) value value'; + if Int64.compare size 2L = 1 then begin + let value = read_file file 1 ((Int64.to_int size) - 2) in + read_tar (Mirage_kv.Key.v file) >>= fun value' -> + let value'' = String.sub value' 1 ((Int64.to_int size) - 2) in + assert_equal ~printer:(fun x -> x) value value''; + Lwt.return_unit + end else Lwt.return_unit + ) files + let can_read_through_BLOCK ?files test_ctxt = - with_tar ?files test_ctxt - (fun tar_filename files -> - B.connect tar_filename >>= fun b -> - let module KV_RO = Tar_mirage.Make_KV_RO(B) in - KV_RO.connect b >>= fun k -> - Lwt_list.iter_s - (fun file -> - let stats = Unix.LargeFile.stat file in - let read_file key ofs len = - let fd = Unix.openfile key [ Unix.O_RDONLY ] 0 in - Fun.protect - (fun () -> - let (_: int) = Unix.lseek fd ofs Unix.SEEK_SET in - let buf = Bytes.make len '\000' in - let len' = Unix.read fd buf 0 len in - assert_equal ~printer:string_of_int len len'; - Bytes.to_string buf - ) ~finally:(fun () -> Unix.close fd) in - let read_tar key = - KV_RO.get k key >>= function - | Error _ -> failwith "KV_RO.read" - | Ok buf -> Lwt.return buf in - (* Read whole file *) - let size = stats.Unix.LargeFile.st_size in - let value = read_file file 0 (Int64.to_int size) in - read_tar (Mirage_kv.Key.v file) >>= fun value' -> - assert_equal ~printer:(fun x -> x) value value'; - if Int64.compare size 2L = 1 then begin - let value = read_file file 1 ((Int64.to_int size) - 2) in - read_tar (Mirage_kv.Key.v file) >>= fun value' -> - let value'' = String.sub value' 1 ((Int64.to_int size) - 2) in - assert_equal ~printer:(fun x -> x) value value''; - Lwt.return_unit - end else Lwt.return_unit - ) files - ) + with_tar ?files ~block_size:B.block_size test_ctxt check_tar + + let write_test test_ctxt = + add_data_to_tar test_ctxt check_tar - let check_not_padded test_ctxt = - Unix.openfile "empty" [ Unix.O_WRONLY; Unix.O_CREAT; Unix.O_TRUNC ] 0o644 |> Unix.close; - can_read_through_BLOCK ~files:["empty"] test_ctxt + let check_not_padded test_ctxt = + Unix.openfile "empty" [ Unix.O_WRONLY; Unix.O_CREAT; Unix.O_TRUNC ] 0o644 |> Unix.close; + can_read_through_BLOCK ~files:["empty"] test_ctxt end module Sector512 = Test(B) @@ -267,6 +268,9 @@ let () = "can write pax headers" >:: can_write_pax; "can read @Longlink" >:: can_list_longlink_tar; "can transform tars" >:: can_transform_tar; + "add_data_to_tar BLOCK/512" >:: OUnitLwt.lwt_wrapper Sector512.write_test; + "write_with_full_archive BLOCK/512" >:: OUnitLwt.lwt_wrapper Sector512.write_with_full_archive; + "add_data_to_tar BLOCK/4096" >:: OUnitLwt.lwt_wrapper Sector4096.write_test; ] in (* pwd = _build/default/lib_test *) Unix.chdir "../../.."; diff --git a/mirage/dune b/mirage/dune index 756b5f2..cfa2272 100644 --- a/mirage/dune +++ b/mirage/dune @@ -1,5 +1,5 @@ (library (name tar_mirage) (public_name tar-mirage) - (libraries tar lwt mirage-kv mirage-block ptime) + (libraries tar lwt mirage-kv mirage-block ptime mirage-clock) (wrapped false)) diff --git a/mirage/tar_mirage.ml b/mirage/tar_mirage.ml index 821f8e0..f99ab89 100644 --- a/mirage/tar_mirage.ml +++ b/mirage/tar_mirage.ml @@ -28,8 +28,11 @@ module Make_KV_RO (BLOCK : Mirage_block.S) = struct type t = { b: BLOCK.t; - map: entry; + mutable map: entry; + (** offset in bytes *) + mutable end_of_archive: int64; info: Mirage_block.info; + write_lock : Lwt_mutex.t; } type key = Mirage_kv.Key.t @@ -71,6 +74,7 @@ module Make_KV_RO (BLOCK : Mirage_block.S) = struct module Reader = struct type in_channel = { b: BLOCK.t; + (** offset in bytes *) mutable offset: int64; info: Mirage_block.info; } @@ -88,8 +92,8 @@ module Make_KV_RO (BLOCK : Mirage_block.S) = struct let tmp = Cstruct.create sector_aligned_len in BLOCK.read in_channel.b sector' [ tmp ] >>= function - | Error e -> Lwt.fail (Failure (Format.asprintf "Failed to read sector %Ld from block device: %a" sector' - BLOCK.pp_error e)) + | Error e -> failwith (Format.asprintf "Failed to read sector %Ld from block device: %a" sector' + BLOCK.pp_error e) | Ok () -> (* If the BLOCK sector size is big, then we need to select the 512 bytes we want *) let offset = Int64.(to_int (sub in_channel.offset (mul sector' (of_int sector_size)))) in @@ -127,7 +131,7 @@ module Make_KV_RO (BLOCK : Mirage_block.S) = struct let start_sector, start_padding = div start_bytes sector_size, rem start_bytes sector_size in - let end_sector = div (pred (add end_bytes sector_size)) sector_size in + let end_sector = div end_bytes sector_size in let n_sectors = succ (sub end_sector start_sector) in let buf = Cstruct.create (to_int (mul n_sectors sector_size)) in let tmps = @@ -159,14 +163,11 @@ module Make_KV_RO (BLOCK : Mirage_block.S) = struct Lwt.return r let to_day_ps hdr = - let ts = - match Ptime.Span.of_float_s (Int64.to_float hdr.Tar.Header.mod_time) with - | None -> Ptime.epoch - | Some span -> match Ptime.add_span Ptime.epoch span with - | None -> Ptime.epoch - | Some ts -> ts + let ptime = + Option.value ~default:Ptime.epoch + (Ptime.of_float_s (Int64.to_float hdr.Tar.Header.mod_time)) in - Ptime.(Span.to_d_ps (to_span ts)) + Ptime.(Span.to_d_ps (to_span ptime)) let last_modified t key = let r = match get_node t.map key with @@ -213,6 +214,9 @@ module Make_KV_RO (BLOCK : Mirage_block.S) = struct let connect b = BLOCK.get_info b >>= fun info -> + let ssize = info.Mirage_block.sector_size in + if ssize mod 512 <> 0 || ssize < 512 then + invalid_arg "Sector size needs to be >= 512 and a multiple of 512"; let in_channel = { Reader.b; offset = 0L; info } in let rec loop map = HR.read in_channel >>= function @@ -233,9 +237,200 @@ module Make_KV_RO (BLOCK : Mirage_block.S) = struct in let root = StringMap.empty in loop root >>= fun map -> + (* This is after the two [zero_block]s *) + let end_of_archive = in_channel.Reader.offset in let map = Dict (Tar.Header.make "/" 0L, map) in - Lwt.return ({ b; map; info }) + let write_lock = Lwt_mutex.create () in + Lwt.return ({ b; map; info; end_of_archive; write_lock }) let disconnect _ = Lwt.return_unit end + + +module Make_KV_RW (CLOCK : Mirage_clock.PCLOCK) (BLOCK : Mirage_block.S) = struct + + include Make_KV_RO(BLOCK) + + let free t = + Int64.(sub (mul (of_int t.info.sector_size) t.info.size_sectors) + t.end_of_archive) + + let is_safe_to_set t key = + let rec find e path = + match e, path with + | (Value _ | Dict _), [] -> Error `Entry_already_exists + | Value _, _hd :: _tl -> Error `Path_segment_is_a_value + | Dict (_, m), hd :: tl -> + match StringMap.find_opt hd m with + | Some e -> find e tl + | None -> + (* if either (part of) the path or the file doesn't exist we're good *) + Ok () + in + find t.map (Mirage_kv.Key.segments key) + + let header_of_key key len = + let mod_time = + let ptime = Ptime.v (CLOCK.now_d_ps ()) in + Int64.of_float (Ptime.to_float_s ptime) + in + Tar.Header.make ~mod_time (Mirage_kv.Key.to_string key) (Int64.of_int len) + + let space_needed header = + let data_size = header.Tar.Header.file_size in + let padding_size = Tar.Header.compute_zero_padding_length header in + Int64.(add (of_int padding_size) data_size) + + let update_insert map key hdr offset = + match map with + | Value _ -> + (* if the root is a value we have done something very wrong. This should + be catched by [is_safe_to_set]. *) + assert false + | Dict (root, map) -> + (* [insert] may raise if [key] is [empty]. However, [is_safe_to_set] + should catch that since [empty] always exists as a dict (root). *) + let map = insert map key (Value (hdr, offset)) in + Dict (root, map) + + module Writer = struct + type out_channel = { + b: BLOCK.t; + (** offset in bytes *) + mutable offset: int64; + info: Mirage_block.info; + } + type 'a t = 'a Lwt.t + exception Read of BLOCK.error + exception Write of BLOCK.write_error + let really_write out_channel data = + assert (Cstruct.length data <= 512); + let data = Cstruct.(append data (create (512 - length data))) in + let sector_size = out_channel.info.sector_size in + let sector = Int64.(div out_channel.offset (of_int sector_size)) in + let block = Cstruct.create sector_size in + BLOCK.read out_channel.b sector [ block ] >>= function + | Error e -> raise (Read e) + | Ok () -> + let start_offset = Int64.to_int out_channel.offset mod sector_size in + Cstruct.blit data 0 block start_offset (Cstruct.length data); + BLOCK.write out_channel.b sector [ block ] >>= function + | Error e -> raise (Write e) + | Ok () -> + Lwt.return_unit + end + module HW = Tar.HeaderWriter(Lwt)(Writer) + + type write_error = [ `Block of BLOCK.error | `Block_write of BLOCK.write_error | Mirage_kv.write_error | `Entry_already_exists | `Path_segment_is_a_value | `Append_only ] + + let pp_write_error ppf = function + | `Block e -> Fmt.pf ppf "read error while writing: %a" BLOCK.pp_error e + | `Block_write e -> BLOCK.pp_write_error ppf e + | #Mirage_kv.write_error as e -> Mirage_kv.pp_write_error ppf e + | `Entry_already_exists -> Fmt.string ppf "entry already exists" + | `Path_segment_is_a_value -> Fmt.string ppf "path segment is a value" + | `Append_only -> Fmt.string ppf "append only" + | `Write_header msg -> Fmt.pf ppf "writing tar header failed: %s" msg + + let set t key data = + Lwt_mutex.with_lock t.write_lock (fun () -> + let data = Cstruct.of_string data in + let ( >>>= ) = Lwt_result.bind in + let r = + let ( let* ) = Result.bind in + let* () = is_safe_to_set t key in + let hdr = header_of_key key (Cstruct.length data) in + let space_needed = space_needed hdr in + let* () = if free t >= space_needed then Ok () else Error `No_space in + Ok (hdr, space_needed) + in + Lwt.return r >>>= fun (hdr, space_needed) -> + let open Int64 in + let sector_size = of_int t.info.Mirage_block.sector_size in + + let start_bytes = sub t.end_of_archive 512L in + let header_start_bytes = sub start_bytes 512L in + let sentinel = 2 * 512 in + let end_bytes = add start_bytes (add space_needed (of_int sentinel)) in + (* Compute the starting sector and ending sector (rounding down then up) *) + let start_sector, start_sector_offset = div start_bytes sector_size, rem start_bytes sector_size in + let end_sector = div end_bytes sector_size in + let end_sector_offset = rem end_bytes sector_size in + let data_sectors = sub end_sector start_sector in + let pad = Tar.Header.compute_zero_padding_length hdr in + let data = Cstruct.append data (Cstruct.create (pad + sentinel)) in + let first_sector, rest = + let s = + Stdlib.min + (Cstruct.length data) + (to_int (sub sector_size start_sector_offset)) + in + Cstruct.split data s + in + let remaining_sectors, last_sector = + let full_sectors = + (* the first sector is special (written last, may be partial) *) + let remaining_sectors = to_int (pred data_sectors) in + let last_is_full = end_sector_offset = 0L in + if last_is_full then remaining_sectors else remaining_sectors - 1 + in + if full_sectors <= 0 then + [], rest + else + List.init full_sectors + (fun sec -> + Cstruct.sub rest (sec * to_int sector_size) + (to_int sector_size)), + Cstruct.shift rest (full_sectors * to_int sector_size) + in + (* to write robustly as we can: + - we first write the last block, then + - we write tar blocks 2..end-1, + - finally the header AND the first tar block + *) + let buf = Cstruct.create (to_int sector_size) in + (if Cstruct.length last_sector > 0 then + Lwt_result.map_error (function e -> `Block e) + (BLOCK.read t.b end_sector [ buf ]) >>>= fun () -> + Cstruct.blit last_sector 0 buf + (to_int end_sector_offset - Cstruct.length last_sector) + (Cstruct.length last_sector); + Lwt_result.map_error (fun e -> `Block_write e) + (BLOCK.write t.b end_sector [ buf ]) + else + Lwt.return (Ok ())) >>>= fun () -> + (* write full blocks 2 .. end *) + Lwt_result.map_error (fun e -> `Block_write e) + (BLOCK.write t.b (succ start_sector) remaining_sectors) >>>= fun () -> + (* finally write header and first block *) + let hw = Writer.{ b = t.b ; offset = header_start_bytes ; info = t.info } in + Lwt.catch + (fun () -> HW.write ~level:Tar.Header.Ustar hdr hw >|= fun () -> Ok ()) + (function + | Writer.Read e -> Lwt.return (Error (`Block e)) + | Writer.Write e -> Lwt.return (Error (`Block_write e)) + | exn -> raise exn) >>>= fun () -> + Lwt_result.map_error (function e -> `Block e) + (BLOCK.read t.b start_sector [ buf ]) >>>= fun () -> + Cstruct.blit first_sector 0 buf (to_int start_sector_offset) + (Cstruct.length first_sector); + Lwt_result.map_error (fun e -> `Block_write e) + (BLOCK.write t.b start_sector [ buf ]) >>>= fun () -> + let tar_offset = Int64.div (sub t.end_of_archive 512L) 512L in + t.end_of_archive <- add t.end_of_archive space_needed; + t.map <- update_insert t.map key hdr tar_offset; + Lwt.return (Ok ())) + + let remove _ _ = + Lwt.return (Error `Append_only) + + let rename _ ~source:_ ~dest:_ = + Lwt.return (Error `Append_only) + + let set_partial _ _ ~offset:_ _ = + Lwt.return (Error `Append_only) + + let batch t ?retries:_ f = f t + +end diff --git a/mirage/tar_mirage.mli b/mirage/tar_mirage.mli index 1ca0a41..f7acfa9 100644 --- a/mirage/tar_mirage.mli +++ b/mirage/tar_mirage.mli @@ -23,4 +23,25 @@ module Make_KV_RO (BLOCK : Mirage_block.S) : sig include Mirage_kv.RO val connect: BLOCK.t -> t Lwt.t + (** [connect block] + + @raise Invalid_argument if [block] has a sector size that is not a + positive multiple of 512. *) +end + +module Make_KV_RW (CLOCK : Mirage_clock.PCLOCK) (BLOCK : Mirage_block.S) : sig + (** Construct a read-write key-value store from an existing block device + containing tar-format data. Note that it is append-only meaning removing + or renaming files is currently unsupported and will return an error. *) + + include Mirage_kv.RW + + val connect: BLOCK.t -> t Lwt.t + (** [connect block] + + @raise Invalid_argument if [block] has a sector size that is not a + positive multiple of 512. *) + + val free : t -> int64 + (** [free t] is the number of unused bytes. *) end diff --git a/tar-mirage.opam b/tar-mirage.opam index 4dad829..f6797d4 100644 --- a/tar-mirage.opam +++ b/tar-mirage.opam @@ -17,12 +17,14 @@ depends: [ "dune" {>= "2.9"} "ocaml" {>= "4.08.0"} "cstruct" {>= "1.9.0"} - "lwt" + "lwt" {>= "5.6.0"} "mirage-block" {>= "2.0.0"} + "mirage-clock" {>= "4.0.0"} "mirage-kv" {>= "5.0.0"} "ptime" "tar" {= version} "mirage-block-unix" {with-test & >= "2.5.0"} + "mirage-clock-unix" {with-test} "ounit2" {with-test} "ounit2-lwt" {with-test} "tar-unix" {with-test & = version}