aboutsummaryrefslogtreecommitdiffstats
path: root/util/system/shellcommand.h
diff options
context:
space:
mode:
authorDevtools Arcadia <arcadia-devtools@yandex-team.ru>2022-02-07 18:08:42 +0300
committerDevtools Arcadia <arcadia-devtools@mous.vla.yp-c.yandex.net>2022-02-07 18:08:42 +0300
commit1110808a9d39d4b808aef724c861a2e1a38d2a69 (patch)
treee26c9fed0de5d9873cce7e00bc214573dc2195b7 /util/system/shellcommand.h
downloadydb-1110808a9d39d4b808aef724c861a2e1a38d2a69.tar.gz
intermediate changes
ref:cde9a383711a11544ce7e107a78147fb96cc4029
Diffstat (limited to 'util/system/shellcommand.h')
-rw-r--r--util/system/shellcommand.h485
1 files changed, 485 insertions, 0 deletions
diff --git a/util/system/shellcommand.h b/util/system/shellcommand.h
new file mode 100644
index 0000000000..8730627fe5
--- /dev/null
+++ b/util/system/shellcommand.h
@@ -0,0 +1,485 @@
+#pragma once
+
+#include <util/generic/noncopyable.h>
+#include <util/generic/string.h>
+#include <util/generic/list.h>
+#include <util/generic/hash.h>
+#include <util/generic/strbuf.h>
+#include <util/generic/maybe.h>
+#include <util/stream/input.h>
+#include <util/stream/output.h>
+#include "file.h"
+#include "getpid.h"
+#include "thread.h"
+#include "mutex.h"
+#include <sys/types.h>
+
+class TShellCommandOptions {
+public:
+ struct TUserOptions {
+ TString Name;
+#if defined(_win_)
+ TString Password;
+#endif
+#if defined(_unix_)
+ /**
+ * Run child process with the user supplementary groups.
+ * If true, the user supplementary groups will be set in the child process upon exec().
+ * If false, the supplementary groups of the parent process will be used.
+ */
+ bool UseUserGroups = false;
+#endif
+ };
+
+ enum EHandleMode {
+ HANDLE_INHERIT,
+ HANDLE_PIPE,
+ HANDLE_STREAM
+ };
+
+public:
+ inline TShellCommandOptions() noexcept
+ : ClearSignalMask(false)
+ , CloseAllFdsOnExec(false)
+ , AsyncMode(false)
+ , PollDelayMs(DefaultSyncPollDelay)
+ , UseShell(true)
+ , QuoteArguments(true)
+ , DetachSession(true)
+ , CloseStreams(false)
+ , ShouldCloseInput(true)
+ , InputMode(HANDLE_INHERIT)
+ , OutputMode(HANDLE_STREAM)
+ , ErrorMode(HANDLE_STREAM)
+ , InputStream(nullptr)
+ , OutputStream(nullptr)
+ , ErrorStream(nullptr)
+ , Nice(0)
+ , FuncAfterFork(std::function<void()>())
+ {
+ }
+
+ inline TShellCommandOptions& SetNice(int value) noexcept {
+ Nice = value;
+
+ return *this;
+ }
+
+ /**
+ * @brief clear signal mask from parent process. If true, child process
+ * clears the signal mask inherited from the parent process; otherwise
+ * child process retains the signal mask of the parent process.
+ *
+ * @param clearSignalMask true if child process should clear signal mask
+ * @note in default child process inherits signal mask.
+ * @return self
+ */
+ inline TShellCommandOptions& SetClearSignalMask(bool clearSignalMask) {
+ ClearSignalMask = clearSignalMask;
+ return *this;
+ }
+
+ /**
+ * @brief set close-on-exec mode. If true, all file descriptors
+ * from the parent process, except stdin, stdout, stderr, will be closed
+ * in the child process upon exec().
+ *
+ * @param closeAllFdsOnExec true if close-on-exec mode is needed
+ * @note in default close-on-exec mode is off.
+ * @return self
+ */
+ inline TShellCommandOptions& SetCloseAllFdsOnExec(bool closeAllFdsOnExec) {
+ CloseAllFdsOnExec = closeAllFdsOnExec;
+ return *this;
+ }
+
+ /**
+ * @brief set asynchronous mode. If true, task will be run
+ * in separate thread, and control will be returned immediately
+ *
+ * @param async true if asynchonous mode is needed
+ * @note in default async mode launcher will need 100% cpu for rapid process termination
+ * @return self
+ */
+ inline TShellCommandOptions& SetAsync(bool async) {
+ AsyncMode = async;
+ if (AsyncMode)
+ PollDelayMs = 0;
+ return *this;
+ }
+
+ /**
+ * @brief specify delay for process controlling loop
+ * @param ms number of milliseconds to poll for
+ * @note for synchronous process default of 1s should generally fit
+ * for async process default is no latency and that consumes 100% one cpu
+ * SetAsync(true) will reset this delay to 0, so call this method after
+ * @return self
+ */
+ inline TShellCommandOptions& SetLatency(size_t ms) {
+ PollDelayMs = ms;
+ return *this;
+ }
+
+ /**
+ * @brief set the stream, which is input fetched from
+ *
+ * @param stream Pointer to stream.
+ * If stream is NULL or not set, input channel will be closed.
+ *
+ * @return self
+ */
+ inline TShellCommandOptions& SetInputStream(IInputStream* stream) {
+ InputStream = stream;
+ if (InputStream == nullptr) {
+ InputMode = HANDLE_INHERIT;
+ } else {
+ InputMode = HANDLE_STREAM;
+ }
+ return *this;
+ }
+
+ /**
+ * @brief set the stream, collecting the command output
+ *
+ * @param stream Pointer to stream.
+ * If stream is NULL or not set, output will be collected to the
+ * internal variable
+ *
+ * @return self
+ */
+ inline TShellCommandOptions& SetOutputStream(IOutputStream* stream) {
+ OutputStream = stream;
+ return *this;
+ }
+
+ /**
+ * @brief set the stream, collecting the command error output
+ *
+ * @param stream Pointer to stream.
+ * If stream is NULL or not set, errors will be collected to the
+ * internal variable
+ *
+ * @return self
+ */
+ inline TShellCommandOptions& SetErrorStream(IOutputStream* stream) {
+ ErrorStream = stream;
+ return *this;
+ }
+
+ /**
+ * @brief set if Finish() should be called on user-supplied streams
+ * if process is run in async mode Finish will be called in process' thread
+ * @param val if Finish() should be called
+ * @return self
+ */
+ inline TShellCommandOptions& SetCloseStreams(bool val) {
+ CloseStreams = val;
+ return *this;
+ }
+
+ /**
+ * @brief set if input stream should be closed after all data is read
+ * call SetCloseInput(false) for interactive process
+ * @param val if input stream should be closed
+ * @return self
+ */
+ inline TShellCommandOptions& SetCloseInput(bool val) {
+ ShouldCloseInput = val;
+ return *this;
+ }
+
+ /**
+ * @brief set if command should be interpreted by OS shell (/bin/sh or cmd.exe)
+ * shell is enabled by default
+ * call SetUseShell(false) for command to be sent to OS verbatim
+ * @note shell operators > < | && || will not work if this option is off
+ * @param useShell if command should be run in shell
+ * @return self
+ */
+ inline TShellCommandOptions& SetUseShell(bool useShell) {
+ UseShell = useShell;
+ if (!useShell)
+ QuoteArguments = false;
+ return *this;
+ }
+
+ /**
+ * @brief set if the arguments should be wrapped in quotes.
+ * Please, note that this option makes no difference between
+ * real arguments and shell syntax, so if you execute something
+ * like \b TShellCommand("sleep") << "3" << "&&" << "ls", your
+ * command will look like:
+ * sleep "3" "&&" "ls"
+ * which will never end successfully.
+ * By default, this option is turned on.
+ *
+ * @note arguments will only be quoted if shell is used
+ * @param quote if the arguments should be quoted
+ *
+ * @return self
+ */
+ inline TShellCommandOptions& SetQuoteArguments(bool quote) {
+ QuoteArguments = quote;
+ return *this;
+ }
+
+ /**
+ * @brief set to run command in new session
+ * @note set this option to off to deliver parent's signals to command as well
+ * @note currently ignored on windows
+ * @param detach if command should be run in new session
+ * @return self
+ */
+ inline TShellCommandOptions& SetDetachSession(bool detach) {
+ DetachSession = detach;
+ return *this;
+ }
+
+ /**
+ * @brief specifies pure function to be called in the child process after fork, before calling execve
+ * @note currently ignored on windows
+ * @param function function to be called after fork
+ * @return self
+ */
+ inline TShellCommandOptions& SetFuncAfterFork(const std::function<void()>& function) {
+ FuncAfterFork = function;
+ return *this;
+ }
+
+ /**
+ * @brief create a pipe for child input
+ * Write end of the pipe will be accessible via TShellCommand::GetInputHandle
+ *
+ * @return self
+ */
+ inline TShellCommandOptions& PipeInput() {
+ InputMode = HANDLE_PIPE;
+ InputStream = nullptr;
+ return *this;
+ }
+
+ inline TShellCommandOptions& PipeOutput() {
+ OutputMode = HANDLE_PIPE;
+ OutputStream = nullptr;
+ return *this;
+ }
+
+ inline TShellCommandOptions& PipeError() {
+ ErrorMode = HANDLE_PIPE;
+ ErrorStream = nullptr;
+ return *this;
+ }
+
+ /**
+ * @brief set if child should inherit output handle
+ *
+ * @param inherit if child should inherit output handle
+ *
+ * @return self
+ */
+ inline TShellCommandOptions& SetInheritOutput(bool inherit) {
+ OutputMode = inherit ? HANDLE_INHERIT : HANDLE_STREAM;
+ return *this;
+ }
+
+ /**
+ * @brief set if child should inherit stderr handle
+ *
+ * @param inherit if child should inherit error output handle
+ *
+ * @return self
+ */
+ inline TShellCommandOptions& SetInheritError(bool inherit) {
+ ErrorMode = inherit ? HANDLE_INHERIT : HANDLE_STREAM;
+ return *this;
+ }
+
+public:
+ bool ClearSignalMask = false;
+ bool CloseAllFdsOnExec = false;
+ bool AsyncMode = false;
+ size_t PollDelayMs = 0;
+ bool UseShell = false;
+ bool QuoteArguments = false;
+ bool DetachSession = false;
+ bool CloseStreams = false;
+ bool ShouldCloseInput = false;
+ EHandleMode InputMode = HANDLE_STREAM;
+ EHandleMode OutputMode = HANDLE_STREAM;
+ EHandleMode ErrorMode = HANDLE_STREAM;
+
+ /// @todo more options
+ // bool SearchPath // search exe name in $PATH
+ // bool UnicodeConsole
+ // bool EmulateConsole // provide isatty == true
+ /// @todo command's stdin should be exposet as IOutputStream to support dialogue
+ IInputStream* InputStream;
+ IOutputStream* OutputStream;
+ IOutputStream* ErrorStream;
+ TUserOptions User;
+ THashMap<TString, TString> Environment;
+ int Nice = 0;
+
+ static const size_t DefaultSyncPollDelay = 1000; // ms
+ std::function<void()> FuncAfterFork = {};
+};
+
+/**
+ * @brief Execute command in shell and provide its results
+ * @attention Not thread-safe
+ */
+class TShellCommand: public TNonCopyable {
+private:
+ TShellCommand();
+
+public:
+ enum ECommandStatus {
+ SHELL_NONE,
+ SHELL_RUNNING,
+ SHELL_FINISHED,
+ SHELL_INTERNAL_ERROR,
+ SHELL_ERROR
+ };
+
+public:
+ /**
+ * @brief create the command with initial arguments list
+ *
+ * @param cmd binary name
+ * @param args arguments list
+ * @param options execution options
+ * @todo store entire options structure
+ */
+ TShellCommand(const TStringBuf cmd, const TList<TString>& args, const TShellCommandOptions& options = TShellCommandOptions(),
+ const TString& workdir = TString());
+ TShellCommand(const TStringBuf cmd, const TShellCommandOptions& options = TShellCommandOptions(), const TString& workdir = TString());
+ ~TShellCommand();
+
+public:
+ /**
+ * @brief append argument to the args list
+ *
+ * @param argument string argument
+ *
+ * @return self
+ */
+ TShellCommand& operator<<(const TStringBuf argument);
+
+ /**
+ * @brief return the collected output from the command.
+ * If the output stream is set, empty string will be returned
+ *
+ * @return collected output
+ */
+ const TString& GetOutput() const;
+
+ /**
+ * @brief return the collected error output from the command.
+ * If the error stream is set, empty string will be returned
+ *
+ * @return collected error output
+ */
+ const TString& GetError() const;
+
+ /**
+ * @brief return the internal error occured while watching
+ * the command execution. Should be called if execution
+ * status is SHELL_INTERNAL_ERROR
+ *
+ * @return error text
+ */
+ const TString& GetInternalError() const;
+
+ /**
+ * @brief get current status of command execution
+ *
+ * @return current status
+ */
+ ECommandStatus GetStatus() const;
+
+ /**
+ * @brief return exit code of finished process
+ * The value is unspecified in case of internal errors or if the process is running
+ *
+ * @return exit code
+ */
+ TMaybe<int> GetExitCode() const;
+
+ /**
+ * @brief get id of underlying process
+ * @note depends on os: pid_t on UNIX, HANDLE on win
+ *
+ * @return pid or handle
+ */
+ TProcessId GetPid() const;
+
+ /**
+ * @brief return the file handle that provides input to the child process
+ *
+ * @return input file handle
+ */
+ TFileHandle& GetInputHandle();
+
+ /**
+ * @brief return the file handle that provides output from the child process
+ *
+ * @return output file handle
+ */
+ TFileHandle& GetOutputHandle();
+
+ /**
+ * @brief return the file handle that provides error output from the child process
+ *
+ * @return error file handle
+ */
+ TFileHandle& GetErrorHandle();
+
+ /**
+ * @brief run the execution
+ *
+ * @return self
+ */
+ TShellCommand& Run();
+
+ /**
+ * @brief terminate the execution
+ * @note if DetachSession is set, it terminates all procs in command's new process group
+ *
+ * @return self
+ */
+ TShellCommand& Terminate();
+
+ /**
+ * @brief wait until the execution is finished
+ *
+ * @return self
+ */
+ TShellCommand& Wait();
+
+ /**
+ * @brief close process' stdin
+ *
+ * @return self
+ */
+ TShellCommand& CloseInput();
+
+ /**
+ * @brief Get quoted command (for debug/view purposes only!)
+ **/
+ TString GetQuotedCommand() const;
+
+private:
+ class TImpl;
+ using TImplRef = TSimpleIntrusivePtr<TImpl>;
+ TImplRef Impl;
+};
+
+/// Appends to dst: quoted arg
+void ShellQuoteArg(TString& dst, TStringBuf arg);
+
+/// Appends to dst: space, quoted arg
+void ShellQuoteArgSp(TString& dst, TStringBuf arg);
+
+/// Returns true if arg should be quoted
+bool ArgNeedsQuotes(TStringBuf arg) noexcept;