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