shellcommand.h 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503
  1. #pragma once
  2. #include <util/generic/noncopyable.h>
  3. #include <util/generic/string.h>
  4. #include <util/generic/list.h>
  5. #include <util/generic/hash.h>
  6. #include <util/generic/strbuf.h>
  7. #include <util/generic/maybe.h>
  8. #include <util/stream/input.h>
  9. #include <util/stream/output.h>
  10. #include "file.h"
  11. #include "getpid.h"
  12. #include "thread.h"
  13. #include "mutex.h"
  14. #include <sys/types.h>
  15. #include <atomic>
  16. class TShellCommandOptions {
  17. class TCopyableAtomicBool: public std::atomic<bool> {
  18. public:
  19. using std::atomic<bool>::atomic;
  20. TCopyableAtomicBool(const TCopyableAtomicBool& other)
  21. : std::atomic<bool>(other.load(std::memory_order_acquire))
  22. {
  23. }
  24. TCopyableAtomicBool& operator=(const TCopyableAtomicBool& other) {
  25. this->store(other.load(std::memory_order_acquire), std::memory_order_release);
  26. return *this;
  27. }
  28. };
  29. public:
  30. struct TUserOptions {
  31. TString Name;
  32. #if defined(_win_)
  33. TString Password;
  34. #endif
  35. #if defined(_unix_)
  36. /**
  37. * Run child process with the user supplementary groups.
  38. * If true, the user supplementary groups will be set in the child process upon exec().
  39. * If false, the supplementary groups of the parent process will be used.
  40. */
  41. bool UseUserGroups = false;
  42. #endif
  43. };
  44. enum EHandleMode {
  45. HANDLE_INHERIT,
  46. HANDLE_PIPE,
  47. HANDLE_STREAM
  48. };
  49. public:
  50. inline TShellCommandOptions() noexcept
  51. : ClearSignalMask(false)
  52. , CloseAllFdsOnExec(false)
  53. , AsyncMode(false)
  54. , PollDelayMs(DefaultSyncPollDelayMs)
  55. , UseShell(true)
  56. , QuoteArguments(true)
  57. , DetachSession(true)
  58. , CloseStreams(false)
  59. , ShouldCloseInput(true)
  60. , InputMode(HANDLE_INHERIT)
  61. , OutputMode(HANDLE_STREAM)
  62. , ErrorMode(HANDLE_STREAM)
  63. , InputStream(nullptr)
  64. , OutputStream(nullptr)
  65. , ErrorStream(nullptr)
  66. , Nice(0)
  67. , FuncAfterFork(std::function<void()>())
  68. {
  69. }
  70. inline TShellCommandOptions& SetNice(int value) noexcept {
  71. Nice = value;
  72. return *this;
  73. }
  74. /**
  75. * @brief clear signal mask from parent process. If true, child process
  76. * clears the signal mask inherited from the parent process; otherwise
  77. * child process retains the signal mask of the parent process.
  78. *
  79. * @param clearSignalMask true if child process should clear signal mask
  80. * @note in default child process inherits signal mask.
  81. * @return self
  82. */
  83. inline TShellCommandOptions& SetClearSignalMask(bool clearSignalMask) {
  84. ClearSignalMask = clearSignalMask;
  85. return *this;
  86. }
  87. /**
  88. * @brief set close-on-exec mode. If true, all file descriptors
  89. * from the parent process, except stdin, stdout, stderr, will be closed
  90. * in the child process upon exec().
  91. *
  92. * @param closeAllFdsOnExec true if close-on-exec mode is needed
  93. * @note in default close-on-exec mode is off.
  94. * @return self
  95. */
  96. inline TShellCommandOptions& SetCloseAllFdsOnExec(bool closeAllFdsOnExec) {
  97. CloseAllFdsOnExec = closeAllFdsOnExec;
  98. return *this;
  99. }
  100. /**
  101. * @brief set asynchronous mode. If true, task will be run
  102. * in separate thread, and control will be returned immediately
  103. *
  104. * @param async true if asynchonous mode is needed
  105. * @note in default async mode launcher will need 100% cpu for rapid process termination
  106. * @return self
  107. */
  108. inline TShellCommandOptions& SetAsync(bool async) {
  109. AsyncMode = async;
  110. if (AsyncMode)
  111. PollDelayMs = 0;
  112. return *this;
  113. }
  114. /**
  115. * @brief specify delay for process controlling loop
  116. * @param ms number of milliseconds to poll for
  117. * @note for synchronous process default of 1s should generally fit
  118. * for async process default is no latency and that consumes 100% one cpu
  119. * SetAsync(true) will reset this delay to 0, so call this method after
  120. * @return self
  121. */
  122. inline TShellCommandOptions& SetLatency(size_t ms) {
  123. PollDelayMs = ms;
  124. return *this;
  125. }
  126. /**
  127. * @brief set the stream, which is input fetched from
  128. *
  129. * @param stream Pointer to stream.
  130. * If stream is NULL or not set, input channel will be closed.
  131. *
  132. * @return self
  133. */
  134. inline TShellCommandOptions& SetInputStream(IInputStream* stream) {
  135. InputStream = stream;
  136. if (InputStream == nullptr) {
  137. InputMode = HANDLE_INHERIT;
  138. } else {
  139. InputMode = HANDLE_STREAM;
  140. }
  141. return *this;
  142. }
  143. /**
  144. * @brief set the stream, collecting the command output
  145. *
  146. * @param stream Pointer to stream.
  147. * If stream is NULL or not set, output will be collected to the
  148. * internal variable
  149. *
  150. * @return self
  151. */
  152. inline TShellCommandOptions& SetOutputStream(IOutputStream* stream) {
  153. OutputStream = stream;
  154. return *this;
  155. }
  156. /**
  157. * @brief set the stream, collecting the command error output
  158. *
  159. * @param stream Pointer to stream.
  160. * If stream is NULL or not set, errors will be collected to the
  161. * internal variable
  162. *
  163. * @return self
  164. */
  165. inline TShellCommandOptions& SetErrorStream(IOutputStream* stream) {
  166. ErrorStream = stream;
  167. return *this;
  168. }
  169. /**
  170. * @brief set if Finish() should be called on user-supplied streams
  171. * if process is run in async mode Finish will be called in process' thread
  172. * @param val if Finish() should be called
  173. * @return self
  174. */
  175. inline TShellCommandOptions& SetCloseStreams(bool val) {
  176. CloseStreams = val;
  177. return *this;
  178. }
  179. /**
  180. * @brief set if input stream should be closed after all data is read
  181. * call SetCloseInput(false) for interactive process
  182. * @param val if input stream should be closed
  183. * @return self
  184. */
  185. inline TShellCommandOptions& SetCloseInput(bool val) {
  186. ShouldCloseInput.store(val);
  187. return *this;
  188. }
  189. /**
  190. * @brief set if command should be interpreted by OS shell (/bin/sh or cmd.exe)
  191. * shell is enabled by default
  192. * call SetUseShell(false) for command to be sent to OS verbatim
  193. * @note shell operators > < | && || will not work if this option is off
  194. * @param useShell if command should be run in shell
  195. * @return self
  196. */
  197. inline TShellCommandOptions& SetUseShell(bool useShell) {
  198. UseShell = useShell;
  199. if (!useShell)
  200. QuoteArguments = false;
  201. return *this;
  202. }
  203. /**
  204. * @brief set if the arguments should be wrapped in quotes.
  205. * Please, note that this option makes no difference between
  206. * real arguments and shell syntax, so if you execute something
  207. * like \b TShellCommand("sleep") << "3" << "&&" << "ls", your
  208. * command will look like:
  209. * sleep "3" "&&" "ls"
  210. * which will never end successfully.
  211. * By default, this option is turned on.
  212. *
  213. * @note arguments will only be quoted if shell is used
  214. * @param quote if the arguments should be quoted
  215. *
  216. * @return self
  217. */
  218. inline TShellCommandOptions& SetQuoteArguments(bool quote) {
  219. QuoteArguments = quote;
  220. return *this;
  221. }
  222. /**
  223. * @brief set to run command in new session
  224. * @note set this option to off to deliver parent's signals to command as well
  225. * @note currently ignored on windows
  226. * @param detach if command should be run in new session
  227. * @return self
  228. */
  229. inline TShellCommandOptions& SetDetachSession(bool detach) {
  230. DetachSession = detach;
  231. return *this;
  232. }
  233. /**
  234. * @brief specifies pure function to be called in the child process after fork, before calling execve
  235. * @note currently ignored on windows
  236. * @param function function to be called after fork
  237. * @return self
  238. */
  239. inline TShellCommandOptions& SetFuncAfterFork(const std::function<void()>& function) {
  240. FuncAfterFork = function;
  241. return *this;
  242. }
  243. /**
  244. * @brief create a pipe for child input
  245. * Write end of the pipe will be accessible via TShellCommand::GetInputHandle
  246. *
  247. * @return self
  248. */
  249. inline TShellCommandOptions& PipeInput() {
  250. InputMode = HANDLE_PIPE;
  251. InputStream = nullptr;
  252. return *this;
  253. }
  254. inline TShellCommandOptions& PipeOutput() {
  255. OutputMode = HANDLE_PIPE;
  256. OutputStream = nullptr;
  257. return *this;
  258. }
  259. inline TShellCommandOptions& PipeError() {
  260. ErrorMode = HANDLE_PIPE;
  261. ErrorStream = nullptr;
  262. return *this;
  263. }
  264. /**
  265. * @brief set if child should inherit output handle
  266. *
  267. * @param inherit if child should inherit output handle
  268. *
  269. * @return self
  270. */
  271. inline TShellCommandOptions& SetInheritOutput(bool inherit) {
  272. OutputMode = inherit ? HANDLE_INHERIT : HANDLE_STREAM;
  273. return *this;
  274. }
  275. /**
  276. * @brief set if child should inherit stderr handle
  277. *
  278. * @param inherit if child should inherit error output handle
  279. *
  280. * @return self
  281. */
  282. inline TShellCommandOptions& SetInheritError(bool inherit) {
  283. ErrorMode = inherit ? HANDLE_INHERIT : HANDLE_STREAM;
  284. return *this;
  285. }
  286. public:
  287. static constexpr size_t DefaultSyncPollDelayMs = 1000;
  288. public:
  289. bool ClearSignalMask = false;
  290. bool CloseAllFdsOnExec = false;
  291. bool AsyncMode = false;
  292. size_t PollDelayMs = 0;
  293. bool UseShell = false;
  294. bool QuoteArguments = false;
  295. bool DetachSession = false;
  296. bool CloseStreams = false;
  297. TCopyableAtomicBool ShouldCloseInput = false;
  298. EHandleMode InputMode = HANDLE_STREAM;
  299. EHandleMode OutputMode = HANDLE_STREAM;
  300. EHandleMode ErrorMode = HANDLE_STREAM;
  301. /// @todo more options
  302. // bool SearchPath // search exe name in $PATH
  303. // bool UnicodeConsole
  304. // bool EmulateConsole // provide isatty == true
  305. /// @todo command's stdin should be exposet as IOutputStream to support dialogue
  306. IInputStream* InputStream;
  307. IOutputStream* OutputStream;
  308. IOutputStream* ErrorStream;
  309. TUserOptions User;
  310. THashMap<TString, TString> Environment;
  311. int Nice = 0;
  312. std::function<void()> FuncAfterFork = {};
  313. };
  314. /**
  315. * @brief Execute command in shell and provide its results
  316. * @attention Not thread-safe
  317. */
  318. class TShellCommand: public TNonCopyable {
  319. private:
  320. TShellCommand();
  321. public:
  322. enum ECommandStatus {
  323. SHELL_NONE,
  324. SHELL_RUNNING,
  325. SHELL_FINISHED,
  326. SHELL_INTERNAL_ERROR,
  327. SHELL_ERROR
  328. };
  329. public:
  330. /**
  331. * @brief create the command with initial arguments list
  332. *
  333. * @param cmd binary name
  334. * @param args arguments list
  335. * @param options execution options
  336. * @todo store entire options structure
  337. */
  338. TShellCommand(const TStringBuf cmd, const TList<TString>& args, const TShellCommandOptions& options = TShellCommandOptions(),
  339. const TString& workdir = TString());
  340. TShellCommand(const TStringBuf cmd, const TShellCommandOptions& options = TShellCommandOptions(), const TString& workdir = TString());
  341. ~TShellCommand();
  342. public:
  343. /**
  344. * @brief append argument to the args list
  345. *
  346. * @param argument string argument
  347. *
  348. * @return self
  349. */
  350. TShellCommand& operator<<(const TStringBuf argument);
  351. /**
  352. * @brief return the collected output from the command.
  353. * If the output stream is set, empty string will be returned
  354. *
  355. * @return collected output
  356. */
  357. const TString& GetOutput() const;
  358. /**
  359. * @brief return the collected error output from the command.
  360. * If the error stream is set, empty string will be returned
  361. *
  362. * @return collected error output
  363. */
  364. const TString& GetError() const;
  365. /**
  366. * @brief return the internal error occured while watching
  367. * the command execution. Should be called if execution
  368. * status is SHELL_INTERNAL_ERROR
  369. *
  370. * @return error text
  371. */
  372. const TString& GetInternalError() const;
  373. /**
  374. * @brief get current status of command execution
  375. *
  376. * @return current status
  377. */
  378. ECommandStatus GetStatus() const;
  379. /**
  380. * @brief return exit code of finished process
  381. * The value is unspecified in case of internal errors or if the process is running
  382. *
  383. * @return exit code
  384. */
  385. TMaybe<int> GetExitCode() const;
  386. /**
  387. * @brief get id of underlying process
  388. * @note depends on os: pid_t on UNIX, HANDLE on win
  389. *
  390. * @return pid or handle
  391. */
  392. TProcessId GetPid() const;
  393. /**
  394. * @brief return the file handle that provides input to the child process
  395. *
  396. * @return input file handle
  397. */
  398. TFileHandle& GetInputHandle();
  399. /**
  400. * @brief return the file handle that provides output from the child process
  401. *
  402. * @return output file handle
  403. */
  404. TFileHandle& GetOutputHandle();
  405. /**
  406. * @brief return the file handle that provides error output from the child process
  407. *
  408. * @return error file handle
  409. */
  410. TFileHandle& GetErrorHandle();
  411. /**
  412. * @brief run the execution
  413. *
  414. * @return self
  415. */
  416. TShellCommand& Run();
  417. /**
  418. * @brief terminate the execution
  419. * @note if DetachSession is set, it terminates all procs in command's new process group
  420. *
  421. * @return self
  422. */
  423. TShellCommand& Terminate(int signal = SIGTERM);
  424. /**
  425. * @brief wait until the execution is finished
  426. *
  427. * @return self
  428. */
  429. TShellCommand& Wait();
  430. /**
  431. * @brief close process' stdin
  432. *
  433. * @return self
  434. */
  435. TShellCommand& CloseInput();
  436. /**
  437. * @brief Get quoted command (for debug/view purposes only!)
  438. **/
  439. TString GetQuotedCommand() const;
  440. private:
  441. class TImpl;
  442. using TImplRef = TSimpleIntrusivePtr<TImpl>;
  443. TImplRef Impl;
  444. };
  445. /// Appends to dst: quoted arg
  446. void ShellQuoteArg(TString& dst, TStringBuf arg);
  447. /// Appends to dst: space, quoted arg
  448. void ShellQuoteArgSp(TString& dst, TStringBuf arg);
  449. /// Returns true if arg should be quoted
  450. bool ArgNeedsQuotes(TStringBuf arg) noexcept;