CernVM-FS  2.12.0
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
swissknife_history.cc
Go to the documentation of this file.
1 
5 #include "swissknife_history.h"
6 #include "cvmfs_config.h"
7 
8 #include <algorithm>
9 #include <cassert>
10 #include <ctime>
11 
12 #include "catalog_rw.h"
13 #include "crypto/hash.h"
14 #include "crypto/signature.h"
15 #include "manifest_fetch.h"
16 #include "network/download.h"
17 #include "upload.h"
18 
19 using namespace std; // NOLINT
20 using namespace swissknife; // NOLINT
21 
22 const std::string CommandTag::kHeadTag = "trunk";
23 const std::string CommandTag::kPreviousHeadTag = "trunk-previous";
24 
25 const std::string CommandTag::kHeadTagDescription = "current HEAD";
26 const std::string CommandTag::kPreviousHeadTagDescription =
27  "default undo target";
28 
30  r->push_back(Parameter::Mandatory('w', "repository directory / url"));
31  r->push_back(Parameter::Mandatory('t', "temporary scratch directory"));
32  r->push_back(Parameter::Optional('p', "public key of the repository"));
33  r->push_back(Parameter::Optional('f', "fully qualified repository name"));
34  r->push_back(Parameter::Optional('r', "spooler definition string"));
35  r->push_back(Parameter::Optional('m', "(unsigned) manifest file to edit"));
36  r->push_back(Parameter::Optional('b', "mounted repository base hash"));
37  r->push_back(
38  Parameter::Optional('e', "hash algorithm to use (default SHA1)"));
39  r->push_back(Parameter::Switch('L', "follow HTTP redirects"));
40  r->push_back(Parameter::Optional('P', "session_token_file"));
41  r->push_back(Parameter::Optional('@', "proxy url"));
42 }
43 
44 CommandTag::Environment *CommandTag::InitializeEnvironment(
45  const ArgumentList &args, const bool read_write) {
46  const string repository_url = MakeCanonicalPath(*args.find('w')->second);
47  const string tmp_path = MakeCanonicalPath(*args.find('t')->second);
48  const string spl_definition =
49  (args.find('r') == args.end())
50  ? ""
51  : MakeCanonicalPath(*args.find('r')->second);
52  const string manifest_path = (args.find('m') == args.end())
53  ? ""
54  : MakeCanonicalPath(*args.find('m')->second);
55  const shash::Algorithms hash_algo =
56  (args.find('e') == args.end())
57  ? shash::kSha1
58  : shash::ParseHashAlgorithm(*args.find('e')->second);
59  const string pubkey_path = (args.find('p') == args.end())
60  ? ""
61  : MakeCanonicalPath(*args.find('p')->second);
62  const shash::Any base_hash =
63  (args.find('b') == args.end())
64  ? shash::Any()
65  : shash::MkFromHexPtr(shash::HexPtr(*args.find('b')->second),
67  const string repo_name =
68  (args.find('f') == args.end()) ? "" : *args.find('f')->second;
69 
70  string session_token_file;
71  if (args.find('P') != args.end()) {
72  session_token_file = *args.find('P')->second;
73  }
74 
75  // Sanity checks
76  if (hash_algo == shash::kAny) {
77  LogCvmfs(kLogCvmfs, kLogStderr, "failed to parse hash algorithm to use");
78  return NULL;
79  }
80 
81  if (read_write && spl_definition.empty()) {
82  LogCvmfs(kLogCvmfs, kLogStderr, "no upstream storage provided (-r)");
83  return NULL;
84  }
85 
86  if (read_write && manifest_path.empty()) {
87  LogCvmfs(kLogCvmfs, kLogStderr, "no (unsigned) manifest provided (-m)");
88  return NULL;
89  }
90 
91  if (!read_write && pubkey_path.empty()) {
92  LogCvmfs(kLogCvmfs, kLogStderr, "no public key provided (-p)");
93  return NULL;
94  }
95 
96  if (!read_write && repo_name.empty()) {
97  LogCvmfs(kLogCvmfs, kLogStderr, "no repository name provided (-f)");
98  return NULL;
99  }
100 
101  if (HasPrefix(spl_definition, "gw", false)) {
102  if (session_token_file.empty()) {
103  PrintError(
104  "Session token file has to be provided "
105  "when upstream type is gw.");
106  return NULL;
107  }
108  }
109 
110  // create new environment
111  // Note: We use this encapsulation because we cannot be sure that the
112  // Command object gets deleted properly. With the Environment object at
113  // hand we have full control and can make heavy and safe use of RAII
114  UniquePtr<Environment> env(new Environment(repository_url, tmp_path));
115  env->manifest_path.Set(manifest_path);
116  env->history_path.Set(CreateTempPath(tmp_path + "/history", 0600));
117 
118  // initialize the (swissknife global) download manager
119  const bool follow_redirects = (args.count('L') > 0);
120  const std::string &proxy = (args.count('@') > 0) ?
121  *args.find('@')->second : "";
122  if (!this->InitDownloadManager(follow_redirects, proxy)) {
123  return NULL;
124  }
125 
126  // initialize the (swissknife global) signature manager (if possible)
127  if (!pubkey_path.empty() &&
128  !this->InitVerifyingSignatureManager(pubkey_path)) {
129  return NULL;
130  }
131 
132  // open the (yet unsigned) manifest file if it is there, otherwise load the
133  // latest manifest from the server
134  env->manifest =
135  (FileExists(env->manifest_path.path()))
136  ? OpenLocalManifest(env->manifest_path.path())
137  : FetchRemoteManifest(env->repository_url, repo_name, base_hash);
138 
139  if (!env->manifest.IsValid()) {
140  LogCvmfs(kLogCvmfs, kLogStderr, "failed to load manifest file");
141  return NULL;
142  }
143 
144  // figure out the hash of the history from the previous revision if needed
145  if (read_write && env->manifest->history().IsNull() && !base_hash.IsNull()) {
146  env->previous_manifest =
147  FetchRemoteManifest(env->repository_url, repo_name, base_hash);
148  if (!env->previous_manifest.IsValid()) {
149  LogCvmfs(kLogCvmfs, kLogStderr, "failed to load previous manifest");
150  return NULL;
151  }
152 
154  "using history database '%s' from previous "
155  "manifest (%s) as basis",
156  env->previous_manifest->history().ToString().c_str(),
157  env->previous_manifest->repository_name().c_str());
158  env->manifest->set_history(env->previous_manifest->history());
159  env->manifest->set_repository_name(
160  env->previous_manifest->repository_name());
161  }
162 
163  // download the history database referenced in the manifest
164  env->history = GetHistory(env->manifest.weak_ref(), env->repository_url,
165  env->history_path.path(), read_write);
166  if (!env->history.IsValid()) {
167  return NULL;
168  }
169 
170  // if the using Command is expected to change the history database, we
171  // need
172  // to initialize the upload spooler for potential later history upload
173  if (read_write) {
174  const bool use_file_chunking = false;
175  const bool generate_legacy_bulk_chunks = false;
176  const upload::SpoolerDefinition sd(spl_definition, hash_algo,
178  generate_legacy_bulk_chunks,
179  use_file_chunking, 0, 0, 0,
180  session_token_file);
181  env->spooler = upload::Spooler::Construct(sd);
182  if (!env->spooler.IsValid()) {
183  LogCvmfs(kLogCvmfs, kLogStderr, "failed to initialize upload spooler");
184  return NULL;
185  }
186  }
187 
188  // return the pointer of the Environment (passing the ownership along)
189  return env.Release();
190 }
191 
192 bool CommandTag::CloseAndPublishHistory(Environment *env) {
193  assert(env->spooler.IsValid());
194 
195  // set the previous revision pointer of the history database
196  env->history->SetPreviousRevision(env->manifest->history());
197 
198  // close the history database
199  history::History *weak_history = env->history.Release();
200  delete weak_history;
201 
202  // compress and upload the new history database
203  Future<shash::Any> history_hash;
204  upload::Spooler::CallbackPtr callback = env->spooler->RegisterListener(
205  &CommandTag::UploadClosure, this, &history_hash);
206  env->spooler->ProcessHistory(env->history_path.path());
207  env->spooler->WaitForUpload();
208  const shash::Any new_history_hash = history_hash.Get();
209  env->spooler->UnregisterListener(callback);
210 
211  // retrieve the (async) uploader result
212  if (new_history_hash.IsNull()) {
213  return false;
214  }
215 
216  // update the (yet unsigned) manifest file
217  env->manifest->set_history(new_history_hash);
218  if (!env->manifest->Export(env->manifest_path.path())) {
219  LogCvmfs(kLogCvmfs, kLogStderr, "failed to export the new manifest '%s'",
220  env->manifest_path.path().c_str());
221  return false;
222  }
223 
224  // disable the unlink guard in order to keep the newly exported manifest file
225  env->manifest_path.Disable();
227  "exported manifest (%" PRIu64 ") with new history '%s'",
228  env->manifest->revision(), new_history_hash.ToString().c_str());
229 
230  return true;
231 }
232 
233 
234 bool CommandTag::UploadCatalogAndUpdateManifest(
236  assert(env->spooler.IsValid());
237 
238  // gather information about catalog to be uploaded and update manifest
239  UniquePtr<catalog::WritableCatalog> wr_catalog(catalog);
240  const std::string catalog_path = wr_catalog->database_path();
241  env->manifest->set_ttl(wr_catalog->GetTTL());
242  env->manifest->set_revision(wr_catalog->GetRevision());
243  env->manifest->set_publish_timestamp(wr_catalog->GetLastModified());
244 
245  // close the catalog
246  catalog::WritableCatalog *weak_catalog = wr_catalog.Release();
247  delete weak_catalog;
248 
249  // upload the catalog
250  Future<shash::Any> catalog_hash;
251  upload::Spooler::CallbackPtr callback = env->spooler->RegisterListener(
252  &CommandTag::UploadClosure, this, &catalog_hash);
253  env->spooler->ProcessCatalog(catalog_path);
254  env->spooler->WaitForUpload();
255  const shash::Any new_catalog_hash = catalog_hash.Get();
256  env->spooler->UnregisterListener(callback);
257 
258  // check if the upload succeeded
259  if (new_catalog_hash.IsNull()) {
260  LogCvmfs(kLogCvmfs, kLogStderr, "failed to upload catalog '%s'",
261  catalog_path.c_str());
262  return false;
263  }
264 
265  // update the catalog size and hash in the manifest
266  const size_t catalog_size = GetFileSize(catalog_path);
267  env->manifest->set_catalog_size(catalog_size);
268  env->manifest->set_catalog_hash(new_catalog_hash);
269 
270  LogCvmfs(kLogCvmfs, kLogVerboseMsg, "uploaded new catalog (%lu bytes) '%s'",
271  catalog_size, new_catalog_hash.ToString().c_str());
272 
273  return true;
274 }
275 
276 void CommandTag::UploadClosure(const upload::SpoolerResult &result,
277  Future<shash::Any> *hash) {
278  assert(!result.IsChunked());
279  if (result.return_code != 0) {
280  LogCvmfs(kLogCvmfs, kLogStderr, "failed to upload history database (%d)",
281  result.return_code);
282  hash->Set(shash::Any());
283  } else {
284  hash->Set(result.content_hash);
285  }
286 }
287 
288 bool CommandTag::UpdateUndoTags(
289  Environment *env, const history::History::Tag &current_head_template,
290  const bool undo_rollback) {
291  assert(env->history.IsValid());
292 
293  history::History::Tag current_head;
294  history::History::Tag current_old_head;
295 
296  // remove previous HEAD tag
297  if (!env->history->Remove(CommandTag::kPreviousHeadTag)) {
298  LogCvmfs(kLogCvmfs, kLogVerboseMsg, "didn't find a previous HEAD tag");
299  }
300 
301  // check if we have a current HEAD tag that needs to renamed to previous
302  // HEAD
303  if (env->history->GetByName(CommandTag::kHeadTag, &current_head)) {
304  // remove current HEAD tag
305  if (!env->history->Remove(CommandTag::kHeadTag)) {
306  LogCvmfs(kLogCvmfs, kLogStderr, "failed to remove current HEAD tag");
307  return false;
308  }
309 
310  // set previous HEAD tag where current HEAD used to be
311  if (!undo_rollback) {
312  current_old_head = current_head;
313  current_old_head.name = CommandTag::kPreviousHeadTag;
314  current_old_head.description = CommandTag::kPreviousHeadTagDescription;
315  if (!env->history->Insert(current_old_head)) {
316  LogCvmfs(kLogCvmfs, kLogStderr, "failed to set previous HEAD tag");
317  return false;
318  }
319  }
320  }
321 
322  // set the current HEAD to the catalog provided by the template HEAD
323  current_head = current_head_template;
324  current_head.name = CommandTag::kHeadTag;
325  current_head.description = CommandTag::kHeadTagDescription;
326  if (!env->history->Insert(current_head)) {
327  LogCvmfs(kLogCvmfs, kLogStderr, "failed to set new current HEAD");
328  return false;
329  }
330 
331  return true;
332 }
333 
334 bool CommandTag::FetchObject(const std::string &repository_url,
335  const shash::Any &object_hash,
336  const std::string &destination_path) const {
337  assert(!object_hash.IsNull());
338 
339  download::Failures dl_retval;
340  const std::string url = repository_url + "/data/" + object_hash.MakePath();
341 
342  cvmfs::PathSink pathsink(destination_path);
343  download::JobInfo download_object(&url, true, false, &object_hash, &pathsink);
344  dl_retval = download_manager()->Fetch(&download_object);
345 
346  if (dl_retval != download::kFailOk) {
347  LogCvmfs(kLogCvmfs, kLogStderr, "failed to download object '%s' (%d - %s)",
348  object_hash.ToStringWithSuffix().c_str(), dl_retval,
349  download::Code2Ascii(dl_retval));
350  return false;
351  }
352 
353  return true;
354 }
355 
356 history::History *CommandTag::GetHistory(const manifest::Manifest *manifest,
357  const std::string &repository_url,
358  const std::string &history_path,
359  const bool read_write) const {
360  const shash::Any history_hash = manifest->history();
362 
363  if (history_hash.IsNull()) {
364  history = history::SqliteHistory::Create(history_path,
365  manifest->repository_name());
366  if (NULL == history) {
367  LogCvmfs(kLogCvmfs, kLogStderr, "failed to create history database");
368  return NULL;
369  }
370  } else {
371  if (!FetchObject(repository_url, history_hash, history_path)) {
372  return NULL;
373  }
374 
375  history = (read_write) ? history::SqliteHistory::OpenWritable(history_path)
376  : history::SqliteHistory::Open(history_path);
377  if (NULL == history) {
378  LogCvmfs(kLogCvmfs, kLogStderr, "failed to open history database (%s)",
379  history_path.c_str());
380  unlink(history_path.c_str());
381  return NULL;
382  }
383 
384  assert(history->fqrn() == manifest->repository_name());
385  }
386 
387  return history;
388 }
389 
390 catalog::Catalog *CommandTag::GetCatalog(const std::string &repository_url,
391  const shash::Any &catalog_hash,
392  const std::string catalog_path,
393  const bool read_write) const {
394  assert(shash::kSuffixCatalog == catalog_hash.suffix);
395  if (!FetchObject(repository_url, catalog_hash, catalog_path)) {
396  return NULL;
397  }
398 
399  const std::string catalog_root_path = "";
400  return (read_write) ? catalog::WritableCatalog::AttachFreely(
401  catalog_root_path, catalog_path, catalog_hash)
403  catalog_root_path, catalog_path, catalog_hash);
404 }
405 
406 void CommandTag::PrintTagMachineReadable(
407  const history::History::Tag &tag) const {
408  LogCvmfs(kLogCvmfs, kLogStdout, "%s %s %" PRIu64 " %" PRIu64 " %ld %s %s",
409  tag.name.c_str(),
410  tag.root_hash.ToString().c_str(), tag.size, tag.revision,
411  tag.timestamp,
412  (tag.branch == "") ? "(default)" : tag.branch.c_str(),
413  tag.description.c_str());
414 }
415 
416 std::string CommandTag::AddPadding(const std::string &str, const size_t padding,
417  const bool align_right,
418  const std::string &fill_char) const {
419  assert(str.size() <= padding);
420  std::string result(str);
421  result.resize(padding);
422  const size_t pos = (align_right) ? 0 : str.size();
423  const size_t padding_width = padding - str.size();
424  for (size_t i = 0; i < padding_width; ++i) result.insert(pos, fill_char);
425  return result;
426 }
427 
428 bool CommandTag::IsUndoTagName(const std::string &tag_name) const {
429  return tag_name == CommandTag::kHeadTag ||
430  tag_name == CommandTag::kPreviousHeadTag;
431 }
432 
433 //------------------------------------------------------------------------------
434 
435 ParameterList CommandEditTag::GetParams() const {
436  ParameterList r;
438 
439  r.push_back(Parameter::Optional('d', "space separated tags to be deleted"));
440  r.push_back(Parameter::Optional('a', "name of the new tag"));
441  r.push_back(Parameter::Optional('D', "description of the tag"));
442  r.push_back(Parameter::Optional('B', "branch of the new tag"));
443  r.push_back(Parameter::Optional('P', "predecessor branch"));
444  r.push_back(Parameter::Optional('h', "root hash of the new tag"));
445  r.push_back(Parameter::Switch('x', "maintain undo tags"));
446  return r;
447 }
448 
449 int CommandEditTag::Main(const ArgumentList &args) {
450  if ((args.find('d') == args.end()) && (args.find('a') == args.end()) &&
451  (args.find('x') == args.end())) {
452  LogCvmfs(kLogCvmfs, kLogStderr, "nothing to do");
453  return 1;
454  }
455 
456  // initialize the Environment (taking ownership)
457  const bool history_read_write = true;
458  UniquePtr<Environment> env(InitializeEnvironment(args, history_read_write));
459  if (!env.IsValid()) {
460  LogCvmfs(kLogCvmfs, kLogStderr, "failed to init environment");
461  return 1;
462  }
463 
464  int retval;
465  if (args.find('d') != args.end()) {
466  retval = RemoveTags(args, env.weak_ref());
467  if (retval != 0) return retval;
468  }
469  if ((args.find('a') != args.end()) || (args.find('x') != args.end())) {
470  retval = AddNewTag(args, env.weak_ref());
471  if (retval != 0) return retval;
472  }
473 
474  // finalize processing and upload new history database
475  if (!CloseAndPublishHistory(env.weak_ref())) {
476  return 1;
477  }
478  return 0;
479 }
480 
481 int CommandEditTag::AddNewTag(const ArgumentList &args, Environment *env) {
482  const std::string tag_name =
483  (args.find('a') != args.end()) ? *args.find('a')->second : "";
484  const std::string tag_description =
485  (args.find('D') != args.end()) ? *args.find('D')->second : "";
486  const bool undo_tags = (args.find('x') != args.end());
487  const std::string root_hash_string =
488  (args.find('h') != args.end()) ? *args.find('h')->second : "";
489  const std::string branch_name =
490  (args.find('B') != args.end()) ? *args.find('B')->second : "";
491  const std::string previous_branch_name =
492  (args.find('P') != args.end()) ? *args.find('P')->second : "";
493 
494  if (tag_name.find(" ") != std::string::npos) {
495  LogCvmfs(kLogCvmfs, kLogStderr, "tag names must not contain spaces");
496  return 1;
497  }
498 
499  assert(!tag_name.empty() || undo_tags);
500 
501  if (IsUndoTagName(tag_name)) {
502  LogCvmfs(kLogCvmfs, kLogStderr, "undo tags are managed internally");
503  return 1;
504  }
505 
506  // set the root hash to be tagged to the current HEAD if no other hash was
507  // given by the user
508  shash::Any root_hash = GetTagRootHash(env, root_hash_string);
509  if (root_hash.IsNull()) {
510  return 1;
511  }
512 
513  // open the catalog to be tagged (to check for existence and for meta info)
514  const UnlinkGuard catalog_path(
515  CreateTempPath(env->tmp_path + "/catalog", 0600));
516  const bool catalog_read_write = false;
517  const UniquePtr<catalog::Catalog> catalog(GetCatalog(
518  env->repository_url, root_hash, catalog_path.path(), catalog_read_write));
519  if (!catalog.IsValid()) {
520  LogCvmfs(kLogCvmfs, kLogStderr, "catalog with hash '%s' does not exist",
521  root_hash.ToString().c_str());
522  return 1;
523  }
524 
525  // check if the catalog is a root catalog
526  if (!catalog->root_prefix().IsEmpty()) {
528  "cannot tag catalog '%s' that is not a "
529  "root catalog.",
530  root_hash.ToString().c_str());
531  return 1;
532  }
533 
534  // create a template for the new tag to be created, moved or used as undo tag
535  history::History::Tag tag_template;
536  tag_template.name = "<template>";
537  tag_template.root_hash = root_hash;
538  tag_template.size = GetFileSize(catalog_path.path());
539  tag_template.revision = catalog->GetRevision();
540  tag_template.timestamp = catalog->GetLastModified();
541  tag_template.branch = branch_name;
542  tag_template.description = tag_description;
543 
544  // manipulate the tag database by creating a new tag or moving an existing one
545  if (!tag_name.empty()) {
546  tag_template.name = tag_name;
547  const bool user_provided_hash = (!root_hash_string.empty());
548 
549  if (!env->history->ExistsBranch(tag_template.branch)) {
551  tag_template.branch,
552  previous_branch_name,
553  tag_template.revision);
554  if (!env->history->InsertBranch(branch)) {
555  LogCvmfs(kLogCvmfs, kLogStderr, "cannot insert branch '%s'",
556  tag_template.branch.c_str());
557  return 1;
558  }
559  }
560 
561  if (!ManipulateTag(env, tag_template, user_provided_hash)) {
562  return 1;
563  }
564  }
565 
566  // handle undo tags ('trunk' and 'trunk-previous') if necessary
567  if (undo_tags && !UpdateUndoTags(env, tag_template)) {
568  return 1;
569  }
570 
571  return 0;
572 }
573 
574 shash::Any CommandEditTag::GetTagRootHash(
575  Environment *env, const std::string &root_hash_string) const {
576  shash::Any root_hash;
577 
578  if (root_hash_string.empty()) {
580  "no catalog hash provided, using hash"
581  "of current HEAD catalog (%s)",
582  env->manifest->catalog_hash().ToString().c_str());
583  root_hash = env->manifest->catalog_hash();
584  } else {
585  root_hash = shash::MkFromHexPtr(shash::HexPtr(root_hash_string),
587  if (root_hash.IsNull()) {
589  "failed to read provided catalog hash '%s'",
590  root_hash_string.c_str());
591  }
592  }
593 
594  return root_hash;
595 }
596 
597 bool CommandEditTag::ManipulateTag(Environment *env,
598  const history::History::Tag &tag_template,
599  const bool user_provided_hash) {
600  const std::string &tag_name = tag_template.name;
601 
602  // check if the tag already exists, otherwise create it and return
603  if (!env->history->Exists(tag_name)) {
604  return CreateTag(env, tag_template);
605  }
606 
607  // tag does exist already, now we need to see if we can move it
608  if (!user_provided_hash) {
610  "a tag with the name '%s' already exists. Do you want to move it? "
611  "(-h <root hash>)",
612  tag_name.c_str());
613  return false;
614  }
615 
616  // move the already existing tag and return
617  return MoveTag(env, tag_template);
618 }
619 
620 bool CommandEditTag::MoveTag(Environment *env,
621  const history::History::Tag &tag_template)
622 {
623  const std::string &tag_name = tag_template.name;
624  history::History::Tag new_tag = tag_template;
625 
626  // get the already existent tag
627  history::History::Tag old_tag;
628  if (!env->history->GetByName(tag_name, &old_tag)) {
629  LogCvmfs(kLogCvmfs, kLogStderr, "failed to retrieve tag '%s' for moving",
630  tag_name.c_str());
631  return false;
632  }
633 
634  // check if we would move the tag to the same hash
635  if (old_tag.root_hash == new_tag.root_hash) {
636  LogCvmfs(kLogCvmfs, kLogStderr, "tag '%s' already points to '%s'",
637  tag_name.c_str(), old_tag.root_hash.ToString().c_str());
638  return false;
639  }
640 
641  // copy over old description if no new description was given
642  if (new_tag.description.empty()) {
643  new_tag.description = old_tag.description;
644  }
645  new_tag.branch = old_tag.branch;
646 
647  // remove the old tag from the database
648  if (!env->history->Remove(tag_name)) {
649  LogCvmfs(kLogCvmfs, kLogStderr, "removing old tag '%s' before move failed",
650  tag_name.c_str());
651  return false;
652  }
653  if (!env->history->PruneBranches()) {
654  LogCvmfs(kLogCvmfs, kLogStderr, "could not prune unused branches");
655  return false;
656  }
657  bool retval = env->history->Vacuum();
658  assert(retval);
659 
660  LogCvmfs(kLogCvmfs, kLogStdout, "moving tag '%s' from '%s' to '%s'",
661  tag_name.c_str(), old_tag.root_hash.ToString().c_str(),
662  tag_template.root_hash.ToString().c_str());
663 
664  // re-create the moved tag
665  return CreateTag(env, new_tag);
666 }
667 
668 bool CommandEditTag::CreateTag(Environment *env,
669  const history::History::Tag &new_tag) {
670  if (!env->history->Insert(new_tag)) {
671  LogCvmfs(kLogCvmfs, kLogStderr, "failed to insert new tag '%s'",
672  new_tag.name.c_str());
673  return false;
674  }
675 
676  return true;
677 }
678 
679 int CommandEditTag::RemoveTags(const ArgumentList &args, Environment *env) {
680  typedef std::vector<std::string> TagNames;
681  const std::string tags_to_delete = *args.find('d')->second;
682 
683  const TagNames condemned_tags = SplitString(tags_to_delete, ' ');
684 
685  // check if user tries to remove a magic undo tag
686  TagNames::const_iterator i = condemned_tags.begin();
687  const TagNames::const_iterator iend = condemned_tags.end();
688  for (; i != iend; ++i) {
689  if (IsUndoTagName(*i)) {
691  "undo tags are handled internally and cannot be deleted");
692  return 1;
693  }
694  }
695 
696  LogCvmfs(kLogCvmfs, kLogDebug, "proceeding to delete %lu tags",
697  condemned_tags.size());
698 
699  // check if the tags to be deleted exist
700  bool all_exist = true;
701  for (i = condemned_tags.begin(); i != iend; ++i) {
702  if (!env->history->Exists(*i)) {
703  LogCvmfs(kLogCvmfs, kLogStderr, "tag '%s' does not exist", i->c_str());
704  all_exist = false;
705  }
706  }
707  if (!all_exist) {
708  return 1;
709  }
710 
711  // delete the tags from the tag database and print their root hashes
712  i = condemned_tags.begin();
713  env->history->BeginTransaction();
714  for (; i != iend; ++i) {
715  // print some information about the tag to be deleted
716  history::History::Tag condemned_tag;
717  const bool found_tag = env->history->GetByName(*i, &condemned_tag);
718  assert(found_tag);
719  LogCvmfs(kLogCvmfs, kLogStdout, "deleting '%s' (%s)",
720  condemned_tag.name.c_str(),
721  condemned_tag.root_hash.ToString().c_str());
722 
723  // remove the tag
724  if (!env->history->Remove(*i)) {
725  LogCvmfs(kLogCvmfs, kLogStderr, "failed to remove tag '%s' from history",
726  i->c_str());
727  return 1;
728  }
729  }
730  bool retval = env->history->PruneBranches();
731  if (!retval) {
733  "failed to prune unused branches from history");
734  return 1;
735  }
736  env->history->CommitTransaction();
737  retval = env->history->Vacuum();
738  assert(retval);
739 
740  return 0;
741 }
742 
743 //------------------------------------------------------------------------------
744 
745 
746 ParameterList CommandListTags::GetParams() const {
747  ParameterList r;
749  r.push_back(Parameter::Switch('x', "machine readable output"));
750  r.push_back(Parameter::Switch('B', "print branch hierarchy"));
751  return r;
752 }
753 
754 void CommandListTags::PrintHumanReadableTagList(
755  const CommandListTags::TagList &tags) const {
756  // go through the list of tags and figure out the column widths
757  const std::string name_label = "Name";
758  const std::string rev_label = "Revision";
759  const std::string time_label = "Timestamp";
760  const std::string branch_label = "Branch";
761  const std::string desc_label = "Description";
762 
763  // figure out the maximal lengths of the fields in the lists
764  TagList::const_reverse_iterator i = tags.rbegin();
765  const TagList::const_reverse_iterator iend = tags.rend();
766  size_t max_name_len = name_label.size();
767  size_t max_rev_len = rev_label.size();
768  size_t max_time_len = desc_label.size();
769  size_t max_branch_len = branch_label.size();
770  for (; i != iend; ++i) {
771  max_name_len = std::max(max_name_len, i->name.size());
772  max_rev_len = std::max(max_rev_len, StringifyInt(i->revision).size());
773  max_time_len =
774  std::max(max_time_len, StringifyTime(i->timestamp, true).size());
775  max_branch_len = std::max(max_branch_len, i->branch.size());
776  }
777 
778  // print the list header
780  "%s \u2502 %s \u2502 %s \u2502 %s \u2502 %s",
781  AddPadding(name_label, max_name_len).c_str(),
782  AddPadding(rev_label, max_rev_len).c_str(),
783  AddPadding(time_label, max_time_len).c_str(),
784  AddPadding(branch_label, max_branch_len).c_str(),
785  desc_label.c_str());
787  "%s\u2500\u253C\u2500%s\u2500\u253C\u2500%s"
788  "\u2500\u253C\u2500%s\u2500\u253C\u2500%s",
789  AddPadding("", max_name_len, false, "\u2500").c_str(),
790  AddPadding("", max_rev_len, false, "\u2500").c_str(),
791  AddPadding("", max_time_len, false, "\u2500").c_str(),
792  AddPadding("", max_branch_len, false, "\u2500").c_str(),
793  AddPadding("", desc_label.size() + 1, false, "\u2500").c_str());
794 
795  // print the rows of the list
796  i = tags.rbegin();
797  for (; i != iend; ++i) {
798  LogCvmfs(
800  "%s \u2502 %s \u2502 %s \u2502 %s \u2502 %s",
801  AddPadding(i->name, max_name_len).c_str(),
802  AddPadding(StringifyInt(i->revision), max_rev_len, true).c_str(),
803  AddPadding(StringifyTime(i->timestamp, true), max_time_len).c_str(),
804  AddPadding(i->branch, max_branch_len).c_str(),
805  i->description.c_str());
806  }
807 
808  // print the list footer
810  "%s\u2500\u2534\u2500%s\u2500\u2534\u2500%s"
811  "\u2500\u2534\u2500%s\u2500\u2534\u2500%s",
812  AddPadding("", max_name_len, false, "\u2500").c_str(),
813  AddPadding("", max_rev_len, false, "\u2500").c_str(),
814  AddPadding("", max_time_len, false, "\u2500").c_str(),
815  AddPadding("", max_branch_len, false, "\u2500").c_str(),
816  AddPadding("", desc_label.size() + 1, false, "\u2500").c_str());
817 
818  // print the number of tags listed
819  LogCvmfs(kLogCvmfs, kLogStdout, "listing contains %lu tags", tags.size());
820 }
821 
822 void CommandListTags::PrintMachineReadableTagList(const TagList &tags) const {
823  TagList::const_iterator i = tags.begin();
824  const TagList::const_iterator iend = tags.end();
825  for (; i != iend; ++i) {
826  PrintTagMachineReadable(*i);
827  }
828 }
829 
830 
831 void CommandListTags::PrintHumanReadableBranchList(
832  const BranchHierarchy &branches) const
833 {
834  unsigned N = branches.size();
835  for (unsigned i = 0; i < N; ++i) {
836  for (unsigned l = 0; l < branches[i].level; ++l) {
838  ((l + 1) == branches[i].level) ? "\u251c " : "\u2502 ");
839  }
840  LogCvmfs(kLogCvmfs, kLogStdout, "%s @%" PRIu64,
841  branches[i].branch.branch.c_str(),
842  branches[i].branch.initial_revision);
843  }
844 }
845 
846 
847 void CommandListTags::PrintMachineReadableBranchList(
848  const BranchHierarchy &branches) const
849 {
850  unsigned N = branches.size();
851  for (unsigned i = 0; i < N; ++i) {
852  LogCvmfs(kLogCvmfs, kLogStdout, "[%u] %s%s @%" PRIu64,
853  branches[i].level,
854  AddPadding("", branches[i].level, false, " ").c_str(),
855  branches[i].branch.branch.c_str(),
856  branches[i].branch.initial_revision);
857  }
858 }
859 
860 
861 void CommandListTags::SortBranchesRecursively(
862  unsigned level,
863  const string &parent_branch,
864  const BranchList &branches,
865  BranchHierarchy *hierarchy) const
866 {
867  // For large numbers of branches, this should be turned into the O(n) version
868  // using a linked list
869  unsigned N = branches.size();
870  for (unsigned i = 0; i < N; ++i) {
871  if (branches[i].branch == "")
872  continue;
873  if (branches[i].parent == parent_branch) {
874  hierarchy->push_back(BranchLevel(branches[i], level));
875  SortBranchesRecursively(
876  level + 1, branches[i].branch, branches, hierarchy);
877  }
878  }
879 }
880 
881 
882 CommandListTags::BranchHierarchy CommandListTags::SortBranches(
883  const BranchList &branches) const
884 {
885  BranchHierarchy hierarchy;
886  hierarchy.push_back(
887  BranchLevel(history::History::Branch("(default)", "", 0), 0));
888  SortBranchesRecursively(1, "", branches, &hierarchy);
889  return hierarchy;
890 }
891 
892 
893 int CommandListTags::Main(const ArgumentList &args) {
894  const bool machine_readable = (args.find('x') != args.end());
895  const bool branch_hierarchy = (args.find('B') != args.end());
896 
897  // initialize the Environment (taking ownership)
898  const bool history_read_write = false;
899  UniquePtr<Environment> env(InitializeEnvironment(args, history_read_write));
900  if (!env.IsValid()) {
901  LogCvmfs(kLogCvmfs, kLogStderr, "failed to init environment");
902  return 1;
903  }
904 
905  if (branch_hierarchy) {
906  BranchList branch_list;
907  if (!env->history->ListBranches(&branch_list)) {
909  "failed to list branches in history database");
910  return 1;
911  }
912  BranchHierarchy branch_hierarchy = SortBranches(branch_list);
913 
914  if (machine_readable) {
915  PrintMachineReadableBranchList(branch_hierarchy);
916  } else {
917  PrintHumanReadableBranchList(branch_hierarchy);
918  }
919  } else {
920  // obtain a full list of all tags
921  TagList tags;
922  if (!env->history->List(&tags)) {
924  "failed to list tags in history database");
925  return 1;
926  }
927 
928  if (machine_readable) {
929  PrintMachineReadableTagList(tags);
930  } else {
931  PrintHumanReadableTagList(tags);
932  }
933  }
934 
935  return 0;
936 }
937 
938 //------------------------------------------------------------------------------
939 
940 ParameterList CommandInfoTag::GetParams() const {
941  ParameterList r;
943 
944  r.push_back(Parameter::Mandatory('n', "name of the tag to be inspected"));
945  r.push_back(Parameter::Switch('x', "machine readable output"));
946  return r;
947 }
948 
949 std::string CommandInfoTag::HumanReadableFilesize(const size_t filesize) const {
950  const size_t kiB = 1024;
951  const size_t MiB = kiB * 1024;
952  const size_t GiB = MiB * 1024;
953 
954  if (filesize > GiB) {
955  return StringifyDouble(static_cast<double>(filesize) / GiB) + " GiB";
956  } else if (filesize > MiB) {
957  return StringifyDouble(static_cast<double>(filesize) / MiB) + " MiB";
958  } else if (filesize > kiB) {
959  return StringifyDouble(static_cast<double>(filesize) / kiB) + " kiB";
960  } else {
961  return StringifyInt(filesize) + " Byte";
962  }
963 }
964 
965 void CommandInfoTag::PrintHumanReadableInfo(
966  const history::History::Tag &tag) const {
968  "Name: %s\n"
969  "Revision: %" PRIu64 "\n"
970  "Timestamp: %s\n"
971  "Branch: %s\n"
972  "Root Hash: %s\n"
973  "Catalog Size: %s\n"
974  "%s",
975  tag.name.c_str(), tag.revision,
976  StringifyTime(tag.timestamp, true /* utc */).c_str(),
977  tag.branch.c_str(),
978  tag.root_hash.ToString().c_str(),
979  HumanReadableFilesize(tag.size).c_str(),
980  tag.description.c_str());
981 }
982 
983 int CommandInfoTag::Main(const ArgumentList &args) {
984  const std::string tag_name = *args.find('n')->second;
985  const bool machine_readable = (args.find('x') != args.end());
986 
987  // initialize the Environment (taking ownership)
988  const bool history_read_write = false;
989  UniquePtr<Environment> env(InitializeEnvironment(args, history_read_write));
990  if (!env.IsValid()) {
991  LogCvmfs(kLogCvmfs, kLogStderr, "failed to init environment");
992  return 1;
993  }
994 
996  const bool found = env->history->GetByName(tag_name, &tag);
997  if (!found) {
998  LogCvmfs(kLogCvmfs, kLogStderr, "tag '%s' does not exist",
999  tag_name.c_str());
1000  return 1;
1001  }
1002 
1003  if (machine_readable) {
1004  PrintTagMachineReadable(tag);
1005  } else {
1006  PrintHumanReadableInfo(tag);
1007  }
1008 
1009  return 0;
1010 }
1011 
1012 //------------------------------------------------------------------------------
1013 
1014 ParameterList CommandRollbackTag::GetParams() const {
1015  ParameterList r;
1017 
1018  r.push_back(Parameter::Optional('n', "name of the tag to be republished"));
1019  return r;
1020 }
1021 
1022 int CommandRollbackTag::Main(const ArgumentList &args) {
1023  const bool undo_rollback = (args.find('n') == args.end());
1024  const std::string tag_name =
1025  (!undo_rollback) ? *args.find('n')->second : CommandTag::kPreviousHeadTag;
1026 
1027  // initialize the Environment (taking ownership)
1028  const bool history_read_write = true;
1029  UniquePtr<Environment> env(InitializeEnvironment(args, history_read_write));
1030  if (!env.IsValid()) {
1031  LogCvmfs(kLogCvmfs, kLogStderr, "failed to init environment");
1032  return 1;
1033  }
1034 
1035  // find tag to be rolled back to
1036  history::History::Tag target_tag;
1037  const bool found = env->history->GetByName(tag_name, &target_tag);
1038  if (!found) {
1039  if (undo_rollback) {
1041  "only one anonymous rollback supported - "
1042  "perhaps you want to provide a tag name?");
1043  } else {
1044  LogCvmfs(kLogCvmfs, kLogStderr, "tag '%s' does not exist",
1045  tag_name.c_str());
1046  }
1047  return 1;
1048  }
1049  if (target_tag.branch != "") {
1051  "rollback is only supported on the default branch");
1052  return 1;
1053  }
1054 
1055  // list the tags that will be deleted
1056  TagList affected_tags;
1057  if (!env->history->ListTagsAffectedByRollback(tag_name, &affected_tags)) {
1059  "failed to list condemned tags prior to rollback to '%s'",
1060  tag_name.c_str());
1061  return 1;
1062  }
1063 
1064  // check if tag is valid to be rolled back to
1065  const uint64_t current_revision = env->manifest->revision();
1066  assert(target_tag.revision <= current_revision);
1067  if (target_tag.revision == current_revision) {
1069  "not rolling back to current head (%" PRIu64 ")",
1070  current_revision);
1071  return 1;
1072  }
1073 
1074  // open the catalog to be rolled back to
1075  const UnlinkGuard catalog_path(
1076  CreateTempPath(env->tmp_path + "/catalog", 0600));
1077  const bool catalog_read_write = true;
1079  dynamic_cast<catalog::WritableCatalog *>(
1080  GetCatalog(env->repository_url, target_tag.root_hash,
1081  catalog_path.path(), catalog_read_write)));
1082  if (!catalog.IsValid()) {
1083  LogCvmfs(kLogCvmfs, kLogStderr, "failed to open catalog with hash '%s'",
1084  target_tag.root_hash.ToString().c_str());
1085  return 1;
1086  }
1087 
1088  // check if the catalog has a supported schema version
1089  if (catalog->schema() < catalog::CatalogDatabase::kLatestSupportedSchema -
1092  "not rolling back to outdated and "
1093  "incompatible catalog schema (%.1f < %.1f)",
1094  catalog->schema(),
1096  return 1;
1097  }
1098 
1099  // update the catalog to be republished
1100  catalog->Transaction();
1101  catalog->UpdateLastModified();
1102  catalog->SetRevision(current_revision + 1);
1103  catalog->SetPreviousRevision(env->manifest->catalog_hash());
1104  catalog->Commit();
1105 
1106  // Upload catalog (handing over ownership of catalog pointer)
1107  if (!UploadCatalogAndUpdateManifest(env.weak_ref(), catalog.Release())) {
1108  LogCvmfs(kLogCvmfs, kLogStderr, "catalog upload failed");
1109  return 1;
1110  }
1111 
1112  // update target tag with newly published root catalog information
1113  history::History::Tag updated_target_tag(target_tag);
1114  updated_target_tag.root_hash = env->manifest->catalog_hash();
1115  updated_target_tag.size = env->manifest->catalog_size();
1116  updated_target_tag.revision = env->manifest->revision();
1117  updated_target_tag.timestamp = env->manifest->publish_timestamp();
1118  if (!env->history->Rollback(updated_target_tag)) {
1119  LogCvmfs(kLogCvmfs, kLogStderr, "failed to rollback history to '%s'",
1120  updated_target_tag.name.c_str());
1121  return 1;
1122  }
1123  bool retval = env->history->Vacuum();
1124  assert(retval);
1125 
1126  // set the magic undo tags
1127  if (!UpdateUndoTags(env.weak_ref(), updated_target_tag, undo_rollback)) {
1128  LogCvmfs(kLogCvmfs, kLogStderr, "failed to update magic undo tags");
1129  return 1;
1130  }
1131 
1132  // finalize the history and upload it
1133  if (!CloseAndPublishHistory(env.weak_ref())) {
1134  return 1;
1135  }
1136 
1137  // print the tags that have been removed by the rollback
1138  PrintDeletedTagList(affected_tags);
1139 
1140  return 0;
1141 }
1142 
1143 void CommandRollbackTag::PrintDeletedTagList(const TagList &tags) const {
1144  size_t longest_name = 0;
1145  TagList::const_iterator i = tags.begin();
1146  const TagList::const_iterator iend = tags.end();
1147  for (; i != iend; ++i) {
1148  longest_name = std::max(i->name.size(), longest_name);
1149  }
1150 
1151  i = tags.begin();
1152  for (; i != iend; ++i) {
1153  LogCvmfs(kLogCvmfs, kLogStdout, "removed tag %s (%s)",
1154  AddPadding(i->name, longest_name).c_str(),
1155  i->root_hash.ToString().c_str());
1156  }
1157 }
1158 
1159 //------------------------------------------------------------------------------
1160 
1161 ParameterList CommandEmptyRecycleBin::GetParams() const {
1162  ParameterList r;
1164  return r;
1165 }
1166 
1167 int CommandEmptyRecycleBin::Main(const ArgumentList &args) {
1168  // initialize the Environment (taking ownership)
1169  const bool history_read_write = true;
1170  UniquePtr<Environment> env(InitializeEnvironment(args, history_read_write));
1171  if (!env.IsValid()) {
1172  LogCvmfs(kLogCvmfs, kLogStderr, "failed to init environment");
1173  return 1;
1174  }
1175 
1176  if (!env->history->EmptyRecycleBin()) {
1177  LogCvmfs(kLogCvmfs, kLogStderr, "failed to empty recycle bin");
1178  return 1;
1179  }
1180 
1181  // finalize the history and upload it
1182  if (!CloseAndPublishHistory(env.weak_ref())) {
1183  return 1;
1184  }
1185 
1186  return 0;
1187 }
int return_code
the return value of the spooler operation
bool IsNull() const
Definition: hash.h:383
const manifest::Manifest * manifest() const
Definition: repository.h:125
static SqliteHistory * Open(const std::string &file_name)
std::string name
Definition: history.h:88
std::vector< Parameter > ParameterList
Definition: swissknife.h:71
UniquePtr< manifest::Manifest > manifest
std::string ToString(const bool with_suffix=false) const
Definition: hash.h:249
const history::History * history() const
std::string ToStringWithSuffix() const
Definition: hash.h:304
std::vector< BranchLevel > BranchHierarchy
std::string CreateTempPath(const std::string &path_prefix, const int mode)
Definition: posix.cc:1034
static void InsertCommonParameters(ParameterList *r)
assert((mem||(size==0))&&"Out Of Memory")
string StringifyTime(const time_t seconds, const bool utc)
Definition: string.cc:105
const std::string & fqrn() const
Definition: history.h:193
string StringifyDouble(const double value)
Definition: string.cc:96
void Set(const T &object)
Definition: future.h:53
std::vector< history::History::Tag > TagList
T & Get()
Definition: future.h:66
Algorithms
Definition: hash.h:41
static SqliteHistory * OpenWritable(const std::string &file_name)
static WritableCatalog * AttachFreely(const std::string &root_path, const std::string &file, const shash::Any &catalog_hash, Catalog *parent=NULL, const bool is_not_root=false)
Definition: catalog_rw.cc:49
std::vector< history::History::Branch > BranchList
std::string description
Definition: history.h:93
bool FileExists(const std::string &path)
Definition: posix.cc:791
uint64_t GetRevision() const
Definition: catalog.cc:528
uint64_t GetLastModified() const
Definition: catalog.cc:533
const char * Code2Ascii(const Failures error)
vector< string > SplitString(const string &str, char delim)
Definition: string.cc:290
static const float kSchemaEpsilon
Definition: sql.h:105
std::string repository_name() const
Definition: manifest.h:130
const char kSuffixCatalog
Definition: hash.h:54
string StringifyInt(const int64_t value)
Definition: string.cc:78
bool HasPrefix(const string &str, const string &prefix, const bool ignore_case)
Definition: string.cc:267
PathString root_prefix() const
Definition: catalog.h:185
bool IsEmpty() const
Definition: shortstring.h:137
std::map< char, SharedPtr< std::string > > ArgumentList
Definition: swissknife.h:72
Algorithms ParseHashAlgorithm(const string &algorithm_option)
Definition: hash.cc:72
shash::Any history() const
Definition: manifest.h:135
shash::Any root_hash
Definition: history.h:89
static Catalog * AttachFreely(const std::string &imaginary_mountpoint, const std::string &file, const shash::Any &catalog_hash, Catalog *parent=NULL, const bool is_nested=false)
Definition: catalog.cc:29
std::string branch
Definition: history.h:97
static SqliteHistory * Create(const std::string &file_name, const std::string &fqrn)
UniquePtr< history::History > history
Any MkFromHexPtr(const HexPtr hex, const char suffix)
Definition: hash.cc:83
int64_t GetFileSize(const std::string &path)
Definition: posix.cc:801
const int kLogVerboseMsg
static const float kLatestSupportedSchema
Definition: catalog_sql.h:44
Suffix suffix
Definition: hash.h:126
std::string MakePath() const
Definition: hash.h:316
std::string MakeCanonicalPath(const std::string &path)
Definition: posix.cc:98
uint64_t revision
Definition: history.h:91
void PrintError(const string &message)
Definition: logging.cc:543
UniquePtr< upload::Spooler > spooler
CVMFS_EXPORT void LogCvmfs(const LogSource source, const int mask, const char *format,...)
Definition: logging.cc:528