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