//===----------------------------------------------------------------------===// // // 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/File.h" #include #include #include #include #include #include #include #include "lldb/Host/Config.h" #include "lldb/Host/FileSystem.h" #include "lldb/Host/Host.h" #include "lldb/Utility/DataBufferHeap.h" #include "lldb/Utility/FileSpec.h" #include "lldb/Utility/LLDBLog.h" #include "lldb/Utility/Log.h" #include "lldb/Utility/VASPrintf.h" #include "llvm/ADT/StringExtras.h" #include "llvm/Support/ConvertUTF.h" #include "llvm/Support/Errno.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Process.h" #include "llvm/Support/raw_ostream.h" using namespace lldb; using namespace lldb_private; using llvm::Expected; Expected File::GetStreamOpenModeFromOptions(File::OpenOptions options) { File::OpenOptions rw = options & (File::eOpenOptionReadOnly | File::eOpenOptionWriteOnly | File::eOpenOptionReadWrite); if (options & File::eOpenOptionAppend) { if (rw == File::eOpenOptionReadWrite) { if (options & File::eOpenOptionCanCreateNewOnly) return "a+x"; else return "a+"; } else if (rw == File::eOpenOptionWriteOnly) { if (options & File::eOpenOptionCanCreateNewOnly) return "ax"; else return "a"; } } else if (rw == File::eOpenOptionReadWrite) { if (options & File::eOpenOptionCanCreate) { if (options & File::eOpenOptionCanCreateNewOnly) return "w+x"; else return "w+"; } else return "r+"; } else if (rw == File::eOpenOptionWriteOnly) { return "w"; } else if (rw == File::eOpenOptionReadOnly) { return "r"; } return llvm::createStringError( llvm::inconvertibleErrorCode(), "invalid options, cannot convert to mode string"); } Expected File::GetOptionsFromMode(llvm::StringRef mode) { OpenOptions opts = llvm::StringSwitch(mode) .Cases({"r", "rb"}, eOpenOptionReadOnly) .Cases({"w", "wb"}, eOpenOptionWriteOnly) .Cases({"a", "ab"}, eOpenOptionWriteOnly | eOpenOptionAppend | eOpenOptionCanCreate) .Cases({"r+", "rb+", "r+b"}, eOpenOptionReadWrite) .Cases({"w+", "wb+", "w+b"}, eOpenOptionReadWrite | eOpenOptionCanCreate | eOpenOptionTruncate) .Cases({"a+", "ab+", "a+b"}, eOpenOptionReadWrite | eOpenOptionAppend | eOpenOptionCanCreate) .Default(eOpenOptionInvalid); if (opts != eOpenOptionInvalid) return opts; return llvm::createStringError( llvm::inconvertibleErrorCode(), "invalid mode, cannot convert to File::OpenOptions"); } int File::kInvalidDescriptor = -1; FILE *File::kInvalidStream = nullptr; Status File::Read(void *buf, size_t &num_bytes) { return std::error_code(ENOTSUP, std::system_category()); } Status File::Write(const void *buf, size_t &num_bytes) { return std::error_code(ENOTSUP, std::system_category()); } bool File::IsValid() const { return false; } Status File::Close() { return Flush(); } IOObject::WaitableHandle File::GetWaitableHandle() { return IOObject::kInvalidHandleValue; } Status File::GetFileSpec(FileSpec &file_spec) const { file_spec.Clear(); return std::error_code(ENOTSUP, std::system_category()); } int File::GetDescriptor() const { return kInvalidDescriptor; } FILE *File::GetStream() { return nullptr; } off_t File::SeekFromStart(off_t offset, Status *error_ptr) { if (error_ptr) *error_ptr = std::error_code(ENOTSUP, std::system_category()); return -1; } off_t File::SeekFromCurrent(off_t offset, Status *error_ptr) { if (error_ptr) *error_ptr = std::error_code(ENOTSUP, std::system_category()); return -1; } off_t File::SeekFromEnd(off_t offset, Status *error_ptr) { if (error_ptr) *error_ptr = std::error_code(ENOTSUP, std::system_category()); return -1; } Status File::Read(void *dst, size_t &num_bytes, off_t &offset) { return std::error_code(ENOTSUP, std::system_category()); } Status File::Write(const void *src, size_t &num_bytes, off_t &offset) { return std::error_code(ENOTSUP, std::system_category()); } Status File::Flush() { return Status(); } Status File::Sync() { return Flush(); } void File::CalculateInteractiveAndTerminal() { // 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; m_supports_colors = eLazyBoolNo; } bool File::GetIsInteractive() { if (m_is_interactive == eLazyBoolCalculate) CalculateInteractiveAndTerminal(); return m_is_interactive == eLazyBoolYes; } bool File::GetIsRealTerminal() { if (m_is_real_terminal == eLazyBoolCalculate) CalculateInteractiveAndTerminal(); return m_is_real_terminal == eLazyBoolYes; } bool File::GetIsTerminalWithColors() { if (m_supports_colors == eLazyBoolCalculate) CalculateInteractiveAndTerminal(); return m_supports_colors == eLazyBoolYes; } size_t File::Printf(const char *format, ...) { va_list args; va_start(args, format); size_t result = PrintfVarArg(format, args); va_end(args); return result; } size_t File::PrintfVarArg(const char *format, va_list args) { llvm::SmallString<0> s; if (VASprintf(s, format, args)) { size_t written = s.size(); Write(s.data(), written); return written; } return 0; } Expected File::GetOptions() const { return llvm::createStringError( llvm::inconvertibleErrorCode(), "GetOptions() not implemented for this File class"); } uint32_t File::GetPermissions(Status &error) const { int fd = GetDescriptor(); if (!DescriptorIsValid(fd)) { error = std::error_code(ENOTSUP, std::system_category()); return 0; } struct stat file_stats; if (::fstat(fd, &file_stats) == -1) { error = Status::FromErrno(); return 0; } error.Clear(); return file_stats.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO); } NativeFileBase::NativeFileBase() = default; NativeFileBase::NativeFileBase(FILE *fh, OpenOptions options, bool transfer_ownership) : m_stream(fh), m_options(options), m_own_stream(transfer_ownership) {} NativeFileBase::NativeFileBase(int fd, OpenOptions options, bool transfer_ownership) : m_descriptor(fd), m_own_descriptor(transfer_ownership), m_options(options) {} bool NativeFileBase::IsValid() const { std::scoped_lock lock(m_descriptor_mutex, m_stream_mutex); return DescriptorIsValidUnlocked() || StreamIsValidUnlocked(); } Expected NativeFileBase::GetOptions() const { return m_options; } int NativeFileBase::Fileno(FILE *fh) const { return ::fileno(fh); } int NativeFileBase::Dup(int fd) const { return ::dup(fd); } int NativeFileBase::GetDescriptor() const { if (ValueGuard descriptor_guard = DescriptorIsValid()) { return m_descriptor; } // 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()) { return Fileno(m_stream); } // Invalid descriptor and invalid stream, return invalid descriptor. return kInvalidDescriptor; } IOObject::WaitableHandle NativeFileBase::GetWaitableHandle() { // The mapping from descriptor/stream to a poll-able WaitableHandle is // platform-specific (an int on POSIX, a HANDLE on Windows). Subclasses // override; the base returns the portable invalid sentinel. return IOObject::kInvalidHandleValue; } FILE *NativeFileBase::GetStream() { ValueGuard stream_guard = StreamIsValid(); if (stream_guard) return m_stream; ValueGuard descriptor_guard = DescriptorIsValid(); if (!descriptor_guard) return m_stream; auto mode_or_err = GetStreamOpenModeFromOptions(m_options); if (!mode_or_err) { LLDB_LOG_ERROR(GetLog(LLDBLog::Object), mode_or_err.takeError(), "Failed to get stream options: {0}"); return m_stream; } const char *mode = *mode_or_err; 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. m_descriptor = Dup(m_descriptor); m_own_descriptor = true; } m_stream = llvm::sys::RetryAfterSignal(nullptr, ::fdopen, m_descriptor, mode); // If we got a stream, then we own the stream and should no longer own // the descriptor because fclose() will close it for us. if (m_stream) { m_own_stream = true; m_own_descriptor = false; OnStreamOpened(); } return m_stream; } Status NativeFileBase::Close() { std::scoped_lock lock(m_descriptor_mutex, m_stream_mutex); Status error; if (StreamIsValidUnlocked()) { if (m_own_stream) { if (::fclose(m_stream) == EOF) error = Status::FromErrno(); } else { File::OpenOptions rw = m_options & (File::eOpenOptionReadOnly | File::eOpenOptionWriteOnly | File::eOpenOptionReadWrite); if (rw == eOpenOptionWriteOnly || rw == eOpenOptionReadWrite) { if (::fflush(m_stream) == EOF) error = Status::FromErrno(); } } } if (DescriptorIsValidUnlocked() && m_own_descriptor) { if (::close(m_descriptor) != 0) error = Status::FromErrno(); } m_stream = kInvalidStream; m_own_stream = false; m_descriptor = kInvalidDescriptor; m_own_descriptor = false; m_options = OpenOptions(0); m_is_interactive = eLazyBoolCalculate; m_is_real_terminal = eLazyBoolCalculate; return error; } 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"); } 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); if (error_ptr) { if (result == -1) *error_ptr = Status::FromErrno(); else error_ptr->Clear(); } return result; } if (ValueGuard stream_guard = StreamIsValid()) { result = ::fseek(m_stream, offset, SEEK_SET); if (error_ptr) { if (result == -1) *error_ptr = Status::FromErrno(); else error_ptr->Clear(); } return result; } if (error_ptr) *error_ptr = Status::FromErrorString("invalid file handle"); return result; } 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); if (error_ptr) { if (result == -1) *error_ptr = Status::FromErrno(); else error_ptr->Clear(); } return result; } if (ValueGuard stream_guard = StreamIsValid()) { result = ::fseek(m_stream, offset, SEEK_CUR); if (error_ptr) { if (result == -1) *error_ptr = Status::FromErrno(); else error_ptr->Clear(); } return result; } if (error_ptr) *error_ptr = Status::FromErrorString("invalid file handle"); return result; } 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); if (error_ptr) { if (result == -1) *error_ptr = Status::FromErrno(); else error_ptr->Clear(); } return result; } if (ValueGuard stream_guard = StreamIsValid()) { result = ::fseek(m_stream, offset, SEEK_END); if (error_ptr) { if (result == -1) *error_ptr = Status::FromErrno(); else error_ptr->Clear(); } return result; } if (error_ptr) *error_ptr = Status::FromErrorString("invalid file handle"); return result; } Status NativeFileBase::Flush() { Status error; if (ValueGuard stream_guard = StreamIsValid()) { if (llvm::sys::RetryAfterSignal(EOF, ::fflush, m_stream) == EOF) error = Status::FromErrno(); return error; } { ValueGuard descriptor_guard = DescriptorIsValid(); if (!descriptor_guard) error = Status::FromErrorString("invalid file handle"); } return error; } #if defined(__APPLE__) // Darwin kernels only can read/write <= INT_MAX bytes #define MAX_READ_SIZE INT_MAX #define MAX_WRITE_SIZE INT_MAX #endif Status NativeFileBase::Read(void *buf, size_t &num_bytes) { Status error; // Ensure the file is open for reading. if ((m_options & File::OpenOptionsModeMask) == eOpenOptionWriteOnly) return Status(std::make_error_code(std::errc::bad_file_descriptor)); #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); // 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 ssize_t bytes_read = -1; if (ValueGuard descriptor_guard = DescriptorIsValid()) { bytes_read = llvm::sys::RetryAfterSignal(-1, ::read, m_descriptor, buf, num_bytes); if (bytes_read == -1) { error = Status::FromErrno(); num_bytes = 0; } else num_bytes = bytes_read; return error; } if (ValueGuard file_lock = StreamIsValid()) { bytes_read = ::fread(buf, 1, num_bytes, m_stream); if (bytes_read == 0) { if (::feof(m_stream)) error = Status::FromErrorString("feof"); else if (::ferror(m_stream)) error = Status::FromErrorString("ferror"); num_bytes = 0; } else num_bytes = bytes_read; return error; } num_bytes = 0; error = Status::FromErrorString("invalid file handle"); return error; } Status NativeFileBase::Write(const void *buf, size_t &num_bytes) { Status error; // Ensure the file is open for writing. if ((m_options & File::OpenOptionsModeMask) == File::eOpenOptionReadOnly) return Status(std::make_error_code(std::errc::bad_file_descriptor)); #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); // 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 ssize_t bytes_written = -1; if (ValueGuard descriptor_guard = DescriptorIsValid()) { bytes_written = llvm::sys::RetryAfterSignal(-1, ::write, m_descriptor, buf, num_bytes); if (bytes_written == -1) { error = Status::FromErrno(); num_bytes = 0; } else num_bytes = bytes_written; return error; } if (ValueGuard stream_guard = StreamIsValid()) { if (TryWriteStreamUnlocked(buf, num_bytes, error)) return error; bytes_written = ::fwrite(buf, 1, num_bytes, m_stream); if (bytes_written == 0) { if (::feof(m_stream)) error = Status::FromErrorString("feof"); else if (::ferror(m_stream)) error = Status::FromErrorString("ferror"); num_bytes = 0; } else num_bytes = bytes_written; return error; } num_bytes = 0; error = Status::FromErrorString("invalid file handle"); return error; } size_t NativeFileBase::PrintfVarArg(const char *format, va_list args) { if (StreamIsValid()) { return ::vfprintf(m_stream, format, args); } else { return File::PrintfVarArg(format, args); } } mode_t File::ConvertOpenOptionsForPOSIXOpen(OpenOptions open_options) { mode_t mode = 0; File::OpenOptions rw = open_options & (File::eOpenOptionReadOnly | File::eOpenOptionWriteOnly | File::eOpenOptionReadWrite); if (rw == eOpenOptionReadWrite) mode |= O_RDWR; else if (rw == eOpenOptionWriteOnly) mode |= O_WRONLY; else if (rw == eOpenOptionReadOnly) mode |= O_RDONLY; if (open_options & eOpenOptionAppend) mode |= O_APPEND; if (open_options & eOpenOptionTruncate) mode |= O_TRUNC; if (open_options & eOpenOptionNonBlocking) mode |= O_NONBLOCK; if (open_options & eOpenOptionCanCreateNewOnly) mode |= O_CREAT | O_EXCL; else if (open_options & eOpenOptionCanCreate) mode |= O_CREAT; return mode; } llvm::Expected SerialPort::OptionsFromURL(llvm::StringRef urlqs) { SerialPort::Options serial_options; for (llvm::StringRef x : llvm::split(urlqs, '&')) { if (x.consume_front("baud=")) { unsigned int baud_rate; if (!llvm::to_integer(x, baud_rate, 10)) return llvm::createStringError(llvm::inconvertibleErrorCode(), "Invalid baud rate: %s", x.str().c_str()); serial_options.BaudRate = baud_rate; } else if (x.consume_front("parity=")) { serial_options.Parity = llvm::StringSwitch>(x) .Case("no", Terminal::Parity::No) .Case("even", Terminal::Parity::Even) .Case("odd", Terminal::Parity::Odd) .Case("mark", Terminal::Parity::Mark) .Case("space", Terminal::Parity::Space) .Default(std::nullopt); if (!serial_options.Parity) return llvm::createStringError( llvm::inconvertibleErrorCode(), "Invalid parity (must be no, even, odd, mark or space): %s", x.str().c_str()); } else if (x.consume_front("parity-check=")) { serial_options.ParityCheck = llvm::StringSwitch>(x) .Case("no", Terminal::ParityCheck::No) .Case("replace", Terminal::ParityCheck::ReplaceWithNUL) .Case("ignore", Terminal::ParityCheck::Ignore) // "mark" mode is not currently supported as it requires special // input processing // .Case("mark", Terminal::ParityCheck::Mark) .Default(std::nullopt); if (!serial_options.ParityCheck) return llvm::createStringError( llvm::inconvertibleErrorCode(), "Invalid parity-check (must be no, replace, ignore or mark): %s", x.str().c_str()); } else if (x.consume_front("stop-bits=")) { unsigned int stop_bits; if (!llvm::to_integer(x, stop_bits, 10) || (stop_bits != 1 && stop_bits != 2)) return llvm::createStringError( llvm::inconvertibleErrorCode(), "Invalid stop bit number (must be 1 or 2): %s", x.str().c_str()); serial_options.StopBits = stop_bits; } else return llvm::createStringError(llvm::inconvertibleErrorCode(), "Unknown parameter: %s", x.str().c_str()); } return serial_options; } llvm::Expected> SerialPort::Create(int fd, OpenOptions options, Options serial_options, bool transfer_ownership) { std::unique_ptr out{ new SerialPort(fd, options, serial_options, transfer_ownership)}; if (!out->GetIsInteractive()) return llvm::createStringError(llvm::inconvertibleErrorCode(), "the specified file is not a teletype"); Terminal term{fd}; if (llvm::Error error = term.SetRaw()) return std::move(error); if (serial_options.BaudRate) { if (llvm::Error error = term.SetBaudRate(*serial_options.BaudRate)) return std::move(error); } if (serial_options.Parity) { if (llvm::Error error = term.SetParity(*serial_options.Parity)) return std::move(error); } if (serial_options.ParityCheck) { if (llvm::Error error = term.SetParityCheck(*serial_options.ParityCheck)) return std::move(error); } if (serial_options.StopBits) { if (llvm::Error error = term.SetStopBits(*serial_options.StopBits)) return std::move(error); } return std::move(out); } SerialPort::SerialPort(int fd, OpenOptions options, SerialPort::Options serial_options, bool transfer_ownership) : NativeFile(fd, options, transfer_ownership), m_state(fd) {} Status SerialPort::Close() { m_state.Restore(); return NativeFile::Close(); } char File::ID = 0; char NativeFileBase::ID = 0; char SerialPort::ID = 0;