shellcommand.cpp 37 KB

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