xref: /dragonfly/usr.bin/dsynth/repo.c (revision 40657594)
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 = 1;\n");
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 	int cac;
132 	int repackage_mode = 0;
133 
134 	cac = 0;
135 	cav[cac++] = PKG_BINARY;
136 	cav[cac++] = "repo";
137 	cav[cac++] = "-m";
138 	cav[cac++] = bulk->s1;
139 	cav[cac++] = "-o";
140 	cav[cac++] = PackagesPath;
141 
142 	/*
143 	 * The yaml needs to generate paths relative to PackagePath
144 	 */
145 	if (strncmp(PackagesPath, RepositoryPath, strlen(PackagesPath)) == 0)
146 		cav[cac++] = PackagesPath;
147 	else
148 		cav[cac++] = RepositoryPath;
149 
150 	printf("pkg repo -m %s -o %s %s\n", bulk->s1, cav[cac-2], cav[cac-1]);
151 
152 	fp = dexec_open(cav, cac, &pid, NULL, 1, 0);
153 	while ((ptr = fgetln(fp, &len)) != NULL)
154 		fwrite(ptr, 1, len, stdout);
155 	if (dexec_close(fp, pid) == 0)
156 		bulk->r1 = strdup("");
157 
158 	/*
159 	 * Check package version.  Pkg version 1.12 and later generates
160 	 * the proper repo compression format.  Prior to that version
161 	 * the repo directive always generated .txz files.
162 	 */
163 	cac = 0;
164 	cav[cac++] = PKG_BINARY;
165 	cav[cac++] = "-v";
166 	fp = dexec_open(cav, cac, &pid, NULL, 1, 0);
167 	if ((ptr = fgetln(fp, &len)) != NULL && len > 0) {
168 		int v1;
169 		int v2;
170 
171 		ptr[len-1] = 0;
172 		if (sscanf(ptr, "%d.%d", &v1, &v2) == 2) {
173 			if (v1 > 1 || (v1 == 1 && v2 >= 12))
174 				repackage_mode = 1;
175 		}
176 	}
177 	dexec_close(fp, pid);
178 
179 	/*
180 	 * Repackage the .txz files created by pkg repo if necessary
181 	 */
182 	if (repackage_mode == 0 && strcmp(UsePkgSufx, ".txz") != 0) {
183 		const char *comp;
184 		const char *decomp;
185 
186 		printf("pkg repo - recompressing digests and packagesite\n");
187 
188 		if (strcmp(UsePkgSufx, ".tar") == 0) {
189 			decomp = "unxz";
190 			comp = "cat";
191 		} else if (strcmp(UsePkgSufx, ".tgz") == 0) {
192 			decomp = "unxz";
193 			comp = "gzip";
194 		} else if (strcmp(UsePkgSufx, ".tbz") == 0) {
195 			decomp = "unxz";
196 			comp = "bzip";
197 		} else {
198 			dfatal("recompressing as %s not supported",
199 			       UsePkgSufx);
200 			decomp = "unxz";
201 			comp = "cat";
202 		}
203 		repackage(PackagesPath, "digests",
204 			  ".txz", UsePkgSufx,
205 			  decomp, comp);
206 		repackage(PackagesPath, "packagesite",
207 			  ".txz", UsePkgSufx,
208 			  decomp, comp);
209 	} else if (repackage_mode == 1 && strcmp(UsePkgSufx, ".txz") != 0) {
210 		const char *comp;
211 		const char *decomp;
212 
213 		printf("pkg repo - recompressing meta\n");
214 
215 		if (strcmp(UsePkgSufx, ".tar") == 0) {
216 			decomp = "cat";
217 			comp = "xz";
218 		} else if (strcmp(UsePkgSufx, ".tgz") == 0) {
219 			decomp = "gunzip";
220 			comp = "xz";
221 		} else if (strcmp(UsePkgSufx, ".tbz") == 0) {
222 			decomp = "bunzip2";
223 			comp = "xz";
224 		} else {
225 			dfatal("recompressing from %s not supported",
226 			       UsePkgSufx);
227 			decomp = "cat";
228 			comp = "cat";
229 		}
230 		repackage(PackagesPath, "meta",
231 			  UsePkgSufx, ".txz",
232 			  decomp, comp);
233 	}
234 }
235 
236 static
237 void
238 repackage(const char *basepath, const char *basefile,
239 	  const char *decomp_suffix, const char *comp_suffix,
240 	  const char *decomp, const char *comp)
241 {
242 	char *buf;
243 
244 	asprintf(&buf, "%s < %s/%s%s | %s > %s/%s%s",
245 		decomp, basepath, basefile, decomp_suffix,
246 		comp, basepath, basefile, comp_suffix);
247 	if (system(buf) != 0) {
248 		dfatal("command failed: %s", buf);
249 	}
250 	free(buf);
251 }
252 
253 void
254 DoUpgradePkgs(pkg_t *pkgs __unused, int ask __unused)
255 {
256 	dfatal("Not Implemented");
257 }
258 
259 void
260 PurgeDistfiles(pkg_t *pkgs)
261 {
262 	pinfo_t *list;
263 	pinfo_t *item;
264 	pinfo_t **list_tail;
265 	pinfo_t **ary;
266 	char *dstr;
267 	char *buf;
268 	int count;
269 	int delcount;
270 	int i;
271 
272 	printf("Scanning distfiles... ");
273 	fflush(stdout);
274 	count = 0;
275 	list = NULL;
276 	list_tail = &list;
277 	scanit(DistFilesPath, NULL, &count, &list_tail, 0);
278 	printf("Checking %d distfiles\n", count);
279 	fflush(stdout);
280 
281 	ary = calloc(count, sizeof(pinfo_t *));
282 	for (i = 0; i < count; ++i) {
283 		ary[i] = list;
284 		list = list->next;
285 	}
286 	ddassert(list == NULL);
287 	qsort(ary, count, sizeof(pinfo_t *), pinfocmp);
288 
289 	for (; pkgs; pkgs = pkgs->bnext) {
290 		if (pkgs->distfiles == NULL || pkgs->distfiles[0] == 0)
291 			continue;
292 		ddprintf(0, "distfiles %s\n", pkgs->distfiles);
293 		dstr = strtok(pkgs->distfiles, " \t");
294 		while (dstr) {
295 			for (;;) {
296 				/*
297 				 * Look for distfile
298 				 */
299 				if (pkgs->distsubdir) {
300 					asprintf(&buf, "%s/%s",
301 						 pkgs->distsubdir, dstr);
302 				} else {
303 					buf = dstr;
304 				}
305 				item = pinfofind(ary, count, buf);
306 				if (item)
307 					item->foundit = 1;
308 				if (item && item->inlocks == 0) {
309 					/*
310 					 * Look for the lock file
311 					 */
312 					int scount;
313 
314 					scount = lkdircount(buf);
315 
316 					for (i = 0; i <= scount; ++i) {
317 						item = pinfofind(ary, count,
318 							     md5lkfile(buf, i));
319 						if (item)
320 							item->foundit = 1;
321 					}
322 				}
323 
324 				/*
325 				 * Cleanup and iterate
326 				 */
327 				if (buf != dstr) {
328 					free(buf);
329 					buf = NULL;
330 				}
331 				if (strrchr(dstr, ':') == NULL)
332 					break;
333 				*strrchr(dstr, ':') = 0;
334 			}
335 			dstr = strtok(NULL, " \t");
336 		}
337 	}
338 
339 	delcount = 0;
340 	for (i = 0; i < count; ++i) {
341 		item = ary[i];
342 		if (item->foundit == 0) {
343 			++delcount;
344 		}
345 	}
346 	if (delcount == 0) {
347 		printf("No obsolete source files out of %d found\n", count);
348 	} else if (askyn("Delete %d of %d items? ", delcount, count)) {
349 		printf("Deleting %d/%d obsolete source distfiles\n",
350 		       delcount, count);
351 		for (i = 0; i < count; ++i) {
352 			item = ary[i];
353 			if (item->foundit == 0) {
354 				asprintf(&buf, "%s/%s",
355 					 DistFilesPath, item->spath);
356 				if (remove(buf) < 0)
357 					printf("Cannot delete %s\n", buf);
358 				else
359 					printf("Deleted %s\n", item->spath);
360 				free(buf);
361 			}
362 		}
363 	}
364 
365 
366 	free(ary);
367 }
368 
369 void
370 RemovePackages(pkg_t *list)
371 {
372 	pkg_t *scan;
373 	char *path;
374 
375 	for (scan = list; scan; scan = scan->bnext) {
376 		if ((scan->flags & PKGF_MANUALSEL) == 0)
377 			continue;
378 		if (scan->pkgfile) {
379 			scan->flags &= ~PKGF_PACKAGED;
380 			scan->pkgfile_size = 0;
381 			asprintf(&path, "%s/%s", RepositoryPath, scan->pkgfile);
382 			if (remove(path) == 0)
383 				printf("Removed: %s\n", path);
384 			free(path);
385 		}
386 		if (scan->pkgfile == NULL ||
387 		    (scan->flags & (PKGF_DUMMY | PKGF_META))) {
388 			removePackagesMetaRecurse(scan);
389 		}
390 	}
391 }
392 
393 static void
394 removePackagesMetaRecurse(pkg_t *pkg)
395 {
396 	pkglink_t *link;
397 	pkg_t *scan;
398 	char *path;
399 
400 	PKGLIST_FOREACH(link, &pkg->idepon_list) {
401 		scan = link->pkg;
402 		if (scan == NULL)
403 			continue;
404 		if (scan->pkgfile == NULL ||
405 		    (scan->flags & (PKGF_DUMMY | PKGF_META))) {
406 			removePackagesMetaRecurse(scan);
407 			continue;
408 		}
409 		scan->flags &= ~PKGF_PACKAGED;
410 		scan->pkgfile_size = 0;
411 
412 		asprintf(&path, "%s/%s", RepositoryPath, scan->pkgfile);
413 		if (remove(path) == 0)
414 			printf("Removed: %s\n", path);
415 		free(path);
416 	}
417 }
418 
419 static int
420 pinfocmp(const void *s1, const void *s2)
421 {
422 	const pinfo_t *item1 = *(const pinfo_t *const*)s1;
423 	const pinfo_t *item2 = *(const pinfo_t *const*)s2;
424 
425 	return (strcmp(item1->spath, item2->spath));
426 }
427 
428 pinfo_t *
429 pinfofind(pinfo_t **ary, int count, char *spath)
430 {
431 	pinfo_t *item;
432 	int res;
433 	int b;
434 	int e;
435 	int m;
436 
437 	b = 0;
438 	e = count;
439 	while (b != e) {
440 		m = b + (e - b) / 2;
441 		item = ary[m];
442 		res = strcmp(spath, item->spath);
443 		if (res == 0)
444 			return item;
445 		if (res < 0) {
446 			e = m;
447 		} else {
448 			b = m + 1;
449 		}
450 	}
451 	return NULL;
452 }
453 
454 void
455 scanit(const char *path, const char *subpath,
456        int *countp, pinfo_t ***list_tailp,
457        int inlocks)
458 {
459 	struct dirent *den;
460 	pinfo_t *item;
461 	char *npath;
462 	char *spath;
463 	DIR *dir;
464 	struct stat st;
465 
466 	if ((dir = opendir(path)) != NULL) {
467 		while ((den = readdir(dir)) != NULL) {
468 			if (den->d_namlen == 1 && den->d_name[0] == '.')
469 				continue;
470 			if (den->d_namlen == 2 && den->d_name[0] == '.' &&
471 			    den->d_name[1] == '.')
472 				continue;
473 			asprintf(&npath, "%s/%s", path, den->d_name);
474 			if (lstat(npath, &st) < 0) {
475 				free(npath);
476 				continue;
477 			}
478 			if (S_ISDIR(st.st_mode)) {
479 				int sublocks;
480 
481 				sublocks =
482 				    (strcmp(den->d_name, ".locks") == 0);
483 
484 				if (subpath) {
485 					asprintf(&spath, "%s/%s",
486 						 subpath, den->d_name);
487 					scanit(npath, spath, countp,
488 					       list_tailp, sublocks);
489 					free(spath);
490 				} else {
491 					scanit(npath, den->d_name, countp,
492 					       list_tailp, sublocks);
493 				}
494 			} else if (S_ISREG(st.st_mode)) {
495 				item = calloc(1, sizeof(*item));
496 				if (subpath) {
497 					asprintf(&item->spath, "%s/%s",
498 						 subpath, den->d_name);
499 				} else {
500 					item->spath = strdup(den->d_name);
501 				}
502 				item->inlocks = inlocks;
503 
504 				**list_tailp = item;
505 				*list_tailp = &item->next;
506 				++*countp;
507 				ddprintf(0, "scan   %s\n", item->spath);
508 			}
509 			free(npath);
510 		}
511 		closedir(dir);
512 	}
513 }
514 
515 /*
516  * This removes any .new files left over in the repo.  These can wind
517  * being left around when dsynth is killed.
518  */
519 static void
520 scandeletenew(const char *path)
521 {
522 	struct dirent *den;
523 	const char *ptr;
524 	DIR *dir;
525 	char *buf;
526 
527 	if ((dir = opendir(path)) == NULL)
528 		dfatal_errno("Cannot scan directory %s", path);
529 	while ((den = readdir(dir)) != NULL) {
530 		if ((ptr = strrchr(den->d_name, '.')) != NULL &&
531 		    strcmp(ptr, ".new") == 0) {
532 			asprintf(&buf, "%s/%s", path, den->d_name);
533 			if (remove(buf) < 0)
534 				dfatal_errno("remove: Garbage %s\n", buf);
535 			printf("Deleted Garbage %s\n", buf);
536 			free(buf);
537 		}
538 	}
539 	closedir(dir);
540 }
541 
542 static void
543 rebuildTerminateSignal(int signo __unused)
544 {
545 	if (RebuildRemovePath)
546 		remove(RebuildRemovePath);
547 	exit(1);
548 
549 }
550 
551 /*
552  * There will be a .locks sub-directory in /usr/distfiles and also
553  * in each sub-directory underneath it containing the MD5 sums for
554  * the files in that subdirectory.
555  *
556  * This is a bit of a mess.  Sometimes the .locks/ for a subdirectory
557  * are in parentdir/.locks and not parentdir/subdir/.locks.  The invocation
558  * of do-fetch can be a bit messy so we look for a .locks subdir everywhere.
559  *
560  * The /usr/dports/Mk/Scripts/do-fetch.sh script uses 'echo blah | md5',
561  * so we have to add a newline to the buffer being md5'd.
562  *
563  * The pass-in rpath is relative to the distfiles base.
564  */
565 static char *
566 md5lkfile(char *rpath, int which_slash)
567 {
568 	static char mstr[128];
569 	static char lkfile[128];
570 	uint8_t digest[MD5_DIGEST_LENGTH];
571 	int bplen;
572 	int i;
573 
574 	bplen = 0;
575 	for (i = 0; i < which_slash; ++i) {
576 		while (rpath[bplen] && rpath[bplen] != '/')
577 			++bplen;
578 		if (rpath[bplen])
579 			++bplen;
580 	}
581 	snprintf(mstr, sizeof(mstr), "%s\n", rpath + bplen);
582 	MD5(mstr, strlen(mstr), digest);
583 
584 	snprintf(lkfile, sizeof(lkfile),
585 		"%*.*s.locks/"
586 		 "%02x%02x%02x%02x%02x%02x%02x%02x"
587 		 "%02x%02x%02x%02x%02x%02x%02x%02x"
588 		 ".lk",
589 		 bplen, bplen, rpath,
590 		 digest[0], digest[1], digest[2], digest[3],
591 		 digest[4], digest[5], digest[6], digest[7],
592 		 digest[8], digest[9], digest[10], digest[11],
593 		 digest[12], digest[13], digest[14], digest[15]);
594 
595 	return lkfile;
596 }
597 
598 static int
599 lkdircount(char *buf)
600 {
601 	int i;
602 	int n;
603 
604 	n = 0;
605 	for (i = 0; buf[i]; ++i) {
606 		if (buf[i] == '/')
607 			++n;
608 	}
609 	return n;
610 }
611