//------------------------------------------------------------------------------ // File: RmCmd.cc // Author: Jozsef Makai - CERN //------------------------------------------------------------------------------ /************************************************************************ * EOS - the CERN Disk Storage System * * Copyright (C) 2018 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 .* ************************************************************************/ #include "RmCmd.hh" #include "mgm/XrdMgmOfs.hh" #include "mgm/XrdMgmOfsDirectory.hh" #include "namespace/interface/IContainerMDSvc.hh" #include "namespace/interface/IFileMDSvc.hh" #include "mgm/Quota.hh" #include "mgm/Recycle.hh" #include "mgm/Macros.hh" #include "mgm/Access.hh" #include "common/Path.hh" #include "common/Glob.hh" EOSMGMNAMESPACE_BEGIN eos::console::ReplyProto eos::mgm::RmCmd::ProcessRequest() noexcept { ACCESSMODE_W; eos::console::ReplyProto reply; std::ostringstream outStream; std::ostringstream errStream; XrdOucString m_err {""}; int ret_c = 0; eos::console::RmProto rm = mReqProto.rm(); auto recursive = rm.recursive(); auto noworkflow = rm.noworkflow(); auto force = rm.bypassrecycle(); std::string spath; if (rm.path().empty()) { std::string full_path; std::string msg; if (rm.fileid()) { GetPathFromFid(full_path, rm.fileid(), msg); } else if (rm.containerid()) { GetPathFromCid(full_path, rm.containerid(), msg); } spath = full_path; if (spath.empty() && (rm.fileid() || rm.containerid())) { if (vid.uid) { reply.set_std_err("warning: removing a pending deletion is allowed " "only for the 'root' role"); reply.set_retc(EPERM); } else { // Try to remove a file/container metadata object pending deletion // which is blocked in the namespace in a detached state bool is_dir = (rm.containerid() != 0ull); // If -F is given then we try to force remove the file without waiting // for the confirmation from the diskservers if (gOFS->RemoveDetached((is_dir ? rm.containerid() : rm.fileid()), is_dir, force, msg)) { reply.set_std_out(msg); reply.set_retc(0); } else { reply.set_std_err(msg); reply.set_retc(errno); } } return reply; } } else { spath = rm.path(); } eos::mgm::NamespaceMap(spath, nullptr, mVid); std::string err_check; int errno_check = 0; const char* path = spath.c_str(); PROC_MVID_TOKEN_SCOPE; // Enforce path checks and identity access rights if (IsOperationForbidden(spath, mVid, err_check, errno_check)) { eos_err("msg=\"operation forbidden\" path=\"%s\" serr_msg=\"%s\" errno=%i", spath.c_str(), err_check.c_str(), errno_check); reply.set_std_err(err_check); reply.set_retc(errno_check); return reply; } eos::common::Path cPath(spath.c_str()); XrdOucString filter = ""; std::set rmList; if (force && (vid.uid)) { errStream << "warning: removing the force flag - this is only allowed " << "for the 'root' role!" << std::endl; force = false; } if (spath.empty()) { errStream << "error: you have to give a path name to call 'rm'"; ret_c = EINVAL; } else { eos::common::Path objPath(spath.c_str()); if (objPath.Globbing()) { spath = objPath.GetParentPath(); filter = objPath.GetName(); } // Check file existence XrdSfsFileExistence file_exists; XrdOucErrInfo errInfo; if (gOFS->_exists(spath.c_str(), file_exists, errInfo, mVid, nullptr)) { errStream << "error: unable to run exists on path '" << spath << "'"; reply.set_std_err(errStream.str()); reply.set_retc(errno); return reply; } if (file_exists == XrdSfsFileExistNo) { errStream << "error: no such file or directory with path '" << spath << "'"; reply.set_std_err(errStream.str()); reply.set_retc(ENOENT); return reply; } if (file_exists == XrdSfsFileExistIsFile) { // if we have rm -r we remove the -r flag recursive = false; } if ((file_exists == XrdSfsFileExistIsDirectory) && filter.length()) { // List the path and match against filter XrdMgmOfsDirectory dir; eos::common::Glob glob; int listrc = dir.open(spath.c_str(), mVid, nullptr); if (!listrc) { const char* val; while ((val = dir.nextEntry())) { XrdOucString mpath = spath.c_str(); XrdOucString entry = val; mpath += val; if ((entry == ".") || (entry == "..")) { continue; } if (glob.Match(filter.c_str(), entry.c_str())) { rmList.insert(mpath.c_str()); } } } // if we have rm * (whatever wildcard) we remove the -r flag recursive = false; } else { rmList.insert(spath); } // Find everything to be deleted if (recursive) { std::map> found; std::map>::const_reverse_iterator rfoundit; std::set::const_iterator fileit; errInfo.clear(); errInfo.setErrCode(E2BIG);// ask to fail E2BIG if (gOFS->_find(spath.c_str(), errInfo, m_err, mVid, found)) { if (errno == E2BIG) { errStream << "error: the directory tree exceeds the configured query limit for you - ask an administrator to increase your query limit or split the operation into several deletions"; } else { errStream << "error: unable to list directory '" << spath << "'"; } ret_c = errno; } else { XrdOucString recyclingAttribute = ""; if (!force) { // only recycle if there is no '-f' flag unsigned long rpos; if ((rpos = spath.find("/.sys.v#.")) == std::string::npos) { // check if this path has a recycle attribute errInfo.clear(); gOFS->_attr_get(spath.c_str(), errInfo, mVid, "", Recycle::gRecyclingAttribute.c_str(), recyclingAttribute); } else { auto ppath = spath; ppath.erase(rpos); // get it from the parent directory for version directories errInfo.clear(); gOFS->_attr_get(ppath.c_str(), errInfo, mVid, "", Recycle::gRecyclingAttribute.c_str(), recyclingAttribute); } } // See if we have a recycle policy set if (recyclingAttribute.length() && (spath.find(Recycle::gRecyclingPrefix) != 0)) { // Two step deletion via recycle bin // delete files in simulation mode std::map user_deletion_size; std::map group_deletion_size; for (rfoundit = found.rbegin(); rfoundit != found.rend(); rfoundit++) { int rpos = 0; RECURSIVE_STALL("Rm", mVid); if ((rpos = rfoundit->first.find("/.sys.v#.")) == STR_NPOS) { for (fileit = rfoundit->second.begin(); fileit != rfoundit->second.end(); fileit++) { std::string fspath = rfoundit->first; std::string entry = *fileit; size_t l_pos; if ((l_pos = entry.find(" ->")) != std::string::npos) { entry.erase(l_pos); } fspath += entry; errInfo.clear(); if (gOFS->_rem(fspath.c_str(), errInfo, mVid, nullptr, true)) { errStream << "error: unable to remove file '" << fspath << "'" << " - bulk deletion aborted" << std::endl; reply.set_std_err(errStream.str()); reply.set_retc(errno); return reply; } } } } // Delete directories in simulation mode for (rfoundit = found.rbegin(); rfoundit != found.rend(); rfoundit++) { RECURSIVE_STALL("RmDir", mVid); // don't even try to delete the root directory std::string fspath = rfoundit->first; if (fspath == "/") { continue; } errInfo.clear(); if (gOFS->_remdir(rfoundit->first.c_str(), errInfo, mVid, nullptr, true) && (errno != ENOENT)) { errStream << "error: unable to remove directory '" << rfoundit->first << "'" << " - bulk deletion aborted" << std::endl; reply.set_std_err(errStream.str()); reply.set_retc(errno); return reply; } } struct stat buf; errInfo.clear(); if (gOFS->_stat(spath.c_str(), &buf, errInfo, mVid, "")) { errStream << "error: failed to stat bulk deletion directory '" << spath << "'"; reply.set_std_err(errStream.str()); reply.set_retc(errno); return reply; } spath += "/"; eos::mgm::Recycle lRecycle(spath.c_str(), recyclingAttribute.c_str(), &mVid, buf.st_uid, buf.st_gid, (unsigned long long) buf.st_ino); errInfo.clear(); if (lRecycle.ToGarbage("rm-r", errInfo)) { errStream << "error: failed to recycle path '" << spath << "'" << std::endl << "reason: " << errInfo.getErrText() << std::endl; reply.set_std_err(errStream.str()); reply.set_retc(errInfo.getErrInfo()); return reply; } else { outStream << "success: you can recycle this deletion using 'recycle restore " << "pxid:" << std::setw(16) << std::setfill('0') << std::hex << buf.st_ino << "'" << std::endl; reply.set_std_out(outStream.str()); reply.set_retc(SFS_OK); return reply; } } else { // Standard way to delete files recursively // delete files starting at the deepest level for (rfoundit = found.rbegin(); rfoundit != found.rend(); rfoundit++) { for (fileit = rfoundit->second.begin(); fileit != rfoundit->second.end(); fileit++) { std::string fspath = rfoundit->first; size_t l_pos; std::string entry = *fileit; if ((l_pos = entry.find(" ->")) != std::string::npos) { entry.erase(l_pos); } fspath += entry; errInfo.clear(); if (gOFS->_rem(fspath.c_str(), errInfo, mVid, nullptr, false, false, force, false, true, noworkflow)) { errStream << "error: unable to remove file '" << fspath.c_str() << "'" << std::endl; ret_c = errno; } } } // Delete directories starting at the deepest level for (rfoundit = found.rbegin(); rfoundit != found.rend(); rfoundit++) { // don't even try to delete the root directory std::string fspath = rfoundit->first; if (fspath == "/") { continue; } errInfo.clear(); if (gOFS->_remdir(rfoundit->first.c_str(), errInfo, mVid, nullptr)) { if (errno != ENOENT) { errStream << "error: unable to remove directory " << "'" << rfoundit->first.c_str() << "'" << std::endl << "reason: " << errInfo.getErrText() << std::endl; ret_c = errno; } } } } } } else { for (const auto& it : rmList) { errInfo.clear(); if (gOFS->_rem(it.c_str(), errInfo, mVid, nullptr, false, false, force, false, true, noworkflow) && (errno != ENOENT)) { errStream << "error: unable to remove file/directory '" << it << "'" << std::endl; ret_c |= errno; } } } } reply.set_retc(ret_c); reply.set_std_out(outStream.str()); reply.set_std_err(errStream.str()); return reply; } EOSMGMNAMESPACE_END