// ---------------------------------------------------------------------- // File: Attr.cc // Author: Andreas-Joachim Peters - CERN // ---------------------------------------------------------------------- /************************************************************************ * EOS - the CERN Disk Storage System * * Copyright (C) 2011 CERN/Switzerland * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see .* ************************************************************************/ //------------------------------------------------------------------------------ // This file is included source code in XrdMgmOfs.cc to make the code more // transparent without slowing down the compilation time. //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ // List extended attributes for a given file/directory - high-level API. //------------------------------------------------------------------------------ int XrdMgmOfs::attr_ls(const char* inpath, XrdOucErrInfo& error, const XrdSecEntity* client, const char* ininfo, eos::IContainerMD::XAttrMap& map) { static const char* epname = "attr_ls"; const char* tident = error.getErrUser(); // use a thread private vid eos::common::VirtualIdentity vid; EXEC_TIMING_BEGIN("IdMap"); eos::common::Mapping::IdMap(client, ininfo, tident, vid, gOFS->mTokenAuthz, AOP_Stat, inpath); EXEC_TIMING_END("IdMap"); NAMESPACEMAP; BOUNCE_ILLEGAL_NAMES; XrdOucEnv access_Env(ininfo); AUTHORIZE(client, &access_Env, AOP_Stat, "access", inpath, error); gOFS->MgmStats.Add("IdMap", vid.uid, vid.gid, 1); BOUNCE_NOT_ALLOWED; return _attr_ls(path, error, vid, ininfo, map); } //------------------------------------------------------------------------------ // List extended attributes for a given file/directory - low-level API. //------------------------------------------------------------------------------ int XrdMgmOfs::_attr_ls(const char* path, XrdOucErrInfo& error, const eos::common::VirtualIdentity& vid, const char* info, eos::IContainerMD::XAttrMap& map, bool links) { static const char* epname = "attr_ls"; std::shared_ptr dh; EXEC_TIMING_BEGIN("AttrLs"); gOFS->MgmStats.Add("AttrLs", vid.uid, vid.gid, 1); errno = 0; eos::Prefetcher::prefetchItemAndWait(gOFS->eosView, path); try { eos::FileOrContainerMD item = gOFS->eosView->getItem(path).get(); listAttributes(gOFS->eosView, item, map, links); // we never show obfuscion keys map.erase("user.obfuscate.key"); } catch (eos::MDException& e) { errno = e.getErrno(); eos_debug("msg=\"exception\" ec=%d emsg=\"%s\"\n", e.getErrno(), e.getMessage().str().c_str()); } EXEC_TIMING_END("AttrLs"); if (errno) { return Emsg(epname, error, errno, "list attributes", path); } return SFS_OK; } //------------------------------------------------------------------------------ // Set an extended attribute for a given file/directory - high-level API. //------------------------------------------------------------------------------ int XrdMgmOfs::attr_set(const char* inpath, XrdOucErrInfo& error, const XrdSecEntity* client, const char* ininfo, const char* key, const char* value) { static const char* epname = "attr_set"; const char* tident = error.getErrUser(); // use a thread private vid eos::common::VirtualIdentity vid; EXEC_TIMING_BEGIN("IdMap"); eos::common::Mapping::IdMap(client, ininfo, tident, vid, gOFS->mTokenAuthz, AOP_Update, inpath); EXEC_TIMING_END("IdMap"); NAMESPACEMAP; BOUNCE_ILLEGAL_NAMES; XrdOucEnv access_Env(ininfo); AUTHORIZE(client, &access_Env, AOP_Update, "update", inpath, error); gOFS->MgmStats.Add("IdMap", vid.uid, vid.gid, 1); BOUNCE_NOT_ALLOWED; return _attr_set(path, error, vid, ininfo, key, value); } //------------------------------------------------------------------------------ // Set an extended attribute for a given file/directory - low-level API. //------------------------------------------------------------------------------ int XrdMgmOfs::_attr_set(const char* path, XrdOucErrInfo& error, eos::common::VirtualIdentity& vid, const char* info, const char* key, const char* value, bool exclusive) { static const char* epname = "attr_set"; EXEC_TIMING_BEGIN("AttrSet"); gOFS->MgmStats.Add("AttrSet", vid.uid, vid.gid, 1); errno = 0; if (!key || !value) { errno = EINVAL; return Emsg(epname, error, errno, "set attribute", path); } XrdOucString Key = key; if (Key.beginswith("sys.") && ((!vid.sudoer) && (vid.uid))) { errno = EPERM; return Emsg(epname, error, errno, "set attribute", path); } // Never put any attribute on version directories if ((strstr(path, EOS_COMMON_PATH_VERSION_PREFIX) != 0) && ((Key.beginswith("sys.forced")) || (Key.beginswith("user.forced")))) { return SFS_OK; } std::shared_ptr dh; eos::IContainerMD::IContainerMDWriteLockerPtr dhLock; eos::Prefetcher::prefetchContainerMDAndWait(gOFS->eosView, path); try { dhLock = gOFS->eosView->getContainerWriteLocked(path); dh = dhLock->getUnderlyingPtr(); // Check permissions in case of user attributes if (!Key.beginswith("sys.") && (vid.uid != dh->getCUid()) && (!vid.sudoer && vid.uid)) { errno = EPERM; } else { XrdOucString val64 = value; XrdOucString ouc_val; eos::common::SymKey::DeBase64(val64, ouc_val); std::string val = ouc_val.c_str(); if (Key.beginswith("sys.acl") || Key.beginswith("user.acl")) { bool is_sys_acl = Key.beginswith("sys.acl"); // Check format of acl if (!Acl::IsValid(val, error, is_sys_acl) && !Acl::IsValid(val, error, is_sys_acl, true)) { errno = EINVAL; return Emsg(epname, error, errno, "set attribute (invalid acl format)", path); } // Convert to numeric representation if (Acl::ConvertIds(val)) { errno = EINVAL; return Emsg(epname, error, errno, "set attribute (failed id conver)", path); } } if (exclusive && dh->hasAttribute(Key.c_str())) { errno = EEXIST; return Emsg(epname, error, errno, "set attribute (exclusive set for existing attribute)", path); } dh->setAttribute(key, val.c_str()); if (Key != "sys.tmp.etag") { dh->setCTimeNow(); } eos::ContainerIdentifier d_id = dh->getIdentifier(); eos::ContainerIdentifier d_pid = dh->getParentIdentifier(); eosView->updateContainerStore(dh.get()); // Release the current lock on the object before broadcasting to fuse dhLock.reset(nullptr); gOFS->FuseXCastRefresh(d_id, d_pid); errno = 0; } } catch (eos::MDException& e) { dh.reset(); errno = e.getErrno(); eos_debug("msg=\"exception\" ec=%d emsg=\"%s\"\n", e.getErrno(), e.getMessage().str().c_str()); } if (!dh) { std::shared_ptr fmd; eos::IFileMD::IFileMDWriteLockerPtr fmdLock; try { fmdLock = gOFS->eosView->getFileWriteLocked(path); fmd = fmdLock->getUnderlyingPtr(); // Check permissions in case of user attributes if (!Key.beginswith("sys.") && (vid.uid != fmd->getCUid()) && (!vid.sudoer && vid.uid)) { errno = EPERM; } else { if (exclusive && fmd->hasAttribute(Key.c_str())) { errno = EEXIST; return Emsg(epname, error, errno, "set attribute (exclusive set for existing attribute)", path); } // screen for attribute locks if (Key == eos::common::EOS_APP_LOCK_ATTR) { errno = 0; eos::IContainerMD::XAttrMap map = fmd->getAttributes(); XattrLock applock(map); if (applock.foreignLock(vid, true)) { errno = EBUSY; return Emsg(epname, error, errno, "set attribute (foreign attribute lock existing)", path); } } XrdOucString val64 = value; XrdOucString val; eos::common::SymKey::DeBase64(val64, val); fmd->setAttribute(key, val.c_str()); if (Key != "sys.tmp.etag") { fmd->setCTimeNow(); } eos::FileIdentifier f_id = fmd->getIdentifier(); eos::ContainerIdentifier c_id = eos::ContainerIdentifier(fmd->getContainerId()); eosView->updateFileStore(fmd.get()); // Release the current lock on the object before broadcasting to fuse fmdLock.reset(nullptr); gOFS->FuseXCastRefresh(f_id, c_id); errno = 0; } } catch (eos::MDException& e) { fmd.reset(); errno = e.getErrno(); eos_debug("msg=\"exception\" ec=%d emsg=\"%s\"\n", e.getErrno(), e.getMessage().str().c_str()); } } EXEC_TIMING_END("AttrSet"); if (errno) { return Emsg(epname, error, errno, "set attributes", path); } return SFS_OK; } //------------------------------------------------------------------------------ // Get an extended attribute for a given entry by key - high-level API. //------------------------------------------------------------------------------ int XrdMgmOfs::attr_get(const char* inpath, XrdOucErrInfo& error, const XrdSecEntity* client, const char* ininfo, const char* key, XrdOucString& value) { static const char* epname = "attr_get"; const char* tident = error.getErrUser(); // use a thread private vid eos::common::VirtualIdentity vid; EXEC_TIMING_BEGIN("IdMap"); eos::common::Mapping::IdMap(client, ininfo, tident, vid, gOFS->mTokenAuthz, AOP_Stat, inpath); EXEC_TIMING_END("IdMap"); NAMESPACEMAP; BOUNCE_ILLEGAL_NAMES; XrdOucEnv access_Env(ininfo); AUTHORIZE(client, &access_Env, AOP_Stat, "access", inpath, error); gOFS->MgmStats.Add("IdMap", vid.uid, vid.gid, 1); BOUNCE_NOT_ALLOWED; return _attr_get(path, error, vid, ininfo, key, value); } //------------------------------------------------------------------------------ // Get an extended attribute for a given entry by key - low-level API. //------------------------------------------------------------------------------ int XrdMgmOfs::_attr_get(const char* path, XrdOucErrInfo& error, eos::common::VirtualIdentity& vid, const char* info, const char* key, XrdOucString& value) { static const char* epname = "attr_get"; std::shared_ptr dh; EXEC_TIMING_BEGIN("AttrGet"); gOFS->MgmStats.Add("AttrGet", vid.uid, vid.gid, 1); errno = 0; if (!key) { return Emsg(epname, error, EINVAL, "get attribute", path); } value = ""; XrdOucString link; bool b64 = false; if (info) { XrdOucEnv env(info); if (env.Get("eos.attr.val.encoding") && (std::string(env.Get("eos.attr.val.encoding")) == "base64")) { b64 = true; } } eos::Prefetcher::prefetchContainerMDAndWait(gOFS->eosView, path); { eos::IContainerMD::IContainerMDReadLockerPtr dhLock; try { dhLock = gOFS->eosView->getContainerReadLocked(path); dh = dhLock->getUnderlyingPtr(); value = (dh->getAttribute(key)).c_str(); } catch (eos::MDException& e) { errno = e.getErrno(); eos_debug("msg=\"exception\" ec=%d emsg=\"%s\"\n", e.getErrno(), e.getMessage().str().c_str()); } if (dh && errno) { // try linked attributes try { std::string lkey = "sys.attr.link"; link = (dh->getAttribute(lkey)).c_str(); dhLock = gOFS->eosView->getContainerReadLocked(link.c_str()); dh = dhLock->getUnderlyingPtr(); value = (dh->getAttribute(key)).c_str(); errno = 0; } catch (eos::MDException& e) { dh.reset(); errno = e.getErrno(); eos_debug("msg=\"exception\" ec=%d emsg=\"%s\"\n", e.getErrno(), e.getMessage().str().c_str()); } } } if (!dh) { std::shared_ptr fmd; eos::IFileMD::IFileMDReadLockerPtr fmdLock; try { fmdLock = gOFS->eosView->getFileReadLocked(path); fmd = fmdLock->getUnderlyingPtr(); if (fmd) { value = (fmd->getAttribute(key)).c_str(); } errno = 0; if (std::string(key) == "user.obfuscate.key") { // we never show this key value = ""; } } catch (eos::MDException& e) { errno = e.getErrno(); eos_debug("msg=\"exception\" ec=%d emsg=\"%s\"\n", e.getErrno(), e.getMessage().str().c_str()); } } // we always decode attributes here, even if they are stored as base64: XrdOucString val64 = value; eos::common::SymKey::DeBase64(val64, value); if (b64) { // on request do base64 encoding XrdOucString nb64 = value; eos::common::SymKey::Base64(nb64, value); } EXEC_TIMING_END("AttrGet"); if (errno) { return Emsg(epname, error, errno, "get attributes", path); } return SFS_OK; } //------------------------------------------------------------------------------ // Get extended attribute for a given md object - low-level API. //------------------------------------------------------------------------------ template static bool attrGetInternal(T& md, std::string key, std::string& rvalue) { //------------------------------------------------------------------------------ // First, check if the cmd itself contains the attribute. //------------------------------------------------------------------------------ if (md.hasAttribute(key)) { rvalue = md.getAttribute(key); return true; } //---------------------------------------------------------------------------- // Nope.. does the fmd have linked attributes? //---------------------------------------------------------------------------- const std::string kMagicKey = "sys.attr.link"; if (!md.hasAttribute(kMagicKey)) { // Nope return false; } //---------------------------------------------------------------------------- // It does, fetch linked container //---------------------------------------------------------------------------- std::string linkedContainer = md.getAttribute(kMagicKey); std::shared_ptr dh; eos::IContainerMD::IContainerMDReadLockerPtr dhLock; eos::Prefetcher::prefetchContainerMDAndWait(gOFS->eosView, linkedContainer); try { dhLock = gOFS->eosView->getContainerReadLocked(linkedContainer); dh = dhLock->getUnderlyingPtr(); } catch (eos::MDException& e) { errno = e.getErrno(); eos_static_err("msg=\"exception while following linked container\" ec=%d emsg=\"%s\"\n", e.getErrno(), e.getMessage().str().c_str()); return false; } //---------------------------------------------------------------------------- // We have the linked container, lookup. //---------------------------------------------------------------------------- if (!dh->hasAttribute(key)) { return false; } rvalue = dh->getAttribute(key); return true; } //------------------------------------------------------------------------------ // Get extended attribute for a given cmd - low-level API. //------------------------------------------------------------------------------ bool XrdMgmOfs::_attr_get(eos::IContainerMD& cmd, std::string key, std::string& rvalue) { return attrGetInternal(cmd, key, rvalue); } //------------------------------------------------------------------------------ // Get extended attribute for a given fmd - low-level API. //------------------------------------------------------------------------------ bool XrdMgmOfs::_attr_get(eos::IFileMD& fmd, std::string key, std::string& rvalue) { return attrGetInternal(fmd, key, rvalue); } //------------------------------------------------------------------------------ // Remove an extended attribute for a given entry - high-level API. //------------------------------------------------------------------------------ int XrdMgmOfs::attr_rem(const char* inpath, XrdOucErrInfo& error, const XrdSecEntity* client, const char* ininfo, const char* key) { static const char* epname = "attr_rm"; const char* tident = error.getErrUser(); // use a thread private vid eos::common::VirtualIdentity vid; EXEC_TIMING_BEGIN("IdMap"); eos::common::Mapping::IdMap(client, ininfo, tident, vid, gOFS->mTokenAuthz, AOP_Delete, inpath); EXEC_TIMING_END("IdMap"); NAMESPACEMAP; BOUNCE_ILLEGAL_NAMES; XrdOucEnv access_Env(ininfo); AUTHORIZE(client, &access_Env, AOP_Delete, "delete", inpath, error); gOFS->MgmStats.Add("IdMap", vid.uid, vid.gid, 1); BOUNCE_NOT_ALLOWED; return _attr_rem(path, error, vid, ininfo, key); } //------------------------------------------------------------------------------ // Remove an extended attribute for a given entry - low-level API. //------------------------------------------------------------------------------ int XrdMgmOfs::_attr_rem(const char* path, XrdOucErrInfo& error, eos::common::VirtualIdentity& vid, const char* info, const char* key) { static const char* epname = "attr_rm"; std::shared_ptr dh; std::shared_ptr fmd; errno = 0; EXEC_TIMING_BEGIN("AttrRm"); gOFS->MgmStats.Add("AttrRm", vid.uid, vid.gid, 1); if (!key) { return Emsg(epname, error, EINVAL, "delete attribute", path); } eos::Prefetcher::prefetchContainerMDAndWait(gOFS->eosView, path); try { auto dhLock = gOFS->eosView->getContainerWriteLocked(path); dh = dhLock->getUnderlyingPtr(); XrdOucString Key = key; if (Key.beginswith("sys.") && ((!vid.sudoer) && (vid.uid))) { errno = EPERM; } else { // TODO: REVIEW: check permissions if (!dh->access(vid.uid, vid.gid, X_OK | W_OK)) { errno = EPERM; } else { if (dh->hasAttribute(key)) { dh->removeAttribute(key); eos::ContainerIdentifier d_id = dh->getIdentifier(); eos::ContainerIdentifier d_pid = dh->getParentIdentifier(); eosView->updateContainerStore(dh.get()); // Release object lock before doing the fuse refresh dhLock.reset(nullptr); gOFS->FuseXCastRefresh(d_id, d_pid); } else { errno = ENODATA; } } } } catch (eos::MDException& e) { dh.reset(); errno = e.getErrno(); eos_debug("msg=\"exception\" ec=%d emsg=\"%s\"\n", e.getErrno(), e.getMessage().str().c_str()); } if (!dh) { try { auto fmdLock = gOFS->eosView->getFileWriteLocked(path); fmd = fmdLock->getUnderlyingPtr(); XrdOucString Key = key; if (Key.beginswith("sys.") && ((!vid.sudoer) && (vid.uid))) { errno = EPERM; } else { if ((vid.uid != fmd->getCUid()) && (!vid.sudoer && vid.uid)) { // TODO: REVIEW: only owner/sudoer can delete file attributes errno = EPERM; } else { if (fmd->hasAttribute(key)) { fmd->removeAttribute(key); eosView->updateFileStore(fmd.get()); eos::FileIdentifier f_id = fmd->getIdentifier(); eos::ContainerIdentifier d_id(fmd->getContainerId()); fmdLock.reset(nullptr); gOFS->FuseXCastRefresh(f_id, d_id); errno = 0; } else { errno = ENODATA; } } } } catch (eos::MDException& e) { fmd.reset(); errno = e.getErrno(); eos_debug("msg=\"exception\" ec=%d emsg=\"%s\"\n", e.getErrno(), e.getMessage().str().c_str()); } } EXEC_TIMING_END("AttrRm"); if (errno) { return Emsg(epname, error, errno, "remove attribute", path); } return SFS_OK; } //------------------------------------------------------------------------------ // Remove all extended attributes for a given file/directory - low-level API. //------------------------------------------------------------------------------ int XrdMgmOfs::_attr_clear(const char* path, XrdOucErrInfo& error, eos::common::VirtualIdentity& vid, const char* info, bool keep_acls) { eos::IContainerMD::XAttrMap map; if (_attr_ls(path, error, vid, info, map)) { return SFS_ERROR; } int success = SFS_OK; for (auto it = map.begin(); it != map.end(); ++it) { if (keep_acls && ( (it->first == "sys.acl") || (it->first == "user.acl"))) { continue; } success |= _attr_rem(path, error, vid, info, it->first.c_str()); } return success; }