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