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