GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/sync_union_overlayfs.cc
Date: 2026-05-19 11:45:12
Exec Total Coverage
Lines: 0 92 0.0%
Branches: 0 163 0.0%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM File System
3 */
4
5 #include "sync_union_overlayfs.h"
6
7 #include <sys/types.h>
8
9 #include <cassert>
10 #include <string>
11 #include <vector>
12
13 #include "sync_item.h"
14 #include "sync_mediator.h"
15 #include "sync_union.h"
16 #include "util/capabilities.h"
17 #include "util/exception.h"
18 #include "util/fs_traversal.h"
19 #include "util/logging.h"
20 #include "util/shared_ptr.h"
21
22 namespace publish {
23
24 SyncUnionOverlayfs::SyncUnionOverlayfs(SyncMediator *mediator,
25 const string &rdonly_path,
26 const string &union_path,
27 const string &scratch_path)
28 : SyncUnion(mediator, rdonly_path, union_path, scratch_path)
29 , hardlink_lower_inode_(0) { }
30
31 bool SyncUnionOverlayfs::Initialize() {
32 // trying to obtain CAP_SYS_ADMIN to read 'trusted' xattrs in the scratch
33 // directory of an OverlayFS installation
34 return ObtainSysAdminCapability() && SyncUnion::Initialize();
35 }
36
37 void SyncUnionOverlayfs::PreprocessSyncItem(SharedPtr<SyncItem> entry) const {
38 SyncUnion::PreprocessSyncItem(entry);
39 if (entry->IsGraftMarker() || entry->IsWhiteout() || entry->IsDirectory()) {
40 return;
41 }
42
43 CheckForBrokenHardlink(entry);
44 MaskFileHardlinks(entry);
45 }
46
47 void SyncUnionOverlayfs::CheckForBrokenHardlink(
48 SharedPtr<SyncItem> entry) const {
49 if (!entry->IsNew() && !entry->WasDirectory()
50 && entry->GetRdOnlyLinkcount() > 1) {
51 PANIC(kLogStderr,
52 "OverlayFS has copied-up a file (%s) "
53 "with existing hardlinks in lowerdir "
54 "(linkcount %d). OverlayFS cannot handle "
55 "hardlinks and would produce "
56 "inconsistencies. \n\n"
57 "Consider running this command: \n"
58 " cvmfs_server eliminate-hardlinks\n\n"
59 "Aborting...",
60 entry->GetUnionPath().c_str(), entry->GetRdOnlyLinkcount());
61 }
62 }
63
64 void SyncUnionOverlayfs::MaskFileHardlinks(SharedPtr<SyncItem> entry) const {
65 assert(entry->IsRegularFile() || entry->IsSymlink()
66 || entry->IsSpecialFile());
67 if (entry->GetUnionLinkcount() > 1) {
68 LogCvmfs(kLogPublish, kLogStderr,
69 "Warning: Found file with linkcount > 1 "
70 "(%s). We will break up these hardlinks.",
71 entry->GetUnionPath().c_str());
72 entry->MaskHardlink();
73 }
74 }
75
76 void SyncUnionOverlayfs::Traverse() {
77 assert(this->IsInitialized());
78
79 FileSystemTraversal<SyncUnionOverlayfs> traversal(this, scratch_path(), true);
80
81 traversal.fn_enter_dir = &SyncUnionOverlayfs::EnterDirectory;
82 traversal.fn_leave_dir = &SyncUnionOverlayfs::LeaveDirectory;
83 traversal.fn_new_file = &SyncUnionOverlayfs::ProcessRegularFile;
84 traversal.fn_new_character_dev = &SyncUnionOverlayfs::ProcessCharacterDevice;
85 traversal.fn_new_block_dev = &SyncUnionOverlayfs::ProcessBlockDevice;
86 traversal.fn_new_fifo = &SyncUnionOverlayfs::ProcessFifo;
87 traversal.fn_new_socket = &SyncUnionOverlayfs::ProcessSocket;
88 traversal.fn_ignore_file = &SyncUnionOverlayfs::IgnoreFilePredicate;
89 traversal.fn_new_dir_prefix = &SyncUnionOverlayfs::ProcessDirectory;
90 traversal.fn_new_symlink = &SyncUnionOverlayfs::ProcessSymlink;
91
92 LogCvmfs(kLogUnionFs, kLogVerboseMsg,
93 "OverlayFS starting traversal "
94 "recursion for scratch_path=[%s]",
95 scratch_path().c_str());
96 traversal.Recurse(scratch_path());
97 }
98
99 /**
100 * Wrapper around readlink to read the value of the symbolic link
101 * and return true if it is equal to the supplied value, or false
102 * otherwise (including if any errors occur)
103 *
104 * @param[in] path to the symbolic link
105 * @param[in] value to compare to link value
106 */
107 bool SyncUnionOverlayfs::ReadlinkEquals(string const &path,
108 string const &compare_value) {
109 char *buf;
110 size_t compare_len;
111
112 // Compare to one more than compare_value length in case the link value
113 // begins with compare_value but ends with something else
114 compare_len = compare_value.length() + 1;
115
116 // Allocate enough space for compare_len and terminating null
117 buf = static_cast<char *>(alloca(compare_len + 1));
118
119 const ssize_t len = ::readlink(path.c_str(), buf, compare_len);
120 if (len != -1) {
121 buf[len] = '\0';
122 // have link, return true if it is equal to compare_value
123 return (std::string(buf) == compare_value);
124 } else {
125 // Error, return false
126 LogCvmfs(kLogUnionFs, kLogDebug,
127 "SyncUnionOverlayfs::ReadlinkEquals error reading link [%s]: %d\n",
128 path.c_str(), errno);
129 return false;
130 }
131 }
132
133 /**
134 * Checks if a given file path has a specified extended attribute attached.
135 *
136 * @param[in] path to the file to be checked
137 * @param[in] attr_name fully qualified name of the extend attribute
138 * (i.e. trusted.overlay.opaque)
139 * @return true if attribute is found
140 */
141 bool SyncUnionOverlayfs::HasXattr(string const &path, string const &attr_name) {
142 // TODO(reneme): it is quite heavy-weight to allocate an object that contains
143 // an std::map<> just to check if an xattr is there...
144 const UniquePtr<XattrList> xattrs(XattrList::CreateFromFile(path));
145 assert(xattrs.IsValid());
146
147 std::vector<std::string> attrs = xattrs->ListKeys();
148 std::vector<std::string>::const_iterator i = attrs.begin();
149 const std::vector<std::string>::const_iterator iend = attrs.end();
150 LogCvmfs(kLogCvmfs, kLogDebug, "Attrs:");
151 for (; i != iend; ++i) {
152 LogCvmfs(kLogCvmfs, kLogDebug, "Attr: %s", i->c_str());
153 }
154
155 return xattrs.IsValid() && xattrs->Has(attr_name);
156 }
157
158 bool SyncUnionOverlayfs::IsWhiteoutEntry(SharedPtr<SyncItem> entry) const {
159 /**
160 * There seem to be two versions of overlayfs out there and in production:
161 * 1. whiteouts are 'character device' files
162 * 2. whiteouts are symlinks pointing to '(overlay-whiteout)'
163 * 3. whiteouts are marked as .wh. (as in aufs)
164 */
165
166 const bool is_chardev_whiteout = entry->IsCharacterDevice()
167 && entry->GetRdevMajor() == 0
168 && entry->GetRdevMinor() == 0;
169 if (is_chardev_whiteout)
170 return true;
171
172 const std::string whiteout_prefix_ = ".wh.";
173 const bool has_wh_prefix = HasPrefix(entry->filename().c_str(),
174 whiteout_prefix_, true);
175 if (has_wh_prefix)
176 return true;
177
178 const bool is_symlink_whiteout = entry->IsSymlink()
179 && IsWhiteoutSymlinkPath(
180 entry->GetScratchPath());
181 if (is_symlink_whiteout)
182 return true;
183
184 return false;
185 }
186
187 bool SyncUnionOverlayfs::IsWhiteoutSymlinkPath(const string &path) const {
188 const bool is_whiteout = ReadlinkEquals(path, "(overlay-whiteout)");
189 // TODO(reneme): check for the xattr trusted.overlay.whiteout
190 // Note: This requires CAP_SYS_ADMIN or root... >.<
191 if (is_whiteout) {
192 LogCvmfs(kLogUnionFs, kLogDebug, "OverlayFS [%s] is whiteout symlink",
193 path.c_str());
194 } else {
195 LogCvmfs(kLogUnionFs, kLogDebug, "OverlayFS [%s] is not a whiteout symlink",
196 path.c_str());
197 }
198 return is_whiteout;
199 }
200
201 bool SyncUnionOverlayfs::IsOpaqueDirectory(
202 SharedPtr<SyncItem> directory) const {
203 const std::string path = directory->GetScratchPath();
204 return DirectoryExists(path) && IsOpaqueDirPath(path);
205 }
206
207 bool SyncUnionOverlayfs::IsOpaqueDirPath(const string &path) const {
208 const bool is_opaque = HasXattr(path.c_str(), "trusted.overlay.opaque");
209 if (is_opaque) {
210 LogCvmfs(kLogUnionFs, kLogDebug, "OverlayFS [%s] has opaque xattr",
211 path.c_str());
212 }
213 return is_opaque;
214 }
215
216 string SyncUnionOverlayfs::UnwindWhiteoutFilename(
217 SharedPtr<SyncItem> entry) const {
218 const std::string whiteout_prefix_ = ".wh.";
219
220 if (HasPrefix(entry->filename().c_str(), whiteout_prefix_, true)) {
221 return entry->filename().substr(whiteout_prefix_.length());
222 } else {
223 return entry->filename();
224 }
225 }
226 } // namespace publish
227