-module(packkit@cpio). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/packkit/cpio.gleam"). -export([format/0, new/0, decode_with_limits/2, decode/1, encode/1]). -export_type([parsed_header/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC( " CPIO \"newc\" (SVR4) archive encoder and decoder.\n" "\n" " The newc format uses fixed 110-byte ASCII hex headers and pads\n" " names and bodies to a 4-byte alignment. This module implements\n" " regular files, directories, and symbolic links. Hard links are\n" " rejected because newc represents them through shared inode numbers\n" " rather than a distinct typeflag.\n" ). -type parsed_header() :: {parsed_header, integer(), integer(), integer(), integer(), integer(), integer()}. -file("src/packkit/cpio.gleam", 38). ?DOC(" CPIO newc archive format marker.\n"). -spec format() -> packkit@archive:archive_format(). format() -> packkit@archive:cpio_newc(). -file("src/packkit/cpio.gleam", 43). ?DOC(" Create an empty CPIO newc archive value.\n"). -spec new() -> packkit@archive:archive(). new() -> packkit@archive:new(format()). -file("src/packkit/cpio.gleam", 61). -spec reject_comment(packkit@archive:archive()) -> {ok, nil} | {error, packkit@error:archive_error()}. reject_comment(Archive_value) -> case packkit@archive:comment(Archive_value) of none -> {ok, nil}; {some, _} -> {error, {archive_comment_unsupported, <<"cpio-newc"/utf8>>}} end. -file("src/packkit/cpio.gleam", 142). -spec check_member_limit(integer(), packkit@limit:limits()) -> {ok, nil} | {error, packkit@error:archive_error()}. check_member_limit(Count, Limits) -> case Count > packkit@limit:max_members(Limits) of true -> {error, {archive_limit_exceeded, <<"max_members"/utf8>>, Count}}; false -> {ok, nil} end. -file("src/packkit/cpio.gleam", 246). -spec entry_error_to_archive_error(packkit@entry:entry_error(), binary()) -> packkit@error:archive_error(). entry_error_to_archive_error(Err, Path) -> case Err of empty_path -> {archive_entry_rejected, Path, <<"empty path"/utf8>>}; {absolute_path, _} -> {archive_entry_rejected, Path, <<"absolute path"/utf8>>}; {path_traversal, _} -> {archive_entry_rejected, Path, <<"path traversal"/utf8>>}; {windows_path, _} -> {archive_entry_rejected, Path, <<"windows path"/utf8>>}; {empty_segment, _} -> {archive_entry_rejected, Path, <<"empty segment"/utf8>>}; {dot_segment, _} -> {archive_entry_rejected, Path, <<"dot segment"/utf8>>}; {contains_nul, _} -> {archive_entry_rejected, Path, <<"nul byte"/utf8>>} end. -file("src/packkit/cpio.gleam", 413). -spec hex_digits_loop(integer(), integer(), bitstring()) -> bitstring(). hex_digits_loop(Value, Remaining, Acc) -> case Remaining of 0 -> Acc; _ -> Digit = erlang:'band'(Value, 15), Next = erlang:'bsr'(Value, 4), Ch = case Digit < 10 of true -> 16#30 + Digit; false -> 16#61 + (Digit - 10) end, hex_digits_loop(Next, Remaining - 1, <>) end. -file("src/packkit/cpio.gleam", 409). -spec hex_digits(integer(), integer()) -> bitstring(). hex_digits(Value, Width) -> hex_digits_loop(Value, Width, <<>>). -file("src/packkit/cpio.gleam", 405). -spec hex_field(integer()) -> bitstring(). hex_field(Value) -> hex_digits(Value, 8). -file("src/packkit/cpio.gleam", 444). -spec byte_repeat_loop(integer(), integer(), bitstring()) -> bitstring(). byte_repeat_loop(Byte, Count, Acc) -> case Count of 0 -> Acc; _ -> byte_repeat_loop(Byte, Count - 1, <>) end. -file("src/packkit/cpio.gleam", 440). -spec byte_repeat(integer(), integer()) -> bitstring(). byte_repeat(Byte, Count) -> byte_repeat_loop(Byte, Count, <<>>). -file("src/packkit/cpio.gleam", 451). -spec slice_or_error(bitstring(), integer(), integer()) -> {ok, bitstring()} | {error, packkit@error:archive_error()}. slice_or_error(Block, Offset, Width) -> case gleam_stdlib:bit_array_slice(Block, Offset, Width) of {ok, Value} -> {ok, Value}; {error, _} -> {error, {archive_invalid, <<"cpio field out of bounds"/utf8>>}} end. -file("src/packkit/cpio.gleam", 484). -spec hex_value(integer()) -> {ok, integer()} | {error, nil}. hex_value(Byte) -> case Byte of N when (N >= 16#30) andalso (N =< 16#39) -> {ok, N - 16#30}; N@1 when (N@1 >= 16#41) andalso (N@1 =< 16#46) -> {ok, (N@1 - 16#41) + 10}; N@2 when (N@2 >= 16#61) andalso (N@2 =< 16#66) -> {ok, (N@2 - 16#61) + 10}; _ -> {error, nil} end. -file("src/packkit/cpio.gleam", 470). -spec parse_hex(bitstring(), integer()) -> {ok, integer()} | {error, packkit@error:archive_error()}. parse_hex(Bytes, Acc) -> case Bytes of <<>> -> {ok, Acc}; <> -> case hex_value(B) of {ok, Value} -> parse_hex(Rest, (Acc * 16) + Value); {error, _} -> {error, {archive_invalid, <<"invalid hex byte in cpio header"/utf8>>}} end; _ -> {ok, Acc} end. -file("src/packkit/cpio.gleam", 462). -spec read_hex_field(bitstring(), integer()) -> {ok, integer()} | {error, packkit@error:archive_error()}. read_hex_field(Block, Offset) -> gleam@result:'try'( slice_or_error(Block, Offset, 8), fun(Slice) -> parse_hex(Slice, 0) end ). -file("src/packkit/cpio.gleam", 493). -spec bytes_to_string(bitstring()) -> {ok, binary()} | {error, packkit@error:archive_error()}. bytes_to_string(Bytes) -> case gleam@bit_array:to_string(Bytes) of {ok, Value} -> {ok, Value}; {error, _} -> {error, {archive_invalid, <<"non-UTF-8 string in cpio header"/utf8>>}} end. -file("src/packkit/cpio.gleam", 506). -spec strip_trailing_nul_loop(bitstring(), integer()) -> bitstring(). strip_trailing_nul_loop(Bytes, Size) -> case Size of 0 -> <<>>; _ -> case gleam_stdlib:bit_array_slice(Bytes, Size - 1, 1) of {ok, <<0>>} -> strip_trailing_nul_loop(Bytes, Size - 1); _ -> Slice@1 = case gleam_stdlib:bit_array_slice(Bytes, 0, Size) of {ok, Slice} -> Slice; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"packkit/cpio"/utf8>>, function => <<"strip_trailing_nul_loop"/utf8>>, line => 513, value => _assert_fail, start => 14304, 'end' => 14358, pattern_start => 14315, pattern_end => 14324}) end, Slice@1 end end. -file("src/packkit/cpio.gleam", 501). -spec strip_trailing_nul(bitstring()) -> bitstring(). strip_trailing_nul(Bytes) -> Size = erlang:byte_size(Bytes), strip_trailing_nul_loop(Bytes, Size). -file("src/packkit/cpio.gleam", 377). -spec build_header( integer(), integer(), integer(), integer(), integer(), integer(), integer(), integer() ) -> bitstring(). build_header(Ino, Mode, Uid, Gid, Nlink, Mtime, Filesize, Namesize) -> gleam_stdlib:bit_array_concat( [gleam_stdlib:identity(<<"070701"/utf8>>), hex_field(Ino), hex_field(Mode), hex_field(Uid), hex_field(Gid), hex_field(Nlink), hex_field(Mtime), hex_field(Filesize), hex_field(0), hex_field(0), hex_field(0), hex_field(0), hex_field(Namesize), hex_field(0)] ). -file("src/packkit/cpio.gleam", 164). -spec parse_header(bitstring()) -> {ok, parsed_header()} | {error, packkit@error:archive_error()}. parse_header(Block) -> gleam@result:'try'( slice_or_error(Block, 0, 6), fun(Magic_bits) -> gleam@result:'try'( bytes_to_string(Magic_bits), fun(Magic) -> gleam@bool:guard( (Magic /= <<"070701"/utf8>>) andalso (Magic /= <<"070702"/utf8>>), {error, {archive_invalid, <<"not a newc/crc cpio magic"/utf8>>}}, fun() -> gleam@result:'try'( read_hex_field(Block, 14), fun(Mode) -> gleam@result:'try'( read_hex_field(Block, 22), fun(Uid) -> gleam@result:'try'( read_hex_field(Block, 30), fun(Gid) -> gleam@result:'try'( read_hex_field( Block, 46 ), fun(Mtime) -> gleam@result:'try'( read_hex_field( Block, 54 ), fun(Filesize) -> gleam@result:'try'( read_hex_field( Block, 94 ), fun( Namesize ) -> {ok, {parsed_header, Mode, Uid, Gid, Mtime, Filesize, Namesize}} end ) end ) end ) end ) end ) end ) end ) end ) end ). -file("src/packkit/cpio.gleam", 433). -spec pad_to_align(integer()) -> integer(). pad_to_align(Position) -> case case 4 of 0 -> 0; Gleam@denominator -> Position rem Gleam@denominator end of 0 -> Position; Rem -> Position + (4 - Rem) end. -file("src/packkit/cpio.gleam", 428). -spec align_padding(integer()) -> bitstring(). align_padding(Position) -> Aligned = pad_to_align(Position), byte_repeat(0, Aligned - Position). -file("src/packkit/cpio.gleam", 355). -spec trailer_record() -> bitstring(). trailer_record() -> Name_bytes = gleam_stdlib:identity(<<"TRAILER!!!"/utf8>>), Name_size = erlang:byte_size(Name_bytes) + 1, Header = build_header(0, 0, 0, 0, 1, 0, 0, Name_size), gleam_stdlib:bit_array_concat( [Header, Name_bytes, <<0>>, align_padding(110 + Name_size)] ). -file("src/packkit/cpio.gleam", 194). -spec header_to_entry( parsed_header(), binary(), bitstring(), packkit@limit:limits() ) -> {ok, packkit@entry:entry()} | {error, packkit@error:archive_error()}. header_to_entry(Header, Name, Body, Limits) -> gleam@bool:guard( erlang:byte_size(Name) > packkit@limit:max_entry_name_bytes(Limits), {error, {archive_limit_exceeded, <<"max_entry_name_bytes"/utf8>>, erlang:byte_size(Name)}}, fun() -> File_type = erlang:'band'(erlang:element(2, Header), 8#170000), Perm = erlang:'band'(erlang:element(2, Header), 8#7777), gleam@result:'try'(case File_type of Type_ when Type_ =:= 8#100000 -> _pipe = packkit@entry:file_checked(Name, Body), gleam@result:map_error( _pipe, fun(_capture) -> entry_error_to_archive_error(_capture, Name) end ); Type_@1 when Type_@1 =:= 8#040000 -> _pipe@1 = packkit@entry:directory_checked(Name), gleam@result:map_error( _pipe@1, fun(_capture@1) -> entry_error_to_archive_error(_capture@1, Name) end ); Type_@2 when Type_@2 =:= 8#120000 -> gleam@result:'try'( bytes_to_string(Body), fun(Target) -> _pipe@2 = packkit@entry:symlink_checked( Name, Target ), gleam@result:map_error( _pipe@2, fun(_capture@2) -> entry_error_to_archive_error( _capture@2, Name ) end ) end ); Other -> {error, {archive_invalid, <<"unsupported cpio mode "/utf8, (erlang:integer_to_binary(Other))/binary>>}} end, fun(Base) -> Depth = packkit@entry:depth(packkit@entry:path(Base)), gleam@bool:guard( Depth > packkit@limit:max_entry_depth(Limits), {error, {archive_limit_exceeded, <<"max_entry_depth"/utf8>>, Depth}}, fun() -> {ok, begin _pipe@3 = Base, _pipe@4 = packkit@entry:with_mode( _pipe@3, Perm ), _pipe@5 = packkit@entry:with_owner( _pipe@4, erlang:element(3, Header), erlang:element(4, Header) ), packkit@entry:with_modified_at( _pipe@5, erlang:element(5, Header) ) end} end ) end) end ). -file("src/packkit/cpio.gleam", 94). -spec decode_loop( bitstring(), list(packkit@entry:entry()), integer(), packkit@limit:limits() ) -> {ok, list(packkit@entry:entry())} | {error, packkit@error:archive_error()}. decode_loop(Bytes, Acc, Count, Limits) -> gleam@bool:guard( erlang:byte_size(Bytes) < 110, {error, {archive_invalid, <<"cpio header truncated"/utf8>>}}, fun() -> Header_bits@1 = case gleam_stdlib:bit_array_slice(Bytes, 0, 110) of {ok, Header_bits} -> Header_bits; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"packkit/cpio"/utf8>>, function => <<"decode_loop"/utf8>>, line => 105, value => _assert_fail, start => 2795, 'end' => 2862, pattern_start => 2806, pattern_end => 2821}) end, gleam@result:'try'( parse_header(Header_bits@1), fun(Header) -> Name_offset = 110, Name_size = erlang:element(7, Header), Name_bytes_with_nul@1 = case gleam_stdlib:bit_array_slice( Bytes, Name_offset, Name_size ) of {ok, Name_bytes_with_nul} -> Name_bytes_with_nul; _assert_fail@1 -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"packkit/cpio"/utf8>>, function => <<"decode_loop"/utf8>>, line => 110, value => _assert_fail@1, start => 2986, 'end' => 3073, pattern_start => 2997, pattern_end => 3020}) end, gleam@result:'try'( bytes_to_string( strip_trailing_nul(Name_bytes_with_nul@1) ), fun(Name) -> Header_plus_name = 110 + Name_size, After_name_padding = pad_to_align(Header_plus_name), Body_offset = After_name_padding, Body_size = erlang:element(6, Header), Body_padding = pad_to_align(Body_size) - Body_size, Next_offset = (Body_offset + Body_size) + Body_padding, gleam@bool:guard( erlang:byte_size(Bytes) < (Body_offset + Body_size), {error, {archive_invalid, <<"cpio entry truncated"/utf8>>}}, fun() -> case Name =:= <<"TRAILER!!!"/utf8>> of true -> {ok, Acc}; false -> gleam@result:'try'( check_member_limit( Count + 1, Limits ), fun(_) -> Body@1 = case gleam_stdlib:bit_array_slice( Bytes, Body_offset, Body_size ) of {ok, Body} -> Body; _assert_fail@2 -> erlang:error( #{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"packkit/cpio"/utf8>>, function => <<"decode_loop"/utf8>>, line => 132, value => _assert_fail@2, start => 3764, 'end' => 3832, pattern_start => 3775, pattern_end => 3783} ) end, gleam@result:'try'( header_to_entry( Header, Name, Body@1, Limits ), fun(Entry_value) -> Advance = Next_offset, Remaining = erlang:byte_size( Bytes ) - Advance, Rest@1 = case gleam_stdlib:bit_array_slice( Bytes, Advance, Remaining ) of {ok, Rest} -> Rest; _assert_fail@3 -> erlang:error( #{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"packkit/cpio"/utf8>>, function => <<"decode_loop"/utf8>>, line => 136, value => _assert_fail@3, start => 4011, 'end' => 4075, pattern_start => 4022, pattern_end => 4030} ) end, decode_loop( Rest@1, [Entry_value | Acc], Count + 1, Limits ) end ) end ) end end ) end ) end ) end ). -file("src/packkit/cpio.gleam", 78). ?DOC(" Decode a newc byte stream using explicit limits.\n"). -spec decode_with_limits(bitstring(), packkit@limit:limits()) -> {ok, packkit@archive:archive()} | {error, packkit@error:archive_error()}. decode_with_limits(Bytes, Limits) -> gleam@bool:guard( erlang:byte_size(Bytes) > packkit@limit:max_input_bytes(Limits), {error, {archive_limit_exceeded, <<"max_input_bytes"/utf8>>, erlang:byte_size(Bytes)}}, fun() -> _pipe = decode_loop(Bytes, [], 0, Limits), _pipe@1 = gleam@result:map(_pipe, fun lists:reverse/1), gleam@result:map( _pipe@1, fun(_capture) -> packkit@archive:from_entries(format(), _capture) end ) end ). -file("src/packkit/cpio.gleam", 71). ?DOC(" Decode a newc byte stream using default limits.\n"). -spec decode(bitstring()) -> {ok, packkit@archive:archive()} | {error, packkit@error:archive_error()}. decode(Bytes) -> decode_with_limits(Bytes, packkit@limit:default()). -file("src/packkit/cpio.gleam", 343). -spec check_hex_field(integer(), binary()) -> {ok, nil} | {error, packkit@error:archive_error()}. check_hex_field(Value, Field) -> case (Value < 0) orelse (Value > 16#FFFFFFFF) of true -> {error, {archive_field_overflow, <<"cpio-newc "/utf8, Field/binary>>, Value, 16#FFFFFFFF}}; false -> {ok, nil} end. -file("src/packkit/cpio.gleam", 268). -spec encode_entry(packkit@entry:entry()) -> {ok, bitstring()} | {error, packkit@error:archive_error()}. encode_entry(Value) -> Kind = packkit@entry:kind(Value), gleam@bool:guard( Kind =:= hardlink, {error, {archive_entry_rejected, packkit@entry:to_string(packkit@entry:path(Value)), <<"cpio newc cannot represent hard links"/utf8>>}}, fun() -> Path = packkit@entry:to_string(packkit@entry:path(Value)), Name_bytes = gleam_stdlib:identity(Path), Name_size = erlang:byte_size(Name_bytes) + 1, Metadata = packkit@entry:metadata(Value), {Mode_bits, Body} = case Kind of file -> {8#100000, packkit@entry:body(Value)}; directory -> {8#040000, <<>>}; symlink -> Target = case packkit@entry:link_target(Value) of {some, T} -> T; none -> <<""/utf8>> end, {8#120000, gleam_stdlib:identity(Target)}; hardlink -> {8#100000, packkit@entry:body(Value)} end, Mode = erlang:'bor'(Mode_bits, packkit@entry:mode(Metadata)), Body_size = erlang:byte_size(Body), Uid = packkit@entry:user_id(Metadata), Gid = packkit@entry:group_id(Metadata), Mtime = packkit@entry:modified_at_unix(Metadata), gleam@result:'try'( check_hex_field(Mode, <<"mode"/utf8>>), fun(_) -> gleam@result:'try'( check_hex_field(Uid, <<"uid"/utf8>>), fun(_) -> gleam@result:'try'( check_hex_field(Gid, <<"gid"/utf8>>), fun(_) -> gleam@result:'try'( check_hex_field(Mtime, <<"mtime"/utf8>>), fun(_) -> gleam@result:'try'( check_hex_field( Body_size, <<"filesize"/utf8>> ), fun(_) -> gleam@result:'try'( check_hex_field( Name_size, <<"namesize"/utf8>> ), fun(_) -> Header = build_header( 0, Mode, Uid, Gid, case Kind =:= directory of true -> 2; false -> 1 end, Mtime, Body_size, Name_size ), Header_with_name = gleam_stdlib:bit_array_concat( [Header, Name_bytes, <<0>>, align_padding( 110 + Name_size )] ), Body_with_padding = gleam_stdlib:bit_array_concat( [Body, align_padding( Body_size )] ), {ok, gleam_stdlib:bit_array_concat( [Header_with_name, Body_with_padding] )} end ) end ) end ) end ) end ) end ) end ). -file("src/packkit/cpio.gleam", 48). ?DOC(" Encode the logical archive to a newc byte stream.\n"). -spec encode(packkit@archive:archive()) -> {ok, bitstring()} | {error, packkit@error:archive_error()}. encode(Archive_value) -> gleam@result:'try'( reject_comment(Archive_value), fun(_) -> _pipe = Archive_value, _pipe@1 = packkit@archive:entries(_pipe), _pipe@2 = gleam@list:try_map(_pipe@1, fun encode_entry/1), gleam@result:map( _pipe@2, fun(Blocks) -> _pipe@3 = [gleam_stdlib:bit_array_concat(Blocks), trailer_record()], gleam_stdlib:bit_array_concat(_pipe@3) end ) end ).