search.c
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 <stdlib.h> 35 #include <string.h> 36 #include <unistd.h> 37 38 #include <pkg.h> 39 40 #include "pkgcli.h" 41 42 typedef struct _cliopt { 43 const char *option; 44 char key; 45 } cliopt; 46 47 /* an option string should not be a prefix of any other option string */ 48 static const cliopt search_label[] = { 49 { "comment", 'c' }, 50 { "comment-description", 'D' }, 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 match_optarg(const cliopt *optlist, const char *opt) 85 { 86 int i, matched = -1; 87 char key = '\0'; 88 size_t optlen; 89 90 optlen = strlen(opt); 91 92 /* Match any unique prefix from optlist */ 93 for (i = 0; optlist[i].option != NULL; i++) { 94 if (strncmp(opt, optlist[i].option, optlen) != 0) 95 continue; 96 /* Exact match: use it immediately */ 97 if (strlen(optlist[i].option) == optlen) { 98 matched = i; 99 key = optlist[i].key; 100 break; 101 } 102 if (matched >= 0) { 103 warnx("\"%s\" is ambiguous. Was " 104 "\"%s\" or \"%s\" meant?", opt, 105 optlist[matched].option, optlist[i].option); 106 key = '\0'; 107 break; 108 } 109 matched = i; 110 key = optlist[i].key; 111 } 112 return (key); 113 } 114 115 static pkgdb_field 116 search_label_opt(const char *optionarg) 117 { 118 pkgdb_field field; 119 120 /* label options */ 121 switch(match_optarg(search_label, optionarg)) { 122 case 'o': 123 field = FIELD_ORIGIN; 124 break; 125 case 'n': 126 field = FIELD_NAME; 127 break; 128 case 'p': 129 field = FIELD_NAMEVER; 130 break; 131 case 'c': 132 field = FIELD_COMMENT; 133 break; 134 case 'd': 135 field = FIELD_DESC; 136 break; 137 case 'D': 138 field = FIELD_COMMENT_DESC; 139 break; 140 default: 141 usage_search(); 142 errx(EXIT_FAILURE, "Unknown search/label option: %s", optionarg); 143 /* NOTREACHED */ 144 } 145 return field; 146 } 147 148 static unsigned int 149 modifier_opt(const char *optionarg) 150 { 151 unsigned int opt; 152 153 /* output modifiers */ 154 switch(match_optarg(modifiers, optionarg)) { 155 case 'A': 156 opt = INFO_ANNOTATIONS; 157 break; 158 case 'a': 159 opt = INFO_ARCH; 160 break; 161 case 'C': 162 opt = INFO_CATEGORIES; 163 break; 164 case 'c': 165 opt = INFO_COMMENT; 166 break; 167 case 'd': 168 opt = INFO_DEPS; 169 break; 170 case 'D': 171 opt = INFO_DESCR; 172 break; 173 case 'f': 174 opt = INFO_FULL; 175 break; 176 case 'l': 177 opt = INFO_LICENSES; 178 break; 179 case 'm': 180 opt = INFO_MAINTAINER; 181 break; 182 case 'n': 183 opt = INFO_NAME; 184 break; 185 case 'o': 186 opt = INFO_OPTIONS; 187 break; 188 case 'P': 189 opt = INFO_PKGSIZE; 190 break; 191 case 'p': 192 opt = INFO_PREFIX; 193 break; 194 case 'R': 195 opt = INFO_REPOSITORY; 196 break; 197 case 'r': 198 opt = INFO_RDEPS; 199 break; 200 case 'B': 201 opt = INFO_SHLIBS_REQUIRED; 202 break; 203 case 'b': 204 opt = INFO_SHLIBS_PROVIDED; 205 break; 206 case 's': 207 opt = INFO_FLATSIZE; 208 break; 209 case 'u': 210 opt = INFO_REPOURL; 211 break; 212 case 'v': 213 opt = INFO_VERSION; 214 break; 215 case 'w': 216 opt = INFO_WWW; 217 break; 218 default: 219 usage_search(); 220 errx(EXIT_FAILURE, "Unkown modifier option %s", optionarg); 221 /* NOTREACHED */ 222 } 223 return opt; 224 } 225 226 void 227 usage_search(void) 228 { 229 int i, n; 230 231 fprintf(stderr, "Usage: pkg search [-eU] [-r repo] [-S search] " 232 "[-L label] [-Q mod]... [-Cgix] <pkg-name>\n"); 233 fprintf(stderr, " pkg search [-cDdefopqRU] [-r repo] " 234 "[-Cgix] <pattern>\n\n"); 235 n = fprintf(stderr, " Search and Label options:"); 236 for (i = 0; search_label[i].option != NULL; i++) { 237 if (n > 72) 238 n = fprintf(stderr, "\n "); 239 n += fprintf(stderr, " %s", search_label[i].option); 240 } 241 fprintf(stderr, "\n"); 242 n = fprintf(stderr, " Output Modifiers:"); 243 for (i = 0; modifiers[i].option != NULL; i++) { 244 if (n > 68) 245 n = fprintf(stderr, "\n "); 246 n += fprintf(stderr, " %s", modifiers[i].option); 247 } 248 fprintf(stderr, "\n"); 249 fprintf(stderr, "For more information see 'pkg help search'.\n"); 250 } 251 252 int 253 exec_search(int argc, char **argv) 254 { 255 const char *pattern = NULL; 256 int ret = EPKG_OK, ch; 257 int flags; 258 uint64_t opt = 0; 259 match_t match = MATCH_REGEX; 260 pkgdb_field search = FIELD_NONE; 261 pkgdb_field label = FIELD_NONE; 262 struct pkgdb *db = NULL; 263 struct pkgdb_it *it = NULL; 264 struct pkg *pkg = NULL; 265 bool atleastone = false; 266 bool old_quiet; 267 c_charv_t reponames = vec_init(); 268 269 struct option longopts[] = { 270 { "case-sensitive", no_argument, NULL, 'C' }, 271 { "comment", no_argument, NULL, 'c' }, 272 { "description", no_argument, NULL, 'D' }, 273 { "depends-on", no_argument, NULL, 'd' }, 274 { "exact", no_argument, NULL, 'e' }, 275 { "full", no_argument, NULL, 'f' }, 276 { "glob", no_argument, NULL, 'g' }, 277 { "case-insensitive", no_argument, NULL, 'i' }, 278 { "label", required_argument, NULL, 'L' }, 279 { "origins", no_argument, NULL, 'o' }, 280 { "prefix", no_argument, NULL, 'p' }, 281 { "quiet", no_argument, NULL, 'q' }, 282 { "query-modifier", required_argument, NULL, 'Q' }, 283 { "repository", required_argument, NULL, 'r' }, 284 { "raw", no_argument, NULL, 'R' }, 285 { "search", required_argument, NULL, 'S' }, 286 { "size", no_argument, NULL, 's' }, 287 { "no-repo-update", no_argument, NULL, 'U' }, 288 { "regex", no_argument, NULL, 'x' }, 289 { "raw-format", required_argument, NULL, 1 }, 290 { NULL, 0, NULL, 0 }, 291 }; 292 293 while ((ch = getopt_long(argc, argv, "+CcDdefgiL:opqQ:r:RS:sUx", longopts, NULL)) != -1) { 294 switch (ch) { 295 case 'C': 296 pkgdb_set_case_sensitivity(true); 297 break; 298 case 'c': /* Same as -S comment */ 299 search = search_label_opt("comment"); 300 break; 301 case 'D': /* Same as -S description */ 302 search = search_label_opt("description"); 303 break; 304 case 'd': /* Same as -Q depends-on */ 305 opt |= modifier_opt("depends-on"); 306 break; 307 case 'e': 308 match = MATCH_EXACT; 309 break; 310 case 'f': /* Same as -Q full */ 311 opt |= modifier_opt("full"); 312 break; 313 case 'g': 314 match = MATCH_GLOB; 315 break; 316 case 'i': 317 pkgdb_set_case_sensitivity(false); 318 break; 319 case 'L': 320 label = search_label_opt(optarg); 321 break; 322 case 'o': /* Same as -L origin */ 323 label = search_label_opt("origin"); 324 break; 325 case 'p': /* Same as -Q prefix */ 326 opt |= modifier_opt("prefix"); 327 break; 328 case 'q': 329 quiet = true; 330 break; 331 case 'Q': 332 opt |= modifier_opt(optarg); 333 break; 334 case 'r': 335 vec_push(&reponames, optarg); 336 break; 337 case 'R': 338 opt = INFO_RAW; 339 break; 340 case 'S': 341 search = search_label_opt(optarg); 342 break; 343 case 's': /* Same as -Q size */ 344 opt |= modifier_opt("size"); 345 break; 346 case 'U': 347 auto_update = false; 348 break; 349 case 'x': 350 match = MATCH_REGEX; 351 break; 352 case 1: 353 if (STRIEQ(optarg, "json")) 354 opt |= INFO_RAW_JSON; 355 else if (STRIEQ(optarg, "json-compact")) 356 opt |= INFO_RAW_JSON_COMPACT; 357 else if (STRIEQ(optarg, "yaml")) 358 opt |= INFO_RAW_YAML; 359 else if (STRIEQ(optarg, "ucl")) 360 opt |= INFO_RAW_UCL; 361 else 362 errx(EXIT_FAILURE, "Invalid format '%s' for the " 363 "raw output, expecting json, json-compact " 364 "or yaml", optarg); 365 break; 366 default: 367 usage_search(); 368 return (EXIT_FAILURE); 369 } 370 } 371 372 argc -= optind; 373 argv += optind; 374 375 if (argc != 1) { 376 usage_search(); 377 return (EXIT_FAILURE); 378 } 379 380 pattern = argv[0]; 381 if (pattern[0] == '\0') { 382 fprintf(stderr, "Pattern must not be empty.\n"); 383 return (EXIT_FAILURE); 384 } 385 if (search == FIELD_NONE) { 386 if (strchr(pattern, '/') != NULL) { 387 if (strchr(pattern, '@') != NULL) 388 search = FIELD_FLAVOR; 389 else 390 search = FIELD_ORIGIN; 391 } else 392 search = FIELD_NAMEVER; /* Default search */ 393 } 394 if (label == FIELD_NONE) 395 label = search; /* By default, show what was searched */ 396 397 switch(label) { 398 case FIELD_NONE: 399 break; /* should never happen */ 400 case FIELD_ORIGIN: 401 if (quiet) { 402 opt = INFO_TAG_ORIGIN; 403 quiet = false; 404 } else { 405 opt |= INFO_TAG_ORIGIN|INFO_COMMENT; 406 } 407 break; 408 case FIELD_FLAVOR: 409 case FIELD_NAME: 410 opt |= INFO_TAG_NAME|INFO_COMMENT; 411 break; 412 case FIELD_NAMEVER: 413 opt |= INFO_TAG_NAMEVER|INFO_COMMENT; 414 break; 415 case FIELD_COMMENT: 416 opt |= INFO_TAG_NAMEVER|INFO_COMMENT; 417 break; 418 case FIELD_DESC: 419 opt |= INFO_TAG_NAMEVER|INFO_DESCR; 420 break; 421 case FIELD_COMMENT_DESC: 422 opt |= INFO_TAG_NAMEVER|INFO_COMMENT|INFO_DESCR; 423 break; 424 } 425 426 if (quiet) { 427 opt = INFO_TAG_NAMEVER; 428 quiet = false; 429 } 430 431 ret = pkgdb_access2(PKGDB_MODE_READ, PKGDB_DB_REPO, &reponames); 432 switch(ret) { 433 case EPKG_ENOACCESS: 434 warnx("Insufficient privileges to query the package database"); 435 return (EXIT_FAILURE); 436 case EPKG_ENODB: 437 if (!auto_update) { 438 warnx("Unable to open remote repository catalogues. Try running '%s update' first.", getprogname()); 439 return (EXIT_FAILURE); 440 } 441 break; 442 case EPKG_OK: 443 break; 444 default: 445 return (EXIT_FAILURE); 446 } 447 448 /* first update the remote repositories if needed */ 449 old_quiet = quiet; 450 quiet = true; 451 if (auto_update && (ret = pkgcli_update(false, false, &reponames)) != EPKG_OK) 452 return (ret); 453 quiet = old_quiet; 454 455 if (pkgdb_open_all2(&db, PKGDB_REMOTE, &reponames) != EPKG_OK) 456 return (EXIT_FAILURE); 457 458 if ((it = pkgdb_repo_search2(db, pattern, match, search, search, 459 &reponames)) == NULL) { 460 pkgdb_close(db); 461 return (EXIT_FAILURE); 462 } 463 464 if (opt & INFO_RAW) { 465 if ((opt & (INFO_RAW_JSON|INFO_RAW_JSON_COMPACT|INFO_RAW_UCL)) == 0) 466 opt |= INFO_RAW_YAML; 467 } 468 469 flags = info_flags(opt, true); 470 if ((opt & INFO_RAW) && 471 (opt & (INFO_RAW_JSON | INFO_RAW_JSON_COMPACT))) { 472 printf("["); 473 } 474 while ((ret = pkgdb_it_next(it, &pkg, flags)) == EPKG_OK) { 475 if ((opt & INFO_RAW) && 476 (opt & (INFO_RAW_JSON | INFO_RAW_JSON_COMPACT)) && 477 (atleastone)) { 478 printf(","); 479 } 480 print_info(NULL, pkg, opt); 481 atleastone = true; 482 } 483 if ((opt & INFO_RAW) && 484 (opt & (INFO_RAW_JSON | INFO_RAW_JSON_COMPACT))) { 485 printf("]\n"); 486 } 487 488 pkg_free(pkg); 489 pkgdb_it_free(it); 490 pkgdb_close(db); 491 492 if (!atleastone) 493 ret = EPKG_FATAL; 494 495 return ((ret == EPKG_OK || ret == EPKG_END) ? EXIT_SUCCESS : EXIT_FAILURE); 496 }