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