1 /*- 2 * SPDX-License-Identifier: BSD-3-Clause 3 * 4 * Copyright (c) 1983, 1993 5 * The Regents of the University of California. All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. Neither the name of the University nor the names of its contributors 16 * may be used to endorse or promote products derived from this software 17 * without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 * SUCH DAMAGE. 30 */ 31 32 #if 0 33 #ifndef lint 34 static const char copyright[] = 35 "@(#) Copyright (c) 1983, 1993\n\ 36 The Regents of the University of California. All rights reserved.\n"; 37 #endif /* not lint */ 38 39 #endif 40 #include <sys/cdefs.h> 41 /* 42 * tunefs: change layout parameters to an existing file system. 43 */ 44 #include <sys/param.h> 45 #include <sys/mount.h> 46 #include <sys/disklabel.h> 47 #include <sys/stat.h> 48 49 #include <ufs/ufs/extattr.h> 50 #include <ufs/ufs/quota.h> 51 #include <ufs/ufs/ufsmount.h> 52 #include <ufs/ufs/dinode.h> 53 #include <ufs/ffs/fs.h> 54 #include <ufs/ufs/dir.h> 55 56 #include <ctype.h> 57 #include <err.h> 58 #include <fcntl.h> 59 #include <fstab.h> 60 #include <libufs.h> 61 #include <mntopts.h> 62 #include <paths.h> 63 #include <stdio.h> 64 #include <stdlib.h> 65 #include <stdint.h> 66 #include <string.h> 67 #include <time.h> 68 #include <unistd.h> 69 70 /* the optimization warning string template */ 71 #define OPTWARN "should optimize for %s with minfree %s %d%%" 72 73 static int blocks; 74 static char clrbuf[MAXBSIZE]; 75 static struct uufsd disk; 76 #define sblock disk.d_fs 77 78 static void usage(void) __dead2; 79 static void printfs(void); 80 static int journal_alloc(int64_t size); 81 static void journal_clear(void); 82 static void sbdirty(void); 83 84 int 85 main(int argc, char *argv[]) 86 { 87 const char *avalue, *jvalue, *Jvalue, *Lvalue, *lvalue, *Nvalue, *nvalue; 88 const char *tvalue; 89 const char *special; 90 const char *name; 91 char *diskname; 92 int Aflag, aflag, eflag, evalue, fflag, fvalue, jflag, Jflag, kflag; 93 int kvalue, Lflag, lflag, mflag, mvalue, Nflag, nflag, oflag, ovalue; 94 int pflag, sflag, svalue, Svalue, tflag; 95 int ch, found_arg, i; 96 const char *chg[2]; 97 98 if (argc < 3) 99 usage(); 100 Aflag = aflag = eflag = fflag = jflag = Jflag = kflag = Lflag = 0; 101 lflag = mflag = Nflag = nflag = oflag = pflag = sflag = tflag = 0; 102 avalue = jvalue = Jvalue = Lvalue = lvalue = Nvalue = nvalue = NULL; 103 evalue = fvalue = mvalue = ovalue = svalue = Svalue = 0; 104 found_arg = 0; /* At least one arg is required. */ 105 while ((ch = getopt(argc, argv, "Aa:e:f:j:J:k:L:l:m:N:n:o:ps:S:t:")) 106 != -1) 107 switch (ch) { 108 109 case 'A': 110 found_arg++; 111 Aflag++; 112 break; 113 114 case 'a': 115 found_arg++; 116 name = "POSIX.1e ACLs"; 117 avalue = optarg; 118 if (strcmp(avalue, "enable") && 119 strcmp(avalue, "disable")) { 120 errx(10, "bad %s (options are %s)", 121 name, "`enable' or `disable'"); 122 } 123 aflag = 1; 124 break; 125 126 case 'e': 127 found_arg++; 128 name = "maximum blocks per file in a cylinder group"; 129 evalue = atoi(optarg); 130 if (evalue < 1) 131 errx(10, "%s must be >= 1 (was %s)", 132 name, optarg); 133 eflag = 1; 134 break; 135 136 case 'f': 137 found_arg++; 138 name = "average file size"; 139 fvalue = atoi(optarg); 140 if (fvalue < 1) 141 errx(10, "%s must be >= 1 (was %s)", 142 name, optarg); 143 fflag = 1; 144 break; 145 146 case 'j': 147 found_arg++; 148 name = "softdep journaled file system"; 149 jvalue = optarg; 150 if (strcmp(jvalue, "enable") && 151 strcmp(jvalue, "disable")) { 152 errx(10, "bad %s (options are %s)", 153 name, "`enable' or `disable'"); 154 } 155 jflag = 1; 156 break; 157 158 case 'J': 159 found_arg++; 160 name = "gjournaled file system"; 161 Jvalue = optarg; 162 if (strcmp(Jvalue, "enable") && 163 strcmp(Jvalue, "disable")) { 164 errx(10, "bad %s (options are %s)", 165 name, "`enable' or `disable'"); 166 } 167 Jflag = 1; 168 break; 169 170 case 'k': 171 found_arg++; 172 name = "space to hold for metadata blocks"; 173 kvalue = atoi(optarg); 174 if (kvalue < 0) 175 errx(10, "bad %s (%s)", name, optarg); 176 kflag = 1; 177 break; 178 179 case 'L': 180 found_arg++; 181 name = "volume label"; 182 Lvalue = optarg; 183 i = -1; 184 while (isalnum(Lvalue[++i]) || Lvalue[i] == '_' || 185 Lvalue[i] == '-') 186 ; 187 if (Lvalue[i] != '\0') { 188 errx(10, "bad %s. Valid characters are " 189 "alphanumerics, dashes, and underscores.", 190 name); 191 } 192 if (strlen(Lvalue) >= MAXVOLLEN) { 193 errx(10, "bad %s. Length is longer than %d.", 194 name, MAXVOLLEN - 1); 195 } 196 Lflag = 1; 197 break; 198 199 case 'l': 200 found_arg++; 201 name = "multilabel MAC file system"; 202 lvalue = optarg; 203 if (strcmp(lvalue, "enable") && 204 strcmp(lvalue, "disable")) { 205 errx(10, "bad %s (options are %s)", 206 name, "`enable' or `disable'"); 207 } 208 lflag = 1; 209 break; 210 211 case 'm': 212 found_arg++; 213 name = "minimum percentage of free space"; 214 mvalue = atoi(optarg); 215 if (mvalue < 0 || mvalue > 99) 216 errx(10, "bad %s (%s)", name, optarg); 217 mflag = 1; 218 break; 219 220 case 'N': 221 found_arg++; 222 name = "NFSv4 ACLs"; 223 Nvalue = optarg; 224 if (strcmp(Nvalue, "enable") && 225 strcmp(Nvalue, "disable")) { 226 errx(10, "bad %s (options are %s)", 227 name, "`enable' or `disable'"); 228 } 229 Nflag = 1; 230 break; 231 232 case 'n': 233 found_arg++; 234 name = "soft updates"; 235 nvalue = optarg; 236 if (strcmp(nvalue, "enable") != 0 && 237 strcmp(nvalue, "disable") != 0) { 238 errx(10, "bad %s (options are %s)", 239 name, "`enable' or `disable'"); 240 } 241 nflag = 1; 242 break; 243 244 case 'o': 245 found_arg++; 246 name = "optimization preference"; 247 if (strcmp(optarg, "space") == 0) 248 ovalue = FS_OPTSPACE; 249 else if (strcmp(optarg, "time") == 0) 250 ovalue = FS_OPTTIME; 251 else 252 errx(10, 253 "bad %s (options are `space' or `time')", 254 name); 255 oflag = 1; 256 break; 257 258 case 'p': 259 found_arg++; 260 pflag = 1; 261 break; 262 263 case 's': 264 found_arg++; 265 name = "expected number of files per directory"; 266 svalue = atoi(optarg); 267 if (svalue < 1) 268 errx(10, "%s must be >= 1 (was %s)", 269 name, optarg); 270 sflag = 1; 271 break; 272 273 case 'S': 274 found_arg++; 275 name = "Softdep Journal Size"; 276 Svalue = atoi(optarg); 277 if (Svalue < SUJ_MIN) 278 errx(10, "%s must be >= %d (was %s)", 279 name, SUJ_MIN, optarg); 280 break; 281 282 case 't': 283 found_arg++; 284 name = "trim"; 285 tvalue = optarg; 286 if (strcmp(tvalue, "enable") != 0 && 287 strcmp(tvalue, "disable") != 0) { 288 errx(10, "bad %s (options are %s)", 289 name, "`enable' or `disable'"); 290 } 291 tflag = 1; 292 break; 293 294 default: 295 usage(); 296 } 297 argc -= optind; 298 argv += optind; 299 if (found_arg == 0 || argc != 1) 300 usage(); 301 302 special = argv[0]; 303 if (ufs_disk_fillout(&disk, special) == -1) 304 goto err; 305 /* 306 * Check for unclean filesystem. 307 */ 308 if ((sblock.fs_clean == 0 || 309 (sblock.fs_flags & (FS_UNCLEAN | FS_NEEDSFSCK)) != 0) && 310 (found_arg > 1 || !pflag)) 311 errx(1, "%s is not clean - run fsck.\n", special); 312 if (pflag) { 313 printfs(); 314 exit(0); 315 } 316 if (Lflag) { 317 name = "volume label"; 318 strncpy(sblock.fs_volname, Lvalue, MAXVOLLEN); 319 } 320 if (aflag) { 321 name = "POSIX.1e ACLs"; 322 if (strcmp(avalue, "enable") == 0) { 323 if (sblock.fs_flags & FS_ACLS) { 324 warnx("%s remains unchanged as enabled", name); 325 } else if (sblock.fs_flags & FS_NFS4ACLS) { 326 warnx("%s and NFSv4 ACLs are mutually " 327 "exclusive", name); 328 } else { 329 sblock.fs_flags |= FS_ACLS; 330 warnx("%s set", name); 331 } 332 } else if (strcmp(avalue, "disable") == 0) { 333 if ((~sblock.fs_flags & FS_ACLS) == 334 FS_ACLS) { 335 warnx("%s remains unchanged as disabled", 336 name); 337 } else { 338 sblock.fs_flags &= ~FS_ACLS; 339 warnx("%s cleared", name); 340 } 341 } 342 } 343 if (eflag) { 344 name = "maximum blocks per file in a cylinder group"; 345 if (sblock.fs_maxbpg == evalue) 346 warnx("%s remains unchanged as %d", name, evalue); 347 else { 348 warnx("%s changes from %d to %d", 349 name, sblock.fs_maxbpg, evalue); 350 sblock.fs_maxbpg = evalue; 351 } 352 } 353 if (fflag) { 354 name = "average file size"; 355 if (sblock.fs_avgfilesize == (unsigned)fvalue) { 356 warnx("%s remains unchanged as %d", name, fvalue); 357 } 358 else { 359 warnx("%s changes from %d to %d", 360 name, sblock.fs_avgfilesize, fvalue); 361 sblock.fs_avgfilesize = fvalue; 362 } 363 } 364 if (jflag) { 365 name = "soft updates journaling"; 366 if (strcmp(jvalue, "enable") == 0) { 367 if ((sblock.fs_flags & (FS_DOSOFTDEP | FS_SUJ)) == 368 (FS_DOSOFTDEP | FS_SUJ)) { 369 warnx("%s remains unchanged as enabled", name); 370 } else if (sblock.fs_clean == 0) { 371 warnx("%s cannot be enabled until fsck is run", 372 name); 373 } else if (journal_alloc(Svalue) != 0) { 374 warnx("%s cannot be enabled", name); 375 } else { 376 sblock.fs_flags |= FS_DOSOFTDEP | FS_SUJ; 377 warnx("%s set", name); 378 } 379 } else if (strcmp(jvalue, "disable") == 0) { 380 if ((~sblock.fs_flags & FS_SUJ) == FS_SUJ) { 381 warnx("%s remains unchanged as disabled", name); 382 } else { 383 journal_clear(); 384 sblock.fs_flags &= ~FS_SUJ; 385 sblock.fs_sujfree = 0; 386 warnx("%s cleared but soft updates still set.", 387 name); 388 389 warnx("remove .sujournal to reclaim space"); 390 } 391 } 392 } 393 if (Jflag) { 394 name = "gjournal"; 395 if (strcmp(Jvalue, "enable") == 0) { 396 if (sblock.fs_flags & FS_GJOURNAL) { 397 warnx("%s remains unchanged as enabled", name); 398 } else { 399 sblock.fs_flags |= FS_GJOURNAL; 400 warnx("%s set", name); 401 } 402 } else if (strcmp(Jvalue, "disable") == 0) { 403 if ((~sblock.fs_flags & FS_GJOURNAL) == 404 FS_GJOURNAL) { 405 warnx("%s remains unchanged as disabled", 406 name); 407 } else { 408 sblock.fs_flags &= ~FS_GJOURNAL; 409 warnx("%s cleared", name); 410 } 411 } 412 } 413 if (kflag) { 414 name = "space to hold for metadata blocks"; 415 if (sblock.fs_metaspace == kvalue) 416 warnx("%s remains unchanged as %d", name, kvalue); 417 else { 418 kvalue = blknum(&sblock, kvalue); 419 if (kvalue > sblock.fs_fpg / 2) { 420 kvalue = blknum(&sblock, sblock.fs_fpg / 2); 421 warnx("%s cannot exceed half the file system " 422 "space", name); 423 } 424 warnx("%s changes from %jd to %d", 425 name, sblock.fs_metaspace, kvalue); 426 sblock.fs_metaspace = kvalue; 427 } 428 } 429 if (lflag) { 430 name = "multilabel"; 431 if (strcmp(lvalue, "enable") == 0) { 432 if (sblock.fs_flags & FS_MULTILABEL) { 433 warnx("%s remains unchanged as enabled", name); 434 } else { 435 sblock.fs_flags |= FS_MULTILABEL; 436 warnx("%s set", name); 437 } 438 } else if (strcmp(lvalue, "disable") == 0) { 439 if ((~sblock.fs_flags & FS_MULTILABEL) == 440 FS_MULTILABEL) { 441 warnx("%s remains unchanged as disabled", 442 name); 443 } else { 444 sblock.fs_flags &= ~FS_MULTILABEL; 445 warnx("%s cleared", name); 446 } 447 } 448 } 449 if (mflag) { 450 name = "minimum percentage of free space"; 451 if (sblock.fs_minfree == mvalue) 452 warnx("%s remains unchanged as %d%%", name, mvalue); 453 else { 454 warnx("%s changes from %d%% to %d%%", 455 name, sblock.fs_minfree, mvalue); 456 sblock.fs_minfree = mvalue; 457 if (mvalue >= MINFREE && sblock.fs_optim == FS_OPTSPACE) 458 warnx(OPTWARN, "time", ">=", MINFREE); 459 if (mvalue < MINFREE && sblock.fs_optim == FS_OPTTIME) 460 warnx(OPTWARN, "space", "<", MINFREE); 461 } 462 } 463 if (Nflag) { 464 name = "NFSv4 ACLs"; 465 if (strcmp(Nvalue, "enable") == 0) { 466 if (sblock.fs_flags & FS_NFS4ACLS) { 467 warnx("%s remains unchanged as enabled", name); 468 } else if (sblock.fs_flags & FS_ACLS) { 469 warnx("%s and POSIX.1e ACLs are mutually " 470 "exclusive", name); 471 } else { 472 sblock.fs_flags |= FS_NFS4ACLS; 473 warnx("%s set", name); 474 } 475 } else if (strcmp(Nvalue, "disable") == 0) { 476 if ((~sblock.fs_flags & FS_NFS4ACLS) == 477 FS_NFS4ACLS) { 478 warnx("%s remains unchanged as disabled", 479 name); 480 } else { 481 sblock.fs_flags &= ~FS_NFS4ACLS; 482 warnx("%s cleared", name); 483 } 484 } 485 } 486 if (nflag) { 487 name = "soft updates"; 488 if (strcmp(nvalue, "enable") == 0) { 489 if (sblock.fs_flags & FS_DOSOFTDEP) 490 warnx("%s remains unchanged as enabled", name); 491 else if (sblock.fs_clean == 0) { 492 warnx("%s cannot be enabled until fsck is run", 493 name); 494 } else { 495 sblock.fs_flags |= FS_DOSOFTDEP; 496 warnx("%s set", name); 497 } 498 } else if (strcmp(nvalue, "disable") == 0) { 499 if ((~sblock.fs_flags & FS_DOSOFTDEP) == FS_DOSOFTDEP) 500 warnx("%s remains unchanged as disabled", name); 501 else { 502 sblock.fs_flags &= ~FS_DOSOFTDEP; 503 warnx("%s cleared", name); 504 } 505 } 506 } 507 if (oflag) { 508 name = "optimization preference"; 509 chg[FS_OPTSPACE] = "space"; 510 chg[FS_OPTTIME] = "time"; 511 if (sblock.fs_optim == ovalue) 512 warnx("%s remains unchanged as %s", name, chg[ovalue]); 513 else { 514 warnx("%s changes from %s to %s", 515 name, chg[sblock.fs_optim], chg[ovalue]); 516 sblock.fs_optim = ovalue; 517 if (sblock.fs_minfree >= MINFREE && 518 ovalue == FS_OPTSPACE) 519 warnx(OPTWARN, "time", ">=", MINFREE); 520 if (sblock.fs_minfree < MINFREE && ovalue == FS_OPTTIME) 521 warnx(OPTWARN, "space", "<", MINFREE); 522 } 523 } 524 if (sflag) { 525 name = "expected number of files per directory"; 526 if (sblock.fs_avgfpdir == (unsigned)svalue) { 527 warnx("%s remains unchanged as %d", name, svalue); 528 } 529 else { 530 warnx("%s changes from %d to %d", 531 name, sblock.fs_avgfpdir, svalue); 532 sblock.fs_avgfpdir = svalue; 533 } 534 } 535 if (tflag) { 536 name = "issue TRIM to the disk"; 537 if (strcmp(tvalue, "enable") == 0) { 538 if (sblock.fs_flags & FS_TRIM) 539 warnx("%s remains unchanged as enabled", name); 540 else { 541 sblock.fs_flags |= FS_TRIM; 542 warnx("%s set", name); 543 } 544 } else if (strcmp(tvalue, "disable") == 0) { 545 if ((~sblock.fs_flags & FS_TRIM) == FS_TRIM) 546 warnx("%s remains unchanged as disabled", name); 547 else { 548 sblock.fs_flags &= ~FS_TRIM; 549 warnx("%s cleared", name); 550 } 551 } 552 } 553 554 if (sbwrite(&disk, Aflag) == -1) 555 goto err; 556 diskname = strdup(disk.d_name); 557 ufs_disk_close(&disk); 558 chkdoreload(getmntpoint(diskname), warnx); 559 exit(0); 560 err: 561 if (disk.d_error != NULL) 562 errx(11, "%s: %s", special, disk.d_error); 563 else 564 err(12, "%s", special); 565 } 566 567 static void 568 sbdirty(void) 569 { 570 disk.d_fs.fs_flags |= FS_UNCLEAN | FS_NEEDSFSCK; 571 disk.d_fs.fs_clean = 0; 572 } 573 574 static ufs2_daddr_t 575 journal_balloc(void) 576 { 577 ufs2_daddr_t blk; 578 struct cg *cgp; 579 int valid; 580 static int contig = 1; 581 582 cgp = &disk.d_cg; 583 for (;;) { 584 blk = cgballoc(&disk); 585 if (blk > 0) 586 break; 587 /* 588 * If we failed to allocate a block from this cg, move to 589 * the next. 590 */ 591 if (cgwrite(&disk) < 0) { 592 warn("Failed to write updated cg"); 593 return (-1); 594 } 595 while ((valid = cgread(&disk)) == 1) { 596 /* 597 * Try to minimize fragmentation by requiring a minimum 598 * number of blocks present. 599 */ 600 if (cgp->cg_cs.cs_nbfree > 256 * 1024) 601 break; 602 if (contig == 0 && cgp->cg_cs.cs_nbfree) 603 break; 604 } 605 if (valid) 606 continue; 607 /* 608 * Try once through looking only for large contiguous regions 609 * and again taking any space we can find. 610 */ 611 if (contig) { 612 contig = 0; 613 disk.d_ccg = 0; 614 warnx("Journal file fragmented."); 615 continue; 616 } 617 warnx("Failed to find sufficient free blocks for the journal"); 618 return -1; 619 } 620 if (bwrite(&disk, fsbtodb(&sblock, blk), clrbuf, 621 sblock.fs_bsize) <= 0) { 622 warn("Failed to initialize new block"); 623 return -1; 624 } 625 return (blk); 626 } 627 628 /* 629 * Search a directory block for the SUJ_FILE. 630 */ 631 static ino_t 632 dir_search(ufs2_daddr_t blk, int bytes) 633 { 634 char block[MAXBSIZE]; 635 struct direct *dp; 636 int off; 637 638 if (bread(&disk, fsbtodb(&sblock, blk), block, bytes) <= 0) { 639 warn("Failed to read dir block"); 640 return (-1); 641 } 642 for (off = 0; off < bytes; off += dp->d_reclen) { 643 dp = (struct direct *)&block[off]; 644 if (dp->d_reclen == 0) 645 break; 646 if (dp->d_ino == 0) 647 continue; 648 if (dp->d_namlen != strlen(SUJ_FILE)) 649 continue; 650 if (bcmp(dp->d_name, SUJ_FILE, dp->d_namlen) != 0) 651 continue; 652 return (dp->d_ino); 653 } 654 655 return (0); 656 } 657 658 /* 659 * Search in the UFS_ROOTINO for the SUJ_FILE. If it exists we can not enable 660 * journaling. 661 */ 662 static ino_t 663 journal_findfile(void) 664 { 665 union dinodep dp; 666 ino_t ino; 667 int i; 668 669 if (getinode(&disk, &dp, UFS_ROOTINO) != 0) { 670 warn("Failed to get root inode: %s", disk.d_error); 671 return (-1); 672 } 673 if (sblock.fs_magic == FS_UFS1_MAGIC) { 674 if ((off_t)dp.dp1->di_size >= lblktosize(&sblock, UFS_NDADDR)) { 675 warnx("UFS_ROOTINO extends beyond direct blocks."); 676 return (-1); 677 } 678 for (i = 0; i < UFS_NDADDR; i++) { 679 if (dp.dp1->di_db[i] == 0) 680 break; 681 if ((ino = dir_search(dp.dp1->di_db[i], 682 sblksize(&sblock, (off_t)dp.dp1->di_size, i))) != 0) 683 return (ino); 684 } 685 } else { 686 if ((off_t)dp.dp2->di_size >= lblktosize(&sblock, UFS_NDADDR)) { 687 warnx("UFS_ROOTINO extends beyond direct blocks."); 688 return (-1); 689 } 690 for (i = 0; i < UFS_NDADDR; i++) { 691 if (dp.dp2->di_db[i] == 0) 692 break; 693 if ((ino = dir_search(dp.dp2->di_db[i], 694 sblksize(&sblock, (off_t)dp.dp2->di_size, i))) != 0) 695 return (ino); 696 } 697 } 698 699 return (0); 700 } 701 702 static void 703 dir_clear_block(const char *block, off_t off) 704 { 705 struct direct *dp; 706 707 for (; off < sblock.fs_bsize; off += DIRBLKSIZ) { 708 dp = (struct direct *)&block[off]; 709 dp->d_ino = 0; 710 dp->d_reclen = DIRBLKSIZ; 711 dp->d_type = DT_UNKNOWN; 712 } 713 } 714 715 /* 716 * Insert the journal at inode 'ino' into directory blk 'blk' at the first 717 * free offset of 'off'. DIRBLKSIZ blocks after off are initialized as 718 * empty. 719 */ 720 static int 721 dir_insert(ufs2_daddr_t blk, off_t off, ino_t ino) 722 { 723 struct direct *dp; 724 char block[MAXBSIZE]; 725 726 if (bread(&disk, fsbtodb(&sblock, blk), block, sblock.fs_bsize) <= 0) { 727 warn("Failed to read dir block"); 728 return (-1); 729 } 730 bzero(&block[off], sblock.fs_bsize - off); 731 dp = (struct direct *)&block[off]; 732 dp->d_ino = ino; 733 dp->d_reclen = DIRBLKSIZ; 734 dp->d_type = DT_REG; 735 dp->d_namlen = strlen(SUJ_FILE); 736 bcopy(SUJ_FILE, &dp->d_name, strlen(SUJ_FILE)); 737 dir_clear_block(block, off + DIRBLKSIZ); 738 if (bwrite(&disk, fsbtodb(&sblock, blk), block, sblock.fs_bsize) <= 0) { 739 warn("Failed to write dir block"); 740 return (-1); 741 } 742 return (0); 743 } 744 745 /* 746 * Extend a directory block in 'blk' by copying it to a full size block 747 * and inserting the new journal inode into .sujournal. 748 */ 749 static int 750 dir_extend(ufs2_daddr_t blk, ufs2_daddr_t nblk, off_t size, ino_t ino) 751 { 752 char block[MAXBSIZE]; 753 754 if (bread(&disk, fsbtodb(&sblock, blk), block, 755 roundup(size, sblock.fs_fsize)) <= 0) { 756 warn("Failed to read dir block"); 757 return (-1); 758 } 759 dir_clear_block(block, size); 760 if (bwrite(&disk, fsbtodb(&sblock, nblk), block, sblock.fs_bsize) 761 <= 0) { 762 warn("Failed to write dir block"); 763 return (-1); 764 } 765 766 return (dir_insert(nblk, size, ino)); 767 } 768 769 /* 770 * Insert the journal file into the UFS_ROOTINO directory. We always extend the 771 * last frag 772 */ 773 static int 774 journal_insertfile(ino_t ino) 775 { 776 union dinodep dp; 777 ufs2_daddr_t nblk; 778 ufs2_daddr_t blk; 779 ufs_lbn_t lbn; 780 int size; 781 int off; 782 783 if (getinode(&disk, &dp, UFS_ROOTINO) != 0) { 784 warn("Failed to get root inode: %s", disk.d_error); 785 sbdirty(); 786 return (-1); 787 } 788 blk = 0; 789 size = 0; 790 nblk = journal_balloc(); 791 if (nblk <= 0) 792 return (-1); 793 /* 794 * For simplicity sake we aways extend the UFS_ROOTINO into a new 795 * directory block rather than searching for space and inserting 796 * into an existing block. However, if the rootino has frags 797 * have to free them and extend the block. 798 */ 799 if (sblock.fs_magic == FS_UFS1_MAGIC) { 800 lbn = lblkno(&sblock, dp.dp1->di_size); 801 off = blkoff(&sblock, dp.dp1->di_size); 802 blk = dp.dp1->di_db[lbn]; 803 size = sblksize(&sblock, (off_t)dp.dp1->di_size, lbn); 804 } else { 805 lbn = lblkno(&sblock, dp.dp2->di_size); 806 off = blkoff(&sblock, dp.dp2->di_size); 807 blk = dp.dp2->di_db[lbn]; 808 size = sblksize(&sblock, (off_t)dp.dp2->di_size, lbn); 809 } 810 if (off != 0) { 811 if (dir_extend(blk, nblk, off, ino) == -1) 812 return (-1); 813 } else { 814 blk = 0; 815 if (dir_insert(nblk, 0, ino) == -1) 816 return (-1); 817 } 818 if (sblock.fs_magic == FS_UFS1_MAGIC) { 819 dp.dp1->di_blocks += (sblock.fs_bsize - size) / DEV_BSIZE; 820 dp.dp1->di_db[lbn] = nblk; 821 dp.dp1->di_size = lblktosize(&sblock, lbn+1); 822 } else { 823 dp.dp2->di_blocks += (sblock.fs_bsize - size) / DEV_BSIZE; 824 dp.dp2->di_db[lbn] = nblk; 825 dp.dp2->di_size = lblktosize(&sblock, lbn+1); 826 } 827 if (putinode(&disk) < 0) { 828 warn("Failed to write root inode: %s", disk.d_error); 829 return (-1); 830 } 831 if (cgwrite(&disk) < 0) { 832 warn("Failed to write updated cg"); 833 sbdirty(); 834 return (-1); 835 } 836 if (blk) { 837 if (cgbfree(&disk, blk, size) < 0) { 838 warn("Failed to write cg"); 839 return (-1); 840 } 841 } 842 843 return (0); 844 } 845 846 static int 847 indir_fill(ufs2_daddr_t blk, int level, int *resid) 848 { 849 char indirbuf[MAXBSIZE]; 850 ufs1_daddr_t *bap1; 851 ufs2_daddr_t *bap2; 852 ufs2_daddr_t nblk; 853 int ncnt; 854 int cnt; 855 int i; 856 857 bzero(indirbuf, sizeof(indirbuf)); 858 bap1 = (ufs1_daddr_t *)indirbuf; 859 bap2 = (void *)bap1; 860 cnt = 0; 861 for (i = 0; i < NINDIR(&sblock) && *resid != 0; i++) { 862 nblk = journal_balloc(); 863 if (nblk <= 0) 864 return (-1); 865 cnt++; 866 if (sblock.fs_magic == FS_UFS1_MAGIC) 867 *bap1++ = nblk; 868 else 869 *bap2++ = nblk; 870 if (level != 0) { 871 ncnt = indir_fill(nblk, level - 1, resid); 872 if (ncnt <= 0) 873 return (-1); 874 cnt += ncnt; 875 } else 876 (*resid)--; 877 } 878 if (bwrite(&disk, fsbtodb(&sblock, blk), indirbuf, 879 sblock.fs_bsize) <= 0) { 880 warn("Failed to write indirect"); 881 return (-1); 882 } 883 return (cnt); 884 } 885 886 /* 887 * Clear the flag bits so the journal can be removed. 888 */ 889 static void 890 journal_clear(void) 891 { 892 union dinodep dp; 893 ino_t ino; 894 895 ino = journal_findfile(); 896 if (ino == (ino_t)-1 || ino == 0) { 897 warnx("Journal file does not exist"); 898 return; 899 } 900 printf("Clearing journal flags from inode %ju\n", (uintmax_t)ino); 901 if (getinode(&disk, &dp, ino) != 0) { 902 warn("Failed to get journal inode: %s", disk.d_error); 903 return; 904 } 905 if (sblock.fs_magic == FS_UFS1_MAGIC) 906 dp.dp1->di_flags = 0; 907 else 908 dp.dp2->di_flags = 0; 909 if (putinode(&disk) < 0) { 910 warn("Failed to write journal inode: %s", disk.d_error); 911 return; 912 } 913 } 914 915 static int 916 journal_alloc(int64_t size) 917 { 918 union dinodep dp; 919 ufs2_daddr_t blk; 920 struct cg *cgp; 921 int resid; 922 ino_t ino; 923 int blks; 924 time_t utime; 925 int i; 926 927 cgp = &disk.d_cg; 928 ino = 0; 929 930 /* 931 * If the journal file exists we can't allocate it. 932 */ 933 ino = journal_findfile(); 934 if (ino == (ino_t)-1) { 935 warnx("journal_findfile() failed."); 936 return (-1); 937 } 938 if (ino > 0) { 939 warnx("Journal file %s already exists, please remove.", 940 SUJ_FILE); 941 return (-1); 942 } 943 /* 944 * If the user didn't supply a size pick one based on the filesystem 945 * size constrained with hardcoded MIN and MAX values. We opt for 946 * 1/1024th of the filesystem up to MAX but not exceeding one CG and 947 * not less than the MIN. 948 */ 949 if (size == 0) { 950 size = (sblock.fs_size * sblock.fs_bsize) / 1024; 951 if (size / sblock.fs_fsize > sblock.fs_fpg) 952 size = sblock.fs_fpg * sblock.fs_fsize; 953 size = MAX(SUJ_MIN, size); 954 } 955 /* fsck does not support fragments in journal files. */ 956 size = roundup(size, sblock.fs_bsize); 957 resid = blocks = size / sblock.fs_bsize; 958 if (sblock.fs_cstotal.cs_nbfree < blocks) { 959 warn("Insufficient free space for %jd byte journal", size); 960 return (-1); 961 } 962 /* 963 * Find a cg with enough blocks to satisfy the journal 964 * size. Presently the journal does not span cgs. 965 */ 966 while (cgread(&disk) == 1) { 967 if (cgp->cg_cs.cs_nifree == 0) 968 continue; 969 ino = cgialloc(&disk); 970 if (ino <= 0) 971 break; 972 printf("Using inode %ju in cg %d for %jd byte journal\n", 973 (uintmax_t)ino, cgp->cg_cgx, size); 974 if (getinode(&disk, &dp, ino) != 0) { 975 warn("Failed to get allocated inode: %s", disk.d_error); 976 sbdirty(); 977 goto out; 978 } 979 /* 980 * We leave fields unrelated to the number of allocated 981 * blocks and size uninitialized. This causes legacy 982 * fsck implementations to clear the inode. 983 */ 984 time(&utime); 985 if (sblock.fs_magic == FS_UFS1_MAGIC) { 986 bzero(dp.dp1, sizeof(*dp.dp1)); 987 dp.dp1->di_size = size; 988 dp.dp1->di_mode = IFREG | IREAD; 989 dp.dp1->di_nlink = 1; 990 dp.dp1->di_flags = 991 SF_IMMUTABLE | SF_NOUNLINK | UF_NODUMP; 992 dp.dp1->di_atime = utime; 993 dp.dp1->di_mtime = utime; 994 dp.dp1->di_ctime = utime; 995 } else { 996 bzero(dp.dp2, sizeof(*dp.dp2)); 997 dp.dp2->di_size = size; 998 dp.dp2->di_mode = IFREG | IREAD; 999 dp.dp2->di_nlink = 1; 1000 dp.dp2->di_flags = 1001 SF_IMMUTABLE | SF_NOUNLINK | UF_NODUMP; 1002 dp.dp2->di_atime = utime; 1003 dp.dp2->di_mtime = utime; 1004 dp.dp2->di_ctime = utime; 1005 dp.dp2->di_birthtime = utime; 1006 } 1007 for (i = 0; i < UFS_NDADDR && resid; i++, resid--) { 1008 blk = journal_balloc(); 1009 if (blk <= 0) 1010 goto out; 1011 if (sblock.fs_magic == FS_UFS1_MAGIC) { 1012 dp.dp1->di_db[i] = blk; 1013 dp.dp1->di_blocks++; 1014 } else { 1015 dp.dp2->di_db[i] = blk; 1016 dp.dp2->di_blocks++; 1017 } 1018 } 1019 for (i = 0; i < UFS_NIADDR && resid; i++) { 1020 blk = journal_balloc(); 1021 if (blk <= 0) 1022 goto out; 1023 blks = indir_fill(blk, i, &resid) + 1; 1024 if (blks <= 0) { 1025 sbdirty(); 1026 goto out; 1027 } 1028 if (sblock.fs_magic == FS_UFS1_MAGIC) { 1029 dp.dp1->di_ib[i] = blk; 1030 dp.dp1->di_blocks += blks; 1031 } else { 1032 dp.dp2->di_ib[i] = blk; 1033 dp.dp2->di_blocks += blks; 1034 } 1035 } 1036 if (sblock.fs_magic == FS_UFS1_MAGIC) 1037 dp.dp1->di_blocks *= sblock.fs_bsize / disk.d_bsize; 1038 else 1039 dp.dp2->di_blocks *= sblock.fs_bsize / disk.d_bsize; 1040 if (putinode(&disk) < 0) { 1041 warn("Failed to write allocated inode: %s", 1042 disk.d_error); 1043 sbdirty(); 1044 return (-1); 1045 } 1046 if (cgwrite(&disk) < 0) { 1047 warn("Failed to write updated cg"); 1048 sbdirty(); 1049 return (-1); 1050 } 1051 if (journal_insertfile(ino) < 0) { 1052 sbdirty(); 1053 return (-1); 1054 } 1055 sblock.fs_sujfree = 0; 1056 return (0); 1057 } 1058 warnx("Insufficient free space for the journal."); 1059 out: 1060 return (-1); 1061 } 1062 1063 static void 1064 usage(void) 1065 { 1066 fprintf(stderr, "%s\n%s\n%s\n%s\n%s\n%s\n", 1067 "usage: tunefs [-A] [-a enable | disable] [-e maxbpg] [-f avgfilesize]", 1068 " [-J enable | disable] [-j enable | disable] [-k metaspace]", 1069 " [-L volname] [-l enable | disable] [-m minfree]", 1070 " [-N enable | disable] [-n enable | disable]", 1071 " [-o space | time] [-p] [-s avgfpdir] [-t enable | disable]", 1072 " special | filesystem"); 1073 exit(2); 1074 } 1075 1076 static void 1077 printfs(void) 1078 { 1079 warnx("POSIX.1e ACLs: (-a) %s", 1080 (sblock.fs_flags & FS_ACLS)? "enabled" : "disabled"); 1081 warnx("NFSv4 ACLs: (-N) %s", 1082 (sblock.fs_flags & FS_NFS4ACLS)? "enabled" : "disabled"); 1083 warnx("MAC multilabel: (-l) %s", 1084 (sblock.fs_flags & FS_MULTILABEL)? "enabled" : "disabled"); 1085 warnx("soft updates: (-n) %s", 1086 (sblock.fs_flags & FS_DOSOFTDEP)? "enabled" : "disabled"); 1087 warnx("soft update journaling: (-j) %s", 1088 (sblock.fs_flags & FS_SUJ)? "enabled" : "disabled"); 1089 warnx("gjournal: (-J) %s", 1090 (sblock.fs_flags & FS_GJOURNAL)? "enabled" : "disabled"); 1091 warnx("trim: (-t) %s", 1092 (sblock.fs_flags & FS_TRIM)? "enabled" : "disabled"); 1093 warnx("maximum blocks per file in a cylinder group: (-e) %d", 1094 sblock.fs_maxbpg); 1095 warnx("average file size: (-f) %d", 1096 sblock.fs_avgfilesize); 1097 warnx("average number of files in a directory: (-s) %d", 1098 sblock.fs_avgfpdir); 1099 warnx("minimum percentage of free space: (-m) %d%%", 1100 sblock.fs_minfree); 1101 warnx("space to hold for metadata blocks: (-k) %jd", 1102 sblock.fs_metaspace); 1103 warnx("optimization preference: (-o) %s", 1104 sblock.fs_optim == FS_OPTSPACE ? "space" : "time"); 1105 if (sblock.fs_minfree >= MINFREE && 1106 sblock.fs_optim == FS_OPTSPACE) 1107 warnx(OPTWARN, "time", ">=", MINFREE); 1108 if (sblock.fs_minfree < MINFREE && 1109 sblock.fs_optim == FS_OPTTIME) 1110 warnx(OPTWARN, "space", "<", MINFREE); 1111 warnx("volume label: (-L) %s", 1112 sblock.fs_volname); 1113 } 1114