Исследуем формат RPM файлов
Михаил (aka @dx3mod)
Не так давно я завершил написание парсера RPM пакетов для одного своего следующего проекта, поэтому хотел поделиться обо всём этом.
RPM, он же RPM Package Manager и он же формат пакетов ПО во всяких энтерпрайзных дистрибутивах Linux и не только. Он вполне хорошо задокументирован (в отличие от всяких мастдайных дебианов).
Поэтому моя статья больше про краткий и неточный пересказ спецификации.
Формат
RPM-пакет - это файл, содержащий в себе метаинформацию (информацию о самом себе) и непосредственно архив (зачастую в формате cpio) с файлами.
Метаинформация идёт в самом начале файла и представляет из себя три секции:
- Ведущую (aka Lead)
- Сигнатуру (aka Signature)
- Заголовок (aka Header)
Все данные выровнены и идут в порядке big-endian.
Секция Lead
Magic bytes
Начинается с четырёх магических байтов ed ab ee db
, обозначающих, что перед нами RPM-пакет.
Собственно, вы можете сами всё посмотреть, воспользовавшись hex-редактором.
Package Format Version
После чего идут два байте, указывающих версию формата пакета. Первый байт это мажорная (major) версия, второй - минорная (minor).
03 00
То есть 03 00
это версия 3.0
, что актуальна на данный момент (хотя есть и четвёртая версия).
Type
Далее двухбайтное поле, содержащие информацию о типе пакета:
- Бинарный (
00 00
) - пакет с компилированными файлами, готовыми к установке. - Source (
00 01
) - пакет с исходными кодами.
Arch
По изначальной задумки тут должна была быть закодирована архитектура, под которую собран пакет, но эта идея устарела aka deprecated.
Двухбайтное поле.
Package filename
Строка до 66 символов (байт). Содержит название файла пакета: <имя>-<версию>-<дистрибутив>.rpm
.
OS
Двухбайтное поле кода операционной системы. Устарело.
Должно быть равно 00 01
.
Signature Type
Двухбайтное поле, указующее тип секции сигнатуры.
Должное быть равно 00 05
.
Пример
Сигнатура и заголовок
Секции signature и header имеют одинаковую структуру (будем называть её заголовком). По факту они являются просто реализацией ассоциативного массива с записями ключ-значение, в которых и содержится всё самое интересное.
Если вы знакомы с бинарными форматами сериализации, типа BSON, то принцип тут тот же (нуууу почти).
Заголовок состоит из трёх частей:
- Индекса (Index)
- Массива записей (Entries) - N-ое количество записей (entry)
- Данных (data) - тут хранятся значения записей
Index
Заголовок также начинается с четырёх магических байт. А после содержит только два важных поля: количество записей и размер секции данных.
Пример:
reserved размер секции данных
v---------v v---------v
8E AD E8 01 00 00 00 00 00 00 00 04 00 00 00 50
^---------^ ^---------^
magic code кол-во записей
На самом деле четвёртый байт (01
) обозначает версию, но оно фиксировано, поэтому можно считать за магическое число.
Entries
В части Entries находится массив из записей. Количество элементов равно полю количество записей из индекса.
Запись
Запись это структура, описывающая данные, находящиеся в части data
.
Запись состоит из:
- Тега - он же ключ в ассоциативном массиве
- Типа - тип значения записи (число, строка, массив строк и т.д.)
- Смещения - относительно начала части data
- Количества - сколько таких элементов
Пример записи:
type count
v---------v v---------v
00 00 00 3E 00 00 00 07 00 00 00 40 00 00 00 10
^---------^ ^---------^
tag offset
{
tag: 0x3E,
type: "bin",
offset: 0x40,
count: 0x10
}
data
После разбора всех записей мы можем считать данные, на которые они указывают.
Например, для предыдущего примера:
buffer.offset = data_part_offset + entry.offset;
buffer.readBytes(entry.count);
Выравнивание
После секции сигнатуры обязательно должно идти выравнивание! Нужный padding можно вычислить по формуле: padding = (8 - (sectionSize % 8)) % 8
.
Секция payload
Финальная секция и самая важная - тут находятся полезная нагрузка. Размер секции указывается в сигнатуре под тегом 1000
.
Обычно тут находится сжатый cpio-архив. Но фактически это описывается в заголовке: под тегом 1124
формат архива, под 1125
алгоритм сжатия и т.д.
Ссылки
- Package File Format
- Мой rpm-parser
- Референс реализация rpm-rs/rpm, на которую я опирался