The stream path in NativeFile::SeekFromEnd was missing a `return result` statement after the fseek block, causing it to fall through to the error handler which overwrites the error status with "invalid file handle" even on success. Both SeekFromStart and SeekFromCurrent correctly return after their stream blocks. while no active callers to this function, It is still worth fixing this.
976 lines
27 KiB
C++
976 lines
27 KiB
C++
//===-- 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.
|
|
// 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>
|
|
|
|
#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"
|
|
#include "lldb/Host/Host.h"
|
|
#include "lldb/Utility/DataBufferHeap.h"
|
|
#include "lldb/Utility/FileSpec.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() {
|
|
const int fd = GetDescriptor();
|
|
if (!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 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
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
NativeFile::NativeFile() = 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.
|
|
int fd = _fileno(fh);
|
|
is_windows_console =
|
|
::GetFileType((HANDLE)::_get_osfhandle(fd)) == 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
|
|
}
|
|
|
|
NativeFile::NativeFile(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.
|
|
is_windows_console =
|
|
::GetFileType((HANDLE)::_get_osfhandle(fd)) == FILE_TYPE_CHAR;
|
|
#endif
|
|
}
|
|
|
|
bool NativeFile::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; }
|
|
|
|
int NativeFile::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()) {
|
|
#if defined(_WIN32)
|
|
return _fileno(m_stream);
|
|
#else
|
|
return fileno(m_stream);
|
|
#endif
|
|
}
|
|
|
|
// Invalid descriptor and invalid stream, return invalid descriptor.
|
|
return kInvalidDescriptor;
|
|
}
|
|
|
|
IOObject::WaitableHandle NativeFile::GetWaitableHandle() {
|
|
#ifdef _WIN32
|
|
return (HANDLE)_get_osfhandle(GetDescriptor());
|
|
#else
|
|
return GetDescriptor();
|
|
#endif
|
|
}
|
|
|
|
FILE *NativeFile::GetStream() {
|
|
ValueGuard stream_guard = StreamIsValid();
|
|
if (!stream_guard) {
|
|
if (ValueGuard descriptor_guard = DescriptorIsValid()) {
|
|
auto mode = GetStreamOpenModeFromOptions(m_options);
|
|
if (!mode)
|
|
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
|
|
m_own_descriptor = true;
|
|
}
|
|
|
|
m_stream = llvm::sys::RetryAfterSignal(nullptr, ::fdopen, m_descriptor,
|
|
mode.get());
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return m_stream;
|
|
}
|
|
|
|
Status NativeFile::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 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(
|
|
"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 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 NativeFile::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 NativeFile::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 NativeFile::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;
|
|
}
|
|
|
|
Status NativeFile::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");
|
|
}
|
|
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 NativeFile::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 NativeFile::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()) {
|
|
#ifdef _WIN32
|
|
if (is_windows_console) {
|
|
llvm::raw_fd_ostream(_fileno(m_stream), false)
|
|
.write((const char *)buf, num_bytes);
|
|
return error;
|
|
}
|
|
#endif
|
|
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;
|
|
}
|
|
|
|
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 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;
|
|
}
|
|
|
|
size_t NativeFile::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 NativeFile::ID = 0;
|
|
char SerialPort::ID = 0;
|