GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/publish/repository_managed.cc
Date: 2026-10-04 02:40:33
Exec Total Coverage
Lines: 0 230 0.0%
Branches: 0 369 0.0%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM File System.
3 */
4
5
6 #include <cstdio>
7 #include <memory>
8
9 #include "crypto/hash.h"
10 #include "manifest.h"
11 #include "publish/except.h"
12 #include "publish/repository.h"
13 #include "publish/repository_util.h"
14 #include "util/posix.h"
15 #include "util/string.h"
16
17
18 namespace publish {
19
20 ✗ void Publisher::ManagedNode::Open() {
21 ✗ AlterMountpoint(kAlterUnionOpen, kLogSyslog);
22 }
23
24
25 ✗ void Publisher::ManagedNode::Lock() {
26 ✗ AlterMountpoint(kAlterUnionLock, kLogSyslog);
27 }
28
29
30 ✗ void Publisher::ManagedNode::Unmount() {
31 try {
32 ✗ AlterMountpoint(kAlterUnionUnmount, kLogSyslog);
33 ✗ } catch (const EPublish &e) {
34 ✗ AlterMountpoint(kAlterUnionLazyUnmount, kLogSyslog);
35 ✗ AlterMountpoint(kAlterRdOnlyKillUnmount, kLogSyslog);
36 ✗ AlterMountpoint(kAlterRdOnlyLazyUnmount, kLogSyslog);
37 ✗ return;
38 }
39
40 try {
41 ✗ AlterMountpoint(kAlterRdOnlyUnmount, kLogSyslog);
42 ✗ } catch (const EPublish &e) {
43 ✗ AlterMountpoint(kAlterRdOnlyKillUnmount, kLogSyslog);
44 ✗ AlterMountpoint(kAlterRdOnlyLazyUnmount, kLogSyslog);
45 }
46 }
47
48 ✗ void Publisher::ManagedNode::Mount() {
49 ✗ AlterMountpoint(kAlterRdOnlyMount, kLogSyslog);
50 ✗ AlterMountpoint(kAlterUnionMount, kLogSyslog);
51 }
52
53 ✗ void Publisher::ManagedNode::ClearScratch() {
54 ✗ const std::string scratch_dir = publisher_->settings_.transaction()
55 ✗ .spool_area()
56 ✗ .scratch_dir();
57 ✗ const std::string scratch_wastebin = publisher_->settings_.transaction()
58 ✗ .spool_area()
59 ✗ .scratch_wastebin();
60 const std::string
61 ✗ tmp_dir = publisher_->settings_.transaction().spool_area().tmp_dir();
62
63 ✗ const std::string waste_dir = CreateTempDir(scratch_wastebin + "/waste");
64 ✗ if (!waste_dir.empty()) {
65 // Normal case: move scratch contents to wastebin for async cleanup.
66 ✗ const int rvi = rename(scratch_dir.c_str(),
67 ✗ (waste_dir + "/delete-me").c_str());
68 ✗ if (rvi != 0)
69 ✗ throw EPublish("cannot move scratch directory to wastebin");
70 ✗ publisher_->CreateDirectoryAsOwner(scratch_dir, kDefaultDirMode);
71 ✗ AlterMountpoint(kAlterScratchWipe, kLogSyslog);
72 } else {
73 // Disk full: cannot create a wastebin directory. Clean scratch/current
74 // in place synchronously instead (deletion does not require free space).
75 ✗ RunSuidHelper("clear_scratch", publisher_->settings_.fqrn());
76 // scratch_dir still exists and is now empty; chown without allocating
77 // space. AlterMountpoint(kAlterScratchWipe) is intentionally skipped: it
78 // runs clear_scratch_async to clean the wastebin, but no wastebin was
79 // created in this path so there is nothing to clean asynchronously.
80 ✗ publisher_->CreateDirectoryAsOwner(scratch_dir, kDefaultDirMode);
81 }
82
83 ✗ std::vector<mode_t> modes;
84 ✗ std::vector<std::string> names;
85 ✗ ListDirectory(tmp_dir, &names, &modes);
86 ✗ for (unsigned i = 0; i < names.size(); ++i) {
87 ✗ if (HasPrefix(names[i], "receiver.", false /* ignore_case */))
88 ✗ continue;
89
90 ✗ unlink((tmp_dir + "/" + names[i]).c_str());
91 }
92 }
93
94
95 ✗ int Publisher::ManagedNode::Check(bool is_quiet) {
96 ✗ const ServerLockFileCheck publish_check(publisher_->is_publishing_);
97 ✗ const std::string rdonly_mnt = publisher_->settings_.transaction()
98 ✗ .spool_area()
99 ✗ .readonly_mnt();
100 const std::string
101 ✗ union_mnt = publisher_->settings_.transaction().spool_area().union_mnt();
102 ✗ const std::string fqrn = publisher_->settings_.fqrn();
103 ✗ const EUnionMountRepairMode repair_mode = publisher_->settings_.transaction()
104 ✗ .spool_area()
105 ✗ .repair_mode();
106
107 ✗ int result = kFailOk;
108
109 // expected_hash is left null when manifest is not available (e.g. abort
110 // with exists=false), in which case the root hash comparison is skipped.
111 const std::unique_ptr<CheckoutMarker> marker(CheckoutMarker::CreateFrom(
112 ✗ publisher_->settings_.transaction().spool_area().checkout_marker()));
113 ✗ shash::Any expected_hash;
114 ✗ if (publisher_->manifest() != NULL)
115 ✗ expected_hash = publisher_->manifest()->catalog_hash();
116 ✗ if (marker.get() != nullptr)
117 ✗ expected_hash = marker->hash();
118
119 ✗ if (!IsMountPoint(rdonly_mnt)) {
120 ✗ result |= kFailRdOnlyBroken;
121 } else {
122 ✗ const std::string root_hash_xattr = "user.root_hash";
123 ✗ std::string root_hash_str;
124 ✗ const bool retval = platform_getxattr(rdonly_mnt, root_hash_xattr,
125 &root_hash_str);
126 ✗ if (retval) {
127 ✗ if (!expected_hash.IsNull()) {
128 ✗ const shash::Any root_hash = shash::MkFromHexPtr(
129 shash::HexPtr(root_hash_str), shash::kSuffixCatalog);
130 ✗ if (expected_hash != root_hash) {
131 ✗ if (marker.get() != nullptr) {
132 ✗ result |= kFailRdOnlyWrongRevision;
133 } else {
134 ✗ result |= kFailRdOnlyOutdated;
135 }
136 }
137 }
138 } else {
139 ✗ if (errno == ENOTCONN) {
140 // Fuse module crashed
141 ✗ result |= kFailRdOnlyBroken;
142 } else {
143 ✗ throw EPublish("cannot retrieve root hash from read-only mount point");
144 }
145 }
146 }
147
148 // The process that opens the transaction does not stay alive for the life
149 // time of the transaction
150 ✗ if (!IsMountPoint(union_mnt)) {
151 ✗ result |= kFailUnionBroken;
152 } else {
153 ✗ const FileSystemInfo fs_info = GetFileSystemInfo(union_mnt);
154 ✗ if (publisher_->in_transaction_.IsSet() && fs_info.is_rdonly)
155 ✗ result |= kFailUnionLocked;
156 ✗ if (!publisher_->in_transaction_.IsSet() && !fs_info.is_rdonly)
157 ✗ result |= kFailUnionWritable;
158 }
159
160 ✗ if (result == kFailOk)
161 ✗ return result;
162
163 // Report & Repair
164
165 ✗ int logFlags = kLogStderr;
166 ✗ if (is_quiet)
167 ✗ logFlags |= kLogNone;
168 ✗ if (result & kFailRdOnlyBroken) {
169 ✗ LogCvmfs(kLogCvmfs, logFlags, "%s is not mounted properly",
170 rdonly_mnt.c_str());
171 }
172 ✗ if (result & kFailRdOnlyOutdated) {
173 ✗ LogCvmfs(kLogCvmfs, logFlags,
174 "%s is not based on the newest published revision", fqrn.c_str());
175 }
176 ✗ if (result & kFailRdOnlyWrongRevision) {
177 ✗ LogCvmfs(kLogCvmfs, logFlags, "%s is not based on the checked out revision",
178 fqrn.c_str());
179 }
180 ✗ if (result & kFailUnionBroken) {
181 ✗ LogCvmfs(kLogCvmfs, logFlags, "%s is not mounted properly",
182 union_mnt.c_str());
183 }
184 ✗ if (result & kFailUnionWritable) {
185 ✗ LogCvmfs(kLogCvmfs, logFlags,
186 "%s is not in a transaction but %s is mounted read/write",
187 fqrn.c_str(), union_mnt.c_str());
188 }
189 ✗ if (result & kFailUnionLocked) {
190 ✗ LogCvmfs(kLogCvmfs, logFlags,
191 "%s is in a transaction but %s is not mounted read/write",
192 fqrn.c_str(), union_mnt.c_str());
193 }
194
195 // Check whether we can repair
196
197 ✗ switch (repair_mode) {
198 ✗ case kUnionMountRepairNever:
199 ✗ return result;
200 ✗ case kUnionMountRepairAlways:
201 ✗ break;
202 ✗ case kUnionMountRepairSafe:
203 ✗ if (!publish_check.owns_lock()) {
204 ✗ LogCvmfs(
205 kLogCvmfs, logFlags,
206 "WARNING: The repository %s is currently publishing and should "
207 "not\n"
208 "be touched. If you are absolutely sure, that this is _not_ the "
209 "case,\nplease run the following command and retry:\n\n"
210 " rm -fR %s\n",
211 ✗ fqrn.c_str(), publisher_->is_publishing_.path().c_str());
212 ✗ return result;
213 }
214
215 ✗ if (publisher_->in_transaction_.IsSet()) {
216 ✗ LogCvmfs(kLogCvmfs, logFlags,
217 "Repository %s is in a transaction and cannot be repaired.\n"
218 "--> Run `cvmfs_server abort $name` to revert and repair.",
219 fqrn.c_str());
220 ✗ return result;
221 }
222
223 ✗ break;
224 ✗ default:
225 ✗ abort();
226 }
227
228 ✗ LogCvmfs(kLogCvmfs, kLogSyslog, "(%s) attempting mountpoint repair (%d)",
229 fqrn.c_str(), result);
230
231 // consecutively bring the mountpoints into a sane state by working bottom up:
232 // 1. solve problems with the rdonly mountpoint
233 // Note: this might require to 'break' the union mount
234 // (kFailUnionBroken -> 1)
235 // 1.1. solve outdated rdonly mountpoint (kFailRdOnlyOutdated -> 0)
236 // 1.2. remount rdonly mountpoint (kFailRdOnlyBroken -> 0)
237 // 2. solve problems with the union mountpoint
238 // 2.1. mount the union mountpoint read-only (kFailUnionBroken -> 0)
239 // 2.2. remount the union mountpoint read-only (kFailUnionWritable -> 0)
240 // 2.2. remount the union mountpoint read-write (kFailUnionLocked -> 0)
241
242 ✗ int log_flags = kLogSyslog;
243 ✗ if (!is_quiet)
244 ✗ log_flags |= kLogStderr;
245
246 ✗ if ((result & kFailRdOnlyOutdated) || (result & kFailRdOnlyWrongRevision)) {
247 ✗ if ((result & kFailUnionBroken) == 0) {
248 ✗ AlterMountpoint(kAlterUnionUnmount, log_flags);
249 ✗ result |= kFailUnionBroken;
250 }
251
252 ✗ if ((result & kFailRdOnlyBroken) == 0) {
253 ✗ AlterMountpoint(kAlterRdOnlyUnmount, log_flags);
254 ✗ result |= kFailRdOnlyBroken;
255 }
256
257 ✗ SetRootHash(expected_hash);
258 ✗ result &= ~kFailRdOnlyOutdated;
259 ✗ result &= ~kFailRdOnlyWrongRevision;
260 }
261
262 ✗ if (result & kFailRdOnlyBroken) {
263 ✗ if ((result & kFailUnionBroken) == 0) {
264 ✗ AlterMountpoint(kAlterUnionUnmount, log_flags);
265 ✗ result |= kFailUnionBroken;
266 }
267 ✗ AlterMountpoint(kAlterRdOnlyMount, log_flags);
268 ✗ result &= ~kFailRdOnlyBroken;
269 }
270
271 ✗ if (result & kFailUnionBroken) {
272 ✗ AlterMountpoint(kAlterUnionMount, log_flags);
273 // read-only mount by default
274 ✗ if (publisher_->in_transaction_.IsSet())
275 ✗ result |= kFailUnionLocked;
276
277 ✗ result &= ~kFailUnionBroken;
278 ✗ result &= ~kFailUnionWritable;
279 }
280
281 ✗ if (result & kFailUnionLocked) {
282 ✗ AlterMountpoint(kAlterUnionOpen, log_flags);
283 ✗ result &= ~kFailUnionLocked;
284 }
285
286 ✗ if (result & kFailUnionWritable) {
287 ✗ AlterMountpoint(kAlterUnionLock, log_flags);
288 ✗ result &= ~kFailUnionWritable;
289 }
290
291 ✗ LogCvmfs(kLogCvmfs, kLogSyslog, "finished mountpoint repair (%d)", result);
292
293 ✗ return result;
294 }
295
296 ✗ void Publisher::ManagedNode::AlterMountpoint(EMountpointAlterations how,
297 int log_level) {
298 ✗ std::string mountpoint;
299 ✗ std::string info_msg;
300 ✗ std::string suid_helper_verb;
301 ✗ switch (how) {
302 ✗ case kAlterUnionUnmount:
303 ✗ mountpoint = publisher_->settings_.transaction().spool_area().union_mnt();
304 ✗ info_msg = "Trying to unmount " + mountpoint;
305 ✗ suid_helper_verb = "rw_umount";
306 ✗ break;
307 ✗ case kAlterUnionLazyUnmount:
308 ✗ mountpoint = publisher_->settings_.transaction().spool_area().union_mnt();
309 ✗ info_msg = "Trying to lazily unmount " + mountpoint;
310 ✗ suid_helper_verb = "rw_lazy_umount";
311 ✗ break;
312 ✗ case kAlterRdOnlyUnmount:
313 ✗ mountpoint = publisher_->settings_.transaction()
314 ✗ .spool_area()
315 ✗ .readonly_mnt();
316 ✗ info_msg = "Trying to unmount " + mountpoint;
317 ✗ suid_helper_verb = "rdonly_umount";
318 ✗ break;
319 ✗ case kAlterRdOnlyKillUnmount:
320 ✗ mountpoint = publisher_->settings_.transaction()
321 ✗ .spool_area()
322 ✗ .readonly_mnt();
323 ✗ info_msg = "Trying to forcefully stop " + mountpoint;
324 ✗ suid_helper_verb = "kill_cvmfs";
325 ✗ break;
326 ✗ case kAlterRdOnlyLazyUnmount:
327 ✗ mountpoint = publisher_->settings_.transaction()
328 ✗ .spool_area()
329 ✗ .readonly_mnt();
330 ✗ info_msg = "Trying to lazily unmount " + mountpoint;
331 ✗ suid_helper_verb = "rdonly_lazy_umount";
332 ✗ break;
333 ✗ case kAlterUnionMount:
334 ✗ mountpoint = publisher_->settings_.transaction().spool_area().union_mnt();
335 ✗ info_msg = "Trying to mount " + mountpoint;
336 ✗ suid_helper_verb = "rw_mount";
337 ✗ break;
338 ✗ case kAlterRdOnlyMount:
339 ✗ mountpoint = publisher_->settings_.transaction()
340 ✗ .spool_area()
341 ✗ .readonly_mnt();
342 ✗ info_msg = "Trying to mount " + mountpoint;
343 ✗ suid_helper_verb = "rdonly_mount";
344 ✗ break;
345 ✗ case kAlterUnionOpen:
346 ✗ mountpoint = publisher_->settings_.transaction().spool_area().union_mnt();
347 ✗ info_msg = "Trying to remount " + mountpoint + " read/write";
348 ✗ suid_helper_verb = "open";
349 ✗ break;
350 ✗ case kAlterUnionLock:
351 ✗ mountpoint = publisher_->settings_.transaction().spool_area().union_mnt();
352 ✗ info_msg = "Trying to remount " + mountpoint + " read-only";
353 ✗ suid_helper_verb = "lock";
354 ✗ break;
355 ✗ case kAlterScratchWipe:
356 ✗ mountpoint = publisher_->settings_.transaction()
357 ✗ .spool_area()
358 ✗ .scratch_dir();
359 ✗ info_msg = "Trying to wipe out " + mountpoint + " (async cleanup)";
360 ✗ suid_helper_verb = "clear_scratch_async";
361 ✗ break;
362 ✗ default:
363 ✗ throw EPublish("internal error: unknown mountpoint alteration");
364 }
365
366 ✗ if (log_level & kLogStdout) {
367 ✗ LogCvmfs(kLogCvmfs, kLogStderr | kLogNoLinebreak, "Note: %s... ",
368 info_msg.c_str());
369 }
370
371 try {
372 ✗ RunSuidHelper(suid_helper_verb, publisher_->settings_.fqrn());
373 ✗ LogCvmfs(kLogCvmfs, (log_level & ~kLogStdout), "%s... success",
374 info_msg.c_str());
375 ✗ if (log_level & kLogStdout)
376 ✗ LogCvmfs(kLogCvmfs, kLogStdout, "success");
377 ✗ } catch (const EPublish &) {
378 ✗ LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr, "%s... fail",
379 info_msg.c_str());
380 ✗ throw EPublish(info_msg + "... fail");
381 }
382 }
383
384
385 ✗ void Publisher::ManagedNode::SetRootHash(const shash::Any &hash) {
386 ✗ const std::string config_path = publisher_->settings_.transaction()
387 ✗ .spool_area()
388 ✗ .client_lconfig();
389 ✗ SetInConfig(config_path, "CVMFS_ROOT_HASH", hash.ToString());
390 }
391
392 } // namespace publish
393