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