CernVM-FS  2.13.0
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
swissknife_check.cc
Go to the documentation of this file.
1 
7 #define __STDC_FORMAT_MACROS
8 
9 
10 #include "swissknife_check.h"
11 
12 #include <inttypes.h>
13 #include <unistd.h>
14 
15 #include <cassert>
16 #include <map>
17 #include <queue>
18 #include <set>
19 #include <string>
20 #include <vector>
21 
22 #include "catalog_sql.h"
24 #include "file_chunk.h"
25 #include "history_sqlite.h"
26 #include "manifest.h"
27 #include "network/download.h"
28 #include "reflog.h"
29 #include "sanitizer.h"
30 #include "shortstring.h"
31 #include "util/exception.h"
32 #include "util/logging.h"
33 #include "util/pointer.h"
34 #include "util/posix.h"
35 
36 using namespace std; // NOLINT
37 
38 // for map of duplicate entries; as in kvstore.cc
39 static inline uint32_t hasher_any(const shash::Any &key) {
40  // We'll just do the same thing as hasher_md5, since every hash is at
41  // least as large.
42  return *const_cast<uint32_t *>(reinterpret_cast<const uint32_t *>(key.digest)
43  + 1);
44 }
45 
46 
47 namespace swissknife {
48 
49 CommandCheck::CommandCheck()
50  : check_chunks_(false), no_duplicates_map_(false), is_remote_(false) {
51  const shash::Any hash_null;
52  duplicates_map_.Init(16, hash_null, hasher_any);
53 }
54 
56  const catalog::DirectoryEntry &b,
57  const bool compare_names,
58  const bool is_transition_point) {
59  typedef catalog::DirectoryEntry::Difference Difference;
60 
62  if (diffs == Difference::kIdentical) {
63  return true;
64  }
65 
66  // in case of a nested catalog transition point the controlling flags are
67  // supposed to differ. If this is the only difference we are done...
68  if (is_transition_point
69  && (diffs ^ Difference::kNestedCatalogTransitionFlags) == 0) {
70  return true;
71  }
72 
73  bool retval = true;
74  if (compare_names) {
75  if (diffs & Difference::kName) {
76  LogCvmfs(kLogCvmfs, kLogStderr, "names differ: %s / %s", a.name().c_str(),
77  b.name().c_str());
78  retval = false;
79  }
80  }
81  if (diffs & Difference::kLinkcount) {
82  LogCvmfs(kLogCvmfs, kLogStderr, "linkcounts differ: %u / %u", a.linkcount(),
83  b.linkcount());
84  retval = false;
85  }
86  if (diffs & Difference::kHardlinkGroup) {
87  LogCvmfs(kLogCvmfs, kLogStderr, "hardlink groups differ: %u / %u",
89  retval = false;
90  }
91  if (diffs & Difference::kSize) {
92  LogCvmfs(kLogCvmfs, kLogStderr, "sizes differ: %" PRIu64 " / %" PRIu64,
93  a.size(), b.size());
94  retval = false;
95  }
96  if (diffs & Difference::kMode) {
97  LogCvmfs(kLogCvmfs, kLogStderr, "modes differ: %u / %u", a.mode(),
98  b.mode());
99  retval = false;
100  }
101  if (diffs & Difference::kMtime) {
102  LogCvmfs(kLogCvmfs, kLogStderr, "timestamps differ: %lu / %lu", a.mtime(),
103  b.mtime());
104  retval = false;
105  }
106  if (diffs & Difference::kChecksum) {
107  LogCvmfs(kLogCvmfs, kLogStderr, "content hashes differ: %s / %s",
108  a.checksum().ToString().c_str(), b.checksum().ToString().c_str());
109  retval = false;
110  }
111  if (diffs & Difference::kSymlink) {
112  LogCvmfs(kLogCvmfs, kLogStderr, "symlinks differ: %s / %s",
113  a.symlink().c_str(), b.symlink().c_str());
114  retval = false;
115  }
116  if (diffs & Difference::kExternalFileFlag) {
118  "external file flag differs: %d / %d "
119  "(%s / %s)",
120  a.IsExternalFile(), b.IsExternalFile(), a.name().c_str(),
121  b.name().c_str());
122  retval = false;
123  }
124  if (diffs & Difference::kHasXattrsFlag) {
126  "extended attributes differ: %d / %d "
127  "(%s / %s)",
128  a.HasXattrs(), b.HasXattrs(), a.name().c_str(), b.name().c_str());
129  retval = false;
130  }
131  if (!is_transition_point) {
132  if (diffs & Difference::kUid) {
133  LogCvmfs(kLogCvmfs, kLogStderr, "uids differ: %d / %d (%s / %s)", a.uid(),
134  b.uid(), a.name().c_str(), b.name().c_str());
135  retval = false;
136  }
137  if (diffs & Difference::kGid) {
138  LogCvmfs(kLogCvmfs, kLogStderr, "gids differ: %d / %d (%s / %s)", a.gid(),
139  b.gid(), a.name().c_str(), b.name().c_str());
140  retval = false;
141  }
142  }
143 
144  return retval;
145 }
146 
147 
149  const catalog::Counters &b) {
150  const catalog::Counters::FieldsMap map_a = a.GetFieldsMap();
151  const catalog::Counters::FieldsMap map_b = b.GetFieldsMap();
152 
153  bool retval = true;
154  catalog::Counters::FieldsMap::const_iterator i = map_a.begin();
155  catalog::Counters::FieldsMap::const_iterator iend = map_a.end();
156  for (; i != iend; ++i) {
157  catalog::Counters::FieldsMap::const_iterator comp = map_b.find(i->first);
158  assert(comp != map_b.end());
159 
160  if (*(i->second) != *(comp->second)) {
162  "catalog statistics mismatch: %s (expected: %" PRIu64 " / "
163  "in catalog: %" PRIu64 ")",
164  comp->first.c_str(), *(i->second), *(comp->second));
165  retval = false;
166  }
167  }
168 
169  return retval;
170 }
171 
172 
176 bool CommandCheck::Exists(const string &file) {
177  if (!is_remote_) {
178  return FileExists(file) || SymlinkExists(file);
179  } else {
180  const string url = repo_base_path_ + "/" + file;
181  LogCvmfs(kLogCvmfs, kLogVerboseMsg, "[Exists::url] %s", url.c_str());
182  download::JobInfo head(&url, false);
183  return download_manager()->Fetch(&head) == download::kFailOk;
184  }
185 }
186 
187 
191 string CommandCheck::FetchPath(const string &path) {
192  string tmp_path;
193  FILE *f = CreateTempFile(temp_directory_ + "/cvmfstmp", kDefaultFileMode,
194  "w+", &tmp_path);
195  assert(f != NULL);
196 
197  const string url = repo_base_path_ + "/" + path;
198  if (is_remote_) {
199  cvmfs::FileSink filesink(f);
200  download::JobInfo download_job(&url, false, false, NULL, &filesink);
201  download::Failures retval = download_manager()->Fetch(&download_job);
202  if (retval != download::kFailOk) {
203  PANIC(kLogStderr, "failed to read %s", url.c_str());
204  }
205  } else {
206  bool retval = CopyPath2File(url, f);
207  if (!retval) {
208  PANIC(kLogStderr, "failed to read %s", url.c_str());
209  }
210  }
211 
212  fclose(f);
213  return tmp_path;
214 }
215 
216 
221 bool CommandCheck::InspectReflog(const shash::Any &reflog_hash,
223  LogCvmfs(kLogCvmfs, kLogStdout, "Inspecting log of references");
224  string reflog_path = FetchPath(".cvmfsreflog");
225  shash::Any computed_hash(reflog_hash.algorithm);
226  manifest::Reflog::HashDatabase(reflog_path, &computed_hash);
227  if (computed_hash != reflog_hash) {
229  "The .cvmfsreflog has unexpected content hash %s (expected %s)",
230  computed_hash.ToString().c_str(), reflog_hash.ToString().c_str());
231  unlink(reflog_path.c_str());
232  return false;
233  }
234 
236  assert(reflog.IsValid());
237  reflog->TakeDatabaseFileOwnership();
238 
239  if (!reflog->ContainsCatalog(manifest->catalog_hash())) {
241  "failed to find catalog root hash %s in .cvmfsreflog",
242  manifest->catalog_hash().ToString().c_str());
243  return false;
244  }
245 
246  if (!reflog->ContainsCertificate(manifest->certificate())) {
248  "failed to find certificate hash %s in .cvmfsreflog",
249  manifest->certificate().ToString().c_str());
250  return false;
251  }
252 
253  if (!manifest->history().IsNull()
254  && !reflog->ContainsHistory(manifest->history())) {
256  "failed to find tag database's hash %s in .cvmfsreflog",
257  manifest->history().ToString().c_str());
258  return false;
259  }
260 
261  if (!manifest->meta_info().IsNull()
262  && !reflog->ContainsMetainfo(manifest->meta_info())) {
264  "failed to find meta info hash %s in .cvmfsreflog",
265  manifest->meta_info().ToString().c_str());
266  return false;
267  }
268 
269  return true;
270 }
271 
272 
277  LogCvmfs(kLogCvmfs, kLogStdout, "Inspecting tag database");
278  bool retval;
279  vector<history::History::Tag> tags;
280  retval = history->List(&tags);
281  if (!retval) {
282  LogCvmfs(kLogCvmfs, kLogStderr, "failed to enumerate tags");
283  return false;
284  }
285  vector<history::History::Branch> branches;
286  retval = history->ListBranches(&branches);
287  if (!retval) {
288  LogCvmfs(kLogCvmfs, kLogStderr, "failed to enumerate branches");
289  return false;
290  }
291 
292  bool result = true;
293 
294  map<string, uint64_t> initial_revisions;
295  sanitizer::BranchSanitizer sanitizer;
296  for (unsigned i = 0; i < branches.size(); ++i) {
297  if (!sanitizer.IsValid(branches[i].branch)) {
298  LogCvmfs(kLogCvmfs, kLogStderr, "invalid branch name: %s",
299  branches[i].branch.c_str());
300  result = false;
301  }
302  initial_revisions[branches[i].branch] = branches[i].initial_revision;
303  }
304 
305  set<string> used_branches; // all branches referenced in tag db
306  // TODO(jblomer): same root hash implies same size and revision
307  for (unsigned i = 0; i < tags.size(); ++i) {
308  used_branches.insert(tags[i].branch);
309  const map<string, uint64_t>::const_iterator iter = initial_revisions.find(
310  tags[i].branch);
311  if (iter == initial_revisions.end()) {
312  LogCvmfs(kLogCvmfs, kLogStderr, "invalid branch %s in tag %s",
313  tags[i].branch.c_str(), tags[i].name.c_str());
314  result = false;
315  } else {
316  if (tags[i].revision < iter->second) {
318  "invalid revision %" PRIu64 " of tag %s", tags[i].revision,
319  tags[i].name.c_str());
320  result = false;
321  }
322  }
323  }
324 
325  if (used_branches.size() != branches.size()) {
326  LogCvmfs(kLogCvmfs, kLogStderr, "unused, dangling branches stored");
327  result = false;
328  }
329 
330  return result;
331 }
332 
333 
342  const PathString &path,
343  catalog::DeltaCounters *computed_counters,
344  set<PathString> *bind_mountpoints) {
346  catalog::DirectoryEntry this_directory;
347 
348  if (!catalog->LookupPath(path, &this_directory)) {
349  LogCvmfs(kLogCvmfs, kLogStderr, "failed to lookup %s", path.c_str());
350  return false;
351  }
352  if (!catalog->ListingPath(path, &entries)) {
353  LogCvmfs(kLogCvmfs, kLogStderr, "failed to list %s", path.c_str());
354  return false;
355  }
356 
357  uint32_t num_subdirs = 0;
358  bool retval = true;
359  typedef map<uint32_t, vector<catalog::DirectoryEntry> > HardlinkMap;
360  HardlinkMap hardlinks;
361  bool found_nested_marker = false;
362 
363  for (unsigned i = 0; i < entries.size(); ++i) {
364  // for performance reasons, keep track of files already checked
365  // and only run requests once per hash
366  const bool entry_needs_check = !entries[i].checksum().IsNull()
367  && !entries[i].IsExternalFile() &&
368  // fallback cli option can force the entry to
369  // be checked
372  entries[i].checksum()));
373  if (entry_needs_check && !no_duplicates_map_)
374  duplicates_map_.Insert(entries[i].checksum(), 1);
375 
376  PathString full_path(path);
377  full_path.Append("/", 1);
378  full_path.Append(entries[i].name().GetChars(),
379  entries[i].name().GetLength());
380  LogCvmfs(kLogCvmfs, kLogVerboseMsg, "[path] %s [needs check] %i",
381  full_path.c_str(), entry_needs_check);
382 
383 
384  // Name must not be empty
385  if (entries[i].name().IsEmpty()) {
386  LogCvmfs(kLogCvmfs, kLogStderr, "empty path at %s", full_path.c_str());
387  retval = false;
388  }
389 
390  // Catalog markers should indicate nested catalogs
391  if (entries[i].name() == NameString(string(".cvmfscatalog"))) {
392  if (catalog->mountpoint() != path) {
394  "found abandoned nested catalog marker at %s",
395  full_path.c_str());
396  retval = false;
397  }
398  found_nested_marker = true;
399  }
400 
401  // Check if checksum is not null
402  if (entries[i].IsRegular() && !entries[i].IsChunkedFile()
403  && entries[i].checksum().IsNull()) {
405  "regular file pointing to zero-hash: '%s'", full_path.c_str());
406  retval = false;
407  }
408 
409  // Check if the chunk is there
410  if (check_chunks_ && entry_needs_check) {
411  string chunk_path = "data/" + entries[i].checksum().MakePath();
412  if (entries[i].IsDirectory())
413  chunk_path += shash::kSuffixMicroCatalog;
414  if (!Exists(chunk_path)) {
415  LogCvmfs(kLogCvmfs, kLogStderr, "data chunk %s (%s) missing",
416  entries[i].checksum().ToString().c_str(), full_path.c_str());
417  retval = false;
418  }
419  }
420 
421  // Add hardlinks to counting map
422  if ((entries[i].linkcount() > 1) && !entries[i].IsDirectory()) {
423  if (entries[i].hardlink_group() == 0) {
424  LogCvmfs(kLogCvmfs, kLogStderr, "invalid hardlink group for %s",
425  full_path.c_str());
426  retval = false;
427  } else {
428  HardlinkMap::iterator hardlink_group = hardlinks.find(
429  entries[i].hardlink_group());
430  if (hardlink_group == hardlinks.end()) {
431  hardlinks[entries[i].hardlink_group()];
432  hardlinks[entries[i].hardlink_group()].push_back(entries[i]);
433  } else {
434  if (!CompareEntries(entries[i], (hardlink_group->second)[0], false)) {
435  LogCvmfs(kLogCvmfs, kLogStderr, "hardlink %s doesn't match",
436  full_path.c_str());
437  retval = false;
438  }
439  hardlink_group->second.push_back(entries[i]);
440  } // Hardlink added to map
441  } // Hardlink group > 0
442  } // Hardlink found
443 
444  // For any kind of entry, the linkcount should be > 0
445  if (entries[i].linkcount() == 0) {
446  LogCvmfs(kLogCvmfs, kLogStderr, "Entry %s has linkcount 0.",
447  entries[i].name().c_str());
448  retval = false;
449  }
450 
451  // Checks depending of entry type
452  if (!entries[i].IsRegular()) {
453  if (entries[i].IsDirectIo()) {
454  LogCvmfs(kLogCvmfs, kLogStderr, "invalid direct i/o flag found: %s",
455  full_path.c_str());
456  retval = false;
457  }
458  }
459  if (entries[i].IsDirectory()) {
460  computed_counters->self.directories++;
461  num_subdirs++;
462  // Directory size
463  // if (entries[i].size() < 4096) {
464  // LogCvmfs(kLogCvmfs, kLogStderr, "invalid file size for %s",
465  // full_path.c_str());
466  // retval = false;
467  // }
468  // No directory hardlinks
469  if (entries[i].hardlink_group() != 0) {
470  LogCvmfs(kLogCvmfs, kLogStderr, "directory hardlink found at %s",
471  full_path.c_str());
472  retval = false;
473  }
474  if (entries[i].IsNestedCatalogMountpoint()
475  || entries[i].IsBindMountpoint()) {
476  // Find transition point
477  if (entries[i].IsNestedCatalogMountpoint())
478  computed_counters->self.nested_catalogs++;
479  shash::Any tmp;
480  uint64_t tmp2;
481  PathString mountpoint(full_path);
482  if (!catalog->FindNested(mountpoint, &tmp, &tmp2)) {
483  LogCvmfs(kLogCvmfs, kLogStderr, "nested catalog at %s not registered",
484  full_path.c_str());
485  retval = false;
486  }
487 
488  // check that the nested mountpoint is empty in the current catalog
489  catalog::DirectoryEntryList nested_entries;
490  if (catalog->ListingPath(full_path, &nested_entries)
491  && !nested_entries.empty()) {
493  "non-empty nested catalog mountpoint "
494  "at %s.",
495  full_path.c_str());
496  retval = false;
497  }
498 
499  if (entries[i].IsBindMountpoint()) {
500  bind_mountpoints->insert(full_path);
501  if (entries[i].IsNestedCatalogMountpoint()) {
503  "bind mountpoint and nested mountpoint mutually exclusive"
504  " at %s.",
505  full_path.c_str());
506  retval = false;
507  }
508  }
509  } else {
510  // Recurse
511  if (!Find(catalog, full_path, computed_counters, bind_mountpoints))
512  retval = false;
513  }
514  } else if (entries[i].IsLink()) {
515  computed_counters->self.symlinks++;
516  // No hash for symbolics links
517  if (!entries[i].checksum().IsNull()) {
518  LogCvmfs(kLogCvmfs, kLogStderr, "symbolic links with hash at %s",
519  full_path.c_str());
520  retval = false;
521  }
522  // Right size of symbolic link?
523  if (entries[i].size() != entries[i].symlink().GetLength()) {
525  "wrong symbolic link size for %s; "
526  "expected %u, got %lu",
527  full_path.c_str(), entries[i].symlink().GetLength(),
528  entries[i].size());
529  retval = false;
530  }
531  } else if (entries[i].IsRegular()) {
532  computed_counters->self.regular_files++;
533  computed_counters->self.file_size += entries[i].size();
534  } else if (entries[i].IsSpecial()) {
535  computed_counters->self.specials++;
536  // Size zero for special files
537  if (entries[i].size() != 0) {
539  "unexpected non-zero special file size %s", full_path.c_str());
540  retval = false;
541  }
542  // No hash for special files
543  if (!entries[i].checksum().IsNull()) {
544  LogCvmfs(kLogCvmfs, kLogStderr, "special file with hash at %s",
545  full_path.c_str());
546  retval = false;
547  }
548  // No symlink
549  if (entries[i].symlink().GetLength() > 0) {
551  "special file with non-zero symlink at %s", full_path.c_str());
552  retval = false;
553  }
554  } else {
555  LogCvmfs(kLogCvmfs, kLogStderr, "unknown file type %s",
556  full_path.c_str());
557  retval = false;
558  }
559 
560  if (entries[i].HasXattrs()) {
561  computed_counters->self.xattrs++;
562  }
563 
564  if (entries[i].IsExternalFile()) {
565  computed_counters->self.externals++;
566  computed_counters->self.external_file_size += entries[i].size();
567  if (!entries[i].IsRegular()) {
569  "only regular files can be external: %s", full_path.c_str());
570  retval = false;
571  }
572  }
573 
574  // checking file chunk integrity
575  if (entries[i].IsChunkedFile()) {
576  FileChunkList chunks;
577  catalog->ListPathChunks(full_path, entries[i].hash_algorithm(), &chunks);
578 
579  computed_counters->self.chunked_files++;
580  computed_counters->self.chunked_file_size += entries[i].size();
581  computed_counters->self.file_chunks += chunks.size();
582 
583  // do we find file chunks for the chunked file in this catalog?
584  if (chunks.size() == 0) {
585  LogCvmfs(kLogCvmfs, kLogStderr, "no file chunks found for big file %s",
586  full_path.c_str());
587  retval = false;
588  }
589 
590  size_t aggregated_file_size = 0;
591  off_t next_offset = 0;
592 
593  for (unsigned j = 0; j < chunks.size(); ++j) {
594  FileChunk this_chunk = chunks.At(j);
595  // check if the chunk boundaries fit together...
596  if (next_offset != this_chunk.offset()) {
597  LogCvmfs(kLogCvmfs, kLogStderr, "misaligned chunk offsets for %s",
598  full_path.c_str());
599  retval = false;
600  }
601  next_offset = this_chunk.offset() + this_chunk.size();
602  aggregated_file_size += this_chunk.size();
603 
604  // are all data chunks in the data store?
605  if (check_chunks_ && !entries[i].IsExternalFile()) {
606  const shash::Any &chunk_hash = this_chunk.content_hash();
607  // for performance reasons, only perform the check once
608  // and skip if the hash has been checked before
609  bool chunk_needs_check = true;
610  if (!no_duplicates_map_ && !duplicates_map_.Contains(chunk_hash)) {
611  duplicates_map_.Insert(chunk_hash, 1);
612  } else if (!no_duplicates_map_) {
613  chunk_needs_check = false;
614  }
615  if (chunk_needs_check) {
616  const string chunk_path = "data/" + chunk_hash.MakePath();
617  if (!Exists(chunk_path)) {
619  "partial data chunk %s (%s -> "
620  "offset: %ld | size: %lu) missing",
621  this_chunk.content_hash().ToStringWithSuffix().c_str(),
622  full_path.c_str(), this_chunk.offset(),
623  this_chunk.size());
624  retval = false;
625  }
626  }
627  }
628  }
629 
630  // is the aggregated chunk size equal to the actual file size?
631  if (aggregated_file_size != entries[i].size()) {
633  "chunks of file %s produce a size "
634  "mismatch. Calculated %zu bytes | %lu "
635  "bytes expected",
636  full_path.c_str(), aggregated_file_size, entries[i].size());
637  retval = false;
638  }
639  }
640  } // Loop through entries
641 
642  // Check if nested catalog marker has been found
643  if (!path.IsEmpty() && (path == catalog->mountpoint())
644  && !found_nested_marker) {
645  LogCvmfs(kLogCvmfs, kLogStderr, "nested catalog without marker at %s",
646  path.c_str());
647  retval = false;
648  }
649 
650  // Check directory linkcount
651  if (this_directory.linkcount() != num_subdirs + 2) {
653  "wrong linkcount for %s; "
654  "expected %u, got %u",
655  path.c_str(), num_subdirs + 2, this_directory.linkcount());
656  retval = false;
657  }
658 
659  // Check hardlink linkcounts
660  for (HardlinkMap::const_iterator i = hardlinks.begin(),
661  iEnd = hardlinks.end();
662  i != iEnd;
663  ++i) {
664  if (i->second[0].linkcount() != i->second.size()) {
666  "hardlink linkcount wrong for %s, "
667  "expected %lu, got %u",
668  (path.ToString() + "/" + i->second[0].name().ToString()).c_str(),
669  i->second.size(), i->second[0].linkcount());
670  retval = false;
671  }
672  }
673 
674  return retval;
675 }
676 
677 
678 string CommandCheck::DownloadPiece(const shash::Any catalog_hash) {
679  string source = "data/" + catalog_hash.MakePath();
680  const string dest = temp_directory_ + "/" + catalog_hash.ToString();
681  const string url = repo_base_path_ + "/" + source;
682 
683  cvmfs::PathSink pathsink(dest);
684  download::JobInfo download_catalog(&url, true, false, &catalog_hash,
685  &pathsink);
686  download::Failures retval = download_manager()->Fetch(&download_catalog);
687  if (retval != download::kFailOk) {
688  LogCvmfs(kLogCvmfs, kLogStderr, "failed to download object %s (%d)",
689  catalog_hash.ToString().c_str(), retval);
690  return "";
691  }
692 
693  return dest;
694 }
695 
696 
697 string CommandCheck::DecompressPiece(const shash::Any catalog_hash) {
698  string source = "data/" + catalog_hash.MakePath();
699  const string dest = temp_directory_ + "/" + catalog_hash.ToString();
700  if (!zlib::DecompressPath2Path(source, dest))
701  return "";
702 
703  return dest;
704 }
705 
706 
708  const shash::Any &catalog_hash,
709  const uint64_t catalog_size) {
710  string tmp_file;
711  if (!is_remote_)
712  tmp_file = DecompressPiece(catalog_hash);
713  else
714  tmp_file = DownloadPiece(catalog_hash);
715 
716  if (tmp_file == "") {
717  LogCvmfs(kLogCvmfs, kLogStderr, "failed to load catalog %s",
718  catalog_hash.ToString().c_str());
719  return NULL;
720  }
721 
722  catalog::Catalog *catalog = catalog::Catalog::AttachFreely(path, tmp_file,
723  catalog_hash);
724  int64_t catalog_file_size = GetFileSize(tmp_file);
725  if (catalog_file_size <= 0) {
726  LogCvmfs(kLogCvmfs, kLogStderr, "Error downloading catalog %s at %s %s",
727  catalog_hash.ToString().c_str(), path.c_str(), tmp_file.c_str());
728  assert(catalog_file_size > 0);
729  }
730  unlink(tmp_file.c_str());
731 
732  if ((catalog_size > 0) && (uint64_t(catalog_file_size) != catalog_size)) {
734  "catalog file size mismatch, "
735  "expected %" PRIu64 ", got %" PRIu64,
736  catalog_size, catalog_file_size);
737  delete catalog;
738  return NULL;
739  }
740 
741  return catalog;
742 }
743 
744 
745 bool CommandCheck::FindSubtreeRootCatalog(const string &subtree_path,
746  shash::Any *root_hash,
747  uint64_t *root_size) {
748  catalog::Catalog *current_catalog = FetchCatalog("", *root_hash);
749  if (current_catalog == NULL) {
750  return false;
751  }
752 
753  typedef vector<string> Tokens;
754  const Tokens path_tokens = SplitString(subtree_path, '/');
755 
756  string current_path = "";
757 
758  Tokens::const_iterator i = path_tokens.begin();
759  Tokens::const_iterator iend = path_tokens.end();
760  for (; i != iend; ++i) {
761  if (i->empty()) {
762  continue;
763  }
764 
765  current_path += "/" + *i;
766  if (current_catalog->FindNested(
767  PathString(current_path), root_hash, root_size)) {
768  delete current_catalog;
769 
770  if (current_path.length() < subtree_path.length()) {
771  current_catalog = FetchCatalog(current_path, *root_hash);
772  if (current_catalog == NULL) {
773  break;
774  }
775  } else {
776  return true;
777  }
778  }
779  }
780  return false;
781 }
782 
783 
787 bool CommandCheck::InspectTree(const string &path,
788  const shash::Any &catalog_hash,
789  const uint64_t catalog_size,
790  const bool is_nested_catalog,
791  const catalog::DirectoryEntry *transition_point,
792  catalog::DeltaCounters *computed_counters) {
793  LogCvmfs(kLogCvmfs, kLogStdout | kLogInform, "[inspecting catalog] %s at %s",
794  catalog_hash.ToString().c_str(), path == "" ? "/" : path.c_str());
795 
796  const catalog::Catalog *catalog = FetchCatalog(
797  path, catalog_hash, catalog_size);
798  if (catalog == NULL) {
799  LogCvmfs(kLogCvmfs, kLogStderr, "failed to open catalog %s",
800  catalog_hash.ToString().c_str());
801  return false;
802  }
803 
804  int retval = true;
805 
806  if (catalog->root_prefix() != PathString(path.data(), path.length())) {
808  "root prefix mismatch; "
809  "expected %s, got %s",
810  path.c_str(), catalog->root_prefix().c_str());
811  retval = false;
812  }
813 
814  // Check transition point
815  catalog::DirectoryEntry root_entry;
816  if (!catalog->LookupPath(catalog->root_prefix(), &root_entry)) {
817  LogCvmfs(kLogCvmfs, kLogStderr, "failed to lookup root entry (%s)",
818  path.c_str());
819  retval = false;
820  }
821  if (!root_entry.IsDirectory()) {
822  LogCvmfs(kLogCvmfs, kLogStderr, "root entry not a directory (%s)",
823  path.c_str());
824  retval = false;
825  }
826  if (is_nested_catalog) {
827  if (transition_point != NULL
828  && !CompareEntries(*transition_point, root_entry, true, true)) {
830  "transition point and root entry differ (%s)", path.c_str());
831  retval = false;
832  }
833  if (!root_entry.IsNestedCatalogRoot()) {
835  "nested catalog root expected but not found (%s)", path.c_str());
836  retval = false;
837  }
838  } else {
839  if (root_entry.IsNestedCatalogRoot()) {
841  "nested catalog root found but not expected (%s)", path.c_str());
842  retval = false;
843  }
844  }
845 
846  // Traverse the catalog
847  set<PathString> bind_mountpoints;
848  if (!Find(catalog, PathString(path.data(), path.length()), computed_counters,
849  &bind_mountpoints)) {
850  retval = false;
851  }
852 
853  // Check number of entries
854  if (root_entry.HasXattrs())
855  computed_counters->self.xattrs++;
856  const uint64_t num_found_entries = 1 + computed_counters->self.regular_files
857  + computed_counters->self.symlinks
858  + computed_counters->self.specials
859  + computed_counters->self.directories;
860  if (num_found_entries != catalog->GetNumEntries()) {
862  "dangling entries in catalog, "
863  "expected %" PRIu64 ", got %" PRIu64,
864  catalog->GetNumEntries(), num_found_entries);
865  retval = false;
866  }
867 
868  // Recurse into nested catalogs
870  &nested_catalogs = catalog->ListNestedCatalogs();
872  own_nested_catalogs = catalog->ListOwnNestedCatalogs();
873  if (own_nested_catalogs.size()
874  != static_cast<uint64_t>(computed_counters->self.nested_catalogs)) {
876  "number of nested catalogs does not match;"
877  " expected %lu, got %lu",
878  computed_counters->self.nested_catalogs,
879  own_nested_catalogs.size());
880  retval = false;
881  }
882  set<PathString> nested_catalog_paths;
883  for (catalog::Catalog::NestedCatalogList::const_iterator
884  i = nested_catalogs.begin(),
885  iEnd = nested_catalogs.end();
886  i != iEnd; ++i) {
887  nested_catalog_paths.insert(i->mountpoint);
888  }
889  if (nested_catalog_paths.size() != nested_catalogs.size()) {
891  "duplicates among nested catalogs and bind mountpoints");
892  retval = false;
893  }
894 
895  for (catalog::Catalog::NestedCatalogList::const_iterator
896  i = nested_catalogs.begin(),
897  iEnd = nested_catalogs.end();
898  i != iEnd; ++i) {
899  if (bind_mountpoints.find(i->mountpoint) != bind_mountpoints.end()) {
900  catalog::DirectoryEntry bind_mountpoint;
901  PathString mountpoint("/" + i->mountpoint.ToString().substr(1));
902  if (!catalog->LookupPath(mountpoint, &bind_mountpoint)) {
903  LogCvmfs(kLogCvmfs, kLogStderr, "failed to lookup bind mountpoint %s",
904  mountpoint.c_str());
905  retval = false;
906  }
907  LogCvmfs(kLogCvmfs, kLogDebug, "skipping bind mountpoint %s",
908  i->mountpoint.c_str());
909  continue;
910  }
911  catalog::DirectoryEntry nested_transition_point;
912  if (!catalog->LookupPath(i->mountpoint, &nested_transition_point)) {
913  LogCvmfs(kLogCvmfs, kLogStderr, "failed to lookup transition point %s",
914  i->mountpoint.c_str());
915  retval = false;
916  } else {
917  catalog::DeltaCounters nested_counters;
918  const bool is_nested = true;
919  if (!InspectTree(i->mountpoint.ToString(), i->hash, i->size, is_nested,
920  &nested_transition_point, &nested_counters))
921  retval = false;
922  nested_counters.PopulateToParent(computed_counters);
923  }
924  }
925 
926  // Check statistics counters
927  // Additionally account for root directory
928  computed_counters->self.directories++;
929  catalog::Counters compare_counters;
930  compare_counters.ApplyDelta(*computed_counters);
931  const catalog::Counters stored_counters = catalog->GetCounters();
932  if (!CompareCounters(compare_counters, stored_counters)) {
933  LogCvmfs(kLogCvmfs, kLogStderr, "statistics counter mismatch [%s]",
934  catalog_hash.ToString().c_str());
935  retval = false;
936  }
937 
938  delete catalog;
939  return retval;
940 }
941 
942 
944  string tag_name;
945  string subtree_path = "";
946  string pubkey_path = "";
947  string repo_name = "";
948  string reflog_chksum_path = "";
949 
950  temp_directory_ = (args.find('t') != args.end()) ? *args.find('t')->second
951  : "/tmp";
952  if (args.find('n') != args.end())
953  tag_name = *args.find('n')->second;
954  if (args.find('c') != args.end())
955  check_chunks_ = true;
956  if (args.find('d') != args.end())
957  no_duplicates_map_ = true;
958  if (args.find('l') != args.end()) {
959  unsigned log_level = kLogLevel0 << String2Uint64(*args.find('l')->second);
960  if (log_level > kLogNone) {
961  LogCvmfs(kLogCvmfs, kLogStderr, "invalid log level");
962  return 1;
963  }
964  SetLogVerbosity(static_cast<LogLevels>(log_level));
965  }
966  if (args.find('k') != args.end())
967  pubkey_path = *args.find('k')->second;
968  if (DirectoryExists(pubkey_path))
969  pubkey_path = JoinStrings(FindFilesBySuffix(pubkey_path, ".pub"), ":");
970  if (args.find('N') != args.end())
971  repo_name = *args.find('N')->second;
972 
973  repo_base_path_ = MakeCanonicalPath(*args.find('r')->second);
974  if (args.find('s') != args.end())
975  subtree_path = MakeCanonicalPath(*args.find('s')->second);
976  if (args.find('R') != args.end())
977  reflog_chksum_path = *args.find('R')->second;
978 
979  // Repository can be HTTP address or on local file system
981 
982  // initialize the (swissknife global) download and signature managers
983  if (is_remote_) {
984  const bool follow_redirects = (args.count('L') > 0);
985  const string proxy = (args.count('@') > 0) ? *args.find('@')->second : "";
986  if (!this->InitDownloadManager(follow_redirects, proxy)) {
987  return 1;
988  }
989 
990  if (pubkey_path.empty() || repo_name.empty()) {
992  "please provide pubkey and repo name for "
993  "remote repositories");
994  return 1;
995  }
996 
997  if (!this->InitSignatureManager(pubkey_path)) {
998  return 1;
999  }
1000  }
1001 
1002  // Load Manifest
1004  bool successful = true;
1005 
1006  if (is_remote_) {
1007  manifest = FetchRemoteManifest(repo_base_path_, repo_name);
1008  } else {
1009  if (chdir(repo_base_path_.c_str()) != 0) {
1010  LogCvmfs(kLogCvmfs, kLogStderr, "failed to switch to directory %s",
1011  repo_base_path_.c_str());
1012  return 1;
1013  }
1014  manifest = OpenLocalManifest(".cvmfspublished");
1015  }
1016 
1017  if (!manifest.IsValid()) {
1018  LogCvmfs(kLogCvmfs, kLogStderr, "failed to load repository manifest");
1019  return 1;
1020  }
1021 
1022  // Check meta-info object
1023  if (!manifest->meta_info().IsNull()) {
1024  string tmp_file;
1025  if (!is_remote_)
1026  tmp_file = DecompressPiece(manifest->meta_info());
1027  else
1028  tmp_file = DownloadPiece(manifest->meta_info());
1029  if (tmp_file == "") {
1030  LogCvmfs(kLogCvmfs, kLogStderr, "failed to load repository metainfo %s",
1031  manifest->meta_info().ToString().c_str());
1032  return 1;
1033  }
1034  unlink(tmp_file.c_str());
1035  }
1036 
1037  shash::Any reflog_hash;
1038  if (!reflog_chksum_path.empty()) {
1039  if (!manifest::Reflog::ReadChecksum(reflog_chksum_path, &reflog_hash)) {
1040  LogCvmfs(kLogCvmfs, kLogStderr, "failed to read reflog checksum file");
1041  return 1;
1042  }
1043  } else {
1044  reflog_hash = manifest->reflog_hash();
1045  }
1046 
1047  if (Exists(".cvmfsreflog")) {
1048  if (reflog_hash.IsNull()) {
1049  // If there is a reflog, we want to check it
1051  ".cvmfsreflog present but no checksum provided, aborting");
1052  return 1;
1053  }
1054  bool retval = InspectReflog(reflog_hash, manifest.weak_ref());
1055  if (!retval) {
1056  LogCvmfs(kLogCvmfs, kLogStderr, "failed to verify reflog");
1057  return 1;
1058  }
1059  } else {
1060  if (!reflog_hash.IsNull()) {
1061  // There is a checksum but no reflog; possibly the checksum is for the
1062  // from the manifest for the stratum 0 reflog
1063  if (!reflog_chksum_path.empty()) {
1065  "local reflog checksum set but reflog itself is missing, "
1066  "aborting");
1067  return 1;
1068  }
1069  }
1070  }
1071 
1072  // Load history
1074  if (!manifest->history().IsNull()) {
1075  string tmp_file;
1076  if (!is_remote_)
1077  tmp_file = DecompressPiece(manifest->history());
1078  else
1079  tmp_file = DownloadPiece(manifest->history());
1080  if (tmp_file == "") {
1081  LogCvmfs(kLogCvmfs, kLogStderr, "failed to load history database %s",
1082  manifest->history().ToString().c_str());
1083  return 1;
1084  }
1085  tag_db = history::SqliteHistory::Open(tmp_file);
1086  if (!tag_db.IsValid()) {
1087  LogCvmfs(kLogCvmfs, kLogStderr, "failed to open history database %s",
1088  manifest->history().ToString().c_str());
1089  return 1;
1090  }
1091  tag_db->TakeDatabaseFileOwnership();
1092  successful = InspectHistory(tag_db.weak_ref()) && successful;
1093  }
1094 
1095  if (manifest->has_alt_catalog_path()) {
1096  if (!Exists(manifest->certificate().MakeAlternativePath())) {
1098  "failed to find alternative certificate link %s",
1099  manifest->certificate().MakeAlternativePath().c_str());
1100  return 1;
1101  }
1102  if (!Exists(manifest->catalog_hash().MakeAlternativePath())) {
1104  "failed to find alternative catalog link %s",
1105  manifest->catalog_hash().MakeAlternativePath().c_str());
1106  return 1;
1107  }
1108  }
1109 
1110  shash::Any root_hash = manifest->catalog_hash();
1111  uint64_t root_size = manifest->catalog_size();
1112  if (tag_name != "") {
1113  if (!tag_db.IsValid()) {
1114  LogCvmfs(kLogCvmfs, kLogStderr, "no history");
1115  return 1;
1116  }
1118  const bool retval = tag_db->GetByName(tag_name, &tag);
1119  if (!retval) {
1120  LogCvmfs(kLogCvmfs, kLogStderr, "no such tag: %s", tag_name.c_str());
1121  return 1;
1122  }
1123  root_hash = tag.root_hash;
1124  root_size = tag.size;
1125  LogCvmfs(kLogCvmfs, kLogStdout, "Inspecting repository tag %s",
1126  tag_name.c_str());
1127  }
1128 
1129  const bool is_nested_catalog = (!subtree_path.empty());
1130  if (is_nested_catalog
1131  && !FindSubtreeRootCatalog(subtree_path, &root_hash, &root_size)) {
1132  LogCvmfs(kLogCvmfs, kLogStderr, "cannot find nested catalog at %s",
1133  subtree_path.c_str());
1134  return 1;
1135  }
1136 
1137 
1138  catalog::DeltaCounters computed_counters;
1139  successful = InspectTree(subtree_path,
1140  root_hash,
1141  root_size,
1142  is_nested_catalog,
1143  NULL,
1144  &computed_counters)
1145  && successful;
1146 
1147  if (!successful) {
1148  LogCvmfs(kLogCvmfs, kLogStderr, "CATALOG PROBLEMS OR OTHER ERRORS FOUND");
1149  return 1;
1150  }
1151 
1152  LogCvmfs(kLogCvmfs, kLogStdout, "no problems found");
1153  return 0;
1154 }
1155 
1156 } // namespace swissknife
uint32_t linkcount() const
void SetLogVerbosity(const LogLevels max_level)
Definition: logging.cc:258
void TakeDatabaseFileOwnership()
Definition: reflog.cc:293
bool IsExternalFile() const
bool ContainsHistory(const shash::Any &history) const
Definition: reflog.cc:231
bool InspectHistory(history::History *history)
bool IsNull() const
Definition: hash.h:371
Differences CompareTo(const DirectoryEntry &other) const
const manifest::Manifest * manifest() const
Definition: repository.h:125
ShortString< kDefaultMaxName, 1 > NameString
Definition: shortstring.h:214
Item At(const size_t index) const
Definition: bigvector.h:48
time_t mtime() const
bool IsDirectory() const
static SqliteHistory * Open(const std::string &file_name)
bool ListPathChunks(const PathString &path, const shash::Algorithms interpret_hashes_as, FileChunkList *chunks) const
Definition: catalog.h:144
T * weak_ref() const
Definition: pointer.h:46
const int kDefaultFileMode
Definition: posix.h:32
static bool ReadChecksum(const std::string &path, shash::Any *checksum)
Definition: reflog.cc:48
#define PANIC(...)
Definition: exception.h:29
CVMFS_EXPORT const LogSource source
Definition: exception.h:33
FILE * CreateTempFile(const std::string &path_prefix, const int mode, const char *open_flags, std::string *final_path)
Definition: posix.cc:1013
uint64_t size() const
string JoinStrings(const vector< string > &strings, const string &joint)
Definition: string.cc:356
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
bool LookupPath(const PathString &path, DirectoryEntry *dirent) const
Definition: catalog.h:124
bool CompareEntries(const catalog::DirectoryEntry &a, const catalog::DirectoryEntry &b, const bool compare_names, const bool is_transition_point=false)
void ApplyDelta(const DeltaCounters &delta)
bool IsHttpUrl(const std::string &path)
Definition: posix.cc:167
bool ListingPath(const PathString &path, DirectoryEntryList *listing, const bool expand_symlink=true) const
Definition: catalog.h:132
std::map< std::string, const Counters_t * > FieldsMap
const char kSuffixMicroCatalog
Definition: hash.h:56
manifest::Manifest * FetchRemoteManifest(const std::string &repository_url, const std::string &repository_name, const shash::Any &base_hash=shash::Any()) const
Definition: server_tool.cc:121
const shash::Any & content_hash() const
Definition: file_chunk.h:37
catalog::Catalog * FetchCatalog(const std::string &path, const shash::Any &catalog_hash, const uint64_t catalog_size=0)
assert((mem||(size==0))&&"Out Of Memory")
bool InspectReflog(const shash::Any &reflog_hash, manifest::Manifest *manifest)
Algorithms algorithm
Definition: hash.h:122
shash::Any checksum() const
static uint32_t hasher_any(const shash::Any &key)
unsigned int mode() const
unsigned char digest[digest_size_]
Definition: hash.h:121
bool Find(const catalog::Catalog *catalog, const PathString &path, catalog::DeltaCounters *computed_counters, std::set< PathString > *bind_mountpoints)
bool ContainsMetainfo(const shash::Any &metainfo) const
Definition: reflog.cc:237
bool CopyPath2File(const std::string &src, FILE *fdest)
Definition: compression.cc:46
bool SymlinkExists(const std::string &path)
Definition: posix.cc:833
static Reflog * Open(const std::string &database_path)
Definition: reflog.cc:17
bool IsValid(const std::string &input) const
Definition: sanitizer.cc:112
bool IsNestedCatalogRoot() const
bool FileExists(const std::string &path)
Definition: posix.cc:803
std::string DownloadPiece(const shash::Any catalog_hash)
std::vector< DirectoryEntry > DirectoryEntryList
NameString name() const
int Main(const ArgumentList &args)
virtual bool ListBranches(std::vector< Branch > *branches) const =0
std::string DecompressPiece(const shash::Any catalog_hash)
bool HasXattrs() const
download::DownloadManager * download_manager() const
Definition: server_tool.cc:96
virtual bool List(std::vector< Tag > *tags) const =0
vector< string > SplitString(const string &str, char delim)
Definition: string.cc:306
bool Exists(const std::string &file)
off_t offset() const
Definition: file_chunk.h:38
shash::Any certificate() const
Definition: manifest.h:127
void PopulateToParent(DeltaCounters *parent) const
std::string FetchPath(const std::string &path)
LinkString symlink() const
void Insert(const Key &key, const Value &value)
Definition: smallhash.h:106
PathString mountpoint() const
Definition: catalog.h:175
static void HashDatabase(const std::string &database_path, shash::Any *hash_reflog)
Definition: reflog.cc:308
shash::Any catalog_hash() const
Definition: manifest.h:125
void Append(const char *chars, const unsigned length)
Definition: shortstring.h:80
SmallHashDynamic< shash::Any, char > duplicates_map_
bool FindSubtreeRootCatalog(const std::string &subtree_path, shash::Any *root_hash, uint64_t *root_size)
bool DirectoryExists(const std::string &path)
Definition: posix.cc:824
bool CompareCounters(const catalog::Counters &a, const catalog::Counters &b)
bool Contains(const Key &key) const
Definition: smallhash.h:99
manifest::Manifest * OpenLocalManifest(const std::string path) const
Definition: server_tool.cc:106
bool InitSignatureManager(const std::string &pubkey_path, const std::string &certificate_path="", const std::string &private_key_path="")
Definition: server_tool.cc:44
std::string ToString() const
Definition: shortstring.h:139
std::vector< NestedCatalog > NestedCatalogList
Definition: catalog.h:204
bool ContainsCatalog(const shash::Any &catalog) const
Definition: reflog.cc:216
bool IsEmpty() const
Definition: shortstring.h:137
uint64_t String2Uint64(const string &value)
Definition: string.cc:240
std::map< char, SharedPtr< std::string > > ArgumentList
Definition: swissknife.h:72
ShortString< kDefaultMaxPath, 0 > PathString
Definition: shortstring.h:213
Failures Fetch(JobInfo *info)
Definition: download.cc:1982
size_t size() const
Definition: file_chunk.h:39
shash::Any history() const
Definition: manifest.h:128
bool InspectTree(const std::string &path, const shash::Any &catalog_hash, const uint64_t catalog_size, const bool is_nested_catalog, const catalog::DirectoryEntry *transition_point, catalog::DeltaCounters *computed_counters)
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
bool FindNested(const PathString &mountpoint, shash::Any *hash, uint64_t *size) const
Definition: catalog.cc:680
bool DecompressPath2Path(const string &src, const string &dest)
Definition: compression.cc:383
int64_t GetFileSize(const std::string &path)
Definition: posix.cc:812
const int kLogVerboseMsg
uid_t uid() const
gid_t gid() const
std::string MakePath() const
Definition: hash.h:306
std::string MakeCanonicalPath(const std::string &path)
Definition: posix.cc:98
void Init(uint32_t expected_size, Key empty, uint32_t(*hasher)(const Key &key))
Definition: smallhash.h:58
bool InitDownloadManager(const bool follow_redirects, const std::string &proxy, const unsigned max_pool_handles=1)
Definition: server_tool.cc:17
const char * c_str() const
Definition: shortstring.h:143
uint32_t hardlink_group() const
bool ContainsCertificate(const shash::Any &certificate) const
Definition: reflog.cc:209
static void size_t size
Definition: smalloc.h:54
std::vector< std::string > FindFilesBySuffix(const std::string &dir, const std::string &suffix)
Definition: posix.cc:1128
unsigned int Differences
shash::Any meta_info() const
Definition: manifest.h:132
size_t size() const
Definition: bigvector.h:117
CVMFS_EXPORT void LogCvmfs(const LogSource source, const int mask, const char *format,...)
Definition: logging.cc:545