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