Files
ARSV/Docs/set:version.md
T
2026-07-03 00:04:36 +03:00

239 lines
9.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# set:version
what is happening inside [set:version](https://git.altlinux.org/gears/r/rpm.git?a=blob;f=lib/set.c)
Актуальный код с наработками расположен в [репозитории](https://github.com/kr0sh512/alt-rpm-set-version.git)
## Зачем
set:version в alt-rpm позволяет сопоставлять Provides и Requires пакетов не обычным сравнением версий, а сравнением специальных set-строк зависимостей вида
```text
libfoo.so.X = set:<encoded-set>
```
`encoded-set` формируется на основе символов, необходимых/предоставляемых пакетом. Данный механизм позволяет гарантировать (с точностью до коллизий хэша, об этом будет далее) наличие всех требуемых символов в библиотеке. Это исключает ситуации, при которых ">= версий" ломается при удалении символа из библиотеки, а также ситуаций совпадения SONAME библиотек с разным набором символов.
set-строки (являющиеся перекодированным списком символов) генерируются способом, который позволяет их сравнивать между собой на предмет включения одного множества символов в другой.
## Реализация `set.c`
`set.c` предоставляет 5 "публичных" API для работы set:version
```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);
```
### `rpmsetcmp()`
Основная функция, [сравнивающая строки](#сравнение-set-строк) и выдающая результат в зависимости от включения:
- 1: set1 > set2
- 0: set1 == set2
- -1: set1 < set2 (aka set1 \subset set2)
- -2: set1 != set2
- -3: set1 decoder error
- -4: set2 decoder error
на основе [данной](https://github.com/svpv/rpmss/blob/4256d86cc9ba1aa4ceb8c0f03f7d48675d9d27bb/set.h#L12) заметки, `set1` лучше делать как `Provides` для лучшей производительности.
### `set_new()`
Создаёт пустой объект `struct set`.
Внутри `struct set` — это временный контейнер для строк символов и их будущих hash-значений.
Из релизации:
> internally struct set is just a bag of strings and their hash values.
### `set_add()`
Добавляет строковый символ в set.
Использование:
```c
set_add(s, "printf@@GLIBC_2.2.5");
set_add(s, "malloc@@GLIBC_2.2.5");
```
### `set_fini()`
Финализирует множество и возвращает готовую set-version строку.
В качестве параметров принимает `struct set *set` и `int bpp`
При `bpp` < 10 или `bpp` > 32 функция возвращает `NULL`.
Работа функции [описана далее](#работа-внутри-set_fini).
### `set_free()`
Освобождает struct set и его внутренние строки.
!!! NOTE В данный момент не освобождается сама структура `struct set`, память под которую в `set_new()` выделяется с `xmalloc`
## Работа внутри `set_fini()`
Основной процесс преобразования массива строк в set-строку происходит следующим образом:
```
массив строк
| (Jenkins OATT)
v
массив хэшей
| (qsort)
v
отсортированный массив хэшей
| (вычисление разницы между элементами)
v
массив delta
| (Rice-Golomb преобразование)
v
битовый массив
| (base62 преобразование)
v
set-строка
```
### hash
Для каждого элемента (строки) высчитывается 32-битный хэш и обрезается до `bpp` бит
```c
set->sv[i].v = hash(set->sv[i].s) & mask;
```
В качестве хэш-функции используется `Jenkins OAAT`.
Получившийся массив сортируется по хэш значениям по возрастанию. При коллизии, печатается `warning: hash collision` в `stderr`
С помощью функции `uniqv()` в массиве остаются только уникальные значения, возвращаемое значение - размер массива после удаления повторов.
Все дальнейшие преобразования происходят внутри функции `encode_set()`. Возвращаемое значение - длина итоговой set-строки, включая bpp и Mshift первыми двумя символами.
!!! NOTE Возвращаемое значение `encode_set()` может использоваться как код ошибки при отрицательных значениях
### delta
Внутри функции `encode_delta()` происходит преобразование массива отсортированных по возрастанию чисел в массив дельт между числами, т.е.
Пример исходного массива:
```c
unsigned *v = {1, 4, 16, 22};
```
Пример результирующего массива:
```c
unsigned *v = {1, 3, 12, 8};
```
### Rice-Golomb
> `encode_golomb()` - Main golomb encoding routine: package integers into bits.
Для сокращения длины итоговой строки применяется Rice-Golomb кодирование, которое позволяет с параметром `Mshift` записать число `n` как `n >> Mshift`, записанный последовательностью нулей и `n & 2^Mshift` записанный стандартным двоичным кодированием. Целая часть и остаток разделяется единичном битом.
`Mshift` выбирается как `bpp - log2(c) - 1`, т.к. при равномерном распределении `c` хэшей по диапазону `2^bpp`, средняя `delta` будет равна `2^bpp / c`
Пример:
```
Mshift = 8
n = 553
q = 553 >> 8 = 2
r = 553 & 255 = 42
bits = 00 1 00101010
```
Функция `encode_golomb()` возвращает длину итоговой битовой последовательности. Сама последовательность находится в `char *bitv` (`char = [1,0]`)
### base62
> `encode_base62()` - pack bitv into base62 string
Последним шагом является преобразование битовой последовательности в `base62` строку
Алфавитом для кодирования является `0-9,a-z,A-Z`, но символ `Z` кодирует сразу 61, 62 и 63 следующим образом:
1. в битовую последовательность после кодированием `Z` добавляется 2 бита:
- `00` для 61
- `01` для 62
- `10` для 63
2. битовая последовательность продолжает кодироваться стандартным образом
Заметим, что данным образом невозможно получить поседовательность `ZZ`, т.к. символ `Z` требует двух старших битов выставленных в `11`
Пример преобразования:
```
Mshift = 5
bits = 1 01111 00 1 00010
```
Или же
```
bits = 101111 001000 10
```
Биты читаются младшим битом вперёд. Первые 6 бит:
```
101111 = 1*1 + 0*2 + 1*4 + 1*8 + 1*16 + 1*32 = 61 = Z
```
Тогда в последовательность добавляется два бита `00` сразу после битов, кодирующих Z.
```
bits = 101111 000010 0010
000010 = 16 = g
0010(00) = 4 = 4
```
Итоговая строка в `base62`:
```
10111100100010 = Zg4
```
## Сравнение set-строк
При сравнении set-строк происходит обратный процесс преобразования до получений хэш-значений
```
set-строка
| (обратное base62 преобразование)
v
битовый массив
| (обратное Rice-Golomb преобразование)
v
массив delta
| (вычисление изначальных значений)
v
массив хэшей
```
После массивы хэшей сравниваются между собой на наличие жлементов одного массива в другом.
## Комментарии
## additional
[коды](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