20 #define _FILE_OFFSET_BITS 64
22 #define __STDC_FORMAT_MACROS
32 #include <sys/capability.h>
63 PrintError(
"overlay (copy on write) directory does not exist");
71 PrintError(
"cvmfs read/only repository does not exist");
84 PrintError(
"data store directory does not exist");
90 PrintError(
"file chunk size values are not sane");
97 "Session token file has to be provided "
98 "when upstream type is gw.");
107 const string manifest_path = *args.find(
'o')->second;
108 const string dir_temp = *args.find(
't')->second;
109 const string spooler_definition = *args.find(
'r')->second;
110 const string repo_name = *args.find(
'n')->second;
111 const string reflog_chksum_path = *args.find(
'R')->second;
112 if (args.find(
'l') != args.end()) {
122 if (args.find(
'a') != args.end()) {
130 const bool volatile_content = (args.count(
'v') > 0);
131 const bool garbage_collectable = (args.count(
'z') > 0);
132 std::string voms_authz;
133 if (args.find(
'V') != args.end()) {
134 voms_authz = *args.find(
'V')->second;
144 dir_temp, volatile_content, voms_authz, spooler.
weak_ref()));
145 if (!manifest.IsValid()) {
146 PrintError(
"Swissknife Sync: Failed to create new repository");
152 PrintError(
"Swissknife Sync: Failed to create fresh Reflog");
161 spooler->UploadReflog(reflog_path);
162 spooler->WaitForUpload();
163 unlink(reflog_path.c_str());
164 if (spooler->GetNumberOfErrors()) {
168 assert(!reflog_chksum_path.empty());
172 const bool needs_bootstrap_shortcuts = !voms_authz.empty();
173 manifest->set_garbage_collectability(garbage_collectable);
174 manifest->set_has_alt_catalog_path(needs_bootstrap_shortcuts);
176 if (!manifest->Export(manifest_path)) {
177 PrintError(
"Swissknife Sync: Failed to create new repository");
185 const string source = *args.find(
'i')->second;
186 const string dest = *args.find(
'o')->second;
187 const string spooler_definition = *args.find(
'r')->second;
189 if (args.find(
'a') != args.end()) {
192 PrintError(
"Swissknife Sync: Unknown hash algorithm");
198 upload::Spooler *
spooler = upload::Spooler::Construct(sd);
200 spooler->Upload(source, dest);
201 spooler->WaitForUpload();
203 if (spooler->GetNumberOfErrors() > 0) {
215 const string file_to_peek = *args.find(
'd')->second;
216 const string spooler_definition = *args.find(
'r')->second;
220 upload::Spooler *
spooler = upload::Spooler::Construct(sd);
222 const bool success = spooler->Peek(file_to_peek);
224 if (spooler->GetNumberOfErrors() > 0) {
226 file_to_peek.c_str());
231 file_to_peek.c_str());
235 file_to_peek.c_str());
243 const string file_to_delete = *args.find(
'o')->second;
244 const string spooler_definition = *args.find(
'r')->second;
248 upload::Spooler *
spooler = upload::Spooler::Construct(sd);
250 spooler->RemoveAsync(file_to_delete);
251 spooler->WaitForUpload();
253 if (spooler->GetNumberOfErrors() > 0) {
255 file_to_delete.c_str());
265 const string dirtab_file = *args.find(
'd')->second;
270 const string stratum0 = *args.find(
'w')->second;
271 const string dir_temp = *args.find(
't')->second;
272 verbose_ = (args.find(
'x') != args.end());
277 "Swissknife Sync: Didn't find a dirtab at '%s'. Skipping...",
278 dirtab_file.c_str());
286 "Swissknife Sync: Invalid or not readable dirtab '%s'",
287 dirtab_file.c_str());
291 "Swissknife Sync: Found %lu rules in dirtab '%s'",
292 dirtab->
RuleCount(), dirtab_file.c_str());
295 const bool auto_manage_catalog_files =
true;
296 const bool follow_redirects = (args.count(
'L') > 0);
297 const string proxy = (args.count(
'@') > 0) ? *args.find(
'@')->second :
"";
298 if (!InitDownloadManager(follow_redirects, proxy)) {
302 base_hash, stratum0, dir_temp, download_manager(), statistics(),
303 auto_manage_catalog_files);
304 catalog_manager.
Init();
306 vector<string> new_nested_catalogs;
307 DetermineNestedCatalogCandidates(*dirtab, &catalog_manager,
308 &new_nested_catalogs);
309 const bool success = CreateCatalogMarkers(new_nested_catalogs);
312 return (success) ? 0 : 1;
324 char resolved_cstr[PATH_MAX];
325 char *retval = realpath(name, resolved_cstr);
326 if (retval == NULL)
return false;
328 std::string resolved(resolved_cstr);
329 if (resolved == *g_glob_uniondir)
return true;
330 if (!
HasPrefix(resolved, (*g_glob_uniondir) +
"/",
false )) {
339 return opendir(name);
343 closedir(static_cast<DIR *>(dirp));
347 return readdir(static_cast<DIR *>(dirp));
352 return lstat(name, st);
357 return stat(name, st);
366 vector<string> *nested_catalog_candidates) {
369 catalog::Dirtab::Rules::const_iterator i = lookup_rules.begin();
370 const catalog::Dirtab::Rules::const_iterator iend = lookup_rules.end();
371 for (; i != iend; ++i) {
376 const std::string &glob_string = i->pathspec.GetGlobString();
377 const std::string &glob_string_abs = union_dir_ + glob_string;
378 const int glob_flags = GLOB_ONLYDIR | GLOB_NOSORT | GLOB_PERIOD |
387 const int glob_retval =
388 glob(glob_string_abs.c_str(), glob_flags, NULL, &glob_res);
390 g_glob_uniondir = NULL;
392 if (glob_retval == 0) {
395 "Swissknife Sync: Found %lu entries for pathspec (%s)",
396 glob_res.gl_pathc, glob_string.c_str());
397 FilterCandidatesFromGlobResult(dirtab, glob_res.gl_pathv,
398 glob_res.gl_pathc, catalog_manager,
399 nested_catalog_candidates);
400 }
else if (glob_retval == GLOB_NOMATCH) {
402 "Swissknife Sync: WARNING: cannot apply pathspec %s",
403 glob_string.c_str());
406 "Swissknife Sync: Failed to run glob matching (%s)",
407 glob_string.c_str());
417 std::vector<std::string> *nested_catalog_candidates) {
419 for (
size_t i = 0; i < npaths; ++i) {
421 const std::string candidate(paths[i]);
422 const std::string candidate_rel = candidate.substr(union_dir_.size());
426 const int lstat_retval =
platform_lstat(candidate.c_str(), &candidate_info);
427 if (lstat_retval != 0) {
430 "Error in processing .cvmfsdirtab: cannot access %s (%d)",
431 candidate.c_str(), errno);
434 assert(lstat_retval == 0);
435 if (!S_ISDIR(candidate_info.st_mode)) {
439 "The '%s' dirtab entry does not point to a directory "
440 "but to a file or a symbolic link",
441 candidate_rel.c_str());
446 assert(candidate_rel.size() >= 2);
447 if (candidate_rel.substr(candidate_rel.size() - 2) ==
"/." ||
448 candidate_rel.substr(candidate_rel.size() - 3) ==
"/..") {
455 "Swissknife Sync: Candidate '%s' is excluded by dirtab",
456 candidate_rel.c_str());
464 const bool lookup_success = catalog_manager->
LookupPath(
466 if (!lookup_success) {
468 "Swissknife Sync: Didn't find '%s' in catalogs, could "
469 "be a new directory and nested catalog.",
470 candidate_rel.c_str());
471 nested_catalog_candidates->push_back(candidate);
475 "Swissknife Sync: Found '%s' in catalogs but is not a "
476 "nested catalog yet.",
477 candidate_rel.c_str());
478 nested_catalog_candidates->push_back(candidate);
488 !
FileExists(union_dir_ + candidate_rel +
"/.cvmfscatalog")) {
490 "Swissknife Sync: WARNING: '%s' should be a nested "
491 "catalog according to the dirtab. "
493 candidate_rel.c_str());
494 nested_catalog_candidates->push_back(candidate);
497 "Found '%s' in catalogs and it already is a nested catalog.",
498 candidate_rel.c_str());
505 const std::vector<std::string> &new_nested_catalogs) {
509 std::vector<std::string>::const_iterator k = new_nested_catalogs.begin();
510 const std::vector<std::string>::const_iterator kend =
511 new_nested_catalogs.end();
512 for (; k != kend; ++k) {
513 assert(!k->empty() && k->size() > union_dir_.size());
516 const std::string marker_path = *k +
"/.cvmfscatalog";
523 const int fd = open(marker_path.c_str(), O_CREAT, mode);
526 "Swissknife Sync: Failed to create nested catalog marker "
527 "at '%s' (errno: %d)",
528 marker_path.c_str(), errno);
537 "Swissknife Sync: Auto-creating nested catalog in %s",
546 chunk_arg(
char param,
size_t *save_to) : param(param), save_to(save_to) {}
553 typedef std::vector<chunk_arg> ChunkArgs;
556 ChunkArgs chunk_args;
562 ChunkArgs::const_iterator i = chunk_args.begin();
563 ChunkArgs::const_iterator iend = chunk_args.end();
564 for (; i != iend; ++i) {
565 swissknife::ArgumentList::const_iterator arg = args.find(i->param);
567 if (arg != args.end()) {
568 size_t arg_value =
static_cast<size_t>(
String2Uint64(*arg->second));
570 *i->save_to = arg_value;
585 std::string watchdog_dir =
"/tmp";
586 char watchdog_path[PATH_MAX];
588 int path_size = snprintf(watchdog_path,
sizeof(watchdog_path),
589 "%s/cvmfs-swissknife-sync-stacktrace.%s.%d",
590 watchdog_dir.c_str(), timestamp.c_str(), getpid());
592 assert(path_size < PATH_MAX);
594 watchdog->
Spawn(std::string(watchdog_path));
605 params.
stratum0 = *args.find(
'w')->second;
610 params.
repo_name = *args.find(
'N')->second;
614 if (args.find(
'f') != args.end())
616 if (args.find(
'A') != args.end()) params.
is_balanced =
true;
618 if (args.find(
'y') != args.end()) params.
dry_run =
true;
619 if (args.find(
'm') != args.end()) params.
mucatalogs =
true;
622 if (args.find(
'V') != args.end()) params.
voms_authz =
true;
623 if (args.find(
'F') != args.end()) params.
authz_file = *args.find(
'F')->second;
626 if (args.find(
'Y') != args.end()) params.
external_data =
true;
627 if (args.find(
'W') != args.end()) params.
direct_io =
true;
628 if (args.find(
'S') != args.end()) {
633 "Swissknife Sync: Invalid virtual catalog options: %s",
634 args.find(
'S')->second->c_str());
638 if (args.find(
'z') != args.end()) {
648 if (args.find(
'X') != args.end())
650 if (args.find(
'M') != args.end())
653 if (args.find(
'p') != args.end()) {
655 if (!ReadFileChunkingArgs(args, ¶ms)) {
656 PrintError(
"Swissknife Sync: Failed to read file chunk size values");
660 if (args.find(
'O') != args.end()) {
664 if (args.find(
'e') != args.end()) {
667 PrintError(
"Swissknife Sync: Unknown hash algorithm");
671 if (args.find(
'Z') != args.end()) {
677 if (args.find(
'Q') != args.end()) {
682 if (args.find(
'R') != args.end()) {
687 if (args.find(
'U') != args.end()) {
693 if (args.find(
'v') != args.end()) {
695 if (!sanitizer.
IsValid(*args.find(
'v')->second)) {
696 PrintError(
"Swissknife Sync: Invalid revision number");
704 if (args.find(
'q') != args.end()) {
708 if (args.find(
'0') != args.end()) {
712 if (args.find(
'T') != args.end()) {
716 if (args.find(
'g') != args.end()) {
720 if (args.find(
'P') != args.end()) {
724 if (args.find(
'H') != args.end()) {
725 params.
key_file = *args.find(
'H')->second;
728 if (args.find(
'D') != args.end()) {
732 if (args.find(
'J') != args.end()) {
736 if (args.find(
'G') != args.end()) {
740 const bool upload_statsdb = (args.count(
'I') > 0);
763 params.
spooler = upload::Spooler::Construct(spooler_definition,
764 &publish_statistics);
765 if (NULL == params.
spooler)
return 3;
767 upload::Spooler::Construct(spooler_definition_catalogs,
768 &publish_statistics));
769 if (!spooler_catalogs.
IsValid())
return 3;
771 const bool follow_redirects = (args.count(
'L') > 0);
772 const string proxy = (args.count(
'@') > 0) ? *args.find(
'@')->second :
"";
773 if (!InitDownloadManager(follow_redirects, proxy)) {
794 params.
base_hash = manifest->catalog_hash();
808 const std::string old_root_hash = manifest->catalog_hash().ToString(
true);
816 catalog_manager.
Init();
824 ((params.
ttl_seconds != catalog_manager.GetTTL()) ||
825 !catalog_manager.HasExplicitTTL())) {
827 "Swissknife Sync: Setting repository TTL to %" PRIu64
"s",
843 "Swissknife Sync: unknown union file system: %s",
850 "Swissknife Sync: Initialization of the synchronisation "
857 assert(!manifest->history().IsNull());
859 manifest.
weak_ref(), download_manager(), &catalog_manager, ¶ms);
865 "Swissknife Sync: Adding contents of authz file %s to"
868 int fd = open(params.
authz_file.c_str(), O_RDONLY);
871 "Swissknife Sync: Unable to open authz file (%s)"
872 "from the publication process: %s",
877 std::string new_authz;
881 if (!read_successful) {
883 "Swissknife Sync: Failed to read authz file (%s): %s",
888 catalog_manager.SetVOMSAuthz(new_authz);
892 PrintError(
"Swissknife Sync: Something went wrong during sync");
895 if (upload_statsdb) {
902 perf::Counter *revision_counter = statistics()->Register(
"publish.revision",
903 "Published revision number");
904 revision_counter->
Set(static_cast<int64_t>(
905 catalog_manager.GetRootCatalog()->revision()));
909 "Swissknife Sync: Wait for all uploads to finish");
910 params.
spooler->WaitForUpload();
911 spooler_catalogs->WaitForUpload();
912 params.
spooler->FinalizeSession(
false);
915 "Swissknife Sync: Exporting repository manifest");
921 const std::string new_root_hash = manifest->catalog_hash().ToString(
true);
923 if (!spooler_catalogs->FinalizeSession(
true, old_root_hash, new_root_hash,
925 PrintError(
"Swissknife Sync: Failed to commit transaction.");
928 if (upload_statsdb) {
937 if (upload_statsdb) {
945 PrintError(
"Swissknife Sync: Failed to create new repository");
void SetLogVerbosity(const LogLevels max_level)
std::string database_file() const
Algorithms ParseCompressionAlgorithm(const std::string &algorithm_option)
size_t avg_file_chunk_size
const manifest::Manifest * manifest() const
int Main(const ArgumentList &args)
std::string GetGMTimestamp(const std::string &format)
bool UploadStatistics(upload::Spooler *spooler, std::string local_path="")
SpoolerDefinition Dup2DefaultCompression() const
std::vector< Rule > Rules
bool generate_legacy_bulk_chunks
unsigned file_mbyte_limit
uint64_t max_concurrent_write_jobs
const int kDefaultFileMode
static const unsigned kDefaultFileMbyteLimit
virtual bool Initialize()
static const unsigned kDefaultNestedKcatalogLimit
static const unsigned kDefaultRootKcatalogLimit
int Main(const ArgumentList &args)
static bool ParseActions(const std::string &action_desc, int *actions)
size_t min_file_chunk_size
unsigned num_upload_tasks
std::string spooler_definition
std::string manifest_path
bool ReadFileChunkingArgs(const swissknife::ArgumentList &args, SyncParameters *params)
void SetName(const std::string &name)
const Rules & positive_rules() const
bool CheckParams(const SyncParameters &p)
zlib::Algorithms compression_alg
unsigned nested_kcatalog_limit
int GlobLstat(const char *name, struct stat *st)
assert((mem||(size==0))&&"Out Of Memory")
bool LookupPath(const PathString &path, const LookupOptions options, DirectoryEntry *entry)
unsigned int number_of_concurrent_uploads
upload::Spooler * spooler
void SetDescription(const std::string &description)
std::string union_fs_type
bool GlobCheckPath(const char *name)
const unsigned kLookupDefault
int Main(const ArgumentList &args)
bool IsNestedCatalogMountpoint() const
virtual bool IsOpposing(const std::string &path) const
chunk_arg(char param, size_t *save_to)
bool IsNestedCatalogRoot() const
bool FileExists(const std::string &path)
int Main(const ArgumentList &args)
static Watchdog * Create(FnOnCrash on_crash)
static manifest::Manifest * CreateRepository(const std::string &dir_temp, const bool volatile_content, const std::string &voms_authz, upload::Spooler *spooler)
void Set(const int64_t val)
void FilterCandidatesFromGlobResult(const catalog::Dirtab &dirtab, char **paths, const size_t npaths, catalog::SimpleCatalogManager *catalog_manager, std::vector< std::string > *nested_catalog_candidates)
bool CheckParams(const swissknife::CommandLease::Parameters &p)
const char kSuffixCatalog
upload::Spooler * spooler
unsigned int num_upload_tasks
static Dirtab * Create(const std::string &dirtab_path)
static const int kActionNone
int Main(const ArgumentList &args)
bool ignore_special_files
static void HashDatabase(const std::string &database_path, shash::Any *hash_reflog)
manifest::Reflog * reflog
int Main(const ArgumentList &args)
struct dirent * GlobReaddir(void *dirp)
bool HasPrefix(const string &str, const string &prefix, const bool ignore_case)
void DropDatabaseFileOwnership()
static StatisticsDatabase * OpenStandardDB(const std::string repo_name)
bool DirectoryExists(const std::string &path)
void * GlobOpendir(const char *name)
bool stop_for_catalog_tweaks
bool SafeReadToString(int fd, std::string *final_result)
unsigned root_kcatalog_limit
size_t max_file_chunk_size
bool StorePublishStatistics(const perf::Statistics *statistics, const std::string &start_time, const bool success)
int GlobStat(const char *name, struct stat *st)
std::string session_token_file
bool ObtainDacReadSearchCapability()
virtual void Traverse()=0
uint64_t String2Uint64(const string &value)
std::map< char, SharedPtr< std::string > > ArgumentList
Algorithms ParseHashAlgorithm(const string &algorithm_option)
void GlobClosedir(void *dirp)
Any MkFromHexPtr(const HexPtr hex, const char suffix)
void DetermineNestedCatalogCandidates(const catalog::Dirtab &dirtab, catalog::SimpleCatalogManager *catalog_manager, std::vector< std::string > *nested_catalog_candidates)
void Generate(int actions)
void Spawn(const std::string &crash_dump_path)
static bool WriteChecksum(const std::string &path, const shash::Any &value)
bool ignore_xdir_hardlinks
std::string MakeCanonicalPath(const std::string &path)
void PrintError(const string &message)
const upload::Spooler * spooler_catalogs() const
static const uint64_t kDefaultTTL
bool CreateCatalogMarkers(const std::vector< std::string > &new_nested_catalogs)
CVMFS_EXPORT void LogCvmfs(const LogSource source, const int mask, const char *format,...)
std::string * g_glob_uniondir