1 /* 2 * Copyright (c) 2019-2020 The DragonFly Project. All rights reserved. 3 * 4 * This code is derived from software contributed to The DragonFly Project 5 * by Matthew Dillon <dillon@backplane.com> 6 * 7 * This code uses concepts and configuration based on 'synth', by 8 * John R. Marino <draco@marino.st>, which was written in ada. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 14 * 1. Redistributions of source code must retain the above copyright 15 * notice, this list of conditions and the following disclaimer. 16 * 2. Redistributions in binary form must reproduce the above copyright 17 * notice, this list of conditions and the following disclaimer in 18 * the documentation and/or other materials provided with the 19 * distribution. 20 * 3. Neither the name of The DragonFly Project nor the names of its 21 * contributors may be used to endorse or promote products derived 22 * from this software without specific, prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 25 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 26 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 27 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 28 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 29 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, 30 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 31 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 32 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 33 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 34 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 35 * SUCH DAMAGE. 36 */ 37 #include "dsynth.h" 38 #include <openssl/md5.h> 39 40 typedef struct pinfo { 41 struct pinfo *next; 42 char *spath; 43 int foundit; 44 int inlocks; 45 } pinfo_t; 46 47 static void removePackagesMetaRecurse(pkg_t *pkg); 48 static int pinfocmp(const void *s1, const void *s2); 49 static void scanit(const char *path, const char *subpath, 50 int *countp, pinfo_t ***list_tailp, 51 int inlocks); 52 pinfo_t *pinfofind(pinfo_t **ary, int count, char *spath); 53 static void childRebuildRepo(bulk_t *bulk); 54 static void scandeletenew(const char *path); 55 56 static void rebuildTerminateSignal(int signo); 57 static char *md5lkfile(char *rpath, int which); 58 static int lkdircount(char *buf); 59 60 static char *RebuildRemovePath; 61 62 void 63 DoRebuildRepo(int ask) 64 { 65 bulk_t *bulk; 66 FILE *fp; 67 int fd; 68 char tpath[256]; 69 const char *sufx; 70 71 if (ask) { 72 if (askyn("Rebuild the repository? ") == 0) 73 return; 74 } 75 76 /* 77 * Scan the repository for temporary .new files and delete them. 78 */ 79 scandeletenew(RepositoryPath); 80 81 /* 82 * Generate temporary file 83 */ 84 snprintf(tpath, sizeof(tpath), "/tmp/meta.XXXXXXXX.conf"); 85 86 signal(SIGTERM, rebuildTerminateSignal); 87 signal(SIGINT, rebuildTerminateSignal); 88 signal(SIGHUP, rebuildTerminateSignal); 89 90 RebuildRemovePath = tpath; 91 92 sufx = UsePkgSufx; 93 fd = mkostemps(tpath, 5, 0); 94 if (fd < 0) 95 dfatal_errno("Cannot create %s", tpath); 96 fp = fdopen(fd, "w"); 97 fprintf(fp, "version = %d;\n", MetaVersion); 98 fprintf(fp, "packing_format = \"%s\";\n", sufx + 1); 99 fclose(fp); 100 101 /* 102 * Run the operation under our bulk infrastructure to 103 * get the correct environment. 104 */ 105 initbulk(childRebuildRepo, 1); 106 queuebulk(tpath, NULL, NULL, NULL); 107 bulk = getbulk(); 108 109 if (bulk->r1) 110 printf("Rebuild succeeded\n"); 111 else 112 printf("Rebuild failed\n"); 113 donebulk(); 114 115 remove(tpath); 116 } 117 118 static void 119 repackage(const char *basepath, const char *basefile, 120 const char *decomp_suffix, const char *comp_suffix, 121 const char *decomp, const char *comp); 122 123 static void 124 childRebuildRepo(bulk_t *bulk) 125 { 126 FILE *fp; 127 char *ptr; 128 size_t len; 129 pid_t pid; 130 const char *cav[MAXCAC]; 131 char *pkg_path; 132 int cac; 133 int repackage_mode = 0; 134 135 /* 136 * We have to use the pkg-static that we built as part of the 137 * build process to rebuild the repo because the system pkg might 138 * not be compatible with the repo format changes made in 1.17. 139 */ 140 asprintf(&pkg_path, "%s/Template/usr/local/sbin/pkg-static", BuildBase); 141 142 cac = 0; 143 cav[cac++] = pkg_path; 144 cav[cac++] = "repo"; 145 cav[cac++] = "-m"; 146 cav[cac++] = bulk->s1; 147 cav[cac++] = "-o"; 148 cav[cac++] = PackagesPath; 149 150 /* 151 * The yaml needs to generate paths relative to PackagePath 152 */ 153 if (strncmp(PackagesPath, RepositoryPath, strlen(PackagesPath)) == 0) 154 cav[cac++] = PackagesPath; 155 else 156 cav[cac++] = RepositoryPath; 157 158 printf("pkg repo -m %s -o %s %s\n", bulk->s1, cav[cac-2], cav[cac-1]); 159 160 fp = dexec_open(NULL, cav, cac, &pid, NULL, 1, 0); 161 while ((ptr = fgetln(fp, &len)) != NULL) 162 fwrite(ptr, 1, len, stdout); 163 if (dexec_close(fp, pid) == 0) 164 bulk->r1 = strdup(""); 165 166 /* 167 * Check package version. Pkg version 1.12 and later generates 168 * the proper repo compression format. Prior to that version 169 * the repo directive always generated .txz files. 170 */ 171 cac = 0; 172 cav[cac++] = pkg_path; 173 cav[cac++] = "-v"; 174 fp = dexec_open(NULL, cav, cac, &pid, NULL, 1, 0); 175 if ((ptr = fgetln(fp, &len)) != NULL && len > 0) { 176 int v1; 177 int v2; 178 179 ptr[len-1] = 0; 180 if (sscanf(ptr, "%d.%d", &v1, &v2) == 2) { 181 printf("pkg repo - pkg version: %d.%d\n", v1, v2); 182 if (v1 > 1 || (v1 == 1 && v2 >= 12)) 183 repackage_mode = 1; 184 } 185 } 186 dexec_close(fp, pid); 187 188 /* 189 * Repackage the .txz files created by pkg repo if necessary 190 */ 191 if (repackage_mode == 0 && strcmp(UsePkgSufx, ".txz") != 0) { 192 const char *comp; 193 const char *decomp; 194 195 printf("pkg repo - recompressing digests and packagesite\n"); 196 197 if (strcmp(UsePkgSufx, ".tar") == 0) { 198 decomp = "unxz"; 199 comp = "cat"; 200 } else if (strcmp(UsePkgSufx, ".tgz") == 0) { 201 decomp = "unxz"; 202 comp = "gzip"; 203 } else if (strcmp(UsePkgSufx, ".tbz") == 0) { 204 decomp = "unxz"; 205 comp = "bzip"; 206 } else if (strcmp(UsePkgSufx, ".tzst") == 0) { 207 decomp = "unxz"; 208 comp = "zstd"; 209 } else { 210 dfatal("recompressing as %s not supported", 211 UsePkgSufx); 212 decomp = "unxz"; 213 comp = "cat"; 214 } 215 repackage(PackagesPath, "digests", 216 ".txz", UsePkgSufx, 217 decomp, comp); 218 repackage(PackagesPath, "packagesite", 219 ".txz", UsePkgSufx, 220 decomp, comp); 221 } else if (repackage_mode == 1 && strcmp(UsePkgSufx, ".txz") != 0) { 222 const char *comp; 223 const char *decomp; 224 225 printf("pkg repo - recompressing meta\n"); 226 227 if (strcmp(UsePkgSufx, ".tar") == 0) { 228 decomp = "cat"; 229 comp = "xz"; 230 } else if (strcmp(UsePkgSufx, ".tgz") == 0) { 231 decomp = "gunzip"; 232 comp = "xz"; 233 } else if (strcmp(UsePkgSufx, ".tbz") == 0) { 234 decomp = "bunzip2"; 235 comp = "xz"; 236 } else if (strcmp(UsePkgSufx, ".tzst") == 0) { 237 decomp = "unzstd"; 238 comp = "xz"; 239 } else { 240 dfatal("recompressing from %s not supported", 241 UsePkgSufx); 242 decomp = "cat"; 243 comp = "cat"; 244 } 245 repackage(PackagesPath, "meta", 246 UsePkgSufx, ".txz", 247 decomp, comp); 248 } 249 free (pkg_path); 250 } 251 252 static 253 void 254 repackage(const char *basepath, const char *basefile, 255 const char *decomp_suffix, const char *comp_suffix, 256 const char *decomp, const char *comp) 257 { 258 char *buf; 259 260 asprintf(&buf, "%s < %s/%s%s | %s > %s/%s%s", 261 decomp, basepath, basefile, decomp_suffix, 262 comp, basepath, basefile, comp_suffix); 263 if (system(buf) != 0) { 264 dfatal("command failed: %s", buf); 265 } 266 free(buf); 267 } 268 269 void 270 DoUpgradePkgs(pkg_t *pkgs __unused, int ask __unused) 271 { 272 dfatal("Not Implemented"); 273 } 274 275 void 276 PurgeDistfiles(pkg_t *pkgs) 277 { 278 pinfo_t *list; 279 pinfo_t *item; 280 pinfo_t **list_tail; 281 pinfo_t **ary; 282 char *dstr; 283 char *buf; 284 int count; 285 int delcount; 286 int i; 287 288 printf("Scanning distfiles... "); 289 fflush(stdout); 290 count = 0; 291 list = NULL; 292 list_tail = &list; 293 scanit(DistFilesPath, NULL, &count, &list_tail, 0); 294 printf("Checking %d distfiles\n", count); 295 fflush(stdout); 296 297 ary = calloc(count, sizeof(pinfo_t *)); 298 for (i = 0; i < count; ++i) { 299 ary[i] = list; 300 list = list->next; 301 } 302 ddassert(list == NULL); 303 qsort(ary, count, sizeof(pinfo_t *), pinfocmp); 304 305 for (; pkgs; pkgs = pkgs->bnext) { 306 if (pkgs->distfiles == NULL || pkgs->distfiles[0] == 0) 307 continue; 308 ddprintf(0, "distfiles %s\n", pkgs->distfiles); 309 dstr = strtok(pkgs->distfiles, " \t"); 310 while (dstr) { 311 for (;;) { 312 /* 313 * Look for distfile 314 */ 315 if (pkgs->distsubdir) { 316 asprintf(&buf, "%s/%s", 317 pkgs->distsubdir, dstr); 318 } else { 319 buf = dstr; 320 } 321 item = pinfofind(ary, count, buf); 322 if (item) 323 item->foundit = 1; 324 if (item && item->inlocks == 0) { 325 /* 326 * Look for the lock file 327 */ 328 int scount; 329 330 scount = lkdircount(buf); 331 332 for (i = 0; i <= scount; ++i) { 333 item = pinfofind(ary, count, 334 md5lkfile(buf, i)); 335 if (item) 336 item->foundit = 1; 337 } 338 } 339 340 /* 341 * Cleanup and iterate 342 */ 343 if (buf != dstr) { 344 free(buf); 345 buf = NULL; 346 } 347 if (strrchr(dstr, ':') == NULL) 348 break; 349 *strrchr(dstr, ':') = 0; 350 } 351 dstr = strtok(NULL, " \t"); 352 } 353 } 354 355 delcount = 0; 356 for (i = 0; i < count; ++i) { 357 item = ary[i]; 358 if (item->foundit == 0) { 359 ++delcount; 360 } 361 } 362 if (delcount == 0) { 363 printf("No obsolete source files out of %d found\n", count); 364 } else if (askyn("Delete %d of %d items? ", delcount, count)) { 365 printf("Deleting %d/%d obsolete source distfiles\n", 366 delcount, count); 367 for (i = 0; i < count; ++i) { 368 item = ary[i]; 369 if (item->foundit == 0) { 370 asprintf(&buf, "%s/%s", 371 DistFilesPath, item->spath); 372 if (remove(buf) < 0) 373 printf("Cannot delete %s\n", buf); 374 else 375 printf("Deleted %s\n", item->spath); 376 free(buf); 377 } 378 } 379 } 380 381 382 free(ary); 383 } 384 385 void 386 RemovePackages(pkg_t *list) 387 { 388 pkg_t *scan; 389 char *path; 390 391 for (scan = list; scan; scan = scan->bnext) { 392 if ((scan->flags & PKGF_MANUALSEL) == 0) 393 continue; 394 if (scan->pkgfile) { 395 scan->flags &= ~PKGF_PACKAGED; 396 scan->pkgfile_size = 0; 397 asprintf(&path, "%s/%s", RepositoryPath, scan->pkgfile); 398 if (remove(path) == 0) 399 printf("Removed: %s\n", path); 400 free(path); 401 } 402 if (scan->pkgfile == NULL || 403 (scan->flags & (PKGF_DUMMY | PKGF_META))) { 404 removePackagesMetaRecurse(scan); 405 } 406 } 407 } 408 409 static void 410 removePackagesMetaRecurse(pkg_t *pkg) 411 { 412 pkglink_t *link; 413 pkg_t *scan; 414 char *path; 415 416 PKGLIST_FOREACH(link, &pkg->idepon_list) { 417 scan = link->pkg; 418 if (scan == NULL) 419 continue; 420 if (scan->pkgfile == NULL || 421 (scan->flags & (PKGF_DUMMY | PKGF_META))) { 422 removePackagesMetaRecurse(scan); 423 continue; 424 } 425 scan->flags &= ~PKGF_PACKAGED; 426 scan->pkgfile_size = 0; 427 428 asprintf(&path, "%s/%s", RepositoryPath, scan->pkgfile); 429 if (remove(path) == 0) 430 printf("Removed: %s\n", path); 431 free(path); 432 } 433 } 434 435 static int 436 pinfocmp(const void *s1, const void *s2) 437 { 438 const pinfo_t *item1 = *(const pinfo_t *const*)s1; 439 const pinfo_t *item2 = *(const pinfo_t *const*)s2; 440 441 return (strcmp(item1->spath, item2->spath)); 442 } 443 444 pinfo_t * 445 pinfofind(pinfo_t **ary, int count, char *spath) 446 { 447 pinfo_t *item; 448 int res; 449 int b; 450 int e; 451 int m; 452 453 b = 0; 454 e = count; 455 while (b != e) { 456 m = b + (e - b) / 2; 457 item = ary[m]; 458 res = strcmp(spath, item->spath); 459 if (res == 0) 460 return item; 461 if (res < 0) { 462 e = m; 463 } else { 464 b = m + 1; 465 } 466 } 467 return NULL; 468 } 469 470 void 471 scanit(const char *path, const char *subpath, 472 int *countp, pinfo_t ***list_tailp, 473 int inlocks) 474 { 475 struct dirent *den; 476 pinfo_t *item; 477 char *npath; 478 char *spath; 479 DIR *dir; 480 struct stat st; 481 482 if ((dir = opendir(path)) != NULL) { 483 while ((den = readdir(dir)) != NULL) { 484 if (den->d_namlen == 1 && den->d_name[0] == '.') 485 continue; 486 if (den->d_namlen == 2 && den->d_name[0] == '.' && 487 den->d_name[1] == '.') 488 continue; 489 asprintf(&npath, "%s/%s", path, den->d_name); 490 if (lstat(npath, &st) < 0) { 491 free(npath); 492 continue; 493 } 494 if (S_ISDIR(st.st_mode)) { 495 int sublocks; 496 497 sublocks = 498 (strcmp(den->d_name, ".locks") == 0); 499 500 if (subpath) { 501 asprintf(&spath, "%s/%s", 502 subpath, den->d_name); 503 scanit(npath, spath, countp, 504 list_tailp, sublocks); 505 free(spath); 506 } else { 507 scanit(npath, den->d_name, countp, 508 list_tailp, sublocks); 509 } 510 } else if (S_ISREG(st.st_mode)) { 511 item = calloc(1, sizeof(*item)); 512 if (subpath) { 513 asprintf(&item->spath, "%s/%s", 514 subpath, den->d_name); 515 } else { 516 item->spath = strdup(den->d_name); 517 } 518 item->inlocks = inlocks; 519 520 **list_tailp = item; 521 *list_tailp = &item->next; 522 ++*countp; 523 ddprintf(0, "scan %s\n", item->spath); 524 } 525 free(npath); 526 } 527 closedir(dir); 528 } 529 } 530 531 /* 532 * This removes any .new files left over in the repo. These can wind 533 * being left around when dsynth is killed. 534 */ 535 static void 536 scandeletenew(const char *path) 537 { 538 struct dirent *den; 539 const char *ptr; 540 DIR *dir; 541 char *buf; 542 543 if ((dir = opendir(path)) == NULL) 544 dfatal_errno("Cannot scan directory %s", path); 545 while ((den = readdir(dir)) != NULL) { 546 if ((ptr = strrchr(den->d_name, '.')) != NULL && 547 strcmp(ptr, ".new") == 0) { 548 asprintf(&buf, "%s/%s", path, den->d_name); 549 if (remove(buf) < 0) 550 dfatal_errno("remove: Garbage %s\n", buf); 551 printf("Deleted Garbage %s\n", buf); 552 free(buf); 553 } 554 } 555 closedir(dir); 556 } 557 558 static void 559 rebuildTerminateSignal(int signo __unused) 560 { 561 if (RebuildRemovePath) 562 remove(RebuildRemovePath); 563 exit(1); 564 565 } 566 567 /* 568 * There will be a .locks sub-directory in /usr/distfiles and also 569 * in each sub-directory underneath it containing the MD5 sums for 570 * the files in that subdirectory. 571 * 572 * This is a bit of a mess. Sometimes the .locks/ for a subdirectory 573 * are in parentdir/.locks and not parentdir/subdir/.locks. The invocation 574 * of do-fetch can be a bit messy so we look for a .locks subdir everywhere. 575 * 576 * The /usr/dports/Mk/Scripts/do-fetch.sh script uses 'echo blah | md5', 577 * so we have to add a newline to the buffer being md5'd. 578 * 579 * The pass-in rpath is relative to the distfiles base. 580 */ 581 static char * 582 md5lkfile(char *rpath, int which_slash) 583 { 584 static char mstr[128]; 585 static char lkfile[128]; 586 uint8_t digest[MD5_DIGEST_LENGTH]; 587 int bplen; 588 int i; 589 590 bplen = 0; 591 for (i = 0; i < which_slash; ++i) { 592 while (rpath[bplen] && rpath[bplen] != '/') 593 ++bplen; 594 if (rpath[bplen]) 595 ++bplen; 596 } 597 snprintf(mstr, sizeof(mstr), "%s\n", rpath + bplen); 598 MD5(mstr, strlen(mstr), digest); 599 600 snprintf(lkfile, sizeof(lkfile), 601 "%*.*s.locks/" 602 "%02x%02x%02x%02x%02x%02x%02x%02x" 603 "%02x%02x%02x%02x%02x%02x%02x%02x" 604 ".lk", 605 bplen, bplen, rpath, 606 digest[0], digest[1], digest[2], digest[3], 607 digest[4], digest[5], digest[6], digest[7], 608 digest[8], digest[9], digest[10], digest[11], 609 digest[12], digest[13], digest[14], digest[15]); 610 611 return lkfile; 612 } 613 614 static int 615 lkdircount(char *buf) 616 { 617 int i; 618 int n; 619 620 n = 0; 621 for (i = 0; buf[i]; ++i) { 622 if (buf[i] == '/') 623 ++n; 624 } 625 return n; 626 } 627