/ src / version.c
version.c
   1  /*-
   2   * Copyright (c) 2011-2023 Baptiste Daroussin <bapt@FreeBSD.org>
   3   * Copyright (c) 2011-2012 Julien Laffaye <jlaffaye@FreeBSD.org>
   4   * Copyright (c) 2011 Philippe Pepiot <phil@philpep.org>
   5   * Copyright (c) 2011-2012 Marin Atanasov Nikolov <dnaeon@gmail.com>
   6   * Copyright (c) 2012 Bryan Drewery <bryan@shatow.net>
   7   * Copyright (c) 2013-2014 Matthew Seaman <matthew@FreeBSD.org>
   8   * All rights reserved.
   9   *
  10   * Redistribution and use in source and binary forms, with or without
  11   * modification, are permitted provided that the following conditions
  12   * are met:
  13   * 1. Redistributions of source code must retain the above copyright
  14   *    notice, this list of conditions and the following disclaimer
  15   *    in this position and unchanged.
  16   * 2. Redistributions in binary form must reproduce the above copyright
  17   *    notice, this list of conditions and the following disclaimer in the
  18   *    documentation and/or other materials provided with the distribution.
  19   *
  20   * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
  21   * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  22   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  23   * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
  24   * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  25   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  26   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  27   * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  28   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  29   * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  30   */
  31  
  32  #include <sys/param.h>
  33  #include <sys/utsname.h>
  34  #include <sys/wait.h>
  35  
  36  #include <err.h>
  37  #include <errno.h>
  38  #include <getopt.h>
  39  #include <fcntl.h>
  40  #include <pkg.h>
  41  #include <stdbool.h>
  42  #include <stdio.h>
  43  #include <stdlib.h>
  44  #include <string.h>
  45  #include <unistd.h>
  46  #include <fnmatch.h>
  47  #include <spawn.h>
  48  #include <sys/types.h>
  49  #include <sys/stat.h>
  50  #include <pkghash.h>
  51  #include <xmalloc.h>
  52  
  53  #include "pkgcli.h"
  54  
  55  extern char **environ;
  56  
  57  struct index_entry {
  58  	char *name;
  59  	char *version;
  60  };
  61  
  62  struct category {
  63  	char *name;
  64  	pkghash *ports;
  65  };
  66  
  67  pkghash *categories = NULL;
  68  
  69  void
  70  usage_version(void)
  71  {
  72  	fprintf(stderr, "Usage: pkg version [-IPR] [-hoqvU] [-l limchar] [-L limchar] [-Cegix pattern]\n");
  73  	fprintf(stderr, "		    [-r reponame] [-O origin|-n pkgname] [index]\n");
  74  	fprintf(stderr, "	pkg version -t <version1> <version2>\n");
  75  	fprintf(stderr, "	pkg version -T <pkgname> <pattern>\n\n");
  76  	fprintf(stderr, "For more information see 'pkg help version'.\n");
  77  }
  78  
  79  static void
  80  print_version(struct pkg *pkg, const char *source, const char *ver,
  81  	      char limchar, unsigned int opt)
  82  {
  83  	char		 key;
  84  	const char	*version = NULL;
  85  	int		 cout;
  86  
  87  	pkg_get(pkg, PKG_ATTR_VERSION, &version);
  88  	if (ver == NULL) {
  89  		if (source == NULL)
  90  			key = '!';
  91  		else
  92  			key = '?';
  93  	} else {
  94  		switch (pkg_version_cmp(version, ver)) {
  95  		case -1:
  96  			key = '<';
  97  			break;
  98  		case 0:
  99  			key = '=';
 100  			break;
 101  		case 1:
 102  			key = '>';
 103  			break;
 104  		default:
 105  			key = '!';
 106  			break;
 107  		}
 108  	}
 109  
 110  	if ((opt & VERSION_STATUS) && limchar != key)
 111  		return;
 112  
 113  	if ((opt & VERSION_NOSTATUS) && limchar == key)
 114  		return;
 115  
 116  	if (opt & VERSION_ORIGIN) {
 117  		pkg_printf("%-34o", pkg);
 118  		printf("%c", key);
 119  	}
 120  	else {
 121  		cout = pkg_printf("%n-%v", pkg, pkg);
 122  		cout = 35 - cout;
 123  		if (cout < 1)
 124  			cout = 1;
 125  		printf("%*s%c", cout, " ", key);
 126  	}
 127  
 128  	if (opt & VERSION_VERBOSE) {
 129  		switch (key) {
 130  		case '<':
 131  			printf("   needs updating (%s has %s)", source, ver);
 132  			break;
 133  		case '=':
 134  			printf("   up-to-date with %s", source);
 135  			break;
 136  		case '>':
 137  			printf("   succeeds %s (%s has %s)", source, source, ver);
 138  			break;
 139  		case '?':
 140  			pkg_printf("   orphaned: %o", pkg);
 141  			break;
 142  		case '!':
 143  		default:
 144  			printf("   Comparison failed");
 145  			break;
 146  		}
 147  	}
 148  
 149  	putchar('\n');
 150  }
 151  
 152  static int
 153  do_testversion(unsigned int opt, int argc, char ** restrict argv)
 154  {
 155  	/* -t must be unique and takes two arguments */
 156  	if ( opt != VERSION_TESTVERSION || argc < 2 ) {
 157  		usage_version();
 158  		return (EXIT_FAILURE);
 159  	}
 160  
 161  	switch (pkg_version_cmp(argv[0], argv[1])) {
 162  	case -1:
 163  		printf("<\n");
 164  		break;
 165  	case 0:
 166  		printf("=\n");
 167  		break;
 168  	case 1:
 169  		printf(">\n");
 170  		break;
 171  	}
 172  
 173  	return (EXIT_SUCCESS);
 174  }
 175  
 176  static int
 177  do_testpattern(unsigned int opt, int argc, char ** restrict argv)
 178  {
 179  	bool	 pattern_from_stdin = false;
 180  	bool	 pkgname_from_stdin = false;
 181  	char	*line = NULL;
 182  	size_t	 linecap = 0;
 183  	ssize_t	 linelen;
 184  	int	 retval = FNM_NOMATCH;
 185  
 186  	/* -T must be unique and takes two arguments */
 187  	if ( opt != VERSION_TESTPATTERN || argc < 2 ) {
 188  		usage_version();
 189  		return (EXIT_FAILURE);
 190  	}
 191  
 192  	if (argv[0][0] == '-')
 193  		pattern_from_stdin = true;
 194  
 195  	if (argv[1][0] == '-')
 196  		pkgname_from_stdin = true;
 197  
 198  	if (pattern_from_stdin && pkgname_from_stdin) {
 199  		usage_version();
 200  		return (EXIT_FAILURE);
 201  	}
 202  
 203  	if (!pattern_from_stdin && !pkgname_from_stdin)
 204  		return (fnmatch(argv[1], argv[0], 0));
 205  
 206  	while ((linelen = getline(&line, &linecap, stdin)) > 0) {
 207  		line[linelen - 1] = '\0'; /* Strip trailing newline */
 208  
 209  		if ((pattern_from_stdin && (fnmatch(argv[1], line, 0) == 0)) ||
 210  		    (pkgname_from_stdin && (fnmatch(line, argv[0], 0) == 0))) {
 211  			retval = EPKG_OK;
 212  			printf("%.*s\n", (int)linelen, line);
 213  		}
 214  	}
 215  
 216  	free(line);
 217  
 218  	return (retval);
 219  }
 220  
 221  static bool
 222  have_ports(const char **portsdir, bool show_error)
 223  {
 224  	char		 portsdirmakefile[MAXPATHLEN];
 225  	struct stat	 sb;
 226  	bool		 have_ports;
 227  
 228  	/* Look for Makefile within $PORTSDIR as indicative of
 229  	 * installed ports tree. */
 230  
 231  	*portsdir = pkg_object_string(pkg_config_get("PORTSDIR"));
 232  	if (*portsdir == NULL)
 233  		err(1, "Cannot get portsdir config entry!");
 234  
 235  	snprintf(portsdirmakefile, sizeof(portsdirmakefile),
 236  		 "%s/Makefile", *portsdir);
 237  
 238  	have_ports = (stat(portsdirmakefile, &sb) == 0 && S_ISREG(sb.st_mode));
 239  
 240  	if (show_error && !have_ports)
 241  		warnx("Cannot find ports tree: unable to open %s",
 242  		      portsdirmakefile);
 243  
 244  	return (have_ports);
 245  }
 246  
 247  static const char*
 248  indexfilename(char *filebuf, size_t filebuflen)
 249  {
 250  	const char	*indexdir;
 251  	const char	*indexfile;
 252  
 253  	/* Construct the canonical name of the indexfile from the
 254  	 * ports directory and the major version number of the OS.
 255  	 * Overridden by INDEXDIR and INDEXFILE if defined. (Mimics
 256  	 * the behaviour of ${PORTSDIR}/Makefile) */
 257  
 258  	indexdir = pkg_object_string(pkg_config_get("INDEXDIR"));
 259  	if (indexdir == NULL) {
 260  		indexdir = pkg_object_string(pkg_config_get("PORTSDIR"));
 261  
 262  		if (indexdir == NULL)
 263  			err(EXIT_FAILURE, "Cannot get either INDEXDIR or "
 264  			    "PORTSDIR config entry!");
 265  	}
 266  
 267  	indexfile = pkg_object_string(pkg_config_get("INDEXFILE"));
 268  	if (indexfile == NULL)
 269  		err(EXIT_FAILURE, "Cannot get INDEXFILE config entry!");
 270  
 271  	strlcpy(filebuf, indexdir, filebuflen);
 272  
 273  	if (filebuf[0] != '\0' && filebuf[strlen(filebuf) - 1] != '/')
 274  		strlcat(filebuf, "/", filebuflen);
 275  
 276  	strlcat(filebuf, indexfile, filebuflen);
 277  
 278  	return (filebuf);
 279  }
 280  
 281  static pkghash *
 282  hash_indexfile(const char *indexfilename)
 283  {
 284  	FILE			*indexfile;
 285  	pkghash			*index = NULL;
 286  	struct index_entry	*entry;
 287  	char			*version, *name;
 288  	char			*line = NULL, *l;
 289  	size_t			 linecap = 0;
 290  
 291  
 292  	/* Create a hash table of all the package names and port
 293  	 * directories from the index file. */
 294  
 295  	indexfile = fopen(indexfilename, "re");
 296  	if (!indexfile)
 297  		err(EXIT_FAILURE, "Unable to open %s", indexfilename);
 298  
 299  	while (getline(&line, &linecap, indexfile) > 0) {
 300  		/* line is pkgname|portdir|... */
 301  
 302  		l = line;
 303  
 304  		version = strsep(&l, "|");
 305  		name = version;
 306  		version = strrchr(version, '-');
 307  		if (version == NULL)
 308  			errx(EXIT_FAILURE, "Invalid INDEX file format: %s",
 309  			    indexfilename);
 310  		version[0] = '\0';
 311  		version++;
 312  
 313  		entry = xmalloc(sizeof(struct index_entry));
 314  		entry->name = xstrdup(name);
 315  		entry->version = xstrdup(version);
 316  
 317  		if (index == NULL)
 318  			index = pkghash_new();
 319  
 320  		if (!pkghash_add(index, entry->name, entry, NULL)) {
 321  			free(entry->version);
 322  			free(entry->name);
 323  			free(entry);
 324  		}
 325  	}
 326  
 327  	free(line);
 328  	fclose(indexfile);
 329  
 330  	if (index == NULL)
 331  		errx(EXIT_FAILURE, "No valid entries found in '%s'",
 332  		    indexfilename);
 333  
 334  	return (index);
 335  }
 336  
 337  static void
 338  free_categories(void)
 339  {
 340  	struct category *cat;
 341  	pkghash_it it;
 342  
 343  	it = pkghash_iterator(categories);
 344  	while (pkghash_next(&it)) {
 345  		cat = (struct category *) it.value;
 346  		free(cat->name);
 347  		pkghash_destroy(cat->ports);
 348  		free(cat);
 349  	}
 350  	pkghash_destroy(categories);
 351  }
 352  
 353  static void
 354  free_index(pkghash *index)
 355  {
 356  	pkghash_it it;
 357  	struct index_entry *entry;
 358  
 359  	it = pkghash_iterator(index);
 360  	while (pkghash_next(&it)) {
 361  		entry = (struct index_entry *)it.value;
 362  		free(entry->version);
 363  		free(entry->name);
 364  		free(entry);
 365  	}
 366  	pkghash_destroy(index);
 367  }
 368  
 369  static bool
 370  have_indexfile(const char **indexfile, char *filebuf, size_t filebuflen,
 371  	       int argc, char ** restrict argv, bool show_error)
 372  {
 373  	bool		have_indexfile = true;
 374  	struct stat	sb;
 375  
 376  	/* If there is a remaining command line argument, take
 377  	   that as the name of the INDEX file to use.  Otherwise,
 378  	   search for INDEX-N within the ports tree */
 379  
 380  	if (argc == 0)
 381  		*indexfile = indexfilename(filebuf, filebuflen);
 382  	else
 383  		*indexfile = argv[0];
 384  
 385  	if (stat(*indexfile, &sb) == -1) {
 386  		if (errno == ENOENT)
 387  			have_indexfile = false;
 388  		else
 389  			warn("Failed to get stat for the INDEX file!");
 390  	}
 391  
 392  	if (show_error && !have_indexfile)
 393  		warn("Can't access %s", *indexfile);
 394  
 395  	return (have_indexfile);
 396  }
 397  
 398  static int
 399  do_source_index(unsigned int opt, char limchar, char *pattern, match_t match,
 400      const char *matchorigin, const char *matchname, const char *indexfile)
 401  {
 402  	pkghash		*index;
 403  	struct index_entry *ie;
 404  	struct pkgdb	*db = NULL;
 405  	struct pkgdb_it	*it = NULL;
 406  	struct pkg	*pkg = NULL;
 407  	const char	*name = NULL;
 408  	const char	*origin = NULL;
 409  	bool		gotnone = true;
 410  
 411  	if ( (opt & VERSION_SOURCES) != VERSION_SOURCE_INDEX) {
 412  		usage_version();
 413  		return (EXIT_FAILURE);
 414  	}
 415  
 416  	if (pkgdb_open(&db, PKGDB_DEFAULT_READONLY) != EPKG_OK)
 417  		return (EXIT_FAILURE);
 418  
 419  	index = hash_indexfile(indexfile);
 420  
 421  	if (pkgdb_obtain_lock(db, PKGDB_LOCK_READONLY) != EPKG_OK) {
 422  		pkgdb_close(db);
 423  		free_index(index);
 424  		warnx("Cannot get a read lock on the database. "
 425  		      "It is locked by another process");
 426  		return (EXIT_FAILURE);
 427  	}
 428  
 429  	it = pkgdb_query(db, pattern, match);
 430  	if (it == NULL)
 431  		goto cleanup;
 432  
 433  	while (pkgdb_it_next(it, &pkg, PKG_LOAD_BASIC) == EPKG_OK) {
 434  		pkg_get(pkg, PKG_ATTR_NAME, &name);
 435  		pkg_get(pkg, PKG_ATTR_ORIGIN, &origin);
 436  
 437  		/* If -O was specified, check if this origin matches */
 438  		if ((opt & VERSION_WITHORIGIN) &&
 439  		    !STREQ(origin, matchorigin))
 440  			continue;
 441  
 442  		/* If -n was specified, check if this name matches */
 443  		if ((opt & VERSION_WITHNAME) &&
 444  		    !STREQ(name, matchname))
 445  			continue;
 446  
 447  		ie = pkghash_get_value(index, name);
 448  		print_version(pkg, "index", ie != NULL ? ie->version : NULL,
 449  		    limchar, opt);
 450  
 451  		/* If we reach here, it means at least one package
 452  		   has matched with our query. */
 453  		gotnone = false;
 454  	}
 455  
 456  cleanup:
 457  	pkgdb_release_lock(db, PKGDB_LOCK_READONLY);
 458  	free_index(index);
 459  	pkg_free(pkg);
 460  	pkgdb_it_free(it);
 461  	pkgdb_close(db);
 462  
 463  	return (gotnone);
 464  }
 465  
 466  static int
 467  do_source_remote(unsigned int opt, char limchar, char *pattern, match_t match,
 468      bool auto_update, c_charv_t *reponames, const char *matchorigin,
 469      const char *matchname)
 470  {
 471  	struct pkgdb	*db = NULL;
 472  	struct pkgdb_it	*it = NULL;
 473  	struct pkgdb_it	*it_remote = NULL;
 474  	struct pkg	*pkg = NULL;
 475  	struct pkg	*pkg_remote = NULL;
 476  	const char	*name = NULL;
 477  	const char	*origin = NULL;
 478  	const char	*version_remote = NULL;
 479  	bool		is_origin = false;
 480  	int		retcode = EXIT_FAILURE;
 481  
 482  	if ( (opt & VERSION_SOURCES) != VERSION_SOURCE_REMOTE ) {
 483  		usage_version();
 484  		return (EXIT_FAILURE);
 485  	}
 486  
 487  	/* Only force remote mode if looking up remote, otherwise
 488  	   user is forced to have a repo.sqlite */
 489  
 490  	if (auto_update) {
 491  		retcode = pkgcli_update(false, false, reponames);
 492  		if (retcode != EPKG_OK)
 493  			return (retcode);
 494  		else
 495  			retcode = EXIT_FAILURE;
 496  	}
 497  
 498  	if (pkgdb_open_all2(&db, PKGDB_REMOTE, reponames) != EPKG_OK)
 499  		return (EXIT_FAILURE);
 500  
 501  	if (pkgdb_obtain_lock(db, PKGDB_LOCK_READONLY) != EPKG_OK) {
 502  		pkgdb_close(db);
 503  		warnx("Cannot get a read lock on a database. "
 504  		      "It is locked by another process");
 505  		return (EXIT_FAILURE);
 506  	}
 507  
 508  	it = pkgdb_query(db, pattern, match);
 509  	if (it == NULL)
 510  		goto cleanup;
 511  
 512  	while (pkgdb_it_next(it, &pkg, PKG_LOAD_BASIC) == EPKG_OK) {
 513  		pkg_get(pkg, PKG_ATTR_NAME, &name);
 514  		pkg_get(pkg, PKG_ATTR_ORIGIN, &origin);
 515  
 516  		/* If -O was specified, check if this origin matches */
 517  		if ((opt & VERSION_WITHORIGIN) &&
 518  		    !STREQ(origin, matchorigin)) {
 519  		    	is_origin = true;
 520  			continue;
 521  		}
 522  
 523  		/* If -n was specified, check if this name matches */
 524  		if ((opt & VERSION_WITHNAME) &&
 525  		    !STREQ(name, matchname)) {
 526  		    	is_origin = false;
 527  			continue;
 528  		}
 529  
 530  		it_remote = pkgdb_repo_query2(db, is_origin ? origin : name, MATCH_EXACT, reponames);
 531  		if (it_remote == NULL) {
 532  			retcode = EXIT_FAILURE;
 533  			goto cleanup;
 534  		}
 535  
 536  		if (pkgdb_it_next(it_remote, &pkg_remote, PKG_LOAD_BASIC)
 537  		    == EPKG_OK) {
 538  			pkg_get(pkg_remote, PKG_ATTR_VERSION, &version_remote);
 539  			print_version(pkg, "remote", version_remote, limchar,
 540  			    opt);
 541  		} else {
 542  			print_version(pkg, "remote", NULL, limchar, opt);
 543  		}
 544  		pkgdb_it_free(it_remote);
 545  
 546  		/* If we reach here, it means at least one package
 547  		   has matched with our query. */
 548  		retcode = EXIT_SUCCESS;
 549  	}
 550  
 551  cleanup:
 552  	pkgdb_release_lock(db, PKGDB_LOCK_READONLY);
 553  
 554  	pkg_free(pkg);
 555  	pkg_free(pkg_remote);
 556  	pkgdb_it_free(it);
 557  	pkgdb_close(db);
 558  
 559  	return (retcode);
 560  }
 561  
 562  static int
 563  exec_buf(xstring *res, char **argv) {
 564  	char buf[BUFSIZ];
 565  	int spawn_err;
 566  	pid_t pid;
 567  	int pfd[2];
 568  	int r, pstat;
 569  	posix_spawn_file_actions_t actions;
 570  
 571  	if (pipe(pfd) < 0) {
 572  		warn("pipe()");
 573  		return (0);
 574  	}
 575  
 576  	if ((spawn_err = posix_spawn_file_actions_init(&actions)) != 0) {
 577  		warnx("%s:%s", argv[0], strerror(spawn_err));
 578  		return (0);
 579  	}
 580  
 581  	if ((spawn_err = posix_spawn_file_actions_addopen(&actions,
 582  	    STDERR_FILENO, "/dev/null", O_RDWR, 0)) != 0 ||
 583  	    (spawn_err = posix_spawn_file_actions_addopen(&actions,
 584  	    STDIN_FILENO, "/dev/null", O_RDONLY, 0)) != 0 ||
 585  	    (spawn_err = posix_spawn_file_actions_adddup2(&actions,
 586  	    pfd[1], STDOUT_FILENO)!= 0) ||
 587  	    (spawn_err = posix_spawnp(&pid, argv[0], &actions, NULL,
 588  	    argv, environ)) != 0) {
 589  		posix_spawn_file_actions_destroy(&actions);
 590  		warnx("%s:%s", argv[0], strerror(spawn_err));
 591  		return (0);
 592  	}
 593  	posix_spawn_file_actions_destroy(&actions);
 594  
 595  	close(pfd[1]);
 596  
 597  	xstring_reset(res);
 598  	while ((r = read(pfd[0], buf, BUFSIZ)) > 0)
 599  		fwrite(buf, sizeof(char), r, res->fp);
 600  
 601  	close(pfd[0]);
 602  	while (waitpid(pid, &pstat, 0) == -1) {
 603  		if (errno != EINTR)
 604  			return (-1);
 605  	}
 606  	if (WEXITSTATUS(pstat) != 0)
 607  		return (-1);
 608  
 609  	fflush(res->fp);
 610  	return (strlen(res->buf));
 611  }
 612  
 613  static struct category *
 614  category_new(int portsfd, const char *category)
 615  {
 616  	struct category	*cat = NULL;
 617  	xstring		*makecmd;
 618  	char		*results, *d;
 619  	char		*argv[5];
 620  
 621  	makecmd = xstring_new();
 622  	fchdir(portsfd);
 623  
 624  	argv[0] = "make";
 625  	argv[1] = "-C";
 626  	argv[2] = (char *)category;
 627  	argv[3] = "-VSUBDIR";
 628  	argv[4] = NULL;
 629  
 630  	if (exec_buf(makecmd, argv) <= 0)
 631  		goto cleanup;
 632  
 633  	fflush(makecmd->fp);
 634  	results = makecmd->buf;
 635  
 636  	if (categories == NULL)
 637  		categories = pkghash_new();
 638  
 639  	cat = xcalloc(1, sizeof(*cat));
 640  	cat->name = xstrdup(category);
 641  
 642  	pkghash_add(categories, cat->name, cat, NULL);
 643  	while ((d = strsep(&results, " \n")) != NULL)
 644  		pkghash_safe_add(cat->ports, d, NULL, NULL);
 645  
 646  cleanup:
 647  	xstring_free(makecmd);
 648  
 649  	return (cat);
 650  }
 651  
 652  static bool
 653  validate_origin(int portsfd, const char *origin)
 654  {
 655  	struct category	*cat;
 656  	char		*category, *buf;
 657  
 658  	/* If the origin does not contain a / ignore it like for
 659  	 * "base"
 660  	 */
 661  	if (strchr(origin, '/') == NULL)
 662  		return (false);
 663  
 664  	category = xstrdup(origin);
 665  	buf = strrchr(category, '/');
 666  	buf[0] = '\0';
 667  
 668  	cat = pkghash_get_value(categories, category);
 669  	if (cat == NULL)
 670  		cat = category_new(portsfd, category);
 671  	if (cat == NULL)
 672  		return (false);
 673  
 674  	buf = strrchr(origin, '/');
 675  	buf++;
 676  
 677  	if (STREQ(origin, "base"))
 678  		return (false);
 679  
 680  	return (pkghash_get(cat->ports, buf) != NULL);
 681  }
 682  
 683  static const char *
 684  port_version(xstring *cmd, int portsfd, const char *origin, const char *pkgname)
 685  {
 686  	char	*output, *walk, *name;
 687  	char	*version = NULL;
 688  	char	*argv[5];
 689  
 690  	/* Validate the port origin -- check the SUBDIR settings
 691  	   in the ports and category Makefiles, then extract the
 692  	   version from the port itself. */
 693  
 694  	if (validate_origin(portsfd, origin)) {
 695  		argv[0] = "make";
 696  		argv[1] = "-C";
 697  		argv[2] = (char *)origin;
 698  		argv[3] = "flavors-package-names";
 699  		argv[4] = NULL;
 700  
 701  		if (exec_buf(cmd, argv) > 0) {
 702  			fflush(cmd->fp);
 703  			output = cmd->buf;
 704  			while ((walk = strsep(&output, "\n")) != NULL) {
 705  				name = walk;
 706  				walk = strrchr(walk, '-');
 707  				if (walk == NULL)
 708  					continue;
 709  				walk[0] = '\0';
 710  				walk++;
 711  				if (STREQ(name, pkgname)) {
 712  					version = walk;
 713  					break;
 714  				}
 715  			}
 716  		}
 717  	}
 718  
 719  	return (version);
 720  }
 721  
 722  static int
 723  do_source_ports(unsigned int opt, char limchar, char *pattern, match_t match,
 724      const char *matchorigin, const char *matchname, const char *portsdir)
 725  {
 726  	struct pkgdb	*db = NULL;
 727  	struct pkgdb_it	*it = NULL;
 728  	struct pkg	*pkg = NULL;
 729  	xstring		*cmd;
 730  	const char	*name = NULL;
 731  	const char	*origin = NULL;
 732  	const char	*version = NULL;
 733  	int		portsfd;
 734  	bool		gotnone = true;
 735  
 736  	if ( (opt & VERSION_SOURCES) != VERSION_SOURCE_PORTS ) {
 737  		usage_version();
 738  		return (EXIT_FAILURE);
 739  	}
 740  
 741  	portsfd = open(portsdir, O_DIRECTORY);
 742  	if (portsfd == -1)
 743  		err(EXIT_FAILURE, "Cannot open '%s'", portsdir);
 744  
 745  	if (pkgdb_open(&db, PKGDB_DEFAULT_READONLY) != EPKG_OK)
 746  		return (EXIT_FAILURE);
 747  
 748  	if (pkgdb_obtain_lock(db, PKGDB_LOCK_READONLY) != EPKG_OK) {
 749  		pkgdb_close(db);
 750  		warnx("Cannot get a read lock on a database. "
 751  		      "It is locked by another process");
 752  		return (EXIT_FAILURE);
 753  	}
 754  
 755  	if ((it = pkgdb_query(db, pattern, match)) == NULL)
 756  		goto cleanup;
 757  
 758  	cmd = xstring_new();
 759  
 760  	while (pkgdb_it_next(it, &pkg, PKG_LOAD_BASIC) == EPKG_OK) {
 761  		pkg_get(pkg, PKG_ATTR_NAME, &name);
 762  		pkg_get(pkg, PKG_ATTR_ORIGIN, &origin);
 763  
 764  		/* If -O was specified, check if this origin matches */
 765  		if ((opt & VERSION_WITHORIGIN) &&
 766  		    !STREQ(origin, matchorigin))
 767  			continue;
 768  
 769  		/* If -n was specified, check if this name matches */
 770  		if ((opt & VERSION_WITHNAME) &&
 771  		    !STREQ(name, matchname))
 772  			continue;
 773  
 774  		version = port_version(cmd, portsfd, origin, name);
 775  		print_version(pkg, "port", version, limchar, opt);
 776  		xstring_reset(cmd);
 777  
 778  		/* If we reach here, it means at least one package
 779  		   has matched with our query. */
 780  		gotnone = false;
 781  	}
 782  
 783  	xstring_free(cmd);
 784  
 785  cleanup:
 786  	pkgdb_release_lock(db, PKGDB_LOCK_READONLY);
 787  
 788  	free_categories();
 789  	pkg_free(pkg);
 790  	pkgdb_it_free(it);
 791  	pkgdb_close(db);
 792  
 793  	return (gotnone);
 794  }
 795  
 796  int
 797  exec_version(int argc, char **argv)
 798  {
 799  	unsigned int	 opt = 0;
 800  	char		 limchar = '-';
 801  	const char	*matchorigin = NULL;
 802  	const char	*matchname = NULL;
 803  	const char	*portsdir;
 804  	const char	*indexfile;
 805  	const char	*versionsource;
 806  	char		 filebuf[MAXPATHLEN];
 807  	match_t		 match = MATCH_ALL;
 808  	char		*pattern = NULL;
 809  	int		 ch;
 810  	c_charv_t	reponames = vec_init();
 811  
 812  	struct option longopts[] = {
 813  		{ "case-sensitive",	no_argument,		NULL,	'C' },
 814  		{ "exact",		required_argument,	NULL,	'e' },
 815  		{ "glob",		required_argument,	NULL,	'g' },
 816  		{ "help",		no_argument,		NULL,	'h' },
 817  		{ "index",		no_argument,		NULL,	'I' },
 818  		{ "case-insensitive",	no_argument,		NULL,	'i' },
 819  		{ "not-like",		required_argument,	NULL,	'L' },
 820  		{ "like",		required_argument,	NULL,	'l' },
 821  		{ "match-name",		required_argument,	NULL,	'n' },
 822  		{ "match-origin",	required_argument,	NULL,	'O' },
 823  		{ "origin",		no_argument,		NULL,	'o' },
 824  		{ "ports",		no_argument,		NULL,	'P' },
 825  		{ "quiet",		no_argument,		NULL,	'q' },
 826  		{ "remote",		no_argument,		NULL,	'R' },
 827  		{ "repository",		required_argument,	NULL,	'r' },
 828  		{ "test-pattern",	no_argument,		NULL,	'T' },
 829  		{ "test-version",	no_argument,		NULL,	't' },
 830  		{ "no-repo-update",	no_argument,		NULL,	'U' },
 831  		{ "verbose",		no_argument,		NULL,	'v' },
 832  		{ "regex",		required_argument,	NULL,	'x' },
 833  		{ NULL,			0,			NULL,	0   },
 834  	};
 835  
 836  	while ((ch = getopt_long(argc, argv, "+Ce:g:hIiL:l:n:O:oPqRr:TtUvx:",
 837  				 longopts, NULL)) != -1) {
 838  		switch (ch) {
 839  		case 'C':
 840  			pkgdb_set_case_sensitivity(true);
 841  			break;
 842  		case 'e':
 843  			match = MATCH_EXACT;
 844  			pattern = optarg;
 845  			break;
 846  		case 'g':
 847  			match = MATCH_GLOB;
 848  			pattern = optarg;
 849  			break;
 850  		case 'h':
 851  			usage_version();
 852  			return (EXIT_SUCCESS);
 853  		case 'I':
 854  			opt |= VERSION_SOURCE_INDEX;
 855  			break;
 856  		case 'i':
 857  			pkgdb_set_case_sensitivity(false);
 858  			break;
 859  		case 'L':
 860  			opt |= VERSION_NOSTATUS;
 861  			limchar = *optarg;
 862  			break;
 863  		case 'l':
 864  			opt |= VERSION_STATUS;
 865  			limchar = *optarg;
 866  			break;
 867  		case 'n':
 868  			opt |= VERSION_WITHNAME;
 869  			matchname = optarg;
 870  			break;
 871  		case 'O':
 872  			opt |= VERSION_WITHORIGIN;
 873  			matchorigin = optarg;
 874  			break;
 875  		case 'o':
 876  			opt |= VERSION_ORIGIN;
 877  			break;
 878  		case 'P':
 879  			opt |= VERSION_SOURCE_PORTS;
 880  			break;
 881  		case 'q':
 882  			opt |= VERSION_QUIET;
 883  			break;
 884  		case 'R':
 885  			opt |= VERSION_SOURCE_REMOTE;
 886  			break;
 887  		case 'r':
 888  			opt |= VERSION_SOURCE_REMOTE;
 889  			vec_push(&reponames, optarg);
 890  			break;
 891  		case 'T':
 892  			opt |= VERSION_TESTPATTERN;
 893  			break;
 894  		case 't':
 895  			opt |= VERSION_TESTVERSION;
 896  			break;
 897  		case 'U':
 898  			auto_update = false;
 899  			break;
 900  		case 'v':
 901  			opt |= VERSION_VERBOSE;
 902  			break;
 903  		case 'x':
 904  			match = MATCH_REGEX;
 905  			pattern = optarg;
 906  			break;
 907  		default:
 908  			usage_version();
 909  			return (EXIT_FAILURE);
 910  		}
 911  	}
 912  	argc -= optind;
 913  	argv += optind;
 914  
 915  	/*
 916  	 * Allowed option combinations:
 917  	 *   -t ver1 ver2	 -- only
 918  	 *   -T pkgname pattern	 -- only
 919  	 *   Only one of -I -P -R can be given
 920  	 */
 921  
 922  	if (matchorigin != NULL && matchname != NULL) {
 923  		usage_version();
 924  		return (EXIT_FAILURE);
 925  	}
 926  
 927  	if ( (opt & VERSION_TESTVERSION) == VERSION_TESTVERSION )
 928  		return (do_testversion(opt, argc, argv));
 929  
 930  	if ( (opt & VERSION_TESTPATTERN) == VERSION_TESTPATTERN )
 931  		return (do_testpattern(opt, argc, argv));
 932  
 933  	if (opt & (VERSION_STATUS|VERSION_NOSTATUS)) {
 934  		if (limchar != '<' &&
 935  		    limchar != '>' &&
 936  		    limchar != '=' &&
 937  		    limchar != '?' &&
 938  		    limchar != '!') {
 939  			usage_version();
 940  			return (EXIT_FAILURE);
 941  		}
 942  	}
 943  
 944  	if (opt & VERSION_QUIET)
 945  		quiet = true;
 946  
 947  	if (argc > 1) {
 948  		usage_version();
 949  		return (EXIT_FAILURE);
 950  	}
 951  
 952  	if ( !(opt & VERSION_SOURCES ) ) {
 953  		versionsource = pkg_object_string(
 954  		    pkg_config_get("VERSION_SOURCE"));
 955  		if (versionsource != NULL) {
 956  			switch (versionsource[0]) {
 957  			case 'I':
 958  				opt |= VERSION_SOURCE_INDEX;
 959  				break;
 960  			case 'P':
 961  				opt |= VERSION_SOURCE_PORTS;
 962  				break;
 963  			case 'R':
 964  				opt |= VERSION_SOURCE_REMOTE;
 965  				break;
 966  			default:
 967  				warnx("Invalid VERSION_SOURCE"
 968  				    " in configuration.");
 969  			}
 970  		}
 971  	}
 972  
 973  	if ( (opt & VERSION_SOURCE_INDEX) == VERSION_SOURCE_INDEX ) {
 974  		if (!have_indexfile(&indexfile, filebuf, sizeof(filebuf),
 975  		     argc, argv, true))
 976  			return (EXIT_FAILURE);
 977  		else
 978  			return (do_source_index(opt, limchar, pattern, match,
 979  				    matchorigin, matchname, indexfile));
 980  	}
 981  
 982  	if ( (opt & VERSION_SOURCE_REMOTE) == VERSION_SOURCE_REMOTE )
 983  		return (do_source_remote(opt, limchar, pattern, match,
 984  			    auto_update, &reponames, matchorigin, matchname));
 985  
 986  	if ( (opt & VERSION_SOURCE_PORTS) == VERSION_SOURCE_PORTS ) {
 987  		if (!have_ports(&portsdir, true))
 988  			return (EXIT_FAILURE);
 989  		else
 990  			return (do_source_ports(opt, limchar, pattern,
 991  				    match, matchorigin, matchname, portsdir));
 992  	}
 993  
 994  	/* If none of -IPR were specified, and INDEX exists use that.
 995  	   Failing that, if portsdir exists and is valid, use that
 996  	   (slow) otherwise fallback to remote. */
 997  
 998  	if (have_indexfile(&indexfile, filebuf, sizeof(filebuf), argc, argv,
 999              false)) {
1000  		opt |= VERSION_SOURCE_INDEX;
1001  		return (do_source_index(opt, limchar, pattern, match,
1002  			    matchorigin, matchname, indexfile));
1003  	} else if (have_ports(&portsdir, false)) {
1004  		if (argc == 1) {
1005  			warnx("No such INDEX file: '%s'", argv[0]);
1006  			return (EXIT_FAILURE);
1007  		}
1008  		opt |= VERSION_SOURCE_PORTS;
1009  		return (do_source_ports(opt, limchar, pattern, match,
1010  			    matchorigin, matchname, portsdir));
1011  	} else {
1012  		opt |= VERSION_SOURCE_REMOTE;
1013  		return (do_source_remote(opt, limchar, pattern, match,
1014  			    auto_update, &reponames, matchorigin, matchname));
1015  	}
1016  
1017  	/* NOTREACHED */
1018  	return (EXIT_FAILURE);
1019  }
1020  /*
1021   * That's All Folks!
1022   */