/** * @file llprocess.h * @brief Utility class for launching, terminating, and tracking child processes. * * $LicenseInfo:firstyear=2008&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. * * 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; * version 2.1 of the License only. * * 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 * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ #ifndef LL_LLPROCESS_H #define LL_LLPROCESS_H #include "llinitparam.h" #include "llsdparam.h" #include "llexception.h" #include #include #include #include #include #include #include #include #include #include #include // std::ostream #if LL_WINDOWS #include "llwin32headers.h" // for HANDLE #elif LL_LINUX || __FreeBSD__ #if defined(Status) #undef Status #endif #endif class LLEventPump; class LLProcess; /// LLProcess instances are created on the heap by static factory methods and /// managed by ref-counted pointers. typedef std::shared_ptr LLProcessPtr; /** * LLProcess handles launching an external process with specified command line * arguments. It also keeps track of whether the process is still running, and * can kill it if required. * * In discussing LLProcess, we use the term "parent" to refer to this process * (the process invoking LLProcess), versus "child" to refer to the process * spawned by LLProcess. * * LLProcess relies on periodic post() calls on the "mainloop" LLEventPump: an * LLProcess object's Status won't update until the next "mainloop" tick. For * instance, the Second Life viewer's main loop already posts to an * LLEventPump by that name once per iteration. See * indra/llcommon/tests/llprocess_test.cpp for an example of waiting for * child-process termination in a standalone test context. */ class LL_COMMON_API LLProcess : public std::enable_shared_from_this { LOG_CLASS(LLProcess); public: /** * Specify what to pass for each of child stdin, stdout, stderr. */ struct FileParam : public LLInitParam::Block { /** * type of file handle to pass to child process * - "" (default): inherit from parent * - "pipe": create a pipe for I/O * - "file": open a filesystem file (future enhancement) */ Optional type; Optional name; FileParam(const std::string& tp = "", const std::string& nm = "") : type("type"), name("name") { if (!tp.empty()) type = tp; if (!nm.empty()) name = nm; } }; /// Param block definition struct Params: public LLInitParam::Block { Params(): executable("executable"), args("args"), envs("envs"), cwd("cwd"), autokill("autokill", true), attached("attached", true), files("files"), postend("postend"), desc("desc") { } /// pathname of executable Mandatory executable; /** * zero or more additional command-line arguments. Arguments are * passed through as exactly as we can manage, whitespace and all. * @note On Windows we manage this by implicitly double-quoting each * argument while assembling the command line. */ Multiple args; /** * zero or more additional command-line environment values. */ Multiple envs; /// current working directory, if need it changed Optional cwd; /// implicitly kill child process on termination of parent, whether /// voluntary or crash (default true) Optional autokill; /// implicitly kill process on destruction of LLProcess object /// (default same as autokill) /// /// Originally, 'autokill' conflated two concepts: kill child process on /// - destruction of its LLProcess object, and /// - termination of parent process, voluntary or otherwise. /// /// It's useful to tease these apart. Some child processes are sent a /// "clean up and terminate" message before the associated LLProcess /// object is destroyed. A child process launched with attached=false /// has an extra time window from the destruction of its LLProcess /// until parent-process termination in which to perform its own /// orderly shutdown, yet autokill=true still guarantees that we won't /// accumulate orphan instances of such processes indefinitely. With /// attached=true, if a child process cannot clean up between the /// shutdown message and LLProcess destruction (presumably very soon /// thereafter), it's forcibly killed anyway -- which can lead to /// distressing user-visible crash indications. /// /// (The usefulness of attached=true with autokill=false is less /// clear, but we don't prohibit that combination.) Optional attached; /** * Up to three FileParam items: for child stdin, stdout, stderr. * Passing two FileParam entries means default treatment for stderr, * and so forth. * * @note LLInitParam::Block permits usage like this: * @code * LLProcess::Params params; * ... * params.files * .add(LLProcess::FileParam()) // stdin * .add(LLProcess::FileParam().type("pipe") // stdout * .add(LLProcess::FileParam().type("file").name("error.log")); * @endcode * * @note While it's theoretically plausible to pass additional open * file handles to a child specifically written to expect them, our * underlying implementation doesn't yet support that. */ Multiple > files; /** * On child-process termination, if this LLProcess object still * exists, post LLSD event to LLEventPump with specified name (default * no event). Event contains at least: * * - "id" as obtained from getProcessID() * - "desc" short string description of child (executable + pid) * - "state" @c state enum value, from Status.mState * - "data" if "state" is EXITED, exit code; if KILLED, on Posix, * signal number * - "string" English text describing "state" and "data" (e.g. "exited * with code 0") */ Optional postend; /** * Description of child process for logging purposes. It need not be * unique; the logged description string will contain the PID as well. * If this is omitted, a description will be derived from the * executable name. */ Optional desc; }; typedef LLSDParamAdapter LLSDOrParams; static LLProcessPtr create(const LLSDOrParams& params); virtual ~LLProcess(); /// Is child process still running? bool isRunning() const; static bool isRunning(const LLProcessPtr&); /** * State of child process */ enum state { UNSTARTED, ///< initial value, invisible to consumer RUNNING, ///< child process launched EXITED, ///< child process terminated voluntarily KILLED ///< child process terminated involuntarily }; /** * Status info */ struct Status { Status() : mState(UNSTARTED), mData(0) {} state mState; int mData; // exit code or signal number }; Status getStatus() const; static Status getStatus(const LLProcessPtr&); std::string getStatusString() const; static std::string getStatusString(const std::string& desc, const LLProcessPtr&); std::string getStatusString(const Status& status) const; static std::string getStatusString(const std::string& desc, const Status& status); bool kill(const std::string& who = ""); static bool kill(const LLProcessPtr& p, const std::string& who = ""); /// Manually drive pending I/O and check process state. /// Use this when the mainloop is not yet running or was terminated. void pump(); #if LL_WINDOWS typedef int id; typedef HANDLE handle; #else typedef pid_t id; typedef pid_t handle; #endif id getProcessID() const; handle getProcessHandle() const; static handle isRunning(handle, const std::string& desc = ""); enum FILESLOT { STDIN = 0, STDOUT = 1, STDERR = 2, NSLOTS = 3 }; /// Exception thrown by getWritePipe(), getReadPipe() if you didn't ask to /// create a pipe at the corresponding FILESLOT. struct NoPipe : public LLException { NoPipe(const std::string& what) : LLException(what) {} }; std::string getPipeName(FILESLOT) const; /// Base class for pipes class LL_COMMON_API BasePipe { public: virtual ~BasePipe() = default; typedef std::size_t size_type; static const size_type npos; virtual size_type size() const = 0; }; /// Write pipe for stdin class WritePipe : public BasePipe { public: virtual std::ostream& get_ostream() = 0; // Called each mainloop tick to initiate any pending async writes. virtual void tick() {} }; /// Read pipe for stdout/stderr class ReadPipe : public BasePipe { public: virtual std::istream& get_istream() = 0; virtual std::string getline() = 0; virtual std::string read(size_type len) = 0; virtual std::string peek(size_type offset = 0, size_type len = npos) const = 0; template bool contains(SEEK seek, size_type offset = 0) const { return find(seek, offset) != npos; } virtual size_type find(const std::string& seek, size_type offset = 0) const = 0; virtual size_type find(char seek, size_type offset = 0) const = 0; virtual LLEventPump& getPump() = 0; virtual void setLimit(size_type limit) = 0; virtual size_type getLimit() const = 0; // True once the pipe has observed child-process EOF. virtual bool atEOF() const = 0; }; WritePipe& getWritePipe(FILESLOT slot = STDIN); ReadPipe& getReadPipe(FILESLOT index); WritePipe* getOptWritePipe(FILESLOT slot = STDIN); ReadPipe* getOptReadPipe(FILESLOT index); static std::string basename(const std::string& path); static std::string getline(std::istream& in); // Constructor is public for the sake of make_shared // but create() should be used instead for proper initialization. LLProcess(const Params& params); private: void launch(const LLSDOrParams& params); void connectMainloop(); void tick(); void handleExit(Status exitStatus); // Boost.Process v2 components boost::asio::io_context mIOContext; std::unique_ptr mChild; // Pipes - using Boost.Asio pipes directly (v2 no longer has async_pipe) // From parent's perspective: write to stdin (writable_pipe), read from stdout/stderr (readable_pipe) // std::shared_ptr so WritePipeImpl/ReadPipeImpl keep the pipe alive as // long as async operations are in flight std::shared_ptr mStdinPipe; std::shared_ptr mStdoutPipe; std::shared_ptr mStderrPipe; // Our pipe wrapper implementations std::unique_ptr mWritePipe; std::unique_ptr mStdoutReadPipe; std::unique_ptr mStderrReadPipe; Status mStatus; std::string mDesc; std::string mPostend; bool mAutokill; bool mAttached; bool mKillCalled; // For integrating with LLEventPump mainloop boost::signals2::scoped_connection mMainloopConnection; }; /// for logging LL_COMMON_API std::ostream& operator<<(std::ostream&, const LLProcess::Params&); #endif // LL_LLPROCESS_H