Select the types of activity you want to include in your feed.
squashfs: Fix build errors
- Fix decompress library dependency (use decompress.de decompress.zl) - Rewrite decompression to use correct Zl.Inf API - Rename inode.data field to inode.inode_data to avoid shadowing
···5566(** Fuzz tests for SquashFS.
7788- Key properties tested:
99- 1. No crashes on malformed input
1010- 2. Parser handles truncated data gracefully
1111-*)
88+ Key properties tested: 1. No crashes on malformed input 2. Parser handles
99+ truncated data gracefully *)
12101311open Crowbar
1412···2422 (* Create data with valid magic but random rest *)
2523 let magic = "hsqs" in
2624 let data = magic ^ input in
2727- match Squashfs.of_string data with
2828- | Ok _ -> ()
2929- | Error _ -> ()
2525+ match Squashfs.of_string data with Ok _ -> () | Error _ -> ()
30263127let () =
3232- add_test ~name:"squashfs: no crash on arbitrary input" [bytes] test_no_crash;
3333- add_test ~name:"squashfs: handle corrupted data after magic" [bytes] test_corrupted_after_magic
2828+ add_test ~name:"squashfs: no crash on arbitrary input" [ bytes ] test_no_crash;
2929+ add_test ~name:"squashfs: handle corrupted data after magic" [ bytes ]
3030+ test_corrupted_after_magic
···88let superblock_size = 96
991010(* Compression types *)
1111-type compression =
1212- | Gzip
1313- | Lzma
1414- | Lzo
1515- | Xz
1616- | Lz4
1717- | Zstd
1111+type compression = Gzip | Lzma | Lzo | Xz | Lz4 | Zstd
18121913let compression_of_int = function
2014 | 1 -> Ok Gzip
···8983let pp_superblock ppf sb =
9084 Format.fprintf ppf
9185 "@[<v>SquashFS superblock:@,\
9292- \ \ version: %d.%d@,\
9393- \ \ compression: %a@,\
9494- \ \ block_size: %d@,\
9595- \ \ inode_count: %d@,\
9696- \ \ bytes_used: %Ld@,\
9797- \ \ modification_time: %d@]"
9898- sb.version_major sb.version_minor
9999- pp_compression sb.compression
8686+ \ version: %d.%d@,\
8787+ \ compression: %a@,\
8888+ \ block_size: %d@,\
8989+ \ inode_count: %d@,\
9090+ \ bytes_used: %Ld@,\
9191+ \ modification_time: %d@]"
9292+ sb.version_major sb.version_minor pp_compression sb.compression
10093 sb.block_size sb.inode_count sb.bytes_used sb.modification_time
1019410295(* Inode representation *)
···115108 file_size : int64;
116109 block_sizes : int array;
117110 }
118118- | Inode_symlink of {
119119- nlink : int;
120120- target : string;
121121- }
122122- | Inode_device of {
123123- nlink : int;
124124- rdev : int;
125125- }
126126- | Inode_ipc of {
127127- nlink : int;
128128- }
111111+ | Inode_symlink of { nlink : int; target : string }
112112+ | Inode_device of { nlink : int; rdev : int }
113113+ | Inode_ipc of { nlink : int }
129114130115type inode = {
131116 inode_type : file_type;
···134119 gid_idx : int;
135120 mtime : int;
136121 inode_number : int;
137137- data : inode_data;
122122+ inode_data : inode_data;
138123}
139124140140-type entry = {
141141- name : string;
142142- inode : inode;
143143- file_type : file_type;
144144-}
125125+type entry = { name : string; inode : inode; file_type : file_type }
145126146127(* Filesystem state *)
147128type t = {
···157138158139(* Binary reading helpers *)
159140let get_u8 s off = Char.code (String.get s off)
160160-let get_u16_le s off =
161161- get_u8 s off lor (get_u8 s (off + 1) lsl 8)
141141+let get_u16_le s off = get_u8 s off lor (get_u8 s (off + 1) lsl 8)
142142+162143let get_u32_le s off =
163163- Int32.to_int (
164164- Int32.logor
165165- (Int32.of_int (get_u16_le s off))
166166- (Int32.shift_left (Int32.of_int (get_u16_le s (off + 2))) 16))
144144+ Int32.to_int
145145+ (Int32.logor
146146+ (Int32.of_int (get_u16_le s off))
147147+ (Int32.shift_left (Int32.of_int (get_u16_le s (off + 2))) 16))
148148+167149let get_i32_le s off =
168150 let v = get_u32_le s off in
169151 if v >= 0x80000000 then v - 0x100000000 else v
152152+170153let get_u64_le s off =
171154 Int64.logor
172155 (Int64.of_int (get_u32_le s off))
173156 (Int64.shift_left (Int64.of_int (get_u32_le s (off + 4))) 32)
174157158158+(* Decompress a metadata block using zlib *)
159159+let decompress_zlib data max_output_size =
160160+ let input_len = String.length data in
161161+ let output_buf = De.bigstring_create max_output_size in
162162+ let window = De.make_window ~bits:15 in
163163+ let allocate _ = window in
164164+ let decoder = Zl.Inf.decoder (`String data) ~o:output_buf ~allocate in
165165+ let rec decode d acc_len =
166166+ match Zl.Inf.decode d with
167167+ | `Await _ -> Error "unexpected await in string source"
168168+ | `Flush d' ->
169169+ let len = De.bigstring_length output_buf - Zl.Inf.dst_rem d' in
170170+ decode (Zl.Inf.flush d') (acc_len + len)
171171+ | `End d' ->
172172+ let len = De.bigstring_length output_buf - Zl.Inf.dst_rem d' in
173173+ let total = acc_len + len in
174174+ let result = Bytes.create total in
175175+ for i = 0 to total - 1 do
176176+ Bytes.set result i (Bigarray.Array1.get output_buf i)
177177+ done;
178178+ Ok (Bytes.to_string result)
179179+ | `Malformed msg -> Error ("zlib decompression failed: " ^ msg)
180180+ in
181181+ ignore input_len;
182182+ decode decoder 0
183183+175184(* Decompress a metadata block *)
176185let decompress_block t ~compressed data =
177177- if not compressed then
178178- Ok data
186186+ if not compressed then Ok data
179187 else
180188 match t.superblock.compression with
181181- | Gzip ->
182182- let input = De.bigstring_create (String.length data) in
183183- for i = 0 to String.length data - 1 do
184184- Bigarray.Array1.set input i (String.get data i)
185185- done;
186186- let output = De.bigstring_create (t.superblock.block_size * 2) in
187187- (match De.Zl.Inf.Ns.inflate input output with
188188- | Ok len ->
189189- let result = Bytes.create len in
190190- for i = 0 to len - 1 do
191191- Bytes.set result i (Bigarray.Array1.get output i)
192192- done;
193193- Ok (Bytes.to_string result)
194194- | Error _ -> Error "gzip decompression failed")
189189+ | Gzip -> decompress_zlib data (t.superblock.block_size * 2)
195190 | Zstd ->
196191 (* TODO: implement zstd decompression *)
197192 Error "zstd decompression not yet implemented"
198193 | _ ->
199199- Error (Format.asprintf "compression %a not supported"
200200- pp_compression t.superblock.compression)
194194+ Error
195195+ (Format.asprintf "compression %a not supported" pp_compression
196196+ t.superblock.compression)
201197202198(* Read a metadata block at the given offset *)
203199let read_metadata_block t offset =
···205201 Error "metadata block offset out of bounds"
206202 else
207203 let header = get_u16_le t.data offset in
208208- let compressed = (header land 0x8000) = 0 in
204204+ let compressed = header land 0x8000 = 0 in
209205 let size = header land 0x7fff in
210206 if offset + 2 + size > String.length t.data then
211207 Error "metadata block extends beyond image"
···222218 else
223219 let magic_val = get_u32_le data 0 in
224220 if Int32.of_int magic_val <> magic then
225225- Error (Printf.sprintf "invalid magic: expected 0x%lx, got 0x%x"
226226- magic magic_val)
221221+ Error
222222+ (Printf.sprintf "invalid magic: expected 0x%lx, got 0x%x" magic
223223+ magic_val)
227224 else
228225 let compression_id = get_u16_le data 20 in
229226 match compression_of_int compression_id with
230227 | Error e -> Error e
231228 | Ok compression ->
232232- Ok {
233233- inode_count = get_u32_le data 4;
234234- modification_time = get_u32_le data 8;
235235- block_size = get_u32_le data 12;
236236- fragment_entry_count = get_u32_le data 16;
237237- compression;
238238- block_log = get_u16_le data 22;
239239- flags = get_u16_le data 24;
240240- id_count = get_u16_le data 26;
241241- version_major = get_u16_le data 28;
242242- version_minor = get_u16_le data 30;
243243- root_inode_ref = get_u64_le data 32;
244244- bytes_used = get_u64_le data 40;
245245- }
229229+ Ok
230230+ {
231231+ inode_count = get_u32_le data 4;
232232+ modification_time = get_u32_le data 8;
233233+ block_size = get_u32_le data 12;
234234+ fragment_entry_count = get_u32_le data 16;
235235+ compression;
236236+ block_log = get_u16_le data 22;
237237+ flags = get_u16_le data 24;
238238+ id_count = get_u16_le data 26;
239239+ version_major = get_u16_le data 28;
240240+ version_minor = get_u16_le data 30;
241241+ root_inode_ref = get_u64_le data 32;
242242+ bytes_used = get_u64_le data 40;
243243+ }
246244247245(* Parse an inode from metadata *)
248246let parse_inode _t data offset =
249249- if offset + 16 > String.length data then
250250- Error "inode header truncated"
247247+ if offset + 16 > String.length data then Error "inode header truncated"
251248 else
252249 let type_and_mode = get_u16_le data offset in
253250 let inode_type_raw = type_and_mode land 0xf in
···267264 let file_size = get_u16_le data (offset + 24) in
268265 let off = get_u16_le data (offset + 26) in
269266 let parent = get_u32_le data (offset + 28) in
270270- (Inode_dir {
271271- start_block;
272272- nlink;
273273- file_size = file_size + 3; (* size includes . and .. *)
274274- offset = off;
275275- parent_inode = parent;
276276- }, offset + 32)
267267+ ( Inode_dir
268268+ {
269269+ start_block;
270270+ nlink;
271271+ file_size = file_size + 3;
272272+ (* size includes . and .. *)
273273+ offset = off;
274274+ parent_inode = parent;
275275+ },
276276+ offset + 32 )
277277 | Regular ->
278278 let start_block = Int64.of_int (get_u32_le data (offset + 16)) in
279279 let fragment = get_i32_le data (offset + 20) in
280280 let off = get_u32_le data (offset + 24) in
281281 let file_size = Int64.of_int (get_u32_le data (offset + 28)) in
282282- (Inode_file {
283283- start_block;
284284- fragment;
285285- offset = off;
286286- file_size;
287287- block_sizes = [||]; (* TODO: parse block list *)
288288- }, offset + 32)
282282+ ( Inode_file
283283+ {
284284+ start_block;
285285+ fragment;
286286+ offset = off;
287287+ file_size;
288288+ block_sizes = [||];
289289+ (* TODO: parse block list *)
290290+ },
291291+ offset + 32 )
289292 | Symlink ->
290293 let nlink = get_u32_le data (offset + 16) in
291294 let target_size = get_u32_le data (offset + 20) in
···299302 let nlink = get_u32_le data (offset + 16) in
300303 (Inode_ipc { nlink }, offset + 20)
301304 in
302302- Ok {
303303- inode_type;
304304- mode = mode land 0o7777;
305305- uid_idx;
306306- gid_idx;
307307- mtime;
308308- inode_number;
309309- data = inode_data;
310310- }
305305+ Ok
306306+ {
307307+ inode_type;
308308+ mode = mode land 0o7777;
309309+ uid_idx;
310310+ gid_idx;
311311+ mtime;
312312+ inode_number;
313313+ inode_data;
314314+ }
311315312316(* Read the ID table *)
313317let read_id_table data sb =
314318 let id_table_start = get_u64_le data 48 in
315319 let count = sb.id_count in
316316- if count = 0 then
317317- Ok [||]
320320+ if count = 0 then Ok [||]
318321 else
319322 (* ID table is an array of block pointers, each block contains IDs *)
320323 let table = Array.make count 0 in
···323326 let block_offset = Int64.to_int (get_u64_le data block_ptr_offset) in
324327 (* Read the block header *)
325328 let header = get_u16_le data block_offset in
326326- let compressed = (header land 0x8000) = 0 in
329329+ let compressed = header land 0x8000 = 0 in
327330 let size = header land 0x7fff in
328331 if compressed then
329332 (* For now, just read uncompressed *)
330333 Error "compressed ID table not yet supported"
331334 else begin
332335 for i = 0 to min count (size / 4) - 1 do
333333- table.(i) <- get_u32_le data (block_offset + 2 + i * 4)
336336+ table.(i) <- get_u32_le data (block_offset + 2 + (i * 4))
334337 done;
335338 Ok table
336339 end
···344347 let abs_offset = inode_table_start + block_offset in
345348 match read_metadata_block t abs_offset with
346349 | Error e -> Error e
347347- | Ok (block_data, _) ->
348348- parse_inode t block_data offset_in_block
350350+ | Ok (block_data, _) -> parse_inode t block_data offset_in_block
349351350352let of_string data =
351353 match parse_superblock data with
352354 | Error e -> Error e
353353- | Ok superblock ->
355355+ | Ok superblock -> (
354356 if superblock.version_major <> 4 then
355355- Error (Printf.sprintf "unsupported version: %d.%d (only 4.0 supported)"
356356- superblock.version_major superblock.version_minor)
357357+ Error
358358+ (Printf.sprintf "unsupported version: %d.%d (only 4.0 supported)"
359359+ superblock.version_major superblock.version_minor)
357360 else
358361 let inode_table_start = get_u64_le data 48 in
359362 let directory_table_start = get_u64_le data 56 in
···363366 let xattr_table_start = get_u64_le data 88 in
364367 match read_id_table data superblock with
365368 | Error e -> Error e
366366- | Ok id_table ->
367367- let t = {
368368- data;
369369- superblock;
370370- id_table;
371371- root_inode = {
372372- inode_type = Directory;
373373- mode = 0o755;
374374- uid_idx = 0;
375375- gid_idx = 0;
376376- mtime = 0;
377377- inode_number = 0;
378378- data = Inode_dir {
379379- start_block = 0;
380380- nlink = 0;
381381- file_size = 0;
382382- offset = 0;
383383- parent_inode = 0;
384384- };
385385- };
386386- inode_table_start;
387387- directory_table_start;
388388- fragment_table_start;
389389- xattr_table_start;
390390- } in
369369+ | Ok id_table -> (
370370+ let t =
371371+ {
372372+ data;
373373+ superblock;
374374+ id_table;
375375+ root_inode =
376376+ {
377377+ inode_type = Directory;
378378+ mode = 0o755;
379379+ uid_idx = 0;
380380+ gid_idx = 0;
381381+ mtime = 0;
382382+ inode_number = 0;
383383+ inode_data =
384384+ Inode_dir
385385+ {
386386+ start_block = 0;
387387+ nlink = 0;
388388+ file_size = 0;
389389+ offset = 0;
390390+ parent_inode = 0;
391391+ };
392392+ };
393393+ inode_table_start;
394394+ directory_table_start;
395395+ fragment_table_start;
396396+ xattr_table_start;
397397+ }
398398+ in
391399 match read_root_inode t with
392400 | Error e -> Error e
393393- | Ok root_inode -> Ok { t with root_inode }
401401+ | Ok root_inode -> Ok { t with root_inode }))
394402395403let of_reader reader =
396404 (* Read entire image into memory for random access *)
···407415408416let superblock t = t.superblock
409417let root t = t.root_inode
410410-411418let inode_type inode = inode.inode_type
412419let inode_mode inode = inode.mode
413420let inode_mtime inode = inode.mtime
414421415422let inode_uid t inode =
416416- if inode.uid_idx < Array.length t.id_table then
417417- t.id_table.(inode.uid_idx)
423423+ if inode.uid_idx < Array.length t.id_table then t.id_table.(inode.uid_idx)
418424 else 0
419425420426let inode_gid t inode =
421421- if inode.gid_idx < Array.length t.id_table then
422422- t.id_table.(inode.gid_idx)
427427+ if inode.gid_idx < Array.length t.id_table then t.id_table.(inode.gid_idx)
423428 else 0
424429425430let inode_size inode =
426426- match inode.data with
431431+ match inode.inode_data with
427432 | Inode_file { file_size; _ } -> file_size
428433 | Inode_dir { file_size; _ } -> Int64.of_int file_size
429434 | Inode_symlink { target; _ } -> Int64.of_int (String.length target)
430435 | _ -> 0L
431436432437let inode_nlink inode =
433433- match inode.data with
438438+ match inode.inode_data with
434439 | Inode_dir { nlink; _ } -> nlink
435440 | Inode_file _ -> 1
436441 | Inode_symlink { nlink; _ } -> nlink
···438443 | Inode_ipc { nlink } -> nlink
439444440445let device_major inode =
441441- match inode.data with
446446+ match inode.inode_data with
442447 | Inode_device { rdev; _ } -> (rdev lsr 8) land 0xfff
443448 | _ -> 0
444449445450let device_minor inode =
446446- match inode.data with
451451+ match inode.inode_data with
447452 | Inode_device { rdev; _ } -> rdev land 0xff
448453 | _ -> 0
449454450455(* Directory reading *)
451456let readdir t inode =
452452- match inode.data with
453453- | Inode_dir { start_block; offset; file_size; _ } ->
457457+ match inode.inode_data with
458458+ | Inode_dir { start_block; offset; file_size; _ } -> (
454459 let dir_table_start = Int64.to_int t.directory_table_start in
455460 let abs_offset = dir_table_start + start_block in
456456- (match read_metadata_block t abs_offset with
457457- | Error e -> Error e
458458- | Ok (block_data, _) ->
459459- (* Parse directory entries *)
460460- let entries = ref [] in
461461- let pos = ref offset in
462462- let remaining = ref file_size in
463463- while !remaining > 0 && !pos + 12 <= String.length block_data do
464464- let count = get_u32_le block_data !pos + 1 in
465465- let _start = get_u32_le block_data (!pos + 4) in
466466- let _inode_number = get_u32_le block_data (!pos + 8) in
467467- pos := !pos + 12;
468468- for _ = 0 to count - 1 do
469469- if !pos + 8 <= String.length block_data then begin
470470- let _offset = get_u16_le block_data !pos in
471471- let _inode_off = get_u16_le block_data (!pos + 2) in
472472- let entry_type = get_u16_le block_data (!pos + 4) in
473473- let name_size = get_u16_le block_data (!pos + 6) + 1 in
474474- if !pos + 8 + name_size <= String.length block_data then begin
475475- let name = String.sub block_data (!pos + 8) name_size in
476476- (match file_type_of_int entry_type with
477477- | Ok file_type ->
478478- entries := {
461461+ match read_metadata_block t abs_offset with
462462+ | Error e -> Error e
463463+ | Ok (block_data, _) ->
464464+ (* Parse directory entries *)
465465+ let entries = ref [] in
466466+ let pos = ref offset in
467467+ let remaining = ref file_size in
468468+ while !remaining > 0 && !pos + 12 <= String.length block_data do
469469+ let count = get_u32_le block_data !pos + 1 in
470470+ let _start = get_u32_le block_data (!pos + 4) in
471471+ let _inode_number = get_u32_le block_data (!pos + 8) in
472472+ pos := !pos + 12;
473473+ for _ = 0 to count - 1 do
474474+ if !pos + 8 <= String.length block_data then begin
475475+ let _offset = get_u16_le block_data !pos in
476476+ let _inode_off = get_u16_le block_data (!pos + 2) in
477477+ let entry_type = get_u16_le block_data (!pos + 4) in
478478+ let name_size = get_u16_le block_data (!pos + 6) + 1 in
479479+ if !pos + 8 + name_size <= String.length block_data then begin
480480+ let name = String.sub block_data (!pos + 8) name_size in
481481+ (match file_type_of_int entry_type with
482482+ | Ok file_type ->
483483+ entries :=
484484+ {
479485 name;
480480- inode = t.root_inode; (* placeholder *)
486486+ inode = t.root_inode;
487487+ (* placeholder *)
481488 file_type;
482482- } :: !entries
483483- | Error _ -> ());
484484- pos := !pos + 8 + name_size
485485- end else
486486- remaining := 0
487487- end else
488488- remaining := 0
489489- done;
490490- remaining := !remaining - (!pos - offset)
491491- done;
492492- Ok (List.rev !entries))
489489+ }
490490+ :: !entries
491491+ | Error _ -> ());
492492+ pos := !pos + 8 + name_size
493493+ end
494494+ else remaining := 0
495495+ end
496496+ else remaining := 0
497497+ done;
498498+ remaining := !remaining - (!pos - offset)
499499+ done;
500500+ Ok (List.rev !entries))
493501 | _ -> Error "not a directory"
494502495503let lookup t dir name =
496504 match readdir t dir with
497505 | Error e -> Error e
498506 | Ok entries ->
499499- Ok (List.find_opt (fun e -> e.name = name) entries
500500- |> Option.map (fun e -> e.inode))
507507+ Ok
508508+ (List.find_opt (fun e -> e.name = name) entries
509509+ |> Option.map (fun e -> e.inode))
501510502511let resolve t path =
503512 let components =
504504- String.split_on_char '/' path
505505- |> List.filter (fun s -> s <> "" && s <> ".")
513513+ String.split_on_char '/' path |> List.filter (fun s -> s <> "" && s <> ".")
506514 in
507515 let rec go inode = function
508516 | [] -> Ok (Some inode)
509517 | ".." :: rest ->
510518 (* TODO: handle parent properly *)
511519 go inode rest
512512- | name :: rest ->
520520+ | name :: rest -> (
513521 match lookup t inode name with
514522 | Error e -> Error e
515523 | Ok None -> Ok None
516516- | Ok (Some child) -> go child rest
524524+ | Ok (Some child) -> go child rest)
517525 in
518526 go t.root_inode components
519527520528(* File reading *)
521529let read_file t inode =
522522- match inode.data with
530530+ match inode.inode_data with
523531 | Inode_file { start_block; file_size; fragment; offset; _ } ->
524532 if fragment >= 0 then
525533 (* File is in fragment *)
526534 Error "fragment reading not yet implemented"
527527- else if file_size = 0L then
528528- Ok ""
535535+ else if file_size = 0L then Ok ""
529536 else
530537 (* Read from data blocks *)
531538 let abs_offset = Int64.to_int start_block in
···537544 if abs_offset + size <= String.length t.data then
538545 (* Try reading as uncompressed first *)
539546 let header = get_u16_le t.data abs_offset in
540540- let compressed = (header land 0x8000) = 0 in
547547+ let compressed = header land 0x8000 = 0 in
541548 let block_size = header land 0x7fff in
542542- if not compressed && abs_offset + 2 + block_size <= String.length t.data then
543543- Ok (String.sub t.data (abs_offset + 2) (min block_size size))
549549+ if
550550+ (not compressed)
551551+ && abs_offset + 2 + block_size <= String.length t.data
552552+ then Ok (String.sub t.data (abs_offset + 2) (min block_size size))
544553 else if compressed then
545554 match read_metadata_block t abs_offset with
546555 | Error e -> Error e
547547- | Ok (data, _) -> Ok (String.sub data offset (min (String.length data - offset) size))
548548- else
549549- Error "file extends beyond image"
550550- else
551551- Error "file extends beyond image"
556556+ | Ok (data, _) ->
557557+ Ok
558558+ (String.sub data offset
559559+ (min (String.length data - offset) size))
560560+ else Error "file extends beyond image"
561561+ else Error "file extends beyond image"
552562 | _ -> Error "not a regular file"
553563554564let read_link _t inode =
555555- match inode.data with
565565+ match inode.inode_data with
556566 | Inode_symlink { target; _ } -> Ok target
557567 | _ -> Error "not a symbolic link"
558568559569(* Extended attributes *)
560560-let has_xattrs t =
561561- t.xattr_table_start <> 0xffffffffffffffffL
570570+let has_xattrs t = t.xattr_table_start <> 0xffffffffffffffffL
562571563572let get_xattr _t _inode _name =
564573 (* TODO: implement xattr reading *)
···572581let fold f t init =
573582 let rec traverse path inode acc =
574583 let acc = f path inode acc in
575575- match inode.data with
576576- | Inode_dir _ ->
577577- (match readdir t inode with
578578- | Error _ -> acc
579579- | Ok entries ->
580580- List.fold_left (fun acc entry ->
581581- let child_path =
582582- if path = "/" then "/" ^ entry.name
583583- else path ^ "/" ^ entry.name
584584- in
585585- traverse child_path entry.inode acc
586586- ) acc entries)
584584+ match inode.inode_data with
585585+ | Inode_dir _ -> (
586586+ match readdir t inode with
587587+ | Error _ -> acc
588588+ | Ok entries ->
589589+ List.fold_left
590590+ (fun acc entry ->
591591+ let child_path =
592592+ if path = "/" then "/" ^ entry.name
593593+ else path ^ "/" ^ entry.name
594594+ in
595595+ traverse child_path entry.inode acc)
596596+ acc entries)
587597 | _ -> acc
588598 in
589599 Ok (traverse "/" t.root_inode init)
+28-36
lib/squashfs.mli
···1616 {2 References}
17171818 - {{:https://dr-emann.github.io/squashfs/} SquashFS specification}
1919- - {{:https://www.kernel.org/doc/html/latest/filesystems/squashfs.html}
2020- Linux kernel documentation} *)
1919+ - {{:https://www.kernel.org/doc/html/latest/filesystems/squashfs.html} Linux
2020+ kernel documentation} *)
21212222(** {1 Types} *)
2323···2727 | Lzo
2828 | Xz
2929 | Lz4
3030- | Zstd
3131-(** Compression algorithms supported by SquashFS. *)
3030+ | Zstd (** Compression algorithms supported by SquashFS. *)
32313332type t
3433(** A SquashFS filesystem image. *)
···4342 | Block_device
4443 | Char_device
4544 | Fifo
4646- | Socket
4747-(** File types in SquashFS. *)
4545+ | Socket (** File types in SquashFS. *)
48464949-type entry = {
5050- name : string;
5151- inode : inode;
5252- file_type : file_type;
5353-}
4747+type entry = { name : string; inode : inode; file_type : file_type }
5448(** A directory entry. *)
55495650(** {1 Superblock Information} *)
···8074(** [of_string data] opens a SquashFS image from a string. *)
81758276val of_reader : Bytesrw.Bytes.Reader.t -> (t, string) result
8383-(** [of_reader reader] opens a SquashFS image from a byte reader.
8484- The reader must support random access (seeking). *)
7777+(** [of_reader reader] opens a SquashFS image from a byte reader. The reader
7878+ must support random access (seeking). *)
85798680(** {1 Navigation} *)
8781···10498(** [inode_mtime inode] returns the modification time (Unix timestamp). *)
10599106100val inode_size : inode -> int64
107107-(** [inode_size inode] returns the file size in bytes.
108108- For directories, this is the uncompressed directory listing size. *)
101101+(** [inode_size inode] returns the file size in bytes. For directories, this is
102102+ the uncompressed directory listing size. *)
109103110104val inode_nlink : inode -> int
111105(** [inode_nlink inode] returns the hard link count. *)
···113107(** {1 Directory Operations} *)
114108115109val readdir : t -> inode -> (entry list, string) result
116116-(** [readdir t inode] lists entries in a directory.
117117- Returns [Error] if [inode] is not a directory. *)
110110+(** [readdir t inode] lists entries in a directory. Returns [Error] if [inode]
111111+ is not a directory. *)
118112119113val lookup : t -> inode -> string -> (inode option, string) result
120120-(** [lookup t dir name] looks up an entry by name in a directory.
121121- Returns [None] if not found, [Error] if [dir] is not a directory. *)
114114+(** [lookup t dir name] looks up an entry by name in a directory. Returns [None]
115115+ if not found, [Error] if [dir] is not a directory. *)
122116123117val resolve : t -> string -> (inode option, string) result
124124-(** [resolve t path] resolves an absolute path to an inode.
125125- Path components are separated by [/]. Leading [/] is optional.
126126- Returns [None] if the path does not exist. *)
118118+(** [resolve t path] resolves an absolute path to an inode. Path components are
119119+ separated by [/]. Leading [/] is optional. Returns [None] if the path does
120120+ not exist. *)
127121128122(** {1 File Operations} *)
129123130124val read_file : t -> inode -> (string, string) result
131131-(** [read_file t inode] reads the entire contents of a regular file.
132132- Returns [Error] if [inode] is not a regular file. *)
125125+(** [read_file t inode] reads the entire contents of a regular file. Returns
126126+ [Error] if [inode] is not a regular file. *)
133127134128val read_link : t -> inode -> (string, string) result
135135-(** [read_link t inode] reads the target of a symbolic link.
136136- Returns [Error] if [inode] is not a symlink. *)
129129+(** [read_link t inode] reads the target of a symbolic link. Returns [Error] if
130130+ [inode] is not a symlink. *)
137131138132(** {1 Device Operations} *)
139133140134val device_major : inode -> int
141141-(** [device_major inode] returns the major device number.
142142- Only valid for block/char device inodes. *)
135135+(** [device_major inode] returns the major device number. Only valid for
136136+ block/char device inodes. *)
143137144138val device_minor : inode -> int
145145-(** [device_minor inode] returns the minor device number.
146146- Only valid for block/char device inodes. *)
139139+(** [device_minor inode] returns the minor device number. Only valid for
140140+ block/char device inodes. *)
147141148142(** {1 Extended Attributes} *)
149143···151145(** [has_xattrs t] returns [true] if the filesystem has extended attributes. *)
152146153147val get_xattr : t -> inode -> string -> (string option, string) result
154154-(** [get_xattr t inode name] gets an extended attribute value.
155155- Returns [None] if the attribute doesn't exist. *)
148148+(** [get_xattr t inode name] gets an extended attribute value. Returns [None] if
149149+ the attribute doesn't exist. *)
156150157151val list_xattrs : t -> inode -> (string list, string) result
158152(** [list_xattrs t inode] lists all extended attribute names. *)
159153160154(** {1 Filesystem Traversal} *)
161155162162-val fold :
163163- (string -> inode -> 'a -> 'a) -> t -> 'a -> ('a, string) result
156156+val fold : (string -> inode -> 'a -> 'a) -> t -> 'a -> ('a, string) result
164157(** [fold f t init] traverses the entire filesystem depth-first.
165158 [f path inode acc] is called for each entry with its full path. *)
166159167167-val iter :
168168- (string -> inode -> unit) -> t -> (unit, string) result
160160+val iter : (string -> inode -> unit) -> t -> (unit, string) result
169161(** [iter f t] iterates over all entries in the filesystem. *)
170162171163(** {1 Pretty Printing} *)