diff options
author | Karen Arutyunov <karen@codesynthesis.com> | 2021-09-28 19:24:31 +0300 |
---|---|---|
committer | Karen Arutyunov <karen@codesynthesis.com> | 2021-09-28 20:29:59 +0300 |
commit | df1ef68cd8e8582724ce1192bfc202e0b9aeaf0c (patch) | |
tree | b731ca4c68e60c00c7e7d499dbf4868ee7b71f44 /libbutl/process.mxx | |
parent | 7a4fc37f264cdb67f2f83fa92703c869215bbc86 (diff) |
Get rid of C++ modules related code and rename *.mxx files to *.hxx
Diffstat (limited to 'libbutl/process.mxx')
-rw-r--r-- | libbutl/process.mxx | 855 |
1 files changed, 0 insertions, 855 deletions
diff --git a/libbutl/process.mxx b/libbutl/process.mxx deleted file mode 100644 index 0677525..0000000 --- a/libbutl/process.mxx +++ /dev/null @@ -1,855 +0,0 @@ -// file : libbutl/process.mxx -*- C++ -*- -// license : MIT; see accompanying LICENSE file - -#ifndef __cpp_modules_ts -#pragma once -#endif - -#ifndef _WIN32 -# include <sys/types.h> // pid_t -#endif - -#include <cassert> - -#ifndef __cpp_lib_modules_ts -#include <string> -#include <vector> -#include <chrono> -#include <ostream> -#include <cstddef> // size_t -#include <cstdint> // uint32_t -#include <system_error> - -#include <utility> // move(), forward(), index_sequence -#endif - -// Other includes. - -#ifdef __cpp_modules_ts -export module butl.process; -#ifdef __cpp_lib_modules_ts -import std.core; -import std.io; -#endif -import butl.path; -import butl.optional; -import butl.fdstream; // auto_fd, fdpipe -import butl.vector_view; -import butl.small_vector; -#else -#include <libbutl/path.mxx> -#include <libbutl/optional.mxx> -#include <libbutl/fdstream.mxx> -#include <libbutl/vector-view.mxx> -#include <libbutl/small-vector.mxx> -#endif - -#include <libbutl/export.hxx> - -LIBBUTL_MODEXPORT namespace butl -{ - struct process_error: std::system_error - { - const bool child; - - process_error (int e, bool child = false) - : system_error (e, std::generic_category ()), child (child) {} - -#ifdef _WIN32 - process_error (const std::string& d, int fallback_errno_code = 0) - : system_error (fallback_errno_code, std::system_category (), d), - child (false) {} -#endif - }; - - struct process_child_error: process_error - { - explicit - process_child_error (int e): process_error (e, true) {} - }; - - // Process arguments (i.e., the command line). The first must be an - // executable name and the last element should be NULL. Can also be the - // multi-process piped command line (see process::print() for details). - // - struct process_args - { - const char* const* argv; - std::size_t argc; - }; - - // A process executable has three paths: initial, recall, and effective. - // Initial is the original "command" that you specify in argv[0] and on - // POSIX that's what ends up in the child's argv[0]. But not on Windows. On - // Windows the command is first searched for in the parent executable's - // directory and if found then that's what should end up in child's argv[0]. - // So this is the recall path. It is called recall because this is what the - // caller of the parent process will be able to execute if you printed the - // command line (provided you haven't changed the CWD). Finally, effective - // is the absolute path to the executable that will include the directory - // part if found in PATH, the .exe extension if one is missing, etc. - // - // As an example, let's say we run foo\foo.exe that itself spawns bar which - // is found as foo\bar.exe. The paths will then be: - // - // initial: bar - // recall: foo\bar - // effective: c:\...\foo\bar.exe - // - // In most cases, at least on POSIX, the first two paths will be the same. - // As an optimization, if the recall path is empty, then it means it is the - // same as initial. Similarly, if the effective path is empty then, it is - // the same as recall (and if that is empty, as initial). - // - // Note that the call to path_search() below adjust args[0] to point to the - // recall path which brings up lifetime issues. To address this this class - // also implements an optional RAII-based auto-restore of args[0] to its - // initial value. - // - class process_path - { - public: - const char* initial = nullptr; - path recall; - path effect; - - // Handle empty recall/effect. - // - const char* recall_string () const; - const char* effect_string () const; - - bool - empty () const - { - return (initial == nullptr || *initial == '\0') && - recall.empty () && effect.empty (); - } - - // Clear recall making it the same as effective. - // - void - clear_recall (); - - // Make all three paths the same. - // - explicit - process_path (path effect); - - process_path (const char* initial, path&& recall, path&& effect); - process_path () = default; - - // Moveable-only type. - // - process_path (process_path&&); - process_path& operator= (process_path&&); - - process_path (const process_path&) = delete; - process_path& operator= (const process_path&) = delete; - - ~process_path (); - - // Manual copying. Should not use args[0] RAII. See path_search() for the - // init semantics. - // - process_path (const process_path&, bool init); - - private: - friend class process; - const char** args0_ = nullptr; - }; - - // Process exit information. - // - struct LIBBUTL_SYMEXPORT process_exit - { - // Status type is the raw exit value as returned by GetExitCodeProcess() - // (NTSTATUS value that represents exit or error codes; MSDN refers to the - // error code as "value of the exception that caused the termination") or - // waitpid(1). Code type is the return value if the process exited - // normally. - // -#ifndef _WIN32 - using status_type = int; - using code_type = std::uint8_t; -#else - using status_type = std::uint32_t; // Win32 DWORD - using code_type = std::uint16_t; // Win32 WORD -#endif - - status_type status; - - process_exit () = default; - - explicit - process_exit (code_type); - - enum as_status_type {as_status}; - process_exit (status_type s, as_status_type): status (s) {} - - // Return false if the process exited abnormally. - // - bool - normal () const; - - // C/C++ don't apply constraints on program exit code other than it being - // of type int. - // - // POSIX specifies that only the least significant 8 bits shall be - // available from wait() and waitpid(); the full value shall be available - // from waitid() (read more at _Exit, _exit Open Group spec). - // - // While the Linux man page for waitid() doesn't mention any deviations - // from the standard, the FreeBSD implementation (as of version 11.0) only - // returns 8 bits like the other wait*() calls. - // - // Windows supports 32-bit exit codes. - // - // Note that in shells some exit values can have special meaning so using - // them can be a source of confusion. For bash values in the [126, 255] - // range are such a special ones (see Appendix E, "Exit Codes With Special - // Meanings" in the Advanced Bash-Scripting Guide). - // - // So [0, 125] appears to be the usable exit code range. - // - code_type - code () const; - - explicit operator bool () const {return normal () && code () == 0;} - - // Abnormal termination information. - // - - // Return the signal number that caused the termination or 0 if no such - // information is available. - // - int - signal () const; - - // Return true if the core file was generated. - // - bool - core () const; - - // Return a description of the reason that caused the process to terminate - // abnormally. On POSIX this is the signal name, on Windows -- the summary - // produced from the corresponding error identifier defined in ntstatus.h. - // - std::string - description () const; - }; - - // Canonical exit status description: - // - // "terminated abnormally: <...> (core dumped)" - // "exited with code <...>" - // - // So you would normally do: - // - // cerr << "process " << args[0] << " " << *pr.exit << endl; - // - LIBBUTL_SYMEXPORT std::string - to_string (process_exit); - - inline std::ostream& - operator<< (std::ostream& os, process_exit pe) - { - return os << to_string (pe); - } - - class LIBBUTL_SYMEXPORT process - { - public: -#ifndef _WIN32 - using handle_type = pid_t; - using id_type = pid_t; -#else - using handle_type = void*; // Win32 HANDLE - using id_type = std::uint32_t; // Win32 DWORD -#endif - - // Start another process using the specified command line. The default - // values to the in, out and err arguments indicate that the child process - // should inherit the parent process stdin, stdout, and stderr, - // respectively. If -1 is passed instead, then the corresponding child - // process descriptor is connected (via a pipe) to out_fd for stdin, - // in_ofd for stdout, and in_efd for stderr (see data members below). If - // -2 is passed, then the corresponding child process descriptor is - // replaced with the null device descriptor (e.g., /dev/null). This - // results in the child process not being able to read anything from stdin - // (gets immediate EOF) and all data written to stdout/stderr being - // discarded. - // - // On Windows parent process pipe descriptors are set to text mode to be - // consistent with the default (text) mode of standard file descriptors of - // the child process. When reading in the text mode the sequence of 0xD, - // 0xA characters is translated into the single OxA character and 0x1A is - // interpreted as EOF. When writing in the text mode the OxA character is - // translated into the 0xD, 0xA sequence. Use the fdmode() function to - // change the mode, if required. - // - // Instead of passing -1, -2 or the default value, you can also pass your - // own descriptors. Note, however, that in this case they are not closed by - // the parent. So you should do this yourself, if required. For example, - // to redirect the child process stdout to stderr, you can do: - // - // process p (..., 0, 2); - // - // The cwd argument allows to change the current working directory of the - // child process. NULL and empty arguments are ignored. - // - // The envvars argument allows to set and unset environment variables in - // the child process. If not NULL, it must contain strings in the - // "name=value" (set) or "name" (unset) forms and be terminated with - // NULL. Note that all other variables are inherited from the parent - // process. - // - // Throw process_error if anything goes wrong. Note that some of the - // exceptions (e.g., if exec() failed) can be thrown in the child - // version of us (as process_child_error). - // - // Note that the versions without the the process_path argument may - // temporarily change args[0] (see path_search() for details). - // - process (const char* [], - int in = 0, int out = 1, int err = 2, - const char* cwd = nullptr, - const char* const* envvars = nullptr); - - process (const process_path&, const char* [], - int in = 0, int out = 1, int err = 2, - const char* cwd = nullptr, - const char* const* envvars = nullptr); - - // If the descriptors are pipes that you have created, then you should use - // this constructor instead to communicate this information. - // - // For generality, if the "other" end of the pipe is -1, then assume this - // is not a pipe. - // - struct pipe - { - int in = -1; - int out = -1; - - pipe () = default; - pipe (int i, int o): in (i), out (o) {} - - explicit - pipe (const fdpipe& p): in (p.in.get ()), out (p.out.get ()) {} - }; - - process (const process_path&, const char* [], - pipe in, pipe out, pipe err, - const char* cwd = nullptr, - const char* const* envvars = nullptr); - - // The "piping" constructor, for example: - // - // process lhs (..., 0, -1); // Redirect stdout to a pipe. - // process rhs (..., lhs); // Redirect stdin to lhs's pipe. - // - // rhs.wait (); // Wait for last first. - // lhs.wait (); - // - process (const char* [], - process&, int out = 1, int err = 2, - const char* cwd = nullptr, - const char* const* envvars = nullptr); - - process (const process_path&, const char* [], - process&, int out = 1, int err = 2, - const char* cwd = nullptr, - const char* const* envvars = nullptr); - - // Wait for the process to terminate. Return true if the process - // terminated normally and with the zero exit code. Unless ignore_error - // is true, throw process_error if anything goes wrong. This function can - // be called multiple times with subsequent calls simply returning the - // status. - // - bool - wait (bool ignore_errors = false); - - // Return the same result as wait() if the process has already terminated - // and nullopt otherwise. - // - optional<bool> - try_wait (); - - // Wait for the process to terminate for up to the specified time - // duration. Return the same result as wait() if the process has - // terminated in this timeframe and nullopt otherwise. - // - template <typename R, typename P> - optional<bool> - timed_wait (const std::chrono::duration<R, P>&); - - // Note that the destructor will wait for the process but will ignore - // any errors and the exit status. - // - ~process () {if (handle != 0) wait (true);} - - // Process termination. - // - - // Send SIGKILL to the process on POSIX and call TerminateProcess() with - // DBG_TERMINATE_PROCESS exit code on Windows. Noop for an already - // terminated process. - // - // Note that if the process is killed, it terminates as if it has called - // abort() (functions registered with atexit() are not called, etc). - // - // Also note that on Windows calling this function for a terminating - // process results in the EPERM process_error exception. - // - void - kill (); - - // Send SIGTERM to the process on POSIX and call kill() on Windows (where - // there is no general way to terminate a console process gracefully). - // Noop for an already terminated process. - // - void - term (); - - // Moveable-only type. - // - process (process&&); - process& operator= (process&&); - - process (const process&) = delete; - process& operator= (const process&) = delete; - - // Create an empty or "already terminated" process. By default the - // termination status is unknown but you can change that. - // - explicit - process (optional<process_exit> = nullopt); - - // Resolve process' paths based on the initial path in args0. If recall - // differs from initial, adjust args0 to point to the recall path. If - // resolution fails, throw process_error. Normally, you will use this - // function like this: - // - // const char* args[] = {"foo", ..., nullptr}; - // - // process_path pp (process::path_search (args[0])) - // - // ... // E.g., print args[0]. - // - // process p (pp, args); - // - // You can also specify the fallback directory which will be tried last. - // This, for example, can be used to implement the Windows "search in the - // parent executable's directory" semantics across platforms. - // - // If path_only is true then only search in the PATH environment variable - // (or in CWD if there is a directory component) ignoring other places - // (like calling process' directory and, gasp, CWD on Windows). - // - // If the paths argument is not NULL, search in this list of paths rather - // than in the PATH environment variable. Note that in this case you may - // want to clear the recall path (process_path::clear_recall()) since the - // path won't be "recallable" (unless you've passed a cache of the PATH - // environment variable or some such). - // - static process_path - path_search (const char*& args0, - const dir_path& fallback = dir_path (), - bool path_only = false, - const char* paths = nullptr); - - // This version is primarily useful when you want to pre-search the - // executable before creating the args[] array. In this case you will - // use the recall path for args[0]. - // - // The init argument determines whether to initialize the initial path to - // the shallow copy of file. If it is true, then initial is the same as - // file and recall is either empty or contain a different path. If it is - // false then initial contains a shallow copy of recall, and recall is - // either a different path or a deep copy of file. Normally you don't care - // about initial once you got recall and the main reason to pass true to - // this argument is to save a copy (since initial and recall are usually - // the same). - // - static process_path - path_search (const char* file, bool init, - const dir_path& = dir_path (), - bool = false, - const char* = nullptr); - - static process_path - path_search (const std::string&, bool, - const dir_path& = dir_path (), - bool = false, - const char* = nullptr); - - static process_path - path_search (const path&, bool, - const dir_path& = dir_path (), - bool = false, - const char* = nullptr); - - // As above but if not found return empty process_path instead of - // throwing. - // - static process_path - try_path_search (const char*, bool, - const dir_path& = dir_path (), - bool = false, - const char* = nullptr); - - static process_path - try_path_search (const std::string&, bool, - const dir_path& = dir_path (), - bool = false, - const char* = nullptr); - - static process_path - try_path_search (const path&, bool, - const dir_path& = dir_path (), - bool = false, - const char* = nullptr); - - // Print process commmand line. If the number of elements is specified, - // then it will print the piped multi-process command line, if present. - // In this case, the expected format is as follows: - // - // name1 arg arg ... nullptr - // name2 arg arg ... nullptr - // ... - // nameN arg arg ... nullptr nullptr - // - static void - print (std::ostream&, const char* const args[], size_t n = 0); - - // Quote and escape the specified command line argument. If batch is true - // then also quote the equal (`=`), comma (`,`) and semicolon (`;`) - // characters which are treated as argument separators in batch file. - // Return the original string if neither is necessary and a pointer to the - // provided buffer string containing the escaped version otherwise. - // -#ifdef _WIN32 - static const char* - quote_argument (const char*, std::string& buffer, bool batch); -#endif - - public: - id_type - id () const; - - static id_type - current_id (); - - public: - handle_type handle; - - // Absence means that the exit information is not (yet) known. This can be - // because you haven't called wait() yet or because wait() failed. - // - optional<process_exit> exit; - - // Use the following file descriptors to communicate with the new process's - // standard streams. - // - auto_fd out_fd; // Write to it to send to stdin. - auto_fd in_ofd; // Read from it to receive from stdout. - auto_fd in_efd; // Read from it to receive from stderr. - }; - - // Higher-level process running interface that aims to make executing a - // process for the common cases as simple as calling a functions. Normally - // it is further simplified by project-specific wrapper functions that - // handle the process_error exception as well as abnormal and/or non-zero - // exit status. - // - // The I/O/E arguments determine the child's stdin/stdout/stderr. They can - // be of type int, auto_fd, fd_pipe and process::pipe (and, in the future, - // perhaps also string, buffer, etc). For example, the following call will - // make stdin read from /dev/null, stdout redirect to stderr, and inherit - // the parent's stderr. - // - // process_run (fdopen_null (), 2, 2, ...) - // - // The P argument is the program path. It can be anything that can be passed - // to process::path_search() (const char*, std::string, path) or the - // process_path itself. - // - // The A arguments can be anything convertible to const char* via the - // overloaded process_arg_as() (see below). Out of the box you can use const - // char* (with NULL values ignored), std::string, path/dir_path, (as well as - // [small_]vector[_view] of these), numeric types, as well as optional<T> of - // all the above with absent arguments ignored. - // - struct process_env - { - const process_path* path; - const dir_path* cwd = nullptr; - const char* const* vars = nullptr; - - // Return true if there is an "environment", that is, either the current - // working directory or environment variables. - // - bool - env () const - { - return (cwd != nullptr && !cwd->empty ()) || - (vars != nullptr && *vars != nullptr); - } - - process_env (): path (nullptr) {} - - process_env (const process_path& p, - const dir_path& c = dir_path (), - const char* const* v = nullptr) - : path (&p), - - // Note that this is not just an optimization. It is required when - // the ctor is called with the default arguments (not to keep the - // temporary object pointer). - // - cwd (!c.empty () ? &c : nullptr), - - vars (v) {} - - process_env (const process_path& p, const char* const* v) - : path (&p), cwd (nullptr), vars (v) {} - - template <typename V> - process_env (const process_path& p, const dir_path& c, const V& v) - : process_env (p, v) {cwd = &c;} - - template <typename V> - process_env (const process_path& p, const V& v) - : process_env (p) {init_vars (v);} - - process_env (const char* p, - const dir_path& c = dir_path (), - const char* const* v = nullptr) - : process_env (path_, c, v) {path_ = process::path_search (p, true);} - - process_env (const std::string& p, - const dir_path& c = dir_path (), - const char* const* v = nullptr) - : process_env (p.c_str (), c, v) {} - - process_env (const butl::path& p, - const dir_path& c = dir_path (), - const char* const* v = nullptr) - : process_env (p.string (), c, v) {} - - template <typename V> - process_env (const char* p, const dir_path& c, const V& v) - : process_env (path_, c, v) {path_ = process::path_search (p, true);} - - template <typename V> - process_env (const std::string& p, const dir_path& c, const V& v) - : process_env (p.c_str (), c, v) {} - - template <typename V> - process_env (const butl::path& p, const dir_path& c, const V& v) - : process_env (p.string (), c, v) {} - - template <typename V> - process_env (const char* p, const V& v) - : process_env (path_, v) {path_ = process::path_search (p, true);} - - template <typename V> - process_env (const std::string& p, const V& v) - : process_env (p.c_str (), v) {} - - template <typename V> - process_env (const butl::path& p, const V& v) - : process_env (p.string (), v) {} - - // Moveable-only type. - // - process_env (process_env&&); - process_env& operator= (process_env&&); - - process_env (const process_env&) = delete; - process_env& operator= (const process_env&) = delete; - - private: - template <typename V> - void - init_vars (const V&); - - template <std::size_t N> - void - init_vars (const char* const (&v)[N]) - { - vars = v; - } - - process_path path_; - small_vector<const char*, 3> vars_; - }; - - template <typename I, - typename O, - typename E, - typename... A> - process_exit - process_run (I&& in, - O&& out, - E&& err, - const process_env&, - A&&... args); - - // The version with the command callback that can be used for printing the - // command line or similar. It should be callable with the following - // signature: - // - // void (const char*[], std::size_t) - // - template <typename C, - typename I, - typename O, - typename E, - typename... A> - process_exit - process_run_callback (const C&, - I&& in, - O&& out, - E&& err, - const process_env&, - A&&... args); - - // Versions that start the process without waiting. - // - template <typename I, - typename O, - typename E, - typename... A> - process - process_start (I&& in, - O&& out, - E&& err, - const process_env&, - A&&... args); - - template <typename C, - typename I, - typename O, - typename E, - typename... A> - process - process_start_callback (const C&, - I&& in, - O&& out, - E&& err, - const process_env&, - A&&... args); - - // Conversion of types to their C string representations. Can be overloaded - // (including via ADL) for custom types. The default implementation calls - // to_string() which covers all the numeric values via std::to_string () and - // also any type that defines to_string() (via ADL). - // - template <typename T> - inline const char* - process_arg_as (const T& x, std::string& storage) - { - return (storage = std::to_string (x)).c_str (); - } - - inline const char* - process_arg_as (const std::string& s, std::string&) {return s.c_str ();} - - template <typename K> - inline const char* - process_arg_as (const basic_path<char, K>& p, std::string&) - { - return p.string ().c_str (); - } - - // char[N] - // - template <typename V> - inline void - process_args_as (V& v, const char* s, std::string&) - { - if (s != nullptr) - v.push_back (s); - } - - template <std::size_t N> - inline const char* - process_arg_as (char (&s)[N], std::string&) {return s;} - - template <std::size_t N> - inline const char* - process_arg_as (const char (&s)[N], std::string&) {return s;} - - template <typename V, typename T> - inline void - process_args_as (V& v, const T& x, std::string& storage) - { - v.push_back (process_arg_as (x, storage)); - } - - template <typename V, typename T> - inline void - process_args_as (V& v, const optional<T>& x, std::string& storage) - { - if (x) - process_args_as (v, *x, storage); - } - - // [small_]vector[_view]<> - // - template <typename V> - inline void - process_args_as (V& v, const std::vector<std::string>& vs, std::string&) - { - for (const std::string& s: vs) - v.push_back (s.c_str ()); - } - - template <typename V, std::size_t N> - inline void - process_args_as (V& v, const small_vector<std::string, N>& vs, std::string&) - { - for (const std::string& s: vs) - v.push_back (s.c_str ()); - } - - template <typename V> - inline void - process_args_as (V& v, const vector_view<std::string>& vs, std::string&) - { - for (const std::string& s: vs) - v.push_back (s.c_str ()); - } - - template <typename V> - inline void - process_args_as (V& v, const std::vector<const char*>& vs, std::string&) - { - for (const char* s: vs) - if (s != nullptr) - v.push_back (s); - } - - template <typename V, std::size_t N> - inline void - process_args_as (V& v, const small_vector<const char*, N>& vs, std::string&) - { - for (const char* s: vs) - if (s != nullptr) - v.push_back (s); - } - - template <typename V> - inline void - process_args_as (V& v, const vector_view<const char*>& vs, std::string&) - { - for (const char* s: vs) - if (s != nullptr) - v.push_back (s); - } -} - -#include <libbutl/process.ixx> -#include <libbutl/process-run.txx> |