SquashFS compressed filesystem reader in pure OCaml
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(squashfs): migrate binary codecs to Wire.Codec

Replace manual binary helpers (get_u8/get_u16_le/get_u32_le/get_i32_le/
get_u64_le in reader; set_u8/set_u16_le/set_u32_le/set_u64_le in writer)
with Wire.Codec definitions for all multi-field on-disk structures:
superblock (96 bytes), inode header (16 bytes), inode bodies (directory,
file, device, IPC, symlink), directory header (12 bytes), and directory
entry header (8 bytes).

Keep minimal get_u16_le/get_u32_le/get_u64_le in reader and set_u16_le
in writer for single-field reads at dynamic offsets (metadata block
headers, ID table entries).

+861 -284
+1 -1
lib/dune
··· 1 1 (library 2 2 (name squashfs) 3 3 (public_name squashfs) 4 - (libraries bytesrw bytesrw.zlib bytesrw.zstd eio)) 4 + (libraries bytesrw bytesrw.zlib bytesrw.zstd eio wire))
+364 -144
lib/squashfs.ml
··· 158 158 xattr_table_start : int64; 159 159 } 160 160 161 - (* Binary reading helpers *) 162 - let get_u8 s off = Char.code (String.get s off) 163 - let get_u16_le s off = get_u8 s off lor (get_u8 s (off + 1) lsl 8) 161 + (* Binary reading helpers — kept for single-field reads at dynamic offsets 162 + (metadata block headers, ID table entries) *) 163 + let get_u16_le s off = 164 + let b0 = Char.code (String.get s off) in 165 + let b1 = Char.code (String.get s (off + 1)) in 166 + b0 lor (b1 lsl 8) 164 167 165 168 let get_u32_le s off = 166 169 Int32.to_int ··· 168 171 (Int32.of_int (get_u16_le s off)) 169 172 (Int32.shift_left (Int32.of_int (get_u16_le s (off + 2))) 16)) 170 173 171 - let get_i32_le s off = 172 - let v = get_u32_le s off in 173 - if v >= 0x80000000 then v - 0x100000000 else v 174 - 175 174 let get_u64_le s off = 176 175 Int64.logor 177 176 (Int64.of_int (get_u32_le s off)) 178 177 (Int64.shift_left (Int64.of_int (get_u32_le s (off + 4))) 32) 179 178 179 + (* --- Wire codecs for on-disk structures --- *) 180 + 181 + (* Raw superblock: 96 bytes, includes table offsets not in public [superblock] *) 182 + type raw_superblock = { 183 + sb_magic : int; 184 + sb_inode_count : int; 185 + sb_modification_time : int; 186 + sb_block_size : int; 187 + sb_fragment_entry_count : int; 188 + sb_compression_id : int; 189 + sb_block_log : int; 190 + sb_flags : int; 191 + sb_id_count : int; 192 + sb_version_major : int; 193 + sb_version_minor : int; 194 + sb_root_inode_ref : int64; 195 + sb_bytes_used : int64; 196 + sb_id_table_start : int64; 197 + sb_xattr_table_start : int64; 198 + sb_inode_table_start : int64; 199 + sb_directory_table_start : int64; 200 + sb_fragment_table_start : int64; 201 + sb_export_table_start : int64; 202 + } 203 + 204 + let superblock_codec = 205 + let open Wire.Codec in 206 + record "SquashfsSuperblock" 207 + (fun 208 + sb_magic 209 + sb_inode_count 210 + sb_modification_time 211 + sb_block_size 212 + sb_fragment_entry_count 213 + sb_compression_id 214 + sb_block_log 215 + sb_flags 216 + sb_id_count 217 + sb_version_major 218 + sb_version_minor 219 + sb_root_inode_ref 220 + sb_bytes_used 221 + sb_id_table_start 222 + sb_xattr_table_start 223 + sb_inode_table_start 224 + sb_directory_table_start 225 + sb_fragment_table_start 226 + sb_export_table_start 227 + -> 228 + { 229 + sb_magic; 230 + sb_inode_count; 231 + sb_modification_time; 232 + sb_block_size; 233 + sb_fragment_entry_count; 234 + sb_compression_id; 235 + sb_block_log; 236 + sb_flags; 237 + sb_id_count; 238 + sb_version_major; 239 + sb_version_minor; 240 + sb_root_inode_ref; 241 + sb_bytes_used; 242 + sb_id_table_start; 243 + sb_xattr_table_start; 244 + sb_inode_table_start; 245 + sb_directory_table_start; 246 + sb_fragment_table_start; 247 + sb_export_table_start; 248 + }) 249 + |+ field "magic" Wire.uint32 (fun t -> t.sb_magic) 250 + |+ field "inode_count" Wire.uint32 (fun t -> t.sb_inode_count) 251 + |+ field "modification_time" Wire.uint32 (fun t -> t.sb_modification_time) 252 + |+ field "block_size" Wire.uint32 (fun t -> t.sb_block_size) 253 + |+ field "fragment_entry_count" Wire.uint32 (fun t -> 254 + t.sb_fragment_entry_count) 255 + |+ field "compression_id" Wire.uint16 (fun t -> t.sb_compression_id) 256 + |+ field "block_log" Wire.uint16 (fun t -> t.sb_block_log) 257 + |+ field "flags" Wire.uint16 (fun t -> t.sb_flags) 258 + |+ field "id_count" Wire.uint16 (fun t -> t.sb_id_count) 259 + |+ field "version_major" Wire.uint16 (fun t -> t.sb_version_major) 260 + |+ field "version_minor" Wire.uint16 (fun t -> t.sb_version_minor) 261 + |+ field "root_inode_ref" Wire.uint64 (fun t -> t.sb_root_inode_ref) 262 + |+ field "bytes_used" Wire.uint64 (fun t -> t.sb_bytes_used) 263 + |+ field "id_table_start" Wire.uint64 (fun t -> t.sb_id_table_start) 264 + |+ field "xattr_table_start" Wire.uint64 (fun t -> t.sb_xattr_table_start) 265 + |+ field "inode_table_start" Wire.uint64 (fun t -> t.sb_inode_table_start) 266 + |+ field "directory_table_start" Wire.uint64 (fun t -> 267 + t.sb_directory_table_start) 268 + |+ field "fragment_table_start" Wire.uint64 (fun t -> 269 + t.sb_fragment_table_start) 270 + |+ field "export_table_start" Wire.uint64 (fun t -> t.sb_export_table_start) 271 + |> seal 272 + 273 + let superblock_struct_ = Wire.Codec.to_struct superblock_codec 274 + 275 + (* Inode common header: 16 bytes *) 276 + type inode_header = { 277 + ih_type_and_mode : int; 278 + ih_mode : int; 279 + ih_uid_idx : int; 280 + ih_gid_idx : int; 281 + ih_mtime : int; 282 + ih_inode_number : int; 283 + } 284 + 285 + let inode_header_codec = 286 + let open Wire.Codec in 287 + record "SquashfsInodeHeader" 288 + (fun 289 + ih_type_and_mode ih_mode ih_uid_idx ih_gid_idx ih_mtime ih_inode_number -> 290 + { 291 + ih_type_and_mode; 292 + ih_mode; 293 + ih_uid_idx; 294 + ih_gid_idx; 295 + ih_mtime; 296 + ih_inode_number; 297 + }) 298 + |+ field "type_and_mode" Wire.uint16 (fun t -> t.ih_type_and_mode) 299 + |+ field "mode" Wire.uint16 (fun t -> t.ih_mode) 300 + |+ field "uid_idx" Wire.uint16 (fun t -> t.ih_uid_idx) 301 + |+ field "gid_idx" Wire.uint16 (fun t -> t.ih_gid_idx) 302 + |+ field "mtime" Wire.uint32 (fun t -> t.ih_mtime) 303 + |+ field "inode_number" Wire.uint32 (fun t -> t.ih_inode_number) 304 + |> seal 305 + 306 + (* Directory inode body: 16 bytes *) 307 + type dir_body = { 308 + db_start_block : int; 309 + db_nlink : int; 310 + db_file_size : int; 311 + db_offset : int; 312 + db_parent_inode : int; 313 + } 314 + 315 + let dir_body_codec = 316 + let open Wire.Codec in 317 + record "SquashfsDirBody" 318 + (fun db_start_block db_nlink db_file_size db_offset db_parent_inode -> 319 + { db_start_block; db_nlink; db_file_size; db_offset; db_parent_inode }) 320 + |+ field "start_block" Wire.uint32 (fun t -> t.db_start_block) 321 + |+ field "nlink" Wire.uint32 (fun t -> t.db_nlink) 322 + |+ field "file_size" Wire.uint16 (fun t -> t.db_file_size) 323 + |+ field "offset" Wire.uint16 (fun t -> t.db_offset) 324 + |+ field "parent_inode" Wire.uint32 (fun t -> t.db_parent_inode) 325 + |> seal 326 + 327 + (* File inode body: 16 bytes *) 328 + type file_body = { 329 + fb_start_block : int; 330 + fb_fragment : int; 331 + fb_offset : int; 332 + fb_file_size : int; 333 + } 334 + 335 + let file_body_codec = 336 + let open Wire.Codec in 337 + record "SquashfsFileBody" 338 + (fun fb_start_block fb_fragment fb_offset fb_file_size -> 339 + { fb_start_block; fb_fragment; fb_offset; fb_file_size }) 340 + |+ field "start_block" Wire.uint32 (fun t -> t.fb_start_block) 341 + |+ field "fragment" Wire.uint32 (fun t -> t.fb_fragment) 342 + |+ field "offset" Wire.uint32 (fun t -> t.fb_offset) 343 + |+ field "file_size" Wire.uint32 (fun t -> t.fb_file_size) 344 + |> seal 345 + 346 + (* Device inode body: 8 bytes *) 347 + type device_body = { devb_nlink : int; devb_rdev : int } 348 + 349 + let device_body_codec = 350 + let open Wire.Codec in 351 + record "SquashfsDeviceBody" (fun devb_nlink devb_rdev -> 352 + { devb_nlink; devb_rdev }) 353 + |+ field "nlink" Wire.uint32 (fun t -> t.devb_nlink) 354 + |+ field "rdev" Wire.uint32 (fun t -> t.devb_rdev) 355 + |> seal 356 + 357 + (* IPC inode body: 4 bytes *) 358 + type ipc_body = { ipcb_nlink : int } 359 + 360 + let ipc_body_codec = 361 + let open Wire.Codec in 362 + record "SquashfsIpcBody" (fun ipcb_nlink -> { ipcb_nlink }) 363 + |+ field "nlink" Wire.uint32 (fun t -> t.ipcb_nlink) 364 + |> seal 365 + 366 + (* Symlink inode body (fixed part): 8 bytes, then variable target *) 367 + type symlink_body = { slb_nlink : int; slb_target_size : int } 368 + 369 + let symlink_body_codec = 370 + let open Wire.Codec in 371 + record "SquashfsSymlinkBody" (fun slb_nlink slb_target_size -> 372 + { slb_nlink; slb_target_size }) 373 + |+ field "nlink" Wire.uint32 (fun t -> t.slb_nlink) 374 + |+ field "target_size" Wire.uint32 (fun t -> t.slb_target_size) 375 + |> seal 376 + 377 + (* Directory header: 12 bytes *) 378 + type dir_header = { 379 + dh_count : int; 380 + dh_start_block : int; 381 + dh_inode_number : int; 382 + } 383 + 384 + let dir_header_codec = 385 + let open Wire.Codec in 386 + record "SquashfsDirHeader" (fun dh_count dh_start_block dh_inode_number -> 387 + { dh_count; dh_start_block; dh_inode_number }) 388 + |+ field "count" Wire.uint32 (fun t -> t.dh_count) 389 + |+ field "start_block" Wire.uint32 (fun t -> t.dh_start_block) 390 + |+ field "inode_number" Wire.uint32 (fun t -> t.dh_inode_number) 391 + |> seal 392 + 393 + (* Directory entry header: 8 bytes, followed by variable name *) 394 + type dir_entry_header = { 395 + de_inode_offset : int; 396 + de_inode_number : int; 397 + de_entry_type : int; 398 + de_name_size : int; 399 + } 400 + 401 + let dir_entry_header_codec = 402 + let open Wire.Codec in 403 + record "SquashfsDirEntryHeader" 404 + (fun de_inode_offset de_inode_number de_entry_type de_name_size -> 405 + { de_inode_offset; de_inode_number; de_entry_type; de_name_size }) 406 + |+ field "inode_offset" Wire.uint16 (fun t -> t.de_inode_offset) 407 + |+ field "inode_number" Wire.uint16 (fun t -> t.de_inode_number) 408 + |+ field "entry_type" Wire.uint16 (fun t -> t.de_entry_type) 409 + |+ field "name_size" Wire.uint16 (fun t -> t.de_name_size) 410 + |> seal 411 + 180 412 (* Decompress a metadata block using bytesrw.zlib *) 181 413 let decompress_zlib data max_output_size = 182 414 try ··· 242 474 if String.length data < superblock_size then 243 475 Error "data too short for superblock" 244 476 else 245 - let magic_val = get_u32_le data 0 in 246 - if Int32.of_int magic_val <> magic then 477 + let raw = 478 + Wire.Codec.decode superblock_codec (Bytes.unsafe_of_string data) 0 479 + in 480 + if Int32.of_int raw.sb_magic <> magic then 247 481 Error 248 482 (Printf.sprintf "invalid magic: expected 0x%lx, got 0x%x" magic 249 - magic_val) 483 + raw.sb_magic) 250 484 else 251 - let compression_id = get_u16_le data 20 in 252 - match compression_of_int compression_id with 485 + match compression_of_int raw.sb_compression_id with 253 486 | Error e -> Error e 254 487 | Ok compression -> 255 - let block_size = get_u32_le data 12 in 256 488 (* Security: validate block_size to prevent decompression bombs *) 257 - if block_size <= 0 || block_size > max_block_size then 489 + if raw.sb_block_size <= 0 || raw.sb_block_size > max_block_size then 258 490 Error 259 - (Printf.sprintf "invalid block_size %d (must be 1-%d)" block_size 260 - max_block_size) 491 + (Printf.sprintf "invalid block_size %d (must be 1-%d)" 492 + raw.sb_block_size max_block_size) 493 + else if 494 + (* Security: CVE-2024-46744 - validate inode_count *) 495 + raw.sb_inode_count < 0 || raw.sb_inode_count > max_inode_count 496 + then 497 + Error 498 + (Printf.sprintf "invalid inode_count %d (must be 0-%d)" 499 + raw.sb_inode_count max_inode_count) 500 + else if 501 + (* Security: validate id_count *) 502 + raw.sb_id_count > max_id_count 503 + then 504 + Error 505 + (Printf.sprintf "invalid id_count %d (must be 0-%d)" 506 + raw.sb_id_count max_id_count) 261 507 else 262 - let inode_count = get_u32_le data 4 in 263 - (* Security: CVE-2024-46744 - validate inode_count to prevent 264 - memory exhaustion and uninitialized memory access *) 265 - if inode_count < 0 || inode_count > max_inode_count then 266 - Error 267 - (Printf.sprintf "invalid inode_count %d (must be 0-%d)" 268 - inode_count max_inode_count) 269 - else 270 - let id_count = get_u16_le data 26 in 271 - (* Security: validate id_count *) 272 - if id_count > max_id_count then 273 - Error 274 - (Printf.sprintf "invalid id_count %d (must be 0-%d)" id_count 275 - max_id_count) 276 - else 277 - Ok 278 - { 279 - inode_count; 280 - modification_time = get_u32_le data 8; 281 - block_size; 282 - fragment_entry_count = get_u32_le data 16; 283 - compression; 284 - block_log = get_u16_le data 22; 285 - flags = get_u16_le data 24; 286 - id_count; 287 - version_major = get_u16_le data 28; 288 - version_minor = get_u16_le data 30; 289 - root_inode_ref = get_u64_le data 32; 290 - bytes_used = get_u64_le data 40; 291 - } 508 + Ok 509 + { 510 + inode_count = raw.sb_inode_count; 511 + modification_time = raw.sb_modification_time; 512 + block_size = raw.sb_block_size; 513 + fragment_entry_count = raw.sb_fragment_entry_count; 514 + compression; 515 + block_log = raw.sb_block_log; 516 + flags = raw.sb_flags; 517 + id_count = raw.sb_id_count; 518 + version_major = raw.sb_version_major; 519 + version_minor = raw.sb_version_minor; 520 + root_inode_ref = raw.sb_root_inode_ref; 521 + bytes_used = raw.sb_bytes_used; 522 + } 292 523 293 524 (* Parse an inode from metadata *) 525 + let inode_header_size = Wire.Codec.wire_size inode_header_codec 526 + let dir_body_size = Wire.Codec.wire_size dir_body_codec 527 + let file_body_size = Wire.Codec.wire_size file_body_codec 528 + let symlink_body_size = Wire.Codec.wire_size symlink_body_codec 529 + let device_body_size = Wire.Codec.wire_size device_body_codec 530 + let ipc_body_size = Wire.Codec.wire_size ipc_body_codec 531 + 294 532 let parse_inode _t data offset = 295 - if offset + 16 > String.length data then Error "inode header truncated" 533 + let data_len = String.length data in 534 + if offset + inode_header_size > data_len then Error "inode header truncated" 296 535 else 297 - let type_and_mode = get_u16_le data offset in 298 - let inode_type_raw = type_and_mode land 0xf in 536 + let buf = Bytes.unsafe_of_string data in 537 + let hdr = Wire.Codec.decode inode_header_codec buf offset in 538 + let inode_type_raw = hdr.ih_type_and_mode land 0xf in 299 539 match file_type_of_int inode_type_raw with 300 540 | Error e -> Error e 301 541 | Ok inode_type -> ( 542 + let body_off = offset + inode_header_size in 302 543 try 303 - let mode = get_u16_le data (offset + 2) in 304 - let uid_idx = get_u16_le data (offset + 4) in 305 - let gid_idx = get_u16_le data (offset + 6) in 306 - let mtime = get_u32_le data (offset + 8) in 307 - let inode_number = get_u32_le data (offset + 12) in 308 - let inode_data, _next_offset = 544 + let inode_data = 309 545 match inode_type with 310 546 | Directory -> 311 - (* Security: bounds check for directory inode fields *) 312 - if offset + 32 > String.length data then 547 + if body_off + dir_body_size > data_len then 313 548 failwith "directory inode truncated"; 314 - let start_block = get_u32_le data (offset + 16) in 315 - let nlink = get_u32_le data (offset + 20) in 316 - let file_size = get_u16_le data (offset + 24) in 317 - let off = get_u16_le data (offset + 26) in 318 - let parent = get_u32_le data (offset + 28) in 319 - ( Inode_dir 320 - { 321 - start_block; 322 - nlink; 323 - file_size = file_size + 3; 324 - (* size includes . and .. *) 325 - offset = off; 326 - parent_inode = parent; 327 - }, 328 - offset + 32 ) 549 + let b = Wire.Codec.decode dir_body_codec buf body_off in 550 + Inode_dir 551 + { 552 + start_block = b.db_start_block; 553 + nlink = b.db_nlink; 554 + file_size = b.db_file_size + 3; 555 + offset = b.db_offset; 556 + parent_inode = b.db_parent_inode; 557 + } 329 558 | Regular -> 330 - (* Security: bounds check for regular file inode fields *) 331 - if offset + 32 > String.length data then 559 + if body_off + file_body_size > data_len then 332 560 failwith "regular file inode truncated"; 333 - let start_block = 334 - Int64.of_int (get_u32_le data (offset + 16)) 561 + let b = Wire.Codec.decode file_body_codec buf body_off in 562 + (* Interpret fragment as signed: uint32 0xFFFFFFFF → -1 *) 563 + let fragment = 564 + if b.fb_fragment >= 0x80000000 then 565 + b.fb_fragment - 0x100000000 566 + else b.fb_fragment 335 567 in 336 - let fragment = get_i32_le data (offset + 20) in 337 - let off = get_u32_le data (offset + 24) in 338 - let file_size = Int64.of_int (get_u32_le data (offset + 28)) in 339 - ( Inode_file 340 - { 341 - start_block; 342 - fragment; 343 - offset = off; 344 - file_size; 345 - block_sizes = [||]; 346 - (* TODO: parse block list *) 347 - }, 348 - offset + 32 ) 568 + Inode_file 569 + { 570 + start_block = Int64.of_int b.fb_start_block; 571 + fragment; 572 + offset = b.fb_offset; 573 + file_size = Int64.of_int b.fb_file_size; 574 + block_sizes = [||]; 575 + } 349 576 | Symlink -> 350 - (* Security: bounds check for symlink inode header *) 351 - if offset + 24 > String.length data then 577 + if body_off + symlink_body_size > data_len then 352 578 failwith "symlink inode truncated"; 353 - let nlink = get_u32_le data (offset + 16) in 354 - let target_size = get_u32_le data (offset + 20) in 355 - (* Security: validate symlink target size *) 579 + let b = Wire.Codec.decode symlink_body_codec buf body_off in 580 + let target_size = b.slb_target_size in 356 581 if target_size > max_symlink_target_size then 357 582 failwith 358 583 (Printf.sprintf "symlink target too large: %d" target_size) 359 - else if offset + 24 + target_size > String.length data then 360 - failwith "symlink target extends beyond data" 584 + else if body_off + symlink_body_size + target_size > data_len 585 + then failwith "symlink target extends beyond data" 361 586 else 362 - let target = String.sub data (offset + 24) target_size in 363 - (Inode_symlink { nlink; target }, offset + 24 + target_size) 587 + let target = 588 + String.sub data (body_off + symlink_body_size) target_size 589 + in 590 + Inode_symlink { nlink = b.slb_nlink; target } 364 591 | Block_device | Char_device -> 365 - (* Security: bounds check for device inode fields *) 366 - if offset + 24 > String.length data then 592 + if body_off + device_body_size > data_len then 367 593 failwith "device inode truncated"; 368 - let nlink = get_u32_le data (offset + 16) in 369 - let rdev = get_u32_le data (offset + 20) in 370 - (Inode_device { nlink; rdev }, offset + 24) 594 + let b = Wire.Codec.decode device_body_codec buf body_off in 595 + Inode_device { nlink = b.devb_nlink; rdev = b.devb_rdev } 371 596 | Fifo | Socket -> 372 - (* Security: bounds check for IPC inode fields *) 373 - if offset + 20 > String.length data then 597 + if body_off + ipc_body_size > data_len then 374 598 failwith "IPC inode truncated"; 375 - let nlink = get_u32_le data (offset + 16) in 376 - (Inode_ipc { nlink }, offset + 20) 599 + let b = Wire.Codec.decode ipc_body_codec buf body_off in 600 + Inode_ipc { nlink = b.ipcb_nlink } 377 601 in 378 602 Ok 379 603 { 380 604 inode_type; 381 - mode = mode land 0o7777; 382 - uid_idx; 383 - gid_idx; 384 - mtime; 385 - inode_number; 605 + mode = hdr.ih_mode land 0o7777; 606 + uid_idx = hdr.ih_uid_idx; 607 + gid_idx = hdr.ih_gid_idx; 608 + mtime = hdr.ih_mtime; 609 + inode_number = hdr.ih_inode_number; 386 610 inode_data; 387 611 } 388 612 with Failure msg -> Error msg) ··· 447 671 (Printf.sprintf "unsupported version: %d.%d (only 4.0 supported)" 448 672 superblock.version_major superblock.version_minor) 449 673 else 450 - (* SquashFS superblock layout per kernel squashfs_fs.h: 451 - offset 48: id_table_start (used by read_id_table) 452 - offset 56: xattr_id_table_start 453 - offset 64: inode_table_start 454 - offset 72: directory_table_start 455 - offset 80: fragment_table_start 456 - offset 88: lookup_table_start (export) *) 457 - let _id_table_start = get_u64_le data 48 in 458 - let xattr_table_start = get_u64_le data 56 in 459 - let inode_table_start = get_u64_le data 64 in 460 - let directory_table_start = get_u64_le data 72 in 461 - let fragment_table_start = get_u64_le data 80 in 462 - let _export_table_start = get_u64_le data 88 in 674 + (* Decode the full raw superblock to get table offsets *) 675 + let raw = 676 + Wire.Codec.decode superblock_codec (Bytes.unsafe_of_string data) 0 677 + in 463 678 match read_id_table data superblock with 464 679 | Error e -> Error e 465 680 | Ok id_table -> ( ··· 486 701 parent_inode = 0; 487 702 }; 488 703 }; 489 - inode_table_start; 490 - directory_table_start; 491 - fragment_table_start; 492 - xattr_table_start; 704 + inode_table_start = raw.sb_inode_table_start; 705 + directory_table_start = raw.sb_directory_table_start; 706 + fragment_table_start = raw.sb_fragment_table_start; 707 + xattr_table_start = raw.sb_xattr_table_start; 493 708 } 494 709 in 495 710 match read_root_inode t with ··· 549 764 | _ -> 0 550 765 551 766 (* Directory reading *) 767 + let dir_header_size = Wire.Codec.wire_size dir_header_codec 768 + let dir_entry_header_size = Wire.Codec.wire_size dir_entry_header_codec 769 + 552 770 let readdir t inode = 553 771 match inode.inode_data with 554 772 | Inode_dir { start_block; offset; file_size; _ } -> ( ··· 557 775 match read_metadata_block t abs_offset with 558 776 | Error e -> Error e 559 777 | Ok (block_data, _) -> 560 - (* Parse directory entries *) 778 + let buf = Bytes.unsafe_of_string block_data in 779 + let block_len = String.length block_data in 561 780 let entries = ref [] in 562 781 let pos = ref offset in 563 782 let remaining = ref file_size in 564 - while !remaining > 0 && !pos + 12 <= String.length block_data do 565 - let count = get_u32_le block_data !pos + 1 in 566 - let _start = get_u32_le block_data (!pos + 4) in 567 - let _inode_number = get_u32_le block_data (!pos + 8) in 568 - pos := !pos + 12; 783 + while !remaining > 0 && !pos + dir_header_size <= block_len do 784 + let dh = Wire.Codec.decode dir_header_codec buf !pos in 785 + let count = dh.dh_count + 1 in 786 + pos := !pos + dir_header_size; 569 787 for _ = 0 to count - 1 do 570 - if !pos + 8 <= String.length block_data then begin 571 - let _offset = get_u16_le block_data !pos in 572 - let _inode_off = get_u16_le block_data (!pos + 2) in 573 - let entry_type = get_u16_le block_data (!pos + 4) in 574 - let name_size = get_u16_le block_data (!pos + 6) + 1 in 575 - if !pos + 8 + name_size <= String.length block_data then begin 576 - let name = String.sub block_data (!pos + 8) name_size in 577 - (match file_type_of_int entry_type with 788 + if !pos + dir_entry_header_size <= block_len then begin 789 + let de = Wire.Codec.decode dir_entry_header_codec buf !pos in 790 + let name_size = de.de_name_size + 1 in 791 + if !pos + dir_entry_header_size + name_size <= block_len then begin 792 + let name = 793 + String.sub block_data 794 + (!pos + dir_entry_header_size) 795 + name_size 796 + in 797 + (match file_type_of_int de.de_entry_type with 578 798 | Ok file_type -> 579 799 entries := 580 800 { ··· 585 805 } 586 806 :: !entries 587 807 | Error _ -> ()); 588 - pos := !pos + 8 + name_size 808 + pos := !pos + dir_entry_header_size + name_size 589 809 end 590 810 else remaining := 0 591 811 end
+31
lib/squashfs.mli
··· 88 88 val superblock : t -> superblock 89 89 (** [superblock t] returns the superblock information. *) 90 90 91 + (** {2 Wire Codec} *) 92 + 93 + type raw_superblock = { 94 + sb_magic : int; 95 + sb_inode_count : int; 96 + sb_modification_time : int; 97 + sb_block_size : int; 98 + sb_fragment_entry_count : int; 99 + sb_compression_id : int; 100 + sb_block_log : int; 101 + sb_flags : int; 102 + sb_id_count : int; 103 + sb_version_major : int; 104 + sb_version_minor : int; 105 + sb_root_inode_ref : int64; 106 + sb_bytes_used : int64; 107 + sb_id_table_start : int64; 108 + sb_xattr_table_start : int64; 109 + sb_inode_table_start : int64; 110 + sb_directory_table_start : int64; 111 + sb_fragment_table_start : int64; 112 + sb_export_table_start : int64; 113 + } 114 + (** Raw 96-byte on-disk superblock, including table offsets. *) 115 + 116 + val superblock_codec : raw_superblock Wire.Codec.t 117 + (** Wire codec for the 96-byte little-endian superblock. *) 118 + 119 + val superblock_struct_ : Wire.struct_ 120 + (** Wire struct definition for the superblock. *) 121 + 91 122 (** {1 Opening and Closing} *) 92 123 93 124 val of_string : string -> (t, string) result
+397 -138
lib/squashfs_writer.ml
··· 74 74 mutable uid_gid_table : (int * int) list; 75 75 } 76 76 77 - (* Binary writing helpers *) 78 - let set_u8 buf off v = Bytes.set buf off (Char.chr (v land 0xff)) 77 + (* Binary writing helper — kept for metadata block headers (single u16 field) *) 78 + let set_u16_le buf off v = 79 + Bytes.set buf off (Char.chr (v land 0xff)); 80 + Bytes.set buf (off + 1) (Char.chr ((v lsr 8) land 0xff)) 81 + 82 + (* --- Wire codecs for encoding on-disk structures --- *) 79 83 80 - let set_u16_le buf off v = 81 - set_u8 buf off v; 82 - set_u8 buf (off + 1) (v lsr 8) 84 + (* Inode common header: 16 bytes *) 85 + type inode_header = { 86 + ih_type_and_mode : int; 87 + ih_mode : int; 88 + ih_uid_idx : int; 89 + ih_gid_idx : int; 90 + ih_mtime : int; 91 + ih_inode_number : int; 92 + } 93 + 94 + let inode_header_codec = 95 + let open Wire.Codec in 96 + record "SquashfsInodeHeader" 97 + (fun 98 + ih_type_and_mode ih_mode ih_uid_idx ih_gid_idx ih_mtime ih_inode_number -> 99 + { 100 + ih_type_and_mode; 101 + ih_mode; 102 + ih_uid_idx; 103 + ih_gid_idx; 104 + ih_mtime; 105 + ih_inode_number; 106 + }) 107 + |+ field "type_and_mode" Wire.uint16 (fun t -> t.ih_type_and_mode) 108 + |+ field "mode" Wire.uint16 (fun t -> t.ih_mode) 109 + |+ field "uid_idx" Wire.uint16 (fun t -> t.ih_uid_idx) 110 + |+ field "gid_idx" Wire.uint16 (fun t -> t.ih_gid_idx) 111 + |+ field "mtime" Wire.uint32 (fun t -> t.ih_mtime) 112 + |+ field "inode_number" Wire.uint32 (fun t -> t.ih_inode_number) 113 + |> seal 83 114 84 - let set_u32_le buf off v = 85 - set_u16_le buf off (v land 0xffff); 86 - set_u16_le buf (off + 2) (v lsr 16) 115 + let inode_header_size = Wire.Codec.wire_size inode_header_codec 87 116 88 - let set_u64_le buf off v = 89 - set_u32_le buf off (Int64.to_int (Int64.logand v 0xffffffffL)); 90 - set_u32_le buf (off + 4) (Int64.to_int (Int64.shift_right_logical v 32)) 117 + (* Directory inode body: 16 bytes *) 118 + type dir_body = { 119 + db_start_block : int; 120 + db_nlink : int; 121 + db_file_size : int; 122 + db_offset : int; 123 + db_parent_inode : int; 124 + } 125 + 126 + let dir_body_codec = 127 + let open Wire.Codec in 128 + record "SquashfsDirBody" 129 + (fun db_start_block db_nlink db_file_size db_offset db_parent_inode -> 130 + { db_start_block; db_nlink; db_file_size; db_offset; db_parent_inode }) 131 + |+ field "start_block" Wire.uint32 (fun t -> t.db_start_block) 132 + |+ field "nlink" Wire.uint32 (fun t -> t.db_nlink) 133 + |+ field "file_size" Wire.uint16 (fun t -> t.db_file_size) 134 + |+ field "offset" Wire.uint16 (fun t -> t.db_offset) 135 + |+ field "parent_inode" Wire.uint32 (fun t -> t.db_parent_inode) 136 + |> seal 137 + 138 + let dir_body_size = Wire.Codec.wire_size dir_body_codec 139 + 140 + (* File inode body: 16 bytes *) 141 + type file_body = { 142 + fb_start_block : int; 143 + fb_fragment : int; 144 + fb_offset : int; 145 + fb_file_size : int; 146 + } 147 + 148 + let file_body_codec = 149 + let open Wire.Codec in 150 + record "SquashfsFileBody" 151 + (fun fb_start_block fb_fragment fb_offset fb_file_size -> 152 + { fb_start_block; fb_fragment; fb_offset; fb_file_size }) 153 + |+ field "start_block" Wire.uint32 (fun t -> t.fb_start_block) 154 + |+ field "fragment" Wire.uint32 (fun t -> t.fb_fragment) 155 + |+ field "offset" Wire.uint32 (fun t -> t.fb_offset) 156 + |+ field "file_size" Wire.uint32 (fun t -> t.fb_file_size) 157 + |> seal 158 + 159 + let file_body_size = Wire.Codec.wire_size file_body_codec 160 + 161 + (* Device inode body: 8 bytes *) 162 + type device_body = { devb_nlink : int; devb_rdev : int } 163 + 164 + let device_body_codec = 165 + let open Wire.Codec in 166 + record "SquashfsDeviceBody" (fun devb_nlink devb_rdev -> 167 + { devb_nlink; devb_rdev }) 168 + |+ field "nlink" Wire.uint32 (fun t -> t.devb_nlink) 169 + |+ field "rdev" Wire.uint32 (fun t -> t.devb_rdev) 170 + |> seal 171 + 172 + let device_body_size = Wire.Codec.wire_size device_body_codec 173 + 174 + (* IPC inode body: 4 bytes *) 175 + type ipc_body = { ipcb_nlink : int } 176 + 177 + let ipc_body_codec = 178 + let open Wire.Codec in 179 + record "SquashfsIpcBody" (fun ipcb_nlink -> { ipcb_nlink }) 180 + |+ field "nlink" Wire.uint32 (fun t -> t.ipcb_nlink) 181 + |> seal 182 + 183 + let ipc_body_size = Wire.Codec.wire_size ipc_body_codec 184 + 185 + (* Symlink inode body (fixed part): 8 bytes *) 186 + type symlink_body = { slb_nlink : int; slb_target_size : int } 187 + 188 + let symlink_body_codec = 189 + let open Wire.Codec in 190 + record "SquashfsSymlinkBody" (fun slb_nlink slb_target_size -> 191 + { slb_nlink; slb_target_size }) 192 + |+ field "nlink" Wire.uint32 (fun t -> t.slb_nlink) 193 + |+ field "target_size" Wire.uint32 (fun t -> t.slb_target_size) 194 + |> seal 195 + 196 + let symlink_body_size = Wire.Codec.wire_size symlink_body_codec 197 + 198 + (* Directory header: 12 bytes *) 199 + type dir_header = { 200 + dh_count : int; 201 + dh_start_block : int; 202 + dh_inode_number : int; 203 + } 204 + 205 + let dir_header_codec = 206 + let open Wire.Codec in 207 + record "SquashfsDirHeader" (fun dh_count dh_start_block dh_inode_number -> 208 + { dh_count; dh_start_block; dh_inode_number }) 209 + |+ field "count" Wire.uint32 (fun t -> t.dh_count) 210 + |+ field "start_block" Wire.uint32 (fun t -> t.dh_start_block) 211 + |+ field "inode_number" Wire.uint32 (fun t -> t.dh_inode_number) 212 + |> seal 213 + 214 + let dir_header_size = Wire.Codec.wire_size dir_header_codec 215 + 216 + (* Directory entry header: 8 bytes *) 217 + type dir_entry_header = { 218 + de_inode_offset : int; 219 + de_inode_number : int; 220 + de_entry_type : int; 221 + de_name_size : int; 222 + } 223 + 224 + let dir_entry_header_codec = 225 + let open Wire.Codec in 226 + record "SquashfsDirEntryHeader" 227 + (fun de_inode_offset de_inode_number de_entry_type de_name_size -> 228 + { de_inode_offset; de_inode_number; de_entry_type; de_name_size }) 229 + |+ field "inode_offset" Wire.uint16 (fun t -> t.de_inode_offset) 230 + |+ field "inode_number" Wire.uint16 (fun t -> t.de_inode_number) 231 + |+ field "entry_type" Wire.uint16 (fun t -> t.de_entry_type) 232 + |+ field "name_size" Wire.uint16 (fun t -> t.de_name_size) 233 + |> seal 234 + 235 + let dir_entry_header_size = Wire.Codec.wire_size dir_entry_header_codec 236 + 237 + (* Raw superblock: 96 bytes *) 238 + type raw_superblock = { 239 + sb_magic : int; 240 + sb_inode_count : int; 241 + sb_modification_time : int; 242 + sb_block_size : int; 243 + sb_fragment_entry_count : int; 244 + sb_compression_id : int; 245 + sb_block_log : int; 246 + sb_flags : int; 247 + sb_id_count : int; 248 + sb_version_major : int; 249 + sb_version_minor : int; 250 + sb_root_inode_ref : int64; 251 + sb_bytes_used : int64; 252 + sb_id_table_start : int64; 253 + sb_xattr_table_start : int64; 254 + sb_inode_table_start : int64; 255 + sb_directory_table_start : int64; 256 + sb_fragment_table_start : int64; 257 + sb_export_table_start : int64; 258 + } 259 + 260 + let superblock_codec = 261 + let open Wire.Codec in 262 + record "SquashfsSuperblock" 263 + (fun 264 + sb_magic 265 + sb_inode_count 266 + sb_modification_time 267 + sb_block_size 268 + sb_fragment_entry_count 269 + sb_compression_id 270 + sb_block_log 271 + sb_flags 272 + sb_id_count 273 + sb_version_major 274 + sb_version_minor 275 + sb_root_inode_ref 276 + sb_bytes_used 277 + sb_id_table_start 278 + sb_xattr_table_start 279 + sb_inode_table_start 280 + sb_directory_table_start 281 + sb_fragment_table_start 282 + sb_export_table_start 283 + -> 284 + { 285 + sb_magic; 286 + sb_inode_count; 287 + sb_modification_time; 288 + sb_block_size; 289 + sb_fragment_entry_count; 290 + sb_compression_id; 291 + sb_block_log; 292 + sb_flags; 293 + sb_id_count; 294 + sb_version_major; 295 + sb_version_minor; 296 + sb_root_inode_ref; 297 + sb_bytes_used; 298 + sb_id_table_start; 299 + sb_xattr_table_start; 300 + sb_inode_table_start; 301 + sb_directory_table_start; 302 + sb_fragment_table_start; 303 + sb_export_table_start; 304 + }) 305 + |+ field "magic" Wire.uint32 (fun t -> t.sb_magic) 306 + |+ field "inode_count" Wire.uint32 (fun t -> t.sb_inode_count) 307 + |+ field "modification_time" Wire.uint32 (fun t -> t.sb_modification_time) 308 + |+ field "block_size" Wire.uint32 (fun t -> t.sb_block_size) 309 + |+ field "fragment_entry_count" Wire.uint32 (fun t -> 310 + t.sb_fragment_entry_count) 311 + |+ field "compression_id" Wire.uint16 (fun t -> t.sb_compression_id) 312 + |+ field "block_log" Wire.uint16 (fun t -> t.sb_block_log) 313 + |+ field "flags" Wire.uint16 (fun t -> t.sb_flags) 314 + |+ field "id_count" Wire.uint16 (fun t -> t.sb_id_count) 315 + |+ field "version_major" Wire.uint16 (fun t -> t.sb_version_major) 316 + |+ field "version_minor" Wire.uint16 (fun t -> t.sb_version_minor) 317 + |+ field "root_inode_ref" Wire.uint64 (fun t -> t.sb_root_inode_ref) 318 + |+ field "bytes_used" Wire.uint64 (fun t -> t.sb_bytes_used) 319 + |+ field "id_table_start" Wire.uint64 (fun t -> t.sb_id_table_start) 320 + |+ field "xattr_table_start" Wire.uint64 (fun t -> t.sb_xattr_table_start) 321 + |+ field "inode_table_start" Wire.uint64 (fun t -> t.sb_inode_table_start) 322 + |+ field "directory_table_start" Wire.uint64 (fun t -> 323 + t.sb_directory_table_start) 324 + |+ field "fragment_table_start" Wire.uint64 (fun t -> 325 + t.sb_fragment_table_start) 326 + |+ field "export_table_start" Wire.uint64 (fun t -> t.sb_export_table_start) 327 + |> seal 328 + 329 + let superblock_size = Wire.Codec.wire_size superblock_codec 91 330 92 331 (* Path validation *) 93 332 let validate_path path = ··· 302 541 block_start : int; (* Start of metadata block relative to inode table *) 303 542 } 304 543 305 - let encode_basic_inode_header buf inode_type mode uid_idx gid_idx mtime 544 + let encode_inode_header buf offset inode_type mode uid_idx gid_idx mtime 306 545 inode_number = 307 546 let type_and_mode = 308 547 inode_type_to_int inode_type lor ((mode land 0o7777) lsl 4) 309 548 in 310 - set_u16_le buf 0 type_and_mode; 311 - set_u16_le buf 2 mode; 312 - set_u16_le buf 4 uid_idx; 313 - set_u16_le buf 6 gid_idx; 314 - set_u32_le buf 8 mtime; 315 - set_u32_le buf 12 inode_number 316 - 317 - let encode_directory_inode buf offset start_block nlink file_size block_offset 318 - parent_inode = 319 - set_u32_le buf offset start_block; 320 - set_u32_le buf (offset + 4) nlink; 321 - set_u16_le buf (offset + 8) (file_size - 3); 322 - (* Size minus . and .. *) 323 - set_u16_le buf (offset + 10) block_offset; 324 - set_u32_le buf (offset + 12) parent_inode 325 - 326 - let encode_file_inode buf offset start_block fragment offset_in_fragment 327 - file_size = 328 - set_u32_le buf offset (Int64.to_int (Int64.logand start_block 0xffffffffL)); 329 - set_u32_le buf (offset + 4) fragment; 330 - set_u32_le buf (offset + 8) offset_in_fragment; 331 - set_u32_le buf (offset + 12) (Int64.to_int file_size) 332 - 333 - let encode_symlink_inode buf offset nlink target = 334 - set_u32_le buf offset nlink; 335 - set_u32_le buf (offset + 4) (String.length target); 336 - Bytes.blit_string target 0 buf (offset + 8) (String.length target) 337 - 338 - let encode_device_inode buf offset nlink rdev = 339 - set_u32_le buf offset nlink; 340 - set_u32_le buf (offset + 4) rdev 341 - 342 - let encode_ipc_inode buf offset nlink = set_u32_le buf offset nlink 343 - 344 - (* Directory entry encoding *) 345 - let encode_directory_entry buf name inode_offset inode_number entry_type = 346 - set_u16_le buf 0 inode_offset; 347 - set_u16_le buf 2 (inode_number land 0xffff); 348 - set_u16_le buf 4 entry_type; 349 - set_u16_le buf 6 (String.length name - 1); 350 - Bytes.blit_string name 0 buf 8 (String.length name); 351 - 8 + String.length name 549 + Wire.Codec.encode inode_header_codec 550 + { 551 + ih_type_and_mode = type_and_mode; 552 + ih_mode = mode; 553 + ih_uid_idx = uid_idx; 554 + ih_gid_idx = gid_idx; 555 + ih_mtime = mtime; 556 + ih_inode_number = inode_number; 557 + } 558 + buf offset 352 559 353 560 (* Build the complete squashfs image *) 354 561 let finalize t = 355 562 let output = Buffer.create 65536 in 356 563 357 564 (* Reserve space for superblock (96 bytes) *) 358 - Buffer.add_string output (String.make 96 '\000'); 565 + Buffer.add_string output (String.make superblock_size '\000'); 359 566 360 567 (* Collect all data blocks and build inode table *) 361 568 let data_blocks = Buffer.create 65536 in ··· 365 572 366 573 (* Write ID table (just uid=0, gid=0 for now) *) 367 574 let id_buf = Bytes.create 4 in 368 - set_u32_le id_buf 0 0; 575 + Wire.UInt32.set_le id_buf 0 0; 369 576 Buffer.add_bytes id_table id_buf; 370 577 371 578 (* Track inode positions *) ··· 389 596 in 390 597 (* Write block size as header *) 391 598 let hdr = Bytes.create 4 in 392 - set_u32_le hdr 0 header; 599 + Wire.UInt32.set_le hdr 0 header; 393 600 Buffer.add_bytes data_blocks hdr; 394 601 Buffer.add_string data_blocks block_data; 395 602 current_data_block := ··· 409 616 (fun (name, entry) -> ignore (write_data_for_entry name entry)) 410 617 t.root; 411 618 619 + (* Helper: encode inode header + body into a buffer *) 620 + let encode_inode inode_type mode inode_number body_size encode_body = 621 + let buf = Bytes.create (inode_header_size + body_size) in 622 + encode_inode_header buf 0 inode_type mode 0 0 t.mtime inode_number; 623 + encode_body buf inode_header_size; 624 + buf 625 + in 626 + 412 627 (* Second pass: write inodes *) 413 628 let inode_block_start = ref 0 in 414 629 let rec write_inode_for_entry parent_inode path entry = ··· 424 639 425 640 match entry with 426 641 | Dir { mode; children } -> 427 - let buf = Bytes.create 32 in 428 - encode_basic_inode_header buf Basic_directory mode 0 0 t.mtime 429 - inode_number; 430 - (* Directory-specific fields *) 431 642 let nlink = List.length children + 2 in 432 - (* . and .. *) 433 643 let file_size = 434 644 List.fold_left 435 645 (fun acc (n, _) -> acc + 8 + String.length n) 436 646 3 children 437 647 in 438 - encode_directory_inode buf 16 0 nlink file_size 0 parent_inode; 648 + let buf = 649 + encode_inode Basic_directory mode inode_number dir_body_size 650 + (fun buf off -> 651 + Wire.Codec.encode dir_body_codec 652 + { 653 + db_start_block = 0; 654 + db_nlink = nlink; 655 + db_file_size = file_size - 3; 656 + db_offset = 0; 657 + db_parent_inode = parent_inode; 658 + } 659 + buf off) 660 + in 439 661 Buffer.add_bytes inode_table buf; 440 662 441 663 (* Process children *) ··· 448 670 children; 449 671 inode_number 450 672 | File { mode; data } -> 451 - let buf = Bytes.create 32 in 452 - encode_basic_inode_header buf Basic_file mode 0 0 t.mtime inode_number; 453 - encode_file_inode buf 16 0L (-1) 0 (Int64.of_int (String.length data)); 673 + (* fragment = -1 → unsigned 0xFFFFFFFF *) 674 + let fragment_unsigned = 0xFFFFFFFF in 675 + let buf = 676 + encode_inode Basic_file mode inode_number file_body_size 677 + (fun buf off -> 678 + Wire.Codec.encode file_body_codec 679 + { 680 + fb_start_block = 0; 681 + fb_fragment = fragment_unsigned; 682 + fb_offset = 0; 683 + fb_file_size = String.length data; 684 + } 685 + buf off) 686 + in 454 687 Buffer.add_bytes inode_table buf; 455 688 inode_number 456 689 | Symlink { target } -> 457 - let buf = Bytes.create (24 + String.length target) in 458 - encode_basic_inode_header buf Basic_symlink 0o777 0 0 t.mtime 459 - inode_number; 460 - encode_symlink_inode buf 16 1 target; 690 + let target_len = String.length target in 691 + let buf = 692 + Bytes.create (inode_header_size + symlink_body_size + target_len) 693 + in 694 + encode_inode_header buf 0 Basic_symlink 0o777 0 0 t.mtime inode_number; 695 + Wire.Codec.encode symlink_body_codec 696 + { slb_nlink = 1; slb_target_size = target_len } 697 + buf inode_header_size; 698 + Bytes.blit_string target 0 buf 699 + (inode_header_size + symlink_body_size) 700 + target_len; 461 701 Buffer.add_bytes inode_table buf; 462 702 inode_number 463 703 | Block_device { mode; major; minor } -> 464 - let buf = Bytes.create 24 in 465 - encode_basic_inode_header buf Basic_block_device mode 0 0 t.mtime 466 - inode_number; 467 704 let rdev = (major lsl 8) lor minor in 468 - encode_device_inode buf 16 1 rdev; 705 + let buf = 706 + encode_inode Basic_block_device mode inode_number device_body_size 707 + (fun buf off -> 708 + Wire.Codec.encode device_body_codec 709 + { devb_nlink = 1; devb_rdev = rdev } 710 + buf off) 711 + in 469 712 Buffer.add_bytes inode_table buf; 470 713 inode_number 471 714 | Char_device { mode; major; minor } -> 472 - let buf = Bytes.create 24 in 473 - encode_basic_inode_header buf Basic_char_device mode 0 0 t.mtime 474 - inode_number; 475 715 let rdev = (major lsl 8) lor minor in 476 - encode_device_inode buf 16 1 rdev; 716 + let buf = 717 + encode_inode Basic_char_device mode inode_number device_body_size 718 + (fun buf off -> 719 + Wire.Codec.encode device_body_codec 720 + { devb_nlink = 1; devb_rdev = rdev } 721 + buf off) 722 + in 477 723 Buffer.add_bytes inode_table buf; 478 724 inode_number 479 725 | Fifo { mode } -> 480 - let buf = Bytes.create 20 in 481 - encode_basic_inode_header buf Basic_fifo mode 0 0 t.mtime inode_number; 482 - encode_ipc_inode buf 16 1; 726 + let buf = 727 + encode_inode Basic_fifo mode inode_number ipc_body_size 728 + (fun buf off -> 729 + Wire.Codec.encode ipc_body_codec { ipcb_nlink = 1 } buf off) 730 + in 483 731 Buffer.add_bytes inode_table buf; 484 732 inode_number 485 733 | Socket { mode } -> 486 - let buf = Bytes.create 20 in 487 - encode_basic_inode_header buf Basic_socket mode 0 0 t.mtime inode_number; 488 - encode_ipc_inode buf 16 1; 734 + let buf = 735 + encode_inode Basic_socket mode inode_number ipc_body_size 736 + (fun buf off -> 737 + Wire.Codec.encode ipc_body_codec { ipcb_nlink = 1 } buf off) 738 + in 489 739 Buffer.add_bytes inode_table buf; 490 740 inode_number 491 741 in 492 742 493 743 (* Write root directory inode first *) 494 744 let root_inode = 495 - let buf = Bytes.create 32 in 496 745 let inode_number = !current_inode in 497 746 incr current_inode; 498 - encode_basic_inode_header buf Basic_directory 0o755 0 0 t.mtime inode_number; 499 747 let nlink = List.length t.root + 2 in 500 748 let file_size = 501 749 List.fold_left (fun acc (n, _) -> acc + 8 + String.length n) 3 t.root 502 750 in 503 - encode_directory_inode buf 16 0 nlink file_size 0 inode_number; 751 + let buf = 752 + encode_inode Basic_directory 0o755 inode_number dir_body_size 753 + (fun buf off -> 754 + Wire.Codec.encode dir_body_codec 755 + { 756 + db_start_block = 0; 757 + db_nlink = nlink; 758 + db_file_size = file_size - 3; 759 + db_offset = 0; 760 + db_parent_inode = inode_number; 761 + } 762 + buf off) 763 + in 504 764 Buffer.add_bytes inode_table buf; 505 765 Hashtbl.add inode_positions "/" 506 766 { inode_number; block_offset = 0; block_start = 0 }; ··· 517 777 let rec write_dir_entries path children = 518 778 if children <> [] then begin 519 779 (* Directory header *) 520 - let header = Bytes.create 12 in 521 - set_u32_le header 0 (List.length children - 1); 522 - (* count - 1 *) 523 - set_u32_le header 4 0; 524 - (* start block *) 525 - set_u32_le header 8 root_inode; 526 - (* inode number of first entry *) 780 + let header = Bytes.create dir_header_size in 781 + Wire.Codec.encode dir_header_codec 782 + { 783 + dh_count = List.length children - 1; 784 + dh_start_block = 0; 785 + dh_inode_number = root_inode; 786 + } 787 + header 0; 527 788 Buffer.add_bytes directory_table header; 528 789 529 790 (* Write entries *) ··· 541 802 | Fifo _ -> 6 542 803 | Socket _ -> 7 543 804 in 544 - let entry_buf = Bytes.create (8 + String.length name) in 545 - ignore 546 - (encode_directory_entry entry_buf name info.block_offset 547 - info.inode_number entry_type); 805 + let name_len = String.length name in 806 + let entry_buf = Bytes.create (dir_entry_header_size + name_len) in 807 + Wire.Codec.encode dir_entry_header_codec 808 + { 809 + de_inode_offset = info.block_offset; 810 + de_inode_number = info.inode_number land 0xffff; 811 + de_entry_type = entry_type; 812 + de_name_size = name_len - 1; 813 + } 814 + entry_buf 0; 815 + Bytes.blit_string name 0 entry_buf dir_entry_header_size name_len; 548 816 Buffer.add_bytes directory_table entry_buf; 549 817 550 818 (* Recurse for directories *) ··· 601 869 (* Write ID table lookup (array of pointers to ID blocks) *) 602 870 let id_table_start = Buffer.length output in 603 871 let id_ptr = Bytes.create 8 in 604 - set_u64_le id_ptr 0 (Int64.of_int id_data_start); 872 + Wire.UInt32.set_le id_ptr 0 873 + (Int64.to_int (Int64.logand (Int64.of_int id_data_start) 0xffffffffL)); 874 + Wire.UInt32.set_le id_ptr 4 875 + (Int64.to_int (Int64.shift_right_logical (Int64.of_int id_data_start) 32)); 605 876 Buffer.add_bytes output id_ptr; 606 877 align_to output 4096; 607 878 ··· 611 882 let bytes_used = Buffer.length output in 612 883 613 884 (* Now write the superblock *) 614 - let sb = Bytes.create 96 in 615 - set_u32_le sb 0 (Int32.to_int magic); 616 - let st = stats t in 617 - set_u32_le sb 4 618 - (st.file_count + st.dir_count + st.symlink_count + st.device_count); 619 - set_u32_le sb 8 t.mtime; 620 - set_u32_le sb 12 t.block_size; 621 - set_u32_le sb 16 0; 622 - (* fragment_entry_count *) 623 - set_u16_le sb 20 (compression_to_int t.compression); 624 885 let rec log2 n = if n <= 1 then 0 else 1 + log2 (n lsr 1) in 625 - set_u16_le sb 22 (log2 t.block_size); 626 - set_u16_le sb 24 0; 627 - (* flags *) 628 - set_u16_le sb 26 1; 629 - (* id_count *) 630 - set_u16_le sb 28 version_major; 631 - set_u16_le sb 30 version_minor; 632 - (* Root inode reference: (block_offset << 16) | offset_in_block 633 - The root inode is at offset 0 in the first metadata block *) 634 - let root_inode_ref = 0L in 635 - (* block_offset=0, offset_in_block=0 *) 636 - ignore root_inode; 637 - (* root_inode is the inode number, not the ref *) 638 - set_u64_le sb 32 root_inode_ref; 639 - set_u64_le sb 40 (Int64.of_int bytes_used); 640 - (* SquashFS superblock layout per kernel squashfs_fs.h: 641 - offset 48: id_table_start 642 - offset 56: xattr_id_table_start 643 - offset 64: inode_table_start 644 - offset 72: directory_table_start 645 - offset 80: fragment_table_start 646 - offset 88: lookup_table_start (export) *) 647 - set_u64_le sb 48 (Int64.of_int id_table_start); 648 - set_u64_le sb 56 xattr_table_start; 649 - set_u64_le sb 64 (Int64.of_int inode_table_start); 650 - set_u64_le sb 72 (Int64.of_int directory_table_start); 651 - set_u64_le sb 80 fragment_table_start; 652 - set_u64_le sb 88 export_table_start; 886 + let st = stats t in 887 + let sb = Bytes.create superblock_size in 888 + Wire.Codec.encode superblock_codec 889 + { 890 + sb_magic = Int32.to_int magic; 891 + sb_inode_count = 892 + st.file_count + st.dir_count + st.symlink_count + st.device_count; 893 + sb_modification_time = t.mtime; 894 + sb_block_size = t.block_size; 895 + sb_fragment_entry_count = 0; 896 + sb_compression_id = compression_to_int t.compression; 897 + sb_block_log = log2 t.block_size; 898 + sb_flags = 0; 899 + sb_id_count = 1; 900 + sb_version_major = version_major; 901 + sb_version_minor = version_minor; 902 + sb_root_inode_ref = 0L; 903 + sb_bytes_used = Int64.of_int bytes_used; 904 + sb_id_table_start = Int64.of_int id_table_start; 905 + sb_xattr_table_start = xattr_table_start; 906 + sb_inode_table_start = Int64.of_int inode_table_start; 907 + sb_directory_table_start = Int64.of_int directory_table_start; 908 + sb_fragment_table_start = fragment_table_start; 909 + sb_export_table_start = export_table_start; 910 + } 911 + sb 0; 653 912 654 913 (* Copy superblock to beginning of output *) 655 914 let result = Buffer.to_bytes output in 656 - Bytes.blit sb 0 result 0 96; 915 + Bytes.blit sb 0 result 0 superblock_size; 657 916 Bytes.to_string result 658 917 659 918 let write t writer =
+1 -1
test/dune
··· 1 1 (test 2 2 (name test) 3 - (libraries squashfs bytesrw alcotest)) 3 + (libraries squashfs bytesrw wire alcotest))
+67
test/test_squashfs.ml
··· 208 208 | Error _ -> () 209 209 | Ok _ -> Alcotest.fail "should reject all-zeros superblock" 210 210 211 + (* Wire codec tests *) 212 + let test_superblock_codec_size () = 213 + Alcotest.(check int) 214 + "superblock codec is 96 bytes" 96 215 + (Wire.Codec.wire_size Squashfs.superblock_codec) 216 + 217 + let test_superblock_roundtrip () = 218 + let sb : Squashfs.raw_superblock = 219 + { 220 + sb_magic = 0x73717368; 221 + sb_inode_count = 42; 222 + sb_modification_time = 1700000000; 223 + sb_block_size = 131072; 224 + sb_fragment_entry_count = 3; 225 + sb_compression_id = 1; 226 + sb_block_log = 17; 227 + sb_flags = 0; 228 + sb_id_count = 1; 229 + sb_version_major = 4; 230 + sb_version_minor = 0; 231 + sb_root_inode_ref = 0x0000_0000_0001_0000L; 232 + sb_bytes_used = 8192L; 233 + sb_id_table_start = 4096L; 234 + sb_xattr_table_start = Int64.minus_one; 235 + sb_inode_table_start = 1024L; 236 + sb_directory_table_start = 2048L; 237 + sb_fragment_table_start = Int64.minus_one; 238 + sb_export_table_start = Int64.minus_one; 239 + } 240 + in 241 + let size = Wire.Codec.wire_size Squashfs.superblock_codec in 242 + let buf = Bytes.create size in 243 + Wire.Codec.encode Squashfs.superblock_codec sb buf 0; 244 + let sb' = Wire.Codec.decode Squashfs.superblock_codec buf 0 in 245 + Alcotest.(check int) "magic" sb.sb_magic sb'.sb_magic; 246 + Alcotest.(check int) "inode_count" sb.sb_inode_count sb'.sb_inode_count; 247 + Alcotest.(check int) 248 + "modification_time" sb.sb_modification_time sb'.sb_modification_time; 249 + Alcotest.(check int) "block_size" sb.sb_block_size sb'.sb_block_size; 250 + Alcotest.(check int) 251 + "compression_id" sb.sb_compression_id sb'.sb_compression_id; 252 + Alcotest.(check int) "version_major" sb.sb_version_major sb'.sb_version_major; 253 + Alcotest.(check int64) 254 + "root_inode_ref" sb.sb_root_inode_ref sb'.sb_root_inode_ref; 255 + Alcotest.(check int64) "bytes_used" sb.sb_bytes_used sb'.sb_bytes_used; 256 + Alcotest.(check int64) 257 + "id_table_start" sb.sb_id_table_start sb'.sb_id_table_start; 258 + Alcotest.(check int64) 259 + "inode_table_start" sb.sb_inode_table_start sb'.sb_inode_table_start; 260 + Alcotest.(check int64) 261 + "directory_table_start" sb.sb_directory_table_start 262 + sb'.sb_directory_table_start; 263 + Alcotest.(check int64) 264 + "xattr_table_start" sb.sb_xattr_table_start sb'.sb_xattr_table_start; 265 + Alcotest.(check int64) 266 + "fragment_table_start" sb.sb_fragment_table_start 267 + sb'.sb_fragment_table_start; 268 + Alcotest.(check int64) 269 + "export_table_start" sb.sb_export_table_start sb'.sb_export_table_start 270 + 211 271 let suite = 212 272 [ 213 273 ( "errors", ··· 242 302 test_metadata_beyond_image; 243 303 Alcotest.test_case "empty input" `Quick test_empty_input; 244 304 Alcotest.test_case "minimum size" `Quick test_minimum_size; 305 + ] ); 306 + ( "wire", 307 + [ 308 + Alcotest.test_case "superblock codec size" `Quick 309 + test_superblock_codec_size; 310 + Alcotest.test_case "superblock roundtrip" `Quick 311 + test_superblock_roundtrip; 245 312 ] ); 246 313 ]