GCC Code Coverage Report
Directory: cvmfs/ Exec Total Coverage
File: cvmfs/sync_union_overlayfs.cc Lines: 0 93 0.0 %
Date: 2019-02-03 02:48:13 Branches: 0 76 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 "fs_traversal.h"
15
#include "sync_mediator.h"
16
#include "util/shared_ptr.h"
17
18
namespace publish {
19
20
SyncUnionOverlayfs::SyncUnionOverlayfs(SyncMediator *mediator,
21
                                       const string &rdonly_path,
22
                                       const string &union_path,
23
                                       const string &scratch_path)
24
    : SyncUnion(mediator, rdonly_path, union_path, scratch_path),
25
      hardlink_lower_inode_(0) {}
26
27
bool SyncUnionOverlayfs::Initialize() {
28
  // trying to obtain CAP_SYS_ADMIN to read 'trusted' xattrs in the scratch
29
  // directory of an OverlayFS installation
30
  return ObtainSysAdminCapability() && SyncUnion::Initialize();
31
}
32
33
bool ObtainSysAdminCapabilityInternal(cap_t caps) {
34
  /*const*/ cap_value_t cap = CAP_SYS_ADMIN;  // is non-const as cap_set_flag()
35
                                              // expects a non-const pointer
36
                                              // on RHEL 5 and older
37
38
// do sanity-check if supported in <sys/capability.h> otherwise just pray...
39
// Note: CAP_SYS_ADMIN is a rather common capability and is very likely to be
40
//       supported by all our target systems. If it is not, one of the next
41
//       commands will fail with a less descriptive error message.
42
#ifdef CAP_IS_SUPPORTED
43
  if (!CAP_IS_SUPPORTED(cap)) {
44
    LogCvmfs(kLogUnionFs, kLogStderr, "System doesn't support CAP_SYS_ADMIN");
45
    return false;
46
  }
47
#endif
48
49
  if (caps == NULL) {
50
    LogCvmfs(kLogUnionFs, kLogStderr,
51
             "Failed to obtain capability state "
52
             "of current process (errno: %d)",
53
             errno);
54
    return false;
55
  }
56
57
  cap_flag_value_t cap_state;
58
  if (cap_get_flag(caps, cap, CAP_EFFECTIVE, &cap_state) != 0) {
59
    LogCvmfs(kLogUnionFs, kLogStderr,
60
             "Failed to check effective set for "
61
             "CAP_SYS_ADMIN (errno: %d)",
62
             errno);
63
    return false;
64
  }
65
66
  if (cap_state == CAP_SET) {
67
    LogCvmfs(kLogUnionFs, kLogDebug, "CAP_SYS_ADMIN is already effective");
68
    return true;
69
  }
70
71
  if (cap_get_flag(caps, cap, CAP_PERMITTED, &cap_state) != 0) {
72
    LogCvmfs(kLogUnionFs, kLogStderr,
73
             "Failed to check permitted set for "
74
             "CAP_SYS_ADMIN (errno: %d)",
75
             errno);
76
    return false;
77
  }
78
79
  if (cap_state != CAP_SET) {
80
    LogCvmfs(kLogUnionFs, kLogStderr,
81
             "CAP_SYS_ADMIN cannot be obtained. It's "
82
             "not in the process's permitted-set.");
83
    return false;
84
  }
85
86
  if (cap_set_flag(caps, CAP_EFFECTIVE, 1, &cap, CAP_SET) != 0) {
87
    LogCvmfs(kLogUnionFs, kLogStderr,
88
             "Cannot set CAP_SYS_ADMIN as effective "
89
             "for the current process (errno: %d)",
90
             errno);
91
    return false;
92
  }
93
94
  if (cap_set_proc(caps) != 0) {
95
    LogCvmfs(kLogUnionFs, kLogStderr,
96
             "Cannot reset capabilities for current "
97
             "process (errno: %d)",
98
             errno);
99
    return false;
100
  }
101
102
  LogCvmfs(kLogUnionFs, kLogDebug, "Successfully obtained CAP_SYS_ADMIN");
103
  return true;
104
}
105
106
bool SyncUnionOverlayfs::ObtainSysAdminCapability() const {
107
  cap_t caps = cap_get_proc();
108
  const bool result = ObtainSysAdminCapabilityInternal(caps);
109
  cap_free(caps);
110
  return result;
111
}
112
113
void SyncUnionOverlayfs::PreprocessSyncItem(SharedPtr<SyncItem> entry) const {
114
  SyncUnion::PreprocessSyncItem(entry);
115
  if (entry->IsGraftMarker() || entry->IsWhiteout() || entry->IsDirectory()) {
116
    return;
117
  }
118
119
  CheckForBrokenHardlink(entry);
120
  MaskFileHardlinks(entry);
121
}
122
123
void SyncUnionOverlayfs::CheckForBrokenHardlink(
124
    SharedPtr<SyncItem> entry) const {
125
  if (!entry->IsNew() && !entry->WasDirectory() &&
126
      entry->GetRdOnlyLinkcount() > 1) {
127
    LogCvmfs(kLogPublish, kLogStderr,
128
             "OverlayFS has copied-up a file (%s) "
129
             "with existing hardlinks in lowerdir "
130
             "(linkcount %d). OverlayFS cannot handle "
131
             "hardlinks and would produce "
132
             "inconsistencies. \n\n"
133
             "Consider running this command: \n"
134
             "  cvmfs_server eliminate-hardlinks\n\n"
135
             "Aborting...",
136
             entry->GetUnionPath().c_str(), entry->GetRdOnlyLinkcount());
137
    abort();
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);
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 && 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
   */
241
  bool is_chardev_whiteout = entry->IsCharacterDevice() &&
242
                             entry->GetRdevMajor() == 0 &&
243
                             entry->GetRdevMinor() == 0;
244
245
  bool is_symlink_whiteout =
246
      entry->IsSymlink() && IsWhiteoutSymlinkPath(entry->GetScratchPath());
247
248
  return is_chardev_whiteout || is_symlink_whiteout;
249
}
250
251
bool SyncUnionOverlayfs::IsWhiteoutSymlinkPath(const string &path) const {
252
  const bool is_whiteout = ReadlinkEquals(path, "(overlay-whiteout)");
253
  // TODO(reneme): check for the xattr trusted.overlay.whiteout
254
  //         Note: This requires CAP_SYS_ADMIN or root... >.<
255
  if (is_whiteout) {
256
    LogCvmfs(kLogUnionFs, kLogDebug, "OverlayFS [%s] is whiteout symlink",
257
             path.c_str());
258
  } else {
259
    LogCvmfs(kLogUnionFs, kLogDebug, "OverlayFS [%s] is not a whiteout symlink",
260
             path.c_str());
261
  }
262
  return is_whiteout;
263
}
264
265
bool SyncUnionOverlayfs::IsOpaqueDirectory(
266
    SharedPtr<SyncItem> directory) const {
267
  const std::string path = directory->GetScratchPath();
268
  return DirectoryExists(path) && IsOpaqueDirPath(path);
269
}
270
271
bool SyncUnionOverlayfs::IsOpaqueDirPath(const string &path) const {
272
  bool is_opaque = HasXattr(path.c_str(), "trusted.overlay.opaque");
273
  if (is_opaque) {
274
    LogCvmfs(kLogUnionFs, kLogDebug, "OverlayFS [%s] has opaque xattr",
275
             path.c_str());
276
  }
277
  return is_opaque;
278
}
279
280
string SyncUnionOverlayfs::UnwindWhiteoutFilename(
281
    SharedPtr<SyncItem> entry) const {
282
  return entry->filename();
283
}
284
}  // namespace publish