xref: /dflybsd-src/usr.bin/dsynth/mount.c (revision 2b0cb81791f7cbae1a99aff6d697787b0372d28e)
1 /*
2  * Copyright (c) 2019-2022 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 static void domount(worker_t *work, int type,
40 			const char *spath, const char *dpath,
41 			const char *discretefmt);
42 static void dounmount(worker_t *work, const char *rpath);
43 static void makeDiscreteCopies(const char *spath, const char *discretefmt);
44 
45 /*
46  * Called by the frontend to create a template which will be cpdup'd
47  * into fresh workers.
48  *
49  * Template must have been previously destroyed.  Errors are fatal
50  */
51 int
DoCreateTemplate(int force)52 DoCreateTemplate(int force)
53 {
54 	struct stat st;
55 	char *goodbuf;
56 	char *buf;
57 	const char *reason = "";
58 	int rc;
59 	int fd;
60 	int n;
61 
62 	rc = 0;
63 	asprintf(&goodbuf, "%s/.template.good", BuildBase);
64 
65 	/*
66 	 * Conditionally create the template and discrete copies of certain
67 	 * directories if we think we are missing things.
68 	 */
69 	if (force == 1)
70 		reason = " (Asked to force template creation)";
71 	if (force == 0) {
72 		time_t ls_mtime;
73 
74 		asprintf(&buf, "%s/Template", BuildBase);
75 		if (stat(buf, &st) < 0) {
76 			force = 1;
77 			reason = " (Template file missing)";
78 		}
79 		free(buf);
80 
81 		/*
82 		 * Check to see if the worker count changed and some
83 		 * template dirs are missing or out of date, and also if
84 		 * a new world was installed (via /bin/ls mtime).
85 		 */
86 		asprintf(&buf, "%s/bin/ls", SystemPath);
87 		if (stat(buf, &st) < 0)
88 			dfatal_errno("Unable to locate %s", buf);
89 		free(buf);
90 		ls_mtime = st.st_mtime;
91 
92 		for (n = 0; n < MaxWorkers; ++n) {
93 			asprintf(&buf, "%s/bin.%03d/ls", BuildBase, n);
94 			if (stat(buf, &st) < 0 || st.st_mtime != ls_mtime) {
95 				force = 1;
96 				reason = " (/bin/ls mtime mismatch)";
97 			}
98 			free(buf);
99 		}
100 
101 		if (stat(goodbuf, &st) < 0) {
102 			force = 1;
103 			reason = " (.template.good file missing)";
104 		}
105 	}
106 
107 	dlog(DLOG_ALL, "Check Template: %s%s\n",
108 	     (force ? "Must-Create" : "Good"),
109 	     reason);
110 
111 	/*
112 	 * Create the template
113 	 */
114 	if (force) {
115 		remove(goodbuf);	/* ignore exit code */
116 
117 		rc = 0;
118 		asprintf(&buf, "%s/mktemplate %s %s/Template",
119 			 SCRIPTPATH(SCRIPTDIR), SystemPath, BuildBase);
120 		rc = system(buf);
121 		if (rc)
122 			dfatal("Command failed: %s\n", buf);
123 		dlog(DLOG_ALL | DLOG_FILTER,
124 		     "Template - rc=%d running %s\n", rc, buf);
125 		free(buf);
126 
127 		/*
128 		 * Make discrete copies of certain extremely heavily used
129 		 * but small directories.
130 		 */
131 		makeDiscreteCopies("$/bin", "/bin.%03d");
132 		makeDiscreteCopies("$/lib", "/lib.%03d");
133 		makeDiscreteCopies("$/libexec", "/libexec.%03d");
134 		makeDiscreteCopies("$/usr/bin", "/usr.bin.%03d");
135 
136 		/*
137 		 * Mark the template good... ah, do a sync() to really
138 		 * be sure that it can't get corrupted.
139 		 */
140 		sync();
141 		fd = open(goodbuf, O_RDWR|O_CREAT|O_TRUNC, 0644);
142 		dassert_errno(fd >= 0, "could not create %s", goodbuf);
143 		close(fd);
144 
145 		dlog(DLOG_ALL | DLOG_FILTER, "Template - done\n");
146 	}
147 	free(goodbuf);
148 
149 	return force;
150 }
151 
152 void
DoDestroyTemplate(void)153 DoDestroyTemplate(void)
154 {
155 	struct stat st;
156 	char *path;
157 	char *buf;
158 	int rc;
159 
160 	/*
161 	 * NOTE: rm -rf safety, use a fixed name 'Template' to ensure we
162 	 *	 do not accidently blow something up.
163 	 */
164 	asprintf(&path, "%s/Template", BuildBase);
165 	if (stat(path, &st) == 0) {
166 		asprintf(&buf, "chflags -R noschg %s; /bin/rm -rf %s",
167 			 path, path);
168 		rc = system(buf);
169 		if (rc)
170 			dfatal("Command failed: %s (ignored)\n", buf);
171 		free(buf);
172 	}
173 	free(path);
174 }
175 
176 /*
177  * Called by the worker support thread to install a new worker
178  * filesystem topology.
179  */
180 void
DoWorkerMounts(worker_t * work)181 DoWorkerMounts(worker_t *work)
182 {
183 	char *buf;
184 	int rc;
185 
186 	/*
187 	 * Generate required mounts, domount() will mkdir() the target
188 	 * directory if necessary and prefix spath with SystemPath if
189 	 * it starts with $/
190 	 */
191 	setNumaDomain(work->index);
192 	domount(work, TMPFS_RW, "dummy", "", NULL);
193 	asprintf(&buf, "%s/usr", work->basedir);
194 	if (mkdir(buf, 0755) != 0) {
195 		fprintf(stderr, "Command failed: mkdir %s\n", buf);
196 		++work->mount_error;
197 	}
198 	free(buf);
199 	asprintf(&buf, "%s/usr/packages", work->basedir);
200 	if (mkdir(buf, 0755) != 0) {
201 		fprintf(stderr, "Command failed: mkdir %s\n", buf);
202 		++work->mount_error;
203 	}
204 	free(buf);
205 
206 	domount(work, TMPFS_RW,  "dummy", "/boot", NULL);
207 
208 	asprintf(&buf, "%s/boot/modules.local", work->basedir);
209 	if (mkdir(buf, 0755) != 0) {
210 		fprintf(stderr, "Command failed: mkdir %s\n", buf);
211 		++work->mount_error;
212 	}
213 	free(buf);
214 
215 	domount(work, DEVFS_RW,  "dummy", "/dev", NULL);
216 	domount(work, PROCFS_RO, "dummy", "/proc", NULL);
217 	domount(work, NULLFS_RO, "$/bin", "/bin", "/bin.%03d");
218 	domount(work, NULLFS_RO, "$/sbin", "/sbin", NULL);
219 	domount(work, NULLFS_RO, "$/lib", "/lib", "/lib.%03d");
220 	domount(work, NULLFS_RO, "$/libexec", "/libexec", "/libexec.%03d");
221 	domount(work, NULLFS_RO, "$/usr/bin", "/usr/bin", "/usr.bin.%03d");
222 	domount(work, NULLFS_RO, "$/usr/include", "/usr/include", NULL);
223 	domount(work, NULLFS_RO, "$/usr/lib", "/usr/lib", NULL);
224 	domount(work, NULLFS_RO, "$/usr/libdata", "/usr/libdata", NULL);
225 	domount(work, NULLFS_RO, "$/usr/libexec", "/usr/libexec", NULL);
226 	domount(work, NULLFS_RO, "$/usr/sbin", "/usr/sbin", NULL);
227 	domount(work, NULLFS_RO, "$/usr/share", "/usr/share", NULL);
228 	domount(work, TMPFS_RW_MED,  "dummy", "/usr/local", NULL);
229 	domount(work, NULLFS_RO, "$/usr/games", "/usr/games", NULL);
230 	if (UseUsrSrc)
231 		domount(work, NULLFS_RO, "$/usr/src", "/usr/src", NULL);
232 	domount(work, NULLFS_RO, DPortsPath, "/xports", NULL);
233 	domount(work, NULLFS_RW, OptionsPath, "/options", NULL);
234 	domount(work, NULLFS_RW, PackagesPath, "/packages", NULL);
235 	domount(work, NULLFS_RW, DistFilesPath, "/distfiles", NULL);
236 	domount(work, TMPFS_RW_BIG, "dummy", "/construction", NULL);
237 	if (UseCCache)
238 		domount(work, NULLFS_RW, CCachePath, "/ccache", NULL);
239 
240 	/*
241 	 * NOTE: Uses blah/. to prevent cp from creating 'Template' under
242 	 *	 work->basedir.  We want to start with the content.
243 	 */
244 	asprintf(&buf, "cp -Rp %s/Template/. %s", BuildBase, work->basedir);
245 	rc = system(buf);
246 	if (rc) {
247 		fprintf(stderr, "Command failed: %s\n", buf);
248 		++work->accum_error;
249 		snprintf(work->status, sizeof(work->status),
250 			 "Template copy failed");
251 	}
252 	free(buf);
253 	setNumaDomain(-1);
254 }
255 
256 /*
257  * Called by the worker support thread to remove a worker
258  * filesystem topology.
259  *
260  * NOTE: No need to conditionalize UseUsrSrc, it doesn't hurt to
261  *	 issue the umount() if it isn't mounted and it ensures that
262  *	 everything is unmounted properly on cleanup if the state
263  *	 changes.
264  */
265 void
DoWorkerUnmounts(worker_t * work)266 DoWorkerUnmounts(worker_t *work)
267 {
268 	int retries;
269 
270 	setNumaDomain(work->index);
271 	work->mount_error = 0;
272 	for (retries = 0; retries < 10; ++retries) {
273 		dounmount(work, "/proc");
274 		dounmount(work, "/dev");
275 		dounmount(work, "/usr/src");
276 		dounmount(work, "/usr/games");
277 		dounmount(work, "/boot");
278 		dounmount(work, "/usr/local");
279 		dounmount(work, "/construction");
280 		dounmount(work, "/ccache");	/* in case of config change */
281 		dounmount(work, "/distfiles");
282 		dounmount(work, "/packages");
283 		dounmount(work, "/options");
284 		dounmount(work, "/xports");
285 		dounmount(work, "/usr/share");
286 		dounmount(work, "/usr/sbin");
287 		dounmount(work, "/usr/libexec");
288 		dounmount(work, "/usr/libdata");
289 		dounmount(work, "/usr/lib");
290 		dounmount(work, "/usr/include");
291 		dounmount(work, "/usr/bin");
292 		dounmount(work, "/libexec");
293 		dounmount(work, "/lib");
294 		dounmount(work, "/sbin");
295 		dounmount(work, "/bin");
296 		dounmount(work, "");
297 		if (work->mount_error == 0)
298 			break;
299 		sleep(5);
300 		work->mount_error = 0;
301 	}
302 	if (work->mount_error) {
303 		++work->accum_error;
304 		snprintf(work->status, sizeof(work->status),
305 			 "Unable to unmount slot");
306 	}
307 	setNumaDomain(-1);
308 }
309 
310 static
311 void
domount(worker_t * work,int type,const char * spath,const char * dpath,const char * discretefmt)312 domount(worker_t *work, int type, const char *spath, const char *dpath,
313 	const char *discretefmt)
314 {
315 	const char *sbase;
316 	const char *rwstr;
317 	const char *optstr;
318 	const char *typestr;
319 	const char *debug;
320 	struct stat st;
321 	char *buf;
322 	char *tmp;
323 	int rc;
324 
325 	/*
326 	 * Make target directory if necessary.  This must occur in-order
327 	 * since directories may have to be created under prior mounts
328 	 * in the sequence.
329 	 */
330 	asprintf(&buf, "%s%s", work->basedir, dpath);
331 	if (stat(buf, &st) != 0) {
332 		if (mkdir(buf, 0755) != 0) {
333 			fprintf(stderr, "Command failed: mkdir %s\n", buf);
334 			++work->mount_error;
335 		}
336 	}
337 	free(buf);
338 
339 	/*
340 	 * Setup for mount arguments
341 	 */
342 	rwstr = (type & MOUNT_TYPE_RW) ? "rw" : "ro";
343 	optstr = "";
344 	typestr = "";
345 
346 	switch(type & MOUNT_TYPE_MASK) {
347 	case MOUNT_TYPE_TMPFS:
348 		/*
349 		 * When creating a tmpfs filesystem, make sure the big ones
350 		 * requested are big enough for the worst-case dport (which
351 		 * is usually chromium).  If debugging is turned on, its even
352 		 * worse.  You'd better have enough swap!
353 		 */
354 		debug = getbuildenv("WITH_DEBUG");
355 		typestr = "tmpfs";
356 		if (type & MOUNT_TYPE_BIG)
357 			optstr = debug ? " -o size=128g" : " -o size=64g";
358 		else if (type & MOUNT_TYPE_MED)
359 			optstr = debug ? " -o size=32g" : " -o size=16g";
360 		else
361 			optstr = " -o size=16g";
362 		break;
363 	case MOUNT_TYPE_NULLFS:
364 #if defined(__DragonFly__)
365 		typestr = "null";
366 #else
367 		typestr = "nullfs";
368 #endif
369 		break;
370 	case MOUNT_TYPE_DEVFS:
371 		typestr = "devfs";
372 		break;
373 	case MOUNT_TYPE_PROCFS:
374 		typestr = "procfs";
375 		break;
376 	default:
377 		dfatal("Illegal mount type: %08x", type);
378 		/* NOT REACHED */
379 		break;
380 	}
381 
382 	/*
383 	 * Prefix spath
384 	 */
385 	if (discretefmt) {
386 		sbase = BuildBase;
387 		asprintf(&tmp, discretefmt, work->index);
388 		spath = tmp;
389 	} else {
390 		if (spath[0] == '$') {
391 			++spath;
392 			sbase = SystemPath;
393 			if (strcmp(sbase, "/") == 0)
394 				++sbase;
395 		} else {
396 			sbase = "";
397 		}
398 		tmp = NULL;
399 	}
400 	asprintf(&buf, "%s%s -t %s -o %s %s%s %s%s",
401 		MOUNT_BINARY, optstr, typestr, rwstr,
402 		sbase, spath, work->basedir, dpath);
403 	rc = system(buf);
404 	if (rc) {
405 		fprintf(stderr, "Command failed: %s\n", buf);
406 		++work->mount_error;
407 	}
408 	free(buf);
409 	if (tmp)
410 		free(tmp);
411 }
412 
413 static
414 void
dounmount(worker_t * work,const char * rpath)415 dounmount(worker_t *work, const char *rpath)
416 {
417 	char *buf;
418 
419 	asprintf(&buf, "%s%s", work->basedir, rpath);
420 	if (unmount(buf, 0) < 0) {
421 		switch(errno) {
422 		case EPERM:	/* This is probably fatal later on in mount */
423 		case ENOENT:	/* Expected if mount already gone */
424 		case EINVAL:	/* Expected if mount already gone (maybe) */
425 			break;
426 		default:
427 			fprintf(stderr, "Cannot umount %s (%s)\n",
428 				buf, strerror(errno));
429 			++work->mount_error;
430 			break;
431 		}
432 	}
433 	free(buf);
434 }
435 
436 static
437 void
makeDiscreteCopies(const char * spath,const char * discretefmt)438 makeDiscreteCopies(const char *spath, const char *discretefmt)
439 {
440 	char *src;
441 	char *dst;
442 	char *buf;
443 	struct stat st;
444 	int i;
445 	int rc;
446 
447 	for (i = 0; i < MaxWorkers; ++i) {
448 		setNumaDomain(i);
449 		if (spath[0] == '$') {
450 			if (strcmp(SystemPath, "/") == 0)
451 				asprintf(&src, "%s%s",
452 					 SystemPath + 1, spath + 1);
453 			else
454 				asprintf(&src, "%s%s",
455 					 SystemPath, spath + 1);
456 		} else {
457 			src = strdup(spath);
458 		}
459 		asprintf(&buf, discretefmt, i);
460 		asprintf(&dst, "%s%s", BuildBase, buf);
461 		free(buf);
462 
463 		if (stat(dst, &st) < 0) {
464 			if (mkdir(dst, 0555) < 0) {
465 				dlog(DLOG_ALL, "Template - mkdir %s failed\n",
466 				     dst);
467 				dfatal_errno("Cannot mkdir %s:", dst);
468 			}
469 		}
470 		asprintf(&buf, "chflags -R noschg %s; "
471 			       "rm -rf %s; "
472 			       "cp -Rp %s/. %s",
473 			       dst, dst, src, dst);
474 		rc = system(buf);
475 		dlog(DLOG_ALL | DLOG_FILTER,
476 		     "Template - rc=%d running %s\n", rc, buf);
477 		if (rc)
478 			dfatal("Command failed: %s", buf);
479 		free(buf);
480 		free(src);
481 		free(dst);
482 		setNumaDomain(-1);
483 	}
484 }
485