[Lldb-commits] [lldb] [lldb] split NativeFile in platform specific implementations (PR #196293)
Charles Zablit via lldb-commits
lldb-commits at lists.llvm.org
Thu May 7 05:08:52 PDT 2026
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 <c_zablit at apple.com>
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;
+}
More information about the lldb-commits
mailing list