xref: /freebsd/sbin/tunefs/tunefs.c (revision bc6940585f5f394c8c3fcff58c764a40f7294d4b)
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 		if (statfs(special, &stfs) != 0)
284 			warn("Can't stat %s", special);
285 		if (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_SUJ;
362 				sblock.fs_sujfree = 0;
363  				warnx("%s cleared but soft updates still set.",
364 				    name);
365 
366 				warnx("remove .sujournal to reclaim space");
367 			}
368  		}
369 	}
370 	if (Jflag) {
371 		name = "gjournal";
372 		if (strcmp(Jvalue, "enable") == 0) {
373 			if (sblock.fs_flags & FS_GJOURNAL) {
374 				warnx("%s remains unchanged as enabled", name);
375 			} else {
376 				sblock.fs_flags |= FS_GJOURNAL;
377 				warnx("%s set", name);
378 			}
379 		} else if (strcmp(Jvalue, "disable") == 0) {
380 			if ((~sblock.fs_flags & FS_GJOURNAL) ==
381 			    FS_GJOURNAL) {
382 				warnx("%s remains unchanged as disabled",
383 				    name);
384 			} else {
385 				sblock.fs_flags &= ~FS_GJOURNAL;
386 				warnx("%s cleared", name);
387 			}
388 		}
389 	}
390 	if (lflag) {
391 		name = "multilabel";
392 		if (strcmp(lvalue, "enable") == 0) {
393 			if (sblock.fs_flags & FS_MULTILABEL) {
394 				warnx("%s remains unchanged as enabled", name);
395 			} else {
396 				sblock.fs_flags |= FS_MULTILABEL;
397 				warnx("%s set", name);
398 			}
399 		} else if (strcmp(lvalue, "disable") == 0) {
400 			if ((~sblock.fs_flags & FS_MULTILABEL) ==
401 			    FS_MULTILABEL) {
402 				warnx("%s remains unchanged as disabled",
403 				    name);
404 			} else {
405 				sblock.fs_flags &= ~FS_MULTILABEL;
406 				warnx("%s cleared", name);
407 			}
408 		}
409 	}
410 	if (mflag) {
411 		name = "minimum percentage of free space";
412 		if (sblock.fs_minfree == mvalue)
413 			warnx("%s remains unchanged as %d%%", name, mvalue);
414 		else {
415 			warnx("%s changes from %d%% to %d%%",
416 				    name, sblock.fs_minfree, mvalue);
417 			sblock.fs_minfree = mvalue;
418 			if (mvalue >= MINFREE && sblock.fs_optim == FS_OPTSPACE)
419 				warnx(OPTWARN, "time", ">=", MINFREE);
420 			if (mvalue < MINFREE && sblock.fs_optim == FS_OPTTIME)
421 				warnx(OPTWARN, "space", "<", MINFREE);
422 		}
423 	}
424 	if (Nflag) {
425 		name = "NFSv4 ACLs";
426 		if (strcmp(Nvalue, "enable") == 0) {
427 			if (sblock.fs_flags & FS_NFS4ACLS) {
428 				warnx("%s remains unchanged as enabled", name);
429 			} else if (sblock.fs_flags & FS_ACLS) {
430 				warnx("%s and POSIX.1e ACLs are mutually "
431 				    "exclusive", name);
432 			} else {
433 				sblock.fs_flags |= FS_NFS4ACLS;
434 				warnx("%s set", name);
435 			}
436 		} else if (strcmp(Nvalue, "disable") == 0) {
437 			if ((~sblock.fs_flags & FS_NFS4ACLS) ==
438 			    FS_NFS4ACLS) {
439 				warnx("%s remains unchanged as disabled",
440 				    name);
441 			} else {
442 				sblock.fs_flags &= ~FS_NFS4ACLS;
443 				warnx("%s cleared", name);
444 			}
445 		}
446 	}
447 	if (nflag) {
448  		name = "soft updates";
449  		if (strcmp(nvalue, "enable") == 0) {
450 			if (sblock.fs_flags & FS_DOSOFTDEP)
451 				warnx("%s remains unchanged as enabled", name);
452 			else if (sblock.fs_clean == 0) {
453 				warnx("%s cannot be enabled until fsck is run",
454 				    name);
455 			} else {
456  				sblock.fs_flags |= FS_DOSOFTDEP;
457  				warnx("%s set", name);
458 			}
459  		} else if (strcmp(nvalue, "disable") == 0) {
460 			if ((~sblock.fs_flags & FS_DOSOFTDEP) == FS_DOSOFTDEP)
461 				warnx("%s remains unchanged as disabled", name);
462 			else {
463  				sblock.fs_flags &= ~FS_DOSOFTDEP;
464  				warnx("%s cleared", name);
465 			}
466  		}
467 	}
468 	if (oflag) {
469 		name = "optimization preference";
470 		chg[FS_OPTSPACE] = "space";
471 		chg[FS_OPTTIME] = "time";
472 		if (sblock.fs_optim == ovalue)
473 			warnx("%s remains unchanged as %s", name, chg[ovalue]);
474 		else {
475 			warnx("%s changes from %s to %s",
476 				    name, chg[sblock.fs_optim], chg[ovalue]);
477 			sblock.fs_optim = ovalue;
478 			if (sblock.fs_minfree >= MINFREE &&
479 			    ovalue == FS_OPTSPACE)
480 				warnx(OPTWARN, "time", ">=", MINFREE);
481 			if (sblock.fs_minfree < MINFREE && ovalue == FS_OPTTIME)
482 				warnx(OPTWARN, "space", "<", MINFREE);
483 		}
484 	}
485 	if (sflag) {
486 		name = "expected number of files per directory";
487 		if (sblock.fs_avgfpdir == (unsigned)svalue) {
488 			warnx("%s remains unchanged as %d", name, svalue);
489 		}
490 		else {
491 			warnx("%s changes from %d to %d",
492 					name, sblock.fs_avgfpdir, svalue);
493 			sblock.fs_avgfpdir = svalue;
494 		}
495 	}
496 
497 	if (sbwrite(&disk, Aflag) == -1)
498 		goto err;
499 	ufs_disk_close(&disk);
500 	if (active) {
501 		bzero(&args, sizeof(args));
502 		if (mount("ufs", on,
503 		    stfs.f_flags | MNT_UPDATE | MNT_RELOAD, &args) < 0)
504 			err(9, "%s: reload", special);
505 		warnx("file system reloaded");
506 	}
507 	exit(0);
508 err:
509 	if (disk.d_error != NULL)
510 		errx(11, "%s: %s", special, disk.d_error);
511 	else
512 		err(12, "%s", special);
513 }
514 
515 void
516 sbdirty(void)
517 {
518 	disk.d_fs.fs_flags |= FS_UNCLEAN | FS_NEEDSFSCK;
519 	disk.d_fs.fs_clean = 0;
520 }
521 
522 int blocks;
523 static char clrbuf[MAXBSIZE];
524 
525 static ufs2_daddr_t
526 journal_balloc(void)
527 {
528 	ufs2_daddr_t blk;
529 	struct cg *cgp;
530 	int valid;
531 	static int contig = 1;
532 
533 	cgp = &disk.d_cg;
534 	for (;;) {
535 		blk = cgballoc(&disk);
536 		if (blk > 0)
537 			break;
538 		/*
539 		 * If we failed to allocate a block from this cg, move to
540 		 * the next.
541 		 */
542 		if (cgwrite(&disk) < 0) {
543 			warn("Failed to write updated cg");
544 			return (-1);
545 		}
546 		while ((valid = cgread(&disk)) == 1) {
547 			/*
548 			 * Try to minimize fragmentation by requiring a minimum
549 			 * number of blocks present.
550 			 */
551 			if (cgp->cg_cs.cs_nbfree > 256 * 1024)
552 				break;
553 			if (contig == 0 && cgp->cg_cs.cs_nbfree)
554 				break;
555 		}
556 		if (valid)
557 			continue;
558 		/*
559 		 * Try once through looking only for large contiguous regions
560 		 * and again taking any space we can find.
561 		 */
562 		if (contig) {
563 			contig = 0;
564 			disk.d_ccg = 0;
565 			warnx("Journal file fragmented.");
566 			continue;
567 		}
568 		warnx("Failed to find sufficient free blocks for the journal");
569 		return -1;
570 	}
571 	if (bwrite(&disk, fsbtodb(&sblock, blk), clrbuf,
572 	    sblock.fs_bsize) <= 0) {
573 		warn("Failed to initialize new block");
574 		return -1;
575 	}
576 	return (blk);
577 }
578 
579 /*
580  * Search a directory block for the SUJ_FILE.
581  */
582 static ino_t
583 dir_search(ufs2_daddr_t blk, int bytes)
584 {
585 	char block[MAXBSIZE];
586 	struct direct *dp;
587 	int off;
588 
589 	if (bread(&disk, fsbtodb(&sblock, blk), block, bytes) <= 0) {
590 		warn("Failed to read dir block");
591 		return (-1);
592 	}
593 	for (off = 0; off < bytes; off += dp->d_reclen) {
594 		dp = (struct direct *)&block[off];
595 		if (dp->d_reclen == 0)
596 			break;
597 		if (dp->d_ino == 0)
598 			continue;
599 		if (dp->d_namlen != strlen(SUJ_FILE))
600 			continue;
601 		if (bcmp(dp->d_name, SUJ_FILE, dp->d_namlen) != 0)
602 			continue;
603 		return (dp->d_ino);
604 	}
605 
606 	return (0);
607 }
608 
609 /*
610  * Search in the ROOTINO for the SUJ_FILE.  If it exists we can not enable
611  * journaling.
612  */
613 static ino_t
614 journal_findfile(void)
615 {
616 	struct ufs1_dinode *dp1;
617 	struct ufs2_dinode *dp2;
618 	ino_t ino;
619 	int mode;
620 	void *ip;
621 	int i;
622 
623 	if (getino(&disk, &ip, ROOTINO, &mode) != 0) {
624 		warn("Failed to get root inode");
625 		return (-1);
626 	}
627 	dp2 = ip;
628 	dp1 = ip;
629 	if (sblock.fs_magic == FS_UFS1_MAGIC) {
630 		if ((off_t)dp1->di_size >= lblktosize(&sblock, NDADDR)) {
631 			warnx("ROOTINO extends beyond direct blocks.");
632 			return (-1);
633 		}
634 		for (i = 0; i < NDADDR; i++) {
635 			if (dp1->di_db[i] == 0)
636 				break;
637 			if ((ino = dir_search(dp1->di_db[i],
638 			    sblksize(&sblock, (off_t)dp1->di_size, i))) != 0)
639 				return (ino);
640 		}
641 	} else {
642 		if ((off_t)dp1->di_size >= lblktosize(&sblock, NDADDR)) {
643 			warnx("ROOTINO extends beyond direct blocks.");
644 			return (-1);
645 		}
646 		for (i = 0; i < NDADDR; i++) {
647 			if (dp2->di_db[i] == 0)
648 				break;
649 			if ((ino = dir_search(dp2->di_db[i],
650 			    sblksize(&sblock, (off_t)dp2->di_size, i))) != 0)
651 				return (ino);
652 		}
653 	}
654 
655 	return (0);
656 }
657 
658 /*
659  * Insert the journal at inode 'ino' into directory blk 'blk' at the first
660  * free offset of 'off'.  DIRBLKSIZ blocks after off are initialized as
661  * empty.
662  */
663 static int
664 dir_insert(ufs2_daddr_t blk, off_t off, ino_t ino)
665 {
666 	struct direct *dp;
667 	char block[MAXBSIZE];
668 
669 	if (bread(&disk, fsbtodb(&sblock, blk), block, sblock.fs_bsize) <= 0) {
670 		warn("Failed to read dir block");
671 		return (-1);
672 	}
673 	bzero(&block[off], sblock.fs_bsize - off);
674 	dp = (struct direct *)&block[off];
675 	dp->d_ino = ino;
676 	dp->d_reclen = DIRBLKSIZ;
677 	dp->d_type = DT_REG;
678 	dp->d_namlen = strlen(SUJ_FILE);
679 	bcopy(SUJ_FILE, &dp->d_name, strlen(SUJ_FILE));
680 	off += DIRBLKSIZ;
681 	for (; off < sblock.fs_bsize; off += DIRBLKSIZ) {
682 		dp = (struct direct *)&block[off];
683 		dp->d_ino = 0;
684 		dp->d_reclen = DIRBLKSIZ;
685 		dp->d_type = DT_UNKNOWN;
686 	}
687 	if (bwrite(&disk, fsbtodb(&sblock, blk), block, sblock.fs_bsize) <= 0) {
688 		warn("Failed to write dir block");
689 		return (-1);
690 	}
691 	return (0);
692 }
693 
694 /*
695  * Extend a directory block in 'blk' by copying it to a full size block
696  * and inserting the new journal inode into .sujournal.
697  */
698 static int
699 dir_extend(ufs2_daddr_t blk, ufs2_daddr_t nblk, off_t size, ino_t ino)
700 {
701 	char block[MAXBSIZE];
702 
703 	if (bread(&disk, fsbtodb(&sblock, blk), block, size) <= 0) {
704 		warn("Failed to read dir block");
705 		return (-1);
706 	}
707 	if (bwrite(&disk, fsbtodb(&sblock, nblk), block, size) <= 0) {
708 		warn("Failed to write dir block");
709 		return (-1);
710 	}
711 
712 	return dir_insert(nblk, size, ino);
713 }
714 
715 /*
716  * Insert the journal file into the ROOTINO directory.  We always extend the
717  * last frag
718  */
719 static int
720 journal_insertfile(ino_t ino)
721 {
722 	struct ufs1_dinode *dp1;
723 	struct ufs2_dinode *dp2;
724 	void *ip;
725 	ufs2_daddr_t nblk;
726 	ufs2_daddr_t blk;
727 	ufs_lbn_t lbn;
728 	int size;
729 	int mode;
730 	int off;
731 
732 	if (getino(&disk, &ip, ROOTINO, &mode) != 0) {
733 		warn("Failed to get root inode");
734 		sbdirty();
735 		return (-1);
736 	}
737 	dp2 = ip;
738 	dp1 = ip;
739 	blk = 0;
740 	size = 0;
741 	nblk = journal_balloc();
742 	if (nblk <= 0)
743 		return (-1);
744 	/*
745 	 * For simplicity sake we aways extend the ROOTINO into a new
746 	 * directory block rather than searching for space and inserting
747 	 * into an existing block.  However, if the rootino has frags
748 	 * have to free them and extend the block.
749 	 */
750 	if (sblock.fs_magic == FS_UFS1_MAGIC) {
751 		lbn = lblkno(&sblock, dp1->di_size);
752 		off = blkoff(&sblock, dp1->di_size);
753 		blk = dp1->di_db[lbn];
754 		size = sblksize(&sblock, (off_t)dp1->di_size, lbn);
755 	} else {
756 		lbn = lblkno(&sblock, dp2->di_size);
757 		off = blkoff(&sblock, dp2->di_size);
758 		blk = dp2->di_db[lbn];
759 		size = sblksize(&sblock, (off_t)dp2->di_size, lbn);
760 	}
761 	if (off != 0) {
762 		if (dir_extend(blk, nblk, off, ino) == -1)
763 			return (-1);
764 	} else {
765 		blk = 0;
766 		if (dir_insert(nblk, 0, ino) == -1)
767 			return (-1);
768 	}
769 	if (sblock.fs_magic == FS_UFS1_MAGIC) {
770 		dp1->di_blocks += (sblock.fs_bsize - size) / DEV_BSIZE;
771 		dp1->di_db[lbn] = nblk;
772 		dp1->di_size = lblktosize(&sblock, lbn+1);
773 	} else {
774 		dp2->di_blocks += (sblock.fs_bsize - size) / DEV_BSIZE;
775 		dp2->di_db[lbn] = nblk;
776 		dp2->di_size = lblktosize(&sblock, lbn+1);
777 	}
778 	if (putino(&disk) < 0) {
779 		warn("Failed to write root inode");
780 		return (-1);
781 	}
782 	if (cgwrite(&disk) < 0) {
783 		warn("Failed to write updated cg");
784 		sbdirty();
785 		return (-1);
786 	}
787 	if (blk) {
788 		if (cgbfree(&disk, blk, size) < 0) {
789 			warn("Failed to write cg");
790 			return (-1);
791 		}
792 	}
793 
794 	return (0);
795 }
796 
797 static int
798 indir_fill(ufs2_daddr_t blk, int level, int *resid)
799 {
800 	char indirbuf[MAXBSIZE];
801 	ufs1_daddr_t *bap1;
802 	ufs2_daddr_t *bap2;
803 	ufs2_daddr_t nblk;
804 	int ncnt;
805 	int cnt;
806 	int i;
807 
808 	bzero(indirbuf, sizeof(indirbuf));
809 	bap1 = (ufs1_daddr_t *)indirbuf;
810 	bap2 = (void *)bap1;
811 	cnt = 0;
812 	for (i = 0; i < NINDIR(&sblock) && *resid != 0; i++) {
813 		nblk = journal_balloc();
814 		if (nblk <= 0)
815 			return (-1);
816 		cnt++;
817 		if (sblock.fs_magic == FS_UFS1_MAGIC)
818 			*bap1++ = nblk;
819 		else
820 			*bap2++ = nblk;
821 		if (level != 0) {
822 			ncnt = indir_fill(nblk, level - 1, resid);
823 			if (ncnt <= 0)
824 				return (-1);
825 			cnt += ncnt;
826 		} else
827 			(*resid)--;
828 	}
829 	if (bwrite(&disk, fsbtodb(&sblock, blk), indirbuf,
830 	    sblock.fs_bsize) <= 0) {
831 		warn("Failed to write indirect");
832 		return (-1);
833 	}
834 	return (cnt);
835 }
836 
837 /*
838  * Clear the flag bits so the journal can be removed.
839  */
840 void
841 journal_clear(void)
842 {
843 	struct ufs1_dinode *dp1;
844 	struct ufs2_dinode *dp2;
845 	ino_t ino;
846 	int mode;
847 	void *ip;
848 
849 	ino = journal_findfile();
850 	if (ino == (ino_t)-1 || ino == 0) {
851 		warnx("Journal file does not exist");
852 		return;
853 	}
854 	printf("Clearing journal flags from inode %d\n", ino);
855 	if (getino(&disk, &ip, ino, &mode) != 0) {
856 		warn("Failed to get journal inode");
857 		return;
858 	}
859 	dp2 = ip;
860 	dp1 = ip;
861 	if (sblock.fs_magic == FS_UFS1_MAGIC)
862 		dp1->di_flags = 0;
863 	else
864 		dp2->di_flags = 0;
865 	if (putino(&disk) < 0) {
866 		warn("Failed to write journal inode");
867 		return;
868 	}
869 }
870 
871 int
872 journal_alloc(int64_t size)
873 {
874 	struct ufs1_dinode *dp1;
875 	struct ufs2_dinode *dp2;
876 	ufs2_daddr_t blk;
877 	void *ip;
878 	struct cg *cgp;
879 	int resid;
880 	ino_t ino;
881 	int blks;
882 	int mode;
883 	int i;
884 
885 	cgp = &disk.d_cg;
886 	ino = 0;
887 
888 	/*
889 	 * If the journal file exists we can't allocate it.
890 	 */
891 	ino = journal_findfile();
892 	if (ino == (ino_t)-1)
893 		return (-1);
894 	if (ino > 0) {
895 		warnx("Journal file %s already exists, please remove.",
896 		    SUJ_FILE);
897 		return (-1);
898 	}
899 	/*
900 	 * If the user didn't supply a size pick one based on the filesystem
901 	 * size constrained with hardcoded MIN and MAX values.  We opt for
902 	 * 1/1024th of the filesystem up to MAX but not exceeding one CG and
903 	 * not less than the MIN.
904 	 */
905 	if (size == 0) {
906 		size = (sblock.fs_size * sblock.fs_bsize) / 1024;
907 		size = MIN(SUJ_MAX, size);
908 		if (size / sblock.fs_fsize > sblock.fs_fpg)
909 			size = sblock.fs_fpg * sblock.fs_fsize;
910 		size = MAX(SUJ_MIN, size);
911 		/* fsck does not support fragments in journal files. */
912 		size = roundup(size, sblock.fs_bsize);
913 	}
914 	resid = blocks = size / sblock.fs_bsize;
915 	if (sblock.fs_cstotal.cs_nbfree < blocks) {
916 		warn("Insufficient free space for %jd byte journal", size);
917 		return (-1);
918 	}
919 	/*
920 	 * Find a cg with enough blocks to satisfy the journal
921 	 * size.  Presently the journal does not span cgs.
922 	 */
923 	while (cgread(&disk) == 1) {
924 		if (cgp->cg_cs.cs_nifree == 0)
925 			continue;
926 		ino = cgialloc(&disk);
927 		if (ino <= 0)
928 			break;
929 		printf("Using inode %d in cg %d for %jd byte journal\n",
930 		    ino, cgp->cg_cgx, size);
931 		if (getino(&disk, &ip, ino, &mode) != 0) {
932 			warn("Failed to get allocated inode");
933 			sbdirty();
934 			goto out;
935 		}
936 		/*
937 		 * We leave fields unrelated to the number of allocated
938 		 * blocks and size uninitialized.  This causes legacy
939 		 * fsck implementations to clear the inode.
940 		 */
941 		dp2 = ip;
942 		dp1 = ip;
943 		if (sblock.fs_magic == FS_UFS1_MAGIC) {
944 			bzero(dp1, sizeof(*dp1));
945 			dp1->di_size = size;
946 			dp1->di_mode = IFREG | IREAD;
947 			dp1->di_nlink = 1;
948 			dp1->di_flags = SF_IMMUTABLE | SF_NOUNLINK | UF_NODUMP;
949 		} else {
950 			bzero(dp2, sizeof(*dp2));
951 			dp2->di_size = size;
952 			dp2->di_mode = IFREG | IREAD;
953 			dp2->di_nlink = 1;
954 			dp2->di_flags = SF_IMMUTABLE | SF_NOUNLINK | UF_NODUMP;
955 		}
956 		for (i = 0; i < NDADDR && resid; i++, resid--) {
957 			blk = journal_balloc();
958 			if (blk <= 0)
959 				goto out;
960 			if (sblock.fs_magic == FS_UFS1_MAGIC) {
961 				dp1->di_db[i] = blk;
962 				dp1->di_blocks++;
963 			} else {
964 				dp2->di_db[i] = blk;
965 				dp2->di_blocks++;
966 			}
967 		}
968 		for (i = 0; i < NIADDR && resid; i++) {
969 			blk = journal_balloc();
970 			if (blk <= 0)
971 				goto out;
972 			blks = indir_fill(blk, i, &resid) + 1;
973 			if (blks <= 0) {
974 				sbdirty();
975 				goto out;
976 			}
977 			if (sblock.fs_magic == FS_UFS1_MAGIC) {
978 				dp1->di_ib[i] = blk;
979 				dp1->di_blocks += blks;
980 			} else {
981 				dp2->di_ib[i] = blk;
982 				dp2->di_blocks += blks;
983 			}
984 		}
985 		if (sblock.fs_magic == FS_UFS1_MAGIC)
986 			dp1->di_blocks *= sblock.fs_bsize / disk.d_bsize;
987 		else
988 			dp2->di_blocks *= sblock.fs_bsize / disk.d_bsize;
989 		if (putino(&disk) < 0) {
990 			warn("Failed to write inode");
991 			sbdirty();
992 			return (-1);
993 		}
994 		if (cgwrite(&disk) < 0) {
995 			warn("Failed to write updated cg");
996 			sbdirty();
997 			return (-1);
998 		}
999 		if (journal_insertfile(ino) < 0) {
1000 			sbdirty();
1001 			return (-1);
1002 		}
1003 		sblock.fs_sujfree = 0;
1004 		return (0);
1005 	}
1006 	warnx("Insufficient free space for the journal.");
1007 out:
1008 	return (-1);
1009 }
1010 
1011 void
1012 usage(void)
1013 {
1014 	fprintf(stderr, "%s\n%s\n%s\n%s\n%s\n",
1015 "usage: tunefs [-A] [-a enable | disable] [-e maxbpg] [-f avgfilesize]",
1016 "              [-J enable | disable] [-j enable | disable]",
1017 "              [-L volname] [-l enable | disable] [-m minfree]",
1018 "              [-N enable | disable] [-n enable | disable]",
1019 "              [-o space | time] [-p] [-s avgfpdir] special | filesystem");
1020 	exit(2);
1021 }
1022 
1023 void
1024 printfs(void)
1025 {
1026 	warnx("POSIX.1e ACLs: (-a)                                %s",
1027 		(sblock.fs_flags & FS_ACLS)? "enabled" : "disabled");
1028 	warnx("NFSv4 ACLs: (-N)                                   %s",
1029 		(sblock.fs_flags & FS_NFS4ACLS)? "enabled" : "disabled");
1030 	warnx("MAC multilabel: (-l)                               %s",
1031 		(sblock.fs_flags & FS_MULTILABEL)? "enabled" : "disabled");
1032 	warnx("soft updates: (-n)                                 %s",
1033 		(sblock.fs_flags & FS_DOSOFTDEP)? "enabled" : "disabled");
1034 	warnx("soft update journaling: (-j)                       %s",
1035 		(sblock.fs_flags & FS_SUJ)? "enabled" : "disabled");
1036 	warnx("gjournal: (-J)                                     %s",
1037 		(sblock.fs_flags & FS_GJOURNAL)? "enabled" : "disabled");
1038 	warnx("maximum blocks per file in a cylinder group: (-e)  %d",
1039 	      sblock.fs_maxbpg);
1040 	warnx("average file size: (-f)                            %d",
1041 	      sblock.fs_avgfilesize);
1042 	warnx("average number of files in a directory: (-s)       %d",
1043 	      sblock.fs_avgfpdir);
1044 	warnx("minimum percentage of free space: (-m)             %d%%",
1045 	      sblock.fs_minfree);
1046 	warnx("optimization preference: (-o)                      %s",
1047 	      sblock.fs_optim == FS_OPTSPACE ? "space" : "time");
1048 	if (sblock.fs_minfree >= MINFREE &&
1049 	    sblock.fs_optim == FS_OPTSPACE)
1050 		warnx(OPTWARN, "time", ">=", MINFREE);
1051 	if (sblock.fs_minfree < MINFREE &&
1052 	    sblock.fs_optim == FS_OPTTIME)
1053 		warnx(OPTWARN, "space", "<", MINFREE);
1054 	warnx("volume label: (-L)                                 %s",
1055 		sblock.fs_volname);
1056 }
1057