Viewing: lustre_rsync.c
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
* Use is subject to license terms.
*
* Copyright (c) 2012, 2017, Intel Corporation.
*/
/*
* This file is part of Lustre, http://www.lustre.org/
*
* lustre/utils/lustre_rsync.c
*
* Author: Kalpak Shah <Kalpak.Shah@Sun.COM>
* Author: Manoj Joseph <Manoj.Joseph@Sun.COM>
*/
/*
* - lustre_rsync is a tool for replicating a lustre filesystem.
*
* - The source-fs is a live lustre filesystem. It is not a
* snapshot. It is mounted and undergoing changes
*
* - The target-fs is a copy of the source-fs from the past. Let's
* call this point, the 'sync point'.
*
* - There is a changelog of all metadata operations that happened on
* the filesystem since the 'sync point'.
*
* - lustre_rsync replicates all the operations saved in the changelog
* on to the target filesystem to make it identical to the source.
*
* To facilitate replication, the lustre filesystem provides
* a) a way to get the current filesystem path of a given FID
* b) a way to open files by specifying its FID
*
* The changelog only has a limited amount of information.
* tfid - The FID of the target file
* pfid - The FID of the parent of the target file (at the time of
* the operation)
* sfid - The FID of the source file
* spfid - The FID of the parent of the source file
* name - The name of the target file (at the time of the operation), the name
* of the source file is appended (delimited with '\0') if this
* operation involves a source
*
* With just this information, it is not alwasy possible to determine
* the file paths for each operation. For instance, if pfid does not
* exist on the source-fs (due to a subsequent deletion), its path
* cannot be queried. In such cases, lustre_rsync keeps the files in a
* special directory ("/.lustrerepl"). Once all the operations in a
* changelog are replayed, all the files in this special directory
* will get moved to the location as in the source-fs.
*
* Shorthand used: f2p(fid) = fid2path(fid)
*
* The following are the metadata operations of interest.
* 1. creat
* If tfid is absent on the source-fs, ignore this operation
* If pfid is absent on the source-fs [or]
* if f2p(pfid) is not present on target-fs [or]
* if f2p(pfid)+name != f2p(tfid)
* creat .lustrerepl/tfid
* track [pfid,tfid,name]
* Else
* creat f2p[tfid]
*
* 2. remove
* If .lustrerepl/[tfid] is present on the target
* rm .lustrerepl/[tfid]
* Else if pfid is present on the source-fs,
* if f2p(pfid)+name is present,
* rm f2p(pfid)+name
*
* 3. move (spfid,sname) to (pfid,name)
* If pfid is present
* if spfid is also present, mv (spfid,sname) to (pfid,name)
* else mv .lustrerepl/[sfid] to (pfid,name)
* Else if pfid is not present,
* if spfid is present, mv (spfid,sname) .lustrerepl/[sfid]
* If moving out of .lustrerepl
* move out all its children in .lustrerepl.
* [pfid,tfid,name] tracked from (1) is used for this.
*/
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <getopt.h>
#include <stdarg.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
#include <limits.h>
#include <utime.h>
#include <time.h>
#include <sys/xattr.h>
#include <linux/types.h>
#include <libcfs/util/string.h>
#include <lustre/lustreapi.h>
#include "lustre_rsync.h"
#include "callvpe.h"
#define REPLICATE_STATUS_VER 1
#define CLEAR_INTERVAL 100
#define DEFAULT_RSYNC_THRESHOLD 0xA00000 /* 10 MB */
#define TYPE_STR_LEN 16
#define DEFAULT_MDT "-MDT0000"
#define SPECIAL_DIR ".lustrerepl"
#define RSYNC "rsync"
#define TYPE "type"
/* Debug flags */
#define DINFO 1
#define DTRACE 2
/*
* Information for processing a changelog record. This structure is
* allocated on the heap instead of allocating large variables on the
* stack.
*/
struct lr_info {
long long recno;
int target_no;
unsigned int is_extended:1;
enum changelog_rec_type type;
char tfid[LR_FID_STR_LEN];
char pfid[LR_FID_STR_LEN];
char sfid[LR_FID_STR_LEN];
char spfid[LR_FID_STR_LEN];
char sname[NAME_MAX + 1];
char name[NAME_MAX + 1];
char src[3 * PATH_MAX + 1];
char dest[3 * PATH_MAX + 1];
char path[PATH_MAX + 1];
char savedpath[PATH_MAX + 1];
char link[PATH_MAX + 1];
char linktmp[PATH_MAX + 1];
int bufsize;
char *buf;
/* Variables for querying the xattributes */
char *xlist;
ssize_t xsize;
char *xvalue;
ssize_t xvsize;
};
struct lr_parent_child_list {
struct lr_parent_child_log pc_log;
struct lr_parent_child_list *pc_next;
};
struct lustre_rsync_status *status;
char *statuslog; /* Name of the status log file */
int logbackedup;
int noxattr; /* Flag to turn off replicating xattrs */
int noclear; /* Flag to turn off clearing changelogs */
int debug; /* Flag to turn debugging information on and off */
int verbose; /* Verbose output */
long long rec_count; /* No of changelog records that were processed */
int errors;
int dryrun;
int use_rsync; /* Flag to turn on use of rsync to copy data */
long long rsync_threshold = DEFAULT_RSYNC_THRESHOLD;
int quit; /* Flag to stop processing the changelog; set on the
* receipt of a signal
*/
int abort_on_err;
char rsync[PATH_MAX + 128];
char rsync_ver[PATH_MAX * 2];
struct lr_parent_child_list *parents;
FILE *debug_log;
/* Command line options */
struct option long_opts[] = {
{ .val = 'l', .name = "statuslog", .has_arg = required_argument },
{ .val = 'm', .name = "mdt", .has_arg = required_argument },
{ .val = 's', .name = "source", .has_arg = required_argument },
{ .val = 't', .name = "target", .has_arg = required_argument },
{ .val = 'u', .name = "user", .has_arg = required_argument },
{ .val = 'v', .name = "verbose", .has_arg = no_argument },
{ .val = 'x', .name = "xattr", .has_arg = required_argument },
{ .val = 'z', .name = "dry-run", .has_arg = no_argument },
{ .val = 'a', .name = "abort-on-err", .has_arg = no_argument },
{ .val = 'h', .name = "help", .has_arg = no_argument },
/* Undocumented options follow */
{ .val = 'c', .name = "cl-clear", .has_arg = required_argument },
{ .val = 'd', .name = "debug", .has_arg = required_argument },
{ .val = 'D', .name = "debuglog", .has_arg = required_argument },
{ .val = 'n', .name = "start-recno", .has_arg = required_argument },
{ .val = 'r', .name = "use-rsync", .has_arg = no_argument },
{ .val = 'y', .name = "rsync-threshold",
.has_arg = required_argument },
{ .name = NULL } };
/* Command line usage */
void lr_usage_short(void)
{
fprintf(stdout,
"Usage: lustre_rsync [option] -s <lustre_root_path> -t <target_path>\n"
"\t-m <mdt> -u <user id> -l <status log>\n");
}
void lr_usage(void)
{
lr_usage_short();
fprintf(stdout,
"The Lustre rsync feature works by periodically running lustre_rsync,\n"
"a userspace program used to synchronize changes in the Lustre file system\n"
"onto the target file system. lustre_rsync can also pick up parameters\n"
"from a status log created earlier.\n"
"Options:\n"
" -s, --source <path>\tThe source Lustre root path to be synchronized.\n"
"\t\t\tMandatory if --statuslog is not specified.\n"
" -t, --target <path>\tThe target root path where the --source file system\n"
"\t\t\twill be synchronized (target)\n"
"\t\t\tMandatory if --statuslog is not specified.\n"
" -m, --mdt <mdt>\tThe metadata device to be synchronized.\n"
"\t\t\tMandatory if --statuslog is not specified.\n"
" -u, --user <user id>\tThe changelog user ID for the specified MDT.\n"
"\t\t\tMandatory if --statuslog is not specified.\n"
" -l, --statuslog <log file>\n"
"\t\t\tLog file to which synchronization status is saved.\n"
"\t\t\tThe {-s,-t,-m,-u} options are read from log file\n"
"\t\t\tif not explicitly specified.\n"
" -x, --xattr <yes|no>\tSynchronize extended attributes (xattr). Default: yes.\n"
" -v, --verbose\t\tProduces verbose output.\n"
" -z, --dry-run\t\tPerform a trial run with no changes made.\n"
" -a, --abort-on-err\tStop on lustre_rsync error. Default: continue on error.\n"
" -h, --help\t\tDisplays this help message.\n");
}
/*
* Print debug information. This is controlled by the value of the
* global variable 'debug'
*/
void lr_debug(int level, const char *fmt, ...)
{
va_list ap;
if (level > debug)
return;
va_start(ap, fmt);
if (debug_log)
vfprintf(debug_log, fmt, ap);
else
vfprintf(stdout, fmt, ap);
va_end(ap);
}
void *lr_grow_buf(void *buf, int size)
{
void *ptr;
ptr = realloc(buf, size);
if (!ptr)
free(buf);
return ptr;
}
/* Use rsync to replicate file data */
int lr_rsync_data(struct lr_info *info)
{
struct stat st_src, st_dest;
int rc;
lr_debug(DTRACE, "Syncing data%s\n", info->tfid);
rc = stat(info->src, &st_src);
if (rc == -1) {
fprintf(stderr, "Error: Unable to stat src=%s %s\n",
info->src, info->name);
if (errno == ENOENT)
return 0;
else
return -errno;
}
rc = stat(info->dest, &st_dest);
if (rc == -1) {
fprintf(stderr, "Error: Unable to stat dest=%s\n",
info->dest);
return -errno;
}
if (st_src.st_mtime != st_dest.st_mtime ||
st_src.st_size != st_dest.st_size) {
/*
* XXX spawning off an rsync for every data sync and
* waiting synchronously is bad for performance.
* librsync could possibly used here. But it does not
* seem to be of production grade. Multi-threaded
* replication is also to be considered.
*/
char *args[] = {
rsync,
"--inplace",
"--",
info->src,
info->dest,
NULL,
};
extern char **environ;
int status;
lr_debug(DTRACE, "\t%s %s %s %s %s %s\n", args[0], args[1],
args[2], args[3], args[4], info->tfid);
status = callvpe(rsync, args, environ);
if (status < 0) {
rc = -errno;
} else if (WIFEXITED(status)) {
status = WEXITSTATUS(status);
if (!status)
rc = 0;
else if (status == 23 || status == 24)
/* Error due to vanished source files;
* Ignore this error
*/
rc = 0;
else
rc = -EINVAL;
if (status)
lr_debug(DINFO, "rsync %s exited with %d %d\n",
info->src, status, rc);
} else {
rc = -EINTR;
}
} else {
lr_debug(DTRACE, "Not syncing %s and %s %s\n", info->src,
info->dest, info->tfid);
}
return rc;
}
int lr_copy_data(struct lr_info *info)
{
int fd_src = -1;
int fd_dest = -1;
int bufsize;
int rsize;
int rc = 0;
struct stat st_src;
struct stat st_dest;
fd_src = open(info->src, O_RDONLY);
if (fd_src == -1)
return -errno;
if (fstat(fd_src, &st_src) == -1 || stat(info->dest, &st_dest) == -1)
goto out;
if (st_src.st_mtime == st_dest.st_mtime &&
st_src.st_size == st_dest.st_size)
goto out;
if (st_src.st_size > rsync_threshold && rsync[0] != '\0') {
/*
* It is more efficient to use rsync to replicate
* large files. Any file larger than rsync_threshold
* is handed off to rsync.
*/
lr_debug(DTRACE, "Using rsync to replicate %s\n", info->tfid);
rc = lr_rsync_data(info);
goto out;
}
fd_dest = open(info->dest, O_WRONLY | O_TRUNC, st_src.st_mode);
if (fd_dest == -1) {
rc = -errno;
goto out;
}
bufsize = st_dest.st_blksize;
if (info->bufsize < bufsize) {
/* Grow buffer */
info->buf = lr_grow_buf(info->buf, bufsize);
if (!info->buf) {
rc = -ENOMEM;
goto out;
}
info->bufsize = bufsize;
}
while (1) {
char *buf;
int wsize;
buf = info->buf;
rsize = read(fd_src, buf, bufsize);
if (rsize == 0) {
rc = 0;
break;
}
if (rsize < 0) {
rc = -errno;
break;
}
do {
wsize = write(fd_dest, buf, rsize);
if (wsize <= 0) {
rc = -errno;
break;
}
rsize -= wsize;
buf += wsize;
} while (rsize > 0);
}
fsync(fd_dest);
out:
if (fd_src != -1)
close(fd_src);
if (fd_dest != -1)
close(fd_dest);
return rc;
}
/* Copy data from source to destination */
int lr_sync_data(struct lr_info *info)
{
if (use_rsync)
return lr_rsync_data(info);
else
return lr_copy_data(info);
}
/* Copy all attributes from file src to file dest */
int lr_copy_attr(const char *src, const char *dest)
{
struct stat st;
struct utimbuf time;
if (stat(src, &st) == -1 || chmod(dest, st.st_mode) == -1 ||
chown(dest, st.st_uid, st.st_gid) == -1)
return -errno;
time.actime = st.st_atime;
time.modtime = st.st_mtime;
if (utime(dest, &time) == -1)
return -errno;
return 0;
}
/* Copy all xattrs from file info->src to info->dest */
int lr_copy_xattr(struct lr_info *info)
{
ssize_t size = info->xsize;
ssize_t rc;
int start;
int len;
if (noxattr)
return 0;
errno = 0;
rc = llistxattr(info->src, info->xlist, size);
lr_debug(DTRACE, "llistxattr(%s,%p) returned %zd, errno=%d\n",
info->src, info->xlist, rc, errno);
if ((rc > 0 && info->xlist == NULL) || errno == ERANGE) {
size = rc > PATH_MAX ? rc : PATH_MAX;
info->xlist = lr_grow_buf(info->xlist, size);
if (!info->xlist)
return -ENOMEM;
info->xsize = size;
rc = llistxattr(info->src, info->xlist, size);
lr_debug(DTRACE, "llistxattr %s returned %zd, errno=%d\n",
info->src, rc, errno);
}
if (rc < 0)
return rc;
len = rc;
start = 0;
while (start < len) {
size = info->xvsize;
rc = lgetxattr(info->src, info->xlist + start,
NULL, 0);
if (rc < 0) {
lr_debug(DTRACE, "\t(%s,0) rc=%zd, errno=%d\n",
info->xlist + start, rc, errno);
start += strlen(info->xlist + start) + 1;
continue;
}
if (rc > size) {
/*
* XATTR_SIZE_MAX should be the upper limit, but
* just in case.
*/
size = rc > XATTR_SIZE_MAX ? rc : XATTR_SIZE_MAX;
info->xvalue = lr_grow_buf(info->xvalue, size);
if (!info->xvalue)
return -ENOMEM;
info->xvsize = size;
}
rc = lgetxattr(info->src, info->xlist + start,
info->xvalue, size);
lr_debug(DTRACE, "\t(%s,%s) rc=%zd\n", info->xlist + start,
info->xvalue, rc);
if (rc > 0) {
size = rc;
rc = lsetxattr(info->dest, info->xlist + start,
info->xvalue, size, 0);
lr_debug(DTRACE, "\tlsetxattr(), rc=%zd, errno=%d\n",
rc, errno);
if (rc == -1) {
if (errno != ENOTSUP) {
fprintf(stderr, "cannot replicate xattrs from '%s' to '%s': %s\n",
info->src, info->dest,
strerror(errno));
errors++;
}
rc = 0;
}
}
start += strlen(info->xlist + start) + 1;
}
lr_debug(DINFO, "setxattr: %s %s\n", info->src, info->dest);
return rc;
}
/*
* Retrieve the filesystem path for a given FID and a given
* linkno. The path is returned in info->path
*/
int lr_get_path_ln(struct lr_info *info, char *fidstr, int linkno)
{
long long recno = -1;
int rc;
rc = llapi_fid2path(status->ls_source, fidstr, info->path,
PATH_MAX, &recno, &linkno);
if (rc < 0 && rc != -ENOENT) {
fprintf(stderr, "fid2path error: (%s, %s) %d %s\n",
status->ls_source, fidstr, -rc, strerror(errno = -rc));
}
return rc;
}
/*
* Retrieve the filesystem path for a given FID. The path is returned
* in info->path
*/
int lr_get_path(struct lr_info *info, char *fidstr)
{
return lr_get_path_ln(info, fidstr, 0);
}
/* Generate the path for opening by FID */
void lr_get_FID_PATH(char *mntpt, char *fidstr, char *buf, int bufsize)
{
/* Open-by-FID path is <mntpt>/.lustre/fid/[SEQ:OID:VER] */
snprintf(buf, bufsize, "%s/%s/fid/%s", mntpt, dot_lustre_name,
fidstr);
}
/* Read the symlink information into 'info->link' */
int lr_get_symlink(struct lr_info *info)
{
int rc;
char *link;
lr_get_FID_PATH(status->ls_source, info->tfid, info->src, PATH_MAX);
rc = readlink(info->src, info->linktmp, PATH_MAX);
if (rc > 0)
info->linktmp[rc] = '\0';
else
return rc;
lr_debug(DTRACE, "symlink: readlink returned %s\n", info->linktmp);
if (strncmp(info->linktmp, status->ls_source,
strlen(status->ls_source)) == 0) {
/* Strip source fs path and replace with target fs path. */
link = info->linktmp + strlen(status->ls_source);
snprintf(info->src, sizeof(info->src), "%s%s",
status->ls_targets[info->target_no], link);
link = info->src;
} else {
link = info->linktmp;
}
rc = snprintf(info->link, sizeof(info->link), "%s", link);
if (rc >= sizeof(info->link))
rc = -E2BIG;
return rc;
}
/* Create file/directory/device file/symlink. */
int lr_mkfile(struct lr_info *info)
{
struct stat st;
int rc = 0;
errno = 0;
lr_debug(DINFO, "mkfile(%d) %s\n", info->type, info->dest);
if (info->type == CL_MKDIR) {
rc = mkdir(info->dest, 0777);
} else if (info->type == CL_SOFTLINK) {
lr_get_symlink(info);
rc = symlink(info->link, info->dest);
} else if (info->type == CL_MKNOD) {
lr_get_FID_PATH(status->ls_source, info->tfid,
info->src, PATH_MAX);
rc = stat(info->src, &st);
if (rc == -1) {
if (errno == ENOENT)
return 0;
else
return -errno;
}
rc = mknod(info->dest, st.st_mode, st.st_rdev);
} else {
rc = mknod(info->dest, S_IFREG | 0777, 0);
}
if (rc < 0) {
if (errno == EEXIST)
rc = 0;
else
return -errno;
}
/* Sync data and attributes */
if (info->type == CL_CREATE || info->type == CL_MKDIR) {
lr_debug(DTRACE, "Syncing data and attributes %s\n",
info->tfid);
(void)lr_copy_xattr(info);
if (info->type == CL_CREATE)
rc = lr_sync_data(info);
if (!rc)
rc = lr_copy_attr(info->src, info->dest);
if (rc == -ENOENT)
/* Source file has disappeared. Not an error. */
rc = 0;
} else {
lr_debug(DTRACE, "Not syncing data and attributes %s\n",
info->tfid);
}
return rc;
}
int lr_add_pc(const char *pfid, const char *tfid, const char *name)
{
struct lr_parent_child_list *p;
size_t len;
p = calloc(1, sizeof(*p));
if (!p)
return -ENOMEM;
len = snprintf(p->pc_log.pcl_pfid, sizeof(p->pc_log.pcl_pfid),
"%s", pfid);
if (len >= sizeof(p->pc_log.pcl_pfid))
goto out_err;
len = snprintf(p->pc_log.pcl_tfid, sizeof(p->pc_log.pcl_tfid),
"%s", tfid);
if (len >= sizeof(p->pc_log.pcl_tfid))
goto out_err;
len = snprintf(p->pc_log.pcl_name, sizeof(p->pc_log.pcl_name),
"%s", name);
if (len >= sizeof(p->pc_log.pcl_name))
goto out_err;
p->pc_next = parents;
parents = p;
return 0;
out_err:
free(p);
return -E2BIG;
}
void lr_cascade_move(const char *fid, const char *dest, struct lr_info *info)
{
struct lr_parent_child_list *curr, *prev;
char d[4 * PATH_MAX + 1];
int rc;
prev = curr = parents;
while (curr) {
if (strcmp(curr->pc_log.pcl_pfid, fid) == 0) {
if (snprintf(d, sizeof(d), "%s/%s", dest,
curr->pc_log.pcl_name) >= sizeof(d)) {
fprintf(stderr, "Buffer truncated\n");
return;
}
if (snprintf(info->src, sizeof(info->src), "%s/%s/%s",
status->ls_targets[info->target_no],
SPECIAL_DIR, curr->pc_log.pcl_tfid) >=
sizeof(info->src))
return;
rc = rename(info->src, d);
if (rc == -1) {
fprintf(stderr, "Error renaming file %s to %s: %d\n",
info->src, d, errno);
errors++;
}
if (curr == parents)
parents = curr->pc_next;
else
prev->pc_next = curr->pc_next;
lr_cascade_move(curr->pc_log.pcl_tfid, d, info);
free(curr);
prev = curr = parents;
} else {
prev = curr;
curr = curr->pc_next;
}
}
}
/* remove [info->spfid, info->sfid] from parents */
int lr_remove_pc(const char *pfid, const char *tfid)
{
struct lr_parent_child_list *curr, *prev;
for (prev = curr = parents; curr; prev = curr, curr = curr->pc_next) {
if (strcmp(curr->pc_log.pcl_pfid, pfid) == 0 &&
strcmp(curr->pc_log.pcl_tfid, tfid) == 0) {
if (curr == parents)
parents = curr->pc_next;
else
prev->pc_next = curr->pc_next;
free(curr);
break;
}
}
return 0;
}
/* Create file under SPECIAL_DIR with its tfid as its name. */
int lr_mk_special(struct lr_info *info)
{
int rc;
snprintf(info->dest, sizeof(info->dest), "%s/%s/%s",
status->ls_targets[info->target_no], SPECIAL_DIR, info->tfid);
rc = lr_mkfile(info);
if (rc)
return rc;
rc = lr_add_pc(info->pfid, info->tfid, info->name);
return rc;
}
/* Remove a file or directory */
int lr_rmfile(struct lr_info *info)
{
int rc;
if (info->type == CL_RMDIR)
rc = rmdir(info->dest);
else
rc = unlink(info->dest);
if (rc == -1)
rc = -errno;
return rc;
}
/* Recursively remove directory and its contents */
int lr_rm_recursive(struct lr_info *info)
{
char *args[] = {
"rm",
"-rf",
"--",
info->dest,
NULL,
};
extern char **environ;
int status;
int rc;
status = callvpe("/bin/rm", args, environ);
if (status < 0)
rc = -errno;
else if (WIFEXITED(status))
rc = WEXITSTATUS(status) == 0 ? 0 : -EINVAL;
else
rc = -EINTR;
return rc;
}
/* Remove a file under SPECIAL_DIR with its tfid as its name. */
int lr_rm_special(struct lr_info *info)
{
int rc;
snprintf(info->dest, sizeof(info->dest), "%s/%s/%s",
status->ls_targets[info->target_no], SPECIAL_DIR, info->tfid);
rc = lr_rmfile(info);
if (rc)
lr_debug(DINFO, "remove: %s; rc=%d, errno=%d\n",
info->dest, rc, errno);
return rc;
}
/* Replicate file and directory create events */
int lr_create(struct lr_info *info)
{
int len;
int rc1 = 0;
int rc;
int mkspecial = 0;
/* Is target FID present on the source? */
rc = lr_get_path(info, info->tfid);
if (rc == -ENOENT) {
/* Source file has disappeared. Not an error. */
lr_debug(DINFO, "create: tfid %s not found on source-fs\n",
info->tfid);
return 0;
} else if (rc) {
return rc;
}
strcpy(info->savedpath, info->path);
/* Is parent FID present on the source */
rc = lr_get_path(info, info->pfid);
if (rc == -ENOENT) {
lr_debug(DINFO, "create: pfid %s not found on source-fs\n",
info->tfid);
mkspecial = 1;
} else if (rc < 0) {
return rc;
}
/* Is f2p(pfid)+name != f2p(tfid)? If not the file has moved. */
len = strlen(info->path);
if (len == 1 && info->path[0] == '/')
snprintf(info->dest, sizeof(info->dest), "%s", info->name);
else if (len - 1 > 0 && info->path[len - 1] == '/')
snprintf(info->dest, sizeof(info->dest), "%s%s", info->path,
info->name);
else
snprintf(info->dest, sizeof(info->dest), "%s/%s", info->path,
info->name);
lr_debug(DTRACE, "dest = %s; savedpath = %s\n", info->dest,
info->savedpath);
if (strncmp(info->dest, info->savedpath, PATH_MAX) != 0) {
lr_debug(DTRACE, "create: file moved (%s). %s != %s\n",
info->tfid, info->dest, info->savedpath);
mkspecial = 1;
}
/* Is f2p(pfid) present on the target? If not, the parent has moved */
if (!mkspecial) {
snprintf(info->dest, sizeof(info->dest), "%s/%s",
status->ls_targets[0], info->path);
if (access(info->dest, F_OK) != 0) {
lr_debug(DTRACE, "create: parent %s not found\n",
info->dest);
mkspecial = 1;
}
}
for (info->target_no = 0; info->target_no < status->ls_num_targets;
info->target_no++) {
snprintf(info->dest, sizeof(info->dest), "%s/%s",
status->ls_targets[info->target_no], info->savedpath);
lr_get_FID_PATH(status->ls_source, info->tfid, info->src,
PATH_MAX);
if (!mkspecial)
rc1 = lr_mkfile(info);
if (mkspecial || rc1 == -ENOENT)
rc1 = lr_mk_special(info);
if (rc1)
rc = rc1;
}
return rc;
}
/* Replicate a file remove (rmdir/unlink) operation */
int lr_remove(struct lr_info *info)
{
int rc = 0;
int rc1;
for (info->target_no = 0; info->target_no < status->ls_num_targets;
info->target_no++) {
rc1 = lr_rm_special(info);
if (!rc1)
continue;
rc1 = lr_get_path(info, info->pfid);
if (rc1 == -ENOENT) {
lr_debug(DINFO, "remove: pfid %s not found\n",
info->pfid);
continue;
}
if (rc1) {
rc = rc1;
continue;
}
snprintf(info->dest, sizeof(info->dest), "%s/%s/%s",
status->ls_targets[info->target_no], info->path,
info->name);
rc1 = lr_rmfile(info);
lr_debug(DINFO, "remove: %s; rc1=%d, errno=%d\n",
info->dest, rc1, errno);
if (rc1 == -ENOTEMPTY)
rc1 = lr_rm_recursive(info);
if (rc1) {
rc = rc1;
continue;
}
}
return rc;
}
/* Replicate a rename/move operation. */
int lr_move(struct lr_info *info)
{
int rc = 0;
int rc1;
int rc_dest, rc_src;
int special_src = 0;
int special_dest = 0;
char srcpath[PATH_MAX + 1] = "";
assert(info->is_extended);
rc_src = lr_get_path(info, info->spfid);
if (rc_src < 0 && rc_src != -ENOENT)
return rc_src;
memcpy(srcpath, info->path, strlen(info->path));
rc_dest = lr_get_path(info, info->pfid);
if (rc_dest < 0 && rc_dest != -ENOENT)
return rc_dest;
for (info->target_no = 0; info->target_no < status->ls_num_targets;
info->target_no++) {
if (!rc_dest) {
snprintf(info->dest, sizeof(info->dest), "%s/%s",
status->ls_targets[info->target_no],
info->path);
if (access(info->dest, F_OK) != 0) {
rc_dest = -errno;
} else {
snprintf(info->dest, sizeof(info->dest),
"%s/%s/%s",
status->ls_targets[info->target_no],
info->path, info->name);
}
lr_debug(DINFO, "dest path %s rc_dest=%d\n", info->dest,
rc_dest);
}
if (rc_dest == -ENOENT) {
snprintf(info->dest, sizeof(info->dest), "%s/%s/%s",
status->ls_targets[info->target_no],
SPECIAL_DIR, info->sfid);
special_dest = 1;
lr_debug(DINFO, "special dest %s\n", info->dest);
}
if (!rc_src) {
snprintf(info->src, sizeof(info->src), "%s/%s/%s",
status->ls_targets[info->target_no],
srcpath, info->sname);
lr_debug(DINFO, "src path %s rc_src=%d\n", info->src,
rc_src);
}
if (rc_src == -ENOENT ||
(access(info->src, F_OK) != 0 && errno == ENOENT)) {
snprintf(info->src, sizeof(info->src), "%s/%s/%s",
status->ls_targets[info->target_no],
SPECIAL_DIR, info->sfid);
special_src = 1;
lr_debug(DINFO, "special src %s\n", info->src);
}
rc1 = 0;
errno = 0;
if (strcmp(info->src, info->dest) != 0) {
rc1 = rename(info->src, info->dest);
if (rc1 == -1)
rc1 = -errno;
lr_debug(DINFO, "rename returns %d\n", rc1);
}
if (special_src)
rc1 = lr_remove_pc(info->spfid, info->sfid);
if (!special_dest)
lr_cascade_move(info->sfid, info->dest, info);
else
rc1 = lr_add_pc(info->pfid, info->sfid, info->name);
lr_debug(DINFO, "move: %s [to] %s rc1=%d, errno=%d\n",
info->src, info->dest, rc1, errno);
if (rc1)
rc = rc1;
}
return rc;
}
/* Replicate a hard link */
int lr_link(struct lr_info *info)
{
int i;
int rc;
int rc1;
struct stat st;
lr_get_FID_PATH(status->ls_source, info->tfid, info->src, PATH_MAX);
rc = stat(info->src, &st);
if (rc == -1)
return -errno;
for (info->target_no = 0; info->target_no < status->ls_num_targets;
info->target_no++) {
info->src[0] = 0;
info->dest[0] = 0;
rc1 = 0;
/*
* The changelog record has the new parent directory FID and
* name of the target file. So info->dest can be constructed
* by getting the path of the new parent directory and
* appending the target file name.
*/
rc1 = lr_get_path(info, info->pfid);
lr_debug(rc1 ? 0 : DTRACE, "\tparent fid2path %s, %s, rc=%d\n",
info->path, info->name, rc1);
if (rc1 == 0) {
snprintf(info->dest, sizeof(info->dest), "%s/%s/%s",
status->ls_targets[info->target_no],
info->path, info->name);
lr_debug(DINFO, "link destination is %s\n", info->dest);
}
/* Search through the hardlinks to get the src */
for (i = 0; i < st.st_nlink && info->src[0] == 0; i++) {
size_t len;
rc1 = lr_get_path_ln(info, info->tfid, i);
lr_debug(rc1 ? 0 : DTRACE,
"\tfid2path %s, %s, %d rc=%d\n", info->path,
info->name, i, rc1);
if (rc1)
break;
/*
* Compare the path of target FID with info->dest
* to find out info->src.
*/
len = sizeof(status->ls_targets[info->target_no]) +
sizeof(info->path);
char srcpath[len + 1];
snprintf(srcpath, sizeof(srcpath), "%s/%s",
status->ls_targets[info->target_no],
info->path);
if (strcmp(srcpath, info->dest) != 0) {
snprintf(info->src, sizeof(info->src), "%s",
srcpath);
lr_debug(DINFO, "link source is %s\n",
info->src);
}
}
if (rc1) {
rc = rc1;
continue;
}
if (info->src[0] == 0)
snprintf(info->src, sizeof(info->src), "%s/%s/%s",
status->ls_targets[info->target_no],
SPECIAL_DIR, info->tfid);
else if (info->dest[0] == 0)
snprintf(info->dest, sizeof(info->dest), "%s/%s/%s",
status->ls_targets[info->target_no],
SPECIAL_DIR, info->tfid);
rc1 = link(info->src, info->dest);
lr_debug(DINFO, "link: %s [to] %s; rc1=%d %s\n",
info->src, info->dest, rc1,
strerror(rc1 ? errno : 0));
if (rc1)
rc = rc1;
}
return rc;
}
int lr_set_dest_for_attr(struct lr_info *info)
{
int rc;
snprintf(info->dest, sizeof(info->dest), "%s/%s",
status->ls_targets[info->target_no], info->path);
rc = access(info->dest, F_OK);
if (rc < 0)
rc = -errno;
if (rc != -ENOENT)
return rc;
snprintf(info->dest, sizeof(info->dest), "%s/%s/%s",
status->ls_targets[info->target_no], SPECIAL_DIR,
info->tfid);
rc = access(info->dest, F_OK);
if (rc < 0)
return -errno;
return 0;
}
/* Replicate file attributes */
int lr_setattr(struct lr_info *info)
{
int rc1;
int rc;
lr_get_FID_PATH(status->ls_source, info->tfid, info->src, PATH_MAX);
rc = lr_get_path(info, info->tfid);
if (rc == -ENOENT)
lr_debug(DINFO, "setattr: %s not present on source-fs\n",
info->src);
if (rc)
return rc;
for (info->target_no = 0; info->target_no < status->ls_num_targets;
info->target_no++) {
rc = lr_set_dest_for_attr(info);
if (rc < 0)
continue;
lr_debug(DINFO, "setattr: %s %s %s", info->src, info->dest,
info->tfid);
rc1 = lr_sync_data(info);
if (!rc1)
rc1 = lr_copy_attr(info->src, info->dest);
if (rc1)
rc = rc1;
}
return rc;
}
/* Replicate xattrs */
int lr_setxattr(struct lr_info *info)
{
int rc, rc1;
lr_get_FID_PATH(status->ls_source, info->tfid, info->src, PATH_MAX);
rc = lr_get_path(info, info->tfid);
if (rc == -ENOENT)
lr_debug(DINFO, "setxattr: %s not present on source-fs\n",
info->src);
if (rc)
return rc;
for (info->target_no = 0; info->target_no < status->ls_num_targets;
info->target_no++) {
rc = lr_set_dest_for_attr(info);
if (rc < 0)
continue;
lr_debug(DINFO, "setxattr: %s %s %s\n", info->src, info->dest,
info->tfid);
rc1 = lr_copy_xattr(info);
if (rc1)
rc = rc1;
}
return rc;
}
/* Parse a line of changelog entry */
int lr_parse_line(void *priv, struct lr_info *info)
{
struct changelog_rec *rec;
struct changelog_ext_rename *rnm;
size_t namelen;
size_t copylen = sizeof(info->name);
if (llapi_changelog_recv(priv, &rec) != 0)
return -1;
info->is_extended = !!(rec->cr_flags & CLF_RENAME);
info->recno = rec->cr_index;
info->type = rec->cr_type;
snprintf(info->tfid, sizeof(info->tfid), DFID, PFID(&rec->cr_tfid));
snprintf(info->pfid, sizeof(info->pfid), DFID, PFID(&rec->cr_pfid));
namelen = strnlen(changelog_rec_name(rec), rec->cr_namelen);
if (copylen > namelen + 1)
copylen = namelen + 1;
snprintf(info->name, copylen, "%s", changelog_rec_name(rec));
/* Don't use rnm if CLF_RENAME isn't set */
rnm = changelog_rec_rename(rec);
if (rec->cr_flags & CLF_RENAME && !fid_is_zero(&rnm->cr_sfid)) {
copylen = sizeof(info->sname);
snprintf(info->sfid, sizeof(info->sfid), DFID,
PFID(&rnm->cr_sfid));
snprintf(info->spfid, sizeof(info->spfid), DFID,
PFID(&rnm->cr_spfid));
namelen = changelog_rec_snamelen(rec);
if (copylen > namelen + 1)
copylen = namelen + 1;
snprintf(info->sname, copylen, "%s", changelog_rec_sname(rec));
if (verbose > 1)
printf("Rec %lld: %d %s %s\n", info->recno, info->type,
info->name, info->sname);
} else {
if (verbose > 1)
printf("Rec %lld: %d %s\n", info->recno, info->type,
info->name);
}
llapi_changelog_free(&rec);
rec_count++;
return 0;
}
/* Initialize the replication parameters */
int lr_init_status(void)
{
size_t size = sizeof(struct lustre_rsync_status) + PATH_MAX + 1;
if (status)
return 0;
status = calloc(size, 1);
if (!status)
return -ENOMEM;
status->ls_version = REPLICATE_STATUS_VER;
status->ls_size = size;
status->ls_last_recno = -1;
return 0;
}
/* Make a backup of the statuslog */
void lr_backup_log(void)
{
char backupfile[PATH_MAX];
if (logbackedup)
return;
snprintf(backupfile, sizeof(backupfile), "%s.old", statuslog);
(void) rename(statuslog, backupfile);
logbackedup = 1;
}
/* Save replication parameters to a statuslog. */
int lr_write_log(void)
{
int fd;
size_t size;
size_t write_size = status->ls_size;
struct lr_parent_child_list *curr;
int rc = 0;
if (!statuslog)
return 0;
lr_backup_log();
fd = open(statuslog, O_WRONLY | O_CREAT | O_SYNC, S_IRUSR | S_IWUSR);
if (fd == -1) {
fprintf(stderr, "Error opening log file for writing (%s)\n",
statuslog);
return -1;
}
errno = 0;
size = write(fd, status, write_size);
if (size != write_size) {
fprintf(stderr, "Error writing to log file (%s) %d\n",
statuslog, errno);
close(fd);
return -1;
}
for (curr = parents; curr; curr = curr->pc_next) {
size = write(fd, &curr->pc_log, sizeof(curr->pc_log));
if (size != sizeof(curr->pc_log)) {
fprintf(stderr, "Error writing to log file (%s) %d\n",
statuslog, errno);
rc = -1;
break;
}
}
close(fd);
return rc;
}
/*
* Read statuslog and populate the replication parameters. Command
* line parameters take precedence over parameters in the log file.
*/
int lr_read_log(void)
{
struct lr_parent_child_list *tmp;
struct lr_parent_child_log rec;
struct lustre_rsync_status *s;
int fd = -1;
size_t size;
size_t read_size = sizeof(struct lustre_rsync_status) + PATH_MAX + 1;
int rc = 0;
if (!statuslog)
return 0;
s = calloc(1, read_size);
if (!s) {
rc = -ENOMEM;
goto out;
}
fd = open(statuslog, O_RDONLY);
if (fd == -1) {
rc = -errno;
goto out;
}
size = read(fd, s, read_size);
if (size != read_size) {
rc = -EINVAL;
goto out;
}
if (read_size < s->ls_size) {
read_size = s->ls_size;
s = lr_grow_buf(s, read_size);
if (!s) {
rc = -ENOMEM;
goto out;
}
if (lseek(fd, 0, SEEK_SET) == -1) {
rc = -ENOMEM;
goto out;
}
size = read(fd, s, read_size);
if (size != read_size) {
rc = -EINVAL;
goto out;
}
}
while (read(fd, &rec, sizeof(rec)) != 0) {
tmp = calloc(1, sizeof(*tmp));
if (!tmp) {
rc = -ENOMEM;
goto out;
}
tmp->pc_log = rec;
tmp->pc_next = parents;
parents = tmp;
}
/* copy uninitialized fields to status */
if (status->ls_num_targets == 0) {
if (status->ls_size != s->ls_size) {
status = lr_grow_buf(status, s->ls_size);
if (!status) {
rc = -ENOMEM;
goto out;
}
status->ls_size = s->ls_size;
}
status->ls_num_targets = s->ls_num_targets;
memcpy(status->ls_targets, s->ls_targets,
(PATH_MAX + 1) * s->ls_num_targets);
}
if (status->ls_last_recno == -1)
status->ls_last_recno = s->ls_last_recno;
if (status->ls_registration[0] == '\0')
snprintf(status->ls_registration,
sizeof(status->ls_registration), "%s",
s->ls_registration);
if (status->ls_mdt_device[0] == '\0')
snprintf(status->ls_mdt_device,
sizeof(status->ls_mdt_device), "%s",
s->ls_mdt_device);
if (status->ls_source_fs[0] == '\0')
snprintf(status->ls_source_fs,
sizeof(status->ls_source_fs), "%s",
s->ls_source_fs);
if (status->ls_source[0] == '\0')
snprintf(status->ls_source,
sizeof(status->ls_source), "%s",
s->ls_source);
out:
if (fd != -1)
close(fd);
if (s)
free(s);
return rc;
}
/*
* Clear changelogs every CLEAR_INTERVAL records or at the end of
* processing.
*/
int lr_clear_cl(struct lr_info *info, int force)
{
char mdt_device[LR_NAME_MAXLEN + 1];
int rc = 0;
if (force || info->recno > status->ls_last_recno + CLEAR_INTERVAL) {
if (!noclear && !dryrun) {
/*
* llapi_changelog_clear modifies the mdt
* device name so make a copy of it until this
* is fixed.
*/
snprintf(mdt_device, sizeof(mdt_device), "%s",
status->ls_mdt_device);
rc = llapi_changelog_clear(mdt_device,
status->ls_registration,
info->recno);
if (rc)
fprintf(stderr,
"Changelog clear (%s, %s, %lld) returned %d\n",
status->ls_mdt_device,
status->ls_registration, info->recno,
rc);
}
if (!rc && !dryrun) {
status->ls_last_recno = info->recno;
lr_write_log();
}
}
return rc;
}
/*
* Locate a usable version of rsync. At this point we'll use any
* version.
*/
int lr_locate_rsync(void)
{
FILE *fp;
int len;
/* Locate rsync */
snprintf(rsync, sizeof(rsync), "%s -p %s", TYPE, RSYNC);
fp = popen(rsync, "r");
if (!fp)
return -1;
if (fgets(rsync, sizeof(rsync), fp) == NULL) {
pclose(fp);
return -1;
}
len = strlen(rsync);
if (len > 0 && rsync[len - 1] == '\n')
rsync[len - 1] = '\0';
pclose(fp);
/* Determine the version of rsync */
snprintf(rsync_ver, sizeof(rsync_ver), "%s --version", rsync);
fp = popen(rsync_ver, "r");
if (!fp)
return -1;
if (fgets(rsync_ver, sizeof(rsync_ver), fp) == NULL) {
pclose(fp);
return -1;
}
len = strlen(rsync_ver);
if (len > 0 && rsync_ver[len - 1] == '\n')
rsync_ver[len - 1] = '\0';
pclose(fp);
return 0;
}
/* Print the replication parameters */
void lr_print_status(struct lr_info *info)
{
int i;
if (!verbose)
return;
printf("Lustre filesystem: %s\n", status->ls_source_fs);
printf("MDT device: %s\n", status->ls_mdt_device);
printf("Source: %s\n", status->ls_source);
for (i = 0; i < status->ls_num_targets; i++)
printf("Target: %s\n", status->ls_targets[i]);
if (statuslog)
printf("Statuslog: %s\n", statuslog);
printf("Changelog registration: %s\n", status->ls_registration);
printf("Starting changelog record: %jd\n",
(uintmax_t)status->ls_last_recno);
if (noxattr)
printf("Replicate xattrs: no\n");
if (noclear)
printf("Clear changelog after use: no\n");
if (use_rsync)
printf("Using rsync: %s (%s)\n", rsync, rsync_ver);
}
void lr_print_failure(struct lr_info *info, int rc)
{
fprintf(stderr,
"Replication of operation failed(%d): %lld %s (%d) %s %s %s\n",
rc, info->recno, changelog_type2str(info->type), info->type,
info->tfid, info->pfid, info->name);
}
/* Replicate filesystem operations from src_path to target_path */
int lr_replicate(void)
{
void *changelog_priv = NULL;
struct lr_info *info;
struct lr_info *ext = NULL;
time_t start;
int xattr_not_supp;
int i, rc;
ssize_t rc1;
start = time(NULL);
info = calloc(1, sizeof(struct lr_info));
if (!info)
return -ENOMEM;
rc = llapi_search_fsname(status->ls_source, status->ls_source_fs);
if (rc) {
fprintf(stderr, "Source path is not a valid Lustre client mountpoint.\n");
goto out;
}
if (status->ls_mdt_device[0] == '\0') {
int len;
len = snprintf(status->ls_mdt_device,
sizeof(status->ls_mdt_device), "%s%s",
status->ls_source_fs, DEFAULT_MDT);
if (len >= sizeof(status->ls_mdt_device)) {
rc = -E2BIG;
goto out;
}
}
ext = calloc(1, sizeof(struct lr_info));
if (!ext) {
rc = -ENOMEM;
goto out;
}
for (i = 0, xattr_not_supp = 0; i < status->ls_num_targets; i++) {
snprintf(info->dest, sizeof(info->dest), "%s/%s",
status->ls_targets[i], SPECIAL_DIR);
rc = mkdir(info->dest, 0777);
if (rc == -1 && errno != EEXIST) {
fprintf(stderr, "Error writing to target path %s.\n",
status->ls_targets[i]);
rc = -errno;
goto out;
}
rc1 = llistxattr(info->src, info->xlist, info->xsize);
if (rc1 == -1 && errno == ENOTSUP) {
fprintf(stderr, "xattrs not supported on %s\n",
status->ls_targets[i]);
xattr_not_supp++;
}
}
if (xattr_not_supp == status->ls_num_targets)
/* None of the targets support xattrs. */
noxattr = 1;
lr_print_status(info);
/* Open changelogs for consumption*/
rc = llapi_changelog_start(&changelog_priv,
CHANGELOG_FLAG_BLOCK |
CHANGELOG_FLAG_JOBID |
CHANGELOG_FLAG_EXTRA_FLAGS,
status->ls_mdt_device,
status->ls_last_recno);
if (rc < 0) {
fprintf(stderr, "Error opening changelog file for fs %s.\n",
status->ls_source_fs);
goto out;
}
rc = llapi_changelog_set_xflags(changelog_priv,
CHANGELOG_EXTRA_FLAG_UIDGID);
if (rc < 0) {
fprintf(stderr, "Error setting xflag in changelog for fs %s.\n",
status->ls_source_fs);
goto out;
}
while (!quit && lr_parse_line(changelog_priv, info) == 0) {
rc = 0;
if (info->type == CL_RENAME && !info->is_extended) {
/*
* Newer rename operations extends changelog to store
* source file information, but old changelog has
* another record.
*/
if (lr_parse_line(changelog_priv, ext) != 0)
break;
memcpy(info->sfid, info->tfid, sizeof(info->sfid));
memcpy(info->spfid, info->pfid, sizeof(info->spfid));
memcpy(info->tfid, ext->tfid, sizeof(info->tfid));
memcpy(info->pfid, ext->pfid, sizeof(info->pfid));
snprintf(info->sname, sizeof(info->sname), "%s",
info->name);
snprintf(info->name, sizeof(info->name), "%s",
ext->name);
info->is_extended = 1;
info->recno = ext->recno; /* For lr_clear_cl(). */
}
if (dryrun)
continue;
lr_debug(DTRACE, "***** Start %lld %s (%d) %s %s %s *****\n",
info->recno, changelog_type2str(info->type),
info->type, info->tfid, info->pfid, info->name);
switch (info->type) {
case CL_CREATE:
case CL_MKDIR:
case CL_MKNOD:
case CL_SOFTLINK:
rc = lr_create(info);
break;
case CL_RMDIR:
case CL_UNLINK:
rc = lr_remove(info);
break;
case CL_RENAME:
rc = lr_move(info);
break;
case CL_HARDLINK:
rc = lr_link(info);
break;
case CL_TRUNC:
case CL_SETATTR:
rc = lr_setattr(info);
break;
case CL_SETXATTR:
rc = lr_setxattr(info);
break;
case CL_CLOSE:
case CL_EXT:
case CL_OPEN:
case CL_GETXATTR:
case CL_DN_OPEN:
case CL_LAYOUT:
case CL_MARK:
/*
* Nothing needs to be done for these entries
* fallthrough
*/
fallthrough;
default:
break;
}
lr_debug(DTRACE, "##### End %lld %s (%d) %s %s %s rc=%d #####\n",
info->recno, changelog_type2str(info->type),
info->type, info->tfid, info->pfid, info->name, rc);
if (rc && rc != -ENOENT) {
lr_print_failure(info, rc);
errors++;
if (abort_on_err)
break;
}
lr_clear_cl(info, 0);
}
llapi_changelog_fini(&changelog_priv);
if (errors || verbose)
printf("Errors: %d\n", errors);
/* Clear changelog records used so far */
lr_clear_cl(info, 1);
if (verbose) {
printf("lustre_rsync took %ld seconds\n", time(NULL) - start);
printf("Changelog records consumed: %lld\n", rec_count);
}
rc = 0;
out:
if (changelog_priv)
free(changelog_priv);
if (ext)
free(ext);
if (info)
free(info);
return rc;
}
void
termination_handler (int signum)
{
/* Set a flag for the replicator to gracefully shutdown */
quit = 1;
printf("lustre_rsync halting.\n");
}
int main(int argc, char *argv[])
{
int newsize;
int numtargets = 0;
int rc = 0;
/* lustre_rsync needs at least one argument */
if (argc < 2) {
lr_usage_short();
return -1;
}
rc = lr_init_status();
if (rc != 0)
return rc;
while ((rc = getopt_long(argc, argv, "as:t:m:u:l:vx:zc:ry:n:d:D:h",
long_opts, NULL)) >= 0) {
switch (rc) {
case 'a':
/* Assume absolute paths */
abort_on_err++;
break;
case 's':
/* Assume absolute paths */
snprintf(status->ls_source, sizeof(status->ls_source),
"%s", optarg);
break;
case 't':
status->ls_num_targets++;
numtargets++;
if (numtargets != status->ls_num_targets) {
/*
* Targets were read from a log file.
* The ones specified on the command line
* take precedence. The ones from the log
* file will be ignored.
*/
status->ls_num_targets = numtargets;
}
newsize = sizeof(struct lustre_rsync_status) +
(status->ls_num_targets * (PATH_MAX + 1));
if (status->ls_size != newsize) {
status->ls_size = newsize;
status = lr_grow_buf(status, newsize);
if (!status)
return -ENOMEM;
}
snprintf(status->ls_targets[status->ls_num_targets - 1],
sizeof(status->ls_targets[0]), "%s", optarg);
break;
case 'm':
snprintf(status->ls_mdt_device,
sizeof(status->ls_mdt_device),
"%s", optarg);
break;
case 'u':
snprintf(status->ls_registration,
sizeof(status->ls_registration),
"%s", optarg);
break;
case 'l':
statuslog = optarg;
(void)lr_read_log();
break;
case 'v':
verbose++;
break;
case 'x':
if (strcmp("no", optarg) == 0) {
noxattr = 1;
} else if (strcmp("yes", optarg) != 0) {
printf("Invalid parameter %s. Specify --xattr=no or --xattr=yes\n",
optarg);
return -1;
}
break;
case 'z':
dryrun = 1;
break;
case 'c':
/* Undocumented option cl-clear */
if (strcmp("no", optarg) == 0) {
noclear = 1;
} else if (strcmp("yes", optarg) != 0) {
printf("Invalid parameter %s. Specify --cl-clear=no or --cl-clear=yes\n",
optarg);
return -1;
}
break;
case 'r':
/* Undocumented option use-rsync */
use_rsync = 1;
break;
case 'y':
/* Undocumented option rsync-threshold */
rsync_threshold = atol(optarg);
break;
case 'n':
/* Undocumented option start-recno */
status->ls_last_recno = atol(optarg);
break;
case 'd':
/* Undocumented option debug */
debug = atoi(optarg);
if (debug < 0 || debug > 2)
debug = 0;
break;
case 'D':
/* Undocumented option debug log file */
if (debug_log)
fclose(debug_log);
debug_log = fopen(optarg, "a");
if (!debug_log) {
printf("Cannot open %s for debug log\n",
optarg);
return -1;
}
break;
case 'h':
lr_usage();
return 0;
default:
fprintf(stderr, "error: %s: option '%s' unrecognized.\n",
argv[0], argv[optind - 1]);
lr_usage_short();
return -1;
}
}
if (status->ls_last_recno == -1)
status->ls_last_recno = 0;
/* Check for mandatory options */
if (strnlen(status->ls_registration, LR_NAME_MAXLEN) == 0) {
fprintf(stderr,
"Please specify changelog consumer registration id (--user).\n");
lr_usage_short();
return -1;
}
if (strnlen(status->ls_source, PATH_MAX) == 0) {
fprintf(stderr, "Please specify the source path (--source).\n");
lr_usage_short();
return -1;
}
if (strnlen(status->ls_targets[0], PATH_MAX) == 0) {
fprintf(stderr, "Please specify the target path (--target).\n");
lr_usage_short();
return -1;
}
if (strnlen(status->ls_mdt_device, LR_NAME_MAXLEN) == 0) {
fprintf(stderr, "Please specify the metadata device (--mdt).\n");
lr_usage_short();
return -1;
}
rc = lr_locate_rsync();
if (use_rsync && rc != 0) {
fprintf(stderr, "Error: unable to locate %s.\n", RSYNC);
exit(-1);
}
signal(SIGINT, termination_handler);
signal(SIGHUP, termination_handler);
signal(SIGTERM, termination_handler);
rc = lr_replicate();
if (debug_log)
fclose(debug_log);
return rc;
}