//------------------------------------------------------------------------------
// @file: NewfindCmd.cc
// @author: Fabio Luchetti, Georgios Bitzes, Jozsef Makai - CERN
//------------------------------------------------------------------------------
/************************************************************************
* EOS - the CERN Disk Storage System *
* Copyright (C) 2017 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
#include "NewfindCmd.hh"
#include "common/LayoutId.hh"
#include "common/Path.hh"
#include "common/Timing.hh"
#include "mgm/Acl.hh"
#include "mgm/FsView.hh"
#include "mgm/Stat.hh"
#include "mgm/XrdMgmOfs.hh"
#include "mgm/auth/AccessChecker.hh"
#include "namespace/interface/IView.hh"
#include "namespace/ns_quarkdb/ContainerMD.hh"
#include "namespace/ns_quarkdb/FileMD.hh"
#include "namespace/ns_quarkdb/NamespaceGroup.hh"
#include "namespace/ns_quarkdb/explorer/NamespaceExplorer.hh"
#include "namespace/utils/BalanceCalculator.hh"
#include "namespace/utils/Checksum.hh"
#include "namespace/utils/Stat.hh"
#include "namespace/utils/Etag.hh"
#include
#include
EOSMGMNAMESPACE_BEGIN
template
static bool eliminateBasedOnFileMatch(const eos::console::FindProto& req,
const T& md)
{
std::string toFilter = md->getName();
std::regex filter(req.name(), std::regex_constants::egrep);
return (!req.name().empty()) && (!std::regex_search(toFilter, filter));
}
//------------------------------------------------------------------------------
// Based on the Uid/Gid of given FileMd / ContainerMd, should it be included
// in the search results?
//------------------------------------------------------------------------------
template
static bool eliminateBasedOnUidGid(const eos::console::FindProto& req,
const T& md)
{
if (req.searchuid() && md->getCUid() != req.uid()) {
return true;
}
if (req.searchnotuid() && md->getCUid() == req.notuid()) {
return true;
}
if (req.searchgid() && md->getCGid() != req.gid()) {
return true;
}
if (req.searchnotgid() && md->getCGid() == req.notgid()) {
return true;
}
return false;
}
//----------------------------------------------------------------------------------
// Check whether to eliminate depending on modification time and options passed to NewfindCmd.
// @note Assume ctime_t is always of type "struct timespec" for both containers and files
//----------------------------------------------------------------------------------
template
static bool eliminateBasedOnTime(const eos::console::FindProto& req,
const T& md)
{
struct timespec xtime;
if (req.ctime()) {
md->getCTime(xtime);
} else {
md->getMTime(xtime);
}
if (req.onehourold()) {
if (xtime.tv_sec > (time(nullptr) - 3600)) {
return true;
}
}
time_t selectoldertime = (time_t) req.olderthan();
time_t selectyoungertime = (time_t) req.youngerthan();
if (selectoldertime > 0) {
if (xtime.tv_sec > selectoldertime) {
return true;
}
}
if (selectyoungertime > 0) {
if (xtime.tv_sec < selectyoungertime) {
return true;
}
}
return false;
}
//------------------------------------------------------------------------------
// Check whether to select depending on permissions.
//------------------------------------------------------------------------------
template
static bool eliminateBasedOnPermissions(const eos::console::FindProto& req,
const T& md)
{
if (!req.searchpermission() && !req.searchnotpermission()) {
return false;
}
mode_t st_mode = eos::modeFromMetadataEntry(md);
std::ostringstream flagOstr;
flagOstr << std::oct << st_mode;
std::string flagStr = flagOstr.str();
std::string permString = flagStr.substr(flagStr.length() - 3);
if (req.searchpermission() && permString != req.permission()) {
return true;
}
if (req.searchnotpermission() && permString == req.notpermission()) {
return true;
}
return false;
}
//------------------------------------------------------------------------------
// Check whether to select depending on attributes.
//------------------------------------------------------------------------------
template
static bool eliminateBasedOnAttr(const eos::console::FindProto& req,
const T& md)
{
if (req.attributekey().empty() || req.attributevalue().empty()) {
return false;
}
std::string attr;
if (!gOFS->_attr_get(*md.get(), req.attributekey(), attr)) {
return true;
}
return (attr != req.attributevalue());
}
//------------------------------------------------------------------------------
// Check whether to select depending on the file/container having faulty ACLs.
//------------------------------------------------------------------------------
template
static bool eliminateBasedOnFaultyAcl(const eos::console::FindProto& req,
const T& md)
{
// if not asking for faulty acls, just don't eliminate it
if (!req.faultyacl()) {
return false;
} else {
XrdOucErrInfo errInfo;
std::string sysacl;
std::string useracl;
// return jumps makes it convoluted, sight...
if (gOFS->_attr_get(*md.get(), "sys.acl", sysacl)) {
if (!Acl::IsValid(sysacl.c_str(), errInfo)) {
return false;
}
}
if (gOFS->_attr_get(*md.get(), "user.acl", useracl)) {
if (!Acl::IsValid(useracl.c_str(), errInfo)) {
return false;
}
}
return true;
}
}
template
static bool FilterOut(const eos::console::FindProto& req,
const T& md)
{
return (
eliminateBasedOnFileMatch(req, md) ||
eliminateBasedOnUidGid(req, md) ||
eliminateBasedOnTime(req, md) ||
eliminateBasedOnPermissions(req, md) ||
eliminateBasedOnAttr(req, md) ||
eliminateBasedOnFaultyAcl(req, md)
);
}
// For files only. Check whether file replicas belong to different scheduling groups
static bool hasSizeZero(std::shared_ptr& fmd)
{
return (fmd->getSize() == 0);
}
// For files only. Check whether file replicas belong to different scheduling groups
static bool hasMixedSchedGroups(std::shared_ptr& fmd)
{
// find files which have replicas on mixed scheduling groups
std::string sGroupRef = "";
std::string sGroup = "";
for (auto lociter : fmd->getLocations()) {
// ignore filesystem id 0
if (!lociter) {
eos_static_err("fsid 0 found fxid=%08llx", fmd->getId());
continue;
}
eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);
eos::common::FileSystem* filesystem = FsView::gFsView.mIdView.lookupByID(
lociter);
if (filesystem != nullptr) {
sGroup = filesystem->GetString("schedgroup");
} else {
sGroup = "none";
}
if (!sGroupRef.empty()) {
if (sGroup != sGroupRef) {
return true;
}
} else {
sGroupRef = sGroup;
}
}
return false;
}
// For files only. Check whether a file has not the nominal number of stripes(replicas)
static bool hasStripeDiff(std::shared_ptr& fmd)
{
return (fmd->getNumLocation() == eos::common::LayoutId::GetStripeNumber(
fmd->getLayoutId()) + 1);
}
//------------------------------------------------------------------------------
// Print path.
//------------------------------------------------------------------------------
template // std::ofstream or std::stringstream
static void printPath(S& ss, const eos::console::FindProto& req,
const std::string& path)
{
if (req.format().length() || req.treecount()) {
ss << "path=\"";
}
if (req.xurl()) {
ss << "root://" << gOFS->MgmOfsAlias << "/";
}
ss << path;
if (req.format().length() || req.treecount()) {
ss << "\"";
}
}
//------------------------------------------------------------------------------
// Print target.
//------------------------------------------------------------------------------
template // std::ofstream or std::stringstream
static void printTarget(S& ss, const eos::console::FindProto& req,
const std::string& path)
{
ss << " target=\"";
ss << path;
ss << "\"";
}
//------------------------------------------------------------------------------
// Print uid / gid of a FileMD or ContainerMD, if requested by req.
//------------------------------------------------------------------------------
template
static void printUidGid(S& ss, const eos::console::FindProto& req, const T& md)
{
if (req.format().length()) {
return;
}
if (req.printuid()) {
ss << " uid=" << md->getCUid();
}
if (req.printgid()) {
ss << " gid=" << md->getCGid();
}
}
template
static void printAttributes(S& ss,
const eos::console::FindProto& req, const T& md)
{
if (req.format().length()) {
return;
}
if (!req.printkey().empty()) {
std::string attr;
if (!gOFS->_attr_get(*md.get(), req.printkey(), attr)) {
attr = "undef";
}
ss << " " << req.printkey() << "=" << attr;
}
}
//------------------------------------------------------------------------------
// Print directories and files count of a ContainerMD, if requested by req.
//------------------------------------------------------------------------------
static void printChildCount(std::ofstream& ss,
const eos::console::FindProto& req,
const std::shared_ptr& cmd, size_t ndirs, size_t nfiles)
{
if (req.format().length()) {
return;
}
if (req.childcount()) {
ss << " ndirs=" << ndirs
<< " nfiles=" << nfiles;
}
}
//------------------------------------------------------------------------------
// Print du information
//------------------------------------------------------------------------------
static void printDu(std::ofstream& ss, const eos::console::FindProto& req,
const std::shared_ptr& cmd, size_t ndirs, size_t nfiles)
{
if (req.du()) {
bool si = req.dusi();
bool readable = req.dureadable();
size_t treesize = cmd->getTreeSize();
std::string size = readable ?
eos::common::StringConversion::GetReadableSizeString(treesize, si ? ((treesize>(10*si))?"iB":"B") : "B",
si ? 1024 : 1000) : std::to_string(treesize);
ss << std::left << std::setw(16) << size << " ";
}
}
//------------------------------------------------------------------------------
// Print user defined format
//------------------------------------------------------------------------------
static void printFormat(std::ofstream& ss, const eos::console::FindProto& req,
const std::shared_ptr& cmd, size_t ndirs, size_t nfiles)
{
if (req.format().length()) {
std::vector tokens;
eos::common::StringConversion::Tokenize(req.format(),
tokens, ",");
for (auto i : tokens) {
if (i == "type") {
ss << " type=directory ";
}
if (i == "size") {
ss << " size=" << std::to_string(cmd->getTreeSize());
}
if (i == "cxid") {
ss << " cxid=" << std::hex << cmd->getId() << std::dec;
}
if (i == "pxid") {
ss << " pxid=" << std::hex << cmd->getParentId() << std::dec;
}
if (i == "cid") {
ss << " cid=" << cmd->getId();
}
if (i == "pid") {
ss << " pid=" << cmd->getParentId();
}
if (i == "uid") {
ss << " uid=" << cmd->getCUid();
}
if (i == "gid") {
ss << " gid=" << cmd->getCGid();
}
if (i == "mode") {
ss << " mode=" << std::oct << cmd->getMode() << std::dec;
}
if (i == "files") {
ss << " files=" << ndirs;
}
if (i == "directories") {
ss << " directories=" << nfiles;
}
if (i == "mtime") {
eos::IFileMD::ctime_t mtime {0, 0};
cmd->getMTime(mtime);
ss << " mtime=" << eos::common::Timing::TimespecToString(mtime);
}
if (i == "btime") {
eos::IFileMD::ctime_t btime {0, 0};
if (cmd->getAttributes().count("sys.eos.btime")) {
eos::common::Timing::Timespec_from_TimespecStr(
cmd->getAttributes()["sys.eos.btime"], btime);
}
ss << " btime=" << eos::common::Timing::TimespecToString(btime);
}
if (i == "ctime") {
eos::IFileMD::ctime_t ctime {0, 0};
cmd->getCTime(ctime);
ss << " ctime=" << eos::common::Timing::TimespecToString(ctime);
}
if (i == "etag") {
std::string etag;
eos::calculateEtag(cmd.get(), etag);
ss << " etag=" << etag;
}
if (i.substr(0, 5) == "attr.") {
std::string attr = i.substr(5);
if (attr == "*") {
for (const auto& elem : cmd->getAttributes()) {
ss << " attr." << elem.first << "=" << "\"" << elem.second << "\"";
}
} else {
if (cmd->getAttributes().count(attr)) {
ss << " " << i << "=\"" << cmd->getAttributes()[attr] << "\"";
}
}
}
}
}
}
//------------------------------------------------------------------------------
// Print du information
//------------------------------------------------------------------------------
static void printDu(std::ofstream& ss, const eos::console::FindProto& req,
const std::shared_ptr& fmd)
{
if (req.du()) {
bool si = req.dusi();
bool readable = req.dureadable();
size_t filesize = fmd->getSize();
std::string size = readable ?
eos::common::StringConversion::GetReadableSizeString(filesize, si ? ((filesize>(10*si))?"iB":"B") : "B",
si ? 1024 : 1000) : std::to_string(filesize);
ss << std::left << std::setw(16) << size << " ";
}
}
//------------------------------------------------------------------------------
// Print user defined format
//------------------------------------------------------------------------------
static void printFormat(std::ofstream& ss, const eos::console::FindProto& req,
const std::shared_ptr& fmd)
{
if (req.format().length()) {
std::vector tokens;
eos::common::StringConversion::Tokenize(req.format(),
tokens, ",");
for (auto i : tokens) {
if (i == "type") {
if (fmd->isLink()) {
ss << " type=symlink ";
} else {
ss << " type=file ";
}
}
if (i == "size") {
ss << " size=" << std::to_string(fmd->getSize());
}
if (i == "fxid") {
ss << " fxid=" << std::hex << fmd->getId() << std::dec;
}
if (i == "cxid") {
ss << " cxid=" << std::hex << fmd->getContainerId() << std::dec;
}
if (i == "fid") {
ss << " fid=" << fmd->getId();
}
if (i == "cid") {
ss << " cid=" << fmd->getContainerId();
}
if (i == "uid") {
ss << " uid=" << fmd->getCUid();
}
if (i == "gid") {
ss << " gid=" << fmd->getCGid();
}
if (i == "flags") {
ss << " flags=" << std::oct << fmd->getFlags() << std::dec;
}
if (i == "atime") {
eos::IFileMD::ctime_t atime {0, 0};
fmd->getCTime(atime);
ss << " atime=" << eos::common::Timing::TimespecToString(atime);
}
if (i == "mtime") {
eos::IFileMD::ctime_t mtime {0, 0};
fmd->getMTime(mtime);
ss << " mtime=" << eos::common::Timing::TimespecToString(mtime);
}
if (i == "btime") {
eos::IFileMD::ctime_t btime {0, 0};
if (fmd->getAttributes().count("sys.eos.btime")) {
eos::common::Timing::Timespec_from_TimespecStr(
fmd->getAttributes()["sys.eos.btime"], btime);
}
ss << " btime=" << eos::common::Timing::TimespecToString(btime);
}
if (i == "ctime") {
eos::IFileMD::ctime_t ctime {0, 0};
fmd->getCTime(ctime);
ss << " ctime=" << eos::common::Timing::TimespecToString(ctime);
}
if (i == "etag") {
std::string etag;
eos::calculateEtag(fmd.get(), etag);
ss << " etag=" << etag;
}
if (i == "checksum") {
std::string xs;
eos::appendChecksumOnStringAsHex(fmd.get(), xs);
ss << " checksum=" << xs;
}
if (i == "checksumtype") {
ss << " checksumtype=" << eos::common::LayoutId::GetChecksumString(
fmd->getLayoutId());
}
if (i.substr(0, 5) == "attr.") {
std::string attr = i.substr(5);
if (attr == "*") {
for (const auto& elem : fmd->getAttributes()) {
ss << " attr." << elem.first << "=" << "\"" << elem.second << "\"";
}
} else {
if (fmd->getAttributes().count(attr)) {
ss << " " << i << "=\"" << fmd->getAttributes()[attr] << "\"";
}
}
}
}
}
}
//------------------------------------------------------------------------------
// Print hex checksum of given fmd, if requested by req.
//------------------------------------------------------------------------------
template // std::ofstream or std::stringstream
static void printChecksum(S& ss, const eos::console::FindProto& req,
const std::shared_ptr& fmd)
{
if (req.format().length()) {
return;
}
if (req.checksum()) {
ss << " checksum=";
std::string checksum;
eos::appendChecksumOnStringAsHex(fmd.get(), checksum);
ss << checksum;
}
}
//------------------------------------------------------------------------------
// Print replica location of an fmd.
//------------------------------------------------------------------------------
template // std::ofstream or std::stringstream
static void printReplicas(S& ss, const std::shared_ptr& fmd,
bool onlyhost, bool selectonline)
{
if (onlyhost) {
ss << " hosts=";
} else {
ss << " partition=";
}
std::set results;
for (auto lociter : fmd->getLocations()) {
// lookup filesystem
eos::common::RWMutexReadLock lock(FsView::gFsView.ViewMutex);
eos::common::FileSystem* filesystem = FsView::gFsView.mIdView.lookupByID(
lociter);
if (!filesystem) {
continue;
}
eos::common::FileSystem::fs_snapshot_t fs;
if (filesystem->SnapShotFileSystem(fs, true)) {
if (selectonline && filesystem->GetActiveStatus() !=
eos::common::ActiveStatus::kOnline) {
continue;
}
std::string item;
if (onlyhost) {
item = fs.mHost;
} else {
item = fs.mHost;
item += ":";
item += fs.mPath;
}
results.insert(item);
}
}
for (auto it = results.begin(); it != results.end(); it++) {
if (it != results.begin()) {
ss << ",";
}
ss << it->c_str();
}
}
//------------------------------------------------------------------------------
// Print fs of a FileMD.
//------------------------------------------------------------------------------
template // std::ofstream or std::stringstream
static void printFs(S& ss, const std::shared_ptr& fmd)
{
ss << " fsid=";
eos::IFileMD::LocationVector loc_vect = fmd->getLocations();
eos::IFileMD::LocationVector::const_iterator lociter;
for (lociter = loc_vect.begin(); lociter != loc_vect.end(); ++lociter) {
if (lociter != loc_vect.begin()) {
ss << ',';
}
ss << *lociter;
}
}
//------------------------------------------------------------------------------
// Print a selected FileMD, according to formatting settings in req.
//------------------------------------------------------------------------------
template // std::ofstream or std::stringstream
static void printFMD(S& ss, const eos::console::FindProto& req,
const std::shared_ptr& fmd)
{
if (req.format().length()) {
return;
}
if (req.size()) {
ss << " size=" << fmd->getSize();
}
if (req.fid()) {
ss << " fid=" << fmd->getId();
}
/* Outputs of uid and gid are duplicated. These are also in printUidGid function
if (req.printuid()) {
ss << " uid=" << fmd->getCUid();
}
if (req.printgid()) {
ss << " gid=" << fmd->getCGid();
}
*/
if (req.fs()) {
printFs(ss, fmd);
}
if (req.partition()) {
printReplicas(ss, fmd, false, req.online());
}
if (req.hosts()) {
printReplicas(ss, fmd, true, req.online());
}
printChecksum(ss, req, fmd);
if (req.ctime()) {
eos::IFileMD::ctime_t ctime;
fmd->getCTime(ctime);
ss << " ctime=" << (unsigned long long) ctime.tv_sec;
ss << '.' << (unsigned long long) ctime.tv_nsec;
}
if (req.mtime()) {
eos::IFileMD::ctime_t mtime;
fmd->getMTime(mtime);
ss << " mtime=" << (unsigned long long) mtime.tv_sec;
ss << '.' << (unsigned long long) mtime.tv_nsec;
}
if (req.nrep()) {
ss << " nrep=" << fmd->getNumLocation();
}
if (req.nunlink()) {
ss << " nunlink=" << fmd->getNumUnlinkedLocation();
}
}
//------------------------------------------------------------------------------
// Purge atomic files
//------------------------------------------------------------------------------
template // std::ofstream or std::stringstream
void
NewfindCmd::ProcessAtomicFilePurge(S& ss,
const std::string& fspath,
eos::IFileMD& fmd)
{
if (fspath.find(EOS_COMMON_PATH_ATOMIC_FILE_PREFIX) == std::string::npos) {
return;
}
ss << "# found atomic " << fspath << std::endl;
if (!(mVid.uid == 0 || mVid.uid == fmd.getCUid())) {
// Not allowed to remove file
ss << "# skipping atomic " << fspath << " [no permission to remove]" <<
std::endl;
return;
}
time_t now = time(nullptr);
eos::IFileMD::ctime_t atime;
fmd.getCTime(atime);
//----------------------------------------------------------------------------
// Is the file older than 1 day?
//----------------------------------------------------------------------------
if (now - atime.tv_sec <= 86400) {
ss << "# skipping atomic " << fspath << " [< 1d old ]" << std::endl;
return;
}
//----------------------------------------------------------------------------
// Perform the rm
//----------------------------------------------------------------------------
XrdOucErrInfo errInfo;
if (!gOFS->_rem(fspath.c_str(), errInfo, mVid, (const char*) nullptr)) {
ss << "# purging atomic " << fspath;
} else {
ss << "# could not purge atomic " << fspath;
}
}
//------------------------------------------------------------------------------
// Purge version directory.
//------------------------------------------------------------------------------
template // std::ofstream or std::stringstream
void
NewfindCmd::PurgeVersions(S& ss, int64_t maxVersion,
const std::string& dirpath)
{
if (dirpath.find(EOS_COMMON_PATH_VERSION_PREFIX) == std::string::npos) {
return;
}
struct stat buf;
XrdOucErrInfo errInfo;
if ((!gOFS->_stat(dirpath.c_str(), &buf, errInfo, mVid, nullptr,
nullptr)) && ((mVid.uid == 0) || (mVid.uid == buf.st_uid))) {
ss << "# purging " << dirpath;
gOFS->PurgeVersion(dirpath.c_str(), errInfo, maxVersion);
}
}
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// Modify layout stripes
//------------------------------------------------------------------------------
void
NewfindCmd::ModifyLayoutStripes(std::ofstream& ss,
const eos::console::FindProto& req,
const std::string& fspath)
{
XrdOucErrInfo errInfo;
ProcCommand fileCmd;
std::string info = "mgm.cmd=file&mgm.subcmd=layout&mgm.path=";
info += fspath;
info += "&mgm.file.layout.stripes=";
info += std::to_string(req.layoutstripes());
if (fileCmd.open("/proc/user", info.c_str(), mVid, &errInfo) == 0) {
std::ostringstream outputStream;
XrdSfsFileOffset offset = 0;
constexpr uint32_t size = 512;
auto bytesRead = 0ul;
char buffer[size];
do {
bytesRead = fileCmd.read(offset, buffer, size);
for (auto i = 0u; i < bytesRead; i++) {
outputStream << buffer[i];
}
offset += bytesRead;
} while (bytesRead == size);
fileCmd.close();
XrdOucEnv env(outputStream.str().c_str());
if (std::stoi(env.Get("mgm.proc.retc")) == 0) {
if (!req.silent()) {
ofstdoutStream << env.Get("mgm.proc.stdout") << std::endl;
}
} else {
ofstderrStream << env.Get("mgm.proc.stderr") << std::endl;
}
}
}
// Find result struct
//------------------------------------------------------------------------------
struct FindResult {
std::string path;
bool isdir;
bool iscache;
bool expansionFilteredOut;
eos::IContainerMD::XAttrMap
attrs; // Filled out as long as populateLinkedAttributes set
uint64_t numFiles = 0;
uint64_t numContainers = 0;
NamespaceItem item;
std::shared_ptr toContainerMD()
{
if (iscache) {
try {
auto cmd = gOFS->eosView->getContainer(path);
numFiles = cmd->getNumFiles();
numContainers = cmd->getNumContainers();
return cmd;
} catch (eos::MDException& e) {
}
return {};
} else {
if (item.isFile) {
return {};
} else {
auto p = std::make_shared(eos::QuarkContainerMD());
p->initializeWithoutChildren(eos::ns::ContainerMdProto(item.containerMd));
// copy xattributes
for (auto i : item.attrs) {
p->setAttribute(i.first, i.second);
}
return p;
}
}
}
std::shared_ptr toFileMD()
{
if (iscache) {
try {
return gOFS->eosView->getFile(path);
} catch (eos::MDException& e) {
}
return {};
} else {
if (item.isFile) {
auto p = std::make_shared(eos::QuarkFileMD());
p->initialize(eos::ns::FileMdProto(item.fileMd));
// copy xattributes
for (auto i : item.attrs) {
p->setAttribute(i.first, i.second);
}
return p;
} else {
return {};
}
}
}
};
//------------------------------------------------------------------------------
// Filter-out directories which we have no permission to access
//------------------------------------------------------------------------------
class TraversalFilter : public ExpansionDecider
{
public:
TraversalFilter(const eos::common::VirtualIdentity& v) : vid(v) {}
virtual bool shouldExpandContainer(const eos::ns::ContainerMdProto& proto,
const eos::IContainerMD::XAttrMap& attrs,
const std::string& fullPath) override
{
eos::QuarkContainerMD cmd;
cmd.initializeWithoutChildren(eos::ns::ContainerMdProto(proto));
return AccessChecker::checkContainer(&cmd, attrs, R_OK | X_OK, vid)
&& AccessChecker::checkPublicAccess(fullPath, vid);
}
private:
const eos::common::VirtualIdentity& vid;
};
//------------------------------------------------------------------------------
// Find result provider class
//------------------------------------------------------------------------------
class FindResultProvider
{
public:
//----------------------------------------------------------------------------
// QDB: Initialize NamespaceExplorer
//----------------------------------------------------------------------------
FindResultProvider(qclient::QClient* qc, const std::string& target,
const uint32_t depthlimit, const bool ignore_files,
const eos::common::VirtualIdentity& v)
: qcl(qc), path(target), depthlimit(depthlimit), ignore_files(ignore_files),
vid(v)
{
restart();
}
//----------------------------------------------------------------------------
// Restart
//----------------------------------------------------------------------------
void restart()
{
if (!found) {
ExplorationOptions options;
options.populateLinkedAttributes = true;
options.view = gOFS->eosView;
options.depthLimit = depthlimit;
options.expansionDecider.reset(new TraversalFilter(vid));
options.ignoreFiles = ignore_files;
explorer.reset(new NamespaceExplorer(path, options, *qcl,
static_cast(gOFS->namespaceGroup.get())->getExecutor()));
}
}
//----------------------------------------------------------------------------
// In-memory: Check whether we need to take deep query mutex lock
//----------------------------------------------------------------------------
FindResultProvider()
{
localfound.reset(new std::map>());
found = localfound.get();
}
~FindResultProvider()
{
if (found) {
found->clear();
}
}
//----------------------------------------------------------------------------
// In-memory: Get map for holding content results
//----------------------------------------------------------------------------
std::map>* getFoundMap()
{
return found;
}
bool nextInMemory(FindResult& res)
{
res.expansionFilteredOut = false;
res.iscache = true;
// Search not started yet?
if (!inMemStarted) {
dirIterator = found->begin();
targetFileSet = &dirIterator->second;
fileIterator = targetFileSet->begin();
inMemStarted = true;
res.path = dirIterator->first;
res.isdir = true;
return true;
}
// At the end of a file iterator? Give out next directory.
if (fileIterator == targetFileSet->end()) {
dirIterator++;
if (dirIterator == found->end()) {
// Search has ended.
return false;
}
targetFileSet = &dirIterator->second;
fileIterator = targetFileSet->begin();
res.path = dirIterator->first;
res.isdir = true;
return true;
}
// Nope, just give out another file.
res.path = dirIterator->first + *fileIterator;
res.isdir = false;
fileIterator++;
return true;
}
bool nextInQDB(FindResult& res)
{
res.iscache = false;
// Just copy the result given by namespace explorer
if (!explorer->fetch(res.item)) {
return false;
}
res.path = res.item.fullPath;
res.isdir = !res.item.isFile;
res.expansionFilteredOut = res.item.expansionFilteredOut;
res.attrs = res.item.attrs;
if (res.item.isFile) {
res.numFiles = 0;
res.numContainers = 0;
} else {
res.numFiles = res.item.numFiles;
res.numContainers = res.item.numContainers;
}
return true;
}
bool next(FindResult& res)
{
if (found) {
// In-memory case
return nextInMemory(res);
}
// QDB case
return nextInQDB(res);
}
private:
//----------------------------------------------------------------------------
// In-memory: Map holding results
//----------------------------------------------------------------------------
std::unique_ptr>> localfound;
std::map>* found = nullptr;
bool inMemStarted = false;
std::map>::iterator dirIterator;
std::set* targetFileSet = nullptr;
std::set::iterator fileIterator;
//----------------------------------------------------------------------------
// QDB: NamespaceExplorer and QClient
//----------------------------------------------------------------------------
qclient::QClient* qcl = nullptr;
std::string path;
uint32_t depthlimit;
bool ignore_files;
std::unique_ptr explorer;
eos::common::VirtualIdentity vid;
};
//------------------------------------------------------------------------------
// Method implementing the specific behaviour of the command executed
//------------------------------------------------------------------------------
eos::console::ReplyProto
NewfindCmd::ProcessRequest() noexcept
{
XrdOucString m_err {""};
eos::console::ReplyProto reply;
if (!OpenTemporaryOutputFiles()) {
reply.set_retc(EIO);
reply.set_std_err(SSTR("error: cannot write find result files on MGM" <<
std::endl));
return reply;
}
const eos::console::FindProto& findRequest = mReqProto.find();
auto& purgeversion = findRequest.purge();
bool purge = false;
bool purge_atomic = (purgeversion == "atomic");
auto max_version = 999999ul;
if (!purge_atomic) {
try {
max_version = std::stoul(purgeversion);
purge = true;
} catch (std::logic_error& err) {
// this error is handled at client side, should not receive bad input from client
}
}
// This hash is used to calculate the balance of the found files over the
// filesystems involved
eos::BalanceCalculator balanceCalculator;
eos::common::Path cPath(findRequest.path().c_str());
XrdOucErrInfo errInfo;
// check what actually is ...
XrdSfsFileExistence file_exists;
if ((gOFS->_exists(findRequest.path().c_str(), file_exists, errInfo, mVid,
nullptr))) {
ofstderrStream << "error: failed to run exists on '" << findRequest.path() <<
"'" <<
std::endl;
reply.set_retc(errno);
return reply;
} else {
if (file_exists == XrdSfsFileExistNo) {
ofstderrStream << "error: no such file or directory" << std::endl;
reply.set_retc(ENOENT);
return reply;
}
}
errInfo.clear();
std::unique_ptr qcl =
std::make_unique(gOFS->mQdbContactDetails.members,
gOFS->mQdbContactDetails.constructOptions());
std::unique_ptr findResultProvider;
int depthlimit = findRequest.Maxdepth__case() ==
eos::console::FindProto::MAXDEPTH__NOT_SET ?
eos::common::Path::MAX_LEVELS : cPath.GetSubPathSize() + findRequest.maxdepth();
// @note Shortcut with bad input --name regex filters. Move to client side?
// Looks like std::regex suffers from https://gcc.gnu.org/bugzilla/show_bug.cgi?id=86164#c7
// Found by filtering like " newfind --name '*sometext' " (note the '*' prefix!),
// which raises - although quite misleading.
// Is this an opportunity for fuzzing?
if (!findRequest.name().empty()) {
try {
std::regex filter(findRequest.name(), std::regex_constants::egrep);
} catch (const std::regex_error& e) {
eos_static_info("caught exception %d %s with newfind findRequest.name()=%s\n",
e.code(), e.what(), findRequest.name().c_str());
ofstderrStream << "error(caught exception " << e.code() << ' ' << e.what() <<
" with newfind --name=" << findRequest.name()
<< ").\nPlease note that --name filters by 'egrep' style regex match, you may have to sanitize your input\n";
if (!CloseTemporaryOutputFiles()) {
reply.set_retc(EIO);
reply.set_std_err("error: cannot save find result files on MGM\n");
return reply;
} else {
return reply;
}
}
}
bool onlydirs = (findRequest.directories() &&
!findRequest.files()) | findRequest.count() | findRequest.treecount() |
findRequest.childcount();
if (findRequest.cache()) {
// read via our in-memory cache using _find
findResultProvider.reset(new FindResultProvider());
std::map>* found =
findResultProvider->getFoundMap();
if (gOFS->_find(findRequest.path().c_str(), errInfo, m_err, mVid, (*found),
findRequest.attributekey().length() ? findRequest.attributekey().c_str() :
nullptr,
findRequest.attributevalue().length() ? findRequest.attributevalue().c_str() :
nullptr,
onlydirs, 0, true, findRequest.maxdepth(),
findRequest.name().empty() ? nullptr : findRequest.name().c_str())) {
ofstderrStream << "error: unable to run find in directory" << std::endl;
reply.set_retc(errno);
return reply;
} else {
if (m_err.length()) {
ofstderrStream << m_err;
reply.set_retc(E2BIG);
return reply;
}
}
} else {
// read from the QDB backend
try {
findResultProvider.reset(new FindResultProvider(qcl.get(),
findRequest.path(), depthlimit,
onlydirs, mVid));
} catch(eos::MDException& e) {
eos_static_info("caught exception errno=%d what=\"%s\" in newfind "
"findRequest.path()=%s",
e.getErrno(), e.what(), findRequest.path().c_str());
if (e.getErrno() == ENOENT) {
ofstderrStream << "error: no such file or directory" << std::endl;
} else {
ofstderrStream << "error: unable to start find" << std::endl;
}
reply.set_retc(e.getErrno());
return reply;
}
}
uint64_t treecount_aggregate_dircounter = 0;
uint64_t treecount_aggregate_filecounter = 0;
uint64_t dircounter = 0;
uint64_t filecounter = 0;
// For general users, cannot return more than 50k dirs and 100k files with one find,
// unless there is an access rule allowing deeper queries.
// Special users (like root) have the limit lifted by default.
const bool limit_result = ((mVid.uid != 0) && (!mVid.hasUid(3)) &&
(!mVid.hasGid(4)) && (!mVid.sudoer));
static uint64_t dir_limit = 50000;
static uint64_t file_limit = 100000;
Access::GetFindLimits(mVid, dir_limit, file_limit);
// @note assume that findResultProvider will serve results DFS-ordered
FindResult findResult;
std::shared_ptr cMD;
std::shared_ptr fMD;
// EXEC_TIMING_BEGIN("Newfind");
std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now();
while (findResultProvider->next(findResult)) {
if (limit_result) {
if (dircounter >= dir_limit || filecounter >= file_limit) {
ofstderrStream << "warning(" << E2BIG
<< "): find results are limited for you to " << dir_limit
<< " directories and " << file_limit << " files.\n"
<< "Result is truncated! (found " << dircounter
<< " directories and " << filecounter
<< " files so far)\n";
reply.set_retc(E2BIG);
break;
}
}
if (findResult.isdir) {
if (!findRequest.directories() && findRequest.files() && !findRequest.count() &&
!findRequest.treecount()) {
continue;
}
if (findResult.expansionFilteredOut) {
// Returns a meaningful error message. Mirrors the checks in shouldExpandContainer
if (!AccessChecker::checkContainer(findResult.toContainerMD().get(),
findResult.attrs, R_OK | X_OK, mVid)) {
ofstderrStream << "error(" << EACCES << "): no permissions to read directory ";
ofstderrStream << findResult.path << std::endl;
reply.set_retc(EACCES);
continue;
} else if (!AccessChecker::checkPublicAccess(findResult.path,
const_cast(mVid))) {
ofstderrStream << "error(" << EACCES <<
"): public access level restriction on directory ";
ofstderrStream << findResult.path << std::endl;
reply.set_retc(EACCES);
continue;
} else {
// @note Empty branch, will be cut out from the compiler anyway.
// Either the findResult container can't be expanded further as it reaches maxdepth
// (this is not an error), either there is something fundamentally wrong. Should never happen.
}
}
// Selection
cMD = findResult.toContainerMD();
if (!cMD) {
continue; // Process next item if we don't have a cMD
}
// --treecount nullify the filters, we don't want to bias the count because of intermediate filtered-out result
// Alse, take the chance to update the total counter while traversing
if (!findRequest.treecount()) {
if (FilterOut(findRequest, cMD)) {
continue;
}
} else {
treecount_aggregate_dircounter += findResult.numContainers;
treecount_aggregate_filecounter += findResult.numFiles;
}
dircounter++;
filecounter += findResult.numFiles;
if (findRequest.count() || findRequest.treecount()) {
continue;
}
// Purge version directory?
if (purge) {
this->PurgeVersions(ofstdoutStream, max_version, findResult.path);
continue;
}
// Printing
// Are we printing fileinfo -m? Then, that's it
if (findRequest.fileinfo()) {
this->PrintFileInfoMinusM(findResult.path, errInfo);
continue;
}
printDu(ofstdoutStream, findRequest, cMD, findResult.numContainers,
findResult.numFiles);
printPath(ofstdoutStream, findRequest, findResult.path);
printChildCount(ofstdoutStream, findRequest, cMD, findResult.numContainers,
findResult.numFiles);
printFormat(ofstdoutStream, findRequest, cMD, findResult.numContainers,
findResult.numFiles);
printUidGid(ofstdoutStream, findRequest, cMD);
printAttributes(ofstdoutStream, findRequest, cMD);
ofstdoutStream << std::endl;
} else {
if (!findRequest.files() && findRequest.directories()) {
continue;
}
// Selection
fMD = findResult.toFileMD();
if (!fMD) {
continue; // Process next item if we don't have a fMD
}
// Balance calculation? If yes,
// ignore selection criteria (TODO: Change this?)
// and simply account all fMD's.
if (findRequest.balance()) {
balanceCalculator.account(fMD);
continue;
}
if (FilterOut(findRequest, fMD)) {
continue;
}
if (findRequest.zerosizefiles() && !hasSizeZero(fMD)) {
continue;
}
if (findRequest.mixedgroups() && !hasMixedSchedGroups(fMD)) {
continue;
}
if (findRequest.stripediff() && hasStripeDiff(fMD)) {
continue; // @note or the opposite?
}
filecounter++;
if (findRequest.count() || findRequest.treecount()) {
continue;
}
// Purge atomic files?
if (purge_atomic) {
this->ProcessAtomicFilePurge(ofstdoutStream, findResult.path, *fMD.get());
continue;
}
// Modify layout stripes?
if (findRequest.dolayoutstripes()) {
this->ModifyLayoutStripes(ofstdoutStream, findRequest, findResult.path);
continue;
}
// Printing
// Are we printing fileinfo -m? Then, that's it
if (findRequest.fileinfo()) {
this->PrintFileInfoMinusM(findResult.path, errInfo);
continue;
}
printDu(ofstdoutStream, findRequest, fMD);
if (findRequest.format().length()) {
// format printing for symlinks
printPath(ofstdoutStream, findRequest, findResult.path);
} else {
// standard represenation for symlinks
printPath(ofstdoutStream, findRequest,
fMD->isLink() ? findResult.path + " -> " + fMD->getLink() : findResult.path);
}
printFormat(ofstdoutStream, findRequest, fMD);
if (fMD->isLink()) {
printTarget(ofstdoutStream, findRequest, fMD->getLink());
}
printUidGid(ofstdoutStream, findRequest, fMD);
printAttributes(ofstdoutStream, findRequest, fMD);
printFMD(ofstdoutStream, findRequest, fMD);
ofstdoutStream << std::endl;
}
}
// EXEC_TIMING_END("Newfind");
std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
gOFS->MgmStats.AddExec("Newfind",
std::chrono::duration_cast(end - begin).count());
gOFS->MgmStats.Add("Newfind", mVid.uid, mVid.gid, 1);
gOFS->MgmStats.Add("NewfindEntries", mVid.uid, mVid.gid, filecounter);
if (findRequest.treecount()) {
printPath(ofstdoutStream, findRequest, findResult.path);
ofstdoutStream << " sum.nfiles=" << treecount_aggregate_filecounter
<< " sum.ndirectories=" << treecount_aggregate_dircounter <<
std::endl;
}
if (findRequest.count()) {
ofstdoutStream << "nfiles=" << filecounter << " ndirectories=" << dircounter <<
std::endl;
}
if (findRequest.balance()) {
balanceCalculator.printSummary(ofstdoutStream);
}
if (!CloseTemporaryOutputFiles()) {
reply.set_retc(EIO);
reply.set_std_err("error: cannot save find result files on MGM\n");
return reply;
}
return reply;
}
//------------------------------------------------------------------------------
// Get fileinfo about path in monitoring format
//------------------------------------------------------------------------------
void
NewfindCmd::PrintFileInfoMinusM(const std::string& path,
XrdOucErrInfo& errInfo)
{
// print fileinfo -m
ProcCommand Cmd;
XrdOucString lStdOut = "";
XrdOucString lStdErr = "";
XrdOucString info = "&mgm.cmd=fileinfo&mgm.path=";
info += path.c_str();
info += "&mgm.file.info.option=-m";
Cmd.open("/proc/user", info.c_str(), mVid, &errInfo);
Cmd.AddOutput(lStdOut, lStdErr);
if (lStdOut.length()) {
ofstdoutStream << lStdOut << std::endl;
}
if (lStdErr.length()) {
ofstderrStream << lStdErr << std::endl;
}
Cmd.close();
}
#ifdef EOS_GRPC
//------------------------------------------------------------------------------
// Method implementing the specific behaviour of the command executed for gRPC
//------------------------------------------------------------------------------
void
NewfindCmd::ProcessRequest(grpc::ServerWriter* writer)
{
XrdOucString m_err {""};
eos::console::ReplyProto StreamReply;
const eos::console::FindProto& findRequest = mReqProto.find();
auto& purgeversion = findRequest.purge();
bool purge = false;
bool purge_atomic = (purgeversion == "atomic");
auto max_version = 999999ul;
if (!purge_atomic) {
try {
max_version = std::stoul(purgeversion);
purge = true;
} catch (std::logic_error& err) {
// this error is handled at client side, should not receive bad input from client
}
}
// This hash is used to calculate the balance of the found files over the
// filesystems involved
eos::BalanceCalculator balanceCalculator;
eos::common::Path cPath(findRequest.path().c_str());
XrdOucErrInfo errInfo;
// check what actually is ...
XrdSfsFileExistence file_exists;
if ((gOFS->_exists(findRequest.path().c_str(), file_exists, errInfo, mVid,
nullptr))) {
StreamReply.set_std_out("");
StreamReply.set_std_err(
"error: failed to run exists on '" + findRequest.path() + "'\n");
StreamReply.set_retc(errno);
writer->Write(StreamReply);
return;
} else if (file_exists == XrdSfsFileExistNo) {
StreamReply.set_std_out("");
StreamReply.set_std_err("error: no such file or directory\n");
StreamReply.set_retc(ENOENT);
writer->Write(StreamReply);
return;
}
errInfo.clear();
std::unique_ptr qcl =
std::make_unique(gOFS->mQdbContactDetails.members,
gOFS->mQdbContactDetails.constructOptions());
std::unique_ptr findResultProvider;
int depthlimit = findRequest.Maxdepth__case() ==
eos::console::FindProto::MAXDEPTH__NOT_SET ?
eos::common::Path::MAX_LEVELS : cPath.GetSubPathSize() + findRequest.maxdepth();
// @note Shortcut with bad input --name regex filters. Move to client side?
// Looks like std::regex suffers from https://gcc.gnu.org/bugzilla/show_bug.cgi?id=86164#c7
// Found by filtering like " newfind --name '*sometext' " (note the '*' prefix!),
// which raises - although quite misleading.
// Is this an opportunity for fuzzing?
if (!findRequest.name().empty()) {
try {
std::regex filter(findRequest.name(), std::regex_constants::egrep);
} catch (const std::regex_error& e) {
eos_static_info("caught exception %d %s with newfind findRequest.name()=%s\n",
e.code(), e.what(), findRequest.name().c_str());
StreamReply.set_std_out("");
StreamReply.set_std_err(
"error(caught exception " + std::to_string(e.code()) + " " + e.what() +
" with find --name=" + findRequest.name() +
").\nPlease note that --name filters by 'egrep' style regex match, you may have to sanitize your input\n");
StreamReply.set_retc(errno);
writer->Write(StreamReply);
return;
}
}
bool onlydirs = (findRequest.directories() && !findRequest.files()) ||
findRequest.treecount();
if (findRequest.cache()) {
// read via our in-memory cache using _find
// TODO: check, may need to reset findResultProvider
std::map>* found =
findResultProvider->getFoundMap();
if (gOFS->_find(findRequest.path().c_str(), errInfo, m_err, mVid, (*found),
findRequest.attributekey().length() ? findRequest.attributekey().c_str() :
nullptr,
findRequest.attributevalue().length() ? findRequest.attributevalue().c_str() :
nullptr,
findRequest.directories(), 0, true, findRequest.maxdepth(),
findRequest.name().empty() ? nullptr : findRequest.name().c_str())) {
ofstderrStream << "error: unable to run find in directory" << std::endl;
StreamReply.set_retc(errno);
return;
} else {
if (m_err.length()) {
ofstderrStream << m_err;
StreamReply.set_retc(E2BIG);
return;
}
}
} else {
// read from the back-end
try {
findResultProvider.reset(new FindResultProvider(qcl.get(),
findRequest.path(), depthlimit,
onlydirs, mVid));
} catch(eos::MDException& e) {
eos_static_info("caught exception errno=%d what=\"%s\" in newfind "
"findRequest.path()=%s",
e.getErrno(), e.what(), findRequest.path().c_str());
StreamReply.set_std_out("");
if (e.getErrno() == ENOENT) {
StreamReply.set_std_err("error: no such file or directory\n");
} else {
StreamReply.set_std_err("error: unable to start find\n");
}
StreamReply.set_retc(e.getErrno());
writer->Write(StreamReply);
return;
}
}
uint64_t treecount_aggregate_dircounter = 0;
uint64_t treecount_aggregate_filecounter = 0;
uint64_t dircounter = 0;
uint64_t filecounter = 0;
// For general users, cannot return more than 50k dirs and 100k files with one find,
// unless there is an access rule allowing deeper queries.
// Special users (like root) have the limit lifted by default.
const bool limit_result = ((mVid.uid != 0) && (!mVid.hasUid(3)) &&
(!mVid.hasGid(4)) && (!mVid.sudoer));
static uint64_t dir_limit = 50000;
static uint64_t file_limit = 100000;
Access::GetFindLimits(mVid, dir_limit, file_limit);
// @note assume that findResultProvider will serve results DFS-ordered
FindResult findResult;
std::shared_ptr cMD;
std::shared_ptr fMD;
// EXEC_TIMING_BEGIN("Newfind");
std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now();
std::string output_str("");
int counter = 0;
while (findResultProvider->next(findResult)) {
if (limit_result) {
if (dircounter >= dir_limit || filecounter >= file_limit) {
output_str += "warning(" + std::to_string(E2BIG) +
"): find results are limited for you to " + std::to_string(dir_limit) +
" directories and " + std::to_string(file_limit) +
" files.\nResult is truncated! (found " +
std::to_string(dircounter) + " directories and " + std::to_string(
filecounter) + " files so far)\n";
break;
}
}
std::stringstream output;
if (findResult.isdir) {
if (!findRequest.directories() && findRequest.files() && !findRequest.count()) {
continue;
}
if (findResult.expansionFilteredOut) {
// Returns a meaningful error message. Mirrors the checks in shouldExpandContainer
if (!AccessChecker::checkContainer(findResult.toContainerMD().get(),
findResult.attrs, R_OK | X_OK, mVid)) {
output_str += "error(" + std::to_string(EACCES) +
"): no permissions to read directory " + findResult.path + "\n";
continue;
} else if (!AccessChecker::checkPublicAccess(findResult.path,
const_cast(mVid))) {
output_str += "error(" + std::to_string(EACCES) +
"): public access level restriction on directory " + findResult.path + "\n";
continue;
} else {
// @note Empty branch, will be cut out from the compiler anyway.
// Either the findResult container can't be expanded further as it reaches maxdepth
// (this is not an error), either there is something fundamentally wrong. Should never happen.
}
}
// Selection
cMD = findResult.toContainerMD();
if (!cMD) {
continue; // Process next item if we don't have a cMD
}
// --treecount nullify the filters, we don't want to bias the count because of intermediate filtered-out result
// Alse, take the chance to update the total counter while traversing
if (!findRequest.treecount()) {
if (FilterOut(findRequest, cMD)) {
continue;
}
} else {
treecount_aggregate_dircounter += findResult.numContainers;
treecount_aggregate_filecounter += findResult.numFiles;
}
dircounter++;
filecounter += findResult.numFiles;
if (findRequest.count() || findRequest.treecount()) {
continue;
}
// Purge version directory?
if (purge) {
this->PurgeVersions(output, max_version, findResult.path);
}
// Printing
// Are we printing fileinfo -m? Then, that's it
else if (findRequest.fileinfo()) {
this->PrintFileInfoMinusM(output, findResult.path, errInfo);
} else {
printDu(ofstdoutStream, findRequest, cMD, findResult.numContainers,
findResult.numFiles);
printPath(output, findRequest, findResult.path);
printChildCount(ofstdoutStream, findRequest, cMD, findResult.numContainers,
findResult.numFiles);
printFormat(ofstdoutStream, findRequest, cMD, findResult.numContainers,
findResult.numFiles);
printUidGid(output, findRequest, cMD);
printAttributes(output, findRequest, cMD);
// Print times for directory
if (findRequest.ctime()) {
eos::IFileMD::ctime_t ctime;
cMD->getCTime(ctime);
output << " ctime=" << (unsigned long long) ctime.tv_sec;
output << '.' << (unsigned long long) ctime.tv_nsec;
}
if (findRequest.mtime()) {
eos::IFileMD::ctime_t mtime;
cMD->getMTime(mtime);
output << " mtime=" << (unsigned long long) mtime.tv_sec;
output << '.' << (unsigned long long) mtime.tv_nsec;
}
}
} else if (!findResult.isdir) { // redundant, no problem
if (!findRequest.files() && findRequest.directories()) {
continue;
}
// Selection
fMD = findResult.toFileMD();
if (!fMD) {
continue; // Process next item if we don't have a fMD
}
// Balance calculation? If yes,
// ignore selection criteria (TODO: Change this?)
// and simply account all fMD's.
if (findRequest.balance()) {
balanceCalculator.account(fMD);
continue;
}
if (FilterOut(findRequest, fMD)) {
continue;
}
if (findRequest.zerosizefiles() && !hasSizeZero(fMD)) {
continue;
}
if (findRequest.mixedgroups() && !hasMixedSchedGroups(fMD)) {
continue;
}
if (findRequest.stripediff() && hasStripeDiff(fMD)) {
continue; // @note or the opposite?
}
if (findRequest.count() || findRequest.treecount()) {
continue;
}
// Purge atomic files?
if (purge_atomic) {
this->ProcessAtomicFilePurge(output, findResult.path, *fMD.get());
}
// Modify layout stripes?
else if (findRequest.dolayoutstripes()) {
this->ModifyLayoutStripes(output, findRequest, findResult.path);
}
// Printing
// Are we printing fileinfo -m? Then, that's it
else if (findRequest.fileinfo()) {
this->PrintFileInfoMinusM(output, findResult.path, errInfo);
} else {
printDu(ofstdoutStream, findRequest, fMD);
printPath(output, findRequest,
fMD->isLink() ? findResult.path + " -> " + fMD->getLink() : findResult.path);
printFormat(ofstdoutStream, findRequest, fMD);
printUidGid(output, findRequest, fMD);
printAttributes(output, findRequest, fMD);
printFMD(output, findRequest, fMD);
}
}
output_str += output.str();
counter++;
// Erase "\t" if it is on the end of entry
if (!output_str.empty() && output_str.substr(output_str.size() - 1) == "\t") {
output_str.erase(output_str.size() - 1);
}
// Add "\n" if doesn't exist
if (!output_str.empty() && output_str.substr(output_str.size() - 1) != "\n") {
output_str += "\n";
}
// Write every 100 lines separately to gRPC
if (counter >= 100) {
StreamReply.set_std_out(output_str);
StreamReply.set_std_err("");
StreamReply.set_retc(0);
writer->Write(StreamReply);
counter = 0;
output_str.clear();
}
}
// Write last part to gRPC, if exists
if (!output_str.empty()) {
StreamReply.set_std_out(output_str);
StreamReply.set_std_err("");
StreamReply.set_retc(0);
writer->Write(StreamReply);
}
// EXEC_TIMING_END("Newfind");
std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
gOFS->MgmStats.AddExec("Newfind",
std::chrono::duration_cast(end - begin).count());
gOFS->MgmStats.Add("Newfind", mVid.uid, mVid.gid, 1);
gOFS->MgmStats.Add("NewfindEntries", mVid.uid, mVid.gid, filecounter);
if (findRequest.treecount()) {
StreamReply.set_std_out(
"path=\"" + findRequest.path() +
"\" sum.nfiles=" + std::to_string(treecount_aggregate_filecounter) +
" sum.ndirectories=" + std::to_string(treecount_aggregate_dircounter) + "\n");
StreamReply.set_std_err("");
StreamReply.set_retc(0);
writer->Write(StreamReply);
}
if (findRequest.count()) {
StreamReply.set_std_out(
"nfiles=" + std::to_string(filecounter) + " ndirectories=" + std::to_string(
dircounter) + "\n");
StreamReply.set_std_err("");
StreamReply.set_retc(0);
writer->Write(StreamReply);
}
if (findRequest.balance()) {
std::stringstream output;
balanceCalculator.printSummary(output);
StreamReply.set_std_out(output.str() + "\n");
StreamReply.set_std_err("");
StreamReply.set_retc(0);
writer->Write(StreamReply);
}
}
#endif
//------------------------------------------------------------------------------
// Get fileinfo about path in monitoring format for gRPC
//------------------------------------------------------------------------------
void
NewfindCmd::PrintFileInfoMinusM(std::stringstream& ss,
const std::string& path, XrdOucErrInfo& errInfo)
{
ProcCommand Cmd;
std::string output_stdout(""), output_stderr("");
std::string info = "&mgm.cmd=fileinfo&mgm.path=" + path +
"&mgm.file.info.option=-m";
Cmd.open("/proc/user", info.c_str(), mVid, &errInfo);
Cmd.AddOutput(output_stdout, output_stderr);
Cmd.close();
if (Cmd.GetRetc() == 0) {
ss << output_stdout;
} else {
ss << output_stderr;
}
}
//------------------------------------------------------------------------------
// Modify layout stripes for gRPC
//------------------------------------------------------------------------------
void
NewfindCmd::ModifyLayoutStripes(std::stringstream& ss,
const eos::console::FindProto& req,
const std::string& fspath)
{
XrdOucErrInfo errInfo;
ProcCommand fileCmd;
std::string info = "mgm.cmd=file&mgm.subcmd=layout&mgm.path=";
info += fspath;
info += "&mgm.file.layout.stripes=";
info += std::to_string(req.layoutstripes());
if (fileCmd.open("/proc/user", info.c_str(), mVid, &errInfo) == 0) {
std::ostringstream outputStream;
XrdSfsFileOffset offset = 0;
constexpr uint32_t size = 512;
auto bytesRead = 0ul;
char buffer[size];
do {
bytesRead = fileCmd.read(offset, buffer, size);
for (auto i = 0u; i < bytesRead; i++) {
outputStream << buffer[i];
}
offset += bytesRead;
} while (bytesRead == size);
fileCmd.close();
XrdOucEnv env(outputStream.str().c_str());
if (std::stoi(env.Get("mgm.proc.retc")) == 0) {
if (!req.silent()) {
ss << env.Get("mgm.proc.stdout");
}
} else {
ss << env.Get("mgm.proc.stderr");
}
}
}
EOSMGMNAMESPACE_END