diff options
author | kerzum <kerzum@yandex-team.ru> | 2022-02-10 16:49:33 +0300 |
---|---|---|
committer | Daniil Cherednik <dcherednik@yandex-team.ru> | 2022-02-10 16:49:33 +0300 |
commit | 47a7e7b29636bfb2deb1df5f92363b3c75229c95 (patch) | |
tree | 5d5cb817648f650d76cf1076100726fd9b8448e8 /util | |
parent | 9a7232babfd763ccfe827bc70e82e0f50cfd8276 (diff) | |
download | ydb-47a7e7b29636bfb2deb1df5f92363b3c75229c95.tar.gz |
Restoring authorship annotation for <kerzum@yandex-team.ru>. Commit 2 of 2.
Diffstat (limited to 'util')
-rw-r--r-- | util/charset/wide.cpp | 4 | ||||
-rw-r--r-- | util/charset/wide.h | 8 | ||||
-rw-r--r-- | util/datetime/parser.rl6 | 2 | ||||
-rw-r--r-- | util/folder/path.cpp | 84 | ||||
-rw-r--r-- | util/folder/path.h | 34 | ||||
-rw-r--r-- | util/generic/buffer.h | 2 | ||||
-rw-r--r-- | util/generic/buffer_ut.cpp | 10 | ||||
-rw-r--r-- | util/generic/string_ut.cpp | 10 | ||||
-rw-r--r-- | util/generic/ylimits_ut.cpp | 2 | ||||
-rw-r--r-- | util/memory/tempbuf.h | 4 | ||||
-rw-r--r-- | util/stream/zerocopy.h | 10 | ||||
-rw-r--r-- | util/string/escape.cpp | 20 | ||||
-rw-r--r-- | util/string/escape_ut.cpp | 8 | ||||
-rw-r--r-- | util/string/util.cpp | 2 | ||||
-rw-r--r-- | util/system/shellcommand.cpp | 552 | ||||
-rw-r--r-- | util/system/shellcommand.h | 138 | ||||
-rw-r--r-- | util/system/shellcommand_ut.cpp | 224 |
17 files changed, 557 insertions, 557 deletions
diff --git a/util/charset/wide.cpp b/util/charset/wide.cpp index ce2d9dcad8..a287438ddd 100644 --- a/util/charset/wide.cpp +++ b/util/charset/wide.cpp @@ -1,5 +1,5 @@ -#include "wide.h" - +#include "wide.h" + #include <util/generic/mem_copy.h> #include <util/string/strip.h> diff --git a/util/charset/wide.h b/util/charset/wide.h index 8bb3bc2409..04e6928aab 100644 --- a/util/charset/wide.h +++ b/util/charset/wide.h @@ -1,5 +1,5 @@ #pragma once - + #include "recode_result.h" #include "unidata.h" #include "utf8.h" @@ -257,7 +257,7 @@ public: return Begin; } }; - + namespace NDetail { template <bool robust, typename TCharType> inline void UTF8ToWideImplScalar(const unsigned char*& cur, const unsigned char* last, TCharType*& dest) noexcept { @@ -622,7 +622,7 @@ template <typename TChar1, typename TChar2> inline void Copy(const TChar1* first, size_t len, TChar2* result) { Copy(first, first + len, result); } - + //! copies symbols from one character sequence to another without any conversion //! @note this function can be used instead of the template constructor of @c std::basic_string: //! template <typename InputIterator> @@ -635,7 +635,7 @@ inline TStringType CopyTo(const TChar* first, const TChar* last) { Copy(first, last, str.begin()); return str; } - + template <typename TStringType, typename TChar> inline TStringType CopyTo(const TChar* s, size_t n) { TStringType str = TStringType::Uninitialized(n); diff --git a/util/datetime/parser.rl6 b/util/datetime/parser.rl6 index 879550727a..931f09eae1 100644 --- a/util/datetime/parser.rl6 +++ b/util/datetime/parser.rl6 @@ -209,7 +209,7 @@ secondFrac = digit {1,6} >clear_int $update_int @{ secondFracTail = (digit*); zoneZ = [Zz] @set_zone_utc; -zoneOffset = space? . ('+' | '-') >{ Sign = fc == '+' ? 1 : -1; } . int2 @{ DateTimeFields.ZoneOffsetMinutes = Sign * (i32)TDuration::Hours(I).Minutes(); } . (':')? . (int2 @{ DateTimeFields.ZoneOffsetMinutes += I * Sign; })?; +zoneOffset = space? . ('+' | '-') >{ Sign = fc == '+' ? 1 : -1; } . int2 @{ DateTimeFields.ZoneOffsetMinutes = Sign * (i32)TDuration::Hours(I).Minutes(); } . (':')? . (int2 @{ DateTimeFields.ZoneOffsetMinutes += I * Sign; })?; zone = zoneZ | zoneOffset; iso8601date = (year . '-' . month . '-' . day) | (year . month . day); diff --git a/util/folder/path.cpp b/util/folder/path.cpp index ad32a522a8..bfe0c67d68 100644 --- a/util/folder/path.cpp +++ b/util/folder/path.cpp @@ -21,10 +21,10 @@ void TFsPath::CheckDefined() const { } } -bool TFsPath::IsSubpathOf(const TFsPath& that) const { - const TSplit& split = GetSplit(); - const TSplit& rsplit = that.GetSplit(); - +bool TFsPath::IsSubpathOf(const TFsPath& that) const { + const TSplit& split = GetSplit(); + const TSplit& rsplit = that.GetSplit(); + if (rsplit.IsAbsolute != split.IsAbsolute) { return false; } @@ -34,12 +34,12 @@ bool TFsPath::IsSubpathOf(const TFsPath& that) const { } if (rsplit.size() >= split.size()) { - return false; + return false; } - + return std::equal(rsplit.begin(), rsplit.end(), split.begin()); -} - +} + bool TFsPath::IsNonStrictSubpathOf(const TFsPath& that) const { const TSplit& split = GetSplit(); const TSplit& rsplit = that.GetSplit(); @@ -59,12 +59,12 @@ bool TFsPath::IsNonStrictSubpathOf(const TFsPath& that) const { return std::equal(rsplit.begin(), rsplit.end(), split.begin()); } -TFsPath TFsPath::RelativeTo(const TFsPath& root) const { - TSplit split = GetSplit(); - const TSplit& rsplit = root.GetSplit(); +TFsPath TFsPath::RelativeTo(const TFsPath& root) const { + TSplit split = GetSplit(); + const TSplit& rsplit = root.GetSplit(); if (split.Reconstruct() == rsplit.Reconstruct()) { - return TFsPath(); + return TFsPath(); } if (!this->IsSubpathOf(root)) { @@ -74,9 +74,9 @@ TFsPath TFsPath::RelativeTo(const TFsPath& root) const { split.erase(split.begin(), split.begin() + rsplit.size()); split.IsAbsolute = false; - return TFsPath(split.Reconstruct()); -} - + return TFsPath(split.Reconstruct()); +} + TFsPath TFsPath::RelativePath(const TFsPath& root) const { TSplit split = GetSplit(); const TSplit& rsplit = root.GetSplit(); @@ -100,22 +100,22 @@ TFsPath TFsPath::RelativePath(const TFsPath& root) const { return r.size() ? TFsPath(r) : TFsPath(); } -TFsPath TFsPath::Parent() const { +TFsPath TFsPath::Parent() const { if (!IsDefined()) { return TFsPath(); } - TSplit split = GetSplit(); + TSplit split = GetSplit(); if (split.size()) { split.pop_back(); } if (!split.size() && !split.IsAbsolute) { - return TFsPath("."); + return TFsPath("."); } - return TFsPath(split.Reconstruct()); -} - -TFsPath& TFsPath::operator/=(const TFsPath& that) { + return TFsPath(split.Reconstruct()); +} + +TFsPath& TFsPath::operator/=(const TFsPath& that) { if (!IsDefined()) { *this = that; @@ -127,18 +127,18 @@ TFsPath& TFsPath::operator/=(const TFsPath& that) { TSplit split = GetSplit(); const TSplit& rsplit = that.GetSplit(); split.insert(split.end(), rsplit.begin(), rsplit.end()); - *this = TFsPath(split.Reconstruct()); - } - return *this; -} - -TFsPath& TFsPath::Fix() { - // just normalize via reconstruction + *this = TFsPath(split.Reconstruct()); + } + return *this; +} + +TFsPath& TFsPath::Fix() { + // just normalize via reconstruction TFsPath(GetSplit().Reconstruct()).Swap(*this); - return *this; -} - + return *this; +} + TString TFsPath::GetName() const { if (!IsDefined()) { return TString(); @@ -194,20 +194,20 @@ TFsPath::TFsPath() { } TFsPath::TFsPath(const TString& path) - : Path_(path) -{ + : Path_(path) +{ VerifyPath(Path_); } TFsPath::TFsPath(const TStringBuf path) - : Path_(ToString(path)) -{ + : Path_(ToString(path)) +{ VerifyPath(Path_); -} - -TFsPath::TFsPath(const char* path) - : Path_(path) -{ +} + +TFsPath::TFsPath(const char* path) + : Path_(path) +{ } TFsPath TFsPath::Child(const TString& name) const { @@ -215,7 +215,7 @@ TFsPath TFsPath::Child(const TString& name) const { ythrow TIoException() << "child name must not be empty"; } - return *this / name; + return *this / name; } struct TClosedir { diff --git a/util/folder/path.h b/util/folder/path.h index ce5a18f394..2fb4d6b4ef 100644 --- a/util/folder/path.h +++ b/util/folder/path.h @@ -46,7 +46,7 @@ public: inline const char* c_str() const { return Path_.c_str(); - } + } inline operator const TString&() const { return Path_; @@ -60,17 +60,17 @@ public: return Path_ != that.Path_; } - TFsPath& operator/=(const TFsPath& that); + TFsPath& operator/=(const TFsPath& that); friend TFsPath operator/(const TFsPath& s, const TFsPath& p) { TFsPath ret(s); return ret /= p; - } - + } + const TPathSplit& PathSplit() const; - TFsPath& Fix(); - + TFsPath& Fix(); + inline const TString& GetPath() const { return Path_; } @@ -97,7 +97,7 @@ public: * @param that - presumable parent path of this * @return True if this is a subpath of that and false otherwise. */ - bool IsSubpathOf(const TFsPath& that) const; + bool IsSubpathOf(const TFsPath& that) const; /** * TFsPath("/a/b").IsNonStrictSubpathOf("/a") -> true @@ -110,9 +110,9 @@ public: */ bool IsNonStrictSubpathOf(const TFsPath& that) const; - bool IsContainerOf(const TFsPath& that) const { - return that.IsSubpathOf(*this); - } + bool IsContainerOf(const TFsPath& that) const { + return that.IsSubpathOf(*this); + } TFsPath RelativeTo(const TFsPath& root) const; //must be subpath of root @@ -127,11 +127,11 @@ public: TFsPath Parent() const; TString Basename() const { - return GetName(); - } + return GetName(); + } TString Dirname() const { - return Parent(); - } + return Parent(); + } TFsPath Child(const TString& name) const; @@ -168,8 +168,8 @@ public: inline bool Stat(TFileStat& stat) const { stat = TFileStat(Path_.data()); - return stat.Mode; - } + return stat.Mode; + } bool Exists() const; /// false if not exists @@ -211,7 +211,7 @@ private: /// cache mutable TSimpleIntrusivePtr<TSplit> Split_; }; - + namespace NPrivate { inline void AppendToFsPath(TFsPath&) { } diff --git a/util/generic/buffer.h b/util/generic/buffer.h index bdf469da08..9576467404 100644 --- a/util/generic/buffer.h +++ b/util/generic/buffer.h @@ -161,7 +161,7 @@ public: Reserve(len); Pos_ = len; } - + // Method works like Resize, but allocates exact specified number of bytes // rather than rounded up to next power of 2 // Use with care diff --git a/util/generic/buffer_ut.cpp b/util/generic/buffer_ut.cpp index d6c7f70e0c..437d7122ec 100644 --- a/util/generic/buffer_ut.cpp +++ b/util/generic/buffer_ut.cpp @@ -56,22 +56,22 @@ Y_UNIT_TEST_SUITE(TBufferTest) { buf.Resize(10); UNIT_ASSERT_VALUES_EQUAL(buf.size(), 10u); - + buf.Resize(0); UNIT_ASSERT_VALUES_EQUAL(buf.size(), 0u); - + buf.Resize(9); memcpy(buf.data(), content, 9); UNIT_ASSERT_VALUES_EQUAL(TString(buf.data(), buf.size()), "some text"); - + buf.Resize(4); UNIT_ASSERT_VALUES_EQUAL(TString(buf.data(), buf.size()), "some"); } - + Y_UNIT_TEST(TestReserve) { TBuffer buf; UNIT_ASSERT_EQUAL(buf.Capacity(), 0); - + buf.Reserve(4); UNIT_ASSERT_EQUAL(buf.Capacity(), 4); diff --git a/util/generic/string_ut.cpp b/util/generic/string_ut.cpp index b9099371b0..ac82e9091d 100644 --- a/util/generic/string_ut.cpp +++ b/util/generic/string_ut.cpp @@ -23,9 +23,9 @@ static_assert(sizeof(TString) == sizeof(const char*), "expect sizeof(TString) == class TStringTestZero: public TTestBase { UNIT_TEST_SUITE(TStringTestZero); UNIT_TEST(TestZero); - UNIT_TEST_SUITE_END(); + UNIT_TEST_SUITE_END(); -public: +public: void TestZero() { const char data[] = "abc\0def\0"; TString s(data, sizeof(data)); @@ -33,7 +33,7 @@ public: UNIT_ASSERT(s.StartsWith(s)); UNIT_ASSERT(s.EndsWith(s)); UNIT_ASSERT(s.Contains('\0')); - + const char raw_def[] = "def"; const char raw_zero[] = "\0"; TString def(raw_def, sizeof(raw_def) - 1); @@ -52,7 +52,7 @@ public: UNIT_ASSERT(s != copy); copy.replace(copy.size() - 1, 1, "\0", 0, 1); UNIT_ASSERT(s == copy); - + TString prefix(data, 5); UNIT_ASSERT(s.StartsWith(prefix)); UNIT_ASSERT(s != prefix); @@ -60,7 +60,7 @@ public: UNIT_ASSERT(s > s.data()); UNIT_ASSERT(s == TString(s.data(), s.size())); UNIT_ASSERT(data < s); - + s.remove(5); UNIT_ASSERT(s == prefix); } diff --git a/util/generic/ylimits_ut.cpp b/util/generic/ylimits_ut.cpp index 896043a145..f1b3c6858c 100644 --- a/util/generic/ylimits_ut.cpp +++ b/util/generic/ylimits_ut.cpp @@ -133,7 +133,7 @@ void TLimitTest::TestLimits() { UNIT_ASSERT(TestFloatLimits(float())); UNIT_ASSERT(TestFloatLimits(double())); using long_double = long double; - UNIT_ASSERT(RUNNING_ON_VALGRIND || TestFloatLimits(long_double())); + UNIT_ASSERT(RUNNING_ON_VALGRIND || TestFloatLimits(long_double())); } void TLimitTest::TestNan() { diff --git a/util/memory/tempbuf.h b/util/memory/tempbuf.h index 315c30ed86..334670eb1e 100644 --- a/util/memory/tempbuf.h +++ b/util/memory/tempbuf.h @@ -51,7 +51,7 @@ private: }; template <typename T> -class TTempArray: private TTempBuf { +class TTempArray: private TTempBuf { private: static T* TypedPointer(char* pointer) noexcept { return reinterpret_cast<T*>(pointer); @@ -100,4 +100,4 @@ public: T* Proceed(size_t off) { return (T*)TTempBuf::Proceed(RawSize(off)); } -}; +}; diff --git a/util/stream/zerocopy.h b/util/stream/zerocopy.h index dffe304457..3315aa3a51 100644 --- a/util/stream/zerocopy.h +++ b/util/stream/zerocopy.h @@ -1,9 +1,9 @@ #pragma once - + #include <util/system/yassert.h> #include <util/system/defaults.h> #include <util/generic/ylimits.h> - + #include "input.h" class IOutputStream; @@ -22,7 +22,7 @@ class IZeroCopyInput: public IInputStream { public: IZeroCopyInput() noexcept = default; ~IZeroCopyInput() override; - + IZeroCopyInput(IZeroCopyInput&&) noexcept = default; IZeroCopyInput& operator=(IZeroCopyInput&&) noexcept = default; @@ -54,8 +54,8 @@ protected: size_t DoSkip(size_t len) override; ui64 DoReadAll(IOutputStream& out) override; virtual size_t DoNext(const void** ptr, size_t len) = 0; -}; - +}; + /** * Input stream with direct access to the input buffer and ability to undo read * diff --git a/util/string/escape.cpp b/util/string/escape.cpp index 3378a14a17..cd09a7dbd0 100644 --- a/util/string/escape.cpp +++ b/util/string/escape.cpp @@ -5,8 +5,8 @@ #include <util/charset/utf8.h> #include <util/charset/wide.h> -/// @todo: escape trigraphs (eg "??/" is "\") - +/// @todo: escape trigraphs (eg "??/" is "\") + /* REFEREBCES FOR ESCAPE SEQUENCE INTERPRETATION: * C99 p. 6.4.3 Universal character names. * C99 p. 6.4.4.4 Character constants. @@ -31,14 +31,14 @@ * - Hexadecimal escape sequence spans until rightmost non-hexadecimal-digit character. * - Universal character name consists of exactly 4 or 8 hexadecimal digit. * - * by kerzum@ - * It is also required to escape trigraphs that are enabled in compilers by default and - * are also processed inside string literals - * The nine trigraphs and their replacements are - * - * Trigraph: ??( ??) ??< ??> ??= ??/ ??' ??! ??- - * Replacement: [ ] { } # \ ^ | ~ - * + * by kerzum@ + * It is also required to escape trigraphs that are enabled in compilers by default and + * are also processed inside string literals + * The nine trigraphs and their replacements are + * + * Trigraph: ??( ??) ??< ??> ??= ??/ ??' ??! ??- + * Replacement: [ ] { } # \ ^ | ~ + * */ namespace { template <typename TChar> diff --git a/util/string/escape_ut.cpp b/util/string/escape_ut.cpp index 49fb122972..cd38ecffd3 100644 --- a/util/string/escape_ut.cpp +++ b/util/string/escape_ut.cpp @@ -41,7 +41,7 @@ static const TExample CommonTestData[] = { {"Slash\\\\dash!", "Slash\\dash!"}, {R"(There\nare\r\nnewlines.)", "There\nare\r\nnewlines."}, {"There\\tare\\ttabs.", "There\tare\ttabs."}, - + {"There are questions \\x3F\\x3F?", "There are questions ???"}, {"There are questions \\x3F?", "There are questions ??"}, }; @@ -94,15 +94,15 @@ Y_UNIT_TEST_SUITE(TEscapeCTest) { UNIT_ASSERT_VALUES_EQUAL(u"h", EscapeC(u'h')); UNIT_ASSERT_VALUES_EQUAL(u"\\xFF", EscapeC(wchar16(255))); } - + Y_UNIT_TEST(TestEscapeTrigraphs) { UNIT_ASSERT_VALUES_EQUAL("?", EscapeC(TString("?"))); UNIT_ASSERT_VALUES_EQUAL("\\x3F?", EscapeC(TString("??"))); UNIT_ASSERT_VALUES_EQUAL("\\x3F\\x3F?", EscapeC(TString("???"))); - // ok but may cause warning about trigraphs + // ok but may cause warning about trigraphs // UNIT_ASSERT_VALUES_EQUAL("[x]?z", EscapeC(TString("??(x??)?z"))); UNIT_ASSERT_VALUES_EQUAL("\\x3F?x\\x3F\\x3F?z", EscapeC(TString("??x???z"))); - } + } Y_UNIT_TEST(TestUnescapeCCharLen) { auto test = [](const char* str, size_t len) { diff --git a/util/string/util.cpp b/util/string/util.cpp index 4e2cc82437..b14f20bf75 100644 --- a/util/string/util.cpp +++ b/util/string/util.cpp @@ -48,7 +48,7 @@ Tr::Tr(const char* from, const char* to) { Map[(ui8)*from] = *to; } } - + size_t Tr::FindFirstChangePosition(const TString& str) const { for (auto it = str.begin(); it != str.end(); ++it) { if (ConvertChar(*it) != *it) { diff --git a/util/system/shellcommand.cpp b/util/system/shellcommand.cpp index 3cc23600cf..b1989b5c8c 100644 --- a/util/system/shellcommand.cpp +++ b/util/system/shellcommand.cpp @@ -4,7 +4,7 @@ #include "sigset.h" #include "atomic.h" -#include <util/folder/dirut.h> +#include <util/folder/dirut.h> #include <util/generic/algorithm.h> #include <util/generic/buffer.h> #include <util/generic/vector.h> @@ -18,12 +18,12 @@ #include <errno.h> -#if defined(_unix_) +#if defined(_unix_) #include <unistd.h> #include <fcntl.h> #include <grp.h> #include <sys/wait.h> - + using TPid = pid_t; using TWaitResult = pid_t; using TExitStatus = int; @@ -34,25 +34,25 @@ using TGetGroupListGid = int; #else using TGetGroupListGid = gid_t; #endif -#elif defined(_win_) +#elif defined(_win_) #include <string> #include "winint.h" - + using TPid = HANDLE; using TWaitResult = DWORD; using TExitStatus = DWORD; #define WAIT_PROCEED WAIT_TIMEOUT #pragma warning(disable : 4296) // 'wait_result >= WAIT_OBJECT_0' : expression is always tru -#else +#else #error("unknown os, shell command is not implemented") -#endif - +#endif + #define DBG(stmt) \ {} -// #define DBG(stmt) stmt - +// #define DBG(stmt) stmt + namespace { constexpr static size_t DATA_BUFFER_SIZE = 128 * 1024; @@ -107,89 +107,89 @@ namespace { #endif } -// temporary measure to avoid rewriting all poll calls on win TPipeHandle -#if defined(_win_) +// temporary measure to avoid rewriting all poll calls on win TPipeHandle +#if defined(_win_) using REALPIPEHANDLE = HANDLE; #define INVALID_REALPIPEHANDLE INVALID_HANDLE_VALUE - -class TRealPipeHandle + +class TRealPipeHandle : public TNonCopyable { -public: +public: inline TRealPipeHandle() noexcept - : Fd_(INVALID_REALPIPEHANDLE) - { - } - + : Fd_(INVALID_REALPIPEHANDLE) + { + } + inline TRealPipeHandle(REALPIPEHANDLE fd) noexcept - : Fd_(fd) - { - } - + : Fd_(fd) + { + } + inline ~TRealPipeHandle() { - Close(); - } - + Close(); + } + bool Close() noexcept { - bool ok = true; - if (Fd_ != INVALID_REALPIPEHANDLE) - ok = CloseHandle(Fd_); - Fd_ = INVALID_REALPIPEHANDLE; - return ok; - } - + bool ok = true; + if (Fd_ != INVALID_REALPIPEHANDLE) + ok = CloseHandle(Fd_); + Fd_ = INVALID_REALPIPEHANDLE; + return ok; + } + inline REALPIPEHANDLE Release() noexcept { - REALPIPEHANDLE ret = Fd_; - Fd_ = INVALID_REALPIPEHANDLE; - return ret; - } - + REALPIPEHANDLE ret = Fd_; + Fd_ = INVALID_REALPIPEHANDLE; + return ret; + } + inline void Swap(TRealPipeHandle& r) noexcept { - DoSwap(Fd_, r.Fd_); - } - + DoSwap(Fd_, r.Fd_); + } + inline operator REALPIPEHANDLE() const noexcept { - return Fd_; - } - + return Fd_; + } + inline bool IsOpen() const noexcept { - return Fd_ != INVALID_REALPIPEHANDLE; - } - + return Fd_ != INVALID_REALPIPEHANDLE; + } + ssize_t Read(void* buffer, size_t byteCount) const noexcept { - DWORD doneBytes; + DWORD doneBytes; if (!ReadFile(Fd_, buffer, byteCount, &doneBytes, nullptr)) - return -1; - return doneBytes; - } + return -1; + return doneBytes; + } ssize_t Write(const void* buffer, size_t byteCount) const noexcept { - DWORD doneBytes; + DWORD doneBytes; if (!WriteFile(Fd_, buffer, byteCount, &doneBytes, nullptr)) - return -1; - return doneBytes; - } - + return -1; + return doneBytes; + } + static void Pipe(TRealPipeHandle& reader, TRealPipeHandle& writer, EOpenMode mode) { (void)mode; - REALPIPEHANDLE fds[2]; + REALPIPEHANDLE fds[2]; if (!CreatePipe(&fds[0], &fds[1], nullptr /* handles are not inherited */, 0)) - ythrow TFileError() << "failed to create a pipe"; - TRealPipeHandle(fds[0]).Swap(reader); - TRealPipeHandle(fds[1]).Swap(writer); - } - + ythrow TFileError() << "failed to create a pipe"; + TRealPipeHandle(fds[0]).Swap(reader); + TRealPipeHandle(fds[1]).Swap(writer); + } + private: - REALPIPEHANDLE Fd_; -}; - -#else + REALPIPEHANDLE Fd_; +}; + +#else using TRealPipeHandle = TPipeHandle; using REALPIPEHANDLE = PIPEHANDLE; #define INVALID_REALPIPEHANDLE INVALID_PIPEHANDLE -#endif - -class TShellCommand::TImpl +#endif + +class TShellCommand::TImpl : public TAtomicRefCount<TShellCommand::TImpl> { -private: +private: TPid Pid; TString Command; TList<TString> Arguments; @@ -208,7 +208,7 @@ private: TFileHandle OutputHandle; TFileHandle ErrorHandle; - /// @todo: store const TShellCommandOptions, no need for so many vars + /// @todo: store const TShellCommandOptions, no need for so many vars bool TerminateFlag = false; bool ClearSignalMask = false; bool CloseAllFdsOnExec = false; @@ -230,9 +230,9 @@ private: struct TProcessInfo { TImpl* Parent; - TRealPipeHandle InputFd; - TRealPipeHandle OutputFd; - TRealPipeHandle ErrorFd; + TRealPipeHandle InputFd; + TRealPipeHandle OutputFd; + TRealPipeHandle ErrorFd; TProcessInfo(TImpl* parent, REALPIPEHANDLE inputFd, REALPIPEHANDLE outputFd, REALPIPEHANDLE errorFd) : Parent(parent) , InputFd(inputFd) @@ -242,12 +242,12 @@ private: } }; - struct TPipes { - TRealPipeHandle OutputPipeFd[2]; - TRealPipeHandle ErrorPipeFd[2]; - TRealPipeHandle InputPipeFd[2]; - // pipes are closed by automatic dtor - void PrepareParents() { + struct TPipes { + TRealPipeHandle OutputPipeFd[2]; + TRealPipeHandle ErrorPipeFd[2]; + TRealPipeHandle InputPipeFd[2]; + // pipes are closed by automatic dtor + void PrepareParents() { if (OutputPipeFd[1].IsOpen()) { OutputPipeFd[1].Close(); } @@ -255,16 +255,16 @@ private: ErrorPipeFd[1].Close(); } if (InputPipeFd[1].IsOpen()) { - InputPipeFd[0].Close(); + InputPipeFd[0].Close(); } - } - void ReleaseParents() { - InputPipeFd[1].Release(); - OutputPipeFd[0].Release(); - ErrorPipeFd[0].Release(); - } - }; - + } + void ReleaseParents() { + InputPipeFd[1].Release(); + OutputPipeFd[0].Release(); + ErrorPipeFd[0].Release(); + } + }; + struct TPipePump { TRealPipeHandle* Pipe; IOutputStream* OutputStream; @@ -273,12 +273,12 @@ private: TString InternalError; }; -#if defined(_unix_) +#if defined(_unix_) void OnFork(TPipes& pipes, sigset_t oldmask, char* const* argv, char* const* envp, const std::function<void()>& afterFork) const; -#else - void StartProcess(TPipes& pipes); -#endif - +#else + void StartProcess(TPipes& pipes); +#endif + public: inline TImpl(const TStringBuf cmd, const TList<TString>& args, const TShellCommandOptions& options, const TString& workdir) : Pid(0) @@ -298,7 +298,7 @@ public: , UseShell(options.UseShell) , QuoteArguments(options.QuoteArguments) , DetachSession(options.DetachSession) - , CloseStreams(options.CloseStreams) + , CloseStreams(options.CloseStreams) , ShouldCloseInput(options.ShouldCloseInput) , InputMode(options.InputMode) , OutputMode(options.OutputMode) @@ -386,24 +386,24 @@ public: return ErrorHandle; } - // start child process - void Run(); + // start child process + void Run(); inline void Terminate() { if (!!Pid && (AtomicGet(ExecutionStatus) == SHELL_RUNNING)) { - bool ok = -#if defined(_unix_) + bool ok = +#if defined(_unix_) kill(DetachSession ? -1 * Pid : Pid, SIGTERM) == 0; if (!ok && (errno == ESRCH) && DetachSession) { // this could fail when called before child proc completes setsid(). ok = kill(Pid, SIGTERM) == 0; kill(-Pid, SIGTERM); // between a failed kill(-Pid) and a successful kill(Pid) a grandchild could have been spawned } -#else +#else TerminateProcess(Pid, 1 /* exit code */); -#endif +#endif if (!ok) { - ythrow TSystemError() << "cannot terminate " << Pid; + ythrow TSystemError() << "cannot terminate " << Pid; } } } @@ -426,28 +426,28 @@ public: pi->InputFd.Close(); pi->ErrorFd.Close(); pi->OutputFd.Close(); - - if (pi->Parent->CloseStreams) { + + if (pi->Parent->CloseStreams) { if (pi->Parent->ErrorStream) { - pi->Parent->ErrorStream->Finish(); + pi->Parent->ErrorStream->Finish(); } if (pi->Parent->OutputStream) { - pi->Parent->OutputStream->Finish(); + pi->Parent->OutputStream->Finish(); } - } - + } + delete pi; return true; } - // interchange io while process is alive + // interchange io while process is alive inline static void Communicate(TProcessInfo* pi); inline static void* WatchProcess(void* data) { TProcessInfo* pi = reinterpret_cast<TProcessInfo*>(data); - Communicate(pi); + Communicate(pi); return nullptr; - } + } inline static void* ReadStream(void* data) noexcept { TPipePump* pump = reinterpret_cast<TPipePump*>(data); @@ -510,16 +510,16 @@ public: } TString GetQuotedCommand() const; -}; +}; -#if defined(_win_) -void TShellCommand::TImpl::StartProcess(TShellCommand::TImpl::TPipes& pipes) { - // Setup STARTUPINFO to redirect handles. +#if defined(_win_) +void TShellCommand::TImpl::StartProcess(TShellCommand::TImpl::TPipes& pipes) { + // Setup STARTUPINFO to redirect handles. STARTUPINFOW startup_info; - ZeroMemory(&startup_info, sizeof(startup_info)); - startup_info.cb = sizeof(startup_info); - startup_info.dwFlags = STARTF_USESTDHANDLES; - + ZeroMemory(&startup_info, sizeof(startup_info)); + startup_info.cb = sizeof(startup_info); + startup_info.dwFlags = STARTF_USESTDHANDLES; + if (OutputMode != TShellCommandOptions::HANDLE_INHERIT) { if (!SetHandleInformation(pipes.OutputPipeFd[1], HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT)) { ythrow TSystemError() << "cannot set handle info"; @@ -531,11 +531,11 @@ void TShellCommand::TImpl::StartProcess(TShellCommand::TImpl::TPipes& pipes) { } } if (InputMode != TShellCommandOptions::HANDLE_INHERIT) { - if (!SetHandleInformation(pipes.InputPipeFd[0], HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT)) - ythrow TSystemError() << "cannot set handle info"; + if (!SetHandleInformation(pipes.InputPipeFd[0], HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT)) + ythrow TSystemError() << "cannot set handle info"; } - - // A sockets do not work as std streams for some reason + + // A sockets do not work as std streams for some reason if (OutputMode != TShellCommandOptions::HANDLE_INHERIT) { startup_info.hStdOutput = pipes.OutputPipeFd[1]; } else { @@ -547,20 +547,20 @@ void TShellCommand::TImpl::StartProcess(TShellCommand::TImpl::TPipes& pipes) { startup_info.hStdError = GetStdHandle(STD_ERROR_HANDLE); } if (InputMode != TShellCommandOptions::HANDLE_INHERIT) { - startup_info.hStdInput = pipes.InputPipeFd[0]; + startup_info.hStdInput = pipes.InputPipeFd[0]; } else { // Don't leave hStdInput unfilled, otherwise any attempt to retrieve the operating-system file handle // that is associated with the specified file descriptor will led to errors. startup_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE); } - - PROCESS_INFORMATION process_info; + + PROCESS_INFORMATION process_info; // TString cmd = "cmd /U" + TUtf16String can be used to read unicode messages from cmd - // /A - ansi charset /Q - echo off, /C - command, /Q - special quotes + // /A - ansi charset /Q - echo off, /C - command, /Q - special quotes TString qcmd = GetQuotedCommand(); TString cmd = UseShell ? "cmd /A /Q /S /C \"" + qcmd + "\"" : qcmd; - // winapi can modify command text, copy it - + // winapi can modify command text, copy it + Y_ENSURE_EX(cmd.size() < MAX_COMMAND_LINE, yexception() << "Command is too long (length=" << cmd.size() << ")"); TTempArray<wchar_t> cmdcopy(MAX_COMMAND_LINE); Copy(cmd.data(), cmd.data() + cmd.size(), cmdcopy.Data()); @@ -617,16 +617,16 @@ void TShellCommand::TImpl::StartProcess(TShellCommand::TImpl::TPipes& pipes) { if (!res) { AtomicSet(ExecutionStatus, SHELL_ERROR); - /// @todo: write to error stream if set + /// @todo: write to error stream if set TStringOutput out(CollectedError); out << "Process was not created: " << LastSystemErrorText() << " command text was: '" << GetAString(cmdcopy.Data()) << "'"; - } + } Pid = process_info.hProcess; CloseHandle(process_info.hThread); DBG(Cerr << "created process id " << Pid << " in dir: " << cwd << ", cmd: " << cmdcopy.Data() << Endl); -} -#endif - +} +#endif + void ShellQuoteArg(TString& dst, TStringBuf argument) { dst.append("\""); TStringBuf l, r; @@ -657,14 +657,14 @@ TString TShellCommand::TImpl::GetQuotedCommand() const { // Don't add unnecessary quotes. It's especially important for the windows with a 32k command line length limit. if (QuoteArguments && ArgNeedsQuotes(argument)) { ::ShellQuoteArgSp(quoted, argument); - } else { + } else { quoted.append(" ").append(argument); - } - } - return quoted; -} - -#if defined(_unix_) + } + } + return quoted; +} + +#if defined(_unix_) void TShellCommand::TImpl::OnFork(TPipes& pipes, sigset_t oldmask, char* const* argv, char* const* envp, const std::function<void()>& afterFork) const { try { if (DetachSession) { @@ -715,7 +715,7 @@ void TShellCommand::TImpl::OnFork(TPipes& pipes, sigset_t oldmask, char* const* sErr.Release(); sErrNew.Release(); } - + if (WorkDir.size()) { NFs::SetCurrentWorkingDirectory(WorkDir); } @@ -724,8 +724,8 @@ void TShellCommand::TImpl::OnFork(TPipes& pipes, sigset_t oldmask, char* const* for (int fd = NSystemInfo::MaxOpenFiles(); fd > STDERR_FILENO; --fd) { fcntl(fd, F_SETFD, FD_CLOEXEC); } - } - + } + if (!User.Name.empty()) { ImpersonateUser(User); } @@ -750,18 +750,18 @@ void TShellCommand::TImpl::OnFork(TPipes& pipes, sigset_t oldmask, char* const* Cerr << "Process was not created: " << "unknown error" << Endl; } - + _exit(-1); -} -#endif - -void TShellCommand::TImpl::Run() { +} +#endif + +void TShellCommand::TImpl::Run() { Y_ENSURE(AtomicGet(ExecutionStatus) != SHELL_RUNNING, TStringBuf("Process is already running")); - // Prepare I/O streams - CollectedOutput.clear(); - CollectedError.clear(); - TPipes pipes; - + // Prepare I/O streams + CollectedOutput.clear(); + CollectedError.clear(); + TPipes pipes; + if (OutputMode != TShellCommandOptions::HANDLE_INHERIT) { TRealPipeHandle::Pipe(pipes.OutputPipeFd[0], pipes.OutputPipeFd[1], CloseOnExec); } @@ -770,11 +770,11 @@ void TShellCommand::TImpl::Run() { } if (InputMode != TShellCommandOptions::HANDLE_INHERIT) { TRealPipeHandle::Pipe(pipes.InputPipeFd[0], pipes.InputPipeFd[1], CloseOnExec); - } - + } + AtomicSet(ExecutionStatus, SHELL_RUNNING); - -#if defined(_unix_) + +#if defined(_unix_) // block all signals to avoid signal handler race after fork() sigset_t oldmask, newmask; SigFillSet(&newmask); @@ -817,12 +817,12 @@ void TShellCommand::TImpl::Run() { envp.push_back(nullptr); } - pid_t pid = fork(); - if (pid == -1) { + pid_t pid = fork(); + if (pid == -1) { AtomicSet(ExecutionStatus, SHELL_ERROR); - /// @todo check if pipes are still open + /// @todo check if pipes are still open ythrow TSystemError() << "Cannot fork"; - } else if (pid == 0) { // child + } else if (pid == 0) { // child if (envp.size() != 0) { OnFork(pipes, oldmask, qargv.data(), envp.data(), FuncAfterFork); } else { @@ -833,17 +833,17 @@ void TShellCommand::TImpl::Run() { if (SigProcMask(SIG_SETMASK, &oldmask, nullptr) != 0) { ythrow TSystemError() << "Cannot restore signal mask in parent"; } - } - Pid = pid; -#else - StartProcess(pipes); -#endif - pipes.PrepareParents(); - + } + Pid = pid; +#else + StartProcess(pipes); +#endif + pipes.PrepareParents(); + if (AtomicGet(ExecutionStatus) != SHELL_RUNNING) { - return; + return; } - + if (InputMode == TShellCommandOptions::HANDLE_PIPE) { TFileHandle inputHandle(pipes.InputPipeFd[1].Release()); InputHandle.Swap(inputHandle); @@ -861,32 +861,32 @@ void TShellCommand::TImpl::Run() { TProcessInfo* processInfo = new TProcessInfo(this, pipes.InputPipeFd[1].Release(), pipes.OutputPipeFd[0].Release(), pipes.ErrorPipeFd[0].Release()); - if (AsyncMode) { + if (AsyncMode) { WatchThread = new TThread(&TImpl::WatchProcess, processInfo); - WatchThread->Start(); - /// @todo wait for child to start its process session (if options.Detach) + WatchThread->Start(); + /// @todo wait for child to start its process session (if options.Detach) } else { - Communicate(processInfo); - } + Communicate(processInfo); + } + + pipes.ReleaseParents(); // not needed +} - pipes.ReleaseParents(); // not needed -} - -void TShellCommand::TImpl::Communicate(TProcessInfo* pi) { +void TShellCommand::TImpl::Communicate(TProcessInfo* pi) { THolder<IOutputStream> outputHolder; IOutputStream* output = pi->Parent->OutputStream; if (!output) { - outputHolder.Reset(output = new TStringOutput(pi->Parent->CollectedOutput)); + outputHolder.Reset(output = new TStringOutput(pi->Parent->CollectedOutput)); } - + THolder<IOutputStream> errorHolder; IOutputStream* error = pi->Parent->ErrorStream; if (!error) { - errorHolder.Reset(error = new TStringOutput(pi->Parent->CollectedError)); + errorHolder.Reset(error = new TStringOutput(pi->Parent->CollectedError)); } - + IInputStream*& input = pi->Parent->InputStream; - + #if defined(_unix_) // not really needed, io is done via poll if (pi->OutputFd.IsOpen()) { @@ -900,7 +900,7 @@ void TShellCommand::TImpl::Communicate(TProcessInfo* pi) { } #endif - try { + try { #if defined(_win_) TPipePump pumps[3] = {0}; pumps[0] = {&pi->ErrorFd, error}; @@ -920,172 +920,172 @@ void TShellCommand::TImpl::Communicate(TProcessInfo* pi) { #else TBuffer buffer(DATA_BUFFER_SIZE); TBuffer inputBuffer(DATA_BUFFER_SIZE); - int bytes; - int bytesToWrite = 0; + int bytes; + int bytesToWrite = 0; char* bufPos = nullptr; #endif - TWaitResult waitPidResult; + TWaitResult waitPidResult; TExitStatus status = 0; - - while (true) { - { + + while (true) { + { with_lock (pi->Parent->TerminateMutex) { if (TerminateIsRequired(pi)) { return; } } - waitPidResult = -#if defined(_unix_) + waitPidResult = +#if defined(_unix_) waitpid(pi->Parent->Pid, &status, WNOHANG); -#else +#else WaitForSingleObject(pi->Parent->Pid /* process_info.hProcess */, pi->Parent->PollDelayMs /* ms */); Y_UNUSED(status); -#endif - // DBG(Cerr << "wait result: " << waitPidResult << Endl); +#endif + // DBG(Cerr << "wait result: " << waitPidResult << Endl); if (waitPidResult != WAIT_PROCEED) { - break; + break; } - } + } /// @todo factor out (poll + wfmo) #if defined(_unix_) bool haveIn = false; bool haveOut = false; bool haveErr = false; - if (!input && pi->InputFd.IsOpen()) { - DBG(Cerr << "closing input stream..." << Endl); - pi->InputFd.Close(); - } - if (!output && pi->OutputFd.IsOpen()) { - DBG(Cerr << "closing output stream..." << Endl); - pi->OutputFd.Close(); - } - if (!error && pi->ErrorFd.IsOpen()) { - DBG(Cerr << "closing error stream..." << Endl); - pi->ErrorFd.Close(); - } - + if (!input && pi->InputFd.IsOpen()) { + DBG(Cerr << "closing input stream..." << Endl); + pi->InputFd.Close(); + } + if (!output && pi->OutputFd.IsOpen()) { + DBG(Cerr << "closing output stream..." << Endl); + pi->OutputFd.Close(); + } + if (!error && pi->ErrorFd.IsOpen()) { + DBG(Cerr << "closing error stream..." << Endl); + pi->ErrorFd.Close(); + } + if (!input && !output && !error) { - continue; + continue; } - + struct pollfd fds[] = { {REALPIPEHANDLE(pi->InputFd), POLLOUT, 0}, {REALPIPEHANDLE(pi->OutputFd), POLLIN, 0}, {REALPIPEHANDLE(pi->ErrorFd), POLLIN, 0}}; - int res; - + int res; + if (!input) { - fds[0].events = 0; + fds[0].events = 0; } if (!output) { - fds[1].events = 0; + fds[1].events = 0; } if (!error) { - fds[2].events = 0; + fds[2].events = 0; } - - res = PollD(fds, 3, TInstant::Now() + TDuration::MilliSeconds(pi->Parent->PollDelayMs)); - // DBG(Cerr << "poll result: " << res << Endl); + + res = PollD(fds, 3, TInstant::Now() + TDuration::MilliSeconds(pi->Parent->PollDelayMs)); + // DBG(Cerr << "poll result: " << res << Endl); if (-res == ETIMEDOUT || res == 0) { - // DBG(Cerr << "poll again..." << Endl); - continue; - } + // DBG(Cerr << "poll again..." << Endl); + continue; + } if (res < 0) { - ythrow yexception() << "poll failed: " << LastSystemErrorText(); + ythrow yexception() << "poll failed: " << LastSystemErrorText(); } - + if ((fds[1].revents & POLLIN) == POLLIN) { - haveOut = true; + haveOut = true; } else if (fds[1].revents & (POLLERR | POLLHUP)) { output = nullptr; } - + if ((fds[2].revents & POLLIN) == POLLIN) { - haveErr = true; + haveErr = true; } else if (fds[2].revents & (POLLERR | POLLHUP)) { error = nullptr; } - + if (input && ((fds[0].revents & POLLOUT) == POLLOUT)) { - haveIn = true; + haveIn = true; } - if (haveOut) { - bytes = pi->OutputFd.Read(buffer.Data(), buffer.Capacity()); - DBG(Cerr << "transferred " << bytes << " bytes of output" << Endl); + if (haveOut) { + bytes = pi->OutputFd.Read(buffer.Data(), buffer.Capacity()); + DBG(Cerr << "transferred " << bytes << " bytes of output" << Endl); if (bytes > 0) { output->Write(buffer.Data(), bytes); } else { output = nullptr; } - } - if (haveErr) { - bytes = pi->ErrorFd.Read(buffer.Data(), buffer.Capacity()); - DBG(Cerr << "transferred " << bytes << " bytes of error" << Endl); + } + if (haveErr) { + bytes = pi->ErrorFd.Read(buffer.Data(), buffer.Capacity()); + DBG(Cerr << "transferred " << bytes << " bytes of error" << Endl); if (bytes > 0) { error->Write(buffer.Data(), bytes); } else { error = nullptr; } - } - - if (haveIn) { - if (!bytesToWrite) { - bytesToWrite = input->Read(inputBuffer.Data(), inputBuffer.Capacity()); - if (bytesToWrite == 0) { + } + + if (haveIn) { + if (!bytesToWrite) { + bytesToWrite = input->Read(inputBuffer.Data(), inputBuffer.Capacity()); + if (bytesToWrite == 0) { if (AtomicGet(pi->Parent->ShouldCloseInput)) { input = nullptr; } - continue; + continue; } - bufPos = inputBuffer.Data(); + bufPos = inputBuffer.Data(); } - + bytes = pi->InputFd.Write(bufPos, bytesToWrite); - if (bytes > 0) { - bytesToWrite -= bytes; - bufPos += bytes; + if (bytes > 0) { + bytesToWrite -= bytes; + bufPos += bytes; } else { input = nullptr; } - - DBG(Cerr << "transferred " << bytes << " bytes of input" << Endl); + + DBG(Cerr << "transferred " << bytes << " bytes of input" << Endl); } #endif - } - DBG(Cerr << "process finished" << Endl); - + } + DBG(Cerr << "process finished" << Endl); + // What's the reason of process exit. // We need to set exit code before waiting for input thread // Otherwise there is no way for input stream provider to discover // that process has exited and stream shouldn't wait for new data. - bool cleanExit = false; + bool cleanExit = false; TMaybe<int> processExitCode; -#if defined(_unix_) +#if defined(_unix_) processExitCode = WEXITSTATUS(status); if (WIFEXITED(status) && processExitCode == 0) { - cleanExit = true; + cleanExit = true; } else if (WIFSIGNALED(status)) { processExitCode = -WTERMSIG(status); } -#else - if (waitPidResult == WAIT_OBJECT_0) { - DWORD exitCode = STILL_ACTIVE; +#else + if (waitPidResult == WAIT_OBJECT_0) { + DWORD exitCode = STILL_ACTIVE; if (!GetExitCodeProcess(pi->Parent->Pid, &exitCode)) { - ythrow yexception() << "GetExitCodeProcess: " << LastSystemErrorText(); + ythrow yexception() << "GetExitCodeProcess: " << LastSystemErrorText(); } - if (exitCode == 0) - cleanExit = true; + if (exitCode == 0) + cleanExit = true; processExitCode = static_cast<int>(exitCode); - DBG(Cerr << "exit code: " << exitCode << Endl); + DBG(Cerr << "exit code: " << exitCode << Endl); } -#endif +#endif pi->Parent->ExitCode = processExitCode; - if (cleanExit) { + if (cleanExit) { AtomicSet(pi->Parent->ExecutionStatus, SHELL_FINISHED); - } else { + } else { AtomicSet(pi->Parent->ExecutionStatus, SHELL_ERROR); } @@ -1108,18 +1108,18 @@ void TShellCommand::TImpl::Communicate(TProcessInfo* pi) { } #endif } catch (const yexception& e) { - // Some error in watch occured, set result to error + // Some error in watch occured, set result to error AtomicSet(pi->Parent->ExecutionStatus, SHELL_INTERNAL_ERROR); - pi->Parent->InternalError = e.what(); + pi->Parent->InternalError = e.what(); if (input) { - pi->InputFd.Close(); + pi->InputFd.Close(); } - Cdbg << "shell command internal error: " << pi->Parent->InternalError << Endl; + Cdbg << "shell command internal error: " << pi->Parent->InternalError << Endl; } - // Now we can safely delete process info struct and other data - pi->Parent->TerminateFlag = true; + // Now we can safely delete process info struct and other data + pi->Parent->TerminateFlag = true; TerminateIsRequired(pi); -} +} TShellCommand::TShellCommand(const TStringBuf cmd, const TList<TString>& args, const TShellCommandOptions& options, const TString& workdir) diff --git a/util/system/shellcommand.h b/util/system/shellcommand.h index 4304202c68..8730627fe5 100644 --- a/util/system/shellcommand.h +++ b/util/system/shellcommand.h @@ -88,7 +88,7 @@ public: * @note in default close-on-exec mode is off. * @return self */ - inline TShellCommandOptions& SetCloseAllFdsOnExec(bool closeAllFdsOnExec) { + inline TShellCommandOptions& SetCloseAllFdsOnExec(bool closeAllFdsOnExec) { CloseAllFdsOnExec = closeAllFdsOnExec; return *this; } @@ -98,30 +98,30 @@ public: * in separate thread, and control will be returned immediately * * @param async true if asynchonous mode is needed - * @note in default async mode launcher will need 100% cpu for rapid process termination + * @note in default async mode launcher will need 100% cpu for rapid process termination * @return self */ - inline TShellCommandOptions& SetAsync(bool async) { + inline TShellCommandOptions& SetAsync(bool async) { AsyncMode = async; - if (AsyncMode) - PollDelayMs = 0; + if (AsyncMode) + PollDelayMs = 0; + return *this; + } + + /** + * @brief specify delay for process controlling loop + * @param ms number of milliseconds to poll for + * @note for synchronous process default of 1s should generally fit + * for async process default is no latency and that consumes 100% one cpu + * SetAsync(true) will reset this delay to 0, so call this method after + * @return self + */ + inline TShellCommandOptions& SetLatency(size_t ms) { + PollDelayMs = ms; return *this; } /** - * @brief specify delay for process controlling loop - * @param ms number of milliseconds to poll for - * @note for synchronous process default of 1s should generally fit - * for async process default is no latency and that consumes 100% one cpu - * SetAsync(true) will reset this delay to 0, so call this method after - * @return self - */ - inline TShellCommandOptions& SetLatency(size_t ms) { - PollDelayMs = ms; - return *this; - } - - /** * @brief set the stream, which is input fetched from * * @param stream Pointer to stream. @@ -168,17 +168,17 @@ public: } /** - * @brief set if Finish() should be called on user-supplied streams - * if process is run in async mode Finish will be called in process' thread - * @param val if Finish() should be called - * @return self - */ - inline TShellCommandOptions& SetCloseStreams(bool val) { - CloseStreams = val; - return *this; - } - - /** + * @brief set if Finish() should be called on user-supplied streams + * if process is run in async mode Finish will be called in process' thread + * @param val if Finish() should be called + * @return self + */ + inline TShellCommandOptions& SetCloseStreams(bool val) { + CloseStreams = val; + return *this; + } + + /** * @brief set if input stream should be closed after all data is read * call SetCloseInput(false) for interactive process * @param val if input stream should be closed @@ -190,21 +190,21 @@ public: } /** - * @brief set if command should be interpreted by OS shell (/bin/sh or cmd.exe) - * shell is enabled by default - * call SetUseShell(false) for command to be sent to OS verbatim - * @note shell operators > < | && || will not work if this option is off - * @param useShell if command should be run in shell - * @return self - */ - inline TShellCommandOptions& SetUseShell(bool useShell) { - UseShell = useShell; - if (!useShell) - QuoteArguments = false; - return *this; - } - - /** + * @brief set if command should be interpreted by OS shell (/bin/sh or cmd.exe) + * shell is enabled by default + * call SetUseShell(false) for command to be sent to OS verbatim + * @note shell operators > < | && || will not work if this option is off + * @param useShell if command should be run in shell + * @return self + */ + inline TShellCommandOptions& SetUseShell(bool useShell) { + UseShell = useShell; + if (!useShell) + QuoteArguments = false; + return *this; + } + + /** * @brief set if the arguments should be wrapped in quotes. * Please, note that this option makes no difference between * real arguments and shell syntax, so if you execute something @@ -214,28 +214,28 @@ public: * which will never end successfully. * By default, this option is turned on. * - * @note arguments will only be quoted if shell is used + * @note arguments will only be quoted if shell is used * @param quote if the arguments should be quoted * * @return self */ - inline TShellCommandOptions& SetQuoteArguments(bool quote) { + inline TShellCommandOptions& SetQuoteArguments(bool quote) { QuoteArguments = quote; return *this; } - /** - * @brief set to run command in new session - * @note set this option to off to deliver parent's signals to command as well - * @note currently ignored on windows - * @param detach if command should be run in new session - * @return self - */ - inline TShellCommandOptions& SetDetachSession(bool detach) { - DetachSession = detach; - return *this; - } - + /** + * @brief set to run command in new session + * @note set this option to off to deliver parent's signals to command as well + * @note currently ignored on windows + * @param detach if command should be run in new session + * @return self + */ + inline TShellCommandOptions& SetDetachSession(bool detach) { + DetachSession = detach; + return *this; + } + /** * @brief specifies pure function to be called in the child process after fork, before calling execve * @note currently ignored on windows @@ -295,7 +295,7 @@ public: return *this; } -public: +public: bool ClearSignalMask = false; bool CloseAllFdsOnExec = false; bool AsyncMode = false; @@ -309,10 +309,10 @@ public: EHandleMode OutputMode = HANDLE_STREAM; EHandleMode ErrorMode = HANDLE_STREAM; - /// @todo more options - // bool SearchPath // search exe name in $PATH - // bool UnicodeConsole - // bool EmulateConsole // provide isatty == true + /// @todo more options + // bool SearchPath // search exe name in $PATH + // bool UnicodeConsole + // bool EmulateConsole // provide isatty == true /// @todo command's stdin should be exposet as IOutputStream to support dialogue IInputStream* InputStream; IOutputStream* OutputStream; @@ -320,8 +320,8 @@ public: TUserOptions User; THashMap<TString, TString> Environment; int Nice = 0; - - static const size_t DefaultSyncPollDelay = 1000; // ms + + static const size_t DefaultSyncPollDelay = 1000; // ms std::function<void()> FuncAfterFork = {}; }; @@ -349,7 +349,7 @@ public: * @param cmd binary name * @param args arguments list * @param options execution options - * @todo store entire options structure + * @todo store entire options structure */ TShellCommand(const TStringBuf cmd, const TList<TString>& args, const TShellCommandOptions& options = TShellCommandOptions(), const TString& workdir = TString()); @@ -440,7 +440,7 @@ public: * * @return self */ - TShellCommand& Run(); + TShellCommand& Run(); /** * @brief terminate the execution @@ -448,14 +448,14 @@ public: * * @return self */ - TShellCommand& Terminate(); + TShellCommand& Terminate(); /** * @brief wait until the execution is finished * * @return self */ - TShellCommand& Wait(); + TShellCommand& Wait(); /** * @brief close process' stdin diff --git a/util/system/shellcommand_ut.cpp b/util/system/shellcommand_ut.cpp index 5daa23e29f..9d849279d2 100644 --- a/util/system/shellcommand_ut.cpp +++ b/util/system/shellcommand_ut.cpp @@ -17,16 +17,16 @@ #include <util/string/strip.h> #include <util/folder/tempdir.h> -#if defined(_win_) +#if defined(_win_) #define NL "\r\n" const char catCommand[] = "sort"; // not really cat but ok const size_t textSize = 1; -#else +#else #define NL "\n" const char catCommand[] = "/bin/cat"; const size_t textSize = 20000; -#endif - +#endif + class TGuardedStringStream: public IInputStream, public IOutputStream { public: TGuardedStringStream() { @@ -72,18 +72,18 @@ Y_UNIT_TEST_SUITE(TShellQuoteTest) { Y_UNIT_TEST_SUITE(TShellCommandTest) { Y_UNIT_TEST(TestNoQuotes) { - TShellCommandOptions options; - options.SetQuoteArguments(false); - TShellCommand cmd("echo hello"); - cmd.Run(); - UNIT_ASSERT_VALUES_EQUAL(cmd.GetError(), ""); - UNIT_ASSERT_VALUES_EQUAL(cmd.GetOutput(), "hello" NL); - UNIT_ASSERT(TShellCommand::SHELL_FINISHED == cmd.GetStatus()); + TShellCommandOptions options; + options.SetQuoteArguments(false); + TShellCommand cmd("echo hello"); + cmd.Run(); + UNIT_ASSERT_VALUES_EQUAL(cmd.GetError(), ""); + UNIT_ASSERT_VALUES_EQUAL(cmd.GetOutput(), "hello" NL); + UNIT_ASSERT(TShellCommand::SHELL_FINISHED == cmd.GetStatus()); UNIT_ASSERT(cmd.GetExitCode().Defined() && 0 == cmd.GetExitCode()); UNIT_ASSERT_VALUES_EQUAL(cmd.GetQuotedCommand(), "echo hello"); - } - + } + Y_UNIT_TEST(TestOnlyNecessaryQuotes) { TShellCommandOptions options; options.SetQuoteArguments(true); @@ -98,75 +98,75 @@ Y_UNIT_TEST_SUITE(TShellCommandTest) { } Y_UNIT_TEST(TestRun) { - TShellCommand cmd("echo"); - cmd << "hello"; - cmd.Run(); - UNIT_ASSERT_VALUES_EQUAL(cmd.GetError(), ""); -#if defined(_win_) - UNIT_ASSERT_VALUES_EQUAL(cmd.GetOutput(), "\"hello\"\r\n"); -#else - UNIT_ASSERT_VALUES_EQUAL(cmd.GetOutput(), "hello\n"); -#endif - UNIT_ASSERT(TShellCommand::SHELL_FINISHED == cmd.GetStatus()); + TShellCommand cmd("echo"); + cmd << "hello"; + cmd.Run(); + UNIT_ASSERT_VALUES_EQUAL(cmd.GetError(), ""); +#if defined(_win_) + UNIT_ASSERT_VALUES_EQUAL(cmd.GetOutput(), "\"hello\"\r\n"); +#else + UNIT_ASSERT_VALUES_EQUAL(cmd.GetOutput(), "hello\n"); +#endif + UNIT_ASSERT(TShellCommand::SHELL_FINISHED == cmd.GetStatus()); UNIT_ASSERT(cmd.GetExitCode().Defined() && 0 == cmd.GetExitCode()); - } - // running with no shell is not implemented for win - // there should be no problem with it as long as SearchPath is on + } + // running with no shell is not implemented for win + // there should be no problem with it as long as SearchPath is on Y_UNIT_TEST(TestNoShell) { -#if defined(_win_) - const char dir[] = "dir"; -#else - const char dir[] = "ls"; -#endif - +#if defined(_win_) + const char dir[] = "dir"; +#else + const char dir[] = "ls"; +#endif + TShellCommandOptions options; - options.SetQuoteArguments(false); - - { - options.SetUseShell(false); - TShellCommand cmd(dir, options); + options.SetQuoteArguments(false); + + { + options.SetUseShell(false); + TShellCommand cmd(dir, options); cmd << "|" << "sort"; - - cmd.Run(); - UNIT_ASSERT(TShellCommand::SHELL_ERROR == cmd.GetStatus()); + + cmd.Run(); + UNIT_ASSERT(TShellCommand::SHELL_ERROR == cmd.GetStatus()); UNIT_ASSERT(cmd.GetExitCode().Defined() && 0 != cmd.GetExitCode()); - } - { - options.SetUseShell(true); - TShellCommand cmd(dir, options); + } + { + options.SetUseShell(true); + TShellCommand cmd(dir, options); cmd << "|" << "sort"; - cmd.Run(); - UNIT_ASSERT(TShellCommand::SHELL_FINISHED == cmd.GetStatus()); + cmd.Run(); + UNIT_ASSERT(TShellCommand::SHELL_FINISHED == cmd.GetStatus()); UNIT_ASSERT_VALUES_EQUAL(cmd.GetError().size(), 0u); UNIT_ASSERT(cmd.GetExitCode().Defined() && 0 == cmd.GetExitCode()); - } - } + } + } Y_UNIT_TEST(TestAsyncRun) { - TShellCommandOptions options; + TShellCommandOptions options; options.SetAsync(true); -#if defined(_win_) - // fails with weird error "Input redirection is not supported" - // TShellCommand cmd("sleep", options); - // cmd << "3"; - TShellCommand cmd("ping 1.1.1.1 -n 1 -w 2000", options); -#else - TShellCommand cmd("sleep", options); - cmd << "2"; -#endif +#if defined(_win_) + // fails with weird error "Input redirection is not supported" + // TShellCommand cmd("sleep", options); + // cmd << "3"; + TShellCommand cmd("ping 1.1.1.1 -n 1 -w 2000", options); +#else + TShellCommand cmd("sleep", options); + cmd << "2"; +#endif UNIT_ASSERT(TShellCommand::SHELL_NONE == cmd.GetStatus()); cmd.Run(); sleep(1); UNIT_ASSERT(TShellCommand::SHELL_RUNNING == cmd.GetStatus()); cmd.Wait(); - UNIT_ASSERT(TShellCommand::SHELL_RUNNING != cmd.GetStatus()); - UNIT_ASSERT_VALUES_EQUAL(cmd.GetError(), ""); -#if !defined(_win_) + UNIT_ASSERT(TShellCommand::SHELL_RUNNING != cmd.GetStatus()); + UNIT_ASSERT_VALUES_EQUAL(cmd.GetError(), ""); +#if !defined(_win_) UNIT_ASSERT(TShellCommand::SHELL_FINISHED == cmd.GetStatus()); UNIT_ASSERT_VALUES_EQUAL(cmd.GetOutput().size(), 0u); UNIT_ASSERT(cmd.GetExitCode().Defined() && 0 == cmd.GetExitCode()); -#endif +#endif } Y_UNIT_TEST(TestQuotes) { TShellCommandOptions options; @@ -174,15 +174,15 @@ Y_UNIT_TEST_SUITE(TShellCommandTest) { TString output; TStringOutput outputStream(output); options.SetOutputStream(&outputStream); - TShellCommand cmd("echo", options); + TShellCommand cmd("echo", options); cmd << input; cmd.Run().Wait(); output = StripString(output); -#if defined(_win_) - UNIT_ASSERT_VALUES_EQUAL("\"a\\\"a a\"", output); -#else +#if defined(_win_) + UNIT_ASSERT_VALUES_EQUAL("\"a\\\"a a\"", output); +#else UNIT_ASSERT_VALUES_EQUAL(input, output); -#endif +#endif UNIT_ASSERT_VALUES_EQUAL(cmd.GetError().size(), 0u); } Y_UNIT_TEST(TestRunNonexistent) { @@ -199,13 +199,13 @@ Y_UNIT_TEST_SUITE(TShellCommandTest) { UNIT_ASSERT_VALUES_UNEQUAL(cmd.GetError().size(), 0u); UNIT_ASSERT(cmd.GetExitCode().Defined() && 2 == cmd.GetExitCode()); } - // 'type con' and 'copy con con' want real console, not stdin, use sort + // 'type con' and 'copy con con' want real console, not stdin, use sort Y_UNIT_TEST(TestInput) { TShellCommandOptions options; TString input = (TString("a") * 2000).append(NL) * textSize; TStringInput inputStream(input); options.SetInputStream(&inputStream); - TShellCommand cmd(catCommand, options); + TShellCommand cmd(catCommand, options); cmd.Run().Wait(); UNIT_ASSERT_VALUES_EQUAL(input, cmd.GetOutput()); UNIT_ASSERT_VALUES_EQUAL(cmd.GetError().size(), 0u); @@ -218,28 +218,28 @@ Y_UNIT_TEST_SUITE(TShellCommandTest) { TString output; TStringOutput outputStream(output); options.SetOutputStream(&outputStream); - TShellCommand cmd(catCommand, options); + TShellCommand cmd(catCommand, options); cmd.Run().Wait(); UNIT_ASSERT_VALUES_EQUAL(input, output); UNIT_ASSERT_VALUES_EQUAL(cmd.GetError().size(), 0u); } Y_UNIT_TEST(TestIO) { - // descriptive test: use all options - TShellCommandOptions options; + // descriptive test: use all options + TShellCommandOptions options; options.SetAsync(true); - options.SetQuoteArguments(false); - options.SetLatency(10); + options.SetQuoteArguments(false); + options.SetLatency(10); options.SetClearSignalMask(true); - options.SetCloseAllFdsOnExec(true); + options.SetCloseAllFdsOnExec(true); options.SetCloseInput(false); TGuardedStringStream write; - options.SetInputStream(&write); + options.SetInputStream(&write); TGuardedStringStream read; - options.SetOutputStream(&read); - options.SetUseShell(true); - + options.SetOutputStream(&read); + options.SetUseShell(true); + TShellCommand cmd("cat", options); - cmd.Run(); + cmd.Run(); write << "alpha" << NL; while (read.Str() != "alpha" NL) { @@ -252,50 +252,50 @@ Y_UNIT_TEST_SUITE(TShellCommandTest) { } write << "zeta" << NL; - cmd.CloseInput(); - cmd.Wait(); - - UNIT_ASSERT_VALUES_EQUAL(cmd.GetError(), ""); - UNIT_ASSERT(TShellCommand::SHELL_FINISHED == cmd.GetStatus()); - UNIT_ASSERT_VALUES_EQUAL(read.Str(), "alpha" NL "omega" NL "zeta" NL); + cmd.CloseInput(); + cmd.Wait(); + + UNIT_ASSERT_VALUES_EQUAL(cmd.GetError(), ""); + UNIT_ASSERT(TShellCommand::SHELL_FINISHED == cmd.GetStatus()); + UNIT_ASSERT_VALUES_EQUAL(read.Str(), "alpha" NL "omega" NL "zeta" NL); UNIT_ASSERT(cmd.GetExitCode().Defined() && 0 == cmd.GetExitCode()); - } + } Y_UNIT_TEST(TestStreamClose) { struct TStream: public IOutputStream { - size_t NumCloses = 0; + size_t NumCloses = 0; void DoWrite(const void* buf, size_t len) override { Y_UNUSED(buf); Y_UNUSED(len); } void DoFinish() override { - ++NumCloses; - } - } stream; - - auto options1 = TShellCommandOptions().SetCloseStreams(false).SetOutputStream(&stream).SetErrorStream(&stream); - TShellCommand("echo hello", options1).Run().Wait(); - UNIT_ASSERT_VALUES_EQUAL(stream.NumCloses, 0); - - auto options = TShellCommandOptions().SetCloseStreams(true).SetOutputStream(&stream).SetErrorStream(&stream); - TShellCommand("echo hello", options).Run().Wait(); + ++NumCloses; + } + } stream; + + auto options1 = TShellCommandOptions().SetCloseStreams(false).SetOutputStream(&stream).SetErrorStream(&stream); + TShellCommand("echo hello", options1).Run().Wait(); + UNIT_ASSERT_VALUES_EQUAL(stream.NumCloses, 0); + + auto options = TShellCommandOptions().SetCloseStreams(true).SetOutputStream(&stream).SetErrorStream(&stream); + TShellCommand("echo hello", options).Run().Wait(); UNIT_ASSERT_VALUES_EQUAL(stream.NumCloses, 2); - } + } Y_UNIT_TEST(TestInterruptSimple) { - TShellCommandOptions options; - options.SetAsync(true); + TShellCommandOptions options; + options.SetAsync(true); options.SetCloseInput(false); TGuardedStringStream write; options.SetInputStream(&write); // set input stream that will be waited by cat - TShellCommand cmd(catCommand, options); - cmd.Run(); + TShellCommand cmd(catCommand, options); + cmd.Run(); sleep(1); - UNIT_ASSERT(TShellCommand::SHELL_RUNNING == cmd.GetStatus()); - cmd.Terminate(); - cmd.Wait(); - UNIT_ASSERT(TShellCommand::SHELL_RUNNING != cmd.GetStatus()); - } -#if !defined(_win_) - // this ut is unix-only, port to win using %TEMP% + UNIT_ASSERT(TShellCommand::SHELL_RUNNING == cmd.GetStatus()); + cmd.Terminate(); + cmd.Wait(); + UNIT_ASSERT(TShellCommand::SHELL_RUNNING != cmd.GetStatus()); + } +#if !defined(_win_) + // this ut is unix-only, port to win using %TEMP% Y_UNIT_TEST(TestInterrupt) { TString tmpfile = TString("shellcommand_ut.interrupt.") + ToString(RandomNumber<ui32>()); @@ -379,7 +379,7 @@ Y_UNIT_TEST_SUITE(TShellCommandTest) { UNIT_ASSERT(TShellCommand::SHELL_FINISHED == cmd.GetStatus()); UNIT_ASSERT(cmd.GetExitCode().Defined() && 0 == cmd.GetExitCode()); } -#endif +#endif Y_UNIT_TEST(TestInternalError) { TString input = (TString("a") * 2000).append("\n"); TStringInput inputStream(input); @@ -387,7 +387,7 @@ Y_UNIT_TEST_SUITE(TShellCommandTest) { TShellCommandOptions options; options.SetInputStream(&inputStream); options.SetOutputStream(&outputStream); - TShellCommand cmd(catCommand, options); + TShellCommand cmd(catCommand, options); cmd.Run().Wait(); UNIT_ASSERT(TShellCommand::SHELL_INTERNAL_ERROR == cmd.GetStatus()); UNIT_ASSERT_VALUES_UNEQUAL(cmd.GetInternalError().size(), 0u); |