GCC Code Coverage Report


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