GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/publish/repository_managed.cc
Date: 2025-06-29 02:35:41
Exec Total Coverage
Lines: 0 226 0.0%
Branches: 0 357 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 throw EPublish("cannot create wastebin directory");
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
73 publisher_->CreateDirectoryAsOwner(scratch_dir, kDefaultDirMode);
74
75 AlterMountpoint(kAlterScratchWipe, kLogSyslog);
76
77 std::vector<mode_t> modes;
78 std::vector<std::string> names;
79 ListDirectory(tmp_dir, &names, &modes);
80 for (unsigned i = 0; i < names.size(); ++i) {
81 if (HasPrefix(names[i], "receiver.", false /* ignore_case */))
82 continue;
83
84 unlink((tmp_dir + "/" + names[i]).c_str());
85 }
86 }
87
88
89 int Publisher::ManagedNode::Check(bool is_quiet) {
90 const ServerLockFileCheck publish_check(publisher_->is_publishing_);
91 const std::string rdonly_mnt = publisher_->settings_.transaction()
92 .spool_area()
93 .readonly_mnt();
94 const std::string
95 union_mnt = publisher_->settings_.transaction().spool_area().union_mnt();
96 const std::string fqrn = publisher_->settings_.fqrn();
97 const EUnionMountRepairMode repair_mode = publisher_->settings_.transaction()
98 .spool_area()
99 .repair_mode();
100
101 int result = kFailOk;
102
103 shash::Any expected_hash = publisher_->manifest()->catalog_hash();
104 const UniquePtr<CheckoutMarker> marker(CheckoutMarker::CreateFrom(
105 publisher_->settings_.transaction().spool_area().checkout_marker()));
106 if (marker.IsValid())
107 expected_hash = marker->hash();
108
109 if (!IsMountPoint(rdonly_mnt)) {
110 result |= kFailRdOnlyBroken;
111 } else {
112 const std::string root_hash_xattr = "user.root_hash";
113 std::string root_hash_str;
114 const bool retval = platform_getxattr(rdonly_mnt, root_hash_xattr,
115 &root_hash_str);
116 if (retval) {
117 const shash::Any root_hash = shash::MkFromHexPtr(
118 shash::HexPtr(root_hash_str), shash::kSuffixCatalog);
119 if (expected_hash != root_hash) {
120 if (marker.IsValid()) {
121 result |= kFailRdOnlyWrongRevision;
122 } else {
123 result |= kFailRdOnlyOutdated;
124 }
125 }
126 } else {
127 if (errno == ENOTCONN) {
128 // Fuse module crashed
129 result |= kFailRdOnlyBroken;
130 } else {
131 throw EPublish("cannot retrieve root hash from read-only mount point");
132 }
133 }
134 }
135
136 // The process that opens the transaction does not stay alive for the life
137 // time of the transaction
138 if (!IsMountPoint(union_mnt)) {
139 result |= kFailUnionBroken;
140 } else {
141 const FileSystemInfo fs_info = GetFileSystemInfo(union_mnt);
142 if (publisher_->in_transaction_.IsSet() && fs_info.is_rdonly)
143 result |= kFailUnionLocked;
144 if (!publisher_->in_transaction_.IsSet() && !fs_info.is_rdonly)
145 result |= kFailUnionWritable;
146 }
147
148 if (result == kFailOk)
149 return result;
150
151 // Report & Repair
152
153 int logFlags = kLogStderr;
154 if (is_quiet)
155 logFlags |= kLogNone;
156 if (result & kFailRdOnlyBroken) {
157 LogCvmfs(kLogCvmfs, logFlags, "%s is not mounted properly",
158 rdonly_mnt.c_str());
159 }
160 if (result & kFailRdOnlyOutdated) {
161 LogCvmfs(kLogCvmfs, logFlags,
162 "%s is not based on the newest published revision", fqrn.c_str());
163 }
164 if (result & kFailRdOnlyWrongRevision) {
165 LogCvmfs(kLogCvmfs, logFlags, "%s is not based on the checked out revision",
166 fqrn.c_str());
167 }
168 if (result & kFailUnionBroken) {
169 LogCvmfs(kLogCvmfs, logFlags, "%s is not mounted properly",
170 union_mnt.c_str());
171 }
172 if (result & kFailUnionWritable) {
173 LogCvmfs(kLogCvmfs, logFlags,
174 "%s is not in a transaction but %s is mounted read/write",
175 fqrn.c_str(), union_mnt.c_str());
176 }
177 if (result & kFailUnionLocked) {
178 LogCvmfs(kLogCvmfs, logFlags,
179 "%s is in a transaction but %s is not mounted read/write",
180 fqrn.c_str(), union_mnt.c_str());
181 }
182
183 // Check whether we can repair
184
185 switch (repair_mode) {
186 case kUnionMountRepairNever:
187 return result;
188 case kUnionMountRepairAlways:
189 break;
190 case kUnionMountRepairSafe:
191 if (!publish_check.owns_lock()) {
192 LogCvmfs(
193 kLogCvmfs, logFlags,
194 "WARNING: The repository %s is currently publishing and should "
195 "not\n"
196 "be touched. If you are absolutely sure, that this is _not_ the "
197 "case,\nplease run the following command and retry:\n\n"
198 " rm -fR %s\n",
199 fqrn.c_str(), publisher_->is_publishing_.path().c_str());
200 return result;
201 }
202
203 if (publisher_->in_transaction_.IsSet()) {
204 LogCvmfs(kLogCvmfs, logFlags,
205 "Repository %s is in a transaction and cannot be repaired.\n"
206 "--> Run `cvmfs_server abort $name` to revert and repair.",
207 fqrn.c_str());
208 return result;
209 }
210
211 break;
212 default:
213 abort();
214 }
215
216 LogCvmfs(kLogCvmfs, kLogSyslog, "(%s) attempting mountpoint repair (%d)",
217 fqrn.c_str(), result);
218
219 // consecutively bring the mountpoints into a sane state by working bottom up:
220 // 1. solve problems with the rdonly mountpoint
221 // Note: this might require to 'break' the union mount
222 // (kFailUnionBroken -> 1)
223 // 1.1. solve outdated rdonly mountpoint (kFailRdOnlyOutdated -> 0)
224 // 1.2. remount rdonly mountpoint (kFailRdOnlyBroken -> 0)
225 // 2. solve problems with the union mountpoint
226 // 2.1. mount the union mountpoint read-only (kFailUnionBroken -> 0)
227 // 2.2. remount the union mountpoint read-only (kFailUnionWritable -> 0)
228 // 2.2. remount the union mountpoint read-write (kFailUnionLocked -> 0)
229
230 int log_flags = kLogSyslog;
231 if (!is_quiet)
232 log_flags |= kLogStderr;
233
234 if ((result & kFailRdOnlyOutdated) || (result & kFailRdOnlyWrongRevision)) {
235 if ((result & kFailUnionBroken) == 0) {
236 AlterMountpoint(kAlterUnionUnmount, log_flags);
237 result |= kFailUnionBroken;
238 }
239
240 if ((result & kFailRdOnlyBroken) == 0) {
241 AlterMountpoint(kAlterRdOnlyUnmount, log_flags);
242 result |= kFailRdOnlyBroken;
243 }
244
245 SetRootHash(expected_hash);
246 result &= ~kFailRdOnlyOutdated;
247 result &= ~kFailRdOnlyWrongRevision;
248 }
249
250 if (result & kFailRdOnlyBroken) {
251 if ((result & kFailUnionBroken) == 0) {
252 AlterMountpoint(kAlterUnionUnmount, log_flags);
253 result |= kFailUnionBroken;
254 }
255 AlterMountpoint(kAlterRdOnlyMount, log_flags);
256 result &= ~kFailRdOnlyBroken;
257 }
258
259 if (result & kFailUnionBroken) {
260 AlterMountpoint(kAlterUnionMount, log_flags);
261 // read-only mount by default
262 if (publisher_->in_transaction_.IsSet())
263 result |= kFailUnionLocked;
264
265 result &= ~kFailUnionBroken;
266 result &= ~kFailUnionWritable;
267 }
268
269 if (result & kFailUnionLocked) {
270 AlterMountpoint(kAlterUnionOpen, log_flags);
271 result &= ~kFailUnionLocked;
272 }
273
274 if (result & kFailUnionWritable) {
275 AlterMountpoint(kAlterUnionLock, log_flags);
276 result &= ~kFailUnionWritable;
277 }
278
279 LogCvmfs(kLogCvmfs, kLogSyslog, "finished mountpoint repair (%d)", result);
280
281 return result;
282 }
283
284 void Publisher::ManagedNode::AlterMountpoint(EMountpointAlterations how,
285 int log_level) {
286 std::string mountpoint;
287 std::string info_msg;
288 std::string suid_helper_verb;
289 switch (how) {
290 case kAlterUnionUnmount:
291 mountpoint = publisher_->settings_.transaction().spool_area().union_mnt();
292 info_msg = "Trying to unmount " + mountpoint;
293 suid_helper_verb = "rw_umount";
294 break;
295 case kAlterUnionLazyUnmount:
296 mountpoint = publisher_->settings_.transaction().spool_area().union_mnt();
297 info_msg = "Trying to lazily unmount " + mountpoint;
298 suid_helper_verb = "rw_lazy_umount";
299 break;
300 case kAlterRdOnlyUnmount:
301 mountpoint = publisher_->settings_.transaction()
302 .spool_area()
303 .readonly_mnt();
304 info_msg = "Trying to unmount " + mountpoint;
305 suid_helper_verb = "rdonly_umount";
306 break;
307 case kAlterRdOnlyKillUnmount:
308 mountpoint = publisher_->settings_.transaction()
309 .spool_area()
310 .readonly_mnt();
311 info_msg = "Trying to forcefully stop " + mountpoint;
312 suid_helper_verb = "kill_cvmfs";
313 break;
314 case kAlterRdOnlyLazyUnmount:
315 mountpoint = publisher_->settings_.transaction()
316 .spool_area()
317 .readonly_mnt();
318 info_msg = "Trying to lazily unmount " + mountpoint;
319 suid_helper_verb = "rdonly_lazy_umount";
320 break;
321 case kAlterUnionMount:
322 mountpoint = publisher_->settings_.transaction().spool_area().union_mnt();
323 info_msg = "Trying to mount " + mountpoint;
324 suid_helper_verb = "rw_mount";
325 break;
326 case kAlterRdOnlyMount:
327 mountpoint = publisher_->settings_.transaction()
328 .spool_area()
329 .readonly_mnt();
330 info_msg = "Trying to mount " + mountpoint;
331 suid_helper_verb = "rdonly_mount";
332 break;
333 case kAlterUnionOpen:
334 mountpoint = publisher_->settings_.transaction().spool_area().union_mnt();
335 info_msg = "Trying to remount " + mountpoint + " read/write";
336 suid_helper_verb = "open";
337 break;
338 case kAlterUnionLock:
339 mountpoint = publisher_->settings_.transaction().spool_area().union_mnt();
340 info_msg = "Trying to remount " + mountpoint + " read-only";
341 suid_helper_verb = "lock";
342 break;
343 case kAlterScratchWipe:
344 mountpoint = publisher_->settings_.transaction()
345 .spool_area()
346 .scratch_dir();
347 info_msg = "Trying to wipe out " + mountpoint + " (async cleanup)";
348 suid_helper_verb = "clear_scratch_async";
349 break;
350 default:
351 throw EPublish("internal error: unknown mountpoint alteration");
352 }
353
354 if (log_level & kLogStdout) {
355 LogCvmfs(kLogCvmfs, kLogStderr | kLogNoLinebreak, "Note: %s... ",
356 info_msg.c_str());
357 }
358
359 try {
360 RunSuidHelper(suid_helper_verb, publisher_->settings_.fqrn());
361 LogCvmfs(kLogCvmfs, (log_level & ~kLogStdout), "%s... success",
362 info_msg.c_str());
363 if (log_level & kLogStdout)
364 LogCvmfs(kLogCvmfs, kLogStdout, "success");
365 } catch (const EPublish &) {
366 LogCvmfs(kLogCvmfs, kLogStderr | kLogSyslogErr, "%s... fail",
367 info_msg.c_str());
368 throw EPublish(info_msg + "... fail");
369 }
370 }
371
372
373 void Publisher::ManagedNode::SetRootHash(const shash::Any &hash) {
374 const std::string config_path = publisher_->settings_.transaction()
375 .spool_area()
376 .client_lconfig();
377 SetInConfig(config_path, "CVMFS_ROOT_HASH", hash.ToString());
378 }
379
380 } // namespace publish
381