#include "output.h" #include #include "format.h" #include #include #include #include #include #if defined(_android_) #include #include #include #include #endif #include #include #include #include #include #if defined(_win_) #include #endif constexpr size_t MAX_UTF8_BYTES = 4; // UTF-8-encoded code point takes between 1 and 4 bytes IOutputStream::IOutputStream() noexcept = default; IOutputStream::~IOutputStream() = default; void IOutputStream::DoFlush() { /* * do nothing */ } void IOutputStream::DoFinish() { Flush(); } void IOutputStream::DoWriteV(const TPart* parts, size_t count) { for (size_t i = 0; i < count; ++i) { const TPart& part = parts[i]; DoWrite(part.buf, part.len); } } void IOutputStream::DoWriteC(char ch) { DoWrite(&ch, 1); } template <> void Out(IOutputStream& o, wchar16 ch) { const wchar32 w32ch = ReadSymbol(&ch, &ch + 1); size_t length; unsigned char buffer[MAX_UTF8_BYTES]; WriteUTF8Char(w32ch, length, buffer); o.Write(buffer, length); } template <> void Out(IOutputStream& o, wchar32 ch) { size_t length; unsigned char buffer[MAX_UTF8_BYTES]; WriteUTF8Char(ch, length, buffer); o.Write(buffer, length); } static void WriteString(IOutputStream& o, const wchar16* w, size_t n) { const size_t buflen = (n * MAX_UTF8_BYTES); // * 4 because the conversion functions can convert unicode character into maximum 4 bytes of UTF8 TTempBuf buffer(buflen + 1); char* const data = buffer.Data(); size_t written = 0; WideToUTF8(w, n, data, written); data[written] = 0; o.Write(data, written); } static void WriteString(IOutputStream& o, const wchar32* w, size_t n) { const size_t buflen = (n * MAX_UTF8_BYTES); // * 4 because the conversion functions can convert unicode character into maximum 4 bytes of UTF8 TTempBuf buffer(buflen + 1); char* const data = buffer.Data(); size_t written = 0; WideToUTF8(w, n, data, written); data[written] = 0; o.Write(data, written); } template <> void Out(IOutputStream& o, const TString& p) { o.Write(p.data(), p.size()); } template <> void Out(IOutputStream& o, const std::string& p) { o.Write(p.data(), p.length()); } template <> void Out(IOutputStream& o, const std::string_view& p) { o.Write(p.data(), p.length()); } template <> void Out(IOutputStream& o, const std::u16string_view& p) { WriteString(o, p.data(), p.length()); } template <> void Out(IOutputStream& o, const std::u32string_view& p) { WriteString(o, p.data(), p.length()); } #ifndef USE_STL_SYSTEM // FIXME thegeorg@: remove #ifndef upon raising minimal macOS version to 10.15 in https://st.yandex-team.ru/DTCC-836 template <> void Out(IOutputStream& o, const std::filesystem::path& p) { o.Write(p.string()); } #endif template <> void Out(IOutputStream& o, const TStringBuf& p) { o.Write(p.data(), p.length()); } template <> void Out(IOutputStream& o, const TWtringBuf& p) { WriteString(o, p.data(), p.length()); } template <> void Out(IOutputStream& o, const TUtf32StringBuf& p) { WriteString(o, p.data(), p.length()); } template <> void Out(IOutputStream& o, const wchar16* w) { if (w) { WriteString(o, w, std::char_traits::length(w)); } else { o.Write("(null)"); } } template <> void Out(IOutputStream& o, const wchar32* w) { if (w) { WriteString(o, w, std::char_traits::length(w)); } else { o.Write("(null)"); } } template <> void Out(IOutputStream& o, const TUtf16String& w) { WriteString(o, w.c_str(), w.size()); } template <> void Out(IOutputStream& o, const TUtf32String& w) { WriteString(o, w.c_str(), w.size()); } #define DEF_CONV_DEFAULT(type) \ template <> \ void Out(IOutputStream & o, type p) { \ o << ToString(p); \ } #define DEF_CONV_CHR(type) \ template <> \ void Out(IOutputStream & o, type p) { \ o.Write((char)p); \ } #define DEF_CONV_NUM(type, len) \ template <> \ void Out(IOutputStream & o, type p) { \ char buf[len]; \ o.Write(buf, ToString(p, buf, sizeof(buf))); \ } \ \ template <> \ void Out(IOutputStream & o, volatile type p) { \ Out(o, p); \ } DEF_CONV_NUM(bool, 64) DEF_CONV_CHR(char) DEF_CONV_CHR(signed char) DEF_CONV_CHR(unsigned char) DEF_CONV_NUM(signed short, 64) DEF_CONV_NUM(signed int, 64) DEF_CONV_NUM(signed long int, 64) DEF_CONV_NUM(signed long long int, 64) DEF_CONV_NUM(unsigned short, 64) DEF_CONV_NUM(unsigned int, 64) DEF_CONV_NUM(unsigned long int, 64) DEF_CONV_NUM(unsigned long long int, 64) DEF_CONV_NUM(float, 512) DEF_CONV_NUM(double, 512) DEF_CONV_NUM(long double, 512) #if !defined(_YNDX_LIBCXX_ENABLE_VECTOR_BOOL_COMPRESSION) || (_YNDX_LIBCXX_ENABLE_VECTOR_BOOL_COMPRESSION == 1) // TODO: acknowledge std::bitset::reference for both libc++ and libstdc++ template <> void Out::reference>(IOutputStream& o, const std::vector::reference& bit) { return Out(o, static_cast(bit)); } #endif #ifndef TSTRING_IS_STD_STRING template <> void Out>(IOutputStream& o, const TBasicCharRef& c) { o << static_cast(c); } template <> void Out>(IOutputStream& o, const TBasicCharRef& c) { o << static_cast(c); } template <> void Out>(IOutputStream& o, const TBasicCharRef& c) { o << static_cast(c); } #endif template <> void Out(IOutputStream& o, const void* t) { o << Hex(size_t(t)); } template <> void Out(IOutputStream& o, void* t) { Out(o, t); } using TNullPtr = decltype(nullptr); template <> void Out(IOutputStream& o, TTypeTraits::TFuncParam) { o << TStringBuf("nullptr"); } #define DEF_OPTIONAL(TYPE) \ template <> \ void Out>(IOutputStream & o, const std::optional& value) { \ if (value) { \ o << *value; \ } else { \ o << "(NULL)"; \ } \ } DEF_OPTIONAL(ui32); DEF_OPTIONAL(i64); DEF_OPTIONAL(ui64); DEF_OPTIONAL(std::string); DEF_OPTIONAL(TString); DEF_OPTIONAL(TStringBuf); #if defined(_android_) namespace { class TAndroidStdIOStreams { public: TAndroidStdIOStreams() : LogLibrary("liblog.so") , LogFuncPtr((TLogFuncPtr)LogLibrary.Sym("__android_log_write")) , Out(LogFuncPtr) , Err(LogFuncPtr) { } public: using TLogFuncPtr = void (*)(int, const char*, const char*); class TAndroidStdOutput: public IOutputStream { public: inline TAndroidStdOutput(TLogFuncPtr logFuncPtr) noexcept : Buffer() , LogFuncPtr(logFuncPtr) { } virtual ~TAndroidStdOutput() { } private: virtual void DoWrite(const void* buf, size_t len) override { with_lock (BufferMutex) { Buffer.Write(buf, len); } } virtual void DoFlush() override { with_lock (BufferMutex) { LogFuncPtr(ANDROID_LOG_DEBUG, GetTag(), Buffer.Data()); Buffer.Clear(); } } virtual const char* GetTag() const = 0; private: TMutex BufferMutex; TStringStream Buffer; TLogFuncPtr LogFuncPtr; }; class TStdErr: public TAndroidStdOutput { public: TStdErr(TLogFuncPtr logFuncPtr) : TAndroidStdOutput(logFuncPtr) { } virtual ~TStdErr() { } private: virtual const char* GetTag() const override { return "stderr"; } }; class TStdOut: public TAndroidStdOutput { public: TStdOut(TLogFuncPtr logFuncPtr) : TAndroidStdOutput(logFuncPtr) { } virtual ~TStdOut() { } private: virtual const char* GetTag() const override { return "stdout"; } }; static bool Enabled; TDynamicLibrary LogLibrary; // field order is important, see constructor TLogFuncPtr LogFuncPtr; TStdOut Out; TStdErr Err; static inline TAndroidStdIOStreams& Instance() { return *SingletonWithPriority(); } }; bool TAndroidStdIOStreams::Enabled = false; } #endif // _android_ namespace { class TStdOutput: public IOutputStream { public: inline TStdOutput(FILE* f) noexcept : F_(f) { } ~TStdOutput() override = default; private: void DoWrite(const void* buf, size_t len) override { if (len != fwrite(buf, 1, len, F_)) { #if defined(_win_) // On Windows, if 'F_' is console -- 'fwrite' returns count of written characters. // If, for example, console output codepage is UTF-8, then returned value is // not equal to 'len'. So, we ignore some 'errno' values... if ((errno == 0 || errno == EINVAL || errno == EILSEQ) && _isatty(fileno(F_))) { return; } #endif ythrow TSystemError() << "write failed"; } } void DoFlush() override { if (fflush(F_) != 0) { ythrow TSystemError() << "fflush failed"; } } private: FILE* F_; }; struct TStdIOStreams { struct TStdErr: public TStdOutput { inline TStdErr() : TStdOutput(stderr) { } ~TStdErr() override = default; }; struct TStdOut: public TStdOutput { inline TStdOut() : TStdOutput(stdout) { } ~TStdOut() override = default; }; TStdOut Out; TStdErr Err; static inline TStdIOStreams& Instance() { return *SingletonWithPriority(); } }; } IOutputStream& NPrivate::StdErrStream() noexcept { #if defined(_android_) if (TAndroidStdIOStreams::Enabled) { return TAndroidStdIOStreams::Instance().Err; } #endif return TStdIOStreams::Instance().Err; } IOutputStream& NPrivate::StdOutStream() noexcept { #if defined(_android_) if (TAndroidStdIOStreams::Enabled) { return TAndroidStdIOStreams::Instance().Out; } #endif return TStdIOStreams::Instance().Out; } void RedirectStdioToAndroidLog(bool redirect) { #if defined(_android_) TAndroidStdIOStreams::Enabled = redirect; #else Y_UNUSED(redirect); #endif }