src/packkit@cpio.erl
-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, <<Ch, Acc/bitstring>>)
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, <<Acc/bitstring, Byte>>)
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};
<<B, Rest/binary>> ->
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 => <<?FILEPATH/utf8>>,
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 => <<?FILEPATH/utf8>>,
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 => <<?FILEPATH/utf8>>,
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 => <<?FILEPATH/utf8>>,
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 => <<?FILEPATH/utf8>>,
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
).