1/*	$OpenBSD: tunefs.c,v 1.41 2016/05/28 23:44:27 tb Exp $	*/
2/*	$NetBSD: tunefs.c,v 1.33 2005/01/19 20:46:16 xtraeme Exp $	*/
3
4/*
5 * Copyright (c) 1983, 1993
6 *	The Regents of the University of California.  All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 * 3. Neither the name of the University nor the names of its contributors
17 *    may be used to endorse or promote products derived from this software
18 *    without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
31 */
32
33/*
34 * tunefs: change layout parameters to an existing file system.
35 */
36#include <sys/param.h>	/* DEV_BSIZE MAXBSIZE */
37
38#include <ufs/ufs/dinode.h>
39#include <ufs/ffs/fs.h>
40#include <ufs/ffs/ffs_extern.h>
41
42#include <err.h>
43#include <errno.h>
44#include <fcntl.h>
45#include <fstab.h>
46#include <paths.h>
47#include <stdio.h>
48#include <stdlib.h>
49#include <string.h>
50#include <unistd.h>
51#include <limits.h>
52#include <util.h>
53
54/* the optimization warning string template */
55#define	OPTWARN	"should optimize for %s with minfree %s %d%%"
56
57union {
58	struct	fs sb;
59	char pad[MAXBSIZE];
60} sbun;
61#define	sblock sbun.sb
62char buf[MAXBSIZE];
63
64int	fi;
65int	is_ufs2 = 0;
66off_t	sblockloc;
67
68static off_t sblock_try[] = SBLOCKSEARCH;
69
70static	void	bwrite(daddr_t, char *, int, const char *);
71static	void	bread(daddr_t, char *, int, const char *);
72static	int	getnum(const char *, const char *, int, int);
73static	void	getsb(struct fs *, const char *);
74static	int	openpartition(char *, int, char **);
75static	void	usage(void);
76
77int
78main(int argc, char *argv[])
79{
80#define	OPTSTRING	"AFNe:g:h:m:o:"
81	int		i, ch, Aflag, Fflag, Nflag, openflags;
82	char		*special;
83	const char	*chg[2];
84	int		maxbpg, minfree, optim;
85	int		avgfilesize, avgfpdir;
86
87	Aflag = Fflag = Nflag = 0;
88	maxbpg = minfree = optim = -1;
89	avgfilesize = avgfpdir = -1;
90	chg[FS_OPTSPACE] = "space";
91	chg[FS_OPTTIME] = "time";
92
93	while ((ch = getopt(argc, argv, OPTSTRING)) != -1) {
94		switch (ch) {
95
96		case 'A':
97			Aflag = 1;
98			break;
99
100		case 'F':
101			Fflag = 1;
102			break;
103
104		case 'N':
105			Nflag = 1;
106			break;
107
108		case 'e':
109			maxbpg = getnum(optarg,
110			    "maximum blocks per file in a cylinder group",
111			    1, INT_MAX);
112			break;
113
114		case 'g':
115			avgfilesize = getnum(optarg,
116			    "average file size", 1, INT_MAX);
117			break;
118
119		case 'h':
120			avgfpdir = getnum(optarg,
121			    "expected number of files per directory",
122			    1, INT_MAX);
123			break;
124
125		case 'm':
126			minfree = getnum(optarg,
127			    "minimum percentage of free space", 0, 99);
128			break;
129
130		case 'o':
131			if (strcmp(optarg, chg[FS_OPTSPACE]) == 0)
132				optim = FS_OPTSPACE;
133			else if (strcmp(optarg, chg[FS_OPTTIME]) == 0)
134				optim = FS_OPTTIME;
135			else
136				errx(10,
137				    "bad %s (options are `space' or `time')",
138				    "optimization preference");
139			break;
140
141		default:
142			usage();
143		}
144	}
145	argc -= optind;
146	argv += optind;
147	if (argc != 1)
148		usage();
149
150	special = argv[0];
151	openflags = Nflag ? O_RDONLY : O_RDWR;
152	if (Fflag)
153		fi = open(special, openflags);
154	else
155		fi = openpartition(special, openflags, &special);
156	if (fi == -1)
157		err(1, "%s", special);
158
159	if (pledge("stdio", NULL) == -1)
160		err(1, "pledge");
161
162	getsb(&sblock, special);
163
164#define CHANGEVAL(old, new, type, suffix) do				\
165	if ((new) != -1) {						\
166		if ((new) == (old))					\
167			warnx("%s remains unchanged at %d%s",		\
168			    (type), (old), (suffix));			\
169		else {							\
170			warnx("%s changes from %d%s to %d%s",		\
171			    (type), (old), (suffix), (new), (suffix));	\
172			(old) = (new);					\
173		}							\
174	} while (/* CONSTCOND */0)
175
176	warnx("tuning %s", special);
177	CHANGEVAL(sblock.fs_maxbpg, maxbpg,
178	    "maximum blocks per file in a cylinder group", "");
179	CHANGEVAL(sblock.fs_minfree, minfree,
180	    "minimum percentage of free space", "%");
181	if (minfree != -1) {
182		if (minfree >= MINFREE &&
183		    sblock.fs_optim == FS_OPTSPACE)
184			warnx(OPTWARN, "time", ">=", MINFREE);
185		if (minfree < MINFREE &&
186		    sblock.fs_optim == FS_OPTTIME)
187			warnx(OPTWARN, "space", "<", MINFREE);
188	}
189	if (optim != -1) {
190		if (sblock.fs_optim == optim) {
191			warnx("%s remains unchanged as %s",
192			    "optimization preference",
193			    chg[optim]);
194		} else {
195			warnx("%s changes from %s to %s",
196			    "optimization preference",
197			    chg[sblock.fs_optim], chg[optim]);
198			sblock.fs_optim = optim;
199			if (sblock.fs_minfree >= MINFREE &&
200			    optim == FS_OPTSPACE)
201				warnx(OPTWARN, "time", ">=", MINFREE);
202			if (sblock.fs_minfree < MINFREE &&
203			    optim == FS_OPTTIME)
204				warnx(OPTWARN, "space", "<", MINFREE);
205		}
206	}
207	CHANGEVAL(sblock.fs_avgfilesize, avgfilesize,
208	    "average file size", "");
209	CHANGEVAL(sblock.fs_avgfpdir, avgfpdir,
210	    "expected number of files per directory", "");
211
212	if (Nflag) {
213		fprintf(stdout, "tunefs: current settings of %s\n", special);
214		fprintf(stdout, "\tmaximum contiguous block count %d\n",
215		    sblock.fs_maxcontig);
216		fprintf(stdout,
217		    "\tmaximum blocks per file in a cylinder group %d\n",
218		    sblock.fs_maxbpg);
219		fprintf(stdout, "\tminimum percentage of free space %d%%\n",
220		    sblock.fs_minfree);
221		fprintf(stdout, "\toptimization preference: %s\n",
222		    chg[sblock.fs_optim]);
223		fprintf(stdout, "\taverage file size: %d\n",
224		    sblock.fs_avgfilesize);
225		fprintf(stdout,
226		    "\texpected number of files per directory: %d\n",
227		    sblock.fs_avgfpdir);
228		fprintf(stdout, "tunefs: no changes made\n");
229		exit(0);
230	}
231
232	memcpy(buf, (char *)&sblock, SBLOCKSIZE);
233	bwrite(sblockloc, buf, SBLOCKSIZE, special);
234	if (Aflag)
235		for (i = 0; i < sblock.fs_ncg; i++)
236			bwrite(fsbtodb(&sblock, cgsblock(&sblock, i)),
237			    buf, SBLOCKSIZE, special);
238	close(fi);
239	exit(0);
240}
241
242static int
243getnum(const char *num, const char *desc, int min, int max)
244{
245	int		n;
246	const char	*errstr;
247
248	n = strtonum(num, min, max, &errstr);
249	if (errstr != NULL)
250		errx(1, "Invalid number `%s' for %s: %s", num, desc, errstr);
251	return (n);
252}
253
254static void
255usage(void)
256{
257	extern char *__progname;
258
259	fprintf(stderr,
260	    "usage: %s [-AFN] [-e maxbpg] [-g avgfilesize] "
261	    "[-h avgfpdir] [-m minfree]\n"
262	    "\t[-o optimize_preference] special | filesys\n",
263	    __progname);
264
265	exit(2);
266}
267
268static void
269getsb(struct fs *fs, const char *file)
270{
271	int i;
272
273	for (i = 0; ; i++) {
274		if (sblock_try[i] == -1)
275			errx(5, "cannot find filesystem superblock");
276		bread(sblock_try[i] / DEV_BSIZE, (char *)fs, SBLOCKSIZE, file);
277		switch(fs->fs_magic) {
278		case FS_UFS2_MAGIC:
279			is_ufs2 = 1;
280			/*FALLTHROUGH*/
281		case FS_UFS1_MAGIC:
282			break;
283		default:
284			continue;
285		}
286		if (!is_ufs2 && sblock_try[i] == SBLOCK_UFS2)
287			continue;
288		if ((is_ufs2 || fs->fs_flags & FS_FLAGS_UPDATED)
289		    && fs->fs_sblockloc != sblock_try[i])
290			continue;
291		break;
292	}
293
294	sblockloc = sblock_try[i] / DEV_BSIZE;
295}
296
297static void
298bwrite(daddr_t blk, char *buffer, int size, const char *file)
299{
300	if (pwrite(fi, buffer, size, blk * DEV_BSIZE) != size)
301		err(7, "%s: writing %d bytes @ %lld", file, size,
302		    (long long)(blk * DEV_BSIZE));
303}
304
305static void
306bread(daddr_t blk, char *buffer, int cnt, const char *file)
307{
308	if ((pread(fi, buffer, cnt, (off_t)blk * DEV_BSIZE)) != cnt)
309		errx(5, "%s: reading %d bytes @ %lld", file, cnt,
310		    (long long)(blk * DEV_BSIZE));
311}
312
313static int
314openpartition(char *name, int flags, char **devicep)
315{
316	char		rawspec[PATH_MAX], *p;
317	struct fstab	*fs;
318	int		fd;
319
320	fs = getfsfile(name);
321	if (fs) {
322		if ((p = strrchr(fs->fs_spec, '/')) != NULL) {
323			snprintf(rawspec, sizeof(rawspec), "%.*s/r%s",
324			    (int)(p - fs->fs_spec), fs->fs_spec, p + 1);
325			name = rawspec;
326		} else
327			name = fs->fs_spec;
328	}
329	fd = opendev(name, flags, 0, devicep);
330	if (fd == -1 && errno == ENOENT)
331		devicep = &name;
332	return (fd);
333}
334