GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/publish/repository.cc
Date: 2026-09-06 02:40:30
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