xref: /dragonfly/usr.bin/dsynth/dsynth.c (revision 556932ec)
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 
38 #include "dsynth.h"
39 
40 static int CheckAddReExec(int lkfd);
41 static void DoAddReExec(int lkfd, int ac, char **oldav);
42 static void DoInit(void);
43 static void usage(int ecode) __dead2;
44 
45 int ForceOpt;
46 int OverridePkgDeleteOpt;
47 int FetchOnlyOpt;
48 int YesOpt;
49 int DebugOpt;
50 int MaskProbeAbort;
51 int ColorOpt = 1;
52 int NullStdinOpt = 1;
53 int SlowStartOpt = -1;
54 long PkgDepMemoryTarget;
55 long PkgDepScaleTarget = 100;	/* 1.00 */
56 char *DSynthExecPath;
57 char *ProfileOverrideOpt;
58 int NiceOpt = 10;
59 
60 int
61 main(int ac, char **av)
62 {
63 	char *lkpath;
64 	pkg_t *pkgs;
65 	int lkfd;
66 	int isworker;
67 	int c;
68 	int sopt;
69 	int doadds;
70 
71 	/*
72 	 * Get our exec path so we can self-exec clean WORKER
73 	 * processes.
74 	 */
75 	{
76 		size_t len;
77 		const int name[] = {
78 			CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1,
79 		};
80 		if (sysctl(name, 4, NULL, &len, NULL, 0) < 0)
81 			dfatal_errno("Cannot get binary path");
82 		DSynthExecPath = malloc(len + 1);
83 		if (sysctl(name, 4, DSynthExecPath, &len, NULL, 0) < 0)
84 			dfatal_errno("Cannot get binary path");
85 		DSynthExecPath[len] = 0;
86 	}
87 
88 	/*
89 	 * Override profile in dsynth.ini (can be further overridden
90 	 * with the -p profile option).
91 	 */
92 	ProfileOverrideOpt = getenv("DSYNTH_PROFILE");
93 
94 	/*
95 	 * Process options and make sure the directive is present
96 	 */
97 	sopt = 0;
98 	while ((c = getopt(ac, av, "dfhm:p:vxys:DPM:NS")) != -1) {
99 		switch(c) {
100 		case 'f':
101 			++ForceOpt;
102 			break;
103 		case 'x':
104 			++OverridePkgDeleteOpt;
105 			break;
106 		case 'y':
107 			++YesOpt;
108 			break;
109 		case 'D':
110 			WorkerProcFlags |= WORKER_PROC_DEVELOPER;
111 			break;
112 		case 'P':
113 			WorkerProcFlags |= WORKER_PROC_CHECK_PLIST;
114 			break;
115 		case 'S':
116 			UseNCurses = 0;
117 			if (++sopt == 2)
118 				ColorOpt = 0;
119 			break;
120 		case 'N':
121 			NiceOpt = 0;
122 			break;
123 		case 'd':
124 			++DebugOpt;
125 			if (DebugOpt >= 2)
126 				UseNCurses = 0;
127 			break;
128 		case 'h':
129 			usage(0);
130 			/* NOT REACHED */
131 			exit(0);
132 		case 'v':
133 			printf("dsynth %s\n", DSYNTH_VERSION);
134 			exit(0);
135 		case 's':
136 			/*
137 			 * Start with N jobs, increasing to the configured
138 			 * maximum slowly.  0 to disable (starts with the
139 			 * full count).
140 			 */
141 			SlowStartOpt = strtol(optarg, NULL, 0);
142 			break;
143 		case 'm':
144 			PkgDepMemoryTarget = strtoul(optarg, NULL, 0);
145 			PkgDepMemoryTarget *= ONEGB;
146 			break;
147 		case 'M':
148 			PkgDepScaleTarget = strtod(optarg, NULL) * 100;
149 			if (PkgDepScaleTarget < 1)
150 				PkgDepScaleTarget = 1;
151 			if (PkgDepScaleTarget > 9900)
152 				PkgDepScaleTarget = 9900;
153 			break;
154 		case 'p':
155 			ProfileOverrideOpt = optarg;
156 			break;
157 		default:
158 			fprintf(stderr, "Unknown option: %c\n", c);
159 			usage(2);
160 			/* NOT REACHED */
161 			break;
162 		}
163 	}
164 	ac -= optind;
165 	av += optind;
166 	pkgs = NULL;
167 	if (ac < 1) {
168 		fprintf(stderr, "Missing directive\n");
169 		usage(2);
170 		/* NOT REACHED */
171 	}
172 
173 	/*
174 	 * Directives which do not require a working configuration
175 	 */
176 	if (strcmp(av[0], "init") == 0) {
177 		DoInit();
178 		exit(0);
179 		/* NOT REACHED */
180 	}
181 	if (strcmp(av[0], "help") == 0) {
182 		usage(0);
183 		exit(0);
184 		/* NOT REACHED */
185 	}
186 	if (strcmp(av[0], "version") == 0) {
187 		printf("dsynth %s\n", DSYNTH_VERSION);
188 		exit(0);
189 		/* NOT REACHED */
190 	}
191 
192 	/*
193 	 * Preconfiguration.
194 	 */
195 	if (strcmp(av[0], "WORKER") == 0) {
196 		isworker = 1;
197 	} else {
198 		isworker = 0;
199 	}
200 
201 	signal(SIGPIPE, SIG_IGN);
202 	ParseConfiguration(isworker);
203 
204 	/*
205 	 * Lock file path (also contains any 'add' directives thrown in
206 	 * during a build).
207 	 */
208 	asprintf(&lkpath, "%s/.lock", BuildBase);
209 
210 	/*
211 	 * Setup some environment for bulk operations (pkglist scan).
212 	 * These are not used by the builder (the builder will replicate
213 	 * all of these).
214 	 *
215 	 * NOTE: PKG_SUFX - pkg versions older than 1.17
216 	 *	 PKG_COMPRESSION_FORMAT - pkg versions >= 1.17
217 	 */
218 	addbuildenv("PORTSDIR", DPortsPath,
219 		    BENV_ENVIRONMENT | BENV_PKGLIST);
220 	addbuildenv("BATCH", "yes",
221 		    BENV_ENVIRONMENT | BENV_PKGLIST);
222 	addbuildenv("PKG_COMPRESSION_FORMAT", UsePkgSufx,
223 		    BENV_ENVIRONMENT | BENV_PKGLIST);
224 	addbuildenv("PKG_SUFX", UsePkgSufx,
225 		    BENV_ENVIRONMENT | BENV_PKGLIST);
226 	addbuildenv("PACKAGE_BUILDING", "yes",
227 		    BENV_ENVIRONMENT | BENV_PKGLIST);
228 	addbuildenv("ARCH", ArchitectureName,
229 		    BENV_ENVIRONMENT | BENV_PKGLIST);
230 
231 #if 0
232 	/*
233 	 *
234 	 */
235 	addbuildenv("OSTYPE", OperatingSystemName,
236 		    BENV_ENVIRONMENT | BENV_PKGLIST);
237 	addbuildenv("MACHTYPE", MachineName,
238 		    BENV_ENVIRONMENT | BENV_PKGLIST);
239 #endif
240 	/*
241 	 * SlowStart auto adjust.  We nominally start with 1 job and increase
242 	 * it to the maximum every 5 seconds to give various dynamic management
243 	 * parameters time to stabilize.
244 	 *
245 	 * This can take a while on a many-core box with a high jobs setting,
246 	 * so increase the initial jobs in such cases.
247 	 */
248 	if (SlowStartOpt > MaxWorkers)
249 		SlowStartOpt = MaxWorkers;
250 	if (SlowStartOpt < 0) {
251 		if (MaxWorkers < 16)
252 			SlowStartOpt = 1;
253 		else
254 			SlowStartOpt = MaxWorkers / 4;
255 	}
256 
257 	/*
258 	 * Special directive for when dsynth execs itself to manage
259 	 * a worker chroot.
260 	 */
261 	if (isworker) {
262 		WorkerProcess(ac, av);
263 		exit(0);
264 	}
265 
266 	/*
267 	 * Build initialization and directive handling
268 	 */
269 	DoInitBuild(-1);
270 
271 	/*
272 	 * Directives that use the configuration but are not interlocked
273 	 * against a running dsynth.
274 	 */
275 	if (strcmp(av[0], "monitor") == 0) {
276 		char *spath;
277 		char *lpath;
278 
279 		if (ac == 1) {
280 			asprintf(&spath, "%s/%s", StatsBase, STATS_FILE);
281 			asprintf(&lpath, "%s/%s", StatsBase, STATS_LOCKFILE);
282 			MonitorDirective(spath, lpath);
283 			free(spath);
284 			free(lpath);
285 		} else {
286 			MonitorDirective(av[1], NULL);
287 		}
288 		exit(0);
289 		/* NOT REACHED */
290 	} else if (strcmp(av[0], "add") == 0) {
291 		char *buf;
292 		int fd;
293 		int i;
294 
295 		/*
296 		 * The lock check is a bit racey XXX
297 		 */
298 		fd = open(lkpath, O_RDWR | O_CREAT | O_APPEND, 0644);
299 		if (flock(fd, LOCK_EX | LOCK_NB) == 0) {
300 			dfatal("No dsynth running to add ports to");
301 			flock(fd, LOCK_UN);
302 		}
303 		for (i = 1; i < ac; ++i) {
304 			asprintf(&buf, "%s\n", av[i]);
305 			write(fd, buf, strlen(buf));
306 			printf("added to run: %s\n", av[i]);
307 		}
308 		close(fd);
309 		exit(0);
310 	}
311 
312 	/*
313 	 * Front-end exec (not a WORKER exec), normal startup.  We have
314 	 * the configuration so the first thing we need to do is check
315 	 * the lock file.
316 	 */
317 	lkfd = open(lkpath, O_RDWR | O_CREAT | O_CLOEXEC, 0644);
318 	if (lkfd < 0)
319 		dfatal_errno("Unable to create %s", lkpath);
320 	if (flock(lkfd, LOCK_EX | LOCK_NB) < 0) {
321 		dfatal("Another dsynth is using %s, exiting",
322 		       BuildBase);
323 	}
324 
325 	/*
326 	 * Starting a new run cleans out any prior add directives
327 	 * that may have been pending.
328 	 */
329 	ftruncate(lkfd, 0);
330 	/* leave descriptor open */
331 
332 	doadds = 0;
333 
334 	if (strcmp(av[0], "debug") == 0) {
335 		DoCleanBuild(1);
336 		OptimizeEnv();
337 		pkgs = ParsePackageList(ac - 1, av + 1, 1);
338 		RemovePackages(pkgs);
339 		DoBuild(pkgs);
340 		doadds = 1;
341 	} else if (strcmp(av[0], "status") == 0) {
342 		OptimizeEnv();
343 		if (ac - 1)
344 			pkgs = ParsePackageList(ac - 1, av + 1, 0);
345 		else
346 			pkgs = GetLocalPackageList();
347 		DoStatus(pkgs);
348 	} else if (strcmp(av[0], "cleanup") == 0) {
349 		DoCleanBuild(0);
350 	} else if (strcmp(av[0], "configure") == 0) {
351 		DoCleanBuild(0);
352 		DoConfigure();
353 	} else if (strcmp(av[0], "fetch-only") == 0) {
354 		if (SlowStartOpt == -1)
355 			SlowStartOpt = 999;
356 		if (PkgDepScaleTarget == 100)
357 			PkgDepScaleTarget = 999;
358 		++FetchOnlyOpt;
359 		++YesOpt;
360 		WorkerProcFlags |= WORKER_PROC_FETCHONLY;
361 		DoCleanBuild(1);
362 		OptimizeEnv();
363 		if (ac == 2 && strcmp(av[1], "everything") == 0) {
364 			MaskProbeAbort = 1;
365 			pkgs = GetFullPackageList();
366 		} else {
367 			pkgs = ParsePackageList(ac - 1, av + 1, 0);
368 		}
369 		DoBuild(pkgs);
370 		doadds = 1;
371 	} else if (strcmp(av[0], "list-system") == 0) {
372 		FILE *fp;
373 
374 		DoCleanBuild(1);
375 		OptimizeEnv();
376 		pkgs = GetLocalPackageList();
377 		if ((fp = fopen("build.txt", "w")) != NULL) {
378 			while (pkgs) {
379 				fprintf(fp, "%s\n", pkgs->portdir);
380 				pkgs = pkgs->bnext;
381 			}
382 			fclose(fp);
383 			printf("list written to build.txt\n");
384 		} else {
385 			fprintf(stderr, "Cannot create 'build.txt'\n");
386 			exit(1);
387 		}
388 	} else if (strcmp(av[0], "upgrade-system") == 0) {
389 		DoCleanBuild(1);
390 		OptimizeEnv();
391 		pkgs = GetLocalPackageList();
392 		DoBuild(pkgs);
393 		DoRebuildRepo(0);
394 		DoUpgradePkgs(pkgs, 0);
395 		dfatal("NOTE: you have to pkg upgrade manually");
396 	} else if (strcmp(av[0], "prepare-system") == 0) {
397 		DeleteObsoletePkgs = 1;
398 		DoCleanBuild(1);
399 		OptimizeEnv();
400 		pkgs = GetLocalPackageList();
401 		DoBuild(pkgs);
402 		DoRebuildRepo(0);
403 	} else if (strcmp(av[0], "rebuild-repository") == 0) {
404 		OptimizeEnv();
405 		DoRebuildRepo(0);
406 	} else if (strcmp(av[0], "purge-distfiles") == 0) {
407 		OptimizeEnv();
408 		pkgs = GetFullPackageList();
409 		PurgeDistfiles(pkgs);
410 	} else if (strcmp(av[0], "reset-db") == 0) {
411 		char *dbmpath;
412 
413 		asprintf(&dbmpath, "%s/ports_crc.db", BuildBase);
414 		remove(dbmpath);
415 		printf("%s reset, will be regenerated on next build\n",
416 		       dbmpath);
417 		free(dbmpath);
418 	} else if (strcmp(av[0], "status-everything") == 0) {
419 		OptimizeEnv();
420 		pkgs = GetFullPackageList();
421 		DoStatus(pkgs);
422 	} else if (strcmp(av[0], "everything") == 0) {
423 		if (WorkerProcFlags & WORKER_PROC_DEVELOPER)
424 			WorkerProcFlags |= WORKER_PROC_CHECK_PLIST;
425 		MaskProbeAbort = 1;
426 		DeleteObsoletePkgs = 1;
427 		DoCleanBuild(1);
428 		OptimizeEnv();
429 		pkgs = GetFullPackageList();
430 		DoBuild(pkgs);
431 		DoRebuildRepo(!CheckAddReExec(lkfd));
432 	} else if (strcmp(av[0], "build") == 0) {
433 		DoCleanBuild(1);
434 		OptimizeEnv();
435 		pkgs = ParsePackageList(ac - 1, av + 1, 0);
436 		DoBuild(pkgs);
437 		DoRebuildRepo(!CheckAddReExec(lkfd));
438 		DoUpgradePkgs(pkgs, 1);
439 		doadds = 1;
440 	} else if (strcmp(av[0], "just-build") == 0) {
441 		DoCleanBuild(1);
442 		OptimizeEnv();
443 		pkgs = ParsePackageList(ac - 1, av + 1, 0);
444 		DoBuild(pkgs);
445 		doadds = 1;
446 	} else if (strcmp(av[0], "install") == 0) {
447 		DoCleanBuild(1);
448 		OptimizeEnv();
449 		pkgs = ParsePackageList(ac - 1, av + 1, 0);
450 		DoBuild(pkgs);
451 		DoRebuildRepo(0);
452 		DoUpgradePkgs(pkgs, 0);
453 		doadds = 1;
454 	} else if (strcmp(av[0], "force") == 0) {
455 		DoCleanBuild(1);
456 		OptimizeEnv();
457 		pkgs = ParsePackageList(ac - 1, av + 1, 0);
458 		RemovePackages(pkgs);
459 		DoBuild(pkgs);
460 		DoRebuildRepo(!CheckAddReExec(lkfd));
461 		DoUpgradePkgs(pkgs, 1);
462 		doadds = 1;
463 	} else if (strcmp(av[0], "test") == 0) {
464 		WorkerProcFlags |= WORKER_PROC_CHECK_PLIST |
465 				   WORKER_PROC_INSTALL |
466 				   WORKER_PROC_DEINSTALL;
467 		DoCleanBuild(1);
468 		OptimizeEnv();
469 		pkgs = ParsePackageList(ac - 1, av + 1, 0);
470 		RemovePackages(pkgs);
471 		WorkerProcFlags |= WORKER_PROC_DEVELOPER;
472 		DoBuild(pkgs);
473 		doadds = 1;
474 	} else {
475 		fprintf(stderr, "Unknown directive '%s'\n", av[0]);
476 		usage(2);
477 	}
478 
479 	/*
480 	 * For directives that support the 'add' directive, check for
481 	 * additions and re-exec.
482 	 *
483 	 * Note that the lockfile is O_CLOEXEC and will be remade on exec.
484 	 *
485 	 * XXX a bit racey vs adds done just as we are finishing
486 	 */
487 	if (doadds && CheckAddReExec(lkfd))
488 		DoAddReExec(lkfd, optind + 1, av - optind);
489 
490 	return 0;
491 }
492 
493 /*
494  * If the 'add' directive was issued while a dsynth build was in
495  * progress, we re-exec dsynth with its original options and
496  * directive along with the added ports.
497  */
498 static int
499 CheckAddReExec(int lkfd)
500 {
501 	struct stat st;
502 
503 	if (fstat(lkfd, &st) < 0 || st.st_size == 0)
504 		return 0;
505 	return 1;
506 }
507 
508 static void
509 DoAddReExec(int lkfd, int ac, char **oldav)
510 {
511 	struct stat st;
512 	char *buf;
513 	char **av;
514 	size_t bi;
515 	size_t i;
516 	int nadd;
517 	int n;
518 
519 	if (fstat(lkfd, &st) < 0 || st.st_size == 0)
520 		return;
521 	buf = malloc(st.st_size + 1);
522 	if (read(lkfd, buf, st.st_size) != st.st_size) {
523 		free(buf);
524 		return;
525 	}
526 	buf[st.st_size] = 0;
527 
528 	nadd = 0;
529 	for (i = 0; i < (size_t)st.st_size; ++i) {
530 		if (buf[i] == '\n' || buf[i] == 0) {
531 			buf[i] = 0;
532 			++nadd;
533 		}
534 	}
535 
536 	av = calloc(ac + nadd + 1, sizeof(char *));
537 
538 	for (n = 0; n < ac; ++n)
539 		av[n] = oldav[n];
540 
541 	nadd = 0;
542 	bi = 0;
543 	for (i = 0; i < (size_t)st.st_size; ++i) {
544 		if (buf[i] == 0) {
545 			av[ac + nadd] = buf + bi;
546 			bi = i + 1;
547 			++nadd;
548 		}
549 	}
550 
551 	printf("dsynth re-exec'ing additionally added packages\n");
552 	for (n = 0; n < ac + nadd; ++n)
553 		printf(" %s", av[n]);
554 	printf("\n");
555 	fflush(stdout);
556 	sleep(2);
557 	execv(DSynthExecPath, av);
558 }
559 
560 static void
561 DoInit(void)
562 {
563 	struct stat st;
564 	char *path;
565 	FILE *fp;
566 
567 	if (stat(ConfigBase1, &st) == 0) {
568 		dfatal("init will not overwrite %s", ConfigBase1);
569 	}
570 	if (stat(ConfigBase2, &st) == 0) {
571 		dfatal("init will not create %s if %s exists",
572 		       ConfigBase2, ConfigBase1);
573 	}
574 	if (mkdir(ConfigBase1, 0755) < 0)
575 		dfatal_errno("Unable to mkdir %s", ConfigBase1);
576 
577 	asprintf(&path, "%s/dsynth.ini", ConfigBase1);
578 	fp = fopen(path, "w");
579 	dassert_errno(fp, "Unable to create %s", path);
580 	fprintf(fp, "%s",
581 	    "; This Synth configuration file is automatically generated\n"
582 	    "; Take care when hand editing!\n"
583 	    "\n"
584 	    "[Global Configuration]\n"
585 	    "profile_selected= LiveSystem\n"
586 	    "\n"
587 	    "[LiveSystem]\n"
588 	    "Operating_system= DragonFly\n"
589 	    "Directory_packages= /build/synth/live_packages\n"
590 	    "Directory_repository= /build/synth/live_packages/All\n"
591 	    "Directory_portsdir= /build/synth/dports\n"
592 	    "Directory_options= /build/synth/options\n"
593 	    "Directory_distfiles= /build/synth/distfiles\n"
594 	    "Directory_buildbase= /build/synth/build\n"
595 	    "Directory_logs= /build/synth/logs\n"
596 	    "Directory_ccache= disabled\n"
597 	    "Directory_system= /\n"
598 	    "Package_suffix= .txz\n"
599 	    "Number_of_builders= 0\n"
600 	    "Max_jobs_per_builder= 0\n"
601 	    "Tmpfs_workdir= true\n"
602 	    "Tmpfs_localbase= true\n"
603 	    "Display_with_ncurses= true\n"
604 	    "leverage_prebuilt= false\n"
605 	    "; Meta_version= 2\n"
606 	    "; Check_plist= false\n"
607 	    "; Numa_setsize= 2\n"
608 	    "\n");
609 	if (fclose(fp))
610 		dfatal_errno("Unable to write to %s\n", ConfigBase1);
611 	free(path);
612 
613 	asprintf(&path, "%s/LiveSystem-make.conf", ConfigBase1);
614 	fp = fopen(path, "w");
615 	dassert_errno(fp, "Unable to create %s", path);
616 	fprintf(fp, "%s",
617 	    "#\n"
618 	    "# Various dports options that might be of interest\n"
619 	    "#\n"
620 	    "#LICENSES_ACCEPTED=      NONE\n"
621 	    "#DISABLE_LICENSES=       yes\n"
622 	    "#DEFAULT_VERSIONS=       ssl=openssl\n"
623 	    "#FORCE_PACKAGE=          yes\n"
624 	    "#DPORTS_BUILDER=         yes\n"
625 	    "#\n"
626 	    "# Turn these on to generate debug binaries.  However, these\n"
627 	    "# options will seriously bloat memory use and storage use,\n"
628 	    "# do not use lightly\n"
629 	    "#\n"
630 	    "#STRIP=\n"
631 	    "#WITH_DEBUG=yes\n"
632 	);
633 	if (fclose(fp))
634 		dfatal_errno("Unable to write to %s\n", ConfigBase1);
635 	free(path);
636 }
637 
638 __dead2 static void
639 usage(int ecode)
640 {
641 	if (ecode == 2) {
642 		fprintf(stderr, "Run 'dsynth help' for usage\n");
643 		exit(1);
644 	}
645 
646 	fprintf(stderr,
647     "dsynth [options] directive\n"
648     "    -d                   - Debug verbosity (-dd disables ncurses)\n"
649     "    -f                   - Force (for purge-distfiles)\n"
650     "    -h                   - Display this screen and exit\n"
651     "    -m gb                - Load management based on pkgdep memory\n"
652     "    -p profile           - Override profile selected in dsynth.ini\n"
653     "    -s n                 - Set initial DynamicMaxWorkers\n"
654     "    -v                   - Print version info and exit\n"
655     "    -x                   - Do not rebuild packages with dependencies\n"
656     "                           which require rebuilding\n"
657     "    -xx                  - Do not rebuild packages whos dports trees\n"
658     "                           change\n"
659     "    -y                   - Automatically answer yes to dsynth questions\n"
660     "    -D                   - Enable DEVELOPER mode\n"
661     "    -P                   - Include the check-plist stage\n"
662     "    -S                   - Disable ncurses\n"
663     "    -N                   - Do not nice-up sub-processes (else nice +10)\n"
664     "\n"
665     "    init                 - Initialize /etc/dsynth\n"
666     "    status               - Dry-run of 'upgrade-system'\n"
667     "    cleanup              - Clean-up mounts\n"
668     "    configure            - Bring up configuration menu\n"
669     "    list-system          - Just generate the build list to build.txt\n"
670     "    upgrade-system       - Incremental build and upgrade using pkg list\n"
671     "                           from local system, then upgrade the local\n"
672     "                           system.\n"
673     "    prepare-system       - 'upgrade-system' but stops after building\n"
674     "    rebuild-repository   - Rebuild database files for current repository\n"
675     "    purge-distfiles      - Delete obsolete source distribution files\n"
676     "    reset-db             - Delete ports_crc.db, regenerate next build\n"
677     "    status-everything    - Dry-run of 'everything'\n"
678     "    everything           - Build entire dports tree and repo database\n"
679     "				(-D everything infers -P)\n"
680     "    version              - Print version info and exit\n"
681     "    help                 - Display this screen and exit\n"
682     "    status     [ports]   - Dry-run of 'build' with given list\n"
683     "    build      [ports]   - Incrementally build dports based on the given\n"
684     "                           list, but asks before updating the repo\n"
685     "                           database and system\n"
686     "    just-build [ports]   - 'build' but skips post-build steps\n"
687     "    install    [ports]   - 'build' but upgrades system without asking\n"
688     "    force      [ports]   - 'build' but deletes existing packages first\n"
689     "    test       [ports]   - 'build' w/DEVELOPER=yes and pre-deletes pkgs\n"
690     "				(also infers -P)\n"
691     "    debug      [ports]   - like 'test' but leaves mounts intact\n"
692     "    fetch-only [ports]   - Fetch src dists only ('everything' ok)\n"
693     "    monitor    [datfile] - Monitor a running dsynth\n"
694     "\n"
695     "    [ports] is a space-delimited list of origins, e.g. editors/joe.  It\n"
696     "            may also be a path to a file containing one origin per line.\n"
697 	);
698 
699 	exit(ecode);
700 }
701