Skip to main content

src/packkit@tar.erl

-module(packkit@tar).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/packkit/tar.gleam").
-export([format/0, new/0, add_file_checked/3, add_file/3, add_directory_checked/2, add_directory/2, add_symlink_checked/3, add_symlink/3, encode/1, decode_with_limits/2, decode/1]).
-export_type([pending_override/0, parsed_header/0, octal_class/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(
    " USTAR (POSIX 1003.1-1988) tar archive encoder and decoder.\n"
    "\n"
    " The implementation is target-neutral and supports regular files,\n"
    " directories, symbolic links, and hard links.  The encoder rejects\n"
    " names longer than the USTAR `prefix`/`name` split allows with a\n"
    " typed archive error.  The decoder additionally consumes GNU\n"
    " `LongName`/`LongLink` extension entries (typeflags `L` and `K`),\n"
    " PAX extended attribute headers (`x` and `g`) — extracting the\n"
    " `path` / `linkpath` records so files emitted by\n"
    " `tar --format=pax` keep their full path past the USTAR 100-char\n"
    " limit — and skips the other PAX attribute keys.\n"
).

-type pending_override() :: {pending_override, binary(), binary()}.

-type parsed_header() :: {parsed_header,
        binary(),
        integer(),
        integer(),
        integer(),
        integer(),
        integer(),
        integer(),
        binary()}.

-type octal_class() :: {octal_digit, integer()} |
    octal_space |
    octal_terminator |
    octal_invalid.

-file("src/packkit/tar.gleam", 34).
?DOC(" Tar archive format marker.\n").
-spec format() -> packkit@archive:archive_format().
format() ->
    packkit@archive:tar().

-file("src/packkit/tar.gleam", 39).
?DOC(" Create an empty tar archive value.\n").
-spec new() -> packkit@archive:archive().
new() ->
    packkit@archive:new(format()).

-file("src/packkit/tar.gleam", 44).
?DOC(" Add a regular file after checked path validation.\n").
-spec add_file_checked(packkit@archive:archive(), binary(), bitstring()) -> {ok,
        packkit@archive:archive()} |
    {error, packkit@entry:entry_error()}.
add_file_checked(Archive_value, Path, Body) ->
    case packkit@entry:file_checked(Path, Body) of
        {ok, Value} ->
            {ok, packkit@archive:add(Archive_value, Value)};

        {error, Err} ->
            {error, Err}
    end.

-file("src/packkit/tar.gleam", 56).
?DOC(" Panicking counterpart of `add_file_checked`.\n").
-spec add_file(packkit@archive:archive(), binary(), bitstring()) -> packkit@archive:archive().
add_file(Archive_value, Path, Body) ->
    packkit@archive:add(Archive_value, packkit@entry:file(Path, Body)).

-file("src/packkit/tar.gleam", 65).
?DOC(" Add a directory after checked path validation.\n").
-spec add_directory_checked(packkit@archive:archive(), binary()) -> {ok,
        packkit@archive:archive()} |
    {error, packkit@entry:entry_error()}.
add_directory_checked(Archive_value, Path) ->
    case packkit@entry:directory_checked(Path) of
        {ok, Value} ->
            {ok, packkit@archive:add(Archive_value, Value)};

        {error, Err} ->
            {error, Err}
    end.

-file("src/packkit/tar.gleam", 76).
?DOC(" Panicking counterpart of `add_directory_checked`.\n").
-spec add_directory(packkit@archive:archive(), binary()) -> packkit@archive:archive().
add_directory(Archive_value, Path) ->
    packkit@archive:add(Archive_value, packkit@entry:directory(Path)).

-file("src/packkit/tar.gleam", 84).
?DOC(" Add a symbolic link after checked path validation.\n").
-spec add_symlink_checked(packkit@archive:archive(), binary(), binary()) -> {ok,
        packkit@archive:archive()} |
    {error, packkit@entry:entry_error()}.
add_symlink_checked(Archive_value, Path, Target) ->
    case packkit@entry:symlink_checked(Path, Target) of
        {ok, Value} ->
            {ok, packkit@archive:add(Archive_value, Value)};

        {error, Err} ->
            {error, Err}
    end.

-file("src/packkit/tar.gleam", 96).
?DOC(" Panicking counterpart of `add_symlink_checked`.\n").
-spec add_symlink(packkit@archive:archive(), binary(), binary()) -> packkit@archive:archive().
add_symlink(Archive_value, Path, Target) ->
    packkit@archive:add(Archive_value, packkit@entry:symlink(Path, Target)).

-file("src/packkit/tar.gleam", 118).
-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, <<"tar"/utf8>>}}
    end.

-file("src/packkit/tar.gleam", 158).
-spec no_pending() -> pending_override().
no_pending() ->
    {pending_override, <<""/utf8>>, <<""/utf8>>}.

-file("src/packkit/tar.gleam", 282).
-spec apply_pending(parsed_header(), pending_override()) -> parsed_header().
apply_pending(Header, Pending) ->
    Name = case erlang:element(2, Pending) of
        <<""/utf8>> ->
            erlang:element(2, Header);

        N ->
            N
    end,
    Linkname = case erlang:element(3, Pending) of
        <<""/utf8>> ->
            erlang:element(9, Header);

        L ->
            L
    end,
    {parsed_header,
        Name,
        erlang:element(3, Header),
        erlang:element(4, Header),
        erlang:element(5, Header),
        erlang:element(6, Header),
        erlang:element(7, Header),
        erlang:element(8, Header),
        Linkname}.

-file("src/packkit/tar.gleam", 297).
?DOC(
    " Read the body of a PAX 'x' / 'g' extended-attribute header.\n"
    " Returns the raw bytes; the caller is responsible for parsing\n"
    " the \"<length> <key>=<value>\\n\" records inside.\n"
).
-spec read_pax_body(bitstring(), integer(), integer()) -> {ok, bitstring()} |
    {error, packkit@error:archive_error()}.
read_pax_body(Bytes, Start, Size) ->
    case gleam_stdlib:bit_array_slice(Bytes, Start, Size) of
        {ok, Chunk} ->
            {ok, Chunk};

        {error, _} ->
            {error,
                {archive_invalid, <<"truncated tar PAX extended header"/utf8>>}}
    end.

-file("src/packkit/tar.gleam", 343).
?DOC(
    " Parse one \"<length> <key>=<value>\\n\" PAX record off the front\n"
    " of `text` and return (key, value, remaining_text).\n"
).
-spec extract_pax_record(binary()) -> {ok, {binary(), binary(), binary()}} |
    {error, nil}.
extract_pax_record(Text) ->
    gleam@result:'try'(
        gleam@string:split_once(Text, <<" "/utf8>>),
        fun(_use0) ->
            {Length_str, After_space} = _use0,
            gleam@result:'try'(
                gleam_stdlib:parse_int(Length_str),
                fun(Length) ->
                    Head_size = string:length(Length_str) + 1,
                    Inner_size = (Length - Head_size) - 1,
                    gleam@bool:guard(
                        Inner_size < 0,
                        {error, nil},
                        fun() ->
                            case gleam@string:slice(After_space, 0, Inner_size) of
                                <<""/utf8>> ->
                                    {error, nil};

                                Body ->
                                    gleam@result:'try'(
                                        gleam@string:split_once(
                                            Body,
                                            <<"="/utf8>>
                                        ),
                                        fun(_use0@1) ->
                                            {Key, Value} = _use0@1,
                                            Rest = gleam@string:slice(
                                                After_space,
                                                Inner_size + 1,
                                                string:length(After_space)
                                            ),
                                            {ok, {Key, Value, Rest}}
                                        end
                                    )
                            end
                        end
                    )
                end
            )
        end
    ).

-file("src/packkit/tar.gleam", 324).
-spec apply_pax_records_loop(pending_override(), binary()) -> pending_override().
apply_pax_records_loop(Pending, Text) ->
    case extract_pax_record(Text) of
        {error, _} ->
            Pending;

        {ok, {Key, Value, Rest}} ->
            Pending@1 = case Key of
                <<"path"/utf8>> ->
                    {pending_override, Value, erlang:element(3, Pending)};

                <<"linkpath"/utf8>> ->
                    {pending_override, erlang:element(2, Pending), Value};

                _ ->
                    Pending
            end,
            apply_pax_records_loop(Pending@1, Rest)
    end.

-file("src/packkit/tar.gleam", 314).
?DOC(
    " Walk a PAX extended-header body and lift the keys we know how\n"
    " to use (\"path\" overrides the next entry's name, \"linkpath\"\n"
    " overrides the next entry's linkname).  Records with other keys\n"
    " are silently dropped — they don't change which bytes the entry\n"
    " holds, only how POSIX-aware tools display its metadata.\n"
).
-spec apply_pax_records(pending_override(), bitstring()) -> pending_override().
apply_pax_records(Pending, Body) ->
    case gleam@bit_array:to_string(Body) of
        {ok, Text} ->
            apply_pax_records_loop(Pending, Text);

        {error, _} ->
            Pending
    end.

-file("src/packkit/tar.gleam", 387).
-spec trim_trailing_nul_size(bitstring(), integer()) -> integer().
trim_trailing_nul_size(Bytes, Size) ->
    case Size of
        0 ->
            0;

        N ->
            case gleam_stdlib:bit_array_slice(Bytes, N - 1, 1) of
                {ok, <<0>>} ->
                    trim_trailing_nul_size(Bytes, N - 1);

                _ ->
                    N
            end
    end.

-file("src/packkit/tar.gleam", 364).
-spec read_string_body(bitstring(), integer(), integer(), binary()) -> {ok,
        binary()} |
    {error, packkit@error:archive_error()}.
read_string_body(Bytes, Start, Size, Label) ->
    case gleam_stdlib:bit_array_slice(Bytes, Start, Size) of
        {ok, Chunk} ->
            Trimmed_size = trim_trailing_nul_size(Chunk, Size),
            Name_bits@1 = case gleam_stdlib:bit_array_slice(
                Chunk,
                0,
                Trimmed_size
            ) of
                {ok, Name_bits} -> Name_bits;
                _assert_fail ->
                    erlang:error(#{gleam_error => let_assert,
                                message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                                file => <<?FILEPATH/utf8>>,
                                module => <<"packkit/tar"/utf8>>,
                                function => <<"read_string_body"/utf8>>,
                                line => 374,
                                value => _assert_fail,
                                start => 11336,
                                'end' => 11402,
                                pattern_start => 11347,
                                pattern_end => 11360})
            end,
            case gleam@bit_array:to_string(Name_bits@1) of
                {ok, Value} ->
                    {ok, Value};

                {error, _} ->
                    {error,
                        {archive_invalid,
                            <<<<"tar "/utf8, Label/binary>>/binary,
                                " is not valid UTF-8"/utf8>>}}
            end;

        {error, _} ->
            {error, {archive_invalid, <<"truncated tar "/utf8, Label/binary>>}}
    end.

-file("src/packkit/tar.gleam", 398).
-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/tar.gleam", 529).
-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/tar.gleam", 521).
-spec link_entry(
    parsed_header(),
    fun((binary(), binary()) -> {ok, packkit@entry:entry()} |
        {error, packkit@entry:entry_error()})
) -> {ok, packkit@entry:entry()} | {error, packkit@error:archive_error()}.
link_entry(Header, Builder) ->
    _pipe = Builder(erlang:element(2, Header), erlang:element(9, Header)),
    gleam@result:map_error(
        _pipe,
        fun(_capture) ->
            entry_error_to_archive_error(_capture, erlang:element(2, Header))
        end
    ).

-file("src/packkit/tar.gleam", 569).
-spec sum_with_checksum_blanked(bitstring(), integer(), integer()) -> integer().
sum_with_checksum_blanked(Block, Position, Acc) ->
    case Block of
        <<>> ->
            Acc;

        <<B, Rest/binary>> ->
            Contribution = case (Position >= 148) andalso (Position < 156) of
                true ->
                    16#20;

                false ->
                    B
            end,
            sum_with_checksum_blanked(Rest, Position + 1, Acc + Contribution);

        _ ->
            Acc
    end.

-file("src/packkit/tar.gleam", 565).
-spec compute_checksum(bitstring()) -> integer().
compute_checksum(Block) ->
    sum_with_checksum_blanked(Block, 0, 0).

-file("src/packkit/tar.gleam", 774).
-spec take_split_loop(list(binary()), integer(), list(binary())) -> {list(binary()),
    list(binary())}.
take_split_loop(Segments, Remaining, Acc) ->
    case {Remaining, Segments} of
        {0, _} ->
            {lists:reverse(Acc), Segments};

        {_, []} ->
            {lists:reverse(Acc), []};

        {_, [Head | Tail]} ->
            take_split_loop(Tail, Remaining - 1, [Head | Acc])
    end.

-file("src/packkit/tar.gleam", 767).
-spec take_split(list(binary()), integer()) -> {list(binary()), list(binary())}.
take_split(Segments, Split_at) ->
    take_split_loop(Segments, Split_at, []).

-file("src/packkit/tar.gleam", 832).
-spec pow_int_loop(integer(), integer(), integer()) -> integer().
pow_int_loop(Base, Exponent, Acc) ->
    case Exponent of
        0 ->
            Acc;

        _ ->
            pow_int_loop(Base, Exponent - 1, Acc * Base)
    end.

-file("src/packkit/tar.gleam", 828).
-spec pow_int(integer(), integer()) -> integer().
pow_int(Base, Exponent) ->
    pow_int_loop(Base, Exponent, 1).

-file("src/packkit/tar.gleam", 853).
-spec version_field() -> bitstring().
version_field() ->
    gleam_stdlib:identity(<<"00"/utf8>>).

-file("src/packkit/tar.gleam", 910).
-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/tar.gleam", 906).
-spec byte_repeat(integer(), integer()) -> bitstring().
byte_repeat(Byte, Count) ->
    byte_repeat_loop(Byte, Count, <<>>).

-file("src/packkit/tar.gleam", 839).
-spec checksum_blank_field() -> bitstring().
checksum_blank_field() ->
    byte_repeat(16#20, 8).

-file("src/packkit/tar.gleam", 857).
-spec uname_field() -> bitstring().
uname_field() ->
    byte_repeat(0, 32).

-file("src/packkit/tar.gleam", 861).
-spec gname_field() -> bitstring().
gname_field() ->
    byte_repeat(0, 32).

-file("src/packkit/tar.gleam", 873).
-spec tail_padding() -> bitstring().
tail_padding() ->
    byte_repeat(0, 12).

-file("src/packkit/tar.gleam", 895).
-spec right_pad(bitstring(), integer(), integer()) -> bitstring().
right_pad(Value, Width, Fill) ->
    Size = erlang:byte_size(Value),
    case Size >= Width of
        true ->
            Slice@1 = case gleam_stdlib:bit_array_slice(Value, 0, Width) 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/tar"/utf8>>,
                                function => <<"right_pad"/utf8>>,
                                line => 899,
                                value => _assert_fail,
                                start => 25154,
                                'end' => 25209,
                                pattern_start => 25165,
                                pattern_end => 25174})
            end,
            Slice@1;

        false ->
            gleam_stdlib:bit_array_concat(
                [Value, byte_repeat(Fill, Width - Size)]
            )
    end.

-file("src/packkit/tar.gleam", 786).
-spec fit_field(binary(), integer(), binary()) -> {ok, bitstring()} |
    {error, packkit@error:archive_error()}.
fit_field(Value, Width, Kind) ->
    Bytes = gleam_stdlib:identity(Value),
    case erlang:byte_size(Bytes) > Width of
        true ->
            {error,
                {archive_entry_rejected,
                    Value,
                    <<<<<<Kind/binary, " longer than "/utf8>>/binary,
                            (erlang:integer_to_binary(Width))/binary>>/binary,
                        " bytes"/utf8>>}};

        false ->
            {ok, right_pad(Bytes, Width, 0)}
    end.

-file("src/packkit/tar.gleam", 848).
-spec magic_field() -> bitstring().
magic_field() ->
    _pipe = gleam_stdlib:identity(<<"ustar"/utf8>>),
    right_pad(_pipe, 6, 0).

-file("src/packkit/tar.gleam", 921).
-spec to_octal_digits_loop(integer(), integer(), bitstring()) -> bitstring().
to_octal_digits_loop(Value, Remaining, Acc) ->
    case Remaining of
        0 ->
            Acc;

        _ ->
            Digit = Value rem 8,
            Next = Value div 8,
            Ch = 16#30 + Digit,
            to_octal_digits_loop(Next, Remaining - 1, <<Ch, Acc/bitstring>>)
    end.

-file("src/packkit/tar.gleam", 917).
-spec to_octal_digits(integer(), integer()) -> bitstring().
to_octal_digits(Value, Digit_width) ->
    to_octal_digits_loop(Value, Digit_width, <<>>).

-file("src/packkit/tar.gleam", 802).
-spec octal_field(integer(), integer()) -> bitstring().
octal_field(Value, Width) ->
    Digit_width = Width - 1,
    Digits = to_octal_digits(Value, Digit_width),
    gleam_stdlib:bit_array_concat([Digits, <<0>>]).

-file("src/packkit/tar.gleam", 810).
-spec checked_octal_field(integer(), integer(), binary()) -> {ok, bitstring()} |
    {error, packkit@error:archive_error()}.
checked_octal_field(Value, Width, Field) ->
    Digit_width = Width - 1,
    Max_value = pow_int(8, Digit_width) - 1,
    case (Value < 0) orelse (Value > Max_value) of
        true ->
            {error,
                {archive_field_overflow,
                    <<"tar "/utf8, Field/binary>>,
                    Value,
                    Max_value}};

        false ->
            {ok, octal_field(Value, Width)}
    end.

-file("src/packkit/tar.gleam", 843).
-spec checksum_value_field(integer()) -> bitstring().
checksum_value_field(Value) ->
    Digits = to_octal_digits(Value, 6),
    gleam_stdlib:bit_array_concat([Digits, <<0, 16#20>>]).

-file("src/packkit/tar.gleam", 865).
-spec devmajor_field() -> bitstring().
devmajor_field() ->
    octal_field(0, 8).

-file("src/packkit/tar.gleam", 869).
-spec devminor_field() -> bitstring().
devminor_field() ->
    octal_field(0, 8).

-file("src/packkit/tar.gleam", 1002).
-spec classify_octal_byte(integer()) -> octal_class().
classify_octal_byte(Byte) ->
    case Byte of
        0 ->
            octal_terminator;

        16#20 ->
            octal_space;

        N when (N >= 16#30) andalso (N =< 16#37) ->
            {octal_digit, N - 16#30};

        _ ->
            octal_invalid
    end.

-file("src/packkit/tar.gleam", 966).
-spec parse_octal(bitstring(), integer(), boolean()) -> {ok, integer()} |
    {error, packkit@error:archive_error()}.
parse_octal(Bytes, Acc, Any_digit) ->
    case Bytes of
        <<>> ->
            case Any_digit of
                true ->
                    {ok, Acc};

                false ->
                    {ok, 0}
            end;

        <<B, Rest/binary>> ->
            case classify_octal_byte(B) of
                {octal_digit, D} ->
                    parse_octal(Rest, (Acc * 8) + D, true);

                octal_space ->
                    parse_octal(Rest, Acc, Any_digit);

                octal_terminator ->
                    case Any_digit of
                        true ->
                            {ok, Acc};

                        false ->
                            parse_octal(Rest, Acc, Any_digit)
                    end;

                octal_invalid ->
                    {error,
                        {archive_invalid,
                            <<"invalid octal byte in tar header"/utf8>>}}
            end;

        _ ->
            {ok, Acc}
    end.

-file("src/packkit/tar.gleam", 954).
-spec read_octal_field(bitstring(), integer(), integer()) -> {ok, integer()} |
    {error, packkit@error:archive_error()}.
read_octal_field(Block, Offset, Width) ->
    case gleam_stdlib:bit_array_slice(Block, Offset, Width) of
        {error, _} ->
            {error, {archive_invalid, <<"header field out of bounds"/utf8>>}};

        {ok, Slice} ->
            parse_octal(Slice, 0, false)
    end.

-file("src/packkit/tar.gleam", 561).
-spec read_checksum_field(bitstring()) -> {ok, integer()} |
    {error, packkit@error:archive_error()}.
read_checksum_field(Block) ->
    read_octal_field(Block, 148, 8).

-file("src/packkit/tar.gleam", 551).
-spec verify_checksum(bitstring()) -> {ok, nil} |
    {error, packkit@error:archive_error()}.
verify_checksum(Block) ->
    gleam@result:'try'(
        read_checksum_field(Block),
        fun(Stored) ->
            Computed = compute_checksum(Block),
            case Stored =:= Computed of
                true ->
                    {ok, nil};

                false ->
                    {error,
                        {archive_invalid,
                            <<"tar header checksum mismatch"/utf8>>}}
            end
        end
    ).

-file("src/packkit/tar.gleam", 1011).
-spec read_byte(bitstring(), integer()) -> {ok, integer()} |
    {error, packkit@error:archive_error()}.
read_byte(Block, Offset) ->
    case gleam_stdlib:bit_array_slice(Block, Offset, 1) of
        {ok, <<B>>} ->
            {ok, B};

        _ ->
            {error, {archive_invalid, <<"header byte out of bounds"/utf8>>}}
    end.

-file("src/packkit/tar.gleam", 1023).
-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/tar"/utf8>>,
                                        function => <<"strip_trailing_nul_loop"/utf8>>,
                                        line => 1030,
                                        value => _assert_fail,
                                        start => 28397,
                                        'end' => 28451,
                                        pattern_start => 28408,
                                        pattern_end => 28417})
                    end,
                    Slice@1
            end
    end.

-file("src/packkit/tar.gleam", 1018).
-spec strip_trailing_nul(bitstring()) -> bitstring().
strip_trailing_nul(Bytes) ->
    Size = erlang:byte_size(Bytes),
    strip_trailing_nul_loop(Bytes, Size).

-file("src/packkit/tar.gleam", 933).
-spec read_string_field(bitstring(), integer(), integer()) -> {ok, binary()} |
    {error, packkit@error:archive_error()}.
read_string_field(Block, Offset, Width) ->
    case gleam_stdlib:bit_array_slice(Block, Offset, Width) of
        {error, _} ->
            {error, {archive_invalid, <<"header field out of bounds"/utf8>>}};

        {ok, Slice} ->
            Trimmed = strip_trailing_nul(Slice),
            case gleam@bit_array:to_string(Trimmed) of
                {ok, Value} ->
                    {ok, Value};

                {error, _} ->
                    {error,
                        {archive_invalid,
                            <<"header field contains non-UTF-8 bytes"/utf8>>}}
            end
    end.

-file("src/packkit/tar.gleam", 1037).
-spec trim_trailing_slash(binary()) -> binary().
trim_trailing_slash(Value) ->
    case gleam_stdlib:string_ends_with(Value, <<"/"/utf8>>) of
        true ->
            gleam@string:drop_end(Value, 1);

        false ->
            Value
    end.

-file("src/packkit/tar.gleam", 514).
-spec directory_entry(parsed_header()) -> {ok, packkit@entry:entry()} |
    {error, packkit@error:archive_error()}.
directory_entry(Header) ->
    _pipe = packkit@entry:directory_checked(
        trim_trailing_slash(erlang:element(2, Header))
    ),
    gleam@result:map_error(
        _pipe,
        fun(_capture) ->
            entry_error_to_archive_error(_capture, erlang:element(2, Header))
        end
    ).

-file("src/packkit/tar.gleam", 1064).
-spec is_zero_block(bitstring()) -> boolean().
is_zero_block(Block) ->
    case Block of
        <<>> ->
            true;

        <<0, Rest/binary>> ->
            is_zero_block(Rest);

        _ ->
            false
    end.

-file("src/packkit/tar.gleam", 502).
-spec regular_entry(parsed_header(), bitstring()) -> {ok, packkit@entry:entry()} |
    {error, packkit@error:archive_error()}.
regular_entry(Header, Bytes) ->
    Body@1 = case gleam_stdlib:bit_array_slice(
        Bytes,
        512,
        erlang:element(6, Header)
    ) of
        {ok, Body} ->
            Body;

        {error, _} ->
            <<>>
    end,
    _pipe = packkit@entry:file_checked(erlang:element(2, Header), Body@1),
    gleam@result:map_error(
        _pipe,
        fun(_capture) ->
            entry_error_to_archive_error(_capture, erlang:element(2, Header))
        end
    ).

-file("src/packkit/tar.gleam", 452).
-spec header_to_entry(parsed_header(), bitstring(), packkit@limit:limits()) -> {ok,
        packkit@entry:entry()} |
    {error, packkit@error:archive_error()}.
header_to_entry(Header, Bytes, Limits) ->
    gleam@bool:guard(
        erlang:byte_size(erlang:element(2, Header)) > packkit@limit:max_entry_name_bytes(
            Limits
        ),
        {error,
            {archive_limit_exceeded,
                <<"max_entry_name_bytes"/utf8>>,
                erlang:byte_size(erlang:element(2, Header))}},
        fun() -> gleam@result:'try'(case erlang:element(8, Header) of
                    16#30 ->
                        regular_entry(Header, Bytes);

                    16#00 ->
                        regular_entry(Header, Bytes);

                    16#35 ->
                        directory_entry(Header);

                    16#32 ->
                        link_entry(
                            Header,
                            fun(P, T) -> packkit@entry:symlink_checked(P, T) end
                        );

                    16#31 ->
                        link_entry(
                            Header,
                            fun(P@1, T@1) ->
                                packkit@entry:hardlink_checked(P@1, T@1)
                            end
                        );

                    Other ->
                        {error,
                            {archive_invalid,
                                <<"unsupported tar typeflag "/utf8,
                                    (erlang:integer_to_binary(Other))/binary>>}}
                end, fun(Base_entry) ->
                    Name_for_depth = trim_trailing_slash(
                        erlang:element(2, Header)
                    ),
                    _pipe = case packkit@entry:path_checked(Name_for_depth) of
                        {ok, Parsed_path} ->
                            case packkit@entry:depth(Parsed_path) > packkit@limit:max_entry_depth(
                                Limits
                            ) of
                                true ->
                                    {error,
                                        {archive_limit_exceeded,
                                            <<"max_entry_depth"/utf8>>,
                                            packkit@entry:depth(Parsed_path)}};

                                false ->
                                    {ok, nil}
                            end;

                        {error, _} ->
                            {ok, nil}
                    end,
                    gleam@result:'try'(
                        _pipe,
                        fun(_) ->
                            {ok,
                                begin
                                    _pipe@1 = Base_entry,
                                    _pipe@2 = packkit@entry:with_mode(
                                        _pipe@1,
                                        erlang:element(3, Header)
                                    ),
                                    _pipe@3 = packkit@entry:with_owner(
                                        _pipe@2,
                                        erlang:element(4, Header),
                                        erlang:element(5, Header)
                                    ),
                                    packkit@entry:with_modified_at(
                                        _pipe@3,
                                        erlang:element(7, Header)
                                    )
                                end}
                        end
                    )
                end) end
    ).

-file("src/packkit/tar.gleam", 877).
-spec end_marker() -> bitstring().
end_marker() ->
    byte_repeat(0, 512 * 2).

-file("src/packkit/tar.gleam", 888).
-spec round_up_to_block(integer()) -> integer().
round_up_to_block(Size) ->
    case case 512 of
        0 -> 0;
        Gleam@denominator -> Size rem Gleam@denominator
    end of
        0 ->
            Size;

        Rem ->
            Size + (512 - Rem)
    end.

-file("src/packkit/tar.gleam", 881).
-spec pad_to_block(bitstring()) -> bitstring().
pad_to_block(Body) ->
    Size = erlang:byte_size(Body),
    Padded_size = round_up_to_block(Size),
    Padding = Padded_size - Size,
    gleam_stdlib:bit_array_concat([Body, byte_repeat(0, Padding)]).

-file("src/packkit/tar.gleam", 1044).
-spec verify_double_zero_terminator(bitstring(), list(packkit@entry:entry())) -> {ok,
        list(packkit@entry:entry())} |
    {error, packkit@error:archive_error()}.
verify_double_zero_terminator(Bytes, Acc) ->
    case gleam_stdlib:bit_array_slice(Bytes, 512, 512) of
        {ok, Second} ->
            case is_zero_block(Second) of
                true ->
                    {ok, Acc};

                false ->
                    {error,
                        {archive_invalid,
                            <<"tar stream ended after a single zero block"/utf8>>}}
            end;

        {error, _} ->
            {error,
                {archive_invalid,
                    <<"tar stream ended after a single zero block"/utf8>>}}
    end.

-file("src/packkit/tar.gleam", 742).
-spec search_prefix_split(
    list(binary()),
    integer(),
    integer(),
    gleam@option:option({binary(), binary()})
) -> gleam@option:option({binary(), binary()}).
search_prefix_split(Segments, Total, Split_at, Best) ->
    case Split_at >= Total of
        true ->
            Best;

        false ->
            {Left, Right} = take_split(Segments, Split_at),
            Prefix = gleam@string:join(Left, <<"/"/utf8>>),
            Rest = gleam@string:join(Right, <<"/"/utf8>>),
            Fits = ((erlang:byte_size(Prefix) =< 155) andalso (erlang:byte_size(
                Rest
            )
            =< 100))
            andalso (erlang:byte_size(Rest) > 0),
            Next_best = case Fits of
                true ->
                    {some, {Prefix, Rest}};

                false ->
                    Best
            end,
            search_prefix_split(Segments, Total, Split_at + 1, Next_best)
    end.

-file("src/packkit/tar.gleam", 732).
-spec find_prefix_split(list(binary())) -> gleam@option:option({binary(),
    binary()}).
find_prefix_split(Segments) ->
    Total = erlang:length(Segments),
    case Total < 2 of
        true ->
            none;

        false ->
            search_prefix_split(Segments, Total, 1, none)
    end.

-file("src/packkit/tar.gleam", 704).
-spec attempt_prefix_split(binary(), integer()) -> {ok,
        {bitstring(), bitstring()}} |
    {error, packkit@error:archive_error()}.
attempt_prefix_split(Canonical, Total_bytes) ->
    case Total_bytes > ((100 + 155) + 1) of
        true ->
            {error,
                {archive_entry_rejected,
                    Canonical,
                    <<"name longer than 256 bytes (USTAR limit)"/utf8>>}};

        false ->
            Segments = gleam@string:split(Canonical, <<"/"/utf8>>),
            case find_prefix_split(Segments) of
                {some, {Prefix, Rest}} ->
                    {ok,
                        {right_pad(gleam_stdlib:identity(Rest), 100, 0),
                            right_pad(gleam_stdlib:identity(Prefix), 155, 0)}};

                none ->
                    {error,
                        {archive_entry_rejected,
                            Canonical,
                            <<"no USTAR-compatible prefix/name split"/utf8>>}}
            end
    end.

-file("src/packkit/tar.gleam", 682).
-spec split_name_field(binary(), packkit@entry:entry_kind()) -> {ok,
        {bitstring(), bitstring()}} |
    {error, packkit@error:archive_error()}.
split_name_field(Path, Kind) ->
    Canonical = case Kind of
        directory ->
            <<Path/binary, "/"/utf8>>;

        _ ->
            Path
    end,
    Canonical_bytes = gleam_stdlib:identity(Canonical),
    Size = erlang:byte_size(Canonical_bytes),
    case Size =< 100 of
        true ->
            {ok, {right_pad(Canonical_bytes, 100, 0), right_pad(<<>>, 155, 0)}};

        false ->
            attempt_prefix_split(Canonical, Size)
    end.

-file("src/packkit/tar.gleam", 422).
-spec parse_header(bitstring()) -> {ok, parsed_header()} |
    {error, packkit@error:archive_error()}.
parse_header(Block) ->
    gleam@result:'try'(
        verify_checksum(Block),
        fun(_) ->
            gleam@result:'try'(
                read_string_field(Block, 0, 100),
                fun(Raw_name) ->
                    gleam@result:'try'(
                        read_octal_field(Block, 100, 8),
                        fun(Mode) ->
                            gleam@result:'try'(
                                read_octal_field(Block, 108, 8),
                                fun(Uid) ->
                                    gleam@result:'try'(
                                        read_octal_field(Block, 116, 8),
                                        fun(Gid) ->
                                            gleam@result:'try'(
                                                read_octal_field(Block, 124, 12),
                                                fun(Size) ->
                                                    gleam@result:'try'(
                                                        read_octal_field(
                                                            Block,
                                                            136,
                                                            12
                                                        ),
                                                        fun(Mtime) ->
                                                            gleam@result:'try'(
                                                                read_byte(
                                                                    Block,
                                                                    156
                                                                ),
                                                                fun(Typeflag) ->
                                                                    gleam@result:'try'(
                                                                        read_string_field(
                                                                            Block,
                                                                            157,
                                                                            100
                                                                        ),
                                                                        fun(
                                                                            Linkname
                                                                        ) ->
                                                                            gleam@result:'try'(
                                                                                read_string_field(
                                                                                    Block,
                                                                                    345,
                                                                                    155
                                                                                ),
                                                                                fun(
                                                                                    Prefix
                                                                                ) ->
                                                                                    Name = case Prefix of
                                                                                        <<""/utf8>> ->
                                                                                            Raw_name;

                                                                                        _ ->
                                                                                            <<<<Prefix/binary,
                                                                                                    "/"/utf8>>/binary,
                                                                                                Raw_name/binary>>
                                                                                    end,
                                                                                    {ok,
                                                                                        {parsed_header,
                                                                                            Name,
                                                                                            Mode,
                                                                                            Uid,
                                                                                            Gid,
                                                                                            Size,
                                                                                            Mtime,
                                                                                            Typeflag,
                                                                                            Linkname}}
                                                                                end
                                                                            )
                                                                        end
                                                                    )
                                                                end
                                                            )
                                                        end
                                                    )
                                                end
                                            )
                                        end
                                    )
                                end
                            )
                        end
                    )
                end
            )
        end
    ).

-file("src/packkit/tar.gleam", 592).
-spec build_header(packkit@entry:entry()) -> {ok, bitstring()} |
    {error, packkit@error:archive_error()}.
build_header(Value) ->
    Kind = packkit@entry:kind(Value),
    Path = packkit@entry:to_string(packkit@entry:path(Value)),
    Metadata = packkit@entry:metadata(Value),
    gleam@result:'try'(
        split_name_field(Path, Kind),
        fun(_use0) ->
            {Name_field, Prefix_field} = _use0,
            Typeflag = case Kind of
                file ->
                    16#30;

                directory ->
                    16#35;

                symlink ->
                    16#32;

                hardlink ->
                    16#31
            end,
            Size = case Kind of
                file ->
                    erlang:byte_size(packkit@entry:body(Value));

                _ ->
                    0
            end,
            Linkname = case packkit@entry:link_target(Value) of
                {some, Target} ->
                    Target;

                none ->
                    <<""/utf8>>
            end,
            gleam@result:'try'(
                fit_field(Linkname, 100, <<"linkname"/utf8>>),
                fun(Linkname_field) ->
                    gleam@result:'try'(
                        checked_octal_field(
                            packkit@entry:mode(Metadata),
                            8,
                            <<"mode"/utf8>>
                        ),
                        fun(Mode_field) ->
                            gleam@result:'try'(
                                checked_octal_field(
                                    packkit@entry:user_id(Metadata),
                                    8,
                                    <<"uid"/utf8>>
                                ),
                                fun(Uid_field) ->
                                    gleam@result:'try'(
                                        checked_octal_field(
                                            packkit@entry:group_id(Metadata),
                                            8,
                                            <<"gid"/utf8>>
                                        ),
                                        fun(Gid_field) ->
                                            gleam@result:'try'(
                                                checked_octal_field(
                                                    Size,
                                                    12,
                                                    <<"size"/utf8>>
                                                ),
                                                fun(Size_field) ->
                                                    gleam@result:'try'(
                                                        checked_octal_field(
                                                            packkit@entry:modified_at_unix(
                                                                Metadata
                                                            ),
                                                            12,
                                                            <<"mtime"/utf8>>
                                                        ),
                                                        fun(Mtime_field) ->
                                                            Initial = gleam_stdlib:bit_array_concat(
                                                                [Name_field,
                                                                    Mode_field,
                                                                    Uid_field,
                                                                    Gid_field,
                                                                    Size_field,
                                                                    Mtime_field,
                                                                    checksum_blank_field(
                                                                        
                                                                    ),
                                                                    <<Typeflag>>,
                                                                    Linkname_field,
                                                                    magic_field(
                                                                        
                                                                    ),
                                                                    version_field(
                                                                        
                                                                    ),
                                                                    uname_field(
                                                                        
                                                                    ),
                                                                    gname_field(
                                                                        
                                                                    ),
                                                                    devmajor_field(
                                                                        
                                                                    ),
                                                                    devminor_field(
                                                                        
                                                                    ),
                                                                    Prefix_field,
                                                                    tail_padding(
                                                                        
                                                                    )]
                                                            ),
                                                            Checksum_value = compute_checksum(
                                                                Initial
                                                            ),
                                                            Prefix_bits@1 = case gleam_stdlib:bit_array_slice(
                                                                Initial,
                                                                0,
                                                                148
                                                            ) of
                                                                {ok,
                                                                    Prefix_bits} -> Prefix_bits;
                                                                _assert_fail ->
                                                                    erlang:error(
                                                                            #{gleam_error => let_assert,
                                                                                message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                                                                                file => <<?FILEPATH/utf8>>,
                                                                                module => <<"packkit/tar"/utf8>>,
                                                                                function => <<"build_header"/utf8>>,
                                                                                line => 670,
                                                                                value => _assert_fail,
                                                                                start => 19525,
                                                                                'end' => 19586,
                                                                                pattern_start => 19536,
                                                                                pattern_end => 19551}
                                                                        )
                                                            end,
                                                            Suffix_bits@1 = case gleam_stdlib:bit_array_slice(
                                                                Initial,
                                                                156,
                                                                erlang:byte_size(
                                                                    Initial
                                                                )
                                                                - 156
                                                            ) of
                                                                {ok,
                                                                    Suffix_bits} -> Suffix_bits;
                                                                _assert_fail@1 ->
                                                                    erlang:error(
                                                                            #{gleam_error => let_assert,
                                                                                message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                                                                                file => <<?FILEPATH/utf8>>,
                                                                                module => <<"packkit/tar"/utf8>>,
                                                                                function => <<"build_header"/utf8>>,
                                                                                line => 671,
                                                                                value => _assert_fail@1,
                                                                                start => 19589,
                                                                                'end' => 19687,
                                                                                pattern_start => 19600,
                                                                                pattern_end => 19615}
                                                                        )
                                                            end,
                                                            {ok,
                                                                gleam_stdlib:bit_array_concat(
                                                                    [Prefix_bits@1,
                                                                        checksum_value_field(
                                                                            Checksum_value
                                                                        ),
                                                                        Suffix_bits@1]
                                                                )}
                                                        end
                                                    )
                                                end
                                            )
                                        end
                                    )
                                end
                            )
                        end
                    )
                end
            )
        end
    ).

-file("src/packkit/tar.gleam", 583).
-spec encode_entry(packkit@entry:entry()) -> {ok, bitstring()} |
    {error, packkit@error:archive_error()}.
encode_entry(Entry_value) ->
    gleam@result:'try'(
        build_header(Entry_value),
        fun(Header) ->
            Body = packkit@entry:body(Entry_value),
            Body_blocks = pad_to_block(Body),
            {ok, gleam_stdlib:bit_array_concat([Header, Body_blocks])}
        end
    ).

-file("src/packkit/tar.gleam", 105).
?DOC(" Encode a logical archive to a USTAR 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),
                        end_marker()],
                    gleam_stdlib:bit_array_concat(_pipe@3)
                end
            ) end
    ).

-file("src/packkit/tar.gleam", 208).
-spec dispatch_typeflag(
    bitstring(),
    bitstring(),
    list(packkit@entry:entry()),
    integer(),
    packkit@limit:limits(),
    pending_override(),
    parsed_header()
) -> {ok, list(packkit@entry:entry())} | {error, packkit@error:archive_error()}.
dispatch_typeflag(Bytes, Rest, Acc, Count, Limits, Pending, Header) ->
    case erlang:element(8, Header) of
        16#4C ->
            gleam@result:'try'(
                read_string_body(
                    Bytes,
                    512,
                    erlang:element(6, Header),
                    <<"GNU long name"/utf8>>
                ),
                fun(Long_name) ->
                    decode_loop_with_pending(
                        Rest,
                        Acc,
                        Count,
                        Limits,
                        {pending_override,
                            Long_name,
                            erlang:element(3, Pending)}
                    )
                end
            );

        16#4B ->
            gleam@result:'try'(
                read_string_body(
                    Bytes,
                    512,
                    erlang:element(6, Header),
                    <<"GNU long linkname"/utf8>>
                ),
                fun(Long_link) ->
                    decode_loop_with_pending(
                        Rest,
                        Acc,
                        Count,
                        Limits,
                        {pending_override,
                            erlang:element(2, Pending),
                            Long_link}
                    )
                end
            );

        16#78 ->
            gleam@result:'try'(
                read_pax_body(Bytes, 512, erlang:element(6, Header)),
                fun(Pax_body) ->
                    Pending@1 = apply_pax_records(Pending, Pax_body),
                    decode_loop_with_pending(
                        Rest,
                        Acc,
                        Count,
                        Limits,
                        Pending@1
                    )
                end
            );

        16#67 ->
            gleam@result:'try'(
                read_pax_body(Bytes, 512, erlang:element(6, Header)),
                fun(Pax_body) ->
                    Pending@1 = apply_pax_records(Pending, Pax_body),
                    decode_loop_with_pending(
                        Rest,
                        Acc,
                        Count,
                        Limits,
                        Pending@1
                    )
                end
            );

        _ ->
            Merged_header = apply_pending(Header, Pending),
            gleam@result:'try'(
                check_member_limit(Count + 1, Limits),
                fun(_) ->
                    gleam@result:'try'(
                        header_to_entry(Merged_header, Bytes, Limits),
                        fun(Entry_value) ->
                            decode_loop_with_pending(
                                Rest,
                                [Entry_value | Acc],
                                Count + 1,
                                Limits,
                                no_pending()
                            )
                        end
                    )
                end
            )
    end.

-file("src/packkit/tar.gleam", 171).
-spec decode_loop_with_pending(
    bitstring(),
    list(packkit@entry:entry()),
    integer(),
    packkit@limit:limits(),
    pending_override()
) -> {ok, list(packkit@entry:entry())} | {error, packkit@error:archive_error()}.
decode_loop_with_pending(Bytes, Acc, Count, Limits, Pending) ->
    gleam@bool:guard(
        erlang:byte_size(Bytes) < 512,
        {error,
            {archive_invalid,
                <<"tar stream ended before terminator blocks"/utf8>>}},
        fun() ->
            Header_bits@1 = case gleam_stdlib:bit_array_slice(Bytes, 0, 512) 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/tar"/utf8>>,
                                function => <<"decode_loop_with_pending"/utf8>>,
                                line => 184,
                                value => _assert_fail,
                                start => 5287,
                                'end' => 5353,
                                pattern_start => 5298,
                                pattern_end => 5313})
            end,
            gleam@bool:lazy_guard(
                is_zero_block(Header_bits@1),
                fun() -> verify_double_zero_terminator(Bytes, Acc) end,
                fun() ->
                    gleam@result:'try'(
                        parse_header(Header_bits@1),
                        fun(Header) ->
                            Body_padded = round_up_to_block(
                                erlang:element(6, Header)
                            ),
                            Total_advance = 512 + Body_padded,
                            gleam@bool:guard(
                                erlang:byte_size(Bytes) < Total_advance,
                                {error,
                                    {archive_invalid,
                                        <<"tar entry body extends beyond the input"/utf8>>}},
                                fun() ->
                                    Rest@1 = case gleam_stdlib:bit_array_slice(
                                        Bytes,
                                        Total_advance,
                                        erlang:byte_size(Bytes) - Total_advance
                                    ) of
                                        {ok, Rest} -> Rest;
                                        _assert_fail@1 ->
                                            erlang:error(
                                                    #{gleam_error => let_assert,
                                                        message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                                                        file => <<?FILEPATH/utf8>>,
                                                        module => <<"packkit/tar"/utf8>>,
                                                        function => <<"decode_loop_with_pending"/utf8>>,
                                                        line => 199,
                                                        value => _assert_fail@1,
                                                        start => 5954,
                                                        'end' => 6086,
                                                        pattern_start => 5965,
                                                        pattern_end => 5973}
                                                )
                                    end,
                                    dispatch_typeflag(
                                        Bytes,
                                        Rest@1,
                                        Acc,
                                        Count,
                                        Limits,
                                        Pending,
                                        Header
                                    )
                                end
                            )
                        end
                    )
                end
            )
        end
    ).

-file("src/packkit/tar.gleam", 162).
-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) ->
    decode_loop_with_pending(Bytes, Acc, Count, Limits, no_pending()).

-file("src/packkit/tar.gleam", 137).
?DOC(
    " Decode a USTAR byte stream into a logical archive using the supplied\n"
    " resource 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/tar.gleam", 129).
?DOC(
    " Decode a USTAR byte stream into a logical archive using the default\n"
    " resource limits.\n"
).
-spec decode(bitstring()) -> {ok, packkit@archive:archive()} |
    {error, packkit@error:archive_error()}.
decode(Bytes) ->
    decode_with_limits(Bytes, packkit@limit:default()).