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