package chamelon
Subset of littlefs filesystem fulfilling MirageOS KV
Install
dune-project
Dependency
Authors
Maintainers
Sources
chamelon-0.2.1.tbz
sha256=782b84fc81d7bf34fe10442437c6c507ca7ada2c9c822970cc23261be6a5178c
sha512=82fd26fdecf760f77289a9d917c93e1996afca95783d73dfd2947a60dfa817e37e068e3131b418388405927344f0d7536dc43db43b2544ab7be0a0807187edb2
doc/src/chamelon.kv/kv.ml.html
Source file kv.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
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) (* error type definitions straight outta mirage-kv *) 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 (* [set] does a little work on top of the filesystem's set functions, because * we need to make directories if the key has >1 segment in it. *) (* Once we've either found or created the parent directory, we can ask the FS layer * to set the data appropriately there. *) 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 = (* [allocate t key ~last_modified size] should only allocate space * if there is nothing already present for this key *) 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 (* [last_modified t key] gives the timestamp metadata for a file/value, * or (for a directory) the most recently modified file/value within the directory. * We don't have to recurse, thankfully, so we only have to examine files. *) 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 -> (* luckily, the spec says we should only check last_modified dates to a depth of 1 *) (* unfortunately, the spec *doesn't* say what the last_modified time of an empty directory is :/ *) (* it's convenient for us to say it's the earliest possible time, * such that our fold can just use the latest time it's seen in the list as the accumulator *) 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 (* let's see whether we can get a digest for the directory contents *) (* unfortunately we can't just run a digest of the block list, * because CTZs can change file contents without changing * metadata if the length remains the same, and also because * there are many differences possible in the entry list that map to the same * filesystem structure *) 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 (* There's no explicit statement in the mli about whether * we should descend beyond 1 dictionary for `digest`, * but I'm not sure how we can meaningfully have a digest if we don't *) 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
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>