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