GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/publish/repository_managed.cc
Date: 2026-04-26 02:35:59
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 "util/pointer.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 space.
77 // AlterMountpoint(kAlterScratchWipe) is intentionally skipped: it runs
78 // clear_scratch_async to clean the wastebin, but no wastebin was created
79 // 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 UniquePtr<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.IsValid())
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.IsValid()) {
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