Line data Source code
1 : /*-
2 : * Copyright (c) 2011-2012 Baptiste Daroussin <bapt@FreeBSD.org>
3 : * Copyright (c) 2011-2012 Julien Laffaye <jlaffaye@FreeBSD.org>
4 : * Copyright (c) 2011-2012 Marin Atanasov Nikolov <dnaeon@gmail.com>
5 : * Copyright (c) 2012-2013 Bryan Drewery <bdrewery@FreeBSD.org>
6 : * Copyright (c) 2014 Matthew Seaman <matthew@FreeBSD.org>
7 : * All rights reserved.
8 : *
9 : * Redistribution and use in source and binary forms, with or without
10 : * modification, are permitted provided that the following conditions
11 : * are met:
12 : * 1. Redistributions of source code must retain the above copyright
13 : * notice, this list of conditions and the following disclaimer
14 : * in this position and unchanged.
15 : * 2. Redistributions in binary form must reproduce the above copyright
16 : * notice, this list of conditions and the following disclaimer in the
17 : * documentation and/or other materials provided with the distribution.
18 : *
19 : * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
20 : * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21 : * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22 : * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
23 : * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
24 : * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 : * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 : * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 : * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28 : * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 : */
30 :
31 : #include <err.h>
32 : #include <getopt.h>
33 : #include <stdio.h>
34 : #include <string.h>
35 : #include <unistd.h>
36 : #include <sysexits.h>
37 :
38 :
39 : #include <pkg.h>
40 :
41 : #include "pkgcli.h"
42 :
43 : typedef struct _cliopt {
44 : const char *option;
45 : char key;
46 : } cliopt;
47 :
48 : /* an option string should not be a prefix of any other option string */
49 : static const cliopt search_label[] = {
50 : { "comment", 'c' },
51 : { "description", 'd' },
52 : { "name", 'n' },
53 : { "origin", 'o' },
54 : { "pkg-name", 'p' },
55 : { NULL, '\0' },
56 : };
57 :
58 : static const cliopt modifiers[] = {
59 : { "annotations", 'A' },
60 : { "arch", 'a' },
61 : { "categories", 'C' },
62 : { "comment", 'c' },
63 : { "depends-on", 'd' },
64 : { "description", 'D' },
65 : { "full", 'f' },
66 : { "licenses", 'l' },
67 : { "maintainer", 'm' },
68 : { "name", 'n' },
69 : { "options", 'o' },
70 : { "pkg-size", 'P' },
71 : { "prefix", 'p' },
72 : { "repository", 'R' },
73 : { "required-by", 'r' },
74 : { "shared-libs-required", 'B' },
75 : { "shared-libs-provided", 'b' },
76 : { "size", 's' },
77 : { "url", 'u' },
78 : { "version", 'v' },
79 : { "www", 'w' },
80 : { NULL, '\0' },
81 : };
82 :
83 : static char
84 2 : match_optarg(const cliopt *optlist, const char *opt)
85 : {
86 2 : int i, matched = -1;
87 2 : char key = '\0';
88 : size_t optlen;
89 :
90 2 : optlen = strlen(opt);
91 :
92 : /* Match any unique prefix from optlist */
93 28 : for (i = 0; optlist[i].option != NULL; i++) {
94 26 : if (strncmp(opt, optlist[i].option, optlen) != 0)
95 24 : continue;
96 2 : if (matched > 0) {
97 0 : warnx("\"%s\" is ambiguous. Was "
98 : "\"%s\" or \"%s\" meant?", opt,
99 0 : optlist[matched].option, optlist[i].option);
100 0 : key = '\0';
101 0 : break;
102 : }
103 2 : matched = i;
104 2 : key = optlist[i].key;
105 : }
106 2 : return (key);
107 : }
108 :
109 : static pkgdb_field
110 1 : search_label_opt(const char *optionarg)
111 : {
112 : pkgdb_field field;
113 :
114 : /* label options */
115 1 : switch(match_optarg(search_label, optionarg)) {
116 : case 'o':
117 0 : field = FIELD_ORIGIN;
118 0 : break;
119 : case 'n':
120 1 : field = FIELD_NAME;
121 1 : break;
122 : case 'p':
123 0 : field = FIELD_NAMEVER;
124 0 : break;
125 : case 'c':
126 0 : field = FIELD_COMMENT;
127 0 : break;
128 : case 'd':
129 0 : field = FIELD_DESC;
130 0 : break;
131 : default:
132 0 : usage_search();
133 0 : errx(EX_USAGE, "Unknown search/label option: %s", optionarg);
134 : /* NOTREACHED */
135 : }
136 1 : return field;
137 : }
138 :
139 : static unsigned int
140 1 : modifier_opt(const char *optionarg)
141 : {
142 : unsigned int opt;
143 :
144 : /* output modifiers */
145 1 : switch(match_optarg(modifiers, optionarg)) {
146 : case 'A':
147 0 : opt = INFO_ANNOTATIONS;
148 0 : break;
149 : case 'a':
150 0 : opt = INFO_ARCH;
151 0 : break;
152 : case 'C':
153 0 : opt = INFO_CATEGORIES;
154 0 : break;
155 : case 'c':
156 1 : opt = INFO_COMMENT;
157 1 : break;
158 : case 'd':
159 0 : opt = INFO_DEPS;
160 0 : break;
161 : case 'D':
162 0 : opt = INFO_DESCR;
163 0 : break;
164 : case 'f':
165 0 : opt = INFO_FULL;
166 0 : break;
167 : case 'l':
168 0 : opt = INFO_LICENSES;
169 0 : break;
170 : case 'm':
171 0 : opt = INFO_MAINTAINER;
172 0 : break;
173 : case 'n':
174 0 : opt = INFO_NAME;
175 0 : break;
176 : case 'o':
177 0 : opt = INFO_OPTIONS;
178 0 : break;
179 : case 'P':
180 0 : opt = INFO_PKGSIZE;
181 0 : break;
182 : case 'p':
183 0 : opt = INFO_PREFIX;
184 0 : break;
185 : case 'R':
186 0 : opt = INFO_REPOSITORY;
187 0 : break;
188 : case 'r':
189 0 : opt = INFO_RDEPS;
190 0 : break;
191 : case 'B':
192 0 : opt = INFO_SHLIBS_REQUIRED;
193 0 : break;
194 : case 'b':
195 0 : opt = INFO_SHLIBS_PROVIDED;
196 0 : break;
197 : case 's':
198 0 : opt = INFO_FLATSIZE;
199 0 : break;
200 : case 'u':
201 0 : opt = INFO_REPOURL;
202 0 : break;
203 : case 'v':
204 0 : opt = INFO_VERSION;
205 0 : break;
206 : case 'w':
207 0 : opt = INFO_WWW;
208 0 : break;
209 : default:
210 0 : usage_search();
211 0 : errx(EX_USAGE, "Unkown modifier option %s", optionarg);
212 : /* NOTREACHED */
213 : }
214 1 : return opt;
215 : }
216 :
217 : void
218 0 : usage_search(void)
219 : {
220 : int i, n;
221 :
222 0 : fprintf(stderr, "Usage: pkg search [-eU] [-r repo] [-S search] "
223 : "[-L label] [-Q mod]... [-Cgix] <pkg-name>\n");
224 0 : fprintf(stderr, " pkg search [-cDdefopqRU] [-r repo] "
225 : "[-Cgix] <pattern>\n\n");
226 0 : n = fprintf(stderr, " Search and Label options:");
227 0 : for (i = 0; search_label[i].option != NULL; i++) {
228 0 : if (n > 72)
229 0 : n = fprintf(stderr, "\n ");
230 0 : n += fprintf(stderr, " %s", search_label[i].option);
231 : }
232 0 : fprintf(stderr, "\n");
233 0 : n = fprintf(stderr, " Output Modifiers:");
234 0 : for (i = 0; modifiers[i].option != NULL; i++) {
235 0 : if (n > 68)
236 0 : n = fprintf(stderr, "\n ");
237 0 : n += fprintf(stderr, " %s", modifiers[i].option);
238 : }
239 0 : fprintf(stderr, "\n");
240 0 : fprintf(stderr, "For more information see 'pkg help search'.\n");
241 0 : }
242 :
243 : int
244 1 : exec_search(int argc, char **argv)
245 : {
246 1 : const char *pattern = NULL;
247 1 : const char *reponame = NULL;
248 1 : int ret = EPKG_OK, ch;
249 : int flags;
250 1 : uint64_t opt = 0;
251 1 : match_t match = MATCH_REGEX;
252 1 : pkgdb_field search = FIELD_NONE;
253 1 : pkgdb_field label = FIELD_NONE;
254 1 : struct pkgdb *db = NULL;
255 1 : struct pkgdb_it *it = NULL;
256 1 : struct pkg *pkg = NULL;
257 1 : bool atleastone = false;
258 : bool old_quiet;
259 :
260 1 : struct option longopts[] = {
261 : { "case-sensitive", no_argument, NULL, 'C' },
262 : { "comment", no_argument, NULL, 'c' },
263 : { "description", no_argument, NULL, 'D' },
264 : { "depends-on", no_argument, NULL, 'd' },
265 : { "exact", no_argument, NULL, 'e' },
266 : { "full", no_argument, NULL, 'f' },
267 : { "glob", no_argument, NULL, 'g' },
268 : { "case-insensitive", no_argument, NULL, 'i' },
269 : { "label", required_argument, NULL, 'L' },
270 : { "origins", no_argument, NULL, 'o' },
271 : { "prefix", no_argument, NULL, 'p' },
272 : { "quiet", no_argument, NULL, 'q' },
273 : { "query-modifier", required_argument, NULL, 'Q' },
274 : { "repository", required_argument, NULL, 'r' },
275 : { "raw", no_argument, NULL, 'R' },
276 : { "search", required_argument, NULL, 'S' },
277 : { "size", no_argument, NULL, 's' },
278 : { "no-repo-update", no_argument, NULL, 'U' },
279 : { "regex", no_argument, NULL, 'x' },
280 : { "raw-format", required_argument, NULL, 1 },
281 : { NULL, 0, NULL, 0 },
282 : };
283 :
284 5 : while ((ch = getopt_long(argc, argv, "+CcDdefgiL:opqQ:r:RS:sUx", longopts, NULL)) != -1) {
285 3 : switch (ch) {
286 : case 'C':
287 0 : pkgdb_set_case_sensitivity(true);
288 0 : break;
289 : case 'c': /* Same as -S comment */
290 0 : search = search_label_opt("comment");
291 0 : break;
292 : case 'D': /* Same as -S description */
293 0 : search = search_label_opt("description");
294 0 : break;
295 : case 'd': /* Same as -Q depends-on */
296 0 : opt |= modifier_opt("depends-on");
297 0 : break;
298 : case 'e':
299 1 : match = MATCH_EXACT;
300 1 : break;
301 : case 'f': /* Same as -Q full */
302 0 : opt |= modifier_opt("full");
303 0 : break;
304 : case 'g':
305 0 : match = MATCH_GLOB;
306 0 : break;
307 : case 'i':
308 0 : pkgdb_set_case_sensitivity(false);
309 0 : break;
310 : case 'L':
311 0 : label = search_label_opt(optarg);
312 0 : break;
313 : case 'o': /* Same as -L origin */
314 0 : label = search_label_opt("origin");
315 0 : break;
316 : case 'p': /* Same as -Q prefix */
317 0 : opt |= modifier_opt("prefix");
318 0 : break;
319 : case 'q':
320 0 : quiet = true;
321 0 : break;
322 : case 'Q':
323 1 : opt |= modifier_opt(optarg);
324 1 : break;
325 : case 'r':
326 0 : reponame = optarg;
327 0 : break;
328 : case 'R':
329 0 : opt = INFO_RAW;
330 0 : break;
331 : case 'S':
332 1 : search = search_label_opt(optarg);
333 1 : break;
334 : case 's': /* Same as -Q size */
335 0 : opt |= modifier_opt("size");
336 0 : break;
337 : case 'U':
338 0 : auto_update = false;
339 0 : break;
340 : case 'x':
341 0 : match = MATCH_REGEX;
342 0 : break;
343 : case 1:
344 0 : if (strcasecmp(optarg, "json") == 0)
345 0 : opt |= INFO_RAW_JSON;
346 0 : else if (strcasecmp(optarg, "json-compact") == 0)
347 0 : opt |= INFO_RAW_JSON_COMPACT;
348 0 : else if (strcasecmp(optarg, "yaml") == 0)
349 0 : opt |= INFO_RAW_YAML;
350 0 : else if (strcasecmp(optarg, "ucl") == 0)
351 0 : opt |= INFO_RAW_UCL;
352 : else
353 0 : errx(EX_USAGE, "Invalid format '%s' for the "
354 : "raw output, expecting json, json-compact "
355 : "or yaml", optarg);
356 0 : break;
357 : default:
358 0 : usage_search();
359 0 : return (EX_USAGE);
360 : }
361 : }
362 :
363 1 : argc -= optind;
364 1 : argv += optind;
365 :
366 1 : if (argc != 1) {
367 0 : usage_search();
368 0 : return (EX_USAGE);
369 : }
370 :
371 1 : pattern = argv[0];
372 1 : if (pattern[0] == '\0') {
373 0 : fprintf(stderr, "Pattern must not be empty.\n");
374 0 : return (EX_USAGE);
375 : }
376 1 : if (search == FIELD_NONE) {
377 0 : if (strchr(pattern, '/') != NULL)
378 0 : search = FIELD_ORIGIN;
379 : else
380 0 : search = FIELD_NAMEVER; /* Default search */
381 : }
382 1 : if (label == FIELD_NONE)
383 1 : label = search; /* By default, show what was searched */
384 :
385 1 : switch(label) {
386 : case FIELD_NONE:
387 0 : break; /* should never happen */
388 : case FIELD_ORIGIN:
389 0 : opt |= INFO_TAG_ORIGIN;
390 0 : break;
391 : case FIELD_NAME:
392 1 : opt |= INFO_TAG_NAME;
393 1 : break;
394 : case FIELD_NAMEVER:
395 0 : opt |= INFO_TAG_NAMEVER;
396 0 : break;
397 : case FIELD_COMMENT:
398 0 : opt |= INFO_TAG_NAMEVER|INFO_COMMENT;
399 0 : break;
400 : case FIELD_DESC:
401 0 : opt |= INFO_TAG_NAMEVER|INFO_DESCR;
402 0 : break;
403 : }
404 :
405 1 : ret = pkgdb_access(PKGDB_MODE_READ, PKGDB_DB_REPO);
406 1 : switch(ret) {
407 : case EPKG_ENOACCESS:
408 0 : warnx("Insufficient privileges to query the package database");
409 0 : return (EX_NOPERM);
410 : case EPKG_ENODB:
411 0 : if (!auto_update) {
412 0 : warnx("Unable to open remote repository catalogues. Try running '%s update' first.", getprogname());
413 0 : return (EX_IOERR);
414 : }
415 0 : break;
416 : case EPKG_OK:
417 1 : break;
418 : default:
419 0 : return (EX_IOERR);
420 : }
421 :
422 : /* first update the remote repositories if needed */
423 1 : old_quiet = quiet;
424 1 : quiet = true;
425 1 : if (auto_update && (ret = pkgcli_update(false, false, reponame)) != EPKG_OK)
426 1 : return (ret);
427 0 : quiet = old_quiet;
428 :
429 0 : if (pkgdb_open_all(&db, PKGDB_REMOTE, reponame) != EPKG_OK)
430 0 : return (EX_IOERR);
431 :
432 0 : if ((it = pkgdb_repo_search(db, pattern, match, search, search,
433 : reponame)) == NULL) {
434 0 : pkgdb_close(db);
435 0 : return (EX_IOERR);
436 : }
437 :
438 0 : if (opt & INFO_RAW) {
439 0 : if ((opt & (INFO_RAW_JSON|INFO_RAW_JSON_COMPACT)) == 0)
440 0 : opt |= INFO_RAW_YAML;
441 : }
442 :
443 0 : flags = info_flags(opt, true);
444 0 : while ((ret = pkgdb_it_next(it, &pkg, flags)) == EPKG_OK) {
445 0 : print_info(pkg, opt);
446 0 : atleastone = true;
447 : }
448 :
449 0 : pkg_free(pkg);
450 0 : pkgdb_it_free(it);
451 0 : pkgdb_close(db);
452 :
453 0 : if (!atleastone)
454 0 : ret = EPKG_FATAL;
455 :
456 0 : return ((ret == EPKG_OK || ret == EPKG_END) ? EX_OK : EX_SOFTWARE);
457 : }
|