// ----------------------------------------------------------------------
// File: com_rclone.cc
// Author: Andreas-Joachim Peters - CERN
// ----------------------------------------------------------------------
/************************************************************************
* EOS - the CERN Disk Storage System *
* Copyright(C) 2023 CERN/Switzerland *
* *
* 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, either version 3 of the License, or *
*(at your option) any later version. *
* *
* This program is distributed in the hope that it will 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, see .*
************************************************************************/
/*----------------------------------------------------------------------------*/
#include "console/ConsoleMain.hh"
#include "console/commands/helpers/NewfindHelper.hh"
#include "common/StringTokenizer.hh"
#include "common/Timing.hh"
#include "common/Path.hh"
#include "common/LayoutId.hh"
/*----------------------------------------------------------------------------*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
extern XrdOucString serveruri;
struct fs_entry {
struct timespec mtime;
size_t size;
std::string type;
std::string target;
bool newer(struct timespec& cmptime) {
if (mtime.tv_sec < cmptime.tv_sec) {
return true;
} else if (mtime.tv_sec > cmptime.tv_sec) {
return false;
} else if (mtime.tv_nsec < cmptime.tv_nsec) {
return true;
} else {
return false;
}
}
};
struct fs_result {
std::map directories;
std::map files;
std::map links;
};
bool dryrun = false;
bool noreplace = false;
bool nodelete = false;
bool verbose = false;
bool is_silent = false;
bool filter_versions = true;
bool filter_atomic = true;
bool filter_hidden = true;
fs_result fs_find(const char* path)
{
fs_result result;
std::stringstream s;
eos::common::Path cPath(path);
namespace fs = std::filesystem;
fs::path path_to_traverse = path;
struct stat buf;
try {
for (const auto& entry : fs::recursive_directory_iterator(path_to_traverse, std::filesystem::directory_options::skip_permission_denied)) {
std::string p = entry.path().string();
// filter functions
eos::common::Path iPath(p.c_str());
if (filter_versions) {
if (iPath.isVersionPath()) {
continue;
}
}
if (filter_atomic) {
if (iPath.isAtomicFile()) {
continue;
}
}
if (filter_hidden && iPath.GetFullPath().find("/.")!=STR_NPOS) {
if (!iPath.isVersionPath() && !iPath.isAtomicFile()) {
continue;
}
}
std::string t = p;
if (!::lstat(p.c_str(), &buf)) {
p.erase(0,cPath.GetFullPath().length());
switch ( (buf.st_mode & S_IFMT) ) {
case S_IFDIR :
p+= "/";
result.directories[p].mtime = buf.st_mtim;
result.directories[p].size = buf.st_size;
// s << "path=\"" << p << "/\" mtime=" << eos::common::Timing::TimespecToString(buf.st_mtim) << " size=" << buf.st_size << std::endl;
break;
case S_IFREG :
result.files[p].mtime = buf.st_mtim;
result.files[p].size = buf.st_size;
//s << "path=\"" << p << "\" mtime=" << eos::common::Timing::TimespecToString(buf.st_mtim) << " size=" << buf.st_size << std::endl;
break;
case S_IFLNK :
result.links[p].size = 0;
result.links[p].mtime = buf.st_mtim;
char link[4096];
ssize_t target = readlink(t.c_str(), link, sizeof(link));
if (target>=0) {
result.links[p].target = std::string(link,target);
}
break;
}
}
}
} catch (std::filesystem::filesystem_error const& ex) {
std::cerr
<< "error: " << ex.what() << '\n'
<< "# path : " << ex.path1() << '\n'
<< "# errc : " << ex.code().value() << '\n'
<< "# msg : " << ex.code().message() << '\n'
<< "# class : " << ex.code().category().name() << '\n';
exit(-1);
}
// std::cout << s.str();
return result;
}
fs_result eos_find(const char* path) {
fs_result result;
eos::common::Path cPath(path);
NewfindHelper find(gGlobalOpts);
std::string args = "--format type,mtime,size,link ";
args += path;
if (!find.ParseCommand(args.c_str())) {
std::cerr << "error: illegal subcommand '" << args << "'" << std::endl;
}
find.Silent();
int rc = find.Execute();
if (!rc) {
std::string findresult = find.GetResult();
std::vector lines;
eos::common::StringConversion::Tokenize(findresult, lines, "\n");
for (auto l:lines) {
std::vector kvs;
eos::common::StringConversion::Tokenize(l, kvs, " ");
struct timespec ts{0,0};
size_t size{0};
string path;
string type;
for (auto k:kvs) {
std::string tag, value;
eos::common::StringConversion::SplitKeyValue(k, tag, value, "=");
if (tag == "mtime") {
eos::common::Timing::Timespec_from_TimespecStr(value, ts);
if (type == "directory") {
result.directories[path].mtime = ts;
} else if (type == "file") {
result.files[path].mtime = ts;
} else if (type == "symlink") {
result.links[path].mtime = ts;
}
}
if (tag == "size") {
size = std::stoull(value.c_str(),0,10);
if (type == "directory") {
result.directories[path].size = size;
} else if (type == "file") {
result.files[path].size = size;
} else if (type == "symlink") {
result.links[path].size = 0;
}
}
if (tag == "path") {
// remove quotes
value.erase(0,1);
value.erase(value.length()-1);
value.erase(0,cPath.GetFullPath().length());
path = value;
}
if (tag == "type") {
type = value;
}
if ( tag == "target" && type == "symlink") {
value.erase(0,1);
value.erase(value.length()-1);
result.links[path].target = value;
}
// filter functions
eos::common::Path iPath(path.c_str());
if (filter_versions) {
if (iPath.isVersionPath()) {
break;
}
}
if (filter_atomic) {
if (iPath.isAtomicFile()) {
break;
}
}
if (filter_hidden && iPath.GetFullPath().find("/.")!=STR_NPOS) {
if (!iPath.isVersionPath() && !iPath.isAtomicFile()) {
break;
}
}
}
}
} else {
std::cerr << "error: " << find.GetError() << std::endl;
exit(rc);
}
return result;
}
int createDir(const std::string& i, eos::common::Path& prefix) {
if (!prefix.GetFullPath().beginswith("/eos/")) {
int rc = 0;
std::string mkpath = std::string(prefix.GetFullPath().c_str()) + std::string("/") + i;
if (!dryrun) {
rc = ::mkdir(mkpath.c_str(), S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
}
std::cerr << "[ mkdir ] : path:" << "[mkdir] path: " << mkpath.c_str() << " retc: " << rc << std::endl;
return rc;
} else {
XrdCl::URL url(serveruri.c_str());
url.SetPath( std::string(prefix.GetFullPath().c_str()) + std::string("/") + i );
if (!url.IsValid()) {
std::cerr << "error: invalid url " << i.c_str() << std::endl;
return 0;
}
XrdCl::FileSystem fs(url);
mode_t mode = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP;
XrdCl::Access::Mode mode_xrdcl = eos::common::LayoutId::MapModeSfs2XrdCl(mode);
XrdCl::XRootDStatus status = fs.MkDir(url.GetPath(),
XrdCl::MkDirFlags::MakePath,
mode_xrdcl);
std::cerr << "[ mkdir ] : url:" << url.GetURL() << " : " << status.IsOK() << std::endl;
return (!status.IsOK());
}
}
int removeDir(const std::string& i, eos::common::Path& prefix) {
if (!prefix.GetFullPath().beginswith("/eos/")) {
int rc = 0;
std::string rmpath = std::string(prefix.GetFullPath().c_str()) + std::string("/") + i;
if (!dryrun) {
rc = ::rmdir(rmpath.c_str());
}
std::cerr << "[ rmdir ] : path:" << rmpath.c_str() << " retc: " << rc << std::endl;
return rc;
} else {
XrdCl::URL url(serveruri.c_str());
url.SetPath( std::string(prefix.GetFullPath().c_str()) + std::string("/") + i );
if (!url.IsValid()) {
std::cerr << "error: invalid url " << i.c_str() << std::endl;
return 0;
}
XrdCl::FileSystem fs(url);
XrdCl::XRootDStatus status = fs.RmDir(url.GetPath());
std::cerr << "[ rmdir ] : url:" << url.GetURL() << " : " << status.IsOK() << std::endl;
return (!status.IsOK());
}
}
int removeFile(const std::string& i, eos::common::Path& prefix) {
if (!prefix.GetFullPath().beginswith("/eos/")) {
int rc = 0;
std::string rmpath = std::string(prefix.GetFullPath().c_str()) + std::string("/") + i;
if (!dryrun) {
rc = ::unlink(rmpath.c_str());
}
std::cerr << "[ unlink ] : path:" << rmpath.c_str() << " retc: " << rc << std::endl;
return rc;
} else {
XrdCl::URL url(serveruri.c_str());
url.SetPath( std::string(prefix.GetFullPath().c_str()) + std::string("/") + i );
if (!url.IsValid()) {
std::cerr << "error: invalid url " << i.c_str() << std::endl;
return 0;
}
XrdCl::FileSystem fs(url);
XrdCl::XRootDStatus status = fs.Rm(url.GetPath());
std::cerr << "[ unlink ] : url:" << url.GetURL() << " : " << status.IsOK() << std::endl;
return (!status.IsOK());
}
}
int createLink(const std::string& i, eos::common::Path& prefix, const std::string& target, eos::common::Path& targetprefix, struct timespec& mtime) {
std::string targetpath = target;
if (targetpath.find(prefix.GetFullPath().c_str()) == 0) {
// might need to rewrite the link target with a new prefix!
targetpath.erase(0, prefix.GetFullPath().length());
targetpath.insert(0,targetprefix.GetFullPath().c_str());
}
if (!prefix.GetFullPath().beginswith("/eos/")) {
int rc = 0;
std::string linkpath = std::string(prefix.GetFullPath().c_str()) + std::string("/") + i;
if (!is_silent && verbose) { std::cout << "[ link ] linking " << linkpath.c_str() << " => " << target.c_str() << " " << mtime.tv_sec << "." << mtime.tv_nsec << std::endl; }
if (!dryrun) {
rc = ::symlink(target.c_str(),linkpath.c_str());
if (rc) {
std::cerr << "error: symlink rc=" << rc << " errno=" << errno << std::endl;
}
}
struct timespec times[2];
times[0] = mtime;
times[1] = mtime;
if (!dryrun) {
int rc2 = utimensat(0, linkpath.c_str(), times, AT_SYMLINK_NOFOLLOW);
rc |= rc2;
if (rc2) {
std::cerr << "error: utimesat rc=" << rc << " errno=" << errno << std::endl;
}
}
if (!is_silent && verbose) {
std::cout << "[ symlink ] : path:" << linkpath.c_str() << " retc: " << rc << std::endl;
}
return rc;
} else {
XrdCl::URL url(serveruri.c_str());
url.SetPath( std::string(prefix.GetFullPath().c_str()) + std::string("/") + i );
if (!url.IsValid()) {
std::cerr << "error: invalid url " << i.c_str() << std::endl;
return 0;
}
int retc=0;
std::string request;
{
// create link
XrdCl::Buffer arg;
XrdCl::Buffer* response=nullptr;
request = eos::common::StringConversion::curl_escaped(std::string(prefix.GetFullPath().c_str()) + std::string("/") + i);
request += "?";
request += "mgm.pcmd=symlink&target=";
request += eos::common::StringConversion::curl_escaped(targetpath);
request += "&eos.encodepath=1";
arg.FromString(request);
XrdCl::FileSystem fs(url);
XrdCl::XRootDStatus status = fs.Query(XrdCl::QueryCode::OpaqueFile, arg, response);
if (response) {
delete response;
}
retc = !status.IsOK();
}
{
// fix mtime
XrdCl::Buffer arg;
XrdCl::Buffer* response=nullptr;
request = eos::common::StringConversion::curl_escaped(std::string(prefix.GetFullPath().c_str()) + std::string("/") + i);
request += "?";
request += "mgm.pcmd=utimes";
request += "&tv1_sec=0"; //ignored
request += "&tv1_nsec=0"; // ignored
request += "&tv2_sec=";
request += std::to_string(mtime.tv_sec);
request += "&tv2_nsec=";
std::stringstream oss;
oss << std::setfill('0') << std::setw(9) << mtime.tv_nsec;
request += oss.str();
request += "&eos.encodepath=1";
arg.FromString(request);
XrdCl::FileSystem fs(url);
XrdCl::XRootDStatus status = fs.Query(XrdCl::QueryCode::OpaqueFile, arg, response);
if (response) {
delete response;
}
retc |= !status.IsOK();
}
if (!is_silent && verbose) {
std::cout << "[ symlink ] : url:" << url.GetURL() << " : " << retc << std::endl;
}
return retc;
}
}
int setDirMtime(const std::string& i, eos::common::Path& prefix, struct timespec mtime) {
std::string mtpath = std::string(prefix.GetFullPath().c_str()) + std::string("/") + i;
if (!prefix.GetFullPath().beginswith("/eos/")) {
// apply local mtime;
struct timespec times[2];
times[0] = mtime;
times[1] = mtime;
int rc = 0;
if (!dryrun) {
rc = utimensat(0, mtpath.c_str(), times, AT_SYMLINK_NOFOLLOW);
}
if (!is_silent && verbose) {
std::cout << "[ mtime ] : path:" << "[utime] path: " << mtpath.c_str() << " retc: " << rc << " " << mtime.tv_sec << ":" << mtime.tv_nsec < tprops;
XrdCl::PropertyList* copyFile(const std::string& i, eos::common::Path& src, eos::common::Path& dst, struct timespec mtime) {
XrdCl::PropertyList props;
XrdCl::PropertyList* result = new XrdCl::PropertyList();
std::string srcurl = std::string(src.GetFullPath().c_str()) + i;
std::string dsturl = std::string(dst.GetFullPath().c_str()) + i;
if (srcurl.substr(0,5) == "/eos/") {
XrdCl::URL surl(serveruri.c_str());
surl.SetPath(srcurl);
srcurl = surl.GetURL();
}
if (dsturl.substr(0,5) == "/eos/") {
XrdCl::URL durl(serveruri.c_str());
durl.SetPath(dsturl);
XrdCl::URL::ParamsMap params;
params["eos.mtime"] = eos::common::Timing::TimespecToString(mtime);
durl.SetParams(params);
dsturl = durl.GetURL();
} else {
XrdCl::URL durl(dsturl);
XrdCl::URL::ParamsMap params;
params["local.mtime"] = eos::common::Timing::TimespecToString(mtime);
durl.SetParams(params);
dsturl = durl.GetURL();
}
props.Set("source", srcurl);
props.Set("target", dsturl);
props.Set("force", true); // allows overwrite
result->Set("source", srcurl);
result->Set("target", dsturl);
// props.Set("parallel", 10);
if (verbose) {
std::cout << "[ copy file ] : srcurl: " << srcurl << " dsturl: " << dsturl << std::endl;
}
copyProcess.AddJob(props,result);
return result;
}
void rclone_usage() {
fprintf(stderr,
"usage: rclone copy src-dir dst-dir [--delete] [--noupdate] [--dryrun] [--atomic] [--versions] [--hidden] [-v|--verbose] [-s|--silent]\n");
fprintf(stderr,
" : copy from source to destination [one-way sync]\n");
fprintf(stderr,
" rclone sync dir1 dir2 [--delete] [--noupdate] [--dryrun] [--atomic] [--versions] [--hidden] [-v|--verbose] [-s|--silent]\n");
fprintf(stderr,
" : bi-directional sync based on modification times\n");
fprintf(stderr,
" --delete : delete based on mtimes (currently unsupported)!\n");
fprintf(stderr,
" --noupdate : never update files, only create new ones!\n");
fprintf(stderr,
" --dryrun : simulate the command and show all actions, but don't do it!\n");
fprintf(stderr,
" --atomic : copy/sync also EOS atomic files\n");
fprintf(stderr,
" --versions : copy/sync also EOS atomic files\n");
fprintf(stderr,
" --hidden : copy/sync also hidden files/directories\n");
fprintf(stderr,
" -v --verbose : display all actions, not only a summary\n");
fprintf(stderr,
" -s --silent : only show errors\n");
exit(-1);
}
std::string parent(const std::string& path) {
std::filesystem::path p(path);
return p.parent_path();
}
std::optional parent_newer(std::map &a, std::map &b, const std::string& path) {
// checks if the parent mtime of b is newer than parent mtime of a !
std::string p_path = parent(path);
if (!a.count(path) || !b.count(path) ||
!a.count(p_path) || !b.count(p_path)) {
return {};
}
if ( (b[p_path].mtime.tv_sec == a[p_path].mtime.tv_sec) &&
(b[p_path].mtime.tv_nsec == a[p_path].mtime.tv_nsec)) {
return {};
}
if (b[p_path].newer(a[p_path].mtime)) {
return true;
} else {
return false;
}
}
int
com_rclone(char* arg1)
{
if (interactive) {
fprintf(stderr,
"error: don't call from an interactive shell - run 'eos -b rclone ...'!\n");
global_retc = -1;
return 0;
}
// split subcommands
XrdOucString mountpoint = "";
eos::common::StringTokenizer subtokenizer(arg1);
subtokenizer.GetLine();
XrdOucString cmd = subtokenizer.GetToken();
std::set target_create_dirs;
std::set target_delete_dirs;
std::set target_mtime_dirs;
std::set target_create_files;
std::set target_delete_files;
std::set target_updated_files;
std::set target_mismatch_files;
std::set target_create_links;
std::set target_delete_links;
std::set target_updated_links;
std::set target_mismatch_links;
std::set source_create_dirs;
std::set source_delete_dirs;
std::set source_mtime_dirs;
std::set source_create_files;
std::set source_delete_files;
std::set source_updated_files;
std::set source_mismatch_files;
std::set source_create_links;
std::set source_delete_links;
std::set source_updated_links;
std::set source_mismatch_links;
std::set cp_target_files;
std::set cp_source_files;
uint64_t copySize=0;
uint64_t copyTransactions=0;
enum eActions {
kTargetDirCreate, kSourceDirCreate,
kTargetDirDelete, kSourceDirDelete,
kTargetFileCreate, kSourceFileCreate,
kTargetFileUpdate, kSourceFileUpdate,
kTargetFileDelete, kSourceFileDelete,
kTargetFileMismatch, kSourceFileMismatch,
kTargetLinkCreate, kSourceLinkCreate,
kTargetLinkUpdate, kSourceLinkUpdate,
kTargetLinkDelete, kSourceLinkDelete,
kTargetLinkMismatch, kSourceLinkMismatch,
kTargetDirMtime, kSourceDirMtime
};
std::vector actions;
if (cmd == "copy") {
actions.push_back(kTargetDirCreate);
actions.push_back(kTargetFileCreate);
if (!noreplace) {
actions.push_back(kTargetFileUpdate);
}
actions.push_back(kTargetFileMismatch);
actions.push_back(kTargetLinkCreate);
if (!noreplace) {
actions.push_back(kTargetLinkUpdate);
}
actions.push_back(kTargetLinkMismatch);
if (!nodelete) {
actions.push_back(kTargetLinkDelete);
actions.push_back(kTargetFileDelete);
actions.push_back(kTargetDirDelete);
}
actions.push_back(kTargetDirMtime);
} else if (cmd == "sync") {
actions.push_back(kTargetDirCreate);
actions.push_back(kTargetFileCreate);
if (!noreplace) {
actions.push_back(kTargetFileUpdate);
}
actions.push_back(kTargetFileMismatch);
actions.push_back(kTargetLinkCreate);
if (!noreplace) {
actions.push_back(kTargetLinkUpdate);
}
actions.push_back(kTargetLinkMismatch);
// we cannot detect two-way deletion without history
// if (!nodelete) {
// actions.push_back(kTargetLinkDelete);
// actions.push_back(kTargetFileDelete);
// actions.push_back(kTargetDirDelete);
// }
actions.push_back(kTargetDirMtime);
actions.push_back(kSourceDirCreate);
actions.push_back(kSourceFileCreate);
actions.push_back(kSourceFileUpdate);
// actions.push_back(kSourceFileMismatch);
actions.push_back(kSourceLinkCreate);
if (!noreplace) {
actions.push_back(kSourceLinkUpdate);
}
// actions.push_back(kSourceLinkMismatch);
// we cannot detec two-way deletion without historya
// if (!nodelete) {
// actions.push_back(kSourceLinkDelete);
// actions.push_back(kSourceFileDelete);
// actions.push_back(kSourceDirDelete);
// }
actions.push_back(kSourceDirMtime);
} else {
rclone_usage();
}
XrdOucString src = subtokenizer.GetToken();
XrdOucString dst = subtokenizer.GetToken();
eos::common::Path srcPath(src.c_str());
eos::common::Path dstPath(dst.c_str());
src = srcPath.GetFullPath();
dst = dstPath.GetFullPath();
if (!src.length() || !dst.length()) {
rclone_usage();
}
nodelete = true;
noreplace = false;
dryrun = false;
XrdOucString option;
do {
option = subtokenizer.GetToken();
if (!option.length()) {
break;
}
if (option == "--delete") {
nodelete = false;
} else if (option == "--noreplace") {
noreplace = true;
} else if (option == "--dryrun") {
dryrun = true;
} else if (option == "--atomic") {
filter_atomic = false;
} else if (option == "--versions") {
filter_versions = false;
} else if (option == "--hidden") {
filter_hidden = false;
} else if (option == "-v" || option == "--verbose") {
verbose = true;
} else if (option == "-s" || option == "--silent") {
is_silent = true;
} else {
rclone_usage();
}
} while (1);
fs_result srcmap;
fs_result dstmap;
bool ignore_errors = false;
if (src.beginswith("/eos/")) {
// get the sync informtion using newfind
srcmap = eos_find(src.c_str());
} else {
// travers using UNIX find
srcmap = fs_find(src.c_str());
}
if (dst.beginswith("/eos/")) {
// get the sync information using newfind
dstmap = eos_find(dst.c_str());
} else {
// travers using UNIX find
dstmap = fs_find(dst.c_str());
}
srcmap.directories.erase("/");
dstmap.directories.erase("/");
// forward comparison
for ( auto d:srcmap.directories ) {
if (!dstmap.directories.count(d.first)) {
if (!is_silent && verbose) { std::cout << "[ target folder missing ] : " << d.first << std::endl; }
target_create_dirs.insert(d.first);
target_mtime_dirs.insert(d.first);
} else {
if (dstmap.directories[d.first].newer(srcmap.directories[d.first].mtime)) {
target_mtime_dirs.insert(d.first);
}
}
}
/// backward
for ( auto d:dstmap.directories ) {
if (!srcmap.directories.count(d.first)) {
if (!is_silent && verbose) { std::cout << "[ source folder missing ] : " << d.first << std::endl; }
if (!nodelete) {
target_delete_dirs.insert(d.first);
target_mtime_dirs.insert(parent(d.first));
} else {
source_create_dirs.insert(d.first);
source_mtime_dirs.insert(d.first);
}
} else {
if (srcmap.directories[d.first].newer(dstmap.directories[d.first].mtime)) {
source_mtime_dirs.insert(d.first);
}
}
}
// forward comparison
for ( auto d:srcmap.files ) {
if (!dstmap.files.count(d.first)) {
if (!is_silent && verbose) { std::cout << "[ target file missing ] : " << d.first << std::endl; }
target_create_files.insert(d.first);
copySize += d.second.size;
copyTransactions++;
} else {
if (dstmap.files[d.first].newer(srcmap.files[d.first].mtime)) {
if (!is_silent && verbose) { std::cout << "[ target file older ] : " << d.first << std::endl; }
target_updated_files.insert(d.first);
if (!noreplace) {
copySize += d.second.size;
copyTransactions++;
}
} else {
if (dstmap.files[d.first].size != srcmap.files[d.first].size) {
if (!is_silent && verbose) { std::cout << "[ target file diff size ] : " << d.first << std::endl; }
target_mismatch_files.insert(d.first);
}
}
}
}
// backward comparison
for ( auto d:dstmap.files ) {
if (!srcmap.files.count(d.first)) {
if (!is_silent && verbose) { std::cout << "[ source file missing ] : " << d.first << std::endl; }
if (!nodelete) {
target_delete_files.insert(d.first);
} else {
source_create_files.insert(d.first);
copySize += d.second.size;
copyTransactions++;
}
} else {
if (srcmap.files[d.first].newer(dstmap.files[d.first].mtime)) {
if (!is_silent && verbose){ std::cout << "[ source file older ] : " << d.first << std::endl; }
source_updated_files.insert(d.first);
if (!noreplace) {
copySize += d.second.size;
copyTransactions++;
}
} else {
if (dstmap.files[d.first].size != srcmap.files[d.first].size) {
if (!is_silent && verbose){ std::cout << "[ source file diff size ] : " << d.first << std::endl; }
source_mismatch_files.insert(d.first);
}
}
}
}
// forward comparison
for ( auto d:srcmap.links ) {
if (!dstmap.links.count(d.first)) {
if (!is_silent && verbose) { std::cout << "[ target link missing ] : " << d.first << std::endl; }
target_create_links.insert(d.first);
} else {
if (dstmap.links[d.first].newer(srcmap.links[d.first].mtime)) {
if (!is_silent && verbose) { std::cout << "[ target link older ] : " << d.first << std::endl; }
target_updated_links.insert(d.first);
} else {
if (dstmap.links[d.first].target != srcmap.links[d.first].target) {
if (!is_silent && verbose) { std::cout << "[ target link diff size ] : " << d.first << std::endl; }
target_mismatch_links.insert(d.first);
}
}
}
}
// backward comparison
for ( auto d:dstmap.links ) {
if (!srcmap.links.count(d.first)) {
if (!is_silent && verbose) { std::cout << "[ source link missing ] : " << d.first << std::endl; }
if (!nodelete) {
target_delete_links.insert(d.first);
} else {
source_create_links.insert(d.first);
}
} else {
if (srcmap.links[d.first].newer(dstmap.links[d.first].mtime)) {
if (!is_silent && verbose) { std::cout << "[ source link older ] : " << d.first << std::endl; }
source_updated_links.insert(d.first);
} else {
if (dstmap.links[d.first].target != srcmap.links[d.first].target) {
if (!is_silent && verbose) { std::cout << "[ source link diff size ] : " << d.first << std::endl; }
source_mismatch_links.insert(d.first);
}
}
}
}
if (!is_silent) {
std::cout << "[ --------------------------- ]" << std::endl;
std::cout << "[ EOS remote sync tool (beta) ]" << std::endl;
}
if (!dryrun) {
if (!is_silent) {
std::cout << "[ --------------------------- ]" << std::endl;
std::cout << "[ target ]" << std::endl;
std::cout << "[ --------------------------- ]" << std::endl;
std::cout << "[ # dir,files,links to create ] : " << target_create_dirs.size() << "," << target_create_files.size() << "," << target_create_links.size() << std::endl;
std::cout << "[ # dir,files,links to delete ] : " << target_delete_dirs.size() << "," << target_delete_files.size() << "," << target_delete_links.size() << std::endl;
std::cout << "[ # files,links to update ] : " << target_updated_files.size() << "," << target_updated_links.size() << std::endl;
std::cout << "[ # files,links mismatch ] : " << target_mismatch_files.size() << "," << target_mismatch_links.size() << std::endl;
std::cout << "[ --------------------------- ]" << std::endl;
std::cout << "[ source ]" << std::endl;
std::cout << "[ --------------------------- ]" << std::endl;
std::cout << "[ # dir,files,links to create ] : " << source_create_dirs.size() << "," << source_create_files.size() << "," << source_create_links.size() << std::endl;
std::cout << "[ # dir,files,links to delete ] : " << source_delete_dirs.size() << "," << source_delete_files.size() << "," << source_delete_links.size() << std::endl;
std::cout << "[ # files,links to update ] : " << source_updated_files.size() << "," << source_updated_links.size() << std::endl;
std::cout << "[ # files,links mismatch ] : " << source_mismatch_files.size() << "," << source_mismatch_links.size() << std::endl;
std::cout << "[ --------------------------- ]" << std::endl;
std::cout << "[ volume ]" << std::endl;
std::cout << "[ --------------------------- ]" << std::endl;
XrdOucString sizestring;
eos::common::StringConversion::GetReadableSizeString(sizestring, copySize, "B");
std::cout << "[ # data size ] : " << sizestring.c_str() << std::endl;
eos::common::StringConversion::GetReadableSizeString(sizestring, copyTransactions, "");
std::cout << "[ # copy transactions ] : " << sizestring.c_str() << std::endl;
std::cout << "[ --------------------------- ]" << std::endl;
}
}
for ( auto a:actions ) {
if (a == kTargetDirCreate) {
for ( auto i:target_create_dirs ) {
int rc = createDir(i, dstPath);
if (rc && !ignore_errors) {
std::cerr << "error: failed to create directory '" << dstPath.GetFullPath() << i << "'" << std::endl;
exit(-1);
}
}
}
if (a == kSourceDirCreate) {
for ( auto i:source_create_dirs ) {
int rc = createDir(i, srcPath);
if (rc && !ignore_errors) {
std::cerr << "error: failed to create directory '" << dstPath.GetFullPath() << i << "'" << std::endl;
exit(-1);
}
}
}
if (a == kTargetDirDelete) {
for ( auto i:target_delete_dirs ) {
int rc = removeDir(i, dstPath);
if (rc && !ignore_errors) {
std::cerr << "error: failed to remove directory '" << dstPath.GetFullPath() << i << "'" << std::endl;
exit(-1);
}
}
}
if (a == kTargetFileDelete) {
for ( auto i:target_delete_files ) {
int rc = removeFile(i, dstPath);
if (rc && !ignore_errors) {
std::cerr << "error: failed to remove file '" << dstPath.GetFullPath() << i << "'" << std::endl;
exit(-1);
}
}
}
if (a == kTargetLinkDelete) {
for ( auto i:target_delete_links ) {
int rc = removeFile(i, dstPath);
if (rc && !ignore_errors) {
std::cerr << "error: failed to remove link '" << dstPath.GetFullPath() << i << "'" << std::endl;
exit(-1);
}
}
}
if (a == kSourceDirDelete) {
for ( auto i:source_delete_dirs ) {
int rc = removeDir(i, srcPath);
if (rc && !ignore_errors) {
std::cerr << "error: failed to remove directory '" << srcPath.GetFullPath() << i << "'" << std::endl;
exit(-1);
}
}
}
if (a == kSourceFileDelete) {
for ( auto i:source_delete_files ) {
int rc = removeFile(i, srcPath);
if (rc && !ignore_errors) {
std::cerr << "error: failed to remove file '" << srcPath.GetFullPath() << i << "'" << std::endl;
exit(-1);
}
}
}
if (a == kSourceLinkDelete) {
for ( auto i:source_delete_links ) {
int rc = removeFile(i, srcPath);
if (rc && !ignore_errors) {
std::cerr << "error: failed to remove link '" << srcPath.GetFullPath() << i << "'" << std::endl;
exit(-1);
}
}
}
if (a == kTargetFileCreate) {
for ( auto i:target_create_files ) {
cp_target_files.insert(i);
}
}
if (a == kTargetFileUpdate) {
for ( auto i:target_updated_files ) {
cp_target_files.insert(i);
}
}
if (a == kTargetFileMismatch) {
for ( auto i:target_mismatch_files ) {
cp_target_files.insert(i);
}
}
if(a == kTargetLinkCreate) {
for ( auto i:target_create_links ) {
if (!is_silent && verbose) { std::cout << " link ] create link " << i.c_str() << " => " << srcmap.links[i].target.c_str() << std::endl; }
if (!dryrun) {
int rc = createLink(i, dstPath, srcmap.links[i].target, srcPath, srcmap.links[i].mtime);
if (rc && !ignore_errors) {
std::cerr << "error: failed to create link '" << dstPath.GetFullPath() << i << "'" << std::endl;
exit(-1);
}
}
}
}
if(a == kTargetLinkUpdate) {
for ( auto i:target_updated_links ) {
if (!is_silent && verbose) { std::cout << "[ link ] update link " << i.c_str() << " => " << srcmap.links[i].target.c_str() << std::endl; }
if (!dryrun) {
int rc = removeFile(i, dstPath);
rc |= createLink(i, dstPath, srcmap.links[i].target, srcPath, srcmap.links[i].mtime);
if (rc && !ignore_errors) {
std::cerr << "error: failed to update link '" << dstPath.GetFullPath() << i << "'" << std::endl;
exit(-1);
}
}
}
}
if(a == kTargetLinkMismatch) {
for ( auto i:target_mismatch_links ) {
if (!is_silent && verbose) { std::cout << "[ link ] remove link " << i.c_str() << std::endl; }
if (!dryrun) {
int rc = removeFile(i, dstPath);
rc |= createLink(i, dstPath, srcmap.links[i].target, srcPath, srcmap.links[i].mtime);
if (rc && !ignore_errors) {
std::cerr << "error: failed to update mismatching link '" << dstPath.GetFullPath() << i << "'" << std::endl;
exit(-1);
}
}
}
}
if (a == kSourceFileCreate) {
for ( auto i:source_create_files ) {
cp_source_files.insert(i);
}
}
if (a == kSourceFileUpdate) {
for ( auto i:source_updated_files ) {
cp_source_files.insert(i);
}
}
if (a == kSourceFileMismatch) {
for ( auto i:source_mismatch_files ) {
cp_source_files.insert(i);
}
}
if(a == kSourceLinkCreate) {
for ( auto i:source_create_links ) {
if (!is_silent && verbose) { std::cout << "[ link ] create link " << i.c_str() << " => " << dstmap.links[i].target.c_str() << std::endl; }
if (!dryrun) {
int rc = createLink(i, srcPath, dstmap.links[i].target, dstPath, dstmap.links[i].mtime);
if (rc && !ignore_errors) {
std::cerr << "error: failed to create link '" << srcPath.GetFullPath() << i << "'" << std::endl;
exit(-1);
}
}
}
}
if(a == kSourceLinkUpdate) {
for ( auto i:source_updated_links ) {
if (!is_silent && verbose) { std::cout << "[ link ] update link " << i.c_str() << dstmap.links[i].target.c_str() << std::endl; }
if (!dryrun) {
int rc = removeFile(i, srcPath);
rc |= createLink(i, srcPath, dstmap.links[i].target, dstPath, dstmap.links[i].mtime);
if (rc && !ignore_errors) {
std::cerr << "error: failed to update link '" << srcPath.GetFullPath() << i << "'" << std::endl;
exit(-1);
}
}
}
}
if(a == kSourceLinkMismatch) {
for ( auto i:source_mismatch_links ) {
if (!is_silent && verbose) { std::cout << "[ link ]remove link " << i.c_str() << std::endl; }
if (!dryrun) {
int rc = removeFile(i, srcPath);
rc |= createLink(i, srcPath, dstmap.links[i].target, dstPath, dstmap.links[i].mtime);
if (rc && !ignore_errors) {
std::cerr << "error: failed to update mismatching link '" << srcPath.GetFullPath() << i << "'" << std::endl;
exit(-1);
}
}
}
}
}
for ( auto i:cp_target_files ) {
if (!dryrun) {
tprops.push_back(copyFile(i, srcPath, dstPath, srcmap.files[i].mtime));
} else {
if (!is_silent && verbose) {
std::cout << "[ copy ] : " << i.c_str() << " " << srcPath.GetFullPath().c_str() << " => " << dstPath.GetFullPath().c_str() << std::endl;
}
}
}
for ( auto i:cp_source_files ) {
if (!dryrun) {
tprops.push_back(copyFile(i, dstPath, srcPath, dstmap.files[i].mtime));
} else {
if (!is_silent && verbose) {
std::cout << "[ copy ] : " << i.c_str() << " " << dstPath.GetFullPath().c_str() << " => " << srcPath.GetFullPath().c_str() << std::endl;
}
}
}
class RCloneProgressHandler : public XrdCl::CopyProgressHandler {
public:
virtual void BeginJob( uint16_t jobNum,
uint16_t jobTotal,
const URL *source,
const URL *destination )
{
n = jobNum;
tot = jobTotal;
}
virtual void EndJob( uint16_t jobNum,
const PropertyList *result )
{
(void)jobNum; (void)result;
std::string src;
std::string dst;
result->Get("source",src);
result->Get("target",dst);
XrdCl::URL durl(dst.c_str());
auto param = durl.GetParams();
if (param.count("local.mtime")) {
// apply mtime changes when done to local files
struct timespec ts;
std::string tss = param["local.mtime"];
if (!eos::common::Timing::Timespec_from_TimespecStr(tss, ts)) {
// apply local mtime;
struct timespec times[2];
times[0] = ts;
times[1] = ts;
if (utimensat(0, durl.GetPath().c_str(), times, AT_SYMLINK_NOFOLLOW)) {
std::cerr << "error: failed to update modification time of '" << durl.GetPath() << "'" << std::endl;
}
}
}
};
virtual void JobProgress( uint16_t jobNum,
uint64_t bytesProcessed,
uint64_t bytesTotal )
{
bp = bytesProcessed;
bt = bytesTotal;
n = jobNum;
if (verbose) {
std::cerr << "[ " << jobNum << "/" << tot << " ] files copied" << std::endl;
} else {
if (!is_silent) {
std::cerr << "[ " << jobNum << "/" << tot << " ] files copied" << "\r";
}
}
}
virtual bool ShouldCancel( uint16_t jobNum )
{
(void)jobNum;
return false;
}
std::atomic bp;
std::atomic bt;
std::atomic n;
std::atomic tot;
};
RCloneProgressHandler copyProgress;
if (!is_silent && verbose) {
std::cerr << "# preparing" << std::endl;
}
copyProcess.Prepare();
if (!is_silent && verbose) {
std::cerr << "# running" << std::endl;
}
copyProcess.Run(©Progress);
if (!is_silent) {
std::cout << std::endl;
}
// last step is to adjust directory mtimes
for ( auto a:actions ) {
if ( a == kTargetDirMtime ) {
for ( auto i:target_mtime_dirs ) {
if (!is_silent && verbose) { std::cout << "[ mtime ] updating target mtime " << i << std::endl; }
if (!dryrun) {
int rc = setDirMtime(i, dstPath, srcmap.directories[i].mtime);
if (rc && !ignore_errors) {
std::cerr << "error: failed to update directory mtime '" << dstPath.GetFullPath() << i << "'" << std::endl;
exit(-1);
}
}
}
}
if ( a == kSourceDirMtime ) {
for ( auto i:source_mtime_dirs ) {
if (!is_silent && verbose) { std::cout << "[ mtime ] updating source mtime " << i << std::endl; }
if (!dryrun) {
int rc = setDirMtime(i, srcPath, dstmap.directories[i].mtime);
if (rc && !ignore_errors) {
std::cerr << "error: failed to update directory mtime '" << srcPath.GetFullPath() << i << "'" << std::endl;
exit(-1);
}
}
}
}
}
if (dryrun && !is_silent) {
std::cout << "[ --------------------------- ]" << std::endl;
std::cout << "[ target ]" << std::endl;
std::cout << "[ --------------------------- ]" << std::endl;
std::cout << "[ # dir,files,links to create ] : " << target_create_dirs.size() << "," << target_create_files.size() << "," << target_create_links.size() << std::endl;
std::cout << "[ # dir,files,links to delete ] : " << target_delete_dirs.size() << "," << target_delete_files.size() << "," << target_delete_links.size() << std::endl;
std::cout << "[ # files,links to update ] : " << target_updated_files.size() << "," << target_updated_links.size() << std::endl;
std::cout << "[ # files,links mismatch ] : " << target_mismatch_files.size() << "," << target_mismatch_links.size() << std::endl;
std::cout << "[ --------------------------- ]" << std::endl;
std::cout << "[ source ]" << std::endl;
std::cout << "[ --------------------------- ]" << std::endl;
std::cout << "[ # dir,files,links to create ] : " << source_create_dirs.size() << "," << source_create_files.size() << "," << source_create_links.size() << std::endl;
std::cout << "[ # dir,files,links to delete ] : " << source_delete_dirs.size() << "," << source_delete_files.size() << "," << source_delete_links.size() << std::endl;
std::cout << "[ # files,links to update ] : " << source_updated_files.size() << "," << source_updated_links.size() << std::endl;
std::cout << "[ # files,links mismatch ] : " << source_mismatch_files.size() << "," << source_mismatch_links.size() << std::endl;
std::cout << "[ --------------------------- ]" << std::endl;
std::cout << "[ volume ]" << std::endl;
std::cout << "[ --------------------------- ]" << std::endl;
XrdOucString sizestring;
eos::common::StringConversion::GetReadableSizeString(sizestring, copySize, "B");
std::cout << "[ # data size ] : " << sizestring.c_str() << std::endl;
eos::common::StringConversion::GetReadableSizeString(sizestring, copyTransactions, "");
std::cout << "[ # copy transactions ] : " << sizestring.c_str() << std::endl;
std::cout << "[ --------------------------- ]" << std::endl;
}
exit(0);
}