/*-
* Copyright (c) 2011-2025 Baptiste Daroussin <bapt@FreeBSD.org>
* Copyright (c) 2014 Matthew Seaman <matthew@FreeBSD.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#if __has_include(<sys/capsicum.h>)
#include <sys/capsicum.h>
#define HAVE_CAPSICUM 1
#endif
#include <err.h>
#include <errno.h>
#include <fnmatch.h>
#include <getopt.h>
#include <pkg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <regex.h>
#include <xmalloc.h>
#include "pkgcli.h"
void
usage_updating(void)
{
fprintf(stderr, "Usage: pkg updating [-i] [-d YYYYMMDD] [-f file] [portname ...]\n");
fprintf(stderr, "For more information see 'pkg help updating'.\n");
}
static char *
convert_re(const char *src)
{
const char *p;
char *q;
bool brace_flag = false;
size_t len = strlen(src);
char *buf = xmalloc(len*2+1);
for (p=src, q=buf; p < src+len; p++) {
switch (*p) {
case '*':
*q++ = '.';
*q++ = '*';
break;
case '?':
*q++ = '.';
break;
case '.':
*q++ = '\\';
*q++ = '.';
break;
case '{':
*q++='(';
brace_flag=true;
break;
case ',':
if (brace_flag)
*q++='|';
else
*q++=*p;
break;
case '}':
*q++=')';
brace_flag=false;
break;
default:
*q++ = *p;
}
}
*q ='\0';
return buf;
}
int
matcher(const char *affects, const char *origin, bool ignorecase)
{
int i, n, count, ret, fnflags;
bool was_spc;
size_t len;
char *re, *buf, *p, **words;
len = strlen(affects);
buf = xstrdup(affects);
for (count = 0, was_spc = true, p = buf; p < buf + len ; p++) {
if (isspace(*p)) {
if (!was_spc)
was_spc = true;
*p = '\0';
} else {
if (was_spc) {
count++;
was_spc = false;
}
}
}
words = xmalloc(sizeof(char*)*count);
for (i = 0, was_spc = true, p = buf; p < buf + len ; p++) {
if (*p == '\0') {
if (!was_spc)
was_spc = true;
} else {
if (was_spc) {
words[i++] = p;
was_spc = false;
}
}
}
for(ret = 0, i = 0; i < count; i++) {
n = strlen(words[i]);
if (words[i][n-1] == ',') {
words[i][n-1] = '\0';
}
fnflags = ignorecase ? FNM_CASEFOLD : 0;
/* Try glob match in both directions: AFFECTS word as
* pattern against origin, and origin as pattern against
* AFFECTS word (for user-provided globs on the command
* line, issue #1786) */
if (fnmatch(words[i], origin, fnflags) == 0 ||
fnmatch(origin, words[i], fnflags) == 0) {
ret = 1;
break;
}
/* Handle {a,b} brace expansion and (a|b) alternation
* via regex conversion (not supported by fnmatch) */
if ((strchr(words[i], '{') != NULL && strchr(words[i], '}') != NULL) ||
(strchr(words[i], '(') != NULL && strchr(words[i], ')') != NULL)) {
re = convert_re(words[i]);
if (re != NULL) {
regex_t reg;
if (regcomp(®, re,
REG_EXTENDED | (ignorecase ? REG_ICASE : 0)) == 0) {
if (regexec(®, origin, 0, NULL, 0) == 0)
ret = 1;
regfree(®);
}
free(re);
if (ret)
break;
}
}
}
free(words);
free(buf);
return (ret);
}
int
exec_updating(int argc, char **argv)
{
char *date = NULL;
char *dateline = NULL;
char *updatingfile = NULL;
bool caseinsensitive = false;
charv_t origins = vec_init();
int ch;
char *line = NULL;
size_t linecap = 0;
char *tmp;
int head = 0;
int found = 0;
struct pkgdb *db = NULL;
struct pkg *pkg = NULL;
struct pkgdb_it *it = NULL;
FILE *fd;
int retcode = EXIT_SUCCESS;
#ifdef HAVE_CAPSICUM
cap_rights_t rights;
#endif
struct option longopts[] = {
{ "date", required_argument, NULL, 'd' },
{ "file", required_argument, NULL, 'f' },
{ "case-insensitive", no_argument, NULL, 'i' },
{ NULL, 0, NULL, 0 },
};
while ((ch = getopt_long(argc, argv, "+d:f:i", longopts, NULL)) != -1) {
switch (ch) {
case 'd':
date = optarg;
break;
case 'f':
updatingfile = optarg;
break;
case 'i':
caseinsensitive = true;
break;
default:
usage_updating();
return (EXIT_FAILURE);
}
}
argc -= optind;
argv += optind;
/* checking date format */
if (date != NULL)
if (strlen(date) != 8 || strspn(date, "0123456789") != 8)
err(EXIT_FAILURE, "Invalid date format");
if (pkgdb_open(&db, PKGDB_DEFAULT_READONLY) != EPKG_OK)
return (EXIT_FAILURE);
if (!pkgdb_lock_or_fail(db, PKGDB_LOCK_READONLY))
return (EXIT_FAILURE);
if (updatingfile == NULL) {
const char *portsdir = pkg_object_string(pkg_config_get("PORTSDIR"));
if (portsdir == NULL) {
retcode = EXIT_FAILURE;
goto cleanup;
}
xasprintf(&updatingfile, "%s/UPDATING", portsdir);
}
fd = fopen(updatingfile, "r");
if (fd == NULL) {
warnx("Unable to open: %s", updatingfile);
goto cleanup;
}
#ifdef HAVE_CAPSICUM
cap_rights_init(&rights, CAP_READ);
if (cap_rights_limit(fileno(fd), &rights) < 0 && errno != ENOSYS ) {
warn("cap_rights_limit() failed");
fclose(fd);
return (EXIT_FAILURE);
}
#ifndef COVERAGE
if (cap_enter() < 0 && errno != ENOSYS) {
warn("cap_enter() failed");
fclose(fd);
return (EXIT_FAILURE);
}
#endif
#endif
if (argc == 0) {
if ((it = pkgdb_query(db, NULL, MATCH_ALL)) == NULL) {
retcode = EXIT_FAILURE;
fclose(fd);
goto cleanup;
}
while (pkgdb_it_next(it, &pkg, PKG_LOAD_BASIC) == EPKG_OK) {
char *orig;
pkg_asprintf(&orig, "%o", pkg);
vec_push(&origins, orig);
}
} else {
while (*argv) {
char *orig = xstrdup(*argv);
vec_push(&origins, orig);
argv++;
}
}
while (getline(&line, &linecap, fd) > 0) {
if (strspn(line, "0123456789:") == 9) {
free(dateline);
dateline = xstrdup(line);
found = 0;
head = 1;
} else if (head == 0) {
continue;
}
tmp = NULL;
if (found == 0) {
if (strstr(line, "AFFECTS") != NULL) {
vec_foreach(origins, i) {
if (matcher(line, origins.d[i], caseinsensitive) != 0) {
tmp = "";
break;
}
}
if (tmp == NULL)
tmp = strcasestr(line, "all users\n");
if (tmp == NULL)
tmp = strcasestr(line, "all ports users\n");
if (tmp != NULL) {
if ((date != NULL) && strncmp(dateline, date, 8) < 0) {
continue;
}
printf("%s%s",dateline, line);
found = 1;
}
}
} else {
printf("%s",line);
}
}
fclose(fd);
cleanup:
vec_free_and_free(&origins, free);
pkgdb_it_free(it);
pkgdb_release_lock(db, PKGDB_LOCK_READONLY);
pkgdb_close(db);
pkg_free(pkg);
free(line);
free(dateline);
return (retcode);
}