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