Исследуем формат RPM файлов

#linux#rpm

Михаил (aka @dx3mod)

Не так давно я завершил написание парсера RPM пакетов для одного своего следующего проекта, поэтому хотел поделиться обо всём этом.

RPM, он же RPM Package Manager и он же формат пакетов ПО во всяких энтерпрайзных дистрибутивах Linux и не только. Он вполне хорошо задокументирован (в отличие от всяких мастдайных дебианов).

Поэтому моя статья больше про краткий и неточный пересказ спецификации.

Формат

RPM-пакет - это файл, содержащий в себе метаинформацию (информацию о самом себе) и непосредственно архив (зачастую в формате cpio) с файлами.

Метаинформация идёт в самом начале файла и представляет из себя три секции:

  1. Ведущую (aka Lead)
  2. Сигнатуру (aka Signature)
  3. Заголовок (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, то принцип тут тот же (нуууу почти).

Заголовок состоит из трёх частей:

  1. Индекса (Index)
  2. Массива записей (Entries) - N-ое количество записей (entry)
  3. Данных (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.

Запись состоит из:

  1. Тега - он же ключ в ассоциативном массиве
  2. Типа - тип значения записи (число, строка, массив строк и т.д.)
  3. Смещения - относительно начала части data
  4. Количества - сколько таких элементов

Пример записи:

                       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 алгоритм сжатия и т.д.

Ссылки