Initial support for VFS-related metadata in User Extended Attributes.
authorFrantišek Dvořák <valtri@civ.zcu.cz>
Mon, 14 Oct 2013 16:20:13 +0000 (18:20 +0200)
committerFrantišek Dvořák <valtri@civ.zcu.cz>
Thu, 17 Oct 2013 19:44:45 +0000 (21:44 +0200)
src/VfsNs.cpp
src/VfsNs.h

index 538567d..83e4db5 100644 (file)
 #include "Vfs.h"
 #include "VfsNs.h"
 
+#define VFS_XATTR "#vfs."
+#define VFS_XATTR_LENGTH 5
+#define VFS_XATTR_TYPE_PERMS 1
+#define VFS_XATTR_TYPE_NONPERMS 2
+#define VFS_XATTR_TYPE_USER 4
+#define VFS_XATTR_TYPE_ALL 7
+#define IS_VFS_XATTR(NAME) (strncmp((NAME), VFS_XATTR, VFS_XATTR_LENGTH) == 0)
+
+// force everything to current owner for now
+#define VFS_UID_NONE xStat.stat.st_uid
+#define VFS_GID_NONE xStat.stat.st_gid
+
 using namespace dmlite;
 
 
@@ -103,7 +115,7 @@ void VfsCatalog::changeDir(const std::string& path) throw (DmException)
   if (vfsCheckPermissions(path, S_IEXEC | S_IREAD))
     vfsThrow(EACCES, "not enough permissions for '%s'", clientName.c_str());
 
-  meta = this->extendedStat(path);
+  meta = this->vfsExtendedStat(path);
   if (checkPermissions(this->secCtx_, meta.acl, meta.stat, S_IEXEC | S_IREAD) != 0)
     vfsThrow(EACCES, "not enough permissions for '%s' on '%s'", clientName.c_str(), meta.name.c_str());
 
@@ -136,6 +148,24 @@ std::string VfsCatalog::getWorkingDir(void) throw (DmException)
 
 
 ///
+/// Update ExtendedStat fields related to permissions.
+/// Requires user extended attributes retrieved with VFS_XATTR_TYPE_PERMS or
+/// VFS_XATTR_TYPE_ALL.
+///
+/// @param xStat   Updated ExtendedStat class.
+/// @param xattrs  User extended attributes (requires acl, group, and owner VFS
+///                attributes).
+///
+void VfsCatalog::vfsUpdateStat(ExtendedStat &xStat, Extensible xattrs) throw (DmException) {
+  if (xattrs.hasField(VFS_XATTR "acl"))
+    xStat.acl = Acl(xattrs.getString(VFS_XATTR "acl"));
+  xStat.stat.st_gid = xattrs.getUnsigned(VFS_XATTR "group", VFS_GID_NONE);
+  xStat.stat.st_uid = xattrs.getUnsigned(VFS_XATTR "owner", VFS_UID_NONE);
+}
+
+
+
+///
 /// Do simple stat()/lstat() without setting st_nlink and checking permissions.
 /// Optionally retrieves also the information related to permissions in
 /// optimized way (using only attributes related to permissions).
@@ -143,10 +173,12 @@ std::string VfsCatalog::getWorkingDir(void) throw (DmException)
 /// @param name    file name
 /// @param path    local disk namespace path
 /// @param follow  follow symlinks (to use stat() or lstat())
+/// @param perms   get information related to permissions
 //
-ExtendedStat VfsCatalog::vfsSimpleStat(const std::string& name, const std::string& path, const std::string& lpath, bool follow) throw (DmException) {
+ExtendedStat VfsCatalog::vfsSimpleStat(const std::string& name, const std::string& path, const std::string& lpath, bool follow, bool perms) throw (DmException) {
   ExtendedStat xStat;
   struct stat  fstat;
+  Extensible xattrs;
 
   if (follow)
     wrapCall(stat(lpath.c_str(), &fstat), "could not stat '%s'", path.c_str());
@@ -159,6 +191,15 @@ ExtendedStat VfsCatalog::vfsSimpleStat(const std::string& name, const std::strin
   xStat.status  = ExtendedStat::kOnline;
   xStat.acl     = Acl();
 
+  if (perms) {
+    xattrs = vfsGetXattrs(path, lpath, true, VFS_XATTR_TYPE_PERMS);
+    vfsUpdateStat(xStat, xattrs);
+  } else {
+    xStat.acl     = Acl();
+    xStat.stat.st_uid = VFS_UID_NONE;
+    xStat.stat.st_gid = VFS_GID_NONE;
+  }
+
   return xStat;
 }
 
@@ -171,8 +212,9 @@ ExtendedStat VfsCatalog::vfsSimpleStat(const std::string& name, const std::strin
 ///
 /// @param path    public namespace path
 /// @param follow  follow symlinks
+/// @param parms   get information related to permissions
 ///
-ExtendedStat VfsCatalog::vfsExtendedStat(const std::string& path, bool follow) throw (DmException) {
+ExtendedStat VfsCatalog::vfsExtendedStat(const std::string& path, bool follow, bool perms) throw (DmException) {
   ExtendedStat meta;
   std::vector<std::string> components;
   std::string dir;
@@ -188,13 +230,13 @@ ExtendedStat VfsCatalog::vfsExtendedStat(const std::string& path, bool follow) t
     if (i < 2) dir += components[i];
     else dir = dir + "/" + components[i];
 
-    meta = vfsSimpleStat(components[i], dir, getLocalPath(dir), true);
+    meta = vfsSimpleStat(components[i], dir, getLocalPath(dir), true, true);
 
     if (checkPermissions(this->secCtx_, meta.acl, meta.stat, S_IEXEC) != 0)
       vfsThrow(EACCES, "not enough permissions for '%s' to list '%s'", clientName.c_str(), meta.name.c_str());
   }
 
-  return vfsSimpleStat(components.back(), path, getLocalPath(path), follow);
+  return vfsSimpleStat(components.back(), path, getLocalPath(path), follow, perms);
 }
 
 
@@ -203,20 +245,30 @@ ExtendedStat VfsCatalog::extendedStat(const std::string& path, bool follow) thro
 {
   ExtendedStat meta;
   Extensible xattrs;
+  std::string key;
+  Extensible::const_iterator it;
 
   if (vfsCheckPermissions(path, S_IREAD))
     vfsThrow(EACCES, "not enough permissions for '%s'", clientName.c_str());
 
-  meta = vfsExtendedStat(path, follow);
+  meta = vfsExtendedStat(path, follow, false);
 
-  // not require working xattrs
-  try {
-    xattrs = vfsGetXattrs(path, getLocalPath(path), follow);
-    meta.copy(xattrs);
-  } catch (DmException e) {
-    if (e.code() != ENOTSUP) throw;
+  //
+  // User extended attributes are not supported on symlinks. We need the same
+  // owner, permissions and other atributes like the original file anyway.
+  // ==> always follow the symlinks.
+  //
+  xattrs = vfsGetXattrs(path, getLocalPath(path), true, VFS_XATTR_TYPE_ALL);
+
+  // copy non-VFS attributes
+  for (it = xattrs.begin(); it != xattrs.end(); it++) {
+    key = it->first;
+    if (!IS_VFS_XATTR(key.c_str()))
+      meta[key] = xattrs[key];
   }
 
+  vfsUpdateStat(meta, xattrs);
+
   meta["pool"] = std::string("vfs");
 
 #if 0
@@ -253,7 +305,7 @@ bool VfsCatalog::access(const std::string& path, int mode) throw (DmException)
     vfsThrow(EACCES, "not enough permissions for '%s'", clientName.c_str());
 
   try {
-    meta = this->extendedStat(path);
+    meta = this->vfsExtendedStat(path);
 
     perm = 0;
     if (mode & R_OK) perm  = S_IREAD;
@@ -311,7 +363,7 @@ std::vector<Replica> VfsCatalog::getReplicas(const std::string& path) throw (DmE
   if (vfsCheckPermissions(path, S_IREAD))
     vfsThrow(EACCES, "not enough permissions for '%s'", clientName.c_str());
 
-  xStat = this->extendedStat(path, true);
+  xStat = this->vfsExtendedStat(path, true, true);
   if (checkPermissions(this->secCtx_, xStat.acl, xStat.stat, S_IREAD) != 0)
     vfsThrow(EACCES, "not enough permissions for '%s' to read '%s'", clientName.c_str(), path.c_str());
 
@@ -366,7 +418,7 @@ std::string VfsCatalog::readLink(const std::string& path) throw (DmException)
   if (vfsCheckPermissions(path, S_IREAD))
     vfsThrow(EACCES, "not enough permissions for '%s'", clientName.c_str());
 
-  meta = this->vfsExtendedStat(path, false);
+  meta = this->vfsExtendedStat(path, false, true);
   if (checkPermissions(this->secCtx_, meta.acl, meta.stat, S_IREAD) != 0)
     vfsThrow(EACCES, "not enough permissions for '%s' on '%s'", clientName.c_str(), path.c_str());
 
@@ -384,7 +436,7 @@ std::string VfsCatalog::readLink(const std::string& path) throw (DmException)
 
 void VfsCatalog::unlink(const std::string& path) throw (DmException)
 {
-  std::string parentPath, name;
+  std::string parentPath, name, lpath;
 
   if (vfsCheckPermissions(path, S_IWRITE))
     vfsThrow(EACCES, "not enough permissions for '%s'", clientName.c_str());
@@ -396,16 +448,19 @@ void VfsCatalog::unlink(const std::string& path) throw (DmException)
   if (checkPermissions(this->secCtx_, parent.acl, parent.stat, S_IWRITE) != 0)
     vfsThrow(EACCES, "not enough permissions for '%s' on '%s' to unlink '%s'", clientName.c_str(), parentPath.c_str(), path.c_str());
 
+  lpath = getLocalPath(path);
+
   // Sticky bit set ==> only directory or file owner can delete
   if ((parent.stat.st_mode & S_ISVTX) == S_ISVTX) {
-    ExtendedStat file = this->extendedStat(path);
+    // not follow symlinks (remove them instead)
+    ExtendedStat file = this->vfsSimpleStat(name, path, lpath, false, true);
     if (getUid(this->secCtx_) != file.stat.st_uid &&
         getUid(this->secCtx_) != parent.stat.st_uid) {
       vfsThrow(EACCES, "not enough permissions for '%s' to unlink '%s' (sticky bit set)", clientName.c_str(), path.c_str());
     }
   }
 
-  wrapCall(::unlink(getLocalPath(path).c_str()));
+  wrapCall(::unlink(lpath.c_str()));
 }
 
 
@@ -427,8 +482,11 @@ void VfsCatalog::create(const std::string& path, mode_t mode) throw (DmException
   if (checkPermissions(this->secCtx_, parent.acl, parent.stat, S_IWRITE) != 0)
     vfsThrow(EACCES, "need write access for '%s' on '%s' to create '%s'", clientName.c_str(), parentPath.c_str(), path.c_str());
 
+  lpath = getLocalPath(path);
+
   try {
-    file = this->extendedStat(path);
+    // follow symlinks
+    file = this->vfsSimpleStat(name, path, lpath, true, true);
   } catch (DmException e) {
     code = DMLITE_ERRNO(e.code());
     if (code != ENOENT) throw;
@@ -449,7 +507,6 @@ void VfsCatalog::create(const std::string& path, mode_t mode) throw (DmException
   }
 #endif
 
-  lpath = getLocalPath(path);
   if (code == ENOENT) {
     // Create new
     wrapCall(f = fopen(lpath.c_str(), "w"));
@@ -487,7 +544,7 @@ void VfsCatalog::setMode(const std::string& path, mode_t mode) throw (DmExceptio
   if (vfsCheckPermissions(path, S_IWRITE))
     vfsThrow(EACCES, "not enough permissions for '%s'", clientName.c_str());
 
-  meta = this->vfsExtendedStat(path, true);
+  meta = this->vfsExtendedStat(path);
 
   // User has to be the owner, or root
   if (getUid(this->secCtx_) != meta.stat.st_uid &&
@@ -520,7 +577,7 @@ void VfsCatalog::setSize(const std::string& path, size_t newSize) throw (DmExcep
   if (vfsCheckPermissions(path, S_IWRITE))
     vfsThrow(EACCES, "not enough permissions for '%s'", clientName.c_str());
 
-  file = this->extendedStat(path);
+  file = this->vfsExtendedStat(path);
   if (S_ISDIR(file.stat.st_mode))
     vfsThrow(EISDIR, "'%s' is directory, can not truncate", path.c_str());
   if (getUid(this->secCtx_) != file.stat.st_uid &&
@@ -555,7 +612,7 @@ void VfsCatalog::utime(const std::string& path, const struct utimbuf* buf) throw
   if (vfsCheckPermissions(path, S_IWRITE))
     vfsThrow(EACCES, "not enough permissions for '%s'", clientName.c_str());
 
-  meta = this->extendedStat(path);
+  meta = this->vfsExtendedStat(path);
   if (getUid(this->secCtx_) != meta.stat.st_uid &&
       checkPermissions(this->secCtx_, meta.acl, meta.stat, S_IWRITE) != 0) {
     vfsThrow(EACCES, "not enough permissions for '%s' to modify the time of '%s'", clientName.c_str(), path.c_str());
@@ -608,12 +665,18 @@ void VfsCatalog::updateExtendedAttributes(const std::string& path,
 
   lpath = getLocalPath(path);
 
-  oldXattrs = vfsGetXattrs(path, lpath, true);
+  oldXattrs = meta;
   newXattrs.copy(attr);
+  oldXattrs.erase("pool");
+  newXattrs.erase("pool");
 
   for (it = newXattrs.begin(); it != newXattrs.end(); it++) {
     key = it->first;
 
+    // not synchronize anything VFS plugin related
+    if (IS_VFS_XATTR(key.c_str()))
+      vfsThrow(EPERM, "change VFS plugin attribute not permitted (%s)", key.c_str());
+
     // used to be bug in Extensible? bool type "true" not mapped to true
     //if (it->second.type() == typeid(bool)) value = Extensible::anyToBoolean(it->second) ? std::string("1") : std::string("0");
     //else value = Extensible::anyToString(it->second);
@@ -621,6 +684,7 @@ void VfsCatalog::updateExtendedAttributes(const std::string& path,
     // OK, it's working now :-)
     value = Extensible::anyToString(it->second);
 
+    // reconcile
     if (oldXattrs.hasField(key)) {
       oldvalue = Extensible::anyToString(oldXattrs[key]);
       if (oldvalue != value)
@@ -630,8 +694,14 @@ void VfsCatalog::updateExtendedAttributes(const std::string& path,
       vfsSetXattr(path, lpath, key, value, ATTR_CREATE);
     }
   }
+
   for (it = oldXattrs.begin(); it != oldXattrs.end(); it++) {
     key = it->first;
+
+    // not synchronize anything VFS plugin related
+    if (IS_VFS_XATTR(key.c_str()))
+      vfsThrow(DMLITE_SYSERR(EINVAL), "internal error: got VFS plugin attribute from extendedStat(), this should not happen");
+
     value = Extensible::anyToString(it->second);
     debug("removing '%s' = '%s' from '%s'", key.c_str(), value.c_str(), lpath.c_str());
     wrapCall(attr_remove(lpath.c_str(), key.c_str(), 0), "could not remove '%s' on '%s'", key.c_str(), path.c_str());
@@ -670,7 +740,7 @@ Directory* VfsCatalog::openDir(const std::string& path) throw (DmException)
   if (vfsCheckPermissions(path, S_IREAD))
     vfsThrow(EACCES, "not enough permissions for '%s'", clientName.c_str());
 
-  meta = this->extendedStat(path);
+  meta = this->vfsExtendedStat(path);
   if (checkPermissions(this->secCtx_, meta.acl, meta.stat, S_IREAD) != 0)
     vfsThrow(EACCES, "not enough permissions for '%s' to read '%s'", clientName.c_str(), path.c_str());
 
@@ -791,7 +861,7 @@ void VfsCatalog::rename(const std::string& oldPath, const std::string& newPath)
 
   // Check sticky
   if (oldParent.stat.st_mode & S_ISVTX) {
-    ExtendedStat old = this->extendedStat(oldPath);
+    ExtendedStat old = this->vfsExtendedStat(oldPath);
     if (getUid(this->secCtx_) != oldParent.stat.st_uid &&
         getUid(this->secCtx_) != old.stat.st_uid &&
         checkPermissions(this->secCtx_, old.acl, old.stat, S_IWRITE) != 0)
@@ -822,7 +892,7 @@ void VfsCatalog::removeDir(const std::string& path) throw (DmException)
     vfsThrow(EINVAL, "can not remove '/'");
 
   ExtendedStat parent = this->getParent(path, &parentPath, &name);
-  ExtendedStat entry = this->extendedStat(path);
+  ExtendedStat entry = this->vfsExtendedStat(path);
   if ((parent.stat.st_mode & S_ISVTX) == S_ISVTX) {
     // Sticky bit set
     if (getUid(this->secCtx_) != entry.stat.st_uid &&
@@ -915,9 +985,9 @@ ExtendedStat VfsCatalog::getParent(const std::string& path,
 
   // Get the files now
   if (!parentPath->empty()) {
-    return this->extendedStat(*parentPath);
+    return this->vfsExtendedStat(*parentPath);
   } else {
-    return this->extendedStat(this->getWorkingDir());
+    return this->vfsExtendedStat(this->getWorkingDir());
   }
 }
 
@@ -955,13 +1025,23 @@ int VfsCatalog::checkPermissions(const SecurityContext *context, const Acl &acl,
 
 
 
-Extensible VfsCatalog::vfsGetXattrs(const std::string& path, const std::string& lpath, bool follow) throw (DmException) {
+///
+/// Get extended attributes.
+///
+/// @param path    local disk namespace path
+/// @param follow  follow symlinks (not well supported, it should be always true), XXX: dangling symlinks fails
+/// @param amount  what to retrieve, bitmask:
+///   VFS_XATTR_TYPE_PERMS    = 1: permissions related attributes (#vfs.acl, #vfs.owner, #vfs.group, ...),
+///   VFS_XATTR_TYPE_NONPERMS = 2: other VFS plugin related attributes (#vfs.replica*, ...),
+///   VFS_XATTR_TYPE_USER     = 4: user attributes
+Extensible VfsCatalog::vfsGetXattrs(const std::string& path, const std::string& lpath, bool follow, int amount) throw (DmException) {
   int len;
   std::string attrValue;
   Extensible xattrs;
   attrlist_cursor_t attr_cursor;
   attrlist_t *list;
   attrlist_ent_t *entry;
+  int attrType;
 
   list = (attrlist_t *)this->buffer;
   memset(&attr_cursor, 0, sizeof attr_cursor);
@@ -969,13 +1049,25 @@ Extensible VfsCatalog::vfsGetXattrs(const std::string& path, const std::string&
     wrapCall(attr_list(lpath.c_str(), this->buffer, sizeof this->buffer, follow ? 0 : ATTR_DONTFOLLOW, &attr_cursor), "could not get list of extended attributes on '%s'", path.c_str());
     for (int i = 0; i < list->al_count; i++) {
       entry = ATTR_ENTRY(this->buffer, i);
-      // "selinux" is returned in the list, but failing to get the value
-      if (strcmp(entry->a_name, "selinux") == 0) {
-        //debug("skipping attribute '%s'", entry->a_name);
+      if (IS_VFS_XATTR(entry->a_name)) {
+        if (strcmp(entry->a_name + VFS_XATTR_LENGTH, "acl") == 0 ||
+            strcmp(entry->a_name + VFS_XATTR_LENGTH, "group") == 0 ||
+            strcmp(entry->a_name + VFS_XATTR_LENGTH, "owner") == 0)
+        {
+          attrType = VFS_XATTR_TYPE_PERMS;
+        } else {
+          attrType = VFS_XATTR_TYPE_NONPERMS;
+        }
       } else {
+        // "selinux" is returned in the list, but failing to get the value
+        if (strcmp(entry->a_name, "selinux") == 0) attrType = 0;
+        else attrType = VFS_XATTR_TYPE_USER;
+      }
+
+      if ((attrType & amount) != 0) {
         len = sizeof this->xattrValue;
         wrapCall(attr_get(lpath.c_str(), entry->a_name, this->xattrValue, &len, follow ? 0 : ATTR_DONTFOLLOW), "could not get extended attribute '%s' on '%s'", entry->a_name, path.c_str());
-        debug("'%s' = '%.*s'", entry->a_name, len, this->xattrValue);
+        debug("'%s' (type %d) = '%.*s'", entry->a_name, attrType, len, this->xattrValue);
         attrValue.assign(this->xattrValue, len);
         xattrs[entry->a_name] = attrValue;
       }
index 7dcf6db..d8fa1fb 100644 (file)
@@ -100,10 +100,11 @@ namespace dmlite {
     /// Get path on the local disk according to "public" path.
     /// Relative paths are unchanged.
     const std::string getLocalPath(const std::string &path);
-    ExtendedStat vfsSimpleStat(const std::string& name, const std::string& path, const std::string& lpath, bool follow) throw (DmException);
-    ExtendedStat vfsExtendedStat(const std::string& path, bool follow) throw (DmException);
+    void vfsUpdateStat(ExtendedStat &xStat, Extensible xattrs) throw (DmException);
+    ExtendedStat vfsSimpleStat(const std::string& name, const std::string& path, const std::string& lpath, bool follow, bool perms) throw (DmException);
+    ExtendedStat vfsExtendedStat(const std::string& path, bool follow = true, bool perms = true) throw (DmException);
     PrivateDir* vfsOpenDir(const std::string& lpath, const std::string& path) throw (DmException);
-    Extensible vfsGetXattrs(const std::string& path, const std::string& lpath, bool follow) throw (DmException);
+    Extensible vfsGetXattrs(const std::string& path, const std::string& lpath, bool follow, int amount) throw (DmException);
     void vfsSetXattr(const std::string& path, const std::string& lpath, const std::string key, const std::string value, int flags);
     regex_t *vfsCompileRegex(const char *name, const std::string value) throw (DmException);
     bool vfsEvalRegex(regex_t *allowRegex, regex_t *denyRegex, const char *subj);