diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fb11540 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +# Xmake cache +.xmake/ +build/ + +# MacOS Cache +.DS_Store + + +.vscode \ No newline at end of file diff --git a/html/index.html b/html/index.html index 69d8de7..ff319bc 100644 --- a/html/index.html +++ b/html/index.html @@ -7,13 +7,28 @@ Document - - - - + + + - \ No newline at end of file + + + + diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..05ddb03 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,48 @@ +#include "stx-execpipe.h" +#include + +#include +#include + +static const std::vector links = { + "2gQ--SCEfmI"}; + +static const std::string YTDLP_BINARY = "/usr/bin/yt-dlp"; + +void DownloadFile() +{ + stx::ExecPipe ep; + + std::string link = "https://music.youtube.com/watch?v=" + links.front(); + + std::vector args = {YTDLP_BINARY, link.c_str(), "--extract-audio", "--audio-format", "vorbis", "-o", "%(id)s.%(ext)s"}; + + ep.add_exec(&args); + ep.run(); +} + +float GetDuration() +{ + stx::ExecPipe ep; + + std::string fileName = links.front() + ".ogg"; + + std::vector args = {"/usr/bin/ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", fileName}; + + ep.add_exec(&args); + std::string output; + ep.set_output_string(&output); + ep.run(); + + return std::stof(output); +} + +int main(int argc, char **argv) +{ + + float duration = GetDuration(); + + std::cout << "Durée : " << duration << std::endl; + + return 0; +} diff --git a/src/stx-execpipe.cpp b/src/stx-execpipe.cpp new file mode 100644 index 0000000..272295d --- /dev/null +++ b/src/stx-execpipe.cpp @@ -0,0 +1,1698 @@ +// -*- mode: c++; fill-column: 79 -*- + +/* + * STX Execution Pipe Library v0.7.0 + * Copyright (C) 2010 Timo Bingmann + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * This library 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +// this should be set by autoconf. check it again here if compiled separately. +#ifndef _FILE_OFFSET_BITS +#define _FILE_OFFSET_BITS 64 +#endif + +#include "stx-execpipe.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LOG_OUTPUT(msg, level) \ + do { \ + if (m_debug_level >= level) { \ + std::ostringstream oss; \ + oss << msg; \ + if (m_debug_output) \ + m_debug_output(oss.str().c_str()); \ + else \ + std::cout << oss.str() << std::endl; \ + } \ + } while (0) + +#define LOG_ERROR(msg) LOG_OUTPUT(msg, ExecPipe::DL_ERROR) +#define LOG_INFO(msg) LOG_OUTPUT(msg, ExecPipe::DL_INFO) +#define LOG_DEBUG(msg) LOG_OUTPUT(msg, ExecPipe::DL_DEBUG) +#define LOG_TRACE(msg) LOG_OUTPUT(msg, ExecPipe::DL_TRACE) + +namespace stx { + +#ifndef _STX_RINGBUFFER_H_ +#define _STX_RINGBUFFER_H_ + +/// namespace containing RingBuffer utility class +namespace { + +/** + * RingBuffer is a byte-oriented, pipe memory buffer which uses the underlying + * space in a circular fashion. + * + * The input stream is write()en into the buffer as blocks of bytes, while the + * buffer is reallocated with exponential growth as needed. + * + * The first unread byte can be accessed using bottom(). The number of unread + * bytes at the ring buffers bottom position is queried by bottomsize(). This + * may not match the total number of unread bytes as returned by size(). After + * processing the bytes at bottom(), the unread cursor may be moved using + * advance(). + * + * The ring buffer has the following two states. + *
+ * +------------------------------------------------------------------+
+ * | unused     |                 data   |               unused       |
+ * +------------+------------------------+----------------------------+
+ *              ^                        ^
+ *              m_bottom                 m_bottom+m_size
+ * 
+ * + * or + * + *
+ * +------------------------------------------------------------------+
+ * | more data  |                 unused               | data         |
+ * +------------+--------------------------------------+--------------+
+ *              ^                                      ^
+ *              m_bottom+m_size                        m_bottom
+ * 
+ * + * The size of the whole buffer is m_buffsize. + */ +class RingBuffer +{ +private: + /// pointer to allocated memory buffer + char* m_data; + + /// number of bytes allocated in m_data + unsigned int m_buffsize; + + /// number of unread bytes in ring buffer + unsigned int m_size; + + /// bottom pointer of unread area + unsigned int m_bottom; + +public: + /// Construct an empty ring buffer. + inline RingBuffer() + : m_data(NULL), + m_buffsize(0), m_size(0), m_bottom(0) + { + } + + /// Free the possibly used memory space. + inline ~RingBuffer() + { + if (m_data) free(m_data); + } + + /// Return the current number of unread bytes. + inline unsigned int size() const + { + return m_size; + } + + /// Return the current number of allocated bytes. + inline unsigned int buffsize() const + { + return m_buffsize; + } + + /// Reset the ring buffer to empty. + inline void clear() + { + m_size = m_bottom = 0; + } + + /** + * Return a pointer to the first unread element. Be warned that the buffer + * may not be linear, thus bottom()+size() might not be valid. You have to + * use bottomsize(). + */ + inline char* bottom() const + { + return m_data + m_bottom; + } + + /// Return the number of bytes available at the bottom() place. + inline unsigned int bottomsize() const + { + return (m_bottom + m_size > m_buffsize) + ? (m_buffsize - m_bottom) + : (m_size); + } + + /** + * Advance the internal read pointer n bytes, thus marking that amount of + * data as read. + */ + inline void advance(unsigned int n) + { + assert(m_size >= n); + m_bottom += n; + m_size -= n; + if (m_bottom >= m_buffsize) m_bottom -= m_buffsize; + } + + /** + * Write len bytes into the ring buffer at the top position, the buffer + * will grow if necessary. + */ + void write(const void *src, unsigned int len) + { + if (len == 0) return; + + if (m_buffsize < m_size + len) + { + // won't fit, we have to grow the buffer, we'll grow the buffer to + // twice the size. + + unsigned int newbuffsize = m_buffsize; + while (newbuffsize < m_size + len) + { + if (newbuffsize == 0) newbuffsize = 1024; + else newbuffsize = newbuffsize * 2; + } + + m_data = static_cast(realloc(m_data, newbuffsize)); + + if (m_bottom + m_size > m_buffsize) + { + // copy the ringbuffer's tail to the new buffer end, use memcpy + // here because there cannot be any overlapping area. + + unsigned int taillen = m_buffsize - m_bottom; + + memcpy(m_data + newbuffsize - taillen, + m_data + m_bottom, taillen); + + m_bottom = newbuffsize - taillen; + } + + m_buffsize = newbuffsize; + } + + // block now fits into the buffer somehow + + // check if the new memory fits into the middle space + if (m_bottom + m_size > m_buffsize) + { + memcpy(m_data + m_bottom + m_size - m_buffsize, src, len); + m_size += len; + } + else + { + // first fill up the buffer's tail, which has tailfit bytes room + unsigned int tailfit = m_buffsize - (m_bottom + m_size); + + if (tailfit >= len) + { + memcpy(m_data + m_bottom + m_size, src, len); + m_size += len; + } + else + { + // doesn't fit into the tail alone, we have to break it up + memcpy(m_data + m_bottom + m_size, src, tailfit); + memcpy(m_data, reinterpret_cast(src) + tailfit, + len - tailfit); + m_size += len; + } + } + } +}; + +} // namespace + +#endif // _STX_RINGBUFFER_H_ + +/** + * \brief Main library implementation (internal object) + * + * Implementation class for stx::ExecPipe. See the documentation of the + * front-end class for detailed information. + */ +class ExecPipeImpl +{ +private: + + /// reference counter + unsigned int m_refs; + +private: + + // *** Debugging Output *** + + /// currently set debug level + enum ExecPipe::DebugLevel m_debug_level; + + /// current debug line output function + void (*m_debug_output)(const char* line); + +public: + + /// Change the current debug level. The default is DL_ERROR. + void set_debug_level(enum ExecPipe::DebugLevel dl) + { + m_debug_level = dl; + } + + /// Change output function for debug messages. If set to NULL (the default) + /// the debug lines are printed to stdout. + void set_debug_output(void (*output)(const char *line)) + { + m_debug_output = output; + } + +private: + + /// Enumeration describing the currently set input or output stream type + enum StreamType + { + ST_NONE = 0, ///< no special redirection requested + ST_FD, ///< redirection to existing fd + ST_FILE, ///< redirection to file path + ST_STRING, ///< input/output directed by/to string + ST_OBJECT ///< input/output attached to program object + }; + + /// describes the currently set input stream type + StreamType m_input; + + // *** Input Stream *** + + /// for ST_FD the input fd given by the user. for ST_STRING and ST_FUNCTION + /// the pipe write fd of the parent process. + int m_input_fd; + + /// for ST_FILE the path of the input file. + const char* m_input_file; + + /// for ST_STRING a pointer to the user-supplied std::string input stream + /// object. + const std::string* m_input_string; + + /// for ST_STRING the current position in the input stream object. + std::string::size_type m_input_string_pos; + + /// for ST_OBJECT the input stream source object + PipeSource* m_input_source; + + /// for ST_OBJECT the input stream ring buffer + RingBuffer m_input_rbuffer; + + // *** Output Stream *** + + /// describes the currently set input stream type + StreamType m_output; + + /// for ST_FD the output fd given by the user. for ST_STRING and + /// ST_FUNCTION the pipe read fd of the parent process. + int m_output_fd; + + /// for ST_FILE the path of the output file. + const char* m_output_file; + + /// for ST_FILE the permission used in the open() call. + int m_output_file_mode; + + /// for ST_STRING a pointer to the user-supplied std::string output stream + /// object. + std::string* m_output_string; + + /// for ST_OBJECT the output stream source object + PipeSink* m_output_sink; + + // *** Pipe Stages *** + + /** + * Structure representing each stage in the pipe. Contains arguments, + * buffers and output variables. + */ + struct Stage + { + /// List of program and arguments copied from simple add_exec() calls. + std::vector args; + + /// Character pointer to program path called + const char* prog; + + /// Pointer to user list of program and arguments. + const std::vector* argsp; + + /// Pointer to environment list supplied by user. + const std::vector* envp; + + /// Pipe stage function object. + PipeFunction* func; + + /// Output stream buffer for function object. + RingBuffer outbuffer; + + // *** Exec Stages Variables *** + + /// Call execp() variants. + bool withpath; + + /// Pid of the running child process + pid_t pid; + + /// Return status of wait() after child exit. + int retstatus; + + /// File descriptor for child stdin. This is dup2()-ed to STDIN. + int stdin_fd; + + /// File descriptor for child stdout. This is dup2()-ed to STDOUT. + int stdout_fd; + + /// Constructor reseting all variables. + Stage() + : prog(NULL), argsp(NULL), envp(NULL), func(NULL), + withpath(false), pid(0), retstatus(0), + stdin_fd(-1), stdout_fd(-1) + { + } + }; + + /// typedef of list of pipe stages. + typedef std::vector stagelist_type; + + /// list of pipe stages. + stagelist_type m_stages; + + /// general buffer used for read() and write() calls. + char m_buffer[4096]; + +public: + + /// Create a new pipe implementation with zero reference counter. + ExecPipeImpl() + : m_refs(0), + m_debug_level(ExecPipe::DL_ERROR), + m_debug_output(NULL), + m_input(ST_NONE), + m_input_fd(-1), + m_output(ST_NONE), + m_output_fd(-1) + { + } + + /// Return writable reference to counter. + unsigned int& refs() + { + return m_refs; + } + + // *** Input Selectors *** + + ///@{ \name Input Selectors + + /** + * Assign an already opened file descriptor as input stream for the first + * exec stage. + */ + void set_input_fd(int fd) + { + assert(m_input == ST_NONE); + if (m_input != ST_NONE) return; + + m_input = ST_FD; + m_input_fd = fd; + } + + /** + * Assign a file as input stream source. This file will be opened read-only + * and read by the first exec stage. + */ + void set_input_file(const char* path) + { + assert(m_input == ST_NONE); + if (m_input != ST_NONE) return; + + m_input = ST_FILE; + m_input_file = path; + } + + /** + * Assign a std::string as input stream source. The contents of the string + * will be written to the first exec stage. The string object is not copied + * and must still exist when run() is called. + */ + void set_input_string(const std::string* input) + { + assert(m_input == ST_NONE); + if (m_input != ST_NONE) return; + + m_input = ST_STRING; + m_input_string = input; + m_input_string_pos = 0; + } + + /** + * Assign a PipeSource as input stream source. The object will be queried + * via the read() function for data which is then written to the first exec + * stage. + */ + void set_input_source(PipeSource* source) + { + assert(m_input == ST_NONE); + if (m_input != ST_NONE) return; + + m_input = ST_OBJECT; + m_input_source = source; + source->m_impl = this; + } + + ///@} + + /** + * Function called by PipeSource::write() to push data into the ring + * buffer. + */ + void input_source_write(const void* data, unsigned int datalen) + { + m_input_rbuffer.write(data, datalen); + } + + // *** Output Selectors *** + + ///@{ \name Output Selectors + + /** + * Assign an already opened file descriptor as output stream for the last + * exec stage. + */ + void set_output_fd(int fd) + { + assert(m_output == ST_NONE); + if (m_output != ST_NONE) return; + + m_output = ST_FD; + m_output_fd = fd; + } + + /** + * Assign a file as output stream destination. This file will be created or + * truncated write-only and written by the last exec stage. + */ + void set_output_file(const char* path, int mode = 0666) + { + assert(m_output == ST_NONE); + if (m_output != ST_NONE) return; + + m_output = ST_FILE; + m_output_file = path; + m_output_file_mode = mode; + } + + /** + * Assign a std::string as output stream destination. The output of the + * last exec stage will be stored as the contents of the string. The string + * object is not copied and must still exist when run() is called. + */ + void set_output_string(std::string* output) + { + assert(m_output == ST_NONE); + if (m_output != ST_NONE) return; + + m_output = ST_STRING; + m_output_string = output; + } + + /** + * Assign a PipeSink as output stream destination. The object will receive + * data via the process() function and is informed via eof() + */ + void set_output_sink(PipeSink* sink) + { + assert(m_output == ST_NONE); + if (m_output != ST_NONE) return; + + m_output = ST_OBJECT; + m_output_sink = sink; + } + + ///@} + + // *** Pipe Stages *** + + ///@{ \name Add Pipe Stages + + /** + * Return the number of pipe stages added. + */ + unsigned int size() const + { + return m_stages.size(); + } + + /** + * Add an exec() stage to the pipe with given arguments. Note that argv[0] + * is set to prog. + */ + void add_exec(const char* prog) + { + struct Stage newstage; + newstage.prog = prog; + newstage.args.push_back(prog); + m_stages.push_back(newstage); + } + + /** + * Add an exec() stage to the pipe with given arguments. Note that argv[0] + * is set to prog. + */ + void add_exec(const char* prog, const char* arg1) + { + struct Stage newstage; + newstage.prog = prog; + newstage.args.push_back(prog); + newstage.args.push_back(arg1); + m_stages.push_back(newstage); + } + + /** + * Add an exec() stage to the pipe with given arguments. Note that argv[0] + * is set to prog. + */ + void add_exec(const char* prog, const char* arg1, const char* arg2) + { + struct Stage newstage; + newstage.prog = prog; + newstage.args.push_back(prog); + newstage.args.push_back(arg1); + newstage.args.push_back(arg2); + m_stages.push_back(newstage); + } + + /** + * Add an exec() stage to the pipe with given arguments. Note that argv[0] + * is set to prog. + */ + void add_exec(const char* prog, const char* arg1, const char* arg2, const char* arg3) + { + struct Stage newstage; + newstage.prog = prog; + newstage.args.push_back(prog); + newstage.args.push_back(arg1); + newstage.args.push_back(arg2); + newstage.args.push_back(arg3); + m_stages.push_back(newstage); + } + + /** + * Add an exec() stage to the pipe with given arguments. The vector of + * arguments is not copied, so it must still exist when run() is + * called. Note that the program called is args[0]. + */ + void add_exec(const std::vector* args) + { + assert(args->size() > 0); + if (args->size() == 0) return; + + struct Stage newstage; + newstage.prog = (*args)[0].c_str(); + newstage.argsp = args; + m_stages.push_back(newstage); + } + + /** + * Add an execp() stage to the pipe with given arguments. The PATH variable + * is search for programs not containing a slash / character. Note that + * argv[0] is set to prog. + */ + void add_execp(const char* prog) + { + struct Stage newstage; + newstage.prog = prog; + newstage.args.push_back(prog); + newstage.withpath = true; + m_stages.push_back(newstage); + } + + /** + * Add an execp() stage to the pipe with given arguments. The PATH variable + * is search for programs not containing a slash / character. Note that + * argv[0] is set to prog. + */ + void add_execp(const char* prog, const char* arg1) + { + struct Stage newstage; + newstage.prog = prog; + newstage.args.push_back(prog); + newstage.args.push_back(arg1); + newstage.withpath = true; + m_stages.push_back(newstage); + } + + /** + * Add an execp() stage to the pipe with given arguments. The PATH variable + * is search for programs not containing a slash / character. Note that + * argv[0] is set to prog. + */ + void add_execp(const char* prog, const char* arg1, const char* arg2) + { + struct Stage newstage; + newstage.prog = prog; + newstage.args.push_back(prog); + newstage.args.push_back(arg1); + newstage.args.push_back(arg2); + newstage.withpath = true; + m_stages.push_back(newstage); + } + + /** + * Add an execp() stage to the pipe with given arguments. The PATH variable + * is search for programs not containing a slash / character. Note that + * argv[0] is set to prog. + */ + void add_execp(const char* prog, const char* arg1, const char* arg2, const char* arg3) + { + struct Stage newstage; + newstage.prog = prog; + newstage.args.push_back(prog); + newstage.args.push_back(arg1); + newstage.args.push_back(arg2); + newstage.args.push_back(arg3); + newstage.withpath = true; + m_stages.push_back(newstage); + } + + /** + * Add an execp() stage to the pipe with given arguments. The PATH variable + * is search for programs not containing a slash / character. The vector of + * arguments is not copied, so it must still exist when run() is + * called. Note that the program called is args[0]. + */ + void add_execp(const std::vector* args) + { + assert(args->size() > 0); + if (args->size() == 0) return; + + struct Stage newstage; + newstage.prog = (*args)[0].c_str(); + newstage.argsp = args; + newstage.withpath = true; + m_stages.push_back(newstage); + } + + /** + * Add an exece() stage to the pipe with the given arguments and + * environments. This is the most flexible exec() call. The vector of + * arguments and environment variables is not copied, so it must still + * exist when run() is called. The env vector pointer may be NULL, the args + * vector must not be NULL. The args[0] is _not_ override with path, so you + * can fake program name calls. + */ + void add_exece(const char* path, + const std::vector* argsp, + const std::vector* envp) + { + assert(path && argsp); + assert(argsp->size() > 0); + if (argsp->size() == 0) return; + + struct Stage newstage; + newstage.prog = path; + newstage.argsp = argsp; + newstage.envp = envp; + m_stages.push_back(newstage); + } + + /** + * Add a function stage to the pipe. This function object will be called in + * the parent process with data passing through the stage. See PipeFunction + * for more information. + */ + void add_function(PipeFunction* func) + { + assert(func); + if (!func) return; + + func->m_impl = this; + func->m_stageid = m_stages.size(); + + struct Stage newstage; + newstage.func = func; + m_stages.push_back(newstage); + } + + ///@} + + /** + * Function called by PipeSource::write() to push data into the ring + * buffer. + */ + void stage_function_write(unsigned int st, const void* data, unsigned int datalen) + { + assert(st < m_stages.size()); + + return m_stages[st].outbuffer.write(data, datalen); + } + + // *** Run Pipe *** + + /** + * Run the configured pipe sequence and wait for all children processes to + * complete. Returns a reference to *this for chaining. + * + * This function call should be wrapped into a try-catch block as it will + * throw() if a system call fails. + */ + void run(); + + // *** Inspection After Pipe Execution *** + + ///@{ \name Inspect Return Codes + + /** + * Get the return status of exec() stage's program run after pipe execution + * as indicated by wait(). + */ + int get_return_status(unsigned int stageid) const + { + assert(stageid < m_stages.size()); + assert(!m_stages[stageid].func); + + return m_stages[stageid].retstatus; + } + + /** + * Get the return code of exec() stage's program run after pipe execution, + * or -1 if the program terminated abnormally. + */ + int get_return_code(unsigned int stageid) const + { + assert(stageid < m_stages.size()); + assert(!m_stages[stageid].func); + + if (WIFEXITED(m_stages[stageid].retstatus)) + return WEXITSTATUS(m_stages[stageid].retstatus); + else + return -1; + } + + /** + * Get the signal of the abnormally terminated exec() stage's program run + * after pipe execution, or -1 if the program terminated normally. + */ + int get_return_signal(unsigned int stageid) const + { + assert(stageid < m_stages.size()); + assert(!m_stages[stageid].func); + + if (WIFSIGNALED(m_stages[stageid].retstatus)) + return WTERMSIG(m_stages[stageid].retstatus); + else + return -1; + } + + /** + * Return true if the return code of all exec() stages were zero. + */ + bool all_return_codes_zero() const + { + for (unsigned int i = 0; i < m_stages.size(); ++i) + { + if (m_stages[i].func) continue; + + if (get_return_code(i) != 0) + return false; + } + + return true; + } + + ///@} + +protected: + + // *** Helper Function for run() *** + + /// Transform arguments and launch an exec stage using the correct exec() + /// variant. + void exec_stage(const Stage& stage); + + /// Print all arguments of exec() call. + void print_exec(const std::vector& args); + + /// Safe close() call and output error if fd was already closed. + void sclose(int fd); +}; + +// --- ExecPipeImpl ----------------------------------------------------- // + +void ExecPipeImpl::print_exec(const std::vector& args) +{ + std::ostringstream oss; + oss << "Exec()"; + for (unsigned ai = 0; ai < args.size(); ++ai) + { + oss << " " << args[ai]; + } + LOG_INFO(oss.str()); +} + +void ExecPipeImpl::exec_stage(const Stage& stage) +{ + // select arguments vector + const std::vector& args = stage.argsp ? *stage.argsp : stage.args; + + // create const char*[] of prog and arguments for syscall. + + const char* cargs[args.size()+1]; + + for (unsigned ai = 0; ai < args.size(); ++ai) + { + cargs[ai] = args[ai].c_str(); + } + cargs[ args.size() ] = NULL; + + if (!stage.envp) + { + if (stage.withpath) + execvp(stage.prog, (char* const*)cargs); + else + execv(stage.prog, (char* const*)cargs); + } + else + { + // create envp const char*[] for syscall. + + const char* cenv[args.size()+1]; + + for (unsigned ei = 0; ei < stage.envp->size(); ++ei) + { + cenv[ei] = (*stage.envp)[ei].c_str(); + } + cenv[ stage.envp->size() ] = NULL; + + execve(stage.prog, (char* const*)cargs, (char* const*)cenv); + } + + LOG_ERROR("Error executing child process: " << strerror(errno)); +} + +void ExecPipeImpl::sclose(int fd) +{ + int r = close(fd); + + if (r != 0) { + LOG_ERROR("Could not correctly close fd: " << strerror(errno)); + } +} + +// --- ExecPipeImpl::run() ---------------------------------------------- // + +void ExecPipeImpl::run() +{ + if (m_stages.size() == 0) + throw(std::runtime_error("No stages to in exec pipe.")); + + // *** Phase 1: prepare all file descriptors ************************* // + + // set up input stream accordingly + switch(m_input) + { + case ST_NONE: + // no file change of file descriptor after fork. + m_stages[0].stdin_fd = -1; + break; + + case ST_STRING: + case ST_OBJECT: { + // create input pipe for strings and function objects. + int pipefd[2]; + + if (pipe(pipefd) != 0) + throw(std::runtime_error(std::string("Could not create an input pipe: ") + strerror(errno))); + + if (fcntl(pipefd[1], F_SETFL, O_NONBLOCK) != 0) + throw(std::runtime_error(std::string("Could not set non-block mode on input pipe: ") + strerror(errno))); + + m_input_fd = pipefd[1]; + m_stages[0].stdin_fd = pipefd[0]; + break; + } + case ST_FILE: { + // open input file + + int infd = open(m_input_file, O_RDONLY); + if (infd < 0) + throw(std::runtime_error(std::string("Could not open input file: ") + strerror(errno))); + + m_stages[0].stdin_fd = infd; + break; + } + case ST_FD: + // assign user-provided fd to first process + m_stages[0].stdin_fd = m_input_fd; + m_input_fd = -1; + break; + } + + // create pipes between exec stages + for (unsigned int i = 0; i < m_stages.size() - 1; ++i) + { + int pipefd[2]; + + if (pipe(pipefd) != 0) + throw(std::runtime_error(std::string("Could not create a stage pipe: ") + strerror(errno))); + + m_stages[i].stdout_fd = pipefd[1]; + m_stages[i+1].stdin_fd = pipefd[0]; + + if (m_stages[i].func) + { + if (fcntl(m_stages[i].stdout_fd, F_SETFL, O_NONBLOCK) != 0) + throw(std::runtime_error(std::string("Could not set non-block mode on a stage pipe: ") + strerror(errno))); + } + if (m_stages[i+1].func) + { + if (fcntl(m_stages[i+1].stdin_fd, F_SETFL, O_NONBLOCK) != 0) + throw(std::runtime_error(std::string("Could not set non-block mode on a stage pipe: ") + strerror(errno))); + } + } + + // set up output stream accordingly + switch(m_output) + { + case ST_NONE: + // no file change of file descriptor after fork. + m_stages.back().stdout_fd = -1; + break; + + case ST_STRING: + case ST_OBJECT: { + // create output pipe for strings and objects. + int pipefd[2]; + + if (pipe(pipefd) != 0) + throw(std::runtime_error(std::string("Could not create an output pipe: ") + strerror(errno))); + + if (fcntl(pipefd[0], F_SETFL, O_NONBLOCK) != 0) + throw(std::runtime_error(std::string("Could not set non-block mode on output pipe: ") + strerror(errno))); + + m_stages.back().stdout_fd = pipefd[1]; + m_output_fd = pipefd[0]; + break; + } + case ST_FILE: { + // create or truncate output file + + int outfd = open(m_output_file, O_WRONLY | O_CREAT | O_TRUNC, m_output_file_mode); + if (outfd < 0) + throw(std::runtime_error(std::string("Could not open output file: ") + strerror(errno))); + + m_stages.back().stdout_fd = outfd; + break; + } + case ST_FD: + // assign user-provided fd to last process + m_stages.back().stdout_fd = m_output_fd; + m_output_fd = -1; + break; + } + + // *** Phase 2: launch child processes ******************************* // + + for (unsigned int i = 0; i < m_stages.size(); ++i) + { + if (m_stages[i].func) continue; + + print_exec(m_stages[i].args); + + pid_t child = fork(); + if (child == 0) + { + // inside child process + + // move assigned file descriptors and close all others + if (m_input_fd >= 0) + sclose(m_input_fd); + + for (unsigned int j = 0; j < m_stages.size(); ++j) + { + if (i == j) + { + // dup2 file descriptors assigned for this stage as stdin and stdout + + if (m_stages[i].stdin_fd >= 0) + { + if (dup2(m_stages[i].stdin_fd, STDIN_FILENO) == -1) { + LOG_ERROR("Could not redirect file descriptor: " << strerror(errno)); + exit(255); + } + } + + if (m_stages[i].stdout_fd >= 0) + { + if (dup2(m_stages[i].stdout_fd, STDOUT_FILENO) == -1) { + LOG_ERROR("Could not redirect file descriptor: " << strerror(errno)); + exit(255); + } + } + } + else + { + // close file descriptors of other stages + + if (m_stages[j].stdin_fd >= 0) + sclose(m_stages[j].stdin_fd); + + if (m_stages[j].stdout_fd >= 0) + sclose(m_stages[j].stdout_fd); + } + } + + if (m_output_fd >= 0) + sclose(m_output_fd); + + // run program + exec_stage(m_stages[i]); + + exit(255); + } + + m_stages[i].pid = child; + } + + // parent process: close all unneeded file descriptors of exec stages. + + for (stagelist_type::const_iterator st = m_stages.begin(); + st != m_stages.end(); ++st) + { + if (st->func) continue; + + if (st->stdin_fd >= 0) + sclose(st->stdin_fd); + + if (st->stdout_fd >= 0) + sclose(st->stdout_fd); + } + + // *** Phase 3: run select() loop and process data ******************* // + + while(1) + { + // build file descriptor sets + + int max_fds = -1; + fd_set read_fds, write_fds; + + FD_ZERO(&read_fds); + FD_ZERO(&write_fds); + + if (m_input_fd >= 0) + { + if (m_input == ST_OBJECT) + { + assert(m_input_source); + + if (!m_input_rbuffer.size() && !m_input_source->poll() && !m_input_rbuffer.size()) + { + sclose(m_input_fd); + m_input_fd = -1; + + LOG_INFO("Closing input file descriptor: " << strerror(errno)); + } + else + { + FD_SET(m_input_fd, &write_fds); + if (max_fds < m_input_fd) max_fds = m_input_fd; + + LOG_DEBUG("Select on input file descriptor"); + } + } + else + { + FD_SET(m_input_fd, &write_fds); + if (max_fds < m_input_fd) max_fds = m_input_fd; + + LOG_DEBUG("Select on input file descriptor"); + } + } + + for (unsigned int i = 0; i < m_stages.size(); ++i) + { + if (!m_stages[i].func) continue; + + if (m_stages[i].stdin_fd >= 0) + { + FD_SET(m_stages[i].stdin_fd, &read_fds); + if (max_fds < m_stages[i].stdin_fd) max_fds = m_stages[i].stdin_fd; + + LOG_DEBUG("Select on stage input file descriptor"); + } + + if (m_stages[i].stdout_fd >= 0) + { + if (m_stages[i].outbuffer.size()) + { + FD_SET(m_stages[i].stdout_fd, &write_fds); + if (max_fds < m_stages[i].stdout_fd) max_fds = m_stages[i].stdout_fd; + + LOG_DEBUG("Select on stage output file descriptor"); + } + else if (m_stages[i].stdin_fd < 0 && !m_stages[i].outbuffer.size()) + { + sclose(m_stages[i].stdout_fd); + m_stages[i].stdout_fd = -1; + + LOG_INFO("Close stage output file descriptor"); + } + } + } + + if (m_output_fd >= 0) + { + FD_SET(m_output_fd, &read_fds); + if (max_fds < m_output_fd) max_fds = m_output_fd; + + LOG_DEBUG("Select on output file descriptor"); + } + + // issue select() call + + if (max_fds < 0) + break; + + int retval = select(max_fds+1, &read_fds, &write_fds, NULL, NULL); + if (retval < 0) + throw(std::runtime_error(std::string("Error during select() on file descriptors: ") + strerror(errno))); + + LOG_TRACE("select() on " << retval << " file descriptors: " << strerror(errno)); + + // handle file descriptors marked by select() in both sets + + if (m_input_fd >= 0 && FD_ISSET(m_input_fd, &write_fds)) + { + if (m_input == ST_STRING) + { + // write string data to first stdin file descriptor. + + assert(m_input_string); + assert(m_input_string_pos < m_input_string->size()); + + ssize_t wb; + + do + { + wb = write(m_input_fd, + m_input_string->data() + m_input_string_pos, + m_input_string->size() - m_input_string_pos); + + LOG_TRACE("Write on input fd: " << wb); + + if (wb < 0) + { + if (errno == EAGAIN || errno == EINTR) + { + } + else + { + LOG_DEBUG("Error writing to input file descriptor: " << strerror(errno)); + + sclose(m_input_fd); + m_input_fd = -1; + + LOG_INFO("Closing input file descriptor: " << strerror(errno)); + } + } + else if (wb > 0) + { + m_input_string_pos += wb; + + if (m_input_string_pos >= m_input_string->size()) + { + sclose(m_input_fd); + m_input_fd = -1; + + LOG_INFO("Closing input file descriptor: " << strerror(errno)); + break; + } + } + } while (wb > 0); + + } + else if (m_input == ST_OBJECT) + { + // write buffered data to first stdin file descriptor. + + ssize_t wb; + + do + { + wb = write(m_input_fd, + m_input_rbuffer.bottom(), + m_input_rbuffer.bottomsize()); + + LOG_TRACE("Write on input fd: " << wb); + + if (wb < 0) + { + if (errno == EAGAIN || errno == EINTR) + { + } + else + { + LOG_INFO("Error writing to input file descriptor: " << strerror(errno)); + + sclose(m_input_fd); + m_input_fd = -1; + + LOG_INFO("Closing input file descriptor: " << strerror(errno)); + } + } + else if (wb > 0) + { + m_input_rbuffer.advance(wb); + } + } while (wb > 0); + } + } + + if (m_output_fd >= 0 && FD_ISSET(m_output_fd, &read_fds)) + { + // read data from last stdout file descriptor + + ssize_t rb; + + do + { + errno = 0; + + rb = read(m_output_fd, + m_buffer, sizeof(m_buffer)); + + LOG_TRACE("Read on output fd: " << rb); + + if (rb <= 0) + { + if (rb == 0 && errno == 0) + { + // zero read indicates eof + + LOG_INFO("Closing output file descriptor: " << strerror(errno)); + + if (m_output == ST_OBJECT) + { + assert(m_output_sink); + m_output_sink->eof(); + } + + sclose(m_output_fd); + m_output_fd = -1; + } + else if (errno == EAGAIN || errno == EINTR) + { + } + else + { + LOG_ERROR("Error reading from output file descriptor: " << strerror(errno)); + } + } + else + { + if (m_output == ST_STRING) + { + assert(m_output_string); + m_output_string->append(m_buffer, rb); + } + else if (m_output == ST_OBJECT) + { + assert(m_output_sink); + m_output_sink->process(m_buffer, rb); + } + } + } while (rb > 0); + } + + for (unsigned int i = 0; i < m_stages.size(); ++i) + { + if (!m_stages[i].func) continue; + + if (m_stages[i].stdin_fd >= 0 && FD_ISSET(m_stages[i].stdin_fd, &read_fds)) + { + ssize_t rb; + + do + { + errno = 0; + + rb = read(m_stages[i].stdin_fd, + m_buffer, sizeof(m_buffer)); + + LOG_TRACE("Read on stage fd: " << rb); + + if (rb <= 0) + { + if (rb == 0 && errno == 0) + { + // zero read indicates eof + + LOG_INFO("Closing stage input file descriptor: " << strerror(errno)); + + m_stages[i].func->eof(); + + sclose(m_stages[i].stdin_fd); + m_stages[i].stdin_fd = -1; + } + else if (errno == EAGAIN || errno == EINTR) + { + } + else + { + LOG_ERROR("Error reading from stage input file descriptor: " << strerror(errno)); + } + } + else + { + m_stages[i].func->process(m_buffer, rb); + } + } while (rb > 0); + } + + if (m_stages[i].stdout_fd >= 0 && FD_ISSET(m_stages[i].stdout_fd, &write_fds)) + { + while (m_stages[i].outbuffer.size() > 0) + { + ssize_t wb = write(m_stages[i].stdout_fd, + m_stages[i].outbuffer.bottom(), + m_stages[i].outbuffer.bottomsize()); + + LOG_TRACE("Write on stage fd: " << wb); + + if (wb < 0) + { + if (errno == EAGAIN || errno == EINTR) + { + } + else + { + LOG_INFO("Error writing to stage output file descriptor: " << strerror(errno)); + } + break; + } + else if (wb > 0) + { + m_stages[i].outbuffer.advance(wb); + } + } + + if (m_stages[i].stdin_fd < 0 && !m_stages[i].outbuffer.size()) + { + LOG_INFO("Closing stage output file descriptor: " << strerror(errno)); + + sclose(m_stages[i].stdout_fd); + m_stages[i].stdout_fd = -1; + } + } + } + } + + // *** Phase 4: call wait() for all children processes *************** // + + unsigned int donepid = 0; + + for (unsigned int i = 0; i < m_stages.size(); ++i) + { + if (!m_stages[i].func) continue; + ++donepid; + } + + while (donepid != m_stages.size()) + { + int status; + int p = wait(&status); + + if (p < 0) + { + LOG_ERROR("Error calling wait(): " << strerror(errno)); + break; + } + + bool found = false; + + for (unsigned int i = 0; i < m_stages.size(); ++i) + { + if (p == m_stages[i].pid) + { + m_stages[i].retstatus = status; + + if (WIFEXITED(status)) + { + LOG_INFO("Finished exec() stage " << p << " with retcode " << WEXITSTATUS(status)); + } + else if (WIFSIGNALED(status)) + { + LOG_INFO("Finished exec() stage " << p << " with signal " << WTERMSIG(status)); + } + else + { + LOG_ERROR("Error in wait(): unknown return status for pid " << p); + } + + ++donepid; + found = true; + break; + } + } + + if (!found) + { + LOG_ERROR("Error in wait(): syscall returned an unknown child pid."); + } + } + + LOG_INFO("Finished running pipe."); +} + +// --- ExecPipe --------------------------------------------------------- // + +ExecPipe::ExecPipe() + : m_impl(new ExecPipeImpl) +{ + ++m_impl->refs(); +} + +ExecPipe::~ExecPipe() +{ + if (--m_impl->refs() == 0) + delete m_impl; +} + +ExecPipe::ExecPipe(const ExecPipe& ep) + : m_impl(ep.m_impl) +{ + ++m_impl->refs(); +} + +ExecPipe& ExecPipe::operator=(const ExecPipe& ep) +{ + if (this != &ep) + { + if (--m_impl->refs() == 0) + delete m_impl; + + m_impl = ep.m_impl; + ++m_impl->refs(); + } + return *this; +} + +void ExecPipe::set_debug_level(enum DebugLevel dl) +{ + return m_impl->set_debug_level(dl); +} + +void ExecPipe::set_debug_output(void (*output)(const char *line)) +{ + return m_impl->set_debug_output(output); +} + +void ExecPipe::set_input_fd(int fd) +{ + return m_impl->set_input_fd(fd); +} + +void ExecPipe::set_input_file(const char* path) +{ + return m_impl->set_input_file(path); +} + +void ExecPipe::set_input_string(const std::string* input) +{ + return m_impl->set_input_string(input); +} + +void ExecPipe::set_input_source(PipeSource* source) +{ + return m_impl->set_input_source(source); +} + +void ExecPipe::set_output_fd(int fd) +{ + return m_impl->set_output_fd(fd); +} + +void ExecPipe::set_output_file(const char* path, int mode) +{ + return m_impl->set_output_file(path, mode); +} + +void ExecPipe::set_output_string(std::string* output) +{ + return m_impl->set_output_string(output); +} + +void ExecPipe::set_output_sink(PipeSink* sink) +{ + return m_impl->set_output_sink(sink); +} + +unsigned int ExecPipe::size() const +{ + return m_impl->size(); +} + +void ExecPipe::add_exec(const char* prog) +{ + return m_impl->add_exec(prog); +} + +void ExecPipe::add_exec(const char* prog, const char* arg1) +{ + return m_impl->add_exec(prog, arg1); +} + +void ExecPipe::add_exec(const char* prog, const char* arg1, const char* arg2) +{ + return m_impl->add_exec(prog, arg1, arg2); +} + +void ExecPipe::add_exec(const char* prog, const char* arg1, const char* arg2, const char* arg3) +{ + return m_impl->add_exec(prog, arg1, arg2, arg3); +} + +void ExecPipe::add_exec(const std::vector* args) +{ + return m_impl->add_exec(args); +} + +void ExecPipe::add_execp(const char* prog) +{ + return m_impl->add_execp(prog); +} + +void ExecPipe::add_execp(const char* prog, const char* arg1) +{ + return m_impl->add_execp(prog, arg1); +} + +void ExecPipe::add_execp(const char* prog, const char* arg1, const char* arg2) +{ + return m_impl->add_execp(prog, arg1, arg2); +} + +void ExecPipe::add_execp(const char* prog, const char* arg1, const char* arg2, const char* arg3) +{ + return m_impl->add_execp(prog, arg1, arg2, arg3); +} + +void ExecPipe::add_execp(const std::vector* args) +{ + return m_impl->add_execp(args); +} + +void ExecPipe::add_exece(const char* path, + const std::vector* args, + const std::vector* env) +{ + return m_impl->add_exece(path, args, env); +} + +void ExecPipe::add_function(PipeFunction* func) +{ + return m_impl->add_function(func); +} + +ExecPipe& ExecPipe::run() +{ + m_impl->run(); + return *this; +} + +int ExecPipe::get_return_status(unsigned int stageid) const +{ + return m_impl->get_return_status(stageid); +} + +int ExecPipe::get_return_code(unsigned int stageid) const +{ + return m_impl->get_return_code(stageid); +} + +int ExecPipe::get_return_signal(unsigned int stageid) const +{ + return m_impl->get_return_signal(stageid); +} + +bool ExecPipe::all_return_codes_zero() const +{ + return m_impl->all_return_codes_zero(); +} + +// --- PipeSource ------------------------------------------------------- // + +PipeSource::PipeSource() + : m_impl(NULL) +{ +} + +void PipeSource::write(const void* data, unsigned int datalen) +{ + assert(m_impl); + return m_impl->input_source_write(data, datalen); +} + +// --- PipeFunction ----------------------------------------------------- // + +PipeFunction::PipeFunction() + : m_impl(NULL), m_stageid(0) +{ +} + +void PipeFunction::write(const void* data, unsigned int datalen) +{ + assert(m_impl); + return m_impl->stage_function_write(m_stageid, data, datalen); +} + +// ---------------------------------------------------------------------- // + +} // namespace stx \ No newline at end of file diff --git a/src/stx-execpipe.h b/src/stx-execpipe.h new file mode 100644 index 0000000..fd1defd --- /dev/null +++ b/src/stx-execpipe.h @@ -0,0 +1,367 @@ +// -*- mode: c++; fill-column: 79 -*- + +/* + * STX Execution Pipe Library v0.7.0 + * Copyright (C) 2010 Timo Bingmann + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * This library 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _STX_EXECPIPE_H_ +#define _STX_EXECPIPE_H_ + +#include +#include + +/// STX - Some Template Extensions namespace +namespace stx { + +/** + * Abstract class used as an input stream source for an ExecPipe. + * + * Derived classes can be used in an ExecPipe to generate an input stream + * source. Data generated by this class is written to the first stage of the + * pipe. + * + * When data is needed by the pipe the function poll() is called. This pure + * virtual function must generate data and push it into a buffer using the + * write() function. The input stream is terminated when poll() returns false. + */ +class PipeSource +{ +private: + /// pointer to associated pipe. filled by ExecPipe::add_input_source() + class ExecPipeImpl* m_impl; + + /// association to the pipe implementation for write access to m_impl. + friend class ExecPipeImpl; + +public: + /// Constructor which clears m_impl. + PipeSource(); + + /// Poll the input source for new data. The input stream is closed when + /// this function returns false, otherwise it will be polled again. + virtual bool poll() = 0; + + /// Write input data to the first stage via a buffer. + void write(const void* data, unsigned int datalen); +}; + +/** + * Abstract class used as an output stream source for an ExecPipe. + * + * Derived classes can be attached to the end of an execution pipe. It will + * receive all data outputted by the final pipe stage. + * + * Data read from the final or preceding stage is passed to the class via + * process(). When the final stage closes the pipe, the function eof() is + * called. + */ +class PipeSink +{ +public: + /// Pure virtual function which receives the output stream as read from the + /// final or preceding pipe stage. + virtual void process(const void* data, unsigned int datalen) = 0; + + /// Pure virtual function called when the final or preceding pipe stage + /// finishes. + virtual void eof() = 0; +}; + +/** + * Abstract class used as an intermediate pipe stage between executed + * processes. + * + * Derived classes can be inserted into an execution pipe between two + * externally executed processes. It will receive all data from the preceding + * pipe stage and after processing it may forward output to the next pipe + * stage. + * + * The class is derived from PipeSink and receives data from the preceding + * stage via the inherited functions process() and also the eof() + * signal. Usually process() will perform some action on the data and then + * forward the resulting data block to the next pipe stage via write(). + */ +class PipeFunction : public PipeSink +{ +private: + /// pointer to associated pipe filled by ExecPipe::add_function() + class ExecPipeImpl* m_impl; + + /// pipe stage identifier + unsigned int m_stageid; + + /// association to the pipe implementation for write access to m_impl. + friend class ExecPipeImpl; + +public: + /// Constructor which clears m_impl and m_stageid. + PipeFunction(); + + /// Write input data to the next pipe stage via a buffer. + void write(const void* data, unsigned int datalen); +}; + +/** + * \brief Main library interface (reference counted pointer) + * + * The ExecPipe class is the main interface to the library. It is a reference + * counted pointer implementation, so you can easily copy and pass around + * without duplicating the inside object. See the \ref index "main page" for + * detailed information and examples. + */ +class ExecPipe +{ +protected: + /// reference-counted pointer implementation + class ExecPipeImpl* m_impl; + +public: + /// Construct a new uninitialize execution pipe. + ExecPipe(); + + /// Release reference to execution pipe. + ~ExecPipe(); + + /// Copy-constructor creates a new reference to the _same_ pipe. + ExecPipe(const ExecPipe& ep); + + /// Assignment operator creates a new reference to the right pipe. + ExecPipe& operator=(const ExecPipe& ep); + + // *** Debugging Output *** + + /// Enumeration for ascending debug levels. + enum DebugLevel + { + DL_ERROR=0, ///< error reporting is always active. shows failed syscalls. + DL_INFO=1, ///< info reports at important points during pipe run. + DL_DEBUG=2, ///< debug shows information about select() calls. + DL_TRACE=3 ///< trace lists lots of info about read() and write() calls. + }; + + /// Change the current debug level. The default is DL_ERROR. + void set_debug_level(enum DebugLevel dl); + + /// Change output function for debug messages. If set to NULL (the default) + /// the debug lines are printed to stdout. + void set_debug_output(void (*output)(const char *line)); + + // *** Input Selectors *** + + ///@{ \name Input Selectors + + /** + * Assign an already opened file descriptor as input stream for the first + * exec stage. + */ + void set_input_fd(int fd); + + /** + * Assign a file as input stream source. This file will be opened read-only + * and read by the first exec stage. + */ + void set_input_file(const char* path); + + /** + * Assign a std::string as input stream source. The contents of the string + * will be written to the first exec stage. The string object is not copied + * and must still exist when run() is called. + */ + void set_input_string(const std::string* input); + + /** + * Assign a PipeSource as input stream source. The object will be queried + * via the read() function for data which is then written to the first exec + * stage. + */ + void set_input_source(PipeSource* source); + + ///@} + + // *** Output Selectors *** + + ///@{ \name Output Selectors + + /** + * Assign an already opened file descriptor as output stream for the last + * exec stage. + */ + void set_output_fd(int fd); + + /** + * Assign a file as output stream destination. This file will be created or + * truncated write-only and written by the last exec stage. + */ + void set_output_file(const char* path, int mode = 0666); + + /** + * Assign a std::string as output stream destination. The output of the + * last exec stage will be stored as the contents of the string. The string + * object is not copied and must still exist when run() is called. + */ + void set_output_string(std::string* output); + + /** + * Assign a PipeSink as output stream destination. The object will receive + * data via the process() function and is informed via eof() + */ + void set_output_sink(PipeSink* sink); + + ///@} + + // *** Pipe Stages *** + + ///@{ \name Add Pipe Stages + + /** + * Return the number of pipe stages added. + */ + unsigned int size() const; + + /** + * Add an exec() stage to the pipe with given arguments. Note that argv[0] + * is set to prog. + */ + void add_exec(const char* prog); + + /** + * Add an exec() stage to the pipe with given arguments. Note that argv[0] + * is set to prog. + */ + void add_exec(const char* prog, const char* arg1); + + /** + * Add an exec() stage to the pipe with given arguments. Note that argv[0] + * is set to prog. + */ + void add_exec(const char* prog, const char* arg1, const char* arg2); + + /** + * Add an exec() stage to the pipe with given arguments. Note that argv[0] + * is set to prog. + */ + void add_exec(const char* prog, const char* arg1, const char* arg2, const char* arg3); + + /** + * Add an exec() stage to the pipe with given arguments. The vector of + * arguments is not copied, so it must still exist when run() is + * called. Note that the program called is args[0]. + */ + void add_exec(const std::vector* args); + + + /** + * Add an execp() stage to the pipe with given arguments. The PATH variable + * is search for programs not containing a slash / character. Note that + * argv[0] is set to prog. + */ + void add_execp(const char* prog); + + /** + * Add an execp() stage to the pipe with given arguments. The PATH variable + * is search for programs not containing a slash / character. Note that + * argv[0] is set to prog. + */ + void add_execp(const char* prog, const char* arg1); + + /** + * Add an execp() stage to the pipe with given arguments. The PATH variable + * is search for programs not containing a slash / character. Note that + * argv[0] is set to prog. + */ + void add_execp(const char* prog, const char* arg1, const char* arg2); + + /** + * Add an execp() stage to the pipe with given arguments. The PATH variable + * is search for programs not containing a slash / character. Note that + * argv[0] is set to prog. + */ + void add_execp(const char* prog, const char* arg1, const char* arg2, const char* arg3); + + /** + * Add an execp() stage to the pipe with given arguments. The PATH variable + * is search for programs not containing a slash / character. The vector of + * arguments is not copied, so it must still exist when run() is + * called. Note that the program called is args[0]. + */ + void add_execp(const std::vector* args); + + /** + * Add an exece() stage to the pipe with the given arguments and + * environments. This is the most flexible exec() call. The vector of + * arguments and environment variables is not copied, so it must still + * exist when run() is called. The args[0] is _not_ override with path, so + * you can fake program name calls. + */ + void add_exece(const char* path, + const std::vector* args, + const std::vector* env); + + /** + * Add a function stage to the pipe. This function object will be called in + * the parent process with data passing through the stage. See PipeFunction + * for more information. + */ + void add_function(PipeFunction* func); + + ///@} + + // *** Run Pipe *** + + /** + * Run the configured pipe sequence and wait for all children processes to + * complete. Returns a reference to *this for chaining. + * + * This function call should be wrapped into a try-catch block as it will + * throw() if a system call fails. + */ + ExecPipe& run(); + + // *** Inspection After Pipe Execution *** + + ///@{ \name Inspect Return Codes + + /** + * Get the return status of exec() stage's program run after pipe execution + * as indicated by wait(). + */ + int get_return_status(unsigned int stageid) const; + + /** + * Get the return code of exec() stage's program run after pipe execution, + * or -1 if the program terminated abnormally. + */ + int get_return_code(unsigned int stageid) const; + + /** + * Get the signal of the abnormally terminated exec() stage's program run + * after pipe execution, or -1 if the program terminated normally. + */ + int get_return_signal(unsigned int stageid) const; + + /** + * Return true if the return code of all exec() stages were zero. + */ + bool all_return_codes_zero() const; + + ///@} +}; + +} // namespace stx + +#endif // _STX_EXECPIPE_H_ \ No newline at end of file diff --git a/xmake.lua b/xmake.lua new file mode 100644 index 0000000..eda0c87 --- /dev/null +++ b/xmake.lua @@ -0,0 +1,78 @@ +add_rules("mode.debug", "mode.release") + +set_languages("c++11") + +target("Songdle") + set_kind("binary") + add_files("src/*.cpp") + set_rundir(".") + +-- +-- If you want to known more usage about xmake, please see https://xmake.io +-- +-- ## FAQ +-- +-- You can enter the project directory firstly before building project. +-- +-- $ cd projectdir +-- +-- 1. How to build project? +-- +-- $ xmake +-- +-- 2. How to configure project? +-- +-- $ xmake f -p [macosx|linux|iphoneos ..] -a [x86_64|i386|arm64 ..] -m [debug|release] +-- +-- 3. Where is the build output directory? +-- +-- The default output directory is `./build` and you can configure the output directory. +-- +-- $ xmake f -o outputdir +-- $ xmake +-- +-- 4. How to run and debug target after building project? +-- +-- $ xmake run [targetname] +-- $ xmake run -d [targetname] +-- +-- 5. How to install target to the system directory or other output directory? +-- +-- $ xmake install +-- $ xmake install -o installdir +-- +-- 6. Add some frequently-used compilation flags in xmake.lua +-- +-- @code +-- -- add debug and release modes +-- add_rules("mode.debug", "mode.release") +-- +-- -- add macro definition +-- add_defines("NDEBUG", "_GNU_SOURCE=1") +-- +-- -- set warning all as error +-- set_warnings("all", "error") +-- +-- -- set language: c99, c++11 +-- set_languages("c99", "c++11") +-- +-- -- set optimization: none, faster, fastest, smallest +-- set_optimize("fastest") +-- +-- -- add include search directories +-- add_includedirs("/usr/include", "/usr/local/include") +-- +-- -- add link libraries and search directories +-- add_links("tbox") +-- add_linkdirs("/usr/local/lib", "/usr/lib") +-- +-- -- add system link libraries +-- add_syslinks("z", "pthread") +-- +-- -- add compilation and link flags +-- add_cxflags("-stdnolib", "-fno-strict-aliasing") +-- add_ldflags("-L/usr/local/lib", "-lpthread", {force = true}) +-- +-- @endcode +-- +