From 96fd2fcc0438252889666b9a238ec6f37ee6fbd6 Mon Sep 17 00:00:00 2001 From: Gordon Messmer Date: Tue, 31 Jan 2023 23:13:47 -0800 Subject: [PATCH 1/3] Enhance requires with version information from the build root. The --libtool-version-fallback option will cause elfdeps to try to use the version information in the shared object's filename for shared objects that don't provide versioned symbols. This additional information allows rpm to track minor-version dependencies. --- CMakeLists.txt | 2 +- build/files.cc | 76 +++++++++- config.h.in | 2 + docs/manual/more_dependencies.md | 8 +- fileattrs/elf.attr | 4 +- macros.in | 12 ++ tests/atlocal.in | 5 + tests/data/misc/.elf-version/libhello.so | 1 + tests/data/misc/libhello.so | Bin 16320 -> 17 bytes tests/data/misc/libhello.so.1.0.0 | Bin 0 -> 16320 bytes tests/rpmbuild.at | 98 ++++++++++++ tools/elfdeps.cc | 184 +++++++++++++++++++++-- 12 files changed, 375 insertions(+), 17 deletions(-) create mode 100644 tests/data/misc/.elf-version/libhello.so mode change 100755 => 120000 tests/data/misc/libhello.so create mode 100755 tests/data/misc/libhello.so.1.0.0 diff --git a/CMakeLists.txt b/CMakeLists.txt index ef2aecbfd0..6f6fe6f10c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -190,7 +190,7 @@ include(CheckVariableExists) set(OPTFUNCS stpcpy stpncpy putenv mempcpy fdatasync lutimes mergesort getauxval setprogname __progname syncfs sched_getaffinity unshare - secure_getenv __secure_getenv mremap strchrnul + secure_getenv __secure_getenv mremap strchrnul dlmopen dlinfo ) set(REQFUNCS mkstemp getcwd basename dirname realpath setenv unsetenv regcomp diff --git a/build/files.cc b/build/files.cc index fba2aff4a4..b019120c66 100644 --- a/build/files.cc +++ b/build/files.cc @@ -19,6 +19,7 @@ #ifdef WITH_CAP #include #endif +#include #ifdef HAVE_LIBDW #include @@ -55,6 +56,8 @@ #define DEBUG_ID_DIR "/usr/lib/debug/.build-id" #define DEBUG_DWZ_DIR "/usr/lib/debug/.dwz" +#define XATTR_NAME_SOVERS "user.rpm_elf_so_version" + /** */ enum specfFlags_e { @@ -1708,6 +1711,63 @@ static void argvAddAttr(ARGV_t *filesp, rpmfileAttrs attrs, const char *path) free(line); } +static int generateElfSoVers(FileList fl) +{ + int rc = 0; + int i; + FileListRec flp; + + /* How are we supposed to create the build-id links? */ + char *elf_so_version_macro = rpmExpand("%{?_elf_so_version}", NULL); + if (*elf_so_version_macro == '\0') { + rc = 1; + rpmlog(RPMLOG_WARNING, + _("_elf_so_version macro not set, skipping elf-version generation\n")); + } + + if (rc != 0) { + free(elf_so_version_macro); + return rc; + } + + /* Save _elf_so_version for ELF shared objects in this package. */ + for (i = 0, flp = fl->files.data(); i < fl->files.size(); i++, flp++) { + struct stat sbuf; + if (lstat(flp->diskPath, &sbuf) == 0 && S_ISREG (sbuf.st_mode)) { + /* We determine whether this is a main or + debug ELF based on path. */ + int isDbg = strncmp (flp->cpioPath, + DEBUG_LIB_PREFIX, strlen (DEBUG_LIB_PREFIX)) == 0; + + /* Only save elf_so_version executable files in the main package. */ + if (isDbg + || (sbuf.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) == 0) + continue; + + int fd = open (flp->diskPath, O_RDONLY); + if (fd >= 0) { + /* Only real ELF files, that are ET_DYN should have _elf_so_version. */ + GElf_Ehdr ehdr; +#ifdef HAVE_DWELF_ELF_BEGIN + Elf *elf = dwelf_elf_begin (fd); +#else + Elf *elf = elf_begin (fd, ELF_C_READ, NULL); +#endif + if (elf != NULL && elf_kind (elf) == ELF_K_ELF + && gelf_getehdr (elf, &ehdr) != NULL + && (ehdr.e_type == ET_DYN)) { + fsetxattr(fd, XATTR_NAME_SOVERS, + elf_so_version_macro, strlen(elf_so_version_macro), 0); + } + elf_end (elf); + close (fd); + } + } + } + free(elf_so_version_macro); + return rc; +} + #ifdef HAVE_LIBDW /* How build id links are generated. See macros.in for description. */ #define BUILD_IDS_NONE 0 @@ -2582,13 +2642,25 @@ static rpmRC processPackageFiles(rpmSpec spec, rpmBuildPkgFlags pkgFlags, if (fl.processingFailed) goto exit; -#ifdef HAVE_LIBDW { /* Check build-ids and add build-ids links for files to package list. */ const char *arch = headerGetString(pkg->header, RPMTAG_ARCH); if (rpmExpandNumeric("%{?__debug_package}") && !rstreq(arch, "noarch")) { /* Go through the current package list and generate a files list. */ ARGV_t idFiles = NULL; + /* Go through the current package list and tag shared object files. */ + if (generateElfSoVers (&fl) != 0) { + rpmlog(RPMLOG_ERR, _("Generating elf-so-version failed\n")); + fl.processingFailed = 1; + goto exit; + } + + if (fl.processingFailed) + goto exit; + +#ifdef HAVE_LIBDW + /* Check build-ids and add build-ids links for files to package list. */ + /* Go through the current package list and generate a files list. */ if (generateBuildIDs (&fl, &idFiles) != 0) { rpmlog(RPMLOG_ERR, _("Generating build-id links failed\n")); fl.processingFailed = 1; @@ -2605,8 +2677,8 @@ static rpmRC processPackageFiles(rpmSpec spec, rpmBuildPkgFlags pkgFlags, if (fl.processingFailed) goto exit; } -} #endif +} /* Verify that file attributes scope over hardlinks correctly. */ if (checkHardLinks(fl.files)) diff --git a/config.h.in b/config.h.in index 47a7d86d97..1b287bf3af 100644 --- a/config.h.in +++ b/config.h.in @@ -13,6 +13,8 @@ #cmakedefine HAVE_DIRENT_H @HAVE_DIRENT_H@ #cmakedefine HAVE_DIRNAME @HAVE_DIRNAME@ #cmakedefine HAVE_DLFCN_H @HAVE_DLFCN_H@ +#cmakedefine HAVE_DLINFO @HAVE_DLINFO@ +#cmakedefine HAVE_DLMOPEN @HAVE_DLMOPEN@ #cmakedefine HAVE_DSA_SET0_KEY @HAVE_DSA_SET0_KEY@ #cmakedefine HAVE_DSA_SET0_PQG @HAVE_DSA_SET0_PQG@ #cmakedefine HAVE_DSA_SIG_SET0 @HAVE_DSA_SIG_SET0@ diff --git a/docs/manual/more_dependencies.md b/docs/manual/more_dependencies.md index a1be24af6c..861d0a9b85 100644 --- a/docs/manual/more_dependencies.md +++ b/docs/manual/more_dependencies.md @@ -39,12 +39,16 @@ This has two "side-effects": It's a fairly common mistake to replace legacy PreReq dependencies with Requires(pre), but this is not the same, due to the latter point above! ## Automatic Dependencies -To reduce the amount of work required by the package builder, RPM scans the file list of a package when it is being built. Any files in the file list which require shared libraries to work (as determined by ldd) cause that package to require the shared library. +To reduce the amount of work required by the package builder, RPM scans the file list of a package when it is being built. Any files in the file list which require shared libraries to work (as determined by elfdeps for ELF objects) cause that package to require the shared library. -For example, if your package contains /bin/vi, RPM will add dependencies for both libtermcap.so.2 and libc.so.5. These are treated as virtual packages, so no version numbers are used. +For example, if your package contains /bin/vi, RPM will add dependencies for both libtermcap.so.2 and libc.so.5. These are treated as virtual packages. A similar process allows RPM to add Provides information automatically. Any shared library in the file list is examined for its soname (the part of the name which must match for two shared libraries to be considered equivalent) and that soname is automatically provided by the package. For example, the libc-5.3.12 package has provides information added for libm.so.5 and libc.so.5. We expect this automatic dependency generation to eliminate the need for most packages to use explicit Requires: lines. +As of rpm version (TBD), the internal dependency generator provides optional support for generating versioned ELF object dependencies on objects that do not provide versioned symbols. Distributions that wish to make use of this support should enable the \_elf\_provide\_fallback\_versions and \_elf\_require\_fallback\_versions macros. When these macros are enabled, the dependency generator will attempt to resolve shared object dependencies to a full path. If the shared object does not provide versioned symbols, and the soname is a symlink to a full name that differs from the soname, and the full name ends in ".so." followed by a sequence of numbers optionally separated by the '.' character, then that trailing sequence will be treated as a version and included in the automatically generated dependencies. While shared objects with versioned symbols are well supported, and are the preferred approach to ensuring that a dependency actually provides the ABI that another package requires, this fallback approach to versioning dependencies provides reasonably good coverage for the large body of ELF shared objects that do not yet maintain versioned symbols. + +The most likely case where the fallback version system will break is a shared object being updated from a purely numeric version suffix to a version suffix with non-numeric characters (e.g. "libfoo.so.3.0.1" is updated to "libfoo.so.3.0.1a"). In that case, the dependency generator would not provide a version for that shared object, and the package maintainer would need to manually add a Provides: tag to the package in order to continue satisfying existing package dependencies. + ## Custom Automatic Dependency Customizing automatic dependency generation is covered in [dependency generator documentation](). diff --git a/fileattrs/elf.attr b/fileattrs/elf.attr index 57325e0269..4dfd9c5898 100644 --- a/fileattrs/elf.attr +++ b/fileattrs/elf.attr @@ -1,5 +1,5 @@ -%__elf_provides %{_rpmconfigdir}/elfdeps --provides --multifile -%__elf_requires %{_rpmconfigdir}/elfdeps --requires --multifile +%__elf_provides %{_rpmconfigdir}/elfdeps %{?_elf_provide_fallback_versions} --provides --multifile +%__elf_requires %{_rpmconfigdir}/elfdeps %{?_elf_require_fallback_versions} --requires --multifile %__elf_magic ^(setuid,? )?(setgid,? )?(sticky )?ELF (32|64)-bit.*$ %__elf_exclude_path ^/lib/modules/.*\\.ko?(\\.[[:alnum:]]*)$ %__elf_protocol multifile diff --git a/macros.in b/macros.in index 7c99eb5731..116fc256bc 100644 --- a/macros.in +++ b/macros.in @@ -551,6 +551,18 @@ Supplements: (%{name} = %{version}-%{release} and langpacks-%{1})\ # Use internal dependency generator rather than external helpers? %_use_internal_dependency_generator 1 +# +# Generate minimum versions for ELF libraries that don't provide +# versioned symbol? +#%_elf_provide_fallback_versions --full-name-version-fallback +#%_elf_require_fallback_versions --full-name-version-fallback + +# +# ELF libraries will usually inherit a version from their package, +# but this can be overridden by defining _elf_so_version, +# especially for interchangeable implementations. +%_elf_so_version %{version} + # Directories whose contents should be considered as documentation. %__docdir_path %{_datadir}/doc:%{_datadir}/man:%{_datadir}/info:%{_datadir}/gtk-doc/html:%{_datadir}/gnome/help:%{?_docdir}:%{?_mandir}:%{?_infodir}:%{?_javadocdir}:/usr/doc:/usr/man:/usr/info:/usr/X11R6/man diff --git a/tests/atlocal.in b/tests/atlocal.in index 62e0008f77..536c3f5807 100644 --- a/tests/atlocal.in +++ b/tests/atlocal.in @@ -50,6 +50,11 @@ if [ "@WITH_IMAEVM@" == "ON" ]; then else IMA_DISABLED=true; fi +if [ "@HAVE_DLINFO@" == 1 ] && [ "@HAVE_DLMOPEN@" == 1 ]; then + HAVE_GNU_DLFCN=true; +else + HAVE_GNU_DLFCN=false; +fi if mknod foodev c 123 123 2>/dev/null; then MKNOD_DISABLED=false rm -f foodev diff --git a/tests/data/misc/.elf-version/libhello.so b/tests/data/misc/.elf-version/libhello.so new file mode 100644 index 0000000000..3eefcb9dd5 --- /dev/null +++ b/tests/data/misc/.elf-version/libhello.so @@ -0,0 +1 @@ +1.0.0 diff --git a/tests/data/misc/libhello.so b/tests/data/misc/libhello.so deleted file mode 100755 index 855067e728e33af2a5a9d360e4b6755dd0b9288e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16320 zcmeHOZ)_Y#6`#9{6DM|UHzdKt71>59x-Nq2&3iL%e>=N3>zjFcWPCK8N-0Q2J*wDxyvra3saP410;z%;P}}Kv zpX$-RTh^;KlwOfaXeI4(A1&-Hc3cliO3EQy0&kvySWO*1-2&T~N8i|Yg82d&SEu*cu_JubIf#v!inGca*) z1aY6CIL`BK1+-5{f1B~LCg%q&rar%2)LDi;W2eukYoB}hd*A-W7ard8>vv~=HhR!| z>ksc-;atEU$F1kLth;CXq4%CX%6^*(J6cUDJ;HW2!MPu+3I5=|$I> zDOKEZ>8YYBm!{^5<#N@zP_30cHFK_3s`xVuPv`2@T)!F{KY4uEIg&e)>(hCdL6uf1 z{Z}Ro*K=WSX*;uiUh)mbIr5kD>B9IId_@}Qj^VIZj~Pe4X3R0VIe)J#q z#dlimD=oh~td#w=6+hj0-M;wa)*HH+M&EAJn1uDi9`AbH?dZ1XF@ zt=Gx4uMWOJfPJ;)HC7L=`Mas<^IWE;w>MTh_bqeq4bkNPg7)wGE~|rg?8~?9>+c@3 zudik7)T{Ozcl=${z=$-^+E|_G+;?Id8FPP3iLQ zZ@X?c8lNQnPtspxn?5Y5rzTWtu`AWnzOD5+dh0(x82=Xi=6j7so*g>6MmxGc-Pv}b zby*$T`^d+S9@x(?`_OB<`VO_He@h+e==yqk_@1pUHN*~yBn%`BBn%`BBn%`BBn%`B zBn%`BBn%`Bd@u~iy3qJKP^|UjHKr`>MgrQ)Mi{2|5>rb(c8Sj99 zz0;_&%UM|jiZ!2kSzOBNPgyQW{SITQXGFJ&=J!~ZU`=X4o59*r{jN$7-fMZ?Da(hA zQkP`WD)3cl2zkCJeze$D3;Mg$F!`^DKmBT~&j&ox>#w%2N&Vl&4)T=tJ}8BC?D78k zWj*ky=&|A9Pgz-7>3z`Z&C@#MUh7EyNWM4U+iztjik@Y=zEm9QKS+%+Pdb!Nd~E1I z>Zscr>?y$Bf@VHg$h=4TZRUfqHXviJnGgEF`t4>uhb12Rzx6?`J@1nbbP!F>|N>8=niqWBJF zHO1S4u@UueSGd+uoSr&X7%}CH>In8tgljXZQ)Qd_cPTulqWf#c6yxX27WJn_6gAK7H0@`=eSvyf z;zG~FHnkkPF82$*62l)NJQd25`(qiVPfBg%?MsAn`&1PX-k*@7K@#WxTZA{uf3x#? zUboY!E?HEFNwapoPd;h-7K@Lo9}=FT9Ut-QdtKujukUNRuZ`pFH`;&qhU<7s@c4D- z3zeqbR76EYTIhtW2x5%;32(OVrGFFu5$(TmAIsz>{uRRaH|bYR*ZjJ_Ff)^zR_ohc z9Dm-K<_#}(!K{#fo2B zQZqGozUX)h^Ycp-60#if^dqj=H$yo&&gjI@sS#)7^aw|M^{O-HRy^K}bK;59 zL#Iv-6H4D=&WJ4m_K69CXx|P($HpH!J~Zw;Hahy*kx6HA==k^uHOPIx?`9v_v{NXA z_v*wp3n5?G=cD%y-NpW(XqzfmUz+#bDbjw;=sA=sRlk^^f~2R->;RX7JM3V z{|n*yX?V{OzIUPd_l>Z~v@n2pTj1Yl4wn|>tQ4RTkNF+XF~TxH2G3a$k9jThi{i}v z(ygiIRSaRiS)ekvjd*31ez~3Ta%n9Brg6FV2kMaIO zE?W3J!os649B)Y+H2O!3R0rLBB^HnGXXv*OC>rsAzZ#3j_Zl?b&)^@=|5=F#|H=|i z(ZTzk!13|(O~M&Nd^_Sq|~-J55f5$VE9?EMg1E}S%?6RCfH>&i6U=4gpDh@_ti;35pe9$8S^tqf zoWHm;qy_OpBVg^zhR~w^LS?+8GmiQ2=cPdlj$BCs9*t$%!Po`Hda>77Kac?azr};} p!TI3%Lo>JF+)aczX8VnPHr9X4)YD>c7mmNi6K#?mv4EJV{spTSXV?G$ diff --git a/tests/data/misc/libhello.so b/tests/data/misc/libhello.so new file mode 120000 index 0000000000..01437b7d36 --- /dev/null +++ b/tests/data/misc/libhello.so @@ -0,0 +1 @@ +libhello.so.1.0.0 \ No newline at end of file diff --git a/tests/data/misc/libhello.so.1.0.0 b/tests/data/misc/libhello.so.1.0.0 new file mode 100755 index 0000000000000000000000000000000000000000..855067e728e33af2a5a9d360e4b6755dd0b9288e GIT binary patch literal 16320 zcmeHOZ)_Y#6`#9{6DM|UHzdKt71>59x-Nq2&3iL%e>=N3>zjFcWPCK8N-0Q2J*wDxyvra3saP410;z%;P}}Kv zpX$-RTh^;KlwOfaXeI4(A1&-Hc3cliO3EQy0&kvySWO*1-2&T~N8i|Yg82d&SEu*cu_JubIf#v!inGca*) z1aY6CIL`BK1+-5{f1B~LCg%q&rar%2)LDi;W2eukYoB}hd*A-W7ard8>vv~=HhR!| z>ksc-;atEU$F1kLth;CXq4%CX%6^*(J6cUDJ;HW2!MPu+3I5=|$I> zDOKEZ>8YYBm!{^5<#N@zP_30cHFK_3s`xVuPv`2@T)!F{KY4uEIg&e)>(hCdL6uf1 z{Z}Ro*K=WSX*;uiUh)mbIr5kD>B9IId_@}Qj^VIZj~Pe4X3R0VIe)J#q z#dlimD=oh~td#w=6+hj0-M;wa)*HH+M&EAJn1uDi9`AbH?dZ1XF@ zt=Gx4uMWOJfPJ;)HC7L=`Mas<^IWE;w>MTh_bqeq4bkNPg7)wGE~|rg?8~?9>+c@3 zudik7)T{Ozcl=${z=$-^+E|_G+;?Id8FPP3iLQ zZ@X?c8lNQnPtspxn?5Y5rzTWtu`AWnzOD5+dh0(x82=Xi=6j7so*g>6MmxGc-Pv}b zby*$T`^d+S9@x(?`_OB<`VO_He@h+e==yqk_@1pUHN*~yBn%`BBn%`BBn%`BBn%`B zBn%`BBn%`Bd@u~iy3qJKP^|UjHKr`>MgrQ)Mi{2|5>rb(c8Sj99 zz0;_&%UM|jiZ!2kSzOBNPgyQW{SITQXGFJ&=J!~ZU`=X4o59*r{jN$7-fMZ?Da(hA zQkP`WD)3cl2zkCJeze$D3;Mg$F!`^DKmBT~&j&ox>#w%2N&Vl&4)T=tJ}8BC?D78k zWj*ky=&|A9Pgz-7>3z`Z&C@#MUh7EyNWM4U+iztjik@Y=zEm9QKS+%+Pdb!Nd~E1I z>Zscr>?y$Bf@VHg$h=4TZRUfqHXviJnGgEF`t4>uhb12Rzx6?`J@1nbbP!F>|N>8=niqWBJF zHO1S4u@UueSGd+uoSr&X7%}CH>In8tgljXZQ)Qd_cPTulqWf#c6yxX27WJn_6gAK7H0@`=eSvyf z;zG~FHnkkPF82$*62l)NJQd25`(qiVPfBg%?MsAn`&1PX-k*@7K@#WxTZA{uf3x#? zUboY!E?HEFNwapoPd;h-7K@Lo9}=FT9Ut-QdtKujukUNRuZ`pFH`;&qhU<7s@c4D- z3zeqbR76EYTIhtW2x5%;32(OVrGFFu5$(TmAIsz>{uRRaH|bYR*ZjJ_Ff)^zR_ohc z9Dm-K<_#}(!K{#fo2B zQZqGozUX)h^Ycp-60#if^dqj=H$yo&&gjI@sS#)7^aw|M^{O-HRy^K}bK;59 zL#Iv-6H4D=&WJ4m_K69CXx|P($HpH!J~Zw;Hahy*kx6HA==k^uHOPIx?`9v_v{NXA z_v*wp3n5?G=cD%y-NpW(XqzfmUz+#bDbjw;=sA=sRlk^^f~2R->;RX7JM3V z{|n*yX?V{OzIUPd_l>Z~v@n2pTj1Yl4wn|>tQ4RTkNF+XF~TxH2G3a$k9jThi{i}v z(ygiIRSaRiS)ekvjd*31ez~3Ta%n9Brg6FV2kMaIO zE?W3J!os649B)Y+H2O!3R0rLBB^HnGXXv*OC>rsAzZ#3j_Zl?b&)^@=|5=F#|H=|i z(ZTzk!13|(O~M&Nd^_Sq|~-J55f5$VE9?EMg1E}S%?6RCfH>&i6U=4gpDh@_ti;35pe9$8S^tqf zoWHm;qy_OpBVg^zhR~w^LS?+8GmiQ2=cPdlj$BCs9*t$%!Po`Hda>77Kac?azr};} p!TI3%Lo>JF+)aczX8VnPHr9X4)YD>c7mmNi6K#?mv4EJV{spTSXV?G$ literal 0 HcmV?d00001 diff --git a/tests/rpmbuild.at b/tests/rpmbuild.at index 02245d915b..bd23804518 100644 --- a/tests/rpmbuild.at +++ b/tests/rpmbuild.at @@ -1523,6 +1523,104 @@ rtld(GNU_HASH) []) RPMTEST_CLEANUP +RPMTEST_SETUP_RW([elf dependencies with fallback]) +AT_KEYWORDS([build]) + +RPMTEST_CHECK([ +runroot setfattr -n user.rpm_elf_so_version -v 1.0.0 /data/misc/libhello.so.1.0.0 +runroot chmod a-x /data/misc/libhello.so +runroot ${RPM_CONFIGDIR_PATH}/elfdeps -R --full-name-version-fallback /data/misc/libhello.so +runroot chmod a+x /data/misc/libhello.so +runroot ${RPM_CONFIGDIR_PATH}/elfdeps -R --full-name-version-fallback /data/misc/libhello.so +], +[0], +[libc.so.6(GLIBC_2.2.5)(64bit) +libc.so.6()(64bit) +rtld(GNU_HASH) +libc.so.6(GLIBC_2.2.5)(64bit) +libc.so.6()(64bit) +rtld(GNU_HASH) +], +[]) + +RPMTEST_CHECK([ +runroot setfattr -n user.rpm_elf_so_version -v 1.0.0 /data/misc/libhello.so.1.0.0 +runroot chmod a-x /data/misc/libhello.so.1.0.0 +runroot ${RPM_CONFIGDIR_PATH}/elfdeps -P --full-name-version-fallback /data/misc/libhello.so +runroot chmod a+x /data/misc/libhello.so.1.0.0 +runroot ${RPM_CONFIGDIR_PATH}/elfdeps -P --full-name-version-fallback /data/misc/libhello.so +], +[0], +[libhello.so()(64bit) = 1.0.0 +libhello.so()(64bit) = 1.0.0 +], +[]) + +RPMTEST_SKIP_IF([! $HAVE_GNU_DLFCN]) +RPMTEST_CHECK([ +runroot setfattr -n user.rpm_elf_so_version -v 1.0.0 /data/misc/libhello.so.1.0.0 +runroot chmod a-x /data/misc/helloexe +runroot env LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+${LD_LIBRARY_PATH}:}/data/misc ${RPM_CONFIGDIR_PATH}/elfdeps -R --full-name-version-fallback /data/misc/helloexe +runroot chmod a+x /data/misc/helloexe +runroot env LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+${LD_LIBRARY_PATH}:}/data/misc ${RPM_CONFIGDIR_PATH}/elfdeps -R --full-name-version-fallback /data/misc/helloexe +], +[0], +[libc.so.6(GLIBC_2.2.5)(64bit) +libhello.so()(64bit) >= 1.0.0 +libc.so.6()(64bit) +rtld(GNU_HASH) +], +[]) + +RPMTEST_SKIP_IF([! $HAVE_GNU_DLFCN]) +RPMTEST_CHECK([ +runroot setfattr -n user.rpm_elf_so_version -v 1.0.0 /data/misc/libhello.so.1.0.0 +runroot chmod a-x /data/misc/hellopie +runroot env LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+${LD_LIBRARY_PATH}:}/data/misc ${RPM_CONFIGDIR_PATH}/elfdeps -R --full-name-version-fallback /data/misc/hellopie +runroot chmod a+x /data/misc/hellopie +runroot env LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+${LD_LIBRARY_PATH}:}/data/misc ${RPM_CONFIGDIR_PATH}/elfdeps -R --full-name-version-fallback /data/misc/hellopie +], +[0], +[libc.so.6(GLIBC_2.2.5)(64bit) +libhello.so()(64bit) >= 1.0.0 +libc.so.6()(64bit) +rtld(GNU_HASH) +], +[]) + +RPMTEST_SKIP_IF([$HAVE_GNU_DLFCN]) +RPMTEST_CHECK([ +runroot setfattr -n user.rpm_elf_so_version -v 1.0.0 /data/misc/libhello.so.1.0.0 +runroot chmod a-x /data/misc/helloexe +runroot env LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+${LD_LIBRARY_PATH}:}/data/misc ${RPM_CONFIGDIR_PATH}/elfdeps -R --full-name-version-fallback /data/misc/helloexe +runroot chmod a+x /data/misc/helloexe +runroot env LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+${LD_LIBRARY_PATH}:}/data/misc ${RPM_CONFIGDIR_PATH}/elfdeps -R --full-name-version-fallback /data/misc/helloexe +], +[0], +[libc.so.6(GLIBC_2.2.5)(64bit) +libhello.so()(64bit) +libc.so.6()(64bit) +rtld(GNU_HASH) +], +[]) + +RPMTEST_SKIP_IF([$HAVE_GNU_DLFCN]) +RPMTEST_CHECK([ +runroot setfattr -n user.rpm_elf_so_version -v 1.0.0 /data/misc/libhello.so.1.0.0 +runroot chmod a-x /data/misc/hellopie +runroot env LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+${LD_LIBRARY_PATH}:}/data/misc ${RPM_CONFIGDIR_PATH}/elfdeps -R --full-name-version-fallback /data/misc/hellopie +runroot chmod a+x /data/misc/hellopie +runroot env LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+${LD_LIBRARY_PATH}:}/data/misc ${RPM_CONFIGDIR_PATH}/elfdeps -R --full-name-version-fallback /data/misc/hellopie +], +[0], +[libc.so.6(GLIBC_2.2.5)(64bit) +libhello.so()(64bit) +libc.so.6()(64bit) +rtld(GNU_HASH) +], +[]) +RPMTEST_CLEANUP + # ------------------------------ # Test spec query functionality RPMTEST_SETUP_RW([rpmspec query 1]) diff --git a/tools/elfdeps.cc b/tools/elfdeps.cc index 4a6821034c..7572f57104 100644 --- a/tools/elfdeps.cc +++ b/tools/elfdeps.cc @@ -6,6 +6,8 @@ #include #include +#include +#include #include #include #include @@ -13,8 +15,14 @@ #include #include +#include +#include + #include +#define XATTR_NAME_SOVERS "user.rpm_elf_so_version" + +int full_name_version_fallback = 0; int soname_only = 0; int fake_soname = 1; int filter_soname = 1; @@ -37,6 +45,120 @@ struct elfInfo { std::vector provides; }; +/* + * If filename is a symlink to a path that contains ".so.", + * return a copy of its _elf_version + */ +static char *getFullNameVer(const char *filename) +{ + const char *link_basename, *dest_basename; + char dest[PATH_MAX]; + int destsize = 0; + + destsize = readlink(filename, dest, PATH_MAX); + if (destsize == -1 && errno != EINVAL) { + exit(EXIT_FAILURE); + } + /* + * filename must be a symlink and the destination must be long enough + * to contain ".so." and a number. + */ + if ((destsize == -1 && errno == EINVAL) || + (destsize < sizeof(".so.1"))) { + return NULL; + } + + /* The base name of filename and dest must be different. */ + link_basename = strrchr(filename, '/'); + dest_basename = strrchr(dest, '/'); + if (link_basename == NULL) link_basename = (char *)filename ; else link_basename++; + if (dest_basename == NULL) dest_basename = dest ; else dest_basename++; + if (strcmp(link_basename, dest_basename) == 0) + return NULL; + if (strstr(link_basename, ".so") == NULL) + return NULL; + + { + char elf_version[64]; + ssize_t bytes_read; + + bytes_read = getxattr(filename, XATTR_NAME_SOVERS, + elf_version, sizeof(elf_version)); + if (bytes_read <= 0) + return NULL; + + return strdup(elf_version); + } +} + +/* + * Rather than re-implement path searching for shared objects, use + * dlmopen(). This will still perform initialization and finalization + * functions, which isn't necessarily safe, so do that in a separate + * process. + */ +static char *getFullNameVerFromShLink(const char *filename) +{ +#if defined(HAVE_DLMOPEN) && defined(HAVE_DLINFO) + char dest[PATH_MAX]; + int pipefd[2]; + pid_t cpid; + + if (pipe(pipefd) == -1) { + exit(EXIT_FAILURE); + } + cpid = fork(); + if (cpid == -1) { + exit(EXIT_FAILURE); + } + if (cpid == 0) { + void *dl_handle; + struct link_map *linkmap; + char *version = NULL; + int written = 0; + + close(pipefd[0]); + dl_handle = dlmopen(LM_ID_NEWLM, filename, RTLD_LAZY); + if (dl_handle == NULL) _exit(EXIT_FAILURE); + if (dlinfo(dl_handle, RTLD_DI_LINKMAP, &linkmap) == -1) + _exit(EXIT_FAILURE); + version = getFullNameVer(linkmap->l_name); + if (version) + written = write(pipefd[1], version, strlen(version)); + if (written < 0) + _exit(1); + close(pipefd[1]); + free(version); + dlclose(dl_handle); + _exit(0); + } else { + ssize_t len; + int wstatus; + + close(pipefd[1]); + dest[0] = 0; + while ((len = read(pipefd[0], dest, sizeof(dest))) == -1 + && errno == EINTR); + if (len > 0) dest[len] = 0; + close(pipefd[0]); + wait(&wstatus); + if (WIFSIGNALED(wstatus) || WEXITSTATUS(wstatus)) + exit(EXIT_FAILURE); + } + if (strlen(dest) > 0) + return strdup(dest); + else + return NULL; +#else + /* + * Without dlmopen and dlinfo, elfdeps cannot improve + * requirement lists, and will simply continue its prior + * behavior of unversioned dependencies. + */ + return NULL; +#endif +} + /* * Rough soname sanity filtering: all sane soname's dependencies need to * contain ".so", and normal linkable libraries start with "lib", @@ -90,6 +212,14 @@ static std::string mkmarker(GElf_Ehdr *ehdr) return marker; } +static int findSonameInDeps(std::vector & deps, const char *soname) +{ + for (auto & dep : deps) { + if (dep.compare(0, strlen(soname), soname) == 0) return 1; + } + return 0; +} + static void addDep(std::vector & deps, const std::string & dep) { deps.push_back(dep); @@ -97,12 +227,20 @@ static void addDep(std::vector & deps, const std::string & dep) static void addSoDep(std::vector & deps, const std::string & soname, - const std::string & ver, const std::string & marker) + const std::string & ver, const std::string & marker, + const std::string & compare_op, const std::string & fallback_ver) { if (skipSoname(soname)) return; - if (ver.empty() && marker.empty()) { + if (!compare_op.empty() && !fallback_ver.empty()) { + /* + * when versioned symbols aren't available, the full name version + * might be used to generate a minimum dependency version. + */ + auto dep = std::format("{}({}){} {} {}", soname, ver, marker, compare_op, fallback_ver); + addDep(deps, dep); + } else if (ver.empty() && marker.empty()) { addDep(deps, soname); } else { auto dep = std::format("{}({}){}", soname, ver, marker); @@ -141,10 +279,10 @@ static void processVerDef(Elf_Scn *scn, GElf_Shdr *shdr, elfInfo *ei) auxoffset += aux->vda_next; continue; } else if (!soname_only) { - addSoDep(ei->provides, soname, s, ei->marker); + addSoDep(ei->provides, soname, s, ei->marker, "", ""); } } - + } } } @@ -178,7 +316,7 @@ static void processVerNeed(Elf_Scn *scn, GElf_Shdr *shdr, elfInfo *ei) break; if (genRequires(ei) && !soname_only) { - addSoDep(ei->requires_, soname, s, ei->marker); + addSoDep(ei->requires_, soname, s, ei->marker, "", ""); } auxoffset += aux->vna_next; } @@ -219,8 +357,23 @@ static void processDynamic(Elf_Scn *scn, GElf_Shdr *shdr, elfInfo *ei) case DT_NEEDED: if (genRequires(ei)) { s = elf_strptr(ei->elf, shdr->sh_link, dyn->d_un.d_val); - if (s) - addSoDep(ei->requires_, s, "", ei->marker); + if (s) { + char *full_name_ver = NULL; + /* + * If soname matches an item already in the deps, then + * it had versioned symbols and doesn't require fallback. + */ + if (full_name_version_fallback && + !findSonameInDeps(ei->requires_, s)) { + full_name_ver = getFullNameVerFromShLink(s); + } + if (full_name_ver) { + addSoDep(ei->requires_, s, "", ei->marker, ">=", full_name_ver); + free(full_name_ver); + } else { + addSoDep(ei->requires_, s, "", ei->marker, "", ""); + } + } } break; } @@ -320,8 +473,18 @@ static int processFile(const char *fn, int dtype) const char *bn = strrchr(fn, '/'); ei->soname = bn ? bn + 1 : fn; } - if (ei->soname.empty() == false) - addSoDep(ei->provides, ei->soname, "", ei->marker); + if (ei->soname.empty() == false) { + char *full_name_ver = NULL; + if (full_name_version_fallback) { + full_name_ver = getFullNameVer(fn); + } + if (full_name_ver) { + addSoDep(ei->provides, ei->soname, "", ei->marker, "=", full_name_ver); + free(full_name_ver); + } else { + addSoDep(ei->provides, ei->soname, "", ei->marker, "", ""); + } + } } /* If requested and present, add dep for interpreter (ie dynamic linker) */ @@ -356,12 +519,13 @@ int main(int argc, char *argv[]) struct poptOption opts[] = { { "provides", 'P', POPT_ARG_VAL, &provides, -1, NULL, NULL }, { "requires", 'R', POPT_ARG_VAL, &requires_, -1, NULL, NULL }, + { "full-name-version-fallback", 0, POPT_ARG_VAL, &full_name_version_fallback, -1, NULL, NULL }, { "soname-only", 0, POPT_ARG_VAL, &soname_only, -1, NULL, NULL }, { "no-fake-soname", 0, POPT_ARG_VAL, &fake_soname, 0, NULL, NULL }, { "no-filter-soname", 0, POPT_ARG_VAL, &filter_soname, 0, NULL, NULL }, { "require-interp", 0, POPT_ARG_VAL, &require_interp, -1, NULL, NULL }, { "multifile", 'm', POPT_ARG_VAL, &multifile, -1, NULL, NULL }, - POPT_AUTOHELP + POPT_AUTOHELP POPT_TABLEEND }; From 5800f367c5de8a92c7d5534350f7a0e06199a4c6 Mon Sep 17 00:00:00 2001 From: Gordon Messmer Date: Wed, 4 Jun 2025 12:32:25 -0700 Subject: [PATCH 2/3] Start removing "full name" references in favor of xattr. --- macros.in | 4 ++-- tests/rpmbuild.at | 25 ++++++++++++------------- tools/elfdeps.cc | 27 ++++++++++++--------------- 3 files changed, 26 insertions(+), 30 deletions(-) diff --git a/macros.in b/macros.in index 116fc256bc..968f79bfa1 100644 --- a/macros.in +++ b/macros.in @@ -554,8 +554,8 @@ Supplements: (%{name} = %{version}-%{release} and langpacks-%{1})\ # # Generate minimum versions for ELF libraries that don't provide # versioned symbol? -#%_elf_provide_fallback_versions --full-name-version-fallback -#%_elf_require_fallback_versions --full-name-version-fallback +#%_elf_provide_fallback_versions --provide-version-fallback=%{_elf_so_version} +#%_elf_require_fallback_versions --require-version-fallback # # ELF libraries will usually inherit a version from their package, diff --git a/tests/rpmbuild.at b/tests/rpmbuild.at index bd23804518..efdf8b5fdb 100644 --- a/tests/rpmbuild.at +++ b/tests/rpmbuild.at @@ -1529,9 +1529,9 @@ AT_KEYWORDS([build]) RPMTEST_CHECK([ runroot setfattr -n user.rpm_elf_so_version -v 1.0.0 /data/misc/libhello.so.1.0.0 runroot chmod a-x /data/misc/libhello.so -runroot ${RPM_CONFIGDIR_PATH}/elfdeps -R --full-name-version-fallback /data/misc/libhello.so +runroot ${RPM_CONFIGDIR_PATH}/elfdeps -R --require-version-fallback /data/misc/libhello.so runroot chmod a+x /data/misc/libhello.so -runroot ${RPM_CONFIGDIR_PATH}/elfdeps -R --full-name-version-fallback /data/misc/libhello.so +runroot ${RPM_CONFIGDIR_PATH}/elfdeps -R --require-version-fallback /data/misc/libhello.so ], [0], [libc.so.6(GLIBC_2.2.5)(64bit) @@ -1544,11 +1544,10 @@ rtld(GNU_HASH) []) RPMTEST_CHECK([ -runroot setfattr -n user.rpm_elf_so_version -v 1.0.0 /data/misc/libhello.so.1.0.0 runroot chmod a-x /data/misc/libhello.so.1.0.0 -runroot ${RPM_CONFIGDIR_PATH}/elfdeps -P --full-name-version-fallback /data/misc/libhello.so +runroot ${RPM_CONFIGDIR_PATH}/elfdeps -P --provide-version-fallback=1.0.0 /data/misc/libhello.so runroot chmod a+x /data/misc/libhello.so.1.0.0 -runroot ${RPM_CONFIGDIR_PATH}/elfdeps -P --full-name-version-fallback /data/misc/libhello.so +runroot ${RPM_CONFIGDIR_PATH}/elfdeps -P --provide-version-fallback=1.0.0 /data/misc/libhello.so ], [0], [libhello.so()(64bit) = 1.0.0 @@ -1560,9 +1559,9 @@ RPMTEST_SKIP_IF([! $HAVE_GNU_DLFCN]) RPMTEST_CHECK([ runroot setfattr -n user.rpm_elf_so_version -v 1.0.0 /data/misc/libhello.so.1.0.0 runroot chmod a-x /data/misc/helloexe -runroot env LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+${LD_LIBRARY_PATH}:}/data/misc ${RPM_CONFIGDIR_PATH}/elfdeps -R --full-name-version-fallback /data/misc/helloexe +runroot env LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+${LD_LIBRARY_PATH}:}/data/misc ${RPM_CONFIGDIR_PATH}/elfdeps -R --require-version-fallback /data/misc/helloexe runroot chmod a+x /data/misc/helloexe -runroot env LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+${LD_LIBRARY_PATH}:}/data/misc ${RPM_CONFIGDIR_PATH}/elfdeps -R --full-name-version-fallback /data/misc/helloexe +runroot env LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+${LD_LIBRARY_PATH}:}/data/misc ${RPM_CONFIGDIR_PATH}/elfdeps -R --require-version-fallback /data/misc/helloexe ], [0], [libc.so.6(GLIBC_2.2.5)(64bit) @@ -1576,9 +1575,9 @@ RPMTEST_SKIP_IF([! $HAVE_GNU_DLFCN]) RPMTEST_CHECK([ runroot setfattr -n user.rpm_elf_so_version -v 1.0.0 /data/misc/libhello.so.1.0.0 runroot chmod a-x /data/misc/hellopie -runroot env LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+${LD_LIBRARY_PATH}:}/data/misc ${RPM_CONFIGDIR_PATH}/elfdeps -R --full-name-version-fallback /data/misc/hellopie +runroot env LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+${LD_LIBRARY_PATH}:}/data/misc ${RPM_CONFIGDIR_PATH}/elfdeps -R --require-version-fallback /data/misc/hellopie runroot chmod a+x /data/misc/hellopie -runroot env LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+${LD_LIBRARY_PATH}:}/data/misc ${RPM_CONFIGDIR_PATH}/elfdeps -R --full-name-version-fallback /data/misc/hellopie +runroot env LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+${LD_LIBRARY_PATH}:}/data/misc ${RPM_CONFIGDIR_PATH}/elfdeps -R --require-version-fallback /data/misc/hellopie ], [0], [libc.so.6(GLIBC_2.2.5)(64bit) @@ -1592,9 +1591,9 @@ RPMTEST_SKIP_IF([$HAVE_GNU_DLFCN]) RPMTEST_CHECK([ runroot setfattr -n user.rpm_elf_so_version -v 1.0.0 /data/misc/libhello.so.1.0.0 runroot chmod a-x /data/misc/helloexe -runroot env LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+${LD_LIBRARY_PATH}:}/data/misc ${RPM_CONFIGDIR_PATH}/elfdeps -R --full-name-version-fallback /data/misc/helloexe +runroot env LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+${LD_LIBRARY_PATH}:}/data/misc ${RPM_CONFIGDIR_PATH}/elfdeps -R --require-version-fallback /data/misc/helloexe runroot chmod a+x /data/misc/helloexe -runroot env LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+${LD_LIBRARY_PATH}:}/data/misc ${RPM_CONFIGDIR_PATH}/elfdeps -R --full-name-version-fallback /data/misc/helloexe +runroot env LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+${LD_LIBRARY_PATH}:}/data/misc ${RPM_CONFIGDIR_PATH}/elfdeps -R --require-version-fallback /data/misc/helloexe ], [0], [libc.so.6(GLIBC_2.2.5)(64bit) @@ -1608,9 +1607,9 @@ RPMTEST_SKIP_IF([$HAVE_GNU_DLFCN]) RPMTEST_CHECK([ runroot setfattr -n user.rpm_elf_so_version -v 1.0.0 /data/misc/libhello.so.1.0.0 runroot chmod a-x /data/misc/hellopie -runroot env LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+${LD_LIBRARY_PATH}:}/data/misc ${RPM_CONFIGDIR_PATH}/elfdeps -R --full-name-version-fallback /data/misc/hellopie +runroot env LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+${LD_LIBRARY_PATH}:}/data/misc ${RPM_CONFIGDIR_PATH}/elfdeps -R --require-version-fallback /data/misc/hellopie runroot chmod a+x /data/misc/hellopie -runroot env LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+${LD_LIBRARY_PATH}:}/data/misc ${RPM_CONFIGDIR_PATH}/elfdeps -R --full-name-version-fallback /data/misc/hellopie +runroot env LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+${LD_LIBRARY_PATH}:}/data/misc ${RPM_CONFIGDIR_PATH}/elfdeps -R --require-version-fallback /data/misc/hellopie ], [0], [libc.so.6(GLIBC_2.2.5)(64bit) diff --git a/tools/elfdeps.cc b/tools/elfdeps.cc index 7572f57104..638bab443c 100644 --- a/tools/elfdeps.cc +++ b/tools/elfdeps.cc @@ -22,7 +22,8 @@ #define XATTR_NAME_SOVERS "user.rpm_elf_so_version" -int full_name_version_fallback = 0; +char *provide_version_fallback = NULL; +int require_version_fallback = 0; int soname_only = 0; int fake_soname = 1; int filter_soname = 1; @@ -358,18 +359,18 @@ static void processDynamic(Elf_Scn *scn, GElf_Shdr *shdr, elfInfo *ei) if (genRequires(ei)) { s = elf_strptr(ei->elf, shdr->sh_link, dyn->d_un.d_val); if (s) { - char *full_name_ver = NULL; + char *require_ver = NULL; /* * If soname matches an item already in the deps, then * it had versioned symbols and doesn't require fallback. */ - if (full_name_version_fallback && + if (require_version_fallback && !findSonameInDeps(ei->requires_, s)) { - full_name_ver = getFullNameVerFromShLink(s); + require_ver = getFullNameVerFromShLink(s); } - if (full_name_ver) { - addSoDep(ei->requires_, s, "", ei->marker, ">=", full_name_ver); - free(full_name_ver); + if (require_ver) { + addSoDep(ei->requires_, s, "", ei->marker, ">=", require_ver); + free(require_ver); } else { addSoDep(ei->requires_, s, "", ei->marker, "", ""); } @@ -474,13 +475,8 @@ static int processFile(const char *fn, int dtype) ei->soname = bn ? bn + 1 : fn; } if (ei->soname.empty() == false) { - char *full_name_ver = NULL; - if (full_name_version_fallback) { - full_name_ver = getFullNameVer(fn); - } - if (full_name_ver) { - addSoDep(ei->provides, ei->soname, "", ei->marker, "=", full_name_ver); - free(full_name_ver); + if (provide_version_fallback) { + addSoDep(ei->provides, ei->soname, "", ei->marker, "=", provide_version_fallback); } else { addSoDep(ei->provides, ei->soname, "", ei->marker, "", ""); } @@ -519,7 +515,8 @@ int main(int argc, char *argv[]) struct poptOption opts[] = { { "provides", 'P', POPT_ARG_VAL, &provides, -1, NULL, NULL }, { "requires", 'R', POPT_ARG_VAL, &requires_, -1, NULL, NULL }, - { "full-name-version-fallback", 0, POPT_ARG_VAL, &full_name_version_fallback, -1, NULL, NULL }, + { "require-version-fallback", 0, POPT_ARG_VAL, &require_version_fallback, -1, NULL, NULL }, + { "provide-version-fallback", 0, POPT_ARG_STRING, &provide_version_fallback, 0, NULL, NULL }, { "soname-only", 0, POPT_ARG_VAL, &soname_only, -1, NULL, NULL }, { "no-fake-soname", 0, POPT_ARG_VAL, &fake_soname, 0, NULL, NULL }, { "no-filter-soname", 0, POPT_ARG_VAL, &filter_soname, 0, NULL, NULL }, From 570f18434f3e429c86520282bf23c3553643675f Mon Sep 17 00:00:00 2001 From: Gordon Messmer Date: Fri, 6 Jun 2025 20:45:30 -0700 Subject: [PATCH 3/3] Starting work on tracking xattr in rpm packages. --- build/files.cc | 35 ++++++++++++++++++++++++---- include/rpm/rpmarchive.h | 1 + include/rpm/rpmfi.h | 9 ++++++++ include/rpm/rpmfiles.h | 3 ++- include/rpm/rpmtag.h | 1 + lib/fsm.cc | 49 ++++++++++++++++++++++++++++++++++++++++ lib/rpmds.cc | 3 +++ lib/rpmfi.cc | 12 ++++++++++ tests/rpmgeneral.at | 1 + 9 files changed, 108 insertions(+), 6 deletions(-) diff --git a/build/files.cc b/build/files.cc index b019120c66..a046dd3bb9 100644 --- a/build/files.cc +++ b/build/files.cc @@ -108,6 +108,7 @@ struct FileListRec_s { rpmVerifyFlags verifyFlags; char *langs; /* XXX locales separated with | */ char *caps; + char *xattrs; bool operator < (const FileListRec_s & other) const { @@ -139,6 +140,7 @@ typedef struct FileEntry_s { ARGV_t langs; char *caps; + char *xattrs; /* these are only ever relevant for current entry */ unsigned devtype; @@ -173,6 +175,7 @@ typedef struct FileList_s { size_t buildRootLen; int processingFailed; int haveCaps; + int haveXattrs; int largeFiles; ARGV_t docDirs; rpmBuildPkgFlags pkgFlags; @@ -223,12 +226,16 @@ static void copyFileEntry(FileEntry src, FileEntry dest) if (src->caps != NULL) { dest->caps = xstrdup(src->caps); } + if (src->xattrs != NULL) { + dest->xattrs = xstrdup(src->xattrs); + } } static void FileEntryFree(FileEntry entry) { argvFree(entry->langs); free(entry->caps); + free(entry->xattrs); memset(entry, 0, sizeof(*entry)); } @@ -306,6 +313,7 @@ static VFA_t const verifyAttrs[] = { { "mode", RPMVERIFY_MODE }, { "rdev", RPMVERIFY_RDEV }, { "caps", RPMVERIFY_CAPS }, + { "xattrs", RPMVERIFY_XATTRS }, { NULL, 0 } }; @@ -1234,6 +1242,9 @@ static void genCpioListAndHeader(FileList fl, rpmSpec spec, Package pkg, int isS if (fl->haveCaps) { headerPutString(h, RPMTAG_FILECAPS, flp->caps); } + if (fl->haveXattrs) { + headerPutString(h, RPMTAG_FILEXATTRS, flp->xattrs); + } buf[0] = '\0'; if (S_ISREG(flp->fl_mode) && !(flp->flags & RPMFILE_GHOST)) @@ -1301,6 +1312,10 @@ static void genCpioListAndHeader(FileList fl, rpmSpec spec, Package pkg, int isS rpmlibNeedsFeature(pkg, "FileCaps", "4.6.1-1"); } + if (fl->haveXattrs) { + rpmlibNeedsFeature(pkg, "FileXattrs", "4.6.1-1"); + } + if (!isSrc && !rpmExpandNumeric("%{_noPayloadPrefix}")) (void) rpmlibNeedsFeature(pkg, "PayloadFilesHavePrefix", "4.0-1"); @@ -1328,6 +1343,7 @@ static void FileRecordsFree(FileRecords & files) free(rec.cpioPath); free(rec.langs); free(rec.caps); + free(rec.xattrs); } files.clear(); } @@ -1559,6 +1575,12 @@ static rpmRC addFile(FileList fl, const char * diskPath, flp->caps = xstrdup(""); } + if (fl->cur.xattrs) { + flp->xattrs = xstrdup(fl->cur.xattrs); + } else { + flp->xattrs = xstrdup(""); + } + flp->flags = fl->cur.attrFlags; flp->specdFlags = fl->cur.specdFlags; flp->verifyFlags = fl->cur.verifyFlags; @@ -1717,9 +1739,9 @@ static int generateElfSoVers(FileList fl) int i; FileListRec flp; - /* How are we supposed to create the build-id links? */ - char *elf_so_version_macro = rpmExpand("%{?_elf_so_version}", NULL); - if (*elf_so_version_macro == '\0') { + /* What ABI version will be recorded? */ + char *elf_so_version_macro = rpmExpand("user.rpm_elf_so_version=%{?_elf_so_version}", NULL); + if (elf_so_version_macro[sizeof("user.rpm_elf_so_version=")-1] == '\0') { rc = 1; rpmlog(RPMLOG_WARNING, _("_elf_so_version macro not set, skipping elf-version generation\n")); @@ -1756,8 +1778,9 @@ static int generateElfSoVers(FileList fl) if (elf != NULL && elf_kind (elf) == ELF_K_ELF && gelf_getehdr (elf, &ehdr) != NULL && (ehdr.e_type == ET_DYN)) { - fsetxattr(fd, XATTR_NAME_SOVERS, - elf_so_version_macro, strlen(elf_so_version_macro), 0); + flp->xattrs = xstrdup(elf_so_version_macro); + // fsetxattr(fd, XATTR_NAME_SOVERS, + // elf_so_version_macro, strlen(elf_so_version_macro), 0); } elf_end (elf); close (fd); @@ -2600,6 +2623,8 @@ static void addPackageFileList (struct FileList_s *fl, Package pkg, if (fl->cur.caps) fl->haveCaps = 1; + if (fl->cur.xattrs) + fl->haveXattrs = 1; } argvFree(fileNames); } diff --git a/include/rpm/rpmarchive.h b/include/rpm/rpmarchive.h index 5eafc2c9c0..e2b0bfd493 100644 --- a/include/rpm/rpmarchive.h +++ b/include/rpm/rpmarchive.h @@ -50,6 +50,7 @@ enum rpmfilesErrorCodes { RPMERR_LSETFCON_FAILED = -32786, RPMERR_SETCAP_FAILED = -32787, RPMERR_CLOSE_FAILED = -32788, + RPMERR_SETXATTR_FAILED = -32789, }; #ifdef __cplusplus diff --git a/include/rpm/rpmfi.h b/include/rpm/rpmfi.h index 957b8d9e6f..1edddd89bd 100644 --- a/include/rpm/rpmfi.h +++ b/include/rpm/rpmfi.h @@ -306,6 +306,15 @@ const char * rpmfiFGroup(rpmfi fi); */ const char * rpmfiFCaps(rpmfi fi); +/** \ingroup rpmfi + * Return textual representation of current file extended attributes + * from file info set iterator. + * @param fi file info set iterator + * @return file extended attribute description, "" for no attributes + * and NULL on invalid + */ +const char * rpmfiFXattrs(rpmfi fi); + /** \ingroup rpmfi * Return current file language(s) from file info set iterator. * @param fi file info set iterator diff --git a/include/rpm/rpmfiles.h b/include/rpm/rpmfiles.h index 46d23d3b43..cfd37f9caf 100644 --- a/include/rpm/rpmfiles.h +++ b/include/rpm/rpmfiles.h @@ -81,7 +81,8 @@ enum rpmVerifyAttrs_e { RPMVERIFY_MODE = (1 << 6), /*!< from %verify(mode) */ RPMVERIFY_RDEV = (1 << 7), /*!< from %verify(rdev) */ RPMVERIFY_CAPS = (1 << 8), /*!< from %verify(caps) */ - /* bits 9-14 unused, reserved for rpmVerifyAttrs */ + RPMVERIFY_XATTRS = (1 << 9), /*!< from %verify(xattrs) */ + /* bits 10-14 unused, reserved for rpmVerifyAttrs */ RPMVERIFY_CONTEXTS = (1 << 15), /*!< verify: from --nocontexts */ /* bits 16-22 used in rpmVerifyFlags */ /* bits 23-27 used in rpmQueryFlags */ diff --git a/include/rpm/rpmtag.h b/include/rpm/rpmtag.h index 73d6484c0b..ba5a3efc2a 100644 --- a/include/rpm/rpmtag.h +++ b/include/rpm/rpmtag.h @@ -400,6 +400,7 @@ typedef enum rpmTag_e { RPMTAG_PACKAGEDIGESTS = 5118, /* s[] */ RPMTAG_PACKAGEDIGESTALGOS = 5119, /* i[] */ RPMTAG_SOURCENEVR = 5120, /* s */ + RPMTAG_FILEXATTRS = 5121, /* s */ RPMTAG_FIRSTFREE_TAG /*!< internal */ } rpmTag; diff --git a/lib/fsm.cc b/lib/fsm.cc index 63580c25ad..419232eb16 100644 --- a/lib/fsm.cc +++ b/lib/fsm.cc @@ -12,6 +12,7 @@ #ifdef WITH_CAP #include #endif +#include #include #include @@ -108,6 +109,17 @@ static int cap_set_fileat(int dirfd, const char *path, cap_t fcaps) } #endif +static int xattr_set_fileat(int dirfd, const char *path, const char *name, const char *val, size_t size, int flags) +{ + int fd = -1; + int rc = fsmOpenat(&fd, dirfd, path, O_RDONLY|O_NOFOLLOW, 0); + if (!rc) { + rc = fsetxattr(fd, name, val, size, flags); + fsmClose(&fd); + } + return rc; +} + static int fsmSetFCaps(int fd, int dirfd, const char *path, const char *captxt) { int rc = 0; @@ -134,6 +146,39 @@ static int fsmSetFCaps(int fd, int dirfd, const char *path, const char *captxt) return rc; } +static int fsmSetFXattrs(int fd, int dirfd, const char *path, const char *xattrstxt) +{ + int rc = 0; + + if (xattrstxt && *xattrstxt != '\0') { + char *eqidx, *xattrname, *xattrval = NULL; + + xattrname = xstrdup(xattrstxt); + eqidx = (char *) strchr(xattrstxt, '='); + if (eqidx == NULL) + rc = RPMERR_SETXATTR_FAILED; + else { + *eqidx = '\0'; + xattrval = xstrdup(eqidx+1); + + if (fd >= 0) { + if (xattrval == NULL || fsetxattr(fd, xattrname, xattrval, strlen(xattrval), 0)) + rc = RPMERR_SETXATTR_FAILED; + } else { + if (xattrval == NULL || xattr_set_fileat(dirfd, path, xattrname, xattrval, strlen(xattrval), 0)) + rc = RPMERR_SETXATTR_FAILED; + } + } + if (_fsm_debug) { + rpmlog(RPMLOG_DEBUG, " %8s (%d - %d %s, %s) %s\n", __func__, + fd, dirfd, path, xattrstxt, (rc < 0 ? strerror(errno) : "")); + } + free(xattrname); + free(xattrval); + } + return rc; +} + static int fsmClose(int *wfdp) { int rc = 0; @@ -754,6 +799,10 @@ static int fsmSetmeta(int fd, int dirfd, const char *path, if (!rc && !nofcaps && S_ISREG(st->st_mode) && !getuid()) { rc = fsmSetFCaps(fd, dirfd, path, rpmfiFCaps(fi)); } + /* Set file extended attributes (if enabled) */ + if (!rc && S_ISREG(st->st_mode)) { + rc = fsmSetFXattrs(fd, dirfd, path, rpmfiFXattrs(fi)); + } if (!rc) { rc = fsmUtime(fd, dirfd, path, st->st_mode, rpmfiFMtime(fi)); } diff --git a/lib/rpmds.cc b/lib/rpmds.cc index 518231174e..a048961c4b 100644 --- a/lib/rpmds.cc +++ b/lib/rpmds.cc @@ -1085,6 +1085,9 @@ static const struct rpmlibProvides_s rpmlibProvides[] = { ( RPMSENSE_EQUAL), N_("support for POSIX.1e file capabilities") }, #endif + { "rpmlib(FileXattrs)", "4.6.1-1", + ( RPMSENSE_EQUAL), + N_("support for file extended attributes.") }, { "rpmlib(ScriptletExpansion)", "4.9.0-1", ( RPMSENSE_EQUAL), N_("package scriptlets can be expanded at install time.") }, diff --git a/lib/rpmfi.cc b/lib/rpmfi.cc index d5d7bde6f0..7799aeaf90 100644 --- a/lib/rpmfi.cc +++ b/lib/rpmfi.cc @@ -124,6 +124,8 @@ struct rpmfiles_s { rpm_loff_t * replacedSizes; /*!< (TR_ADDED) */ int magic; std::atomic_int nrefs; /*!< Reference count. */ + + char ** fxattrs; /*!< File extended attribute strings (header) */ }; static int indexSane(rpmtd xd, rpmtd yd, rpmtd zd); @@ -785,6 +787,15 @@ const char * rpmfilesFCaps(rpmfiles fi, int ix) return fcaps; } +const char * rpmfilesFXattrs(rpmfiles fi, int ix) +{ + const char *fxattrs = NULL; + if (fi != NULL && ix >= 0 && ix < rpmfilesFC(fi)) { + fxattrs = fi->fxattrs ? fi->fxattrs[ix] : ""; + } + return fxattrs; +} + const char * rpmfilesFLangs(rpmfiles fi, int ix) { const char *flangs = NULL; @@ -1874,6 +1885,7 @@ RPMFI_ITERFUNC(const char *, FLink, i) RPMFI_ITERFUNC(const char *, FUser, i) RPMFI_ITERFUNC(const char *, FGroup, i) RPMFI_ITERFUNC(const char *, FCaps, i) +RPMFI_ITERFUNC(const char *, FXattrs, i) RPMFI_ITERFUNC(const char *, FLangs, i) RPMFI_ITERFUNC(const char *, FClass, i) RPMFI_ITERFUNC(const char *, FMime, i) diff --git a/tests/rpmgeneral.at b/tests/rpmgeneral.at index 88d9f1bc4a..5308f56840 100644 --- a/tests/rpmgeneral.at +++ b/tests/rpmgeneral.at @@ -200,6 +200,7 @@ FILETRIGGERTYPE FILETRIGGERVERSION FILEUSERNAME FILEVERIFYFLAGS +FILEXATTRS FSCONTEXTS GIF GROUP