//------------------------------------------------------------------------------
//! @file ICmdHelper.cc
//! @author Elvin Sindrilaru - 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 "console/commands/ICmdHelper.hh"
#include "common/Logging.hh"
#include "common/SymKeys.hh"
#include "XrdCl/XrdClFile.hh"
#include "XrdOuc/XrdOucEnv.hh"
#include
#include
//------------------------------------------------------------------------------
// Execute command and display any output information
//------------------------------------------------------------------------------
int
ICmdHelper::Execute(bool print_err, bool add_route)
{
if (mIsLocal) {
return 0;
}
int retc = ExecuteWithoutPrint(add_route);
if (!mIsSilent && !mOutcome.result.empty()) {
std::cout << GetResult();
}
if (print_err && !mOutcome.error.empty()) {
std::cerr << GetError();
}
return retc;
}
//------------------------------------------------------------------------------
// Execute command without displaying the result
//------------------------------------------------------------------------------
int
ICmdHelper::ExecuteWithoutPrint(bool add_route)
{
if (!mReq.command_case()) {
std::cerr << "error: generic request object not populated with command"
<< std::endl;
return EINVAL;
}
std::string b64buff;
if (!eos::common::SymKey::ProtobufBase64Encode(&mReq, b64buff)) {
std::cerr << "error: failed to base64 encode the request" << std::endl;
return EINVAL;
}
std::string cmd = "mgm.cmd.proto=";
cmd += b64buff;
if (add_route) {
AddRouteInfo(cmd);
}
std::ostringstream oss;
oss << mGlobalOpts.mMgmUri
<< (mIsAdmin ? "//proc/admin/" : "//proc/user/") << "?"
<< cmd;
if (!mGlobalOpts.mUserRole.empty()) {
oss << "&eos.ruid=" << mGlobalOpts.mUserRole;
}
if (!mGlobalOpts.mGroupRole.empty()) {
oss << "&eos.rgid=" << mGlobalOpts.mGroupRole;
}
if (mGlobalOpts.mForceSss) {
oss << "&xrd.wantprot=sss";
}
if (getenv("EOSAUTHZ")) {
oss << "&authz=" << getenv("EOSAUTHZ");
}
if (getenv("EOSAPP")) {
oss << "&eos.app=" << getenv("EOSAPP");
}
if (mGlobalOpts.mDebug) {
PrintDebugMsg(oss.str());
}
return RawExecute(oss.str());
}
//------------------------------------------------------------------------------
// Execute command using the xrootd client
//------------------------------------------------------------------------------
int
ICmdHelper::RawExecute(const std::string& full_url)
{
if (mSimulationMode) {
if (mSimulatedData.front().expectedCommand != full_url) {
mSimulationErrors +=
SSTR("Expected command '" << mSimulatedData.front().expectedCommand
<< "', received '" << full_url << "'");
return EIO;
}
// Command is OK
mOutcome = mSimulatedData.front().outcome;
mSimulatedData.pop();
return mOutcome.errc;
}
std::ostringstream oss;
if (mGlobalOpts.mMgmUri.substr(0, 6) == "ipc://") {
// ZMQ connection
zmq::context_t context(1);
zmq::socket_t socket(context, ZMQ_REQ);
std::string path = full_url;
path.erase(0, mGlobalOpts.mMgmUri.length() + 1);
socket.connect(mGlobalOpts.mMgmUri);
zmq::message_t request(path.length());
memcpy(request.data(), path.c_str(), path.length());
socket.send(request, zmq::send_flags::none);
std::string sout;
zmq::message_t response;
zmq::recv_result_t ret_recv = socket.recv(response);
if (ret_recv.has_value()) {
sout.assign((char*)response.data(), response.size());
oss << sout;
}
} else {
// XRootD connection
std::unique_ptr client {new XrdCl::File()};
XrdCl::XRootDStatus status = client->Open(full_url.c_str(),
XrdCl::OpenFlags::Read);
if (status.IsOK()) {
off_t offset = 0;
uint32_t nbytes = 0;
char buffer[4096 + 1];
status = client->Read(offset, 4096, buffer, nbytes);
while (status.IsOK() && (nbytes > 0)) {
buffer[nbytes] = 0;
oss << buffer;
offset += nbytes;
status = client->Read(offset, 4096, buffer, nbytes);
}
status = client->Close();
} else {
int retc = status.GetShellCode();
if (status.errNo) {
retc = status.errNo;
}
oss << "mgm.proc.stdout="
<< "&mgm.proc.stderr=" << "error: errc=" << retc
<< " msg=\"" << status.ToString() << "\""
<< "&mgm.proc.retc=" << retc;
}
}
return ProcessResponse(oss.str());
}
//------------------------------------------------------------------------------
// Process MGM response
//------------------------------------------------------------------------------
int ICmdHelper::ProcessResponse(const std::string& response)
{
if (response.empty()) {
mOutcome.error = "error: failed to read proc response";
mOutcome.errc = EIO;
return mOutcome.errc;
}
if (mGlobalOpts.mDebug) {
PrintDebugMsg(response);
}
mOutcome.errc = 0;
std::vector> tags {
std::make_pair("mgm.proc.stdout=", -1),
std::make_pair("&mgm.proc.stderr=", -1),
std::make_pair("&mgm.proc.retc=", -1)
};
for (auto& elem : tags) {
elem.second = response.find(elem.first);
}
if ((tags[0].second == std::string::npos) &&
(tags[1].second == std::string::npos)) {
// This is a "FUSE" format response that only contains the stdout without
// error message or return code
mOutcome.result = response;
return mOutcome.errc;
}
// Parse stdout.
if (tags[0].second != std::string::npos) {
if (tags[1].second != std::string::npos) {
mOutcome.result = response.substr(tags[0].first.length(),
tags[1].second - tags[1].first.length() + 1);
} else {
mOutcome.result = response.substr(tags[0].first.length(),
tags[2].second - tags[2].first.length() - 1);
}
}
// Parse stderr
if (tags[1].second != std::string::npos) {
mOutcome.error = response.substr(tags[1].second + tags[1].first.length(),
tags[2].second - (tags[1].second + tags[1].first.length()));
}
// Parse return code
try {
mOutcome.errc = std::stoi(response.substr(tags[2].second +
tags[2].first.length()));
} catch (...) {
mOutcome.error = "error: failed to parse response from server";
return EINVAL;
}
return mOutcome.errc;
}
//------------------------------------------------------------------------------
// Method used for user confirmation of the specified command
//------------------------------------------------------------------------------
bool
ICmdHelper::ConfirmOperation()
{
std::ostringstream out;
std::string confirmation;
srand(time(NULL));
for (int i = 0; i < 10; i++) {
confirmation += std::to_string((int)(9.0 * rand() / RAND_MAX));
}
out << "Confirm operation by typing => " << confirmation << std::endl;
out << " => ";
std::string userInput;
std::cout << out.str();
getline(std::cin, userInput);
if (userInput == confirmation) {
std::cout << std::endl << "Operation confirmed" << std::endl;
return true;
} else {
std::cout << std::endl << "Operation not confirmed" << std::endl;
return false;
}
}
//------------------------------------------------------------------------------
// Get command output string
//------------------------------------------------------------------------------
std::string
ICmdHelper::GetResult()
{
// Add new line if necessary
std::string out = mOutcome.result;
if (*out.rbegin() != '\n') {
out += '\n';
}
return out;
}
//------------------------------------------------------------------------------
// Get command error string
//------------------------------------------------------------------------------
std::string
ICmdHelper::GetError()
{
// Add new line if necessary
std::string err = mOutcome.error;
if (*err.rbegin() != '\n') {
err += '\n';
}
return err;
}
//------------------------------------------------------------------------------
// Guess a default 'route' e.g. home directory
//------------------------------------------------------------------------------
std::string
ICmdHelper::DefaultRoute(bool verbose)
{
std::string default_route = "";
// add a default 'route' for the command
if (getenv("EOSHOME")) {
default_route = getenv("EOSHOME");
} else {
char default_home[4096];
std::string username;
if (getenv("EOSUSER")) {
username = getenv("EOSUSER");
}
if (getenv("USER")) {
username = getenv("USER");
}
if (username.length()) {
snprintf(default_home, sizeof(default_home), "/eos/user/%s/%s/",
username.substr(0, 1).c_str(), username.c_str());
if (verbose) {
// @note route warning is no longer displayed
// fprintf(stderr,
// "# pre-configuring default route to %s\n"
// "# -use $EOSHOME variable to override\n",
// default_home);
}
default_route = default_home;
}
}
return default_route;
}
//------------------------------------------------------------------------------
// Add eos.route opaque info depending on the type of request and on the
// default route configuration
//------------------------------------------------------------------------------
void
ICmdHelper::AddRouteInfo(std::string& cmd)
{
using eos::console::RequestProto;
bool verbose = true;
// suppress routing output for formatted quota command
switch (mReq.command_case()) {
case RequestProto::kQuota:
if (mReq.quota().lsuser().format()) {
verbose = false;
}
if (mReq.quota().ls().format()) {
verbose = false;
}
break;
case RequestProto::kRm:
verbose = false;
break;
default:
break;
}
const std::string default_route = DefaultRoute(verbose);
std::ostringstream oss;
switch (mReq.command_case()) {
case RequestProto::kRecycle:
if (!default_route.empty()) {
oss << "&eos.route=" << default_route;
}
break;
case RequestProto::kAcl:
oss << "&eos.route=" << mReq.acl().path();
break;
case RequestProto::kToken:
oss << "&eos.route=" << mReq.token().path();
break;
case RequestProto::kRm:
if (mReq.rm().path().empty()) {
if (!default_route.empty()) {
oss << "&eos.route=" << default_route;
}
} else {
oss << "&eos.route=" << mReq.rm().path();
}
break;
case RequestProto::kQuota:
if (mReq.quota().subcmd_case() ==
eos::console::QuotaProto::kLsuser) {
oss << "&eos.route=" << mReq.quota().lsuser().space();
}
break;
case RequestProto::kFind:
oss << "&eos.route=" << mReq.find().path();
break;
default:
break;
}
cmd += oss.str();
}