GCC Code Coverage Report


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