Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

libpkg: allow filtering provided shlibs #2422

Merged
merged 5 commits into from
Feb 24, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 46 additions & 3 deletions libpkg/pkg_abi.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "private/binfmt.h"
#include "private/event.h"
#include "private/pkg.h"
#include "private/utils.h"
#include "xmalloc.h"

#define _PATH_UNAME "/usr/bin/uname"
Expand Down Expand Up @@ -448,7 +449,8 @@ pkg_analyse_files(struct pkgdb *db __unused, struct pkg *pkg, const char *stage)
bool failures = false;

int (*pkg_analyse_init)(const char *stage);
int (*pkg_analyse)(const bool developer_mode, struct pkg *pkg, const char *fpath);
int (*pkg_analyse)(const bool developer_mode, struct pkg *pkg,
const char *fpath, char **provided, enum pkg_shlib_flags *flags);
int (*pkg_analyse_close)();

if (0 == strncmp(pkg->abi, "Darwin", 6)) {
Expand Down Expand Up @@ -479,25 +481,64 @@ pkg_analyse_files(struct pkgdb *db __unused, struct pkg *pkg, const char *stage)
pkg->flags &= ~(PKG_CONTAINS_ELF_OBJECTS |
PKG_CONTAINS_STATIC_LIBS | PKG_CONTAINS_LA);

/* shlibs that are provided by files in the package but not matched by
SHLIB_PROVIDE_PATHS_* are still used to filter the shlibs
required by the package */
stringlist_t internal_provided = tll_init();

while (pkg_files(pkg, &file) == EPKG_OK) {
if (stage != NULL)
snprintf(fpath, sizeof(fpath), "%s/%s", stage,
file->path);
else
strlcpy(fpath, file->path, sizeof(fpath));

ret = pkg_analyse(ctx.developer_mode, pkg, fpath);
char *provided = NULL;
enum pkg_shlib_flags provided_flags = PKG_SHLIB_FLAGS_NONE;
ret = pkg_analyse(ctx.developer_mode, pkg, fpath, &provided, &provided_flags);
if (EPKG_WARN == ret) {
failures = true;
}

if (provided != NULL) {
const ucl_object_t *paths = NULL;

switch (provided_flags) {
case PKG_SHLIB_FLAGS_NONE:
paths = pkg_config_get("SHLIB_PROVIDE_PATHS_NATIVE");
break;
case PKG_SHLIB_FLAGS_COMPAT_32:
paths = pkg_config_get("SHLIB_PROVIDE_PATHS_COMPAT_32");
break;
case PKG_SHLIB_FLAGS_COMPAT_LINUX:
paths = pkg_config_get("SHLIB_PROVIDE_PATHS_COMPAT_LINUX");
break;
case (PKG_SHLIB_FLAGS_COMPAT_32 | PKG_SHLIB_FLAGS_COMPAT_LINUX):
paths = pkg_config_get("SHLIB_PROVIDE_PATHS_COMPAT_LINUX_32");
break;
default:
assert(0);
}
assert(paths != NULL);

/* If the corresponding PATHS option isn't set (i.e. an empty ucl array)
don't do any filtering for backwards compatibility. */
if (ucl_array_size(paths) == 0 || pkg_match_paths_list(paths, file->path)) {
pkg_addshlib_provided(pkg, provided, provided_flags);
free(provided);
} else {
tll_push_back(internal_provided, provided);
}
}
}

/*
* Do not depend on libraries that a package provides itself
*/
tll_foreach(pkg->shlibs_required, s)
{
if (stringlist_contains(&pkg->shlibs_provided, s->item)) {
if (stringlist_contains(&pkg->shlibs_provided, s->item) ||
stringlist_contains(&internal_provided, s->item)) {
pkg_debug(2,
"remove %s from required shlibs as the "
"package %s provides this library itself",
Expand All @@ -521,6 +562,8 @@ pkg_analyse_files(struct pkgdb *db __unused, struct pkg *pkg, const char *stage)
}
}

tll_free_and_free(internal_provided, free);

/*
* if the package is not supposed to provide share libraries then
* drop the provided one
Expand Down
20 changes: 16 additions & 4 deletions libpkg/pkg_abi_macho.c
Original file line number Diff line number Diff line change
Expand Up @@ -301,8 +301,12 @@ pkg_macho_abi_from_fd(int fd, struct pkg_abi *abi, enum pkg_arch arch_hint)
}

static int
analyse_macho(int fd, struct pkg *pkg)
analyse_macho(int fd, struct pkg *pkg, char **provided,
enum pkg_shlib_flags *provided_flags)
{
assert(*provided == NULL);
assert(*provided_flags == PKG_SHLIB_FLAGS_NONE);

ssize_t x;
pkg_error_t ret = EPKG_END;

Expand Down Expand Up @@ -381,7 +385,11 @@ analyse_macho(int fd, struct pkg *pkg)
xasprintf(&lib_with_version, "%s-%"PRIuFAST16".%"PRIuFAST16, basename, dylib->current_version.major, dylib->current_version.minor);
}
if (LC_ID_DYLIB == loadcmd) {
pkg_addshlib_provided(pkg, lib_with_version, PKG_SHLIB_FLAGS_NONE);
if (*provided != NULL) {
pkg_emit_error("malformed Macho-O file has multiple LC_ID_DYLIB entries");
goto cleanup;
}
*provided = xstrdup(lib_with_version);
} else {
pkg_addshlib_required(pkg, lib_with_version, PKG_SHLIB_FLAGS_NONE);
}
Expand Down Expand Up @@ -416,8 +424,12 @@ pkg_analyse_init_macho(__unused const char *stage)
}

int
pkg_analyse_macho(const bool developer_mode, struct pkg *pkg, const char *fpath)
pkg_analyse_macho(const bool developer_mode, struct pkg *pkg,
const char *fpath, char **provided, enum pkg_shlib_flags *provided_flags)
{
assert(*provided == NULL);
assert(*provided_flags == PKG_SHLIB_FLAGS_NONE);

int ret = EPKG_OK;
pkg_debug(1, "Analysing Mach-O %s", fpath);

Expand All @@ -428,7 +440,7 @@ pkg_analyse_macho(const bool developer_mode, struct pkg *pkg, const char *fpath)
// Be consistent with analyse_elf and return no error if fpath cannot be opened
return ret;
} else {
ret = analyse_macho(fd, pkg);
ret = analyse_macho(fd, pkg, provided, provided_flags);
if (-1 == close(fd)) {
pkg_emit_errno("close_pkg_analyse_macho", fpath);
ret = EPKG_FATAL;
Expand Down
20 changes: 20 additions & 0 deletions libpkg/pkg_config.c
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,26 @@ static struct config_entry c[] = {
"FILES_IGNORE_REGEX",
NULL,
},
{
PKG_ARRAY,
"SHLIB_PROVIDE_PATHS_NATIVE",
NULL,
},
{
PKG_ARRAY,
"SHLIB_PROVIDE_PATHS_COMPAT_32",
NULL,
},
{
PKG_ARRAY,
"SHLIB_PROVIDE_PATHS_COMPAT_LINUX",
NULL,
},
{
PKG_ARRAY,
"SHLIB_PROVIDE_PATHS_COMPAT_LINUX_32",
NULL,
},
{
PKG_ARRAY,
"SHLIB_REQUIRE_IGNORE_GLOB",
Expand Down
22 changes: 18 additions & 4 deletions libpkg/pkg_elf.c
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,12 @@ typedef Elf32_Nhdr Elf_Note;
#endif

static int
analyse_elf(struct pkg *pkg, const char *fpath)
analyse_elf(struct pkg *pkg, const char *fpath, char **provided,
enum pkg_shlib_flags *provided_flags)
{
assert(*provided == NULL);
assert(*provided_flags == PKG_SHLIB_FLAGS_NONE);

int ret = EPKG_OK;

pkg_debug(1, "analysing elf %s", fpath);
Expand Down Expand Up @@ -232,7 +236,13 @@ analyse_elf(struct pkg *pkg, const char *fpath)
}

if (dyn->d_tag == DT_SONAME) {
pkg_addshlib_provided(pkg, shlib, flags);
if (*provided != NULL) {
pkg_emit_error("malformed ELF file %s has "
"multiple DT_SONAME entries", fpath);
goto cleanup;
}
*provided = xstrdup(shlib);
*provided_flags = flags;
} else if (dyn->d_tag == DT_NEEDED) {
/*
* some packages record fullpath to a lib
Expand Down Expand Up @@ -659,9 +669,13 @@ int pkg_analyse_init_elf(__unused const char* stage) {
return (EPKG_OK);
}

int pkg_analyse_elf(const bool developer_mode, struct pkg *pkg, const char *fpath)
int pkg_analyse_elf(const bool developer_mode, struct pkg *pkg,
const char *fpath, char **provided, enum pkg_shlib_flags *provided_flags)
{
int ret = analyse_elf(pkg, fpath);
assert(*provided == NULL);
assert(*provided_flags == PKG_SHLIB_FLAGS_NONE);

int ret = analyse_elf(pkg, fpath, provided, provided_flags);
if (developer_mode) {
if (ret != EPKG_OK && ret != EPKG_END) {
return EPKG_WARN;
Expand Down
6 changes: 4 additions & 2 deletions libpkg/private/binfmt.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ enum pkg_provide_flags {

int pkg_elf_abi_from_fd(int fd, struct pkg_abi *abi);
int pkg_analyse_init_elf(const char* stage);
int pkg_analyse_elf(const bool developer_mode, struct pkg *pkg, const char *fpath);
int pkg_analyse_elf(const bool developer_mode, struct pkg *pkg,
const char *fpath, char **provided, enum pkg_shlib_flags *provided_flags);
int pkg_analyse_close_elf();

int pkg_macho_abi_from_fd(int fd, struct pkg_abi *abi, enum pkg_arch arch_hint);
int pkg_analyse_init_macho(const char* stage);
int pkg_analyse_macho(const bool developer_mode, struct pkg *pkg, const char *fpath);
int pkg_analyse_macho(const bool developer_mode, struct pkg *pkg,
const char *fpath, char **provided, enum pkg_shlib_flags *provided_flags);
int pkg_analyse_close_macho();
1 change: 1 addition & 0 deletions libpkg/private/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ bool mkdirat_p(int fd, const char *path);
int get_socketpair(int *);
int checkflags(const char *mode, int *optr);
bool match_ucl_lists(const char *buffer, const ucl_object_t *globs, const ucl_object_t *regexes);
bool pkg_match_paths_list(const ucl_object_t *paths, const char *file);
char *get_dirname(char *dir);
char *rtrimspace(char *buf);
void hidden_tempfile(char *buf, int buflen, const char *path);
Expand Down
69 changes: 69 additions & 0 deletions libpkg/utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,75 @@ match_ucl_lists(const char *buf, const ucl_object_t *globs, const ucl_object_t *
return (false);
}

/* Check if two absolute directory paths are equal, collapsing consecutive
* path separators and ignoring trailing path separators. */
static bool
dir_paths_equal(const char *a, const char *b)
{
assert(a != NULL);
assert(b != NULL);
assert(*a == '/');
assert(*b == '/');

while (*a == *b) {
if (*a == '\0') {
return (true);
}

/* Skip over consecutive path separators */
if (*a == '/') {
while (*a == '/') a++;
while (*b == '/') b++;
} else {
a++;
b++;
}
}

/* There may be trailing path separators on one path but not the other */
while (*a == '/') a++;
while (*b == '/') b++;

return (*a == *b);
}

/*
* Given a ucl list of directory paths, check if the file is in one of the
* directories in the list (subdirectories not included).
*
* Asserts that file is an absolute path that does not end in /. */
bool
pkg_match_paths_list(const ucl_object_t *paths, const char *file)
{
assert(file != NULL);
assert(file[0] == '/');

char *copy = xstrdup(file);
char *final_slash = strrchr(copy, '/');
assert(final_slash != NULL);
assert(*(final_slash + 1) != '\0');
if (final_slash == copy) {
*(final_slash + 1) = '\0';
} else {
*final_slash = '\0';
}
const char *dirname = copy;

bool found = false;
const ucl_object_t *cur;
ucl_object_iter_t it = NULL;
while ((cur = ucl_object_iterate(paths, &it, true))) {
if (dir_paths_equal(dirname, ucl_object_tostring(cur))) {
found = true;
break;
}
}

free(copy);

return (found);
}

int
pkg_mkdirs(const char *_path)
{
Expand Down
13 changes: 11 additions & 2 deletions tests/frontend/create-parsebin.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ genmanifest() {
local PKG_SHA256=""
local NL="
"
local hide_provided="$1"
shift

bin_meta "$1"
while [ -n "$1" ]; do
Expand All @@ -29,6 +31,10 @@ genmanifest() {
shift
done

if [ -n "${hide_provided}" ]; then
Xshlibs_provided=""
fi

cat << EOF > ${PKG_NAME}.manifest
name: ${PKG_NAME}
origin: ${PKG_NAME}
Expand Down Expand Up @@ -94,15 +100,17 @@ EOF
do_check() {
local PKG_NAME=$1
local file1=$(atf_get_srcdir)/$2
local hide_provided=$3

genmanifest ${PKG_NAME} ${file1}
genmanifest ${PKG_NAME} "${hide_provided}" ${file1}

# cat ${PKG_NAME}.manifest
atf_check \
-o empty \
-e empty \
-s exit:0 \
pkg -o IGNORE_OSMAJOR=1 -o ABI_FILE=${file1} create -M ./${PKG_NAME}.manifest -r ${TMPDIR}
pkg -o IGNORE_OSMAJOR=1 -o ABI_FILE=${file1} $hide_provided \
create -M ./${PKG_NAME}.manifest -r ${TMPDIR}

# cat ${PKG_NAME}.expected
atf_check \
Expand All @@ -122,5 +130,6 @@ create_from_bin_body() {
macosfatlib.bin "macosfatlib.bin#x86_64" "macosfatlib.bin#aarch64"
do
do_check testbin $bin
do_check testbin $bin "-o SHLIB_PROVIDE_PATHS_NATIVE=/does/not/exist"
done
}
Loading