// ---------------------------------------------------------------------- // File: GrpcNsInterface.cc // Author: Andreas-Joachim Peters - CERN // ---------------------------------------------------------------------- /************************************************************************ * EOS - the CERN Disk Storage System * * Copyright (C) 2018 CERN/Switzerland * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see .* ************************************************************************/ /*----------------------------------------------------------------------------*/ #include "GrpcNsInterface.hh" /*----------------------------------------------------------------------------*/ #include "common/LayoutId.hh" #include "common/SymKeys.hh" #include "common/Timing.hh" #include "common/Path.hh" #include "common/LinuxFds.hh" #include "common/LinuxStat.hh" #include "common/LinuxMemConsumption.hh" #include "mgm/Acl.hh" #include "mgm/proc/IProcCommand.hh" #include "mgm/proc/user/AclCmd.hh" #include "mgm/proc/admin/QuotaCmd.hh" #include "mgm/proc/user/RmCmd.hh" #include "mgm/proc/user/TokenCmd.hh" #include "mgm/XrdMgmOfs.hh" #include "mgm/XrdMgmOfsDirectory.hh" #include "mgm/Recycle.hh" #include "namespace/Prefetcher.hh" #include "namespace/MDException.hh" #include "namespace/interface/ContainerIterators.hh" #include "namespace/utils/Etag.hh" #include "namespace/utils/Attributes.hh" #include /*----------------------------------------------------------------------------*/ EOSMGMNAMESPACE_BEGIN #ifdef EOS_GRPC bool GrpcNsInterface::Filter(std::shared_ptr md, const eos::rpc::MDSelection& filter) { errno = 0; // see if filtering is enabled if (!filter.select()) { return false; } eos::IFileMD::ctime_t ctime; eos::IFileMD::ctime_t mtime; md->getCTime(ctime); md->getMTime(mtime); // empty file if (filter.size().zero()) { if (!md->getSize()) { // accepted } else { return true; } } else { if ((filter.size().min() <= md->getSize()) && ((md->getSize() <= filter.size().max()) || (!filter.size().max()))) { // accepted } else { return true; } } if (filter.ctime().zero()) { if (!ctime.tv_sec && !ctime.tv_nsec) { // accepted } else { return true; } } else { if ((filter.ctime().min() <= (uint64_t)(ctime.tv_sec)) && ((filter.ctime().max() >= (uint64_t)(ctime.tv_sec)) || (!filter.ctime().max()))) { // accepted } else { return true; } } if (filter.mtime().zero()) { if (!mtime.tv_sec && !mtime.tv_nsec) { // accepted } else { return true; } } else { if ((filter.mtime().min() <= (uint64_t)(mtime.tv_sec)) && ((filter.mtime().max() >= (uint64_t)(mtime.tv_sec)) || (!filter.mtime().max()))) { // accepted } else { return true; } } if (filter.mtime().zero()) { if (!mtime.tv_sec && !mtime.tv_nsec) { // accepted } else { return true; } } else { if ((filter.mtime().min() <= (uint64_t)(mtime.tv_sec)) && ((filter.mtime().max() >= (uint64_t)(mtime.tv_sec)) || (!filter.mtime().max()))) { // accepted } else { return true; } } if (filter.locations().zero()) { if (!mtime.tv_sec && !mtime.tv_nsec) { // accepted } else { return true; } } else { if ((filter.locations().min() <= (uint64_t)(mtime.tv_sec)) && ((filter.locations().max() >= (uint64_t)(mtime.tv_sec)) || (!filter.locations().max()))) { // accepted } else { return true; } } if (filter.owner_root()) { if (!md->getCUid()) { // accepted } else { return true; } } else { if (filter.owner()) { if (filter.owner() == md->getCUid()) { // accepted } else { return true; } } } if (filter.group_root()) { if (!md->getCGid()) { // accepted } else { return true; } } else { if (filter.group()) { if (filter.group() == md->getCGid()) { // accepted } else { return true; } } } if (filter.layoutid()) { if (md->getLayoutId() != filter.layoutid()) { return true; } } if (filter.flags()) { if (md->getFlags() != filter.flags()) { return true; } } if (filter.symlink()) { if (!md->isLink()) { return true; } } if (filter.checksum().type().length()) { if (filter.checksum().type() != eos::common::LayoutId::GetChecksumStringReal( md->getLayoutId())) { return true; } } if (filter.checksum().value().length()) { std::string cks(md->getChecksum().getDataPtr(), md->getChecksum().size()); if (filter.checksum().value() != cks) { return true; } } eos::IFileMD::XAttrMap xattr = md->getAttributes(); for (const auto& elem : filter.xattr()) { if (xattr.count(elem.first)) { if (elem.second.length()) { if (xattr[elem.first] == elem.second) { // accepted } else { return true; } } else { // accepted } } else { return true; } } if (filter.regexp_filename().length()) { int regexErrorCode; int result; regex_t regex; std::string regexString = filter.regexp_filename(); regexErrorCode = regcomp(®ex, regexString.c_str(), REG_EXTENDED); if (regexErrorCode) { regfree(®ex); errno = EINVAL; return true; } result = regexec(®ex, md->getName().c_str(), 0, NULL, 0); regfree(®ex); if (result == 0) { // accepted } else if (result == REG_NOMATCH) { return true; } else { errno = ENOMEM; return true; } } return false;; } bool GrpcNsInterface::Filter(std::shared_ptr md, const eos::rpc::MDSelection& filter) { errno = 0; // see if filtering is enabled if (!filter.select()) { return false; } eos::IContainerMD::ctime_t ctime; eos::IContainerMD::ctime_t mtime; eos::IContainerMD::ctime_t stime; md->getCTime(ctime); md->getMTime(mtime); md->getTMTime(stime); size_t nchildren = md->getNumContainers() + md->getNumFiles(); uint64_t treesize = md->getTreeSize(); // empty file if (filter.children().zero()) { if (!nchildren) { // accepted } else { return true; } } else { if ((filter.children().min() <= nchildren) && ((nchildren <= filter.children().max()) || (!filter.children().max()))) { // accepted } else { return true; } } if (filter.treesize().zero()) { if (!treesize) { // accepted } else { return true; } } else { if ((filter.treesize().min() <= treesize) && ((treesize <= filter.treesize().max()) || (!filter.treesize().max()))) { // accepted } else { return true; } } if (filter.ctime().zero()) { if (!ctime.tv_sec && !ctime.tv_nsec) { // accepted } else { return true; } } else { if ((filter.ctime().min() <= (uint64_t)(ctime.tv_sec)) && ((filter.ctime().max() >= (uint64_t)(ctime.tv_sec)) || (!filter.ctime().max()))) { // accepted } else { return true; } } if (filter.mtime().zero()) { if (!mtime.tv_sec && !mtime.tv_nsec) { // accepted } else { return true; } } else { if ((filter.mtime().min() <= (uint64_t)(mtime.tv_sec)) && ((filter.mtime().max() >= (uint64_t)(mtime.tv_sec)) || (!filter.mtime().max()))) { // accepted } else { return true; } } if (filter.stime().zero()) { if (!stime.tv_sec && !stime.tv_nsec) { // accepted } else { return true; } } else { if ((filter.stime().min() <= (uint64_t)(stime.tv_sec)) && ((filter.stime().max() >= (uint64_t)(stime.tv_sec)) || (!filter.stime().max()))) { // accepted } else { return true; } } if (filter.owner_root()) { if (!md->getCUid()) { // accepted } else { return true; } } else { if (filter.owner()) { if (filter.owner() == md->getCUid()) { // accepted } else { return true; } } } if (filter.group_root()) { if (!md->getCGid()) { // accepted } else { return true; } } else { if (filter.group()) { if (filter.group() == md->getCGid()) { // accepted } else { return true; } } } if (filter.flags()) { if (md->getFlags() != filter.flags()) { return true; } } eos::IContainerMD::XAttrMap xattr = md->getAttributes(); for (const auto& elem : filter.xattr()) { if (xattr.count(elem.first)) { if (elem.second.length()) { if (xattr[elem.first] == elem.second) { // accepted } else { return true; } } else { // accepted } } else { return true; } } if (filter.regexp_dirname().length()) { int regexErrorCode; int result; regex_t regex; std::string regexString = filter.regexp_dirname(); regexErrorCode = regcomp(®ex, regexString.c_str(), REG_EXTENDED); if (regexErrorCode) { regfree(®ex); errno = EINVAL; return true; } result = regexec(®ex, md->getName().c_str(), 0, NULL, 0); regfree(®ex); if (result == 0) { // accepted } else if (result == REG_NOMATCH) { return true; } else { errno = ENOMEM; return true; } } return false; } grpc::Status GrpcNsInterface::GetMD(eos::common::VirtualIdentity& vid, grpc::ServerWriter* writer, const eos::rpc::MDRequest* request, bool check_perms, bool lock, bool access_self) { eos::common::RWMutexReadLock viewReadLock; if ((request->type() == eos::rpc::FILE) || (request->type() == eos::rpc::STAT)) { // stream file meta data eos::common::RWMutexReadLock viewReadLock; std::shared_ptr fmd; std::shared_ptr pmd; eos::IFileMD::XAttrMap attrmapF; unsigned long fid = 0; uint64_t clock = 0; std::string path; bool fallthrough = false; if (request->id().ino()) { // get by inode fid = eos::common::FileId::InodeToFid(request->id().ino()); } else if (request->id().id()) { // get by fileid fid = request->id().id(); } if (fid) { eos::Prefetcher::prefetchFileMDAndWait(gOFS->eosView, fid); } else { eos::Prefetcher::prefetchFileMDAndWait(gOFS->eosView, request->id().path()); } if (lock) { viewReadLock.Grab(gOFS->eosViewRWMutex); } if (fid) { try { fmd = gOFS->eosFileService->getFileMD(fid, &clock); path = gOFS->eosView->getUri(fmd.get()); eos::listAttributes(gOFS->eosView, fmd.get(), attrmapF, false); if (check_perms) { pmd = gOFS->eosDirectoryService->getContainerMD(fmd->getContainerId()); } } catch (eos::MDException& e) { errno = e.getErrno(); eos_static_debug("caught exception %d %s\n", e.getErrno(), e.getMessage().str().c_str()); if ((request->type() != eos::rpc::STAT)) { return grpc::Status((grpc::StatusCode)(errno), e.getMessage().str().c_str()); } else { fallthrough = true; } } } else { try { fmd = gOFS->eosView->getFile(request->id().path()); path = gOFS->eosView->getUri(fmd.get()); eos::listAttributes(gOFS->eosView, fmd.get(), attrmapF, false); if (check_perms) { pmd = gOFS->eosDirectoryService->getContainerMD(fmd->getContainerId()); } } catch (eos::MDException& e) { errno = e.getErrno(); eos_static_debug("caught exception %d %s\n", e.getErrno(), e.getMessage().str().c_str()); if ((request->type() != eos::rpc::STAT)) { return grpc::Status((grpc::StatusCode)(errno), e.getMessage().str().c_str()); } else { fallthrough = true; } } } if (!fallthrough) { if (check_perms && !Access(vid, X_OK, pmd, &attrmapF)) { return grpc::Status(grpc::StatusCode::PERMISSION_DENIED, "access to parent container denied"); } if (Filter(fmd, request->selection())) { // short-cut for filtered MD return grpc::Status::OK; } // create GRPC protobuf object eos::rpc::MDResponse gRPCResponse; gRPCResponse.set_type(eos::rpc::FILE); eos::rpc::FileMdProto gRPCFmd; gRPCResponse.mutable_fmd()->set_name(fmd->getName()); gRPCResponse.mutable_fmd()->set_id(fmd->getId()); gRPCResponse.mutable_fmd()->set_inode(eos::common::FileId::FidToInode( fmd->getId())); gRPCResponse.mutable_fmd()->set_cont_id(fmd->getContainerId()); gRPCResponse.mutable_fmd()->set_uid(fmd->getCUid()); gRPCResponse.mutable_fmd()->set_gid(fmd->getCGid()); gRPCResponse.mutable_fmd()->set_size(fmd->getSize()); gRPCResponse.mutable_fmd()->set_layout_id(fmd->getLayoutId()); gRPCResponse.mutable_fmd()->set_flags(fmd->getFlags()); gRPCResponse.mutable_fmd()->set_link_name(fmd->getLink()); eos::IFileMD::ctime_t ctime; eos::IFileMD::ctime_t mtime; fmd->getCTime(ctime); fmd->getMTime(mtime); gRPCResponse.mutable_fmd()->mutable_ctime()->set_sec(ctime.tv_sec); gRPCResponse.mutable_fmd()->mutable_ctime()->set_n_sec(ctime.tv_nsec); gRPCResponse.mutable_fmd()->mutable_mtime()->set_sec(mtime.tv_sec); gRPCResponse.mutable_fmd()->mutable_mtime()->set_n_sec(mtime.tv_nsec); gRPCResponse.mutable_fmd()->mutable_checksum()->set_value( fmd->getChecksum().getDataPtr(), fmd->getChecksum().size()); gRPCResponse.mutable_fmd()->mutable_checksum()->set_type( eos::common::LayoutId::GetChecksumStringReal(fmd->getLayoutId())); for (const auto& loca : fmd->getLocations()) { gRPCResponse.mutable_fmd()->add_locations(loca); } for (const auto& loca : fmd->getUnlinkedLocations()) { gRPCResponse.mutable_fmd()->add_unlink_locations(loca); } for (const auto& elem : fmd->getAttributes()) { (*gRPCResponse.mutable_fmd()->mutable_xattrs())[elem.first] = elem.second; } std::string etag; eos::calculateEtag(fmd.get(), etag); if (fmd->hasAttribute("sys.eos.mdino")) { etag = "hardlink"; } gRPCResponse.mutable_fmd()->set_etag(etag); gRPCResponse.mutable_fmd()->set_path(path); writer->Write(gRPCResponse); return grpc::Status::OK; } } if ((request->type() == eos::rpc::CONTAINER) || (request->type() == eos::rpc::STAT)) { // stream container meta data std::shared_ptr cmd; std::shared_ptr pmd; unsigned long cid = 0; uint64_t clock = 0; std::string path; if (request->id().ino()) { // get by inode cid = request->id().ino(); } else if (request->id().id()) { // get by containerid cid = request->id().id(); } if (!cid) { eos::Prefetcher::prefetchContainerMDAndWait(gOFS->eosView, request->id().path()); } else { eos::Prefetcher::prefetchContainerMDAndWait(gOFS->eosView, cid); } if (lock) { viewReadLock.Grab(gOFS->eosViewRWMutex); } if (cid) { try { cmd = gOFS->eosDirectoryService->getContainerMD(cid, &clock); path = gOFS->eosView->getUri(cmd.get()); pmd = gOFS->eosDirectoryService->getContainerMD(cmd->getParentId()); } catch (eos::MDException& e) { errno = e.getErrno(); eos_static_debug("caught exception %d %s\n", e.getErrno(), e.getMessage().str().c_str()); return grpc::Status((grpc::StatusCode)(errno), e.getMessage().str().c_str()); } } else { try { cmd = gOFS->eosView->getContainer(request->id().path()); path = gOFS->eosView->getUri(cmd.get()); pmd = gOFS->eosDirectoryService->getContainerMD(cmd->getParentId()); } catch (eos::MDException& e) { errno = e.getErrno(); eos_static_debug("caught exception %d %s\n", e.getErrno(), e.getMessage().str().c_str()); return grpc::Status((grpc::StatusCode)(errno), e.getMessage().str().c_str()); } } if (request->type() == eos::rpc::STAT) { // if a client sends a eos::rpc::STAT request, we check permissions on a folder itself and never on the parent (to enabe stat'ing CERNBOX shares) access_self = true; } if (access_self) { if (!Access(vid, X_OK, cmd)) { return grpc::Status(grpc::StatusCode::PERMISSION_DENIED, "access to container denied"); } } else { if (!Access(vid, X_OK, pmd)) { return grpc::Status(grpc::StatusCode::PERMISSION_DENIED, "access to parent container denied"); } } if (Filter(cmd, request->selection())) { // short-cut for filtered MD return grpc::Status::OK; } // create GRPC protobuf object eos::rpc::MDResponse gRPCResponse; gRPCResponse.set_type(eos::rpc::CONTAINER); eos::rpc::ContainerMdProto gRPCFmd; gRPCResponse.mutable_cmd()->set_name(cmd->getName()); gRPCResponse.mutable_cmd()->set_id(cmd->getId()); gRPCResponse.mutable_cmd()->set_inode(cmd->getId()); gRPCResponse.mutable_cmd()->set_parent_id(cmd->getParentId()); gRPCResponse.mutable_cmd()->set_uid(cmd->getCUid()); gRPCResponse.mutable_cmd()->set_gid(cmd->getCGid()); gRPCResponse.mutable_cmd()->set_tree_size(cmd->getTreeSize()); gRPCResponse.mutable_cmd()->set_flags(cmd->getFlags()); gRPCResponse.mutable_cmd()->set_mode(cmd->getMode()); eos::IContainerMD::ctime_t ctime; eos::IContainerMD::ctime_t mtime; eos::IContainerMD::ctime_t stime; cmd->getCTime(ctime); cmd->getMTime(mtime); cmd->getTMTime(stime); gRPCResponse.mutable_cmd()->mutable_ctime()->set_sec(ctime.tv_sec); gRPCResponse.mutable_cmd()->mutable_ctime()->set_n_sec(ctime.tv_nsec); gRPCResponse.mutable_cmd()->mutable_mtime()->set_sec(mtime.tv_sec); gRPCResponse.mutable_cmd()->mutable_mtime()->set_n_sec(mtime.tv_nsec); gRPCResponse.mutable_cmd()->mutable_stime()->set_sec(stime.tv_sec); gRPCResponse.mutable_cmd()->mutable_stime()->set_n_sec(stime.tv_nsec); std::string etag; eos::calculateEtag(cmd.get(), etag); gRPCResponse.mutable_cmd()->set_etag(etag); for (const auto& elem : cmd->getAttributes()) { (*gRPCResponse.mutable_cmd()->mutable_xattrs())[elem.first] = elem.second; } gRPCResponse.mutable_cmd()->set_path(path); writer->Write(gRPCResponse); return grpc::Status::OK; } return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, "invalid argument"); } grpc::Status GrpcNsInterface::Stat(eos::common::VirtualIdentity& ivid, grpc::ServerWriter* writer, const eos::rpc::MDRequest* request) { eos::common::VirtualIdentity vid = ivid; if ((ivid.uid != request->role().uid()) || (ivid.gid != request->role().gid())) { if (!ivid.sudoer) { return grpc::Status(grpc::StatusCode::PERMISSION_DENIED, std::string("Ask an admin to map your auth key to a sudo'er account - permission denied")); } else { vid = eos::common::Mapping::Someone(request->role().uid(), request->role().gid()); } } return GetMD(vid, writer, request); } grpc::Status GrpcNsInterface::StreamMD(eos::common::VirtualIdentity& ivid, grpc::ServerWriter* writer, const eos::rpc::MDRequest* request, bool streamparent, std::vector* childdirs) { eos::common::VirtualIdentity vid = ivid; if (request->role().uid() || request->role().gid()) { if ((ivid.uid != request->role().uid()) || (ivid.gid != request->role().gid())) { if (!ivid.sudoer) { return grpc::Status(grpc::StatusCode::PERMISSION_DENIED, std::string("Ask an admin to map your auth key to a sudo'er account - permission denied")); } else { vid = eos::common::Mapping::Someone(request->role().uid(), request->role().gid()); } } } else { // we don't implement sudo to root } // stream container meta data eos::common::RWMutexReadLock viewReadLock; std::shared_ptr cmd; unsigned long cid = 0; uint64_t clock = 0; std::string path; if (request->id().ino()) { // get by inode cid = request->id().ino(); } else if (request->id().id()) { // get by containerid cid = request->id().id(); } if (!cid) { eos::Prefetcher::prefetchContainerMDWithChildrenAndWait(gOFS->eosView, request->id().path()); } else { eos::Prefetcher::prefetchContainerMDWithChildrenAndWait(gOFS->eosView, cid); } viewReadLock.Grab(gOFS->eosViewRWMutex); if (cid) { try { cmd = gOFS->eosDirectoryService->getContainerMD(cid, &clock); path = gOFS->eosView->getUri(cmd.get()); } catch (eos::MDException& e) { errno = e.getErrno(); eos_static_debug("caught exception %d %s\n", e.getErrno(), e.getMessage().str().c_str()); return grpc::Status((grpc::StatusCode)(errno), e.getMessage().str().c_str()); } } else { try { cmd = gOFS->eosView->getContainer(request->id().path()); cid = cmd->getId(); path = gOFS->eosView->getUri(cmd.get()); } catch (eos::MDException& e) { errno = e.getErrno(); eos_static_debug("caught exception %d %s\n", e.getErrno(), e.getMessage().str().c_str()); return grpc::Status((grpc::StatusCode)(errno), e.getMessage().str().c_str()); } } // check if we can read this directory if (!Access(vid, R_OK, cmd)) { return grpc::Status(grpc::StatusCode::PERMISSION_DENIED, std::string("access to read directory denied")); } grpc::Status status; if (streamparent && (request->type() != eos::rpc::FILE)) { // stream the requested container eos::rpc::MDRequest c_dir; c_dir.mutable_selection()->CopyFrom(request->selection()); c_dir.mutable_id()->set_id(cid); c_dir.set_type(eos::rpc::CONTAINER); status = GetMD(vid, writer, &c_dir, true, false, true); if (!status.ok()) { return status; } } bool first = true; auto itf = eos::FileMapIterator(cmd); auto itc = eos::ContainerMapIterator(cmd); viewReadLock.Release(); // stream for listing and file type if (request->type() != eos::rpc::CONTAINER) { // stream all the children files for (; itf.valid(); itf.next()) { eos::rpc::MDRequest c_file; c_file.mutable_selection()->CopyFrom(request->selection()); c_file.mutable_id()->set_id(itf.value()); c_file.set_type(eos::rpc::FILE); status = GetMD(vid, writer, &c_file, first); if (!status.ok()) { return status; } first = false; } } // stream all the children container for (; itc.valid(); itc.next()) { if (request->type() != eos::rpc::FILE) { // stream for listing and container type eos::rpc::MDRequest c_dir; c_dir.mutable_id()->set_id(itc.value()); c_dir.mutable_selection()->CopyFrom(request->selection()); c_dir.set_type(eos::rpc::CONTAINER); status = GetMD(vid, writer, &c_dir, first); if (!status.ok()) { return status; } } if (childdirs) { childdirs->push_back(itc.value()); } first = false; } // finished streaming return grpc::Status::OK; } grpc::Status GrpcNsInterface::Find(eos::common::VirtualIdentity& ivid, grpc::ServerWriter* writer, const eos::rpc::FindRequest* request) { eos::common::VirtualIdentity vid = ivid; if (request->role().uid() || request->role().gid()) { if ((ivid.uid != request->role().uid()) || (ivid.gid != request->role().gid())) { if (!ivid.sudoer) { return grpc::Status(grpc::StatusCode::PERMISSION_DENIED, std::string("Ask an admin to map your auth key to a sudo'er account - permission denied")); } else { vid = eos::common::Mapping::Someone(request->role().uid(), request->role().gid()); } } } else { // we don't implement sudo to root } std::string path; std::vector< std::vector > found_dirs; found_dirs.resize(1); found_dirs[0].resize(1); found_dirs[0][0] = 0; uint64_t deepness = 0; // find for a single directory if (request->maxdepth() == 0) { grpc::Status status = grpc::Status::OK; eos::rpc::MDRequest c_dir; *(c_dir.mutable_id()) = request->id(); if (request->type() != eos::rpc::FILE) { c_dir.mutable_selection()->CopyFrom(request->selection()); c_dir.set_type(eos::rpc::CONTAINER); status = GetMD(vid, writer, &c_dir, true, false, true); } return status; } // find for multiple directories/files do { bool streamparent = false; found_dirs.resize(deepness + 2); // loop over all directories in that deepness for (unsigned int i = 0; i < found_dirs[deepness].size(); i++) { uint64_t id = found_dirs[deepness][i]; eos::rpc::MDRequest lrequest; if ((deepness == 0) && (id == 0)) { // that is the root of a find *(lrequest.mutable_id()) = request->id(); eos_static_warning("%s %llu %llu", lrequest.id().path().c_str(), lrequest.id().id(), lrequest.id().ino()); streamparent = true; } else { lrequest.mutable_id()->set_id(id); streamparent = false; } lrequest.set_type(request->type()); lrequest.mutable_selection()->CopyFrom(request->selection()); *(lrequest.mutable_role()) = request->role(); std::vector children; grpc::Status status = StreamMD(vid, writer, &lrequest, streamparent, &children); if (!status.ok()) { return status; } for (auto const& value : children) { // stream dirs under path into cpath found_dirs[deepness + 1].push_back(value); } } deepness++; if (deepness >= request->maxdepth()) { break; } } while (found_dirs[deepness].size()); return grpc::Status::OK; } bool GrpcNsInterface::Access(eos::common::VirtualIdentity& vid, int mode, std::shared_ptr cmd, eos::IFileMD::XAttrMap* attrmapF) { // UNIX permissions if (cmd->access(vid.uid, vid.gid, mode)) { return true; } // ACLs - WARNING: this does not support ACLs to be linked attributes ! eos::IContainerMD::XAttrMap xattr = cmd->getAttributes(); eos::mgm::Acl acl; acl.SetFromAttrMap(xattr, vid, attrmapF); // check for immutable if (vid.uid && !acl.IsMutable() && (mode & W_OK)) { return false; } bool permok = false; if (acl.HasAcl()) { permok = true; if ((mode & W_OK) && (!acl.CanWrite())) { permok = false; } if (mode & R_OK) { if (!acl.CanRead()) { permok = false; } } if (mode & X_OK) { if ((!acl.CanBrowse())) { permok = false; } } } return permok; } grpc::Status GrpcNsInterface::NsStat(eos::common::VirtualIdentity& vid, eos::rpc::NsStatResponse* reply, const eos::rpc::NsStatRequest* request) { if (!vid.sudoer) { // Block everyone who is not a sudoer reply->set_emsg("Not a sudoer, refusing to run command"); reply->set_code(EPERM); return grpc::Status::OK; } reply->set_state(namespaceStateToString(gOFS->mNamespaceState)); reply->set_nfiles(gOFS->eosFileService->getNumFiles()); reply->set_ncontainers(gOFS->eosDirectoryService->getNumContainers()); // Namespace must be booted to reach this code --> we can use TotalInitTime reply->set_boot_time(gOFS->mTotalInitTime); reply->set_current_fid(gOFS->eosFileService->getFirstFreeId()); reply->set_current_cid(gOFS->eosDirectoryService->getFirstFreeId()); int retc = 0; std::ostringstream err; eos::common::LinuxFds::linux_fds_t fds; eos::common::LinuxStat::linux_stat_t pstat; eos::common::LinuxMemConsumption::linux_mem_t mem; // Lambda function to store error and return code auto storeError = [&err, &retc](const std::string & msg) { err << "error: " << msg << std::endl; retc = errno; }; if (!eos::common::LinuxMemConsumption::GetMemoryFootprint(mem)) { storeError("failed to get memory usage information"); } if (!eos::common::LinuxStat::GetStat(pstat)) { storeError("failed to get process stat information"); } if (!eos::common::LinuxFds::GetFdUsage(fds)) { storeError("failed to get process fd information"); } reply->set_mem_virtual(mem.vmsize); reply->set_mem_resident(mem.resident); reply->set_mem_share(mem.share); reply->set_mem_growth(pstat.vsize - gOFS->LinuxStatsStartup.vsize); reply->set_threads(pstat.threads); reply->set_fds(fds.all); reply->set_uptime(time(NULL) - gOFS->mStartTime); reply->set_emsg(err.str()); reply->set_code(retc); return grpc::Status::OK; } grpc::Status GrpcNsInterface::FileInsert(eos::common::VirtualIdentity& vid, eos::rpc::InsertReply* reply, const eos::rpc::FileInsertRequest* request) { if (!vid.sudoer) { // block every one who is not a sudoer reply->add_message("Not a sudoer, refusing to run command"); reply->add_retc(EPERM); return grpc::Status::OK; } std::shared_ptr newfile; eos::common::RWMutexWriteLock lock(gOFS->eosViewRWMutex); std::vector> conflicts; for (auto it : request->files()) { if (it.id() <= 0) { conflicts.emplace_back(eos::IFileMDPtr( nullptr)); // folly::makeFuture(eos::IFileMDPtr(nullptr))); } else { conflicts.emplace_back(gOFS->eosFileService->getFileMDFut(it.id())); } } int counter = -1; for (auto it : request->files()) { counter++; conflicts[counter].wait(); if (!conflicts[counter].hasException() && std::move(conflicts[counter]).get() != nullptr) { std::ostringstream ss; ss << "Attempted to create file with id=" << it.id() << ", which already exists"; eos_static_err("%s", ss.str().c_str()); reply->add_message(ss.str()); reply->add_retc(EINVAL); continue; } eos_static_info("creating path=%s id=%lx", it.path().c_str(), it.id()); try { try { newfile = gOFS->eosView->createFile(it.path(), it.uid(), it.gid(), it.id()); } catch (eos::MDException& e) { std::ostringstream msg; msg << "Failed to call gOFS->eosView->createFile(): " << e.getMessage().str(); e.getMessage().str(msg.str()); throw; } eos::IFileMD::ctime_t ctime; eos::IFileMD::ctime_t mtime; ctime.tv_sec = it.ctime().sec(); ctime.tv_nsec = it.ctime().n_sec(); mtime.tv_sec = it.mtime().sec(); mtime.tv_nsec = it.mtime().n_sec(); newfile->setFlags(it.flags()); newfile->setCTime(ctime); newfile->setMTime(mtime); newfile->setCUid(it.uid()); newfile->setCGid(it.gid()); newfile->setLayoutId(it.layout_id()); newfile->setSize(it.size()); newfile->setChecksum(it.checksum().value().c_str(), it.checksum().value().size()); for (auto attrit : it.xattrs()) { newfile->setAttribute(attrit.first, attrit.second); } for (auto locit : it.locations()) { newfile->addLocation(locit); } try { gOFS->eosView->updateFileStore(newfile.get()); } catch (eos::MDException& e) { std::ostringstream msg; msg << "Failed to call gOFS->eosView->updateFileStore(): " << e.getMessage().str(); e.getMessage().str(msg.str()); throw; } reply->add_message(""); reply->add_retc(0); } catch (eos::MDException& e) { eos_static_err("msg=\"exception\" ec=%d emsg=\"%s\" path=\"%s\" fxid=%08llx\n", e.getErrno(), e.getMessage().str().c_str(), it.path().c_str(), it.id()); reply->add_message(SSTR("Failed to insert fid=" << it.id() << ", errno=" << e.getErrno() << ", path=" << it.path() << ": " << e.getMessage().str())); reply->add_retc(-1); } } return grpc::Status::OK; } grpc::Status GrpcNsInterface::ContainerInsert(eos::common::VirtualIdentity& vid, eos::rpc::InsertReply* reply, const eos::rpc::ContainerInsertRequest* request) { if (!vid.sudoer) { // block every one who is not a sudoer reply->add_message("Not a sudoer, refusing to run command"); reply->add_retc(EPERM); return grpc::Status::OK; } std::shared_ptr newdir; eos::common::RWMutexWriteLock lock(gOFS->eosViewRWMutex); std::vector> conflicts; for (auto it : request->container()) { if (it.id() <= 0) { conflicts.emplace_back(eos::IContainerMDPtr(nullptr)); } else { conflicts.emplace_back(gOFS->eosDirectoryService->getContainerMDFut(it.id())); } } int counter = -1; bool inherit = request->inherit_md(); for (auto it : request->container()) { counter++; conflicts[counter].wait(); if (!conflicts[counter].hasException() && std::move(conflicts[counter]).get() != nullptr) { std::ostringstream ss; ss << "Attempted to create container with id=" << it.id() << ", which already exists"; eos_static_err("%s", ss.str().c_str()); reply->add_message(ss.str()); reply->add_retc(EINVAL); continue; } eos_static_info("creating path=%s id=%lx inherit_md=%d", it.path().c_str(), it.id(), inherit); try { try { newdir = gOFS->eosView->createContainer(it.path(), false, it.id()); } catch (eos::MDException& e) { std::ostringstream msg; msg << "Failed to call gOFS->eosView->createContainer(): " << e.getMessage().str(); e.getMessage().str(msg.str()); throw; } eos::IContainerMD::ctime_t ctime; eos::IContainerMD::ctime_t mtime; eos::IContainerMD::ctime_t stime; ctime.tv_sec = it.ctime().sec(); ctime.tv_nsec = it.ctime().n_sec(); mtime.tv_sec = it.mtime().sec(); mtime.tv_nsec = it.mtime().n_sec(); stime.tv_sec = it.stime().sec(); stime.tv_nsec = it.stime().n_sec(); // we can send flags or mode to store in flags ... sigh newdir->setFlags(it.flags()); newdir->setCTime(ctime); newdir->setMTime(mtime); newdir->setTMTime(stime); newdir->setCUid(it.uid()); newdir->setCGid(it.gid()); newdir->setMode(it.mode() | S_IFDIR); std::shared_ptr parent; if (inherit) { eos::common::Path cPath(it.path()); try { parent = gOFS->eosView->getContainer(cPath.GetParentPath()); } catch (eos::MDException& e) { std::ostringstream msg; msg << "Failed to call parent gOFS->eosView->getContainer(): " << e.getMessage().str(); e.getMessage().str(msg.str()); throw; } if (it.mode() == 0) { newdir->setMode(parent->getMode()); } for (const auto& attrit : parent->getAttributes()) { newdir->setAttribute(attrit.first, attrit.second); } } struct timespec now; eos::common::Timing::GetTimeSpec(now); newdir->setAttribute("sys.eos.btime", SSTR(now.tv_sec << "." << now.tv_nsec)); for (auto attrit : it.xattrs()) { newdir->setAttribute(attrit.first, attrit.second); } try { gOFS->eosView->updateContainerStore(newdir.get()); if (parent) { parent->setMTime(ctime); parent->notifyMTimeChange(gOFS->eosDirectoryService); gOFS->eosView->updateContainerStore(parent.get()); } } catch (eos::MDException& e) { std::ostringstream msg; msg << "Failed to call gOFS->eosView->updateContainerStore(): " << e.getMessage().str(); e.getMessage().str(msg.str()); throw; } reply->add_message(""); reply->add_retc(0); } catch (eos::MDException& e) { eos_static_err("msg=\"exception\" ec=%d emsg=\"%s\" path=\"%s\" fxid=%08llx\n", e.getErrno(), e.getMessage().str().c_str(), it.path().c_str(), it.id()); reply->add_message(SSTR("Failed to insert cid=" << it.id() << ", errno=" << e.getErrno() << ", path=" << it.path() << ": " << e.getMessage().str())); reply->add_retc(e.getErrno()); } } return grpc::Status::OK; } grpc::Status GrpcNsInterface::Exec(eos::common::VirtualIdentity& ivid, eos::rpc::NSResponse* reply, const eos::rpc::NSRequest* request) { eos::common::VirtualIdentity vid = ivid; if (request->role().uid() || request->role().gid()) { if ((ivid.uid != request->role().uid()) || (ivid.gid != request->role().gid())) { if (!ivid.sudoer) { reply->mutable_error()->set_code(EPERM); reply->mutable_error()->set_msg( "Ask an admin to map your auth key to a sudo'er account - permission denied"); return grpc::Status::OK; } else { vid = eos::common::Mapping::Someone(request->role().uid(), request->role().gid()); } } } else { // we don't implement sudo to root } switch (request->command_case()) { case eos::rpc::NSRequest::kMkdir: return Mkdir(vid, reply->mutable_error() , &(request->mkdir())); break; case eos::rpc::NSRequest::kRmdir: return Rmdir(vid, reply->mutable_error() , &(request->rmdir())); break; case eos::rpc::NSRequest::kTouch: return Touch(vid, reply->mutable_error() , &(request->touch())); break; case eos::rpc::NSRequest::kUnlink: return Unlink(vid, reply->mutable_error() , &(request->unlink())); break; case eos::rpc::NSRequest::kRm: return Rm(vid, reply->mutable_error() , &(request->rm())); break; case eos::rpc::NSRequest::kRename: return Rename(vid, reply->mutable_error() , &(request->rename())); break; case eos::rpc::NSRequest::kSymlink: return Symlink(vid, reply->mutable_error() , &(request->symlink())); break; case eos::rpc::NSRequest::kXattr: return SetXAttr(vid, reply->mutable_error() , &(request->xattr())); break; case eos::rpc::NSRequest::kVersion: return Version(vid, reply->mutable_version() , &(request->version())); break; case eos::rpc::NSRequest::kRecycle: return Recycle(vid, reply->mutable_recycle() , &(request->recycle())); break; case eos::rpc::NSRequest::kChown: return Chown(vid, reply->mutable_error() , &(request->chown())); break; case eos::rpc::NSRequest::kChmod: return Chmod(vid, reply->mutable_error() , &(request->chmod())); break; case eos::rpc::NSRequest::kAcl: return Acl(vid, reply->mutable_acl() , &(request->acl())); break; case eos::rpc::NSRequest::kToken: return Token(vid, reply->mutable_error(), &(request->token())); break; case eos::rpc::NSRequest::kQuota: return Quota(vid, reply->mutable_quota() , &(request->quota())); break; default: reply->mutable_error()->set_code(EINVAL); reply->mutable_error()->set_msg("error: command not supported"); break; } return grpc::Status::OK; } grpc::Status GrpcNsInterface::Mkdir(eos::common::VirtualIdentity& vid, eos::rpc::NSResponse::ErrorResponse* reply, const eos::rpc::NSRequest::MkdirRequest* request) { mode_t mode = request->mode(); if (request->recursive()) { mode |= SFS_O_MKPTH; } std::string path; path = request->id().path(); if (path.empty()) { reply->set_code(EINVAL); reply->set_msg("error:path is empty"); return grpc::Status::OK; } XrdOucErrInfo error; errno = 0; if (gOFS->_mkdir(path.c_str(), mode, error, vid, (const char*) 0)) { reply->set_code(errno); reply->set_msg(error.getErrText()); return grpc::Status::OK; } XrdSfsMode sfsmode = mode; // the mkdir command always inherits the parent mode setting and ignores the mode parameter if (gOFS->_chmod(path.c_str(), sfsmode, error, vid, (const char*) 0)) { reply->set_code(errno); reply->set_msg(error.getErrText()); return grpc::Status::OK; } if (errno == EEXIST) { reply->set_code(EEXIST); std::string msg = "info: directory existed already '"; msg += path.c_str(); msg += "'"; reply->set_msg(msg); } else { reply->set_code(0); std::string msg = "info: created directory '"; msg += path.c_str(); msg += "'"; reply->set_msg(msg); } return grpc::Status::OK; } grpc::Status GrpcNsInterface::Rmdir(eos::common::VirtualIdentity& vid, eos::rpc::NSResponse::ErrorResponse* reply, const eos::rpc::NSRequest::RmdirRequest* request) { std::string path; path = request->id().path(); if (path.empty()) { try { eos::common::RWMutexReadLock vlock(gOFS->eosViewRWMutex); path = gOFS->eosView->getUri(gOFS->eosDirectoryService->getContainerMD( request->id().id()).get()); } catch (eos::MDException& e) { path = ""; errno = e.getErrno(); } } if (path.empty()) { if (request->id().id()) { reply->set_code(ENOENT); reply->set_msg("error: directory id does not exist"); return grpc::Status::OK; } else { reply->set_code(EINVAL); reply->set_msg("error: path is empty"); return grpc::Status::OK; } } XrdOucErrInfo error; errno = 0; if (gOFS->_remdir(path.c_str(), error, vid, (const char*) 0)) { reply->set_code(errno); reply->set_msg(error.getErrText()); return grpc::Status::OK; } reply->set_code(0); std::string msg = "info: deleted directory '"; msg += path.c_str(); msg += "'"; reply->set_msg(msg); return grpc::Status::OK; } grpc::Status GrpcNsInterface::Touch(eos::common::VirtualIdentity& vid, eos::rpc::NSResponse::ErrorResponse* reply, const eos::rpc::NSRequest::TouchRequest* request) { std::string path; path = request->id().path(); if (path.empty()) { reply->set_code(EINVAL); reply->set_msg("error:path is empty"); return grpc::Status::OK; } XrdOucErrInfo error; errno = 0; if (gOFS->_touch(path.c_str(), error, vid, (const char*) 0)) { reply->set_code(errno); reply->set_msg(error.getErrText()); return grpc::Status::OK; } reply->set_code(0); std::string msg = "info: touched file '"; msg += path.c_str(); msg += "'"; reply->set_msg(msg); return grpc::Status::OK; } grpc::Status GrpcNsInterface::Unlink(eos::common::VirtualIdentity& vid, eos::rpc::NSResponse::ErrorResponse* reply, const eos::rpc::NSRequest::UnlinkRequest* request) { bool norecycle = false; if (request->norecycle()) { norecycle = true; } std::string path; path = request->id().path(); if (path.empty()) { try { eos::common::RWMutexReadLock vlock(gOFS->eosViewRWMutex); path = gOFS->eosView->getUri(gOFS->eosDirectoryService->getContainerMD( request->id().id()).get()); } catch (eos::MDException& e) { path = ""; errno = e.getErrno(); } } if (path.empty()) { if (request->id().id()) { reply->set_code(ENOENT); reply->set_msg("error: directory id does not exist"); return grpc::Status::OK; } else { reply->set_code(EINVAL); reply->set_msg("error: path is empty"); return grpc::Status::OK; } } XrdOucErrInfo error; errno = 0; if (gOFS->_rem(path.c_str(), error, vid, (const char*) 0, false, false, norecycle)) { reply->set_code(errno); reply->set_msg(error.getErrText()); return grpc::Status::OK; } reply->set_code(0); std::string msg = "info: unlinked file '"; msg += path.c_str(); msg += "'"; reply->set_msg(msg); return grpc::Status::OK; } grpc::Status GrpcNsInterface::Rm(eos::common::VirtualIdentity& vid, eos::rpc::NSResponse::ErrorResponse* reply, const eos::rpc::NSRequest::RmRequest* request) { eos::console::RequestProto req; if (!request->id().path().empty()) { req.mutable_rm()->set_path(request->id().path()); } else { if (request->id().type() == eos::rpc::FILE) { req.mutable_rm()->set_fileid(request->id().id()); } else { req.mutable_rm()->set_containerid(request->id().id()); } } if (request->recursive()) { req.mutable_rm()->set_recursive(true); } if (request->norecycle()) { req.mutable_rm()->set_bypassrecycle(true); } eos::mgm::RmCmd rmcmd(std::move(req), vid); eos::console::ReplyProto preply = rmcmd.ProcessRequest(); if (preply.retc()) { reply->set_code(preply.retc()); reply->set_msg(preply.std_err()); return grpc::Status::OK; } reply->set_code(0); std::string msg = "info: "; msg += "deleted directory tree '"; if (!request->id().path().empty()) { msg += request->id().path().c_str(); } else { std::stringstream s; s << std::hex << request->id().id(); msg += s.str().c_str(); } reply->set_msg(msg); return grpc::Status::OK; } grpc::Status GrpcNsInterface::Rename(eos::common::VirtualIdentity& vid, eos::rpc::NSResponse::ErrorResponse* reply, const eos::rpc::NSRequest::RenameRequest* request) { std::string path; std::string target; path = request->id().path(); target = request->target(); if (path.empty()) { reply->set_code(EINVAL); reply->set_msg("error:path is empty"); return grpc::Status::OK; } if (target.empty()) { reply->set_code(EINVAL); reply->set_msg("error:target is empty"); return grpc::Status::OK; } XrdOucErrInfo error; errno = 0; if (gOFS->_rename( path.c_str(), target.c_str(), error, vid)) { reply->set_code(errno); reply->set_msg(error.getErrText()); return grpc::Status::OK; } reply->set_code(0); std::string msg = "info: renamed '"; msg += path.c_str(); msg += "' to '"; msg += target.c_str(); msg += "'"; reply->set_msg(msg); return grpc::Status::OK; } grpc::Status GrpcNsInterface::Symlink(eos::common::VirtualIdentity& vid, eos::rpc::NSResponse::ErrorResponse* reply, const eos::rpc::NSRequest::SymlinkRequest* request) { std::string path; std::string target; path = request->id().path(); target = request->target(); if (path.empty()) { reply->set_code(EINVAL); reply->set_msg("error:path is empty"); return grpc::Status::OK; } if (target.empty()) { reply->set_code(EINVAL); reply->set_msg("error:target is empty"); return grpc::Status::OK; } XrdOucErrInfo error; errno = 0; if (gOFS->_symlink( path.c_str(), target.c_str(), error, vid)) { reply->set_code(errno); reply->set_msg(error.getErrText()); return grpc::Status::OK; } reply->set_code(0); std::string msg = "info: symlinked '"; msg += path.c_str(); msg += "' to '"; msg += target.c_str(); msg += "'"; reply->set_msg(msg); return grpc::Status::OK; } grpc::Status GrpcNsInterface::SetXAttr(eos::common::VirtualIdentity& vid, eos::rpc::NSResponse::ErrorResponse* reply, const eos::rpc::NSRequest::SetXAttrRequest* request) { std::string path; path = request->id().path(); if (path.empty()) { if (request->id().type() == eos::rpc::FILE) { try { eos::common::RWMutexReadLock vlock(gOFS->eosViewRWMutex); path = gOFS->eosView->getUri(gOFS->eosFileService->getFileMD( request->id().id()).get()); } catch (eos::MDException& e) { path = ""; errno = e.getErrno(); } } else { try { eos::common::RWMutexReadLock vlock(gOFS->eosViewRWMutex); path = gOFS->eosView->getUri(gOFS->eosDirectoryService->getContainerMD( request->id().id()).get()); } catch (eos::MDException& e) { path = ""; errno = e.getErrno(); } } if (path.empty()) { reply->set_code(EINVAL); reply->set_msg("error:path is empty"); return grpc::Status::OK; } } XrdOucErrInfo error; errno = 0; // setting keys for (auto it = request->xattrs().begin(); it != request->xattrs().end(); ++it) { std::string key = it->first; std::string value = it->second; std::string b64value; eos::common::SymKey::Base64(value, b64value); if (gOFS->_attr_set(path.c_str(), error, vid, (const char*) 0, key.c_str(), b64value.c_str(), request->create())) { reply->set_code(errno); reply->set_msg(error.getErrText()); return grpc::Status::OK; } } // deleting keys for (auto i = 0; i < request->keystodelete().size(); i++) { if (gOFS->_attr_rem(path.c_str(), error, vid, (const char*) 0, request->keystodelete()[i].c_str())) { reply->set_code(errno); reply->set_msg(error.getErrText()); return grpc::Status::OK; } } reply->set_code(0); std::string msg = "info: setxattr on '"; msg += path.c_str(); msg += "'"; reply->set_msg(msg); return grpc::Status::OK; } grpc::Status GrpcNsInterface::Version(eos::common::VirtualIdentity& vid, eos::rpc::NSResponse::VersionResponse* reply, const eos::rpc::NSRequest::VersionRequest* request) { /* message VersionRequest { enum VERSION_CMD { CREATE= 0; PURGE = 1; LIST = 2; GRAB = 3; } MDId id = 1; VERSION_CMD cmd = 2; int maxversion = 3; } */ std::string path; uint64_t fid = 0; path = request->id().path(); if (path.empty()) { if (request->id().ino()) { // get by inode fid = eos::common::FileId::InodeToFid(request->id().ino()); } else if (request->id().id()) { // get by fileid fid = request->id().id(); } try { eos::common::RWMutexReadLock vlock(gOFS->eosViewRWMutex); path = gOFS->eosView->getUri(gOFS->eosFileService->getFileMD(fid).get()); } catch (eos::MDException& e) { path = ""; errno = e.getErrno(); } if (path.empty()) { reply->set_code(EINVAL); reply->set_msg("error:path is empty"); return grpc::Status::OK; } } std::string vpath; eos::common::Path cPath(path.c_str()); vpath += cPath.GetParentPath(); vpath += EOS_COMMON_PATH_VERSION_PREFIX; vpath += cPath.GetName(); vpath += "/"; if (request->cmd() == eos::rpc::NSRequest::VersionRequest::CREATE) { // create a new version ProcCommand cmd; XrdOucErrInfo error; XrdOucString info = "mgm.cmd=file&mgm.subcmd=version&mgm.purge.version="; info += std::to_string(request->maxversion()).c_str(); if (fid) { info += "&mgm.file.id="; info += std::to_string(fid).c_str(); } else { // this has a problem with '&' encoding, prefer to use create by fid .. info += "&mgm.path="; info += path.c_str(); } cmd.open("/proc/user", info.c_str(), vid, &error); cmd.close(); int rc = cmd.GetRetc(); if (rc) { std::string msg = "Creation failed: "; msg += cmd.GetStdErr(); reply->set_code((rc > 0) ? -rc : rc); reply->set_msg(msg); return grpc::Status::OK; } else { std::string msg = "info: created new version for path='"; msg += path; msg += "'"; reply->set_msg(msg); return grpc::Status::OK; } } else { if (request->cmd() == eos::rpc::NSRequest::VersionRequest::PURGE) { // purge versions XrdOucErrInfo error; int rc = gOFS->PurgeVersion(vpath.c_str(), error, request->maxversion()); if (rc) { reply->set_code(errno); reply->set_msg(error.getErrText()); } else { reply->set_code(0); std::string msg; msg = "info: purged versions of path='"; msg += path; msg += "' to maxversion="; msg += std::to_string(request->maxversion()); reply->set_msg(msg); } } else { if (request->cmd() == eos::rpc::NSRequest::VersionRequest::LIST) { // list versions XrdMgmOfsDirectory directory; int listrc = directory.open(vpath.c_str(), vid, (const char*) 0); if (!listrc) { const char* val = 0; while ((val = directory.nextEntry())) { std::string entryname = val; if ((entryname == ".") || (entryname == "..")) { continue; } eos::rpc::NSResponse::VersionResponse::VersionInfo info; std::string smtime, sfid; eos::common::StringConversion::SplitKeyValue(entryname, smtime, sfid, "."); uint64_t mtime = strtoull(smtime.c_str(), 0, 10); uint64_t fid = strtoull(sfid.c_str(), 0, 16); uint64_t inode = eos::common::FileId::FidToInode(fid); std::string fullpath = vpath + "/"; fullpath += entryname; info.mutable_mtime()->set_sec(mtime); info.mutable_id()->set_id(fid); info.mutable_id()->set_ino(inode); info.mutable_id()->set_path(fullpath); info.mutable_id()->set_type(eos::rpc::FILE); auto new_version = reply->add_versions(); new_version->CopyFrom(info); } } } else { if (request->cmd() == eos::rpc::NSRequest::VersionRequest::GRAB) { // grab a given version XrdOucErrInfo error; struct stat buf; struct stat vbuf; if (gOFS->_stat(path.c_str(), &buf, error, vid, "")) { std::string msg; msg = "error; unable to stat path="; msg += path.c_str(); reply->set_code(errno); reply->set_msg(msg); return grpc::Status::OK; } XrdOucString versionname = request->grabversion().c_str(); if (!versionname.length()) { std::string msg = "error: you have to provide the version you want to stage!"; reply->set_code(EINVAL); reply->set_msg(msg); return grpc::Status::OK; } XrdOucString versionpath = cPath.GetVersionDirectory(); versionpath += versionname; if (gOFS->_stat(versionpath.c_str(), &vbuf, error, vid, "")) { std::string msg; msg = "error: failed to stat your provided version path='"; msg += versionpath.c_str(); msg += "'"; reply->set_code(errno); reply->set_msg(msg); return grpc::Status::OK; } // now stage a new version of the existing file XrdOucString versionedpath; if (gOFS->Version(eos::common::FileId::InodeToFid(buf.st_ino), error, vid, -1, &versionedpath)) { std::string msg; msg += "error: unable to create a version of path="; msg += path.c_str(); msg += "\n"; msg += "error: "; msg += error.getErrText(); reply->set_code(error.getErrInfo()); reply->set_msg(msg); return grpc::Status::OK; } // and stage back the desired version if (gOFS->rename(versionpath.c_str(), path.c_str(), error, vid)) { std::string msg; msg += "error: unable to stage"; msg += " '"; msg += versionpath.c_str(); msg += "' back to '"; msg += path.c_str(); msg += "'"; reply->set_code(errno); reply->set_msg(msg); return grpc::Status::OK; } else { std::string msg; msg += "success: staged '"; msg += versionpath.c_str(); msg += "' back to '"; msg += path.c_str(); msg += "'"; msg += " - the previous file is now '"; msg += versionedpath.c_str(); msg += ";"; reply->set_code(0); reply->set_msg(msg); } } else { reply->set_code(EINVAL); reply->set_msg("error: command is not supported"); } } } } return grpc::Status::OK; } grpc::Status GrpcNsInterface::Recycle(eos::common::VirtualIdentity& vid, eos::rpc::NSResponse::RecycleResponse* reply, const eos::rpc::NSRequest::RecycleRequest* request) { if (request->cmd() == eos::rpc::NSRequest::RecycleRequest::RESTORE) { if (!request->key().length()) { reply->set_code(EINVAL); reply->set_msg("error: you need to define the recycle key in the request"); return grpc::Status::OK; } std::string std_out, std_err; eos_static_info("restore: key=% flags=%d:%d:%d", request->key().c_str(), request->restoreflag().force(), request->restoreflag().versions(), request->restoreflag().mkpath()); int retc = Recycle::Restore(std_out, std_err, vid, request->key().c_str(), request->restoreflag().force(), request->restoreflag().versions(), request->restoreflag().mkpath()); if (retc) { reply->set_code(retc); reply->set_msg(std_err.c_str()); } else { reply->set_msg(std_out.c_str()); } // check restore flags return grpc::Status::OK; } else if (request->cmd() == eos::rpc::NSRequest::RecycleRequest::PURGE) { std::string std_out, std_err; std::string date; if (request->purgedate().year()) { date += std::to_string(request->purgedate().year()); if (request->purgedate().month()) { char smonth[1024]; snprintf(smonth, sizeof(smonth), "/%02u", request->purgedate().month()); date += smonth; if (request->purgedate().day()) { char sday[1024]; snprintf(sday, sizeof(sday), "/%02u", request->purgedate().day()); date += sday; } } } eos_static_info("purge: date=%s", date.c_str()); // we need a sudoer flag to purge a recycle bin via grpc vid.sudoer = true; int retc = Recycle::Purge(std_out, std_err, vid, date, false, request->key()); if (retc) { reply->set_code(retc); reply->set_msg(std_err.c_str()); } else { reply->set_msg(std_out.c_str()); } return grpc::Status::OK; } else if (request->cmd() == eos::rpc::NSRequest::RecycleRequest::LIST) { fprintf(stderr, "Doing Listing\n"); std::string std_out, std_err; Recycle::RecycleListing rvec; Recycle::Print(std_out, std_err, vid, true, true, true, "", false, &rvec); for (auto item : rvec) { eos::rpc::NSResponse::RecycleResponse::RecycleInfo info; if (item["type"] == "file") { info.set_type(eos::rpc::NSResponse::RecycleResponse::RecycleInfo::FILE); } else if (item["type"] == "recursive-dir") { info.set_type(eos::rpc::NSResponse::RecycleResponse::RecycleInfo::TREE); } info.mutable_dtime()->set_sec((strtoull(item["dtime"].c_str(), 0, 10))); info.mutable_owner()->set_username(item["username"]); info.mutable_owner()->set_groupname(item["groupname"]); info.mutable_owner()->set_uid(strtoul(item["uid"].c_str(), 0, 10)); info.mutable_owner()->set_gid(strtoul(item["gid"].c_str(), 0, 10)); info.set_size(strtoull(item["size"].c_str(), 0, 10)); info.mutable_id()->set_path(item["path"]); info.set_key(item["key"]); auto new_info = reply->add_recycles(); fprintf(stderr, "Adding one\n"); new_info->CopyFrom(info); } return grpc::Status::OK; } else { reply->set_code(EINVAL); reply->set_msg("error: command is currently not supported"); return grpc::Status::OK; } } grpc::Status GrpcNsInterface::Chown(eos::common::VirtualIdentity& vid, eos::rpc::NSResponse::ErrorResponse* reply, const eos::rpc::NSRequest::ChownRequest* request) { std::string path; path = request->id().path(); if (path.empty()) { if (request->id().type() == eos::rpc::FILE) { try { eos::common::RWMutexReadLock vlock(gOFS->eosViewRWMutex); path = gOFS->eosView->getUri(gOFS->eosFileService->getFileMD( request->id().id()).get()); } catch (eos::MDException& e) { path = ""; errno = e.getErrno(); } } else { try { eos::common::RWMutexReadLock vlock(gOFS->eosViewRWMutex); path = gOFS->eosView->getUri(gOFS->eosDirectoryService->getContainerMD( request->id().id()).get()); } catch (eos::MDException& e) { path = ""; errno = e.getErrno(); } } if (path.empty()) { reply->set_code(EINVAL); reply->set_msg("error:path is empty"); return grpc::Status::OK; } } XrdOucErrInfo error; errno = 0; uid_t uid = request->owner().uid(); gid_t gid = request->owner().gid(); std::string user = request->owner().username(); std::string group = request->owner().groupname(); if (!user.empty()) { int errc = 0; uid = eos::common::Mapping::UserNameToUid(user, errc); if (errc) { reply->set_code(EINVAL); std::string msg = "error: unable to translate username to uid '"; msg += user; msg += "'"; reply->set_msg(msg); return grpc::Status::OK; } } if (!group.empty()) { int errc = 0; gid = eos::common::Mapping::GroupNameToGid(group, errc); if (errc) { reply->set_code(EINVAL); std::string msg = "error: unable to translate groupname to gid '"; msg += group; msg += "'"; reply->set_msg(msg); return grpc::Status::OK; } } if (gOFS->_chown(path.c_str(), uid, gid, error, vid, (const char*) 0)) { reply->set_code(errno); reply->set_msg(error.getErrText()); return grpc::Status::OK; } reply->set_code(0); std::string msg = "info: chown file '"; msg += path.c_str(); msg += "' uid="; msg += std::to_string(uid); msg += "' gid="; msg += std::to_string(gid); reply->set_msg(msg); return grpc::Status::OK; } grpc::Status GrpcNsInterface::Chmod(eos::common::VirtualIdentity& vid, eos::rpc::NSResponse::ErrorResponse* reply, const eos::rpc::NSRequest::ChmodRequest* request) { std::string path; path = request->id().path(); if (path.empty()) { if (request->id().type() == eos::rpc::FILE) { try { eos::common::RWMutexReadLock vlock(gOFS->eosViewRWMutex); path = gOFS->eosView->getUri(gOFS->eosFileService->getFileMD( request->id().id()).get()); } catch (eos::MDException& e) { path = ""; errno = e.getErrno(); } } else { try { eos::common::RWMutexReadLock vlock(gOFS->eosViewRWMutex); path = gOFS->eosView->getUri(gOFS->eosDirectoryService->getContainerMD( request->id().id()).get()); } catch (eos::MDException& e) { path = ""; errno = e.getErrno(); } } if (path.empty()) { reply->set_code(EINVAL); reply->set_msg("error:path is empty"); return grpc::Status::OK; } } XrdOucErrInfo error; errno = 0; mode_t mode = request->mode(); XrdSfsMode sfsmode = mode; if (gOFS->_chmod(path.c_str(), sfsmode, error, vid, (const char*) 0)) { reply->set_code(errno); reply->set_msg(error.getErrText()); return grpc::Status::OK; } reply->set_code(0); std::string msg = "info: chmod file '"; msg += path.c_str(); msg += "' mode="; std::stringstream s; s << std::oct << mode; msg += s.str().c_str(); reply->set_msg(msg); return grpc::Status::OK; } grpc::Status GrpcNsInterface::Acl(eos::common::VirtualIdentity& vid, eos::rpc::NSResponse::AclResponse* reply, const eos::rpc::NSRequest::AclRequest* request) { eos::console::RequestProto req; std::string path; uint64_t fid = 0; uint64_t cid = 0; path = request->id().path(); if (path.empty()) { if (request->id().ino()) { // get by inode if (request->id().type() == eos::rpc::FILE) { fid = eos::common::FileId::InodeToFid(request->id().ino()); } else { cid = request->id().ino(); } } else if (request->id().id()) { // get by id if (request->id().type() == eos::rpc::FILE) { fid = request->id().id(); } else { cid = request->id().id(); } } try { eos::common::RWMutexReadLock vlock(gOFS->eosViewRWMutex); if (fid) { path = gOFS->eosView->getUri(gOFS->eosFileService->getFileMD(fid).get()); } else { path = gOFS->eosView->getUri(gOFS->eosDirectoryService->getContainerMD(cid).get()); } } catch (eos::MDException& e) { path = ""; errno = e.getErrno(); } if (path.empty()) { reply->set_code(EINVAL); reply->set_msg("error:path is empty"); return grpc::Status::OK; } } // transformt the proto request if (request->type() == eos::rpc::NSRequest::AclRequest::SYS_ACL) { req.mutable_acl()->set_sys_acl(true); } req.mutable_acl()->set_path(path); req.mutable_acl()->set_recursive(request->recursive()); if (request->cmd() == eos::rpc::NSRequest::AclRequest::MODIFY) { req.mutable_acl()->set_op(eos::console::AclProto::MODIFY); } if (request->cmd() == eos::rpc::NSRequest::AclRequest::LIST) { req.mutable_acl()->set_op(eos::console::AclProto::LIST); } uint32_t position = request->position(); if (position) { req.mutable_acl()->set_position(position); } req.mutable_acl()->set_rule(request->rule()); eos::mgm::AclCmd aclcmd(std::move(req), vid); eos::console::ReplyProto preply = aclcmd.ProcessRequest(); if (preply.retc()) { reply->set_code(preply.retc()); reply->set_msg(preply.std_err()); return grpc::Status::OK; } else { if (request->cmd() == eos::rpc::NSRequest::AclRequest::MODIFY) { // retrieve the current version of the acls now req.mutable_acl()->set_op(eos::console::AclProto::LIST); eos::mgm::AclCmd aclcmd(std::move(req), vid); eos::console::ReplyProto preply = aclcmd.ProcessRequest(); if (preply.retc()) { reply->set_code(preply.retc()); reply->set_msg(preply.std_err()); return grpc::Status::OK; } else { reply->set_rule(preply.std_out()); } } else { reply->set_rule(preply.std_out()); } } reply->set_code(0); return grpc::Status::OK; } grpc::Status GrpcNsInterface::Token(eos::common::VirtualIdentity& vid, eos::rpc::NSResponse::ErrorResponse* reply, const eos::rpc::NSRequest::TokenRequest* request) { eos::console::RequestProto req; // translate the grpc request proto to a console request proto req.mutable_token()->set_path(request->token().token().path()); req.mutable_token()->set_permission(request->token().token().permission()); req.mutable_token()->set_owner(request->token().token().owner()); req.mutable_token()->set_group(request->token().token().group()); req.mutable_token()->set_expires(request->token().token().expires()); req.mutable_token()->set_generation(request->token().token().generation()); req.mutable_token()->set_allowtree(request->token().token().allowtree()); req.mutable_token()->set_vtoken(request->token().token().vtoken()); for (int i = 0; i < request->token().token().origins_size(); ++i) { const eos::rpc::ShareAuth& auth = request->token().token().origins(i); eos::console::TokenAuth* newauth = req.mutable_token()->add_origins(); newauth->set_host(auth.host()); newauth->set_prot(auth.prot()); newauth->set_name(auth.name()); } eos::mgm::TokenCmd tokencmd(std::move(req), vid); // reuse the CLI implementation eos::console::ReplyProto preply = tokencmd.ProcessRequest(); if (preply.retc()) { reply->set_code(preply.retc()); reply->set_msg(preply.std_err()); return grpc::Status::OK; } reply->set_code(0); reply->set_msg(preply.std_out()); return grpc::Status::OK; } grpc::Status GrpcNsInterface::Quota(eos::common::VirtualIdentity& vid, eos::rpc::NSResponse::QuotaResponse* reply, const eos::rpc::NSRequest::QuotaRequest* request) { eos::console::RequestProto req; if (request->op() == eos::rpc::QUOTAOP::GET) { // get quota request eos::console::QuotaProto_LsProto* ls = req.mutable_quota()->mutable_ls(); int rc = 0; ls->set_format(true); // filter by username if (request->id().username().length()) { ls->set_uid(request->id().username()); } else { ls->set_uid(std::to_string(request->id().uid())); } if (request->id().groupname().length()) { ls->set_gid(request->id().groupname()); } else { ls->set_gid(std::to_string(request->id().gid())); } if (request->path().length()) { ls->set_space(request->path()); } eos::mgm::QuotaCmd cmd(std::move(req), vid); eos::console::ReplyProto preply = cmd.ProcessRequest(); if ((rc = preply.retc())) { std::string msg = "Quota Command Failed: "; msg += preply.std_err(); reply->set_code((rc > 0) ? -rc : rc); reply->set_msg(msg); return grpc::Status::OK; } // parse the output into a protobuf structure; std::istringstream f(preply.std_out()); std::string line; while (std::getline(f, line)) { std::map info; if (eos::common::StringConversion::GetKeyValueMap(line.c_str(), info, "=", " ")) { auto node = reply->add_quotanode(); node->set_path(info["space"]); if (info.count("uid")) { node->set_name(info["uid"]); node->set_type(eos::rpc::QUOTATYPE::USER); } if (info.count("gid")) { node->set_name(info["gid"]); if (info["gid"] == "project") { node->set_type(eos::rpc::QUOTATYPE::PROJECT); } else { node->set_type(eos::rpc::QUOTATYPE::GROUP); } } node->set_usedbytes(strtoull(info["usedbytes"].c_str(), 0, 10)); node->set_usedlogicalbytes(strtoull(info["usedlogicalbytes"].c_str(), 0, 10)); node->set_usedfiles(strtoull(info["usedfiles"].c_str(), 0, 10)); node->set_maxbytes(strtoull(info["maxbytes"].c_str(), 0, 10)); node->set_maxlogicalbytes(strtoull(info["maxlogicalbytes"].c_str(), 0, 10)); node->set_maxfiles(strtoull(info["maxfiles"].c_str(), 0, 10)); if (node->maxbytes() > 0) { node->set_percentageusedbytes(100.0 * node->usedbytes() / node->maxbytes()); } else { node->set_percentageusedbytes(0); } if (node->maxfiles() > 0) { node->set_percentageusedfiles(100.0 * node->usedfiles() / node->maxfiles()); } else { node->set_percentageusedfiles(0); } node->set_statusbytes(info["statusbytes"]); node->set_statusfiles(info["statusfiles"]); } } } else if (request->op() == eos::rpc::QUOTAOP::SET) { // set quota request eos::console::QuotaProto_SetProto* sp = req.mutable_quota()->mutable_set(); int rc = 0; // filter by username if (request->id().username().length()) { sp->set_uid(request->id().username()); } else { if (request->id().uid()) { sp->set_uid(std::to_string(request->id().uid())); } } if (request->id().groupname().length()) { sp->set_gid(request->id().groupname()); } else { if (request->id().gid()) { sp->set_gid(std::to_string(request->id().gid())); } } if (request->path().length()) { sp->set_space(request->path()); } sp->set_maxbytes(std::to_string(request->maxbytes())); sp->set_maxinodes(std::to_string(request->maxfiles())); eos::mgm::QuotaCmd cmd(std::move(req), vid); eos::console::ReplyProto preply = cmd.ProcessRequest(); if ((rc = preply.retc())) { std::string msg = "Quota Command Failed: "; msg += preply.std_err(); reply->set_code((rc > 0) ? -rc : rc); reply->set_msg(msg); return grpc::Status::OK; } } else if (request->op() == eos::rpc::QUOTAOP::RM) { // delete quota entry eos::console::QuotaProto_RmProto* sp = req.mutable_quota()->mutable_rm(); int rc = 0; // select uid/gid if (request->id().username().length()) { sp->set_uid(request->id().username()); } else { if (request->id().uid()) { sp->set_uid(std::to_string(request->id().uid())); } } if (request->id().groupname().length()) { sp->set_gid(request->id().groupname()); } else { if (request->id().gid()) { sp->set_gid(std::to_string(request->id().gid())); } } if (request->path().length()) { sp->set_space(request->path()); } switch (request->entry()) { case eos::rpc::QUOTAENTRY::NONE : sp->set_type(eos::console::QuotaProto_RmProto::NONE); break; case eos::rpc::QUOTAENTRY::VOLUME : sp->set_type(eos::console::QuotaProto_RmProto::VOLUME); break; case eos::rpc::QUOTAENTRY::INODE : sp->set_type(eos::console::QuotaProto_RmProto::INODE); break; default: sp->set_type(eos::console::QuotaProto_RmProto::NONE); break; } eos::mgm::QuotaCmd cmd(std::move(req), vid); eos::console::ReplyProto preply = cmd.ProcessRequest(); if ((rc = preply.retc())) { std::string msg = "Quota Command Failed: "; msg += preply.std_err(); reply->set_code((rc > 0) ? -rc : rc); reply->set_msg(msg); return grpc::Status::OK; } } else if (request->op() == eos::rpc::QUOTAOP::RMNODE) { // delete quota node eos::console::QuotaProto_RmnodeProto* sp = req.mutable_quota()->mutable_rmnode(); int rc = 0; if (request->path().length()) { sp->set_space(request->path()); } eos::mgm::QuotaCmd cmd(std::move(req), vid); eos::console::ReplyProto preply = cmd.ProcessRequest(); if ((rc = preply.retc())) { std::string msg = "Quota Command Failed: "; msg += preply.std_err(); reply->set_code((rc > 0) ? -rc : rc); reply->set_msg(msg); return grpc::Status::OK; } } reply->set_code(0); return grpc::Status::OK; } #endif EOSMGMNAMESPACE_END