| //===-- 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; |