GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/sync_union_overlayfs.cc
Date: 2025-06-22 02:36:02
Exec Total Coverage
Lines: 0 128 0.0%
Branches: 0 219 0.0%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM File System
3 */
4
5 #define __STDC_FORMAT_MACROS
6
7 #include "sync_union_overlayfs.h"
8
9 #include <sys/capability.h>
10
11 #include <string>
12 #include <vector>
13
14 #include "sync_mediator.h"
15 #include "sync_union.h"
16 #include "util/exception.h"
17 #include "util/fs_traversal.h"
18 #include "util/shared_ptr.h"
19
20 namespace publish {
21
22 SyncUnionOverlayfs::SyncUnionOverlayfs(SyncMediator *mediator,
23 const string &rdonly_path,
24 const string &union_path,
25 const string &scratch_path)
26 : SyncUnion(mediator, rdonly_path, union_path, scratch_path)
27 , hardlink_lower_inode_(0) { }
28
29 bool SyncUnionOverlayfs::Initialize() {
30 // trying to obtain CAP_SYS_ADMIN to read 'trusted' xattrs in the scratch
31 // directory of an OverlayFS installation
32 return ObtainSysAdminCapability() && SyncUnion::Initialize();
33 }
34
35 bool ObtainSysAdminCapabilityInternal(cap_t caps) {
36 /*const*/ const cap_value_t cap =
37 CAP_SYS_ADMIN; // is non-const as cap_set_flag()
38 // expects a non-const pointer
39 // on RHEL 5 and older
40
41 // do sanity-check if supported in <sys/capability.h> otherwise just pray...
42 // Note: CAP_SYS_ADMIN is a rather common capability and is very likely to be
43 // supported by all our target systems. If it is not, one of the next
44 // commands will fail with a less descriptive error message.
45 #ifdef CAP_IS_SUPPORTED
46 if (!CAP_IS_SUPPORTED(cap)) {
47 LogCvmfs(kLogUnionFs, kLogStderr, "System doesn't support CAP_SYS_ADMIN");
48 return false;
49 }
50 #endif
51
52 if (caps == NULL) {
53 LogCvmfs(kLogUnionFs, kLogStderr,
54 "Failed to obtain capability state "
55 "of current process (errno: %d)",
56 errno);
57 return false;
58 }
59
60 cap_flag_value_t cap_state;
61 if (cap_get_flag(caps, cap, CAP_EFFECTIVE, &cap_state) != 0) {
62 LogCvmfs(kLogUnionFs, kLogStderr,
63 "Failed to check effective set for "
64 "CAP_SYS_ADMIN (errno: %d)",
65 errno);
66 return false;
67 }
68
69 if (cap_state == CAP_SET) {
70 LogCvmfs(kLogUnionFs, kLogDebug, "CAP_SYS_ADMIN is already effective");
71 return true;
72 }
73
74 if (cap_get_flag(caps, cap, CAP_PERMITTED, &cap_state) != 0) {
75 LogCvmfs(kLogUnionFs, kLogStderr,
76 "Failed to check permitted set for "
77 "CAP_SYS_ADMIN (errno: %d)",
78 errno);
79 return false;
80 }
81
82 if (cap_state != CAP_SET) {
83 LogCvmfs(kLogUnionFs, kLogStderr,
84 "CAP_SYS_ADMIN cannot be obtained. It's "
85 "not in the process's permitted-set.");
86 return false;
87 }
88
89 if (cap_set_flag(caps, CAP_EFFECTIVE, 1, &cap, CAP_SET) != 0) {
90 LogCvmfs(kLogUnionFs, kLogStderr,
91 "Cannot set CAP_SYS_ADMIN as effective "
92 "for the current process (errno: %d)",
93 errno);
94 return false;
95 }
96
97 if (cap_set_proc(caps) != 0) {
98 LogCvmfs(kLogUnionFs, kLogStderr,
99 "Cannot reset capabilities for current "
100 "process (errno: %d)",
101 errno);
102 return false;
103 }
104
105 LogCvmfs(kLogUnionFs, kLogDebug, "Successfully obtained CAP_SYS_ADMIN");
106 return true;
107 }
108
109 bool SyncUnionOverlayfs::ObtainSysAdminCapability() const {
110 cap_t caps = cap_get_proc();
111 const bool result = ObtainSysAdminCapabilityInternal(caps);
112 cap_free(caps);
113 return result;
114 }
115
116 void SyncUnionOverlayfs::PreprocessSyncItem(SharedPtr<SyncItem> entry) const {
117 SyncUnion::PreprocessSyncItem(entry);
118 if (entry->IsGraftMarker() || entry->IsWhiteout() || entry->IsDirectory()) {
119 return;
120 }
121
122 CheckForBrokenHardlink(entry);
123 MaskFileHardlinks(entry);
124 }
125
126 void SyncUnionOverlayfs::CheckForBrokenHardlink(
127 SharedPtr<SyncItem> entry) const {
128 if (!entry->IsNew() && !entry->WasDirectory()
129 && entry->GetRdOnlyLinkcount() > 1) {
130 PANIC(kLogStderr,
131 "OverlayFS has copied-up a file (%s) "
132 "with existing hardlinks in lowerdir "
133 "(linkcount %d). OverlayFS cannot handle "
134 "hardlinks and would produce "
135 "inconsistencies. \n\n"
136 "Consider running this command: \n"
137 " cvmfs_server eliminate-hardlinks\n\n"
138 "Aborting...",
139 entry->GetUnionPath().c_str(), entry->GetRdOnlyLinkcount());
140 }
141 }
142
143 void SyncUnionOverlayfs::MaskFileHardlinks(SharedPtr<SyncItem> entry) const {
144 assert(entry->IsRegularFile() || entry->IsSymlink()
145 || entry->IsSpecialFile());
146 if (entry->GetUnionLinkcount() > 1) {
147 LogCvmfs(kLogPublish, kLogStderr,
148 "Warning: Found file with linkcount > 1 "
149 "(%s). We will break up these hardlinks.",
150 entry->GetUnionPath().c_str());
151 entry->MaskHardlink();
152 }
153 }
154
155 void SyncUnionOverlayfs::Traverse() {
156 assert(this->IsInitialized());
157
158 FileSystemTraversal<SyncUnionOverlayfs> traversal(this, scratch_path(), true);
159
160 traversal.fn_enter_dir = &SyncUnionOverlayfs::EnterDirectory;
161 traversal.fn_leave_dir = &SyncUnionOverlayfs::LeaveDirectory;
162 traversal.fn_new_file = &SyncUnionOverlayfs::ProcessRegularFile;
163 traversal.fn_new_character_dev = &SyncUnionOverlayfs::ProcessCharacterDevice;
164 traversal.fn_new_block_dev = &SyncUnionOverlayfs::ProcessBlockDevice;
165 traversal.fn_new_fifo = &SyncUnionOverlayfs::ProcessFifo;
166 traversal.fn_new_socket = &SyncUnionOverlayfs::ProcessSocket;
167 traversal.fn_ignore_file = &SyncUnionOverlayfs::IgnoreFilePredicate;
168 traversal.fn_new_dir_prefix = &SyncUnionOverlayfs::ProcessDirectory;
169 traversal.fn_new_symlink = &SyncUnionOverlayfs::ProcessSymlink;
170
171 LogCvmfs(kLogUnionFs, kLogVerboseMsg,
172 "OverlayFS starting traversal "
173 "recursion for scratch_path=[%s]",
174 scratch_path().c_str());
175 traversal.Recurse(scratch_path());
176 }
177
178 /**
179 * Wrapper around readlink to read the value of the symbolic link
180 * and return true if it is equal to the supplied value, or false
181 * otherwise (including if any errors occur)
182 *
183 * @param[in] path to the symbolic link
184 * @param[in] value to compare to link value
185 */
186 bool SyncUnionOverlayfs::ReadlinkEquals(string const &path,
187 string const &compare_value) {
188 char *buf;
189 size_t compare_len;
190
191 // Compare to one more than compare_value length in case the link value
192 // begins with compare_value but ends with something else
193 compare_len = compare_value.length() + 1;
194
195 // Allocate enough space for compare_len and terminating null
196 buf = static_cast<char *>(alloca(compare_len + 1));
197
198 const ssize_t len = ::readlink(path.c_str(), buf, compare_len);
199 if (len != -1) {
200 buf[len] = '\0';
201 // have link, return true if it is equal to compare_value
202 return (std::string(buf) == compare_value);
203 } else {
204 // Error, return false
205 LogCvmfs(kLogUnionFs, kLogDebug,
206 "SyncUnionOverlayfs::ReadlinkEquals error reading link [%s]: %d\n",
207 path.c_str(), errno);
208 return false;
209 }
210 }
211
212 /**
213 * Checks if a given file path has a specified extended attribute attached.
214 *
215 * @param[in] path to the file to be checked
216 * @param[in] attr_name fully qualified name of the extend attribute
217 * (i.e. trusted.overlay.opaque)
218 * @return true if attribute is found
219 */
220 bool SyncUnionOverlayfs::HasXattr(string const &path, string const &attr_name) {
221 // TODO(reneme): it is quite heavy-weight to allocate an object that contains
222 // an std::map<> just to check if an xattr is there...
223 const UniquePtr<XattrList> xattrs(XattrList::CreateFromFile(path));
224 assert(xattrs.IsValid());
225
226 std::vector<std::string> attrs = xattrs->ListKeys();
227 std::vector<std::string>::const_iterator i = attrs.begin();
228 const std::vector<std::string>::const_iterator iend = attrs.end();
229 LogCvmfs(kLogCvmfs, kLogDebug, "Attrs:");
230 for (; i != iend; ++i) {
231 LogCvmfs(kLogCvmfs, kLogDebug, "Attr: %s", i->c_str());
232 }
233
234 return xattrs.IsValid() && xattrs->Has(attr_name);
235 }
236
237 bool SyncUnionOverlayfs::IsWhiteoutEntry(SharedPtr<SyncItem> entry) const {
238 /**
239 * There seem to be two versions of overlayfs out there and in production:
240 * 1. whiteouts are 'character device' files
241 * 2. whiteouts are symlinks pointing to '(overlay-whiteout)'
242 * 3. whiteouts are marked as .wh. (as in aufs)
243 */
244
245 const bool is_chardev_whiteout = entry->IsCharacterDevice() &&
246 entry->GetRdevMajor() == 0 &&
247 entry->GetRdevMinor() == 0;
248 if (is_chardev_whiteout)
249 return true;
250
251 const std::string whiteout_prefix_ = ".wh.";
252 const bool has_wh_prefix =
253 HasPrefix(entry->filename().c_str(), whiteout_prefix_, true);
254 if (has_wh_prefix)
255 return true;
256
257 const bool is_symlink_whiteout =
258 entry->IsSymlink() && IsWhiteoutSymlinkPath(entry->GetScratchPath());
259 if (is_symlink_whiteout)
260 return true;
261
262 return false;
263 }
264
265 bool SyncUnionOverlayfs::IsWhiteoutSymlinkPath(const string &path) const {
266 const bool is_whiteout = ReadlinkEquals(path, "(overlay-whiteout)");
267 // TODO(reneme): check for the xattr trusted.overlay.whiteout
268 // Note: This requires CAP_SYS_ADMIN or root... >.<
269 if (is_whiteout) {
270 LogCvmfs(kLogUnionFs, kLogDebug, "OverlayFS [%s] is whiteout symlink",
271 path.c_str());
272 } else {
273 LogCvmfs(kLogUnionFs, kLogDebug, "OverlayFS [%s] is not a whiteout symlink",
274 path.c_str());
275 }
276 return is_whiteout;
277 }
278
279 bool SyncUnionOverlayfs::IsOpaqueDirectory(
280 SharedPtr<SyncItem> directory) const {
281 const std::string path = directory->GetScratchPath();
282 return DirectoryExists(path) && IsOpaqueDirPath(path);
283 }
284
285 bool SyncUnionOverlayfs::IsOpaqueDirPath(const string &path) const {
286 const bool is_opaque = HasXattr(path.c_str(), "trusted.overlay.opaque");
287 if (is_opaque) {
288 LogCvmfs(kLogUnionFs, kLogDebug, "OverlayFS [%s] has opaque xattr",
289 path.c_str());
290 }
291 return is_opaque;
292 }
293
294 string SyncUnionOverlayfs::UnwindWhiteoutFilename(
295 SharedPtr<SyncItem> entry) const {
296 const std::string whiteout_prefix_ = ".wh.";
297
298 if (HasPrefix(entry->filename().c_str(), whiteout_prefix_, true)) {
299 return entry->filename().substr(whiteout_prefix_.length());
300 } else {
301 return entry->filename();
302 }
303 }
304 } // namespace publish
305