blob: 499df4e542ab2eb94ed7a7dc068b323125b3e292 [file] [edit]
//===----------------------------------------------------------------------===//
//
// 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 <cerrno>
#include <climits>
#include <cstdarg>
#include <cstdio>
#include <fcntl.h>
#include <optional>
#include <sys/stat.h>
#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<const char *>
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::OpenOptions> File::GetOptionsFromMode(llvm::StringRef mode) {
OpenOptions opts =
llvm::StringSwitch<OpenOptions>(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::OpenOptions> 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<std::mutex, std::mutex> lock(m_descriptor_mutex,
m_stream_mutex);
return DescriptorIsValidUnlocked() || StreamIsValidUnlocked();
}
Expected<File::OpenOptions> 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<std::mutex, std::mutex> 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::Options>
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<std::optional<Terminal::Parity>>(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<std::optional<Terminal::ParityCheck>>(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<std::unique_ptr<SerialPort>>
SerialPort::Create(int fd, OpenOptions options, Options serial_options,
bool transfer_ownership) {
std::unique_ptr<SerialPort> 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;