test tty writer
This commit is contained in:
@ -38,11 +38,17 @@ void TtyProtocolWriter::close_fd() noexcept {}
|
||||
|
||||
#else
|
||||
|
||||
#include <array>
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <optional>
|
||||
#include <pty.h>
|
||||
#include <sstream>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace {
|
||||
@ -53,31 +59,113 @@ std::string io_error(const std::string& action, const std::string& path) {
|
||||
return out.str();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TtyProtocolWriter::TtyProtocolWriter(std::string path) : path_(std::move(path)) {
|
||||
fd_ = ::open(path_.c_str(), O_WRONLY | O_NOCTTY);
|
||||
if (fd_ < 0) {
|
||||
throw std::runtime_error(io_error("Cannot open tty output", path_));
|
||||
void set_fd_raw(int fd) {
|
||||
struct termios tio {};
|
||||
if (::tcgetattr(fd, &tio) != 0) {
|
||||
throw std::runtime_error(io_error("Cannot read tty attributes for", std::to_string(fd)));
|
||||
}
|
||||
::cfmakeraw(&tio);
|
||||
if (::tcsetattr(fd, TCSANOW, &tio) != 0) {
|
||||
throw std::runtime_error(io_error("Cannot apply raw tty attributes to", std::to_string(fd)));
|
||||
}
|
||||
}
|
||||
|
||||
bool is_character_device_path(const std::string& path) {
|
||||
struct stat st {};
|
||||
if (::stat(path.c_str(), &st) != 0) {
|
||||
if (errno == ENOENT) {
|
||||
return false;
|
||||
}
|
||||
throw std::runtime_error(io_error("Cannot stat tty output", path));
|
||||
}
|
||||
return S_ISCHR(st.st_mode);
|
||||
}
|
||||
|
||||
std::optional<std::string> read_link_target(const std::string& path) {
|
||||
std::array<char, PATH_MAX> buf {};
|
||||
const ssize_t len = ::readlink(path.c_str(), buf.data(), buf.size() - 1U);
|
||||
if (len < 0) {
|
||||
if (errno == EINVAL || errno == ENOENT) {
|
||||
return std::nullopt;
|
||||
}
|
||||
throw std::runtime_error(io_error("Cannot read symlink", path));
|
||||
}
|
||||
buf[static_cast<std::size_t>(len)] = '\0';
|
||||
return std::string(buf.data());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TtyProtocolWriter::TtyProtocolWriter(std::string path) : path_(std::move(path)) {
|
||||
if (is_character_device_path(path_)) {
|
||||
fd_ = ::open(path_.c_str(), O_WRONLY | O_NOCTTY);
|
||||
if (fd_ < 0) {
|
||||
throw std::runtime_error(io_error("Cannot open tty output", path_));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
std::array<char, PATH_MAX> slave_name {};
|
||||
if (::openpty(&fd_, &slave_fd_, slave_name.data(), nullptr, nullptr) != 0) {
|
||||
throw std::runtime_error(io_error("Cannot create PTY bridge for", path_));
|
||||
}
|
||||
try {
|
||||
slave_path_ = slave_name.data();
|
||||
set_fd_raw(slave_fd_);
|
||||
|
||||
struct stat st {};
|
||||
if (::lstat(path_.c_str(), &st) == 0) {
|
||||
if (!S_ISLNK(st.st_mode) && !S_ISREG(st.st_mode)) {
|
||||
throw std::runtime_error("Refusing to replace non-link path '" + path_ + "'");
|
||||
}
|
||||
if (::unlink(path_.c_str()) != 0) {
|
||||
throw std::runtime_error(io_error("Cannot remove existing tty link", path_));
|
||||
}
|
||||
} else if (errno != ENOENT) {
|
||||
throw std::runtime_error(io_error("Cannot inspect tty link path", path_));
|
||||
}
|
||||
|
||||
if (::symlink(slave_path_.c_str(), path_.c_str()) != 0) {
|
||||
throw std::runtime_error(io_error("Cannot create tty symlink", path_));
|
||||
}
|
||||
} catch (...) {
|
||||
close_slave_fd();
|
||||
close_fd();
|
||||
throw;
|
||||
}
|
||||
owns_link_ = true;
|
||||
}
|
||||
|
||||
TtyProtocolWriter::~TtyProtocolWriter() {
|
||||
remove_owned_link();
|
||||
close_slave_fd();
|
||||
close_fd();
|
||||
}
|
||||
|
||||
TtyProtocolWriter::TtyProtocolWriter(TtyProtocolWriter&& other) noexcept
|
||||
: path_(std::move(other.path_)),
|
||||
fd_(other.fd_) {
|
||||
fd_(other.fd_),
|
||||
slave_fd_(other.slave_fd_),
|
||||
slave_path_(std::move(other.slave_path_)),
|
||||
owns_link_(other.owns_link_) {
|
||||
other.fd_ = -1;
|
||||
other.slave_fd_ = -1;
|
||||
other.owns_link_ = false;
|
||||
}
|
||||
|
||||
TtyProtocolWriter& TtyProtocolWriter::operator=(TtyProtocolWriter&& other) noexcept {
|
||||
if (this != &other) {
|
||||
remove_owned_link();
|
||||
close_slave_fd();
|
||||
close_fd();
|
||||
path_ = std::move(other.path_);
|
||||
fd_ = other.fd_;
|
||||
slave_fd_ = other.slave_fd_;
|
||||
slave_path_ = std::move(other.slave_path_);
|
||||
owns_link_ = other.owns_link_;
|
||||
other.fd_ = -1;
|
||||
other.slave_fd_ = -1;
|
||||
other.owns_link_ = false;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
@ -125,4 +213,27 @@ void TtyProtocolWriter::close_fd() noexcept {
|
||||
}
|
||||
}
|
||||
|
||||
void TtyProtocolWriter::close_slave_fd() noexcept {
|
||||
if (slave_fd_ >= 0) {
|
||||
::close(slave_fd_);
|
||||
slave_fd_ = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void TtyProtocolWriter::remove_owned_link() noexcept {
|
||||
if (!owns_link_ || path_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const auto target = read_link_target(path_);
|
||||
if (target && (*target == slave_path_)) {
|
||||
::unlink(path_.c_str());
|
||||
}
|
||||
} catch (...) {
|
||||
}
|
||||
|
||||
owns_link_ = false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user