xref: /dragonfly/usr.bin/dsynth/dsynth.c (revision c9b86fef)
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 void DoInit(void);
41 static void usage(int ecode) __dead2;
42 
43 int YesOpt;
44 int DebugOpt;
45 int ColorOpt = 1;
46 int NullStdinOpt = 1;
47 int SlowStartOpt = 1;
48 long PkgDepMemoryTarget;
49 char *DSynthExecPath;
50 char *ProfileOverrideOpt;
51 
52 int
53 main(int ac, char **av)
54 {
55 	pkg_t *pkgs;
56 	int isworker;
57 	int c;
58 	int sopt;
59 
60 	/*
61 	 * Get our exec path so we can self-exec clean WORKER
62 	 * processes.
63 	 */
64 	{
65 		size_t len;
66 		const int name[] = {
67 			CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1,
68 		};
69 		if (sysctl(name, 4, NULL, &len, NULL, 0) < 0)
70 			dfatal_errno("Cannot get binary path");
71 		DSynthExecPath = malloc(len + 1);
72 		if (sysctl(name, 4, DSynthExecPath, &len, NULL, 0) < 0)
73 			dfatal_errno("Cannot get binary path");
74 		DSynthExecPath[len] = 0;
75 	}
76 
77 	/*
78 	 * Override profile in dsynth.ini (can be further overridden
79 	 * with the -p profile option).
80 	 */
81 	ProfileOverrideOpt = getenv("DSYNTH_PROFILE");
82 
83 	/*
84 	 * Process options and make sure the directive is present
85 	 */
86 	sopt = 0;
87 	while ((c = getopt(ac, av, "dhm:p:vys:DPS")) != -1) {
88 		switch(c) {
89 		case 'y':
90 			++YesOpt;
91 			break;
92 		case 'D':
93 			WorkerProcFlags |= WORKER_PROC_DEVELOPER;
94 			break;
95 		case 'P':
96 			WorkerProcFlags |= WORKER_PROC_CHECK_PLIST;
97 			break;
98 		case 'S':
99 			UseNCurses = 0;
100 			if (++sopt == 2)
101 				ColorOpt = 0;
102 			break;
103 		case 'd':
104 			++DebugOpt;
105 			if (DebugOpt >= 2)
106 				UseNCurses = 0;
107 			break;
108 		case 'h':
109 			usage(0);
110 			/* NOT REACHED */
111 			exit(0);
112 		case 'v':
113 			printf("dsynth %s\n", DSYNTH_VERSION);
114 			exit(0);
115 		case 's':
116 			SlowStartOpt = strtol(optarg, NULL, 0);
117 			break;
118 		case 'm':
119 			PkgDepMemoryTarget = strtoul(optarg, NULL, 0);
120 			PkgDepMemoryTarget *= ONEGB;
121 			break;
122 		case 'p':
123 			ProfileOverrideOpt = optarg;
124 			break;
125 		default:
126 			fprintf(stderr, "Unknown option: %c\n", c);
127 			usage(2);
128 			/* NOT REACHED */
129 			break;
130 		}
131 	}
132 	ac -= optind;
133 	av += optind;
134 	pkgs = NULL;
135 	if (ac < 1) {
136 		fprintf(stderr, "Missing directive\n");
137 		usage(2);
138 		/* NOT REACHED */
139 	}
140 
141 	/*
142 	 * Directives which do not require a working configuration
143 	 */
144 	if (strcmp(av[0], "init") == 0) {
145 		DoInit();
146 		exit(0);
147 		/* NOT REACHED */
148 	}
149 	if (strcmp(av[0], "help") == 0) {
150 		usage(0);
151 		exit(0);
152 		/* NOT REACHED */
153 	}
154 	if (strcmp(av[0], "version") == 0) {
155 		printf("dsynth %s\n", DSYNTH_VERSION);
156 		exit(0);
157 		/* NOT REACHED */
158 	}
159 
160 	/*
161 	 * Preconfiguration.
162 	 */
163 	if (strcmp(av[0], "WORKER") == 0) {
164 		isworker = 1;
165 	} else {
166 		isworker = 0;
167 	}
168 
169 	signal(SIGPIPE, SIG_IGN);
170 	ParseConfiguration(isworker);
171 
172 	/*
173 	 * Setup some environment for bulk operations (pkglist scan).
174 	 * These are not used by the builder (the builder will replicate
175 	 * all of these).
176 	 */
177 	addbuildenv("PORTSDIR", DPortsPath,
178 		    BENV_ENVIRONMENT | BENV_PKGLIST);
179 	addbuildenv("BATCH", "yes",
180 		    BENV_ENVIRONMENT | BENV_PKGLIST);
181 	addbuildenv("PKG_SUFX", UsePkgSufx,
182 		    BENV_ENVIRONMENT | BENV_PKGLIST);
183 	addbuildenv("PACKAGE_BUILDING", "yes",
184 		    BENV_ENVIRONMENT | BENV_PKGLIST);
185 	addbuildenv("ARCH", ArchitectureName,
186 		    BENV_ENVIRONMENT | BENV_PKGLIST);
187 
188 #if 0
189 	/*
190 	 *
191 	 */
192 	addbuildenv("OSTYPE", OperatingSystemName,
193 		    BENV_ENVIRONMENT | BENV_PKGLIST);
194 	addbuildenv("MACHTYPE", MachineName,
195 		    BENV_ENVIRONMENT | BENV_PKGLIST);
196 #endif
197 
198 	/*
199 	 * Special directive for when dsynth execs itself to manage
200 	 * a worker chroot.
201 	 */
202 	if (isworker) {
203 		WorkerProcess(ac, av);
204 		exit(0);
205 	}
206 
207 	/*
208 	 * Build initialization and directive handling
209 	 */
210 	DoInitBuild(-1);
211 
212 	/*
213 	 * Directives that use the configuration but are not interlocked
214 	 * against a running dsynth.
215 	 */
216 	if (strcmp(av[0], "monitor") == 0) {
217 		char *spath;
218 		char *lpath;
219 
220 		if (ac == 1) {
221 			asprintf(&spath, "%s/%s", StatsBase, STATS_FILE);
222 			asprintf(&lpath, "%s/%s", StatsBase, STATS_LOCKFILE);
223 			MonitorDirective(spath, lpath);
224 			free(spath);
225 			free(lpath);
226 		} else {
227 			MonitorDirective(av[1], NULL);
228 		}
229 		exit(0);
230 		/* NOT REACHED */
231 	}
232 
233 	/*
234 	 * Front-end exec (not a WORKER exec), normal startup.  We have
235 	 * the configuration so the first thing we need to do is check
236 	 * the lock file.
237 	 */
238 	{
239 		char *lkpath;
240 		int fd;
241 
242 		asprintf(&lkpath, "%s/.lock", BuildBase);
243 		fd = open(lkpath, O_RDWR | O_CREAT | O_CLOEXEC, 0644);
244 		if (fd < 0)
245 			dfatal_errno("Unable to create %s", lkpath);
246 		if (flock(fd, LOCK_EX | LOCK_NB) < 0) {
247 			dfatal("Another dsynth is using %s, exiting",
248 			       BuildBase);
249 		}
250 		/* leave descriptor open */
251 	}
252 
253 	if (strcmp(av[0], "debug") == 0) {
254 		DoCleanBuild(1);
255 		OptimizeEnv();
256 		pkgs = ParsePackageList(ac - 1, av + 1, 1);
257 		RemovePackages(pkgs);
258 		DoBuild(pkgs);
259 	} else if (strcmp(av[0], "status") == 0) {
260 		OptimizeEnv();
261 		if (ac - 1)
262 			pkgs = ParsePackageList(ac - 1, av + 1, 0);
263 		else
264 			pkgs = GetLocalPackageList();
265 		DoStatus(pkgs);
266 	} else if (strcmp(av[0], "cleanup") == 0) {
267 		DoCleanBuild(0);
268 	} else if (strcmp(av[0], "configure") == 0) {
269 		DoCleanBuild(0);
270 		DoConfigure();
271 	} else if (strcmp(av[0], "upgrade-system") == 0) {
272 		DoCleanBuild(1);
273 		OptimizeEnv();
274 		pkgs = GetLocalPackageList();
275 		DoBuild(pkgs);
276 		DoRebuildRepo(0);
277 		DoUpgradePkgs(pkgs, 0);
278 	} else if (strcmp(av[0], "prepare-system") == 0) {
279 		DoCleanBuild(1);
280 		OptimizeEnv();
281 		pkgs = GetLocalPackageList();
282 		DoBuild(pkgs);
283 		DoRebuildRepo(0);
284 	} else if (strcmp(av[0], "rebuild-repository") == 0) {
285 		OptimizeEnv();
286 		DoRebuildRepo(0);
287 	} else if (strcmp(av[0], "purge-distfiles") == 0) {
288 		OptimizeEnv();
289 		pkgs = GetFullPackageList();
290 		PurgeDistfiles(pkgs);
291 	} else if (strcmp(av[0], "status-everything") == 0) {
292 		OptimizeEnv();
293 		pkgs = GetFullPackageList();
294 		DoStatus(pkgs);
295 	} else if (strcmp(av[0], "everything") == 0) {
296 		if (WorkerProcFlags & WORKER_PROC_DEVELOPER)
297 			WorkerProcFlags |= WORKER_PROC_CHECK_PLIST;
298 		DoCleanBuild(1);
299 		OptimizeEnv();
300 		pkgs = GetFullPackageList();
301 		DoBuild(pkgs);
302 		DoRebuildRepo(1);
303 	} else if (strcmp(av[0], "build") == 0) {
304 		DoCleanBuild(1);
305 		OptimizeEnv();
306 		pkgs = ParsePackageList(ac - 1, av + 1, 0);
307 		DoBuild(pkgs);
308 		DoRebuildRepo(1);
309 		DoUpgradePkgs(pkgs, 1);
310 	} else if (strcmp(av[0], "just-build") == 0) {
311 		DoCleanBuild(1);
312 		OptimizeEnv();
313 		pkgs = ParsePackageList(ac - 1, av + 1, 0);
314 		DoBuild(pkgs);
315 	} else if (strcmp(av[0], "install") == 0) {
316 		DoCleanBuild(1);
317 		OptimizeEnv();
318 		pkgs = ParsePackageList(ac - 1, av + 1, 0);
319 		DoBuild(pkgs);
320 		DoRebuildRepo(0);
321 		DoUpgradePkgs(pkgs, 0);
322 	} else if (strcmp(av[0], "force") == 0) {
323 		DoCleanBuild(1);
324 		OptimizeEnv();
325 		pkgs = ParsePackageList(ac - 1, av + 1, 0);
326 		RemovePackages(pkgs);
327 		DoBuild(pkgs);
328 		DoRebuildRepo(1);
329 		DoUpgradePkgs(pkgs, 1);
330 	} else if (strcmp(av[0], "test") == 0) {
331 		WorkerProcFlags |= WORKER_PROC_CHECK_PLIST;
332 		DoCleanBuild(1);
333 		OptimizeEnv();
334 		pkgs = ParsePackageList(ac - 1, av + 1, 0);
335 		RemovePackages(pkgs);
336 		WorkerProcFlags |= WORKER_PROC_DEVELOPER;
337 		DoBuild(pkgs);
338 	} else {
339 		fprintf(stderr, "Unknown directive '%s'\n", av[0]);
340 		usage(2);
341 	}
342 	return 0;
343 }
344 
345 static void
346 DoInit(void)
347 {
348 	struct stat st;
349 	char *path;
350 	FILE *fp;
351 
352 	if (stat(ConfigBase1, &st) == 0) {
353 		dfatal("init will not overwrite %s", ConfigBase1);
354 	}
355 	if (stat(ConfigBase2, &st) == 0) {
356 		dfatal("init will not create %s if %s exists",
357 		       ConfigBase2, ConfigBase1);
358 	}
359 	if (mkdir(ConfigBase1, 0755) < 0)
360 		dfatal_errno("Unable to mkdir %s", ConfigBase1);
361 
362 	asprintf(&path, "%s/dsynth.ini", ConfigBase1);
363 	fp = fopen(path, "w");
364 	dassert_errno(fp, "Unable to create %s", path);
365 	fprintf(fp, "%s",
366 	    "; This Synth configuration file is automatically generated\n"
367 	    "; Take care when hand editing!\n"
368 	    "\n"
369 	    "[Global Configuration]\n"
370 	    "profile_selected= LiveSystem\n"
371 	    "\n"
372 	    "[LiveSystem]\n"
373 	    "Operating_system= DragonFly\n"
374 	    "Directory_packages= /build/synth/live_packages\n"
375 	    "Directory_repository= /build/synth/live_packages/All\n"
376 	    "Directory_portsdir= /build/synth/dports\n"
377 	    "Directory_options= /build/synth/options\n"
378 	    "Directory_distfiles= /build/synth/distfiles\n"
379 	    "Directory_buildbase= /build/synth/build\n"
380 	    "Directory_logs= /build/synth/logs\n"
381 	    "Directory_ccache= disabled\n"
382 	    "Directory_system= /\n"
383 	    "Package_suffix= .txz\n"
384 	    "Number_of_builders= 0\n"
385 	    "Max_jobs_per_builder= 0\n"
386 	    "Tmpfs_workdir= true\n"
387 	    "Tmpfs_localbase= true\n"
388 	    "Display_with_ncurses= true\n"
389 	    "leverage_prebuilt= false\n"
390 	    "\n");
391 	if (fclose(fp))
392 		dfatal_errno("Unable to write to %s\n", ConfigBase1);
393 	free(path);
394 
395 	asprintf(&path, "%s/LiveSystem-make.conf", ConfigBase1);
396 	fp = fopen(path, "w");
397 	dassert_errno(fp, "Unable to create %s", path);
398 	fprintf(fp, "%s",
399 	    "#\n"
400 	    "# Various dports options that might be of interest\n"
401 	    "#\n"
402 	    "#LICENSES_ACCEPTED=      NONE\n"
403 	    "#DISABLE_LICENSES=       yes\n"
404 	    "#DEFAULT_VERSIONS=       ssl=openssl\n"
405 	    "#FORCE_PACKAGE=          yes\n"
406 	    "#DPORTS_BUILDER=         yes\n"
407 	    "#\n"
408 	    "# Turn these on to generate debug binaries.  However, these\n"
409 	    "# options will seriously bloat memory use and storage use,\n"
410 	    "# do not use lightly\n"
411 	    "#\n"
412 	    "#STRIP=\n"
413 	    "#WITH_DEBUG=yes\n"
414 	);
415 	if (fclose(fp))
416 		dfatal_errno("Unable to write to %s\n", ConfigBase1);
417 	free(path);
418 }
419 
420 __dead2 static void
421 usage(int ecode)
422 {
423 	if (ecode == 2) {
424 		fprintf(stderr, "Run 'dsynth help' for usage\n");
425 		exit(1);
426 	}
427 
428 	fprintf(stderr,
429     "dsynth [options] directive\n"
430     "    -d                   - Debug verbosity (-dd disables ncurses)\n"
431     "    -h                   - Display this screen and exit\n"
432     "    -m gb                - Load management based on pkgdep memory\n"
433     "    -p profile           - Override profile selected in dsynth.ini\n"
434     "    -s n                 - Set initial DynamicMaxWorkers\n"
435     "    -v                   - Print version info and exit\n"
436     "    -y                   - Automatically answer yes to dsynth questions\n"
437     "    -D                   - Enable DEVELOPER mode\n"
438     "    -P                   - Include the check-plist stage\n"
439     "    -S                   - Disable ncurses\n"
440     "\n"
441     "    init                 - Initialize /etc/dsynth\n"
442     "    status               - Dry-run of 'upgrade-system'\n"
443     "    cleanup              - Clean-up mounts\n"
444     "    configure            - Bring up configuration menu\n"
445     "    upgrade-system       - Incremental build and upgrade using pkg list\n"
446     "                           from local system, then upgrade the local\n"
447     "                           system.\n"
448     "    prepare-system       - 'upgrade-system' but stops after building\n"
449     "    rebuild-repository   - Rebuild database files for current repository\n"
450     "    purge-distfiles      - Delete obsolete source distribution files\n"
451     "    status-everything    - Dry-run of 'everything'\n"
452     "    everything           - Build entire dports tree and repo database\n"
453     "				(-D everything infers -P)\n"
454     "    version              - Print version info and exit\n"
455     "    help                 - Display this screen and exit\n"
456     "    status     [ports]   - Dry-run of 'build' with given list\n"
457     "    build      [ports]   - Incrementally build dports based on the given\n"
458     "                           list, but asks before updating the repo\n"
459     "                           database and system\n"
460     "    just-build [ports]   - 'build' but skips post-build steps\n"
461     "    install    [ports]   - 'build' but upgrades system without asking\n"
462     "    force      [ports]   - 'build' but deletes existing packages first\n"
463     "    test       [ports]   - 'build' w/DEVELOPER=yes and pre-deletes pkgs\n"
464     "				(also infers -P)\n"
465     "    debug      [ports]   - like 'test' but leaves mounts intact\n"
466     "    monitor    [datfile] - Monitor a running dsynth\n"
467     "\n"
468     "    [ports] is a space-delimited list of origins, e.g. editors/joe.  It\n"
469     "            may also be a path to a file containing one origin per line.\n"
470 	);
471 
472 	exit(ecode);
473 }
474