xfs_shrinkfs(8) for xfsprogs.
This version does not really works for data moving, it only would be used for
reflect
my current thoughts for implementing this utility.
Signed-off-by: Jie Liu <jeff.liu@xxxxxxxxxx>
---
Makefile | 3 +-
include/xfs_dfrag.h | 20 +
include/xfs_fs.h | 6 +
shrinkfs/Makefile | 30 ++
shrinkfs/xfs_shrink.c | 1184 ++++++++++++++++++++++++++++++++++++++++++++++
shrinkfs/xfs_shrinkfs.sh | 99 ++++
6 files changed, 1341 insertions(+), 1 deletion(-)
create mode 100644 shrinkfs/Makefile
create mode 100644 shrinkfs/xfs_shrink.c
create mode 100755 shrinkfs/xfs_shrinkfs.sh
diff --git a/Makefile b/Makefile
index 0bdc5e8..c30ea21 100644
--- a/Makefile
+++ b/Makefile
@@ -41,7 +41,7 @@ endif
LIB_SUBDIRS = libxfs libxlog libxcmd libhandle libdisk
TOOL_SUBDIRS = copy db estimate fsck fsr growfs io logprint mkfs quota \
- mdrestore repair rtcp m4 man doc po debian
+ mdrestore repair rtcp m4 man doc po debian shrinkfs
SUBDIRS = include $(LIB_SUBDIRS) $(TOOL_SUBDIRS)
@@ -62,6 +62,7 @@ io: libxcmd libhandle
mkfs: libxfs
quota: libxcmd
repair: libxfs libxlog
+shrinkfs: libxfs libxcmd
ifneq ($(ENABLE_BLKID), yes)
mkfs: libdisk
diff --git a/include/xfs_dfrag.h b/include/xfs_dfrag.h
index 20bdd93..17a076b 100644
--- a/include/xfs_dfrag.h
+++ b/include/xfs_dfrag.h
@@ -38,6 +38,21 @@ typedef struct xfs_swapext
*/
#define XFS_SX_VERSION 0
+/*
+ * Structure passed to xfs_swapino.
+ */
+typedef struct xfs_swapino {
+ __int64_t si_version; /* version */
+ __int64_t si_fdtarget; /* fd of target file */
+ __int64_t si_fdtmp; /* fd of temp file */
+ char si_pad[16]; /* pad space, unused */
+} xfs_swapino_t;
+
+/*
+ * Version flag.
+ */
+#define XFS_SI_VERSION 0
+
#ifdef __KERNEL__
/*
* Prototypes for visible xfs_dfrag.c routines.
@@ -48,6 +63,11 @@ typedef struct xfs_swapext
*/
int xfs_swapext(struct xfs_swapext *sx);
+/*
+ * Syscall interface for xfs_swapino
+ */
+int xfs_swapino(struct xfs_swapino *si);
+
#endif /* __KERNEL__ */
#endif /* __XFS_DFRAG_H__ */
diff --git a/include/xfs_fs.h b/include/xfs_fs.h
index c749474..5b9c1b4 100644
--- a/include/xfs_fs.h
+++ b/include/xfs_fs.h
@@ -267,6 +267,10 @@ typedef struct xfs_growfs_rt {
__u32 extsize; /* new realtime extent size, fsblocks */
} xfs_growfs_rt_t;
+typedef struct xfs_shrinkfs_data {
+ __u64 newblocks;
+ __u32 imaxpct;
+} xfs_shrinkfs_data_t;
/*
* Structures returned from ioctl XFS_IOC_FSBULKSTAT &
XFS_IOC_FSBULKSTAT_SINGLE
@@ -485,6 +489,8 @@ typedef struct xfs_handle {
#define XFS_IOC_GOINGDOWN _IOR ('X', 125, __uint32_t)
#define XFS_IOC_SET_AGSTATE _IOW ('X', 126, struct xfs_ioc_agstate)
#define XFS_IOC_GET_AGSTATE _IOR ('X', 127, struct xfs_ioc_agstate)
+#define XFS_IOC_SWAPINO _IOWR ('X', 128, struct
xfs_swapino)
+#define XFS_IOC_FSSHRINKFS_DATA _IOW ('X', 129, struct
xfs_shrinkfs_data)
/* XFS_IOC_GETFSUUID ---------- deprecated 140 */
diff --git a/shrinkfs/Makefile b/shrinkfs/Makefile
new file mode 100644
index 0000000..48a623e
--- /dev/null
+++ b/shrinkfs/Makefile
@@ -0,0 +1,30 @@
+TOPDIR = ..
+include $(TOPDIR)/include/builddefs
+
+LTCOMMAND = xfs_shrink
+
+CFILES = xfs_shrink.c
+
+LLDLIBS = $(LIBXFS) $(LIBXCMD) $(LIBUUID) $(LIBRT) $(LIBPTHREAD)
+ifeq ($(ENABLE_READLINE),yes)
+LLDLIBS += $(LIBREADLINE) $(LIBTERMCAP)
+endif
+
+ifeq ($(ENABLE_EDITLINE),yes)
+LLDLIBS += $(LIBEDITLINE) $(LIBTERMCAP)
+endif
+
+LTDEPENDENCIES = $(LIBXFS) $(LIBXCMD)
+LLDFLAGS = -static
+
+default: depend $(LTCOMMAND)
+
+include $(BUILDRULES)
+
+install: default
+ $(INSTALL) -m 755 -d $(PKG_SBIN_DIR)
+ $(LTINSTALL) -m 755 $(LTCOMMAND) $(PKG_SBIN_DIR)
+ $(INSTALL) -m 755 xfs_shrinkfs.sh $(PKG_SBIN_DIR)/xfs_shrinkfs
+install-dev:
+
+-include .dep
diff --git a/shrinkfs/xfs_shrink.c b/shrinkfs/xfs_shrink.c
new file mode 100644
index 0000000..6f72063
--- /dev/null
+++ b/shrinkfs/xfs_shrink.c
@@ -0,0 +1,1184 @@
+#include <xfs/xfs.h>
+#include <xfs/libxfs.h>
+#include <xfs/xfs_types.h>
+#include <xfs/xfs_dfrag.h>
+#include <xfs/xfs_bmap_btree.h>
+#include <xfs/jdm.h>
+#include <libxfs.h>
+
+#include <path.h>
+#include <fcntl.h>
+#include <ftw.h>
+#include <libgen.h>
+#include <malloc.h>
+#include <mntent.h>
+#include <sys/ioctl.h>
+#include <sys/vfs.h>
+#include <sys/statvfs.h>
+#include <errno.h>
+
+#define MNTTYPE_XFS "xfs"
+#define ECBUFSZ 4096
+#define SMBUFSZ 1024
+
+const char *program = "xfs_shrinkfs";
+int vflag = 0; /* -v flag print verbose data moving info */
+int mflag = 0;
+int offline_agcount = 0;
+int imaxpct;
+long int end_ag_freesize = -1; /* a.g. free blocks in the last
one */
+static __uint8_t aginolog;
+xfs_agnumber_t *offline_aglist = NULL;
+xfs_agnumber_t first_offline_agno = 0; /* the 1st AG in offline state
*/
+xfs_fsop_geom_t geom; /* old filesystem geometry */
+
+/*
+ * Which kind of volume to be shrink?
+ */
+enum {
+ DVOLUME = 0,
+ LVOLUME,
+ RVOLUME,
+};
+
+__uint8_t
+get_aginolog(void)
+{
+ int blocklog;
+ int inodelog;
+ int inopblog;
+ int agblklog;
+
+ blocklog = libxfs_highbit32(geom.blocksize);
+ inodelog = libxfs_highbit32(geom.inodesize);
+ inopblog = blocklog - inodelog;
+ agblklog = (__uint8_t)libxfs_log2_roundup((unsigned int)geom.agblocks);
+
+ return (__uint8_t)(inopblog + agblklog);
+}
+
+static int
+ino_to_agno(
+ ino_t st_ino)
+{
+ xfs_ino_t ino;
+
+ ino = (xfs_ino_t)st_ino;
+ return ino >> aginolog;
+}
+
+static uint32_t
+get_ag_state(
+ const char *fname,
+ int fd,
+ xfs_agnumber_t agno)
+{
+ xfs_ioc_agstate_t agstate;
+
+ agstate.agno = agno;
+ if (xfsctl(fname, fd, XFS_IOC_GET_AGSTATE, &agstate) < 0) {
+ fprintf(stderr, _("unable to get state of ag %u\n"), agno);
+ return -1;
+ }
+
+ return agstate.state;
+}
+
+static int
+change_ag_state(
+ const char *fname,
+ int fd,
+ xfs_agnumber_t agno,
+ unsigned int state)
+{
+ xfs_ioc_agstate_t agstate;
+
+ agstate.agno = agno;
+ agstate.state = state;
+ if (xfsctl(fname, fd, XFS_IOC_SET_AGSTATE, &agstate) < 0) {
+ fprintf(stderr, _("unable to change state for ag %u\n"), agno);
+ return -errno;
+ }
+
+ return 0;
+}
+
+static int
+aglist_add(
+ xfs_agnumber_t agno)
+{
+ offline_aglist = realloc(offline_aglist,
+ (offline_agcount + 1) *
+ sizeof(*offline_aglist));
+ if (!offline_aglist)
+ return -ENOMEM;
+
+ offline_aglist[offline_agcount] = agno;
+ offline_agcount++;
+
+ return 0;
+}
+
+static void
+aglist_free(void)
+{
+ int agno;
+
+ for (agno = 0; agno < offline_agcount; agno++)
+ free(&offline_aglist[agno]);
+ free(offline_aglist);
+}
+
+void
+report_info(
+ char *mntpoint,
+ int isint,
+ char *logname,
+ char *rtname,
+ int lazycount,
+ int dirversion,
+ int logversion,
+ int attrversion,
+ int cimode)
+{
+ printf(_(
+ "meta-data=%-22s isize=%-6u agcount=%u, agsize=%u blks\n"
+ " =%-22s sectsz=%-5u attr=%u\n"
+ "data =%-22s bsize=%-6u blocks=%llu, imaxpct=%u\n"
+ " =%-22s sunit=%-6u swidth=%u blks\n"
+ "naming =version %-14u bsize=%-6u ascii-ci=%d\n"
+ "log =%-22s bsize=%-6u blocks=%u, version=%u\n"
+ " =%-22s sectsz=%-5u sunit=%u blks, lazy-count=%u\n"
+ "realtime =%-22s extsz=%-6u blocks=%llu, rtextents=%llu\n"),
+ mntpoint, geom.inodesize, geom.agcount, geom.agblocks,
+ "", geom.sectsize, attrversion,
+ "", geom.blocksize, (unsigned long long)geom.datablocks,
+ geom.imaxpct,
+ "", geom.sunit, geom.swidth,
+ dirversion, geom.dirblocksize, cimode,
+ isint ? _("internal") : logname ? logname : _("external"),
+ geom.blocksize, geom.logblocks, logversion,
+ "", geom.logsectsize, geom.logsunit / geom.blocksize,
+ lazycount,
+ !geom.rtblocks ? _("none") : rtname ? rtname : _("external"),
+ geom.rtextsize * geom.blocksize,
+ (unsigned long long)geom.rtblocks,
+ (unsigned long long)geom.rtextents);
+}
+
+int
+xfs_bulkstat_single(
+ int fd,
+ xfs_ino_t *lastip,
+ xfs_bstat_t *ubuffer)
+{
+ xfs_fsop_bulkreq_t bulkreq;
+
+ bulkreq.lastip = (__u64 *)lastip;
+ bulkreq.icount = 1;
+ bulkreq.ubuffer = ubuffer;
+ bulkreq.ocount = NULL;
+
+ return ioctl(fd, XFS_IOC_FSBULKSTAT_SINGLE, &bulkreq);
+}
+
+int
+xfs_bulkstat(
+ int fd,
+ xfs_ino_t *lastip,
+ int icount,
+ xfs_bstat_t *ubuffer,
+ __s32 *ocount)
+{
+ xfs_fsop_bulkreq_t bulkreq;
+
+ bulkreq.lastip = (__u64 *)lastip;
+ bulkreq.icount = icount;
+ bulkreq.ubuffer = ubuffer;
+ bulkreq.ocount = ocount;
+ return ioctl(fd, XFS_IOC_FSBULKSTAT, &bulkreq);
+}
+
+static int
+xfs_swapino(
+ int fd,
+ xfs_swapino_t *si)
+{
+ return ioctl(fd, XFS_IOC_SWAPINO, si);
+}
+
+int
+xfs_fscounts(
+ int fd,
+ xfs_fsop_counts_t *counts)
+{
+ return ioctl(fd, XFS_IOC_FSCOUNTS, counts);
+}
+
+static int
+xfs_getxattr(int fd, struct fsxattr *attr)
+{
+ return ioctl(fd, XFS_IOC_FSGETXATTR, attr);
+}
+
+static int
+move_dir(
+ const char *srcname,
+ const struct stat64 *st)
+{
+ xfs_swapino_t si;
+ xfs_bstat_t bstatbuf;
+ struct fsxattr fsx;
+ char target[PATH_MAX] = "";
+ char *pname = NULL;
+ int sfd = -1;
+ int tfd = -1;
+ int error = -1;
+
+ sfd = open(srcname, O_RDONLY);
+ if (sfd < 0) {
+ fprintf(stderr,
+ _("unable to open source directory %s: %s\n"),
+ srcname, strerror(errno));
+ goto out;
+ }
+
+ if (!platform_test_xfs_fd(sfd)) {
+ fprintf(stderr, _("%s is not an xfs file\n"), srcname);
+ goto out;
+ }
+
+ if (xfs_getxattr(sfd, &fsx) < 0) {
+ fprintf(stderr,
+ _("unable to get source directory %s attrs\n"),
+ srcname);
+ goto out;
+ }
+ if (fsx.fsx_xflags & (XFS_XFLAG_IMMUTABLE | XFS_XFLAG_APPEND)) {
+ fprintf(stderr, _("%s: immutable/append, ignoring\n"),
+ srcname);
+ goto out;
+ }
+
+ /* mkdir parent/target */
+ pname = strdup(srcname);
+ if (!pname) {
+ fprintf(stderr,
+ _("unable to duplicate source directory %s: %s\n"),
+ pname, strerror(ENOMEM));
+ goto out;
+ }
+
+ dirname(pname);
+ sprintf(target, "%s/shrinkXXXXXX", pname);
+ if (!mkdtemp(target)) {
+ fprintf(stderr,
+ _("unable to create temp directory for target %s\n"),
+ target);
+ goto out;
+ }
+
+ tfd = open(target, O_RDONLY);
+ if (tfd < 0) {
+ fprintf(stderr,
+ _("Unable to create target directory %s\n"),
+ target);
+ goto out;
+ }
+
+ /* swap inodes src->target */
+ si.si_version = XFS_SI_VERSION;
+ si.si_fdtarget = tfd;
+ si.si_fdtmp = sfd;
+ error = xfs_swapino(tfd, &si);
+ if (error < 0) {
+ fprintf(stderr,
+ _("unable to swap inodes from %s to target %s: %s\n"),
+ srcname, target, strerror(errno));
+ goto out_unlink;
+ }
+
+ /* verify swapino result */
+ if (xfs_bulkstat_single(sfd, (xfs_ino_t *)&st->st_ino, &bstatbuf) < 0) {
+ fprintf(stderr,
+ _("unable to bulkstat source file %s\n"),
+ srcname);
+ unlink(target);
+ goto out;
+ }
+ if (bstatbuf.bs_ino != st->st_ino) {
+ fprintf(stderr,
+ _("bulkstat of source directory %s returned wrong
inode\n"),
+ srcname);
+ goto out;
+ }
+
+ ftruncate64(tfd, bstatbuf.bs_size);
+
+ /* remove source directory */
+ error = rmdir(srcname);
+ if (error < 0) {
+ fprintf(stderr,
+ _("unable to remove source directory %s\n"),
+ srcname);
+ goto out;
+ }
+
+ /* rename current target to source */
+ error = rename(target, srcname);
+ if (error < 0) {
+ /*
+ * We can't abort since the source directory is gone now.
+ * Let the admin clean this one up.
+ */
+ fprintf(stderr,
+ _("unable to rename directory from %s to %s\n"),
+ target, srcname);
+ }
+ goto out;
+
+out_unlink:
+ error = rmdir(target);
+ if (error < 0) {
+ fprintf(stderr,
+ _("unable to remove target directory %s\n"),
+ target);
+ }
+
+out:
+ if (sfd >= 0)
+ close(sfd);
+ if (tfd >= 0)
+ close(tfd);
+
+ free(pname);
+ return error;
+}
+
+static int
+move_slink(
+ const char *srcname,
+ const struct stat64 *st)
+{
+ xfs_swapino_t si;
+ char target[PATH_MAX] = "";
+ char linkbuf[PATH_MAX];
+ char *pname = NULL;
+ int i = 0;
+ int sfd = -1;
+ int tfd = -1;
+ int error = -1;
+
+ sfd = open(srcname, O_RDWR | O_DIRECT);
+ if (sfd < 0) {
+ fprintf(stderr,
+ _("unable to open source file %s as %s\n"),
+ srcname, strerror(errno));
+ goto out;
+ }
+
+ i = readlink(srcname, linkbuf, sizeof(linkbuf) - 1);
+ if (i < 0) {
+ fprintf(stderr,
+ _("unable to read link of source file %s as %s\n"),
+ srcname, strerror(errno));
+ goto out;
+ }
+ linkbuf[i] = '\0';
+
+ error = -1;
+ pname = strdup(srcname);
+ if (!pname) {
+ fprintf(stderr,
+ _("unable to duplicate source file %s as %s\n"),
+ srcname, strerror(ENOMEM));
+ goto out;
+ }
+
+ dirname(pname);
+ sprintf(target, "%s/shrinkXXXXXX", pname);
+ tfd = mkstemp(target);
+ if (tfd < 0) {
+ fprintf(stderr,
+ _("unable to create target file %s\n"),
+ target);
+ goto out;
+ }
+
+ if (symlink(linkbuf, target) != 0) {
+ fprintf(stderr,
+ _("unable to create source symbol link %s to %s\n"),
+ linkbuf, target);
+ goto out;
+ }
+
+ /* swap inode src->target */
+ si.si_version = XFS_SI_VERSION;
+ si.si_fdtarget = sfd;
+ si.si_fdtmp = tfd;
+ error = xfs_swapino(sfd, &si);
+ if (error < 0) {
+ fprintf(stderr, _("unable to swap inodes from %s to %s: %s\n"),
+ srcname, target, strerror(errno));
+ goto out;
+ }
+
+ error = unlink(srcname);
+ if (error < 0) {
+ fprintf(stderr, _("unable to unlink source file %s: %s\n"),
+ srcname, strerror(errno));
+ goto out;
+ }
+
+ error = rename(target, srcname);
+ if (error < 0)
+ fprintf(stderr, _("unable to rename %s to %s\n"),
+ target, srcname);
+
+out:
+ if (sfd > 0)
+ close(sfd);
+ if (tfd > 0)
+ close(tfd);
+
+ free(pname);
+ return error;
+}
+
+static int
+move_file(
+ const char *srcname,
+ const struct stat64 *st)
+{
+ xfs_swapino_t si;
+ xfs_bstat_t bstatbuf;
+ struct fsxattr fsx;
+ char target[SMBUFSZ];
+ char *pname = NULL;
+ int sfd = -1;
+ int tfd = -1;
+ int error = -1;
+
+ sfd = open(srcname, O_RDWR | O_DIRECT);
+ if (sfd < 0) {
+ fprintf(stderr,
+ _("count not open source source file: %s: %s\n"),
+ srcname, strerror(errno));
+ goto out;
+ }
+
+ if (!platform_test_xfs_fd(sfd)) {
+ fprintf(stderr,
+ _("specified file [\"%s\"] is not on an XFS
filesystem\n"),
+ srcname);
+ goto out;
+ }
+
+ if (fsync(sfd) < 0) {
+ fprintf(stderr, _("sync failed: %s: %s\n"),
+ srcname, strerror(errno));
+ goto out;
+ }
+
+ /*
+ * Check if a mandatory lock is set on the file to try and avoid
+ * blocking indefinitely on the reads later. Note that someone
+ * could still set a mandatory lock after this check but before
+ * all reads have completed to block xfs_shrinkfs reads. This
+ * change just closes the window a bit.
+ */
+ if ((st->st_mode & S_ISGID) && !(st->st_mode & S_IXGRP)) {
+ struct flock fl;
+
+ fl.l_type = F_RDLCK;
+ fl.l_whence = SEEK_SET;
+ fl.l_start = (off_t)0;
+ fl.l_len = 0;
+ if (fcntl(sfd, F_GETLK, &fl) < 0) {
+ fprintf(stderr, _("locking check failed: %s\n"),
+ srcname);
+ goto out;
+ }
+ if (fl.l_type != F_UNLCK) {
+ fprintf(stderr, _("mandatory lock: %s: ignoring\n"),
+ srcname);
+ goto out;
+ }
+ }
+
+ if (xfs_getxattr(sfd, &fsx) < 0) {
+ fprintf(stderr, _("unable to get the attrs of source file
%s\n"),
+ srcname);
+ goto out;
+ }
+
+ if (fsx.fsx_xflags & (XFS_XFLAG_IMMUTABLE | XFS_XFLAG_APPEND)) {
+ fprintf(stderr, _("skip to move immutable file %s\n"),
+ srcname);
+ goto out;
+ }
+
+ /* create target */
+ pname = strdup(srcname);
+ if (!pname) {
+ fprintf(stderr, _("unable to duplate source file %s: %s\n"),
+ srcname, strerror(ENOMEM));
+ goto out;
+ }
+ dirname(pname);
+
+ sprintf(target, "%s/shrinkXXXXXX", pname);
+ tfd = mkstemp(target);
+ if (tfd < 0) {
+ fprintf(stderr, _("unable to create target file %s\n"),
+ target);
+ goto out;
+ }
+
+ /* swap inode source->target */
+ si.si_version = XFS_SI_VERSION;
+ si.si_fdtarget = sfd;
+ si.si_fdtmp = tfd;
+ error = xfs_swapino(sfd, &si);
+ if (error < 0) {
+ fprintf(stderr,
+ _("unable to swap inodes from %s to %s: %s\n"),
+ srcname, target, strerror(errno));
+ goto out_unlink;
+ }
+
+ if (xfs_bulkstat_single(sfd, (xfs_ino_t*)&st->st_ino, &bstatbuf) < 0) {
+ fprintf(stderr, _("unable to bulkstat source file: %s\n"),
+ srcname);
+ unlink(target);
+ goto out;
+ }
+
+ if (bstatbuf.bs_ino != st->st_ino) {
+ fprintf(stderr,
+ _("bulkstat of source file returned wrong inode: %s\n"),
+ srcname);
+ unlink(target);
+ goto out;
+ }
+
+ /* switch to the owner's id, to keep quota in line */
+ if (fchown(tfd, bstatbuf.bs_uid, bstatbuf.bs_gid) < 0) {
+ fprintf(stderr, _("failed to fchown tmpfile %s: %s\n"),
+ target, strerror(errno));
+ close(tfd);
+ goto out;
+ }
+
+ /* unlink src */
+ error = unlink(srcname);
+ if (error < 0) {
+ fprintf(stderr, _("unable to remove source file: %s"),
+ srcname);
+ goto out;
+ }
+
+ /* rename target source */
+ error = rename(target, srcname);
+ if (error < 0) {
+ /*
+ * We can't abort since the source file is gone now.
+ * Let the admin clean this one up.
+ */
+ fprintf(stderr, _("unable to rename file %s to %s\n"),
+ target, srcname);
+ }
+
+out_unlink:
+ error = unlink(target);
+ if (error < 0)
+ fprintf(stderr, _("unable to remove file %s\n"), target);
+
+out:
+ if (sfd >= 0)
+ close(sfd);
+ if (tfd >= 0)
+ close(tfd);
+ if (pname)
+ free(pname);
+
+ return error;
+}
+
+static int
+move_item_common(
+ const char *item_path,
+ const struct stat64 *st,
+ int type,
+ struct FTW *sntfw)
+{
+ xfs_agnumber_t agno;
+ int error = 0;
+
+ /* Skip item if is not located at an offline ag */
+ agno = ino_to_agno(st->st_ino);
+ if (agno < first_offline_agno)
+ return error;
+
+ if (vflag)
+ fprintf(stderr, _("start moving %s out of ag %d\n"),
+ item_path, agno);
+
+ switch (type) {
+ case FTW_D:
+ error = move_dir(item_path, st);
+ break;
+ case FTW_F:
+ error = move_file(item_path, st);
+ break;
+ case FTW_SL:
+ error = move_slink(item_path, st);
+ break;
+ default:
+ if (vflag) {
+ fprintf(stderr, _("skip file %s for data moving.\n"),
+ item_path);
+ }
+ /* FIXME: how to deal with such kind of file? */
+ break;
+ }
+
+ return error;
+}
+
+static int
+shrinkfs_init(
+ const char *mntdir, /* filesystem mount path */
+ int ffd, /* filesystem fd */
+ unsigned int vtype, /* which kind of volume to
shrink(data/log/realtime) */
+ long long fs_freesize, /* filesystem free space in
blocks */
+ long long newsize) /* volume size to be shrink up
to */
+{
+ xfs_agnumber_t start_agno; /* start agno to truncate */
+ xfs_agnumber_t agno; /* agno index variable */
+ xfs_agnumber_t temp; /* temporary agno variable */
+ __uint32_t agcount; /* # of ag in filesystem */
+ __uint32_t agblocks; /* # of blocks per ag */
+ __uint64_t totalsize; /* volume size */
+ int error = -1;
+
+ agcount = geom.agcount;
+ agblocks = geom.agblocks;
+
+ switch (vtype) {
+ case DVOLUME:
+ totalsize = geom.datablocks;
+ break;
+ case LVOLUME:
+ totalsize = geom.logblocks;
+ break;
+ case RVOLUME:
+ totalsize = geom.rtblocks;
+ break;
+ default:
+ fprintf(stderr, _("Invalid volume type\n"));
+ return -1;
+ }
+
+ if (newsize > totalsize) {
+ fprintf(stderr,
+ _("the new data section size %lld should less than old size %lld\n"),
+ (long long)newsize, (long long)geom.datablocks);
+ goto out;
+ } else if (newsize == totalsize) {
+ fprintf(stderr, _("data section size keep unchanged\n"));
+ goto out;
+ }
+
+ /*
+ * There is no enough free space to truncate for shrinkfs, or we
+ * can not save up the required space accrording to the new size.
+ */
+ if (newsize < (totalsize - fs_freesize)) {
+ fprintf(stderr,
+ _("can't shrink filesystem %s: no enough free space\n"),
+ mntdir);
+ goto out;
+ }
+
+ start_agno = ((newsize / agblocks) + (newsize % agblocks != 0));
+ if (vflag) {
+ fprintf(stderr,
+ _("shrinkfs will make ag to be offline starting from: %d\n"),
+ start_agno);
+ }
+
+ first_offline_agno = --start_agno;
+ /*
+ * Start to set those affected AGs to be offline.
+ */
+ for (agno = first_offline_agno; agno < agcount; agno++) {
+ error = change_ag_state(mntdir, ffd, agno,
+ XFS_AG_STATE_ALLOC_DENY);
+ if (error) {
+ temp = agno;
+ goto out_cancel;
+ }
+
+ aglist_add(agno);
+ if (vflag) {
+ fprintf(stderr, _("ag %d is offline for moving data\n"),
+ agno);
+ }
+ }
+
+ /*
+ * That's would be great if the truncated size is less that
+ * the free blocks in the latest AG, so that we can avoid
+ * going through the overall filesystem for moving data and
+ * metadata to save up spaces.
+ */
+ if (totalsize - newsize < end_ag_freesize)
+ goto out;
+
+ /*
+ * Starting to move items from offline a.g. to others.
+ */
+ error = nftw64(mntdir, move_item_common, 100, FTW_PHYS | FTW_MOUNT);
+ goto out;
+
+out_cancel:
+ for (agno = first_offline_agno; agno < temp; agno++) {
+ error = change_ag_state(mntdir, ffd, agno,
+ !XFS_AG_STATE_ALLOC_DENY);
+ if (error) {
+ fprintf(stderr, _("unable to set ag %d back to
online\n"),
+ agno);
+ goto out_free;
+ }
+ offline_agcount--;
+ if (vflag)
+ fprintf(stderr, _("ag %d is back to be online\n"),
+ agno);
+ }
+
+out_free:
+ aglist_free();
+
+out:
+ return error;
+}
+
+static int
+shrinkfs(
+ const char *mntdir,
+ int ffd,
+ int vtype,
+ __uint64_t newsize)
+{
+ xfs_shrinkfs_data_t in;
+ int error;
+
+ if (vtype != DVOLUME) {
+ fprintf(stderr,
+ _("only support data volume shrink up for now\n"));
+ return -1;
+ }
+
+ in.newblocks = newsize;
+ in.imaxpct = mflag ? imaxpct : geom.imaxpct;
+ error = xfsctl(mntdir, ffd, XFS_IOC_FSSHRINKFS_DATA, &in);
+ if (error < 0) {
+ if (errno == EWOULDBLOCK) {
+ fprintf(stderr, _(
+ "%s: shrinkfs operation in progress already\n"),
+ progname);
+ } else {
+ fprintf(stderr, _(
+ "%s: XFS_IOC_FSSHRINKFS_DATA xfsctl failed:
%s\n"),
+ progname, strerror(errno));
+ }
+ }
+
+ return error;
+}
+
+/*
+ * Show filesystem statistics after shrinking up.
+ */
+static int
+shrinkfs_done(
+ const char *fname,
+ int ffd)
+{
+ xfs_fsop_geom_v1_t ngeom; /* new fs geometry */
+ __uint32_t state;
+ int agno;
+ int error;
+
+ /*
+ * Free those affected AGs from the list and turn them back
+ * to online.
+ */
+ for (agno = first_offline_agno; agno < offline_agcount; agno++) {
+ error = change_ag_state(fname, ffd, agno,
+ !XFS_AG_STATE_ALLOC_DENY);
+ if (error)
+ goto out_free_aglist;
+ }
+
+ /*
+ * Verify that if those AGs state are changed back to online.
+ */
+ for (agno = first_offline_agno; agno < offline_agcount; agno++) {
+ state = get_ag_state(fname, ffd, agno);
+ if (state < 0)
+ goto out_free_aglist;
+ if (state != 0) {
+ fprintf(stderr, _("ag %d state: %d is wrong\n"),
+ agno, state);
+ goto out_free_aglist;
+ }
+
+ if (vflag) {
+ fprintf(stderr, _("ag %d is back to online\n"),
+ agno);
+ }
+ }
+
+ /*
+ * Show current filesystem geomerty.
+ */
+ if (xfsctl(fname, ffd, XFS_IOC_FSGEOMETRY_V1, &ngeom) < 0) {
+ fprintf(stderr,
+ _("XFS_IOC_FSGEOMETRY xfsctl failed: %s\n"),
+ strerror(errno));
+ error = -1;
+ goto out;
+ }
+
+ if (geom.datablocks != ngeom.datablocks)
+ fprintf(stderr, _("data blocks changed from %lld to %lld\n"),
+ (long long)geom.datablocks, (long
long)ngeom.datablocks);
+ if (geom.imaxpct != ngeom.imaxpct)
+ fprintf(stderr, _("inode max percent changed from %d to %d\n"),
+ geom.imaxpct, ngeom.imaxpct);
+ if (geom.logblocks != ngeom.logblocks)
+ fprintf(stderr, _("log blocks changed from %d to %d\n"),
+ geom.logblocks, ngeom.logblocks);
+ if ((geom.logstart == 0) != (ngeom.logstart == 0))
+ fprintf(stderr, _("log changed from %s to %s\n"),
+ geom.logstart ? _("internal") : _("external"),
+ ngeom.logstart ? _("internal") : _("external"));
+ if (geom.rtblocks != ngeom.rtblocks)
+ fprintf(stderr, _("realtime blocks changed from %lld to
%lld\n"),
+ (long long)geom.rtblocks, (long long)ngeom.rtblocks);
+ if (geom.rtextsize != ngeom.rtextsize)
+ fprintf(stderr, _("realtime extent size changed from %d to
%d\n"),
+ geom.rtextsize, ngeom.rtextsize);
+
+ goto out;
+
+out_free_aglist:
+ aglist_free();
+
+out:
+ return error;
+}
+
+int
+get_fsgeom(
+ const char *mntdir,
+ int ffd)
+{
+ /* Get the current filesystem size & geometry */
+ if (xfsctl(mntdir, ffd, XFS_IOC_FSGEOMETRY, &geom) < 0) {
+ /*
+ * OK, new xfsctl barfed - back off and try earlier version
+ * as we're probably running an older kernel version.
+ * Only field added in the v2 geometry xfsctl is "logsunit"
+ * so we'll zero that out for later display (as zero).
+ */
+ geom.logsunit = 0;
+ if (xfsctl(mntdir, ffd, XFS_IOC_FSGEOMETRY_V1, &geom) < 0) {
+ fprintf(stderr, _(
+ "%s: cannot determine geometry of filesystem mounted at %s:
%s\n"),
+ progname, mntdir, strerror(errno));
+ close(ffd);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+void
+usage(void)
+{
+ fprintf(stderr, _(
+"Usage: %s [options] device mountpoint\n\n"
+"Options:\n\
+ -d shrink data/metadata section\n\
+ -l shrink log section\n\
+ -r shrink realtime section\n\
+ -n don't change anything, just show geometry\n\
+ -D size shrink data/metadata section to size blks\n\
+ -L size shrink log section to size blks\n\
+ -R size shrink realtime section to size blks\n\
+ -m imaxpct set inode max percent to imaxpct\n\
+ -v print verbose shrinking info\n\
+ -V print version information\n"),
+ program);
+ exit(2);
+}
+
+int
+main(
+ int argc,
+ char **argv)
+{
+ libxfs_init_t xi; /* libxfs structure */
+ fs_path_t *fs; /* mount point information */
+ long long dsize = 0; /* new data size in fs blocks */
+ long long lsize = 0; /* new log size in fs blocks */
+ long long rsize = 0; /* new realtime size in fs
blocks */
+ long long fs_freesize = 0;/* device size in 512-byte
blocks */
+ char *device; /* data device name */
+ char *mntdir; /* mount point name */
+ char *fsname; /* filesystem name*/
+ char *datadev; /* data device name */
+ char *logdev; /* log device name */
+ char *rtdev; /* realtime device name */
+ int attrversion; /* attribute version number */
+ int dirversion; /* directory version number */
+ int logversion; /* log version number */
+ int ffd = 0; /* mount point file descriptor
*/
+ int dflag = 0; /* -d flag, shrink data area */
+ int lflag = 0; /* -l flag, shrink log area */
+ int rflag = 0; /* -r flag, shrink realtime
area */
+ int nflag = 0; /* -n flag */
+ int done = 0; /* shrinkfs performed? */
+ int isint; /* log is currently internal */
+ int ci; /* ASCII case-insensitive fs */
+ int c; /* current option character */
+ int lazycount; /* lazy superblock counters */
+ int error = 0; /* we have hit an error */
+
+ while ((c = getopt(argc, argv, "dlnrvVF:f:m:D:L:R:")) != EOF) {
+ switch (c) {
+ case 'F':
+ fs_freesize = strtoll(optarg, NULL, 10);
+ break;
+ case 'f':
+ end_ag_freesize = strtol(optarg, NULL, 10);
+ break;
+ case 'D':
+ dsize = strtoll(optarg, NULL, 10);
+ break;
+ case 'L':
+ lsize = strtoll(optarg, NULL, 10);
+ break;
+ case 'R':
+ rsize = strtoll(optarg, NULL, 10);
+ break;
+ case 'm':
+ mflag = 1;
+ imaxpct = atoi(optarg);
+ break;
+ case 'd':
+ dflag = 1;
+ break;
+ case 'l':
+ lflag = 1;
+ break;
+ case 'r':
+ rflag = 1;
+ break;
+ case 'n':
+ nflag = 1;
+ break;
+ case 'v':
+ vflag = 1;
+ break;
+ case 'V':
+ printf(_("%s version %s\n"), program, VERSION);
+ exit(0);
+ case '?':
+ default:
+ usage();
+ }
+ }
+
+ if (argc - optind != 2)
+ usage();
+
+ if (dflag + lflag + rflag > 1) {
+ fprintf(stderr,
+ _("cannot shrink multiple filesystems for one time\n"));
+ exit(1);
+ }
+
+ if (!fs_freesize || end_ag_freesize < 0) {
+ fprintf(stderr, _("Invalid options\n"));
+ exit(1);
+ }
+
+ device = argv[optind++];
+ mntdir = argv[optind];
+ if (!device || !mntdir) {
+ fprintf(stderr, _("Invalid options\n"));
+ exit(1);
+ }
+
+ fs_table_initialise(0, NULL, 0, NULL);
+ fs = fs_table_lookup(mntdir, FS_MOUNT_POINT);
+ if (!fs) {
+ fprintf(stderr,
+ _("%s: %s is not a mounted XFS filesystem\n"),
+ progname, argv[optind]);
+ goto out;
+ }
+
+ fsname = fs->fs_dir;
+ datadev = fs->fs_name;
+ logdev = fs->fs_log;
+ rtdev = fs->fs_rt;
+
+ if (strcmp(fsname, mntdir)) {
+ fprintf(stderr, _("mount point %s is invalid\n"), mntdir);
+ goto out;
+ }
+ if (dflag && strcmp(datadev, device)) {
+ fprintf(stderr, _("device %s is invalid\n"), device);
+ goto out;
+ }
+ if (lflag && logdev && strcmp(logdev, device)) {
+ fprintf(stderr, _("device %s is invalid\n"), device);
+ goto out;
+ }
+ if (rflag && rtdev && strcmp(rtdev, device)) {
+ fprintf(stderr, _("device %s is invalid\n"), device);
+ goto out;
+ }
+
+ ffd = open(mntdir, O_RDONLY);
+ if (ffd < 0) {
+ fprintf(stderr, _("cannot open %s: %s\n"),
+ mntdir, strerror(errno));
+ goto out;
+ }
+
+ if (!platform_test_xfs_fd(ffd)) {
+ fprintf(stderr,
+ _("%s: specified file [\"%s\"] is not on an XFS filesystem\n"),
+ progname, mntdir);
+ goto out;
+ }
+
+ if (get_fsgeom(mntdir, ffd) < 0)
+ goto out;
+
+ isint = geom.logstart > 0;
+ lazycount = geom.flags & XFS_FSOP_GEOM_FLAGS_LAZYSB ? 1 : 0;
+ dirversion = geom.flags & XFS_FSOP_GEOM_FLAGS_DIRV2 ? 2 : 1;
+ logversion = geom.flags & XFS_FSOP_GEOM_FLAGS_LOGV2 ? 2 : 1;
+ attrversion = geom.flags & XFS_FSOP_GEOM_FLAGS_ATTR2 ? 2 : \
+ (geom.flags & XFS_FSOP_GEOM_FLAGS_ATTR ? 1 : 0);
+ ci = geom.flags & XFS_FSOP_GEOM_FLAGS_DIRV2CI ? 1 : 0;
+
+ /*
+ * Specifies that no change to the filesystem is to be made.
+ * The filesystem geometry is printed, but no shrink ocurrs.
+ */
+ if (nflag) {
+ report_info(datadev, isint, logdev, rtdev, lazycount,
+ dirversion, logversion, attrversion, ci);
+ goto out;
+ }
+
+ if (getuid() != 0) {
+ fprintf(stderr,
+ _("cannot shrink filesystem %s: Permission denied\n"),
+ mntdir);
+ goto out;
+ }
+
+ /*
+ * Need root access from here on (using raw devices)...
+ */
+ memset(&xi, 0, sizeof(xi));
+ xi.dname = datadev;
+ xi.logname = logdev;
+ xi.rtname = rtdev;
+ xi.isreadonly = LIBXFS_ISREADONLY;
+ if (!libxfs_init(&xi))
+ goto out;
+
+ /*
+ * Check we got the info for all the sections we are trying to modify.
+ */
+ if (dflag && !xi.ddev) {
+ fprintf(stderr,
+ _("%s: failed to access data device for %s\n"),
+ progname, fsname);
+ goto out;
+ } else if (lflag && !xi.logdev) {
+ fprintf(stderr,
+ _("%s: failed to access log device for %s\n"),
+ progname, fsname);
+ goto out;
+ } else if (rflag && !xi.rtdev) {
+ fprintf(stderr,
+ _("%s: failed to access realtime device for %s\n"),
+ progname, fsname);
+ goto out;
+ }
+
+ report_info(datadev, isint, logdev, rtdev, lazycount,
+ dirversion, logversion, attrversion, ci);
+
+ /* Get agino_log which would be used at ino_to_agno() */
+ get_aginolog();
+
+ if (dflag && dsize > 0) {
+ error = shrinkfs_init(fsname, ffd, DVOLUME,
+ fs_freesize, dsize);
+ if (error)
+ goto out;
+
+ error = shrinkfs(fsname, ffd, DVOLUME, dsize);
+ if (error)
+ goto out;
+ done = 1;
+ } else if (lflag && lsize > 0) {
+ error = shrinkfs_init(fsname, ffd, LVOLUME,
+ fs_freesize, lsize);
+ if (error)
+ goto out;
+
+ error = shrinkfs(fsname, ffd, LVOLUME, lsize);
+ if (error)
+ goto out;
+ done = 1;
+ } else if (rflag && rsize > 0) {
+ error = shrinkfs_init(fsname, ffd, RVOLUME,
+ fs_freesize, rsize);
+ if (error)
+ goto out;
+
+ error = shrinkfs(fsname, ffd, RVOLUME, rsize);
+ if (error)
+ goto out;
+ done = 1;
+ }
+
+ /* options are invalid */
+ if (!done) {
+ fprintf(stderr, _("%s: Invalid arguments %s\n"),
+ program, fsname);
+ goto out;
+ }
+
+ error = shrinkfs_done(fsname, ffd);
+
+out:
+ if (ffd > 0)
+ close(ffd);
+
+ return error;
+}
diff --git a/shrinkfs/xfs_shrinkfs.sh b/shrinkfs/xfs_shrinkfs.sh
new file mode 100755
index 0000000..319f09b
--- /dev/null
+++ b/shrinkfs/xfs_shrinkfs.sh
@@ -0,0 +1,99 @@
+#!/bin/sh -f
+
+DEVICE=""
+declare -A FSINFO
+FS_FREEBLKS=0;
+END_AG_FREEBLKS=0;
+
+_check_permission()
+{
+ [ `id -u ` -ne 0 ] && {
+ echo "Abort shrinking: permission denied" && exit 1
+ }
+}
+
+_get_ag_freeblks()
+{
+ local __agno=$1
+ local -a __temp
+
+ __temp=(`xfs_db -r -c "agf $__agno" -c 'p freeblks' -c 'p btreeblks' \
+ $DEVICE|cut -d' ' -f 3,6`)
+ echo $((${__temp[0]}-${__temp[1]}))
+}
+
+#
+# Fetch the free blocks of lastest A.G.
+#
+_get_lastest_agfreeblks()
+{
+ local __agcount=0
+ local __agno=0
+
+ __agcount=`xfs_db -r -c 'sb' -c 'p agcount' $DEVICE|cut -d' ' -f 3`
+ __agno=$(($__agcount - 1))
+
+ END_AG_FREEBLKS=$(_get_ag_freeblks $__agno)
+}
+
+_get_fsfreeblks()
+{
+ local __fs_freebllks=0
+ local __ag_freeblks=0
+ local __agcount=0
+ local __i=0
+
+ __agcount=`xfs_db -r -c 'sb' -c 'p agcount' $DEVICE|cut -d' ' -f 3`
+ while [ $__i -lt ${__agcount} ]
+ do
+ __ag_freeblks=$(_get_ag_freeblks $__i)
+ let "__fs_freeblks += __ag_freeblks"
+ let "__i += 1"
+ done
+
+ FS_FREEBLKS=${__fs_freeblks}
+}
+
+OPTS=" "
+USAGE="Usage: xfs_shrinkfs [-V] [options] device mountpoint"
+
+while getopts "dlrnvD:L:R:V" c
+do
+ case $c in
+ d) OPTS=$OPTS"-d ";;
+ l) OPTS=$OPTS"-l ";;
+ r) OPTS=$OPTS"-r ";;
+ m) OPTS=$OPTS"-m ";;
+ n) OPTS=$OPTS"-n ";;
+ v) OPTS=$OPTS"-v ";;
+ D) OPTS=$OPTS"-D $OPTARG";;
+ L) OPTS=$OPTS"-L $OPTARG";;
+ R) OPTS=$OPTS"-R $OPTARG";;
+ V) xfs_shrink -V
+ status=$?
+ exit $status
+ ;;
+ \?) echo $USAGE 1>&2
+ exit 2
+ ;;
+ esac
+done
+
+set -- extra $@
+shift $OPTIND
+case $# in
+ 2)
+ DEVICE="$1"
+ MNTDIR="$2"
+ _check_permission
+ _get_fsfreeblks "${DEVICE}"
+ _get_lastest_agfreeblks
+ echo ${FS_FREEBLKS} ${END_AG_FREEBLKS} ${OPTS}
+ xfs_shrink -F "$FS_FREEBLKS" -f "$END_AG_FREEBLKS" $OPTS
"${DEVICE}" "${MNTDIR}"
+ status=$?
+ ;;
+ *) echo $USAGE 1>&2
+ exit 2
+ ;;
+esac
+exit $status
--
1.7.9.5
|