// ----------------------------------------------------------------------
// File: Recycle.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 .*
************************************************************************/
#include "common/Constants.hh"
#include "common/Logging.hh"
#include "common/LayoutId.hh"
#include "common/Mapping.hh"
#include "common/utils/BackOffInvoker.hh"
#include "common/RWMutex.hh"
#include "common/Path.hh"
#include "mgm/Recycle.hh"
#include "mgm/XrdMgmOfs.hh"
#include "mgm/Quota.hh"
#include "mgm/QdbMaster.hh"
#include "mgm/XrdMgmOfsDirectory.hh"
#include "namespace/interface/IView.hh"
#include "namespace/Prefetcher.hh"
#include "XrdOuc/XrdOucErrInfo.hh"
// MgmOfsConfigure prepends the proc directory path e.g. the bin is
// /eos//recycle/'
// - one should define an attribute like 'sys.recycle.keeptime' on this dir
// to define the time in seconds how long files stay in the recycle bin
//------------------------------------------------------------------------------
void
Recycle::Recycler(ThreadAssistant& assistant) noexcept
{
eos::common::VirtualIdentity rootvid = eos::common::VirtualIdentity::Root();
XrdOucErrInfo lError;
time_t lKeepTime = 0;
double lSpaceKeepRatio = 0;
std::multimap lDeletionMap;
time_t snoozetime = 10;
unsigned long long lLowInodesWatermark = 0;
unsigned long long lLowSpaceWatermark = 0;
bool show_attribute_missing = true;
eos_static_info("%s", "\"msg = \"recycling thread started\"");
gOFS->WaitUntilNamespaceIsBooted(assistant);
if (assistant.terminationRequested()) {
return;
}
assistant.wait_for(std::chrono::seconds(10));
eos::common::BackOffInvoker backoff_logger;
while (!assistant.terminationRequested()) {
// Every now and then we wake up
backoff_logger.invoke([&snoozetime]() {
eos_static_info("msg=\"recycler thread\" snooze-time=%llu",
snoozetime);
});
for (int i = 0; i < snoozetime / 10; i++) {
if (assistant.terminationRequested()) {
eos_static_info("%s", "msg=\"recycler thread exiting\"");
return;
}
assistant.wait_for(std::chrono::seconds(10));
if (mWakeUp) {
mWakeUp = false;
break;
}
}
if (!gOFS->mMaster->IsMaster()) {
continue;
}
// This will be reconfigured to an appropriate value later
snoozetime = gRecyclingPollTime;
// Read our current policy setting
eos::IContainerMD::XAttrMap attrmap;
// Check if this path has a recycle attribute
if (gOFS->_attr_ls(Recycle::gRecyclingPrefix.c_str(), lError, rootvid, "",
attrmap)) {
eos_static_err("msg=\"unable to get attribute on recycle path\" recycle-path=%s",
Recycle::gRecyclingPrefix.c_str());
} else {
if (attrmap.count(Recycle::gRecyclingKeepRatio)) {
// One can define a space threshold which actually leaves even older
// files in the garbage bin until the threshold is reached for
// simplicity we apply this threshold to volume & inodes
lSpaceKeepRatio = strtod(attrmap[Recycle::gRecyclingKeepRatio].c_str(), 0);
// Get group statistics for space and project id
auto map_quotas = Quota::GetGroupStatistics(Recycle::gRecyclingPrefix,
Quota::gProjectId);
if (!map_quotas.empty()) {
unsigned long long usedbytes = map_quotas[SpaceQuota::kGroupBytesIs];
unsigned long long maxbytes = map_quotas[SpaceQuota::kGroupBytesTarget];
unsigned long long usedfiles = map_quotas[SpaceQuota::kGroupFilesIs];
unsigned long long maxfiles = map_quotas[SpaceQuota::kGroupFilesTarget];
if ((lSpaceKeepRatio > (1.0 * usedbytes / (maxbytes ? maxbytes : 999999999))) &&
(lSpaceKeepRatio > (1.0 * usedfiles / (maxfiles ? maxfiles : 999999999)))) {
eos_static_debug("msg=\"skipping recycle clean-up - ratio still low\" "
"ratio=%.02f space-ratio=%.02f inode-ratio=%.02f",
lSpaceKeepRatio,
1.0 * usedbytes / (maxbytes ? maxbytes : 999999999),
1.0 * usedfiles / (maxfiles ? maxfiles : 999999999));
continue;
}
if ((lSpaceKeepRatio - 0.1) > 0) {
lSpaceKeepRatio -= 0.1;
}
lLowInodesWatermark = (maxfiles * lSpaceKeepRatio);
lLowSpaceWatermark = (maxbytes * lSpaceKeepRatio);
eos_static_info("msg=\"cleaning by ratio policy\" low-inodes-mark=%lld "
"low-space-mark=%lld mark=%.02f", lLowInodesWatermark,
lLowSpaceWatermark, lSpaceKeepRatio);
}
}
if (attrmap.count(Recycle::gRecyclingTimeAttribute)) {
lKeepTime = strtoull(attrmap[Recycle::gRecyclingTimeAttribute].c_str(), 0, 10);
eos_static_info("keep-time=%llu deletion-map=%llu", lKeepTime,
lDeletionMap.size());
if (lKeepTime > 0) {
if (!lDeletionMap.size()) {
//...................................................................
// the deletion map is filled if there is nothing inside with files/
// directories found previously in the garbage bin
//...................................................................
// the old reyccle bin gid/uid/
{
std::string subdirs;
XrdMgmOfsDirectory dirl1;
XrdMgmOfsDirectory dirl2;
XrdMgmOfsDirectory dirl3;
int listrc = dirl1.open(Recycle::gRecyclingPrefix.c_str(), rootvid,
(const char*) 0);
if (listrc) {
eos_static_err("msg=\"unable to list the garbage directory level-1\" recycle-path=%s",
Recycle::gRecyclingPrefix.c_str());
} else {
// loop over all directories = group directories
const char* dname1;
while ((dname1 = dirl1.nextEntry())) {
{
std::string sdname = dname1;
if ((sdname == ".") || (sdname == "..")) {
continue;
}
}
std::string l2 = Recycle::gRecyclingPrefix;
l2 += dname1;
// list level-2 user directories
listrc = dirl2.open(l2.c_str(), rootvid, (const char*) 0);
if (listrc) {
eos_static_err("msg=\"unable to list the garbage directory level-2\" recycle-path=%s l2-path=%s",
Recycle::gRecyclingPrefix.c_str(), l2.c_str());
} else {
const char* dname2;
while ((dname2 = dirl2.nextEntry())) {
{
std::string sdname = dname2;
if ((sdname == ".") || (sdname == "..")) {
continue;
}
}
std::string l3 = l2;
l3 += "/";
l3 += dname2;
// list the level-3 entries
listrc = dirl3.open(l3.c_str(), rootvid, (const char*) 0);
if (listrc) {
eos_static_err("msg=\"unable to list the garbage directory level-2\" recycle-path=%s l2-path=%s l3-path=%s",
Recycle::gRecyclingPrefix.c_str(), l2.c_str(), l3.c_str());
} else {
const char* dname3;
while ((dname3 = dirl3.nextEntry())) {
{
std::string sdname = dname3;
if ((sdname == ".") || (sdname == "..")) {
continue;
}
}
std::string l4 = l3;
l4 += "/";
l4 += dname3;
eos_static_debug("path=%s", l4.c_str());
// Stat the directory to get the mtime
struct stat buf;
if (gOFS->_stat(l4.c_str(), &buf, lError, rootvid, "", nullptr, false)) {
eos_static_err("msg=\"unable to stat a garbage directory entry\" "
"recycle-path=%s l2-path=%s l3-path=%s",
Recycle::gRecyclingPrefix.c_str(), l2.c_str(), l3.c_str());
} else {
// Add to the garbage fifo deletion multimap
if (!S_ISDIR(buf.st_mode)) {
eos_static_debug("adding %s to deletion map", l4.c_str());
lDeletionMap.insert(std::pair (buf.st_ctime, l4));
} else {
eos_static_debug("not adding %s to deletion map", l4.c_str());
}
}
}
dirl3.close();
}
}
dirl2.close();
}
}
dirl1.close();
}
// the new recycle bin
{
std::map> findmap;
char sdir[4096];
snprintf(sdir, sizeof(sdir) - 1, "%s/", Recycle::gRecyclingPrefix.c_str());
XrdOucErrInfo lError;
int depth = 6;
XrdOucString err_msg;
time_t now = time(NULL);
// a recycle bin directory has the ctime with the last entry added, to get
time_t max_ctime_dir = now - lKeepTime + (31 * 86400);
time_t max_ctime_file = now - lKeepTime;
std::map ctime_map;
// send a restricted query, which applies ctime constraints from deepness 1
(void) gOFS->_find(sdir, lError, err_msg, rootvid, findmap,
0, 0, false, 0, true, depth, 0, false, NULL,
max_ctime_dir, max_ctime_file,
&ctime_map,
1, 1);
eos_static_notice("time-limited query for ctime=%u:%u nfiles=%lu",
max_ctime_dir, max_ctime_file, ctime_map.size());
for (auto dirit = findmap.begin(); dirit != findmap.end(); ++dirit) {
XrdOucString dirname = dirit->first.c_str();
if (dirname.endswith(".d/")) {
// check again the ctime here, because we had to enalrge the query window by 31 days for the organization of the recycle bin
struct stat buf;
if (!gOFS->_stat(dirname.c_str(),
&buf, lError, rootvid, "", nullptr , false, 0)) {
if (buf.st_ctime > max_ctime_file) {
// skip this recusrive deletion, it is still inside the keep window
continue;
}
}
dirname.erase(dirname.length() - 1);
eos::common::Path cpath(dirname.c_str());
dirname = cpath.GetParentPath();
dirit->second.insert(cpath.GetName());
}
eos_static_debug("dir=%s", dirit->first.c_str());
for (auto fileit = dirit->second.begin(); fileit != dirit->second.end();
++fileit) {
// Symlink files returned by the find command above contain
// a pointer to the original name which needs to be removed
// so that we can properly stat the file.
std::string fname = *fileit;
size_t pos = fname.find(" -> ");
if (pos != std::string::npos) {
fname.erase(pos);
eos_static_debug("orig_path=\"%s\" symlink_path=\"%s\"",
fileit->c_str(), fname.c_str());
}
XrdOucString originode;
XrdOucString origpath = fname.c_str();
eos_static_debug("path=%s", origpath.c_str());
if ((origpath != "/") && !origpath.beginswith("#")) {
continue;
}
std::string fullpath = dirname.c_str();
fullpath += fname;
// Add to the garbage fifo deletion multimap
lDeletionMap.insert(std::pair (ctime_map[*fileit],
fullpath.c_str()));
eos_static_debug("new-bin: adding to deletionmap : %s ctime: %u",
fullpath.c_str(), ctime_map[*fileit]);
}
}
}
}
} else {
snoozetime = 0; // this will be redefined by the oldest entry time
auto it = lDeletionMap.begin();
time_t now = time(NULL);
while (it != lDeletionMap.end()) {
// take the first element and see if it is exceeding the keep time
if ((it->first + lKeepTime) < now) {
// This entry can be removed
// If there is a keep-ratio policy defined we abort deletion once
// we are enough under the thresholds
if (attrmap.count(Recycle::gRecyclingKeepRatio)) {
auto map_quotas = Quota::GetGroupStatistics(Recycle::gRecyclingPrefix,
Quota::gProjectId);
if (!map_quotas.empty()) {
unsigned long long usedbytes = map_quotas[SpaceQuota::kGroupBytesIs];
unsigned long long usedfiles = map_quotas[SpaceQuota::kGroupFilesIs];
eos_static_debug("low-volume=%lld is-volume=%lld low-inodes=%lld is-inodes=%lld",
usedfiles,
lLowInodesWatermark,
usedbytes,
lLowSpaceWatermark);
if ((lLowInodesWatermark >= usedfiles) &&
(lLowSpaceWatermark >= usedbytes)) {
eos_static_debug("msg=\"skipping recycle clean-up - ratio went under low watermarks\"");
break; // leave the deletion loop
}
}
}
XrdOucString delpath = it->second.c_str();
if ((it->second.length()) &&
(delpath.endswith(Recycle::gRecyclingPostFix.c_str()))) {
// Do a directory deletion - first find all subtree children
std::map > found;
std::map >::const_reverse_iterator rfoundit;
XrdOucString err_msg;
if (gOFS->_find(it->second.c_str(), lError, err_msg, rootvid, found)) {
eos_static_err("msg=\"unable to do a find in subtree\" path=%s stderr=\"%s\"",
it->second.c_str(), err_msg.c_str());
} else {
// Delete files starting at the deepest level
for (rfoundit = found.rbegin(); rfoundit != found.rend(); rfoundit++) {
for (auto fileit = rfoundit->second.begin();
fileit != rfoundit->second.end();
fileit++) {
// Symlink files returned by the find command above contain
// a pointer to the original name which needs to be removed
// so that we can properly stat the file.
std::string fname = *fileit;
size_t pos = fname.find(" -> ");
if (pos != std::string::npos) {
fname.erase(pos);
eos_static_debug("orig_path=\"%s\" symlink_path=\"%s\"",
fileit->c_str(), fname.c_str());
}
std::string fullpath = rfoundit->first;
fullpath += fname;
if (gOFS->_rem(fullpath.c_str(), lError, rootvid, (const char*) 0)) {
eos_static_err("msg=\"unable to remove file\" path=%s",
fullpath.c_str());
} else {
eos_static_info("msg=\"permanently deleted file from recycle bin\" "
"path=%s keep-time=%llu", fullpath.c_str(), lKeepTime);
}
}
}
// 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.c_str();
if (fspath == "/") {
continue;
}
if (gOFS->_remdir(rfoundit->first.c_str(), lError, rootvid, (const char*) 0)) {
eos_static_err("msg=\"unable to remove directory\" path=%s",
fspath.c_str());
} else {
eos_static_info("msg=\"permanently deleted directory from "
"recycle bin\" path=%s keep-time=%llu",
fspath.c_str(), lKeepTime);
}
}
}
lDeletionMap.erase(it);
it = lDeletionMap.begin();
} else {
// Do a single file deletion
if (gOFS->_rem(it->second.c_str(), lError, rootvid, (const char*) 0)) {
eos_static_err("msg=\"unable to remove file\" path=\"%s\" "
"err_msg=\"%s\" errc=%i", it->second.c_str(),
lError.getErrText(), lError.getErrInfo());
}
lDeletionMap.erase(it);
it = lDeletionMap.begin();
}
} else {
// This entry has still to be kept
eos_static_info("oldest entry: %lld sec to deletion",
it->first + lKeepTime - now);
if (!snoozetime) {
// define the sleep period from the oldest entry
snoozetime = it->first + lKeepTime - now;
if (snoozetime < gRecyclingPollTime) {
// avoid to activate this thread too many times, 5 minutes
// resolution is perfectly fine
snoozetime = gRecyclingPollTime;
}
if (snoozetime > lKeepTime) {
eos_static_warning("msg=\"snooze time exceeds keeptime\" snooze-time=%llu keep-time=%llu",
snoozetime, lKeepTime);
// That is sort of strange but let's have a fix for that
snoozetime = lKeepTime;
}
}
// we can leave the loop because all other entries don't match anymore the time constraint
break;
}
}
if (!snoozetime) {
snoozetime = gRecyclingPollTime;
}
}
} else {
eos_static_warning("msg=\"parsed '%s' attribute as keep-time of %llu seconds - ignoring!\" recycle-path=%s",
Recycle::gRecyclingTimeAttribute.c_str(), Recycle::gRecyclingPrefix.c_str());
}
} else {
if (show_attribute_missing) {
eos_static_warning("msg=\"unable to read '%s' attribute on recycle path - undefined!\" recycle-path=%s",
Recycle::gRecyclingTimeAttribute.c_str(), Recycle::gRecyclingPrefix.c_str());
show_attribute_missing = false;
}
}
}
}
eos_static_info("%s", "msg=\"recycler thread exiting\"");
}
/*----------------------------------------------------------------------------*/
int
Recycle::ToGarbage(const char* epname, XrdOucErrInfo& error, bool fusexcast)
{
eos::common::VirtualIdentity rootvid = eos::common::VirtualIdentity::Root();
char srecyclepath[4096];
// If path ends with '/' we recycle a full directory tree aka directory
bool isdir = false;
// rewrite the file name /a/b/c as #:#a#:#b#:#c
XrdOucString contractedpath = mPath.c_str();
if (contractedpath.endswith("/")) {
isdir = true;
mPath.erase(mPath.length() - 1);
// remove the '/' indicating a recursive directory recycling
contractedpath.erase(contractedpath.length() - 1);
}
if (mRecycleDir.length() > 1) {
if (mRecycleDir[mRecycleDir.length() - 1] == '/') {
mRecycleDir.erase(mRecycleDir.length() - 1);
}
}
while (contractedpath.replace("/", "#:#")) {
}
// For dir's we add a '.d' in the end of the recycle path
std::string lPostFix = "";
if (isdir) {
lPostFix = Recycle::gRecyclingPostFix;
}
std::string rpath;
int rc = 0;
// retrieve the current valid index directory
if ((rc = GetRecyclePrefix(epname, error, rpath))) {
return rc;
}
snprintf(srecyclepath, sizeof(srecyclepath) - 1, "%s/%s.%016llx%s",
rpath.c_str(),
contractedpath.c_str(),
mId, lPostFix.c_str());
mRecyclePath = srecyclepath;
// Finally do the rename
if (gOFS->_rename(mPath.c_str(), srecyclepath, error, rootvid, "", "", true,
true, false, fusexcast)) {
return gOFS->Emsg(epname, error, EIO, "rename file/directory", srecyclepath);
}
// store the recycle path in the error object
error.setErrInfo(0, srecyclepath);
return SFS_OK;
}
/*----------------------------------------------------------------------------*/
void
Recycle::Print(std::string& std_out, std::string& std_err,
eos::common::VirtualIdentity& vid, bool monitoring,
bool translateids, bool details, std::string date, bool global,
Recycle::RecycleListing* rvec, bool whodeleted)
{
using namespace eos::common;
XrdOucString uids;
XrdOucString gids;
std::map printmap;
eos::common::VirtualIdentity rootvid = eos::common::VirtualIdentity::Root();
std::ostringstream oss_out;
// fix security hole
if (date.find("..")!=std::string::npos) {return;}
if (global && ((!vid.uid) ||
(vid.hasUid(3)) ||
(vid.hasGid(4)))) {
// add everything found in the recycle directory structure to the printmap
std::string subdirs;
XrdMgmOfsDirectory dirl;
int listrc = dirl.open(Recycle::gRecyclingPrefix.c_str(), rootvid,
(const char*) 0);
if (listrc) {
eos_static_err("msg=\"unable to list the garbage directory level-1\" recycle-path=%s",
Recycle::gRecyclingPrefix.c_str());
} else {
// loop over all directories = group directories
const char* dname1;
while ((dname1 = dirl.nextEntry())) {
std::string sdname = dname1;
if ((sdname == ".") || (sdname == "..")) {
continue;
}
if (sdname.substr(0, 4) == "uid:") {
uid_t uid = std::stoull(sdname.substr(4));
printmap[uid] = true;
}
}
dirl.close();
}
} else {
// add only the virtual user to the printmap
printmap[vid.uid] = true;
}
eos::common::Path dPath(std::string("/") + date);
if (details) {
size_t count = 0;
for (auto ituid = printmap.begin(); ituid != printmap.end(); ituid++) {
std::map> findmap;
char sdir[4096];
snprintf(sdir, sizeof(sdir) - 1, "%s/uid:%u/%s",
Recycle::gRecyclingPrefix.c_str(),
(unsigned int) ituid->first, date.c_str());
XrdOucErrInfo lError;
int depth = 5 ;
if (dPath.GetSubPathSize()) {
if (depth > (int) dPath.GetSubPathSize()) {
depth -= dPath.GetSubPathSize();
}
}
XrdOucString err_msg;
int retc = gOFS->_find(sdir, lError, err_msg, rootvid, findmap,
0, 0, false, 0, true, depth);
if (retc && errno != ENOENT) {
std_err = err_msg.c_str();
eos_static_err("find command failed in dir='%s'", sdir);
}
for (auto dirit = findmap.begin(); dirit != findmap.end(); ++dirit) {
XrdOucString dirname = dirit->first.c_str();
if (dirname.endswith(".d/")) {
dirname.erase(dirname.length() - 1);
eos::common::Path cpath(dirname.c_str());
dirname = cpath.GetParentPath();
dirit->second.insert(cpath.GetName());
}
eos_static_debug("dir=%s", dirit->first.c_str());
for (auto fileit = dirit->second.begin(); fileit != dirit->second.end();
++fileit) {
// Symlink files returned by the find command above contain
// a pointer to the original name which needs to be removed
// so that we can properly stat the file.
std::string fname = *fileit;
size_t pos = fname.find(" -> ");
if (pos != std::string::npos) {
fname.erase(pos);
eos_static_debug("orig_path=\"%s\" symlink_path=\"%s\"",
fileit->c_str(), fname.c_str());
}
std::string fullpath = dirname.c_str();
fullpath += fname;
XrdOucString originode;
XrdOucString origpath = fname.c_str();
eos_static_debug("file=%s", origpath.c_str());
if ((origpath != "/") && !origpath.beginswith("#")) {
continue;
}
// demangle the original pathname
while (origpath.replace("#:#", "/")) {
}
XrdOucErrInfo error;
XrdOucString type = "file";
XrdOucString deleter;
struct stat buf;
if (!gOFS->_stat(fullpath.c_str(), &buf, error, vid, "", nullptr, false)) {
if (translateids) {
int errc = 0;
uids = eos::common::Mapping::UidToUserName(buf.st_uid, errc).c_str();
if (errc) {
uids = eos::common::Mapping::UidAsString(buf.st_uid).c_str();
}
gids = eos::common::Mapping::GidToGroupName(buf.st_gid, errc).c_str();
if (errc) {
gids = eos::common::Mapping::GidAsString(buf.st_gid).c_str();
}
} else {
uids = eos::common::Mapping::UidAsString(buf.st_uid).c_str();
gids = eos::common::Mapping::GidAsString(buf.st_gid).c_str();
}
if (origpath.endswith(Recycle::gRecyclingPostFix.c_str())) {
type = "recursive-dir";
origpath.erase(origpath.length() - Recycle::gRecyclingPostFix.length());
}
originode = origpath;
originode.erase(0, origpath.length() - 16);
origpath.erase(origpath.length() - 17);
// put the key prefixes
if (type == "file") {
originode.insert("fxid:", 0);
} else {
originode.insert("pxid:", 0);
}
if (whodeleted) {
if (!gOFS->_attr_get(fullpath.c_str(), error, vid, "", eos::common::EOS_DTRACE_ATTR,
deleter)) {
} else {
deleter = "{}";
}
}
if (monitoring) {
oss_out << "recycle=ls recycle-bin=" << Recycle::gRecyclingPrefix
<< " uid=" << uids.c_str() << " gid=" << gids.c_str()
<< " size=" << std::to_string(buf.st_size)
<< " deletion-time=" << std::to_string(buf.st_ctime)
<< " type=" << type.c_str()
<< " keylength.restore-path=" << origpath.length()
<< " restore-path=" << origpath.c_str()
<< " restore-key=" << originode.c_str()
<< " dtrace=\"" << deleter.c_str() << "\""
<< std::endl;
if (rvec) {
std::map rmap;
rmap["uid"] = std::to_string(buf.st_uid);
rmap["gid"] = std::to_string(buf.st_gid);
rmap["username"] = uids.c_str();
rmap["groupname"] = gids.c_str();
rmap["size"] = std::to_string(buf.st_size);
rmap["dtime"] = std::to_string(buf.st_ctime);
rmap["type"] = type.c_str();
rmap["path"] = origpath.c_str();
rmap["key"] = originode.c_str();
rmap["dtrace"] = deleter.c_str();
rvec->push_back(rmap);
}
} else {
char sline[4096];
if (count == 0) {
// print a header
snprintf(sline, sizeof(sline) - 1,
"# %-24s %-8s %-8s %-12s %-13s %-21s %-64s %-32s\n", "Deletion Time", "UID", "GID",
"SIZE", "TYPE", "RESTORE-KEY", "RESTORE-PATH", "DTRACE");
oss_out << sline
<< "# ================================================"
<< "=================================================="
<< "========================================================="
<< "============================="
<< std::endl;
}
char tdeltime[4096];
std::string deltime = ctime_r(&buf.st_ctime, tdeltime);
deltime.erase(deltime.length() - 1);
snprintf(sline, sizeof(sline) - 1, "%-26s %-8s %-8s %-12s %-13s %-16s %-64s %-32s",
deltime.c_str(), uids.c_str(), gids.c_str(),
StringConversion::GetSizeString((unsigned long long) buf.st_size).c_str(),
type.c_str(), originode.c_str(), origpath.c_str(), deleter.c_str());
if (oss_out.tellp() > 1 * 1024 * 1024 * 1024) {
retc = E2BIG;
oss_out << "... (truncated after 1G of output)" << std::endl;
std_out += oss_out.str();
std_err += "warning: list too long - truncated after 1GB of output!\n";
return;
}
oss_out << sline << std::endl;
}
count++;
if ((vid.uid) && (!vid.sudoer) && (count > 100000)) {
retc = E2BIG;
oss_out << "... (truncated)" << std::endl;
std_out += oss_out.str();
std_err += "warning: list too long - truncated after 100000 entries!\n";
return;
}
}
}
}
}
} else {
auto map_quotas = Quota::GetGroupStatistics(Recycle::gRecyclingPrefix,
Quota::gProjectId);
if (!map_quotas.empty()) {
unsigned long long used_bytes = map_quotas[SpaceQuota::kGroupBytesIs];
unsigned long long max_bytes = map_quotas[SpaceQuota::kGroupBytesTarget];
unsigned long long used_inodes = map_quotas[SpaceQuota::kGroupFilesIs];
unsigned long long max_inodes = map_quotas[SpaceQuota::kGroupFilesTarget];
char sline[4096];
eos::IContainerMD::XAttrMap attrmap;
XrdOucErrInfo error;
// Check if this path has a recycle attribute
if (gOFS->_attr_ls(Recycle::gRecyclingPrefix.c_str(), error, rootvid, "",
attrmap)) {
eos_static_err("msg=\"unable to get attribute on recycle path\" "
"recycle-path=%s", Recycle::gRecyclingPrefix.c_str());
}
if (!monitoring) {
oss_out << "# _________________________________________________________"
<< "___________________________________________________________"
<< "___________________________" << std::endl;
snprintf(sline, sizeof(sline) - 1, "# used %s out of %s (%.02f%% volume) "
"used %llu out of %llu (%.02f%% inodes used) Object-Lifetime %s [s] Keep-Ratio %s",
StringConversion::GetReadableSizeString(used_bytes, "B").c_str(),
StringConversion::GetReadableSizeString(max_bytes, "B").c_str(),
used_bytes * 100.0 / max_bytes,
used_inodes, max_inodes, used_inodes * 100.0 / max_inodes,
attrmap.count(Recycle::gRecyclingTimeAttribute) ?
attrmap[Recycle::gRecyclingTimeAttribute].c_str() : "not configured",
attrmap.count(Recycle::gRecyclingKeepRatio) ?
attrmap[Recycle::gRecyclingKeepRatio].c_str() : "not configured");
oss_out << sline << std::endl
<< "# _________________________________________________________"
<< "___________________________________________________________"
<< "___________________________" << std::endl;
} else {
snprintf(sline, sizeof(sline) - 1, "recycle-bin=%s usedbytes=%llu "
"maxbytes=%llu volumeusage=%.02f%% usedinodes=%llu "
"maxinodes=%llu inodeusage=%.02f%% lifetime=%s ratio=%s",
Recycle::gRecyclingPrefix.c_str(),
used_bytes, max_bytes, used_bytes * 100.0 / max_bytes,
used_inodes, max_inodes, used_inodes * 100.0 / max_inodes,
attrmap.count(Recycle::gRecyclingTimeAttribute) ?
attrmap[Recycle::gRecyclingTimeAttribute].c_str() : "-1",
attrmap.count(Recycle::gRecyclingKeepRatio) ?
attrmap[Recycle::gRecyclingKeepRatio].c_str() : "-1");
oss_out << sline << std::endl;
}
}
}
std_out += oss_out.str();
}
/*----------------------------------------------------------------------------*/
void
Recycle::PrintOld(std::string& std_out, std::string& std_err,
eos::common::VirtualIdentity& vid, bool monitoring,
bool translateids, bool details)
{
using namespace eos::common;
XrdOucString uids;
XrdOucString gids;
std::map > printmap;
eos::common::VirtualIdentity rootvid = eos::common::VirtualIdentity::Root();
if ((!vid.uid) ||
(vid.hasUid(3)) ||
(vid.hasGid(4))) {
// add everything found in the recycle directory structure to the printmap
std::string subdirs;
XrdMgmOfsDirectory dirl1;
XrdMgmOfsDirectory dirl2;
int listrc = dirl1.open(Recycle::gRecyclingPrefix.c_str(), rootvid,
(const char*) 0);
if (listrc) {
eos_static_err("msg=\"unable to list the garbage directory level-1\" recycle-path=%s",
Recycle::gRecyclingPrefix.c_str());
} else {
// loop over all directories = group directories
const char* dname1;
while ((dname1 = dirl1.nextEntry())) {
std::string sdname = dname1;
if ((sdname == ".") || (sdname == "..")) {
continue;
}
if (sdname.substr(0, 4) == "uid:") {
continue;
}
gid_t gid = strtoull(dname1, 0, 10);
std::string l2 = Recycle::gRecyclingPrefix;
l2 += dname1;
// list level-2 user directories
listrc = dirl2.open(l2.c_str(), rootvid, (const char*) 0);
if (listrc) {
eos_static_err("msg=\"unable to list the garbage directory level-2\" recycle-path=%s l2-path=%s",
Recycle::gRecyclingPrefix.c_str(), l2.c_str());
} else {
const char* dname2;
while ((dname2 = dirl2.nextEntry())) {
std::string sdname = dname2;
if ((sdname == ".") || (sdname == "..")) {
continue;
}
uid_t uid = strtoull(dname2, 0, 10);
printmap[gid][uid] = true;
}
dirl2.close();
}
}
dirl1.close();
}
} else {
// add only the virtual user to the printmap
printmap[vid.gid][vid.uid] = true;
}
std::ostringstream oss_out;
if (details) {
size_t count = 0;
for (auto itgid = printmap.begin(); itgid != printmap.end(); itgid++) {
for (auto ituid = itgid->second.begin(); ituid != itgid->second.end();
ituid++) {
XrdMgmOfsDirectory dirl;
char sdir[4096];
snprintf(sdir, sizeof(sdir) - 1, "%s/%u/%u/", Recycle::gRecyclingPrefix.c_str(),
(unsigned int) itgid->first, (unsigned int) ituid->first);
int retc = dirl.open(sdir, vid, "");
if (!retc) {
const char* dname;
while ((dname = dirl.nextEntry())) {
std::string sdname = dname;
if ((sdname == ".") || (sdname == "..")) {
continue;
}
std::string fullpath = sdir;
fullpath += dname;
XrdOucString originode;
XrdOucString origpath = dname;
// demangle the original pathname
while (origpath.replace("#:#", "/")) {
}
XrdOucString type = "file";
struct stat buf;
XrdOucErrInfo error;
if (!gOFS->_stat(fullpath.c_str(), &buf, error, vid, "", nullptr, false)) {
if (translateids) {
int errc = 0;
uids = eos::common::Mapping::UidToUserName(buf.st_uid, errc).c_str();
if (errc) {
uids = eos::common::Mapping::UidAsString(buf.st_uid).c_str();
}
gids = eos::common::Mapping::GidToGroupName(buf.st_gid, errc).c_str();
if (errc) {
gids = eos::common::Mapping::GidAsString(buf.st_gid).c_str();
}
} else {
uids = eos::common::Mapping::UidAsString(buf.st_uid).c_str();
gids = eos::common::Mapping::GidAsString(buf.st_gid).c_str();
}
if (origpath.endswith(Recycle::gRecyclingPostFix.c_str())) {
type = "recursive-dir";
origpath.erase(origpath.length() - Recycle::gRecyclingPostFix.length());
}
originode = origpath;
originode.erase(0, origpath.length() - 16);
origpath.erase(origpath.length() - 17);
// put the key prefixes
if (type == "file") {
originode.insert("fxid:", 0);
} else {
originode.insert("pxid:", 0);
}
if (monitoring) {
XrdOucString sizestring;
oss_out << "recycle=ls recycle-bin=" << Recycle::gRecyclingPrefix.c_str()
<< " uid=" << uids.c_str() << " gid=" << gids.c_str()
<< " size=" << std::to_string(buf.st_size)
<< " deletion-time=" << std::to_string(buf.st_ctime)
<< " type=" << type.c_str()
<< " keylength.restore-path=" << origpath.length()
<< " restore-path=" << origpath.c_str()
<< " restore-key=" << originode.c_str() << std::endl;
} else {
char sline[4096];
XrdOucString sizestring;
if (count == 0) {
// print a header
snprintf(sline, sizeof(sline) - 1,
"# %-24s %-8s %-8s %-12s %-13s %-20s %-64s\n",
"Deletion Time", "UID", "GID", "SIZE", "TYPE",
"RESTORE-KEY", "RESTORE-PATH");
oss_out << sline
<< "# ================================================"
"=========================================================="
"====================\n";
}
char tdeltime[4096];
std::string deltime = ctime_r(&buf.st_ctime, tdeltime);
deltime.erase(deltime.length() - 1);
snprintf(sline, sizeof(sline) - 1, "%-26s %-8s %-8s %-12s %-13s %-20s %-64s",
deltime.c_str(), uids.c_str(), gids.c_str(),
StringConversion::GetSizeString((unsigned long long) buf.st_size).c_str(),
type.c_str(), originode.c_str(), origpath.c_str());
if (oss_out.tellp() > 1 * 1024 * 1024 * 1024) {
retc = E2BIG;
oss_out << "... (truncated after 1G of output)" << std::endl;
std_out += oss_out.str();
std_err += "warning: list too long - truncated after 1GB of output!\n";
return;
}
oss_out << sline << std::endl;
}
count++;
if ((vid.uid) && (!vid.sudoer) && (count > 100000)) {
retc = E2BIG;
oss_out << "... (truncated)" << std::endl;
std_out += oss_out.str();
std_err += "warning: list too long - truncated after 100000 entries!\n";
return;
}
}
}
}
}
}
}
std_out += oss_out.str();
}
/*----------------------------------------------------------------------------*/
int
Recycle::Restore(std::string& std_out, std::string& std_err,
eos::common::VirtualIdentity& vid, const char* key,
bool force_orig_name, bool restore_versions, bool make_path)
{
eos::common::VirtualIdentity rootvid = eos::common::VirtualIdentity::Root();
if (!key) {
std_err += "error: invalid argument as recycle key\n";
return EINVAL;
}
XrdOucString skey = key;
bool force_file = false;
bool force_directory = false;
if (skey.beginswith("fxid:")) {
skey.erase(0, 5);
force_file = true;
}
if (skey.beginswith("pxid:")) {
skey.erase(0, 5);
force_directory = true;
}
unsigned long long fid = strtoull(skey.c_str(), 0, 16);
// convert the hex inode number into decimal and retrieve path name
std::shared_ptr fmd;
std::shared_ptr cmd;
std::string recyclepath;
XrdOucString repath;
XrdOucString rprefix = Recycle::gRecyclingPrefix.c_str();
rprefix += "/";
rprefix += (int) vid.gid;
rprefix += "/";
rprefix += (int) vid.uid;
XrdOucString newrprefix = Recycle::gRecyclingPrefix.c_str();
newrprefix += "/uid:";
newrprefix += (int) vid.uid;
while (rprefix.replace("//", "/")) {
}
while (newrprefix.replace("//", "/")) {
}
{
// TODO(gbitzes): This could be more precise...
eos::Prefetcher::prefetchFileMDWithParentsAndWait(gOFS->eosView, fid);
eos::Prefetcher::prefetchContainerMDWithParentsAndWait(gOFS->eosView, fid);
eos::common::RWMutexReadLock lock(gOFS->eosViewRWMutex);
if (!force_directory) {
try {
fmd = gOFS->eosFileService->getFileMD(fid);
recyclepath = gOFS->eosView->getUri(fmd.get());
repath = recyclepath.c_str();
if (!repath.beginswith(rprefix.c_str()) &&
!repath.beginswith(newrprefix.c_str())) {
std_err = "error: this is not a file in your recycle bin - try to "
"prefix the key with pxid:\n";
return EPERM;
}
} catch (eos::MDException& e) {
}
}
if (!force_file && !fmd) {
try {
cmd = gOFS->eosDirectoryService->getContainerMD(fid);
recyclepath = gOFS->eosView->getUri(cmd.get());
repath = recyclepath.c_str();
if (!repath.beginswith(rprefix.c_str()) &&
!repath.beginswith(newrprefix.c_str())) {
std_err = "error: this is not a directory in your recycle bin\n";
return EPERM;
}
} catch (eos::MDException& e) {
}
}
if (!recyclepath.length()) {
std_err = "error: cannot find object referenced by recycle-key=";
std_err += key;
return ENOENT;
}
}
// reconstruct original file name
eos::common::Path cPath(recyclepath.c_str());
XrdOucString originalpath = cPath.GetName();
// Demangle path
while (originalpath.replace("#:#", "/")) {
}
if (originalpath.endswith(Recycle::gRecyclingPostFix.c_str())) {
originalpath.erase(originalpath.length() - Recycle::gRecyclingPostFix.length() -
16 - 1);
} else {
originalpath.erase(originalpath.length() - 16 - 1);
}
// Check that this is a path to recycle
if (!repath.beginswith(Recycle::gRecyclingPrefix.c_str())) {
std_err = "error: referenced object cannot be recycled\n";
return EINVAL;
}
eos::common::Path oPath(originalpath.c_str());
// Check if the client is the owner of the object to recycle
struct stat buf;
XrdOucErrInfo lError;
eos_static_info("msg=\"trying to restore file\" path=\"%s\"",
cPath.GetPath());
if (gOFS->_stat(cPath.GetPath(), &buf, lError, rootvid, "", nullptr, false)) {
std_err += "error: unable to stat path to be recycled\n";
return EIO;
}
// check that the client is the owner of that object
if (vid.uid != buf.st_uid) {
std_err +=
"error: to recycle this file you have to have the role of the file owner: uid=";
std_err += (int) buf.st_uid;
std_err += "\n";
return EPERM;
}
// check if original parent path exists
if (gOFS->_stat(oPath.GetParentPath(), &buf, lError, rootvid, "")) {
if (make_path) {
XrdOucErrInfo lError;
// create path
ProcCommand cmd;
XrdOucString info = "mgm.cmd=mkdir&mgm.option=p&mgm.path=";
info += oPath.GetParentPath();
cmd.open("/proc/user", info.c_str(), vid, &lError);
cmd.close();
int rc = cmd.GetRetc();
if (rc) {
std_err += "error: creation failed: ";
std_err += cmd.GetStdErr();
return rc;
}
} else {
std_err = "error: you have to recreate the restore directory path=";
std_err += oPath.GetParentPath();
std_err += " to be able to restore this file/tree\n";
std_err += "hint: retry after creating the mentioned directory\n";
return ENOENT;
}
}
// check if original path is existing
if (!gOFS->_stat(oPath.GetPath(), &buf, lError, rootvid, "", nullptr, false)) {
if (force_orig_name == false) {
std_err +=
"error: the original path already exists, use '-f|--force-original-name' \n"
"to put the deleted file/tree back and rename the file/tree in place to .\n";
return EEXIST;
} else {
std::string newold = oPath.GetPath();
char sp[256];
snprintf(sp, sizeof(sp) - 1, "%016llx",
(unsigned long long)(S_ISDIR(buf.st_mode) ? buf.st_ino :
eos::common::FileId::InodeToFid(buf.st_ino)));
newold += ".";
newold += sp;
if (gOFS->_rename(oPath.GetPath(), newold.c_str(), lError, rootvid, "", "",
true, true)) {
std_err +=
"error: failed to rename the existing file/tree where we need to restore path=";
std_err += oPath.GetPath();
std_err += "\n";
std_err += lError.getErrText();
return EIO;
} else {
std_out += "warning: renamed restore path=";
std_out += oPath.GetPath();
std_out += " to backup-path=";
std_out += newold.c_str();
std_out += "\n";
}
}
}
// do the 'undelete' aka rename
if (gOFS->_rename(cPath.GetPath(), oPath.GetPath(), lError, rootvid, "", "",
true)) {
std_err += "error: failed to undelete path=";
std_err += oPath.GetPath();
std_err += "\n";
return EIO;
} else {
std_out += "success: restored path=";
std_out += oPath.GetPath();
std_out += "\n";
}
if (restore_versions == false) {
// don't restore old versions
return 0;
}
XrdOucString vkey;
if (gOFS->_attr_get(oPath.GetPath(), lError, rootvid, "",
Recycle::gRecyclingVersionKey.c_str(), vkey)) {
// no version directory to restore
return 0;
}
int retc = Restore(std_out, std_err, vid, vkey.c_str(), force_orig_name,
restore_versions);
// mask an non existant version reference
if (retc == ENOENT) {
return 0;
}
return retc;
}
/*----------------------------------------------------------------------------*/
int
Recycle::PurgeOld(std::string& std_out, std::string& std_err,
eos::common::VirtualIdentity& vid)
{
eos::common::VirtualIdentity rootvid = eos::common::VirtualIdentity::Root();
XrdMgmOfsDirectory dirl;
char sdir[4096];
snprintf(sdir, sizeof(sdir) - 1, "%s/%u/%u/", Recycle::gRecyclingPrefix.c_str(),
(unsigned int) vid.gid, (unsigned int) vid.uid);
int retc = dirl.open(sdir, vid, "");
if (retc) {
std_out = "success: nothing has been purged in the old recycle bin!\n";
return 0;
}
const char* dname;
int nfiles_deleted = 0;
int nbulk_deleted = 0;
while ((dname = dirl.nextEntry())) {
std::string sdname = dname;
if ((sdname == ".") || (sdname == "..")) {
continue;
}
std::string pathname = sdir;
pathname += dname;
struct stat buf;
XrdOucErrInfo lError;
if (!gOFS->_stat(pathname.c_str(), &buf, lError, vid, "", nullptr, false)) {
// execute a proc command
ProcCommand Cmd;
XrdOucString info;
if (S_ISDIR(buf.st_mode)) {
// we need recursive deletion
info = "mgm.cmd=rm&mgm.option=r&mgm.path=";
} else {
info = "mgm.cmd=rm&mgm.path=";
}
info += pathname.c_str();
int result = Cmd.open("/proc/user", info.c_str(), rootvid, &lError);
Cmd.AddOutput(std_out, std_err);
if (*std_out.rbegin() != '\n') {
std_out += "\n";
}
if (*std_err.rbegin() != '\n') {
std_err += "\n";
}
Cmd.close();
if (!result) {
if (S_ISDIR(buf.st_mode)) {
nbulk_deleted++;
} else {
nfiles_deleted++;
}
}
}
}
dirl.close();
std_out += "success: purged ";
std_out += std::to_string(nbulk_deleted);
std_out += " bulk deletions and ";
std_out += std::to_string(nfiles_deleted);
std_out += " individual files from the old recycle bin!\n";
return 0;
}
/*----------------------------------------------------------------------------*/
int
Recycle::Purge(std::string& std_out, std::string& std_err,
eos::common::VirtualIdentity& vid,
std::string date,
bool global,
std::string key)
{
eos::common::VirtualIdentity rootvid = eos::common::VirtualIdentity::Root();
XrdMgmOfsDirectory dirl;
char sdir[4096];
XrdOucErrInfo lError;
int nfiles_deleted = 0;
int nbulk_deleted = 0;
std::string rpath;
// fix security hole
if (date.find("..")!=std::string::npos) {
std_err = "error: the date contains an illegal character sequence";
return EINVAL;
}
// translate key into search pattern
if (key.length()) {
if (key.substr(0, 5) == "fxid:") {
// purge file
key.erase(0, 5);
} else {
if (key.substr(0, 5) == "pxid:") {
// purge directory
key.erase(0, 5);
key += ".d";
} else {
std_err = "error: the given key to purge is invalid - must start with fxid: or pxid: (see output of recycle ls)";
return EINVAL;
}
}
}
if (vid.uid && !vid.sudoer &&
!(vid.hasUid(3)) &&
!(vid.hasGid(4))) {
std_err = "error: you cannot purge your recycle bin without being a sudor "
"or having an admin role";
return EPERM;
}
if (!global || (global && vid.uid)) {
snprintf(sdir, sizeof(sdir) - 1, "%s/uid:%u/%s",
Recycle::gRecyclingPrefix.c_str(),
(unsigned int) vid.uid,
date.c_str());
} else {
snprintf(sdir, sizeof(sdir) - 1, "%s/", Recycle::gRecyclingPrefix.c_str());
}
std::map> findmap;
int depth = 5 + (int) global;
eos::common::Path dPath(std::string("/") + date);
if (dPath.GetSubPathSize()) {
if (depth > (int) dPath.GetSubPathSize()) {
depth -= dPath.GetSubPathSize();
}
}
XrdOucString err_msg;
int retc = gOFS->_find(sdir, lError, err_msg, rootvid, findmap,
0, 0, false, 0, true, depth);
if (retc && errno != ENOENT) {
std_err = err_msg.c_str();
eos_static_err("msg=\"find command failed\" dir=\"%s\"", sdir);
}
for (auto dirit = findmap.begin(); dirit != findmap.end(); ++dirit) {
eos_static_debug("dir=%s", dirit->first.c_str());
XrdOucString dirname = dirit->first.c_str();
if (dirname.endswith(".d/")) {
dirname.erase(dirname.length() - 1);
eos::common::Path cpath(dirname.c_str());
dirname = cpath.GetParentPath();
dirit->second.insert(cpath.GetName());
}
for (auto fileit = dirit->second.begin(); fileit != dirit->second.end();
++fileit) {
// Symlink files returned by the find command above contain
// a pointer to the original name which needs to be removed
// so that we can properly stat the file.
std::string fname = *fileit;
size_t pos = fname.find(" -> ");
if (pos != std::string::npos) {
fname.erase(pos);
eos_static_debug("orig_path=\"%s\" symlink_path=\"%s\"",
fileit->c_str(), fname.c_str());
}
if ((fname != "/") && (fname.find('#') != 0)) {
continue;
}
struct stat buf;
XrdOucErrInfo lError;
std::string fullpath = dirname.c_str();
fullpath += fname;
if (!gOFS->_stat(fullpath.c_str(), &buf, lError, rootvid, "", nullptr, false)) {
if (key.length()) {
// check for a particular string pattern
if (fullpath.find(key) == std::string::npos) {
continue;
}
}
// execute a proc command
ProcCommand Cmd;
XrdOucString info;
if (S_ISDIR(buf.st_mode)) {
// we need recursive deletion
info = "mgm.cmd=rm&mgm.option=r&mgm.path=";
} else {
info = "mgm.cmd=rm&mgm.path=";
}
info += fullpath.c_str();
int result = Cmd.open("/proc/user", info.c_str(), rootvid, &lError);
Cmd.AddOutput(std_out, std_err);
if (*std_out.rbegin() != '\n') {
std_out += "\n";
}
if (*std_err.rbegin() != '\n') {
std_err += "\n";
}
Cmd.close();
if (!result) {
if (S_ISDIR(buf.st_mode)) {
nbulk_deleted++;
} else {
nfiles_deleted++;
}
}
}
}
}
std_out += "success: purged ";
std_out += std::to_string(nbulk_deleted);
std_out += " bulk deletions and ";
std_out += std::to_string(nfiles_deleted);
std_out += " individual files from the recycle bin!";
if (key.length() &&
(!nbulk_deleted) &&
(!nfiles_deleted)) {
std_err += "error: no entry for key='";
std_err += key;
std_err += "'";
return ENODATA;
}
return 0;
}
/*----------------------------------------------------------------------------*/
int
Recycle::Config(std::string& std_out, std::string& std_err,
eos::common::VirtualIdentity& vid,
const std::string& key, const std::string& value)
{
XrdOucErrInfo lError;
eos::common::VirtualIdentity rootvid = eos::common::VirtualIdentity::Root();
if (vid.uid != 0) {
std_err = "error: you need to be root to configure the recycle bin"
" and/or recycle polcies\n";
return EPERM;
}
if (key == "--add-bin") {
if (value.empty()) {
std_err = "error: missing subtree argument\n";
return EINVAL;
}
// execute a proc command
ProcCommand Cmd;
XrdOucString info;
info = "eos.rgid=0&eos.ruid=0&mgm.cmd=attr&mgm.subcmd=set&mgm.option=r&mgm.path=";
info += value.c_str();
info += "&mgm.attr.key=";
info += Recycle::gRecyclingAttribute.c_str();
info += "&mgm.attr.value=";
info += Recycle::gRecyclingPrefix.c_str();
int result = Cmd.open("/proc/user", info.c_str(), rootvid, &lError);
Cmd.AddOutput(std_out, std_err);
Cmd.close();
return result;
}
if (key == "--remove-bin") {
if (value.empty()) {
std_err = "error: missing subtree argument\n";
return EINVAL;
}
// execute a proc command
ProcCommand Cmd;
XrdOucString info;
info = "eos.rgid=0&eos.ruid=0&mgm.cmd=attr&mgm.subcmd=rm&mgm.option=r&mgm.path=";
info += value.c_str();
info += "&mgm.attr.key=";
info += Recycle::gRecyclingAttribute.c_str();
int result = Cmd.open("/proc/user", info.c_str(), rootvid, &lError);
Cmd.AddOutput(std_out, std_err);
Cmd.close();
return result;
}
if (key == "--lifetime") {
if (value.empty()) {
std_err = "error: missing lifetime argument";
return EINVAL;
}
uint64_t size = 0ull;
try {
size = std::stoull(value);
} catch (...) {
size = 0ull;
}
if (!size) {
std_err = "error: lifetime has been converted to 0 seconds - probably you made a typo!";
return EINVAL;
}
if (size < 60) {
std_err = "error: a recycle bin lifetime less than 60s is not accepted!";
return EINVAL;
}
if (gOFS->_attr_set(Recycle::gRecyclingPrefix.c_str(),
lError, rootvid, "",
Recycle::gRecyclingTimeAttribute.c_str(),
value.c_str())) {
std_err = "error: failed to set extended attribute '";
std_err += Recycle::gRecyclingTimeAttribute.c_str();
std_err += "'";
std_err += " at '";
std_err += Recycle::gRecyclingPrefix.c_str();
std_err += "'";
return EIO;
} else {
std_out += "success: recycle bin lifetime configured!\n";
}
gOFS->Recycler->WakeUp();
}
if (key == "--ratio") {
if (value.empty()) {
std_err = "error: missing ratio argument\n";
return EINVAL;
}
double ratio = 0.0;
try {
ratio = std::stod(value);
} catch (...) {
ratio = 0.0;
}
if (!ratio) {
std_err = "error: ratio must be != 0";
return EINVAL;
}
if ((ratio <= 0) || (ratio > 0.99)) {
std_err = "error: a recycle bin ratio has to be 0 < ratio < 1.0!";
return EINVAL;
}
if (gOFS->_attr_set(Recycle::gRecyclingPrefix.c_str(),
lError, rootvid, "",
Recycle::gRecyclingKeepRatio.c_str(),
value.c_str())) {
std_err = "error: failed to set extended attribute '";
std_err += Recycle::gRecyclingKeepRatio.c_str();
std_err += "'";
std_err += " at '";
std_err += Recycle::gRecyclingPrefix.c_str();
std_err += "'";
return EIO;
} else {
std_out += "success: recycle bin ratio configured!";
}
gOFS->Recycler->WakeUp();
}
return 0;
}
/*----------------------------------------------------------------------------*/
int
Recycle::GetRecyclePrefix(const char* epname, XrdOucErrInfo& error,
std::string& recyclepath, int i_index)
/*----------------------------------------------------------------------------*/
{
eos::common::VirtualIdentity rootvid = eos::common::VirtualIdentity::Root();
char srecycleuser[4096];
time_t now = time(NULL);
struct tm nowtm;
localtime_r(&now, &nowtm);
size_t index = (i_index == -1) ? 0 : i_index;
do {
snprintf(srecycleuser, sizeof(srecycleuser) - 1, "%s/uid:%u/%04u/%02u/%02u/%lu",
mRecycleDir.c_str(),
mOwnerUid,
1900 + nowtm.tm_year,
nowtm.tm_mon + 1,
nowtm.tm_mday,
index);
struct stat buf;
// if i_index is not -1, we just compute the path for the given index and return if it exists already
if (i_index >= 0) {
if (!gOFS->_stat(srecycleuser, &buf, error, rootvid, "")) {
recyclepath = srecycleuser;
} else {
return gOFS->Emsg(epname, error, ENOENT, "stat index directory - "
"the computed index recycle directory does not exist");
}
return SFS_OK;
}
// check in case the index directory exists, that it has not more than 1M files,
// otherwise increment the index by one
if (!gOFS->_stat(srecycleuser, &buf, error, rootvid, "")) {
if (buf.st_blksize > 100000) {
index++;
continue;
}
}
// Verify/create group/user directory
if (gOFS->_mkdir(srecycleuser, S_IRUSR | S_IXUSR | SFS_O_MKPTH, error, rootvid,
"")) {
return gOFS->Emsg(epname, error, EIO, "remove existing file - the "
"recycle space user directory couldn't be created");
}
// Check the user recycle directory
if (gOFS->_stat(srecycleuser, &buf, error, rootvid, "")) {
return gOFS->Emsg(epname, error, EIO, "remove existing file - could not "
"determine ownership of the recycle space user directory",
srecycleuser);
}
// Check the ownership of the user directory
if ((buf.st_uid != mOwnerUid) || (buf.st_gid != mOwnerGid)) {
// Set the correct ownership
if (gOFS->_chown(srecycleuser, mOwnerUid, mOwnerGid, error, rootvid, "")) {
return gOFS->Emsg(epname, error, EIO, "remove existing file - could not "
"change ownership of the recycle space user directory",
srecycleuser);
}
}
recyclepath = srecycleuser;
return SFS_OK;
} while (1);
}
EOSMGMNAMESPACE_END