defmodule GitGud.Packages.Formats.Rpm.Cpio do
@moduledoc """
Lists an RPM payload: a cpio archive in the "new ASCII" format, the
one `rpmbuild` has written since forever.
Each member is a 110-byte header of hex fields, the NUL-terminated
name, then the data, each padded to four bytes. A symlink's data is
its target. The archive ends at a member named `TRAILER!!!`.
Members come back as sub-binaries of the payload, and paths are
normalised the way `GitGud.Packages.Formats.Tar` normalises them, so
the two can be searched by the same code.
Not understood: rpm's "stripped" `07070X` format, which it uses only
for packages holding a file over 4 GB and which names members by
index into the header rather than by path. Such a payload lists as
empty.
"""
alias GitGud.Packages.Formats.Tar
@trailer "TRAILER!!!"
@doc "Every file and symlink in `cpio`, keyed by absolute path."
@spec entries(binary()) :: {:ok, %{String.t() => Tar.entry()}} | :error
def entries(cpio) when is_binary(cpio), do: read(cpio, 0, %{})
defp read(cpio, at, acc) do
case cpio do
<<_::binary-size(^at), "07070", c, fields::binary-size(104), _::binary>>
when c in [?1, ?2] ->
with {:ok, [_ino, mode, _uid, _gid, _nlink, _mtime, size, _, _, _, _, namesize, _check]} <-
hex_fields(fields),
name_at = at + 110,
data_at = align4(name_at + namesize),
true <- byte_size(cpio) >= data_at + size || :error do
name = binary_part(cpio, name_at, max(namesize - 1, 0))
data = binary_part(cpio, data_at, size)
next = align4(data_at + size)
if name == @trailer do
{:ok, acc}
else
read(cpio, next, add(acc, mode, Tar.normalize(name), data))
end
end
_ ->
{:ok, acc}
end
end
# S_IFMT: 0o100000 a regular file, 0o120000 a symlink.
defp add(acc, mode, path, data) do
case Bitwise.band(mode, 0o170000) do
0o100000 -> Map.put(acc, path, {:file, data})
0o120000 -> Map.put(acc, path, {:symlink, data})
_ -> acc
end
end
# Thirteen eight-digit hex numbers.
defp hex_fields(fields) do
parsed =
for <<field::binary-size(8) <- fields>> do
case Integer.parse(field, 16) do
{n, ""} -> n
_ -> nil
end
end
if Enum.any?(parsed, &is_nil/1), do: :error, else: {:ok, parsed}
end
defp align4(n), do: n + rem(4 - rem(n, 4), 4)
end
neiam
/gitgud
Git Gud
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
1
Log in to mark this repository.
2.4 KiB · text
History
b868907