GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/publish/repository_transaction.cc
Date: 2026-08-30 02:40:36
Exec Total Coverage
Lines: 0 96 0.0%
Branches: 0 222 0.0%

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