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(®, re, 144 REG_EXTENDED | (ignorecase ? REG_ICASE : 0)) == 0) { 145 if (regexec(®, origin, 0, NULL, 0) == 0) 146 ret = 1; 147 regfree(®); 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 }