pkg_cudf.c
1 /*- 2 * Copyright (c) 2013 Vsevolod Stakhov <vsevolod@FreeBSD.org> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer 10 * in this position and unchanged. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR 16 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 17 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 18 * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, 19 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 20 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 21 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 22 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 24 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27 #include <stdio.h> 28 #include <ctype.h> 29 30 #include "pkg.h" 31 #include "private/event.h" 32 #include "private/pkg.h" 33 #include "private/pkgdb.h" 34 #include "private/pkg_jobs.h" 35 36 /* 37 * CUDF does not support packages with '_' in theirs names, therefore 38 * use this ugly function to replace '_' to '@' 39 */ 40 static inline int 41 cudf_print_package_name(FILE *f, const char *name) 42 { 43 const char *p, *c; 44 int r = 0; 45 46 p = c = name; 47 while (*p) { 48 if (*p == '_') { 49 r += fprintf(f, "%.*s", (int)(p - c), c); 50 fputc('@', f); 51 r ++; 52 c = p + 1; 53 } 54 p ++; 55 } 56 if (p > c) { 57 r += fprintf(f, "%.*s", (int)(p - c), c); 58 } 59 60 return r; 61 } 62 63 static inline int 64 cudf_print_element(FILE *f, const char *line, bool has_next, size_t *column) 65 { 66 int ret = 0; 67 if (*column > 80) { 68 *column = 0; 69 ret += fprintf(f, "\n "); 70 } 71 72 ret += cudf_print_package_name(f, line); 73 74 if (has_next) 75 ret += fprintf(f, ", "); 76 else 77 ret += fprintf(f, "\n"); 78 79 if (ret > 0) 80 *column += ret; 81 82 return (ret); 83 } 84 85 static inline int 86 cudf_print_conflict(FILE *f, const char *uid, int ver, bool has_next, size_t *column) 87 { 88 int ret = 0; 89 if (*column > 80) { 90 *column = 0; 91 ret += fprintf(f, "\n "); 92 } 93 94 ret += cudf_print_package_name(f, uid); 95 ret += fprintf(f, "=%d", ver); 96 97 if (has_next) 98 ret += fprintf(f, ", "); 99 else 100 ret += fprintf(f, "\n"); 101 102 if (ret > 0) 103 *column += ret; 104 105 return (ret); 106 } 107 108 109 static int 110 cudf_emit_pkg(struct pkg *pkg, int version, FILE *f, 111 struct pkg_job_universe_item *conflicts_chain) 112 { 113 struct pkg_dep *dep; 114 struct pkg_conflict *conflict; 115 struct pkg_job_universe_item *u; 116 size_t column = 0; 117 int ver; 118 119 if (fprintf(f, "package: ") < 0) 120 return (EPKG_FATAL); 121 122 if (cudf_print_package_name(f, pkg->uid) < 0) 123 return (EPKG_FATAL); 124 125 if (fprintf(f, "\nversion: %d\n", version) < 0) 126 return (EPKG_FATAL); 127 128 if (pkghash_count(pkg->depshash) > 0) { 129 if (fprintf(f, "depends: ") < 0) 130 return (EPKG_FATAL); 131 LL_FOREACH(pkg->depends, dep) { 132 if (cudf_print_element(f, dep->name, 133 column + 1 == pkghash_count(pkg->depshash), &column) < 0) { 134 return (EPKG_FATAL); 135 } 136 } 137 } 138 139 column = 0; 140 if (vec_len(&pkg->provides) > 0) { 141 if (fprintf(f, "provides: ") < 0) 142 return (EPKG_FATAL); 143 vec_foreach(pkg->provides, i) { 144 if (cudf_print_element(f, pkg->provides.d[i], 145 column + 1 == vec_len(&pkg->provides), &column) < 0) { 146 return (EPKG_FATAL); 147 } 148 } 149 } 150 151 column = 0; 152 if (pkghash_count(pkg->conflictshash) > 0 || 153 (conflicts_chain->next != NULL && 154 !conflicts_chain->next->cudf_emit_skip)) { 155 if (fprintf(f, "conflicts: ") < 0) 156 return (EPKG_FATAL); 157 LL_FOREACH(pkg->conflicts, conflict) { 158 if (cudf_print_element(f, conflict->uid, 159 (conflict->next != NULL), &column) < 0) { 160 return (EPKG_FATAL); 161 } 162 } 163 ver = 1; 164 LL_FOREACH(conflicts_chain, u) { 165 if (u->pkg != pkg && !u->cudf_emit_skip) { 166 if (cudf_print_conflict(f, pkg->uid, ver, 167 (u->next != NULL && u->next->pkg != pkg), &column) < 0) { 168 return (EPKG_FATAL); 169 } 170 } 171 ver ++; 172 } 173 } 174 175 if (fprintf(f, "installed: %s\n\n", pkg->type == PKG_INSTALLED ? 176 "true" : "false") < 0) 177 return (EPKG_FATAL); 178 179 return (EPKG_OK); 180 } 181 182 static int 183 cudf_emit_request_packages(const char *op, struct pkg_jobs *j, FILE *f) 184 { 185 struct pkg_job_request *req; 186 size_t column = 0, cnt = 0, max; 187 bool printed = false; 188 pkghash_it it; 189 190 max = pkghash_count(j->request_add); 191 if (fprintf(f, "%s: ", op) < 0) 192 return (EPKG_FATAL); 193 it = pkghash_iterator(j->request_add); 194 while (pkghash_next(&it)) { 195 req = it.value; 196 cnt++; 197 if (req->skip) 198 continue; 199 if (cudf_print_element(f, req->item->pkg->uid, 200 (max > cnt), &column) < 0) { 201 return (EPKG_FATAL); 202 } 203 printed = true; 204 } 205 206 if (!printed) 207 if (fputc('\n', f) < 0) 208 return (EPKG_FATAL); 209 210 column = 0; 211 printed = false; 212 if (fprintf(f, "remove: ") < 0) 213 return (EPKG_FATAL); 214 max = pkghash_count(j->request_delete); 215 it = pkghash_iterator(j->request_delete); 216 while (pkghash_next(&it)) { 217 req = it.value; 218 cnt++; 219 if (req->skip) 220 continue; 221 if (cudf_print_element(f, req->item->pkg->uid, 222 (max > cnt), &column) < 0) { 223 return (EPKG_FATAL); 224 } 225 printed = true; 226 } 227 228 if (!printed) 229 if (fputc('\n', f) < 0) 230 return (EPKG_FATAL); 231 232 return (EPKG_OK); 233 } 234 235 static int 236 pkg_cudf_version_cmp(struct pkg_job_universe_item *a, struct pkg_job_universe_item *b) 237 { 238 int ret; 239 240 ret = pkg_version_cmp(a->pkg->version, b->pkg->version); 241 if (ret == 0) { 242 /* Ignore remote packages whose versions are equal to ours */ 243 if (a->pkg->type != PKG_INSTALLED) 244 a->cudf_emit_skip = true; 245 else if (b->pkg->type != PKG_INSTALLED) 246 b->cudf_emit_skip = true; 247 } 248 249 250 return (ret); 251 } 252 253 int 254 pkg_jobs_cudf_emit_file(struct pkg_jobs *j, pkg_jobs_t t, FILE *f) 255 { 256 struct pkg *pkg; 257 struct pkg_job_universe_item *it, *icur; 258 int version; 259 pkghash_it hit; 260 261 if (fprintf(f, "preamble: \n\n") < 0) 262 return (EPKG_FATAL); 263 264 hit = pkghash_iterator(j->universe->items); 265 while (pkghash_next(&hit)) { 266 it = (struct pkg_job_universe_item *)hit.value; 267 /* XXX 268 * Here are dragons: 269 * after sorting it we actually modify the head of the list, but there is 270 * no simple way to update a pointer in uthash, therefore universe hash 271 * contains not a head of list but a random elt of the conflicts chain: 272 * before: 273 * head -> elt1 -> elt2 -> elt3 274 * after: 275 * elt1 -> elt3 -> head -> elt2 276 * 277 * But hash would still point to head whilst the real head is elt1. 278 * So after sorting we need to rotate conflicts chain back to find the new 279 * head. 280 */ 281 DL_SORT(it, pkg_cudf_version_cmp); 282 283 version = 1; 284 LL_FOREACH(it, icur) { 285 if (!icur->cudf_emit_skip) { 286 pkg = icur->pkg; 287 288 if (cudf_emit_pkg(pkg, version ++, f, it) != EPKG_OK) 289 return (EPKG_FATAL); 290 } 291 } 292 } 293 294 if (fprintf(f, "request: \n") < 0) 295 return (EPKG_FATAL); 296 297 switch (t) { 298 case PKG_JOBS_FETCH: 299 case PKG_JOBS_INSTALL: 300 case PKG_JOBS_DEINSTALL: 301 case PKG_JOBS_AUTOREMOVE: 302 if (cudf_emit_request_packages("install", j, f) != EPKG_OK) 303 return (EPKG_FATAL); 304 break; 305 case PKG_JOBS_UPGRADE: 306 if (cudf_emit_request_packages("upgrade", j, f) != EPKG_OK) 307 return (EPKG_FATAL); 308 break; 309 } 310 return (EPKG_OK); 311 } 312 313 /* 314 * Perform backward conversion of an uid replacing '@' to '_' 315 */ 316 static char * 317 cudf_strdup(const char *in) 318 { 319 size_t len = strlen(in); 320 char *out, *d; 321 const char *s; 322 323 out = xmalloc(len + 1); 324 325 s = in; 326 d = out; 327 while (isspace(*s)) 328 s++; 329 while (*s) { 330 if (!isspace(*s)) 331 *d++ = (*s == '@') ? '_' : *s; 332 s++; 333 } 334 335 *d = '\0'; 336 return (out); 337 } 338 339 static void 340 pkg_jobs_cudf_insert_res_job (pkg_solved_list *target, 341 struct pkg_job_universe_item *it_new, 342 struct pkg_job_universe_item *it_old, 343 int type) 344 { 345 struct pkg_solved *res; 346 347 res = xcalloc(1, sizeof(struct pkg_solved)); 348 349 res->items[0] = it_new; 350 res->type = type; 351 if (it_old != NULL) 352 res->items[1] = it_old; 353 354 vec_push(target, res); 355 } 356 357 struct pkg_cudf_entry { 358 char *uid; 359 bool was_installed; 360 bool installed; 361 char *version; 362 }; 363 364 static int 365 pkg_jobs_cudf_add_package(struct pkg_jobs *j, struct pkg_cudf_entry *entry) 366 { 367 struct pkg_job_universe_item *it, *cur, *selected = NULL, *old = NULL, *head; 368 int ver, n; 369 370 it = pkg_jobs_universe_find(j->universe, entry->uid); 371 if (it == NULL) { 372 pkg_emit_error("package %s is found in CUDF output but not in the universe", 373 entry->uid); 374 return (EPKG_FATAL); 375 } 376 377 /* 378 * Now we need to select an appropriate version. We assume that 379 * the order of packages in list is the same as was passed to the 380 * cudf solver. 381 */ 382 ver = strtoul(entry->version, NULL, 10); 383 384 /* Find the old head, see the comment in `pkg_jobs_cudf_emit_file` */ 385 cur = it; 386 do { 387 head = cur; 388 cur = cur->prev; 389 } while (cur->next != NULL); 390 391 n = 1; 392 LL_FOREACH(head, cur) { 393 if (n == ver) { 394 selected = cur; 395 break; 396 } 397 n ++; 398 } 399 400 if (selected == NULL) { 401 pkg_emit_error("package %s-%d is found in CUDF output but the " 402 "universe has no such version (only %d versions found)", 403 entry->uid, ver, n); 404 return (EPKG_FATAL); 405 } 406 407 if (n == 1) { 408 if (entry->installed && selected->pkg->type != PKG_INSTALLED) { 409 pkg_debug(3, "pkg_cudf: schedule installation of %s(%d)", 410 entry->uid, ver); 411 pkg_jobs_cudf_insert_res_job (&j->jobs, selected, NULL, PKG_SOLVED_INSTALL); 412 } 413 else if (!entry->installed && selected->pkg->type == PKG_INSTALLED) { 414 pkg_debug(3, "pkg_cudf: schedule removing of %s(%d)", 415 entry->uid, ver); 416 pkg_jobs_cudf_insert_res_job (&j->jobs, selected, NULL, PKG_SOLVED_DELETE); 417 } 418 } 419 else { 420 /* Define upgrade */ 421 LL_FOREACH(head, cur) { 422 if (cur != selected) { 423 old = cur; 424 break; 425 } 426 } 427 pkg_debug(3, "pkg_cudf: schedule upgrade of %s(to %d)", 428 entry->uid, ver); 429 assert(old != NULL); 430 /* XXX: this is a hack due to iterators stupidity */ 431 selected->pkg->old_version = old->pkg->version; 432 pkg_jobs_cudf_insert_res_job (&j->jobs, selected, old, PKG_SOLVED_UPGRADE); 433 } 434 435 return (EPKG_OK); 436 } 437 438 int 439 pkg_jobs_cudf_parse_output(struct pkg_jobs *j, FILE *f) 440 { 441 char *line = NULL, *begin, *param, *value; 442 size_t linecap = 0; 443 struct pkg_cudf_entry cur_pkg; 444 445 memset(&cur_pkg, 0, sizeof(cur_pkg)); 446 447 while (getline(&line, &linecap, f) > 0) { 448 /* Split line, cut spaces */ 449 begin = line; 450 param = strsep(&begin, ": \t"); 451 value = begin; 452 while(begin != NULL) 453 value = strsep(&begin, " \t"); 454 455 if (STREQ(param, "package")) { 456 if (cur_pkg.uid != NULL) { 457 if (pkg_jobs_cudf_add_package(j, &cur_pkg) != EPKG_OK) { 458 free(line); 459 return (EPKG_FATAL); 460 } 461 } 462 cur_pkg.uid = cudf_strdup(value); 463 cur_pkg.was_installed = false; 464 cur_pkg.installed = false; 465 cur_pkg.version = NULL; 466 } 467 else if (STREQ(param, "version")) { 468 if (cur_pkg.uid == NULL) { 469 pkg_emit_error("version line has no corresponding uid in CUDF output"); 470 free(line); 471 return (EPKG_FATAL); 472 } 473 cur_pkg.version = cudf_strdup(value); 474 } 475 else if (STREQ(param, "installed")) { 476 if (cur_pkg.uid == NULL) { 477 pkg_emit_error("installed line has no corresponding uid in CUDF output"); 478 free(line); 479 return (EPKG_FATAL); 480 } 481 if (strncmp(value, "true", 4) == 0) 482 cur_pkg.installed = true; 483 } 484 else if (STREQ(param, "was-installed")) { 485 if (cur_pkg.uid == NULL) { 486 pkg_emit_error("was-installed line has no corresponding uid in CUDF output"); 487 free(line); 488 return (EPKG_FATAL); 489 } 490 if (strncmp(value, "true", 4) == 0) 491 cur_pkg.was_installed = true; 492 } 493 } 494 495 if (cur_pkg.uid != NULL) { 496 if (pkg_jobs_cudf_add_package(j, &cur_pkg) != EPKG_OK) { 497 free(line); 498 return (EPKG_FATAL); 499 } 500 } 501 502 free(line); 503 504 return (EPKG_OK); 505 }