/* * Copyright 2005-2021 Gentoo Foundation * Distributed under the terms of the GNU General Public License v2 * * 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 #include #include #include #include #include #include "set.h" #include "rmspace.h" #include "tree.h" #include "xarray.h" #include "xregex.h" #define QUSE_FLAGS "eaLDIp:RF:" COMMON_FLAGS static struct option const quse_long_opts[] = { {"exact", no_argument, NULL, 'e'}, {"all", no_argument, NULL, 'a'}, {"license", no_argument, NULL, 'L'}, {"describe", no_argument, NULL, 'D'}, {"installed", no_argument, NULL, 'I'}, {"package", a_argument, NULL, 'p'}, {"repo", no_argument, NULL, 'R'}, {"format", a_argument, NULL, 'F'}, COMMON_LONG_OPTS }; static const char * const quse_opts_help[] = { "Show exact non regexp matching using strcmp", "List all ebuilds, don't match anything", "Use the LICENSE vs IUSE", "Describe the USE flag", "Only search installed packages", "Restrict matching to package or category", "Show repository the ebuild originates from", "Print matched atom using given format string", COMMON_OPTS_HELP }; #define quse_usage(ret) usage(ret, QUSE_FLAGS, quse_long_opts, quse_opts_help, NULL, lookup_applet_idx("quse")) struct quse_state { int argc; char **argv; char **retv; const char *overlay; const char *repo; bool do_all:1; bool do_regex:1; bool do_describe:1; bool do_licence:1; bool do_installed:1; bool do_list:1; bool need_full_atom:1; depend_atom *match; regex_t *pregv; const char *fmt; }; static char *_quse_getline_buf = NULL; static size_t _quse_getline_buflen = 0; #define GETLINE(FD, BUF, LEN) \ LEN = getline(&_quse_getline_buf, &_quse_getline_buflen, FD); \ BUF = _quse_getline_buf static bool quse_search_use_local_desc(int portdirfd, struct quse_state *state) { int fd; FILE *f; ssize_t linelen; char *buf; char *p; char *q; int i; bool match = false; bool ret = false; depend_atom *atom; fd = openat(portdirfd, "profiles/use.local.desc", O_RDONLY | O_CLOEXEC); if (fd == -1) return false; f = fdopen(fd, "r"); if (f == NULL) { close(fd); return false; } /* use.local.desc: : - */ do { GETLINE(f, buf, linelen); if (linelen < 0) break; rmspace_len(buf, (size_t)linelen); if (buf[0] == '#' || buf[0] == '\0') continue; if ((p = strchr(buf, ':')) == NULL) continue; *p++ = '\0'; q = strchr(p, ' '); if (q == NULL || q[1] != '-') continue; *q = '\0'; q += 3; /* " - " */ match = false; for (i = 0; i < state->argc; i++) { if (state->do_list && state->retv[i] != NULL) continue; if (state->do_regex) { if (regexec(&state->pregv[i], p, 0, NULL, 0) != 0) continue; } else { if (strcmp(p, state->argv[i]) != 0) continue; } match = true; break; } if (match) { if ((atom = atom_explode(buf)) == NULL) continue; if (state->match == NULL || atom_compare_flg(atom, state->match, ATOM_COMP_NOSLOT | ATOM_COMP_NOREPO) == EQUAL) { if (state->do_list) { state->retv[i] = xstrdup(q); } else { atom->SLOT = NULL; /* reset fake slot */ printf("%s[%s%s%s] %s\n", atom_format(state->fmt, atom), MAGENTA, p, NORM, q); } } atom_implode(atom); ret = true; } } while (1); if (state->do_list && ret) { /* check if all requested flags are retrieved */ ret = true; for (i = 0; i < state->argc; i++) if (state->retv[i] == NULL) break; if (i < state->argc) ret = false; } fclose(f); return ret; } static bool quse_search_use_desc(int portdirfd, struct quse_state *state) { int fd; FILE *f; ssize_t linelen; char *buf; char *p; int i; bool match = false; bool ret = false; fd = openat(portdirfd, "profiles/use.desc", O_RDONLY | O_CLOEXEC); if (fd == -1) return false; f = fdopen(fd, "r"); if (f == NULL) { close(fd); return false; } /* use.desc: - */ do { GETLINE(f, buf, linelen); if (linelen < 0) break; rmspace_len(buf, (size_t)linelen); if (buf[0] == '#' || buf[0] == '\0') continue; p = strchr(buf, ' '); if (p == NULL || p[1] != '-') continue; *p = '\0'; p += 3; /* " - " */ match = false; for (i = 0; i < state->argc; i++) { if (state->do_list && state->retv[i] != NULL) continue; if (state->do_regex) { if (regexec(&state->pregv[i], buf, 0, NULL, 0) != 0) continue; } else { if (strcmp(buf, state->argv[i]) != 0) continue; } match = true; break; } if (match) { if (state->do_list) { state->retv[i] = xstrdup(p); } else { printf("%sglobal%s%s%s%s[%s%s%s] %s\n", BOLD, NORM, GREEN, state->repo == NULL ? "" : state->repo, NORM, MAGENTA, buf, NORM, p); } ret = true; } } while (1); if (state->do_list && ret) { /* check if all requested flags are retrieved */ ret = true; for (i = 0; i < state->argc; i++) if (state->retv[i] == NULL) break; if (i < state->argc) ret = false; } fclose(f); return ret; } static bool quse_search_profiles_desc( int portdirfd, struct quse_state *state) { int fd; FILE *f; ssize_t linelen; char *buf; char *p; int i; bool match = false; bool ret = false; size_t namelen; size_t arglen; char ubuf[_Q_PATH_MAX]; struct dirent *de; DIR *d; int dfd; fd = openat(portdirfd, "profiles/desc", O_RDONLY|O_CLOEXEC); if (fd == -1) return false; d = fdopendir(fd); if (!d) { close(fd); return false; } if (_quse_getline_buf == NULL) { _quse_getline_buflen = _Q_PATH_MAX; _quse_getline_buf = xmalloc(sizeof(char) * _quse_getline_buflen); } while ((de = readdir(d))) { if (de->d_name[0] == '.') continue; namelen = strlen(de->d_name); if (namelen <= 5 || strcmp(de->d_name + namelen - 5, ".desc") != 0) { ret = false; break; } snprintf(_quse_getline_buf, _quse_getline_buflen, "profiles/desc/%s", de->d_name); dfd = openat(portdirfd, _quse_getline_buf, O_RDONLY | O_CLOEXEC); if (dfd == -1) { ret = false; break; } f = fdopen(dfd, "r"); if (f == NULL) { ret = false; break; } /* remove trailing .desc */ namelen -= 5; /* use.desc: - */ do { GETLINE(f, buf, linelen); if (linelen < 0) break; rmspace_len(buf, (size_t)linelen); if (buf[0] == '#' || buf[0] == '\0') continue; p = strchr(buf, ' '); if (p == NULL || p[1] != '-') continue; *p = '\0'; p += 3; /* " - " */ match = false; for (i = 0; i < state->argc; i++) { if (state->do_list && state->retv[i] != NULL) continue; arglen = strlen(state->argv[i]); if (arglen > namelen) { /* nginx_modules_http_lua = NGINX_MODULES_HTTP[lua] */ match = strncmp(state->argv[i], de->d_name, namelen) == 0; if (match && state->argv[i][namelen] == '_') { match = strcmp(&state->argv[i][namelen + 1], buf) == 0; } else { match = false; } if (match) break; } if (state->do_regex) { if (regexec(&state->pregv[i], buf, 0, NULL, 0) != 0) continue; } else { if (strcmp(buf, state->argv[i]) != 0) continue; } match = true; break; } if (match) { if (state->do_list) { state->retv[i] = xstrdup(p); } else { const char *r = de->d_name; char *s = ubuf; do { *s++ = (char)toupper((int)*r); } while (++r < (de->d_name + namelen)); *s = '\0'; printf("%s%s%s%s%s%s[%s%s%s] %s\n", BOLD, ubuf, NORM, GREEN, state->repo == NULL ? "" : state->repo, NORM, MAGENTA, buf, NORM, p); } ret = true; } } while (1); fclose(f); } closedir(d); if (state->do_list && ret) { /* check if all requested flags are retrieved */ ret = true; for (i = 0; i < state->argc; i++) if (state->retv[i] == NULL) break; if (i < state->argc) ret = false; } return ret; } static bool quse_describe_flag(const char *root, const char *overlay, struct quse_state *state) { char buf[_Q_PATH_MAX]; int portdirfd; bool ret = false; snprintf(buf, sizeof(buf), "%s/%s", root, overlay); portdirfd = open(buf, O_RDONLY|O_CLOEXEC|O_PATH); if (portdirfd == -1) return false; ret |= quse_search_use_desc(portdirfd, state); ret |= quse_search_use_local_desc(portdirfd, state); ret |= quse_search_profiles_desc(portdirfd, state); close(portdirfd); return ret; } static int quse_results_cb(tree_pkg_ctx *pkg_ctx, void *priv) { struct quse_state *state = (struct quse_state *)priv; depend_atom *atom = NULL; /* pacify compiler */ char buf[8192]; set *use = NULL; bool match; char *p; char *q; char *s; char *v; char *w; int i; int len; int maxlen; int cnt; int portdirfd = -1; /* pacify compiler */ int ret = 0; if (state->match || state->do_describe) { atom = tree_get_atom(pkg_ctx, false); if (atom == NULL) return 0; } if (!state->do_licence) { if ((s = tree_pkg_meta_get(pkg_ctx, IUSE)) == NULL) return 0; if (state->do_describe) { portdirfd = openat(pkg_ctx->cat_ctx->ctx->portroot_fd, state->overlay == NULL ? main_overlay : state->overlay, O_RDONLY | O_CLOEXEC | O_PATH); if (portdirfd == -1) return 0; } /* available when dealing with VDB or binpkgs */ if ((p = tree_pkg_meta_get(pkg_ctx, USE)) != NULL) { while ((q = strchr(p, (int)' ')) != NULL) { *q++ = '\0'; use = add_set(p, use); p = q; } if (*p != '\0') use = add_set(p, use); } } else { if ((s = tree_pkg_meta_get(pkg_ctx, LICENSE)) == NULL) return 0; } maxlen = 0; cnt = 0; match = false; p = q = s; /* set to IUSE or LICENSE above */ buf[0] = '\0'; v = buf; w = buf + sizeof(buf); if (state->do_all && !state->do_describe) { match = true; v = q; } else { do { if (*p == ' ' || *p == '\0') { /* skip over consequtive whitespace */ if (p == q) { q++; continue; } s = q; if (*q == '-' || *q == '+' || *q == '@') q++; if (state->do_all) { i = 0; match = true; } else if (state->do_regex) { char r; for (i = 0; i < state->argc; i++) { r = *p; *p = '\0'; if (regexec(&state->pregv[i], q, 0, NULL, 0) == 0) { *p = r; v += snprintf(v, w - v, "%s%.*s%s%c", RED, (int)(p - s), s, NORM, *p); match = true; break; } *p = r; } } else { for (i = 0; i < state->argc; i++) { len = strlen(state->argv[i]); if (len == (int)(p - q) && strncmp(q, state->argv[i], len) == 0) { v += snprintf(v, w - v, "%s%.*s%s%c", RED, (int)(p - s), s, NORM, *p); match = true; break; } } } if (i == state->argc) v += snprintf(v, w - v, "%.*s%c", (int)(p - s), s, *p); if (maxlen < p - q) maxlen = p - q; cnt++; q = p + 1; } } while (*p++ != '\0' && v < w); v = buf; } if (match) { ret++; atom = tree_get_atom(pkg_ctx, state->need_full_atom); if (quiet) { printf("%s\n", atom_format(state->fmt, atom)); } else if (state->do_describe && !state->do_licence) { /* multi-line result, printing USE-flags with their descs */ size_t desclen; struct quse_state us = { .do_regex = false, .do_describe = false, .do_list = true, .match = atom, .repo = state->repo, .argc = cnt, .argv = xmalloc(sizeof(char *) * cnt), .retv = xzalloc(sizeof(char *) * cnt), .overlay = NULL, }; printf("%s\n", atom_format(state->fmt, atom)); q = p = tree_pkg_meta_get(pkg_ctx, IUSE); buf[0] = '\0'; v = buf; w = buf + sizeof(buf); i = 0; do { if (*p == ' ' || *p == '\0') { s = q; if (*q == '-' || *q == '+' || *q == '@') q++; /* pre-padd everything such that we always refer to * the char before the USE-flag */ us.argv[i++] = v + 1; v += snprintf(v, w - v, "%c%.*s", s == q ? ' ' : *s, (int)(p - q), q) + 1; q = p + 1; } } while (*p++ != '\0' && i < cnt && v < w); /* harvest descriptions for USE-flags */ if (!quse_search_use_local_desc(portdirfd, &us)) if (!quse_search_use_desc(portdirfd, &us)) quse_search_profiles_desc(portdirfd, &us); /* calculate available space in the terminal to print * descriptions, assume this makes sense from 10 chars */ if (twidth > maxlen + 2 + 1 + 2 + 10) { len = twidth - maxlen - 2 - 1 - 2; } else { len = 0; } for (i = 0; i < cnt; i++) { match = use != NULL && contains_set(us.argv[i], use); desclen = us.retv[i] != NULL ? strlen(us.retv[i]) : 0; p = NULL; if (desclen > (size_t)len) { /* need to wrap */ for (p = &us.retv[i][len]; p > us.retv[i]; p--) if (isspace((int)*p)) break; if (p > us.retv[i]) { *p++ = '\0'; desclen -= p - us.retv[i]; } else { p = NULL; } } printf(" %c%s%s%s%c%*s %s\n", us.argv[i][-1], match ? GREEN : MAGENTA, us.argv[i], NORM, match ? '*' : ' ', (int)(maxlen - strlen(us.argv[i])), "", us.retv[i] == NULL ? "" : us.retv[i]); while (p != NULL) { /* continue wrapped description */ q = p; p = NULL; if ((size_t)len < desclen) { for (p = q + len; p > q; p--) if (isspace((int)*p)) break; if (p > q) { *p++ = '\0'; desclen -= p - q; } else { p = NULL; } } printf(" %*s %s\n", maxlen, "", q); } if (us.retv[i] != NULL) free(us.retv[i]); } free(us.retv); free(us.argv); } else { printf("%s: %s\n", atom_format(state->fmt, atom), v); } } if (use != NULL) free_set(use); if (state->do_describe && !state->do_licence) close(portdirfd); return ret; } int quse_main(int argc, char **argv) { int i; int ret; size_t n; const char *overlay; char *match = NULL; struct quse_state state = { .do_all = false, .do_regex = true, .do_describe = false, .do_licence = false, .do_installed = false, .need_full_atom = false, .match = NULL, .overlay = NULL, .fmt = NULL, }; while ((i = GETOPT_LONG(QUSE, quse, "")) != -1) { switch (i) { case 'e': state.do_regex = false; break; case 'a': state.do_all = true; break; case 'L': state.do_licence = true; break; case 'D': state.do_describe = true; break; case 'I': state.do_installed = true; break; case 'p': match = optarg; break; case 'F': state.fmt = optarg; /* fall through */ case 'R': state.need_full_atom = true; break; COMMON_GETOPTS_CASES(quse) } } if (argc == optind && !state.do_all) { if (match != NULL) { /* default to printing everything if just package is given */ state.do_all = true; } else { quse_usage(EXIT_FAILURE); } } state.argc = argc - optind; state.argv = &argv[optind]; if (match != NULL) { state.match = atom_explode(match); if (state.match == NULL) errf("invalid atom: %s", match); } if (state.do_regex) { state.pregv = xmalloc(sizeof(state.pregv[0]) * state.argc); for (i = 0; i < state.argc; i++) xregcomp(&state.pregv[i], state.argv[i], REG_EXTENDED | REG_NOSUB); } if (state.fmt == NULL) { if (state.need_full_atom) if (verbose) state.fmt = "%[CATEGORY]%[PF]%[REPO]"; else state.fmt = "%[CATEGORY]%[PN]%[REPO]"; else if (verbose) state.fmt = "%[CATEGORY]%[PF]"; else state.fmt = "%[CATEGORY]%[PN]"; } ret = EXIT_FAILURE; if (state.do_describe && state.match == NULL) { array_for_each(overlays, n, overlay) { tree_ctx *t = NULL; if (state.need_full_atom) t = tree_open(portroot, overlay); /* used for repo */ if (t != NULL) state.repo = t->repo; if (quse_describe_flag(portroot, overlay, &state)) ret = EXIT_SUCCESS; if (t != NULL) tree_close(t); } } else if (state.do_installed) { tree_ctx *t = tree_open_vdb(portroot, portvdb); if (t != NULL) { state.overlay = NULL; state.repo = NULL; if (tree_foreach_pkg_sorted(t, quse_results_cb, &state, state.match) > 0) ret = EXIT_SUCCESS; tree_close(t); } } else { array_for_each(overlays, n, overlay) { tree_ctx *t = tree_open(portroot, overlay); state.overlay = overlay; if (t != NULL) { state.repo = state.need_full_atom ? t->repo : NULL; if (tree_foreach_pkg_sorted(t, quse_results_cb, &state, state.match) > 0) ret = EXIT_SUCCESS; tree_close(t); } } } if (state.do_regex) { for (i = 0; i < state.argc; i++) regfree(&state.pregv[i]); free(state.pregv); } if (state.match != NULL) atom_implode(state.match); return ret; }