xref: /dflybsd-src/usr.bin/dsynth/repo.c (revision 64664f41552507cf567d3408c157ebabc9bb7f74)
1 /*
2  * Copyright (c) 2019 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 
39 typedef struct pinfo {
40 	struct pinfo *next;
41 	char *spath;
42 	int foundit;
43 } pinfo_t;
44 
45 static void removePackagesMetaRecurse(pkg_t *pkg);
46 static int pinfocmp(const void *s1, const void *s2);
47 static void scanit(const char *path, const char *subpath,
48 			int *countp, pinfo_t ***list_tailp);
49 pinfo_t *pinfofind(pinfo_t **ary, int count, char *spath);
50 static void childRebuildRepo(bulk_t *bulk);
51 static void scandeletenew(const char *path);
52 
53 static void rebuildTerminateSignal(int signo);
54 
55 static char *RebuildRemovePath;
56 
57 void
58 DoRebuildRepo(int ask)
59 {
60 	bulk_t *bulk;
61 	FILE *fp;
62 	int fd;
63 	char tpath[256];
64 	const char *sufx;
65 
66 	if (ask) {
67 		if (askyn("Rebuild the repository? ") == 0)
68 			return;
69 	}
70 
71 	/*
72 	 * Scan the repository for temporary .new files and delete them.
73 	 */
74 	scandeletenew(RepositoryPath);
75 
76 	/*
77 	 * Generate temporary file
78 	 */
79 	snprintf(tpath, sizeof(tpath), "/tmp/meta.XXXXXXXX.conf");
80 
81 	signal(SIGTERM, rebuildTerminateSignal);
82 	signal(SIGINT, rebuildTerminateSignal);
83 	signal(SIGHUP, rebuildTerminateSignal);
84 
85 	RebuildRemovePath = tpath;
86 
87 	sufx = UsePkgSufx;
88 	fd = mkostemps(tpath, 5, 0);
89 	if (fd < 0)
90 		dfatal_errno("Cannot create %s", tpath);
91 	fp = fdopen(fd, "w");
92 	fprintf(fp, "version = 1;\n");
93 	fprintf(fp, "packing_format = \"%s\";\n", sufx + 1);
94 	fclose(fp);
95 
96 	/*
97 	 * Run the operation under our bulk infrastructure to
98 	 * get the correct environment.
99 	 */
100 	initbulk(childRebuildRepo, 1);
101 	queuebulk(tpath, NULL, NULL, NULL);
102 	bulk = getbulk();
103 
104 	if (bulk->r1)
105 		printf("Rebuild succeeded\n");
106 	else
107 		printf("Rebuild failed\n");
108 	donebulk();
109 
110 	remove(tpath);
111 }
112 
113 static void
114 repackage(const char *basepath, const char *basefile,
115 	  const char *decomp_suffix, const char *comp_suffix,
116 	  const char *decomp, const char *comp);
117 
118 static void
119 childRebuildRepo(bulk_t *bulk)
120 {
121 	FILE *fp;
122 	char *ptr;
123 	size_t len;
124 	pid_t pid;
125 	const char *cav[MAXCAC];
126 	int cac;
127 	int repackage_mode = 0;
128 
129 	cac = 0;
130 	cav[cac++] = PKG_BINARY;
131 	cav[cac++] = "repo";
132 	cav[cac++] = "-m";
133 	cav[cac++] = bulk->s1;
134 	cav[cac++] = "-o";
135 	cav[cac++] = PackagesPath;
136 
137 	/*
138 	 * The yaml needs to generate paths relative to PackagePath
139 	 */
140 	if (strncmp(PackagesPath, RepositoryPath, strlen(PackagesPath)) == 0)
141 		cav[cac++] = PackagesPath;
142 	else
143 		cav[cac++] = RepositoryPath;
144 
145 	printf("pkg repo -m %s -o %s %s\n", bulk->s1, cav[cac-2], cav[cac-1]);
146 
147 	fp = dexec_open(cav, cac, &pid, NULL, 1, 0);
148 	while ((ptr = fgetln(fp, &len)) != NULL)
149 		fwrite(ptr, 1, len, stdout);
150 	if (dexec_close(fp, pid) == 0)
151 		bulk->r1 = strdup("");
152 
153 	/*
154 	 * Check package version.  Pkg version 1.12 and later generates
155 	 * the proper repo compression format.  Prior to that version
156 	 * the repo directive always generated .txz files.
157 	 */
158 	cac = 0;
159 	cav[cac++] = PKG_BINARY;
160 	cav[cac++] = "-v";
161 	fp = dexec_open(cav, cac, &pid, NULL, 1, 0);
162 	if ((ptr = fgetln(fp, &len)) != NULL && len > 0) {
163 		int v1;
164 		int v2;
165 
166 		ptr[len-1] = 0;
167 		if (sscanf(ptr, "%d.%d", &v1, &v2) == 2) {
168 			if (v1 > 1 || (v1 == 1 && v2 >= 12))
169 				repackage_mode = 1;
170 		}
171 	}
172 	dexec_close(fp, pid);
173 
174 	/*
175 	 * Repackage the .txz files created by pkg repo if necessary
176 	 */
177 	if (repackage_mode == 0 && strcmp(UsePkgSufx, ".txz") != 0) {
178 		const char *comp;
179 		const char *decomp;
180 
181 		printf("pkg repo - recompressing digests and packagesite\n");
182 
183 		if (strcmp(UsePkgSufx, ".tar") == 0) {
184 			decomp = "unxz";
185 			comp = "cat";
186 		} else if (strcmp(UsePkgSufx, ".tgz") == 0) {
187 			decomp = "unxz";
188 			comp = "gzip";
189 		} else if (strcmp(UsePkgSufx, ".tbz") == 0) {
190 			decomp = "unxz";
191 			comp = "bzip";
192 		} else {
193 			dfatal("recompressing as %s not supported",
194 			       UsePkgSufx);
195 			decomp = "unxz";
196 			comp = "cat";
197 		}
198 		repackage(PackagesPath, "digests",
199 			  ".txz", UsePkgSufx,
200 			  decomp, comp);
201 		repackage(PackagesPath, "packagesite",
202 			  ".txz", UsePkgSufx,
203 			  decomp, comp);
204 	} else if (repackage_mode == 1 && strcmp(UsePkgSufx, ".txz") != 0) {
205 		const char *comp;
206 		const char *decomp;
207 
208 		printf("pkg repo - recompressing meta\n");
209 
210 		if (strcmp(UsePkgSufx, ".tar") == 0) {
211 			decomp = "cat";
212 			comp = "xz";
213 		} else if (strcmp(UsePkgSufx, ".tgz") == 0) {
214 			decomp = "gunzip";
215 			comp = "xz";
216 		} else if (strcmp(UsePkgSufx, ".tbz") == 0) {
217 			decomp = "bunzip2";
218 			comp = "xz";
219 		} else {
220 			dfatal("recompressing from %s not supported",
221 			       UsePkgSufx);
222 			decomp = "cat";
223 			comp = "cat";
224 		}
225 		repackage(PackagesPath, "meta",
226 			  UsePkgSufx, ".txz",
227 			  decomp, comp);
228 	}
229 }
230 
231 static
232 void
233 repackage(const char *basepath, const char *basefile,
234 	  const char *decomp_suffix, const char *comp_suffix,
235 	  const char *decomp, const char *comp)
236 {
237 	char *buf;
238 
239 	asprintf(&buf, "%s < %s/%s%s | %s > %s/%s%s",
240 		decomp, basepath, basefile, decomp_suffix,
241 		comp, basepath, basefile, comp_suffix);
242 	if (system(buf) != 0) {
243 		dfatal("command failed: %s", buf);
244 	}
245 	free(buf);
246 }
247 
248 void
249 DoUpgradePkgs(pkg_t *pkgs __unused, int ask __unused)
250 {
251 	dfatal("Not Implemented");
252 }
253 
254 void
255 PurgeDistfiles(pkg_t *pkgs)
256 {
257 	pinfo_t *list;
258 	pinfo_t *item;
259 	pinfo_t **list_tail;
260 	pinfo_t **ary;
261 	char *dstr;
262 	char *buf;
263 	int count;
264 	int delcount;
265 	int i;
266 
267 	printf("Scanning distfiles... ");
268 	fflush(stdout);
269 	count = 0;
270 	list = NULL;
271 	list_tail = &list;
272 	scanit(DistFilesPath, NULL, &count, &list_tail);
273 	printf("Checking %d distfiles\n", count);
274 	fflush(stdout);
275 
276 	ary = calloc(count, sizeof(pinfo_t *));
277 	for (i = 0; i < count; ++i) {
278 		ary[i] = list;
279 		list = list->next;
280 	}
281 	ddassert(list == NULL);
282 	qsort(ary, count, sizeof(pinfo_t *), pinfocmp);
283 
284 	for (; pkgs; pkgs = pkgs->bnext) {
285 		if (pkgs->distfiles == NULL || pkgs->distfiles[0] == 0)
286 			continue;
287 		ddprintf(0, "distfiles %s\n", pkgs->distfiles);
288 		dstr = strtok(pkgs->distfiles, " \t");
289 		while (dstr) {
290 			for (;;) {
291 				if (pkgs->distsubdir) {
292 					asprintf(&buf, "%s/%s",
293 						 pkgs->distsubdir, dstr);
294 					item = pinfofind(ary, count, buf);
295 					ddprintf(0, "TEST %s %p\n", buf, item);
296 					free(buf);
297 					buf = NULL;
298 				} else {
299 					item = pinfofind(ary, count, dstr);
300 					ddprintf(0, "TEST %s %p\n", dstr, item);
301 				}
302 				if (item) {
303 					item->foundit = 1;
304 					break;
305 				}
306 				if (strrchr(dstr, ':') == NULL)
307 					break;
308 				*strrchr(dstr, ':') = 0;
309 			}
310 			dstr = strtok(NULL, " \t");
311 		}
312 	}
313 
314 	delcount = 0;
315 	for (i = 0; i < count; ++i) {
316 		item = ary[i];
317 		if (item->foundit == 0) {
318 			++delcount;
319 		}
320 	}
321 	if (askyn("Delete %d of %d items? ", delcount, count)) {
322 		printf("Deleting %d/%d obsolete source distfiles\n",
323 		       delcount, count);
324 		for (i = 0; i < count; ++i) {
325 			item = ary[i];
326 			if (item->foundit == 0) {
327 				asprintf(&buf, "%s/%s",
328 					 DistFilesPath, item->spath);
329 				if (remove(buf) < 0)
330 					printf("Cannot delete %s\n", buf);
331 				free(buf);
332 			}
333 		}
334 	}
335 
336 
337 	free(ary);
338 }
339 
340 void
341 RemovePackages(pkg_t *list)
342 {
343 	pkg_t *scan;
344 	char *path;
345 
346 	for (scan = list; scan; scan = scan->bnext) {
347 		if ((scan->flags & PKGF_MANUALSEL) == 0)
348 			continue;
349 		if (scan->pkgfile) {
350 			scan->flags &= ~PKGF_PACKAGED;
351 			scan->pkgfile_size = 0;
352 			asprintf(&path, "%s/%s", RepositoryPath, scan->pkgfile);
353 			if (remove(path) == 0)
354 				printf("Removed: %s\n", path);
355 			free(path);
356 		}
357 		if (scan->pkgfile == NULL ||
358 		    (scan->flags & (PKGF_DUMMY | PKGF_META))) {
359 			removePackagesMetaRecurse(scan);
360 		}
361 	}
362 }
363 
364 static void
365 removePackagesMetaRecurse(pkg_t *pkg)
366 {
367 	pkglink_t *link;
368 	pkg_t *scan;
369 	char *path;
370 
371 	PKGLIST_FOREACH(link, &pkg->idepon_list) {
372 		scan = link->pkg;
373 		if (scan == NULL)
374 			continue;
375 		if (scan->pkgfile == NULL ||
376 		    (scan->flags & (PKGF_DUMMY | PKGF_META))) {
377 			removePackagesMetaRecurse(scan);
378 			continue;
379 		}
380 		scan->flags &= ~PKGF_PACKAGED;
381 		scan->pkgfile_size = 0;
382 
383 		asprintf(&path, "%s/%s", RepositoryPath, scan->pkgfile);
384 		if (remove(path) == 0)
385 			printf("Removed: %s\n", path);
386 		free(path);
387 	}
388 }
389 
390 static int
391 pinfocmp(const void *s1, const void *s2)
392 {
393 	const pinfo_t *item1 = *(const pinfo_t *const*)s1;
394 	const pinfo_t *item2 = *(const pinfo_t *const*)s2;
395 
396 	return (strcmp(item1->spath, item2->spath));
397 }
398 
399 pinfo_t *
400 pinfofind(pinfo_t **ary, int count, char *spath)
401 {
402 	pinfo_t *item;
403 	int res;
404 	int b;
405 	int e;
406 	int m;
407 
408 	b = 0;
409 	e = count;
410 	while (b != e) {
411 		m = b + (e - b) / 2;
412 		item = ary[m];
413 		res = strcmp(spath, item->spath);
414 		if (res == 0)
415 			return item;
416 		if (res < 0) {
417 			e = m;
418 		} else {
419 			b = m + 1;
420 		}
421 	}
422 	return NULL;
423 }
424 
425 void
426 scanit(const char *path, const char *subpath,
427        int *countp, pinfo_t ***list_tailp)
428 {
429 	struct dirent *den;
430 	pinfo_t *item;
431 	char *npath;
432 	char *spath;
433 	DIR *dir;
434 	struct stat st;
435 
436 	if ((dir = opendir(path)) != NULL) {
437 		while ((den = readdir(dir)) != NULL) {
438 			if (den->d_namlen == 1 && den->d_name[0] == '.')
439 				continue;
440 			if (den->d_namlen == 2 && den->d_name[0] == '.' &&
441 			    den->d_name[1] == '.')
442 				continue;
443 			asprintf(&npath, "%s/%s", path, den->d_name);
444 			if (lstat(npath, &st) < 0) {
445 				free(npath);
446 				continue;
447 			}
448 			if (S_ISDIR(st.st_mode)) {
449 				if (subpath) {
450 					asprintf(&spath, "%s/%s",
451 						 subpath, den->d_name);
452 					scanit(npath, spath,
453 					       countp, list_tailp);
454 					free(spath);
455 				} else {
456 					scanit(npath, den->d_name,
457 					       countp, list_tailp);
458 				}
459 			} else if (S_ISREG(st.st_mode)) {
460 				item = calloc(1, sizeof(*item));
461 				if (subpath) {
462 					asprintf(&item->spath, "%s/%s",
463 						 subpath, den->d_name);
464 				} else {
465 					item->spath = strdup(den->d_name);
466 				}
467 				**list_tailp = item;
468 				*list_tailp = &item->next;
469 				++*countp;
470 				ddprintf(0, "scan   %s\n", item->spath);
471 			}
472 			free(npath);
473 		}
474 		closedir(dir);
475 	}
476 }
477 
478 /*
479  * This removes any .new files left over in the repo.  These can wind
480  * being left around when dsynth is killed.
481  */
482 static void
483 scandeletenew(const char *path)
484 {
485 	struct dirent *den;
486 	const char *ptr;
487 	DIR *dir;
488 	char *buf;
489 
490 	if ((dir = opendir(path)) == NULL)
491 		dfatal_errno("Cannot scan directory %s", path);
492 	while ((den = readdir(dir)) != NULL) {
493 		if ((ptr = strrchr(den->d_name, '.')) != NULL &&
494 		    strcmp(ptr, ".new") == 0) {
495 			asprintf(&buf, "%s/%s", path, den->d_name);
496 			if (remove(buf) < 0)
497 				dfatal_errno("remove: Garbage %s\n", buf);
498 			printf("Deleted Garbage %s\n", buf);
499 			free(buf);
500 		}
501 	}
502 	closedir(dir);
503 }
504 
505 static void
506 rebuildTerminateSignal(int signo __unused)
507 {
508 	if (RebuildRemovePath)
509 		remove(RebuildRemovePath);
510 	exit(1);
511 
512 }
513