/* * Copyright 2005-2024 Gentoo Foundation * Distributed under the terms of the GNU General Public License v2 * * Copyright 2005 Martin Schlemmer - * Copyright 2005-2010 Ned Ludd - * Copyright 2005-2014 Mike Frysinger - * Copyright 2018- Fabian Groffen - */ #include "main.h" #include "applets.h" #include #include #include #include #include "atom.h" #include "contents.h" #include "tree.h" #include "xpak.h" #include "xregex.h" #define QLIST_FLAGS "IktSRUcmDedosF:" COMMON_FLAGS static struct option const qlist_long_opts[] = { {"installed", no_argument, NULL, 'I'}, {"binpkgs", no_argument, NULL, 'k'}, {"tree", no_argument, NULL, 't'}, {"slots", no_argument, NULL, 'S'}, {"repo", no_argument, NULL, 'R'}, {"umap", no_argument, NULL, 'U'}, {"columns", no_argument, NULL, 'c'}, {"masks", no_argument, NULL, 'm'}, {"showdebug", no_argument, NULL, 128}, {"exact", no_argument, NULL, 'e'}, {"dir", no_argument, NULL, 'd'}, {"obj", no_argument, NULL, 'o'}, {"sym", no_argument, NULL, 's'}, /* {"file", a_argument, NULL, 'f'}, */ {"format", a_argument, NULL, 'F'}, COMMON_LONG_OPTS }; static const char * const qlist_opts_help[] = { "Just show installed package names", "Use binpkgs instead of installed packages", "Use available packages in the tree instead of installed", "Display installed packages with slots (use twice for subslots)", "Display installed packages with repository", "Display installed packages with flags used", "Display column view", "Exclude matches masked by profiles", "Show /usr/lib/debug and /usr/src/debug files", "Exact match (only CAT/PN or PN without PV)", "Only show directories", "Only show objects", "Only show symlinks", /* "query filename for pkgname", */ "Print matched atom using given format string", COMMON_OPTS_HELP }; #define qlist_usage(ret) usage(ret, QLIST_FLAGS, qlist_long_opts, qlist_opts_help, NULL, lookup_applet_idx("qlist")) static int cmpstringp(const void *p1, const void *p2) { /* PMS 3.1.4: USE-flag names may contain any of the characters * [A-Za-z0-9+_@-]. */ return strcmp(*((char * const *)p1), *((char * const *)p2)); } /* * ==> /var/db/pkg/mail-mta/exim-4.92/IUSE <== * arc dane dcc +dkim dlfunc dmarc +dnsdb doc dovecot-sasl dsn * elibc_glibc exiscan-acl gnutls idn ipv6 ldap libressl lmtp maildir * mbx mysql nis pam perl pkcs11 postgres +prdr proxy radius redis sasl * selinux spf sqlite srs ssl syslog tcpd +tpda X * * ==> /var/db/pkg/mail-mta/exim-4.92/PKGUSE <== * -X dkim dmarc exiscan-acl ipv6 -ldap lmtp maildir -mbox pam -perl spf * ssl tcpd * * ==> /var/db/pkg/mail-mta/exim-4.92/USE <== * abi_x86_64 amd64 dkim dmarc dnsdb elibc_glibc exiscan-acl ipv6 * kernel_linux lmtp maildir pam prdr spf ssl tcpd tpda userland_GNU * * % emerge -pv exim * * These are the packages that would be merged, in order: * * Calculating dependencies... done! * [ebuild R ~] mail-mta/exim-4.92::gentoo USE="dkim dmarc dnsdb * exiscan-acl ipv6 lmtp maildir pam prdr spf ssl tcpd tpda -X -arc * -dane -dcc -dlfunc -doc -dovecot-sasl -dsn -gnutls -idn -ldap * -libressl -mbx -mysql -nis -perl -pkcs11 -postgres -proxy -radius * -redis -sasl (-selinux) -sqlite -srs -syslog" 0 KiB * * % qlist -IUv exim * mail-mta/exim-4.92 -arc -dane -dcc dkim -dlfunc dmarc dnsdb -doc * -dovecot-sasl -dsn exiscan-acl -gnutls -idn ipv6 -ldap -libressl lmtp * maildir -mbx -mysql -nis pam -perl -pkcs11 -postgres prdr -proxy * -radius -redis -sasl -selinux spf -sqlite -srs ssl -syslog tcpd tpda * -X */ static void umapstr(tree_pkg_ctx *pkg_ctx) { char *use = NULL; char *iuse = NULL; int use_argc = 0; int iuse_argc = 0; char **use_argv = NULL; char **iuse_argv = NULL; int i; int u; int d; use = tree_pkg_meta_get(pkg_ctx, USE); if (use == NULL || *use == '\0') return; iuse = tree_pkg_meta_get(pkg_ctx, IUSE); if (iuse == NULL || *iuse == '\0') return; /* strip out possible leading +/- flags in IUSE */ u = (int)strlen(iuse); for (i = 0; i < u; i++) if (iuse[i] == '+' || iuse[i] == '-') if (i == 0 || iuse[i - 1] == ' ') iuse[i] = ' '; makeargv(use, &use_argc, &use_argv); makeargv(iuse, &iuse_argc, &iuse_argv); /* merge join, ensure inputs are sorted (Portage does this, but just * to be sure) */ qsort(&use_argv[1], use_argc - 1, sizeof(char *), cmpstringp); qsort(&iuse_argv[1], iuse_argc - 1, sizeof(char *), cmpstringp); for (i = 1, u = 1; i < iuse_argc; i++) { /* filter out implicits */ if (strncmp(iuse_argv[i], "elibc_", 6) == 0 || strncmp(iuse_argv[i], "kernel_", 7) == 0 || strncmp(iuse_argv[i], "userland_", 9) == 0) continue; /* ensure USE is in IUSE */ for (d = 1; u < use_argc; u++) { d = strcmp(use_argv[u], iuse_argv[i]); if (d >= 0) break; } if (d == 0) { printf(" %s%s%s", RED, iuse_argv[i], NORM); u++; } else if (verbose) { printf(" %s-%s%s", DKBLUE, iuse_argv[i], NORM); } } freeargv(iuse_argc, iuse_argv); freeargv(use_argc, use_argv); } /* forward declaration necessary for misuse from qmerge.c, see HACK there */ bool qlist_match( tree_pkg_ctx *pkg_ctx, const char *name, depend_atom **name_atom, bool exact, bool applymasks); bool qlist_match( tree_pkg_ctx *pkg_ctx, const char *name, depend_atom **name_atom, bool exact, bool applymasks) { char buf[_Q_PATH_MAX]; char uslot[32]; char *usslot = NULL; const char *urepo; size_t pf_len; depend_atom *atom; depend_atom *_atom = NULL; uslot[0] = '\0'; urepo = strchr(name, ':'); if (urepo != NULL) { pf_len = urepo - name; if (*++urepo == ':') { uslot[0] = '\0'; urepo++; } else { usslot = (char *)urepo; urepo = strstr(urepo, "::"); if (urepo != NULL) { snprintf(uslot, sizeof(uslot), "%.*s", (int)(urepo - usslot), usslot); urepo += 2; } else { snprintf(uslot, sizeof(uslot), "%s", usslot); } if ((usslot = strchr(uslot, '/')) != NULL) *usslot++ = '\0'; } } else { pf_len = strlen(name); } atom = tree_get_atom(pkg_ctx, uslot[0] != '\0' || urepo != NULL); /* maybe they're using a version range */ switch (name[0]) { case '=': case '>': case '<': case '~': if (!name_atom) name_atom = &_atom; if (!*name_atom) { if ((*name_atom = atom_explode(name)) == NULL) { warn("invalid atom %s", name); return false; } } return atom_compare(atom, *name_atom) == EQUAL; } if (uslot[0] != '\0') { if (pkg_ctx->slot == NULL) return false; /* Require exact match on SLOTs. If the user didn't include a * subslot, then ignore it when checking the package's value. */ if (strcmp(atom->SLOT, uslot) != 0) return false; if (usslot != NULL && (atom->SUBSLOT == NULL || strcmp(atom->SUBSLOT, usslot) != 0)) return false; } if (urepo) { /* require exact match on repositories */ if (strcmp(atom->REPO, urepo) != 0) return false; } if (applymasks) { DECLARE_ARRAY(masks); depend_atom *matom; char *mask; size_t n; bool match = false; array_set(package_masks, masks); array_for_each(masks, n, mask) { if ((matom = atom_explode(mask)) == NULL) continue; match = atom_compare(atom, matom) == EQUAL; atom_implode(matom); if (match) break; } xarrayfree_int(masks); if (match) return false; } if (exact) { int i; snprintf(buf, sizeof(buf), "%s/%s%s%s%s%s%s%s", atom->CATEGORY, atom->PF, atom->SLOT != NULL ? ":" : "", atom->SLOT != NULL ? atom->SLOT : "", atom->SUBSLOT != NULL ? "/" : "", atom->SUBSLOT != NULL ? atom->SUBSLOT : "", atom->REPO != NULL ? "::" : "", atom->REPO != NULL ? atom->REPO : ""); /* exact match: CAT/PF[:SLOT][::REPO] */ if (strcmp(name, buf) == 0) return true; /* exact match: PF[:SLOT][::REPO] */ if (strcmp(name, strstr(buf, "/") + 1) == 0) return true; /* let's try exact matching w/out the PV */ i = snprintf(buf, sizeof(buf), "%s/%s", atom->CATEGORY, atom->PN); if (uslot[0] != '\0' && i < (int)sizeof(buf)) i += snprintf(buf + i, sizeof(buf) - i, ":%s", atom->SLOT); if (urepo && i < (int)sizeof(buf)) i += snprintf(buf + i, sizeof(buf) - i, "::%s", atom->REPO); /* exact match: CAT/PN[:SLOT][::REPO] */ if (strcmp(name, buf) == 0) return true; /* exact match: PN[:SLOT][::REPO] */ if (strcmp(name, strstr(buf, "/") + 1) == 0) return true; } else { /* partial leading match: CAT/PF */ snprintf(buf, sizeof(buf), "%s/%s", atom->CATEGORY, atom->PF); if (strncmp(name, buf, pf_len) == 0 || rematch(name, buf, REG_EXTENDED) == 0) return true; /* partial leading match: PF */ if (strncmp(name, atom->PF, pf_len) == 0 || rematch(name, buf, REG_EXTENDED) == 0) return true; } return false; } struct qlist_opt_state { int argc; char **argv; depend_atom **atoms; bool exact:1; bool all:1; bool do_binpkgs:1; bool do_tree:1; bool just_pkgname:1; bool show_dir:1; bool show_obj:1; bool show_sym:1; bool apply_masks:1; bool need_full_atom:1; bool show_umap:1; bool show_dbg:1; char *buf; size_t buflen; const char *fmt; }; static int qlist_cb(tree_pkg_ctx *pkg_ctx, void *priv) { struct qlist_opt_state *state = priv; int i; char *contents; char *line; char *savep; depend_atom *atom; /* see if this cat/pkg is requested */ if (!state->all) { for (i = optind; i < state->argc; ++i) if (qlist_match(pkg_ctx, state->argv[i], &state->atoms[i - optind], state->exact, state->apply_masks)) break; if (i == state->argc) return 0; } atom = tree_get_atom(pkg_ctx, state->need_full_atom); if (state->just_pkgname) { printf("%s", atom_format(state->fmt, atom)); if (state->show_umap) umapstr(pkg_ctx); printf("\n"); return 1; } if (verbose) printf("%s %sCONTENTS%s:\n", atom_format(state->fmt, atom), DKBLUE, NORM); if ((contents = tree_pkg_meta_get(pkg_ctx, CONTENTS)) == NULL) return 1; while ((line = strtok_r(contents, "\n", &savep)) != NULL) { contents_entry *e; contents = NULL; /* for strtok_r */ e = contents_parse_line(line); if (!e) continue; if (!state->show_dbg) { if ((strncmp(e->name, "/usr/lib/debug", 14) == 0 || strncmp(e->name, "/usr/src/debug", 14) == 0) && (e->name[14] == '/' || e->name[14] == '\0')) continue; } switch (e->type) { case CONTENTS_DIR: if (state->show_dir) printf("%s%s%s/\n", YELLOW, e->name, NORM); break; case CONTENTS_OBJ: if (state->show_obj) printf("%s%s%s\n", DKGREEN, e->name, NORM); break; case CONTENTS_SYM: if (state->show_sym) { if (verbose) printf("%s%s%s -> %s%s%s\n", CYAN, e->name, NORM, CYAN, e->sym_target, NORM); else printf("%s%s%s\n", CYAN, e->name, NORM); } break; } } return 1; } int qlist_main(int argc, char **argv) { int i; int ret; tree_ctx *vdb; int show_slots = 0; bool show_repo = false; bool do_columns = false; char qfmt[128]; struct qlist_opt_state state = { .argc = argc, .argv = argv, .exact = false, .all = false, .do_binpkgs = false, .do_tree = false, .just_pkgname = false, .show_dir = false, .show_obj = false, .show_sym = false, .apply_masks = false, .need_full_atom = false, .show_umap = false, .show_dbg = false, .buflen = _Q_PATH_MAX, .fmt = NULL, }; while ((i = GETOPT_LONG(QLIST, qlist, "")) != -1) { switch (i) { COMMON_GETOPTS_CASES(qlist) case 'I': state.just_pkgname = true; break; case 'k': state.do_binpkgs = true; break; case 't': state.do_tree = true; break; case 'S': state.just_pkgname = true; show_slots++; break; case 'R': state.just_pkgname = show_repo = true; break; case 'U': state.just_pkgname = state.show_umap = true; break; case 'e': state.exact = true; break; case 'd': state.show_dir = true; break; case 128: state.show_dbg = true; break; case 'o': state.show_obj = true; break; case 's': state.show_sym = true; break; case 'c': do_columns = true; break; case 'm': state.apply_masks = true; break; case 'F': state.fmt = optarg; break; } } /* default to showing syms and objs */ if (!state.show_dir && !state.show_obj && !state.show_sym) state.show_obj = state.show_sym = true; if (argc == optind) { if (state.just_pkgname) state.all = true; else qlist_usage(EXIT_FAILURE); } if (state.fmt == NULL) { const char *l = "%["; const char *r = "]"; if (do_columns) { l = "%{"; r = "} "; } snprintf(qfmt, sizeof(qfmt), "%sCATEGORY%s" "%s%s%s" /* PN/PF */ "%s%s%s" /* BUILDID */ "%s%s%s" /* SLOT */ "%s%s%s" /* SUBSLOT */ "%s%s%s", /* REPO */ l, r, l, verbose ? "PF" : "PN", r, verbose && state.do_binpkgs ? l : "", verbose && state.do_binpkgs ? "BUILDID" : "", verbose && state.do_binpkgs ? r : "", show_slots >= 1 ? l : "", show_slots >= 1 ? "SLOT" : "", show_slots >= 1 ? r : "", show_slots >= 2 ? l : "", show_slots >= 2 ? "SUBSLOT" : "", show_slots >= 2 ? r : "", show_repo ? l : "", show_repo ? "REPO" : "", show_repo ? r : ""); state.fmt = qfmt; state.need_full_atom = show_slots > 0 || show_repo; } else { state.need_full_atom = true; } state.buf = xmalloc(state.buflen); state.atoms = xcalloc(argc - optind, sizeof(*state.atoms)); ret = 1; if (state.do_tree) { size_t n; const char *overlay; array_for_each(overlays, n, overlay) { vdb = tree_open(portroot, overlay); if (vdb != NULL) { ret |= tree_foreach_pkg_sorted(vdb, qlist_cb, &state, NULL); tree_close(vdb); } } } else { if (state.do_binpkgs) vdb = tree_open_binpkg("/", pkgdir); else vdb = tree_open_vdb(portroot, portvdb); if (vdb != NULL) { ret = tree_foreach_pkg_sorted(vdb, qlist_cb, &state, NULL); tree_close(vdb); } } free(state.buf); for (i = optind; i < state.argc; ++i) if (state.atoms[i - optind]) atom_implode(state.atoms[i - optind]); free(state.atoms); /* The return value is whether we matched anything. */ return ret ? EXIT_SUCCESS : EXIT_FAILURE; }