-module(packkit@entry). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/packkit/entry.gleam"). -export([kind/1, is_file/1, is_directory/1, is_symlink/1, is_hardlink/1, path/1, body/1, link_target/1, metadata/1, with_owner_checked/3, with_owner/3, with_modified_at_checked/2, with_modified_at/2, to_string/1, depth/1, mode/1, user_id/1, group_id/1, modified_at_unix/1, path_checked/1, path_unchecked/1, file_checked/2, file/2, directory_checked/1, directory/1, symlink_checked/2, symlink/2, hardlink_checked/2, hardlink/2, with_mode_checked/2, with_mode/2]). -export_type([entry_path/0, entry_kind/0, metadata/0, entry/0, entry_error/0, metadata_error/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. -opaque entry_path() :: {entry_path, binary(), list(binary())}. -type entry_kind() :: file | directory | symlink | hardlink. -opaque metadata() :: {metadata, integer(), integer(), integer(), integer()}. -opaque entry() :: {entry, entry_kind(), entry_path(), bitstring(), gleam@option:option(binary()), metadata()}. -type entry_error() :: empty_path | {absolute_path, binary()} | {path_traversal, binary()} | {windows_path, binary()} | {empty_segment, binary()} | {dot_segment, binary()} | {contains_nul, binary()}. -type metadata_error() :: {mode_out_of_range, integer()} | {owner_out_of_range, integer()} | {modified_at_out_of_range, integer()}. -file("src/packkit/entry.gleam", 227). ?DOC(" Read the logical entry kind.\n"). -spec kind(entry()) -> entry_kind(). kind(Entry) -> erlang:element(2, Entry). -file("src/packkit/entry.gleam", 233). ?DOC( " Short-circuit predicate for `kind(entry) == File`. Saves callers\n" " from importing `EntryKind` just to filter a list of entries.\n" ). -spec is_file(entry()) -> boolean(). is_file(Entry) -> erlang:element(2, Entry) =:= file. -file("src/packkit/entry.gleam", 238). ?DOC(" Short-circuit predicate for `kind(entry) == Directory`.\n"). -spec is_directory(entry()) -> boolean(). is_directory(Entry) -> erlang:element(2, Entry) =:= directory. -file("src/packkit/entry.gleam", 243). ?DOC(" Short-circuit predicate for `kind(entry) == Symlink`.\n"). -spec is_symlink(entry()) -> boolean(). is_symlink(Entry) -> erlang:element(2, Entry) =:= symlink. -file("src/packkit/entry.gleam", 248). ?DOC(" Short-circuit predicate for `kind(entry) == Hardlink`.\n"). -spec is_hardlink(entry()) -> boolean(). is_hardlink(Entry) -> erlang:element(2, Entry) =:= hardlink. -file("src/packkit/entry.gleam", 253). ?DOC(" Read the validated entry path.\n"). -spec path(entry()) -> entry_path(). path(Entry) -> erlang:element(3, Entry). -file("src/packkit/entry.gleam", 258). ?DOC(" Read the raw byte body for file-like entries.\n"). -spec body(entry()) -> bitstring(). body(Entry) -> erlang:element(4, Entry). -file("src/packkit/entry.gleam", 263). ?DOC(" Read the optional link target.\n"). -spec link_target(entry()) -> gleam@option:option(binary()). link_target(Entry) -> erlang:element(5, Entry). -file("src/packkit/entry.gleam", 268). ?DOC(" Read the metadata bundle.\n"). -spec metadata(entry()) -> metadata(). metadata(Entry) -> erlang:element(6, Entry). -file("src/packkit/entry.gleam", 339). ?DOC( " Override the entry owner identifiers after validating they are\n" " non-negative. Format-side overflow (e.g. tar's 21-bit field) is\n" " still surfaced at encode time as `ArchiveFieldOverflow`.\n" ). -spec with_owner_checked(entry(), integer(), integer()) -> {ok, entry()} | {error, metadata_error()}. with_owner_checked(Entry, User_id, Group_id) -> gleam@bool:guard( User_id < 0, {error, {owner_out_of_range, User_id}}, fun() -> gleam@bool:guard( Group_id < 0, {error, {owner_out_of_range, Group_id}}, fun() -> {metadata, Mode, _, _, Modified_at_unix} = erlang:element( 6, Entry ), {ok, {entry, erlang:element(2, Entry), erlang:element(3, Entry), erlang:element(4, Entry), erlang:element(5, Entry), {metadata, Mode, User_id, Group_id, Modified_at_unix}}} end ) end ). -file("src/packkit/entry.gleam", 324). ?DOC( " Override the entry owner identifiers. Negative values panic;\n" " see [with_owner_checked] for the validated variant. No upper\n" " bound is enforced here — format-specific encoders (tar, ar, cpio\n" " newc) each apply their own narrower field-width checks at encode\n" " time, so a value that's valid for one format and oversized for\n" " another can still be expressed as an `Entry`.\n" ). -spec with_owner(entry(), integer(), integer()) -> entry(). with_owner(Entry, User_id, Group_id) -> case with_owner_checked(Entry, User_id, Group_id) of {ok, E} -> E; {error, _} -> erlang:error(#{gleam_error => panic, message => <<"packkit/entry.with_owner: uid/gid must be non-negative"/utf8>>, file => <>, module => <<"packkit/entry"/utf8>>, function => <<"with_owner"/utf8>>, line => 332}) end. -file("src/packkit/entry.gleam", 383). ?DOC( " Override the last-modified timestamp after validating it is\n" " non-negative. The format-specific upper bound is enforced at\n" " encode time as `ArchiveFieldOverflow`.\n" ). -spec with_modified_at_checked(entry(), integer()) -> {ok, entry()} | {error, metadata_error()}. with_modified_at_checked(Entry, Unix_seconds) -> gleam@bool:guard( Unix_seconds < 0, {error, {modified_at_out_of_range, Unix_seconds}}, fun() -> {metadata, Mode, User_id, Group_id, _} = erlang:element(6, Entry), {ok, {entry, erlang:element(2, Entry), erlang:element(3, Entry), erlang:element(4, Entry), erlang:element(5, Entry), {metadata, Mode, User_id, Group_id, Unix_seconds}}} end ). -file("src/packkit/entry.gleam", 372). ?DOC( " Override the last-modified timestamp. Negative values panic;\n" " see [with_modified_at_checked] for the validated variant. As\n" " with [with_owner], the format-specific upper bound (gzip 32-bit,\n" " tar 11-octal-digit, …) is enforced at encode time.\n" ). -spec with_modified_at(entry(), integer()) -> entry(). with_modified_at(Entry, Unix_seconds) -> case with_modified_at_checked(Entry, Unix_seconds) of {ok, E} -> E; {error, _} -> erlang:error(#{gleam_error => panic, message => <<"packkit/entry.with_modified_at: unix_seconds must be non-negative"/utf8>>, file => <>, module => <<"packkit/entry"/utf8>>, function => <<"with_modified_at"/utf8>>, line => 376}) end. -file("src/packkit/entry.gleam", 408). ?DOC(" Convert an `EntryPath` back to its canonical string form.\n"). -spec to_string(entry_path()) -> binary(). to_string(Path) -> erlang:element(2, Path). -file("src/packkit/entry.gleam", 413). ?DOC(" Number of segments in the path.\n"). -spec depth(entry_path()) -> integer(). depth(Path) -> erlang:length(erlang:element(3, Path)). -file("src/packkit/entry.gleam", 418). ?DOC(" File mode stored in metadata.\n"). -spec mode(metadata()) -> integer(). mode(Metadata) -> erlang:element(2, Metadata). -file("src/packkit/entry.gleam", 423). ?DOC(" User identifier stored in metadata.\n"). -spec user_id(metadata()) -> integer(). user_id(Metadata) -> erlang:element(3, Metadata). -file("src/packkit/entry.gleam", 428). ?DOC(" Group identifier stored in metadata.\n"). -spec group_id(metadata()) -> integer(). group_id(Metadata) -> erlang:element(4, Metadata). -file("src/packkit/entry.gleam", 433). ?DOC(" Last-modified Unix timestamp stored in metadata.\n"). -spec modified_at_unix(metadata()) -> integer(). modified_at_unix(Metadata) -> erlang:element(5, Metadata). -file("src/packkit/entry.gleam", 437). -spec file_metadata() -> metadata(). file_metadata() -> {metadata, 420, 0, 0, 0}. -file("src/packkit/entry.gleam", 441). -spec directory_metadata() -> metadata(). directory_metadata() -> {metadata, 493, 0, 0, 0}. -file("src/packkit/entry.gleam", 445). -spec link_metadata() -> metadata(). link_metadata() -> {metadata, 511, 0, 0, 0}. -file("src/packkit/entry.gleam", 449). -spec contains_empty_segment(list(binary())) -> boolean(). contains_empty_segment(Segments) -> case Segments of [] -> false; [Segment | Rest] -> (Segment =:= <<""/utf8>>) orelse contains_empty_segment(Rest) end. -file("src/packkit/entry.gleam", 456). -spec contains_dot_segment(list(binary())) -> boolean(). contains_dot_segment(Segments) -> case Segments of [] -> false; [Segment | Rest] -> (Segment =:= <<"."/utf8>>) orelse contains_dot_segment(Rest) end. -file("src/packkit/entry.gleam", 463). -spec contains_traversal_segment(list(binary())) -> boolean(). contains_traversal_segment(Segments) -> case Segments of [] -> false; [Segment | Rest] -> (Segment =:= <<".."/utf8>>) orelse contains_traversal_segment(Rest) end. -file("src/packkit/entry.gleam", 66). ?DOC(" Validate a relative archive path.\n"). -spec path_checked(binary()) -> {ok, entry_path()} | {error, entry_error()}. path_checked(Value) -> gleam@bool:guard( Value =:= <<""/utf8>>, {error, empty_path}, fun() -> gleam@bool:guard( gleam_stdlib:contains_string(Value, <<"\x{0000}"/utf8>>), {error, {contains_nul, Value}}, fun() -> gleam@bool:guard( gleam_stdlib:string_starts_with(Value, <<"/"/utf8>>), {error, {absolute_path, Value}}, fun() -> gleam@bool:guard( gleam_stdlib:contains_string( Value, <<"\\"/utf8>> ), {error, {windows_path, Value}}, fun() -> Segments = gleam@string:split( Value, <<"/"/utf8>> ), gleam@bool:guard( contains_empty_segment(Segments), {error, {empty_segment, Value}}, fun() -> gleam@bool:guard( contains_dot_segment(Segments), {error, {dot_segment, Value}}, fun() -> gleam@bool:guard( contains_traversal_segment( Segments ), {error, {path_traversal, Value}}, fun() -> {ok, {entry_path, Value, Segments}} end ) end ) end ) end ) end ) end ) end ). -file("src/packkit/entry.gleam", 101). ?DOC( " Panicking counterpart of `path_checked`. The getter on `Entry`\n" " claims the short name; this constructor takes the explicit suffix.\n" ). -spec path_unchecked(binary()) -> entry_path(). path_unchecked(Value) -> case path_checked(Value) of {ok, Path} -> Path; {error, _} -> erlang:error(#{gleam_error => panic, message => <<"packkit/entry.path_unchecked: entry path must be a safe relative path"/utf8>>, file => <>, module => <<"packkit/entry"/utf8>>, function => <<"path_unchecked"/utf8>>, line => 105}) end. -file("src/packkit/entry.gleam", 110). ?DOC(" Build a regular file entry after path validation.\n"). -spec file_checked(binary(), bitstring()) -> {ok, entry()} | {error, entry_error()}. file_checked(Path, Body) -> case path_checked(Path) of {ok, Safe_path} -> {ok, {entry, file, Safe_path, Body, none, file_metadata()}}; {error, Error} -> {error, Error} end. -file("src/packkit/entry.gleam", 128). ?DOC(" Panicking counterpart of `file_checked`.\n"). -spec file(binary(), bitstring()) -> entry(). file(Path, Body) -> case file_checked(Path, Body) of {ok, Entry} -> Entry; {error, _} -> erlang:error(#{gleam_error => panic, message => <<"packkit/entry.file: entry path must be a safe relative path"/utf8>>, file => <>, module => <<"packkit/entry"/utf8>>, function => <<"file"/utf8>>, line => 132}) end. -file("src/packkit/entry.gleam", 137). ?DOC(" Build a directory entry after path validation.\n"). -spec directory_checked(binary()) -> {ok, entry()} | {error, entry_error()}. directory_checked(Path) -> case path_checked(Path) of {ok, Safe_path} -> {ok, {entry, directory, Safe_path, <<>>, none, directory_metadata()}}; {error, Error} -> {error, Error} end. -file("src/packkit/entry.gleam", 152). ?DOC(" Panicking counterpart of `directory_checked`.\n"). -spec directory(binary()) -> entry(). directory(Path) -> case directory_checked(Path) of {ok, Entry} -> Entry; {error, _} -> erlang:error(#{gleam_error => panic, message => <<"packkit/entry.directory: entry path must be a safe relative path"/utf8>>, file => <>, module => <<"packkit/entry"/utf8>>, function => <<"directory"/utf8>>, line => 156}) end. -file("src/packkit/entry.gleam", 162). ?DOC( " Build a symbolic-link entry. The entry path is strictly validated.\n" " The link target is preserved as metadata but must not contain NUL.\n" ). -spec symlink_checked(binary(), binary()) -> {ok, entry()} | {error, entry_error()}. symlink_checked(Path, Target) -> gleam@bool:guard( gleam_stdlib:contains_string(Target, <<"\x{0000}"/utf8>>), {error, {contains_nul, Target}}, fun() -> case path_checked(Path) of {ok, Safe_path} -> {ok, {entry, symlink, Safe_path, <<>>, {some, Target}, link_metadata()}}; {error, Error} -> {error, Error} end end ). -file("src/packkit/entry.gleam", 185). ?DOC(" Panicking counterpart of `symlink_checked`.\n"). -spec symlink(binary(), binary()) -> entry(). symlink(Path, Target) -> case symlink_checked(Path, Target) of {ok, Entry} -> Entry; {error, _} -> erlang:error(#{gleam_error => panic, message => <<"packkit/entry.symlink: invalid archive path or link target"/utf8>>, file => <>, module => <<"packkit/entry"/utf8>>, function => <<"symlink"/utf8>>, line => 189}) end. -file("src/packkit/entry.gleam", 195). ?DOC( " Build a hard-link entry. The entry path is strictly validated.\n" " The link target is preserved as metadata but must not contain NUL.\n" ). -spec hardlink_checked(binary(), binary()) -> {ok, entry()} | {error, entry_error()}. hardlink_checked(Path, Target) -> gleam@bool:guard( gleam_stdlib:contains_string(Target, <<"\x{0000}"/utf8>>), {error, {contains_nul, Target}}, fun() -> case path_checked(Path) of {ok, Safe_path} -> {ok, {entry, hardlink, Safe_path, <<>>, {some, Target}, link_metadata()}}; {error, Error} -> {error, Error} end end ). -file("src/packkit/entry.gleam", 218). ?DOC(" Panicking counterpart of `hardlink_checked`.\n"). -spec hardlink(binary(), binary()) -> entry(). hardlink(Path, Target) -> case hardlink_checked(Path, Target) of {ok, Entry} -> Entry; {error, _} -> erlang:error(#{gleam_error => panic, message => <<"packkit/entry.hardlink: invalid archive path or link target"/utf8>>, file => <>, module => <<"packkit/entry"/utf8>>, function => <<"hardlink"/utf8>>, line => 222}) end. -file("src/packkit/entry.gleam", 290). ?DOC( " Override the entry mode after validating it fits the widest mode\n" " field any of our archive families serialise it through.\n" ). -spec with_mode_checked(entry(), integer()) -> {ok, entry()} | {error, metadata_error()}. with_mode_checked(Entry, Mode) -> gleam@bool:guard( (Mode < 0) orelse (Mode > 16#FFFF), {error, {mode_out_of_range, Mode}}, fun() -> {metadata, _, User_id, Group_id, Modified_at_unix} = erlang:element( 6, Entry ), {ok, {entry, erlang:element(2, Entry), erlang:element(3, Entry), erlang:element(4, Entry), erlang:element(5, Entry), {metadata, Mode, User_id, Group_id, Modified_at_unix}}} end ). -file("src/packkit/entry.gleam", 280). ?DOC( " Override the entry mode. Out-of-range values panic; use\n" " [with_mode_checked] for caller-controlled error handling.\n" ). -spec with_mode(entry(), integer()) -> entry(). with_mode(Entry, Mode) -> case with_mode_checked(Entry, Mode) of {ok, E} -> E; {error, _} -> erlang:error(#{gleam_error => panic, message => <<"packkit/entry.with_mode: mode must be in the inclusive range 0..0xFFFF"/utf8>>, file => <>, module => <<"packkit/entry"/utf8>>, function => <<"with_mode"/utf8>>, line => 284}) end.