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