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