shellcommand.h 14 KB

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