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