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