From 6404bb81183a7192096b7124351d12c414bf2240 Mon Sep 17 00:00:00 2001 From: Nathan Scott Date: Wed, 23 Jul 2003 00:43:47 +0000 Subject: [PATCH] Move xfs_copy into xfsprogs, build and install it too. Reasons for it being in xfsdump package are no longer there, and its a libxfs user so keep it with the rest of them --- Makefile | 2 +- VERSION | 2 +- copy/Makefile | 50 ++ copy/xfs_copy.c | 1141 +++++++++++++++++++++++++++++++++++++++++++ copy/xfs_copy.h | 100 ++++ debian/changelog | 6 + doc/CHANGES | 3 +- man/man8/xfs_copy.8 | 150 ++++++ 8 files changed, 1451 insertions(+), 3 deletions(-) create mode 100644 copy/Makefile create mode 100644 copy/xfs_copy.c create mode 100644 copy/xfs_copy.h create mode 100644 man/man8/xfs_copy.8 diff --git a/Makefile b/Makefile index bbc2dab92..947231421 100644 --- a/Makefile +++ b/Makefile @@ -44,7 +44,7 @@ LDIRT = config.log .dep config.status config.cache confdefs.h conftest* \ Logs/* built .census install.* install-dev.* *.gz SUBDIRS = include libxfs libxlog libhandle libdisk \ - db freeze fsck growfs io imap logprint mkfile mkfs repair rtcp \ + copy db freeze fsck growfs io imap logprint mkfile mkfs repair rtcp \ m4 man doc po debian build default: $(CONFIGURE) diff --git a/VERSION b/VERSION index 89d1fe832..b4b13be7a 100644 --- a/VERSION +++ b/VERSION @@ -3,5 +3,5 @@ # PKG_MAJOR=2 PKG_MINOR=5 -PKG_REVISION=3 +PKG_REVISION=4 PKG_BUILD=0 diff --git a/copy/Makefile b/copy/Makefile new file mode 100644 index 000000000..5a8de52ad --- /dev/null +++ b/copy/Makefile @@ -0,0 +1,50 @@ +# +# Copyright (c) 2000-2003 Silicon Graphics, Inc. All Rights Reserved. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of version 2 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. +# +# Further, this software is distributed without any warranty that it is +# free of the rightful claim of any third person regarding infringement +# or the like. Any license provided herein, whether implied or +# otherwise, applies only to this software file. Patent licenses, if +# any, provided herein do not apply to combinations of this program with +# other software, or any other product whatsoever. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write the Free Software Foundation, Inc., 59 +# Temple Place - Suite 330, Boston MA 02111-1307, USA. +# +# Contact information: Silicon Graphics, Inc., 1600 Amphitheatre Pkwy, +# Mountain View, CA 94043, or: +# +# http://www.sgi.com +# +# For further information regarding this notice, see: +# +# http://oss.sgi.com/projects/GenInfo/SGIGPLNoticeExplan/ +# + +TOPDIR = .. +include $(TOPDIR)/include/builddefs + +LTCOMMAND = xfs_copy +CFILES = xfs_copy.c + +LLDLIBS = $(LIBXFS) $(LIBUUID) $(LIBPTHREAD) +LTDEPENDENCIES = $(LIBXFS) +LLDFLAGS = -static + +default: $(LTCOMMAND) + +include $(BUILDRULES) + +install: default + $(INSTALL) -m 755 -d $(PKG_BIN_DIR) + $(LTINSTALL) -m 755 $(LTCOMMAND) $(PKG_BIN_DIR) +install-dev: diff --git a/copy/xfs_copy.c b/copy/xfs_copy.c new file mode 100644 index 000000000..28e18a274 --- /dev/null +++ b/copy/xfs_copy.c @@ -0,0 +1,1141 @@ +/* + * Copyright (c) 2000-2003 Silicon Graphics, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 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. + * + * Further, this software is distributed without any warranty that it is + * free of the rightful claim of any third person regarding infringement + * or the like. Any license provided herein, whether implied or + * otherwise, applies only to this software file. Patent licenses, if + * any, provided herein do not apply to combinations of this program with + * other software, or any other product whatsoever. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston MA 02111-1307, USA. + * + * Contact information: Silicon Graphics, Inc., 1600 Amphitheatre Pkwy, + * Mountain View, CA 94043, or: + * + * http://www.sgi.com + * + * For further information regarding this notice, see: + * + * http://oss.sgi.com/projects/GenInfo/SGIGPLNoticeExplan/ + */ + +#define ustat __kernel_ustat +#include +#include +#undef ustat +#include +#include +#include +#include +#include +#include "xfs_copy.h" + +#define rounddown(x, y) (((x)/(y))*(y)) + +int logfd; +char *logfile_name; +FILE *logerr; +char LOGFILE_NAME[] = "/var/tmp/xfs_copy.log.XXXXXX"; + +char *source_name; +int source_fd; + +unsigned int source_blocksize; /* source filesystem blocksize */ +unsigned int source_sectorsize; /* source disk sectorsize */ + +xfs_agblock_t first_agbno; + +unsigned int num_targets; +target_control *target; + +wbuf w_buf; +wbuf btree_buf; + +pid_t parent_pid; +unsigned int kids; + +thread_control glob_masks; +thread_args *targ; + +pthread_mutex_t mainwait; + +#define ACTIVE 1 +#define INACTIVE 2 + + +/* general purpose message reporting routine */ + +#define OUT 0x01 /* use stdout stream */ +#define ERR 0x02 /* use stderr stream */ +#define LOG 0x04 /* use logerr stream */ +#define PRE 0x08 /* append strerror string */ +#define LAST 0x10 /* final message we print */ + +void +do_message(int flags, int code, const char *fmt, ...) +{ + va_list ap; + int eek = 0; + + va_start(ap, fmt); + if (flags & LOG) + if (vfprintf(logerr, fmt, ap) <= 0) + eek = 1; + if (eek) + flags |= ERR; /* failed, force stderr */ + if (flags & ERR) + vfprintf(stderr, fmt, ap); + else if (flags & OUT) + vfprintf(stdout, fmt, ap); + va_end(ap); + + if (flags & PRE) { + do_message(flags & ~PRE, 0, ": %s\n", strerror(code)); + if (flags & LAST) + fprintf(stderr, + _("Check logfile \"%s\" for more details\n"), + logfile_name); + } + + /* logfile is broken, force a write to stderr */ + if (eek) { + fprintf(stderr, _("%s: could not write to logfile \"%s\".\n"), + progname, logfile_name); + fprintf(stderr, + _("Aborting XFS copy -- logfile error -- reason: %s\n"), + strerror(errno)); + pthread_exit(NULL); + } +} + +#define do_out(args...) do_message(OUT|LOG, 0, ## args) +#define do_log(args...) do_message(ERR|LOG, 0, ## args) +#define do_warn(args...) do_message(LOG, 0, ## args) +#define do_error(e,s) do_message(ERR|LOG|PRE, e, s) +#define do_fatal(e,s) do_message(ERR|LOG|PRE|LAST, e, s) +#define do_vfatal(e,s,args...) do_message(ERR|LOG|PRE|LAST, e, s, ## args) +#define die_perror(void) \ + do { \ + do_message(ERR|LOG|PRE|LAST, errno, \ + _("Aborting XFS copy - reason")); \ + exit(1); \ + } while (0); + +void +check_errors(void) +{ + int i, first_error = 0; + + /* now check for errors */ + + for (i = 0; i < num_targets; i++) { + if (target[i].state == INACTIVE) { + if (first_error == 0) { + first_error++; + do_log( + _("THE FOLLOWING COPIES FAILED TO COMPLETE\n")); + } + do_log(" %s -- ", target[i].name); + if (target[i].err_type == 0) + do_log(_("write error")); + else + do_log(_("lseek64 error")); + do_log(_(" at offset %lld\n"), target[i].position); + } + } + if (first_error == 0) { + fprintf(stdout, _("All copies completed.\n")); + fflush(NULL); + } else { + fprintf(stderr, _("See \"%s\" for more details.\n"), + logfile_name); + exit(1); + } +} + +/* + * don't have to worry about alignment and mins because those + * are taken care of when the buffer's read in + */ + +void * +begin_reader(void *arg) +{ + thread_args *args = arg; + int res, error = 0; + + for (;;) { + pthread_mutex_lock(&args->wait); + + /* write */ + if (target[args->id].position != w_buf.position) { + if (lseek64(args->fd, w_buf.position, SEEK_SET) < 0) { + error = 1; + target[args->id].err_type = 1; + } else { + target[args->id].position = w_buf.position; + } + } + + if ((res = write(target[args->id].fd, w_buf.data, + w_buf.length)) == w_buf.length) { + target[args->id].position += res; + } else { + error = 2; + } + + if (error) { + goto handle_error; + } + + pthread_mutex_lock(&glob_masks.mutex); + if (--glob_masks.num_working == 0) + pthread_mutex_unlock(&mainwait); + pthread_mutex_unlock(&glob_masks.mutex); + } + /* NOTREACHED */ + +handle_error: + /* error will be logged by primary thread */ + + target[args->id].error = errno; + target[args->id].position = w_buf.position; + + pthread_mutex_lock(&glob_masks.mutex); + target[args->id].state = INACTIVE; + if (--glob_masks.num_working == 0) + pthread_mutex_unlock(&mainwait); + pthread_mutex_unlock(&glob_masks.mutex); + pthread_exit(NULL); +} + +void +killall(void) +{ + int i; + + /* only the parent gets to kill things */ + + if (getpid() != parent_pid) + return; + + for (i = 0; i < num_targets; i++) { + if (target[i].state == ACTIVE) { + /* kill up target threads */ + pthread_kill(target[i].pid, SIGKILL); + pthread_mutex_unlock(&targ[i].wait); + } + } +} + +void +handler() +{ + pid_t pid = getpid(); + int status, i; + + pid = wait(&status); + + kids--; + + for (i = 0; i < num_targets; i++) { + if (target[i].pid == pid) { + if (target[i].state == INACTIVE) { + /* thread got an I/O error */ + + if (target[i].err_type == 0) { + do_warn( + _("%s: write error on target %d \"%s\" at offset %lld\n"), + progname, i, target[i].name, + target[i].position); + } else { + do_warn( + _("%s: lseek64 error on target %d \"%s\" at offset %lld\n"), + progname, i, target[i].name, + target[i].position); + } + + do_vfatal(target[i].error, + _("Aborting target %d - reason"), i); + + if (kids == 0) { + do_log( + _("Aborting XFS copy - no more targets.\n")); + check_errors(); + pthread_exit(NULL); + } + + sigset(SIGCLD, handler); + return; + } else { + /* it just croaked it bigtime, log it */ + + do_warn( + _("%s: thread %d died unexpectedly, target \"%s\" incomplete\n"), + progname, i, target[i].name); + + do_warn( + _("%s: offset was probably %lld\n"), + progname, target[i].position); + do_fatal(target[i].error, + _("Aborting XFS copy - reason")); + pthread_exit(NULL); + } + } + } + + /* unknown child -- something very wrong */ + + do_warn(_("%s: Unknown child died (should never happen!)\n"), progname); + die_perror(); + pthread_exit(NULL); + sigset(SIGCLD, handler); +} + +void +usage(void) +{ + fprintf(stderr, + _("Usage: %s [-bd] [-L logfile] source target [target ...]\n"), + progname); + exit(1); +} + +__uint64_t barcount[11]; +int howfar = 0; +char *bar[11] = { + " 0% ", + " ... 10% ", + " ... 20% ", + " ... 30% ", + " ... 40% ", + " ... 50% ", + " ... 60% ", + " ... 70% ", + " ... 80% ", + " ... 90% ", + " ... 100%\n\n", +}; + +void +bump_bar(int tenths) +{ + printf("%s", bar[tenths]); + fflush(stdout); +} + +static xfs_off_t source_position = -1; + +wbuf * +wbuf_init(wbuf *buf, int data_size, int data_align, int min_io_size, int id) +{ + buf->id = id; + if ((buf->data = memalign(data_align, data_size)) == NULL) + return NULL; + ASSERT(min_io_size % BBSIZE == 0); + buf->min_io_size = min_io_size; + buf->size = MAX(data_size, 2*min_io_size); + return buf; +} + +void +read_wbuf(int fd, wbuf *buf, xfs_mount_t *mp) +{ + int res = 0; + xfs_off_t lres = 0; + xfs_off_t newpos; + size_t diff; + + newpos = rounddown(buf->position, (xfs_off_t) buf->min_io_size); + + if (newpos != buf->position) { + diff = buf->position - newpos; + buf->position = newpos; + + buf->length += diff; + } + + if (source_position != buf->position) { + lres = lseek64(fd, buf->position, SEEK_SET); + if (lres < 0LL) { + do_warn(_("%s: lseek64 failure at offset %lld\n"), + progname, source_position); + die_perror(); + } + source_position = buf->position; + } + + ASSERT(source_position % source_sectorsize == 0); + + /* round up length for direct I/O if necessary */ + + if (buf->length % buf->min_io_size != 0) + buf->length = roundup(buf->length, buf->min_io_size); + + if (buf->length > buf->size) { + do_warn(_("assert error: buf->length = %d, buf->size = %d\n"), + buf->length, buf->size); + killall(); + abort(); + } + + if ((res = read(fd, buf->data, buf->length)) < 0) { + do_warn(_("%s: read failure at offset %lld\n"), + progname, source_position); + die_perror(); + } + + if (res < buf->length && + source_position + res == mp->m_sb.sb_dblocks * source_blocksize) + res = buf->length; + else + ASSERT(res == buf->length); + source_position += res; + buf->length = res; +} + +int +read_ag_header(int fd, xfs_agnumber_t agno, wbuf *buf, ag_header_t *ag, + xfs_mount_t *mp, int blocksize, int sectorsize) +{ + xfs_daddr_t off; + int length; + xfs_off_t newpos; + size_t diff; + + /* initial settings */ + + diff = 0; + off = XFS_AG_DADDR(mp, agno, XFS_SB_DADDR); + buf->position = (xfs_off_t) off * (xfs_off_t) BBSIZE; + length = buf->length = first_agbno * blocksize; + + /* handle alignment stuff */ + + newpos = rounddown(buf->position, (xfs_off_t) buf->min_io_size); + if (newpos != buf->position) { + diff = buf->position - newpos; + buf->position = newpos; + buf->length += diff; + } + + /* round up length for direct I/O if necessary */ + + if (buf->length % buf->min_io_size != 0) + buf->length = roundup(buf->length, buf->min_io_size); + + ASSERT(length != 0); + read_wbuf(fd, buf, mp); + ASSERT(buf->length >= length); + + ag->xfs_sb = (xfs_sb_t *) (buf->data + diff); + ASSERT(INT_GET(ag->xfs_sb->sb_magicnum, ARCH_CONVERT)==XFS_SB_MAGIC); + ag->xfs_agf = (xfs_agf_t *) (buf->data + diff + sectorsize); + ASSERT(INT_GET(ag->xfs_agf->agf_magicnum, ARCH_CONVERT)==XFS_AGF_MAGIC); + ag->xfs_agi = (xfs_agi_t *) (buf->data + diff + 2*sectorsize); + ASSERT(INT_GET(ag->xfs_agi->agi_magicnum, ARCH_CONVERT)==XFS_AGI_MAGIC); + ag->xfs_agfl = (xfs_agfl_t *) (buf->data + diff + 3*sectorsize); + return 1; +} + +void +write_wbuf(void) +{ + int i; + + /* verify target threads */ + for (i = 0; i < num_targets; i++) + if (target[i].state != INACTIVE) + glob_masks.num_working++; + + /* release target threads */ + for (i = 0; i < num_targets; i++) + if (target[i].state != INACTIVE) + pthread_mutex_unlock(&targ[i].wait); /* wake up */ + + sigrelse(SIGCLD); + pthread_mutex_lock(&mainwait); + sighold(SIGCLD); +} + + +int +main(int argc, char **argv) +{ + int i; + int open_flags; + xfs_off_t pos; + size_t length; + int c, size, sizeb, first_residue, tmp_residue; + __uint64_t numblocks = 0; + __uint64_t source_blocks; + int wblocks = 0; + int num_threads = 0; + struct dioattr d; + int wbuf_size; + int wbuf_align; + int wbuf_miniosize; + int source_is_file = 0; + int buffered_output = 0; + int duplicate_uuids = 0; + uuid_t fsid; + uint btree_levels, current_level; + ag_header_t ag_hdr; + xfs_mount_t *mp; + xfs_mount_t mbuf; + xfs_buf_t *sbp; + xfs_sb_t *sb; + xfs_agnumber_t num_ags, agno; + xfs_agblock_t bno; + xfs_daddr_t begin, next_begin, ag_begin, new_begin, ag_end; + xfs_alloc_block_t *block; + xfs_alloc_ptr_t *ptr; + xfs_alloc_rec_t *rec_ptr; + extern char *optarg; + extern int optind; + libxfs_init_t xargs; + thread_args *tcarg; + struct ustat ustat_buf; + struct stat64 statbuf; + + progname = basename(argv[0]); + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + + while ((c = getopt(argc, argv, "bdL:V")) != EOF) { + switch (c) { + case 'b': + buffered_output = 1; + break; + case 'd': + duplicate_uuids = 1; + break; + case 'L': + logfile_name = optarg; + break; + case 'V': + printf(_("%s version %s\n"), progname, VERSION); + exit(0); + case '?': + usage(); + } + } + + if (argc - optind < 2) + usage(); + + if (logfile_name) { + logfd = open(logfile_name, O_CREAT|O_WRONLY|O_EXCL, 0600); + } else { + logfile_name = LOGFILE_NAME; + logfd = mkstemp(logfile_name); + } + + if (logfd < 0) { + fprintf(stderr, _("%s: couldn't open log file \"%s\"\n"), + progname, logfile_name); + perror(_("Aborting XFS copy - reason")); + exit(1); + } + + if ((logerr = fdopen(logfd, "w")) == NULL) { + fprintf(stderr, _("%s: couldn't set up logfile stream\n"), + progname); + perror(_("Aborting XFS copy - reason")); + exit(1); + } + + source_name = argv[optind]; + source_fd = -1; + optind++; + + num_targets = argc - optind; + if ((target = malloc(sizeof(target_control) * num_targets)) == NULL) { + do_log(_("Couldn't allocate target array\n")); + die_perror(); + } + for (i = 0; optind < argc; i++, optind++) { + target[i].name = argv[optind]; + target[i].fd = -1; + target[i].position = -1; + target[i].state = INACTIVE; + target[i].error = 0; + target[i].err_type = 0; + } + + parent_pid = getpid(); + + if (atexit(killall)) { + do_log(_("%s: couldn't register atexit function.\n"), progname); + die_perror(); + } + + /* open up source -- is it a file? */ + + open_flags = O_RDONLY; + + if ((source_fd = open(source_name, open_flags)) < 0) { + do_log(_("%s: couldn't open source \"%s\"\n"), + progname, source_name); + die_perror(); + } + + if (fstat64(source_fd, &statbuf) < 0) { + do_log(_("%s: couldn't stat source \"%s\"\n"), + progname, source_name); + die_perror(); + } + + if (S_ISREG(statbuf.st_mode)) + source_is_file = 1; + + if (source_is_file && platform_test_xfs_fd(source_fd)) { + if (fcntl(source_fd, F_SETFL, open_flags | O_DIRECT) < 0) { + do_log(_("%s: Cannot set direct I/O flag on \"%s\".\n"), + progname, source_name); + die_perror(); + } + if (xfsctl(source_name, source_fd, XFS_IOC_DIOINFO, &d) < 0) { + do_log(_("%s: xfsctl on file \"%s\" failed.\n"), + progname, source_name); + die_perror(); + } + + wbuf_align = d.d_mem; + wbuf_size = d.d_maxiosz; + wbuf_miniosize = d.d_miniosz; + } else { + /* set arbitrary I/O params, miniosize at least 1 disk block */ + + wbuf_align = 4096*4; + wbuf_size = 1024 * 4000; + wbuf_miniosize = -1; /* set after mounting source fs */ + } + + if (!source_is_file) { + /* + * check to make sure a filesystem isn't mounted + * on the device + */ + if (ustat(statbuf.st_rdev, &ustat_buf) == 0) { + do_log( + _("%s: Warning -- a filesystem is mounted on the source device.\n"), + progname); + do_log( + _("\t\tGenerated copies may be corrupt unless the source is\n")); + do_log( + _("\t\tunmounted or mounted read-only. Copy proceeding...\n")); + } + } + + /* prepare the libxfs_init structure */ + + memset(&xargs, 0, sizeof(xargs)); + xargs.notvolmsg = "oh no %s"; + xargs.isreadonly = LIBXFS_ISREADONLY; + xargs.notvolok = 1; + + if (source_is_file) { + xargs.dname = source_name; + xargs.disfile = 1; + } else + xargs.volname = source_name; + + if (!libxfs_init(&xargs)) { + do_log(_("%s: couldn't initialize XFS library\n" + "%s: Aborting.\n"), progname, progname); + exit(1); + } + + /* prepare the mount structure */ + + sbp = libxfs_readbuf(xargs.ddev, XFS_SB_DADDR, 1, 0); + memset(&mbuf, 0, sizeof(xfs_mount_t)); + sb = &mbuf.m_sb; + libxfs_xlate_sb(XFS_BUF_PTR(sbp), sb, 1, ARCH_CONVERT, XFS_SB_ALL_BITS); + + mp = libxfs_mount(&mbuf, sb, xargs.ddev, xargs.logdev, xargs.rtdev, 1); + if (mp == NULL) { + do_log(_("%s: %s filesystem failed to initialize\n" + "%s: Aborting.\n"), progname, source_name, progname); + exit(1); + } else if (mp->m_sb.sb_inprogress) { + do_log(_("%s %s filesystem failed to initialize\n" + "%s: Aborting.\n"), progname, source_name, progname); + exit(1); + } else if (mp->m_sb.sb_logstart == 0) { + do_log(_("%s: %s has an external log.\n%s: Aborting.\n"), + progname, source_name, progname); + exit(1); + } else if (mp->m_sb.sb_rextents != 0) { + do_log(_("%s: %s has a real-time section.\n" + "%s: Aborting.\n"), progname, source_name, progname); + exit(1); + } + + source_blocksize = mp->m_sb.sb_blocksize; + source_sectorsize = mp->m_sb.sb_sectsize; + + if (wbuf_miniosize == -1) + wbuf_miniosize = source_sectorsize; + + ASSERT(source_blocksize % source_sectorsize == 0); + ASSERT(source_sectorsize % BBSIZE == 0); + + if (source_blocksize > source_sectorsize) { + /* get number of leftover sectors in last block of ag header */ + + tmp_residue = ((XFS_AGFL_DADDR(mp) + 1) * source_sectorsize) + % source_blocksize; + first_residue = (tmp_residue == 0) ? 0 : + source_blocksize - tmp_residue; + ASSERT(first_residue % source_sectorsize == 0); + } else if (source_blocksize == source_sectorsize) { + first_residue = 0; + } else { + do_log(_("Error: filesystem block size is smaller than the" + " disk sectorsize.\nAborting XFS copy now.\n")); + exit(1); + } + + first_agbno = (((XFS_AGFL_DADDR(mp) + 1) * source_sectorsize) + + first_residue) / source_blocksize; + ASSERT(first_agbno != 0); + ASSERT( ((((XFS_AGFL_DADDR(mp) + 1) * source_sectorsize) + + first_residue) % source_blocksize) == 0); + + if (!duplicate_uuids) + uuid_generate(fsid); + + /* now open targets */ + + open_flags = O_RDWR; + + for (i = 0; i < num_targets; i++) { + int write_last_block = 0; + + if (stat64(target[i].name, &statbuf) < 0) { + /* ok, assume it's a file and create it */ + + do_out(_("Creating file %s\n"), target[i].name); + + open_flags |= O_CREAT; + if (!buffered_output) + open_flags |= O_DIRECT; + write_last_block = 1; + } else if (S_ISREG(statbuf.st_mode)) { + open_flags |= O_TRUNC; + if (!buffered_output) + open_flags |= O_DIRECT; + write_last_block = 1; + } else { + /* + * check to make sure a filesystem isn't mounted + * on the device + */ + if (ustat(statbuf.st_rdev, &ustat_buf) == 0) { + do_log(_("%s: a filesystem is mounted " + "on target device \"%s\".\n" + "%s cannot copy to mounted filesystems." + " Aborting\n"), + progname, target[i].name, progname); + exit(1); + } + } + + target[i].fd = open(target[i].name, open_flags, 0644); + if (target[i].fd < 0) { + do_log(_("%s: couldn't open target \"%s\"\n"), + progname, target[i].name); + die_perror(); + } + + if (write_last_block) { + /* ensure regular files are correctly sized */ + + if (ftruncate64(target[i].fd, mp->m_sb.sb_dblocks * + source_blocksize)) { + do_log(_("%s: cannot grow data section.\n"), + progname); + die_perror(); + } + if (xfsctl(target[i].name, target[i].fd, + XFS_IOC_DIOINFO, &d) < 0) { + do_log(_("%s: xfsctl on \"%s\" failed.\n"), + progname, target[i].name); + die_perror(); + } + } else { + char *buf[XFS_MAX_SECTORSIZE] = { 0 }; + + /* ensure device files are sufficiently large */ + + if (pwrite64(target[i].fd, buf, sizeof(buf), + (mp->m_sb.sb_dblocks * source_blocksize) + - sizeof(buf))) { + do_log(_("%s: failed to write last block\n"), + progname); + do_log(_("\tIs target \"%s\" too small?\n"), + target[i].name); + die_perror(); + } + } + + wbuf_align = MAX(wbuf_align, d.d_mem); + wbuf_size = MIN(d.d_maxiosz, wbuf_size); + wbuf_miniosize = MAX(d.d_miniosz, wbuf_miniosize); + } + + /* initialize locks and bufs */ + + if (pthread_mutex_init(&glob_masks.mutex, NULL) != 0) { + do_log(_("Couldn't initialize global thread mask\n")); + die_perror(); + } + glob_masks.num_working = 0; + + if (wbuf_init(&w_buf, wbuf_size, wbuf_align, + wbuf_miniosize, 0) == NULL) { + do_log(_("Error initializing wbuf 0\n")); + die_perror(); + } + + wblocks = wbuf_size / BBSIZE; + + if (wbuf_init(&btree_buf, MAX(MAX(source_blocksize, source_sectorsize), + wbuf_miniosize), wbuf_align, + wbuf_miniosize, 1) == NULL) { + do_log(_("Error initializing btree buf 1\n")); + die_perror(); + } + + if (pthread_mutex_init(&mainwait,NULL) != 0) { + do_log(_("Error creating first semaphore.\n")); + die_perror(); + exit(1); + } + /* need to start out blocking */ + pthread_mutex_lock(&mainwait); + + /* set up sigchild signal handler */ + + sigset(SIGCLD, handler); + sighold(SIGCLD); + + /* make children */ + + if ((targ = malloc(num_targets * sizeof(thread_args))) == NULL) { + do_log(_("Couldn't malloc space for thread args\n")); + die_perror(); + exit(1); + } + + for (i = 0, tcarg = targ; i < num_targets; i++, tcarg++) { + if (pthread_mutex_init(&tcarg->wait, NULL) != 0) { + do_log(_("Error creating thread mutex %d\n"), i); + die_perror(); + exit(1); + } + /* need to start out blocking */ + pthread_mutex_lock(&tcarg->wait); + } + + for (i = 0, tcarg = targ; i < num_targets; i++, tcarg++) { + tcarg->id = i; + tcarg->fd = target[i].fd; + + target[i].state = ACTIVE; + num_threads++; + + if (pthread_create(&target[i].pid, NULL, + begin_reader, (void *)tcarg)) { + do_log(_("Error creating thread for target %d\n"), i); + die_perror(); + } + } + + ASSERT(num_targets == num_threads); + + /* set up statistics */ + + num_ags = mp->m_sb.sb_agcount; + + source_blocks = mp->m_sb.sb_blocksize / BBSIZE + * ((__uint64_t)mp->m_sb.sb_dblocks + - (__uint64_t)mp->m_sb.sb_fdblocks + 10 * num_ags); + + for (i = 0; i < 11; i++) + barcount[i] = (source_blocks/10)*i; + + kids = num_targets; + block = (xfs_alloc_block_t *) btree_buf.data; + + for (agno = 0; agno < num_ags && kids > 0; agno++) { + /* read in first blocks of the ag */ + + read_ag_header(source_fd, agno, &w_buf, &ag_hdr, mp, + source_blocksize, source_sectorsize); + + /* reset uuid and if applicable the in_progress bit */ + + if (!duplicate_uuids) + uuid_copy(ag_hdr.xfs_sb->sb_uuid, fsid); + + if (agno == 0) + INT_SET(ag_hdr.xfs_sb->sb_inprogress, ARCH_CONVERT, 1); + + /* save what we need (agf) in the btree buffer */ + + bcopy(ag_hdr.xfs_agf, btree_buf.data, source_sectorsize); + ag_hdr.xfs_agf = (xfs_agf_t *) btree_buf.data; + btree_buf.length = source_blocksize; + + /* write the ag header out */ + + write_wbuf(); + + /* traverse btree until we get to the leftmost leaf node */ + + bno = INT_GET(ag_hdr.xfs_agf->agf_roots[XFS_BTNUM_BNOi], + ARCH_CONVERT); + current_level = 0; + btree_levels = INT_GET( + ag_hdr.xfs_agf->agf_levels[XFS_BTNUM_BNOi], + ARCH_CONVERT); + + ag_end = XFS_AGB_TO_DADDR(mp, agno, + INT_GET(ag_hdr.xfs_agf->agf_length,ARCH_CONVERT) - 1) + + source_blocksize/BBSIZE; + + for (;;) { + /* none of this touches the w_buf buffer */ + + ASSERT(current_level < btree_levels); + + current_level++; + + btree_buf.position = pos = (xfs_off_t) + XFS_AGB_TO_DADDR(mp,agno,bno) << BBSHIFT; + btree_buf.length = source_blocksize; + + read_wbuf(source_fd, &btree_buf, mp); + block = (xfs_alloc_block_t *) ((char *) btree_buf.data + + pos - btree_buf.position); + + ASSERT(INT_GET(block->bb_magic,ARCH_CONVERT) == + XFS_ABTB_MAGIC); + + if (INT_GET(block->bb_level,ARCH_CONVERT) == 0) + break; + + ptr = XFS_BTREE_PTR_ADDR(sourceb_blocksize, xfs_alloc, + block, 1, mp->m_alloc_mxr[1]), + + bno = *ptr; + } + + /* align first data copy but don't overwrite ag header */ + + pos = w_buf.position >> BBSHIFT; + length = w_buf.length >> BBSHIFT; + next_begin = pos + length; + ag_begin = next_begin; + + ASSERT(w_buf.position % source_sectorsize == 0); + + /* handle the rest of the ag */ + + for (;;) { + if (INT_GET(block->bb_level,ARCH_CONVERT) != 0) { + do_log( + _("WARNING: source filesystem inconsistent.\n")); + do_log( + _(" A leaf btree rec isn't a leaf. Aborting now.\n")); + exit(1); + } + + rec_ptr = XFS_BTREE_REC_ADDR(source_blocksize, + xfs_alloc, block, 1, mp->m_alloc_mxr[0]); + + for (i = 0; + i < INT_GET(block->bb_numrecs,ARCH_CONVERT); + i++, rec_ptr++) { + /* calculate in daddr's */ + + begin = next_begin; + + /* + * protect against pathological case of a + * hole right after the ag header in a + * mis-aligned case + */ + + if (begin < ag_begin) + begin = ag_begin; + + /* + * round size up to ensure we copy a + * range bigger than required + */ + + sizeb = XFS_AGB_TO_DADDR(mp, agno, + INT_GET(rec_ptr->ar_startblock, + ARCH_CONVERT)) - begin; + size = roundup(sizeb < 0) { + /* copy extent */ + + w_buf.position = (xfs_off_t) + begin << BBSHIFT; + + while (size > 0) { + /* + * let lower layer do alignment + */ + if (size > w_buf.size) { + w_buf.length = w_buf.size; + size -= w_buf.size; + sizeb -= wblocks; + numblocks += wblocks; + } else { + w_buf.length = size; + numblocks += sizeb; + size = 0; + } + + read_wbuf(source_fd, &w_buf, mp); + write_wbuf(); + + w_buf.position += w_buf.length; + + while (howfar < 10 && numblocks + > barcount[howfar]) { + bump_bar(howfar); + howfar++; + } + } + } + + /* round next starting point down */ + + new_begin = XFS_AGB_TO_DADDR(mp, agno, + INT_GET(rec_ptr->ar_startblock, + ARCH_CONVERT) + + INT_GET(rec_ptr->ar_blockcount, + ARCH_CONVERT)); + next_begin = rounddown(new_begin, + w_buf.min_io_size >> BBSHIFT); + } + + if (INT_GET(block->bb_rightsib,ARCH_CONVERT) == + NULLAGBLOCK) + break; + + /* read in next btree record block */ + + btree_buf.position = pos = (xfs_off_t) + XFS_AGB_TO_DADDR(mp, agno, + INT_GET(block->bb_rightsib, + ARCH_CONVERT)) << BBSHIFT; + btree_buf.length = source_blocksize; + + /* let read_wbuf handle alignment */ + + read_wbuf(source_fd, &btree_buf, mp); + + block = (xfs_alloc_block_t *) ((char *) btree_buf.data + + pos - btree_buf.position); + + ASSERT(INT_GET(block->bb_magic,ARCH_CONVERT) == + XFS_ABTB_MAGIC); + } + + /* + * write out range of used blocks after last range + * of free blocks in AG + */ + if (next_begin < ag_end) { + begin = next_begin; + + sizeb = ag_end - begin; + size = roundup(sizeb << BBSHIFT, wbuf_miniosize); + + if (size > 0) { + /* copy extent */ + + w_buf.position = (xfs_off_t) begin << BBSHIFT; + + while (size > 0) { + /* + * let lower layer do alignment + */ + if (size > w_buf.size) { + w_buf.length = w_buf.size; + size -= w_buf.size; + sizeb -= wblocks; + numblocks += wblocks; + } else { + w_buf.length = size; + numblocks += sizeb; + size = 0; + } + + read_wbuf(source_fd, &w_buf, mp); + write_wbuf(); + + w_buf.position += w_buf.length; + + while (howfar < 10 && + numblocks > barcount[howfar]) { + bump_bar(howfar); + howfar++; + } + } + } + } + } + + if (kids > 0) { + /* reread and rewrite the first ag */ + + read_ag_header(source_fd, 0, &w_buf, &ag_hdr, mp, + source_blocksize, source_sectorsize); + + ag_hdr.xfs_sb->sb_inprogress = 0; + + if (!duplicate_uuids) + uuid_copy(ag_hdr.xfs_sb->sb_uuid, fsid); + + write_wbuf(); + bump_bar(10); + } + + /* nathans TODO: come back and do this properly... */ + for (i = 0; i < num_targets; i++) { + char command[2048]; + + snprintf(command, sizeof(command), + "%s -x -c 'uuid rewrite' %s >/dev/null 2>&1\n", + "/usr/sbin/xfs_db", target[i].name); + system(command); + } + + check_errors(); + killall(); + pthread_exit(NULL); + /*NOTREACHED*/ + return 0; +} diff --git a/copy/xfs_copy.h b/copy/xfs_copy.h new file mode 100644 index 000000000..181c00c2f --- /dev/null +++ b/copy/xfs_copy.h @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2000-2003 Silicon Graphics, Inc. All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 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. + * + * Further, this software is distributed without any warranty that it is + * free of the rightful claim of any third person regarding infringement + * or the like. Any license provided herein, whether implied or + * otherwise, applies only to this software file. Patent licenses, if + * any, provided herein do not apply to combinations of this program with + * other software, or any other product whatsoever. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston MA 02111-1307, USA. + * + * Contact information: Silicon Graphics, Inc., 1600 Amphitheatre Pkwy, + * Mountain View, CA 94043, or: + * + * http://www.sgi.com + * + * For further information regarding this notice, see: + * + * http://oss.sgi.com/projects/GenInfo/SGIGPLNoticeExplan/ + */ + +/* + * An on-disk allocation group header is composed of 4 structures, + * each of which is 1 disk sector long where the sector size is at + * least 512 bytes long (BBSIZE). + * + * There's one ag_header per ag and the superblock in the first ag + * is the contains the real data for the entire filesystem (although + * most of the relevant data won't change anyway even on a growfs). + * + * The filesystem superblock specifies the number of AG's and + * the AG size. That splits the filesystem up into N pieces, + * each of which is an AG and has an ag_header at the beginning. + */ +typedef struct ag_header { + xfs_sb_t *xfs_sb; /* superblock for filesystem or AG */ + xfs_agf_t *xfs_agf; /* free space info */ + xfs_agi_t *xfs_agi; /* free inode info */ + xfs_agfl_t *xfs_agfl; /* AG freelist */ + char *residue; + int residue_length; +} ag_header_t; + +/* + * The position/buf_position, length/buf_length, data/buffer pairs + * exist because of alignment constraints for direct i/o and dealing + * with scenarios where either the source or target or both is a file + * and the blocksize of the filesystem where file resides is different + * from that of the filesystem image being duplicated. You can get + * alignment problems resulting in things like ag's starting on + * non-aligned points in the filesystem. So you have to be able + * to read from points "before" the requested starting point and + * read in more data than requested. + */ +typedef struct { + int id; /* buffer ID */ + size_t size; /* size of buffer -- fixed */ + size_t min_io_size; /* for direct I/O */ + xfs_off_t position; /* requested position */ + size_t length; /* length of buffer (bytes) */ + char *data; /* pointer to data buffer */ +} wbuf; + +typedef struct { + int id; + pthread_mutex_t wait; + int fd; +} thread_args; + +typedef struct { + pthread_mutex_t mutex; + int num_working; + wbuf *buffer; +} thread_control; + +typedef int thread_id; +typedef int tm_index; /* index into thread mask array */ +typedef __uint32_t thread_mask; /* a thread mask */ + +typedef struct { + char *name; + int fd; + xfs_off_t position; + pthread_t pid; + int state; + int error; + int err_type; +} target_control; + diff --git a/debian/changelog b/debian/changelog index 58e5c80af..dc143121c 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +xfsprogs (2.5.4-1) unstable; urgency=low + + * New upstream release, with new xfs_copy command + + -- Nathan Scott Wed, 23 Jul 2003 10:36:28 +1000 + xfsprogs (2.5.3-1) unstable; urgency=low * New upstream release diff --git a/doc/CHANGES b/doc/CHANGES index 28f5aca8c..1e7a16b04 100644 --- a/doc/CHANGES +++ b/doc/CHANGES @@ -1,6 +1,7 @@ -[cvs] +xfsprogs-2.5.4 (23 July 2003) - Update xfs_io bmap command to report unwritten extent flag if it is set on an extent (in verbose mode only). + - Introducing xfs_copy. xfsprogs-2.5.3 (07 July 2003) - Update xfs_io commands which take user input in terms of diff --git a/man/man8/xfs_copy.8 b/man/man8/xfs_copy.8 new file mode 100644 index 000000000..a4d7c2cf2 --- /dev/null +++ b/man/man8/xfs_copy.8 @@ -0,0 +1,150 @@ +.TH xfs_copy 8 +.SH NAME +xfs_copy \- copy the contents of an XFS filesystem +.SH SYNOPSIS +.nf +\f3xfs_copy\f1 [ \f3\-bd\f1 ] [ \f3\-L\f1 log ] source target1 [ target2 target3 ... ] +.fi +.SH DESCRIPTION +.I xfs_copy +copies an XFS filesystem to one or more targets in parallel +(see +.IR xfs (5)). +The +first (source) +argument must be the pathname of the device or file +containing the XFS filesystem. +The remaining arguments specify one or more target devices +or file names. +If the pathnames specify devices, a copy of the source +XFS filesystem is created on each device. +The target can also be the name of a regular file, +in which case an image of the source XFS filesystem is +created in that file. +If the file does not exist, +.I xfs_copy +creates the file. +The length of the resulting file is equal to the size +of the source filesystem. +However, if the file is created on an XFS filesystem, +the file consumes roughly the amount of space actually +used in the source filesystem by the filesystem and the XFS log. +The space saving is because +.I xfs_copy +seeks over free blocks instead of copying them +and the XFS filesystem supports sparse files efficiently. +.PP +.I xfs_copy +should only be used to copy unmounted filesystems, read-only mounted +filesystems, or frozen filesystems (see xfs_freeze(8)). +Otherwise, the generated filesystem(s) would be inconsistent +or corrupt. +.PP +.I xfs_copy +does not alter the source filesystem in any way. +Each new (target) filesystem is identical to the original +filesystem except that new filesystems each have a new unique +filesystem identifier (UUID). +Therefore, +if both the old and new filesystems will be used as +separate distinct filesystems, +.I xfs_copy +or +.IR xfsdump / xfsrestore +should be used to generate the new filesystem(s) instead of +.IR dd (1) +or other programs that do block-by-block disk copying. +.PP +The +.B \-d +(duplicate) option can be used if a true clone is +desired. +This should be done only if the new filesystem +will be used as a replacement for the original +filesystem (such as in the case of disk replacement). +.PP +.I xfs_copy +uses synchronous writes to ensure that write errors are +detected. +.PP +The +.B \-b +(buffered) option can be used to ensure direct IO is not attempted +to any of the target files. +This is useful when the filesystem holding the target file does not +support direct IO. +.I xfs_copy +also uses +\f2pthreads\f1s +to perform simultaneous parallel writes. +.I xfs_copy +creates one additional thread for each target to be written. +All threads die if +.I xfs_copy +terminates or aborts. +.PP +.I xfs_copy +does not copy XFS filesystems that have a real-time section +or XFS filesystems with external logs. +In both cases, +.I xfs_copy +aborts with an error message. +.SH DIAGNOSTICS +.I xfs_copy +reports errors to both stderr and +in more detailed form to a generated +log file whose name is of the form +.I /var/tmp/xfs_copy.log.XXXXXX +or a log file specified by the +.B \-L +option. +If +.I xfs_copy +detects a write error on a target, +the copy of that one target is aborted and an error +message is issued to both stderr and the log file, but +the rest of the copies continue. +When +.I xfs_copy +terminates, all aborted targets are reported to both stderr and +the log file. +.PP +If all targets abort or if there is an error reading the source filesystem, +.I xfs_copy +immediately aborts. +.PP +.I xfs_copy +returns an exit code of 0 if all targets are successfully +copied and an exit code of 1 if any target fails. +.SH NOTES +When moving filesystems from one disk to another, +if the original filesystem is significantly smaller +than the new filesystem, and will be made larger, we +recommend that +.I +mkfs +and +.I xfsdump/xfsrestore +be used instead of using +.I xfs_copy +and +.I xfs_growfs. +The filesystem layout resulting from using +.I xfs_copy/xfs_growfs +is almost always worse than the result of using +.I mkfs/xfsdump/xfsrestore +but in the case of small filesystems, the +differences can have a significant performance +impact. +This is due to the way +.I xfs_growfs +works, and not due to any shortcoming in +.I xfs_copy +itself. +.SH SEE ALSO +mkfs.xfs(8), +xfsdump(8), +xfsrestore(8), +xfs_freeze(8), +xfs_growfs(8), +xfs(5). -- 2.47.2