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