GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/publish/repository_managed.cc
Date: 2026-04-05 02:35:23
Exec Total Coverage
Lines: 0 230 0.0%
Branches: 0 367 0.0%

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