// ----------------------------------------------------------------------
// File: XrdMgmOfsDirectory.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/XrdMgmOfsDirectory.hh"
#include "mgm/Stat.hh"
#include "mgm/XrdMgmOfsTrace.hh"
#include "mgm/XrdMgmOfsSecurity.hh"
#include "mgm/Macros.hh"
#include "mgm/Access.hh"
#include "mgm/Acl.hh"
#include "common/Path.hh"
#include "common/Strerror_r_wrapper.hh"
#include "namespace/interface/IContainerMD.hh"
#include "namespace/interface/IView.hh"
#include "namespace/Prefetcher.hh"
#include "namespace/interface/ContainerIterators.hh"
#include "XrdOuc/XrdOucEnv.hh"
#ifdef __APPLE__
#define ECOMM 70
#endif
#ifndef S_IAMB
#define S_IAMB 0x1FF
#endif
eos::common::LRU::Cache>
XrdMgmOfsDirectory::dirCache(1024, 0);
//------------------------------------------------------------------------------
//! MGM Directory Interface
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
XrdMgmOfsDirectory::XrdMgmOfsDirectory(char* user, int MonID):
XrdSfsDirectory(user, MonID)
{
dirName = "";
vid = eos::common::VirtualIdentity::Nobody();
eos::common::LogId();
}
//------------------------------------------------------------------------------
// Construct a key name to cache a listing entry
//------------------------------------------------------------------------------
std::string
XrdMgmOfsDirectory::getCacheName(uint64_t id, uint64_t mtime_sec,
uint64_t mtime_nsec, bool nofiles, bool nodirs)
{
std::string cacheentry = std::to_string(id);
cacheentry += ":";
cacheentry += std::to_string(mtime_sec);
cacheentry += ".";
cacheentry += std::to_string(mtime_nsec);
if (nofiles) {
cacheentry += "!f";
}
if (nodirs) {
cacheentry += "!d";
}
return cacheentry;
}
//------------------------------------------------------------------------------
// Open a directory object with bouncing/mapping & namespace mapping
//------------------------------------------------------------------------------
int
XrdMgmOfsDirectory::open(const char* inpath,
const XrdSecEntity* client,
const char* ininfo)
{
static const char* epname = "opendir";
const char* tident = error.getErrUser();
NAMESPACEMAP;
BOUNCE_ILLEGAL_NAMES;
XrdOucEnv Open_Env(ininfo);
AUTHORIZE(client, &Open_Env, AOP_Readdir, "open directory", inpath, error);
EXEC_TIMING_BEGIN("IdMap");
eos::common::Mapping::IdMap(client, ininfo, tident, vid);
EXEC_TIMING_END("IdMap");
gOFS->MgmStats.Add("IdMap", vid.uid, vid.gid, 1);
BOUNCE_NOT_ALLOWED;
ACCESSMODE_R;
MAYSTALL;
MAYREDIRECT;
return _open(path, vid, ininfo);
}
//------------------------------------------------------------------------------
// Open a directory by vid
//------------------------------------------------------------------------------
int
XrdMgmOfsDirectory::open(const char* inpath,
eos::common::VirtualIdentity& vid,
const char* ininfo)
{
static const char* epname = "opendir";
NAMESPACEMAP;
BOUNCE_ILLEGAL_NAMES;
XrdOucEnv Open_Env(ininfo);
BOUNCE_NOT_ALLOWED;
ACCESSMODE_R;
MAYSTALL;
MAYREDIRECT;
// we have to show this as a directory inside the tokens cope
if (!vid.scope.empty() && vid.scope.back() != '/') {
vid.scope += "/";
}
return _open(path, vid, ininfo);
}
//------------------------------------------------------------------------------
// Open a directory - low-level interface
//------------------------------------------------------------------------------
int
XrdMgmOfsDirectory::_open(const char* dir_path,
eos::common::VirtualIdentity& vid,
const char* info)
{
static const char* epname = "opendir";
static bool use_cache = (getenv("EOS_MGM_LISTING_CACHE") &&
(dirCache.setMaxSize(atoi(getenv("EOS_MGM_LISTING_CACHE")))));
XrdOucEnv Open_Env(info);
errno = 0;
EXEC_TIMING_BEGIN("OpenDir");
eos::common::Path cPath(dir_path);
// Skip printout when listing the /eos/MgmStats.Add("OpenDir", vid.uid, vid.gid, 1);
XrdOucEnv env(info);
// Open the directory
bool permok = false;
eos::Prefetcher::prefetchContainerMDWithChildrenAndWait(gOFS->eosView,
cPath.GetPath());
//----------------------------------------------------------------------------
std::shared_ptr dh;
eos::common::RWMutexReadLock lock(gOFS->eosViewRWMutex);
std::string cacheentry;
try {
eos::IContainerMD::XAttrMap attrmap;
dh = gOFS->eosView->getContainer(cPath.GetPath());
eos::IFileMD::ctime_t mtime;
dh->getMTime(mtime);
cacheentry = getCacheName(dh->getId(), mtime.tv_sec, mtime.tv_nsec,
env.Get("ls.skip.files"), env.Get("ls.skip.directories"));
lock.Release();
permok = dh->access(vid.uid, vid.gid, R_OK | X_OK);
eos::common::VirtualIdentity rootvid = eos::common::VirtualIdentity::Root();
// ACL and permission check
Acl acl(cPath.GetPath(), error, vid, attrmap, false);
eos_debug("acl=%d r=%d w=%d wo=%d x=%d egroup=%d", acl.HasAcl(),
acl.CanRead(), acl.CanWrite(), acl.CanWriteOnce(),
acl.CanBrowse(), acl.HasEgroup());
// Browse permission by ACL
if (acl.HasAcl()) {
// If there is an allow reset permissions regardless of mod permissions
// If there is a deny from ACLs then deny the browse!
if (acl.CanBrowse()) {
permok = true;
} else if (acl.CanNotBrowse()) {
permok = false;
}
}
if (permok) {
// Add all the files and subdirectories
gOFS->MgmStats.Add("OpenDir-Entry", vid.uid, vid.gid,
dh->getNumContainers() + dh->getNumFiles());
std::unique_lock scope_lock(mDirLsMutex);
// try to get the listing from the cache
if (!use_cache || !dirCache.tryGet(cacheentry, dh_list)) {
dh_list = std::make_shared();
if (!env.Get("ls.skip.files")) {
// Collect all file names
for (auto it = eos::FileMapIterator(dh); it.valid(); it.next()) {
dh_list->insert(it.key());
}
}
if (!env.Get("ls.skip.directories")) {
// Collect all subcontainers
for (auto it = eos::ContainerMapIterator(dh); it.valid(); it.next()) {
dh_list->insert(it.key());
}
dh_list->insert(".");
// The root dir has no .. entry
if (strcmp(dir_path, "/")) {
dh_list->insert("..");
}
}
}
dh_it = dh_list->begin();
if (use_cache) {
dirCache.insert(cacheentry, dh_list); // cache listing
}
}
} catch (eos::MDException& e) {
dh.reset();
errno = e.getErrno();
eos_debug("msg=\"exception\" ec=%d emsg=\"%s\"\n",
e.getErrno(), e.getMessage().str().c_str());
}
// Verify that this object is not already associated with an open directory
if (dh == nullptr) {
return Emsg(epname, error, errno, "open directory", cPath.GetPath());
}
eos_debug("msg=\"access\" uid=%d gid=%d retc=%d mode=%o",
vid.uid, vid.gid, (dh->access(vid.uid, vid.gid, R_OK | X_OK)),
dh->getMode());
if (!permok) {
errno = EPERM;
return Emsg(epname, error, errno,
"open directory", cPath.GetPath());
}
if (!gOFS->allow_public_access(cPath.GetPath(), vid)) {
errno = EACCES;
return Emsg(epname, error, EACCES, "access - public access level restriction",
cPath.GetPath());
}
dirName = dir_path;
EXEC_TIMING_END("OpenDir");
return SFS_OK;
}
//------------------------------------------------------------------------------
// Red the next directory entry
//------------------------------------------------------------------------------
const char*
XrdMgmOfsDirectory::nextEntry()
{
std::unique_lock scope_lock(mDirLsMutex);
if ((!dh_list) ||
(dh_list->empty()) ||
(dh_it == dh_list->end())) {
// No more entries
return (const char*) 0;
}
const char* name = dh_it->c_str();
++dh_it;
return name;
}
//------------------------------------------------------------------------------
// Close a directory object
//------------------------------------------------------------------------------
int
XrdMgmOfsDirectory::close()
{
std::unique_lock scope_lock(mDirLsMutex);
dh_list = nullptr;
return SFS_OK;
}
/*----------------------------------------------------------------------------*/
int
XrdMgmOfsDirectory::Emsg(const char* pfx,
XrdOucErrInfo& einfo,
int ecode,
const char* op,
const char* target)
/*----------------------------------------------------------------------------*/
/*
* @brief create an error message for a directory object
*
* @param pfx message prefix value
* @param einfo error text/code object
* @param ecode error code
* @param op name of the operation performed
* @param target target of the operation e.g. file name etc.
*
* @return SFS_ERROR in all cases
*
* This routines prints also an error message into the EOS log.
*/
/*----------------------------------------------------------------------------*/
{
char etext[128], buffer[4096];
if (ecode < 0) {
ecode = -ecode;
}
if (eos::common::strerror_r(ecode, etext, sizeof(etext))) {
snprintf(etext, sizeof(etext), "reason unknown (%d)", ecode);
}
// Format the error message
snprintf(buffer, sizeof(buffer), "Unable to %s %s; %s", op, target, etext);
if (ecode == ENOENT) {
eos_debug("Unable to %s %s; %s", op, target, etext);
} else {
eos_err("Unable to %s %s; %s", op, target, etext);
}
// Place the error message in the error object and return
einfo.setErrInfo(ecode, buffer);
return SFS_ERROR;
}