// file : butl/process -*- C++ -*- // copyright : Copyright (c) 2014-2016 Code Synthesis Ltd // license : MIT; see accompanying LICENSE file #ifndef BUTL_PROCESS #define BUTL_PROCESS #ifndef _WIN32 # include // pid_t #endif #include #include // uint32_t #include #include #include namespace butl { struct process_error: std::system_error { bool child () const {return child_;} public: #ifndef _WIN32 process_error (int e, bool child) : system_error (e, std::system_category ()), child_ (child) {} #else process_error (int e, bool child = false) : system_error (e, std::system_category ()), child_ (child) {} process_error (const std::string& d, int e = ECHILD) : system_error (e, std::system_category (), d), child_ (false) {} #endif private: bool child_; }; // 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. Finally, effective is the actual 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: foo\bar.exe // // In most cases, at least on POSIX, all three 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 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 && recall.empty () && effect.empty (); } // 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 () = default; process_path (const char* i, path&& r, path&& e); ~process_path (); private: friend class process; const char** args0_ = nullptr; }; class LIBBUTL_EXPORT process { public: // 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 _setmode() 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); // // 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. // // Note that the versions without the the process_path argument may // temporarily change args[0] (see path_search() for details). // process (const char* args[], int in = 0, int out = 1, int err = 2); process (const process_path&, const char* args[], int in = 0, int out = 1, int err = 2); // 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* args[], process& in, int out = 1, int err = 2); process (const process_path&, const char* args[], process& in, int out = 1, int err = 2); // Versions of the above constructors that allow us to change the // current working directory of the child process. NULL and empty // cwd arguments are ignored. // process (const char* cwd, const char* [], int = 0, int = 1, int = 2); process (const char* cwd, const process_path&, const char* [], int = 0, int = 1, int = 2); process (const char* cwd, const char* [], process&, int = 1, int = 2); process (const char* cwd, const process_path&, const char* [], process&, int = 1, int = 2); // Wait for the process to terminate. Return true if the process // terminated normally and with the zero exit status. 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 true if the process has already terminated in which case // the argument is set to the result of wait(). // bool try_wait (bool&); // Note that the destructor will wait for the process but will ignore // any errors and the exit status. // ~process () {if (handle != 0) wait (true);} // Moveable-only type. // process (process&&); process& operator= (process&&); process (const process&) = delete; process& operator= (const process&) = delete; // Create an empty or "already terminated" process. That is, handle is 0 // and exit status is 0. // process (); // 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. // static process_path path_search (const char*& args0, const dir_path& fallback = dir_path ()); // 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 ()); static process_path path_search (const std::string&, bool, const dir_path& = dir_path ()); static process_path path_search (const path&, bool, const dir_path& = dir_path ()); // 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 ()); static process_path try_path_search (const std::string&, bool, const dir_path& = dir_path ()); static process_path try_path_search (const path&, bool, const dir_path& = dir_path ()); public: #ifndef _WIN32 using handle_type = pid_t; using id_type = pid_t; using status_type = int; #else using handle_type = void*; // Win32 HANDLE using id_type = std::uint32_t; // Win32 DWORD using status_type = std::uint32_t; // Win32 DWORD #endif static id_type current_id (); public: handle_type handle; status_type status; int out_fd; // Write to this fd to send to the new process' stdin. int in_ofd; // Read from this fd to receive from the new process' stdout. int in_efd; // Read from this fd to receive from the new process' stderr. }; } #include #endif // BUTL_PROCESS