GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/publish/repository.cc
Date: 2026-07-19 02:35:15
Exec Total Coverage
Lines: 0 568 0.0%
Branches: 0 1183 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
12 #include "catalog_mgr_ro.h"
13 #include "catalog_mgr_rw.h"
14 #include "crypto/hash.h"
15 #include "crypto/signature.h"
16 #include "gateway_util.h"
17 #include "history_sqlite.h"
18 #include "ingestion/ingestion_source.h"
19 #include "manifest.h"
20 #include "manifest_fetch.h"
21 #include "network/download.h"
22 #include "network/sink_file.h"
23 #include "network/sink_mem.h"
24 #include "publish/except.h"
25 #include "publish/repository_util.h"
26 #include "publish/settings.h"
27 #include "reflog.h"
28 #include "statistics.h"
29 #include "sync_mediator.h"
30 #include "sync_union_aufs.h"
31 #include "sync_union_overlayfs.h"
32 #include "sync_union_tarball.h"
33 #include "upload.h"
34 #include "upload_spooler_definition.h"
35 #include "util/logging.h"
36 #include "util/pointer.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,
278 statistics_publish_.weak_ref());
279 if (spooler_files_ == NULL)
280 throw EPublish("could not initialize file spooler");
281
282 const upload::SpoolerDefinition sd_catalogs(sd.Dup2DefaultCompression());
283 spooler_catalogs_ = upload::Spooler::Construct(
284 sd_catalogs, statistics_publish_.weak_ref());
285 if (spooler_catalogs_ == NULL) {
286 delete spooler_files_;
287 throw EPublish("could not initialize catalog spooler");
288 }
289 }
290
291
292 void Publisher::CreateKeychain() {
293 if (settings_.keychain().HasDanglingMasterKeys()) {
294 throw EPublish("dangling master key pair");
295 }
296 if (settings_.keychain().HasDanglingRepositoryKeys()) {
297 throw EPublish("dangling repository keys");
298 }
299 if (!settings_.keychain().HasMasterKeys())
300 signature_mgr_->GenerateMasterKeyPair();
301 if (!settings_.keychain().HasRepositoryKeys())
302 signature_mgr_->GenerateCertificate(settings_.fqrn());
303
304 whitelist_ = new whitelist::Whitelist(settings_.fqrn(), NULL, signature_mgr_);
305 const std::string whitelist_str = whitelist::Whitelist::CreateString(
306 settings_.fqrn(), settings_.whitelist_validity_days(),
307 settings_.transaction().hash_algorithm(), signature_mgr_);
308 const whitelist::Failures rv_wl = whitelist_->LoadMem(whitelist_str);
309 if (rv_wl != whitelist::kFailOk)
310 throw EPublish("whitelist generation failed");
311 }
312
313
314 void Publisher::CreateRootObjects() {
315 // Reflog
316 const std::string reflog_path = CreateTempPath(
317 settings_.transaction().spool_area().tmp_dir() + "/cvmfs_reflog", 0600);
318 reflog_ = manifest::Reflog::Create(reflog_path, settings_.fqrn());
319 if (reflog_ == NULL)
320 throw EPublish("could not create reflog");
321 reflog_->TakeDatabaseFileOwnership();
322
323 // Root file catalog and initial manifest
324 manifest_ = catalog::WritableCatalogManager::CreateRepository(
325 settings_.transaction().spool_area().tmp_dir(),
326 settings_.transaction().is_volatile(),
327 settings_.transaction().voms_authz(),
328 spooler_catalogs_);
329 spooler_catalogs_->WaitForUpload();
330 if (manifest_ == NULL)
331 throw EPublish("could not create initial file catalog");
332 reflog_->AddCatalog(manifest_->catalog_hash());
333
334 manifest_->set_repository_name(settings_.fqrn());
335 manifest_->set_ttl(settings_.transaction().ttl_second());
336 const bool
337 needs_bootstrap_shortcuts = !settings_.transaction().voms_authz().empty();
338 manifest_->set_has_alt_catalog_path(needs_bootstrap_shortcuts);
339 manifest_->set_garbage_collectability(
340 settings_.transaction().is_garbage_collectable());
341
342 // Tag database
343 const std::string tags_path = CreateTempPath(
344 settings_.transaction().spool_area().tmp_dir() + "/cvmfs_tags", 0600);
345 history_ = history::SqliteHistory::Create(tags_path, settings_.fqrn());
346 if (history_ == NULL)
347 throw EPublish("could not create tag database");
348 history_->TakeDatabaseFileOwnership();
349 const history::History::Tag tag_trunk(
350 "trunk", manifest_->catalog_hash(), manifest_->catalog_size(),
351 manifest_->revision(), manifest_->publish_timestamp(), "empty repository",
352 "" /* branch */);
353 history_->Insert(tag_trunk);
354
355 // Meta information, TODO(jblomer)
356 meta_info_ = "{}";
357 }
358
359
360 void Publisher::CreateStorage() {
361 ConstructSpoolers();
362 if (!spooler_files_->Create())
363 throw EPublish("could not initialize repository storage area");
364 }
365
366
367 void Publisher::PushCertificate() {
368 upload::Spooler::CallbackPtr callback = spooler_files_->RegisterListener(
369 &Publisher::OnProcessCertificate, this);
370 spooler_files_->ProcessCertificate(
371 new StringIngestionSource(signature_mgr_->GetCertificate()));
372 spooler_files_->WaitForUpload();
373 spooler_files_->UnregisterListener(callback);
374 }
375
376
377 void Publisher::PushHistory() {
378 assert(history_ != NULL);
379 history_->SetPreviousRevision(manifest_->history());
380 const string history_path = history_->filename();
381 history_->DropDatabaseFileOwnership();
382 delete history_;
383
384 upload::Spooler::CallbackPtr callback = spooler_files_->RegisterListener(
385 &Publisher::OnProcessHistory, this);
386 spooler_files_->ProcessHistory(history_path);
387 spooler_files_->WaitForUpload();
388 spooler_files_->UnregisterListener(callback);
389
390 history_ = history::SqliteHistory::OpenWritable(history_path);
391 assert(history_ != NULL);
392 history_->TakeDatabaseFileOwnership();
393 }
394
395
396 void Publisher::PushMetainfo() {
397 upload::Spooler::CallbackPtr callback = spooler_files_->RegisterListener(
398 &Publisher::OnProcessMetainfo, this);
399 spooler_files_->ProcessMetainfo(new StringIngestionSource(meta_info_));
400 spooler_files_->WaitForUpload();
401 spooler_files_->UnregisterListener(callback);
402 }
403
404
405 void Publisher::PushManifest() {
406 std::string signed_manifest = manifest_->ExportString();
407 shash::Any manifest_hash(settings_.transaction().hash_algorithm());
408 shash::HashMem(
409 reinterpret_cast<const unsigned char *>(signed_manifest.data()),
410 signed_manifest.length(), &manifest_hash);
411 signed_manifest += "--\n" + manifest_hash.ToString() + "\n";
412 unsigned char *signature;
413 unsigned sig_size;
414 bool rvb = signature_mgr_->Sign(
415 reinterpret_cast<const unsigned char *>(manifest_hash.ToString().data()),
416 manifest_hash.GetHexSize(), &signature, &sig_size);
417 if (!rvb)
418 throw EPublish("cannot sign manifest");
419 signed_manifest += std::string(reinterpret_cast<char *>(signature), sig_size);
420 free(signature);
421
422 // Create alternative bootstrapping symlinks for VOMS secured repos
423 if (manifest_->has_alt_catalog_path()) {
424 rvb = spooler_files_->PlaceBootstrappingShortcut(manifest_->certificate())
425 && spooler_files_->PlaceBootstrappingShortcut(
426 manifest_->catalog_hash())
427 && (manifest_->history().IsNull()
428 || spooler_files_->PlaceBootstrappingShortcut(
429 manifest_->history()))
430 && (manifest_->meta_info().IsNull()
431 || spooler_files_->PlaceBootstrappingShortcut(
432 manifest_->meta_info()));
433 if (!rvb)
434 EPublish("cannot place VOMS bootstrapping symlinks");
435 }
436
437 upload::Spooler::CallbackPtr callback = spooler_files_->RegisterListener(
438 &Publisher::OnUploadManifest, this);
439 spooler_files_->Upload(".cvmfspublished",
440 new StringIngestionSource(signed_manifest));
441 spooler_files_->WaitForUpload();
442 spooler_files_->UnregisterListener(callback);
443 }
444
445
446 void Publisher::PushReflog() {
447 const string reflog_path = reflog_->database_file();
448 reflog_->DropDatabaseFileOwnership();
449 delete reflog_;
450
451 shash::Any hash_reflog(settings_.transaction().hash_algorithm());
452 manifest::Reflog::HashDatabase(reflog_path, &hash_reflog);
453
454 upload::Spooler::CallbackPtr callback = spooler_files_->RegisterListener(
455 &Publisher::OnUploadReflog, this);
456 spooler_files_->UploadReflog(reflog_path);
457 spooler_files_->WaitForUpload();
458 spooler_files_->UnregisterListener(callback);
459
460 manifest_->set_reflog_hash(hash_reflog);
461
462 reflog_ = manifest::Reflog::Open(reflog_path);
463 assert(reflog_ != NULL);
464 reflog_->TakeDatabaseFileOwnership();
465 }
466
467
468 void Publisher::PushWhitelist() {
469 // TODO(jblomer): PKCS7 handling
470 upload::Spooler::CallbackPtr callback = spooler_files_->RegisterListener(
471 &Publisher::OnUploadWhitelist, this);
472 spooler_files_->Upload(".cvmfswhitelist",
473 new StringIngestionSource(whitelist_->ExportString()));
474 spooler_files_->WaitForUpload();
475 spooler_files_->UnregisterListener(callback);
476 }
477
478
479 Publisher *Publisher::Create(const SettingsPublisher &settings) {
480 UniquePtr<Publisher> publisher(new Publisher(settings, false));
481
482 LogCvmfs(kLogCvmfs, publisher->llvl_ | kLogStdout | kLogNoLinebreak,
483 "Creating Key Chain... ");
484 publisher->CreateKeychain();
485 publisher->ExportKeychain();
486 LogCvmfs(kLogCvmfs, publisher->llvl_ | kLogStdout, "done");
487
488 LogCvmfs(kLogCvmfs, publisher->llvl_ | kLogStdout | kLogNoLinebreak,
489 "Creating Backend Storage... ");
490 publisher->CreateStorage();
491 publisher->PushWhitelist();
492 LogCvmfs(kLogCvmfs, publisher->llvl_ | kLogStdout, "done");
493
494 LogCvmfs(kLogCvmfs, publisher->llvl_ | kLogStdout | kLogNoLinebreak,
495 "Creating Initial Repository... ");
496 publisher->InitSpoolArea();
497 publisher->CreateRootObjects();
498 publisher->PushHistory();
499 publisher->PushCertificate();
500 publisher->PushMetainfo();
501 publisher->PushReflog();
502 publisher->PushManifest();
503 // TODO(jblomer): meta-info
504
505 // Re-create from empty repository in order to properly initialize
506 // parent Repository object
507 publisher = new Publisher(settings);
508
509 LogCvmfs(kLogCvmfs, publisher->llvl_ | kLogStdout, "done");
510
511 return publisher.Release();
512 }
513
514 void Publisher::ExportKeychain() {
515 CreateDirectoryAsOwner(settings_.keychain().keychain_dir(), kDefaultDirMode);
516
517 bool rvb;
518 rvb = SafeWriteToFile(signature_mgr_->GetActivePubkeys(),
519 settings_.keychain().master_public_key_path(), 0644);
520 if (!rvb)
521 throw EPublish("cannot export public master key");
522 rvb = SafeWriteToFile(signature_mgr_->GetCertificate(),
523 settings_.keychain().certificate_path(), 0644);
524 if (!rvb)
525 throw EPublish("cannot export certificate");
526
527 rvb = SafeWriteToFile(signature_mgr_->GetPrivateKey(),
528 settings_.keychain().private_key_path(), 0600);
529 if (!rvb)
530 throw EPublish("cannot export private certificate key");
531 rvb = SafeWriteToFile(signature_mgr_->GetPrivateMasterKey(),
532 settings_.keychain().master_private_key_path(), 0600);
533 if (!rvb)
534 throw EPublish("cannot export private master key");
535
536 int rvi;
537 rvi = chown(settings_.keychain().master_public_key_path().c_str(),
538 settings_.owner_uid(), settings_.owner_gid());
539 if (rvi != 0)
540 throw EPublish("cannot set key file ownership");
541 rvi = chown(settings_.keychain().certificate_path().c_str(),
542 settings_.owner_uid(), settings_.owner_gid());
543 if (rvi != 0)
544 throw EPublish("cannot set key file ownership");
545 rvi = chown(settings_.keychain().private_key_path().c_str(),
546 settings_.owner_uid(), settings_.owner_gid());
547 if (rvi != 0)
548 throw EPublish("cannot set key file ownership");
549 rvi = chown(settings_.keychain().master_private_key_path().c_str(),
550 settings_.owner_uid(), settings_.owner_gid());
551 if (rvi != 0)
552 throw EPublish("cannot set key file ownership");
553 }
554
555 void Publisher::OnProcessCertificate(const upload::SpoolerResult &result) {
556 if (result.return_code != 0) {
557 throw EPublish("cannot write certificate to storage");
558 }
559 manifest_->set_certificate(result.content_hash);
560 reflog_->AddCertificate(result.content_hash);
561 }
562
563 void Publisher::OnProcessHistory(const upload::SpoolerResult &result) {
564 if (result.return_code != 0) {
565 throw EPublish("cannot write tag database to storage");
566 }
567 manifest_->set_history(result.content_hash);
568 reflog_->AddHistory(result.content_hash);
569 }
570
571 void Publisher::OnProcessMetainfo(const upload::SpoolerResult &result) {
572 if (result.return_code != 0) {
573 throw EPublish("cannot write repository meta info to storage");
574 }
575 manifest_->set_meta_info(result.content_hash);
576 reflog_->AddMetainfo(result.content_hash);
577 }
578
579 void Publisher::OnUploadManifest(const upload::SpoolerResult &result) {
580 if (result.return_code != 0) {
581 throw EPublish("cannot write manifest to storage");
582 }
583 }
584
585 void Publisher::OnUploadReflog(const upload::SpoolerResult &result) {
586 if (result.return_code != 0) {
587 throw EPublish("cannot write reflog to storage");
588 }
589 }
590
591 void Publisher::OnUploadWhitelist(const upload::SpoolerResult &result) {
592 if (result.return_code != 0) {
593 throw EPublish("cannot write whitelist to storage");
594 }
595 }
596
597 void Publisher::CreateDirectoryAsOwner(const std::string &path, int mode) {
598 const bool rvb = MkdirDeep(path, mode);
599 if (!rvb)
600 throw EPublish("cannot create directory " + path);
601 const int rvi = chown(path.c_str(), settings_.owner_uid(),
602 settings_.owner_gid());
603 if (rvi != 0)
604 throw EPublish("cannot set ownership on directory " + path);
605 }
606
607 void Publisher::InitSpoolArea() {
608 CreateDirectoryAsOwner(settings_.transaction().spool_area().workspace(),
609 kPrivateDirMode);
610 CreateDirectoryAsOwner(settings_.transaction().spool_area().tmp_dir(),
611 kPrivateDirMode);
612 CreateDirectoryAsOwner(settings_.transaction().spool_area().cache_dir(),
613 kPrivateDirMode);
614 CreateDirectoryAsOwner(settings_.transaction().spool_area().scratch_dir(),
615 kDefaultDirMode);
616 CreateDirectoryAsOwner(settings_.transaction().spool_area().ovl_work_dir(),
617 kPrivateDirMode);
618
619 // On a managed node, the mount points are already mounted
620 if (!DirectoryExists(settings_.transaction().spool_area().readonly_mnt())) {
621 CreateDirectoryAsOwner(settings_.transaction().spool_area().readonly_mnt(),
622 kDefaultDirMode);
623 }
624 if (!DirectoryExists(settings_.transaction().spool_area().union_mnt())) {
625 CreateDirectoryAsOwner(settings_.transaction().spool_area().union_mnt(),
626 kDefaultDirMode);
627 }
628 }
629
630 Publisher::Publisher(const SettingsPublisher &settings, const bool exists)
631 : Repository(SettingsRepository(settings), exists)
632 , settings_(settings)
633 , statistics_publish_(new perf::StatisticsTemplate("publish", statistics_))
634 , llvl_(settings.is_silent() ? kLogNone : kLogNormal)
635 , in_transaction_(settings.transaction().spool_area().transaction_lock())
636 , is_publishing_(settings.transaction().spool_area().publishing_lock())
637 , spooler_files_(NULL)
638 , spooler_catalogs_(NULL)
639 , catalog_mgr_(NULL)
640 , sync_parameters_(NULL)
641 , sync_mediator_(NULL)
642 , sync_union_(NULL) {
643 if (settings.transaction().layout_revision() != kRequiredLayoutRevision) {
644 const unsigned layout_revision = settings.transaction().layout_revision();
645 throw EPublish("This repository uses layout revision "
646 + StringifyInt(layout_revision)
647 + ".\n"
648 "This version of CernVM-FS requires layout revision "
649 + StringifyInt(kRequiredLayoutRevision)
650 + ", which is\n"
651 "incompatible to "
652 + StringifyInt(layout_revision)
653 + ".\n\n"
654 "Please run `cvmfs_server migrate` to update your "
655 "repository before "
656 "proceeding.",
657 EPublish::kFailLayoutRevision);
658 }
659
660 // Session and managed node are needed even when skipping downloads (e.g.
661 // for abort under disk-full conditions), so initialize them before the
662 // early return below.
663 if (settings.is_managed())
664 managed_node_ = new ManagedNode(this);
665 session_ = new Session(settings_, llvl_);
666
667 if (!exists)
668 return;
669
670 CreateDirectoryAsOwner(settings_.transaction().spool_area().tmp_dir(),
671 kPrivateDirMode);
672
673 if (settings.storage().type() == upload::SpoolerDefinition::Gateway) {
674 if (!settings.keychain().HasGatewayKey()) {
675 throw EPublish("gateway key missing: "
676 + settings.keychain().gw_key_path());
677 }
678 gw_key_ = gateway::ReadGatewayKey(settings.keychain().gw_key_path());
679 if (!gw_key_.IsValid()) {
680 throw EPublish("cannot read gateway key: "
681 + settings.keychain().gw_key_path());
682 }
683 }
684
685 if ((settings.storage().type() != upload::SpoolerDefinition::Gateway)
686 && !settings.transaction().in_enter_session()) {
687 int rvb = signature_mgr_->LoadCertificatePath(
688 settings.keychain().certificate_path());
689 if (!rvb)
690 throw EPublish("cannot load certificate, thus cannot commit changes");
691 rvb = signature_mgr_->LoadPrivateKeyPath(
692 settings.keychain().private_key_path(), "");
693 if (!rvb)
694 throw EPublish("cannot load private key, thus cannot commit changes");
695 // The private master key might be on a key card instead
696 if (FileExists(settings.keychain().master_private_key_path())) {
697 rvb = signature_mgr_->LoadPrivateMasterKeyPath(
698 settings.keychain().master_private_key_path());
699 if (!rvb)
700 throw EPublish("cannot load private master key");
701 }
702 if (!signature_mgr_->KeysMatch())
703 throw EPublish("corrupted keychain");
704 }
705
706 if (in_transaction_.IsSet())
707 ConstructSpoolers();
708 }
709
710 Publisher::~Publisher() {
711 delete sync_union_;
712 delete sync_mediator_;
713 delete sync_parameters_;
714 delete catalog_mgr_;
715 delete spooler_catalogs_;
716 delete spooler_files_;
717 }
718
719
720 void Publisher::ConstructSyncManagers() {
721 ConstructSpoolers();
722
723 if (catalog_mgr_ == NULL) {
724 catalog_mgr_ = new catalog::WritableCatalogManager(
725 settings_.transaction().base_hash(),
726 settings_.url(),
727 settings_.transaction().spool_area().tmp_dir(),
728 spooler_catalogs_,
729 download_mgr_,
730 settings_.transaction().enforce_limits(),
731 settings_.transaction().limit_nested_catalog_kentries(),
732 settings_.transaction().limit_root_catalog_kentries(),
733 settings_.transaction().limit_file_size_mb(),
734 statistics_,
735 settings_.transaction().use_catalog_autobalance(),
736 settings_.transaction().autobalance_max_weight(),
737 settings_.transaction().autobalance_min_weight(),
738 "");
739 catalog_mgr_->Init();
740 }
741
742 if (sync_parameters_ == NULL) {
743 SyncParameters *p = new SyncParameters();
744 p->spooler = spooler_files_;
745 p->repo_name = settings_.fqrn();
746 p->dir_union = settings_.transaction().spool_area().union_mnt();
747 p->dir_scratch = settings_.transaction().spool_area().scratch_dir();
748 p->dir_rdonly = settings_.transaction().spool_area().readonly_mnt();
749 p->dir_temp = settings_.transaction().spool_area().tmp_dir();
750 p->base_hash = settings_.transaction().base_hash();
751 p->stratum0 = settings_.url();
752 // p->manifest_path = SHOULD NOT BE NEEDED
753 // p->spooler_definition = SHOULD NOT BE NEEDED;
754 // p->union_fs_type = SHOULD NOT BE NEEDED
755 p->print_changeset = settings_.transaction().print_changeset();
756 p->dry_run = settings_.transaction().dry_run();
757 sync_parameters_ = p;
758 }
759
760 if (sync_mediator_ == NULL) {
761 sync_mediator_ = new SyncMediator(catalog_mgr_, sync_parameters_,
762 *statistics_publish_);
763 }
764
765 if (sync_union_ == NULL) {
766 switch (settings_.transaction().union_fs()) {
767 case kUnionFsAufs:
768 sync_union_ = new publish::SyncUnionAufs(
769 sync_mediator_,
770 settings_.transaction().spool_area().readonly_mnt(),
771 settings_.transaction().spool_area().union_mnt(),
772 settings_.transaction().spool_area().scratch_dir());
773 break;
774 case kUnionFsOverlay:
775 sync_union_ = new publish::SyncUnionOverlayfs(
776 sync_mediator_,
777 settings_.transaction().spool_area().readonly_mnt(),
778 settings_.transaction().spool_area().union_mnt(),
779 settings_.transaction().spool_area().scratch_dir());
780 break;
781 case kUnionFsTarball:
782 sync_union_ = new publish::SyncUnionTarball(
783 sync_mediator_,
784 settings_.transaction().spool_area().readonly_mnt(),
785 // TODO(jblomer): get from settings
786 "tar_file",
787 "base_directory",
788 -1u,
789 -1u,
790 "to_delete",
791 false /* create_catalog */);
792 break;
793 default:
794 throw EPublish("unknown union file system");
795 }
796 const bool rvb = sync_union_->Initialize();
797 if (!rvb) {
798 delete sync_union_;
799 sync_union_ = NULL;
800 throw EPublish("cannot initialize union file system engine");
801 }
802 }
803 }
804
805 void Publisher::ExitShell() {
806 const std::string session_dir = Env::GetEnterSessionDir();
807 const std::string session_pid_tmp = session_dir + "/session_pid";
808 std::string session_pid;
809 const int fd_session_pid = open(session_pid_tmp.c_str(), O_RDONLY);
810 if (fd_session_pid < 0)
811 throw EPublish("Session pid cannot be retrieved");
812 SafeReadToString(fd_session_pid, &session_pid);
813
814 const pid_t pid_child = String2Uint64(session_pid);
815 kill(pid_child, SIGUSR1);
816 }
817
818 void Publisher::Sync() {
819 const ServerLockFileGuard g(is_publishing_);
820
821 ConstructSyncManagers();
822
823 sync_union_->Traverse();
824 bool rvb = sync_mediator_->Commit(manifest_);
825 if (!rvb)
826 throw EPublish("cannot write change set to storage");
827
828 if (!settings_.transaction().dry_run()) {
829 spooler_files_->WaitForUpload();
830 spooler_catalogs_->WaitForUpload();
831 spooler_files_->FinalizeSession(false /* commit */);
832
833 const std::string old_root_hash = settings_.transaction()
834 .base_hash()
835 .ToString(true /* with_suffix */);
836 const std::string new_root_hash = manifest_->catalog_hash().ToString(
837 true /* with_suffix */);
838 rvb = spooler_catalogs_->FinalizeSession(
839 true /* commit */, old_root_hash, new_root_hash,
840 /* TODO(jblomer) */ sync_parameters_->repo_tag);
841 if (!rvb)
842 throw EPublish("failed to commit transaction");
843
844 // Reset to the new catalog root hash
845 settings_.GetTransaction()->SetBaseHash(manifest_->catalog_hash());
846 // TODO(jblomer): think about how to deal with the scratch area at
847 // this point
848 // WipeScratchArea();
849 }
850
851 delete sync_union_;
852 delete sync_mediator_;
853 delete sync_parameters_;
854 delete catalog_mgr_;
855 sync_union_ = NULL;
856 sync_mediator_ = NULL;
857 sync_parameters_ = NULL;
858 catalog_mgr_ = NULL;
859
860 if (!settings_.transaction().dry_run()) {
861 LogCvmfs(kLogCvmfs, kLogStdout, "New revision: %" PRIu64,
862 manifest_->revision());
863 reflog_->AddCatalog(manifest_->catalog_hash());
864 }
865 }
866
867 void Publisher::Publish() {
868 if (!in_transaction_.IsSet())
869 throw EPublish("cannot publish outside transaction");
870
871 PushReflog();
872 PushManifest();
873 in_transaction_.Clear();
874 }
875
876
877 void Publisher::MarkReplicatible(bool value) {
878 ConstructSpoolers();
879
880 if (value) {
881 spooler_files_->Upload("/dev/null", "/.cvmfs_master_replica");
882 } else {
883 spooler_files_->RemoveAsync("/.cvmfs_master_replica");
884 }
885 spooler_files_->WaitForUpload();
886 if (spooler_files_->GetNumberOfErrors() > 0)
887 throw EPublish("cannot set replication mode");
888 }
889
890 void Publisher::Ingest() { }
891 void Publisher::Migrate() { }
892 void Publisher::Resign() { }
893 void Publisher::Rollback() { }
894 void Publisher::UpdateMetaInfo() { }
895
896 void Publisher::Transaction() {
897 TransactionRetry();
898 session()->SetKeepAlive(true);
899 }
900
901 //------------------------------------------------------------------------------
902
903
904 Replica::Replica(const SettingsReplica &settings)
905 : Repository(SettingsRepository(settings)) { }
906
907
908 Replica::~Replica() { }
909
910 } // namespace publish
911