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