/ src / updating.c
updating.c
  1  /*-
  2   * Copyright (c) 2011-2025 Baptiste Daroussin <bapt@FreeBSD.org>
  3   * Copyright (c) 2014 Matthew Seaman <matthew@FreeBSD.org>
  4   *
  5   * SPDX-License-Identifier: BSD-2-Clause
  6   */
  7  
  8  #if __has_include(<sys/capsicum.h>)
  9  #include <sys/capsicum.h>
 10  #define HAVE_CAPSICUM 1
 11  #endif
 12  
 13  #include <err.h>
 14  #include <errno.h>
 15  #include <fnmatch.h>
 16  #include <getopt.h>
 17  #include <pkg.h>
 18  #include <stdio.h>
 19  #include <stdlib.h>
 20  #include <string.h>
 21  #include <unistd.h>
 22  #include <ctype.h>
 23  #include <regex.h>
 24  
 25  #include <xmalloc.h>
 26  #include "pkgcli.h"
 27  
 28  void
 29  usage_updating(void)
 30  {
 31  	fprintf(stderr, "Usage: pkg updating [-i] [-d YYYYMMDD] [-f file] [portname ...]\n");
 32  	fprintf(stderr, "For more information see 'pkg help updating'.\n");
 33  
 34  }
 35  
 36  static char *
 37  convert_re(const char *src)
 38  {
 39  	const char *p;
 40  	char *q;
 41  	bool brace_flag = false;
 42  	size_t len = strlen(src);
 43  	char *buf = xmalloc(len*2+1);
 44  
 45  	for (p=src, q=buf; p < src+len; p++) {
 46  		switch (*p) {
 47  		case '*':
 48  			*q++ = '.';
 49  			*q++ = '*';
 50  			break;
 51  		case '?':
 52  			*q++ = '.';
 53  			break;
 54  		case '.':
 55  			*q++ = '\\';
 56  			*q++ = '.';
 57  			break;
 58  		case '{':
 59  			*q++='(';
 60  			brace_flag=true;
 61  			break;
 62  		case ',':
 63  			if (brace_flag)
 64  				*q++='|';
 65  			else
 66  				*q++=*p;
 67  			break;
 68  		case '}':
 69  			*q++=')';
 70  			brace_flag=false;
 71  			break;
 72  		default:
 73  			*q++ = *p;
 74  		}
 75  	}
 76  	*q ='\0';
 77  	return buf;
 78  }
 79  
 80  int
 81  matcher(const char *affects, const char *origin, bool ignorecase)
 82  {
 83  	int i, n, count, ret, fnflags;
 84  	bool was_spc;
 85  	size_t len;
 86  	char *re, *buf, *p, **words;
 87  
 88  	len = strlen(affects);
 89  	buf = xstrdup(affects);
 90  
 91  	for (count = 0, was_spc = true, p = buf; p < buf + len ; p++) {
 92  		if (isspace(*p)) {
 93  			if (!was_spc)
 94  				was_spc = true;
 95  			*p = '\0';
 96  		} else {
 97  			if (was_spc) {
 98  				count++;
 99  				was_spc = false;
100  			}
101  		}
102  	}
103  
104  	words = xmalloc(sizeof(char*)*count);
105  
106  	for (i = 0, was_spc = true, p = buf; p < buf + len ; p++) {
107  		if (*p == '\0') {
108  			if (!was_spc)
109  				was_spc = true;
110  		} else {
111  			if (was_spc) {
112  				words[i++] = p;
113  				was_spc = false;
114  			}
115  		}
116  	}
117  
118  	for(ret = 0, i = 0; i < count; i++) {
119  		n = strlen(words[i]);
120  		if (words[i][n-1] == ',') {
121  			words[i][n-1] = '\0';
122  		}
123  
124  		fnflags = ignorecase ? FNM_CASEFOLD : 0;
125  
126  		/* Try glob match in both directions: AFFECTS word as
127  		 * pattern against origin, and origin as pattern against
128  		 * AFFECTS word (for user-provided globs on the command
129  		 * line, issue #1786) */
130  		if (fnmatch(words[i], origin, fnflags) == 0 ||
131  		    fnmatch(origin, words[i], fnflags) == 0) {
132  			ret = 1;
133  			break;
134  		}
135  
136  		/* Handle {a,b} brace expansion and (a|b) alternation
137  		 * via regex conversion (not supported by fnmatch) */
138  		if ((strchr(words[i], '{') != NULL && strchr(words[i], '}') != NULL) ||
139  		    (strchr(words[i], '(') != NULL && strchr(words[i], ')') != NULL)) {
140  			re = convert_re(words[i]);
141  			if (re != NULL) {
142  				regex_t reg;
143  				if (regcomp(&reg, re,
144  				    REG_EXTENDED | (ignorecase ? REG_ICASE : 0)) == 0) {
145  					if (regexec(&reg, origin, 0, NULL, 0) == 0)
146  						ret = 1;
147  					regfree(&reg);
148  				}
149  				free(re);
150  				if (ret)
151  					break;
152  			}
153  		}
154  	}
155  
156  	free(words);
157  	free(buf);
158  	return (ret);
159  }
160  
161  int
162  exec_updating(int argc, char **argv)
163  {
164  	char			*date = NULL;
165  	char			*dateline = NULL;
166  	char			*updatingfile = NULL;
167  	bool			caseinsensitive = false;
168  	charv_t 		 origins = vec_init();
169  	int			 ch;
170  	char			*line = NULL;
171  	size_t			 linecap = 0;
172  	char			*tmp;
173  	int			 head = 0;
174  	int			 found = 0;
175  	struct pkgdb		*db = NULL;
176  	struct pkg		*pkg = NULL;
177  	struct pkgdb_it		*it = NULL;
178  	FILE			*fd;
179  	int			 retcode = EXIT_SUCCESS;
180  #ifdef HAVE_CAPSICUM
181  	cap_rights_t rights;
182  #endif
183  
184  	struct option longopts[] = {
185  		{ "date",	required_argument,	NULL,	'd' },
186  		{ "file",	required_argument,	NULL,	'f' },
187  		{ "case-insensitive",	no_argument,	NULL,	'i' },
188  		{ NULL,		0,			NULL,	0   },
189  	};
190  
191  	while ((ch = getopt_long(argc, argv, "+d:f:i", longopts, NULL)) != -1) {
192  		switch (ch) {
193  		case 'd':
194  			date = optarg;
195  			break;
196  		case 'f':
197  			updatingfile = optarg;
198  			break;
199  		case 'i':
200  			caseinsensitive = true;
201  			break;
202  		default:
203  			usage_updating();
204  			return (EXIT_FAILURE);
205  		}
206  	}
207  	argc -= optind;
208  	argv += optind;
209  
210  	/* checking date format */
211  	if (date != NULL)
212  		if (strlen(date) != 8 || strspn(date, "0123456789") != 8)
213  			err(EXIT_FAILURE, "Invalid date format");
214  
215  	if (pkgdb_open(&db, PKGDB_DEFAULT_READONLY) != EPKG_OK)
216  		return (EXIT_FAILURE);
217  
218  	if (pkgdb_obtain_lock(db, PKGDB_LOCK_READONLY) != EPKG_OK) {
219  		pkgdb_close(db);
220  		warnx("Cannot get a read lock on a database, it is locked by another process");
221  		return (EXIT_FAILURE);
222  	}
223  
224  	if (updatingfile == NULL) {
225  		const char *portsdir = pkg_object_string(pkg_config_get("PORTSDIR"));
226  		if (portsdir == NULL) {
227  			retcode = EXIT_FAILURE;
228  			goto cleanup;
229  		}
230  		xasprintf(&updatingfile, "%s/UPDATING", portsdir);
231  	}
232  
233  	fd = fopen(updatingfile, "r");
234  	if (fd == NULL) {
235  		warnx("Unable to open: %s", updatingfile);
236  		goto cleanup;
237  	}
238  
239  #ifdef HAVE_CAPSICUM
240  	cap_rights_init(&rights, CAP_READ);
241  	if (cap_rights_limit(fileno(fd), &rights) < 0 && errno != ENOSYS ) {
242  		warn("cap_rights_limit() failed");
243  		fclose(fd);
244  		return (EXIT_FAILURE);
245  	}
246  
247  #ifndef COVERAGE
248  	if (cap_enter() < 0 && errno != ENOSYS) {
249  		warn("cap_enter() failed");
250  		fclose(fd);
251  		return (EXIT_FAILURE);
252  	}
253  #endif
254  #endif
255  
256  	if (argc == 0) {
257  		if ((it = pkgdb_query(db, NULL, MATCH_ALL)) == NULL) {
258  			retcode = EXIT_FAILURE;
259  			fclose(fd);
260  			goto cleanup;
261  		}
262  
263  		while (pkgdb_it_next(it, &pkg, PKG_LOAD_BASIC) == EPKG_OK) {
264  			char *orig;
265  			pkg_asprintf(&orig, "%o", pkg);
266  			vec_push(&origins, orig);
267  		}
268  	} else {
269  		while (*argv) {
270  			char *orig = xstrdup(*argv);
271  			vec_push(&origins, orig);
272  			argv++;
273  		}
274  	}
275  
276  	while (getline(&line, &linecap, fd) > 0) {
277  		if (strspn(line, "0123456789:") == 9) {
278  			free(dateline);
279  			dateline = xstrdup(line);
280  			found = 0;
281  			head = 1;
282  		} else if (head == 0) {
283  			continue;
284  		}
285  
286  		tmp = NULL;
287  		if (found == 0) {
288  			if (strstr(line, "AFFECTS") != NULL) {
289  				vec_foreach(origins, i) {
290  					if (matcher(line, origins.d[i], caseinsensitive) != 0) {
291  						tmp = "";
292  						break;
293  					}
294  				}
295  				if (tmp == NULL)
296  					tmp = strcasestr(line, "all users\n");
297  				if (tmp == NULL)
298  					tmp = strcasestr(line, "all ports users\n");
299  				if (tmp != NULL) {
300  					if ((date != NULL) && strncmp(dateline, date, 8) < 0) {
301  						continue;
302  					}
303  					printf("%s%s",dateline, line);
304  					found = 1;
305  				}
306  			}
307  		} else {
308  			printf("%s",line);
309  		}
310  	}
311  	fclose(fd);
312  
313  cleanup:
314  	vec_free_and_free(&origins, free);
315  	pkgdb_it_free(it);
316  	pkgdb_release_lock(db, PKGDB_LOCK_READONLY);
317  	pkgdb_close(db);
318  	pkg_free(pkg);
319  	free(line);
320  	free(dateline);
321  
322  	return (retcode);
323  }