GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/publish/repository_transaction.cc
Date: 2026-07-19 02:35:15
Exec Total Coverage
Lines: 0 79 0.0%
Branches: 0 182 0.0%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM File System.
3 */
4
5
6 #include <string>
7
8 #include "backoff.h"
9 #include "catalog_mgr_ro.h"
10 #include "catalog_mgr_rw.h"
11 #include "directory_entry.h"
12 #include "manifest.h"
13 #include "publish/except.h"
14 #include "publish/repository.h"
15 #include "publish/repository_util.h"
16 #include "publish/settings.h"
17 #include "util/exception.h"
18 #include "util/logging.h"
19 #include "util/pointer.h"
20 #include "util/posix.h"
21
22 namespace publish {
23
24
25 void Publisher::TransactionRetry() {
26 if (managed_node_.IsValid()) {
27 const int rvi = managed_node_->Check(false /* is_quiet */);
28 if (rvi != 0)
29 throw EPublish("cannot establish writable mountpoint");
30 }
31
32 BackoffThrottle throttle(500, 5000, 10000);
33 // Negative timeouts (i.e.: no retry) will result in a deadline that has
34 // already passed and thus has the correct effect
35 uint64_t deadline = platform_monotonic_time()
36 + settings_.transaction().GetTimeoutS();
37 if (settings_.transaction().GetTimeoutS() == 0)
38 deadline = uint64_t(-1);
39
40 while (true) {
41 try {
42 TransactionImpl();
43 break;
44 } catch (const publish::EPublish &e) {
45 if (e.failure() != EPublish::kFailTransactionState) {
46 session_->Drop();
47 in_transaction_.Clear();
48 }
49
50 if ((e.failure() == EPublish::kFailTransactionState)
51 || (e.failure() == EPublish::kFailLeaseBusy)) {
52 if (platform_monotonic_time() > deadline)
53 throw;
54
55 LogCvmfs(kLogCvmfs, kLogStdout, "repository busy, retrying");
56 throttle.Throttle();
57 continue;
58 }
59
60 throw;
61 } // try-catch
62 } // while (true)
63
64 if (managed_node_.IsValid())
65 managed_node_->Open();
66 }
67
68
69 void Publisher::TransactionImpl() {
70 if (in_transaction_.IsSet()) {
71 throw EPublish("another transaction is already open",
72 EPublish::kFailTransactionState);
73 }
74
75 InitSpoolArea();
76
77 // On error, Transaction() will release the transaction lock and drop
78 // the session
79 session_->Acquire();
80
81 // Now that the lease is held (the lease subtree is frozen) refresh to the
82 // current HEAD before any HEAD-dependent step. The manifest fetched when this
83 // process started can be stale: another release manager may have advanced HEAD
84 // in the meantime. Refreshing here makes both the lease-path validation below
85 // and the catalog diff at publish time see post-lease HEAD. Without it, a
86 // parent path another publisher just created looks absent (spurious
87 // kFailLeaseNoEntry), and concurrently-added content looks deleted and gets
88 // dropped (#3867). Done on every gateway transaction, not only when waiting on
89 // a busy lease -- staleness is independent of contention. DownloadRootObjects
90 // also invalidates the cached read-only catalog manager; Check() remounts the
91 // read-only layer only if outdated; managed_node_ is absent for mount-less
92 // publishing.
93 if (settings_.storage().type() == upload::SpoolerDefinition::Gateway) {
94 DownloadRootObjects(settings_.url(), settings_.fqrn(),
95 settings_.transaction().spool_area().tmp_dir());
96 if (managed_node_.IsValid()) {
97 const int rvi = managed_node_->Check(true /* is_quiet */);
98 if (rvi != 0)
99 throw EPublish("cannot establish writable mountpoint");
100 }
101 }
102
103 // We might have a valid lease for a non-existing path. Nevertheless, we run
104 // run into problems when merging catalogs later, so for the time being we
105 // disallow transactions on non-existing paths.
106 if (!settings_.transaction().lease_path().empty()) {
107 const std::string path = GetParentPath(
108 "/" + settings_.transaction().lease_path());
109 catalog::SimpleCatalogManager *catalog_mgr = GetSimpleCatalogManager();
110 catalog::DirectoryEntry dirent;
111 const bool retval = catalog_mgr->LookupPath(path, catalog::kLookupDefault,
112 &dirent);
113 if (!retval) {
114 throw EPublish("cannot open transaction on non-existing path " + path,
115 EPublish::kFailLeaseNoEntry);
116 }
117 if (!dirent.IsDirectory()) {
118 throw EPublish(
119 "cannot open transaction on " + path + ", which is not a directory",
120 EPublish::kFailLeaseNoDir);
121 }
122 }
123
124 const UniquePtr<CheckoutMarker> marker(CheckoutMarker::CreateFrom(
125 settings_.transaction().spool_area().checkout_marker()));
126
127 in_transaction_.Set();
128 // Pre-create the publishing lock file so that Abort() can acquire it even
129 // if the disk fills up before abort is called.
130 is_publishing_.Touch();
131 ConstructSpoolers();
132 if (marker.IsValid())
133 settings_.GetTransaction()->SetBaseHash(marker->hash());
134 else
135 settings_.GetTransaction()->SetBaseHash(manifest_->catalog_hash());
136
137 if (settings_.transaction().HasTemplate()) {
138 LogCvmfs(kLogCvmfs, llvl_ | kLogStdout | kLogNoLinebreak,
139 "CernVM-FS: cloning template %s --> %s ... ",
140 settings_.transaction().template_from().c_str(),
141 settings_.transaction().template_to().c_str());
142 ConstructSyncManagers();
143
144 try {
145 catalog_mgr_->CloneTree(settings_.transaction().template_from(),
146 settings_.transaction().template_to());
147 } catch (const ECvmfsException &e) {
148 const std::string panic_msg = e.what();
149 in_transaction_.Clear();
150 // TODO(aandvalenzuela): release session token (gateway publishing)
151 throw publish::EPublish("cannot clone directory tree. " + panic_msg,
152 publish::EPublish::kFailInput);
153 }
154
155 Sync();
156 SendTalkCommand(
157 settings_.transaction().spool_area().readonly_talk_socket(),
158 "chroot " + settings_.transaction().base_hash().ToString() + "\n");
159 LogCvmfs(kLogCvmfs, llvl_ | kLogStdout, "[done]");
160 // TODO(jblomer): fix-me
161 // PushReflog();
162 }
163
164 LogCvmfs(kLogCvmfs, llvl_ | kLogDebug | kLogSyslog, "(%s) opened transaction",
165 settings_.fqrn().c_str());
166 }
167
168 } // namespace publish
169