GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/publish/repository.cc
Date: 2026-09-27 02:40:09
Exec Total Coverage
Lines: 0 568 0.0%
Branches: 0 1169 0.0%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM File System.
3 */
4
5
6 #include "publish/repository.h"
7
8 #include <cassert>
9 #include <cstddef>
10 #include <cstdlib>
11 #include <memory>
12
13 #include "catalog_mgr_ro.h"
14 #include "catalog_mgr_rw.h"
15 #include "crypto/hash.h"
16 #include "crypto/signature.h"
17 #include "gateway_util.h"
18 #include "history_sqlite.h"
19 #include "ingestion/ingestion_source.h"
20 #include "manifest.h"
21 #include "manifest_fetch.h"
22 #include "network/download.h"
23 #include "network/sink_file.h"
24 #include "network/sink_mem.h"
25 #include "publish/except.h"
26 #include "publish/repository_util.h"
27 #include "publish/settings.h"
28 #include "reflog.h"
29 #include "statistics.h"
30 #include "sync_mediator.h"
31 #include "sync_union_aufs.h"
32 #include "sync_union_overlayfs.h"
33 #include "sync_union_tarball.h"
34 #include "upload.h"
35 #include "upload_spooler_definition.h"
36 #include "util/logging.h"
37 #include "whitelist.h"
38
39 // TODO(jblomer): Remove Me
40 namespace swissknife {
41 class CommandTag {
42 static const std::string kHeadTag;
43 static const std::string kPreviousHeadTag;
44 };
45 const std::string CommandTag::kHeadTag = "trunk";
46 const std::string CommandTag::kPreviousHeadTag = "trunk-previous";
47 } // namespace swissknife
48
49 namespace publish {
50
51 ✗ Repository::Repository(const SettingsRepository &settings, const bool exists)
52 ✗ : settings_(settings)
53 ✗ , statistics_(new perf::Statistics())
54 ✗ , signature_mgr_(new signature::SignatureManager())
55 ✗ , download_mgr_(NULL)
56 ✗ , simple_catalog_mgr_(NULL)
57 ✗ , whitelist_(NULL)
58 ✗ , reflog_(NULL)
59 ✗ , manifest_(NULL)
60 ✗ , history_(NULL) {
61 ✗ signature_mgr_->Init();
62
63 ✗ if (exists) {
64 int rvb;
65 const std::string keys = JoinStrings(
66 ✗ FindFilesBySuffix(settings.keychain().keychain_dir(), ".pub"), ":");
67 ✗ rvb = signature_mgr_->LoadPublicRsaKeys(keys);
68 ✗ if (!rvb) {
69 ✗ signature_mgr_->Fini();
70 ✗ delete signature_mgr_;
71 ✗ delete statistics_;
72 ✗ throw EPublish("cannot load public rsa key");
73 }
74 }
75
76 ✗ if (!settings.cert_bundle().empty()) {
77 ✗ const int rvi = setenv("X509_CERT_BUNDLE", settings.cert_bundle().c_str(),
78 1 /* overwrite */);
79 ✗ if (rvi != 0)
80 ✗ throw EPublish("cannot set X509_CERT_BUNDLE environment variable");
81 }
82 ✗ download_mgr_ = new download::DownloadManager(
83 ✗ 16, perf::StatisticsTemplate("download", statistics_));
84 ✗ download_mgr_->UseSystemCertificatePath();
85
86 ✗ if (settings.proxy() != "") {
87 ✗ download_mgr_->SetProxyChain(settings.proxy(), "",
88 download::DownloadManager::kSetProxyBoth);
89 }
90
91 ✗ if (exists) {
92 try {
93 ✗ DownloadRootObjects(settings.url(), settings.fqrn(), settings.tmp_dir());
94 ✗ } catch (const EPublish &e) {
95 ✗ signature_mgr_->Fini();
96 ✗ delete signature_mgr_;
97 ✗ delete download_mgr_;
98 ✗ delete statistics_;
99 ✗ throw;
100 }
101 }
102 }
103
104 ✗ Repository::~Repository() {
105 ✗ if (signature_mgr_ != NULL)
106 ✗ signature_mgr_->Fini();
107
108 ✗ delete history_;
109 ✗ delete manifest_;
110 ✗ delete reflog_;
111 ✗ delete whitelist_;
112 ✗ delete signature_mgr_;
113 ✗ delete download_mgr_;
114 ✗ delete simple_catalog_mgr_;
115 ✗ delete statistics_;
116 }
117
118 ✗ const history::History *Repository::history() const { return history_; }
119
120 ✗ catalog::SimpleCatalogManager *Repository::GetSimpleCatalogManager() {
121 ✗ if (simple_catalog_mgr_ != NULL)
122 ✗ return simple_catalog_mgr_;
123
124 ✗ simple_catalog_mgr_ = new catalog::SimpleCatalogManager(
125 ✗ manifest_->catalog_hash(),
126 ✗ settings_.url(),
127 ✗ settings_.tmp_dir(),
128 download_mgr_,
129 statistics_,
130 ✗ true /* manage_catalog_files */);
131 ✗ simple_catalog_mgr_->Init();
132 ✗ return simple_catalog_mgr_;
133 }
134
135
136 ✗ void Repository::DownloadRootObjects(const std::string &url,
137 const std::string &fqrn,
138 const std::string &tmp_dir) {
139 ✗ delete whitelist_;
140 ✗ whitelist_ = new whitelist::Whitelist(fqrn, download_mgr_, signature_mgr_);
141 ✗ const whitelist::Failures rv_whitelist = whitelist_->LoadUrl(url);
142 ✗ if (whitelist_->status() != whitelist::Whitelist::kStAvailable) {
143 ✗ throw EPublish(std::string("cannot load whitelist [")
144 ✗ + whitelist::Code2Ascii(rv_whitelist) + "]");
145 }
146
147 ✗ manifest::ManifestEnsemble ensemble;
148 ✗ const uint64_t minimum_timestamp = 0;
149 ✗ const shash::Any *base_catalog = NULL;
150 ✗ const manifest::Failures rv_manifest = manifest::Fetch(
151 url, fqrn, minimum_timestamp, base_catalog, signature_mgr_, download_mgr_,
152 &ensemble);
153 ✗ if (rv_manifest != manifest::kFailOk)
154 ✗ throw EPublish("cannot load manifest");
155 ✗ delete manifest_;
156 ✗ manifest_ = new manifest::Manifest(*ensemble.manifest);
157
158 // The read-only catalog manager is cached and bound to the previous root
159 // hash; drop it so it is rebuilt against the refreshed manifest on next use.
160 ✗ delete simple_catalog_mgr_;
161 ✗ simple_catalog_mgr_ = NULL;
162
163 ✗ std::string reflog_path;
164 ✗ FILE *reflog_fd = CreateTempFile(tmp_dir + "/reflog", kPrivateFileMode, "w",
165 &reflog_path);
166 ✗ if (reflog_fd == NULL)
167 ✗ throw EPublish("cannot create reflog temp file (disk full?)");
168 ✗ const std::string reflog_url = url + "/.cvmfsreflog";
169 // TODO(jblomer): verify reflog hash
170 // shash::Any reflog_hash(manifest_->GetHashAlgorithm());
171 ✗ cvmfs::FileSink filesink(reflog_fd);
172 download::JobInfo download_reflog(&reflog_url, false /* compressed */,
173 ✗ false /* probe hosts */, NULL, &filesink);
174 ✗ download::Failures rv_dl = download_mgr_->Fetch(&download_reflog);
175 ✗ fclose(reflog_fd);
176 ✗ if (rv_dl == download::kFailOk) {
177 ✗ delete reflog_;
178 ✗ reflog_ = manifest::Reflog::Open(reflog_path);
179 ✗ if (reflog_ == NULL)
180 ✗ throw EPublish("cannot open reflog");
181 ✗ reflog_->TakeDatabaseFileOwnership();
182 } else {
183 ✗ if (!download_reflog.IsFileNotFound()) {
184 ✗ throw EPublish(std::string("cannot load reflog [")
185 ✗ + download::Code2Ascii(rv_dl) + "]");
186 }
187 ✗ assert(reflog_ == NULL);
188 }
189
190 ✗ std::string tags_path;
191 ✗ FILE *tags_fd = CreateTempFile(tmp_dir + "/tags", kPrivateFileMode, "w",
192 &tags_path);
193 ✗ if (tags_fd == NULL)
194 ✗ throw EPublish("cannot create tags temp file (disk full?)");
195 ✗ if (!manifest_->history().IsNull()) {
196 ✗ const std::string tags_url = url + "/data/"
197 ✗ + manifest_->history().MakePath();
198 ✗ const shash::Any tags_hash(manifest_->history());
199 ✗ cvmfs::FileSink filesink(tags_fd);
200 download::JobInfo download_tags(&tags_url, true /* compressed */,
201 true /* probe hosts */, &tags_hash,
202 ✗ &filesink);
203 ✗ rv_dl = download_mgr_->Fetch(&download_tags);
204 ✗ fclose(tags_fd);
205 ✗ if (rv_dl != download::kFailOk)
206 ✗ throw EPublish("cannot load tag database");
207 ✗ delete history_;
208 ✗ history_ = history::SqliteHistory::OpenWritable(tags_path);
209 ✗ if (history_ == NULL)
210 ✗ throw EPublish("cannot open tag database");
211 ✗ } else {
212 ✗ fclose(tags_fd);
213 ✗ delete history_;
214 ✗ history_ = history::SqliteHistory::Create(tags_path, fqrn);
215 ✗ if (history_ == NULL)
216 ✗ throw EPublish("cannot create tag database");
217 }
218 ✗ history_->TakeDatabaseFileOwnership();
219
220 ✗ if (!manifest_->meta_info().IsNull()) {
221 ✗ const shash::Any info_hash(manifest_->meta_info());
222 ✗ const std::string info_url = url + "/data/" + info_hash.MakePath();
223 ✗ cvmfs::MemSink metainfo_memsink;
224 download::JobInfo download_info(&info_url, true /* compressed */,
225 true /* probe_hosts */, &info_hash,
226 ✗ &metainfo_memsink);
227 ✗ const download::Failures rv_info = download_mgr_->Fetch(&download_info);
228 ✗ if (rv_info != download::kFailOk) {
229 ✗ throw EPublish(std::string("cannot load meta info [")
230 ✗ + download::Code2Ascii(rv_info) + "]");
231 }
232 ✗ meta_info_ = std::string(reinterpret_cast<char *>(metainfo_memsink.data()),
233 ✗ metainfo_memsink.pos());
234 ✗ } else {
235 ✗ meta_info_ = "n/a";
236 }
237 }
238
239
240 ✗ std::string Repository::GetFqrnFromUrl(const std::string &url) {
241 ✗ return GetFileName(MakeCanonicalPath(url));
242 }
243
244
245 ✗ bool Repository::IsMasterReplica() {
246 ✗ const std::string url = settings_.url() + "/.cvmfs_master_replica";
247 ✗ download::JobInfo head(&url, false /* probe_hosts */);
248 ✗ const download::Failures retval = download_mgr_->Fetch(&head);
249 ✗ if (retval == download::kFailOk) {
250 ✗ return true;
251 }
252 ✗ if (head.IsFileNotFound()) {
253 ✗ return false;
254 }
255
256 ✗ throw EPublish(std::string("error looking for .cvmfs_master_replica [")
257 ✗ + download::Code2Ascii(retval) + "]");
258 }
259
260
261 //------------------------------------------------------------------------------
262
263
264 ✗ void Publisher::ConstructSpoolers() {
265 ✗ if ((spooler_files_ != NULL) && (spooler_catalogs_ != NULL))
266 ✗ return;
267 ✗ assert((spooler_files_ == NULL) && (spooler_catalogs_ == NULL));
268
269 ✗ upload::SpoolerDefinition sd(settings_.storage().GetLocator(),
270 ✗ settings_.transaction().hash_algorithm(),
271 ✗ settings_.transaction().compression_algorithm());
272 ✗ sd.session_token_file = settings_.transaction()
273 ✗ .spool_area()
274 ✗ .gw_session_token();
275 ✗ sd.key_file = settings_.keychain().gw_key_path();
276
277 ✗ spooler_files_ = upload::Spooler::Construct(sd, statistics_publish_.get());
278 ✗ if (spooler_files_ == NULL)
279 ✗ throw EPublish("could not initialize file spooler");
280
281 ✗ const upload::SpoolerDefinition sd_catalogs(sd.Dup2DefaultCompression());
282 ✗ spooler_catalogs_ = upload::Spooler::Construct(sd_catalogs,
283 statistics_publish_.get());
284 ✗ if (spooler_catalogs_ == NULL) {
285 ✗ delete spooler_files_;
286 ✗ throw EPublish("could not initialize catalog spooler");
287 }
288 }
289
290
291 ✗ void Publisher::CreateKeychain() {
292 ✗ if (settings_.keychain().HasDanglingMasterKeys()) {
293 ✗ throw EPublish("dangling master key pair");
294 }
295 ✗ if (settings_.keychain().HasDanglingRepositoryKeys()) {
296 ✗ throw EPublish("dangling repository keys");
297 }
298 ✗ if (!settings_.keychain().HasMasterKeys())
299 ✗ signature_mgr_->GenerateMasterKeyPair();
300 ✗ if (!settings_.keychain().HasRepositoryKeys())
301 ✗ signature_mgr_->GenerateCertificate(settings_.fqrn());
302
303 ✗ whitelist_ = new whitelist::Whitelist(settings_.fqrn(), NULL, signature_mgr_);
304 const std::string whitelist_str = whitelist::Whitelist::CreateString(
305 ✗ settings_.fqrn(), settings_.whitelist_validity_days(),
306 ✗ settings_.transaction().hash_algorithm(), signature_mgr_);
307 ✗ const whitelist::Failures rv_wl = whitelist_->LoadMem(whitelist_str);
308 ✗ if (rv_wl != whitelist::kFailOk)
309 ✗ throw EPublish("whitelist generation failed");
310 }
311
312
313 ✗ void Publisher::CreateRootObjects() {
314 // Reflog
315 const std::string reflog_path = CreateTempPath(
316 ✗ settings_.transaction().spool_area().tmp_dir() + "/cvmfs_reflog", 0600);
317 ✗ reflog_ = manifest::Reflog::Create(reflog_path, settings_.fqrn());
318 ✗ if (reflog_ == NULL)
319 ✗ throw EPublish("could not create reflog");
320 ✗ reflog_->TakeDatabaseFileOwnership();
321
322 // Root file catalog and initial manifest
323 ✗ manifest_ = catalog::WritableCatalogManager::CreateRepository(
324 ✗ settings_.transaction().spool_area().tmp_dir(),
325 ✗ settings_.transaction().is_volatile(),
326 ✗ settings_.transaction().voms_authz(),
327 spooler_catalogs_);
328 ✗ spooler_catalogs_->WaitForUpload();
329 ✗ if (manifest_ == NULL)
330 ✗ throw EPublish("could not create initial file catalog");
331 ✗ reflog_->AddCatalog(manifest_->catalog_hash());
332
333 ✗ manifest_->set_repository_name(settings_.fqrn());
334 ✗ manifest_->set_ttl(settings_.transaction().ttl_second());
335 const bool
336 ✗ needs_bootstrap_shortcuts = !settings_.transaction().voms_authz().empty();
337 ✗ manifest_->set_has_alt_catalog_path(needs_bootstrap_shortcuts);
338 ✗ manifest_->set_garbage_collectability(
339 ✗ settings_.transaction().is_garbage_collectable());
340
341 // Tag database
342 const std::string tags_path = CreateTempPath(
343 ✗ settings_.transaction().spool_area().tmp_dir() + "/cvmfs_tags", 0600);
344 ✗ history_ = history::SqliteHistory::Create(tags_path, settings_.fqrn());
345 ✗ if (history_ == NULL)
346 ✗ throw EPublish("could not create tag database");
347 ✗ history_->TakeDatabaseFileOwnership();
348 const history::History::Tag tag_trunk(
349 ✗ "trunk", manifest_->catalog_hash(), manifest_->catalog_size(),
350 ✗ manifest_->revision(), manifest_->publish_timestamp(), "empty repository",
351 ✗ "" /* branch */);
352 ✗ history_->Insert(tag_trunk);
353
354 // Meta information, TODO(jblomer)
355 ✗ meta_info_ = "{}";
356 }
357
358
359 ✗ void Publisher::CreateStorage() {
360 ✗ ConstructSpoolers();
361 ✗ if (!spooler_files_->Create())
362 ✗ throw EPublish("could not initialize repository storage area");
363 }
364
365
366 ✗ void Publisher::PushCertificate() {
367 ✗ upload::Spooler::CallbackPtr callback = spooler_files_->RegisterListener(
368 &Publisher::OnProcessCertificate, this);
369 ✗ spooler_files_->ProcessCertificate(
370 ✗ new StringIngestionSource(signature_mgr_->GetCertificate()));
371 ✗ spooler_files_->WaitForUpload();
372 ✗ spooler_files_->UnregisterListener(callback);
373 }
374
375
376 ✗ void Publisher::PushHistory() {
377 ✗ assert(history_ != NULL);
378 ✗ history_->SetPreviousRevision(manifest_->history());
379 ✗ const string history_path = history_->filename();
380 ✗ history_->DropDatabaseFileOwnership();
381 ✗ delete history_;
382
383 ✗ upload::Spooler::CallbackPtr callback = spooler_files_->RegisterListener(
384 &Publisher::OnProcessHistory, this);
385 ✗ spooler_files_->ProcessHistory(history_path);
386 ✗ spooler_files_->WaitForUpload();
387 ✗ spooler_files_->UnregisterListener(callback);
388
389 ✗ history_ = history::SqliteHistory::OpenWritable(history_path);
390 ✗ assert(history_ != NULL);
391 ✗ history_->TakeDatabaseFileOwnership();
392 }
393
394
395 ✗ void Publisher::PushMetainfo() {
396 ✗ upload::Spooler::CallbackPtr callback = spooler_files_->RegisterListener(
397 &Publisher::OnProcessMetainfo, this);
398 ✗ spooler_files_->ProcessMetainfo(new StringIngestionSource(meta_info_));
399 ✗ spooler_files_->WaitForUpload();
400 ✗ spooler_files_->UnregisterListener(callback);
401 }
402
403
404 ✗ void Publisher::PushManifest() {
405 ✗ std::string signed_manifest = manifest_->ExportString();
406 ✗ shash::Any manifest_hash(settings_.transaction().hash_algorithm());
407 ✗ shash::HashMem(
408 ✗ reinterpret_cast<const unsigned char *>(signed_manifest.data()),
409 ✗ signed_manifest.length(), &manifest_hash);
410 ✗ signed_manifest += "--\n" + manifest_hash.ToString() + "\n";
411 unsigned char *signature;
412 unsigned sig_size;
413 ✗ bool rvb = signature_mgr_->Sign(
414 ✗ reinterpret_cast<const unsigned char *>(manifest_hash.ToString().data()),
415 manifest_hash.GetHexSize(), &signature, &sig_size);
416 ✗ if (!rvb)
417 ✗ throw EPublish("cannot sign manifest");
418 ✗ signed_manifest += std::string(reinterpret_cast<char *>(signature), sig_size);
419 ✗ free(signature);
420
421 // Create alternative bootstrapping symlinks for VOMS secured repos
422 ✗ if (manifest_->has_alt_catalog_path()) {
423 ✗ rvb = spooler_files_->PlaceBootstrappingShortcut(manifest_->certificate())
424 ✗ && spooler_files_->PlaceBootstrappingShortcut(
425 ✗ manifest_->catalog_hash())
426 ✗ && (manifest_->history().IsNull()
427 ✗ || spooler_files_->PlaceBootstrappingShortcut(
428 ✗ manifest_->history()))
429 ✗ && (manifest_->meta_info().IsNull()
430 ✗ || spooler_files_->PlaceBootstrappingShortcut(
431 ✗ manifest_->meta_info()));
432 ✗ if (!rvb)
433 ✗ EPublish("cannot place VOMS bootstrapping symlinks");
434 }
435
436 ✗ upload::Spooler::CallbackPtr callback = spooler_files_->RegisterListener(
437 &Publisher::OnUploadManifest, this);
438 ✗ spooler_files_->Upload(".cvmfspublished",
439 ✗ new StringIngestionSource(signed_manifest));
440 ✗ spooler_files_->WaitForUpload();
441 ✗ spooler_files_->UnregisterListener(callback);
442 }
443
444
445 ✗ void Publisher::PushReflog() {
446 ✗ const string reflog_path = reflog_->database_file();
447 ✗ reflog_->DropDatabaseFileOwnership();
448 ✗ delete reflog_;
449
450 ✗ shash::Any hash_reflog(settings_.transaction().hash_algorithm());
451 ✗ manifest::Reflog::HashDatabase(reflog_path, &hash_reflog);
452
453 ✗ upload::Spooler::CallbackPtr callback = spooler_files_->RegisterListener(
454 &Publisher::OnUploadReflog, this);
455 ✗ spooler_files_->UploadReflog(reflog_path);
456 ✗ spooler_files_->WaitForUpload();
457 ✗ spooler_files_->UnregisterListener(callback);
458
459 ✗ manifest_->set_reflog_hash(hash_reflog);
460
461 ✗ reflog_ = manifest::Reflog::Open(reflog_path);
462 ✗ assert(reflog_ != NULL);
463 ✗ reflog_->TakeDatabaseFileOwnership();
464 }
465
466
467 ✗ void Publisher::PushWhitelist() {
468 // TODO(jblomer): PKCS7 handling
469 ✗ upload::Spooler::CallbackPtr callback = spooler_files_->RegisterListener(
470 &Publisher::OnUploadWhitelist, this);
471 ✗ spooler_files_->Upload(".cvmfswhitelist",
472 ✗ new StringIngestionSource(whitelist_->ExportString()));
473 ✗ spooler_files_->WaitForUpload();
474 ✗ spooler_files_->UnregisterListener(callback);
475 }
476
477
478 ✗ Publisher *Publisher::Create(const SettingsPublisher &settings) {
479 ✗ std::unique_ptr<Publisher> publisher(new Publisher(settings, false));
480
481 ✗ LogCvmfs(kLogCvmfs, publisher->llvl_ | kLogStdout | kLogNoLinebreak,
482 "Creating Key Chain... ");
483 ✗ publisher->CreateKeychain();
484 ✗ publisher->ExportKeychain();
485 ✗ LogCvmfs(kLogCvmfs, publisher->llvl_ | kLogStdout, "done");
486
487 ✗ LogCvmfs(kLogCvmfs, publisher->llvl_ | kLogStdout | kLogNoLinebreak,
488 "Creating Backend Storage... ");
489 ✗ publisher->CreateStorage();
490 ✗ publisher->PushWhitelist();
491 ✗ LogCvmfs(kLogCvmfs, publisher->llvl_ | kLogStdout, "done");
492
493 ✗ LogCvmfs(kLogCvmfs, publisher->llvl_ | kLogStdout | kLogNoLinebreak,
494 "Creating Initial Repository... ");
495 ✗ publisher->InitSpoolArea();
496 ✗ publisher->CreateRootObjects();
497 ✗ publisher->PushHistory();
498 ✗ publisher->PushCertificate();
499 ✗ publisher->PushMetainfo();
500 ✗ publisher->PushReflog();
501 ✗ publisher->PushManifest();
502 // TODO(jblomer): meta-info
503
504 // Re-create from empty repository in order to properly initialize
505 // parent Repository object
506 ✗ publisher = std::unique_ptr<Publisher>(new Publisher(settings));
507
508 ✗ LogCvmfs(kLogCvmfs, publisher->llvl_ | kLogStdout, "done");
509
510 ✗ return publisher.release();
511 }
512
513 ✗ void Publisher::ExportKeychain() {
514 ✗ CreateDirectoryAsOwner(settings_.keychain().keychain_dir(), kDefaultDirMode);
515
516 bool rvb;
517 ✗ rvb = SafeWriteToFile(signature_mgr_->GetActivePubkeys(),
518 ✗ settings_.keychain().master_public_key_path(), 0644);
519 ✗ if (!rvb)
520 ✗ throw EPublish("cannot export public master key");
521 ✗ rvb = SafeWriteToFile(signature_mgr_->GetCertificate(),
522 ✗ settings_.keychain().certificate_path(), 0644);
523 ✗ if (!rvb)
524 ✗ throw EPublish("cannot export certificate");
525
526 ✗ rvb = SafeWriteToFile(signature_mgr_->GetPrivateKey(),
527 ✗ settings_.keychain().private_key_path(), 0600);
528 ✗ if (!rvb)
529 ✗ throw EPublish("cannot export private certificate key");
530 ✗ rvb = SafeWriteToFile(signature_mgr_->GetPrivateMasterKey(),
531 ✗ settings_.keychain().master_private_key_path(), 0600);
532 ✗ if (!rvb)
533 ✗ throw EPublish("cannot export private master key");
534
535 int rvi;
536 ✗ rvi = chown(settings_.keychain().master_public_key_path().c_str(),
537 settings_.owner_uid(), settings_.owner_gid());
538 ✗ if (rvi != 0)
539 ✗ throw EPublish("cannot set key file ownership");
540 ✗ rvi = chown(settings_.keychain().certificate_path().c_str(),
541 settings_.owner_uid(), settings_.owner_gid());
542 ✗ if (rvi != 0)
543 ✗ throw EPublish("cannot set key file ownership");
544 ✗ rvi = chown(settings_.keychain().private_key_path().c_str(),
545 settings_.owner_uid(), settings_.owner_gid());
546 ✗ if (rvi != 0)
547 ✗ throw EPublish("cannot set key file ownership");
548 ✗ rvi = chown(settings_.keychain().master_private_key_path().c_str(),
549 settings_.owner_uid(), settings_.owner_gid());
550 ✗ if (rvi != 0)
551 ✗ throw EPublish("cannot set key file ownership");
552 }
553
554 ✗ void Publisher::OnProcessCertificate(const upload::SpoolerResult &result) {
555 ✗ if (result.return_code != 0) {
556 ✗ throw EPublish("cannot write certificate to storage");
557 }
558 ✗ manifest_->set_certificate(result.content_hash);
559 ✗ reflog_->AddCertificate(result.content_hash);
560 }
561
562 ✗ void Publisher::OnProcessHistory(const upload::SpoolerResult &result) {
563 ✗ if (result.return_code != 0) {
564 ✗ throw EPublish("cannot write tag database to storage");
565 }
566 ✗ manifest_->set_history(result.content_hash);
567 ✗ reflog_->AddHistory(result.content_hash);
568 }
569
570 ✗ void Publisher::OnProcessMetainfo(const upload::SpoolerResult &result) {
571 ✗ if (result.return_code != 0) {
572 ✗ throw EPublish("cannot write repository meta info to storage");
573 }
574 ✗ manifest_->set_meta_info(result.content_hash);
575 ✗ reflog_->AddMetainfo(result.content_hash);
576 }
577
578 ✗ void Publisher::OnUploadManifest(const upload::SpoolerResult &result) {
579 ✗ if (result.return_code != 0) {
580 ✗ throw EPublish("cannot write manifest to storage");
581 }
582 }
583
584 ✗ void Publisher::OnUploadReflog(const upload::SpoolerResult &result) {
585 ✗ if (result.return_code != 0) {
586 ✗ throw EPublish("cannot write reflog to storage");
587 }
588 }
589
590 ✗ void Publisher::OnUploadWhitelist(const upload::SpoolerResult &result) {
591 ✗ if (result.return_code != 0) {
592 ✗ throw EPublish("cannot write whitelist to storage");
593 }
594 }
595
596 ✗ void Publisher::CreateDirectoryAsOwner(const std::string &path, int mode) {
597 ✗ const bool rvb = MkdirDeep(path, mode);
598 ✗ if (!rvb)
599 ✗ throw EPublish("cannot create directory " + path);
600 ✗ const int rvi = chown(path.c_str(), settings_.owner_uid(),
601 settings_.owner_gid());
602 ✗ if (rvi != 0)
603 ✗ throw EPublish("cannot set ownership on directory " + path);
604 }
605
606 ✗ void Publisher::InitSpoolArea() {
607 ✗ CreateDirectoryAsOwner(settings_.transaction().spool_area().workspace(),
608 kPrivateDirMode);
609 ✗ CreateDirectoryAsOwner(settings_.transaction().spool_area().tmp_dir(),
610 kPrivateDirMode);
611 ✗ CreateDirectoryAsOwner(settings_.transaction().spool_area().cache_dir(),
612 kPrivateDirMode);
613 ✗ CreateDirectoryAsOwner(settings_.transaction().spool_area().scratch_dir(),
614 kDefaultDirMode);
615 ✗ CreateDirectoryAsOwner(settings_.transaction().spool_area().ovl_work_dir(),
616 kPrivateDirMode);
617
618 // On a managed node, the mount points are already mounted
619 ✗ if (!DirectoryExists(settings_.transaction().spool_area().readonly_mnt())) {
620 ✗ CreateDirectoryAsOwner(settings_.transaction().spool_area().readonly_mnt(),
621 kDefaultDirMode);
622 }
623 ✗ if (!DirectoryExists(settings_.transaction().spool_area().union_mnt())) {
624 ✗ CreateDirectoryAsOwner(settings_.transaction().spool_area().union_mnt(),
625 kDefaultDirMode);
626 }
627 }
628
629 ✗ Publisher::Publisher(const SettingsPublisher &settings, const bool exists)
630 ✗ : Repository(SettingsRepository(settings), exists)
631 ✗ , settings_(settings)
632 ✗ , statistics_publish_(new perf::StatisticsTemplate("publish", statistics_))
633 ✗ , llvl_(settings.is_silent() ? kLogNone : kLogNormal)
634 ✗ , in_transaction_(settings.transaction().spool_area().transaction_lock())
635 ✗ , is_publishing_(settings.transaction().spool_area().publishing_lock())
636 ✗ , spooler_files_(NULL)
637 ✗ , spooler_catalogs_(NULL)
638 ✗ , catalog_mgr_(NULL)
639 ✗ , sync_parameters_(NULL)
640 ✗ , sync_mediator_(NULL)
641 ✗ , sync_union_(NULL) {
642 ✗ if (settings.transaction().layout_revision() != kRequiredLayoutRevision) {
643 ✗ const unsigned layout_revision = settings.transaction().layout_revision();
644 ✗ throw EPublish("This repository uses layout revision "
645 ✗ + StringifyInt(layout_revision)
646 ✗ + ".\n"
647 "This version of CernVM-FS requires layout revision "
648 ✗ + StringifyInt(kRequiredLayoutRevision)
649 ✗ + ", which is\n"
650 "incompatible to "
651 ✗ + StringifyInt(layout_revision)
652 ✗ + ".\n\n"
653 "Please run `cvmfs_server migrate` to update your "
654 "repository before "
655 "proceeding.",
656 ✗ EPublish::kFailLayoutRevision);
657 }
658
659 // Session and managed node are needed even when skipping downloads (e.g.
660 // for abort under disk-full conditions), so initialize them before the
661 // early return below.
662 ✗ if (settings.is_managed())
663 ✗ managed_node_ = std::unique_ptr<ManagedNode>(new ManagedNode(this));
664 ✗ session_ = std::unique_ptr<Session>(new Session(settings_, llvl_));
665
666 ✗ if (!exists)
667 ✗ return;
668
669 ✗ CreateDirectoryAsOwner(settings_.transaction().spool_area().tmp_dir(),
670 kPrivateDirMode);
671
672 ✗ if (settings.storage().type() == upload::SpoolerDefinition::Gateway) {
673 ✗ if (!settings.keychain().HasGatewayKey()) {
674 ✗ throw EPublish("gateway key missing: "
675 ✗ + settings.keychain().gw_key_path());
676 }
677 ✗ gw_key_ = gateway::ReadGatewayKey(settings.keychain().gw_key_path());
678 ✗ if (!gw_key_.IsValid()) {
679 ✗ throw EPublish("cannot read gateway key: "
680 ✗ + settings.keychain().gw_key_path());
681 }
682 }
683
684 ✗ if ((settings.storage().type() != upload::SpoolerDefinition::Gateway)
685 ✗ && !settings.transaction().in_enter_session()) {
686 ✗ int rvb = signature_mgr_->LoadCertificatePath(
687 ✗ settings.keychain().certificate_path());
688 ✗ if (!rvb)
689 ✗ throw EPublish("cannot load certificate, thus cannot commit changes");
690 ✗ rvb = signature_mgr_->LoadPrivateKeyPath(
691 ✗ settings.keychain().private_key_path(), "");
692 ✗ if (!rvb)
693 ✗ throw EPublish("cannot load private key, thus cannot commit changes");
694 // The private master key might be on a key card instead
695 ✗ if (FileExists(settings.keychain().master_private_key_path())) {
696 ✗ rvb = signature_mgr_->LoadPrivateMasterKeyPath(
697 ✗ settings.keychain().master_private_key_path());
698 ✗ if (!rvb)
699 ✗ throw EPublish("cannot load private master key");
700 }
701 ✗ if (!signature_mgr_->KeysMatch())
702 ✗ throw EPublish("corrupted keychain");
703 }
704
705 ✗ if (in_transaction_.IsSet())
706 ✗ ConstructSpoolers();
707 }
708
709 ✗ Publisher::~Publisher() {
710 ✗ delete sync_union_;
711 ✗ delete sync_mediator_;
712 ✗ delete sync_parameters_;
713 ✗ delete catalog_mgr_;
714 ✗ delete spooler_catalogs_;
715 ✗ delete spooler_files_;
716 }
717
718
719 ✗ void Publisher::ConstructSyncManagers() {
720 ✗ ConstructSpoolers();
721
722 ✗ if (catalog_mgr_ == NULL) {
723 ✗ catalog_mgr_ = new catalog::WritableCatalogManager(
724 ✗ settings_.transaction().base_hash(),
725 ✗ settings_.url(),
726 ✗ settings_.transaction().spool_area().tmp_dir(),
727 spooler_catalogs_,
728 download_mgr_,
729 ✗ settings_.transaction().enforce_limits(),
730 ✗ settings_.transaction().limit_nested_catalog_kentries(),
731 ✗ settings_.transaction().limit_root_catalog_kentries(),
732 ✗ settings_.transaction().limit_file_size_mb(),
733 statistics_,
734 ✗ settings_.transaction().use_catalog_autobalance(),
735 ✗ settings_.transaction().autobalance_max_weight(),
736 ✗ settings_.transaction().autobalance_min_weight(),
737 ✗ "");
738 ✗ catalog_mgr_->Init();
739 }
740
741 ✗ if (sync_parameters_ == NULL) {
742 ✗ SyncParameters *p = new SyncParameters();
743 ✗ p->spooler = spooler_files_;
744 ✗ p->repo_name = settings_.fqrn();
745 ✗ p->dir_union = settings_.transaction().spool_area().union_mnt();
746 ✗ p->dir_scratch = settings_.transaction().spool_area().scratch_dir();
747 ✗ p->dir_rdonly = settings_.transaction().spool_area().readonly_mnt();
748 ✗ p->dir_temp = settings_.transaction().spool_area().tmp_dir();
749 ✗ p->base_hash = settings_.transaction().base_hash();
750 ✗ p->stratum0 = settings_.url();
751 // p->manifest_path = SHOULD NOT BE NEEDED
752 // p->spooler_definition = SHOULD NOT BE NEEDED;
753 // p->union_fs_type = SHOULD NOT BE NEEDED
754 ✗ p->print_changeset = settings_.transaction().print_changeset();
755 ✗ p->dry_run = settings_.transaction().dry_run();
756 ✗ sync_parameters_ = p;
757 }
758
759 ✗ if (sync_mediator_ == NULL) {
760 ✗ sync_mediator_ = new SyncMediator(catalog_mgr_, sync_parameters_,
761 ✗ *statistics_publish_);
762 }
763
764 ✗ if (sync_union_ == NULL) {
765 ✗ switch (settings_.transaction().union_fs()) {
766 ✗ case kUnionFsAufs:
767 ✗ sync_union_ = new publish::SyncUnionAufs(
768 sync_mediator_,
769 ✗ settings_.transaction().spool_area().readonly_mnt(),
770 ✗ settings_.transaction().spool_area().union_mnt(),
771 ✗ settings_.transaction().spool_area().scratch_dir());
772 ✗ break;
773 ✗ case kUnionFsOverlay:
774 ✗ sync_union_ = new publish::SyncUnionOverlayfs(
775 sync_mediator_,
776 ✗ settings_.transaction().spool_area().readonly_mnt(),
777 ✗ settings_.transaction().spool_area().union_mnt(),
778 ✗ settings_.transaction().spool_area().scratch_dir());
779 ✗ break;
780 ✗ case kUnionFsTarball:
781 ✗ sync_union_ = new publish::SyncUnionTarball(
782 ✗ sync_mediator_,
783 ✗ settings_.transaction().spool_area().readonly_mnt(),
784 // TODO(jblomer): get from settings
785 "tar_file",
786 "base_directory",
787 -1u,
788 -1u,
789 "to_delete",
790 ✗ false /* create_catalog */);
791 ✗ break;
792 ✗ default:
793 ✗ throw EPublish("unknown union file system");
794 }
795 ✗ const bool rvb = sync_union_->Initialize();
796 ✗ if (!rvb) {
797 ✗ delete sync_union_;
798 ✗ sync_union_ = NULL;
799 ✗ throw EPublish("cannot initialize union file system engine");
800 }
801 }
802 }
803
804 ✗ void Publisher::ExitShell() {
805 ✗ const std::string session_dir = Env::GetEnterSessionDir();
806 ✗ const std::string session_pid_tmp = session_dir + "/session_pid";
807 ✗ std::string session_pid;
808 ✗ const int fd_session_pid = open(session_pid_tmp.c_str(), O_RDONLY);
809 ✗ if (fd_session_pid < 0)
810 ✗ throw EPublish("Session pid cannot be retrieved");
811 ✗ SafeReadToString(fd_session_pid, &session_pid);
812
813 ✗ const pid_t pid_child = String2Uint64(session_pid);
814 ✗ kill(pid_child, SIGUSR1);
815 }
816
817 ✗ void Publisher::Sync() {
818 ✗ const ServerLockFileGuard g(is_publishing_);
819
820 ✗ ConstructSyncManagers();
821
822 ✗ sync_union_->Traverse();
823 ✗ bool rvb = sync_mediator_->Commit(manifest_);
824 ✗ if (!rvb)
825 ✗ throw EPublish("cannot write change set to storage");
826
827 ✗ if (!settings_.transaction().dry_run()) {
828 ✗ spooler_files_->WaitForUpload();
829 ✗ spooler_catalogs_->WaitForUpload();
830 ✗ spooler_files_->FinalizeSession(false /* commit */);
831
832 ✗ const std::string old_root_hash = settings_.transaction()
833 ✗ .base_hash()
834 ✗ .ToString(true /* with_suffix */);
835 ✗ const std::string new_root_hash = manifest_->catalog_hash().ToString(
836 ✗ true /* with_suffix */);
837 ✗ rvb = spooler_catalogs_->FinalizeSession(
838 true /* commit */, old_root_hash, new_root_hash,
839 ✗ /* TODO(jblomer) */ sync_parameters_->repo_tag);
840 ✗ if (!rvb)
841 ✗ throw EPublish("failed to commit transaction");
842
843 // Reset to the new catalog root hash
844 ✗ settings_.GetTransaction()->SetBaseHash(manifest_->catalog_hash());
845 // TODO(jblomer): think about how to deal with the scratch area at
846 // this point
847 // WipeScratchArea();
848 }
849
850 ✗ delete sync_union_;
851 ✗ delete sync_mediator_;
852 ✗ delete sync_parameters_;
853 ✗ delete catalog_mgr_;
854 ✗ sync_union_ = NULL;
855 ✗ sync_mediator_ = NULL;
856 ✗ sync_parameters_ = NULL;
857 ✗ catalog_mgr_ = NULL;
858
859 ✗ if (!settings_.transaction().dry_run()) {
860 ✗ LogCvmfs(kLogCvmfs, kLogStdout, "New revision: %" PRIu64,
861 ✗ manifest_->revision());
862 ✗ reflog_->AddCatalog(manifest_->catalog_hash());
863 }
864 }
865
866 ✗ void Publisher::Publish() {
867 ✗ if (!in_transaction_.IsSet())
868 ✗ throw EPublish("cannot publish outside transaction");
869
870 ✗ PushReflog();
871 ✗ PushManifest();
872 ✗ in_transaction_.Clear();
873 }
874
875
876 ✗ void Publisher::MarkReplicatible(bool value) {
877 ✗ ConstructSpoolers();
878
879 ✗ if (value) {
880 ✗ spooler_files_->Upload("/dev/null", "/.cvmfs_master_replica");
881 } else {
882 ✗ spooler_files_->RemoveAsync("/.cvmfs_master_replica");
883 }
884 ✗ spooler_files_->WaitForUpload();
885 ✗ if (spooler_files_->GetNumberOfErrors() > 0)
886 ✗ throw EPublish("cannot set replication mode");
887 }
888
889 ✗ void Publisher::Ingest() { }
890 ✗ void Publisher::Migrate() { }
891 ✗ void Publisher::Resign() { }
892 ✗ void Publisher::Rollback() { }
893 ✗ void Publisher::UpdateMetaInfo() { }
894
895 ✗ void Publisher::Transaction() {
896 ✗ TransactionRetry();
897 ✗ session()->SetKeepAlive(true);
898 }
899
900 //------------------------------------------------------------------------------
901
902
903 ✗ Replica::Replica(const SettingsReplica &settings)
904 ✗ : Repository(SettingsRepository(settings)) { }
905
906
907 ✗ Replica::~Replica() { }
908
909 } // namespace publish
910