xref: /dragonfly/usr.bin/dsynth/repo.c (revision 63e03116)
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