GCC Code Coverage Report


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