// ----------------------------------------------------------------------
// File: proc/user/Ls.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 "mgm/proc/ProcInterface.hh"
#include "mgm/XrdMgmOfs.hh"
#include "mgm/XrdMgmOfsDirectory.hh"
#include "mgm/Access.hh"
#include "mgm/Macros.hh"
#include "mgm/Stat.hh"
#include "common/Glob.hh"
#include "common/Path.hh"
#include "common/StringUtils.hh"
#include "common/Timing.hh"
#include "common/LayoutId.hh"
#include "namespace/utils/Mode.hh"
EOSMGMNAMESPACE_BEGIN
int
ProcCommand::Ls()
{
struct result {
std::string out;
std::string err;
int retc;
std::string getCacheName(uint64_t ino, uint64_t mtime_sec, uint64_t mtime_nsec,
std::string options)
{
std::string cacheentry;
cacheentry = std::to_string(ino);
cacheentry += ":";
cacheentry += std::to_string(mtime_sec);
cacheentry += ".";
cacheentry += std::to_string(mtime_nsec);
if (options.length()) {
cacheentry += ":";
cacheentry += options;
}
return cacheentry;
}
};
static eos::common::LRU::Cache dirCache;
static bool use_cache = (getenv("EOS_MGM_LISTING_CACHE") &&
(dirCache.setMaxSize(atoi(getenv("EOS_MGM_LISTING_CACHE")))));
std::ostringstream oss;
gOFS->MgmStats.Add("Ls", pVid->uid, pVid->gid, 1);
XrdOucString spath = pOpaque->Get("mgm.path");
if (pOpaque->Get("eos.encodepath")) {
spath = eos::common::StringConversion::curl_unescaped(spath.c_str()).c_str();
}
if (spath.length() >= FILENAME_MAX) {
oss << "error: path length longer than " << FILENAME_MAX << " bytes";
eos_err("msg=\"%s\"", oss.str().c_str());
stdErr = oss.str().c_str();
retc = E2BIG;
return SFS_OK;
}
eos::common::Path cPath(spath.c_str());
// Check for globbing, we support a maximum depth of 255
if (cPath.GetSubPathSize() > eos::common::Path::MAX_LEVELS) {
oss << "error: path has more than " << eos::common::Path::MAX_LEVELS
<< " levels";
eos_err("msg=\"%s\"", oss.str().c_str());
stdErr = oss.str().c_str();
retc = E2BIG;
return SFS_OK;
}
const char* inpath = cPath.GetPath();
NAMESPACEMAP;
PROC_BOUNCE_ILLEGAL_NAMES;
PROC_BOUNCE_NOT_ALLOWED;
PROC_TOKEN_SCOPE;
eos_info("mapped to %s", path);
spath = path;
XrdOucString option = pOpaque->Get("mgm.option");
bool showbackendstatus = false;
bool longlisting = false;
if (!spath.length()) {
stdErr = "error: you have to give a path name to call 'ls'";
retc = EINVAL;
} else {
XrdMgmOfsDirectory dir;
struct stat buf;
int listrc = 0;
XrdOucString filter = "";
eos::common::Glob glob;
eos::common::Path cPath(spath.c_str());
if (cPath.Globbing()) {
// always use globbing
spath = cPath.GetParentPath();
filter = cPath.GetName();
}
XrdOucString ls_file;
std::string uri;
std::string cacheentry;
struct result cachedresult;
if (gOFS->_stat(spath.c_str(), &buf, *mError, *pVid, (const char*) 0, 0, true,
&uri)) {
stdErr = mError->getErrText();
retc = errno;
} else {
// put the resolved uri path
spath = uri.c_str();
cacheentry = cachedresult.getCacheName(buf.st_ino, buf.st_mtim.tv_sec,
buf.st_mtim.tv_nsec, std::string(option.length() ? option.c_str() : ""));
if (use_cache && dirCache.tryGet(cacheentry, cachedresult)) {
if (!gOFS->_access(spath.c_str(), R_OK | X_OK, *mError, *pVid, 0)) {
// return from cache
retc = cachedresult.retc;
stdOut = cachedresult.out.c_str();
stdErr = cachedresult.err.c_str();
// reinsert LRU
dirCache.insert(cacheentry, cachedresult);
return SFS_OK;
}
// fall through to report permission errors
}
// if this is a directory open it and list
if (S_ISDIR(buf.st_mode) && ((option.find("d")) == STR_NPOS)) {
listrc = dir.open(spath.c_str(), *pVid, (const char*) 0);
} else {
// if this is a file, open the parent and set the filter
if ((spath.length() > 1) && spath.endswith("/")) {
spath.erase(spath.length() - 1);
}
int rpos = spath.rfind("/");
if (rpos == STR_NPOS) {
listrc = SFS_ERROR;
retc = ENOENT;
} else {
// this is an 'ls ' command which has to return only one entry!
ls_file.assign(spath, rpos + 1);
spath.erase(rpos);
listrc = SFS_OK;
}
}
bool translateids = true;
if ((option.find("n")) != STR_NPOS) {
translateids = false;
}
if ((option.find("s")) != STR_NPOS) {
// just return '0' if this is a directory
return SFS_OK;
}
if ((option.find("y")) != STR_NPOS) {
showbackendstatus = true;
option += "l";
}
if ((option.find("l") != STR_NPOS)) {
longlisting = true;
}
if (!listrc) {
const char* val;
while ((ls_file.length() && (val = ls_file.c_str())) ||
(val = dir.nextEntry())) {
// this return's a single file or a (filtered) directory list
XrdOucString entryname = val;
if (((option.find("a")) == STR_NPOS) && entryname.beginswith(".")) {
// quit if we list a hidden file without 'a' flag
if (ls_file.length()) {
break;
}
// skip over . .. and hidden files
continue;
}
if ((filter.length()) && !glob.Match(filter.c_str(), entryname.c_str())) {
// apply filter & skip single file/directories
if (ls_file.length()) {
break;
}
continue;
}
if ((((option.find("l")) == STR_NPOS)) && ((option.find("F")) == STR_NPOS)) {
stdOut += val;
stdOut += "\n";
} else {
std::string backendstatus;
// return full information
XrdOucString statpath = spath;
statpath += "/";
statpath += val;
while (statpath.replace("//", "/")) {
}
struct stat buf;
std::string cks;
if (gOFS->_stat(statpath.c_str(), &buf, *mError, *pVid, (const char*) 0, 0,
false, 0, &cks)) {
if (errno != ENOENT) {
stdErr += "error: unable to stat path ";
stdErr += statpath;
stdErr += "\n";
retc = errno;
}
} else {
// TODO: convert virtual IDs back
XrdOucString suid = "";
suid += (int) buf.st_uid;
XrdOucString sgid = "";
sgid += (int) buf.st_gid;
XrdOucString sizestring = "";
struct tm* t_tm;
struct tm t_tm_local;
t_tm = localtime_r(&buf.st_mtime, &t_tm_local);
char modestr[11];
eos::modeToBuffer(buf.st_mode, modestr);
if (showbackendstatus) {
std::string rsymbol = eos::common::LayoutId::GetRedundancySymbol(
buf.st_mode & EOS_TAPE_MODE_T, buf.st_nlink);
char sbsts[256];
snprintf(sbsts, sizeof(sbsts), "%-9s", rsymbol.c_str());
backendstatus = sbsts;
}
if (translateids) {
{
// try to translate with password database
int terrc = 0;
std::string username = "";
username = eos::common::Mapping::UidToUserName(buf.st_uid, terrc);
if (!terrc) {
char uidlimit[16];
snprintf(uidlimit, 12, "%s", username.c_str());
suid = uidlimit;
}
}
{
// try to translate with password database
std::string groupname = "";
int terrc = 0;
groupname = eos::common::Mapping::GidToGroupName(buf.st_gid, terrc);
if (!terrc) {
char gidlimit[16];
snprintf(gidlimit, 12, "%s", groupname.c_str());
sgid = gidlimit;
}
}
}
std::string t_creat = eos::common::Timing::ToLsFormat(t_tm);
char lsline[4096];
XrdOucString dirmarker = "";
if ((option.find("F")) != STR_NPOS) {
dirmarker = "/";
}
if (modestr[0] != 'd') {
dirmarker = "";
}
if ((option.find("i")) != STR_NPOS) {
// add inode information
char sinode[16];
bool isfile = (modestr[0] != 'd');
snprintf(sinode, 16, "%llu",
(unsigned long long)(isfile ? eos::common::FileId::InodeToFid(
buf.st_ino) : buf.st_ino));
sprintf(lsline, "%-16s", sinode);
stdOut += lsline;
}
if ((option.find("c")) != STR_NPOS) {
// add checksum information
char checksum[36];
sprintf(checksum, "%-34s", cks.c_str());
stdOut += checksum;
}
if ((option.find("h")) == STR_NPOS)
sprintf(lsline, "%s%s %3d %-8.8s %-8.8s %12s %s %s%s", backendstatus.c_str(),
modestr,
(int) buf.st_nlink,
suid.c_str(), sgid.c_str(),
eos::common::StringConversion::GetSizeString(sizestring,
(unsigned long long) buf.st_size),
t_creat.c_str(), val, dirmarker.c_str());
else
sprintf(lsline, "%s%s %3d %-8.8s %-8.8s %12s %s %s%s", backendstatus.c_str(),
modestr,
(int) buf.st_nlink,
suid.c_str(), sgid.c_str(),
eos::common::StringConversion::GetReadableSizeString(sizestring,
(unsigned long long) buf.st_size, ""),
t_creat.c_str(), val, dirmarker.c_str());
if ((option.find("l")) != STR_NPOS) {
stdOut += lsline;
if (S_ISLNK(buf.st_mode)) {
stdOut += " -> ";
XrdOucString link;
if (!gOFS->_readlink(statpath.c_str(), *mError, *pVid, link)) {
stdOut += link.c_str();
} else {
stdOut += "( error )\n";
}
}
stdOut += "\n";
} else {
stdOut += val;
stdOut += dirmarker;
stdOut += "\n";
}
}
}
if (stdOut.length() > 1 * 1024 * 1024 * 1024) {
stdOut += "... (truncated after 1G of output)\n";
retc = E2BIG;
stdErr += "warning: list too long - truncated after 1GB of output!\n";
break;
}
if (ls_file.length()) {
// this was a single file to be listed
break;
}
}
if (!ls_file.length()) {
dir.close();
}
} else {
stdErr += "error: unable to open directory";
if (retc == 0) {
retc = errno;
}
}
}
if (!retc && !showbackendstatus && !longlisting) {
// we cannot cache listing where people ask for dynamicinformation of children like folder size, Y option ...
cachedresult.retc = retc;
cachedresult.out = stdOut.c_str();
cachedresult.err = stdErr.c_str();
if (use_cache) {
dirCache.insert(cacheentry, cachedresult);
}
}
}
return SFS_OK;
}
EOSMGMNAMESPACE_END