// ---------------------------------------------------------------------- // 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/ShellExecutor.hh" #include "XrdSys/XrdSysPthread.hh" #include #include #include #include #include #ifdef __APPLE__ #ifndef TEMP_FAILURE_RETRY #define TEMP_FAILURE_RETRY(exp) \ ({ \ decltype(exp) _rc; \ do { \ _rc = (exp); \ } while ((_rc == -1) && (errno == EINTR)); \ _rc; \ }) #endif // TEMP_FAILURE_RETRY #endif // __APPLE__ EOSCOMMONNAMESPACE_BEGIN const std::string ShellExecutor::stdout = "stdout"; const std::string ShellExecutor::stderr = "stderr"; const std::string ShellExecutor::stdin = "stdin"; /*----------------------------------------------------------------------------*/ ShellExecutor::ShellExecutor() { outfd[0] = outfd[1] = -1; infd[0] = infd[1] = -1; // create a pipe for IPC if (pipe(outfd) == -1 || pipe(infd) == -1) { throw ShellException("Not able to create a pipe!"); } // fork the worker process pid_t pid; if ((pid = fork()) < 0) { throw ShellException("Not able to fork!"); } // handle forking if (pid == 0) { // run the child code run_child(); } else { // close the 'read-end' of output pipe on parent side close(outfd[0]); // close the 'write-end' of input pipe on parent side close(infd[1]); } } /*----------------------------------------------------------------------------*/ ShellExecutor::~ShellExecutor() { // --------------------------------------------------------------------------- // close the 'write-end' of output pipe on parent side // reader will receive EOF // --------------------------------------------------------------------------- close(outfd[1]); // wait for the child process the exit wait(0); // close the 'read-end' of input pipe on parent side close(infd[0]); } /*----------------------------------------------------------------------------*/ pid_t ShellExecutor::execute(const std::string& cmd, fifo_uuid_t uuid) const { static XrdSysMutex executeMutex; XrdSysMutexHelper lLock(&executeMutex); // Offset in case we need to send several messages to cover the command string size_t offset = 0; // Message for the child process msg_t msg(uuid); // send the command to child process while (!msg.complete) { // calculate the size of the message size_t size = ((cmd.size() - offset < msg_t::max_size) ? cmd.size() - offset : msg_t::max_size - 1); memset(msg.buff, 0, sizeof(msg.buff)); // copy the command strncpy(msg.buff, cmd.c_str() + offset, size); // terminate the string msg.buff[size] = 0; // set the complete flag msg.complete = (cmd.size() <= offset + size); // send the command to child process if (write(outfd[1], &msg, sizeof(msg_t)) < 0) { throw ShellException("Not able to send message to child process"); } // update offset offset += size; } // the child will respond with pid of the 'command' process pid_t pid = 0; TEMP_FAILURE_RETRY(read(infd[0], &pid, sizeof(pid_t))); return pid; } void ShellExecutor::alarm(int signal) { if (kill(getppid(), 0)) { throw ShellException("Parent died - aborting"); } } /*----------------------------------------------------------------------------*/ void ShellExecutor::run_child() const { // close the 'write-end' of input pipe on child side close(outfd[1]); // close the 'read-end' of output pipe on child side close(infd[0]); // make sure there are no zombie 'command' processes struct sigaction sigchld_action; memset(&sigchld_action, 0, sizeof sigchld_action); sigchld_action.sa_handler = SIG_DFL; sigchld_action.sa_flags = SA_NOCLDWAIT; sigaction(SIGCHLD, &sigchld_action, 0); // Install an alarm handler to check for the death of our parent process struct sigaction sa; sa.sa_handler = &ShellExecutor::alarm; sa.sa_flags = 0; sigfillset(&sa.sa_mask); sigaction(SIGALRM, &sa, NULL); // The message received from the parent msg_t msg; // the command to be executed std::string cmd; // check every 5 seconds for parent death alarm(5); size_t nread = 0; off_t off = 0; while ((nread = TEMP_FAILURE_RETRY(read(outfd[0], (char*)&msg + off, sizeof(msg) - off))) > 0) { alarm(0); off += nread; if (off == sizeof(msg)) { cmd += msg.buff; off = 0; if (msg.complete) { // execute the command pid_t pid = system(cmd.c_str(), msg.uuid); // respond with 'command' pid (void) !write(infd[1], &pid, sizeof(pid_t)); // clean up msg.complete = false; cmd.erase(); } } alarm(5); } // close the 'read-end' of input pipe on child side close(outfd[0]); // close the 'write-end' of output pipe on child side close(infd[1]); // exit from the child process _exit(0); } /*----------------------------------------------------------------------------*/ pid_t ShellExecutor::system(char const* cmd, fifo_uuid_t uuid) const { pid_t pid; if ((pid = fork()) == 0) { // file descriptor for redirecting 'stdout' int stdout_fd = -1, stderr_fd = -1, stdin_fd = -1; // if a timestamp has been given redirect // the 'stdout', 'stderr' and 'stdin' to a named pipes if (uuid != 0 && strlen(uuid) != 0) { // ----------------------------------------------------------------------- // the order in which 'fifos' are opened is not random! // it has to match the order in 'shell_cmd' // otherwise the two process will deadlock) // redirect 'stdout' // ----------------------------------------------------------------------- std::string stdout_name = fifo_name(uuid, stdout); stdout_fd = open(stdout_name.c_str(), O_WRONLY); if (stdout_fd < 0) { throw ShellException("Unable to open stdout file"); } if (dup2(stdout_fd, STDOUT_FILENO) != STDOUT_FILENO) { throw ShellException("Not able to redirect the 'sdtout' to FIFO!"); } // redirect 'stdin' std::string stdin_name = fifo_name(uuid, stdin); stdin_fd = open(stdin_name.c_str(), O_RDONLY); if (stdin_fd < 0) { throw ShellException("Unable to open stdin file"); } if (dup2(stdin_fd, STDIN_FILENO) != STDIN_FILENO) { throw ShellException("Not able to redirect the 'sdtin' to FIFO!"); } // redirect 'stderr' std::string stderr_name = fifo_name(uuid, stderr); stderr_fd = open(stderr_name.c_str(), O_WRONLY); if (stderr_fd < 0) { throw ShellException("Unalbe to open stderr file"); } if (dup2(stderr_fd, STDERR_FILENO) != STDERR_FILENO) { throw ShellException("Not able to redirect the 'sdterr' to FIFO!"); } } // ------------------------------------------------------------------------- // execute the command // ------------------------------------------------------------------------- execl("/bin/sh", "sh", "-c", cmd, (char*) 0); // close file descriptors if necessary if (stdout_fd != -1) { close(stdout_fd); } if (stdout_fd != -1) { close(stdin_fd); } if (stdout_fd != -1) { close(stderr_fd); } // exit _exit(127); } return pid; } /*----------------------------------------------------------------------------*/ ShellExecutor::msg_t::msg_t () : complete(false) { memset(uuid, 0, sizeof(fifo_uuid_t)); memset(buff, 0, max_size); } /*----------------------------------------------------------------------------*/ ShellExecutor::msg_t::msg_t (fifo_uuid_t uuid) : complete(false) { // if the UUID is a NULL pointer if (uuid == 0) { // make it an empty string memset(this->uuid, 0, sizeof(fifo_uuid_t)); } else { // otherwise copy the UUID strncpy(this->uuid, uuid, 36); this->uuid[36] = 0; } memset(buff, 0, max_size); } /*----------------------------------------------------------------------------*/ std::string ShellExecutor::fifo_name(fifo_uuid_t uuid, std::string const& sufix) { return "/tmp/cmd-fifo-" + std::string(uuid) + "-" + sufix; } EOSCOMMONNAMESPACE_END