743 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			743 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * Copyright (c) 2014-2019 Juan Romero Pardines
 | 
						|
 * Copyright (c) 2012-2014 Dave Elusive <davehome@redthumb.info.tm>
 | 
						|
 * Copyright (c) 2019      Duncan Overbruck <mail@duncano.de>
 | 
						|
 * All rights reserved
 | 
						|
 *
 | 
						|
 * Redistribution and use in source and binary forms, with or without
 | 
						|
 *  modification, are permitted provided that the following conditions
 | 
						|
 *  are met:
 | 
						|
 *  1. Redistributions of source code must retain the above copyright
 | 
						|
 *     notice, this list of conditions and the following disclaimer.
 | 
						|
 *  2. Redistributions in binary form must reproduce the above copyright
 | 
						|
 *     notice, this list of conditions and the following disclaimer in the
 | 
						|
 *     documentation and/or other materials provided with the distribution.
 | 
						|
 *
 | 
						|
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 | 
						|
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 | 
						|
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 | 
						|
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 | 
						|
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 | 
						|
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 | 
						|
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 | 
						|
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 | 
						|
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 | 
						|
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 | 
						|
 *
 | 
						|
 */
 | 
						|
 | 
						|
#include <getopt.h>
 | 
						|
#include <stdbool.h>
 | 
						|
#include <stdio.h>
 | 
						|
#include <stddef.h>
 | 
						|
#include <stdlib.h>
 | 
						|
#include <string.h>
 | 
						|
#include <errno.h>
 | 
						|
#include <ctype.h>
 | 
						|
#include <dirent.h>
 | 
						|
#include <limits.h>
 | 
						|
#include <sys/stat.h>
 | 
						|
#include <assert.h>
 | 
						|
 | 
						|
#include <xbps.h>
 | 
						|
 | 
						|
#define GOT_PKGNAME_VAR 	0x1
 | 
						|
#define GOT_VERSION_VAR 	0x2
 | 
						|
#define GOT_REVISION_VAR 	0x4
 | 
						|
 | 
						|
typedef struct _rcv_t {
 | 
						|
	const char *prog, *fname;
 | 
						|
	char *xbps_conf, *rootdir, *distdir;
 | 
						|
	char *buf;
 | 
						|
	size_t bufsz;
 | 
						|
	size_t len;
 | 
						|
	char *ptr;
 | 
						|
	uint8_t have_vars;
 | 
						|
	xbps_dictionary_t env;
 | 
						|
	xbps_dictionary_t pkgd;
 | 
						|
	xbps_dictionary_t cache;
 | 
						|
	struct xbps_handle xhp;
 | 
						|
	bool show_all;
 | 
						|
	bool manual;
 | 
						|
	bool installed;
 | 
						|
	const char *format;
 | 
						|
	char *cachefile;
 | 
						|
} rcv_t;
 | 
						|
 | 
						|
typedef int (*rcv_check_func)(rcv_t *);
 | 
						|
typedef int (*rcv_proc_func)(rcv_t *, const char *, rcv_check_func);
 | 
						|
 | 
						|
static char *
 | 
						|
xstrdup(const char *src)
 | 
						|
{
 | 
						|
	char *p;
 | 
						|
	if (!(p = strdup(src))) {
 | 
						|
		fprintf(stderr, "Error: %s\n", strerror(errno));
 | 
						|
		exit(1);
 | 
						|
	}
 | 
						|
	return p;
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
show_usage(const char *prog)
 | 
						|
{
 | 
						|
	fprintf(stderr,
 | 
						|
"Usage: %s [OPTIONS] [FILES...]\n\n"
 | 
						|
"OPTIONS:\n"
 | 
						|
" -h --help		Show this helpful help-message for help.\n"
 | 
						|
" -C --config <dir> 	Set path to xbps.d\n"
 | 
						|
" -D --distdir <dir>	Set (or override) the path to void-packages\n"
 | 
						|
"  			(defaults to ~/void-packages).\n"
 | 
						|
" -d --debug 		Enable debug output to stderr.\n"
 | 
						|
" -f --format <fmt>	Output format.\n"
 | 
						|
" -I --installed 	Check for outdated packages in rootdir, rather\n"
 | 
						|
"  			than in the XBPS repositories.\n"
 | 
						|
" -i --ignore-conf-repos	Ignore repositories defined in xbps.d.\n"
 | 
						|
" -m --manual		Only process listed files.\n"
 | 
						|
" -R --repository=<url>	Append repository to the head of repository list.\n"
 | 
						|
" -r --rootdir <dir>	Set root directory (defaults to /).\n"
 | 
						|
" -s --show-all		List all packages, in the format 'pkgname repover srcver'.\n"
 | 
						|
"\n  [FILES...]		Extra packages to process with the outdated\n"
 | 
						|
"			ones (only processed if missing).\n\n",
 | 
						|
prog);
 | 
						|
	return EXIT_FAILURE;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
rcv_init(rcv_t *rcv, const char *prog)
 | 
						|
{
 | 
						|
	rcv->prog = prog;
 | 
						|
	rcv->have_vars = 0;
 | 
						|
	rcv->ptr = rcv->buf = NULL;
 | 
						|
 | 
						|
	rcv->cache = xbps_dictionary_internalize_from_file(rcv->cachefile);
 | 
						|
	if (!rcv->cache)
 | 
						|
		rcv->cache = xbps_dictionary_create();
 | 
						|
	assert(rcv->cache);
 | 
						|
 | 
						|
	if (rcv->xbps_conf != NULL) {
 | 
						|
		xbps_strlcpy(rcv->xhp.confdir, rcv->xbps_conf, sizeof(rcv->xhp.confdir));
 | 
						|
	}
 | 
						|
	if (rcv->rootdir != NULL) {
 | 
						|
		xbps_strlcpy(rcv->xhp.rootdir, rcv->rootdir, sizeof(rcv->xhp.rootdir));
 | 
						|
	}
 | 
						|
	if (xbps_init(&rcv->xhp) != 0)
 | 
						|
		abort();
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
rcv_end(rcv_t *rcv)
 | 
						|
{
 | 
						|
	xbps_dictionary_externalize_to_file(rcv->cache, rcv->cachefile);
 | 
						|
 | 
						|
	if (rcv->buf != NULL) {
 | 
						|
		free(rcv->buf);
 | 
						|
		rcv->buf = NULL;
 | 
						|
	}
 | 
						|
	if (rcv->env != NULL) {
 | 
						|
		xbps_object_release(rcv->env);
 | 
						|
		rcv->env = NULL;
 | 
						|
	}
 | 
						|
 | 
						|
	xbps_end(&rcv->xhp);
 | 
						|
 | 
						|
	if (rcv->xbps_conf != NULL)
 | 
						|
		free(rcv->xbps_conf);
 | 
						|
	if (rcv->distdir != NULL)
 | 
						|
		free(rcv->distdir);
 | 
						|
 | 
						|
	free(rcv->cachefile);
 | 
						|
}
 | 
						|
 | 
						|
static bool
 | 
						|
rcv_load_file(rcv_t *rcv, const char *fname)
 | 
						|
{
 | 
						|
	FILE *file;
 | 
						|
	long offset;
 | 
						|
	rcv->fname = fname;
 | 
						|
 | 
						|
	if ((file = fopen(rcv->fname, "r")) == NULL) {
 | 
						|
		if (!rcv->manual) {
 | 
						|
			fprintf(stderr, "FileError: can't open '%s': %s\n",
 | 
						|
				rcv->fname, strerror(errno));
 | 
						|
		}
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	fseek(file, 0, SEEK_END);
 | 
						|
	offset = ftell(file);
 | 
						|
	fseek(file, 0, SEEK_SET);
 | 
						|
 | 
						|
	if (offset == -1) {
 | 
						|
		fclose(file);
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
	rcv->len = (size_t)offset;
 | 
						|
 | 
						|
	if (rcv->buf == NULL) {
 | 
						|
		rcv->bufsz = rcv->len+1;
 | 
						|
		if (!(rcv->buf = calloc(rcv->bufsz, sizeof(char)))) {
 | 
						|
			fprintf(stderr, "MemError: can't allocate memory: %s\n",
 | 
						|
				strerror(errno));
 | 
						|
			fclose(file);
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
	} else if (rcv->bufsz <= rcv->len) {
 | 
						|
		rcv->bufsz = rcv->len+1;
 | 
						|
		if (!(rcv->buf = realloc(rcv->buf, rcv->bufsz))) {
 | 
						|
			fprintf(stderr, "MemError: can't allocate memory: %s\n",
 | 
						|
				strerror(errno));
 | 
						|
			fclose(file);
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	(void)fread(rcv->buf, sizeof(char), rcv->len, file);
 | 
						|
	rcv->buf[rcv->len] = '\0';
 | 
						|
	fclose(file);
 | 
						|
	rcv->ptr = rcv->buf;
 | 
						|
 | 
						|
	return true;
 | 
						|
}
 | 
						|
 | 
						|
static char *
 | 
						|
rcv_sh_substitute(rcv_t *rcv, const char *str, size_t len)
 | 
						|
{
 | 
						|
	const char *p;
 | 
						|
	char *cmd, *ret;
 | 
						|
	xbps_string_t out = xbps_string_create();
 | 
						|
	char b[2] = {0};
 | 
						|
 | 
						|
	for (p = str; *p && p < str+len; p++) {
 | 
						|
		switch (*p) {
 | 
						|
		case '$':
 | 
						|
			if (p+1 < str+len) {
 | 
						|
				char buf[1024] = {0};
 | 
						|
				const char *ref;
 | 
						|
				const char *val;
 | 
						|
				size_t reflen;
 | 
						|
				p++;
 | 
						|
				if (*p == '(') {
 | 
						|
					FILE *fp;
 | 
						|
					int c;
 | 
						|
					for (ref = ++p; *p && p < str+len && *p != ')'; p++)
 | 
						|
						;
 | 
						|
					if (*p != ')')
 | 
						|
						goto err1;
 | 
						|
					cmd = strndup(ref, p-ref);
 | 
						|
					if ((fp = popen(cmd, "r")) == NULL)
 | 
						|
						goto err2;
 | 
						|
					while ((c = fgetc(fp)) != EOF && c != '\n') {
 | 
						|
						*b = c;
 | 
						|
						xbps_string_append_cstring(out, b);
 | 
						|
					}
 | 
						|
					if (pclose(fp) != 0)
 | 
						|
						goto err2;
 | 
						|
					free(cmd);
 | 
						|
					cmd = NULL;
 | 
						|
					continue;
 | 
						|
				} else if (*p == '{') {
 | 
						|
					for (ref = ++p; *p && p < str+len && (isalnum(*p) || *p == '_'); p++)
 | 
						|
						;
 | 
						|
					reflen = p-ref;
 | 
						|
					switch (*p) {
 | 
						|
					case '/': /* fallthrough */
 | 
						|
					case '%': /* fallthrough */
 | 
						|
					case '#': /* fallthrough */
 | 
						|
					case ':':
 | 
						|
						for (; *p && p < str+len && *p != '}'; p++)
 | 
						|
							;
 | 
						|
						if (*p != '}')
 | 
						|
							goto err1;
 | 
						|
						break;
 | 
						|
					case '}':
 | 
						|
						break;
 | 
						|
					default:
 | 
						|
						goto err1;
 | 
						|
					}
 | 
						|
				} else {
 | 
						|
					for (ref = p; *p && p < str+len && (isalnum(*p) || *p == '_'); p++)
 | 
						|
						;
 | 
						|
					reflen = p-ref;
 | 
						|
					p--;
 | 
						|
				}
 | 
						|
				if (reflen) {
 | 
						|
					if (reflen >= sizeof buf) {
 | 
						|
						fprintf(stderr, "out of memory\n");
 | 
						|
						exit(1);
 | 
						|
					}
 | 
						|
					strncpy(buf, ref, reflen);
 | 
						|
					if (xbps_dictionary_get_cstring_nocopy(rcv->env, buf, &val))
 | 
						|
						xbps_string_append_cstring(out, val);
 | 
						|
					else
 | 
						|
						xbps_string_append_cstring(out, "NULL");
 | 
						|
					break;
 | 
						|
				}
 | 
						|
			}
 | 
						|
			/* fallthrough */
 | 
						|
		default:
 | 
						|
			*b = *p;
 | 
						|
			xbps_string_append_cstring(out, b);
 | 
						|
		}
 | 
						|
	}
 | 
						|
	
 | 
						|
	ret = xbps_string_cstring(out);
 | 
						|
	xbps_object_release(out);
 | 
						|
	return ret;
 | 
						|
 | 
						|
err1:
 | 
						|
	fprintf(stderr, "syntax error: in file '%s'\n", rcv->fname);
 | 
						|
	exit(1);
 | 
						|
err2:
 | 
						|
	fprintf(stderr,
 | 
						|
		"Shell cmd failed: '%s' for "
 | 
						|
		"template '%s'",
 | 
						|
		cmd, rcv->fname);
 | 
						|
	if (errno > 0) {
 | 
						|
		fprintf(stderr, ": %s\n",
 | 
						|
			strerror(errno));
 | 
						|
	} else {
 | 
						|
		fputc('\n', stderr);
 | 
						|
	}
 | 
						|
	exit(1);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
rcv_get_pkgver(rcv_t *rcv)
 | 
						|
{
 | 
						|
	size_t klen, vlen;
 | 
						|
	char c, *ptr = rcv->ptr, *e, *p, *k, *v, *comment, *d, *key, *val;
 | 
						|
	uint8_t vars = 0;
 | 
						|
 | 
						|
	while ((c = *ptr) != '\0') {
 | 
						|
		if (c == '#' || c == '.') {
 | 
						|
			goto nextline;
 | 
						|
		}
 | 
						|
		if (c == '\n') {
 | 
						|
			ptr++;
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
		if (c == 'u' && (strncmp("unset", ptr, 5)) == 0) {
 | 
						|
			goto nextline;
 | 
						|
		}
 | 
						|
		if ((e = strchr(ptr, '=')) == NULL)
 | 
						|
			goto nextline;
 | 
						|
 | 
						|
		p = strchr(ptr, '\n');
 | 
						|
		k = ptr;
 | 
						|
		v = e + 1;
 | 
						|
 | 
						|
		assert(p);
 | 
						|
		assert(k);
 | 
						|
		assert(v);
 | 
						|
 | 
						|
		klen = strlen(k) - strlen(e);
 | 
						|
		vlen = strlen(v) - strlen(p);
 | 
						|
 | 
						|
		if (v[0] == '"' && vlen == 1) {
 | 
						|
			while (*ptr++ != '"')
 | 
						|
				;
 | 
						|
			goto nextline;
 | 
						|
		}
 | 
						|
		if (v[0] == '"') {
 | 
						|
			v++;
 | 
						|
			vlen--;
 | 
						|
		}
 | 
						|
		if (v[vlen-1] == '"') {
 | 
						|
			vlen--;
 | 
						|
		}
 | 
						|
		comment = strchr(v, '#');
 | 
						|
		if (comment && comment < p && (comment > v && comment[-1] == ' ')) {
 | 
						|
			while (v[vlen-1] != '#') {
 | 
						|
				vlen--;
 | 
						|
			}
 | 
						|
			vlen--;
 | 
						|
			while (isspace(v[vlen-1])) {
 | 
						|
				vlen--;
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if (vlen == 0) {
 | 
						|
			goto nextline;
 | 
						|
		}
 | 
						|
		key = strndup(k, klen);
 | 
						|
 | 
						|
		if ((d = strchr(v, '$')) && d < v+vlen)
 | 
						|
			val = rcv_sh_substitute(rcv, v, vlen);
 | 
						|
		else
 | 
						|
			val = strndup(v, vlen);
 | 
						|
 | 
						|
		if (!xbps_dictionary_set(rcv->env, key,
 | 
						|
		    xbps_string_create_cstring(val))) {
 | 
						|
			fprintf(stderr, "error: xbps_dictionary_set");
 | 
						|
			exit(1);
 | 
						|
		}
 | 
						|
 | 
						|
		if (rcv->xhp.flags & XBPS_FLAG_DEBUG)
 | 
						|
			fprintf(stderr, "%s: %s %s\n", rcv->fname, key, val);
 | 
						|
 | 
						|
		free(key);
 | 
						|
		free(val);
 | 
						|
 | 
						|
		if (strncmp("pkgname", k, klen) == 0) {
 | 
						|
			rcv->have_vars |= GOT_PKGNAME_VAR;
 | 
						|
			vars++;
 | 
						|
		} else if (strncmp("version",  k, klen) == 0) {
 | 
						|
			rcv->have_vars |= GOT_VERSION_VAR;
 | 
						|
			vars++;
 | 
						|
		} else if (strncmp("revision", k, klen) == 0) {
 | 
						|
			rcv->have_vars |= GOT_REVISION_VAR;
 | 
						|
			vars++;
 | 
						|
		}
 | 
						|
		if (vars > 2)
 | 
						|
			return;
 | 
						|
 | 
						|
nextline:
 | 
						|
		ptr = strchr(ptr, '\n') + 1;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
rcv_process_file(rcv_t *rcv, const char *fname, rcv_check_func check)
 | 
						|
{
 | 
						|
	int rv = 0;
 | 
						|
	xbps_object_t keysym;
 | 
						|
	xbps_object_iterator_t iter;
 | 
						|
	xbps_dictionary_t d;
 | 
						|
	xbps_data_t mtime;
 | 
						|
	const char *pkgname, *version, *revision, *reverts;
 | 
						|
	struct stat st;
 | 
						|
	bool allocenv = false;
 | 
						|
 | 
						|
	if (stat(fname, &st) == -1) {
 | 
						|
		rv = EXIT_FAILURE;
 | 
						|
		goto ret;
 | 
						|
	}
 | 
						|
 | 
						|
	if ((d = xbps_dictionary_get(rcv->cache, fname))) {
 | 
						|
		mtime = xbps_dictionary_get(d, "mtime");
 | 
						|
		if (!xbps_data_equals_data(mtime, &st.st_mtim, sizeof st.st_mtim))
 | 
						|
			goto update;
 | 
						|
		rcv->env = d;
 | 
						|
		rcv->have_vars = GOT_PKGNAME_VAR | GOT_VERSION_VAR | GOT_REVISION_VAR;
 | 
						|
		rcv->fname = fname;
 | 
						|
	} else {
 | 
						|
update:
 | 
						|
		if (!rcv_load_file(rcv, fname)) {
 | 
						|
			rv = EXIT_FAILURE;
 | 
						|
			goto ret;
 | 
						|
		}
 | 
						|
		assert(rcv);
 | 
						|
		rcv->env = xbps_dictionary_create();
 | 
						|
		assert(rcv->env);
 | 
						|
		allocenv = true;
 | 
						|
		rcv_get_pkgver(rcv);
 | 
						|
 | 
						|
		if (!xbps_dictionary_get_cstring_nocopy(rcv->env, "pkgname", &pkgname) ||
 | 
						|
			!xbps_dictionary_get_cstring_nocopy(rcv->env, "version", &version) ||
 | 
						|
			!xbps_dictionary_get_cstring_nocopy(rcv->env, "revision", &revision)) {
 | 
						|
			fprintf(stderr, "ERROR: '%s':"
 | 
						|
			    " missing required variable (pkgname, version or revision)!",
 | 
						|
			    fname);
 | 
						|
			exit(1);
 | 
						|
		}
 | 
						|
		if (!d) {
 | 
						|
			d = xbps_dictionary_create();
 | 
						|
			xbps_dictionary_set(rcv->cache, fname, d);
 | 
						|
		}
 | 
						|
		xbps_dictionary_set_cstring(d, "pkgname", pkgname);
 | 
						|
		xbps_dictionary_set_cstring(d, "version", version);
 | 
						|
		xbps_dictionary_set_cstring(d, "revision", revision);
 | 
						|
 | 
						|
		reverts = NULL;
 | 
						|
		xbps_dictionary_get_cstring_nocopy(rcv->env, "reverts", &reverts);
 | 
						|
		if (reverts)
 | 
						|
			xbps_dictionary_set_cstring(d, "reverts", reverts);
 | 
						|
 | 
						|
		mtime = xbps_data_create_data(&st.st_mtim, sizeof st.st_mtim);
 | 
						|
		xbps_dictionary_set(d, "mtime", mtime);
 | 
						|
	}
 | 
						|
 | 
						|
	check(rcv);
 | 
						|
 | 
						|
ret:
 | 
						|
	if (allocenv) {
 | 
						|
		iter = xbps_dictionary_iterator(rcv->env);
 | 
						|
		while ((keysym = xbps_object_iterator_next(iter)))
 | 
						|
			xbps_object_release(xbps_dictionary_get_keysym(rcv->env, keysym));
 | 
						|
		xbps_object_iterator_release(iter);
 | 
						|
		xbps_object_release(rcv->env);
 | 
						|
	}
 | 
						|
	rcv->env = NULL;
 | 
						|
	return rv;
 | 
						|
}
 | 
						|
 | 
						|
static bool
 | 
						|
check_reverts(const char *repover, const char *reverts)
 | 
						|
{
 | 
						|
	const char *s, *e;
 | 
						|
	size_t len;
 | 
						|
 | 
						|
	assert(reverts);
 | 
						|
 | 
						|
	s = reverts;
 | 
						|
	if ((len = strlen(s)) == 0)
 | 
						|
		return false;
 | 
						|
 | 
						|
	for (s = reverts; s < reverts+len; s = e+1) {
 | 
						|
		if (!(e = strchr(s, ' ')))
 | 
						|
			e = reverts+len;
 | 
						|
		if (strncmp(s, repover, e-s) == 0)
 | 
						|
			return true;
 | 
						|
	}
 | 
						|
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
rcv_printf(rcv_t *rcv, FILE *fp, const char *pkgname, const char *repover,
 | 
						|
    const char *srcver, const char *repourl)
 | 
						|
{
 | 
						|
	const char *f, *p;
 | 
						|
 | 
						|
	for (f = rcv->format; *f; f++) {
 | 
						|
		if (*f == '\\') {
 | 
						|
			f++;
 | 
						|
			switch (*f) {
 | 
						|
			case '\n': fputc('\n', fp); break;
 | 
						|
			case '\t': fputc('\t', fp); break;
 | 
						|
			case '\0': fputc('\0', fp); break;
 | 
						|
			default:
 | 
						|
				fputc('\\', fp);
 | 
						|
				fputc(*f, fp);
 | 
						|
				break;
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if (*f != '%') {
 | 
						|
			fputc(*f, fp);
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
		switch (*++f) {
 | 
						|
		case '%': fputc(*f, fp); break;
 | 
						|
		case 'n': fputs(pkgname, fp); break;
 | 
						|
		case 'r': fputs(repover, fp); break;
 | 
						|
		case 'R': fputs(repourl, fp); break;
 | 
						|
		case 's': fputs(srcver, fp); break;
 | 
						|
		case 't':
 | 
						|
			p = strchr(rcv->fname, '/');
 | 
						|
			fwrite(rcv->fname, p ? (size_t)(p - rcv->fname) : strlen(rcv->fname), 1, fp);
 | 
						|
			break;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	fputc('\n', fp);
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
rcv_check_version(rcv_t *rcv)
 | 
						|
{
 | 
						|
	const char *repover = NULL;
 | 
						|
	char srcver[BUFSIZ] = { '\0' };
 | 
						|
	const char *pkgname, *version, *revision, *reverts, *repourl;
 | 
						|
	int sz;
 | 
						|
 | 
						|
	assert(rcv);
 | 
						|
 | 
						|
	if ((rcv->have_vars & GOT_PKGNAME_VAR) == 0) {
 | 
						|
		fprintf(stderr, "ERROR: '%s': missing pkgname variable!\n", rcv->fname);
 | 
						|
		exit(EXIT_FAILURE);
 | 
						|
	}
 | 
						|
	if ((rcv->have_vars & GOT_VERSION_VAR) == 0) {
 | 
						|
		fprintf(stderr, "ERROR: '%s': missing version variable!\n", rcv->fname);
 | 
						|
		exit(EXIT_FAILURE);
 | 
						|
	}
 | 
						|
	if ((rcv->have_vars & GOT_REVISION_VAR) == 0) {
 | 
						|
		fprintf(stderr, "ERROR: '%s': missing revision variable!\n", rcv->fname);
 | 
						|
		exit(EXIT_FAILURE);
 | 
						|
	}
 | 
						|
 | 
						|
	if (!xbps_dictionary_get_cstring_nocopy(rcv->env, "pkgname", &pkgname) ||
 | 
						|
	    !xbps_dictionary_get_cstring_nocopy(rcv->env, "version", &version) ||
 | 
						|
	    !xbps_dictionary_get_cstring_nocopy(rcv->env, "revision", &revision)) {
 | 
						|
		fprintf(stderr, "error:\n");
 | 
						|
		exit(1);
 | 
						|
	}
 | 
						|
 | 
						|
	reverts = NULL;
 | 
						|
	xbps_dictionary_get_cstring_nocopy(rcv->env, "reverts", &reverts);
 | 
						|
 | 
						|
	sz = snprintf(srcver, sizeof srcver, "%s_%s", version, revision);
 | 
						|
	if (sz < 0 || (size_t)sz >= sizeof srcver)
 | 
						|
		exit(EXIT_FAILURE);
 | 
						|
 | 
						|
	repourl = NULL;
 | 
						|
	if (rcv->installed) {
 | 
						|
		rcv->pkgd = xbps_pkgdb_get_pkg(&rcv->xhp, pkgname);
 | 
						|
	} else {
 | 
						|
		rcv->pkgd = xbps_rpool_get_pkg(&rcv->xhp, pkgname);
 | 
						|
		xbps_dictionary_get_cstring_nocopy(rcv->pkgd, "repository", &repourl);
 | 
						|
	}
 | 
						|
	xbps_dictionary_get_cstring_nocopy(rcv->pkgd, "pkgver", &repover);
 | 
						|
	if (repover)
 | 
						|
		repover += strlen(pkgname)+1;
 | 
						|
 | 
						|
	if (!repover && rcv->manual)
 | 
						|
		;
 | 
						|
	else if (rcv->show_all)
 | 
						|
		;
 | 
						|
	else if (repover && (xbps_cmpver(repover, srcver) < 0 ||
 | 
						|
		    (reverts && check_reverts(repover, reverts))))
 | 
						|
		;
 | 
						|
	else
 | 
						|
		return 0;
 | 
						|
 | 
						|
	repover = repover ? repover : "?";
 | 
						|
	repourl = repourl ? repourl : "?";
 | 
						|
	rcv_printf(rcv, stdout, pkgname, repover, srcver, repourl);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
rcv_process_dir(rcv_t *rcv, rcv_proc_func process)
 | 
						|
{
 | 
						|
	DIR *dir = NULL;
 | 
						|
	struct dirent entry, *result;
 | 
						|
	struct stat st;
 | 
						|
	char filename[BUFSIZ];
 | 
						|
	int i, ret = 0;
 | 
						|
 | 
						|
	if (!(dir = opendir(".")))
 | 
						|
		goto error;
 | 
						|
 | 
						|
	for (;;) {
 | 
						|
		i = readdir_r(dir, &entry, &result);
 | 
						|
		if (i > 0)
 | 
						|
			goto error;
 | 
						|
		if (result == NULL)
 | 
						|
			break;
 | 
						|
		if ((strcmp(result->d_name, ".") == 0) ||
 | 
						|
		    (strcmp(result->d_name, "..") == 0))
 | 
						|
			continue;
 | 
						|
		if ((lstat(result->d_name, &st)) != 0)
 | 
						|
			goto error;
 | 
						|
		if (S_ISLNK(st.st_mode) != 0)
 | 
						|
			continue;
 | 
						|
 | 
						|
		snprintf(filename, sizeof(filename), "%s/template", result->d_name);
 | 
						|
		ret = process(rcv, filename, rcv_check_version);
 | 
						|
	}
 | 
						|
 | 
						|
	if ((closedir(dir)) != -1)
 | 
						|
		return ret;
 | 
						|
error:
 | 
						|
	fprintf(stderr, "Error: while processing dir '%s/srcpkgs': %s\n",
 | 
						|
	    rcv->distdir, strerror(errno));
 | 
						|
	exit(1);
 | 
						|
}
 | 
						|
 | 
						|
int
 | 
						|
main(int argc, char **argv)
 | 
						|
{
 | 
						|
	int i, c;
 | 
						|
	rcv_t rcv;
 | 
						|
	const char *prog = argv[0], *sopts = "hC:D:df:iImR:r:sV";
 | 
						|
	const struct option lopts[] = {
 | 
						|
		{ "help", no_argument, NULL, 'h' },
 | 
						|
		{ "config", required_argument, NULL, 'C' },
 | 
						|
		{ "distdir", required_argument, NULL, 'D' },
 | 
						|
		{ "debug", no_argument, NULL, 'd' },
 | 
						|
		{ "format", required_argument, NULL, 'f' },
 | 
						|
		{ "installed", no_argument, NULL, 'I' },
 | 
						|
		{ "ignore-conf-repos", no_argument, NULL, 'i' },
 | 
						|
		{ "manual", no_argument, NULL, 'm' },
 | 
						|
		{ "repository", required_argument, NULL, 'R' },
 | 
						|
		{ "rootdir", required_argument, NULL, 'r' },
 | 
						|
		{ "show-all", no_argument, NULL, 's' },
 | 
						|
		{ "version", no_argument, NULL, 'V' },
 | 
						|
		{ NULL, 0, NULL, 0 }
 | 
						|
	};
 | 
						|
 | 
						|
	memset(&rcv, 0, sizeof(rcv_t));
 | 
						|
	rcv.manual = false;
 | 
						|
	rcv.format = "%n %r %s %t %R";
 | 
						|
 | 
						|
	while ((c = getopt_long(argc, argv, sopts, lopts, NULL)) != -1) {
 | 
						|
		switch (c) {
 | 
						|
		case 'h':
 | 
						|
			return show_usage(prog);
 | 
						|
		case 'C':
 | 
						|
			rcv.xbps_conf = xstrdup(optarg);
 | 
						|
			break;
 | 
						|
		case 'D':
 | 
						|
			rcv.distdir = xstrdup(optarg);
 | 
						|
			break;
 | 
						|
		case 'd':
 | 
						|
			rcv.xhp.flags |= XBPS_FLAG_DEBUG;
 | 
						|
			break;
 | 
						|
		case 'f':
 | 
						|
			rcv.format = optarg;
 | 
						|
			break;
 | 
						|
		case 'i':
 | 
						|
			rcv.xhp.flags |= XBPS_FLAG_IGNORE_CONF_REPOS;
 | 
						|
			break;
 | 
						|
		case 'I':
 | 
						|
			rcv.installed = true;
 | 
						|
			break;
 | 
						|
		case 'm':
 | 
						|
			rcv.manual = true;
 | 
						|
			break;
 | 
						|
		case 'R':
 | 
						|
			xbps_repo_store(&rcv.xhp, optarg);
 | 
						|
			break;
 | 
						|
		case 'r':
 | 
						|
			rcv.rootdir = optarg;
 | 
						|
			break;
 | 
						|
		case 's':
 | 
						|
			rcv.show_all = true;
 | 
						|
			break;
 | 
						|
		case 'V':
 | 
						|
			printf("%s\n", XBPS_RELVER);
 | 
						|
			exit(EXIT_SUCCESS);
 | 
						|
		default:
 | 
						|
			return show_usage(prog);
 | 
						|
		}
 | 
						|
	}
 | 
						|
	/*
 | 
						|
	 * If --distdir not set default to ~/void-packages.
 | 
						|
	 */
 | 
						|
	if (rcv.distdir == NULL)
 | 
						|
		rcv.distdir = xbps_xasprintf("%s/void-packages", getenv("HOME"));
 | 
						|
	rcv.cachefile = xbps_xasprintf("%s/.xbps-checkvers.plist", rcv.distdir);
 | 
						|
 | 
						|
	argc -= optind;
 | 
						|
	argv += optind;
 | 
						|
 | 
						|
	rcv_init(&rcv, prog);
 | 
						|
 | 
						|
	if (chdir(rcv.distdir) == -1 || chdir("srcpkgs") == -1) {
 | 
						|
		fprintf(stderr, "Error: while changing directory to '%s/srcpkgs': %s\n",
 | 
						|
		    rcv.distdir, strerror(errno));
 | 
						|
		exit(1);
 | 
						|
	}
 | 
						|
 | 
						|
	if (!rcv.manual)
 | 
						|
		rcv_process_dir(&rcv, rcv_process_file);
 | 
						|
 | 
						|
	rcv.manual = true;
 | 
						|
	for (i = 0; i < argc; i++) {
 | 
						|
		char tmp[PATH_MAX] = {0}, *tmpl, *p;
 | 
						|
		if (strncmp(argv[i], "srcpkgs/", sizeof ("srcpkgs/")-1) == 0) {
 | 
						|
			argv[i] += sizeof ("srcpkgs/")-1;
 | 
						|
		}
 | 
						|
		if ((p = strrchr(argv[i], '/')) && (strcmp(p, "/template")) == 0) {
 | 
						|
			tmpl = argv[i];
 | 
						|
		} else {
 | 
						|
			xbps_strlcat(tmp, argv[i], sizeof tmp);
 | 
						|
			xbps_strlcat(tmp, "/template", sizeof tmp);
 | 
						|
			tmpl = tmp;
 | 
						|
		}
 | 
						|
		rcv_process_file(&rcv, tmpl, rcv_check_version);
 | 
						|
	}
 | 
						|
	rcv_end(&rcv);
 | 
						|
	exit(EXIT_SUCCESS);
 | 
						|
}
 |