GCC Code Coverage Report
Directory: cvmfs/ Exec Total Coverage
File: cvmfs/swissknife_check.cc Lines: 0 516 0.0 %
Date: 2019-02-03 02:48:13 Branches: 0 380 0.0 %

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