/* * Unix Command Execution * (C) 1999-2007 Jack Lloyd * 2012 Markus Wanner * * Distributed under the terms of the Botan license */ #include #include #include #include #include #include #include #include #include #include namespace Botan { namespace { /** * Attempt to execute the command */ void do_exec(const std::vector& arg_list, const std::vector& paths) { const size_t args = arg_list.size() - 1; const char* arg1 = (args >= 1) ? arg_list[1].c_str() : nullptr; const char* arg2 = (args >= 2) ? arg_list[2].c_str() : nullptr; const char* arg3 = (args >= 3) ? arg_list[3].c_str() : nullptr; const char* arg4 = (args >= 4) ? arg_list[4].c_str() : nullptr; for(size_t j = 0; j != paths.size(); j++) { const std::string full_path = paths[j] + "/" + arg_list[0]; const char* fsname = full_path.c_str(); ::execl(fsname, fsname, arg1, arg2, arg3, arg4, NULL); } } } /** * Local information about the pipe */ struct pipe_wrapper { int fd; pid_t pid; pipe_wrapper(int f, pid_t p) : fd(f), pid(p) {} ~pipe_wrapper() { ::close(fd); } }; /** * Read from the pipe */ size_t DataSource_Command::read(byte buf[], size_t length) { if(end_of_data()) return 0; fd_set set; FD_ZERO(&set); FD_SET(pipe->fd, &set); struct ::timeval tv; tv.tv_sec = 0; tv.tv_usec = MAX_BLOCK_USECS; ssize_t got = 0; if(::select(pipe->fd + 1, &set, nullptr, nullptr, &tv) == 1) { if(FD_ISSET(pipe->fd, &set)) got = ::read(pipe->fd, buf, length); } if(got <= 0) { shutdown_pipe(); return 0; } bytes_read += got; return static_cast(got); } /** * Peek at the pipe contents */ size_t DataSource_Command::peek(byte[], size_t, size_t) const { if(end_of_data()) throw Invalid_State("DataSource_Command: Cannot peek when out of data"); throw Stream_IO_Error("Cannot peek/seek on a command pipe"); } /** * Check if we reached EOF */ bool DataSource_Command::end_of_data() const { return (pipe) ? false : true; } size_t DataSource_Command::get_bytes_read() const { return bytes_read; } /** * Return the Unix file descriptor of the pipe */ int DataSource_Command::fd() const { if(!pipe) return -1; return pipe->fd; } /** * Return a human-readable ID for this stream */ std::string DataSource_Command::id() const { return "Unix command: " + arg_list[0]; } /** * Create the pipe */ void DataSource_Command::create_pipe(const std::vector& paths) { bool found_something = false; for(size_t j = 0; j != paths.size(); j++) { const std::string full_path = paths[j] + "/" + arg_list[0]; if(::access(full_path.c_str(), X_OK) == 0) { found_something = true; break; } } if(!found_something) return; int pipe_fd[2]; if(::pipe(pipe_fd) != 0) return; pid_t pid = ::fork(); if(pid == -1) { ::close(pipe_fd[0]); ::close(pipe_fd[1]); } else if(pid > 0) { pipe = new pipe_wrapper(pipe_fd[0], pid); ::close(pipe_fd[1]); } else { if(dup2(pipe_fd[1], STDOUT_FILENO) == -1) ::exit(127); if(close(pipe_fd[0]) != 0 || close(pipe_fd[1]) != 0) ::exit(127); if(close(STDERR_FILENO) != 0) ::exit(127); do_exec(arg_list, paths); ::exit(127); } } /** * Shutdown the pipe */ void DataSource_Command::shutdown_pipe() { if(pipe) { pid_t reaped = waitpid(pipe->pid, nullptr, WNOHANG); if(reaped == 0) { kill(pipe->pid, SIGTERM); struct ::timeval tv; tv.tv_sec = 0; tv.tv_usec = KILL_WAIT; select(0, nullptr, nullptr, nullptr, &tv); reaped = ::waitpid(pipe->pid, nullptr, WNOHANG); if(reaped == 0) { ::kill(pipe->pid, SIGKILL); do reaped = ::waitpid(pipe->pid, nullptr, 0); while(reaped == -1); } } delete pipe; pipe = nullptr; } } /** * DataSource_Command Constructor */ DataSource_Command::DataSource_Command(const std::string& prog_and_args, const std::vector& paths) : MAX_BLOCK_USECS(100000), KILL_WAIT(10000) { arg_list = split_on(prog_and_args, ' '); if(arg_list.size() == 0) throw Invalid_Argument("DataSource_Command: No command given"); if(arg_list.size() > 5) throw Invalid_Argument("DataSource_Command: Too many args"); pipe = nullptr; create_pipe(paths); } /** * DataSource_Command Destructor */ DataSource_Command::~DataSource_Command() { if(!end_of_data()) shutdown_pipe(); } }