diff --git a/bin/xbps-query/defs.h b/bin/xbps-query/defs.h index 2a1747253..f9f47abff 100644 --- a/bin/xbps-query/defs.h +++ b/bin/xbps-query/defs.h @@ -64,7 +64,9 @@ int list_pkgs_pkgdb(struct xbps_handle *); int repo_list(struct xbps_handle *); /* from search.c */ -int search(struct xbps_handle *, bool, const char *, const char *, bool); - +int search( + struct xbps_handle *xhp, bool repo_mode, const char *pattern, bool regex); +int search_prop(struct xbps_handle *xhp, bool repo_mode, const char *prop, + const char *pattern, bool regex); #endif /* !_XBPS_QUERY_DEFS_H_ */ diff --git a/bin/xbps-query/main.c b/bin/xbps-query/main.c index 2b86fac20..4d3405df8 100644 --- a/bin/xbps-query/main.c +++ b/bin/xbps-query/main.c @@ -301,7 +301,10 @@ main(int argc, char **argv) } else if (pkg_search) { /* search mode */ - rv = search(&xh, repo_mode, pkg, props, regex); + if (props) + rv = search_prop(&xh, repo_mode, pkg, props, regex); + else + rv = search(&xh, repo_mode, pkg, regex); } else if (catfile) { /* repo cat file mode */ diff --git a/bin/xbps-query/search.c b/bin/xbps-query/search.c index 7f3dfe0ff..0ea4cd558 100644 --- a/bin/xbps-query/search.c +++ b/bin/xbps-query/search.c @@ -23,8 +23,6 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "compat.h" - #include #include #include @@ -37,19 +35,21 @@ #include #include + #include "defs.h" -struct search_data { +struct search_ctx { bool regex, repo_mode; regex_t regexp; unsigned int maxcols; - const char *pat, *prop, *repourl; + const char *pattern; + const char *repourl; xbps_array_t results; char *linebuf; }; static void -print_results(struct xbps_handle *xhp, struct search_data *sd) +print_results(struct xbps_handle *xhp, struct search_ctx *sd) { const char *pkgver = NULL, *desc = NULL; unsigned int align = 0, len; @@ -76,125 +76,56 @@ print_results(struct xbps_handle *xhp, struct search_data *sd) align, pkgver, desc); /* add ellipsis if the line was truncated */ if (len >= sd->maxcols && sd->maxcols > 4) { - for (unsigned int j = 0; j < 3; j++) - sd->linebuf[sd->maxcols-j-1] = '.'; - sd->linebuf[sd->maxcols] = '\0'; + sd->linebuf[sd->maxcols - 4] = '.'; + sd->linebuf[sd->maxcols - 3] = '.'; + sd->linebuf[sd->maxcols - 2] = '.'; } puts(sd->linebuf); } } static int -search_array_cb(struct xbps_handle *xhp UNUSED, - xbps_object_t obj, - const char *key UNUSED, - void *arg, - bool *done UNUSED) +search_cb(struct xbps_handle *xhp UNUSED, xbps_object_t pkgd, + const char *key UNUSED, void *arg, bool *done UNUSED) { - xbps_object_t obj2; - struct search_data *sd = arg; - const char *pkgver = NULL, *desc = NULL, *str = NULL; + struct search_ctx *ctx = arg; + bool vpkgfound = false; + const char *pkgver = NULL, *desc = NULL; - if (!xbps_dictionary_get_cstring_nocopy(obj, "pkgver", &pkgver)) - return 0; + if (!xbps_dictionary_get_cstring_nocopy(pkgd, "pkgver", &pkgver)) + abort(); - if (sd->prop == NULL) { - bool vpkgfound = false; - /* no prop set, match on pkgver/short_desc objects */ - xbps_dictionary_get_cstring_nocopy(obj, "short_desc", &desc); + if (!xbps_dictionary_get_cstring_nocopy(pkgd, "short_desc", &desc)) { + xbps_error_printf("%s: missing short_desc property\n", pkgver); + return -EINVAL; + } - if (sd->repo_mode && xbps_match_virtual_pkg_in_dict(obj, sd->pat)) - vpkgfound = true; + if (ctx->repo_mode && xbps_match_virtual_pkg_in_dict(pkgd, ctx->pattern)) + vpkgfound = true; - if (sd->regex) { - if ((regexec(&sd->regexp, pkgver, 0, 0, 0) == 0) || - (regexec(&sd->regexp, desc, 0, 0, 0) == 0)) { - xbps_array_add_cstring_nocopy(sd->results, pkgver); - xbps_array_add_cstring_nocopy(sd->results, desc); - } - return 0; - } - if (vpkgfound) { - xbps_array_add_cstring_nocopy(sd->results, pkgver); - xbps_array_add_cstring_nocopy(sd->results, desc); - } else { - if ((strcasestr(pkgver, sd->pat)) || - (strcasestr(desc, sd->pat)) || - (xbps_pkgpattern_match(pkgver, sd->pat))) { - xbps_array_add_cstring_nocopy(sd->results, pkgver); - xbps_array_add_cstring_nocopy(sd->results, desc); - } + if (ctx->regex) { + if ((regexec(&ctx->regexp, pkgver, 0, 0, 0) == 0) || + (regexec(&ctx->regexp, desc, 0, 0, 0) == 0)) { + if (!xbps_array_add_cstring_nocopy(ctx->results, pkgver)) + return xbps_error_oom(); + if (!xbps_array_add_cstring_nocopy(ctx->results, desc)) + return xbps_error_oom(); } return 0; } - /* prop set, match on prop object instead */ - obj2 = xbps_dictionary_get(obj, sd->prop); - if (xbps_object_type(obj2) == XBPS_TYPE_ARRAY) { - /* property is an array */ - for (unsigned int i = 0; i < xbps_array_count(obj2); i++) { - xbps_array_get_cstring_nocopy(obj2, i, &str); - if (sd->regex) { - if (regexec(&sd->regexp, str, 0, 0, 0) == 0) { - if (sd->repo_mode) - printf("%s: %s (%s)\n", pkgver, str, sd->repourl); - else - printf("%s: %s\n", pkgver, str); - } - } else { - if (strcasestr(str, sd->pat)) { - if (sd->repo_mode) - printf("%s: %s (%s)\n", pkgver, str, sd->repourl); - else - printf("%s: %s\n", pkgver, str); - } - } - } - } else if (xbps_object_type(obj2) == XBPS_TYPE_NUMBER) { - /* property is a number */ - char size[8]; - - if (xbps_humanize_number(size, xbps_number_integer_value(obj2)) == -1) - exit(EXIT_FAILURE); - - if (sd->regex) { - if (regexec(&sd->regexp, size, 0, 0, 0) == 0) { - if (sd->repo_mode) - printf("%s: %s (%s)\n", pkgver, size, sd->repourl); - else - printf("%s: %s\n", pkgver, size); - } - } else { - if (strcasestr(size, sd->pat)) { - if (sd->repo_mode) - printf("%s: %s (%s)\n", pkgver, size, sd->repourl); - else - printf("%s: %s\n", pkgver, size); - } - } - } else if (xbps_object_type(obj2) == XBPS_TYPE_BOOL) { - /* property is a bool */ - if (sd->repo_mode) - printf("%s: true (%s)\n", pkgver, sd->repourl); - else - printf("%s: true\n", pkgver); - - } else if (xbps_object_type(obj2) == XBPS_TYPE_STRING) { - /* property is a string */ - str = xbps_string_cstring_nocopy(obj2); - if (sd->regex) { - if (regexec(&sd->regexp, str, 0, 0, 0) == 0) { - if (sd->repo_mode) - printf("%s: %s (%s)\n", pkgver, str, sd->repourl); - else - printf("%s: %s\n", pkgver, str); - } - } else { - if (strcasestr(str, sd->pat)) { - if (sd->repo_mode) - printf("%s: %s (%s)\n", pkgver, str, sd->repourl); - else - printf("%s: %s\n", pkgver, str); - } + if (vpkgfound) { + if (!xbps_array_add_cstring_nocopy(ctx->results, pkgver)) + return xbps_error_oom(); + if (!xbps_array_add_cstring_nocopy(ctx->results, desc)) + return xbps_error_oom(); + } else { + if ((strcasestr(pkgver, ctx->pattern)) || + (strcasestr(desc, ctx->pattern)) || + (xbps_pkgpattern_match(pkgver, ctx->pattern))) { + if (!xbps_array_add_cstring_nocopy(ctx->results, pkgver)) + return xbps_error_oom(); + if (!xbps_array_add_cstring_nocopy(ctx->results, desc)) + return xbps_error_oom(); } } return 0; @@ -203,64 +134,212 @@ search_array_cb(struct xbps_handle *xhp UNUSED, static int search_repo_cb(struct xbps_repo *repo, void *arg, bool *done UNUSED) { - xbps_array_t allkeys; - struct search_data *sd = arg; + xbps_array_t keys; + struct search_ctx *ctx = arg; int rv; - if (repo->idx == NULL) - return 0; + keys = xbps_dictionary_all_keys(repo->idx); + if (!keys) + return xbps_error_oom(); + + ctx->repourl = repo->uri; + rv = xbps_array_foreach_cb(repo->xhp, keys, repo->idx, search_cb, ctx); - sd->repourl = repo->uri; - allkeys = xbps_dictionary_all_keys(repo->idx); - rv = xbps_array_foreach_cb(repo->xhp, allkeys, repo->idx, search_array_cb, sd); - xbps_object_release(allkeys); + xbps_object_release(keys); return rv; } int -search(struct xbps_handle *xhp, bool repo_mode, const char *pat, const char *prop, bool regex) +search(struct xbps_handle *xhp, bool repo_mode, const char *pattern, bool regex) { - struct search_data sd; - int rv; + struct search_ctx ctx = { + .repo_mode = repo_mode, + .regex = regex, + .pattern = pattern, + .maxcols = get_maxcols(), + }; + int r; - sd.regex = regex; if (regex) { - if (regcomp(&sd.regexp, pat, REG_EXTENDED|REG_NOSUB|REG_ICASE) != 0) - return errno; + r = regcomp( + &ctx.regexp, pattern, REG_EXTENDED | REG_NOSUB | REG_ICASE); + if (r != 0) { + char errbuf[4096]; + regerror(r, &ctx.regexp, errbuf, sizeof(errbuf)); + xbps_error_printf("failed to compile regexp: %s: %s\n", + pattern, errbuf); + return EXIT_FAILURE; + } } - sd.repo_mode = repo_mode; - sd.pat = pat; - sd.prop = prop; - sd.maxcols = get_maxcols(); - sd.results = xbps_array_create(); - sd.linebuf = NULL; - if (sd.maxcols > 0) { - sd.linebuf = malloc(sd.maxcols); - if (sd.linebuf == NULL) - exit(1); + + ctx.results = xbps_array_create(); + if (!ctx.results) { + r = xbps_error_oom(); + goto err; } - if (repo_mode) { - rv = xbps_rpool_foreach(xhp, search_repo_cb, &sd); - if (rv != 0 && rv != ENOTSUP) { - xbps_error_printf("Failed to initialize rpool: %s\n", - strerror(rv)); - return rv; + if (ctx.maxcols > 0) { + ctx.linebuf = malloc(ctx.maxcols); + if (!ctx.linebuf) { + r = xbps_error_oom(); + goto err; } + } + + if (repo_mode) + r = xbps_rpool_foreach(xhp, search_repo_cb, &ctx); + else + r = xbps_pkgdb_foreach_cb(xhp, search_cb, &ctx); + if (r != 0) + goto err; + + print_results(xhp, &ctx); + +err: + if (ctx.results) + xbps_object_release(ctx.results); + if (regex) + regfree(&ctx.regexp); + free(ctx.linebuf); + return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE; +} + +struct search_prop_ctx { + bool regex, repo_mode; + const char *pattern; + const char *prop; + regex_t regexp; + const char *repourl; +}; + +static int +match_prop_cstring(const struct search_prop_ctx *ctx, const char *pkgver, const char *str) +{ + if (ctx->regex) { + if (regexec(&ctx->regexp, str, 0, 0, 0) != 0) + return 0; } else { - rv = xbps_pkgdb_foreach_cb(xhp, search_array_cb, &sd); - if (rv != 0) { - xbps_error_printf("Failed to initialize pkgdb: %s\n", - strerror(rv)); - return rv; - } + if (!strcasestr(str, ctx->pattern)) + return 0; } - if (!prop && xbps_array_count(sd.results)) { - print_results(xhp, &sd); - xbps_object_release(sd.results); + + if (ctx->repo_mode) + printf("%s: %s (%s)\n", pkgver, str, ctx->repourl); + else + printf("%s: %s\n", pkgver, str); + + return 0; +} + +static int +match_prop_number(const struct search_prop_ctx *ctx, const char *pkgver, xbps_number_t num) +{ + char fmt[8]; + + if (xbps_humanize_number(fmt, xbps_number_integer_value(num)) == -1) + return -EINVAL; + + return match_prop_cstring(ctx, pkgver, fmt); +} + +static int +match_prop_object(const struct search_prop_ctx *ctx, const char *pkgver, xbps_object_t obj) +{ + switch (xbps_object_type(obj)) { + case XBPS_TYPE_UNKNOWN: + case XBPS_TYPE_ARRAY: + case XBPS_TYPE_DATA: + case XBPS_TYPE_DICTIONARY: + case XBPS_TYPE_DICT_KEYSYM: + return 0; + case XBPS_TYPE_BOOL: + return match_prop_cstring( + ctx, pkgver, xbps_bool_true(obj) ? "true" : "false"); + case XBPS_TYPE_NUMBER: + return match_prop_number(ctx, pkgver, obj); + case XBPS_TYPE_STRING: + return match_prop_cstring( + ctx, pkgver, xbps_string_cstring_nocopy(obj)); } - if (regex) - regfree(&sd.regexp); + abort(); +} + +static int +match_prop_array(const struct search_prop_ctx *ctx, const char *pkgver, xbps_array_t array) +{ + for (unsigned int i = 0; i < xbps_array_count(array); i++) { + int r = match_prop_object(ctx, pkgver, xbps_array_get(array, i)); + if (r < 0) + return r; + } + return 0; +} + +static int +search_prop_cb(struct xbps_handle *xhp UNUSED, xbps_object_t pkgd, + const char *key UNUSED, void *arg, bool *done UNUSED) +{ + const struct search_prop_ctx *ctx = arg; + xbps_object_t obj; + const char *pkgver = NULL; + + if (!xbps_dictionary_get_cstring_nocopy(pkgd, "pkgver", &pkgver)) + abort(); + obj = xbps_dictionary_get(pkgd, ctx->prop); + if (xbps_object_type(obj) == XBPS_TYPE_ARRAY) + return match_prop_array(ctx, pkgver, obj); + return match_prop_object(ctx, pkgver, obj); +} + +static int +search_prop_repo_cb(struct xbps_repo *repo, void *arg, bool *done UNUSED) +{ + xbps_array_t keys; + struct search_prop_ctx *ctx = arg; + int rv; + + keys = xbps_dictionary_all_keys(repo->idx); + if (!keys) + return xbps_error_oom(); + + ctx->repourl = repo->uri; + rv = xbps_array_foreach_cb( + repo->xhp, keys, repo->idx, search_prop_cb, ctx); + + xbps_object_release(keys); return rv; } + +int +search_prop(struct xbps_handle *xhp, bool repo_mode, const char *pattern, const char *prop, bool regex) +{ + struct search_prop_ctx ctx = { + .repo_mode = repo_mode, + .regex = regex, + .pattern = pattern, + .prop = prop, + }; + int r; + + if (regex) { + r = regcomp( + &ctx.regexp, pattern, REG_EXTENDED | REG_NOSUB | REG_ICASE); + if (r != 0) { + char errbuf[4096]; + regerror(r, &ctx.regexp, errbuf, sizeof(errbuf)); + xbps_error_printf("failed to compile regexp: %s: %s\n", + pattern, errbuf); + return EXIT_FAILURE; + } + } + + if (repo_mode) + r = xbps_rpool_foreach(xhp, search_prop_repo_cb, &ctx); + else + r = xbps_pkgdb_foreach_cb(xhp, search_prop_cb, &ctx); + if (r != 0) + return EXIT_FAILURE; + + return EXIT_SUCCESS; +} diff --git a/tests/xbps/xbps-query/query_test.sh b/tests/xbps/xbps-query/query_test.sh index 284e1470b..6bfaf9743 100644 --- a/tests/xbps/xbps-query/query_test.sh +++ b/tests/xbps/xbps-query/query_test.sh @@ -39,7 +39,146 @@ repo_cat_file_body() { atf_check_equal "$res" "hello world!" } +search_head() { + atf_set "descr" "xbps-query(1) --search" +} + +search_body() { + mkdir -p root some_repo pkg_A pkg_B pkg_C + + cd some_repo + atf_check -o ignore -- xbps-create -A noarch -n foo-1.0_1 -s "foo pkg" ../pkg_A + atf_check -o ignore -- xbps-create -A noarch -n bar-1.0_1 -s "bar pkg" ../pkg_B + atf_check -o ignore -- xbps-create -A noarch -n fizz-1.0_1 -s "fizz pkg" ../pkg_C + atf_check -o ignore -- xbps-rindex -a $PWD/*.xbps + cd .. + + # regex error + atf_check -e match:"ERROR: failed to compile regexp: \(:" -s exit:1 -- \ + xbps-query -r root --repository=some_repo --regex -s '(' + + # repo mode + atf_check -o inline:"[-] foo-1.0_1 foo pkg\n" -- \ + xbps-query -r root --repository=some_repo -s foo + atf_check -o inline:"[-] fizz-1.0_1 fizz pkg\n[-] foo-1.0_1 foo pkg\n" -- \ + xbps-query -r root --repository=some_repo -s f + + atf_check -o ignore -- \ + xbps-install -r root --repository=some_repo -y foo + + # repo mode + installed + atf_check -o inline:"[*] foo-1.0_1 foo pkg\n" -- \ + xbps-query -r root --repository=some_repo -s foo + atf_check -o inline:"[-] fizz-1.0_1 fizz pkg\n[*] foo-1.0_1 foo pkg\n" -- \ + xbps-query -r root --repository=some_repo -s f + + # installed mode + atf_check -o inline:"[*] foo-1.0_1 foo pkg\n" -- \ + xbps-query -r root -s foo +} + +search_prop_head() { + atf_set "descr" "xbps-query(1) --property --search" +} + +search_prop_body() { + mkdir -p root some_repo pkg_A pkg_B pkg_C + + cd some_repo + atf_check -o ignore -- xbps-create -A noarch -n foo-1.0_1 -s "foo pkg" ../pkg_A + atf_check -o ignore -- xbps-create -A noarch -n bar-1.0_1 -s "bar pkg" ../pkg_B + atf_check -o ignore -- xbps-create -A noarch -n fizz-1.0_1 -s "fizz pkg" ../pkg_C + atf_check -o ignore -- xbps-rindex -a $PWD/*.xbps + cd .. + + xbps-query -r root --repository=some_repo -S foo + xbps-query -r root --repository=some_repo -S bar + xbps-query -r root --repository=some_repo -S fizz + + # regex error + atf_check -e match:"ERROR: failed to compile regexp: \(:" -s exit:1 -- \ + xbps-query -r root --repository=some_repo --property pkgver --regex -s '(' + + # repo mode + atf_check -o match:"^foo-1\.0_1: foo-1\.0_1 \(.*\)$" -- \ + xbps-query -r root --repository=some_repo --property pkgver -s foo + atf_check \ + -o match:"^foo-1\.0_1: foo-1\.0_1 \(.*\)$" \ + -o match:"^fizz-1\.0_1: fizz-1\.0_1 \(.*\)$" \ + -- xbps-query -r root --repository=some_repo --property pkgver -s f + + atf_check -o ignore -- \ + xbps-install -r root --repository=some_repo -y foo + + # installed mode + atf_check -o match:"^foo-1\.0_1: foo-1\.0_1$" -- \ + xbps-query -r root --property pkgver -s foo +} + +show_prop_head() { + atf_set "descr" "xbps-query(1) --property" +} + +show_prop_body() { + mkdir -p root some_repo pkg_A/bin + touch pkg_A/bin/foo + + cd some_repo + atf_check -o ignore -- xbps-create -A noarch -n foo-1.0_1 -s "foo pkg" ../pkg_A + atf_check -o ignore -- xbps-rindex -a $PWD/*.xbps + cd .. + + # repo mode single property + atf_check -o inline:"foo-1.0_1\n" -- \ + xbps-query -r root --repository=some_repo --property pkgver foo-1.0_1 + + # repo mode missing single property + # XXX: should this be an error? + atf_check -o empty -- \ + xbps-query -r root --repository=some_repo --property asdf foo-1.0_1 + + # repo mode multiple properties + atf_check -o inline:"foo-1.0_1\nfoo\nnoarch\n" -- \ + xbps-query -r root --repository=some_repo --property pkgver,pkgname,architecture foo-1.0_1 + + # repo mode multiple properties and one missing + # XXX: should this be an error? + atf_check -o inline:"foo-1.0_1\nfoo\nnoarch\n" -- \ + xbps-query -r root --repository=some_repo --property pkgver,pkgname,architecture,asdf foo-1.0_1 + + # repo mode package not found + atf_check -o empty -s exit:2 -- \ + xbps-query -r root --repository=some_repo --property pkgver bar-1.0_1 + + atf_check -o ignore -e ignore -- xbps-install -r root -R some_repo -y foo + + # pkgdb mode single property + atf_check -o inline:"foo-1.0_1\n" -- \ + xbps-query -r root --property pkgver foo-1.0_1 + + # pkgdb mode missing single property + # XXX: should this be error? + atf_check -o empty -- \ + xbps-query -r root --property asdf foo-1.0_1 + + # pkgdb mode multiple properties and one missing + # XXX: should this be an error? + atf_check -o inline:"foo-1.0_1\nfoo\nnoarch\n" -- \ + xbps-query -r root --property pkgver,pkgname,architecture,asdf foo-1.0_1 + + # pkgdb mode multiple properties + atf_check -o inline:"foo-1.0_1\nfoo\nnoarch\n" -- \ + xbps-query -r root --property pkgver,pkgname,architecture foo-1.0_1 + + # pkgdb mode package not found + atf_check -o empty -s exit:2 -- \ + xbps-query -r root --property pkgver bar-1.0_1 +} + atf_init_test_cases() { atf_add_test_case cat_file atf_add_test_case repo_cat_file + atf_add_test_case search + atf_add_test_case search_prop + atf_add_test_case show_prop }