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