set.c
1 /*- 2 * Copyright (c) 2012 Baptiste Daroussin <bapt@FreeBSD.org> 3 * Copyright (c) 2014 Matthew Seaman <matthew@FreeBSD.org> 4 * Copyright (c) 2014 Vsevolod Stakhov <vsevolod@FreeBSD.org> 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer 12 * in this position and unchanged. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR 18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20 * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, 21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include <err.h> 30 #include <getopt.h> 31 #include <stdio.h> 32 #include <stdbool.h> 33 #include <stdlib.h> 34 #include <string.h> 35 #include <unistd.h> 36 37 #include <pkg.h> 38 39 #include <xmalloc.h> 40 41 #include <bsd_compat.h> 42 43 #include "pkgcli.h" 44 45 #define AUTOMATIC 1U<<0 46 #define ORIGIN 1U<<1 47 #define NAME 1U<<2 48 #define VITAL 1U<<3 49 50 void 51 usage_set(void) 52 { 53 fprintf(stderr, "Usage: pkg set [-ay] [-A 0|1] [-o <oldorigin>:<neworigin>] [-n <oldname>:<newname>] [-p] [-Cgix] [-v 0|1] <pkg-name>\n\n"); 54 fprintf(stderr, "For more information see 'pkg help set'. \n"); 55 } 56 57 static bool 58 check_change_values(const char *opt, char **oldv, char **newv, char guard) 59 { 60 const char *semicolon; 61 62 if (opt == NULL) 63 return (false); 64 65 semicolon = strrchr(opt, ':'); 66 67 if (semicolon == NULL) 68 return (false); 69 70 *oldv = xmalloc(semicolon - opt + 1); 71 strlcpy(*oldv, opt, semicolon - opt + 1); 72 *newv = xstrdup(semicolon + 1); 73 74 if (guard != '\0') { 75 /* Check guard symbol in both new and old values */ 76 if (strrchr(*oldv, guard) == NULL || 77 strrchr(*newv, guard) == NULL) { 78 free(*oldv); 79 free(*newv); 80 *oldv = NULL; 81 *newv = NULL; 82 83 return (false); 84 } 85 } 86 87 return (true); 88 } 89 90 int 91 exec_set(int argc, char **argv) 92 { 93 struct pkgdb *db = NULL; 94 struct pkgdb_it *it = NULL; 95 struct pkg *pkg = NULL; 96 int ch; 97 int i; 98 match_t match = MATCH_EXACT; 99 int64_t newautomatic = -1; 100 int64_t newvital = -1; 101 bool automatic = false; 102 bool rc = false; 103 bool vital = false; 104 const char *changed = NULL; 105 char *newvalue = NULL; 106 char *oldvalue = NULL; 107 unsigned int loads = PKG_LOAD_BASIC; 108 unsigned int sets = 0; 109 unsigned int field = 0, depfield = 0; 110 int retcode; 111 bool partial = false; 112 113 struct option longopts[] = { 114 { "automatic", required_argument, NULL, 'A' }, 115 { "all", no_argument, NULL, 'a' }, 116 { "case-sensitive", no_argument, NULL, 'C' }, 117 { "glob", no_argument, NULL, 'g' }, 118 { "case-insensitive", no_argument, NULL, 'i' }, 119 { "change-origin", required_argument, NULL, 'o' }, 120 { "partial", no_argument, NULL, 'p' }, 121 { "change-name", required_argument, NULL, 'n' }, 122 { "regex", no_argument, NULL, 'x' }, 123 { "vital", required_argument, NULL, 'v' }, 124 { "yes", no_argument, NULL, 'y' }, 125 { NULL, 0, NULL, 0 }, 126 }; 127 128 while ((ch = getopt_long(argc, argv, "+A:aCgio:pxyn:v:", longopts, NULL)) != -1) { 129 switch (ch) { 130 case 'A': 131 sets |= AUTOMATIC; 132 newautomatic = optarg[0] - '0'; 133 if (newautomatic != 0 && newautomatic != 1) 134 errx(EXIT_FAILURE, "Wrong value for -A. " 135 "Expecting 0 or 1, got: %s", 136 optarg); 137 break; 138 case 'a': 139 match = MATCH_ALL; 140 break; 141 case 'C': 142 pkgdb_set_case_sensitivity(true); 143 break; 144 case 'g': 145 match = MATCH_GLOB; 146 break; 147 case 'i': 148 pkgdb_set_case_sensitivity(false); 149 break; 150 case 'o': 151 sets |= ORIGIN; 152 loads |= PKG_LOAD_DEPS; 153 match = MATCH_ALL; 154 changed = "origin"; 155 if (!check_change_values(optarg, &oldvalue, &newvalue, '\0')) { 156 errx(EXIT_FAILURE, "Wrong format for -o. " 157 "Expecting oldorigin:neworigin, got: %s", 158 optarg); 159 } 160 break; 161 case 'n': 162 sets |= NAME; 163 loads |= PKG_LOAD_DEPS; 164 match = MATCH_ALL; 165 changed = "name"; 166 if (!check_change_values(optarg, &oldvalue, &newvalue, '\0')) { 167 errx(EXIT_FAILURE, "Wrong format for -n. " 168 "Expecting oldname:newname, got: %s", 169 optarg); 170 } 171 break; 172 case 'x': 173 match = MATCH_REGEX; 174 break; 175 case 'v': 176 sets |= VITAL; 177 newvital = optarg[0] - '0'; 178 if (newvital != 0 && newvital != 1) 179 errx(EXIT_FAILURE, "Wrong value for -v. " 180 "Expecting 0 or 1, got: %s", 181 optarg); 182 break; 183 case 'y': 184 yes = true; 185 break; 186 case 'p': 187 partial = true; 188 break; 189 default: 190 free(oldvalue); 191 free(newvalue); 192 usage_set(); 193 return (EXIT_FAILURE); 194 } 195 } 196 197 argc -= optind; 198 argv += optind; 199 200 if ((argc < 1 && match != MATCH_ALL) || 201 (newautomatic == -1 && newvital == -1 && newvalue == NULL) || 202 (sets & (NAME|ORIGIN)) == (NAME|ORIGIN)) { 203 free(newvalue); 204 usage_set(); 205 return (EXIT_FAILURE); 206 } 207 208 if (sets & NAME) { 209 field = PKG_SET_NAME; 210 depfield = PKG_SET_DEPNAME; 211 } 212 else if (sets & ORIGIN) { 213 field = PKG_SET_ORIGIN; 214 depfield = PKG_SET_DEPORIGIN; 215 } 216 if (partial && ((sets & (NAME|ORIGIN)) == 0)) { 217 warnx("-p requires either -o or -n option"); 218 usage_set(); 219 return (EXIT_FAILURE); 220 } 221 222 retcode = pkgdb_access(PKGDB_MODE_READ|PKGDB_MODE_WRITE, 223 PKGDB_DB_LOCAL); 224 if (retcode == EPKG_ENODB) { 225 free(newvalue); 226 if (match == MATCH_ALL) 227 return (EXIT_SUCCESS); 228 if (!quiet) 229 warnx("No packages installed. Nothing to do!"); 230 return (EXIT_SUCCESS); 231 } else if (retcode == EPKG_ENOACCESS) { 232 free(newvalue); 233 warnx("Insufficient privileges to modify the package database"); 234 return (EXIT_FAILURE); 235 } else if (retcode != EPKG_OK) { 236 warnx("Error accessing package database"); 237 free(newvalue); 238 return (EXIT_FAILURE); 239 } 240 241 if (pkgdb_open(&db, PKGDB_DEFAULT) != EPKG_OK) { 242 free(newvalue); 243 return (EXIT_FAILURE); 244 } 245 246 if (pkgdb_obtain_lock(db, PKGDB_LOCK_EXCLUSIVE) != EPKG_OK) { 247 pkgdb_close(db); 248 free(newvalue); 249 warnx("Cannot get an exclusive lock on a database, it is locked by another process"); 250 return (EXIT_FAILURE); 251 } 252 253 if (pkgdb_transaction_begin(db, NULL) != EPKG_OK) { 254 pkgdb_close(db); 255 free(newvalue); 256 warnx("Cannot start transaction for update"); 257 return (EXIT_FAILURE); 258 } 259 260 if (partial) { 261 int cnt, cntdep; 262 rc = yes; 263 if (!yes) 264 rc = query_yesno(false, "Do you want to batch replace '%S' in package %S with '%S'?", 265 oldvalue, changed, newvalue); 266 if (!rc) { 267 retcode = EXIT_SUCCESS; 268 goto cleanup; 269 } 270 cnt = pkgdb_replace(db, field, oldvalue, newvalue); 271 if (cnt < 0) { 272 retcode = EXIT_FAILURE; 273 goto cleanup; 274 } else if (cnt == 0) { 275 if (!quiet) 276 warnx("No packages renamed."); 277 retcode = EXIT_FAILURE; 278 goto cleanup; 279 } 280 cntdep = pkgdb_replace(db, depfield, oldvalue, newvalue); 281 if (cntdep < 0) { 282 retcode = EXIT_FAILURE; 283 goto cleanup; 284 } 285 if (!quiet) 286 printf("%d packages have been renamed.\n", cnt); 287 retcode = EXIT_SUCCESS; 288 goto cleanup; 289 } 290 291 if (oldvalue != NULL) { 292 match = MATCH_ALL; 293 if ((it = pkgdb_query(db, oldvalue, 294 (sets & NAME) ? MATCH_INTERNAL : MATCH_EXACT)) == NULL) { 295 retcode = EXIT_FAILURE; 296 goto cleanup; 297 } 298 299 if (pkgdb_it_next(it, &pkg, PKG_LOAD_BASIC) != EPKG_OK) 300 pkg = NULL; 301 302 rc = yes; 303 if (!yes) { 304 if (pkg != NULL) 305 rc = query_yesno(false, "Change %S from %S to %S for %n-%v? ", 306 changed, oldvalue, newvalue, pkg, pkg); 307 else 308 rc = query_yesno(false, "Change %S from %S to %S for all dependencies? ", 309 changed, oldvalue, newvalue); 310 } 311 if (pkg != NULL && rc) { 312 if (pkgdb_set(db, pkg, field, newvalue) != EPKG_OK) { 313 retcode = EXIT_FAILURE; 314 goto cleanup; 315 } 316 } 317 pkgdb_it_free(it); 318 } 319 i = 0; 320 do { 321 bool saved_rc = rc; 322 bool gotone = false; 323 324 if ((it = pkgdb_query(db, argv[i], match)) == NULL) { 325 retcode = EXIT_FAILURE; 326 goto cleanup; 327 } 328 329 while (pkgdb_it_next(it, &pkg, loads) == EPKG_OK) { 330 gotone = true; 331 if ((sets & AUTOMATIC) == AUTOMATIC) { 332 pkg_get(pkg, PKG_ATTR_AUTOMATIC, &automatic); 333 if (automatic == newautomatic) 334 continue; 335 if (!rc) { 336 if (newautomatic) 337 rc = query_yesno(false, 338 "Mark %n-%v as automatically installed? ", 339 pkg, pkg); 340 else 341 rc = query_yesno(false, 342 "Mark %n-%v as not automatically installed? ", 343 pkg, pkg); 344 } 345 if (rc) 346 pkgdb_set(db, pkg, PKG_SET_AUTOMATIC, (int)newautomatic); 347 rc = saved_rc; 348 } 349 if ((sets & VITAL) == VITAL) { 350 pkg_get(pkg, PKG_ATTR_VITAL, &vital); 351 if (vital == newvital) 352 continue; 353 if (!rc) { 354 if (newvital) 355 rc = query_yesno(false, 356 "Mark %n-%v as vital? ", 357 pkg, pkg); 358 else 359 rc = query_yesno(false, 360 "Mark %n-%v as not vital? ", 361 pkg, pkg); 362 } 363 if (rc) 364 pkgdb_set(db, pkg, PKG_SET_VITAL, (int)newvital); 365 rc = saved_rc; 366 } 367 if (sets & (ORIGIN|NAME)) { 368 struct pkg_dep *d = NULL; 369 while (pkg_deps(pkg, &d) == EPKG_OK) { 370 /* 371 * Do not query user when he has already 372 * been queried. 373 */ 374 if (pkgdb_set(db, pkg, depfield, oldvalue, newvalue) != EPKG_OK) { 375 retcode = EXIT_FAILURE; 376 goto cleanup; 377 } 378 } 379 } 380 } 381 if (!gotone) { 382 warnx("No package(s) matching %s", argv[i]); 383 retcode = EXIT_FAILURE; 384 } 385 pkgdb_it_free(it); 386 i++; 387 } while (i < argc); 388 389 cleanup: 390 free(oldvalue); 391 free(newvalue); 392 pkg_free(pkg); 393 394 if (retcode == 0) { 395 pkgdb_transaction_commit(db, NULL); 396 } 397 else { 398 pkgdb_transaction_rollback(db, NULL); 399 } 400 401 pkgdb_release_lock(db, PKGDB_LOCK_EXCLUSIVE); 402 pkgdb_close(db); 403 404 return (retcode); 405 }