From 9a65f023ace226865fa1f8032ca15a0a038f1156 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Franti=C5=A1ek=20Dvo=C5=99=C3=A1k?= Date: Mon, 14 Oct 2013 18:20:13 +0200 Subject: [PATCH] Initial support for VFS-related metadata in User Extended Attributes. --- src/VfsNs.cpp | 160 +++++++++++++++++++++++++++++++++++++++++++++------------- src/VfsNs.h | 7 +-- 2 files changed, 130 insertions(+), 37 deletions(-) diff --git a/src/VfsNs.cpp b/src/VfsNs.cpp index 538567d..83e4db5 100644 --- a/src/VfsNs.cpp +++ b/src/VfsNs.cpp @@ -17,6 +17,18 @@ #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 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 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; } diff --git a/src/VfsNs.h b/src/VfsNs.h index 7dcf6db..d8fa1fb 100644 --- a/src/VfsNs.h +++ b/src/VfsNs.h @@ -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); -- 1.8.2.3