// ----------------------------------------------------------------------
// File: Rem.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.
// -----------------------------------------------------------------------
/*----------------------------------------------------------------------------*/
int
XrdMgmOfs::rem(const char* inpath,
XrdOucErrInfo& error,
const XrdSecEntity* client,
const char* ininfo)
/*----------------------------------------------------------------------------*/
/*
* @brief delete a file from the namespace
*
* @param inpath file to delete
* @param error error object
* @param client XRootD authenticiation object
* @param ininfo CGI
* @return SFS_OK if success otherwise SFS_ERROR
*
* Deletion supports a recycle bin. See internal implementation of _rem for details.
*/
/*----------------------------------------------------------------------------*/
{
static const char* epname = "rem";
const char* tident = error.getErrUser();
// use a thread private vid
eos::common::VirtualIdentity vid;
NAMESPACEMAP;
BOUNCE_ILLEGAL_NAMES;
TOKEN_SCOPE;
XrdOucEnv env(ininfo);
AUTHORIZE(client, &env, AOP_Delete, "remove", inpath, error);
EXEC_TIMING_BEGIN("IdMap");
eos::common::Mapping::IdMap(client, ininfo, tident, vid, gOFS->mTokenAuthz,
AOP_Delete, path);
EXEC_TIMING_END("IdMap");
gOFS->MgmStats.Add("IdMap", vid.uid, vid.gid, 1);
BOUNCE_NOT_ALLOWED;
ACCESSMODE_W;
MAYSTALL;
MAYREDIRECT;
return _rem(path, error, vid, ininfo);
}
/*----------------------------------------------------------------------------*/
int
XrdMgmOfs::_rem(const char* path,
XrdOucErrInfo& error,
eos::common::VirtualIdentity& vid,
const char* ininfo,
bool simulate,
bool keepversion,
bool no_recycling,
bool no_quota_enforcement,
bool fusexcast,
bool no_workflow)
/*----------------------------------------------------------------------------*/
/*
* @brief delete a file from the namespace
*
* @param inpath file to delete
* @param error error object
* @param vid virtual identity of the client
* @param ininfo CGI
* @param simulate indicates 'simulate deletion' e.g. it can be used as a test if a deletion would succeed
* @param keepversion indicates if the deletion should wipe the version directory
* @param no_recycling suppresses the recycle bin
* @param no_quota_enforcment disables quota check on the recycle bin
* @param fusexcast broadcast deletions if true
* @param no_workflow skip workflow if true
* @return SFS_OK if success otherwise SFS_ERROR
*
* Deletion supports the recycle bin if configured on the parent directory of
* the file to be deleted. The simulation mode is used to test if there is
* enough space in the recycle bin to move the object. If the simulation succeeds
* the real deletion is executed. 'keepversion' is needed when we want to recover
* an old version into the current version
*/
/*----------------------------------------------------------------------------*/
{
static const char* epname = "rem";
EXEC_TIMING_BEGIN("Rm");
eos_info("path=%s vid.uid=%u vid.gid=%u", path, vid.uid, vid.gid);
if (!simulate) {
gOFS->MgmStats.Add("Rm", vid.uid, vid.gid, 1);
}
std::string errMsg = "remove";
// Perform the actual deletion
errno = 0;
XrdSfsFileExistence file_exists;
vid.scope = path;
if ((_exists(path, file_exists, error, vid, 0))) {
return SFS_ERROR;
}
if (file_exists != XrdSfsFileExistIsFile) {
if (file_exists == XrdSfsFileExistIsDirectory) {
errno = EISDIR;
} else {
errno = ENOENT;
}
return Emsg(epname, error, errno, "remove", path);
}
// ---------------------------------------------------------------------------
eos::Prefetcher::prefetchFileMDAndWait(gOFS->eosView, path);
eos::common::RWMutexWriteLock lock(gOFS->eosViewRWMutex);
// free the booked quota
std::shared_ptr fmd;
std::shared_ptr container;
eos::IContainerMD::XAttrMap attrmap;
uid_t owner_uid = 0;
gid_t owner_gid = 0;
eos::common::FileId::fileid_t fid = 0;
bool doRecycle = false; // indicating two-step deletion via recycle-bin
std::string aclpath;
try {
fmd = gOFS->eosView->getFile(path, false);
} catch (eos::MDException& e) {
errno = e.getErrno();
eos_debug("msg=\"exception\" ec=%d emsg=\"%s\"\n", e.getErrno(),
e.getMessage().str().c_str());
}
uid_t container_owner_uid = 0;
bool container_vtx = false;
if (fmd) {
owner_uid = fmd->getCUid();
owner_gid = fmd->getCGid();
fid = fmd->getId();
try {
container = gOFS->eosDirectoryService->getContainerMD(fmd->getContainerId());
container_owner_uid = container->getCUid();
container_vtx = container->getMode() & S_ISVTX;
aclpath = gOFS->eosView->getUri(container.get());
} catch (eos::MDException& e) {
container.reset();
}
// ACL and permission check
Acl acl(aclpath.c_str(), error, vid, attrmap, false);
eos_info("acl=%s mutable=%d", attrmap["sys.acl"].c_str(), acl.IsMutable());
if (vid.uid && !acl.IsMutable()) {
errno = EPERM;
return Emsg(epname, error, errno, "remove file - immutable", path);
}
// check publicaccess level
if (!gOFS->allow_public_access(aclpath.c_str(), vid)) {
errno = EACCES;
return Emsg(epname, error, EACCES, "access - public access level restriction",
aclpath.c_str());
}
bool stdpermcheck = false;
if (acl.HasAcl() && (!container_vtx)) {
eos_info("acl=%d r=%d w=%d wo=%d egroup=%d delete=%d not-delete=%d mutable=%d",
acl.HasAcl(), acl.CanRead(), acl.CanWrite(), acl.CanWriteOnce(),
acl.HasEgroup(), acl.CanDelete(), acl.CanNotDelete(), acl.IsMutable());
if ((!acl.CanWrite()) && (!acl.CanWriteOnce())) {
// we have to check the standard permissions
stdpermcheck = true;
}
} else {
stdpermcheck = true;
}
if (container_vtx) {
if (
(container_owner_uid == vid.uid) ||
(owner_uid == vid.uid)) {
// great VTX allows the owner to delete, this is not overruled by a !delete acl
} else {
if (vid.uid) {
// forbidden because of VTX bit
errno = EPERM;
std::ostringstream oss;
oss << path << " by tident=" << vid.tident;
return Emsg(epname, error, errno, "remove file", oss.str().c_str());
}
}
} else {
// try other permissions
if (container) {
if (stdpermcheck && (!container->access(vid.uid, vid.gid, W_OK | X_OK))) {
errno = EPERM;
std::ostringstream oss;
oss << path << " by tident=" << vid.tident;
return Emsg(epname, error, errno, "remove file", oss.str().c_str());
}
// check if this directory is write-once for the mapped user
if (acl.CanWriteOnce() && (fmd->getSize())) {
errno = EPERM;
// this is a write once user
return Emsg(epname, error, EPERM,
"remove existing file - you are write-once user");
}
// if there is a !d policy we cannot delete files which we don't own
if (((vid.uid) && (vid.uid != 3) && (vid.gid != 4) && (acl.CanNotDelete())) &&
((fmd->getCUid() != vid.uid))) {
errno = EPERM;
// deletion is forbidden for not-owner
return Emsg(epname, error, EPERM,
"remove existing file - ACL forbids file deletion");
}
if ((!stdpermcheck) && (!acl.CanWrite())) {
errno = EPERM;
// this user is not allowed to write
return Emsg(epname, error, EPERM,
"remove existing file - you don't have write permissions");
}
}
// -----------------------------------------------------------------------
// check if there is a recycling bin specified and avoid recycling of the
// already recycled files/dirs
// -----------------------------------------------------------------------
XrdOucString sPath = path;
if (!(no_recycling) &&
(gOFS->enforceRecycleBin || attrmap.count(Recycle::gRecyclingAttribute)) &&
(!sPath.beginswith(Recycle::gRecyclingPrefix.c_str()))) {
// ---------------------------------------------------------------------
// this is two-step deletion via a recyle bin
// ---------------------------------------------------------------------
if (gOFS->enforceRecycleBin) {
// add the recycle attribute to enable recycling funcionality
attrmap[Recycle::gRecyclingAttribute] = Recycle::gRecyclingPrefix;
}
doRecycle = true;
} else {
// ---------------------------------------------------------------------
// this is one-step deletion just removing files 'forever' and now
// ---------------------------------------------------------------------
if (!simulate) {
try {
eos::IQuotaNode* ns_quota = gOFS->eosView->getQuotaNode(container.get());
eos_info("got quota node=%lld", (unsigned long long) ns_quota);
if (ns_quota) {
ns_quota->removeFile(fmd.get());
}
} catch (eos::MDException& e) { }
}
}
}
} else { /* file does not exist */
errno = ENOENT;
return Emsg(epname, error, errno, "remove", path);
}
if (!doRecycle) {
try {
if (!simulate) {
eos_info("unlinking from view %s", path);
if (!no_workflow) {
Workflow workflow;
// eventually trigger a workflow
workflow.Init(&attrmap, path, fid);
errno = 0;
lock.Release();
auto ret_wfe = workflow.Trigger("sync::delete", "default", vid, ininfo, errMsg);
if (ret_wfe < 0 && errno == ENOKEY) {
eos_info("msg=\"no workflow defined for delete\"");
} else {
eos_info("msg=\"workflow trigger returned\" retc=%d errno=%d", ret_wfe, errno);
}
if (ret_wfe && errno != ENOKEY) {
eos::MDException e(errno);
e.getMessage() << "Deletion workflow failed";
throw e;
}
lock.Grab(gOFS->eosViewRWMutex);
}
/* create a Copy-on-Write clone if needed */
XrdMgmOfsFile::create_cow(XrdMgmOfsFile::cowDelete, container, fmd, vid, error);
if (!XrdMgmOfsFile::handleHardlinkDelete(container, fmd, vid)) {
gOFS->eosView->unlinkFile(path);
// Reload file object that was modifed in the unlinkFile method
// TODO: this can be dropped if you use the unlinkFile which takes
// as argument the IFileMD object
fmd = gOFS->eosFileService->getFileMD(fmd->getId());
// Drop the TAPE_FS_ID which otherwise would prevent the
// file metadata cleanup
if (fmd->hasUnlinkedLocation(eos::common::TAPE_FS_ID)) {
fmd->removeLocation(eos::common::TAPE_FS_ID);
}
if ((!fmd->getNumUnlinkedLocation()) && (!fmd->getNumLocation())) {
gOFS->eosView->removeFile(fmd.get());
}
gOFS->WriteRmRecord(fmd);
if (container) {
container->setMTimeNow();
container->notifyMTimeChange(gOFS->eosDirectoryService);
eosView->updateContainerStore(container.get());
std::string deletion_name = fmd->getName();
eos::ContainerIdentifier c_ident = container->getIdentifier();
eos::ContainerIdentifier p_ident = container->getParentIdentifier();
lock.Release();
gOFS->FuseXCastDeletion(c_ident, deletion_name);
gOFS->FuseXCastRefresh(c_ident, p_ident);
}
}
}
errno = 0;
} catch (eos::MDException& e) {
errno = e.getErrno();
eos_debug("msg=\"exception\" ec=%d emsg=\"%s\"",
e.getErrno(), e.getMessage().str().c_str());
}
}
if (doRecycle && (!simulate)) {
// Two-step deletion recycle logic
XrdOucString recyclePath;
lock.Release();
// -------------------------------------------------------------------------
std::string recycle_space = attrmap[Recycle::gRecyclingAttribute].c_str();
if (Quota::ExistsResponsible(recycle_space)) {
if (!no_quota_enforcement &&
!Quota::Check(recycle_space, fmd->getCUid(), fmd->getCGid(),
fmd->getSize(), fmd->getNumLocation())) {
// This is the very critical case where we have to reject the delete
// since the recycle space is full
errno = ENOSPC;
return Emsg(epname, error, ENOSPC, "remove existing file - the recycle "
"space is full");
} else {
// Move the file to the recycle bin
eos::common::VirtualIdentity rootvid = eos::common::VirtualIdentity::Root();
int rc = 0;
Recycle lRecycle(path, attrmap[Recycle::gRecyclingAttribute].c_str(),
&vid, fmd->getCUid(), fmd->getCGid(),
fmd->getId());
if ((rc = lRecycle.ToGarbage(epname, error, fusexcast))) {
return rc;
} else {
if (container) {
if (XrdMgmOfsFile::create_cow(XrdMgmOfsFile::cowUnlink, container, fmd, vid,
error) > -1) {
eos_info("create_cow for recycled %s (fxid:%lx)", fmd->getName().c_str(),
fmd->getId());
}
}
recyclePath = error.getErrText();
gOFS->WriteRecycleRecord(fmd);
}
}
} else {
// There is no quota defined on that recycle path
errno = ENODEV;
return Emsg(epname, error, ENODEV, "remove existing file - the recycle "
"space has no quota configuration");
}
// track who is deleting
if (gOFS->_attr_set(recyclePath.c_str(), error, vid, "",
eos::common::EOS_DTRACE_ATTR, vid.getTrace(true).c_str())) {
eos_err("msg=\"failed to set attribute on recycle path\" path=%s",
recyclePath.c_str());
}
if (!keepversion) {
// call the version purge function in case there is a version (without gQuota locked)
eos::common::Path cPath(path);
XrdOucString vdir;
vdir += cPath.GetVersionDirectory();
// tag the version directory key on the garbage file
if (recyclePath.length()) {
eos::common::VirtualIdentity rootvid = eos::common::VirtualIdentity::Root();
struct stat buf;
if (!gOFS->_stat(vdir.c_str(), &buf, error, rootvid, 0, 0)) {
char sp[256];
snprintf(sp, sizeof(sp) - 1, "%016llx", (unsigned long long) buf.st_ino);
if (gOFS->_attr_set(recyclePath.c_str(), error, vid, "",
Recycle::gRecyclingVersionKey.c_str(), sp)) {
eos_err("msg=\"failed to set attribute on recycle path\" path=%s",
recyclePath.c_str());
}
}
}
gOFS->PurgeVersion(vdir.c_str(), error, 0);
error.clear();
errno = 0; // purge might return ENOENT if there was no version
}
} else {
lock.Release();
if ((!errno) && (!keepversion)) {
// call the version purge function in case there is a version (without gQuota locked)
eos::common::Path cPath(path);
XrdOucString vdir;
vdir += cPath.GetVersionDirectory();
gOFS->PurgeVersion(vdir.c_str(), error, 0);
error.clear();
errno = 0; // purge might return ENOENT if there was no version
}
}
EXEC_TIMING_END("Rm");
if (errno) {
return Emsg(epname, error, errno, errMsg.c_str(), path);
} else {
eos_info("msg=\"deleted\" can-recycle=%d path=%s owner.uid=%u owner.gid=%u vid.uid=%u vid.gid=%u",
doRecycle, path, owner_uid, owner_gid, vid.uid, vid.gid);
return SFS_OK;
}
}