xref: /freebsd/sbin/tunefs/tunefs.c (revision 8aac90f18aef7c9eea906c3ff9a001ca7b94f375)
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