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 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 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 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 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 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 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 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