// ---------------------------------------------------------------------- // File: ShellExecutor.cc // Author: Michal Kamin Simon - 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 "common/Namespace.hh" #include "common/ShellCmd.hh" #include #include #include #include #include #include #include #include #include #include #ifdef __APPLE__ #define EOS_PTRACE_CONTINUE PT_CONTINUE #define EOS_PTRACE_ATTACH PT_ATTACHEXC #else #define EOS_PTRACE_CONTINUE PTRACE_CONT #define EOS_PTRACE_ATTACH PTRACE_ATTACH #endif //__APPLE__ EOSCOMMONNAMESPACE_BEGIN //------------------------------------------------------------------------------ // Constructor //------------------------------------------------------------------------------ ShellCmd::ShellCmd(std::string const& cmd): cmd(cmd), monitor_active(false), monitor_joined(false) { // Generate the 'uuid' for the 'fifos' uuid_t uu; uuid_generate_time(uu); uuid_unparse(uu, uuid); // create a 'fifo' for 'stdout' stdout_name = ShellExecutor::fifo_name(uuid, ShellExecutor::stdout); (void) mkfifo(stdout_name.c_str(), 0666); // create a 'fifo' for 'stderr' stderr_name = ShellExecutor::fifo_name(uuid, ShellExecutor::stderr); (void) mkfifo(stderr_name.c_str(), 0666); // create a 'fifo' for 'stdin' stdin_name = ShellExecutor::fifo_name(uuid, ShellExecutor::stdin); (void) mkfifo(stdin_name.c_str(), 0666); // execute the command try { pid = ShellExecutor::instance().execute(cmd, uuid); // Start the monitor thread monitor_thread = std::thread(&ShellCmd::monitor, this); } catch (const eos::common::ShellException& e) { // There was an exception thrown while executing the command cmd_stat.exited = true; cmd_stat.exit_code = ESRCH; cmd_stat.signaled = false; cmd_stat.signo = 0; cmd_stat.status = ESRCH;; } //---------------------------------------------------------------------------- // open the 'fifos' // (the order is not random: it has to match the order in // 'ShellExecutor' otherwise the two process will deadlock) //---------------------------------------------------------------------------- outfd = open(stdout_name.c_str(), O_RDONLY); infd = open(stdin_name.c_str(), O_WRONLY); errfd = open(stderr_name.c_str(), O_RDONLY); } //------------------------------------------------------------------------------ // Destructor //------------------------------------------------------------------------------ ShellCmd::~ShellCmd() { // Close file descriptors (void) close(outfd); (void) close(errfd); (void) close(infd); // Delete 'fifos' (void) remove(stdout_name.c_str()); (void) remove(stderr_name.c_str()); (void) remove(stdin_name.c_str()); // kill the 'cmd' if active if (is_active()) { (void) kill(); } // Wait for the monitor thread to exit gracefully // (make sure the thread is joined to avoid memory leaks) if (monitor_active || !monitor_joined) { monitor_thread.join(); } } /*----------------------------------------------------------------------------*/ void ShellCmd::monitor() noexcept { // set the active flag monitor_active = true; // switch this thread to root to be able to attach #ifdef __APPLE__ if (setreuid(-1, 0) < 0) { perror("failed while calling setreuid\n"); return; } #else syscall(SYS_setresuid, 0, 0, 0); #endif // Trace the 'command' process (without stopping it), in this way the given //process becomes its parent and can use 'waitpid' for waiting if ((ptrace(EOS_PTRACE_ATTACH, pid, 0, 0)) == -1) { // ptrace attach failed, we cannot proceed, but we block until the child terminated perror("error: failed to attach to forked process"); while (is_active()) { std::this_thread::sleep_for(std::chrono::milliseconds(250)); } cmd_stat.exited = false; cmd_stat.exit_code = EPERM; cmd_stat.signaled = false; cmd_stat.signo = 0; cmd_stat.status = 0; // reset the active flag monitor_active = false; return; } // wait for the 'command' process int status = 0; // wait for the process to terminate while (true) { // wait for a change in the process status if (waitpid(pid, &status, 0) == pid) { // if the process has been stopped (not terminated) // resume it and keep waiting if (status && WIFSTOPPED(status)) { ptrace(EOS_PTRACE_CONTINUE, pid, 0, 0); continue; } // if the process has been just resumed keep waiting if (status && WIFCONTINUED(status)) { continue; } // otherwise the process is terminated and we are done with waiting break; } else { perror("error: failed to waitpid for attached process"); if (!is_active()) { break; } // Prevent tight loops std::this_thread::sleep_for(std::chrono::milliseconds(250)); } } // the status of the 'command' process cmd_stat.exited = WIFEXITED(status); cmd_stat.exit_code = WEXITSTATUS(status); cmd_stat.signaled = WIFSIGNALED(status); cmd_stat.signo = WTERMSIG(status); cmd_stat.status = status; // reset the active flag monitor_active = false; } /*----------------------------------------------------------------------------*/ cmd_status ShellCmd::wait() { if (monitor_active) { monitor_joined = true; monitor_thread.join(); } return cmd_stat; } /*----------------------------------------------------------------------------*/ cmd_status ShellCmd::wait(size_t timeout) { size_t exp_sleep = 1; for (size_t i = 0; i < timeout + 9; ++i) { if (!is_active()) { break; } std::this_thread::sleep_for(std::chrono::milliseconds(exp_sleep)); if (exp_sleep < 512) { exp_sleep *= 2; } else { exp_sleep = 1000; } } // stop it if the timeout is exceeded if (is_active()) { kill(); } if (monitor_active) { monitor_joined = true; monitor_thread.join(); } return cmd_stat; } /*----------------------------------------------------------------------------*/ void ShellCmd::kill(int sig) const { ::kill(pid, sig); } /*----------------------------------------------------------------------------*/ bool ShellCmd::is_active() const { //---------------------------------------------------------------------------- // send the null signal to check if the process exists // if not 'errno' will be set to 'ESRCH' //---------------------------------------------------------------------------- if (::kill(pid, 0) == -1) { return errno != ESRCH; } return true; } EOSCOMMONNAMESPACE_END