tunefs.c revision 1.35
1/*	$NetBSD: tunefs.c,v 1.35 2008/07/31 05:38:04 simonb Exp $	*/
2
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#include <sys/cdefs.h>
33#ifndef lint
34__COPYRIGHT("@(#) Copyright (c) 1983, 1993\
35 The Regents of the University of California.  All rights reserved.");
36#endif /* not lint */
37
38#ifndef lint
39#if 0
40static char sccsid[] = "@(#)tunefs.c	8.3 (Berkeley) 5/3/95";
41#else
42__RCSID("$NetBSD: tunefs.c,v 1.35 2008/07/31 05:38:04 simonb Exp $");
43#endif
44#endif /* not lint */
45
46/*
47 * tunefs: change layout parameters to an existing file system.
48 */
49#include <sys/param.h>
50
51#include <ufs/ffs/fs.h>
52#include <ufs/ffs/ffs_extern.h>
53#include <ufs/ufs/ufs_wapbl.h>
54
55#include <machine/bswap.h>
56
57#include <err.h>
58#include <errno.h>
59#include <fcntl.h>
60#include <fstab.h>
61#include <paths.h>
62#include <stdio.h>
63#include <stdlib.h>
64#include <string.h>
65#include <unistd.h>
66#include <util.h>
67
68/* the optimization warning string template */
69#define	OPTWARN	"should optimize for %s with minfree %s %d%%"
70
71union {
72	struct	fs sb;
73	char pad[MAXBSIZE];
74} sbun;
75#define	sblock sbun.sb
76char buf[MAXBSIZE];
77
78int	fi;
79long	dev_bsize = 512;
80int	needswap = 0;
81int	is_ufs2 = 0;
82off_t	sblockloc;
83
84static off_t sblock_try[] = SBLOCKSEARCH;
85
86static	void	bwrite(daddr_t, char *, int, const char *);
87static	void	bread(daddr_t, char *, int, const char *);
88static	void	change_log_info(long long);
89static	void	getsb(struct fs *, const char *);
90static	int	openpartition(const char *, int, char *, size_t);
91static	void	show_log_info(void);
92static	void	usage(void);
93
94int
95main(int argc, char *argv[])
96{
97#define	OPTSTRINGBASE	"AFNe:g:h:l:m:o:"
98#ifdef TUNEFS_SOFTDEP
99	int		softdep;
100#define	OPTSTRING	OPTSTRINGBASE ## "n:"
101#else
102#define	OPTSTRING	OPTSTRINGBASE
103#endif
104	int		i, ch, Aflag, Fflag, Nflag, openflags;
105	const char	*special, *chg[2];
106	char		device[MAXPATHLEN];
107	int		maxbpg, minfree, optim;
108	int		avgfilesize, avgfpdir;
109	long long	logfilesize;
110
111	Aflag = Fflag = Nflag = 0;
112	maxbpg = minfree = optim = -1;
113	avgfilesize = avgfpdir = -1;
114	logfilesize = -1;
115#ifdef TUNEFS_SOFTDEP
116	softdep = -1;
117#endif
118	chg[FS_OPTSPACE] = "space";
119	chg[FS_OPTTIME] = "time";
120
121	while ((ch = getopt(argc, argv, OPTSTRING)) != -1) {
122		switch (ch) {
123
124		case 'A':
125			Aflag++;
126			break;
127
128		case 'F':
129			Fflag++;
130			break;
131
132		case 'N':
133			Nflag++;
134			break;
135
136		case 'e':
137			maxbpg = strsuftoll(
138			    "maximum blocks per file in a cylinder group",
139			    optarg, 1, INT_MAX);
140			break;
141
142		case 'g':
143			avgfilesize = strsuftoll("average file size", optarg,
144			    1, INT_MAX);
145			break;
146
147		case 'h':
148			avgfpdir = strsuftoll(
149			    "expected number of files per directory",
150			    optarg, 1, INT_MAX);
151			break;
152
153		case 'l':
154			logfilesize = strsuftoll("journal log file size",
155			    optarg, 0, INT_MAX);
156			break;
157
158		case 'm':
159			minfree = strsuftoll("minimum percentage of free space",
160			    optarg, 0, 99);
161			break;
162
163#ifdef TUNEFS_SOFTDEP
164		case 'n':
165			if (strcmp(optarg, "enable") == 0)
166				softdep = 1;
167			else if (strcmp(optarg, "disable") == 0)
168				softdep = 0;
169			else {
170				errx(10, "bad soft dependencies "
171					"(options are `enable' or `disable')");
172			}
173			break;
174#endif
175
176		case 'o':
177			if (strcmp(optarg, chg[FS_OPTSPACE]) == 0)
178				optim = FS_OPTSPACE;
179			else if (strcmp(optarg, chg[FS_OPTTIME]) == 0)
180				optim = FS_OPTTIME;
181			else
182				errx(10,
183				    "bad %s (options are `space' or `time')",
184				    "optimization preference");
185			break;
186
187		default:
188			usage();
189		}
190	}
191	argc -= optind;
192	argv += optind;
193	if (argc != 1)
194		usage();
195
196	special = argv[0];
197	openflags = Nflag ? O_RDONLY : O_RDWR;
198	if (Fflag)
199		fi = open(special, openflags);
200	else {
201		fi = openpartition(special, openflags, device, sizeof(device));
202		special = device;
203	}
204	if (fi == -1)
205		err(1, "%s", special);
206	getsb(&sblock, special);
207
208#define CHANGEVAL(old, new, type, suffix) do				\
209	if ((new) != -1) {						\
210		if ((new) == (old))					\
211			warnx("%s remains unchanged at %d%s",		\
212			    (type), (old), (suffix));			\
213		else {							\
214			warnx("%s changes from %d%s to %d%s",		\
215			    (type), (old), (suffix), (new), (suffix));	\
216			(old) = (new);					\
217		}							\
218	} while (/* CONSTCOND */0)
219
220	warnx("tuning %s", special);
221	CHANGEVAL(sblock.fs_maxbpg, maxbpg,
222	    "maximum blocks per file in a cylinder group", "");
223	CHANGEVAL(sblock.fs_minfree, minfree,
224	    "minimum percentage of free space", "%");
225	if (minfree != -1) {
226		if (minfree >= MINFREE &&
227		    sblock.fs_optim == FS_OPTSPACE)
228			warnx(OPTWARN, "time", ">=", MINFREE);
229		if (minfree < MINFREE &&
230		    sblock.fs_optim == FS_OPTTIME)
231			warnx(OPTWARN, "space", "<", MINFREE);
232	}
233#ifdef TUNEFS_SOFTDEP
234	if (softdep == 1) {
235		sblock.fs_flags |= FS_DOSOFTDEP;
236		warnx("soft dependencies set");
237	} else if (softdep == 0) {
238		sblock.fs_flags &= ~FS_DOSOFTDEP;
239		warnx("soft dependencies cleared");
240	}
241#endif
242	if (optim != -1) {
243		if (sblock.fs_optim == optim) {
244			warnx("%s remains unchanged as %s",
245			    "optimization preference",
246			    chg[optim]);
247		} else {
248			warnx("%s changes from %s to %s",
249			    "optimization preference",
250			    chg[sblock.fs_optim], chg[optim]);
251			sblock.fs_optim = optim;
252			if (sblock.fs_minfree >= MINFREE &&
253			    optim == FS_OPTSPACE)
254				warnx(OPTWARN, "time", ">=", MINFREE);
255			if (sblock.fs_minfree < MINFREE &&
256			    optim == FS_OPTTIME)
257				warnx(OPTWARN, "space", "<", MINFREE);
258		}
259	}
260	CHANGEVAL(sblock.fs_avgfilesize, avgfilesize,
261	    "average file size", "");
262	CHANGEVAL(sblock.fs_avgfpdir, avgfpdir,
263	    "expected number of files per directory", "");
264
265	if (logfilesize >= 0)
266		change_log_info(logfilesize);
267
268	if (Nflag) {
269		fprintf(stdout, "tunefs: current settings of %s\n", special);
270		fprintf(stdout, "\tmaximum contiguous block count %d\n",
271		    sblock.fs_maxcontig);
272		fprintf(stdout,
273		    "\tmaximum blocks per file in a cylinder group %d\n",
274		    sblock.fs_maxbpg);
275		fprintf(stdout, "\tminimum percentage of free space %d%%\n",
276		    sblock.fs_minfree);
277#ifdef TUNEFS_SOFTDEP
278		fprintf(stdout, "\tsoft dependencies: %s\n",
279		    (sblock.fs_flags & FS_DOSOFTDEP) ? "on" : "off");
280#endif
281		fprintf(stdout, "\toptimization preference: %s\n",
282		    chg[sblock.fs_optim]);
283		fprintf(stdout, "\taverage file size: %d\n",
284		    sblock.fs_avgfilesize);
285		fprintf(stdout,
286		    "\texpected number of files per directory: %d\n",
287		    sblock.fs_avgfpdir);
288		show_log_info();
289		fprintf(stdout, "tunefs: no changes made\n");
290		exit(0);
291	}
292
293	memcpy(buf, (char *)&sblock, SBLOCKSIZE);
294	if (needswap)
295		ffs_sb_swap((struct fs*)buf, (struct fs*)buf);
296	bwrite(sblockloc, buf, SBLOCKSIZE, special);
297	if (Aflag)
298		for (i = 0; i < sblock.fs_ncg; i++)
299			bwrite(fsbtodb(&sblock, cgsblock(&sblock, i)),
300			    buf, SBLOCKSIZE, special);
301	close(fi);
302	exit(0);
303}
304
305static void
306show_log_info(void)
307{
308	const char *loc;
309	uint64_t size, blksize;
310	int print;
311
312	switch (sblock.fs_journal_location) {
313	case UFS_WAPBL_JOURNALLOC_NONE:
314		print = blksize = 0;
315		/* nothing */
316		break;
317	case UFS_WAPBL_JOURNALLOC_END_PARTITION:
318		loc = "end of partition";
319		size = sblock.fs_journallocs[UFS_WAPBL_EPART_COUNT];
320		blksize = sblock.fs_journallocs[UFS_WAPBL_EPART_BLKSZ];
321		print = 1;
322		break;
323	case UFS_WAPBL_JOURNALLOC_IN_FILESYSTEM:
324		loc = "in filesystem";
325		size = sblock.fs_journallocs[UFS_WAPBL_INFS_COUNT];
326		blksize = sblock.fs_journallocs[UFS_WAPBL_INFS_BLKSZ];
327		print = 1;
328		break;
329	default:
330		loc = "unknown";
331		size = blksize = 0;
332		print = 1;
333		break;
334	}
335
336	if (print) {
337		fprintf(stdout, "\tjournal log file location: %s\n", loc);
338		fprintf(stdout, "\tjournal log file size: %" PRIu64 "\n",
339		    size * blksize);
340		fprintf(stdout, "\tjournal log flags:");
341		if (sblock.fs_journal_flags & UFS_WAPBL_FLAGS_CREATE_LOG)
342			fprintf(stdout, " clear-log");
343		if (sblock.fs_journal_flags & UFS_WAPBL_FLAGS_CLEAR_LOG)
344			fprintf(stdout, " clear-log");
345		fprintf(stdout, "\n");
346	}
347}
348
349static void
350change_log_info(long long logfilesize)
351{
352	/*
353	 * NOTES:
354	 *  - only operate on in-filesystem log sizes
355	 *  - can't change size of existing log
356	 *  - if current is same, no action
357	 *  - if current is zero and new is non-zero, set flag to create log
358	 *    on next mount
359	 *  - if current is non-zero and new is zero, set flag to clear log
360	 *    on next mount
361	 */
362	int in_fs_log;
363	uint64_t old_size;
364
365	old_size = 0;
366	switch (sblock.fs_journal_location) {
367	case UFS_WAPBL_JOURNALLOC_END_PARTITION:
368		in_fs_log = 0;
369		old_size = sblock.fs_journallocs[UFS_WAPBL_EPART_COUNT] *
370		    sblock.fs_journallocs[UFS_WAPBL_EPART_BLKSZ];
371		break;
372
373	case UFS_WAPBL_JOURNALLOC_IN_FILESYSTEM:
374		in_fs_log = 1;
375		old_size = sblock.fs_journallocs[UFS_WAPBL_INFS_COUNT] *
376		    sblock.fs_journallocs[UFS_WAPBL_INFS_BLKSZ];
377		break;
378
379	case UFS_WAPBL_JOURNALLOC_NONE:
380	default:
381		in_fs_log = 0;
382		old_size = 0;
383		break;
384	}
385
386	if (!in_fs_log)
387		errx(1, "Can't change size of non-in-filesystem log");
388
389	if (old_size == logfilesize && logfilesize > 0) {
390		/* no action */
391		warnx("log file size remains unchanged at %lld", logfilesize);
392		return;
393	}
394
395	if (logfilesize == 0) {
396		/*
397		 * Don't clear out the locators - the kernel might need
398		 * these to find the log!  Just set the "clear the log"
399		 * flag and let the kernel do the rest.
400		 */
401		sblock.fs_journal_flags |= UFS_WAPBL_FLAGS_CLEAR_LOG;
402		sblock.fs_journal_flags &= ~UFS_WAPBL_FLAGS_CREATE_LOG;
403		warnx("log file size cleared from %" PRIu64 "", old_size);
404		return;
405	}
406
407	if (old_size == 0) {
408		/* create new log of desired size next mount */
409		sblock.fs_journal_location = UFS_WAPBL_JOURNALLOC_IN_FILESYSTEM;
410		sblock.fs_journallocs[UFS_WAPBL_INFS_ADDR] = 0;
411		sblock.fs_journallocs[UFS_WAPBL_INFS_COUNT] = logfilesize;
412		sblock.fs_journallocs[UFS_WAPBL_INFS_BLKSZ] = 0;
413		sblock.fs_journallocs[UFS_WAPBL_INFS_INO] = 0;
414		sblock.fs_journal_flags |= UFS_WAPBL_FLAGS_CREATE_LOG;
415		sblock.fs_journal_flags &= ~UFS_WAPBL_FLAGS_CLEAR_LOG;
416		warnx("log file size set to %lld", logfilesize);
417	} else {
418		errx(1,
419		    "Can't change existing log size from %" PRIu64 " to %lld",
420		     old_size, logfilesize);
421	}
422}
423
424static void
425usage(void)
426{
427
428	fprintf(stderr, "usage: tunefs [-AFN] tuneup-options special-device\n");
429	fprintf(stderr, "where tuneup-options are:\n");
430	fprintf(stderr, "\t-e maximum blocks per file in a cylinder group\n");
431	fprintf(stderr, "\t-g average file size\n");
432	fprintf(stderr, "\t-h expected number of files per directory\n");
433	fprintf(stderr, "\t-l journal log file size (`0' to clear journal)\n");
434	fprintf(stderr, "\t-m minimum percentage of free space\n");
435#ifdef TUNEFS_SOFTDEP
436	fprintf(stderr, "\t-n soft dependencies (`enable' or `disable')\n");
437#endif
438	fprintf(stderr, "\t-o optimization preference (`space' or `time')\n");
439	exit(2);
440}
441
442static void
443getsb(struct fs *fs, const char *file)
444{
445	int i;
446
447	for (i = 0; ; i++) {
448		if (sblock_try[i] == -1)
449			errx(5, "cannot find filesystem superblock");
450		bread(sblock_try[i] / dev_bsize, (char *)fs, SBLOCKSIZE, file);
451		switch(fs->fs_magic) {
452		case FS_UFS2_MAGIC:
453			is_ufs2 = 1;
454			/*FALLTHROUGH*/
455		case FS_UFS1_MAGIC:
456			break;
457		case FS_UFS2_MAGIC_SWAPPED:
458			is_ufs2 = 1;
459			/*FALLTHROUGH*/
460		case FS_UFS1_MAGIC_SWAPPED:
461			warnx("%s: swapping byte order", file);
462			needswap = 1;
463			ffs_sb_swap(fs, fs);
464			break;
465		default:
466			continue;
467		}
468		if (!is_ufs2 && sblock_try[i] == SBLOCK_UFS2)
469			continue;
470		if ((is_ufs2 || fs->fs_old_flags & FS_FLAGS_UPDATED)
471		    && fs->fs_sblockloc != sblock_try[i])
472			continue;
473		break;
474	}
475
476	dev_bsize = fs->fs_fsize / fsbtodb(fs, 1);
477	sblockloc = sblock_try[i] / dev_bsize;
478}
479
480static void
481bwrite(daddr_t blk, char *buffer, int size, const char *file)
482{
483	off_t	offset;
484
485	offset = (off_t)blk * dev_bsize;
486	if (lseek(fi, offset, SEEK_SET) == -1)
487		err(6, "%s: seeking to %lld", file, (long long)offset);
488	if (write(fi, buffer, size) != size)
489		err(7, "%s: writing %d bytes", file, size);
490}
491
492static void
493bread(daddr_t blk, char *buffer, int cnt, const char *file)
494{
495	off_t	offset;
496	int	i;
497
498	offset = (off_t)blk * dev_bsize;
499	if (lseek(fi, offset, SEEK_SET) == -1)
500		err(4, "%s: seeking to %lld", file, (long long)offset);
501	if ((i = read(fi, buffer, cnt)) != cnt)
502		errx(5, "%s: short read", file);
503}
504
505static int
506openpartition(const char *name, int flags, char *device, size_t devicelen)
507{
508	char		rawspec[MAXPATHLEN], *p;
509	struct fstab	*fs;
510	int		fd, oerrno;
511
512	fs = getfsfile(name);
513	if (fs) {
514		if ((p = strrchr(fs->fs_spec, '/')) != NULL) {
515			snprintf(rawspec, sizeof(rawspec), "%.*s/r%s",
516			    (int)(p - fs->fs_spec), fs->fs_spec, p + 1);
517			name = rawspec;
518		} else
519			name = fs->fs_spec;
520	}
521	fd = opendisk(name, flags, device, devicelen, 0);
522	if (fd == -1 && errno == ENOENT) {
523		oerrno = errno;
524		strlcpy(device, name, devicelen);
525		errno = oerrno;
526	}
527	return (fd);
528}
529