Improve growfs(8) in a few ways; unfortunately, it's somewhat hard to untangle

them and commit separately.

1. Rewrite the way growfs(8) finds the device and mount point.  This makes
   it possible to use e.g. "growfs /mnt"; it's also used to display more
   helpful messages.

2. Be more user-friendly, using descriptive messages, like this:

   OK to grow filesystem on /dev/md0, mounted on /mnt, from 9.8GB to 20GB? [Yes/No]"

3. Allow to specify the size (-s option) just like with mdconfig(8), i.e. with
   postfixes ("mdconfig -s 10g").

4. Reload read-only filesystem after growing.

Reviewed by:	kib, mckusick (earlier version)
Sponsored by:	The FreeBSD Foundation
This commit is contained in:
Edward Tomasz Napierala 2012-04-30 16:08:02 +00:00
parent f62651920d
commit e35497f13b
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=234846
3 changed files with 264 additions and 187 deletions

View File

@ -6,12 +6,18 @@
#GFSDBG=
.PATH: ${.CURDIR}/../mount
PROG= growfs
SRCS= growfs.c
SRCS= growfs.c getmntopts.c
MAN= growfs.8
CFLAGS+=-I${.CURDIR}/../mount
.if defined(GFSDBG)
SRCS+= debug.c
.endif
DPADD= ${LIBUTIL}
LDADD= -lutil
.include <bsd.prog.mk>

View File

@ -37,7 +37,7 @@
.\" $TSHeader: src/sbin/growfs/growfs.8,v 1.3 2000/12/12 19:31:00 tomsoft Exp $
.\" $FreeBSD$
.\"
.Dd June 29, 2011
.Dd April 30, 2012
.Dt GROWFS 8
.Os
.Sh NAME
@ -47,41 +47,20 @@
.Nm
.Op Fl Ny
.Op Fl s Ar size
.Ar special
.Ar special | filesystem
.Sh DESCRIPTION
The
.Nm
utility extends the
.Xr newfs 8
program.
Before starting
utility makes it possible to expand an UFS file system.
Before running
.Nm
the disk must be labeled to a bigger size using
.Xr bsdlabel 8 .
If you wish to grow a file system beyond the boundary of
the slice it resides in, you must re-size the slice using
.Xr gpart 8
before running
.Nm .
the partition or slice containing the file system must be extended using
.Xr gpart 8 .
If you are using volumes you must enlarge them by using
.Xr gvinum 8 .
The
.Nm
utility extends the size of the file system on the specified special file.
Currently
.Nm
can only enlarge unmounted file systems.
Do not try enlarging a mounted file system, your system may panic and you will
not be able to use the file system any longer.
Most of the
.Xr newfs 8
options cannot be changed by
.Nm .
In fact, you can only increase the size of the file system.
Use
.Xr tunefs 8
for other changes.
.Pp
The following options are available:
.Bl -tag -width indent
.It Fl N
@ -103,6 +82,13 @@ So use this option with great care!
Determines the
.Ar size
of the file system after enlarging in sectors.
.Ar Size
is the number of 512 byte sectors unless suffixed with a
.Cm b , k , m , g ,
or
.Cm t
which
denotes byte, kilobyte, megabyte, gigabyte and terabyte respectively.
This value defaults to the size of the raw partition specified in
.Ar special
(in other words,
@ -110,19 +96,18 @@ This value defaults to the size of the raw partition specified in
will enlarge the file system to the size of the entire partition).
.El
.Sh EXAMPLES
.Dl growfs -s 4194304 /dev/vinum/testvol
.Dl growfs -s 2G /dev/ada0p1
.Pp
will enlarge
.Pa /dev/vinum/testvol
.Pa /dev/ada0p1
up to 2GB if there is enough space in
.Pa /dev/vinum/testvol .
.Pa /dev/ada0p1 .
.Sh SEE ALSO
.Xr bsdlabel 8 ,
.Xr dumpfs 8 ,
.Xr ffsinfo 8 ,
.Xr fsck 8 ,
.Xr fsdb 8 ,
.Xr gpart 8 ,
.Xr gvinum 8 ,
.Xr newfs 8 ,
.Xr tunefs 8
.Sh HISTORY
@ -134,61 +119,12 @@ utility first appeared in
.An Christoph Herrmann Aq chm@FreeBSD.org
.An Thomas-Henning von Kamptz Aq tomsoft@FreeBSD.org
.An The GROWFS team Aq growfs@Tomsoft.COM
.An Edward Tomasz Napierala Aq trasz@FreeBSD.org
.Sh BUGS
The
.Nm
utility works starting with
.Fx
3.x.
There may be cases on
.Fx
3.x only, when
.Nm
does not recognize properly whether or not the file system is mounted and
exits with an error message.
Then please use
.Nm
.Fl y
if you are sure that the file system is not mounted.
It is also recommended to always use
.Xr fsck 8
after enlarging (just to be on the safe side).
.Pp
For enlarging beyond certain limits, it is essential to have some free blocks
available in the first cylinder group.
If that space is not available in the first cylinder group, a critical data
structure has to be relocated into one of the new available cylinder groups.
On
.Fx
3.x this will cause problems with
.Xr fsck 8
afterwards.
So
.Xr fsck 8
needs to be patched if you want to use
.Nm
for
.Fx
3.x.
This patch is already integrated in
.Fx
starting with
.Fx 4.4 .
To avoid an unexpected relocation of that structure it is possible to use
.Nm ffsinfo
.Fl g Ar 0
.Fl l Ar 4
on the first cylinder group to verify that
.Em nbfree
in the CYLINDER SUMMARY (internal cs) of the CYLINDER GROUP
.Em cgr0
has enough blocks.
As a rule of thumb for default file system parameters one block is needed for
every 2 GB of total file system size.
.Pp
Normally
.Nm
writes this critical structure to disk and reads it again later for doing more
writes cylinder group summary to disk and reads it again later for doing more
updates.
This read operation will provide unexpected data when using
.Fl N .

View File

@ -1,11 +1,15 @@
/*
* Copyright (c) 2000 Christoph Herrmann, Thomas-Henning von Kamptz
* Copyright (c) 1980, 1989, 1993 The Regents of the University of California.
* Copyright (c) 2000 Christoph Herrmann, Thomas-Henning von Kamptz
* Copyright (c) 2012 The FreeBSD Foundation
* All rights reserved.
*
* This code is derived from software contributed to Berkeley by
* Christoph Herrmann and Thomas-Henning von Kamptz, Munich and Frankfurt.
*
* Portions of this software were developed by Edward Tomasz Napierala
* under sponsorship from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
@ -53,13 +57,18 @@ __FBSDID("$FreeBSD$");
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/disk.h>
#include <sys/ucred.h>
#include <sys/mount.h>
#include <stdio.h>
#include <paths.h>
#include <ctype.h>
#include <err.h>
#include <fcntl.h>
#include <fstab.h>
#include <inttypes.h>
#include <limits.h>
#include <mntopts.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
@ -67,6 +76,7 @@ __FBSDID("$FreeBSD$");
#include <unistd.h>
#include <ufs/ufs/dinode.h>
#include <ufs/ffs/fs.h>
#include <libutil.h>
#include "debug.h"
@ -109,7 +119,7 @@ static void updjcg(int, time_t, int, int, unsigned int);
static void updcsloc(time_t, int, int, unsigned int);
static void frag_adjust(ufs2_daddr_t, int);
static void updclst(int);
static void get_dev_size(int, int *);
static void mount_reload(const struct statfs *stfs);
/*
* Here we actually start growing the file system. We basically read the
@ -177,6 +187,7 @@ growfs(int fsi, int fso, unsigned int Nflag)
/*
* Dump out summary information about file system.
*/
#ifdef FS_DEBUG
#define B2MBFACTOR (1 / (1024.0 * 1024.0))
printf("growfs: %.1fMB (%jd sectors) block size %d, fragment size %d\n",
(float)sblock.fs_size * sblock.fs_fsize * B2MBFACTOR,
@ -188,6 +199,7 @@ growfs(int fsi, int fso, unsigned int Nflag)
if (sblock.fs_flags & FS_DOSOFTDEP)
printf("\twith soft updates\n");
#undef B2MBFACTOR
#endif /* FS_DEBUG */
/*
* Now build the cylinders group blocks and
@ -774,7 +786,7 @@ updjcg(int cylno, time_t modtime, int fsi, int fso, unsigned int Nflag)
/*
* Here we update the location of the cylinder summary. We have two possible
* ways of growing the cylinder summary.
* ways of growing the cylinder summary:
* (1) We can try to grow the summary in the current location, and relocate
* possibly used blocks within the current cylinder group.
* (2) Alternatively we can relocate the whole cylinder summary to the first
@ -1238,24 +1250,104 @@ charsperline(void)
return (columns);
}
/*
* Get the size of the partition.
*/
static void
get_dev_size(int fd, int *size)
static int
is_dev(const char *name)
{
int sectorsize;
off_t mediasize;
struct stat devstat;
if (ioctl(fd, DIOCGSECTORSIZE, &sectorsize) == -1)
err(1,"DIOCGSECTORSIZE");
if (ioctl(fd, DIOCGMEDIASIZE, &mediasize) == -1)
err(1,"DIOCGMEDIASIZE");
if (stat(name, &devstat) != 0)
return (0);
if (!S_ISCHR(devstat.st_mode))
return (0);
return (1);
}
if (sectorsize <= 0)
errx(1, "bogus sectorsize: %d", sectorsize);
/*
* Return mountpoint on which the device is currently mounted.
*/
static const struct statfs *
dev_to_statfs(const char *dev)
{
struct stat devstat, mntdevstat;
struct statfs *mntbuf, *statfsp;
char device[MAXPATHLEN];
char *mntdevname;
int i, mntsize;
*size = mediasize / sectorsize;
/*
* First check the mounted filesystems.
*/
if (stat(dev, &devstat) != 0)
return (NULL);
if (!S_ISCHR(devstat.st_mode) && !S_ISBLK(devstat.st_mode))
return (NULL);
mntsize = getmntinfo(&mntbuf, MNT_NOWAIT);
for (i = 0; i < mntsize; i++) {
statfsp = &mntbuf[i];
mntdevname = statfsp->f_mntfromname;
if (*mntdevname != '/') {
strcpy(device, _PATH_DEV);
strcat(device, mntdevname);
mntdevname = device;
}
if (stat(mntdevname, &mntdevstat) == 0 &&
mntdevstat.st_rdev == devstat.st_rdev)
return (statfsp);
}
return (NULL);
}
static const char *
mountpoint_to_dev(const char *mountpoint)
{
struct statfs *mntbuf, *statfsp;
struct fstab *fs;
int i, mntsize;
/*
* First check the mounted filesystems.
*/
mntsize = getmntinfo(&mntbuf, MNT_NOWAIT);
for (i = 0; i < mntsize; i++) {
statfsp = &mntbuf[i];
if (strcmp(statfsp->f_mntonname, mountpoint) == 0)
return (statfsp->f_mntfromname);
}
/*
* Check the fstab.
*/
fs = getfsfile(mountpoint);
if (fs != NULL)
return (fs->fs_spec);
return (NULL);
}
static const char *
getdev(const char *name)
{
static char device[MAXPATHLEN];
const char *cp, *dev;
if (is_dev(name))
return (name);
cp = strrchr(name, '/');
if (cp == 0) {
snprintf(device, sizeof(device), "%s%s", _PATH_DEV, name);
if (is_dev(device))
return (device);
}
dev = mountpoint_to_dev(name);
if (dev != NULL && is_dev(dev))
return (dev);
return (NULL);
}
/*
@ -1283,17 +1375,13 @@ int
main(int argc, char **argv)
{
DBG_FUNC("main")
char *device, *special;
int ch;
unsigned int size = 0;
size_t len;
unsigned int Nflag = 0;
int ExpertFlag = 0;
struct stat st;
int i, fsi, fso;
u_int32_t p_size;
char reply[5];
int j;
const char *device;
const struct statfs *statfsp;
uint64_t size = 0;
off_t mediasize;
int error, i, j, fsi, fso, ch, Nflag = 0, yflag = 0;
char *p, reply[5], oldsizebuf[6], newsizebuf[6];
void *testbuf;
DBG_ENTER;
@ -1303,14 +1391,27 @@ main(int argc, char **argv)
Nflag = 1;
break;
case 's':
size = (size_t)atol(optarg);
if (size < 1)
usage();
size = (off_t)strtoumax(optarg, &p, 0);
if (p == NULL || *p == '\0')
size *= DEV_BSIZE;
else if (*p == 'b' || *p == 'B')
; /* do nothing */
else if (*p == 'k' || *p == 'K')
size <<= 10;
else if (*p == 'm' || *p == 'M')
size <<= 20;
else if (*p == 'g' || *p == 'G')
size <<= 30;
else if (*p == 't' || *p == 'T') {
size <<= 30;
size <<= 10;
} else
errx(1, "unknown suffix on -s argument");
break;
case 'v': /* for compatibility to newfs */
break;
case 'y':
ExpertFlag = 1;
yflag = 1;
break;
case '?':
/* FALLTHROUGH */
@ -1324,71 +1425,29 @@ main(int argc, char **argv)
if (argc != 1)
usage();
device = *argv;
/*
* Now try to guess the (raw)device name.
* Now try to guess the device name.
*/
if (0 == strrchr(device, '/')) {
/*
* No path prefix was given, so try in that order:
* /dev/r%s
* /dev/%s
* /dev/vinum/r%s
* /dev/vinum/%s.
*
* FreeBSD now doesn't distinguish between raw and block
* devices any longer, but it should still work this way.
*/
len = strlen(device) + strlen(_PATH_DEV) + 2 + strlen("vinum/");
special = (char *)malloc(len);
if (special == NULL)
errx(1, "malloc failed");
snprintf(special, len, "%sr%s", _PATH_DEV, device);
if (stat(special, &st) == -1) {
snprintf(special, len, "%s%s", _PATH_DEV, device);
if (stat(special, &st) == -1) {
snprintf(special, len, "%svinum/r%s",
_PATH_DEV, device);
if (stat(special, &st) == -1) {
/* For now this is the 'last resort' */
snprintf(special, len, "%svinum/%s",
_PATH_DEV, device);
}
}
}
device = special;
}
device = getdev(*argv);
if (device == NULL)
errx(1, "cannot find special device for %s", *argv);
/*
* Try to access our devices for writing ...
*/
if (Nflag) {
fso = -1;
} else {
fso = open(device, O_WRONLY);
if (fso < 0)
err(1, "%s", device);
}
statfsp = dev_to_statfs(device);
/*
* ... and reading.
*/
fsi = open(device, O_RDONLY);
if (fsi < 0)
err(1, "%s", device);
/*
* Try to guess the slice if not specified. This code should guess
* the right thing and avoid to bother the user with the task
* of specifying the option -v on vinum volumes.
* Try to guess the slice size if not specified.
*/
get_dev_size(fsi, &p_size);
if (ioctl(fsi, DIOCGMEDIASIZE, &mediasize) == -1)
err(1,"DIOCGMEDIASIZE");
/*
* Check if that partition is suitable for growing a file system.
*/
if (p_size < 1)
if (mediasize < 1)
errx(1, "partition is unavailable");
/*
@ -1414,16 +1473,36 @@ main(int argc, char **argv)
/*
* Determine size to grow to. Default to the device size.
*/
sblock.fs_size = dbtofsb(&osblock, p_size);
if (size != 0) {
if (size > p_size)
errx(1, "there is not enough space (%d < %d)",
p_size, size);
sblock.fs_size = dbtofsb(&osblock, size);
if (size == 0)
size = mediasize;
else {
if (size > (uint64_t)mediasize) {
humanize_number(oldsizebuf, sizeof(oldsizebuf), size,
"B", HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL);
humanize_number(newsizebuf, sizeof(newsizebuf),
mediasize,
"B", HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL);
errx(1, "requested size %s is larger "
"than the available %s", oldsizebuf, newsizebuf);
}
}
if (size <= (uint64_t)(osblock.fs_size * osblock.fs_fsize)) {
humanize_number(oldsizebuf, sizeof(oldsizebuf),
osblock.fs_size * osblock.fs_fsize,
"B", HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL);
humanize_number(newsizebuf, sizeof(newsizebuf), size,
"B", HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL);
errx(1, "requested size %s is not larger than the current "
"filesystem size %s", newsizebuf, oldsizebuf);
}
sblock.fs_size = dbtofsb(&osblock, size / DEV_BSIZE);
/*
* Are we really growing ?
* Are we really growing?
*/
if (osblock.fs_size >= sblock.fs_size) {
errx(1, "we are not growing (%jd->%jd)",
@ -1433,7 +1512,7 @@ main(int argc, char **argv)
/*
* Check if we find an active snapshot.
*/
if (ExpertFlag == 0) {
if (yflag == 0) {
for (j = 0; j < FSMAXSNAP; j++) {
if (sblock.fs_snapinum[j]) {
errx(1, "active snapshot found in file system; "
@ -1445,10 +1524,23 @@ main(int argc, char **argv)
}
}
if (ExpertFlag == 0 && Nflag == 0) {
printf("We strongly recommend you to make a backup "
if (yflag == 0 && Nflag == 0) {
if (statfsp != NULL && (statfsp->f_flags & MNT_RDONLY) == 0)
errx(1, "%s is mounted read-write on %s",
statfsp->f_mntfromname, statfsp->f_mntonname);
printf("It's strongly recommended to make a backup "
"before growing the file system.\n"
"Did you backup your data (Yes/No)? ");
"OK to grow filesystem on %s", device);
if (statfsp != NULL)
printf(", mounted on %s,", statfsp->f_mntonname);
humanize_number(oldsizebuf, sizeof(oldsizebuf),
osblock.fs_size * osblock.fs_fsize,
"B", HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL);
humanize_number(newsizebuf, sizeof(newsizebuf),
sblock.fs_size * sblock.fs_fsize,
"B", HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL);
printf(" from %s to %s? [Yes/No] ", oldsizebuf, newsizebuf);
fflush(stdout);
fgets(reply, (int)sizeof(reply), stdin);
if (strcmp(reply, "Yes\n")){
printf("\nNothing done\n");
@ -1456,15 +1548,30 @@ main(int argc, char **argv)
}
}
printf("New file system size is %jd frags\n", (intmax_t)sblock.fs_size);
/*
* Try to access our device for writing. If it's not mounted,
* or mounted read-only, simply open it; otherwise, use UFS
* suspension mechanism.
*/
if (Nflag) {
fso = -1;
} else {
fso = open(device, O_WRONLY);
if (fso < 0)
err(1, "%s", device);
}
/*
* Try to access our new last block in the file system. Even if we
* later on realize we have to abort our operation, on that block
* there should be no data, so we can't destroy something yet.
* Try to access our new last block in the file system.
*/
wtfs((ufs2_daddr_t)p_size - 1, (size_t)DEV_BSIZE, (void *)&sblock,
fso, Nflag);
testbuf = malloc(sblock.fs_fsize);
if (testbuf == NULL)
err(1, "malloc");
rdfs((ufs2_daddr_t)((size / DEV_BSIZE) - sblock.fs_fsize),
sblock.fs_fsize, testbuf, fsi);
wtfs((ufs2_daddr_t)((size / DEV_BSIZE) - sblock.fs_fsize),
sblock.fs_fsize, testbuf, fso, Nflag);
free(testbuf);
/*
* Now calculate new superblock values and check for reasonable
@ -1520,8 +1627,13 @@ main(int argc, char **argv)
growfs(fsi, fso, Nflag);
close(fsi);
if (fso > -1)
close(fso);
if (fso > -1) {
error = close(fso);
if (error != 0)
err(1, "close");
}
if (statfsp != NULL)
mount_reload(statfsp);
DBG_CLOSE;
@ -1539,7 +1651,7 @@ usage(void)
DBG_ENTER;
fprintf(stderr, "usage: growfs [-Ny] [-s size] special\n");
fprintf(stderr, "usage: growfs [-Ny] [-s size] special | filesystem\n");
DBG_LEAVE;
exit(1);
@ -1586,3 +1698,26 @@ updclst(int block)
DBG_LEAVE;
return;
}
static void
mount_reload(const struct statfs *stfs)
{
char errmsg[255];
struct iovec *iov;
int iovlen;
iov = NULL;
iovlen = 0;
*errmsg = '\0';
build_iovec(&iov, &iovlen, "fstype", __DECONST(char *, "ffs"), 4);
build_iovec(&iov, &iovlen, "fspath", __DECONST(char *, stfs->f_mntonname), (size_t)-1);
build_iovec(&iov, &iovlen, "errmsg", errmsg, sizeof(errmsg));
build_iovec(&iov, &iovlen, "update", NULL, 0);
build_iovec(&iov, &iovlen, "reload", NULL, 0);
if (nmount(iov, iovlen, stfs->f_flags) < 0) {
errmsg[sizeof(errmsg) - 1] = '\0';
err(9, "%s: cannot reload filesystem%s%s", stfs->f_mntonname,
*errmsg != '\0' ? ": " : "", errmsg);
}
}