GCC Code Coverage Report


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