// ---------------------------------------------------------------------- // File: Mkdir.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. //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ /* * @brief create a directory with the given mode * * @param inpath directory path to create * @param Mode mode to set * @param error error object * @param client XRootD authentication object * @param ininfo CGI * @param outino return inode number * @return SFS_OK if success otherwise SFS_ERROR * * If mode contains SFS_O_MKPTH the full path is (possibly) created. */ //------------------------------------------------------------------------------ int XrdMgmOfs::mkdir(const char* inpath, XrdSfsMode Mode, XrdOucErrInfo& error, const XrdSecEntity* client, const char* ininfo, ino_t* outino) { static const char* epname = "mkdir"; 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_Mkdir, inpath); EXEC_TIMING_END("IdMap"); NAMESPACEMAP; BOUNCE_ILLEGAL_NAMES; TOKEN_SCOPE; XrdOucEnv mkdir_Env(ininfo); gOFS->MgmStats.Add("IdMap", vid.uid, vid.gid, 1); eos_info("path=%s ininfo=%s", path, ininfo); BOUNCE_NOT_ALLOWED; ACCESSMODE_W; MAYSTALL; MAYREDIRECT; return _mkdir(path, Mode, error, vid, ininfo, outino); } //------------------------------------------------------------------------------ /* * @brief create a directory with the given mode * * @param inpath directory path to create * @param Mode mode to set * @param error error object * @param client XRootD authentication object * @param ininfo CGI * @param outino return inode number * @param nopermissioncheck - true: skip it false; do it * @return SFS_OK on success otherwise SFS_ERROR * * If mode contains SFS_O_MKPTH the full path is (possibly) created. * */ //------------------------------------------------------------------------------ int XrdMgmOfs::_mkdir(const char* path, XrdSfsMode Mode, XrdOucErrInfo& error, eos::common::VirtualIdentity& vid, const char* ininfo, ino_t* outino, bool nopermissioncheck) { static const char* epname = "_mkdir"; //mode_t acc_mode = (Mode & S_IAMB) | S_IFDIR; errno = 0; EXEC_TIMING_BEGIN("Mkdir"); gOFS->MgmStats.Add("Mkdir", vid.uid, vid.gid, 1); XrdOucString spath = path; eos_info("path=%s", spath.c_str()); if (!spath.beginswith("/")) { errno = EINVAL; return Emsg(epname, error, EINVAL, "create directory - you have to specify" " an absolute pathname", path); } bool recurse = false; eos::common::Path cPath(path); bool noParent = false; std::shared_ptr dir; eos::IContainerMD::XAttrMap attrmap; { eos::Prefetcher::prefetchContainerMDAndWait(gOFS->eosView, cPath.GetParentPath()); eos::common::RWMutexReadLock lock(gOFS->eosViewRWMutex); // Check for the parent directory if (spath != "/") { try { dir = eosView->getContainer(cPath.GetParentPath()); } catch (eos::MDException& e) { eos_debug("msg=\"exception\" ec=%d emsg=\"%s\"\n", e.getErrno(), e.getMessage().str().c_str()); dir.reset(); noParent = true; } } // Check permission if (dir) { uid_t d_uid = dir->getCUid(); gid_t d_gid = dir->getCGid(); // ACL and permission check Acl acl(cPath.GetParentPath(), error, vid, attrmap, false); eos_info("path=%s acl=%d r=%d w=%d wo=%d egroup=%d mutable=%d", cPath.GetParentPath(), acl.HasAcl(), acl.CanRead(), acl.CanWrite(), acl.CanWriteOnce(), acl.HasEgroup(), acl.IsMutable()); if (!nopermissioncheck) { // Immutable directory if (vid.uid && !acl.IsMutable()) { errno = EPERM; return Emsg(epname, error, EPERM, "create directory - immutable", cPath.GetParentPath()); } } bool sticky_owner; attr::checkDirOwner(attrmap, d_uid, d_gid, vid, sticky_owner, path); bool stdpermcheck = false; if (acl.HasAcl()) { if ((!acl.CanWrite()) && (!acl.CanWriteOnce())) { // we have to check the standard permissions stdpermcheck = true; } } else { stdpermcheck = true; } // Admin can always create a directory if (!nopermissioncheck && stdpermcheck && (!dir->access(vid.uid, vid.gid, X_OK | W_OK))) { errno = EPERM; return Emsg(epname, error, EPERM, "access(XW) parent directory", cPath.GetParentPath()); } if (sticky_owner) { eos_info("msg=\"client acting as directory owner\" path=\"%s\"uid=\"%u=>%u\" gid=\"%u=>%u\"", path, vid.uid, vid.gid, d_uid, d_gid); // The client can operate as the owner, we rewrite the virtual identity // to the directory uid/gid pair vid.uid = d_uid; vid.gid = d_gid; } } } // Check if the path exists anyway if (Mode & SFS_O_MKPTH) { recurse = true; eos_debug("SFS_O_MKPATH set", path); if (dir) { std::shared_ptr fulldir; eos::Prefetcher::prefetchContainerMDAndWait(gOFS->eosView, path); eos::common::RWMutexReadLock lock(gOFS->eosViewRWMutex); // Only if the parent exists, can the full path exist! try { fulldir = eosView->getContainer(path); } catch (eos::MDException& e) { fulldir.reset(); eos_debug("msg=\"exception\" ec=%d emsg=\"%s\"\n", e.getErrno(), e.getMessage().str().c_str()); } if (fulldir) { EXEC_TIMING_END("Exists"); return SFS_OK; } } } eos_debug("mkdir path=%s deepness=%d dirname=%s basename=%s", path, cPath.GetSubPathSize(), cPath.GetParentPath(), cPath.GetName()); std::shared_ptr newdir; if (noParent) { if (recurse) { int i, j; uid_t d_uid = 99; gid_t d_gid = 99; std::string existingdir; // Walk up the paths until one exists for (i = cPath.GetSubPathSize() - 1; i >= 0; i--) { eos_debug("testing path %s", cPath.GetSubPath(i)); errno = 0; eos::Prefetcher::prefetchContainerMDAndWait(gOFS->eosView, cPath.GetSubPath(i)); eos::common::RWMutexReadLock lock(gOFS->eosViewRWMutex); attrmap.clear(); try { dir = eosView->getContainer(cPath.GetSubPath(i)); existingdir = cPath.GetSubPath(i); d_uid = dir->getCUid(); d_gid = dir->getCGid(); } catch (eos::MDException& e) { dir.reset(); } if (dir) { break; } } // This is really a serious problem! if (!dir) { eos_crit("didn't find any parent path traversing the namespace"); errno = ENODATA; return Emsg(epname, error, ENODATA, "create directory", cPath.GetSubPath(i)); } // ACL and permission check Acl acl(existingdir.c_str(), error, vid, attrmap, true); eos_info("acl=%d r=%d w=%d wo=%d egroup=%d mutable=%d", acl.HasAcl(), acl.CanRead(), acl.CanWrite(), acl.CanWriteOnce(), acl.HasEgroup(), acl.IsMutable()); // Check for sys.owner.auth entries, which let people operate as the owner of the directory bool sticky_owner; if (attr::checkDirOwner(attrmap, d_uid, d_gid, vid, sticky_owner, path)) { if (sticky_owner) { vid.uid = d_uid; vid.gid = d_gid; } eos_info("msg=\"client acting as directory owner\" path=\"%s\"uid=\"%u=>%u\" gid=\"%u=>%u\"", existingdir.c_str(), vid.uid, vid.gid, d_uid, d_gid); } if (vid.uid && !acl.IsMutable()) { errno = EPERM; return Emsg(epname, error, EPERM, "create parent directory - immutable", cPath.GetParentPath()); } bool stdpermcheck = false; if (acl.HasAcl()) { if (!acl.CanWrite() && !acl.CanWriteOnce()) { // We have to check the standard permissions stdpermcheck = true; } } else { stdpermcheck = true; } if (stdpermcheck && (!dir->access(vid.uid, vid.gid, X_OK | W_OK))) { errno = EPERM; return Emsg(epname, error, EPERM, "create parent directory", cPath.GetParentPath()); } eos::common::Path tmp_path(""); for (j = i + 1; j < (int) cPath.GetSubPathSize(); ++j) { eos::common::RWMutexWriteLock lock(gOFS->eosViewRWMutex); try { errno = 0; eos_debug("creating path %s", cPath.GetSubPath(j)); tmp_path.Init(cPath.GetSubPath(j)); dir = eosView->getContainer(tmp_path.GetParentPath()); newdir = eosView->createContainer(cPath.GetSubPath(j), recurse); newdir->setCUid(vid.uid); newdir->setCGid(vid.gid); newdir->setMode(dir->getMode() & ~(1UL << 9)); // Inherit the attributes eos::IFileMD::XAttrMap xattrs = dir->getAttributes(); for (const auto& elem : xattrs) { newdir->setAttribute(elem.first, elem.second); } // Store the in-memory modification time into the parent eos::IContainerMD::ctime_t ctime; newdir->getCTime(ctime); newdir->setMTime(ctime); // Store the birth time char btime[256]; snprintf(btime, sizeof(btime), "%lu.%lu", ctime.tv_sec, ctime.tv_nsec); newdir->setAttribute("sys.eos.btime", btime); dir->setMTime(ctime); dir->notifyMTimeChange(gOFS->eosDirectoryService); // commit eosView->updateContainerStore(newdir.get()); eosView->updateContainerStore(dir.get()); dir->notifyMTimeChange(gOFS->eosDirectoryService); newdir->notifyMTimeChange(gOFS->eosDirectoryService); eos::ContainerIdentifier nd_id = newdir->getIdentifier(); eos::ContainerIdentifier d_id = dir->getIdentifier(); eos::ContainerIdentifier d_pid = dir->getParentIdentifier(); lock.Release(); gOFS->FuseXCastRefresh(nd_id, d_id); gOFS->FuseXCastRefresh(d_id, d_pid); } catch (eos::MDException& e) { errno = e.getErrno(); eos_debug("msg=\"exception\" ec=%d emsg=\"%s\"\n", e.getErrno(), e.getMessage().str().c_str()); return Emsg(epname, error, errno, "mkdir", path); } if (!newdir && (errno != EEXIST)) { return Emsg(epname, error, errno, "mkdir - newdir is 0", path); } dir.swap(newdir); } } else { errno = ENOENT; return Emsg(epname, error, errno, "mkdir", path); } } // This might not be needed, but it is detected by coverty if (!dir) { return Emsg(epname, error, errno, "mkdir", path); } eos::common::RWMutexWriteLock lock(gOFS->eosViewRWMutex); try { errno = 0; dir = eosView->getContainer(cPath.GetParentPath()); newdir = eosView->createContainer(path); newdir->setCUid(vid.uid); newdir->setCGid(vid.gid); // @note: we always inherit the mode of the parent directory. So far nobody // complained so we'll keep it as it is until someone does. The VTX bit is removed. //newdir->setMode(acc_mode); newdir->setMode(dir->getMode() & ~(1UL << 9)); // Store the in-memory modification time eos::IContainerMD::ctime_t ctime; newdir->getCTime(ctime); newdir->setMTime(ctime); // Store the birth time char btime[256]; snprintf(btime, sizeof(btime), "%lu.%lu", ctime.tv_sec, ctime.tv_nsec); newdir->setAttribute("sys.eos.btime", btime); dir->setMTime(ctime); // If not version directory, then inherit attributes if (cPath.GetFullPath().find(EOS_COMMON_PATH_VERSION_PREFIX) == STR_NPOS) { eos::IFileMD::XAttrMap xattrs = dir->getAttributes(); for (const auto& elem : xattrs) { newdir->setAttribute(elem.first, elem.second); } } if (outino) { *outino = newdir->getId(); } // Commit to backend eosView->updateContainerStore(newdir.get()); eosView->updateContainerStore(dir.get()); // Notify after attribute inheritance newdir->notifyMTimeChange(gOFS->eosDirectoryService); dir->notifyMTimeChange(gOFS->eosDirectoryService); eos::ContainerIdentifier nd_id = newdir->getIdentifier(); eos::ContainerIdentifier d_id = dir->getIdentifier(); eos::ContainerIdentifier d_pid = dir->getParentIdentifier(); lock.Release(); gOFS->FuseXCastRefresh(nd_id, d_id); gOFS->FuseXCastRefresh(d_id, d_pid); } catch (eos::MDException& e) { errno = e.getErrno(); eos_debug("msg=\"exception\" ec=%d emsg=\"%s\"", e.getErrno(), e.getMessage().str().c_str()); } if (!newdir && (errno != EEXIST)) { return Emsg(epname, error, errno, "mkdir", path); } EXEC_TIMING_END("Mkdir"); return SFS_OK; }