defmodule GitGud.RpmFixtures do
@moduledoc """
A synthetic but structurally real `.rpm`.
Real packages are tens of megabytes and their interesting part —
the header — is a few kilobytes of it. `GitGud.Packages.Formats.Rpm`
never reads past the header, so a fixture that carries a correct lead,
a correct signature header and a correct main header over a payload
of nonsense exercises everything the adapter does.
The parser's own tests use a real openSUSE package instead, truncated
to its headers. That is the right fixture for asserting *what RPM
looks like*; this is the right one for asserting what the adapter
does with it, and the two are deliberately separate.
"""
# {tag number, type} for each field this builder knows how to write.
# Types are rpm's: 4 INT32, 6 STRING, 8 STRING_ARRAY, 9 I18NSTRING.
@tags %{
name: {1000, 6},
version: {1001, 6},
release: {1002, 6},
epoch: {1003, 4},
summary: {1004, 9},
description: {1005, 9},
buildtime: {1006, 4},
buildhost: {1007, 6},
size: {1009, 4},
license: {1014, 6},
arch: {1022, 6},
providename: {1047, 8},
requireflags: {1048, 4},
requirename: {1049, 8},
requireversion: {1050, 8},
dirindexes: {1116, 4},
basenames: {1117, 8},
dirnames: {1118, 8},
payloadcompressor: {1125, 6}
}
@doc """
Build an `.rpm`. `overrides` replaces any of the default fields;
pass `%{name: "widget"}` for a second package, `%{epoch: 2}` to
exercise epochs. `:payload` replaces the nonsense after the header
with real (already compressed) bytes, for what reads the payload.
"""
def build_rpm(overrides \\ %{}) do
{payload, overrides} = Map.pop(overrides, :payload, "not a real cpio payload")
fields = Map.merge(defaults(), overrides)
# lead(96) + signature header — which lands on a multiple of 8 here
# by construction, so the alignment the parser does is exercised by
# the real fixture rather than this one — then the main header,
# then a payload nothing reads.
<<0xED, 0xAB, 0xEE, 0xDB>> <>
:binary.copy(<<0>>, 92) <>
header(%{}) <> header(fields) <> payload
end
defp defaults do
%{
name: "koco",
version: "0.1.2",
release: "1",
arch: "x86_64",
summary: "A Kodi Controller",
description: "A Kodi Controller, in Rust.",
license: "GPLv3",
buildhost: "runner",
buildtime: 1_757_000_000,
size: 4096,
providename: ["koco", "koco(x86-64)"],
# Two requirements, one of them rpmlib — which the index must
# drop, because createrepo does.
requirename: ["libc.so.6()(64bit)", "rpmlib(FileDigests)"],
requireflags: [0, 0x1000008],
requireversion: ["", "4.6.0-1"],
# One file that belongs in primary.xml and one that doesn't.
dirnames: ["/usr/bin/", "/usr/share/doc/koco/"],
dirindexes: [0, 1],
basenames: ["koco", "README.md"]
}
end
defp header(fields) do
{index, store} =
Enum.reduce(fields, {[], <<>>}, fn {key, value}, {index, store} ->
{tag, type} = Map.fetch!(@tags, key)
{bytes, count} = encode(type, value)
{[<<tag::32, type::32, byte_size(store)::32, count::32>> | index], store <> bytes}
end)
index = IO.iodata_to_binary(Enum.reverse(index))
<<0x8E, 0xAD, 0xE8, 0x01, 0::32, div(byte_size(index), 16)::32, byte_size(store)::32>> <>
index <> store
end
defp encode(type, value) when type in [6, 9], do: {value <> <<0>>, 1}
@doc """
A cpio archive in the newc format rpm writes. `members` are
`{path, data}` for files and `{path, {:symlink, target}}` for links.
"""
def cpio(members) do
body =
members
|> Enum.with_index(1)
|> Enum.map_join(fn {{path, data}, ino} ->
{mode, data} =
case data do
{:symlink, target} -> {0o120777, target}
data -> {0o100644, data}
end
cpio_member(path, mode, data, ino)
end)
body <> cpio_member("TRAILER!!!", 0, "", 0)
end
defp cpio_member(name, mode, data, ino) do
name = name <> <<0>>
fields =
[ino, mode, 0, 0, 1, 0, byte_size(data), 0, 0, 0, 0, byte_size(name), 0]
|> Enum.map_join(&(&1 |> Integer.to_string(16) |> String.pad_leading(8, "0")))
header = "070701" <> fields <> name
pad(header) <> pad(data)
end
defp pad(bytes), do: bytes <> :binary.copy(<<0>>, rem(4 - rem(byte_size(bytes), 4), 4))
defp encode(8, values), do: {Enum.map_join(values, &(&1 <> <<0>>)), length(values)}
defp encode(4, values) when is_list(values),
do: {for(v <- values, into: <<>>, do: <<v::32>>), length(values)}
defp encode(4, value), do: {<<value::32>>, 1}
end
neiam
/gitgud
Git Gud
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
1
Log in to mark this repository.
4.6 KiB · text
History
69f26e0