В системе зависимостей 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 определит зависимость не только от библиотеки, но и от ее минимально необходимой версии.
[…] Зависимости в RPM. Автоматически и вручную проставляемы… 17.01.2022 […]