shellcommand.cpp 37 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177
  1. #include "shellcommand.h"
  2. #include "user.h"
  3. #include "nice.h"
  4. #include "sigset.h"
  5. #include <util/folder/dirut.h>
  6. #include <util/generic/algorithm.h>
  7. #include <util/generic/buffer.h>
  8. #include <util/generic/vector.h>
  9. #include <util/generic/yexception.h>
  10. #include <util/memory/tempbuf.h>
  11. #include <util/network/socket.h>
  12. #include <util/stream/pipe.h>
  13. #include <util/stream/str.h>
  14. #include <util/string/cast.h>
  15. #include <util/system/info.h>
  16. #include <errno.h>
  17. #if defined(_unix_)
  18. #include <unistd.h>
  19. #include <fcntl.h>
  20. #include <grp.h>
  21. #include <sys/wait.h>
  22. using TPid = pid_t;
  23. using TWaitResult = pid_t;
  24. using TExitStatus = int;
  25. #define WAIT_PROCEED 0
  26. #if defined(_darwin_)
  27. using TGetGroupListGid = int;
  28. #else
  29. using TGetGroupListGid = gid_t;
  30. #endif
  31. #elif defined(_win_)
  32. #include <string>
  33. #include "winint.h"
  34. using TPid = HANDLE;
  35. using TWaitResult = DWORD;
  36. using TExitStatus = DWORD;
  37. #define WAIT_PROCEED WAIT_TIMEOUT
  38. #pragma warning(disable : 4296) // 'wait_result >= WAIT_OBJECT_0' : expression is always tru
  39. #else
  40. #error("unknown os, shell command is not implemented")
  41. #endif
  42. #define DBG(stmt) \
  43. {}
  44. // #define DBG(stmt) stmt
  45. namespace {
  46. constexpr static size_t DATA_BUFFER_SIZE = 128 * 1024;
  47. #if defined(_unix_)
  48. void SetUserGroups(const passwd* pw) {
  49. int ngroups = 1;
  50. THolder<gid_t, TFree> groups = THolder<gid_t, TFree>(static_cast<gid_t*>(malloc(ngroups * sizeof(gid_t))));
  51. if (getgrouplist(pw->pw_name, pw->pw_gid, reinterpret_cast<TGetGroupListGid*>(groups.Get()), &ngroups) == -1) {
  52. groups.Reset(static_cast<gid_t*>(malloc(ngroups * sizeof(gid_t))));
  53. if (getgrouplist(pw->pw_name, pw->pw_gid, reinterpret_cast<TGetGroupListGid*>(groups.Get()), &ngroups) == -1) {
  54. ythrow TSystemError() << "getgrouplist failed: user " << pw->pw_name << " (" << pw->pw_uid << ")";
  55. }
  56. }
  57. if (setgroups(ngroups, groups.Get()) == -1) {
  58. ythrow TSystemError(errno) << "Unable to set groups for user " << pw->pw_name << Endl;
  59. }
  60. }
  61. void ImpersonateUser(const TShellCommandOptions::TUserOptions& userOpts) {
  62. if (GetUsername() == userOpts.Name) {
  63. return;
  64. }
  65. const passwd* newUser = getpwnam(userOpts.Name.c_str());
  66. if (!newUser) {
  67. ythrow TSystemError(errno) << "getpwnam failed";
  68. }
  69. if (userOpts.UseUserGroups) {
  70. SetUserGroups(newUser);
  71. }
  72. if (setuid(newUser->pw_uid)) {
  73. ythrow TSystemError(errno) << "setuid failed";
  74. }
  75. }
  76. #elif defined(_win_)
  77. constexpr static size_t MAX_COMMAND_LINE = 32 * 1024;
  78. std::wstring GetWString(const char* astring) {
  79. if (!astring) {
  80. return std::wstring();
  81. }
  82. std::string str(astring);
  83. return std::wstring(str.begin(), str.end());
  84. }
  85. std::string GetAString(const wchar_t* wstring) {
  86. if (!wstring) {
  87. return std::string();
  88. }
  89. std::wstring str(wstring);
  90. return std::string(str.begin(), str.end());
  91. }
  92. #endif
  93. } // namespace
  94. // temporary measure to avoid rewriting all poll calls on win TPipeHandle
  95. #if defined(_win_)
  96. using REALPIPEHANDLE = HANDLE;
  97. #define INVALID_REALPIPEHANDLE INVALID_HANDLE_VALUE
  98. class TRealPipeHandle
  99. : public TNonCopyable {
  100. public:
  101. inline TRealPipeHandle() noexcept
  102. : Fd_(INVALID_REALPIPEHANDLE)
  103. {
  104. }
  105. inline TRealPipeHandle(REALPIPEHANDLE fd) noexcept
  106. : Fd_(fd)
  107. {
  108. }
  109. inline ~TRealPipeHandle() {
  110. Close();
  111. }
  112. bool Close() noexcept {
  113. bool ok = true;
  114. if (Fd_ != INVALID_REALPIPEHANDLE) {
  115. ok = CloseHandle(Fd_);
  116. }
  117. Fd_ = INVALID_REALPIPEHANDLE;
  118. return ok;
  119. }
  120. inline REALPIPEHANDLE Release() noexcept {
  121. REALPIPEHANDLE ret = Fd_;
  122. Fd_ = INVALID_REALPIPEHANDLE;
  123. return ret;
  124. }
  125. inline void Swap(TRealPipeHandle& r) noexcept {
  126. DoSwap(Fd_, r.Fd_);
  127. }
  128. inline operator REALPIPEHANDLE() const noexcept {
  129. return Fd_;
  130. }
  131. inline bool IsOpen() const noexcept {
  132. return Fd_ != INVALID_REALPIPEHANDLE;
  133. }
  134. ssize_t Read(void* buffer, size_t byteCount) const noexcept {
  135. DWORD doneBytes;
  136. if (!ReadFile(Fd_, buffer, byteCount, &doneBytes, nullptr)) {
  137. return -1;
  138. }
  139. return doneBytes;
  140. }
  141. ssize_t Write(const void* buffer, size_t byteCount) const noexcept {
  142. DWORD doneBytes;
  143. if (!WriteFile(Fd_, buffer, byteCount, &doneBytes, nullptr)) {
  144. return -1;
  145. }
  146. return doneBytes;
  147. }
  148. static void Pipe(TRealPipeHandle& reader, TRealPipeHandle& writer, EOpenMode mode) {
  149. (void)mode;
  150. REALPIPEHANDLE fds[2];
  151. if (!CreatePipe(&fds[0], &fds[1], nullptr /* handles are not inherited */, 0)) {
  152. ythrow TFileError() << "failed to create a pipe";
  153. }
  154. TRealPipeHandle(fds[0]).Swap(reader);
  155. TRealPipeHandle(fds[1]).Swap(writer);
  156. }
  157. private:
  158. REALPIPEHANDLE Fd_;
  159. };
  160. #else
  161. using TRealPipeHandle = TPipeHandle;
  162. using REALPIPEHANDLE = PIPEHANDLE;
  163. #define INVALID_REALPIPEHANDLE INVALID_PIPEHANDLE
  164. #endif
  165. class TShellCommand::TImpl
  166. : public TAtomicRefCount<TShellCommand::TImpl> {
  167. private:
  168. TString Command;
  169. TList<TString> Arguments;
  170. TShellCommandOptions Options_;
  171. TString WorkDir;
  172. TShellCommandOptions::EHandleMode InputMode = TShellCommandOptions::HANDLE_STREAM;
  173. TPid Pid;
  174. std::atomic<size_t> ExecutionStatus; // TShellCommand::ECommandStatus
  175. TThread* WatchThread;
  176. bool TerminateFlag = false;
  177. TMaybe<int> ExitCode;
  178. TString CollectedOutput;
  179. TString CollectedError;
  180. TString InternalError;
  181. TMutex TerminateMutex;
  182. TFileHandle InputHandle;
  183. TFileHandle OutputHandle;
  184. TFileHandle ErrorHandle;
  185. private:
  186. struct TProcessInfo {
  187. TImpl* Parent;
  188. TRealPipeHandle InputFd;
  189. TRealPipeHandle OutputFd;
  190. TRealPipeHandle ErrorFd;
  191. TProcessInfo(TImpl* parent, REALPIPEHANDLE inputFd, REALPIPEHANDLE outputFd, REALPIPEHANDLE errorFd)
  192. : Parent(parent)
  193. , InputFd(inputFd)
  194. , OutputFd(outputFd)
  195. , ErrorFd(errorFd)
  196. {
  197. }
  198. };
  199. struct TPipes {
  200. TRealPipeHandle OutputPipeFd[2];
  201. TRealPipeHandle ErrorPipeFd[2];
  202. TRealPipeHandle InputPipeFd[2];
  203. // pipes are closed by automatic dtor
  204. void PrepareParents() {
  205. if (OutputPipeFd[1].IsOpen()) {
  206. OutputPipeFd[1].Close();
  207. }
  208. if (ErrorPipeFd[1].IsOpen()) {
  209. ErrorPipeFd[1].Close();
  210. }
  211. if (InputPipeFd[1].IsOpen()) {
  212. InputPipeFd[0].Close();
  213. }
  214. }
  215. void ReleaseParents() {
  216. InputPipeFd[1].Release();
  217. OutputPipeFd[0].Release();
  218. ErrorPipeFd[0].Release();
  219. }
  220. };
  221. struct TPipePump {
  222. TRealPipeHandle* Pipe;
  223. IOutputStream* OutputStream;
  224. IInputStream* InputStream;
  225. std::atomic<bool>* ShouldClosePipe;
  226. TString InternalError;
  227. };
  228. #if defined(_unix_)
  229. void OnFork(TPipes& pipes, sigset_t oldmask, char* const* argv, char* const* envp, const std::function<void()>& afterFork) const;
  230. #else
  231. void StartProcess(TPipes& pipes);
  232. #endif
  233. public:
  234. inline TImpl(const TStringBuf cmd, const TList<TString>& args, const TShellCommandOptions& options, const TString& workdir)
  235. : Command(ToString(cmd))
  236. , Arguments(args)
  237. , Options_(options)
  238. , WorkDir(workdir)
  239. , InputMode(options.InputMode)
  240. , Pid(0)
  241. , ExecutionStatus(SHELL_NONE)
  242. , WatchThread(nullptr)
  243. , TerminateFlag(false)
  244. {
  245. if (Options_.InputStream) {
  246. // TODO change usages to call SetInputStream instead of directly assigning to InputStream
  247. InputMode = TShellCommandOptions::HANDLE_STREAM;
  248. }
  249. }
  250. inline ~TImpl() {
  251. if (WatchThread) {
  252. with_lock (TerminateMutex) {
  253. TerminateFlag = true;
  254. }
  255. delete WatchThread;
  256. }
  257. #if defined(_win_)
  258. if (Pid) {
  259. CloseHandle(Pid);
  260. }
  261. #endif
  262. }
  263. inline void AppendArgument(const TStringBuf argument) {
  264. if (ExecutionStatus.load(std::memory_order_acquire) == SHELL_RUNNING) {
  265. ythrow yexception() << "You cannot change command parameters while process is running";
  266. }
  267. Arguments.push_back(ToString(argument));
  268. }
  269. inline const TString& GetOutput() const {
  270. if (ExecutionStatus.load(std::memory_order_acquire) == SHELL_RUNNING) {
  271. ythrow yexception() << "You cannot retrieve output while process is running.";
  272. }
  273. return CollectedOutput;
  274. }
  275. inline const TString& GetError() const {
  276. if (ExecutionStatus.load(std::memory_order_acquire) == SHELL_RUNNING) {
  277. ythrow yexception() << "You cannot retrieve output while process is running.";
  278. }
  279. return CollectedError;
  280. }
  281. inline const TString& GetInternalError() const {
  282. if (ExecutionStatus.load(std::memory_order_acquire) != SHELL_INTERNAL_ERROR) {
  283. ythrow yexception() << "Internal error hasn't occured so can't be retrieved.";
  284. }
  285. return InternalError;
  286. }
  287. inline ECommandStatus GetStatus() const {
  288. return static_cast<ECommandStatus>(ExecutionStatus.load(std::memory_order_acquire));
  289. }
  290. inline TMaybe<int> GetExitCode() const {
  291. return ExitCode;
  292. }
  293. inline TProcessId GetPid() const {
  294. #if defined(_win_)
  295. return GetProcessId(Pid);
  296. #else
  297. return Pid;
  298. #endif
  299. }
  300. inline TFileHandle& GetInputHandle() {
  301. return InputHandle;
  302. }
  303. inline TFileHandle& GetOutputHandle() {
  304. return OutputHandle;
  305. }
  306. inline TFileHandle& GetErrorHandle() {
  307. return ErrorHandle;
  308. }
  309. // start child process
  310. void Run();
  311. inline void Terminate(int signal) {
  312. if (!!Pid && (ExecutionStatus.load(std::memory_order_acquire) == SHELL_RUNNING)) {
  313. #if defined(_unix_)
  314. bool ok = kill(Options_.DetachSession ? -1 * Pid : Pid, signal) == 0;
  315. if (!ok && (errno == ESRCH) && Options_.DetachSession) {
  316. // this could fail when called before child proc completes setsid().
  317. ok = kill(Pid, signal) == 0;
  318. kill(-Pid, signal); // between a failed kill(-Pid) and a successful kill(Pid) a grandchild could have been spawned
  319. }
  320. #else
  321. Y_UNUSED(signal);
  322. bool ok = TerminateProcess(Pid, 1 /* exit code */);
  323. #endif
  324. if (!ok) {
  325. ythrow TSystemError() << "cannot terminate " << Pid;
  326. }
  327. }
  328. }
  329. inline void Wait() {
  330. if (WatchThread) {
  331. WatchThread->Join();
  332. }
  333. }
  334. inline void CloseInput() {
  335. Options_.ShouldCloseInput.store(true);
  336. }
  337. inline static bool TerminateIsRequired(void* processInfo) {
  338. TProcessInfo* pi = reinterpret_cast<TProcessInfo*>(processInfo);
  339. if (!pi->Parent->TerminateFlag) {
  340. return false;
  341. }
  342. pi->InputFd.Close();
  343. pi->ErrorFd.Close();
  344. pi->OutputFd.Close();
  345. if (pi->Parent->Options_.CloseStreams) {
  346. if (pi->Parent->Options_.ErrorStream) {
  347. pi->Parent->Options_.ErrorStream->Finish();
  348. }
  349. if (pi->Parent->Options_.OutputStream) {
  350. pi->Parent->Options_.OutputStream->Finish();
  351. }
  352. }
  353. delete pi;
  354. return true;
  355. }
  356. // interchange io while process is alive
  357. inline static void Communicate(TProcessInfo* pi);
  358. inline static void* WatchProcess(void* data) {
  359. TProcessInfo* pi = reinterpret_cast<TProcessInfo*>(data);
  360. Communicate(pi);
  361. return nullptr;
  362. }
  363. inline static void* ReadStream(void* data) noexcept {
  364. TPipePump* pump = reinterpret_cast<TPipePump*>(data);
  365. try {
  366. int bytes = 0;
  367. TBuffer buffer(DATA_BUFFER_SIZE);
  368. while (true) {
  369. bytes = pump->Pipe->Read(buffer.Data(), buffer.Capacity());
  370. if (bytes > 0) {
  371. pump->OutputStream->Write(buffer.Data(), bytes);
  372. } else {
  373. break;
  374. }
  375. }
  376. if (pump->Pipe->IsOpen()) {
  377. pump->Pipe->Close();
  378. }
  379. } catch (...) {
  380. pump->InternalError = CurrentExceptionMessage();
  381. }
  382. return nullptr;
  383. }
  384. inline static void* WriteStream(void* data) noexcept {
  385. TPipePump* pump = reinterpret_cast<TPipePump*>(data);
  386. try {
  387. int bytes = 0;
  388. int bytesToWrite = 0;
  389. char* bufPos = nullptr;
  390. TBuffer buffer(DATA_BUFFER_SIZE);
  391. while (true) {
  392. if (!bytesToWrite) {
  393. bytesToWrite = pump->InputStream->Read(buffer.Data(), buffer.Capacity());
  394. if (bytesToWrite == 0) {
  395. if (pump->ShouldClosePipe->load(std::memory_order_acquire)) {
  396. break;
  397. }
  398. continue;
  399. }
  400. bufPos = buffer.Data();
  401. }
  402. bytes = pump->Pipe->Write(bufPos, bytesToWrite);
  403. if (bytes > 0) {
  404. bytesToWrite -= bytes;
  405. bufPos += bytes;
  406. } else {
  407. break;
  408. }
  409. }
  410. if (pump->Pipe->IsOpen()) {
  411. pump->Pipe->Close();
  412. }
  413. } catch (...) {
  414. pump->InternalError = CurrentExceptionMessage();
  415. }
  416. return nullptr;
  417. }
  418. TString GetQuotedCommand() const;
  419. };
  420. #if defined(_win_)
  421. void TShellCommand::TImpl::StartProcess(TShellCommand::TImpl::TPipes& pipes) {
  422. // Setup STARTUPINFO to redirect handles.
  423. STARTUPINFOW startup_info;
  424. ZeroMemory(&startup_info, sizeof(startup_info));
  425. startup_info.cb = sizeof(startup_info);
  426. startup_info.dwFlags = STARTF_USESTDHANDLES;
  427. if (Options_.OutputMode != TShellCommandOptions::HANDLE_INHERIT) {
  428. if (!SetHandleInformation(pipes.OutputPipeFd[1], HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT)) {
  429. ythrow TSystemError() << "cannot set handle info";
  430. }
  431. }
  432. if (Options_.ErrorMode != TShellCommandOptions::HANDLE_INHERIT) {
  433. if (!SetHandleInformation(pipes.ErrorPipeFd[1], HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT)) {
  434. ythrow TSystemError() << "cannot set handle info";
  435. }
  436. }
  437. if (InputMode != TShellCommandOptions::HANDLE_INHERIT) {
  438. if (!SetHandleInformation(pipes.InputPipeFd[0], HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT)) {
  439. ythrow TSystemError() << "cannot set handle info";
  440. }
  441. }
  442. // A sockets do not work as std streams for some reason
  443. if (Options_.OutputMode != TShellCommandOptions::HANDLE_INHERIT) {
  444. startup_info.hStdOutput = pipes.OutputPipeFd[1];
  445. } else {
  446. startup_info.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
  447. }
  448. if (Options_.ErrorMode != TShellCommandOptions::HANDLE_INHERIT) {
  449. startup_info.hStdError = pipes.ErrorPipeFd[1];
  450. } else {
  451. startup_info.hStdError = GetStdHandle(STD_ERROR_HANDLE);
  452. }
  453. if (InputMode != TShellCommandOptions::HANDLE_INHERIT) {
  454. startup_info.hStdInput = pipes.InputPipeFd[0];
  455. } else {
  456. // Don't leave hStdInput unfilled, otherwise any attempt to retrieve the operating-system file handle
  457. // that is associated with the specified file descriptor will led to errors.
  458. startup_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
  459. }
  460. PROCESS_INFORMATION process_info;
  461. // TString cmd = "cmd /U" + TUtf16String can be used to read unicode messages from cmd
  462. // /A - ansi charset /Q - echo off, /C - command, /Q - special quotes
  463. TString qcmd = GetQuotedCommand();
  464. TString cmd = Options_.UseShell ? "cmd /A /Q /S /C \"" + qcmd + "\"" : qcmd;
  465. // winapi can modify command text, copy it
  466. Y_ENSURE_EX(cmd.size() < MAX_COMMAND_LINE, yexception() << "Command is too long (length=" << cmd.size() << ")");
  467. TTempArray<wchar_t> cmdcopy(MAX_COMMAND_LINE);
  468. Copy(cmd.data(), cmd.data() + cmd.size(), cmdcopy.Data());
  469. *(cmdcopy.Data() + cmd.size()) = 0;
  470. const wchar_t* cwd = NULL;
  471. std::wstring cwdBuff;
  472. if (WorkDir.size()) {
  473. cwdBuff = GetWString(WorkDir.data());
  474. cwd = cwdBuff.c_str();
  475. }
  476. void* lpEnvironment = nullptr;
  477. TString env;
  478. if (!Options_.Environment.empty()) {
  479. for (auto e = Options_.Environment.begin(); e != Options_.Environment.end(); ++e) {
  480. env += e->first + '=' + e->second + '\0';
  481. }
  482. env += '\0';
  483. lpEnvironment = const_cast<char*>(env.data());
  484. }
  485. // disable messagebox (may be in debug too)
  486. #ifndef NDEBUG
  487. SetErrorMode(GetErrorMode() | SEM_NOGPFAULTERRORBOX);
  488. #endif
  489. BOOL res = 0;
  490. if (Options_.User.Name.empty() || GetUsername() == Options_.User.Name) {
  491. res = CreateProcessW(
  492. nullptr, // image name
  493. cmdcopy.Data(),
  494. nullptr, // process security attributes
  495. nullptr, // thread security attributes
  496. TRUE, // inherit handles - needed for IO, CloseAllFdsOnExec not respected
  497. 0, // obscure creation flags
  498. lpEnvironment, // environment
  499. cwd, // current directory
  500. &startup_info,
  501. &process_info);
  502. } else {
  503. res = CreateProcessWithLogonW(
  504. GetWString(Options_.User.Name.data()).c_str(),
  505. nullptr, // domain (if this parameter is NULL, the user name must be specified in UPN format)
  506. GetWString(Options_.User.Password.data()).c_str(),
  507. 0, // logon flags
  508. NULL, // image name
  509. cmdcopy.Data(),
  510. 0, // obscure creation flags
  511. lpEnvironment, // environment
  512. cwd, // current directory
  513. &startup_info,
  514. &process_info);
  515. }
  516. if (!res) {
  517. ExecutionStatus.store(SHELL_ERROR, std::memory_order_release);
  518. /// @todo: write to error stream if set
  519. TStringOutput out(CollectedError);
  520. out << "Process was not created: " << LastSystemErrorText() << " command text was: '" << GetAString(cmdcopy.Data()) << "'";
  521. }
  522. Pid = process_info.hProcess;
  523. CloseHandle(process_info.hThread);
  524. DBG(Cerr << "created process id " << Pid << " in dir: " << cwd << ", cmd: " << cmdcopy.Data() << Endl);
  525. }
  526. #endif
  527. void ShellQuoteArg(TString& dst, TStringBuf argument) {
  528. dst.append("\"");
  529. TStringBuf l, r;
  530. while (argument.TrySplit('"', l, r)) {
  531. dst.append(l);
  532. dst.append("\\\"");
  533. argument = r;
  534. }
  535. dst.append(argument);
  536. dst.append("\"");
  537. }
  538. void ShellQuoteArgSp(TString& dst, TStringBuf argument) {
  539. dst.append(' ');
  540. ShellQuoteArg(dst, argument);
  541. }
  542. bool ArgNeedsQuotes(TStringBuf arg) noexcept {
  543. if (arg.empty()) {
  544. return true;
  545. }
  546. return arg.find_first_of(" \"\'\t&()*<>\\`^|") != TString::npos;
  547. }
  548. TString TShellCommand::TImpl::GetQuotedCommand() const {
  549. TString quoted = Command; /// @todo command itself should be quoted too
  550. for (const auto& argument : Arguments) {
  551. // Don't add unnecessary quotes. It's especially important for the windows with a 32k command line length limit.
  552. if (Options_.QuoteArguments && ArgNeedsQuotes(argument)) {
  553. ::ShellQuoteArgSp(quoted, argument);
  554. } else {
  555. quoted.append(" ").append(argument);
  556. }
  557. }
  558. return quoted;
  559. }
  560. #if defined(_unix_)
  561. void TShellCommand::TImpl::OnFork(TPipes& pipes, sigset_t oldmask, char* const* argv, char* const* envp, const std::function<void()>& afterFork) const {
  562. try {
  563. if (Options_.DetachSession) {
  564. setsid();
  565. }
  566. // reset signal handlers from parent
  567. struct sigaction sa;
  568. sa.sa_handler = SIG_DFL;
  569. sa.sa_flags = 0;
  570. SigEmptySet(&sa.sa_mask);
  571. for (int i = 0; i < NSIG; ++i) {
  572. // some signals cannot be caught, so just ignore return value
  573. sigaction(i, &sa, nullptr);
  574. }
  575. if (Options_.ClearSignalMask) {
  576. SigEmptySet(&oldmask);
  577. }
  578. // clear / restore signal mask
  579. if (SigProcMask(SIG_SETMASK, &oldmask, nullptr) != 0) {
  580. ythrow TSystemError() << "Cannot " << (Options_.ClearSignalMask ? "clear" : "restore") << " signal mask in child";
  581. }
  582. TFileHandle sIn(0);
  583. TFileHandle sOut(1);
  584. TFileHandle sErr(2);
  585. if (InputMode != TShellCommandOptions::HANDLE_INHERIT) {
  586. pipes.InputPipeFd[1].Close();
  587. TFileHandle sInNew(pipes.InputPipeFd[0]);
  588. sIn.LinkTo(sInNew);
  589. sIn.Release();
  590. sInNew.Release();
  591. } else {
  592. // do not close fd 0 - next open will return it and confuse all readers
  593. /// @todo in case of real need - reopen /dev/null
  594. }
  595. if (Options_.OutputMode != TShellCommandOptions::HANDLE_INHERIT) {
  596. pipes.OutputPipeFd[0].Close();
  597. TFileHandle sOutNew(pipes.OutputPipeFd[1]);
  598. sOut.LinkTo(sOutNew);
  599. sOut.Release();
  600. sOutNew.Release();
  601. }
  602. if (Options_.ErrorMode != TShellCommandOptions::HANDLE_INHERIT) {
  603. pipes.ErrorPipeFd[0].Close();
  604. TFileHandle sErrNew(pipes.ErrorPipeFd[1]);
  605. sErr.LinkTo(sErrNew);
  606. sErr.Release();
  607. sErrNew.Release();
  608. }
  609. if (WorkDir.size()) {
  610. NFs::SetCurrentWorkingDirectory(WorkDir);
  611. }
  612. if (Options_.CloseAllFdsOnExec) {
  613. for (int fd = NSystemInfo::MaxOpenFiles(); fd > STDERR_FILENO; --fd) {
  614. fcntl(fd, F_SETFD, FD_CLOEXEC);
  615. }
  616. }
  617. if (!Options_.User.Name.empty()) {
  618. ImpersonateUser(Options_.User);
  619. }
  620. if (Options_.Nice) {
  621. // Don't verify Nice() call - it does not work properly with WSL https://github.com/Microsoft/WSL/issues/1838
  622. ::Nice(Options_.Nice);
  623. }
  624. if (afterFork) {
  625. afterFork();
  626. }
  627. if (envp == nullptr) {
  628. execvp(argv[0], argv);
  629. } else {
  630. execve(argv[0], argv, envp);
  631. }
  632. Cerr << "Process was not created: " << LastSystemErrorText() << Endl;
  633. } catch (const std::exception& error) {
  634. Cerr << "Process was not created: " << error.what() << Endl;
  635. } catch (...) {
  636. Cerr << "Process was not created: "
  637. << "unknown error" << Endl;
  638. }
  639. _exit(-1);
  640. }
  641. #endif
  642. void TShellCommand::TImpl::Run() {
  643. Y_ENSURE(ExecutionStatus.load(std::memory_order_acquire) != SHELL_RUNNING, TStringBuf("Process is already running"));
  644. // Prepare I/O streams
  645. CollectedOutput.clear();
  646. CollectedError.clear();
  647. TPipes pipes;
  648. if (Options_.OutputMode != TShellCommandOptions::HANDLE_INHERIT) {
  649. TRealPipeHandle::Pipe(pipes.OutputPipeFd[0], pipes.OutputPipeFd[1], CloseOnExec);
  650. }
  651. if (Options_.ErrorMode != TShellCommandOptions::HANDLE_INHERIT) {
  652. TRealPipeHandle::Pipe(pipes.ErrorPipeFd[0], pipes.ErrorPipeFd[1], CloseOnExec);
  653. }
  654. if (InputMode != TShellCommandOptions::HANDLE_INHERIT) {
  655. TRealPipeHandle::Pipe(pipes.InputPipeFd[0], pipes.InputPipeFd[1], CloseOnExec);
  656. }
  657. ExecutionStatus.store(SHELL_RUNNING, std::memory_order_release);
  658. #if defined(_unix_)
  659. // block all signals to avoid signal handler race after fork()
  660. sigset_t oldmask, newmask;
  661. SigFillSet(&newmask);
  662. if (SigProcMask(SIG_SETMASK, &newmask, &oldmask) != 0) {
  663. ythrow TSystemError() << "Cannot block all signals in parent";
  664. }
  665. /* arguments holders */
  666. TString shellArg;
  667. TVector<char*> qargv;
  668. /*
  669. Following "const_cast"s are safe:
  670. http://pubs.opengroup.org/onlinepubs/9699919799/functions/exec.html
  671. */
  672. if (Options_.UseShell) {
  673. shellArg = GetQuotedCommand();
  674. qargv.reserve(4);
  675. qargv.push_back(const_cast<char*>("/bin/sh"));
  676. qargv.push_back(const_cast<char*>("-c"));
  677. // two args for 'sh -c -- ',
  678. // one for program name, and one for NULL at the end
  679. qargv.push_back(const_cast<char*>(shellArg.data()));
  680. } else {
  681. qargv.reserve(Arguments.size() + 2);
  682. qargv.push_back(const_cast<char*>(Command.data()));
  683. for (auto& i : Arguments) {
  684. qargv.push_back(const_cast<char*>(i.data()));
  685. }
  686. }
  687. qargv.push_back(nullptr);
  688. TVector<TString> envHolder;
  689. TVector<char*> envp;
  690. if (!Options_.Environment.empty()) {
  691. for (auto& env : Options_.Environment) {
  692. envHolder.emplace_back(env.first + '=' + env.second);
  693. envp.push_back(const_cast<char*>(envHolder.back().data()));
  694. }
  695. envp.push_back(nullptr);
  696. }
  697. pid_t pid = fork();
  698. if (pid == -1) {
  699. ExecutionStatus.store(SHELL_ERROR, std::memory_order_release);
  700. /// @todo check if pipes are still open
  701. ythrow TSystemError() << "Cannot fork";
  702. } else if (pid == 0) { // child
  703. if (envp.size() != 0) {
  704. OnFork(pipes, oldmask, qargv.data(), envp.data(), Options_.FuncAfterFork);
  705. } else {
  706. OnFork(pipes, oldmask, qargv.data(), nullptr, Options_.FuncAfterFork);
  707. }
  708. } else { // parent
  709. // restore signal mask
  710. if (SigProcMask(SIG_SETMASK, &oldmask, nullptr) != 0) {
  711. ythrow TSystemError() << "Cannot restore signal mask in parent";
  712. }
  713. }
  714. Pid = pid;
  715. #else
  716. StartProcess(pipes);
  717. #endif
  718. pipes.PrepareParents();
  719. if (ExecutionStatus.load(std::memory_order_acquire) != SHELL_RUNNING) {
  720. return;
  721. }
  722. if (InputMode == TShellCommandOptions::HANDLE_PIPE) {
  723. TFileHandle inputHandle(pipes.InputPipeFd[1].Release());
  724. InputHandle.Swap(inputHandle);
  725. }
  726. if (Options_.OutputMode == TShellCommandOptions::HANDLE_PIPE) {
  727. TFileHandle outputHandle(pipes.OutputPipeFd[0].Release());
  728. OutputHandle.Swap(outputHandle);
  729. }
  730. if (Options_.ErrorMode == TShellCommandOptions::HANDLE_PIPE) {
  731. TFileHandle errorHandle(pipes.ErrorPipeFd[0].Release());
  732. ErrorHandle.Swap(errorHandle);
  733. }
  734. TProcessInfo* processInfo = new TProcessInfo(this,
  735. pipes.InputPipeFd[1].Release(), pipes.OutputPipeFd[0].Release(), pipes.ErrorPipeFd[0].Release());
  736. if (Options_.AsyncMode) {
  737. WatchThread = new TThread(&TImpl::WatchProcess, processInfo);
  738. WatchThread->Start();
  739. /// @todo wait for child to start its process session (if options.Detach)
  740. } else {
  741. Communicate(processInfo);
  742. }
  743. pipes.ReleaseParents(); // not needed
  744. }
  745. void TShellCommand::TImpl::Communicate(TProcessInfo* pi) {
  746. THolder<IOutputStream> outputHolder;
  747. IOutputStream* output = pi->Parent->Options_.OutputStream;
  748. if (!output) {
  749. outputHolder.Reset(output = new TStringOutput(pi->Parent->CollectedOutput));
  750. }
  751. THolder<IOutputStream> errorHolder;
  752. IOutputStream* error = pi->Parent->Options_.ErrorStream;
  753. if (!error) {
  754. errorHolder.Reset(error = new TStringOutput(pi->Parent->CollectedError));
  755. }
  756. IInputStream*& input = pi->Parent->Options_.InputStream;
  757. #if defined(_unix_)
  758. // not really needed, io is done via poll
  759. if (pi->OutputFd.IsOpen()) {
  760. SetNonBlock(pi->OutputFd);
  761. }
  762. if (pi->ErrorFd.IsOpen()) {
  763. SetNonBlock(pi->ErrorFd);
  764. }
  765. if (pi->InputFd.IsOpen()) {
  766. SetNonBlock(pi->InputFd);
  767. }
  768. #endif
  769. try {
  770. #if defined(_win_)
  771. TPipePump pumps[3] = {0};
  772. pumps[0] = {&pi->ErrorFd, error};
  773. pumps[1] = {&pi->OutputFd, output};
  774. TVector<THolder<TThread>> streamThreads;
  775. streamThreads.emplace_back(new TThread(&TImpl::ReadStream, &pumps[0]));
  776. streamThreads.emplace_back(new TThread(&TImpl::ReadStream, &pumps[1]));
  777. if (input) {
  778. pumps[2] = {&pi->InputFd, nullptr, input, &pi->Parent->Options_.ShouldCloseInput};
  779. streamThreads.emplace_back(new TThread(&TImpl::WriteStream, &pumps[2]));
  780. }
  781. for (auto& threadHolder : streamThreads) {
  782. threadHolder->Start();
  783. }
  784. #else
  785. TBuffer buffer(DATA_BUFFER_SIZE);
  786. TBuffer inputBuffer(DATA_BUFFER_SIZE);
  787. int bytes;
  788. int bytesToWrite = 0;
  789. char* bufPos = nullptr;
  790. #endif
  791. TWaitResult waitPidResult;
  792. TExitStatus status = 0;
  793. while (true) {
  794. {
  795. with_lock (pi->Parent->TerminateMutex) {
  796. if (TerminateIsRequired(pi)) {
  797. return;
  798. }
  799. }
  800. waitPidResult =
  801. #if defined(_unix_)
  802. waitpid(pi->Parent->Pid, &status, WNOHANG);
  803. #else
  804. WaitForSingleObject(pi->Parent->Pid /* process_info.hProcess */, pi->Parent->Options_.PollDelayMs /* ms */);
  805. Y_UNUSED(status);
  806. #endif
  807. // DBG(Cerr << "wait result: " << waitPidResult << Endl);
  808. if (waitPidResult != WAIT_PROCEED) {
  809. break;
  810. }
  811. }
  812. /// @todo factor out (poll + wfmo)
  813. #if defined(_unix_)
  814. bool haveIn = false;
  815. bool haveOut = false;
  816. bool haveErr = false;
  817. if (!input && pi->InputFd.IsOpen()) {
  818. DBG(Cerr << "closing input stream..." << Endl);
  819. pi->InputFd.Close();
  820. }
  821. if (!output && pi->OutputFd.IsOpen()) {
  822. DBG(Cerr << "closing output stream..." << Endl);
  823. pi->OutputFd.Close();
  824. }
  825. if (!error && pi->ErrorFd.IsOpen()) {
  826. DBG(Cerr << "closing error stream..." << Endl);
  827. pi->ErrorFd.Close();
  828. }
  829. if (!input && !output && !error) {
  830. continue;
  831. }
  832. struct pollfd fds[] = {
  833. {REALPIPEHANDLE(pi->InputFd), POLLOUT, 0},
  834. {REALPIPEHANDLE(pi->OutputFd), POLLIN, 0},
  835. {REALPIPEHANDLE(pi->ErrorFd), POLLIN, 0}};
  836. int res;
  837. if (!input) {
  838. fds[0].events = 0;
  839. }
  840. if (!output) {
  841. fds[1].events = 0;
  842. }
  843. if (!error) {
  844. fds[2].events = 0;
  845. }
  846. res = PollD(fds, 3, TInstant::Now() + TDuration::MilliSeconds(pi->Parent->Options_.PollDelayMs));
  847. // DBG(Cerr << "poll result: " << res << Endl);
  848. if (-res == ETIMEDOUT || res == 0) {
  849. // DBG(Cerr << "poll again..." << Endl);
  850. continue;
  851. }
  852. if (res < 0) {
  853. ythrow yexception() << "poll failed: " << LastSystemErrorText();
  854. }
  855. if ((fds[1].revents & POLLIN) == POLLIN) {
  856. haveOut = true;
  857. } else if (fds[1].revents & (POLLERR | POLLHUP)) {
  858. output = nullptr;
  859. }
  860. if ((fds[2].revents & POLLIN) == POLLIN) {
  861. haveErr = true;
  862. } else if (fds[2].revents & (POLLERR | POLLHUP)) {
  863. error = nullptr;
  864. }
  865. if (input && ((fds[0].revents & POLLOUT) == POLLOUT)) {
  866. haveIn = true;
  867. }
  868. if (haveOut) {
  869. bytes = pi->OutputFd.Read(buffer.Data(), buffer.Capacity());
  870. DBG(Cerr << "transferred " << bytes << " bytes of output" << Endl);
  871. if (bytes > 0) {
  872. output->Write(buffer.Data(), bytes);
  873. } else {
  874. output = nullptr;
  875. }
  876. }
  877. if (haveErr) {
  878. bytes = pi->ErrorFd.Read(buffer.Data(), buffer.Capacity());
  879. DBG(Cerr << "transferred " << bytes << " bytes of error" << Endl);
  880. if (bytes > 0) {
  881. error->Write(buffer.Data(), bytes);
  882. } else {
  883. error = nullptr;
  884. }
  885. }
  886. if (haveIn) {
  887. if (!bytesToWrite) {
  888. bytesToWrite = input->Read(inputBuffer.Data(), inputBuffer.Capacity());
  889. if (bytesToWrite == 0) {
  890. if (pi->Parent->Options_.ShouldCloseInput.load(std::memory_order_acquire)) {
  891. input = nullptr;
  892. }
  893. continue;
  894. }
  895. bufPos = inputBuffer.Data();
  896. }
  897. bytes = pi->InputFd.Write(bufPos, bytesToWrite);
  898. if (bytes > 0) {
  899. bytesToWrite -= bytes;
  900. bufPos += bytes;
  901. } else {
  902. input = nullptr;
  903. }
  904. DBG(Cerr << "transferred " << bytes << " bytes of input" << Endl);
  905. }
  906. #endif
  907. }
  908. DBG(Cerr << "process finished" << Endl);
  909. // What's the reason of process exit.
  910. // We need to set exit code before waiting for input thread
  911. // Otherwise there is no way for input stream provider to discover
  912. // that process has exited and stream shouldn't wait for new data.
  913. bool cleanExit = false;
  914. TMaybe<int> processExitCode;
  915. #if defined(_unix_)
  916. processExitCode = WEXITSTATUS(status);
  917. if (WIFEXITED(status) && processExitCode == 0) {
  918. cleanExit = true;
  919. } else if (WIFSIGNALED(status)) {
  920. processExitCode = -WTERMSIG(status);
  921. }
  922. #else
  923. if (waitPidResult == WAIT_OBJECT_0) {
  924. DWORD exitCode = STILL_ACTIVE;
  925. if (!GetExitCodeProcess(pi->Parent->Pid, &exitCode)) {
  926. ythrow yexception() << "GetExitCodeProcess: " << LastSystemErrorText();
  927. }
  928. if (exitCode == 0) {
  929. cleanExit = true;
  930. }
  931. processExitCode = static_cast<int>(exitCode);
  932. DBG(Cerr << "exit code: " << exitCode << Endl);
  933. }
  934. #endif
  935. pi->Parent->ExitCode = processExitCode;
  936. if (cleanExit) {
  937. pi->Parent->ExecutionStatus.store(SHELL_FINISHED, std::memory_order_release);
  938. } else {
  939. pi->Parent->ExecutionStatus.store(SHELL_ERROR, std::memory_order_release);
  940. }
  941. #if defined(_win_)
  942. for (auto& threadHolder : streamThreads) {
  943. threadHolder->Join();
  944. }
  945. for (const auto pump : pumps) {
  946. if (!pump.InternalError.empty()) {
  947. throw yexception() << pump.InternalError;
  948. }
  949. }
  950. #else
  951. // Now let's read remaining stdout/stderr
  952. while (output && (bytes = pi->OutputFd.Read(buffer.Data(), buffer.Capacity())) > 0) {
  953. DBG(Cerr << bytes << " more bytes of output: " << Endl);
  954. output->Write(buffer.Data(), bytes);
  955. }
  956. while (error && (bytes = pi->ErrorFd.Read(buffer.Data(), buffer.Capacity())) > 0) {
  957. DBG(Cerr << bytes << " more bytes of error" << Endl);
  958. error->Write(buffer.Data(), bytes);
  959. }
  960. #endif
  961. } catch (const yexception& e) {
  962. // Some error in watch occured, set result to error
  963. pi->Parent->ExecutionStatus.store(SHELL_INTERNAL_ERROR, std::memory_order_release);
  964. pi->Parent->InternalError = e.what();
  965. if (input) {
  966. pi->InputFd.Close();
  967. }
  968. Cdbg << "shell command internal error: " << pi->Parent->InternalError << Endl;
  969. }
  970. // Now we can safely delete process info struct and other data
  971. pi->Parent->TerminateFlag = true;
  972. TerminateIsRequired(pi);
  973. }
  974. TShellCommand::TShellCommand(const TStringBuf cmd, const TList<TString>& args, const TShellCommandOptions& options,
  975. const TString& workdir)
  976. : Impl(new TImpl(cmd, args, options, workdir))
  977. {
  978. }
  979. TShellCommand::TShellCommand(const TStringBuf cmd, const TShellCommandOptions& options, const TString& workdir)
  980. : Impl(new TImpl(cmd, TList<TString>(), options, workdir))
  981. {
  982. }
  983. TShellCommand::~TShellCommand() = default;
  984. TShellCommand& TShellCommand::operator<<(const TStringBuf argument) {
  985. Impl->AppendArgument(argument);
  986. return *this;
  987. }
  988. const TString& TShellCommand::GetOutput() const {
  989. return Impl->GetOutput();
  990. }
  991. const TString& TShellCommand::GetError() const {
  992. return Impl->GetError();
  993. }
  994. const TString& TShellCommand::GetInternalError() const {
  995. return Impl->GetInternalError();
  996. }
  997. TShellCommand::ECommandStatus TShellCommand::GetStatus() const {
  998. return Impl->GetStatus();
  999. }
  1000. TMaybe<int> TShellCommand::GetExitCode() const {
  1001. return Impl->GetExitCode();
  1002. }
  1003. TProcessId TShellCommand::GetPid() const {
  1004. return Impl->GetPid();
  1005. }
  1006. TFileHandle& TShellCommand::GetInputHandle() {
  1007. return Impl->GetInputHandle();
  1008. }
  1009. TFileHandle& TShellCommand::GetOutputHandle() {
  1010. return Impl->GetOutputHandle();
  1011. }
  1012. TFileHandle& TShellCommand::GetErrorHandle() {
  1013. return Impl->GetErrorHandle();
  1014. }
  1015. TShellCommand& TShellCommand::Run() {
  1016. Impl->Run();
  1017. return *this;
  1018. }
  1019. TShellCommand& TShellCommand::Terminate(int signal) {
  1020. Impl->Terminate(signal);
  1021. return *this;
  1022. }
  1023. TShellCommand& TShellCommand::Wait() {
  1024. Impl->Wait();
  1025. return *this;
  1026. }
  1027. TShellCommand& TShellCommand::CloseInput() {
  1028. Impl->CloseInput();
  1029. return *this;
  1030. }
  1031. TString TShellCommand::GetQuotedCommand() const {
  1032. return Impl->GetQuotedCommand();
  1033. }