Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Page
Library
Module
Module type
Parameter
Class
Class type
Source
block.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
(* a block is, physically, a revision count and series of commits *) (* it can also have a hardtail, pointing at another blockpair where * the directory continues *) module IdSet = Set.Make(Int) type t = { revision_count : int; commits : Commit.t list; hardtail : Entry.t option; } type write_result = [ `Ok | `Split | `Split_emergency ] let commits t = t.commits let revision_count t = t.revision_count let entries t = List.(flatten @@ map Commit.entries t.commits) let crc_of_revision_count revision_count = let start_crc = Checkseum.Crc32.default in let cs = Cstruct.create 4 in let sizeof_crc = 4 in Cstruct.LE.set_uint32 cs 0 (Int32.of_int revision_count); let revision_count_crc = Checkseum.Crc32.(digest_bigstring (Cstruct.to_bigarray cs) 0 sizeof_crc start_crc) in (* hey hey, ho ho, we don't want no overflow *) Optint.((logand) revision_count_crc @@ of_unsigned_int32 0xffffffffl) let linked_blocks t = (* we call `compact` on the entry list because otherwise we'd incorrectly follow * deleted entries *) List.filter_map Entry.links @@ Entry.compact @@ entries t let of_commits ~hardtail ~revision_count commits = (* we have to redo the crc for the first commit when the revision count changes :( *) (* we don't have to recalculate CRCs for any subsequent commits because the only data * dependency on a previous commit for later commits in a block * is on the last tag of the previous commit, which is the CRC tag. * The CRC tag's variable fields are its length and chunk value only, * and changes to the revision count are changes to a fixed-width field that * doesn't affect the chunk. *) match commits with | [] -> { commits; revision_count; hardtail} | commit :: l -> let crc = crc_of_revision_count revision_count in let new_commit = Commit.(of_entries_filter_crc (seed_tag commit) crc (entries commit)) in {commits = (new_commit :: l); revision_count; hardtail} let of_entries ~revision_count entries = let crc = crc_of_revision_count revision_count in let commit = Commit.of_entries_filter_crc (Cstruct.of_string "\xff\xff\xff\xff") crc entries in of_commits ~hardtail:None ~revision_count (commit::[]) let compact t = let revision_count = t.revision_count + 1 in let entries = List.map Commit.entries t.commits |> List.flatten |> Entry.compact in of_entries ~revision_count entries let add_commit {revision_count; commits; hardtail} entries = let revision_count = revision_count + 1 in match commits with | [] -> of_entries ~revision_count entries | l -> let last = List.(nth l ((length l) - 1)) in let commit = Commit.commit_after last entries in of_commits ~hardtail ~revision_count (l @ [commit]) let hardtail t = match t.hardtail with | None -> None | Some e -> Dir.hard_tail_links e (* return variants `Split and `Ok are successful; `Split warns that the block * took up more than 1/2 of the block size and the metadata block * should be split into two pairs. * * `Split_emergency means the size of `block` exceeds the block size, * and the data *cannot* be written. A partial serialization remains in [cs] in * this case. *) let into_cstruct ~program_block_size cs block = (* the hardtail is handled specially since its presence alters our next_commit_valid * and all entries need to be before it. *) let write_hardtail ~after ~starting_xor_tag ~starting_offset t cs = match t.hardtail with | None -> (0, starting_xor_tag) | Some entry -> let commit = Commit.commit_after after [entry] in Commit.into_cstruct ~filter_hardtail:false ~starting_offset ~program_block_size ~starting_xor_tag ~next_commit_valid:false cs commit in (* if there's nothing to write, just return *) match block.commits, block.hardtail with | [], None -> `Ok | commits, _ -> Cstruct.LE.set_uint32 cs 0 (Int32.of_int block.revision_count); try let after_last_crc, starting_xor_tag, starting_offset = List.fold_left (fun (pointer, prev_commit_last_tag, starting_offset) commit -> (* it may be a bit surprising that we don't use `last_tag` from `commit` as the previous tag here. * as serialized, the last tag in the commit is the tag for the CRC, so we need to XOR with that * rather than the last non-CRC tag, which is what's represented in `commit.last_tag`. *) let this_commit_region = Cstruct.shift cs pointer in let (bytes_written, raw_crc_tag) = Commit.into_cstruct ~filter_hardtail:true ~next_commit_valid:true ~program_block_size ~starting_xor_tag:prev_commit_last_tag ~starting_offset this_commit_region commit in (* only the first commit has nonzero offset; all subsequent ones have an offset of 0, * since each commit is padded to a multiple of the program block size. *) (pointer + bytes_written, raw_crc_tag, 0) ) (4, (Cstruct.of_string "\xff\xff\xff\xff"), 4) block.commits in let hardtail_region = Cstruct.shift cs after_last_crc in let hardtail_bytes, _raw_crc = write_hardtail ~after:List.(hd @@ rev commits) ~starting_xor_tag ~starting_offset block hardtail_region in if (after_last_crc + hardtail_bytes) > ((Cstruct.length cs) / 2) then `Split else `Ok with | Invalid_argument _ -> `Split_emergency let ids t = let id_of_entry e = (fst e).Tag.id in let commit_ids c = List.map id_of_entry (Commit.entries c) in let block_ids = List.(flatten @@ map commit_ids t.commits) in IdSet.of_list block_ids let split block next_blockpair = let entry = Dir.hard_tail_at next_blockpair in {block with hardtail = Some entry}, of_entries ~revision_count:1 [] let to_cstruct ~program_block_size ~block_size block = let cs = Cstruct.create block_size in let res = into_cstruct ~program_block_size cs block in cs, res let of_cstruct ~program_block_size cs = if Cstruct.length cs <= Tag.size then Error (`Msg "block is too small to contain littlefs commits") else begin let revision_count = Cstruct.LE.get_uint32 cs 0 |> Int32.to_int in let revision_count_crc = crc_of_revision_count revision_count in let commit_list = Cstruct.shift cs 4 in let starting_xor_tag = Cstruct.of_string "\xff\xff\xff\xff" in (* the first commit is at an offset of 4, because the revision count is hanging out at the front of the block *) let commits = Commit.of_cstructv ~preceding_crc:revision_count_crc ~starting_offset:4 ~starting_xor_tag ~program_block_size commit_list in let entries = List.(flatten @@ map Commit.entries commits) in let hardtail = List.find_opt (fun (t, _d) -> Tag.is_hardtail t) entries in Ok {revision_count; commits; hardtail} end