xbps/bin/xbps-checkvers/main.c

755 lines
18 KiB
C
Raw Normal View History

/*
* 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, *format;
char *xbps_conf, *rootdir, *distdir, *buf, *ptr, *cachefile;
size_t bufsz, len;
uint8_t have_vars;
bool show_all, manual, installed;
xbps_dictionary_t env;
xbps_dictionary_t pkgd;
xbps_dictionary_t cache;
struct xbps_handle xhp;
} 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"
2019-06-14 16:29:25 +05:30
" -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;
}
2019-06-14 16:29:25 +05:30
static void
rcv_printf(rcv_t *rcv, FILE *fp, const char *pkgname, const char *repover,
const char *srcver, const char *repourl)
2019-06-14 16:29:25 +05:30
{
const char *f, *p;
2019-06-14 16:29:25 +05:30
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;
2019-06-14 16:29:25 +05:30
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;
2019-06-14 16:29:25 +05:30
}
}
fputc('\n', fp);
}
static int
rcv_check_version(rcv_t *rcv)
{
const char *repover = NULL;
char srcver[BUFSIZ] = { '\0' }, *binpkgname = NULL, *s = NULL;
const char *pkgname, *version, *revision, *reverts, *repourl;
int sz;
size_t len;
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);
}
2015-12-21 21:21:24 +05:30
reverts = NULL;
xbps_dictionary_get_cstring_nocopy(rcv->env, "reverts", &reverts);
2019-06-14 16:29:25 +05:30
sz = snprintf(srcver, sizeof srcver, "%s_%s", version, revision);
if (sz < 0 || (size_t)sz >= sizeof srcver)
exit(EXIT_FAILURE);
/* Check against binpkg's pkgname, not pkgname from template */
s = strchr(rcv->fname, '/');
len = s ? strlen(rcv->fname) - strlen(s) : strlen(rcv->fname);
binpkgname = strndup(rcv->fname, len);
assert(binpkgname);
repourl = NULL;
if (rcv->installed) {
rcv->pkgd = xbps_pkgdb_get_pkg(&rcv->xhp, binpkgname);
} else {
rcv->pkgd = xbps_rpool_get_pkg(&rcv->xhp, binpkgname);
xbps_dictionary_get_cstring_nocopy(rcv->pkgd, "repository", &repourl);
}
xbps_dictionary_get_cstring_nocopy(rcv->pkgd, "pkgver", &repover);
if (repover)
repover += strlen(binpkgname)+1;
free(binpkgname);
2019-06-13 16:58:48 +05:30
if (!repover && rcv->manual)
;
else if (rcv->show_all)
;
else if (repover && (xbps_cmpver(repover, srcver) < 0 ||
(reverts && check_reverts(repover, reverts))))
2019-06-13 16:58:48 +05:30
;
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;
2019-06-14 16:29:25 +05:30
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;
2019-06-14 16:29:25 +05:30
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':
2019-06-13 16:33:47 +05:30
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);
}
}
/*
2014-09-26 17:54:06 +05:30
* If --distdir not set default to ~/void-packages.
*/
if (rcv.distdir == NULL)
rcv.distdir = xbps_xasprintf("%s/void-packages", getenv("HOME"));
{
char *tmp = rcv.distdir;
rcv.distdir = realpath(tmp, NULL);
if (rcv.distdir == NULL) {
fprintf(stderr, "Error: realpath(%s): %s\n", tmp, strerror(errno));
exit(1);
}
free(tmp);
}
rcv.cachefile = xbps_xasprintf("%s/.xbps-checkvers-0.58.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);
}