https://github.com/charles-zablit created https://github.com/llvm/llvm-project/pull/196293
This patch splits the `NativeFile` class in 2 implementations: `NativeFilePosix` and `NativeFileWindows`. They both inherit from `NativeFileBase`. The `NativeFile` typedef allows to automatically use the correct implementation depending on the platform. >From aef7024f690f9e458c4493cad425420ad4a41a44 Mon Sep 17 00:00:00 2001 From: Charles Zablit <[email protected]> Date: Thu, 7 May 2026 13:05:09 +0100 Subject: [PATCH] [lldb] split NativeFile in platform specific implementations --- lldb/include/lldb/Host/File.h | 460 +------------------ lldb/include/lldb/Host/FileBase.h | 245 ++++++++++ lldb/include/lldb/Host/posix/FilePosix.h | 49 ++ lldb/include/lldb/Host/windows/FileWindows.h | 65 +++ lldb/source/Host/CMakeLists.txt | 2 + lldb/source/Host/common/File.cpp | 348 +++----------- lldb/source/Host/posix/FilePosix.cpp | 220 +++++++++ lldb/source/Host/windows/FileWindows.cpp | 139 ++++++ 8 files changed, 790 insertions(+), 738 deletions(-) create mode 100644 lldb/include/lldb/Host/FileBase.h create mode 100644 lldb/include/lldb/Host/posix/FilePosix.h create mode 100644 lldb/include/lldb/Host/windows/FileWindows.h create mode 100644 lldb/source/Host/posix/FilePosix.cpp create mode 100644 lldb/source/Host/windows/FileWindows.cpp diff --git a/lldb/include/lldb/Host/File.h b/lldb/include/lldb/Host/File.h index 590c9fa523b29..3e15961a5c239 100644 --- a/lldb/include/lldb/Host/File.h +++ b/lldb/include/lldb/Host/File.h @@ -1,4 +1,4 @@ -//===-- File.h --------------------------------------------------*- C++ -*-===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -9,457 +9,25 @@ #ifndef LLDB_HOST_FILE_H #define LLDB_HOST_FILE_H -#include "lldb/Host/PosixApi.h" +#include "lldb/Host/FileBase.h" #include "lldb/Host/Terminal.h" -#include "lldb/Utility/IOObject.h" -#include "lldb/Utility/Status.h" -#include "lldb/lldb-private.h" -#include "llvm/ADT/BitmaskEnum.h" -#include <cstdarg> -#include <cstdio> -#include <mutex> +#include <memory> #include <optional> -#include <sys/types.h> - -namespace lldb_private { - -LLVM_ENABLE_BITMASK_ENUMS_IN_NAMESPACE(); - -/// \class File File.h "lldb/Host/File.h" -/// An abstract base class for files. -/// -/// Files will often be NativeFiles, which provides a wrapper -/// around host OS file functionality. But it -/// is also possible to subclass file to provide objects that have file -/// or stream functionality but are not backed by any host OS file. -class File : public IOObject { -public: - static int kInvalidDescriptor; - static FILE *kInvalidStream; - - // NB this enum is used in the lldb platform gdb-remote packet - // vFile:open: and existing values cannot be modified. - // - // The first set of values is defined by gdb headers and can be found - // in the documentation at: - // * https://sourceware.org/gdb/onlinedocs/gdb/Open-Flags.html#Open-Flags - // - // The second half are LLDB extensions and use the highest uint32_t bits - // to avoid risk of collisions with future gdb remote protocol changes. - enum OpenOptions : uint32_t { - eOpenOptionReadOnly = 0x0, // Open file for reading (only) - eOpenOptionWriteOnly = 0x1, // Open file for writing (only) - eOpenOptionReadWrite = 0x2, // Open file for both reading and writing - eOpenOptionAppend = - 0x8, // Don't truncate file when opening, append to end of file - eOpenOptionCanCreate = 0x200, // Create file if doesn't already exist - eOpenOptionTruncate = 0x400, // Truncate file when opening - eOpenOptionCanCreateNewOnly = - 0x800, // Can create file only if it doesn't already exist - - eOpenOptionNonBlocking = (1u << 28), // File reads - eOpenOptionDontFollowSymlinks = (1u << 29), - eOpenOptionCloseOnExec = - (1u << 30), // Close the file when executing a new process - eOpenOptionInvalid = (1u << 31), // Used as invalid value - LLVM_MARK_AS_BITMASK_ENUM(/* largest_value= */ eOpenOptionInvalid) - }; - - static constexpr OpenOptions OpenOptionsModeMask = - eOpenOptionReadOnly | eOpenOptionWriteOnly | eOpenOptionReadWrite; - - static mode_t ConvertOpenOptionsForPOSIXOpen(OpenOptions open_options); - static llvm::Expected<OpenOptions> GetOptionsFromMode(llvm::StringRef mode); - static bool DescriptorIsValid(int descriptor) { return descriptor >= 0; }; - static llvm::Expected<const char *> - GetStreamOpenModeFromOptions(OpenOptions options); - - File() : IOObject(eFDTypeFile){}; - - /// Read bytes from a file from the current file position into buf. - /// - /// NOTE: This function is NOT thread safe. Use the read function - /// that takes an "off_t &offset" to ensure correct operation in multi- - /// threaded environments. - /// - /// \param[in,out] num_bytes - /// Pass in the size of buf. Read will pass out the number - /// of bytes read. Zero bytes read with no error indicates - /// EOF. - /// - /// \return - /// success, ENOTSUP, or another error. - Status Read(void *buf, size_t &num_bytes) override; - - /// Write bytes from buf to a file at the current file position. - /// - /// NOTE: This function is NOT thread safe. Use the write function - /// that takes an "off_t &offset" to ensure correct operation in multi- - /// threaded environments. - /// - /// \param[in,out] num_bytes - /// Pass in the size of buf. Write will pass out the number - /// of bytes written. Write will attempt write the full number - /// of bytes and will not return early except on error. - /// - /// \return - /// success, ENOTSUP, or another error. - Status Write(const void *buf, size_t &num_bytes) override; - - /// IsValid - /// - /// \return - /// true iff the file is valid. - bool IsValid() const override; - - /// Flush any buffers and release any resources owned by the file. - /// After Close() the file will be invalid. - /// - /// \return - /// success or an error. - Status Close() override; - - /// Get a handle that can be used for OS polling interfaces, such - /// as WaitForMultipleObjects, select, or epoll. This may return - /// IOObject::kInvalidHandleValue if none is available. This will - /// generally be the same as the file descriptor, this function - /// is not interchangeable with GetDescriptor(). A WaitableHandle - /// must only be used for polling, not actual I/O. - /// - /// \return - /// a valid handle or IOObject::kInvalidHandleValue - WaitableHandle GetWaitableHandle() override; - - /// Get the file specification for this file, if possible. - /// - /// \param[out] file_spec - /// the file specification. - /// \return - /// ENOTSUP, success, or another error. - virtual Status GetFileSpec(FileSpec &file_spec) const; - - /// Get underlying OS file descriptor for this file, or kInvalidDescriptor. - /// If the descriptor is valid, then it may be used directly for I/O - /// However, the File may also perform it's own buffering, so avoid using - /// this if it is not necessary, or use Flush() appropriately. - /// - /// \return - /// a valid file descriptor for this file or kInvalidDescriptor - virtual int GetDescriptor() const; - - /// Get the underlying libc stream for this file, or NULL. - /// - /// Not all valid files will have a FILE* stream. This should only be - /// used if absolutely necessary, such as to interact with 3rd party - /// libraries that need FILE* streams. - /// - /// \return - /// a valid stream or NULL; - virtual FILE *GetStream(); - - /// Seek to an offset relative to the beginning of the file. - /// - /// NOTE: This function is NOT thread safe, other threads that - /// access this object might also change the current file position. For - /// thread safe reads and writes see the following functions: @see - /// File::Read (void *, size_t, off_t &) \see File::Write (const void *, - /// size_t, off_t &) - /// - /// \param[in] offset - /// The offset to seek to within the file relative to the - /// beginning of the file. - /// - /// \param[in] error_ptr - /// A pointer to a lldb_private::Status object that will be - /// filled in if non-nullptr. - /// - /// \return - /// The resulting seek offset, or -1 on error. - virtual off_t SeekFromStart(off_t offset, Status *error_ptr = nullptr); - - /// Seek to an offset relative to the current file position. - /// - /// NOTE: This function is NOT thread safe, other threads that - /// access this object might also change the current file position. For - /// thread safe reads and writes see the following functions: @see - /// File::Read (void *, size_t, off_t &) \see File::Write (const void *, - /// size_t, off_t &) - /// - /// \param[in] offset - /// The offset to seek to within the file relative to the - /// current file position. - /// - /// \param[in] error_ptr - /// A pointer to a lldb_private::Status object that will be - /// filled in if non-nullptr. - /// - /// \return - /// The resulting seek offset, or -1 on error. - virtual off_t SeekFromCurrent(off_t offset, Status *error_ptr = nullptr); - - /// Seek to an offset relative to the end of the file. - /// - /// NOTE: This function is NOT thread safe, other threads that - /// access this object might also change the current file position. For - /// thread safe reads and writes see the following functions: @see - /// File::Read (void *, size_t, off_t &) \see File::Write (const void *, - /// size_t, off_t &) - /// - /// \param[in,out] offset - /// The offset to seek to within the file relative to the - /// end of the file which gets filled in with the resulting - /// absolute file offset. - /// - /// \param[in] error_ptr - /// A pointer to a lldb_private::Status object that will be - /// filled in if non-nullptr. - /// - /// \return - /// The resulting seek offset, or -1 on error. - virtual off_t SeekFromEnd(off_t offset, Status *error_ptr = nullptr); - - /// Read bytes from a file from the specified file offset. - /// - /// NOTE: This function is thread safe in that clients manager their - /// own file position markers and reads on other threads won't mess up the - /// current read. - /// - /// \param[in] dst - /// A buffer where to put the bytes that are read. - /// - /// \param[in,out] num_bytes - /// The number of bytes to read from the current file position - /// which gets modified with the number of bytes that were read. - /// - /// \param[in,out] offset - /// The offset within the file from which to read \a num_bytes - /// bytes. This offset gets incremented by the number of bytes - /// that were read. - /// - /// \return - /// An error object that indicates success or the reason for - /// failure. - virtual Status Read(void *dst, size_t &num_bytes, off_t &offset); - - /// Write bytes to a file at the specified file offset. - /// - /// NOTE: This function is thread safe in that clients manager their - /// own file position markers, though clients will need to implement their - /// own locking externally to avoid multiple people writing to the file at - /// the same time. - /// - /// \param[in] src - /// A buffer containing the bytes to write. - /// - /// \param[in,out] num_bytes - /// The number of bytes to write to the file at offset \a offset. - /// \a num_bytes gets modified with the number of bytes that - /// were read. - /// - /// \param[in,out] offset - /// The offset within the file at which to write \a num_bytes - /// bytes. This offset gets incremented by the number of bytes - /// that were written. - /// - /// \return - /// An error object that indicates success or the reason for - /// failure. - virtual Status Write(const void *src, size_t &num_bytes, off_t &offset); - - /// Flush the current stream - /// - /// \return - /// An error object that indicates success or the reason for - /// failure. - virtual Status Flush(); - - /// Sync to disk. - /// - /// \return - /// An error object that indicates success or the reason for - /// failure. - virtual Status Sync(); - - /// Output printf formatted output to the stream. - /// - /// NOTE: this is not virtual, because it just calls the va_list - /// version of the function. - /// - /// Print some formatted output to the stream. - /// - /// \param[in] format - /// A printf style format string. - /// - /// \param[in] ... - /// Variable arguments that are needed for the printf style - /// format string \a format. - size_t Printf(const char *format, ...) __attribute__((format(printf, 2, 3))); - - /// Output printf formatted output to the stream. - /// - /// Print some formatted output to the stream. - /// - /// \param[in] format - /// A printf style format string. - /// - /// \param[in] args - /// Variable arguments that are needed for the printf style - /// format string \a format. - virtual size_t PrintfVarArg(const char *format, va_list args); - - /// Return the OpenOptions for this file. - /// - /// Some options like eOpenOptionDontFollowSymlinks only make - /// sense when a file is being opened (or not at all) - /// and may not be preserved for this method. But any valid - /// File should return either eOpenOptionReadOnly, eOpenOptionWriteOnly - /// or eOpenOptionReadWrite here. - /// - /// \return - /// OpenOptions flags for this file, or an error. - virtual llvm::Expected<OpenOptions> GetOptions() const; - - llvm::Expected<const char *> GetOpenMode() const { - auto opts = GetOptions(); - if (!opts) - return opts.takeError(); - return GetStreamOpenModeFromOptions(opts.get()); - } - - /// Get the permissions for a this file. - /// - /// \return - /// Bits logical OR'ed together from the permission bits defined - /// in lldb_private::File::Permissions. - uint32_t GetPermissions(Status &error) const; - - /// Return true if this file is interactive. - /// - /// \return - /// True if this file is a terminal (tty or pty), false - /// otherwise. - bool GetIsInteractive(); - - /// Return true if this file from a real terminal. - /// - /// Just knowing a file is a interactive isn't enough, we also need to know - /// if the terminal has a width and height so we can do cursor movement and - /// other terminal manipulations by sending escape sequences. - /// - /// \return - /// True if this file is a terminal (tty, not a pty) that has - /// a non-zero width and height, false otherwise. - bool GetIsRealTerminal(); - - /// Return true if this file is a terminal which supports colors. - /// - /// \return - /// True iff this is a terminal and it supports colors. - bool GetIsTerminalWithColors(); - - operator bool() const { return IsValid(); }; - - bool operator!() const { return !IsValid(); }; - static char ID; - virtual bool isA(const void *classID) const { return classID == &ID; } - static bool classof(const File *file) { return file->isA(&ID); } - -protected: - LazyBool m_is_interactive = eLazyBoolCalculate; - LazyBool m_is_real_terminal = eLazyBoolCalculate; - LazyBool m_supports_colors = eLazyBoolCalculate; - - void CalculateInteractiveAndTerminal(); - -private: - File(const File &) = delete; - const File &operator=(const File &) = delete; -}; - -class NativeFile : public File { -public: - enum TransferOwnership : bool { - Owned = true, - Unowned = false, - }; +#if defined(_WIN32) +#include "lldb/Host/windows/FileWindows.h" +#else +#include "lldb/Host/posix/FilePosix.h" +#endif - NativeFile(); - - NativeFile(FILE *fh, OpenOptions options, bool transfer_ownership); - - NativeFile(int fd, OpenOptions options, bool transfer_ownership); - - ~NativeFile() override { Close(); } - - bool IsValid() const override; - - Status Read(void *buf, size_t &num_bytes) override; - Status Write(const void *buf, size_t &num_bytes) override; - Status Close() override; - WaitableHandle GetWaitableHandle() override; - Status GetFileSpec(FileSpec &file_spec) const override; - int GetDescriptor() const override; - FILE *GetStream() override; - off_t SeekFromStart(off_t offset, Status *error_ptr = nullptr) override; - off_t SeekFromCurrent(off_t offset, Status *error_ptr = nullptr) override; - off_t SeekFromEnd(off_t offset, Status *error_ptr = nullptr) override; - Status Read(void *dst, size_t &num_bytes, off_t &offset) override; - Status Write(const void *src, size_t &num_bytes, off_t &offset) override; - Status Flush() override; - Status Sync() override; - size_t PrintfVarArg(const char *format, va_list args) override; - llvm::Expected<OpenOptions> GetOptions() const override; - - static char ID; - bool isA(const void *classID) const override { - return classID == &ID || File::isA(classID); - } - static bool classof(const File *file) { return file->isA(&ID); } - -protected: - struct ValueGuard { - ValueGuard(std::mutex &m, bool b) : guard(m, std::adopt_lock), value(b) {} - std::lock_guard<std::mutex> guard; - bool value; - operator bool() { return value; } - }; - - bool DescriptorIsValidUnlocked() const { - - return File::DescriptorIsValid(m_descriptor); - } - - bool StreamIsValidUnlocked() const { return m_stream != kInvalidStream; } - - ValueGuard DescriptorIsValid() const { - m_descriptor_mutex.lock(); - return ValueGuard(m_descriptor_mutex, DescriptorIsValidUnlocked()); - } - - ValueGuard StreamIsValid() const { - m_stream_mutex.lock(); - return ValueGuard(m_stream_mutex, StreamIsValidUnlocked()); - } - - int m_descriptor = kInvalidDescriptor; - bool m_own_descriptor = false; - mutable std::mutex m_descriptor_mutex; - - FILE *m_stream = kInvalidStream; - mutable std::mutex m_stream_mutex; - - OpenOptions m_options{}; - bool m_own_stream = false; - std::mutex offset_access_mutex; - - bool is_windows_console = false; +namespace lldb_private { -private: - NativeFile(const NativeFile &) = delete; - const NativeFile &operator=(const NativeFile &) = delete; -}; +#if defined(_WIN32) +typedef NativeFileWindows NativeFile; +#else +typedef NativeFilePosix NativeFile; +#endif class SerialPort : public NativeFile { public: diff --git a/lldb/include/lldb/Host/FileBase.h b/lldb/include/lldb/Host/FileBase.h new file mode 100644 index 0000000000000..51a0de9a93887 --- /dev/null +++ b/lldb/include/lldb/Host/FileBase.h @@ -0,0 +1,245 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_HOST_FILEBASE_H +#define LLDB_HOST_FILEBASE_H + +#include "lldb/Host/PosixApi.h" +#include "lldb/Utility/IOObject.h" +#include "lldb/Utility/Status.h" +#include "lldb/lldb-private.h" +#include "llvm/ADT/BitmaskEnum.h" + +#include <cstdarg> +#include <cstdio> +#include <mutex> +#include <sys/types.h> + +namespace lldb_private { + +LLVM_ENABLE_BITMASK_ENUMS_IN_NAMESPACE(); + +/// \class File File.h "lldb/Host/File.h" +/// An abstract base class for files. +/// +/// Files will often be NativeFiles, which provides a wrapper +/// around host OS file functionality. But it +/// is also possible to subclass file to provide objects that have file +/// or stream functionality but are not backed by any host OS file. +class File : public IOObject { +public: + static int kInvalidDescriptor; + static FILE *kInvalidStream; + + // NB this enum is used in the lldb platform gdb-remote packet + // vFile:open: and existing values cannot be modified. + // + // The first set of values is defined by gdb headers and can be found + // in the documentation at: + // * https://sourceware.org/gdb/onlinedocs/gdb/Open-Flags.html#Open-Flags + // + // The second half are LLDB extensions and use the highest uint32_t bits + // to avoid risk of collisions with future gdb remote protocol changes. + enum OpenOptions : uint32_t { + eOpenOptionReadOnly = 0x0, // Open file for reading (only) + eOpenOptionWriteOnly = 0x1, // Open file for writing (only) + eOpenOptionReadWrite = 0x2, // Open file for both reading and writing + eOpenOptionAppend = + 0x8, // Don't truncate file when opening, append to end of file + eOpenOptionCanCreate = 0x200, // Create file if doesn't already exist + eOpenOptionTruncate = 0x400, // Truncate file when opening + eOpenOptionCanCreateNewOnly = + 0x800, // Can create file only if it doesn't already exist + + eOpenOptionNonBlocking = (1u << 28), // File reads + eOpenOptionDontFollowSymlinks = (1u << 29), + eOpenOptionCloseOnExec = + (1u << 30), // Close the file when executing a new process + eOpenOptionInvalid = (1u << 31), // Used as invalid value + LLVM_MARK_AS_BITMASK_ENUM(/* largest_value= */ eOpenOptionInvalid) + }; + + static constexpr OpenOptions OpenOptionsModeMask = + eOpenOptionReadOnly | eOpenOptionWriteOnly | eOpenOptionReadWrite; + + static mode_t ConvertOpenOptionsForPOSIXOpen(OpenOptions open_options); + static llvm::Expected<OpenOptions> GetOptionsFromMode(llvm::StringRef mode); + static bool DescriptorIsValid(int descriptor) { return descriptor >= 0; }; + static llvm::Expected<const char *> + GetStreamOpenModeFromOptions(OpenOptions options); + + File() : IOObject(eFDTypeFile) {}; + + Status Read(void *buf, size_t &num_bytes) override; + Status Write(const void *buf, size_t &num_bytes) override; + bool IsValid() const override; + Status Close() override; + WaitableHandle GetWaitableHandle() override; + + virtual Status GetFileSpec(FileSpec &file_spec) const; + virtual int GetDescriptor() const; + virtual FILE *GetStream(); + virtual off_t SeekFromStart(off_t offset, Status *error_ptr = nullptr); + virtual off_t SeekFromCurrent(off_t offset, Status *error_ptr = nullptr); + virtual off_t SeekFromEnd(off_t offset, Status *error_ptr = nullptr); + virtual Status Read(void *dst, size_t &num_bytes, off_t &offset); + virtual Status Write(const void *src, size_t &num_bytes, off_t &offset); + virtual Status Flush(); + virtual Status Sync(); + + size_t Printf(const char *format, ...) __attribute__((format(printf, 2, 3))); + virtual size_t PrintfVarArg(const char *format, va_list args); + + virtual llvm::Expected<OpenOptions> GetOptions() const; + + llvm::Expected<const char *> GetOpenMode() const { + auto opts = GetOptions(); + if (!opts) + return opts.takeError(); + return GetStreamOpenModeFromOptions(opts.get()); + } + + uint32_t GetPermissions(Status &error) const; + + bool GetIsInteractive(); + bool GetIsRealTerminal(); + bool GetIsTerminalWithColors(); + + operator bool() const { return IsValid(); }; + bool operator!() const { return !IsValid(); }; + + static char ID; + virtual bool isA(const void *classID) const { return classID == &ID; } + static bool classof(const File *file) { return file->isA(&ID); } + +protected: + LazyBool m_is_interactive = eLazyBoolCalculate; + LazyBool m_is_real_terminal = eLazyBoolCalculate; + LazyBool m_supports_colors = eLazyBoolCalculate; + + /// Refresh the cached interactive / terminal / color flags by inspecting + /// the underlying descriptor. The default implementation leaves the + /// flags set to "no"; concrete subclasses override this with the + /// appropriate platform-specific terminal probing. + virtual void CalculateInteractiveAndTerminal(); + +private: + File(const File &) = delete; + const File &operator=(const File &) = delete; +}; + +/// \class NativeFileBase File.h "lldb/Host/File.h" +/// Common base for the platform-specific NativeFile implementations. +/// +/// Holds the descriptor / stream state and provides the platform-neutral +/// method bodies. The platform-specific behaviour lives in +/// NativeFilePosix (Host/posix/FilePosix.h) and NativeFileWindows +/// (Host/windows/FileWindows.h); user code should refer to the +/// `NativeFile` typedef in lldb/Host/File.h, which resolves to the right +/// concrete class for the host. +class NativeFileBase : public File { +public: + enum TransferOwnership : bool { + Owned = true, + Unowned = false, + }; + + NativeFileBase(); + + NativeFileBase(FILE *fh, OpenOptions options, bool transfer_ownership); + + NativeFileBase(int fd, OpenOptions options, bool transfer_ownership); + + ~NativeFileBase() override { Close(); } + + bool IsValid() const override; + + Status Read(void *buf, size_t &num_bytes) override; + Status Write(const void *buf, size_t &num_bytes) override; + Status Close() override; + WaitableHandle GetWaitableHandle() override; + Status GetFileSpec(FileSpec &file_spec) const override; + int GetDescriptor() const override; + FILE *GetStream() override; + off_t SeekFromStart(off_t offset, Status *error_ptr = nullptr) override; + off_t SeekFromCurrent(off_t offset, Status *error_ptr = nullptr) override; + off_t SeekFromEnd(off_t offset, Status *error_ptr = nullptr) override; + Status Read(void *dst, size_t &num_bytes, off_t &offset) override; + Status Write(const void *src, size_t &num_bytes, off_t &offset) override; + Status Flush() override; + Status Sync() override; + size_t PrintfVarArg(const char *format, va_list args) override; + llvm::Expected<OpenOptions> GetOptions() const override; + + static char ID; + bool isA(const void *classID) const override { + return classID == &ID || File::isA(classID); + } + static bool classof(const File *file) { return file->isA(&ID); } + +protected: + struct ValueGuard { + ValueGuard(std::mutex &m, bool b) : guard(m, std::adopt_lock), value(b) {} + std::lock_guard<std::mutex> guard; + bool value; + operator bool() { return value; } + }; + + bool DescriptorIsValidUnlocked() const { + return File::DescriptorIsValid(m_descriptor); + } + + bool StreamIsValidUnlocked() const { return m_stream != kInvalidStream; } + + ValueGuard DescriptorIsValid() const { + m_descriptor_mutex.lock(); + return ValueGuard(m_descriptor_mutex, DescriptorIsValidUnlocked()); + } + + ValueGuard StreamIsValid() const { + m_stream_mutex.lock(); + return ValueGuard(m_stream_mutex, StreamIsValidUnlocked()); + } + + /// Map a stream to its underlying file descriptor. Subclasses override + /// when the host libc spells fileno() differently (e.g. _fileno on + /// Windows MSVC). + virtual int Fileno(FILE *fh) const; + + /// Duplicate a file descriptor. Subclasses override when the host libc + /// spells dup() differently (e.g. _dup on Windows MSVC). + virtual int Dup(int fd) const; + + /// Hook for stream writes that bypass the default fwrite path. Returns + /// true if the bytes were consumed by the override (e.g. the Windows + /// console output path); false to fall through to the default fwrite. + /// Called with m_stream_mutex held. + virtual bool TryWriteStreamUnlocked(const void *buf, size_t &num_bytes, + Status &error) { + return false; + } + + int m_descriptor = kInvalidDescriptor; + bool m_own_descriptor = false; + mutable std::mutex m_descriptor_mutex; + + FILE *m_stream = kInvalidStream; + mutable std::mutex m_stream_mutex; + + OpenOptions m_options{}; + bool m_own_stream = false; + std::mutex offset_access_mutex; + +private: + NativeFileBase(const NativeFileBase &) = delete; + const NativeFileBase &operator=(const NativeFileBase &) = delete; +}; + +} // namespace lldb_private + +#endif // LLDB_HOST_FILEBASE_H diff --git a/lldb/include/lldb/Host/posix/FilePosix.h b/lldb/include/lldb/Host/posix/FilePosix.h new file mode 100644 index 0000000000000..e55d9a0338897 --- /dev/null +++ b/lldb/include/lldb/Host/posix/FilePosix.h @@ -0,0 +1,49 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_HOST_POSIX_FILEPOSIX_H +#define LLDB_HOST_POSIX_FILEPOSIX_H + +#include "lldb/Host/FileBase.h" + +namespace lldb_private { + +/// \class NativeFilePosix FilePosix.h "lldb/Host/posix/FilePosix.h" +/// POSIX implementation of NativeFile. +/// +/// Most of the work happens in NativeFileBase; this class adds POSIX-flavoured +/// terminal probing and the debug-mode access-mode assertion that fires when a +/// FILE* is wrapped with mismatched OpenOptions. +class NativeFilePosix : public NativeFileBase { +public: + NativeFilePosix() = default; + + NativeFilePosix(FILE *fh, OpenOptions options, bool transfer_ownership); + + NativeFilePosix(int fd, OpenOptions options, bool transfer_ownership); + + // Bring the inherited single-argument Read/Write into scope so they aren't + // hidden by the positional overloads declared below. + using NativeFileBase::Read; + using NativeFileBase::Write; + + Status GetFileSpec(FileSpec &file_spec) const override; + Status Read(void *dst, size_t &num_bytes, off_t &offset) override; + Status Write(const void *src, size_t &num_bytes, off_t &offset) override; + +protected: + void CalculateInteractiveAndTerminal() override; + +private: + NativeFilePosix(const NativeFilePosix &) = delete; + const NativeFilePosix &operator=(const NativeFilePosix &) = delete; +}; + +} // namespace lldb_private + +#endif // LLDB_HOST_POSIX_FILEPOSIX_H diff --git a/lldb/include/lldb/Host/windows/FileWindows.h b/lldb/include/lldb/Host/windows/FileWindows.h new file mode 100644 index 0000000000000..a6c2c6bb03b21 --- /dev/null +++ b/lldb/include/lldb/Host/windows/FileWindows.h @@ -0,0 +1,65 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_HOST_WINDOWS_FILEWINDOWS_H +#define LLDB_HOST_WINDOWS_FILEWINDOWS_H + +#include "lldb/Host/FileBase.h" + +namespace lldb_private { + +/// \class NativeFileWindows FileWindows.h "lldb/Host/windows/FileWindows.h" +/// Windows implementation of NativeFile. +/// +/// Overrides the bits of NativeFileBase that need to call Win32 directly: +/// terminal probing, FlushFileBuffers-based Sync, the OS-handle waitable, +/// the lseek/SeekFromStart positional read/write fallback (Windows lacks +/// pread/pwrite), and the console output path that bypasses fwrite for +/// proper non-ASCII rendering. +class NativeFileWindows : public NativeFileBase { +public: + NativeFileWindows() = default; + + NativeFileWindows(FILE *fh, OpenOptions options, bool transfer_ownership); + + NativeFileWindows(int fd, OpenOptions options, bool transfer_ownership); + + // Bring the inherited single-argument Read/Write into scope so they aren't + // hidden by the positional overloads declared below. + using NativeFileBase::Read; + using NativeFileBase::Write; + + WaitableHandle GetWaitableHandle() override; + Status Sync() override; + Status Read(void *dst, size_t &num_bytes, off_t &offset) override; + Status Write(const void *src, size_t &num_bytes, off_t &offset) override; + +protected: + void CalculateInteractiveAndTerminal() override; + + int Fileno(FILE *fh) const override; + int Dup(int fd) const override; + + bool TryWriteStreamUnlocked(const void *buf, size_t &num_bytes, + Status &error) override; + +private: + /// Set when this file wraps stdin/stdout/stderr connected to a console; + /// triggers the raw_fd_ostream path for correct non-ASCII output. + bool m_is_windows_console = false; + + void DetectIsWindowsConsole(FILE *fh); + void DetectIsWindowsConsole(int fd); + + NativeFileWindows(const NativeFileWindows &) = delete; + const NativeFileWindows &operator=(const NativeFileWindows &) = delete; +}; + +} // namespace lldb_private + +#endif // LLDB_HOST_WINDOWS_FILEWINDOWS_H diff --git a/lldb/source/Host/CMakeLists.txt b/lldb/source/Host/CMakeLists.txt index e1329af67de32..e1db2ed35485b 100644 --- a/lldb/source/Host/CMakeLists.txt +++ b/lldb/source/Host/CMakeLists.txt @@ -71,6 +71,7 @@ if (CMAKE_SYSTEM_NAME MATCHES "Windows") windows/ConnectionConPTYWindows.cpp windows/ConnectionGenericFileWindows.cpp windows/FileSystem.cpp + windows/FileWindows.cpp windows/Host.cpp windows/HostInfoWindows.cpp windows/HostProcessWindows.cpp @@ -86,6 +87,7 @@ if (CMAKE_SYSTEM_NAME MATCHES "Windows") else() add_host_subdirectory(posix posix/DomainSocket.cpp + posix/FilePosix.cpp posix/FileSystemPosix.cpp posix/HostInfoPosix.cpp posix/HostProcessPosix.cpp diff --git a/lldb/source/Host/common/File.cpp b/lldb/source/Host/common/File.cpp index 9ffdf014a8bdb..0a22c8f949547 100644 --- a/lldb/source/Host/common/File.cpp +++ b/lldb/source/Host/common/File.cpp @@ -1,4 +1,4 @@ -//===-- File.cpp ----------------------------------------------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -14,15 +14,7 @@ #include <cstdio> #include <fcntl.h> #include <optional> - -#ifdef _WIN32 -#include "lldb/Host/windows/windows.h" -#else -#include <sys/ioctl.h> #include <sys/stat.h> -#include <termios.h> -#include <unistd.h> -#endif #include "lldb/Host/Config.h" #include "lldb/Host/FileSystem.h" @@ -158,36 +150,12 @@ Status File::Flush() { return Status(); } Status File::Sync() { return Flush(); } void File::CalculateInteractiveAndTerminal() { - const int fd = GetDescriptor(); - if (!DescriptorIsValid(fd)) { - m_is_interactive = eLazyBoolNo; - m_is_real_terminal = eLazyBoolNo; - m_supports_colors = eLazyBoolNo; - return; - } + // The base class has no descriptor of its own; concrete subclasses probe + // the platform. Default to "not a terminal" so callers see consistent + // values when they didn't override. m_is_interactive = eLazyBoolNo; m_is_real_terminal = eLazyBoolNo; -#if defined(_WIN32) - if (_isatty(fd)) { - m_is_interactive = eLazyBoolYes; - m_is_real_terminal = eLazyBoolYes; -#if defined(ENABLE_VIRTUAL_TERMINAL_PROCESSING) - m_supports_colors = eLazyBoolYes; -#endif - } -#else - if (isatty(fd)) { - m_is_interactive = eLazyBoolYes; - struct winsize window_size; - if (::ioctl(fd, TIOCGWINSZ, &window_size) == 0) { - if (window_size.ws_col > 0) { - m_is_real_terminal = eLazyBoolYes; - if (llvm::sys::Process::FileDescriptorHasColors(fd)) - m_supports_colors = eLazyBoolYes; - } - } - } -#endif + m_supports_colors = eLazyBoolNo; } bool File::GetIsInteractive() { @@ -247,78 +215,32 @@ uint32_t File::GetPermissions(Status &error) const { return file_stats.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO); } -NativeFile::NativeFile() = default; +NativeFileBase::NativeFileBase() = default; -NativeFile::NativeFile(FILE *fh, OpenOptions options, bool transfer_ownership) - : m_stream(fh), m_options(options), m_own_stream(transfer_ownership) { -#ifdef _WIN32 - // In order to properly display non ASCII characters in Windows, we need to - // use Windows APIs to print to the console. This is only required if the - // stream outputs to a console. - { - HANDLE h = INVALID_HANDLE_VALUE; - if (fh == stdin) - h = ::GetStdHandle(STD_INPUT_HANDLE); - else if (fh == stdout) - h = ::GetStdHandle(STD_OUTPUT_HANDLE); - else if (fh == stderr) - h = ::GetStdHandle(STD_ERROR_HANDLE); - is_windows_console = - h != INVALID_HANDLE_VALUE && ::GetFileType(h) == FILE_TYPE_CHAR; - } -#else -#ifndef NDEBUG - int fd = fileno(fh); - if (fd != -1) { - int required_mode = ConvertOpenOptionsForPOSIXOpen(options) & O_ACCMODE; - int mode = fcntl(fd, F_GETFL); - if (mode != -1) { - mode &= O_ACCMODE; - // Check that the file is open with a valid subset of the requested file - // access mode, e.g. if we expected the file to be writable then ensure it - // was opened with O_WRONLY or O_RDWR. - assert( - (required_mode == O_RDWR && mode == O_RDWR) || - (required_mode == O_RDONLY && (mode == O_RDWR || mode == O_RDONLY) || - (required_mode == O_WRONLY && - (mode == O_RDWR || mode == O_WRONLY))) && - "invalid file access mode"); - } - } -#endif -#endif -} +NativeFileBase::NativeFileBase(FILE *fh, OpenOptions options, + bool transfer_ownership) + : m_stream(fh), m_options(options), m_own_stream(transfer_ownership) {} -NativeFile::NativeFile(int fd, OpenOptions options, bool transfer_ownership) +NativeFileBase::NativeFileBase(int fd, OpenOptions options, + bool transfer_ownership) : m_descriptor(fd), m_own_descriptor(transfer_ownership), - m_options(options) { -#ifdef _WIN32 - // In order to properly display non ASCII characters in Windows, we need to - // use Windows APIs to print to the console. This is only required if the - // file outputs to a console. - { - HANDLE h = INVALID_HANDLE_VALUE; - if (fd == STDIN_FILENO) - h = ::GetStdHandle(STD_INPUT_HANDLE); - else if (fd == STDOUT_FILENO) - h = ::GetStdHandle(STD_OUTPUT_HANDLE); - else if (fd == STDERR_FILENO) - h = ::GetStdHandle(STD_ERROR_HANDLE); - is_windows_console = - h != INVALID_HANDLE_VALUE && ::GetFileType(h) == FILE_TYPE_CHAR; - } -#endif -} + m_options(options) {} -bool NativeFile::IsValid() const { +bool NativeFileBase::IsValid() const { std::scoped_lock<std::mutex, std::mutex> lock(m_descriptor_mutex, m_stream_mutex); return DescriptorIsValidUnlocked() || StreamIsValidUnlocked(); } -Expected<File::OpenOptions> NativeFile::GetOptions() const { return m_options; } +Expected<File::OpenOptions> NativeFileBase::GetOptions() const { + return m_options; +} + +int NativeFileBase::Fileno(FILE *fh) const { return ::fileno(fh); } -int NativeFile::GetDescriptor() const { +int NativeFileBase::Dup(int fd) const { return ::dup(fd); } + +int NativeFileBase::GetDescriptor() const { if (ValueGuard descriptor_guard = DescriptorIsValid()) { return m_descriptor; } @@ -326,26 +248,18 @@ int NativeFile::GetDescriptor() const { // Don't open the file descriptor if we don't need to, just get it from the // stream if we have one. if (ValueGuard stream_guard = StreamIsValid()) { -#if defined(_WIN32) - return _fileno(m_stream); -#else - return fileno(m_stream); -#endif + return Fileno(m_stream); } // Invalid descriptor and invalid stream, return invalid descriptor. return kInvalidDescriptor; } -IOObject::WaitableHandle NativeFile::GetWaitableHandle() { -#ifdef _WIN32 - return (HANDLE)_get_osfhandle(GetDescriptor()); -#else +IOObject::WaitableHandle NativeFileBase::GetWaitableHandle() { return GetDescriptor(); -#endif } -FILE *NativeFile::GetStream() { +FILE *NativeFileBase::GetStream() { ValueGuard stream_guard = StreamIsValid(); if (!stream_guard) { if (ValueGuard descriptor_guard = DescriptorIsValid()) { @@ -354,13 +268,9 @@ FILE *NativeFile::GetStream() { llvm::consumeError(mode.takeError()); else { if (!m_own_descriptor) { -// We must duplicate the file descriptor if we don't own it because when you -// call fdopen, the stream will own the fd -#ifdef _WIN32 - m_descriptor = ::_dup(m_descriptor); -#else - m_descriptor = dup(m_descriptor); -#endif + // We must duplicate the file descriptor if we don't own it because + // when you call fdopen, the stream will own the fd. + m_descriptor = Dup(m_descriptor); m_own_descriptor = true; } @@ -380,7 +290,7 @@ FILE *NativeFile::GetStream() { return m_stream; } -Status NativeFile::Close() { +Status NativeFileBase::Close() { std::scoped_lock<std::mutex, std::mutex> lock(m_descriptor_mutex, m_stream_mutex); @@ -417,43 +327,16 @@ Status NativeFile::Close() { return error; } -Status NativeFile::GetFileSpec(FileSpec &file_spec) const { - Status error; -#ifdef F_GETPATH - if (IsValid()) { - char path[PATH_MAX]; - if (::fcntl(GetDescriptor(), F_GETPATH, path) == -1) - error = Status::FromErrno(); - else - file_spec.SetFile(path, FileSpec::Style::native); - } else { - error = Status::FromErrorString("invalid file handle"); - } -#elif defined(__linux__) - char proc[64]; - char path[PATH_MAX]; - if (::snprintf(proc, sizeof(proc), "/proc/self/fd/%d", GetDescriptor()) < 0) - error = Status::FromErrorString("cannot resolve file descriptor"); - else { - ssize_t len; - if ((len = ::readlink(proc, path, sizeof(path) - 1)) == -1) - error = Status::FromErrno(); - else { - path[len] = '\0'; - file_spec.SetFile(path, FileSpec::Style::native); - } - } -#else - error = Status::FromErrorString( +Status NativeFileBase::GetFileSpec(FileSpec &file_spec) const { + // Default: not supported. POSIX subclass overrides with F_GETPATH / + // /proc/self/fd lookups; the Windows subclass currently has no equivalent + // and inherits this behaviour. + file_spec.Clear(); + return Status::FromErrorString( "NativeFile::GetFileSpec is not supported on this platform"); -#endif - - if (error.Fail()) - file_spec.Clear(); - return error; } -off_t NativeFile::SeekFromStart(off_t offset, Status *error_ptr) { +off_t NativeFileBase::SeekFromStart(off_t offset, Status *error_ptr) { off_t result = 0; if (ValueGuard descriptor_guard = DescriptorIsValid()) { result = ::lseek(m_descriptor, offset, SEEK_SET); @@ -484,7 +367,7 @@ off_t NativeFile::SeekFromStart(off_t offset, Status *error_ptr) { return result; } -off_t NativeFile::SeekFromCurrent(off_t offset, Status *error_ptr) { +off_t NativeFileBase::SeekFromCurrent(off_t offset, Status *error_ptr) { off_t result = -1; if (ValueGuard descriptor_guard = DescriptorIsValid()) { result = ::lseek(m_descriptor, offset, SEEK_CUR); @@ -515,7 +398,7 @@ off_t NativeFile::SeekFromCurrent(off_t offset, Status *error_ptr) { return result; } -off_t NativeFile::SeekFromEnd(off_t offset, Status *error_ptr) { +off_t NativeFileBase::SeekFromEnd(off_t offset, Status *error_ptr) { off_t result = -1; if (ValueGuard descriptor_guard = DescriptorIsValid()) { result = ::lseek(m_descriptor, offset, SEEK_END); @@ -546,7 +429,7 @@ off_t NativeFile::SeekFromEnd(off_t offset, Status *error_ptr) { return result; } -Status NativeFile::Flush() { +Status NativeFileBase::Flush() { Status error; if (ValueGuard stream_guard = StreamIsValid()) { if (llvm::sys::RetryAfterSignal(EOF, ::fflush, m_stream) == EOF) @@ -562,17 +445,11 @@ Status NativeFile::Flush() { return error; } -Status NativeFile::Sync() { +Status NativeFileBase::Sync() { Status error; if (ValueGuard descriptor_guard = DescriptorIsValid()) { -#ifdef _WIN32 - int err = FlushFileBuffers((HANDLE)_get_osfhandle(m_descriptor)); - if (err == 0) - error = Status::FromErrorString("unknown error"); -#else if (llvm::sys::RetryAfterSignal(-1, ::fsync, m_descriptor) == -1) error = Status::FromErrno(); -#endif } else { error = Status::FromErrorString("invalid file handle"); } @@ -585,7 +462,7 @@ Status NativeFile::Sync() { #define MAX_WRITE_SIZE INT_MAX #endif -Status NativeFile::Read(void *buf, size_t &num_bytes) { +Status NativeFileBase::Read(void *buf, size_t &num_bytes) { Status error; // Ensure the file is open for reading. @@ -653,7 +530,7 @@ Status NativeFile::Read(void *buf, size_t &num_bytes) { return error; } -Status NativeFile::Write(const void *buf, size_t &num_bytes) { +Status NativeFileBase::Write(const void *buf, size_t &num_bytes) { Status error; // Ensure the file is open for writing. @@ -703,13 +580,8 @@ Status NativeFile::Write(const void *buf, size_t &num_bytes) { } if (ValueGuard stream_guard = StreamIsValid()) { -#ifdef _WIN32 - if (is_windows_console) { - llvm::raw_fd_ostream(_fileno(m_stream), false) - .write((const char *)buf, num_bytes); + if (TryWriteStreamUnlocked(buf, num_bytes, error)) return error; - } -#endif bytes_written = ::fwrite(buf, 1, num_bytes, m_stream); if (bytes_written == 0) { @@ -728,131 +600,23 @@ Status NativeFile::Write(const void *buf, size_t &num_bytes) { return error; } -Status NativeFile::Read(void *buf, size_t &num_bytes, off_t &offset) { - Status error; - -#if defined(MAX_READ_SIZE) - if (num_bytes > MAX_READ_SIZE) { - uint8_t *p = (uint8_t *)buf; - size_t bytes_left = num_bytes; - // Init the num_bytes read to zero - num_bytes = 0; - - while (bytes_left > 0) { - size_t curr_num_bytes; - if (bytes_left > MAX_READ_SIZE) - curr_num_bytes = MAX_READ_SIZE; - else - curr_num_bytes = bytes_left; - - error = Read(p + num_bytes, curr_num_bytes, offset); - - // Update how many bytes were read - num_bytes += curr_num_bytes; - if (bytes_left < curr_num_bytes) - bytes_left = 0; - else - bytes_left -= curr_num_bytes; - - if (error.Fail()) - break; - } - return error; - } -#endif - -#ifndef _WIN32 - int fd = GetDescriptor(); - if (fd != kInvalidDescriptor) { - ssize_t bytes_read = - llvm::sys::RetryAfterSignal(-1, ::pread, fd, buf, num_bytes, offset); - if (bytes_read < 0) { - num_bytes = 0; - error = Status::FromErrno(); - } else { - offset += bytes_read; - num_bytes = bytes_read; - } - } else { - num_bytes = 0; - error = Status::FromErrorString("invalid file handle"); - } -#else - std::lock_guard<std::mutex> guard(offset_access_mutex); - long cur = ::lseek(m_descriptor, 0, SEEK_CUR); - SeekFromStart(offset); - error = Read(buf, num_bytes); - if (!error.Fail()) - SeekFromStart(cur); -#endif - return error; +Status NativeFileBase::Read(void *buf, size_t &num_bytes, off_t &offset) { + // Default: not supported. POSIX subclass implements this with pread(); + // Windows subclass emulates it with lseek/Read since Win32 lacks pread. + num_bytes = 0; + return Status::FromErrorString( + "positional Read is not supported on this platform"); } -Status NativeFile::Write(const void *buf, size_t &num_bytes, off_t &offset) { - Status error; - -#if defined(MAX_WRITE_SIZE) - if (num_bytes > MAX_WRITE_SIZE) { - const uint8_t *p = (const uint8_t *)buf; - size_t bytes_left = num_bytes; - // Init the num_bytes written to zero - num_bytes = 0; - - while (bytes_left > 0) { - size_t curr_num_bytes; - if (bytes_left > MAX_WRITE_SIZE) - curr_num_bytes = MAX_WRITE_SIZE; - else - curr_num_bytes = bytes_left; - - error = Write(p + num_bytes, curr_num_bytes, offset); - - // Update how many bytes were read - num_bytes += curr_num_bytes; - if (bytes_left < curr_num_bytes) - bytes_left = 0; - else - bytes_left -= curr_num_bytes; - - if (error.Fail()) - break; - } - return error; - } -#endif - - int fd = GetDescriptor(); - if (fd != kInvalidDescriptor) { -#ifndef _WIN32 - ssize_t bytes_written = llvm::sys::RetryAfterSignal( - -1, ::pwrite, m_descriptor, buf, num_bytes, offset); - if (bytes_written < 0) { - num_bytes = 0; - error = Status::FromErrno(); - } else { - offset += bytes_written; - num_bytes = bytes_written; - } -#else - std::lock_guard<std::mutex> guard(offset_access_mutex); - long cur = ::lseek(m_descriptor, 0, SEEK_CUR); - SeekFromStart(offset); - error = Write(buf, num_bytes); - long after = ::lseek(m_descriptor, 0, SEEK_CUR); - - if (!error.Fail()) - SeekFromStart(cur); - - offset = after; -#endif - } else { - num_bytes = 0; - error = Status::FromErrorString("invalid file handle"); - } - return error; +Status NativeFileBase::Write(const void *buf, size_t &num_bytes, + off_t &offset) { + // Default: not supported. See Read(off_t) above. + num_bytes = 0; + return Status::FromErrorString( + "positional Write is not supported on this platform"); } -size_t NativeFile::PrintfVarArg(const char *format, va_list args) { +size_t NativeFileBase::PrintfVarArg(const char *format, va_list args) { if (StreamIsValid()) { return ::vfprintf(m_stream, format, args); } else { @@ -988,5 +752,5 @@ Status SerialPort::Close() { } char File::ID = 0; -char NativeFile::ID = 0; +char NativeFileBase::ID = 0; char SerialPort::ID = 0; diff --git a/lldb/source/Host/posix/FilePosix.cpp b/lldb/source/Host/posix/FilePosix.cpp new file mode 100644 index 0000000000000..fb18420435eae --- /dev/null +++ b/lldb/source/Host/posix/FilePosix.cpp @@ -0,0 +1,220 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "lldb/Host/posix/FilePosix.h" + +#include <cassert> +#include <climits> +#include <fcntl.h> +#include <sys/ioctl.h> +#include <termios.h> +#include <unistd.h> + +#include "lldb/Utility/FileSpec.h" +#include "lldb/Utility/Status.h" +#include "llvm/Support/Errno.h" +#include "llvm/Support/Process.h" + +using namespace lldb_private; + +#if defined(__APPLE__) +// Darwin kernels only can read/write <= INT_MAX bytes. Match the chunking +// limit used by NativeFileBase::Read/Write. +#define MAX_READ_SIZE INT_MAX +#define MAX_WRITE_SIZE INT_MAX +#endif + +NativeFilePosix::NativeFilePosix(FILE *fh, OpenOptions options, + bool transfer_ownership) + : NativeFileBase(fh, options, transfer_ownership) { +#ifndef NDEBUG + int fd = fileno(fh); + if (fd != -1) { + int required_mode = ConvertOpenOptionsForPOSIXOpen(options) & O_ACCMODE; + int mode = fcntl(fd, F_GETFL); + if (mode != -1) { + mode &= O_ACCMODE; + // Check that the file is open with a valid subset of the requested file + // access mode, e.g. if we expected the file to be writable then ensure it + // was opened with O_WRONLY or O_RDWR. + assert( + (required_mode == O_RDWR && mode == O_RDWR) || + (required_mode == O_RDONLY && (mode == O_RDWR || mode == O_RDONLY) || + (required_mode == O_WRONLY && + (mode == O_RDWR || mode == O_WRONLY))) && + "invalid file access mode"); + } + } +#endif +} + +NativeFilePosix::NativeFilePosix(int fd, OpenOptions options, + bool transfer_ownership) + : NativeFileBase(fd, options, transfer_ownership) {} + +void NativeFilePosix::CalculateInteractiveAndTerminal() { + const int fd = GetDescriptor(); + if (!File::DescriptorIsValid(fd)) { + m_is_interactive = eLazyBoolNo; + m_is_real_terminal = eLazyBoolNo; + m_supports_colors = eLazyBoolNo; + return; + } + m_is_interactive = eLazyBoolNo; + m_is_real_terminal = eLazyBoolNo; + if (isatty(fd)) { + m_is_interactive = eLazyBoolYes; + struct winsize window_size; + if (::ioctl(fd, TIOCGWINSZ, &window_size) == 0) { + if (window_size.ws_col > 0) { + m_is_real_terminal = eLazyBoolYes; + if (llvm::sys::Process::FileDescriptorHasColors(fd)) + m_supports_colors = eLazyBoolYes; + } + } + } +} + +Status NativeFilePosix::GetFileSpec(FileSpec &file_spec) const { + Status error; +#ifdef F_GETPATH + if (IsValid()) { + char path[PATH_MAX]; + if (::fcntl(GetDescriptor(), F_GETPATH, path) == -1) + error = Status::FromErrno(); + else + file_spec.SetFile(path, FileSpec::Style::native); + } else { + error = Status::FromErrorString("invalid file handle"); + } +#elif defined(__linux__) + char proc[64]; + char path[PATH_MAX]; + if (::snprintf(proc, sizeof(proc), "/proc/self/fd/%d", GetDescriptor()) < 0) + error = Status::FromErrorString("cannot resolve file descriptor"); + else { + ssize_t len; + if ((len = ::readlink(proc, path, sizeof(path) - 1)) == -1) + error = Status::FromErrno(); + else { + path[len] = '\0'; + file_spec.SetFile(path, FileSpec::Style::native); + } + } +#else + error = Status::FromErrorString( + "NativeFile::GetFileSpec is not supported on this platform"); +#endif + + if (error.Fail()) + file_spec.Clear(); + return error; +} + +Status NativeFilePosix::Read(void *buf, size_t &num_bytes, off_t &offset) { + Status error; + +#if defined(MAX_READ_SIZE) + if (num_bytes > MAX_READ_SIZE) { + uint8_t *p = (uint8_t *)buf; + size_t bytes_left = num_bytes; + // Init the num_bytes read to zero + num_bytes = 0; + + while (bytes_left > 0) { + size_t curr_num_bytes; + if (bytes_left > MAX_READ_SIZE) + curr_num_bytes = MAX_READ_SIZE; + else + curr_num_bytes = bytes_left; + + error = Read(p + num_bytes, curr_num_bytes, offset); + + // Update how many bytes were read + num_bytes += curr_num_bytes; + if (bytes_left < curr_num_bytes) + bytes_left = 0; + else + bytes_left -= curr_num_bytes; + + if (error.Fail()) + break; + } + return error; + } +#endif + + int fd = GetDescriptor(); + if (fd != kInvalidDescriptor) { + ssize_t bytes_read = + llvm::sys::RetryAfterSignal(-1, ::pread, fd, buf, num_bytes, offset); + if (bytes_read < 0) { + num_bytes = 0; + error = Status::FromErrno(); + } else { + offset += bytes_read; + num_bytes = bytes_read; + } + } else { + num_bytes = 0; + error = Status::FromErrorString("invalid file handle"); + } + return error; +} + +Status NativeFilePosix::Write(const void *buf, size_t &num_bytes, + off_t &offset) { + Status error; + +#if defined(MAX_WRITE_SIZE) + if (num_bytes > MAX_WRITE_SIZE) { + const uint8_t *p = (const uint8_t *)buf; + size_t bytes_left = num_bytes; + // Init the num_bytes written to zero + num_bytes = 0; + + while (bytes_left > 0) { + size_t curr_num_bytes; + if (bytes_left > MAX_WRITE_SIZE) + curr_num_bytes = MAX_WRITE_SIZE; + else + curr_num_bytes = bytes_left; + + error = Write(p + num_bytes, curr_num_bytes, offset); + + // Update how many bytes were read + num_bytes += curr_num_bytes; + if (bytes_left < curr_num_bytes) + bytes_left = 0; + else + bytes_left -= curr_num_bytes; + + if (error.Fail()) + break; + } + return error; + } +#endif + + int fd = GetDescriptor(); + if (fd != kInvalidDescriptor) { + ssize_t bytes_written = llvm::sys::RetryAfterSignal( + -1, ::pwrite, m_descriptor, buf, num_bytes, offset); + if (bytes_written < 0) { + num_bytes = 0; + error = Status::FromErrno(); + } else { + offset += bytes_written; + num_bytes = bytes_written; + } + } else { + num_bytes = 0; + error = Status::FromErrorString("invalid file handle"); + } + return error; +} diff --git a/lldb/source/Host/windows/FileWindows.cpp b/lldb/source/Host/windows/FileWindows.cpp new file mode 100644 index 0000000000000..f2c788d78a3b6 --- /dev/null +++ b/lldb/source/Host/windows/FileWindows.cpp @@ -0,0 +1,139 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "lldb/Host/windows/FileWindows.h" + +#include "lldb/Host/windows/windows.h" + +#include <climits> +#include <io.h> +#include <mutex> +#include <stdio.h> + +#include "lldb/Utility/Status.h" +#include "llvm/Support/raw_ostream.h" + +using namespace lldb_private; + +void NativeFileWindows::DetectIsWindowsConsole(FILE *fh) { + // Windows consoles need a special I/O path to render non-ASCII characters + // correctly; remember which standard streams are connected to the console. + HANDLE h = INVALID_HANDLE_VALUE; + if (fh == stdin) + h = ::GetStdHandle(STD_INPUT_HANDLE); + else if (fh == stdout) + h = ::GetStdHandle(STD_OUTPUT_HANDLE); + else if (fh == stderr) + h = ::GetStdHandle(STD_ERROR_HANDLE); + m_is_windows_console = + h != INVALID_HANDLE_VALUE && ::GetFileType(h) == FILE_TYPE_CHAR; +} + +void NativeFileWindows::DetectIsWindowsConsole(int fd) { + HANDLE h = INVALID_HANDLE_VALUE; + if (fd == STDIN_FILENO) + h = ::GetStdHandle(STD_INPUT_HANDLE); + else if (fd == STDOUT_FILENO) + h = ::GetStdHandle(STD_OUTPUT_HANDLE); + else if (fd == STDERR_FILENO) + h = ::GetStdHandle(STD_ERROR_HANDLE); + m_is_windows_console = + h != INVALID_HANDLE_VALUE && ::GetFileType(h) == FILE_TYPE_CHAR; +} + +NativeFileWindows::NativeFileWindows(FILE *fh, OpenOptions options, + bool transfer_ownership) + : NativeFileBase(fh, options, transfer_ownership) { + DetectIsWindowsConsole(fh); +} + +NativeFileWindows::NativeFileWindows(int fd, OpenOptions options, + bool transfer_ownership) + : NativeFileBase(fd, options, transfer_ownership) { + DetectIsWindowsConsole(fd); +} + +void NativeFileWindows::CalculateInteractiveAndTerminal() { + const int fd = GetDescriptor(); + if (!File::DescriptorIsValid(fd)) { + m_is_interactive = eLazyBoolNo; + m_is_real_terminal = eLazyBoolNo; + m_supports_colors = eLazyBoolNo; + return; + } + m_is_interactive = eLazyBoolNo; + m_is_real_terminal = eLazyBoolNo; + if (_isatty(fd)) { + m_is_interactive = eLazyBoolYes; + m_is_real_terminal = eLazyBoolYes; +#if defined(ENABLE_VIRTUAL_TERMINAL_PROCESSING) + m_supports_colors = eLazyBoolYes; +#endif + } +} + +int NativeFileWindows::Fileno(FILE *fh) const { return ::_fileno(fh); } + +int NativeFileWindows::Dup(int fd) const { return ::_dup(fd); } + +IOObject::WaitableHandle NativeFileWindows::GetWaitableHandle() { + return (HANDLE)_get_osfhandle(GetDescriptor()); +} + +Status NativeFileWindows::Sync() { + Status error; + if (ValueGuard descriptor_guard = DescriptorIsValid()) { + if (FlushFileBuffers((HANDLE)_get_osfhandle(m_descriptor)) == 0) + error = Status::FromErrorString("unknown error"); + } else { + error = Status::FromErrorString("invalid file handle"); + } + return error; +} + +bool NativeFileWindows::TryWriteStreamUnlocked(const void *buf, + size_t &num_bytes, + Status &error) { + if (!m_is_windows_console) + return false; + // Bypass fwrite for console output: use raw_fd_ostream so that the Windows + // console renders non-ASCII characters via its UTF-16 path. + llvm::raw_fd_ostream(_fileno(m_stream), false) + .write((const char *)buf, num_bytes); + return true; +} + +Status NativeFileWindows::Read(void *buf, size_t &num_bytes, off_t &offset) { + // Win32 has no pread(); emulate it by saving the current offset, seeking, + // reading, and restoring. Serialize against other positional Reads/Writes + // so concurrent callers don't trample the file pointer. + std::lock_guard<std::mutex> guard(offset_access_mutex); + long cur = ::lseek(m_descriptor, 0, SEEK_CUR); + SeekFromStart(offset); + Status error = NativeFileBase::Read(buf, num_bytes); + if (!error.Fail()) + SeekFromStart(cur); + return error; +} + +Status NativeFileWindows::Write(const void *buf, size_t &num_bytes, + off_t &offset) { + // Win32 has no pwrite(); same trick as Read above, but the post-write + // file position is what the caller wants reported back via `offset`. + std::lock_guard<std::mutex> guard(offset_access_mutex); + long cur = ::lseek(m_descriptor, 0, SEEK_CUR); + SeekFromStart(offset); + Status error = NativeFileBase::Write(buf, num_bytes); + long after = ::lseek(m_descriptor, 0, SEEK_CUR); + + if (!error.Fail()) + SeekFromStart(cur); + + offset = after; + return error; +} _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
