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
open Lwt.Infix
module Make(Sectors : Mirage_block.S) = struct
module Fs = Fs.Make(Sectors)
type key = Mirage_kv.Key.t
let log_src = Logs.Src.create "chamelon-kv" ~doc:"chamelon KV layer"
module Log = (val Logs.src_log log_src : Logs.LOG)
type error = [
| `Not_found of key (** key not found *)
| `Dictionary_expected of key (** key does not refer to a dictionary. *)
| `Value_expected of key (** key does not refer to a value. *)
]
type write_error = [
| error
| `No_space (** No space left on the device. *)
| `Already_present of key (** The key is already present. *)
| `Rename_source_prefix of key * key (** The source is a prefix of destination in rename. *)
]
let pp_error fmt = function
| `Not_found key -> Format.fprintf fmt "key %a not found" Mirage_kv.Key.pp key
| `Dictionary_expected key -> Format.fprintf fmt "%a was not a dictionary" Mirage_kv.Key.pp key
| `Value_expected key -> Format.fprintf fmt "%a was not a value" Mirage_kv.Key.pp key
let pp_write_error fmt = function
| `No_space -> Format.fprintf fmt "no space left on device"
| `Already_present key -> Format.fprintf fmt "key %a is already present" Mirage_kv.Key.pp key
| `Rename_source_prefix (k1, k2) -> Format.fprintf fmt "rename %a is a prefix of %a" Mirage_kv.Key.pp k1 Mirage_kv.Key.pp k2
| #error as e -> pp_error fmt e
type t = Fs.t
let get = Fs.File_read.get
let get_partial t key ~offset ~length =
Fs.File_read.get_partial t key ~offset:(Optint.Int63.to_int offset) ~length
let set t key data : (unit, write_error) result Lwt.t =
let name_length = String.length @@ Mirage_kv.Key.basename key in
let name_length_max = Int32.to_int @@ Fs.name_length_max t in
if name_length > name_length_max then begin
Log.err (fun f -> f "key length %d exceeds max length %d - refusing to write" name_length name_length_max);
Lwt.return @@ Error (`Not_found Mirage_kv.Key.empty)
end else begin
let dir = Mirage_kv.Key.parent key in
Fs.mkdir t (Mirage_kv.Key.segments dir) >>= function
| Ok block_pair ->
Log.debug (fun m -> m "found dir for %a on block pair %a"
Mirage_kv.Key.pp key
Fs.pp_blockpair block_pair);
Fs.File_write.set_in_directory block_pair t (Mirage_kv.Key.basename key) data
| Error (`Already_present e) ->
Log.err (fun f -> f "error making directory for write to %a: component %s already present and not a directory" Mirage_kv.Key.pp key e);
Lwt.return @@ Error (`Dictionary_expected (Mirage_kv.Key.v e))
| Error `No_space as e -> Lwt.return e
| Error (`Not_found _k) as e -> Lwt.return e
end
let set_partial t key ~offset data =
get t key >>= function
| Error e -> Lwt.return (Error (e :> write_error))
| Ok d ->
let offset = Optint.Int63.to_int offset in
Bytes.blit_string data 0 (Bytes.unsafe_of_string d) offset (String.length data);
set t key d
let allocate t key ?last_modified:_ size =
get t key >>= function
| Ok _ -> Lwt.return @@ Error (`Already_present key)
| Error (`Not_found key) ->
set t key (String.make (Optint.Int63.to_int size) '\000')
| Error e -> Lwt.return @@ Error (e :> write_error)
(** [list t key], where [key] is a reachable directory,
* gives the files and directories (values and dictionaries) in [key].
* It is not a recursive listing. *)
let list t key : ((key * [`Dictionary | `Value]) list, error) result Lwt.t =
Fs.ls t key
(** [exists t key] returns true *only* for a file/value called (basename key) set in (dirname key).
* A directory/dictionary doesn't cut it. *)
let exists t key =
list t (Mirage_kv.Key.parent key) >>= function
| Error _ as e -> Lwt.return e
| Ok l ->
let lookup (name, dict_or_val) =
if Mirage_kv.Key.equal name key then
Some dict_or_val
else None
in
Lwt.return @@ Ok (List.find_map lookup l)
let size t key = Fs.Size.size t key
let remove t key = Fs.remove t key
let rename t ~source ~dest =
get t source >>= function
| Error e -> Lwt.return (Error (e :> write_error))
| Ok data ->
set t dest data >>= function
| Ok () ->
remove t source
| Error _ as e -> Lwt.return e
let last_modified t key =
let ptimeify = function
| Error _ as e -> e
| Ok s ->
let ts =
let (let*) = Option.bind in
let* span = Ptime.Span.of_d_ps s in
let* ts = Ptime.of_span span in
Some ts
in
match ts with
| Some ts -> Ok ts
| None -> Error (`Not_found key)
in
Fs.is_directory t key >>= function
| false -> Fs.last_modified_value t key >|= ptimeify
| true ->
Fs.ls t key >>= function
| Error _ as e -> Lwt.return e
| Ok l ->
Lwt_list.fold_left_s (fun ts entry ->
match entry with
| _, `Dictionary -> Lwt.return ts
| (name, `Value) ->
Fs.last_modified_value t name >>= fun new_span ->
match ts, ptimeify new_span with
| Error e, _ -> Lwt.return (Error e)
| Ok _, Error e -> Lwt.return @@ Error e
| Ok ts, Ok a_ts ->
if Ptime.is_later a_ts ~than:ts
then Lwt.return @@ Ok a_ts
else Lwt.return @@ Ok ts
)
(Ok Ptime.epoch) l
(** [digest t key] is the SHA256 sum of `key` if `key` is a value.
* If [key] is a dictionary, it's a recursive digest of `key`'s contents. *)
let digest t key =
let rec aux ctx t key =
get t key >>= function
| Ok v ->
let digest = Digestif.SHA256.feed_string ctx v in
Lwt.return @@ Ok digest
| Error (`Value_expected _) -> begin
list t key >>= function
| Error e ->
Log.err (fun m -> m "error listing %a: %a\n%!" Mirage_kv.Key.pp key pp_error e);
Lwt.return @@ Error (`Not_found key)
| Ok l -> begin
Lwt_list.fold_left_s (fun ctx_result (path, _) ->
match ctx_result with
| Error _ as e -> Lwt.return e
| Ok ctx -> aux ctx t path
) (Ok ctx) l
end
end
| Error _ as e -> Lwt.return e
in
let ctx = Digestif.SHA256.init () in
Log.debug (fun f -> f "context for digest initiated");
aux ctx t key >|= function
| Error e -> Error e
| Ok ctx -> Ok Digestif.SHA256.(to_raw_string @@ get ctx)
let disconnect _ = Lwt.return_unit
let connect ~program_block_size block =
Sectors.get_info block >>= fun info ->
let block_size = info.Mirage_block.sector_size in
Fs.connect ~program_block_size ~block_size block
let format ~program_block_size block =
Sectors.get_info block >>= fun info ->
let block_size = info.Mirage_block.sector_size in
Fs.format ~program_block_size ~block_size block
let dump fmt t = Fs.dump fmt t
end