xref: /dragonfly/usr.bin/dsynth/repo.c (revision f9993810)
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 }
273 
274 void
275 PurgeDistfiles(pkg_t *pkgs)
276 {
277 	pinfo_t *list;
278 	pinfo_t *item;
279 	pinfo_t **list_tail;
280 	pinfo_t **ary;
281 	char *dstr;
282 	char *buf;
283 	int count;
284 	int delcount;
285 	int i;
286 
287 	printf("Scanning distfiles... ");
288 	fflush(stdout);
289 	count = 0;
290 	list = NULL;
291 	list_tail = &list;
292 	scanit(DistFilesPath, NULL, &count, &list_tail, 0);
293 	printf("Checking %d distfiles\n", count);
294 	fflush(stdout);
295 
296 	ary = calloc(count, sizeof(pinfo_t *));
297 	for (i = 0; i < count; ++i) {
298 		ary[i] = list;
299 		list = list->next;
300 	}
301 	ddassert(list == NULL);
302 	qsort(ary, count, sizeof(pinfo_t *), pinfocmp);
303 
304 	for (; pkgs; pkgs = pkgs->bnext) {
305 		if (pkgs->distfiles == NULL || pkgs->distfiles[0] == 0)
306 			continue;
307 		ddprintf(0, "distfiles %s\n", pkgs->distfiles);
308 		dstr = strtok(pkgs->distfiles, " \t");
309 		while (dstr) {
310 			for (;;) {
311 				/*
312 				 * Look for distfile
313 				 */
314 				if (pkgs->distsubdir) {
315 					asprintf(&buf, "%s/%s",
316 						 pkgs->distsubdir, dstr);
317 				} else {
318 					buf = dstr;
319 				}
320 				item = pinfofind(ary, count, buf);
321 				if (item)
322 					item->foundit = 1;
323 				if (item && item->inlocks == 0) {
324 					/*
325 					 * Look for the lock file
326 					 */
327 					int scount;
328 
329 					scount = lkdircount(buf);
330 
331 					for (i = 0; i <= scount; ++i) {
332 						item = pinfofind(ary, count,
333 							     md5lkfile(buf, i));
334 						if (item)
335 							item->foundit = 1;
336 					}
337 				}
338 
339 				/*
340 				 * Cleanup and iterate
341 				 */
342 				if (buf != dstr) {
343 					free(buf);
344 					buf = NULL;
345 				}
346 				if (strrchr(dstr, ':') == NULL)
347 					break;
348 				*strrchr(dstr, ':') = 0;
349 			}
350 			dstr = strtok(NULL, " \t");
351 		}
352 	}
353 
354 	delcount = 0;
355 	for (i = 0; i < count; ++i) {
356 		item = ary[i];
357 		if (item->foundit == 0) {
358 			++delcount;
359 		}
360 	}
361 	if (delcount == 0) {
362 		printf("No obsolete source files out of %d found\n", count);
363 	} else if (askyn("Delete %d of %d items? ", delcount, count)) {
364 		printf("Deleting %d/%d obsolete source distfiles\n",
365 		       delcount, count);
366 		for (i = 0; i < count; ++i) {
367 			item = ary[i];
368 			if (item->foundit == 0) {
369 				asprintf(&buf, "%s/%s",
370 					 DistFilesPath, item->spath);
371 				if (remove(buf) < 0)
372 					printf("Cannot delete %s\n", buf);
373 				else
374 					printf("Deleted %s\n", item->spath);
375 				free(buf);
376 			}
377 		}
378 	}
379 
380 
381 	free(ary);
382 }
383 
384 void
385 RemovePackages(pkg_t *list)
386 {
387 	pkg_t *scan;
388 	char *path;
389 
390 	for (scan = list; scan; scan = scan->bnext) {
391 		if ((scan->flags & PKGF_MANUALSEL) == 0)
392 			continue;
393 		if (scan->pkgfile) {
394 			scan->flags &= ~PKGF_PACKAGED;
395 			scan->pkgfile_size = 0;
396 			asprintf(&path, "%s/%s", RepositoryPath, scan->pkgfile);
397 			if (remove(path) == 0)
398 				printf("Removed: %s\n", path);
399 			free(path);
400 		}
401 		if (scan->pkgfile == NULL ||
402 		    (scan->flags & (PKGF_DUMMY | PKGF_META))) {
403 			removePackagesMetaRecurse(scan);
404 		}
405 	}
406 }
407 
408 static void
409 removePackagesMetaRecurse(pkg_t *pkg)
410 {
411 	pkglink_t *link;
412 	pkg_t *scan;
413 	char *path;
414 
415 	PKGLIST_FOREACH(link, &pkg->idepon_list) {
416 		scan = link->pkg;
417 		if (scan == NULL)
418 			continue;
419 		if (scan->pkgfile == NULL ||
420 		    (scan->flags & (PKGF_DUMMY | PKGF_META))) {
421 			removePackagesMetaRecurse(scan);
422 			continue;
423 		}
424 		scan->flags &= ~PKGF_PACKAGED;
425 		scan->pkgfile_size = 0;
426 
427 		asprintf(&path, "%s/%s", RepositoryPath, scan->pkgfile);
428 		if (remove(path) == 0)
429 			printf("Removed: %s\n", path);
430 		free(path);
431 	}
432 }
433 
434 static int
435 pinfocmp(const void *s1, const void *s2)
436 {
437 	const pinfo_t *item1 = *(const pinfo_t *const*)s1;
438 	const pinfo_t *item2 = *(const pinfo_t *const*)s2;
439 
440 	return (strcmp(item1->spath, item2->spath));
441 }
442 
443 pinfo_t *
444 pinfofind(pinfo_t **ary, int count, char *spath)
445 {
446 	pinfo_t *item;
447 	int res;
448 	int b;
449 	int e;
450 	int m;
451 
452 	b = 0;
453 	e = count;
454 	while (b != e) {
455 		m = b + (e - b) / 2;
456 		item = ary[m];
457 		res = strcmp(spath, item->spath);
458 		if (res == 0)
459 			return item;
460 		if (res < 0) {
461 			e = m;
462 		} else {
463 			b = m + 1;
464 		}
465 	}
466 	return NULL;
467 }
468 
469 void
470 scanit(const char *path, const char *subpath,
471        int *countp, pinfo_t ***list_tailp,
472        int inlocks)
473 {
474 	struct dirent *den;
475 	pinfo_t *item;
476 	char *npath;
477 	char *spath;
478 	DIR *dir;
479 	struct stat st;
480 
481 	if ((dir = opendir(path)) != NULL) {
482 		while ((den = readdir(dir)) != NULL) {
483 			if (den->d_namlen == 1 && den->d_name[0] == '.')
484 				continue;
485 			if (den->d_namlen == 2 && den->d_name[0] == '.' &&
486 			    den->d_name[1] == '.')
487 				continue;
488 			asprintf(&npath, "%s/%s", path, den->d_name);
489 			if (lstat(npath, &st) < 0) {
490 				free(npath);
491 				continue;
492 			}
493 			if (S_ISDIR(st.st_mode)) {
494 				int sublocks;
495 
496 				sublocks =
497 				    (strcmp(den->d_name, ".locks") == 0);
498 
499 				if (subpath) {
500 					asprintf(&spath, "%s/%s",
501 						 subpath, den->d_name);
502 					scanit(npath, spath, countp,
503 					       list_tailp, sublocks);
504 					free(spath);
505 				} else {
506 					scanit(npath, den->d_name, countp,
507 					       list_tailp, sublocks);
508 				}
509 			} else if (S_ISREG(st.st_mode)) {
510 				item = calloc(1, sizeof(*item));
511 				if (subpath) {
512 					asprintf(&item->spath, "%s/%s",
513 						 subpath, den->d_name);
514 				} else {
515 					item->spath = strdup(den->d_name);
516 				}
517 				item->inlocks = inlocks;
518 
519 				**list_tailp = item;
520 				*list_tailp = &item->next;
521 				++*countp;
522 				ddprintf(0, "scan   %s\n", item->spath);
523 			}
524 			free(npath);
525 		}
526 		closedir(dir);
527 	}
528 }
529 
530 /*
531  * This removes any .new files left over in the repo.  These can wind
532  * being left around when dsynth is killed.
533  */
534 static void
535 scandeletenew(const char *path)
536 {
537 	struct dirent *den;
538 	const char *ptr;
539 	DIR *dir;
540 	char *buf;
541 
542 	if ((dir = opendir(path)) == NULL)
543 		dfatal_errno("Cannot scan directory %s", path);
544 	while ((den = readdir(dir)) != NULL) {
545 		if ((ptr = strrchr(den->d_name, '.')) != NULL &&
546 		    strcmp(ptr, ".new") == 0) {
547 			asprintf(&buf, "%s/%s", path, den->d_name);
548 			if (remove(buf) < 0)
549 				dfatal_errno("remove: Garbage %s\n", buf);
550 			printf("Deleted Garbage %s\n", buf);
551 			free(buf);
552 		}
553 	}
554 	closedir(dir);
555 }
556 
557 static void
558 rebuildTerminateSignal(int signo __unused)
559 {
560 	if (RebuildRemovePath)
561 		remove(RebuildRemovePath);
562 	exit(1);
563 
564 }
565 
566 /*
567  * There will be a .locks sub-directory in /usr/distfiles and also
568  * in each sub-directory underneath it containing the MD5 sums for
569  * the files in that subdirectory.
570  *
571  * This is a bit of a mess.  Sometimes the .locks/ for a subdirectory
572  * are in parentdir/.locks and not parentdir/subdir/.locks.  The invocation
573  * of do-fetch can be a bit messy so we look for a .locks subdir everywhere.
574  *
575  * The /usr/dports/Mk/Scripts/do-fetch.sh script uses 'echo blah | md5',
576  * so we have to add a newline to the buffer being md5'd.
577  *
578  * The pass-in rpath is relative to the distfiles base.
579  */
580 static char *
581 md5lkfile(char *rpath, int which_slash)
582 {
583 	static char mstr[128];
584 	static char lkfile[128];
585 	uint8_t digest[MD5_DIGEST_LENGTH];
586 	int bplen;
587 	int i;
588 
589 	bplen = 0;
590 	for (i = 0; i < which_slash; ++i) {
591 		while (rpath[bplen] && rpath[bplen] != '/')
592 			++bplen;
593 		if (rpath[bplen])
594 			++bplen;
595 	}
596 	snprintf(mstr, sizeof(mstr), "%s\n", rpath + bplen);
597 	MD5(mstr, strlen(mstr), digest);
598 
599 	snprintf(lkfile, sizeof(lkfile),
600 		"%*.*s.locks/"
601 		 "%02x%02x%02x%02x%02x%02x%02x%02x"
602 		 "%02x%02x%02x%02x%02x%02x%02x%02x"
603 		 ".lk",
604 		 bplen, bplen, rpath,
605 		 digest[0], digest[1], digest[2], digest[3],
606 		 digest[4], digest[5], digest[6], digest[7],
607 		 digest[8], digest[9], digest[10], digest[11],
608 		 digest[12], digest[13], digest[14], digest[15]);
609 
610 	return lkfile;
611 }
612 
613 static int
614 lkdircount(char *buf)
615 {
616 	int i;
617 	int n;
618 
619 	n = 0;
620 	for (i = 0; buf[i]; ++i) {
621 		if (buf[i] == '/')
622 			++n;
623 	}
624 	return n;
625 }
626