Compare commits
3
Commits
7518ed0b2f
...
292421d970
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
292421d970 | ||
|
|
b95decdffb | ||
|
|
086dda83ca |
@@ -1,125 +0,0 @@
|
||||
# Сравнение set-зависимостей Sisyphus с `newset.c`
|
||||
|
||||
Скрипт [`scripts/compare_sisyphus_set_versions.sh`](../scripts/compare_sisyphus_set_versions.sh)
|
||||
сравнивает set-строки из RPM-заголовка с результатом `reimplement/newset.c`.
|
||||
|
||||
## Что именно сравнивается
|
||||
|
||||
1. Из изолированных APT-индексов `Sisyphus/x86_64` и `Sisyphus/noarch`
|
||||
берутся `Package`, `Architecture`, `Version`, `Filename`, `MD5Sum`,
|
||||
`Provides` и `Depends`/`Pre-Depends`.
|
||||
2. RPM скачивается непосредственно из `Sisyphus/files/<arch>/RPMS` и
|
||||
проверяется по `MD5Sum` из индекса.
|
||||
3. Эталонные зависимости читаются из RPM-заголовка через массивы тегов:
|
||||
`PROVIDENAME/PROVIDEFLAGS/PROVIDEVERSION` и
|
||||
`REQUIRENAME/REQUIREFLAGS/REQUIREVERSION`.
|
||||
4. Для воспроизведения зависимостей вызываются установленные вместе с
|
||||
`rpm-build` программы `%_rpmlibdir/find-provides` и
|
||||
`%_rpmlibdir/find-requires` с методом `none,lib`.
|
||||
5. В каталоге инструментов заменяется **только** `mkset`: вместо штатного
|
||||
`mkset` подставляется совместимая обвязка над `newset.c`. `provided_symbols`,
|
||||
`ldd --bindings`, `eu-readelf`, `objdump`, `file` и остальные средства
|
||||
отбора/извлечения символов остаются штатными инструментами `rpm-build`.
|
||||
|
||||
Для `Requires` APT получает точный уже скачанный RPM-файл как вход и скачивает
|
||||
его полный runtime dependency closure в пустой пользовательский RPM root. Так
|
||||
версия и архитектура целевого пакета не выбираются повторно по имени. APT
|
||||
запускается с пустым `APT_CONFIG`; source list, package cache, archives, lists,
|
||||
status и preferences перенаправлены во временный каталог, поэтому настройки,
|
||||
hooks и RPM-база основной системы не участвуют в разрешении зависимостей.
|
||||
|
||||
Перед распаковкой проверяются имена элементов cpio и цели символических ссылок.
|
||||
Абсолютная RPM-ссылка считается путём внутри виртуального package root: например,
|
||||
`/usr/sbin/update-alternatives -> /bin/true` безопасно переписывается в
|
||||
`../../bin/true`, а не направляется в `/bin/true` основной системы. Все symlink
|
||||
сначала собираются из RPM-заголовков и создаются в порядке от родительских путей
|
||||
к дочерним; затем `cpio` извлекает только нессылочные элементы. Благодаря этому
|
||||
usrmerge-ссылки вроде `/bin -> usr/bin` создаются до файлов `/bin/*`.
|
||||
|
||||
Имена элементов с выходом через `..` и относительные symlink, фактически
|
||||
выходящие из временного root, по-прежнему отклоняются. После проверки RPM-файлы
|
||||
распаковываются без установки. Это позволяет штатному `rpm-build/ldd`
|
||||
использовать интерпретатор и библиотеки из того же снимка Sisyphus, не изменяя
|
||||
систему.
|
||||
|
||||
## Ограниченный тест
|
||||
|
||||
На ALT p11 с установленным `rpm-build`:
|
||||
|
||||
```sh
|
||||
bash scripts/compare_sisyphus_set_versions.sh \
|
||||
--package strace \
|
||||
--report strace-set-report.tsv
|
||||
```
|
||||
|
||||
Несколько пакетов:
|
||||
|
||||
```sh
|
||||
bash scripts/compare_sisyphus_set_versions.sh \
|
||||
--package strace \
|
||||
--package libdw \
|
||||
--report sample-set-report.tsv
|
||||
```
|
||||
|
||||
Либо первые десять записей индекса:
|
||||
|
||||
```sh
|
||||
bash scripts/compare_sisyphus_set_versions.sh \
|
||||
--limit 10 \
|
||||
--report sample-set-report.tsv
|
||||
```
|
||||
|
||||
Без `--all`, `--limit` или `--package` скрипт завершится до обращения к
|
||||
репозиторию. Это защищает от случайного полного запуска.
|
||||
|
||||
## Полный запуск
|
||||
|
||||
Команда предусмотрена, но в ходе разработки не запускалась:
|
||||
|
||||
```sh
|
||||
bash scripts/compare_sisyphus_set_versions.sh \
|
||||
--all \
|
||||
--report sisyphus-set-report.tsv
|
||||
```
|
||||
|
||||
Продолжение после прерывания:
|
||||
|
||||
```sh
|
||||
bash scripts/compare_sisyphus_set_versions.sh \
|
||||
--all \
|
||||
--resume \
|
||||
--report sisyphus-set-report.tsv
|
||||
```
|
||||
|
||||
`--resume` пропускает только уже завершённую комбинацию
|
||||
`package/architecture/version`; обновившийся пакет будет обработан заново.
|
||||
Завершённость подтверждается маркером `complete=1` в полной 11-польной строке
|
||||
`SUMMARY`, поэтому оборванная при записи строка не считается результатом.
|
||||
|
||||
Во время работы отчёт можно наблюдать отдельно:
|
||||
|
||||
```sh
|
||||
tail -f sisyphus-set-report.tsv
|
||||
```
|
||||
|
||||
`START` записывается до обработки пакета, а `DEPENDENCY` и `SUMMARY` — сразу
|
||||
после сравнения пакета. Поэтому файл обновляется в процессе, а не только после
|
||||
завершения всего обхода.
|
||||
|
||||
## Статусы отчёта
|
||||
|
||||
- `match` — set-строка из RPM совпала с результатом `newset.c`;
|
||||
- `mismatch` — capability и оператор совпали, set-строки различаются;
|
||||
- `missing_generated` — RPM содержит set-зависимость, но повторный запуск
|
||||
`rpm-build` её не сгенерировал;
|
||||
- `extra_generated` — повторный запуск сгенерировал зависимость, которой нет в
|
||||
RPM-заголовке;
|
||||
- `no_set_metadata` — в APT metadata пакета нет set-строк;
|
||||
- `*_error` — ошибка скачивания, контрольной суммы, распаковки либо генератора.
|
||||
|
||||
`extra_generated` показывается в строках `DEPENDENCY` и делает итоговый статус
|
||||
пакета равным `mismatch`. При анализе нужно учитывать, что бинарный RPM не
|
||||
содержит исходных spec-фильтров (`filter_from_requires` и аналогичных), поэтому
|
||||
часть таких расхождений может относиться не к `newset.c`, а к невозможности
|
||||
полностью воспроизвести фильтрацию исходного spec. Их число отдельно указано в
|
||||
поле `extras=N` строки `SUMMARY`.
|
||||
@@ -1,71 +0,0 @@
|
||||
# Notes
|
||||
|
||||
## to read
|
||||
|
||||
https://www.altlinux.org/Features/Specific
|
||||
|
||||
## Hash
|
||||
|
||||
в данный момент используется Jenkins OAAT. Считается чуть устаревшим(check), главным аналогом является:
|
||||
|
||||
https://github.com/cyan4973/xxhash
|
||||
|
||||
xxHash64 (XXH64): относительно простая имплементация на си, хвалят за скорость, 64 бит, параллель
|
||||
|
||||
необходимы тесты и графики:
|
||||
|
||||
1. насколько быстрее/медленнее
|
||||
2. коллизии на наборе букв в сравнении с рандомными данными
|
||||
3. коллизии у первого и второго хэшей между собой
|
||||
|
||||
> В XXH3 внутренний цикл, который оптимально обрабатывается векторизацией. Благодаря этому функция использует аппаратную поддержку на наборах инструкций SSE2, AVX2 и NEON. Производительность зависит от комилятора. Неожиданно оказалось, что версия, скомпилированная clang, намного превосходит остальные. Ян Колле даже подумал, что производительность хеш-функции здесь превысит пропускную способность памяти. Этой версии на графике соответствует пунктирная линия.
|
||||
|
||||
есть ещё cityhash от гугла, но она c++
|
||||
|
||||
в случае XXH64 строк кода ~200, можно вставить в файл
|
||||
в случае XXH3 строк кода ~2к, стоит использовать библиотеку напрямую, бессмысленно
|
||||
|
||||
## about collizions
|
||||
|
||||
> А вы пробовали побитовую вероятностную карту строить?
|
||||
> Есть у нас по условию множество из входных значений, которые замапились в N хешей (включая коллизии!). Эти N хешей имеют одинаковый размер и, по сути, являются массивами из K бит.
|
||||
> Так вот можно среди этих хешей посчитать 0 и посчитать 1, получится K распределений. Чем они все ближе к пополамному ± 6%, тем лучше.
|
||||
> https://raw.altlinux.team/arseny/snippets/2026-06/QSIrJSuwxp.txt
|
||||
> https://raw.altlinux.team/arseny/snippets/2026-06/QSIrJSuwxp.c
|
||||
|
||||
## free
|
||||
|
||||
free не делает очистку самой структуры, valgrind
|
||||
|
||||
## data
|
||||
|
||||
алфавит для символ библиотек:
|
||||
|
||||
```
|
||||
.0123456789@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz
|
||||
```
|
||||
|
||||
- предоставленные/определенные символы из библиотек обычно используют @@VERSION
|
||||
- обязательные/неопределенные символы из двоичных файлов обычно используют @VERSION
|
||||
|
||||
## additional about set:version.md
|
||||
|
||||
[коды](https://altlinux.space/arseny/atsv-research) от Арсения для наглядности происходящего
|
||||
|
||||
some funny [msg's](https://lists.pld-linux.org/mailman/pipermail/pld-devel-en/2013-November/012467.html)
|
||||
|
||||
по коду неоднократно раскидано `bpp < 10 || bpp > 32` проверки
|
||||
|
||||
запись `char = [1,0]` мне не нравится.
|
||||
|
||||
про "отрицательные значения = код ошибки" встречается в многих местах, стоит вынести отдельно
|
||||
проверить кодом примеры, особенно base62
|
||||
|
||||
кэш вечно копируется и переносится, немного странно
|
||||
PIVOT_SIZE странный
|
||||
|
||||
прыжки IFLT8 и IFLT4 я бы возможно делал как c1/c2
|
||||
|
||||
учитывая оптимизации, возможно стоит самостоятельно менять массивы местами до начала всех операций (но проблема с кэшем возможна)
|
||||
|
||||
хочу себе день, чтобы переписать это всё на English
|
||||
@@ -1,46 +0,0 @@
|
||||
# raw_data
|
||||
|
||||
## messages from tg
|
||||
|
||||
[6/23/26 9:57 PM] Arseny: Кстати. У меня есть серьёзная математическая, она же очень серьёзная алгоритмическая задача на исследование. ALT set:version (https://git.altlinux.org/gears/r/rpm.git?a=blob;f=lib/set.c) нужен майнтейнер.
|
||||
[6/23/26 9:57 PM] Arseny: Вы хотели алгоритмов? Вот, расскажите мне про этот.
|
||||
[6/23/26 9:57 PM] Arseny: весь алгоритм можно разбить на 4 последовательных стадии.
|
||||
|
||||
0) Задача в том, чтобы проверять, выполняется ли R ⊂ P.
|
||||
множества, стоящие в позиции P, даются командой навроде nm --dynamic -j -U /usr/lib64/libyourfavourite.so.N.
|
||||
множества, стоящие в позиции R, даются командой навроде nm --dynamic -j -u /usr/bin/yourfavouriteprog, или там может быть иной .so.
|
||||
Тут, наверное, ещё и введение в предметную область должно быть.
|
||||
|
||||
1) hash: N элементов сбрасываются в этот самый хеш; его эффективность бы стоило оценить хорошенько. (гипотеза от меня: он вообще-то плохой и даёт много коллизий на интересных нам инпутах), в результате K <= N отсортированных хешей, чисел из [0; 2**10) (по умолчанию 10 бит на хеш);
|
||||
2) delta: K <= N чисел из прошлого шага заменяются на разности с предыдущими, и получаются по-другому распределённые числа, чаще маленькие и очень, очень мало сверхбольших; есть мнение, что они для равномерно распределённых хешей распределены геометрически;
|
||||
3) golomb: эти K разностей кодируются по Rice-Golomb (https://en.wikipedia.org/wiki/Golomb_coding), делитель по умолчанию 2**7; получается префиксный код из K битострок, которые можно слева направо прочесть без lookahead и восстановить массив разностей;
|
||||
4) (якобы) base62: их конкатенация кодируется в буквы.
|
||||
|
||||
(2) и (3) близки к очевидно-понятным; я поизучал, реализовал на питоне (https://altlinux.space/arseny/atsv-research) и снова поизучал.
|
||||
А вот (1) и (4) я не занимался совсем.
|
||||
От (4) у меня тоже башка взрывается, а (1) можно очень хорошо так поизучать, и существует, наверное, обширная литература.
|
||||
[6/23/26 9:57 PM] Arseny: grep -A16 Jenkins lib/set.c
|
||||
вот в этом дереве исходников (https://git.altlinux.org/gears/r/rpm.git?p=rpm.git;a=blob;f=lib/set.c;h=9474a2ee6d7c;hb=a01a87db6e8a)
|
||||
[6/23/26 9:57 PM] Arseny: Пусть параметр-делитель равен M, и энкодеру на вход поступает число A. Тогда ищутся такие q и r < q, что A = qM + r. Частное кодируется унарной кодировкой из единиц, за ним следует нулик, а далее ровно log₂M бит на остаток. Подобрать M нужно с умом.
|
||||
```sh
|
||||
python3 stringset.py 10 7 <<EOF
|
||||
foo
|
||||
bar
|
||||
quux
|
||||
EOF
|
||||
hashes=[298, 487, 588]
|
||||
deltas=[298, 189, 101]
|
||||
codes=[b'1100101010', b'100111101', b'01100101']
|
||||
golomb divisor: 128 (minimum word bit length: 8)
|
||||
total bit length: 27
|
||||
per-elem bit length: 9.000
|
||||
```
|
||||
Но здесь не хеш из файла выше, а питонический hash() обычный. Чтобы смотреть на актуальные результаты, нужно подсунуть актуальный.
|
||||
[6/23/26 9:58 PM] Arseny: А вот страничка set:version на ALT Wiki (https://www.altlinux.org/Set-version)
|
||||
[6/23/26 9:58 PM] Arseny: Putze, F.; Sanders, P.; Singler, J. (2007),
|
||||
253 * "Cache-, Hash- and Space-Efficient Bloom Filters",
|
||||
254 * http://algo2.iti.uni-karlsruhe.de/singler/publications/cacheefficientbloomfilters-wea2007.pdf
|
||||
[6/23/26 9:58 PM] Arseny: Сайта нет, но статью поискать https://t.me/c/1622441719/1359/9771
|
||||
[6/23/26 9:59 PM] Arseny: И ещё наследие Алексея Турбина:
|
||||
https://ftp.altlinux.org/pub/people/at/
|
||||
https://github.com/svpv
|
||||
@@ -1,444 +0,0 @@
|
||||
# `rpmquery` в ALT Linux
|
||||
|
||||
## Краткий вывод
|
||||
|
||||
`rpmquery` — отдельная точка входа в тот же механизм запросов RPM, что и
|
||||
`rpm -q`. В ALT Linux p11 `/usr/bin/rpmquery` является символьной ссылкой на
|
||||
ELF-программу `/usr/lib/rpm/rpmq`; имя запуска заранее включает режим запроса,
|
||||
поэтому `-q` обычно не нужен:
|
||||
|
||||
```sh
|
||||
rpmquery rpm
|
||||
rpmquery -q rpm
|
||||
rpm -q rpm
|
||||
```
|
||||
|
||||
Все три команды дают один и тот же результат. При этом `rpmquery` не является
|
||||
интерфейсом к репозиторию APT: без `-p` он читает только установленную базу RPM,
|
||||
по умолчанию `/var/lib/rpm`. Для поиска доступных, но не установленных пакетов
|
||||
нужны `apt-cache`, `apt-get` или веб/RDB-интерфейсы ALT.
|
||||
|
||||
Исследование проверено на официальном контейнере `alt:p11`, обновлённом до
|
||||
`rpm-4.13.0.1-alt45.x86_64`, и по ветке `p11` исходников RPM, commit
|
||||
`7e71ed3da438289d916f1cccc9c0a83e594455dc`.
|
||||
|
||||
## Где ищет `rpmquery`
|
||||
|
||||
| Вызов | Источник данных |
|
||||
|---|---|
|
||||
| `rpmquery NAME` | установленная база RPM |
|
||||
| `rpmquery -a` | все пакеты из установленной базы RPM |
|
||||
| `rpmquery -p FILE.rpm` | заголовок указанного RPM-файла, установка не требуется |
|
||||
| `rpmquery --root ROOT ...` | база и файловая система относительно `ROOT` |
|
||||
| `rpmquery --dbpath DIR ...` | явно указанная база RPM |
|
||||
|
||||
Практическая граница была проверена так: `apt-cache show strace` находил пакет
|
||||
в p11, тогда как `rpmquery strace` отвечал `package strace is not installed` и
|
||||
завершался с кодом `1`.
|
||||
|
||||
## Работа с APT-репозиторием
|
||||
|
||||
APT-RPM загружает индексы подключённых репозиториев в `/var/lib/apt/lists`.
|
||||
Обычный порядок работы:
|
||||
|
||||
```sh
|
||||
# Показать настроенные источники ALT, если установлен пакет apt-repo.
|
||||
apt-repo
|
||||
|
||||
# Обновить локальные индексы после изменения источников.
|
||||
apt-get update
|
||||
```
|
||||
|
||||
В минимальных контейнерах `apt-repo` может отсутствовать; источники всё равно
|
||||
задаются файлами `/etc/apt/sources.list` и `/etc/apt/sources.list.d/*.list`.
|
||||
|
||||
Сами `pkglist.*` являются бинарными header-list файлами RPM, поэтому искать в
|
||||
них обычным `grep` не следует. Для доступа к кэшу предназначен `apt-cache`:
|
||||
|
||||
| Задача | Команда |
|
||||
|---|---|
|
||||
| Поиск по имени и описанию | `apt-cache search REGEX` |
|
||||
| Перечень имён | `apt-cache pkgnames` |
|
||||
| Выбранная версия и приоритеты источников | `apt-cache policy PACKAGE` |
|
||||
| Полная доступная запись, включая версии зависимостей | `apt-cache show PACKAGE` |
|
||||
| Граф разрешения зависимостей | `apt-cache depends PACKAGE` |
|
||||
| Обратные зависимости | `apt-cache whatdepends PACKAGE` |
|
||||
| Низкоуровневое представление версий и зависимостей | `apt-cache showpkg PACKAGE` |
|
||||
| Метаданные исходного пакета | `apt-cache showsrc PACKAGE` |
|
||||
| Все доступные записи репозиториев | `apt-cache dumpavail` |
|
||||
| Проверка плана установки без изменений | `apt-get -s install PACKAGE` |
|
||||
| Только скачать бинарный пакет и зависимости | `apt-get -d install PACKAGE` |
|
||||
| Скачать исходники | `apt-get source PACKAGE` |
|
||||
|
||||
`apt-cache depends` удобен для просмотра найденных провайдеров, но в его
|
||||
обычном выводе set-версия зависимости теряется. Для полных set-строк нужны
|
||||
`apt-cache show`, `apt-cache showpkg`, скачанный RPM или RDB API.
|
||||
|
||||
## Set-строки пакета из APT-репозитория
|
||||
|
||||
### Способ 1: прямо из локального кэша APT
|
||||
|
||||
Это самый короткий способ, установка и скачивание RPM не нужны:
|
||||
|
||||
```sh
|
||||
pkg=libdw
|
||||
|
||||
# Сначала проверить, какую версию и из какого источника выбрал APT.
|
||||
apt-cache policy "$pkg"
|
||||
|
||||
# Полные поля с set-Requires и set-Provides.
|
||||
apt-cache show "$pkg" |
|
||||
sed -n -E '/^(Pre-Depends|Depends|Provides):/p' |
|
||||
grep -F 'set:'
|
||||
```
|
||||
|
||||
В проверенном p11 поля имели вид:
|
||||
|
||||
```text
|
||||
Depends: ... liblzma.so.5()(64bit) (>= set:kiyIz7cr3p0), ...
|
||||
Provides: libdw.so.1()(64bit) (= set:ldm5ZH1pjPBqjZ9bdJbLDnKGbC1hkJSX...)
|
||||
```
|
||||
|
||||
`Depends` и `Pre-Depends` соответствуют требованиям пакета, `Provides` — тому,
|
||||
что пакет предоставляет. `apt-cache show` может вывести несколько записей,
|
||||
если в подключённых источниках доступны разные версии; выбранный кандидат
|
||||
виден в `apt-cache policy`.
|
||||
|
||||
Для выгрузки всего подключённого репозитория можно использовать:
|
||||
|
||||
```sh
|
||||
apt-cache dumpavail >available.txt
|
||||
grep -E '^(Pre-Depends|Depends|Provides):.*set:' available.txt
|
||||
```
|
||||
|
||||
В отличие от HTML-страниц `packages.altlinux.org`, кэш APT содержит полные
|
||||
set-строки без визуального многоточия.
|
||||
|
||||
### Способ 2: скачать RPM через APT и запросить его заголовок
|
||||
|
||||
Это наиболее точный локальный способ: выводятся исходные массивы тегов RPM и
|
||||
их удобно преобразовать в TSV. `apt-get -d` скачивает также отсутствующие
|
||||
зависимости, но ничего не устанавливает.
|
||||
|
||||
```sh
|
||||
pkg=strace
|
||||
cache=$(mktemp -d)
|
||||
trap 'rm -rf "$cache"' EXIT
|
||||
mkdir -p "$cache/partial"
|
||||
|
||||
apt-get -y -d \
|
||||
-o "Dir::Cache::archives=$cache" \
|
||||
install "$pkg"
|
||||
|
||||
rpmfile=$(find "$cache" -maxdepth 1 -type f \
|
||||
-name "${pkg}_*.rpm" -print -quit)
|
||||
```
|
||||
|
||||
Set-Requires выбранного пакета:
|
||||
|
||||
```sh
|
||||
rpmquery -p --qf \
|
||||
'[%{REQUIRENAME}\t%{REQUIREFLAGS:depflags}\t%{REQUIREVERSION}\n]' \
|
||||
"$rpmfile" |
|
||||
awk -F '\t' '$3 ~ /^set:/'
|
||||
```
|
||||
|
||||
Set-Provides выбранного пакета:
|
||||
|
||||
```sh
|
||||
rpmquery -p --qf \
|
||||
'[%{PROVIDENAME}\t%{PROVIDEFLAGS:depflags}\t%{PROVIDEVERSION}\n]' \
|
||||
"$rpmfile" |
|
||||
awk -F '\t' '$3 ~ /^set:/'
|
||||
```
|
||||
|
||||
Для `strace-7.0-alt1.x86_64.rpm` в p11 получены два требования:
|
||||
|
||||
```text
|
||||
libdw.so.1()(64bit) >= set:lgHyMNBkvSTjjY0en8Yi3vFojgjVM7jRsRF4zCFBzNVmhhGF
|
||||
libselinux.so.1()(64bit) >= set:liZ0N709Wr2dbKPs2
|
||||
```
|
||||
|
||||
Set-Provides у этого пакета нет: исполняемый `strace` требует библиотеки, но не
|
||||
экспортирует собственную разделяемую библиотеку. У скачанного вместе с ним
|
||||
пакета `libdw` присутствует длинный Provide
|
||||
`libdw.so.1()(64bit) = set:...`.
|
||||
|
||||
Если пакет уже установлен и APT не скачивает его повторно, его заголовок можно
|
||||
прочитать напрямую командой `rpmquery PACKAGE`; для гарантированного получения
|
||||
конкретного репозиторного RPM удобнее использовать RDB API.
|
||||
|
||||
### Способ 3: структурированные данные RDB API
|
||||
|
||||
RDB удобен для автоматизации, другой ветки/архитектуры и больших выборок. Он
|
||||
возвращает полные строки и явно различает `provide`, `require` и `conflict`:
|
||||
|
||||
```sh
|
||||
branch=p11
|
||||
arch=x86_64
|
||||
pkg=libdw
|
||||
|
||||
hash=$(curl -fsS \
|
||||
"https://rdb.altlinux.org/api/site/pkghash_by_binary_name?branch=$branch&name=$pkg&arch=$arch" |
|
||||
jq -r '.pkghash')
|
||||
|
||||
curl -fsS \
|
||||
"https://rdb.altlinux.org/api/dependencies/binary_package_dependencies/$hash" |
|
||||
jq -r '
|
||||
.dependencies[]
|
||||
| select(
|
||||
(.type == "provide" or .type == "require") and
|
||||
(.version | startswith("set:"))
|
||||
)
|
||||
| [.type, .name, .version]
|
||||
| @tsv
|
||||
'
|
||||
```
|
||||
|
||||
Проверенный результат для `libdw` содержит один set-Provide и четыре
|
||||
set-Requires. Поле `flag_decoded` того же JSON позволяет восстановить оператор:
|
||||
`RPMSENSE_EQUAL` для обычного Provide и
|
||||
`RPMSENSE_GREATER` + `RPMSENSE_EQUAL` для `>=` Require.
|
||||
|
||||
Через RDB можно также получить URL самого RPM:
|
||||
|
||||
```sh
|
||||
curl -fsS \
|
||||
"https://rdb.altlinux.org/api/site/package_downloads_bin/$hash?branch=$branch&arch=$arch" |
|
||||
jq -r '.downloads[].packages[].url'
|
||||
```
|
||||
|
||||
## Основные запросы
|
||||
|
||||
### Пакет и его метаданные
|
||||
|
||||
```sh
|
||||
# Версия установленного пакета в стандартном формате NVRA.
|
||||
rpmquery rpm
|
||||
|
||||
# Подробная информация. В ALT вывод включает DistTag.
|
||||
rpmquery -i rpm
|
||||
rpmquery --info rpm
|
||||
|
||||
# Стабильный формат для скрипта.
|
||||
rpmquery --qf '%{NAME}|%{EPOCHNUM}|%{VERSION}|%{RELEASE}|%{DISTTAG}|%{ARCH}\n' rpm
|
||||
|
||||
# Доступные имена тегов заголовка.
|
||||
rpmquery --querytags
|
||||
```
|
||||
|
||||
В `rpmquery` короткая опция `-i` означает `--info`, а не установку пакета. Это
|
||||
ALT-алиас из `rpmpopt`; он появился в changelog RPM в версии
|
||||
`4.13.0-alt7`. Для установки используется другой режим программы `rpm`, но в
|
||||
обычной работе с ALT зависимости и репозитории следует поручать APT.
|
||||
|
||||
### Файлы
|
||||
|
||||
```sh
|
||||
# Какому установленному пакету принадлежит файл.
|
||||
rpmquery -f /usr/bin/rpmquery
|
||||
|
||||
# Все файлы пакета.
|
||||
rpmquery -l rpm
|
||||
|
||||
# Только конфигурационные или документационные файлы.
|
||||
rpmquery -c rpm
|
||||
rpmquery -d rpm
|
||||
|
||||
# Пакет из файла без установки.
|
||||
rpmquery -p ./package.rpm
|
||||
rpmquery -lp ./package.rpm
|
||||
rpmquery -ip ./package.rpm
|
||||
```
|
||||
|
||||
Для аргумента-пути `--whatprovides` сначала обращается к индексу установленных
|
||||
файлов, затем к индексу capability. Поэтому для существующего установленного
|
||||
файла эти команды обычно совпадают:
|
||||
|
||||
```sh
|
||||
rpmquery -f /usr/bin/rpmquery
|
||||
rpmquery --whatprovides /usr/bin/rpmquery
|
||||
```
|
||||
|
||||
### Provides, Requires и обратные запросы
|
||||
|
||||
```sh
|
||||
# Что объявляет один пакет.
|
||||
rpmquery --provides rpm
|
||||
rpmquery --requires rpm
|
||||
rpmquery -R rpm
|
||||
|
||||
# Какие установленные пакеты объявляют capability или требуют его.
|
||||
rpmquery --whatprovides 'libpopt.so.0()(64bit)'
|
||||
rpmquery --whatrequires 'libpopt.so.0()(64bit)'
|
||||
```
|
||||
|
||||
`--provides`, `--requires`, `--info`, `--last`, `--scripts` и ряд других
|
||||
удобных режимов реализованы как `popt`-алиасы над `--queryformat`, а не как
|
||||
отдельные алгоритмы запросов.
|
||||
|
||||
## Формат запросов для автоматической обработки
|
||||
|
||||
Стандартный вывод удобен человеку, но надёжнее не разбирать его пробелами.
|
||||
Массивы тегов RPM обходятся форматом в квадратных скобках. Чтобы внутри такого
|
||||
итератора повторять скалярный тег для каждого элемента массива, знак `=` ставят
|
||||
внутри имени тега: `%{=NAME}`.
|
||||
|
||||
### Все set-Requires в TSV
|
||||
|
||||
```sh
|
||||
LC_ALL=C rpmquery -a --qf \
|
||||
'[%{=NAME}\t%{REQUIRENAME}\t%{REQUIREFLAGS:depflags}\t%{REQUIREVERSION}\n]' |
|
||||
awk -F '\t' '$4 ~ /^set:/'
|
||||
```
|
||||
|
||||
Поля: пакет, имя capability, оператор, версия зависимости.
|
||||
|
||||
Пример:
|
||||
|
||||
```text
|
||||
rpm libpopt.so.0()(64bit) >= set:jgtcU6BLBccTnteGxrE0
|
||||
```
|
||||
|
||||
### Все set-Provides в TSV
|
||||
|
||||
```sh
|
||||
LC_ALL=C rpmquery -a --qf \
|
||||
'[%{=NAME}\t%{PROVIDENAME}\t%{PROVIDEFLAGS:depflags}\t%{PROVIDEVERSION}\n]' |
|
||||
awk -F '\t' '$4 ~ /^set:/'
|
||||
```
|
||||
|
||||
Пример:
|
||||
|
||||
```text
|
||||
libpopt libpopt.so.0()(64bit) = set:jdtcJcAdqxTmPJUyuYcVIbLNhqmj...
|
||||
```
|
||||
|
||||
Это предпочтительнее `grep` по человекочитаемому выводу, если нужны имена
|
||||
пакетов, операторы или однозначное разделение полей.
|
||||
|
||||
## Что `rpmquery` делает и не делает с `set:version`
|
||||
|
||||
### Показывает сохранённые зависимости
|
||||
|
||||
`rpmquery --requires` и `rpmquery --provides` извлекают из заголовков пакетов
|
||||
имя capability, флаги отношения и строку версии. Они не декодируют `set:` и не
|
||||
показывают исходные ELF-символы.
|
||||
|
||||
Для установленного `rpm` в p11 наблюдалось:
|
||||
|
||||
```text
|
||||
libpopt.so.0()(64bit) >= set:jgtcU6BLBccTnteGxrE0
|
||||
```
|
||||
|
||||
Установленный провайдер находился по одному имени capability:
|
||||
|
||||
```sh
|
||||
rpmquery --whatprovides 'libpopt.so.0()(64bit)'
|
||||
# libpopt-1.18-alt1.x86_64
|
||||
```
|
||||
|
||||
### Не сравнивает выражение, переданное в `--whatprovides`
|
||||
|
||||
Передача полного выражения не запускает `rpmsetcmp()`:
|
||||
|
||||
```sh
|
||||
rpmquery --whatprovides \
|
||||
'libpopt.so.0()(64bit) >= set:jgtcU6BLBccTnteGxrE0'
|
||||
```
|
||||
|
||||
Проверенный результат:
|
||||
|
||||
```text
|
||||
no package provides libpopt.so.0()(64bit) >= set:jgtcU6BLBccTnteGxrE0
|
||||
```
|
||||
|
||||
Для непутевого аргумента `--whatprovides` делает точный поиск по индексу
|
||||
`PROVIDENAME`. Поэтому сначала нужно искать провайдера по имени capability, а
|
||||
его set-строку получать отдельным запросом:
|
||||
|
||||
```sh
|
||||
name='libpopt.so.0()(64bit)'
|
||||
provider=$(rpmquery --whatprovides "$name" | head -n1)
|
||||
rpmquery --qf \
|
||||
'[%{PROVIDENAME}\t%{PROVIDEFLAGS:depflags}\t%{PROVIDEVERSION}\n]' \
|
||||
"$provider" |
|
||||
grep -F "$name"
|
||||
```
|
||||
|
||||
Настоящее сравнение двух `set:`-версий происходит в `rpmdsCompareEVR()` при
|
||||
проверке совместимости зависимостей. Если обе версии начинаются с `set:`, код
|
||||
вызывает `rpmsetcmp()`; если `set:` имеет только одна сторона, зависимости не
|
||||
пересекаются. Следовательно, `rpmquery` полезен для извлечения корпуса строк,
|
||||
но не является CLI для непосредственного сравнения двух наборов.
|
||||
|
||||
## Как `rpmquery` используется при сборке самого ALT RPM
|
||||
|
||||
В `alt/rpm.spec` ветки p11 после первой сборки выполняется:
|
||||
|
||||
```sh
|
||||
rpmquery -a --provides | fgrep '= set:' | sort >P
|
||||
rpmquery -a --requires | fgrep '= set:' | sort >R
|
||||
join -o 1.3,2.3 P R | shuf >setcmp-data
|
||||
time ./setcmp <setcmp-data >/dev/null
|
||||
```
|
||||
|
||||
Смысл конвейера:
|
||||
|
||||
1. Из локальной базы сборочного окружения собираются все set-Provides и
|
||||
set-Requires.
|
||||
2. Строка поиска `= set:` захватывает как Provides с `= set:`, так и Requires с
|
||||
`>= set:`, потому что вторая строка тоже содержит подстроку `= set:`.
|
||||
3. После сортировки `join` соединяет файлы по первому полю — имени capability.
|
||||
4. `-o 1.3,2.3` оставляет только третьи поля: set провайдера и set требования.
|
||||
5. Полученные реальные пары подаются в `tools/setcmp` для замера реализации
|
||||
`rpmsetcmp()` и профиль-управляемой пересборки `lib/set.c`.
|
||||
|
||||
В обновлённом минимальном контейнере p11 точный конвейер дал:
|
||||
|
||||
```text
|
||||
P: 75 строк
|
||||
R: 109 строк, из них 13 полных дублей
|
||||
setcmp-data: 109 пар
|
||||
```
|
||||
|
||||
Эти числа описывают только состав минимального контейнера, а не весь p11 или
|
||||
Sisyphus. Также конвейер spec не сохраняет имя capability и пакет-владелец;
|
||||
для исследовательского корпуса удобнее TSV-форматы выше.
|
||||
|
||||
Современная замена устаревающего `fgrep` без изменения смысла:
|
||||
|
||||
```sh
|
||||
grep -F '= set:'
|
||||
```
|
||||
|
||||
## Практические ограничения
|
||||
|
||||
- `rpmquery -a` означает все **установленные** пакеты, а не весь репозиторий.
|
||||
- `--whatprovides` и `--whatrequires` ищут заголовки по имени capability; они
|
||||
не принимают полноценное выражение зависимости как запрос сравнения.
|
||||
- Несколько установленных версий или провайдеров могут дать несколько строк.
|
||||
- Set-строки крупных библиотек очень длинные; лучше писать полный вывод в файл,
|
||||
а в терминале показывать только статистику или начало строки.
|
||||
- Для скриптов следует фиксировать `LC_ALL=C` и использовать `--queryformat`.
|
||||
- `--requires`/`--provides` отражают метаданные RPM, а не текущее содержимое ELF
|
||||
на диске. Для восстановления исходных символов нужны `nm`, `readelf` и
|
||||
генераторы автозависимостей rpm-build.
|
||||
- При запросе другой системы безопаснее явно использовать `--root`; `--dbpath`
|
||||
меняет только путь к БД и требует внимательности к относительным путям.
|
||||
- ALT предоставляет `--nowait-lock` как popt-алиас, отключающий ожидание
|
||||
блокировки БД. Его стоит применять только когда отказ предпочтительнее
|
||||
ожидания, а не как способ обходить активную транзакцию RPM/APT.
|
||||
|
||||
## Источники
|
||||
|
||||
1. [Документация платформы ALT 10.1: утилита RPM](https://docs.altlinux.org/ru-RU/platform/10.1/html/alt-platform/utilita_komandnoj_stroki_rpm.html) — RPM работает с файлами, пакетами, зависимостями и собственной БД, но не знает о репозиториях.
|
||||
2. [Настройка списка репозиториев APT в ALT](https://docs.altlinux.org/ru-RU/platform/10.1/html/alt-platform/nastrojka_spiska_repozitoriev_apt.html).
|
||||
3. [Пакет `rpm` в p11](https://packages.altlinux.org/en/p11/srpms/rpm/) и [specfile](https://packages.altlinux.org/en/p11/srpms/rpm/specfiles/).
|
||||
4. [RDB: текущий бинарный пакет `rpm` для p11/x86_64](https://rdb.altlinux.org/api/site/pkghash_by_binary_name?branch=p11&name=rpm&arch=x86_64).
|
||||
5. [`rpmqv.c`](https://git.altlinux.org/gears/r/rpm.git?p=rpm.git;a=blob;f=rpmqv.c;hb=7e71ed3da438289d916f1cccc9c0a83e594455dc) — выбор режима Query по имени `rpmquery`/`rpmq`.
|
||||
6. [`rpmpopt.in`](https://git.altlinux.org/gears/r/rpm.git?p=rpm.git;a=blob;f=rpmpopt.in;hb=7e71ed3da438289d916f1cccc9c0a83e594455dc) — ALT-алиасы `--requires`, `--provides`, `--info`, `-i`, `--nowait-lock`.
|
||||
7. [`lib/query.c`](https://git.altlinux.org/gears/r/rpm.git?p=rpm.git;a=blob;f=lib/query.c;hb=7e71ed3da438289d916f1cccc9c0a83e594455dc) — индексы для `--whatprovides`, запросы файлов и формат `%{nvra}` по умолчанию.
|
||||
8. [`lib/rpmds.c`](https://git.altlinux.org/gears/r/rpm.git?p=rpm.git;a=blob;f=lib/rpmds.c;hb=7e71ed3da438289d916f1cccc9c0a83e594455dc) — вызов `rpmsetcmp()` при сравнении двух set-версий.
|
||||
9. [`alt/rpm.spec`](https://git.altlinux.org/gears/r/rpm.git?p=rpm.git;a=blob;f=alt/rpm.spec;hb=7e71ed3da438289d916f1cccc9c0a83e594455dc) — построение `setcmp-data` через `rpmquery`.
|
||||
10. [Официальный контейнер ALT](https://hub.docker.com/_/alt) — среда воспроизведения `alt:p11`.
|
||||
@@ -1,80 +0,0 @@
|
||||
rg rpmsetcmp:
|
||||
|
||||
```
|
||||
tools/setcmp.c
|
||||
12-{
|
||||
13: int cmp = rpmsetcmp(s1, s2);
|
||||
14- switch (cmp) {
|
||||
|
||||
alt/rpm.spec
|
||||
709-_ Mon Nov 25 2019 Andrew Savchenko <bircoph@altlinux.org> 4.13.0.1-alt15
|
||||
710:- Support rpmsetcmp profiling on E2K.
|
||||
711-
|
||||
--
|
||||
713-- Added triggers circumvention for packagekit offline update (by Aleksei Nikiforov).
|
||||
714:- Imported rpmsetcmp optimization from rpm-build.
|
||||
715-
|
||||
--
|
||||
1094-- set.c: Increased cache size from 160 to 256 slots, 75 percent hit ratio.
|
||||
1095:- set.c: Implemented 4-byte and 8-byte steppers for rpmsetcmp main loop.
|
||||
1096-
|
||||
--
|
||||
1314-_ Mon Sep 20 2010 Alexey Tourbin <at@altlinux.ru> 4.0.4-alt98.47
|
||||
1315:- set.c (rpmsetcmp): Fixed check for set2 decoding error.
|
||||
1316-- brp-cleanup: Updated for /usr/lib64/perl5 and /usr/share/perl5.
|
||||
|
||||
lib/rpmds.c
|
||||
1087- if (aset && bset) {
|
||||
1088: sense = rpmsetcmp(AEVR, BEVR);
|
||||
1089- if (sense < -1) {
|
||||
|
||||
---
|
||||
|
||||
rg set_new:
|
||||
none
|
||||
|
||||
---
|
||||
|
||||
rg set_add
|
||||
none
|
||||
|
||||
---
|
||||
|
||||
rg set_fini:
|
||||
none
|
||||
|
||||
---
|
||||
|
||||
rg set_free:
|
||||
none
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
rg setcmp:
|
||||
|
||||
```
|
||||
Makefile.am
|
||||
218-
|
||||
219:noinst_PROGRAMS = setcmp
|
||||
220:setcmp_SOURCES = tools/setcmp.c
|
||||
221:setcmp_LDADD = lib/librpm.la
|
||||
222-
|
||||
|
||||
alt/rpm.spec
|
||||
311-rpmquery -a --requires |fgrep '= set:' |sort >R
|
||||
312:join -o 1.3,2.3 P R |shuf >setcmp-data
|
||||
313:time ./setcmp <setcmp-data >/dev/null
|
||||
314-rm lib/set.lo lib/librpm.la
|
||||
--
|
||||
318-%if_with profile
|
||||
319:time ./setcmp <setcmp-data >/dev/null
|
||||
320-rm lib/set.lo lib/librpm.la
|
||||
321-%make_build -C lib set.lo librpm.la CFLAGS="$set_c_cflags -fprofile-generate"
|
||||
322:./setcmp <setcmp-data >/dev/null
|
||||
323-%ifnarch %e2k
|
||||
--
|
||||
332-%make_build
|
||||
333:time ./setcmp <setcmp-data >/dev/null
|
||||
334-
|
||||
```
|
||||
@@ -1,99 +0,0 @@
|
||||
rg rpmsetcmp:
|
||||
|
||||
```
|
||||
tools/setcmp.c
|
||||
12-{
|
||||
13: int cmp = rpmsetcmp(s1, s2);
|
||||
14- switch (cmp) {
|
||||
|
||||
lib/depends.c
|
||||
124- if (aset && bset) {
|
||||
125: sense = rpmsetcmp(AEVR, BEVR);
|
||||
126- if (sense < -1) {
|
||||
|
||||
rpm-4_0.spec
|
||||
1231-- set.c: Increased cache size from 160 to 256 slots, 75 percent hit ratio.
|
||||
1232:- set.c: Implemented 4-byte and 8-byte steppers for rpmsetcmp main loop.
|
||||
1233-
|
||||
--
|
||||
1451-* Mon Sep 20 2010 Alexey Tourbin <at@altlinux.ru> 4.0.4-alt98.47
|
||||
1452:- set.c (rpmsetcmp): Fixed check for set2 decoding error.
|
||||
1453-- brp-cleanup: Updated for /usr/lib64/perl5 and /usr/share/perl5.
|
||||
|
||||
build/reqprov.c
|
||||
175- if (aset && bset) {
|
||||
176: sense = rpmsetcmp(Aevr, Bevr);
|
||||
177- if (sense < -1)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
rg set_new:
|
||||
|
||||
```
|
||||
tools/mkset.c
|
||||
11- assert(bpp <= 32);
|
||||
12: struct set *set = set_new();
|
||||
13- char *line = NULL;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
rg set_add:
|
||||
|
||||
```
|
||||
tools/mkset.c
|
||||
21- continue;
|
||||
22: set_add(set, line);
|
||||
23- added++;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
rg set_fini:
|
||||
|
||||
```
|
||||
tools/mkset.c
|
||||
25- assert(added > 0);
|
||||
26: const char *str = set_fini(set, bpp);
|
||||
27- assert(str);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
rg set_free:
|
||||
none
|
||||
|
||||
---
|
||||
|
||||
rg mkset:
|
||||
|
||||
```
|
||||
rpm-4_0.spec
|
||||
431-# set-version helpers
|
||||
432:%rpmattr %_rpmlibdir/mkset
|
||||
433-%rpmattr %_rpmlibdir/setcmp
|
||||
--
|
||||
1458-- rpmlibprov.c: Added rpmlib(SetVersions) feature.
|
||||
1459:- %_rpmlibdir/mkset: Command-line helper for making set-versions.
|
||||
1460-- lib.prov: Implemented soname set-versioning with exported symbols.
|
||||
|
||||
tools/Makefile.am
|
||||
37- relative \
|
||||
38: mkset \
|
||||
39- setcmp \
|
||||
--
|
||||
62-
|
||||
63:mkset_SOURCES = mkset.c
|
||||
64-setcmp_SOURCES = setcmp.c
|
||||
|
||||
autodeps/lib.prov.in
|
||||
112- Info "$f: $n symbols, $bpp bpp"
|
||||
113: set=$(printf '%s\n' "$sym" |"${RPMB_TOOLS_DIR-@RPMCONFIGDIR@}/mkset" "$bpp")
|
||||
114- printf '%s = %s\n' "$provname" "$set"
|
||||
|
||||
autodeps/lib.req.in
|
||||
306- #printf '%s\n' "$reqsym" |LC_ALL=C sort -c -u
|
||||
307: set=$(printf '%s\n' "$reqsym" |"${RPMB_TOOLS_DIR-@RPMCONFIGDIR@}/mkset" "$bpp")
|
||||
308- printf '%s >= %s\n' "$dep" "$set"
|
||||
```
|
||||
+586
@@ -0,0 +1,586 @@
|
||||
# Реализация set:version в `set9.c`
|
||||
|
||||
Документ описывает экспериментальную реализацию [`reimplement/set9.c`](../reimplement/set9.c).
|
||||
Общий смысл механизма set:version и устройство исходного [`set.c`](../set.c) разобраны в
|
||||
[`set:version_ru.md`](set:version_ru.md). Здесь основное внимание уделено отличиям `set9.c`:
|
||||
потоковому кодированию и декодированию, сортировке, двум LRU-кэшам и
|
||||
новому алгоритму сравнения отсортированных множеств.
|
||||
|
||||
## Назначение
|
||||
|
||||
ALT RPM записывает наборы ELF-символов в версии зависимостей вида:
|
||||
|
||||
```text
|
||||
libfoo.so.X()(64bit) = set:<encoded-set>
|
||||
libfoo.so.X()(64bit) >= set:<encoded-set>
|
||||
```
|
||||
|
||||
`Provides` содержит хэши предоставляемых символов, а `Requires` — хэши символов, требуемых
|
||||
от конкретной библиотеки. `rpmsetcmp()` сравнивает декодированные множества по включению,
|
||||
а не как обычные RPM-версии.
|
||||
|
||||
`set9.c` сохраняет формат строк и Jenkins OAAT из исходной реализации. Поэтому его задача —
|
||||
изменить внутреннее представление и горячие пути, не меняя смысл корректных set-строк.
|
||||
Совпадение формата не устраняет фундаментальную вероятность коллизий: сравниваются усечённые
|
||||
32-битные хэши, а не исходные имена символов.
|
||||
|
||||
## Основные отличия от исходного `set.c`
|
||||
|
||||
| Область | Исходный `set.c` | `set9.c` |
|
||||
| ------------------------- | ---------------------------------------------------- | ---------------------------------------------------------------- |
|
||||
| Хранение имён | отдельный `xstrdup()` для каждого имени | общая растущая строковая арена и массив смещений |
|
||||
| Сортировка | всегда `qsort()` | `qsort()` для `< 128` элементов, LSD radix sort для остальных |
|
||||
| Кодирование | отдельные массивы delta, битов и Base62 | один поток `hash -> delta -> Golomb-Rice -> Base62` |
|
||||
| Декодирование | сложный табличный декодер пар символов | простой потоковый декодер с 64-битным аккумулятором |
|
||||
| Кэш | один кэш первого операнда на 256 записей | два независимых кэша по 512 записей, по одному на каждый операнд |
|
||||
| Поиск в кэше | линейный просмотр и `memmove()` | хэш-бакеты и двусвязный LRU со сменой позиции за `O(1)` |
|
||||
| Нормализация `bpp` | выполняется для каждого сравнения | выполняется при промахе кэша и сохраняется в записи кэша |
|
||||
| Сравнение | флаги `ge`/`le`, sentinel-элементы и макросы прыжков | проверка мощности, `memcmp()` и `sorted_subset()` |
|
||||
| Освобождение `struct set` | сама структура не освобождается | освобождаются арена, массив символов и сама структура |
|
||||
|
||||
## Публичный API
|
||||
|
||||
Как и исходная реализация, файл предоставляет пять функций:
|
||||
|
||||
```c
|
||||
int rpmsetcmp(const char *set1, const char *set2);
|
||||
|
||||
struct set *set_new(void);
|
||||
void set_add(struct set *set, const char *sym);
|
||||
const char *set_fini(struct set *set, int bpp);
|
||||
struct set *set_free(struct set *set);
|
||||
```
|
||||
|
||||
### `set_new()`
|
||||
|
||||
Создаёт пустой `struct set`. Все счётчики и ёмкости устанавливаются в ноль, указатели — в
|
||||
`NULL`.
|
||||
|
||||
### `set_add()`
|
||||
|
||||
Добавляет копию строки символа во внутреннюю строковую арену:
|
||||
|
||||
1. массив `symbols_v` увеличивается блоками по 1024 элемента;
|
||||
2. строковая арена при первом выделении получает 4096 байт;
|
||||
3. при нехватке места ёмкость арены удваивается, пока не вместит новую строку;
|
||||
4. в `symbols_v` сохраняются смещение строки в арене и нулевое начальное значение хэша.
|
||||
|
||||
В отличие от хранения отдельных указателей, перемещение арены через `xrealloc()` не делает
|
||||
записи массива недействительными: в них находятся смещения, а не адреса строк.
|
||||
|
||||
### `set_fini()`
|
||||
|
||||
Хэширует, сортирует и кодирует добавленные имена. Возвращаемая строка выделена через
|
||||
`xstrdup()` и **не содержит** префикс `set:`. Префикс добавляет вызывающий код, например
|
||||
`mkset`.
|
||||
|
||||
В `set9.c` контракт проверяется через `assert()`:
|
||||
|
||||
```text
|
||||
set != NULL
|
||||
set->cnt > 0
|
||||
10 <= bpp <= 32
|
||||
```
|
||||
|
||||
Это отличается от исходного `set.c`, где пустое множество или недопустимый `bpp` приводят к
|
||||
`NULL`. При сборке с `NDEBUG` проверки `assert()` исчезают, поэтому передавать некорректные
|
||||
аргументы нельзя.
|
||||
|
||||
### `set_free()`
|
||||
|
||||
Освобождает строковую арену, массив `symbols_v` и сам `struct set`, затем возвращает `NULL`.
|
||||
Типичный вызов:
|
||||
|
||||
```c
|
||||
set = set_free(set);
|
||||
```
|
||||
|
||||
### `rpmsetcmp()`
|
||||
|
||||
Сравнивает две set-строки. Префикс `set:` у каждого операнда необязателен.
|
||||
|
||||
Результаты совместимы с исходным API:
|
||||
|
||||
| Код | Значение |
|
||||
| ---: | ---------------------------------------------------- |
|
||||
| `1` | первое множество строго содержит второе |
|
||||
| `0` | множества равны |
|
||||
| `-1` | первое множество строго содержится во втором |
|
||||
| `-2` | множества несравнимы по включению |
|
||||
| `-3` | ошибка метаданных или декодирования первого операнда |
|
||||
| `-4` | ошибка метаданных или декодирования второго операнда |
|
||||
|
||||
В типичном RPM-вызове первым операндом остаётся `Provides`, вторым — `Requires`.
|
||||
|
||||
## Формат set-строки
|
||||
|
||||
После необязательного `set:` строка имеет структуру:
|
||||
|
||||
```text
|
||||
<bpp><Mshift><payload>
|
||||
```
|
||||
|
||||
Первые два символа кодируют числа формулой:
|
||||
|
||||
```c
|
||||
value = character + 7 - 'a';
|
||||
character = value - 7 + 'a';
|
||||
```
|
||||
|
||||
Ограничения:
|
||||
|
||||
```text
|
||||
10 <= bpp <= 32
|
||||
7 <= Mshift <= 31
|
||||
Mshift < bpp
|
||||
payload не пуст
|
||||
```
|
||||
|
||||
`payload` использует алфавит:
|
||||
|
||||
```text
|
||||
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
|
||||
```
|
||||
|
||||
Его числовые значения расположены как `0..9`, `a..z`, `A..Z`. Символ `Z` имеет значение
|
||||
61 и одновременно служит escape-маркером для исходных шестибитных значений 61, 62 и 63.
|
||||
|
||||
## Внутреннее представление создаваемого множества
|
||||
|
||||
```c
|
||||
struct set {
|
||||
size_t cnt;
|
||||
size_t symbols_cap;
|
||||
size_t strings_len;
|
||||
size_t strings_cap;
|
||||
char *strings;
|
||||
struct symbols {
|
||||
size_t offset;
|
||||
unsigned hash;
|
||||
} *symbols_v;
|
||||
};
|
||||
```
|
||||
|
||||
Здесь:
|
||||
|
||||
- `cnt` — число добавленных имён;
|
||||
- `symbols_cap` — ёмкость массива метаданных;
|
||||
- `strings_len` и `strings_cap` — занятая и выделенная части строковой арены;
|
||||
- `strings` — последовательность NUL-терминированных имён;
|
||||
- `offset` — начало конкретного имени относительно `strings`;
|
||||
- `hash` — хэш, вычисляемый только при `set_fini()`.
|
||||
|
||||
Такое представление сокращает число отдельных выделений памяти при построении больших наборов.
|
||||
|
||||
## Работа `set_fini()`
|
||||
|
||||
Полный путь выглядит так:
|
||||
|
||||
```text
|
||||
строковая арена и offsets
|
||||
| Jenkins OAAT + mask(bpp)
|
||||
v
|
||||
массив struct symbols с хэшами
|
||||
| qsort или LSD radix sort
|
||||
v
|
||||
отсортированные хэши
|
||||
| предупреждения о коллизиях + удаление повторов
|
||||
v
|
||||
массив уникальных хэшей
|
||||
| потоковый delta + Golomb-Rice + Base62
|
||||
v
|
||||
строка <bpp><Mshift><payload>
|
||||
```
|
||||
|
||||
### Jenkins OAAT и усечение
|
||||
|
||||
Для каждого имени вычисляется тот же Jenkins one-at-a-time hash, что и в исходном файле.
|
||||
Начальное состояние равно `0x9e3779b9`. Результат ограничивается младшими `bpp` битами:
|
||||
|
||||
```c
|
||||
unsigned mask = (bpp < 32) ? (1u << bpp) - 1 : ~0u;
|
||||
hash_value = hash(symbol) & mask;
|
||||
```
|
||||
|
||||
### `sort_symbols()`
|
||||
|
||||
Для малых наборов (`count < 128`) используется `qsort()`. Для больших наборов применяется
|
||||
стабильная LSD radix sort по байтам хэша:
|
||||
|
||||
1. число проходов равно `ceil(bpp / 8)`;
|
||||
2. на каждом проходе строятся 256 счётчиков/смещений;
|
||||
3. элементы стабильно распределяются по текущему байту;
|
||||
4. источник и приёмник меняются местами;
|
||||
5. если итог оказался во временном массиве, он копируется назад.
|
||||
|
||||
Поскольку перед сортировкой хэш уже ограничен `bpp` битами, обработки
|
||||
`ceil(bpp / 8)` байтов достаточно. Временный массив размером `count` размещается на стеке.
|
||||
|
||||
### Коллизии и повторяющиеся символы
|
||||
|
||||
После сортировки соседние равные хэши проверяются попарно. Для разных строк печатается:
|
||||
|
||||
```text
|
||||
warning: hash collision: <left> <right>
|
||||
```
|
||||
|
||||
Две одинаковые строки предупреждения не создают. Затем все одинаковые **хэш-значения**
|
||||
схлопываются в одно. Поэтому настоящая хэш-коллизия после предупреждения всё равно становится
|
||||
одним элементом кодируемого множества — это свойство исходного формата.
|
||||
|
||||
### Выбор `Mshift`
|
||||
|
||||
Параметр Golomb-Rice вычисляется как:
|
||||
|
||||
```text
|
||||
Mshift = bpp - floor(log2(count)) - 1
|
||||
```
|
||||
|
||||
После этого значение ограничивается диапазоном `7..31` и проверяется условие
|
||||
`Mshift < bpp`. Идея та же, что в исходной реализации: при примерно равномерном распределении
|
||||
хэшей средняя дельта близка к `2^bpp / count`.
|
||||
|
||||
### Потоковый `encode_set()`
|
||||
|
||||
Исходный `set.c` сначала создаёт массив дельт, затем массив по одному байту на бит и лишь потом
|
||||
Base62-строку. В `set9.c` эти стадии объединены.
|
||||
|
||||
Для каждого отсортированного хэша вычисляется:
|
||||
|
||||
```c
|
||||
unsigned delta = current - previous;
|
||||
unsigned q = delta >> Mshift;
|
||||
unsigned r = delta & ((1u << Mshift) - 1);
|
||||
```
|
||||
|
||||
В `encode_writer` последовательно добавляются:
|
||||
|
||||
```text
|
||||
q нулевых битов
|
||||
1 — разделитель
|
||||
Mshift младших битов r
|
||||
```
|
||||
|
||||
Все биты идут младшими вперёд (LSB-first), как и в исходном формате.
|
||||
|
||||
### `struct encode_writer`
|
||||
|
||||
"Писатель" содержит:
|
||||
|
||||
- `uint64_t bits` — накопленные ещё не выведенные биты;
|
||||
- `filled` — число занятых битов;
|
||||
- `escaped` — ожидание второй половины escape-пары;
|
||||
- `pending_high` — старшие два бита значения 61, 62 или 63;
|
||||
- `output` — текущую позицию в выходной строке.
|
||||
|
||||
`encode_writer_put()` добавляет обычное поле битов. `encode_writer_zeros()` добавляет длинную
|
||||
unary-последовательность нулей блоками не более 56 бит, чтобы аккумулятор оставался в пределах
|
||||
`uint64_t`. `encode_writer_flush()` выводит накопленные Base62-цифры.
|
||||
|
||||
В обычном состоянии потребляется шесть потоковых битов. Если значение равно 61, 62 или 63:
|
||||
|
||||
1. выводится `Z`;
|
||||
2. различие между 61/62/63 сохраняется как два старших бита следующего символа;
|
||||
3. из потока для следующего символа берутся только четыре бита.
|
||||
|
||||
При завершении неполный символ дополняется нулями. После payload записывается `\0`.
|
||||
|
||||
## Метаданные декодируемой строки
|
||||
|
||||
Для декодирования используется `struct set_meta`:
|
||||
|
||||
```c
|
||||
struct set_meta {
|
||||
const char *str;
|
||||
const char *payload;
|
||||
size_t len;
|
||||
size_t payload_len;
|
||||
int bpp;
|
||||
int Mshift;
|
||||
int bit_capacity;
|
||||
int value_capacity;
|
||||
};
|
||||
```
|
||||
|
||||
Подготовка разделена на две части.
|
||||
|
||||
### `set_meta_init()` — быстрая проверка
|
||||
|
||||
Функция читает только начало строки:
|
||||
|
||||
1. проверяет наличие двух метасимволов и хотя бы одного символа payload;
|
||||
2. декодирует и проверяет `bpp`;
|
||||
3. декодирует и проверяет `Mshift`;
|
||||
4. сохраняет `str`, `payload`, `bpp` и `Mshift`.
|
||||
|
||||
На этой стадии `strlen()` не вызывается. Это позволяет сначала выполнить дешёвую проверку и
|
||||
поискать уже готовую запись в кэше.
|
||||
|
||||
### `set_meta_fini()` — вычисление размеров
|
||||
|
||||
Функция вызывается только при промахе кэша:
|
||||
|
||||
```text
|
||||
len = strlen(str)
|
||||
payload_len = len - 2
|
||||
bit_capacity = payload_len * 6
|
||||
value_capacity = bit_capacity / (Mshift + 1)
|
||||
```
|
||||
|
||||
`bit_capacity` является верхней границей: обычный символ даёт шесть битов, а два символа
|
||||
escape-пары дают десять битов, то есть не больше двенадцати. Каждое Golomb-Rice-значение
|
||||
требует как минимум `Mshift + 1` бит. Если места нет даже для одного значения,
|
||||
строка отклоняется.
|
||||
|
||||
## Потоковое декодирование
|
||||
|
||||
Путь декодирования объединяет три логических стадии:
|
||||
|
||||
```text
|
||||
Base62 payload
|
||||
| decode_chunk()
|
||||
v
|
||||
6- или 10-битные блоки
|
||||
| q-state + r-state
|
||||
v
|
||||
Golomb-Rice delta
|
||||
| previous += delta
|
||||
v
|
||||
отсортированные хэши
|
||||
```
|
||||
|
||||
Отдельные массивы битов и дельт не создаются.
|
||||
|
||||
### Таблица `char_to_num[]`
|
||||
|
||||
Таблица из 256 элементов классифицирует любой байт:
|
||||
|
||||
- `0..60` — обычная Base62-цифра;
|
||||
- `61` — `Z`;
|
||||
- `0xff` — конец строки;
|
||||
- `0xee` — недопустимый символ.
|
||||
|
||||
Индекс берётся как `unsigned char`, поэтому байты с установленным старшим битом тоже безопасно
|
||||
попадают в диапазон таблицы и отклоняются как недопустимые.
|
||||
|
||||
### `decode_chunk()`
|
||||
|
||||
Обычный символ со значением меньше 61 возвращает шесть битов. При `Z` функция читает следующий
|
||||
символ и:
|
||||
|
||||
1. отклоняет конец строки сразу после `Z`;
|
||||
2. отклоняет не-Base62 символ;
|
||||
3. отклоняет комбинацию старших битов `11`, которая создала бы новый `Z`;
|
||||
4. собирает один 10-битный блок из значения 61/62/63 и четырёх младших битов второго символа.
|
||||
|
||||
Таким образом escape-пара сразу преобразуется в исходные десять потоковых битов.
|
||||
|
||||
### `decode_set()`
|
||||
|
||||
Декодер хранит ещё не обработанные биты в `uint64_t bits`, а их количество — в `filled`.
|
||||
Для каждого значения он проходит два состояния.
|
||||
|
||||
#### Unary-часть `q`
|
||||
|
||||
Если аккумулятор пуст, загружается следующий блок. Полностью нулевой блок целиком прибавляется
|
||||
к `q`. Иначе `__builtin_ctzll(bits)` находит число нулей до разделительной единицы. Нули и сама
|
||||
единица удаляются из аккумулятора.
|
||||
|
||||
#### Остаток `r`
|
||||
|
||||
Пока доступно меньше `Mshift` битов, загружаются следующие блоки. Затем младшие `Mshift` битов
|
||||
образуют остаток, а дельта восстанавливается как:
|
||||
|
||||
```c
|
||||
unsigned delta = (q << Mshift) | r;
|
||||
```
|
||||
|
||||
Дельта сразу превращается в исходный хэш:
|
||||
|
||||
```c
|
||||
previous += delta;
|
||||
hash_arr[count++] = previous;
|
||||
```
|
||||
|
||||
#### Завершение строки
|
||||
|
||||
Конец payload допустим в состоянии чтения `q`, если осталось не более пяти нулевых битов
|
||||
Base62-дополнения. Более длинный нулевой хвост даёт внутреннюю ошибку `-10`.
|
||||
|
||||
Конец строки во время набора `Mshift` битов остатка означает незавершённое значение и даёт
|
||||
внутреннюю ошибку `-11`. Ошибки `decode_chunk()` также возвращаются вверх. Публичный
|
||||
`rpmsetcmp()` скрывает конкретный внутренний код и преобразует его в `-3` или `-4` в зависимости
|
||||
от операнда.
|
||||
|
||||
## Нормализация точности: `downsample_set()`
|
||||
|
||||
Строки с разным `bpp` нельзя сравнивать напрямую. Обе стороны приводятся к:
|
||||
|
||||
```text
|
||||
target_bpp = min(bpp1, bpp2)
|
||||
```
|
||||
|
||||
Один вызов `downsample_set()` уменьшает точность с `bpp + 1` до `bpp`:
|
||||
|
||||
1. маска равна `(1u << bpp) - 1`;
|
||||
2. бинарным поиском находится первый элемент с удаляемым старшим битом;
|
||||
3. исходный отсортированный массив делится на две отсортированные половины;
|
||||
4. у второй половины удаляется старший бит;
|
||||
5. половины сливаются как два отсортированных массива;
|
||||
6. появившиеся после усечения дубликаты удаляются.
|
||||
|
||||
Если требуется убрать несколько битов, операция повторяется по одному биту. В `set9.c` результат
|
||||
нормализации сохраняется в кэше, поэтому та же строка при том же `target_bpp` не проходит этот
|
||||
цикл повторно.
|
||||
|
||||
## Два кэша декодированных множеств
|
||||
|
||||
`cache_decode_set()` обслуживает два независимых кэша:
|
||||
|
||||
```text
|
||||
cache_id = 0 — первый операнд
|
||||
cache_id = 1 — второй операнд
|
||||
```
|
||||
|
||||
Каждый кэш содержит до `CACHE_SIZE = 512` записей и 1024 бакета. Разделение не даёт потоку
|
||||
часто повторяющихся `Requires` вытеснять кэш `Provides` и наоборот, хотя реальный эффект зависит
|
||||
от порядка вызовов решателя зависимостей.
|
||||
|
||||
### Ключ записи
|
||||
|
||||
Для быстрого предварительного отбора вычисляется fingerprint:
|
||||
|
||||
```c
|
||||
str[0] | (str[2] << 8) | (str[3] << 16)
|
||||
```
|
||||
|
||||
Он смешивается с `target_bpp`, после чего выбирается бакет. Совпадения fingerprint недостаточно:
|
||||
успешный hit требует также равного `target_bpp` и полного `strcmp()` строки. Поэтому коллизия
|
||||
fingerprint влияет только на длину цепочки, но не подменяет декодированное множество.
|
||||
|
||||
`target_bpp` входит в ключ, поскольку одна и та же исходная строка может участвовать в
|
||||
сравнениях с операндами разной точности и, следовательно, иметь разные нормализованные массивы.
|
||||
|
||||
### Устройство записи
|
||||
|
||||
Одним `xmalloc()` выделяются:
|
||||
|
||||
```text
|
||||
struct cache_ent
|
||||
массив unsigned для хэшей
|
||||
копия исходной строки
|
||||
```
|
||||
|
||||
Запись хранит fingerprint, исходную строку, число элементов и `target_bpp`.
|
||||
|
||||
### Промах
|
||||
|
||||
При промахе происходят следующие шаги:
|
||||
|
||||
1. `set_meta_fini()` вычисляет длины и верхнюю границу числа значений;
|
||||
2. выделяется новая запись;
|
||||
3. `decode_set()` создаёт отсортированный массив хэшей;
|
||||
4. при необходимости массив поэтапно уменьшается до `target_bpp`;
|
||||
5. строка и метаданные копируются в запись;
|
||||
6. запись добавляется в хэш-бакет и в начало логического LRU.
|
||||
|
||||
### LRU
|
||||
|
||||
Все записи одного кэша образуют двусвязный список от `oldest` к `newest`. При hit запись
|
||||
отцепляется от текущего места и становится `newest`; это не требует сдвига массива записей.
|
||||
При заполненном кэше удаляется `oldest`, включая поиск ссылки на него в соответствующей цепочке
|
||||
бакета.
|
||||
|
||||
## Сравнение в `rpmsetcmp()`
|
||||
|
||||
Последовательность работы:
|
||||
|
||||
1. снять `set:`, если он присутствует;
|
||||
2. выполнить `set_meta_init()` для обеих строк;
|
||||
3. выбрать минимальный `target_bpp`;
|
||||
4. получить первый операнд из кэша `0` или декодировать его;
|
||||
5. если строки полностью одинаковы, вернуть `0`;
|
||||
6. получить второй операнд из кэша `1` или декодировать его;
|
||||
7. сравнить мощности и содержимое нормализованных массивов.
|
||||
|
||||
Быстрый возврат для одинаковых строк выполняется после полноценного декодирования первого
|
||||
операнда. Поэтому равенство строк не позволяет принять некорректный payload без проверки.
|
||||
Метаданные второго операнда к этому моменту также уже прошли начальную проверку.
|
||||
|
||||
### Равная мощность
|
||||
|
||||
Нормализованные массивы отсортированы и не содержат дубликатов. Если `cnt1 == cnt2`, два
|
||||
множества могут быть либо равны, либо несравнимы. Поэтому достаточно:
|
||||
|
||||
```c
|
||||
memcmp(hash_arr1, hash_arr2, cnt1 * sizeof(unsigned))
|
||||
```
|
||||
|
||||
Совпадение даёт `0`, различие — `-2`.
|
||||
|
||||
### Разная мощность
|
||||
|
||||
Множество с большим числом уникальных хэшей не может быть строгим подмножеством меньшего.
|
||||
Поэтому проверяется только одно возможное направление:
|
||||
|
||||
```text
|
||||
cnt1 > cnt2: set2 ⊆ set1 ? 1 : -2
|
||||
cnt1 < cnt2: set1 ⊆ set2 ? -1 : -2
|
||||
```
|
||||
|
||||
Саму проверку выполняет `sorted_subset(small, large)`.
|
||||
|
||||
### `sorted_subset()`
|
||||
|
||||
Алгоритм выбирается по отношению размеров:
|
||||
|
||||
```c
|
||||
jump = large_count / small_count;
|
||||
```
|
||||
|
||||
Если `jump < 4`, используется обычное линейное слияние: указатель большого массива двигается
|
||||
до текущего элемента малого. Для близких по размеру множеств это последовательный проход с
|
||||
хорошей локальностью.
|
||||
|
||||
Если `jump >= 4`, вызывается `step_lower_bound()`. Функция сначала делает шаги примерно на
|
||||
среднее расстояние между искомыми элементами, затем делит шаг пополам, пока не найдёт первый
|
||||
элемент, который не меньше искомого. Это позволяет пропускать части большого массива при
|
||||
разреженном малом множестве без sentinel-значений и выхода за границы.
|
||||
|
||||
При первом отсутствующем элементе `sorted_subset()` возвращает ложь.
|
||||
|
||||
## `SELF_TEST`
|
||||
|
||||
При `SELF_TEST` компилируется `main()`, который проверяет только публичный API на коротких
|
||||
наборах:
|
||||
|
||||
1. строгое надмножество возвращает `1`;
|
||||
2. равные множества возвращают `0`;
|
||||
3. строгое подмножество возвращает `-1`;
|
||||
4. пересекающиеся несравнимые множества возвращают `-2`;
|
||||
5. выделенные строки и оба объекта освобождаются.
|
||||
|
||||
В отличие от исходного `set.c`, встроенный тест является smoke-тестом, а не полной проверкой совместимости.
|
||||
|
||||
## "Карта функций"
|
||||
|
||||
| Функция | Роль |
|
||||
| ------------------------ | -------------------------------------------- |
|
||||
| `set_new()` | создание контейнера символов |
|
||||
| `set_add()` | добавление имени в строковую арену |
|
||||
| `set_free()` | освобождение контейнера |
|
||||
| `hash()` | Jenkins OAAT |
|
||||
| `sort_symbols()` | выбор `qsort()` или radix sort |
|
||||
| `encode_golomb_Mshift()` | вычисление параметра Golomb-Rice |
|
||||
| `encode_writer_*()` | потоковая упаковка битов в Base62 |
|
||||
| `encode_set()` | объединённые delta, Golomb-Rice и Base62 |
|
||||
| `set_fini()` | полный путь от имён до set-строки |
|
||||
| `set_meta_init()` | быстрая проверка заголовка строки |
|
||||
| `set_meta_fini()` | длины и верхние границы буферов |
|
||||
| `decode_chunk()` | чтение обычного или escape Base62-блока |
|
||||
| `decode_set()` | объединённые Base62, Golomb-Rice и delta |
|
||||
| `cache_decode_set()` | два бакетных LRU-кэша |
|
||||
| `downsample_set()` | уменьшение точности на один бит |
|
||||
| `step_lower_bound()` | прыжок и уточнение позиции в большом массиве |
|
||||
| `sorted_subset()` | проверка включения отсортированных множеств |
|
||||
| `rpmsetcmp()` | публичное сравнение set-строк |
|
||||
@@ -3,6 +3,7 @@
|
||||
Research on ALT Linux RPM set.c and dependency comparison using encoded symbol sets.
|
||||
|
||||
- [Docs about current set:version implementation](Docs/set:version.md)
|
||||
- [Docs about rewrite implementation (ru)](Docs/set9_ru.md)
|
||||
|
||||
# Compare results
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#include "system.h"
|
||||
|
||||
#define CACHE_SIZE 512
|
||||
#define PIVOT_SIZE 486
|
||||
|
||||
struct set {
|
||||
size_t cnt;
|
||||
@@ -483,26 +482,6 @@ static const unsigned char char_to_num[255 + 1] = {[0] = 0xff, /* конец с
|
||||
|
||||
[('z' + 1)... 255] = 0xee};
|
||||
|
||||
static char* put6bits(int c, char* bit_pt) {
|
||||
*bit_pt++ = (c >> 0) & 1;
|
||||
*bit_pt++ = (c >> 1) & 1;
|
||||
*bit_pt++ = (c >> 2) & 1;
|
||||
*bit_pt++ = (c >> 3) & 1;
|
||||
*bit_pt++ = (c >> 4) & 1;
|
||||
*bit_pt++ = (c >> 5) & 1;
|
||||
|
||||
return bit_pt;
|
||||
}
|
||||
|
||||
static char* put4bits(int c, char* bit_pt) {
|
||||
*bit_pt++ = (c >> 0) & 1;
|
||||
*bit_pt++ = (c >> 1) & 1;
|
||||
*bit_pt++ = (c >> 2) & 1;
|
||||
*bit_pt++ = (c >> 3) & 1;
|
||||
|
||||
return bit_pt;
|
||||
}
|
||||
|
||||
// Decode base62 and Golomb-Rice in one pass. Base62 is LSB-first; a Z escape contributes 10 stream
|
||||
// bits.
|
||||
static inline int decode_chunk(const unsigned char** input, uint64_t* chunk, unsigned* width) {
|
||||
|
||||
Reference in New Issue
Block a user