Зависимости в RPM. Автоматически и вручную проставляемые Requires и Provides. Общая концепция

В системе зависимостей RPM-пакетов есть 2 основные сущности:

  • Provides — предоставляемые пакетом «возможности»,
  • Requires — зависимости пакета — какие «возможности» нужны для работы этого пакета.


В этой статье рассмотрим их общую концепцию. Это актуально и для пользователей, и для сборщиков пакетов. Рассматривать будем на примере дистрибутива ROSA 12 (rosa2021.1).

В Provides всегда есть «имя_пакета = эпоха:версия-релиз», дополнительно могут быть произвольные провайды, как версионированные (foo = X), так и нет (foo).
Requires тоже могут быть как версионированными, так и нет.

Существует система автоматизации генерации провайдов (Provides) и зависимостей (Requires), которая позволяет там, где есть таковая техническая возможность, автоматизировать расставление зависимостей между пакетами: один пакет предоставляет «возможность» (Provides), а другой ее требует (Requires).

Provides и Requires могут быть проставлены как автоматически, так и вручную. Команда вида dnf install foo поставит пакет, имеющий foo в Provides, не обязательно называющийся foo.

Рассмотрим пример.
Посмотрим зависимости установленного пакета patchelf:

$ rpm -q --requires patchelf
libm.so.6()(64bit)
libm.so.6(Glibm_2.14)(64bit)
libm.so.6(Glibm_2.2.5)(64bit)
libm.so.6(Glibm_2.3.4)(64bit)
libm.so.6(Glibm_2.32)(64bit)
libm.so.6(Glibm_2.33)(64bit)
libm.so.6(Glibm_2.4)(64bit)
libgcc_s.so.1()(64bit)
libgcc_s.so.1(GCC_3.0)(64bit)
libm.so.6()(64bit)
libstdc++.so.6()(64bit)
libstdc++.so.6(CXXABI_1.3)(64bit)
libstdc++.so.6(GlibmXX_3.4)(64bit)
libstdc++.so.6(GlibmXX_3.4.9)(64bit)
rpmlib(CompressedFileNames) <= 3.0.4-1
rpmlib(FileDigests) <= 4.6.0-1
rpmlib(PayloadFilesHavePrefix) <= 4.0-1
rpmlib(PayloadIsXz) <= 5.2-1
rtld(GNU_HASH)

То же самое для любого пакета из репозитория, в т.ч. не установленного, можно посмотреть через пакетный менеджер dnf:
sudo dnf repoquery --requires patchelf

Выше перечислено по одной зависимости на строку. Давайте узнаем, какой же пакет предоставляет libm.so.6()(64bit):

$ rpm -q --whatprovides 'libm.so.6()(64bit)'
glibc-2.33-4.x86_64

Если хочется узнать только имя пакета, без версии и пр.:

$ rpm -q --qf '%{name}\n' --whatprovides 'libm.so.6()(64bit)'
glibc

Сделать то же самое для всех пакетов в репозитории, а не только уже установленных, можно так:
sudo dnf repoquery --whatrequires 'libm.so.6()(64bit)'
sudo dnf repoquery --qf '%{name}' --whatrequires 'libm.so.6()(64bit)'

Посмотрим все провайды пакета glibc:

$ rpm -q --provides glibc
/sbin/ldconfig
config(glibc) = 6:2.33-4
glibc = 6:2.33-4
glibc(x86-64) = 6:2.33-4
ld-linux-x86-64.so.2()(64bit)
ld-linux-x86-64.so.2(GLIBC_2.2.5)(64bit)
ld-linux-x86-64.so.2(GLIBC_2.3)(64bit)
ld-linux-x86-64.so.2(GLIBC_2.4)(64bit)
ld.so = 6:2.33-4
ldconfig = 6:2.33-4
lib64nss_files2 = 6:2.33-4
libBrokenLocale.so.1()(64bit)
libBrokenLocale.so.1(GLIBC_2.2.5)(64bit)
libCNS.so()(64bit)
libGB.so()(64bit)
libISOIR165.so()(64bit)
libJIS.so()(64bit)
libJISX0213.so()(64bit)
libKSC.so()(64bit)
libSegFault.so()(64bit)
libanl.so.1()(64bit)
libanl.so.1(GLIBC_2.2.5)(64bit)
libc.so.6()(64bit)
libc.so.6(GLIBC_2.10)(64bit)
libc.so.6(GLIBC_2.11)(64bit)
libc.so.6(GLIBC_2.12)(64bit)
libc.so.6(GLIBC_2.13)(64bit)
libc.so.6(GLIBC_2.14)(64bit)
libc.so.6(GLIBC_2.15)(64bit)
libc.so.6(GLIBC_2.16)(64bit)
libc.so.6(GLIBC_2.17)(64bit)
libc.so.6(GLIBC_2.18)(64bit)
libc.so.6(GLIBC_2.2.5)(64bit)
libc.so.6(GLIBC_2.2.6)(64bit)
libc.so.6(GLIBC_2.22)(64bit)
libc.so.6(GLIBC_2.23)(64bit)
libc.so.6(GLIBC_2.24)(64bit)
libc.so.6(GLIBC_2.25)(64bit)
libc.so.6(GLIBC_2.26)(64bit)
libc.so.6(GLIBC_2.27)(64bit)
libc.so.6(GLIBC_2.28)(64bit)
libc.so.6(GLIBC_2.29)(64bit)
libc.so.6(GLIBC_2.3)(64bit)
libc.so.6(GLIBC_2.3.2)(64bit)
libc.so.6(GLIBC_2.3.3)(64bit)
libc.so.6(GLIBC_2.3.4)(64bit)
libc.so.6(GLIBC_2.30)(64bit)
libc.so.6(GLIBC_2.32)(64bit)
libc.so.6(GLIBC_2.33)(64bit)
libc.so.6(GLIBC_2.4)(64bit)
libc.so.6(GLIBC_2.5)(64bit)
libc.so.6(GLIBC_2.6)(64bit)
libc.so.6(GLIBC_2.7)(64bit)
libc.so.6(GLIBC_2.8)(64bit)
libc.so.6(GLIBC_2.9)(64bit)
libdl.so.2()(64bit)
libdl.so.2(GLIBC_2.2.5)(64bit)
libdl.so.2(GLIBC_2.3.3)(64bit)
libdl.so.2(GLIBC_2.3.4)(64bit)
libm.so.6()(64bit)
libm.so.6(GLIBC_2.15)(64bit)
libm.so.6(GLIBC_2.18)(64bit)
libm.so.6(GLIBC_2.2.5)(64bit)
libm.so.6(GLIBC_2.23)(64bit)
libm.so.6(GLIBC_2.24)(64bit)
libm.so.6(GLIBC_2.25)(64bit)
libm.so.6(GLIBC_2.26)(64bit)
libm.so.6(GLIBC_2.27)(64bit)
libm.so.6(GLIBC_2.28)(64bit)
libm.so.6(GLIBC_2.29)(64bit)
libm.so.6(GLIBC_2.31)(64bit)
libm.so.6(GLIBC_2.32)(64bit)
libm.so.6(GLIBC_2.4)(64bit)
libmvec.so.1()(64bit)
libmvec.so.1(GLIBC_2.22)(64bit)
libnsl.so.1()(64bit)
libnsl.so.1(GLIBC_2.2.5)(64bit)
libnss_compat.so.2()(64bit)
libnss_db.so.2()(64bit)
libnss_dns.so.2()(64bit)
libnss_files.so.2()(64bit)
libnss_hesiod.so.2()(64bit)
libpthread.so.0()(64bit)
libpthread.so.0(GLIBC_2.11)(64bit)
libpthread.so.0(GLIBC_2.12)(64bit)
libpthread.so.0(GLIBC_2.18)(64bit)
libpthread.so.0(GLIBC_2.2.5)(64bit)
libpthread.so.0(GLIBC_2.2.6)(64bit)
libpthread.so.0(GLIBC_2.28)(64bit)
libpthread.so.0(GLIBC_2.3.2)(64bit)
libpthread.so.0(GLIBC_2.3.3)(64bit)
libpthread.so.0(GLIBC_2.3.4)(64bit)
libpthread.so.0(GLIBC_2.30)(64bit)
libpthread.so.0(GLIBC_2.31)(64bit)
libpthread.so.0(GLIBC_2.4)(64bit)
libresolv.so.2()(64bit)
libresolv.so.2(GLIBC_2.2.5)(64bit)
libresolv.so.2(GLIBC_2.3.2)(64bit)
libresolv.so.2(GLIBC_2.9)(64bit)
librt.so.1()(64bit)
librt.so.1(GLIBC_2.2.5)(64bit)
librt.so.1(GLIBC_2.3.3)(64bit)
librt.so.1(GLIBC_2.3.4)(64bit)
librt.so.1(GLIBC_2.4)(64bit)
librt.so.1(GLIBC_2.7)(64bit)
libthread_db.so.1()(64bit)
libthread_db.so.1(GLIBC_2.2.5)(64bit)
libthread_db.so.1(GLIBC_2.3)(64bit)
libthread_db.so.1(GLIBC_2.3.3)(64bit)
libutil.so.1()(64bit)
libutil.so.1(GLIBC_2.2.5)(64bit)
rtld(GNU_HASH)
should-restart = system

Сделать то же самое для не установленного пакета можно так:
sudo dnf repoquery --provides glibc

Кстати, dnf repoquery можно сокращать до dnf rq:
sudo dnf rq --provides glibc

Откуда же в пакете glibc появился провайд libm.so.6()(64bit)? Найдем эту библиотеку среди файлов этого пакета:

$ rpm -ql glibc | grep libm.so.6
/lib64/libm.so.6

Убедимся, что это 64-разрядная библиотека:

$ file $(realpath /lib64/libm.so.6)
/lib64/libm-2.33.so: ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), dynamically linked, BuildID[sha1]=2416a17b6d6afdf08c8bc3fd2f4e78c2353a4f88, for GNU/Linux 4.14.0, not stripped

Для пакета, в котором лежит разделяемая библиотека, RPM автоматически формирует провайд по имени библиотеки: libfoo.so.N()(64bit), где libfoo.so.N ­— это soname библиотеки, далее идет значение макроса %_arch_tag_suffix, которое пустое для 32-битных ELF-файлов и имеет значение ()(64bit) для 64-битных.

Проверим soname библиотеки:

$ patchelf --print-soname /lib64/libm-2.33.so
libm.so.6

Обратите внимание, что soname "вшит" в сам ELF-файл и чисто технически не обязан совпадать с именем файла.

Посмотрим на значение макроса %_arch_tag_suffix в 64 и 32-битных случаях:

[user@notb1 ~]$ rpm -E "%_arch_tag_suffix"
()(64bit)
[user@notb1 ~]$ setarch i386 rpm -E "%_arch_tag_suffix"

[user@notb1 ~]$

Этого макроса не было в старых версиях RPM и нет в, например, rpm-build 4.0 в ALT Linux, однако фактические провайды такие же, просто этот "суффикс" не вынесен в макрос.

Мы рассмотрели, почему у RPM-пакета glibc появился провайд (Provides) libm.so.6()(64bit). Теперь рассмотрим, почему у пакета patchelf автоматически появилась зависимость (Requires) libm.so.6()(64bit).

Здесь все просто: в исполняемом ELF-файле /usr/bin/patchelf прописана зависимость от библиотеки libm.so.6, а сам файл является 64-битным, а значит и библиотека нужна 64-битная; соединяем "libm.so.6" и "()(64bit)" и получаем "libm.so.6()(64bit)", что добавляется в зависимости пакета, в котором лежит файл /usr/bin/patchelf.

Посмотрим на список библиотек, необходимых для запуска patchelf:

$ patchelf --print-needed /usr/bin/patchelf
libstdc++.so.6
libm.so.6
libgcc_s.so.1
libc.so.6

Видите среди них libm.so.6? Это оно!

RPM также поддерживает версионирование символов в библиотеках. Среди зависимостей пакета patchelf выше была такая: libc.so.6(GLIBC_2.33)(64bit). Откуда же взялось (GLIBC_2.33)?

Посмотрим на символы, требуемые ELF-файлом:

$ readelf -a /usr/bin/patchelf | grep @GLIBC_2.33
0000004251e8  004900000007 R_X86_64_JUMP_SLO 0000000000000000 stat64@GLIBC_2.33 + 0
    73: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND [...]@GLIBC_2.33 (10)

Видим, что используется функция stat64, которая появилась в библиотеке glibc версии 2.33. Это было выяснено и записано в ELF-файл на этапе его сборки (компиляции, линковки и пр.).

Посмотрим, из какой библиотеки на самом деле подгружается символ stat64 при запуске patchelf, т.е. какая библиотека предоставляем функцию stat64:

$ LD_DEBUG=symbols /lib64/ld-linux-x86-64.so.2 /usr/bin/patchelf --help
<...>
1968:	symbol=stat64;  lookup in file=/usr/bin/patchelf [0]
1968:	symbol=stat64;  lookup in file=/lib64/libstdc++.so.6 [0]
1968:	symbol=stat64;  lookup in file=/lib64/libm.so.6 [0]
1968:	symbol=stat64;  lookup in file=/lib64/libgcc_s.so.1 [0]
1968:	symbol=stat64;  lookup in file=/lib64/libc.so.6 [0]
<...>

Последней в списке идет libc.so.6, а значит символ взят из нее. Если вы с удивлением прочитали команду выше, то рекомендую почитать man ld-linux (аналог для FreeBSD — man rtld).

Как видите, если предоставляемые библиотекой символы версионированы, то RPM определит зависимость не только от библиотеки, но и от ее минимально необходимой версии.

Отправить ответ

avatar
  Subscribe  
Сообщать по почте