From c907b8ec08b06a4a256fd12b79b4bcf5088fbb19 Mon Sep 17 00:00:00 2001 From: Nathan Wagner Date: Tue, 13 Nov 2018 04:51:51 +0000 Subject: [PATCH] add zpm-search to look for packages and libraries --- Makefile | 5 +- lib/dbquery.c | 10 +- lib/findpkg.c | 96 ++++++++- lib/jsw/jsw_atree.h | 8 +- lib/jsw/jsw_hlib.c | 7 +- lib/vercmp.c | 10 + lib/zpm.c | 8 + zpm-search.c | 516 ++++++++++++++++++++++++++++++++++++++++++++ zpm.h | 5 +- 9 files changed, 657 insertions(+), 8 deletions(-) create mode 100644 zpm-search.c diff --git a/Makefile b/Makefile index d018fd3..e845acc 100644 --- a/Makefile +++ b/Makefile @@ -29,7 +29,7 @@ curdir=$(shell pwd) ZPKGBIN=zpm-addfile zpm-extract zpm-init zpm-vercmp zpm-stat zpm-hash \ zpm-findpkg zpm-shell zpm-soneed zpm-foreach-path zpm-parse \ zpm-script zpm-soname zpm-syncfs zpm-packagehash zpm-verify \ - zpm-elftype zpm-quote zpm-note + zpm-elftype zpm-quote zpm-note zpm-search SCRIPTS=zpm zpm-install zpm-merge zpm-list zpm-preserve zpm-test zpm-log \ zpm-contents zpm-uninstall zpm-pathmod zpm-rmpackage zpm-newpackage \ @@ -143,6 +143,9 @@ zpm-foreach-path: zpm-foreach-path.o libzpm.a sqlite/sqlite3.h zpm-findpkg: zpm-findpkg.o libzpm.a $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< -lzpm -lelf +zpm-search: zpm-search.o libzpm.a + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< -lzpm -lelf + zpm-note: zpm-note.o libzpm.a $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< -lzpm -lelf diff --git a/lib/dbquery.c b/lib/dbquery.c index cba9537..1113164 100644 --- a/lib/dbquery.c +++ b/lib/dbquery.c @@ -35,6 +35,7 @@ sqlite3_stmt *zpm_dbqueryv(struct zpm *zpm, char *query, va_list args) { rv = sqlite3_prepare_v2(db, sql, strlen(sql), &st, NULL); if (rv != SQLITE_OK) { SQLERROR(sqlite3_errmsg(db)); + fprintf(stderr, "sql (%d): %s\n", rv, sql); zpm->error = rv; return 0; } @@ -94,11 +95,18 @@ char *zpm_db_string(struct zpm *zpm, char *query, ...) { rv = sqlite3_step(st); - if (rv == SQLITE_ROW) { + switch (rv) { + case SQLITE_ROW: result = (char *)sqlite3_column_text(st, 0); if (result) { result = strdup(result); } + break; + case SQLITE_DONE: break; + default: + zpm->error = 1; + zpm_seterror(zpm, "db error: %s", sqlite3_errstr(rv)); + break; } sqlite3_finalize(st); diff --git a/lib/findpkg.c b/lib/findpkg.c index cbf7004..6ea843c 100644 --- a/lib/findpkg.c +++ b/lib/findpkg.c @@ -8,6 +8,7 @@ #include "zpm.h" #include "sqlite3.h" +#include "lib/jsw/jsw_atree.h" #define DMARK fprintf(stderr, "mark %s %s:%d\n", __FILE__, __func__, __LINE__) @@ -34,11 +35,102 @@ void zpm_sqlite_error(struct zpm *zpm) { zpm->errmsg = strdup((const char *)sqlite3_errmsg(zpm->db)); } +char *zpm_findlib(struct zpm *zpm, char *soname, char *where) { + char *select = "select pkgid, package, version, release from packagefiles_status join elflibraries on file = hash"; + char *group = "order by package, version collate vercmp desc, cast(release as integer)"; + sqlite3_str *sql; + char *query, *pkgid = 0; + + /* null pkgstr find "best" package + * best is shortest complete package if any are complete + * shortest incomplete if any are incomplete + * where more than one version is in those sets, best is + * latest as determined by vercmp + */ + + sql = sqlite3_str_new(zpm->db); + + sqlite3_str_appendall(sql, select); + sqlite3_str_appendf(sql, " where soname = %Q", soname); + + if (where) { + sqlite3_str_appendall(sql, " and "); + sqlite3_str_appendall(sql, where); + } + + sqlite3_str_appendall(sql, " "); + sqlite3_str_appendall(sql, group); + sqlite3_str_appendall(sql, " limit 1;"); + + if (sqlite3_str_errcode(sql)) { + zpm->error = 1; + sqlite3_free(sqlite3_str_finish(sql)); + return 0; + } + + query = sqlite3_str_finish(sql); + + pkgid = zpm_db_string(zpm, query);; + + sqlite3_free(query); + + return pkgid; +} + +int zpm_libraries_needed(struct zpm *zpm, char *pkgid, jsw_atree_t *list) { + char *pkglibsneeded = "\ + with pkglibs as (\ + select distinct EN.needed as soname, PF.pkgid\ + from elfneeded EN\ + join packagefiles_pkgid PF on PF.hash = EN.file\ + ),\ + pkgprovides as (\ + select distinct EL.soname, PF.pkgid\ + from elflibraries EL\ + join packagefiles_pkgid PF on PF.hash = EL.file\ + )\ + select distinct PL.soname, PP.soname is not null as selfsatisfied\ + from pkglibs PL\ + left join pkgprovides PP on PL.pkgid = PP.pkgid and PL.soname = PP.soname\ + where PL.pkgid = %Q\ + "; + + sqlite3_stmt *st; + int count = 0; + int rv; + + st = zpm_dbquery(zpm, pkglibsneeded, pkgid); + + while ((rv = sqlite3_step(st)) == SQLITE_ROW) { + char *soname; + int self; + + soname = (char *)sqlite3_column_text(st, 0); + /* TODO check and skip self sats */ + self = sqlite3_column_int(st,1); + if (self) { + continue; + } + count++; + + /* if it's in needed, we've already looked at this one */ + if (list && !jsw_afind(list, soname)) { + jsw_ainsert(list, soname); + } + } + + sqlite3_finalize(st); + return count; +} + char *zpm_findpkg(struct zpm *zpm, char *pkgstr, char *where) { char *select = "select pkgid, package, version, release from packages_pkgid"; +#if 0 char *group = "group by package having max( version||'-'||release collate vercmp) order by length(package), package, version||'-'||release collate vercmp"; - -// char *order = "order by package, version collate vercmp, cast(release as integer)"; +#else + char *group = "order by package, version collate vercmp desc, cast(release as integer)"; + //group = "order by package, version , cast(release as integer)"; +#endif sqlite3_str *sql; char *query, *pkgid = 0; diff --git a/lib/jsw/jsw_atree.h b/lib/jsw/jsw_atree.h index 54e4fd8..f0fe7d9 100644 --- a/lib/jsw/jsw_atree.h +++ b/lib/jsw/jsw_atree.h @@ -27,12 +27,18 @@ typedef struct jsw_atree jsw_atree_t; typedef struct jsw_atrav jsw_atrav_t; /* User-defined item handling */ +#if 0 typedef int (*cmp_f) ( const void *p1, const void *p2 ); +#endif typedef void *(*dup_f) ( void *p ); typedef void (*rel_f) ( void *p ); /* Andersson tree functions */ -jsw_atree_t *jsw_anew ( cmp_f cmp, dup_f dup, rel_f rel ); +jsw_atree_t *jsw_anew ( + int (cmp)(const void *, const void *), + void *(dup)(void *), + void (rel)(void *) + ); void jsw_adelete ( jsw_atree_t *tree ); void *jsw_afind ( jsw_atree_t *tree, void *data ); int jsw_ainsert ( jsw_atree_t *tree, void *data ); diff --git a/lib/jsw/jsw_hlib.c b/lib/jsw/jsw_hlib.c index 4cd9f8f..a431405 100644 --- a/lib/jsw/jsw_hlib.c +++ b/lib/jsw/jsw_hlib.c @@ -8,6 +8,7 @@ */ #include #include +#include #include "jsw_hlib.h" @@ -206,8 +207,9 @@ int jsw_hinsert ( jsw_hash_t *htab, void *key, void *item ) jsw_node_t *new_item; /* Disallow duplicate keys */ - if ( jsw_hfind ( htab, key ) != NULL ) + if ( jsw_hfind ( htab, key ) != NULL ) { return 0; + } /* Attempt to create a new item */ dupkey = htab->keydup ( key ); @@ -215,8 +217,9 @@ int jsw_hinsert ( jsw_hash_t *htab, void *key, void *item ) new_item = new_node ( dupkey, dupitem, NULL ); - if ( new_item == NULL ) + if ( new_item == NULL ) { return 0; + } /* Create a chain if the bucket is empty */ if ( htab->table[h] == NULL ) { diff --git a/lib/vercmp.c b/lib/vercmp.c index e6a415c..cfc7edd 100644 --- a/lib/vercmp.c +++ b/lib/vercmp.c @@ -101,6 +101,16 @@ int zpm_vercmp(const char *vsa, const char *vsb) { int an, bn; int cmp; + if (vsa && !vsb) { + return 1; + } + if (vsb && !vsa) { + return -1; + } + if (!vsa && !vsb) { + return 0; + } + init_ver(&a, vsa); init_ver(&b, vsb); do { diff --git a/lib/zpm.c b/lib/zpm.c index 5511841..3f1cb2b 100644 --- a/lib/zpm.c +++ b/lib/zpm.c @@ -266,6 +266,14 @@ int zpm_open(struct zpm *zpm, char *path) { zpm_clearmem(zpm); + if (!path) { + path = getenv("ZPMDB"); + } + + if (!path) { + path = "/var/lib/zpm/local.db"; + } + rc = sqlite3_open_v2(path, &db, SQLITE_OPEN_READWRITE, NULL); if (rc) { SQLERROR(sqlite3_errmsg(db)); diff --git a/zpm-search.c b/zpm-search.c new file mode 100644 index 0000000..ff6d730 --- /dev/null +++ b/zpm-search.c @@ -0,0 +1,516 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include + +#include + +#include "zpm.h" +#include "lib/jsw/jsw_hlib.h" +#include "lib/jsw/jsw_atree.h" + +/* + * find packages, and lib deps + */ + +struct pkgloc { + char *id; + char *file; + int info; +}; + +char *checkfile(char *pkgstr, char *path) { + struct zpm pkgfile; + char *pkgid = 0; + + if (!zpm_open(&pkgfile, path)) { + return NULL; + } + + pkgid = zpm_findpkg(&pkgfile, pkgstr, "hash is not null"); + zpm_close(&pkgfile); + + return pkgid; +} + +char *checkfileforlib(char *soname, char *path) { + struct zpm pkgfile; + char *pkgid = 0; + + if (!zpm_open(&pkgfile, path)) { + return NULL; + } + + pkgid = zpm_findlib(&pkgfile, soname, "hash is not null"); + if (pkgfile.error) { + fprintf(stderr, "sql error: %s\n", pkgfile.errmsg); + } + zpm_close(&pkgfile); + + return pkgid; +} + +int find_lib(char *soname, struct pkgloc *pkg) { + char *latest = 0; + char *installed = 0, *found = 0; + char *pkgfile = 0; + int rv; + struct zpm localdb; + + if (zpm_open(&localdb, NULL)) { + installed = zpm_findlib(&localdb, soname, "status = 'installed'"); + zpm_close(&localdb); + + if (installed) { + /* we're done, the lib is installed */ + return 2; + } + } else { + fprintf(stderr, "can't open localdb\n"); + } + + glob_t repos; + repos.gl_offs = 0; + rv = glob("/var/lib/zpm/repos/*.repo", 0, NULL, &repos); + switch (rv) { + case GLOB_NOSPACE: + fprintf(stderr, "glob no mem\n"); + exit(EXIT_FAILURE); break; + case GLOB_ABORTED: + fprintf(stderr, "glob abort\n"); + exit(EXIT_FAILURE); break; + case GLOB_NOMATCH: + break; + case 0: + break; + default: + break; + } + + unsigned i; + for (i = 0; i < repos.gl_pathc; i++) { + found = checkfileforlib(soname, repos.gl_pathv[i]); + if (found) { + rv = zpm_vercmp(found, latest); + if (rv == 1) { + latest = found; + free(pkgfile); + pkgfile = strdup(repos.gl_pathv[i]); + } + } + } + + globfree(&repos); + repos.gl_offs = 0; + + char *pkgdir = "/home/zoranix/zrepo/packages"; + char *pkgglob = 0; + + size_t globlen = strlen(pkgdir)+7; + pkgglob = malloc(globlen+1); + snprintf(pkgglob, globlen, "%s/*.zpm", pkgdir); + rv = glob(pkgglob, 0, NULL, &repos); + free(pkgglob); + + switch (rv) { + case GLOB_NOSPACE: + exit(EXIT_FAILURE); break; + case GLOB_ABORTED: + exit(EXIT_FAILURE); break; + case GLOB_NOMATCH: break; + case 0: + break; + default: + break; + } + + for (i = 0; i < repos.gl_pathc; i++) { + found = checkfileforlib(soname, repos.gl_pathv[i]); + if (found) { + rv = zpm_vercmp(found, latest); + if (rv == 1) { + latest = found; + free(pkgfile); + pkgfile = strdup(repos.gl_pathv[i]); + } + } + } + + if (latest && pkgfile) { + pkg->id = strdup(latest); + pkg->file = pkgfile; + return 1; + } + + return 0; +} + +struct pkgloc *find_package(char *pkgstr) { + char *latest = 0; + char *installed = 0, *found = 0; + char *pkgfile; + struct pkgloc *pkg = 0; + int rv; + struct zpm localdb; + + if (zpm_open(&localdb, NULL)) { + installed = zpm_findpkg(&localdb, pkgstr, "status = 'installed'"); + if (installed) { + latest = installed; + } + found = zpm_findpkg(&localdb, pkgstr, NULL); + if (found) { + rv = zpm_vercmp(found, latest); + if (rv == 1) { + latest = found; + pkgfile = localdb.path; + } + } + zpm_close(&localdb); + } else { + fprintf(stderr, "can't open localdb\n"); + } + + glob_t repos; + repos.gl_offs = 0; + rv = glob("/var/lib/zpm/repos/*.repo", 0, NULL, &repos); + switch (rv) { + case GLOB_NOSPACE: + fprintf(stderr, "glob no mem\n"); + exit(EXIT_FAILURE); break; + case GLOB_ABORTED: + fprintf(stderr, "glob abort\n"); + exit(EXIT_FAILURE); break; + case GLOB_NOMATCH: + break; + case 0: + break; + default: + break; + } + + unsigned i; + for (i = 0; i < repos.gl_pathc; i++) { + found = checkfile(pkgstr, repos.gl_pathv[i]); + if (found) { + rv = zpm_vercmp(found, latest); + if (rv == 1) { + latest = found; + pkgfile = repos.gl_pathv[i]; + } + } + } + + globfree(&repos); + repos.gl_offs = 0; + + char *pkgdir = "/home/zoranix/zrepo/packages"; + char *pkgglob = 0; + + size_t globlen = strlen(pkgdir)+strlen(pkgstr)+7; + pkgglob = malloc(globlen+1); + snprintf(pkgglob, globlen, "%s/%s*.zpm", pkgdir, pkgstr); + + rv = glob(pkgglob, 0, NULL, &repos); + switch (rv) { + case GLOB_NOSPACE: + exit(EXIT_FAILURE); break; + case GLOB_ABORTED: + exit(EXIT_FAILURE); break; + case GLOB_NOMATCH: break; + case 0: + break; + default: + break; + } + + for (i = 0; i < repos.gl_pathc; i++) { + found = checkfile(pkgstr, repos.gl_pathv[i]); + if (found) { + rv = zpm_vercmp(found, latest); + if (rv == 1) { + latest = found; + pkgfile = strdup(repos.gl_pathv[i]); + } + } + } + + if (latest && pkgfile) { + pkg = malloc(sizeof *pkg); + pkg->id = strdup(latest); + pkg->file = pkgfile; + } + + return pkg; +} + +char *pkglibsneeded = "\ +with pkglibs as (\ + select distinct EN.needed as soname, PF.pkgid\ + from elfneeded EN\ + join packagefiles_pkgid PF on PF.hash = EN.file\ + ),\ + pkgprovides as (\ + select distinct EL.soname, PF.pkgid\ + from elflibraries EL\ + join packagefiles_pkgid PF on PF.hash = EL.file\ + )\ + select distinct PL.soname, PP.soname is not null as selfsatisfied\ + from pkglibs PL\ + left join pkgprovides PP on PL.pkgid = PP.pkgid and PL.soname = PP.soname\ + where PL.pkgid = %Q\ + "; + +void checklibs(jsw_hash_t *check, jsw_hash_t *forlibs, jsw_hash_t *nfound) { + char *pkgid = 0, *pkgfile = 0; + struct zpm src; + + /* checked_libs is a list of checked sonames + * checked is a list of checked packages + * needed is list of libs for package + */ + jsw_atree_t *needed = 0, *checked, *checked_libs; + + int libs; + jsw_atrav_t *i; + char *soname; + + checked = jsw_anew((cmp_f)strcmp, (dup_f)strdup, (rel_f)free); + checked_libs = jsw_anew((cmp_f)strcmp, (dup_f)strdup, (rel_f)free); + + while (jsw_hsize(check) > 0) { + free(pkgid); + free(pkgfile); + + jsw_hreset(check); + + pkgid = strdup(jsw_hkey(check)); + pkgfile = strdup(jsw_hitem(check)); + + if (!pkgid || !pkgfile) { + break; + } + + jsw_herase(check, pkgid); + + if (jsw_afind(checked, pkgid)) { + /* already checked this one */ + continue; + } + + /* we do this now so we catch self deps */ + jsw_ainsert(checked, pkgid); + + /* get the libraries needed by this package */ + if (!zpm_open(&src, pkgfile)) { + fprintf(stderr, "can't zpm open %s\n", pkgfile); + break; + } + + if (needed) jsw_adelete(needed); + needed = jsw_anew((cmp_f)strcmp, (dup_f)strdup, (rel_f)free); + libs = zpm_libraries_needed(&src, pkgid, needed); + zpm_close(&src); + + if (libs == 0) { + continue; + } + + i = jsw_atnew(); + + for (soname = jsw_atfirst(i, needed); soname; soname = jsw_atnext(i)) { + int found; + struct pkgloc pkginfo; + + /* if it's in checked_libs, we've already looked at this one */ + if (jsw_afind(checked_libs, soname)) { + continue; + } + + /* haven't found this soname */ + jsw_ainsert(checked_libs, soname); + + found = find_lib(soname, &pkginfo); + if (!found) { + if (!jsw_hinsert(nfound, soname, pkgid)) { + fprintf(stderr,"insert error\n"); + } + continue; + } + + if (found == 1) { + /* if not alreacy checked, add to check */ + if (!jsw_afind(checked, pkginfo.file)) { + jsw_hinsert(check,pkginfo.id,pkginfo.file); + } + /* shouldn't be in there already */ + jsw_hinsert(forlibs, pkginfo.id, pkginfo.file); + } + /* otherwise found == 2, so just continue */ + if (found) { + free(pkginfo.file); + free(pkginfo.id); + } + } + } + free(pkgid); + free(pkgfile); +} + +char *pathcat(char *dir, char *path) { + size_t dirlen = 0, pathlen = 0; + char *cat; + + /* chop off trailing / on dir */ + if (dir) { + dirlen = strlen(dir); + while (dirlen && dir[dirlen-1] == '/') { + dirlen--; + } + } + + if (path) { + pathlen = strlen(path); + while (*path && *path == '/') { + path++; + pathlen--; + } + } + + cat = malloc(dirlen + pathlen + 2); + if (cat) { + strncpy(cat, dir, dirlen); + cat[dirlen] = '/'; + strcpy(cat+dirlen+1, path); + } + return cat; +} + +void print_pkghash(jsw_hash_t *hash, int json) { + const char *pkgid, *file; + char *fmt; + char *sep; + int count = 0; + + fmt = json ? "\"%s\": \"%s\"" : "%s:%s"; + sep = json ? ", " : "\n"; + + if (json) { + printf("{ "); + } + + if (jsw_hsize(hash) > 0) { + for (jsw_hreset(hash); jsw_hitem(hash); jsw_hnext(hash)) { + pkgid = jsw_hkey(hash); + file = jsw_hitem(hash); + if (count++) { + printf("%s", sep); + } + printf(fmt, pkgid, file); + } + } + if (json) printf(" }"); + printf("\n"); +} + +int main(int ac, char *av[]) { + int opt, argn; + int findlibs = 0, json = 0; + struct pkgloc *found; + jsw_hash_t *packages, *check, *forlibs, *nolib; + jsw_atree_t *nfound; + + /* -l also find packages needed for libs + * -j output json + * -q quiet - suppress output + * -v verbose - + * -d debug - trace each step + * + * environment: + * ZPMDB - path to localdb, /var/lib/zpm/local.db + * ZPM_REPO_DIR - path to *.repo files, '/var/lib/zpm/repos' + * ZPM_PACKAGE_DIRS - : separated paths to *.zpm files, + * '/var/lib/zpm/packages' + * ZPM_ROOT_DIR :- prepends to above paths + * + * -M (missing) invert the output and only output missing + * outputs + * packages found (suppress with -P) + * package strings not found (suppress with -S) + * library dependency packages needed and found (suppress with -N) + * library dependency packages already installed (suppress with -H) + * library dependency packages in install set (suppress with -I) + * library dependency sonames not found (suppress with -L) + * + * exit value is 0 if no missing libs and no bad package strings + * exit value is non zero otherwise + */ + + int output = 1; + while ((opt = getopt(ac, av, "lj")) != -1) { + switch (opt) { + case 'l': findlibs = 1; break; + case 'j': json = 1; break; + case 'q': output = 0; + default: + exit(EXIT_FAILURE); + break; + } + } + argn = optind; + + packages = jsw_hnew(ac,NULL,(cmp_f)strcmp,(keydup_f)strdup, + (itemdup_f)strdup,free,free); + check = jsw_hnew(ac,NULL,(cmp_f)strcmp,(keydup_f)strdup, + (itemdup_f)strdup,free,free); + forlibs = jsw_hnew(ac,NULL,(cmp_f)strcmp,(keydup_f)strdup, + (itemdup_f)strdup,free,free); + nolib = jsw_hnew(ac,NULL,(cmp_f)strcmp,(keydup_f)strdup, + (itemdup_f)strdup,free,free); + nfound = jsw_anew((cmp_f)strcmp, (dup_f)strdup, (rel_f)free); + + int arg; + for (arg = argn; arg < ac; arg++) { + found = find_package(av[arg]); + if (found) { + jsw_hinsert(packages, found->id, found->file); + jsw_hinsert(check, found->id, found->file); + free(found->id); + free(found->file); + free(found); + } else { + jsw_ainsert(nfound, av[arg]); + } + + } + + if (findlibs) { + checklibs(check, forlibs, nolib); + } + + if (output) { + print_pkghash(packages, json); + print_pkghash(forlibs, json); + } + + if (jsw_hsize(nolib) > 0) { + for (jsw_hreset(nolib); jsw_hitem(nolib); jsw_hnext(nolib)) { + const char *pkgid, *file; + pkgid = jsw_hkey(nolib); + file = jsw_hitem(nolib); + fprintf(stderr, "no lib found %s:%s\n", pkgid, file); + } + } + + if (jsw_asize(nfound) > 0) { + jsw_atrav_t *i; + i = jsw_atnew(); + char *pkgstr; + + for (pkgstr = jsw_atfirst(i, nfound); pkgstr; pkgstr = jsw_atnext(i)) { + fprintf(stderr, "%s:\n", pkgstr); + } + } + + return jsw_asize(nfound) > 0 || jsw_hsize(nolib) > 0 ? 1 : 0; +} diff --git a/zpm.h b/zpm.h index 106314e..f8ce56b 100644 --- a/zpm.h +++ b/zpm.h @@ -9,6 +9,7 @@ #include #include +#include #define ZPM_HASH_STRLEN 64 @@ -53,7 +54,7 @@ struct zpm_tag { struct zpm_package { struct zpm *zpm; - struct jsw_hash *ht; + struct jsw_hash_t *ht; /* char pointers are just pointers into the hash table */ /* integers/times and such are passed through atoi */ @@ -77,6 +78,8 @@ struct zpm_package { int zpm_parse_package(char *pstr, char *name, char *ver, int *rel); char *zpm_findpkg(struct zpm *zpm, char *pkgstr, char *where); +char *zpm_findlib(struct zpm *zpm, char *soname, char *where); +int zpm_libraries_needed(struct zpm *zpm, char *pkgid, jsw_atree_t *list); int zpm_quote(char *value, char *dest, size_t n); struct zpm_file { -- 2.40.0