Hello, This is a revised version of xfs_reno(8), originally from Barry. Currently, it works for regular files but failed for swapping directories. I post it here since I tried to test the inode swap ioctl(2) via this program, but it does not means that I took over this task. I'll only continue to improve it if nobody is working on this tools. Thanks, -Jeff Signed-off-by: Barry Naujok <bnaujok@xxxxxxx> Signed-off-by: Jie Liu <jeff.liu@xxxxxxxxxx> --- Makefile | 2 +- include/xfs_dfrag.h | 20 + include/xfs_fs.h | 8 + reno/Makefile | 34 ++ reno/xfs_reno.c | 1647 +++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 1710 insertions(+), 1 deletion(-) create mode 100644 reno/Makefile create mode 100644 reno/xfs_reno.c diff --git a/Makefile b/Makefile index 0bdc5e8..24c0997 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 reno m4 man doc po debian SUBDIRS = include $(LIB_SUBDIRS) $(TOOL_SUBDIRS) 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 faac5af..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 @@ -483,6 +487,10 @@ typedef struct xfs_handle { #define XFS_IOC_ATTRMULTI_BY_HANDLE _IOW ('X', 123, struct xfs_fsop_attrmulti_handlereq) #define XFS_IOC_FSGEOMETRY _IOR ('X', 124, struct xfs_fsop_geom) #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/reno/Makefile b/reno/Makefile new file mode 100644 index 0000000..3d9f165 --- /dev/null +++ b/reno/Makefile @@ -0,0 +1,34 @@ +# +# Copyright (c) 2000-2001,2004-2005 Silicon Graphics, Inc. All Rights Reserved. +# + +TOPDIR = .. +include $(TOPDIR)/include/builddefs + +LTCOMMAND = xfs_reno + +CFILES = xfs_reno.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_reno $(PKG_SBIN_DIR)/xfs_reno +install-dev: + +-include .dep diff --git a/reno/xfs_reno.c b/reno/xfs_reno.c new file mode 100644 index 0000000..0d7ab20 --- /dev/null +++ b/reno/xfs_reno.c @@ -0,0 +1,1647 @@ +/* + * Copyright (c) 2007 Silicon Graphics, Inc. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it would be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * xfs_reno - renumber 64-bit inodes + * + * xfs_reno [-f] [-n] [-p] [-q] [-v] [-P seconds] path ... + * xfs_reno [-r] path ... + * + * Renumbers all inodes > 32 bits into 32 bit space. Requires the filesytem + * to be mounted with inode32. + * + * -f force conversion on all inodes rather than just + * those with a 64bit inode number. + * -n nothing, do not renumber inodes + * -p show progress status. + * -q quiet, do not report progress, only errors. + * -v verbose, more -v's more verbose. + * -P seconds set the interval for the progress status in seconds. + * -r recover from an interrupted run. + */ + +#include <xfs/xfs.h> + +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <ftw.h> +#include <libgen.h> +#include <malloc.h> +#include <signal.h> +#include <stdint.h> +#include <sys/ioctl.h> +#include <attr/attributes.h> +#include <xfs/xfs_dfrag.h> +#include <xfs/xfs_inum.h> + +#define SCAN_PHASE 0x00 +#define DIR_PHASE 0x10 /* nothing done or all done */ +#define DIR_PHASE_1 0x11 /* temp dir created */ +#define DIR_PHASE_2 0x12 /* swapped extents and inodes */ +#define DIR_PHASE_3 0x13 /* src dir removed */ +#define DIR_PHASE_MAX 0x13 /* renamed temp to source name */ +#define FILE_PHASE 0x20 /* nothing done or all done */ +#define FILE_PHASE_1 0x21 /* temp file created */ +#define FILE_PHASE_2 0x22 /* swapped extents and inodes */ +#define FILE_PHASE_3 0x23 /* unlinked source */ +#define FILE_PHASE_4 0x24 /* hard links copied */ +#define FILE_PHASE_MAX 0x24 /* renamed temp to source name */ +#define SLINK_PHASE 0x30 /* nothing done or all done */ +#define SLINK_PHASE_1 0x31 /* temp symlink created */ +#define SLINK_PHASE_2 0x32 /* symlink attrs copied */ +#define SLINK_PHASE_3 0x33 /* unlinked source */ +#define SLINK_PHASE_4 0x34 /* hard links copied */ +#define SLINK_PHASE_MAX 0x34 /* renamed temp to source name */ + +static void update_recoverfile(void); +#define SET_PHASE(x) (cur_phase = x, update_recoverfile()) + +#define LOG_ERR 0 +#define LOG_NORMAL 1 +#define LOG_INFO 2 +#define LOG_DEBUG 3 +#define LOG_NITTY 4 + +#define NH_BUCKETS 65536 +#define NH_HASH(ino) (nodehash + ((ino) % NH_BUCKETS)) + +typedef struct { + xfs_ino_t ino; + int ftw_flags; + nlink_t numpaths; + char **paths; +} bignode_t; + +typedef struct { + bignode_t *nodes; + uint64_t listlen; + uint64_t lastnode; +} nodelist_t; + +static const char *cmd_prefix = "xfs_reno_"; + +static char *progname; +static int log_level = LOG_NORMAL; +static int force_all; +static nodelist_t *nodehash; +static int realuid; +static uint64_t numdirnodes; +static uint64_t numfilenodes; +static uint64_t numslinknodes; +static uint64_t numdirsdone; +static uint64_t numfilesdone; +static uint64_t numslinksdone; +static int poll_interval; +static time_t starttime; +static bignode_t *cur_node; +static char *cur_target; +static int cur_phase; +static int highest_numpaths; +static char *recover_file; +static int recover_fd; +static volatile int poll_output; +static int global_rval; + +/* + * message handling + */ +static void +log_message( + int level, + char *fmt, ...) +{ + char buf[1024]; + va_list ap; + + if (log_level < level) + return; + + va_start(ap, fmt); + vsnprintf(buf, 1024, fmt, ap); + va_end(ap); + + printf("%c%s: %s\n", poll_output ? '\n' : '\r', progname, buf); + poll_output = 0; +} + +static void +err_message( + char *fmt, ...) +{ + char buf[1024]; + va_list ap; + + va_start(ap, fmt); + vsnprintf(buf, 1024, fmt, ap); + va_end(ap); + + fprintf(stderr, "%c%s: %s\n", poll_output ? '\n' : '\r', progname, buf); + poll_output = 0; +} + +static void +err_nomem(void) +{ + err_message(_("Out of memory")); +} + +static void +err_open( + const char *s) +{ + err_message(_("Cannot open %s: %s"), s, strerror(errno)); +} + +static void +err_not_xfs( + const char *s) +{ + err_message(_("%s is not on an XFS filesystem"), s); +} + +static void +err_stat( + const char *s) +{ + err_message(_("Cannot stat %s: %s\n"), s, strerror(errno)); +} + +static void +err_swapino( + int err, + const char *srcname) +{ + if (log_level >= LOG_DEBUG) { + switch (err) { + case EIO: + err_message(_("Filesystem is going down: %s: %s"), + srcname, strerror(err)); + break; + + default: + err_message(_("Swap inode failed: %s: %s"), + srcname, strerror(err)); + break; + } + } else + err_message(_("Swap inode failed: %s: %s"), + srcname, strerror(err)); +} + +/* + * usage message + */ +static void +usage(void) +{ + fprintf(stderr, _("%s [-fnpqv] [-P <interval>] [-r] <path>\n"), + progname); + exit(1); +} + + +/* + * XFS interface functions + */ + +static 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); +} + +static int +xfs_swapino(int fd, xfs_swapino_t *iu) +{ + return ioctl(fd, XFS_IOC_SWAPINO, iu); +} + +static int +xfs_getxattr(int fd, struct fsxattr *attr) +{ + return ioctl(fd, XFS_IOC_FSGETXATTR, attr); +} + +/* + * A hash table of inode numbers and associated paths. + */ +static nodelist_t * +init_nodehash(void) +{ + int i; + + nodehash = calloc(NH_BUCKETS, sizeof(nodelist_t)); + if (nodehash == NULL) { + err_nomem(); + return NULL; + } + + for (i = 0; i < NH_BUCKETS; i++) { + nodehash[i].nodes = NULL; + nodehash[i].lastnode = 0; + nodehash[i].listlen = 0; + } + + return nodehash; +} + +static void +free_nodehash(void) +{ + int i, j, k; + + for (i = 0; i < NH_BUCKETS; i++) { + bignode_t *nodes = nodehash[i].nodes; + + for (j = 0; j < nodehash[i].lastnode; j++) { + for (k = 0; k < nodes[j].numpaths; k++) { + free(nodes[j].paths[k]); + } + free(nodes[j].paths); + } + + free(nodes); + } + free(nodehash); +} + +static nlink_t +add_path( + bignode_t *node, + const char *path) +{ + node->paths = realloc(node->paths, + sizeof(char *) * (node->numpaths + 1)); + if (node->paths == NULL) { + err_nomem(); + exit(1); + } + + node->paths[node->numpaths] = strdup(path); + if (node->paths[node->numpaths] == NULL) { + err_nomem(); + exit(1); + } + + node->numpaths++; + if (node->numpaths > highest_numpaths) + highest_numpaths = node->numpaths; + + return node->numpaths; +} + +static bignode_t * +add_node( + nodelist_t *list, + xfs_ino_t ino, + int ftw_flags, + const char *path) +{ + bignode_t *node; + + if (list->lastnode >= list->listlen) { + list->listlen += 500; + list->nodes = realloc(list->nodes, + sizeof(bignode_t) * list->listlen); + if (list->nodes == NULL) { + err_nomem(); + return NULL; + } + } + + node = list->nodes + list->lastnode; + + node->ino = ino; + node->ftw_flags = ftw_flags; + node->paths = NULL; + node->numpaths = 0; + add_path(node, path); + + list->lastnode++; + + return node; +} + +static bignode_t * +find_node( + xfs_ino_t ino) +{ + int i; + nodelist_t *nodelist; + bignode_t *nodes; + + nodelist = NH_HASH(ino); + nodes = nodelist->nodes; + + for(i = 0; i < nodelist->lastnode; i++) { + if (nodes[i].ino == ino) { + return &nodes[i]; + } + } + + return NULL; +} + +static bignode_t * +add_node_path( + xfs_ino_t ino, + int ftw_flags, + const char *path) +{ + nodelist_t *nodelist; + bignode_t *node; + + log_message(LOG_NITTY, "add_node_path: ino %llu, path %s", ino, path); + + node = find_node(ino); + if (node == NULL) { + nodelist = NH_HASH(ino); + return add_node(nodelist, ino, ftw_flags, path); + } + + add_path(node, path); + return node; +} + +static void +dump_node( + char *msg, + bignode_t *node) +{ + int k; + + if (log_level < LOG_DEBUG) + return; + + log_message(LOG_DEBUG, "%s: %llu %llu %s", msg, node->ino, + node->numpaths, node->paths[0]); + + for (k = 1; k < node->numpaths; k++) + log_message(LOG_DEBUG, "\t%s", node->paths[k]); +} + +static void +dump_nodehash(void) +{ + int i, j; + + if (log_level < LOG_NITTY) + return; + + for (i = 0; i < NH_BUCKETS; i++) { + bignode_t *nodes = nodehash[i].nodes; + for (j = 0; j < nodehash[i].lastnode; j++, nodes++) + dump_node("nodehash", nodes); + } +} + +static int +for_all_nodes( + int (*fn)(bignode_t *node), + int ftw_flags, + int quit_on_error) +{ + int i; + int j; + int rval = 0; + + for (i = 0; i < NH_BUCKETS; i++) { + bignode_t *nodes = nodehash[i].nodes; + + for (j = 0; j < nodehash[i].lastnode; j++, nodes++) { + if (nodes->ftw_flags == ftw_flags) { + rval = fn(nodes); + if (rval && quit_on_error) + goto quit; + } + } + } + +quit: + return rval; +} + +/* + * Adds appropriate files to the inode hash table + */ +static int +nftw_addnodes( + const char *path, + const struct stat64 *st, + int flags, + struct FTW *sntfw) +{ + if (st->st_ino <= XFS_MAXINUMBER_32 && !force_all) + return 0; + + if (flags == FTW_F) + numfilenodes++; + else if (flags == FTW_D) + numdirnodes++; + else if (flags == FTW_SL) + numslinknodes++; + else + return 0; + + add_node_path(st->st_ino, flags, path); + + return 0; +} + +static int +process_dir( + bignode_t *node) +{ + xfs_bstat_t bstatbuf; + xfs_swapino_t si; + struct stat64 st; + struct fsxattr fsx; + char *srcname = NULL; + char *pname = NULL; + char target[PATH_MAX] = ""; + int sfd = -1; + int tfd = -1; + int rval = 0; + + SET_PHASE(DIR_PHASE); + + dump_node("directory", node); + + cur_node = node; + srcname = node->paths[0]; + + if (stat64(srcname, &st) < 0) { + if (errno != ENOENT) { + err_stat(srcname); + global_rval |= 2; + } + goto quit; + } + if (st.st_ino <= XFS_MAXINUMBER_32 && !force_all) { + /* + * This directory has already changed ino's, probably due + * to being moved during processing of a parent directory. + */ + log_message(LOG_DEBUG, "process_dir: skipping %s", srcname); + goto quit; + } + + rval = 1; + + sfd = open(srcname, O_RDONLY); + if (sfd == -1) { + err_open(srcname); + goto quit; + } + + if (!platform_test_xfs_fd(sfd)) { + err_not_xfs(srcname); + goto quit; + } + + if (fsync(sfd) < 0) { + err_message(_("sync failed: %s: %s"), + srcname, strerror(errno)); + goto quit; + } + + if (xfs_getxattr(sfd, &fsx) < 0) { + err_message(_("failed to get inode attrs: %s"), srcname); + goto quit; + } + if (fsx.fsx_xflags & (XFS_XFLAG_IMMUTABLE | XFS_XFLAG_APPEND)) { + err_message(_("%s: immutable/append, ignoring"), srcname); + global_rval |= 2; + goto quit; + } + + if (realuid != 0 && realuid != st.st_uid) { + errno = EACCES; + err_open(srcname); + goto quit; + } + + /* mkdir parent/target */ + pname = strdup(srcname); + if (pname == NULL) { + err_nomem(); + goto quit; + } + dirname(pname); + sprintf(target, "%s/%sXXXXXX", pname, cmd_prefix); + if (mkdtemp(target) == NULL) { + err_message(_("Unable to create directory copy: %s"), srcname); + goto quit; + } + tfd = open(target, O_RDONLY); + if (tfd == -1) { + err_open(target); + goto quit; + } + + cur_target = strdup(target); + if (!cur_target) { + err_nomem(); + goto quit; + } + + SET_PHASE(DIR_PHASE_1); + + if (xfs_bulkstat_single(sfd, &st.st_ino, &bstatbuf) < 0) { + err_message(_("unable to bulkstat source file: %s"), + srcname); + unlink(target); + goto quit; + } + + /* switch to the owner's id, to keep quota in line */ + if (fchown(tfd, bstatbuf.bs_uid, bstatbuf.bs_gid) < 0) { + err_message(_("unable to swith to the owner's id: %s"), + target); + close(tfd); + return -1; + } + + /* swap the inodes */ + si.si_version = XFS_SI_VERSION; + si.si_fdtarget = tfd; + si.si_fdtmp = sfd; + rval = xfs_swapino(tfd, &si); + if (rval < 0) { + err_swapino(rval, srcname); + goto quit_unlink; + } + + fsync(sfd); + fsync(tfd); + + SET_PHASE(DIR_PHASE_2); + + /* rmdir source directory */ + rval = execlp("rm", "rm", "-rf", srcname, (char *)0); + if (rval < 0) { + err_message(_("unable to remove directory %s as %s"), + srcname, strerror(errno)); + goto quit_unlink; + } + + SET_PHASE(DIR_PHASE_3); + /* rename cur_target src */ + rval = rename(target, srcname); + if (rval != 0) { + /* + * we can't abort since the src dir is now gone. + * let the admin clean this one up + */ + err_message(_("unable to rename directory: %s to %s"), + cur_target, srcname); + } + goto quit; + +quit_unlink: + rval = rmdir(target); + if (rval != 0) + err_message(_("unable to remove directory: %s"), target); + +quit: + SET_PHASE(DIR_PHASE); + + if (sfd >= 0) + close(sfd); + if (tfd >= 0) + close(tfd); + + free(pname); + free(cur_target); + + cur_target = NULL; + cur_node = NULL; + + numdirsdone++; + return rval; +} + +static int +process_file( + bignode_t *node) +{ + int sfd = -1; + int tfd = -1; + int i = 0; + int rval = 0; + struct stat64 st; + char *srcname = NULL; + char *pname = NULL; + xfs_swapino_t si; + xfs_bstat_t bstatbuf; + struct fsxattr fsx; + char target[PATH_MAX] = ""; + + cur_node = node; + srcname = node->paths[0]; + + if (stat64(srcname, &st) < 0) { + if (errno != ENOENT) { + err_stat(srcname); + global_rval |= 2; + } + goto quit; + } + + /* this file has changed, and no longer needs processing */ + if (st.st_ino <= XFS_MAXINUMBER_32 && !force_all) + goto quit; + + rval = 1; + /* open and sync source */ + sfd = open(srcname, O_RDWR | O_DIRECT); + if (sfd < 0) { + err_open(srcname); + goto quit; + } + + if (!platform_test_xfs_fd(sfd)) { + err_not_xfs(srcname); + goto quit; + } + + if (fsync(sfd) < 0) { + err_message(_("sync failed: %s: %s"), + srcname, strerror(errno)); + goto quit; + } + + + /* + * 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_reno 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 ) { + if (log_level >= LOG_DEBUG) + err_message("locking check failed: %s", + srcname); + global_rval |= 2; + goto quit; + } + if (fl.l_type != F_UNLCK) { + if (log_level >= LOG_DEBUG) + err_message("mandatory lock: %s: ignoring", + srcname); + global_rval |= 2; + goto quit; + } + } + + if (xfs_getxattr(sfd, &fsx) < 0) { + err_message(_("failed to get inode attrs: %s"), srcname); + goto quit; + } + if (fsx.fsx_xflags & (XFS_XFLAG_IMMUTABLE | XFS_XFLAG_APPEND)) { + err_message(_("%s: immutable/append, ignoring"), srcname); + global_rval |= 2; + goto quit; + } + + if (realuid != 0 && realuid != st.st_uid) { + errno = EACCES; + err_open(srcname); + goto quit; + } + + /* creat target */ + pname = strdup(srcname); + if (pname == NULL) { + err_nomem(); + goto quit; + } + dirname(pname); + + sprintf(target, "%s/%sXXXXXX", pname, cmd_prefix); + tfd = mkstemp(target); + if (tfd == -1) { + err_message("unable to create file copy"); + goto quit; + } + cur_target = strdup(target); + if (cur_target == NULL) { + err_nomem(); + goto quit; + } + + SET_PHASE(FILE_PHASE_1); + + if (xfs_bulkstat_single(sfd, &st.st_ino, &bstatbuf) < 0) { + err_message(_("unable to bulkstat source file: %s"), + srcname); + unlink(target); + goto quit; + } + if (bstatbuf.bs_ino != st.st_ino) { + err_message(_("bulkstat of source file returned wrong inode: %s"), + srcname); + unlink(target); + goto quit; + } + + ftruncate64(tfd, bstatbuf.bs_size); + + /* switch to the owner's id, to keep quota in line */ + if (fchown(tfd, bstatbuf.bs_uid, bstatbuf.bs_gid) < 0) { + err_message(_("unable to swith to the owner's id: %s"), + target); + close(tfd); + return -1; + } + + /* swapino src target */ + si.si_version = XFS_SI_VERSION; + si.si_fdtarget = tfd; + si.si_fdtmp = sfd; + + /* swap the inodes */ + rval = xfs_swapino(tfd, &si); + if (rval < 0) { + err_swapino(rval, srcname); + goto quit_unlink; + } + + SET_PHASE(FILE_PHASE_2); + + /* unlink src */ + rval = unlink(srcname); + if (rval != 0) { + err_message(_("unable to remove file %s: %s"), + srcname, strerror(errno)); + goto quit; + } + + SET_PHASE(FILE_PHASE_3); + + /* rename target src */ + rval = rename(target, srcname); + if (rval != 0) { + /* + * we can't abort since the src file is now gone. + * let the admin clean this one up + */ + err_message(_("unable to rename file: %s to %s"), + target, srcname); + goto quit; + } + + SET_PHASE(FILE_PHASE_4); + + /* for each hardlink, unlink and creat pointing to target */ + for (i = 1; i < node->numpaths; i++) { + /* unlink src */ + rval = unlink(node->paths[i]); + if (rval != 0) { + err_message(_("unable to remove file: %s"), + node->paths[i]); + goto quit; + } + + rval = link(srcname, node->paths[i]); + if (rval != 0) { + err_message("unable to link to file: %s", srcname); + goto quit; + } + numfilesdone++; + } + goto quit; + +quit_unlink: + rval = unlink(target); + if (rval != 0) + err_message(_("unable to remove file: %s"), target); + +quit: + + SET_PHASE(FILE_PHASE); + + if (sfd >= 0) + close(sfd); + if (tfd >= 0) + close(tfd); + + free(pname); + free(cur_target); + + cur_target = NULL; + cur_node = NULL; + + numfilesdone++; + return rval; +} + + +static int +process_slink( + bignode_t *node) +{ + struct stat64 st; + xfs_swapino_t si; + char *srcname = NULL; + char *pname = NULL; + char target[PATH_MAX] = ""; + char linkbuf[PATH_MAX]; + int i = 0; + int sfd = -1; + int tfd = -1; + int rval = 0; + + SET_PHASE(SLINK_PHASE); + + dump_node("symlink", node); + + cur_node = node; + srcname = node->paths[0]; + + bzero(&st, sizeof(st)); + bzero(&si, sizeof(si)); + + if (lstat64(srcname, &st) < 0) { + if (errno != ENOENT) { + err_stat(srcname); + global_rval |= 2; + } + goto quit; + } + if (st.st_ino <= XFS_MAXINUMBER_32 && !force_all) + /* this file has changed, and no longer needs processing */ + goto quit; + + rval = 1; + + /* open source */ + sfd = open(srcname, O_RDWR | O_DIRECT); + if (sfd < 0) { + err_open(srcname); + goto quit; + } + + i = readlink(srcname, linkbuf, sizeof(linkbuf) - 1); + if (i < 0) { + err_message(_("unable to read symlink: %s"), srcname); + goto quit; + } + linkbuf[i] = '\0'; + + if (realuid != 0 && realuid != st.st_uid) { + errno = EACCES; + err_open(srcname); + goto quit; + } + + /* create target */ + pname = strdup(srcname); + if (pname == NULL) { + err_nomem(); + goto quit; + } + dirname(pname); + + sprintf(target, "%s/%sXXXXXX", pname, cmd_prefix); + tfd = mkstemp(target); + if (tfd == -1) { + err_message(_("unable to create temp symlink name")); + goto quit; + } + cur_target = strdup(target); + if (cur_target == NULL) { + err_nomem(); + goto quit; + } + + if (symlink(linkbuf, target) != 0) { + err_message(_("unable to create symlink: %s"), target); + goto quit; + } + + SET_PHASE(SLINK_PHASE_1); + + /* swapino src target */ + si.si_version = XFS_SI_VERSION; + si.si_fdtarget = tfd; + si.si_fdtmp = sfd; + + /* swap the inodes */ + rval = xfs_swapino(tfd, &si); + if (rval < 0) { + err_swapino(rval, srcname); + goto quit; + } + + SET_PHASE(SLINK_PHASE_2); + + /* unlink src */ + rval = unlink(srcname); + if (rval != 0) { + err_message(_("unable to remove symlink: %s"), srcname); + goto quit; + } + + SET_PHASE(SLINK_PHASE_3); + + /* rename target src */ + rval = rename(target, srcname); + if (rval != 0) { + /* + * we can't abort since the src file is now gone. + * let the admin clean this one up + */ + err_message(_("unable to rename symlink: %s to %s"), + target, srcname); + goto quit; + } + + SET_PHASE(SLINK_PHASE_4); + + /* for each hardlink, unlink and creat pointing to target */ + for (i = 1; i < node->numpaths; i++) { + /* unlink src */ + rval = unlink(node->paths[i]); + if (rval != 0) { + err_message(_("unable to remove symlink: %s"), + node->paths[i]); + goto quit; + } + + rval = link(srcname, node->paths[i]); + if (rval != 0) { + err_message("unable to link to symlink: %s", srcname); + goto quit; + } + numslinksdone++; + } + +quit: + cur_node = NULL; + + SET_PHASE(SLINK_PHASE); + + free(pname); + free(cur_target); + + cur_target = NULL; + + numslinksdone++; + return rval; +} + +static int +open_recoverfile(void) +{ + recover_fd = open(recover_file, O_RDWR | O_SYNC | O_CREAT | O_EXCL, + 0600); + if (recover_fd < 0) { + if (errno == EEXIST) + err_message(_("Recovery file already exists, either " + "run '%s -r %s' or remove the file."), + progname, recover_file); + else + err_open(recover_file); + return 1; + } + + if (!platform_test_xfs_fd(recover_fd)) { + err_not_xfs(recover_file); + close(recover_fd); + return 1; + } + + return 0; +} + +static void +update_recoverfile(void) +{ + static const char null_file[] = "0\n0\n0\n\ntarget: \ntemp: \nend\n"; + static size_t buf_size = 0; + static char *buf = NULL; + int i, len; + + if (recover_fd <= 0) + return; + + if (cur_node == NULL || cur_phase == 0) { + /* inbetween processing or still scanning */ + lseek(recover_fd, 0, SEEK_SET); + write(recover_fd, null_file, sizeof(null_file)); + return; + } + + ASSERT(highest_numpaths > 0); + if (buf == NULL) { + buf_size = (highest_numpaths + 3) * PATH_MAX; + buf = malloc(buf_size); + if (buf == NULL) { + err_nomem(); + exit(1); + } + } + + len = sprintf(buf, "%d\n%llu\n%d\n", cur_phase, + (long long)cur_node->ino, cur_node->ftw_flags); + + for (i = 0; i < cur_node->numpaths; i++) + len += sprintf(buf + len, "%s\n", cur_node->paths[i]); + + ASSERT(len < buf_size); + + lseek(recover_fd, 0, SEEK_SET); + ftruncate(recover_fd, 0); + write(recover_fd, buf, len); +} + +static void +cleanup(void) +{ + log_message(LOG_NORMAL, _("Interrupted -- cleaning up...")); + + free_nodehash(); + + log_message(LOG_NORMAL, _("Done.")); +} + +static void +sighandler(int sig) +{ + static char cycle[4] = "-\\|/"; + static uint64_t cur_cycle = 0; + double percent; + char *typename; + uint64_t nodes, done; + + alarm(0); + + if (sig != SIGALRM) { + cleanup(); + exit(1); + } + + if (cur_phase == SCAN_PHASE) { + if (log_level >= LOG_INFO) + fprintf(stderr, _("\r%llu files, %llu dirs and %llu " + "symlinks to renumber found... %c"), + (long long)numfilenodes, + (long long)numdirnodes, + (long long)numslinknodes, + cycle[cur_cycle % 4]); + else + fprintf(stderr, "\r%c", + cycle[cur_cycle % 4]); + cur_cycle++; + } else { + if (cur_phase >= DIR_PHASE && cur_phase <= DIR_PHASE_MAX) { + nodes = numdirnodes; + done = numdirsdone; + typename = _("dirs"); + } else if (cur_phase >= FILE_PHASE && cur_phase <= FILE_PHASE_MAX) { + nodes = numfilenodes; + done = numfilesdone; + typename = _("files"); + } else { + nodes = numslinknodes; + done = numslinksdone; + typename = _("symlinks"); + } + percent = 100.0 * (double)done / (double)nodes; + if (percent > 100.0) + percent = 100.0; + if (log_level >= LOG_INFO) + fprintf(stderr, _("\r%.1f%%, %llu of %llu %s, " + "%u seconds elapsed"), percent, + (long long)done, (long long)nodes, + typename, (int)(time(0) - starttime)); + else + fprintf(stderr, "\r%.1f%%", percent); + } + poll_output = 1; + signal(SIGALRM, sighandler); + + if (poll_interval) + alarm(poll_interval); +} + +static int +read_recover_file( + char *recover_file, + bignode_t **node, + char **target, + char **temp, + int *phase) +{ + FILE *file; + int rval = 1; + ino_t ino; + int ftw_flags; + char buf[PATH_MAX + 10]; /* path + "target: " */ + struct stat64 st; + int first_path; + + /* + + A recovery file should look like: + + <phase> + <ino number> + <ftw flags> + <first path to inode> + <hardlinks to inode> + target: <path to target dir or file> + temp: <path to temp dir if dir phase> + end + */ + + file = fopen(recover_file, "r"); + if (file == NULL) { + err_open(recover_file); + return 1; + } + + /* read phase */ + *phase = 0; + if (fgets(buf, PATH_MAX + 10, file) == NULL) { + err_message("Recovery failed: unable to read phase"); + goto quit; + } + buf[strlen(buf) - 1] = '\0'; + *phase = atoi(buf); + if (*phase == SCAN_PHASE) { + fclose(file); + return 0; + } + if ((*phase < DIR_PHASE || *phase > DIR_PHASE_MAX) && + (*phase < FILE_PHASE || *phase > FILE_PHASE_MAX)) { + err_message("Recovery failed: failed to read valid recovery phase"); + goto quit; + } + + /* read inode number */ + if (fgets(buf, PATH_MAX + 10, file) == NULL) { + err_message("Recovery failed: unable to read inode number"); + goto quit; + } + buf[strlen(buf) - 1] = '\0'; + ino = strtoull(buf, NULL, 10); + if (ino == 0) { + err_message("Recovery failed: unable to read inode number"); + goto quit; + } + + /* read ftw_flags */ + if (fgets(buf, PATH_MAX + 10, file) == NULL) { + err_message("Recovery failed: unable to read flags"); + goto quit; + } + buf[strlen(buf) - 1] = '\0'; + if (buf[1] != '\0' || (buf[0] != '0' && buf[0] != '1')) { + err_message("Recovery failed: unable to read flags: '%s'", buf); + goto quit; + } + ftw_flags = atoi(buf); + + /* read paths and target path */ + *node = NULL; + *target = NULL; + first_path = 1; + while (fgets(buf, PATH_MAX + 10, file) != NULL) { + buf[strlen(buf) - 1] = '\0'; + + log_message(LOG_DEBUG, "path: '%s'", buf); + + if (buf[0] == '/') { + if (stat64(buf, &st) < 0) { + err_message(_("Recovery failed: cannot " + "stat '%s'"), buf); + goto quit; + } + if (st.st_ino != ino) { + err_message(_("Recovery failed: inode " + "number for '%s' does not " + "match recorded number"), buf); + goto quit; + } + + if (first_path) { + first_path = 0; + *node = add_node_path(ino, ftw_flags, buf); + } else { + add_path(*node, buf); + } + } else if (strncmp(buf, "target: ", 8) == 0) { + *target = strdup(buf + 8); + if (*target == NULL) { + err_nomem(); + goto quit; + } + if (stat64(*target, &st) < 0) { + err_message(_("Recovery failed: cannot " + "stat '%s'"), *target); + goto quit; + } + } else if (strncmp(buf, "temp: ", 6) == 0) { + *temp = strdup(buf + 6); + if (*temp == NULL) { + err_nomem(); + goto quit; + } + } else if (strcmp(buf, "end") == 0) { + rval = 0; + goto quit; + } else { + err_message(_("Recovery failed: unrecognised " + "string: '%s'"), buf); + goto quit; + } + } + + err_message(_("Recovery failed: end of recovery file not found")); + + quit: + if (*node == NULL) { + err_message(_("Recovery failed: no valid inode or paths " + "specified")); + rval = 1; + } + + if (*target == NULL) { + err_message(_("Recovery failed: no inode target specified")); + rval = 1; + } + + fclose(file); + + return rval; +} + +int +recover( + bignode_t *node, + char *target, + char *tname, + int phase) +{ + char *srcname = NULL; + int rval = 0; + int i; + int dir; + + dump_node("recover", node); + log_message(LOG_DEBUG, "target: %s, phase: %x", target, phase); + + if (node) + srcname = node->paths[0]; + + dir = (phase < DIR_PHASE || phase > DIR_PHASE_MAX); + + switch (phase) { + + case DIR_PHASE_1: + case FILE_PHASE_1: + case SLINK_PHASE_1: + log_message(LOG_NORMAL, _("Unlinking temporary %s: \'%s\'"), + dir ? "directory" : "file", target); + + rval = dir ? rmdir(target) : unlink(target); + + if ( rval < 0 && errno != ENOENT) + err_message(_("unable to remove %s: %s"), + dir ? "directory" : "file", target); + + break; + + case DIR_PHASE_2: + case FILE_PHASE_2: + case SLINK_PHASE_2: + log_message(LOG_NORMAL, _("Unlinking old %s: \'%s\'"), + dir ? "directory" : "file", srcname); + + rval = dir ? rmdir(target) : unlink(srcname); + + if (rval < 0 && errno != ENOENT) { + err_message(_("unable to remove %s: %s"), + dir ? "directory" : "file", srcname); + break; + } + /* FALL THRU */ + case DIR_PHASE_3: + case FILE_PHASE_3: + case SLINK_PHASE_3: + log_message(LOG_NORMAL, _("Renaming: " + "\'%s\' -> \'%s\'"), target, srcname); + rval = rename(target, srcname); + if (rval != 0) { + /* we can't abort since the src file is now gone. + * let the admin clean this one up + */ + err_message(_("unable to rename: %s to %s"), + target, srcname); + break; + } + if (dir) + break; + /* FALL THRU */ + case FILE_PHASE_4: + case SLINK_PHASE_4: + /* for each hardlink, unlink and creat pointing to target */ + for (i = 1; i < node->numpaths; i++) { + if (i == 1) + log_message(LOG_NORMAL, _("Resetting hardlinks " + "to new file")); + + rval = unlink(node->paths[i]); + if (rval != 0) { + err_message(_("unable to remove file: %s"), + node->paths[i]); + break; + } + rval = link(srcname, node->paths[i]); + if (rval != 0) { + err_message(_("unable to link to file: %s"), + srcname); + break; + } + } + break; + } + + if (rval == 0) { + log_message(LOG_NORMAL, _("Removing recover file: \'%s\'"), + recover_file); + unlink(recover_file); + log_message(LOG_NORMAL, _("Recovery done.")); + } else { + log_message(LOG_NORMAL, _("Leaving recover file: \'%s\'"), + recover_file); + log_message(LOG_NORMAL, _("Recovery failed.")); + } + + return rval; +} + +int +main( + int argc, + char *argv[]) +{ + int c = 0; + int rval = 0; + int q_opt = 0; + int v_opt = 0; + int p_opt = 0; + int n_opt = 0; + char pathname[PATH_MAX]; + struct stat64 st; + + progname = basename(argv[0]); + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + + while ((c = getopt(argc, argv, "fnpqvP:r:")) != -1) { + switch (c) { + case 'f': + force_all = 1; + break; + case 'n': + n_opt++; + break; + case 'p': + p_opt++; + break; + case 'q': + if (v_opt) + err_message(_("'q' option incompatible " + "with 'v' option")); + q_opt++; + log_level=0; + break; + case 'v': + if (q_opt) + err_message(_("'v' option incompatible " + "with 'q' option")); + v_opt++; + log_level++; + break; + case 'P': + poll_interval = atoi(optarg); + break; + case 'r': + recover_file = optarg; + break; + default: + err_message(_("%s: illegal option -- %c\n"), progname, c); + usage(); + /* NOTREACHED */ + break; + } + } + + if (optind != argc - 1 && recover_file == NULL) { + usage(); + exit(1); + } + + realuid = getuid(); + starttime = time(0); + + init_nodehash(); + + signal(SIGALRM, sighandler); + signal(SIGABRT, sighandler); + signal(SIGHUP, sighandler); + signal(SIGINT, sighandler); + signal(SIGQUIT, sighandler); + signal(SIGTERM, sighandler); + + if (p_opt && poll_interval == 0) + poll_interval = 1; + + if (poll_interval) + alarm(poll_interval); + + if (recover_file) { + bignode_t *node = NULL; + char *target = NULL; + char *tname = NULL; + int phase = 0; + + if (n_opt) + goto quit; + + /* read node info from recovery file */ + if (read_recover_file(recover_file, &node, &target, + &tname, &phase) != 0) + exit(1); + + rval = recover(node, target, tname, phase); + + free(target); + free(tname); + + return rval; + } + + recover_file = malloc(PATH_MAX); + if (recover_file == NULL) { + err_nomem(); + exit(1); + } + recover_file[0] = '\0'; + + strcpy(pathname, argv[optind]); + if (pathname[0] != '/') { + err_message(_("pathname must begin with a slash ('/')")); + exit(1); + } + + if (stat64(pathname, &st) < 0) { + err_stat(pathname); + exit(1); + } + if (S_ISREG(st.st_mode)) { + /* single file specified */ + if (st.st_nlink > 1) { + err_message(_("cannot process single file with a " + "link count greater than 1")); + exit(1); + } + + strcpy(recover_file, pathname); + dirname(recover_file); + + strcpy(recover_file + strlen(recover_file), "/xfs_reno.recover"); + if (!n_opt) { + if (open_recoverfile() != 0) + exit(1); + } + add_node_path(st.st_ino, FTW_F, pathname); + } else if (S_ISDIR(st.st_mode)) { + /* directory tree specified */ + strcpy(recover_file, pathname); + + strcpy(recover_file + strlen(recover_file), "/xfs_reno.recover"); + if (!n_opt) { + if (open_recoverfile() != 0) + exit(1); + } + + /* directory scan */ + log_message(LOG_INFO, _("\rScanning directory tree...")); + SET_PHASE(SCAN_PHASE); + nftw64(pathname, nftw_addnodes, 100, FTW_PHYS | FTW_MOUNT); + } else { + err_message(_("pathname must be either a regular file " + "or directory")); + exit(1); + } + + dump_nodehash(); + + if (n_opt) { + /* n flag set, don't do anything */ + if (numdirnodes) + log_message(LOG_NORMAL, "\rWould process %d %s", + numdirnodes, numdirnodes == 1 ? + "directory" : "directories"); + else + log_message(LOG_NORMAL, "\rNo directories to process"); + + if (numfilenodes) + /* process files */ + log_message(LOG_NORMAL, "\rWould process %d %s", + numfilenodes, numfilenodes == 1 ? + "file" : "files"); + else + log_message(LOG_NORMAL, "\rNo files to process"); + if (numslinknodes) + /* process files */ + log_message(LOG_NORMAL, "\rWould process %d %s", + numslinknodes, numslinknodes == 1 ? + "symlinx" : "symlinks"); + else + log_message(LOG_NORMAL, "\rNo symlinks to process"); + } else { + /* process directories */ + if (numdirnodes) { + log_message(LOG_INFO, _("\rProcessing %d %s..."), + numdirnodes, numdirnodes == 1 ? + _("directory") : _("directories")); + cur_phase = DIR_PHASE; + rval = for_all_nodes(process_dir, FTW_D, 1); + if (rval != 0) + goto quit; + } else { + log_message(LOG_INFO, _("\rNo directories to process...")); + } + + if (numfilenodes) { + /* process files */ + log_message(LOG_INFO, _("\rProcessing %d %s..."), + numfilenodes, numfilenodes == 1 ? + _("file") : _("files")); + cur_phase = FILE_PHASE; + for_all_nodes(process_file, FTW_F, 0); + } else { + log_message(LOG_INFO, _("\rNo files to process...")); + } + + if (numslinknodes) { + /* process symlinks */ + log_message(LOG_INFO, _("\rProcessing %d %s..."), + numslinknodes, numslinknodes == 1 ? + _("symlink") : _("symlinks")); + cur_phase = SLINK_PHASE; + for_all_nodes(process_slink, FTW_SL, 0); + } else { + log_message(LOG_INFO, _("\rNo symlinks to process...")); + } + } +quit: + free_nodehash(); + + close(recover_fd); + + if (rval == 0) + unlink(recover_file); + + log_message(LOG_DEBUG, "\r%u seconds elapsed", time(0) - starttime); + log_message(LOG_INFO, _("\rDone. ")); + + return rval | global_rval; +} -- 1.7.9.5 _______________________________________________ xfs mailing list xfs@xxxxxxxxxxx http://oss.sgi.com/mailman/listinfo/xfs