Support piping data to client stdin

Pass the client stdin fd to the server and open a fifo buffer
from it.

Fixes #3394
This commit is contained in:
Maxime Coste 2020-05-10 15:21:49 +10:00
parent 2104af0771
commit 60154300f9
7 changed files with 91 additions and 19 deletions

View File

@ -265,4 +265,15 @@ Vector<String> undo_group_as_strings(const Buffer::UndoGroup& undo_group)
return res;
}
String generate_buffer_name(StringView pattern)
{
auto& buffer_manager = BufferManager::instance();
for (int i = 0; true; ++i)
{
String name = format(pattern, i);
if (buffer_manager.get_buffer_ifp(name) == nullptr)
return name;
}
}
}

View File

@ -87,6 +87,8 @@ void write_to_debug_buffer(StringView str);
Vector<String> history_as_strings(const Vector<Buffer::HistoryNode>& history);
Vector<String> undo_group_as_strings(const Buffer::UndoGroup& undo_group);
String generate_buffer_name(StringView pattern);
}
#endif // buffer_utils_hh_INCLUDED

View File

@ -356,16 +356,8 @@ void edit(const ParametersParser& parser, Context& context, const ShellContext&)
(parser.get_switch("debug") ? Buffer::Flags::Debug : Buffer::Flags::None);
auto& buffer_manager = BufferManager::instance();
auto generate_scratch_name = [&] {
for (int i = 0; true; ++i)
{
String name = format("*scratch-{}*", i);
if (buffer_manager.get_buffer_ifp(name) == nullptr)
return name;
}
};
const auto& name = parser.positional_count() > 0 ?
parser[0] : (scratch ? generate_scratch_name() : context.buffer().name());
parser[0] : (scratch ? generate_buffer_name("*scratch-{}*") : context.buffer().name());
Buffer* buffer = buffer_manager.get_buffer_ifp(name);
if (scratch)

View File

@ -661,9 +661,21 @@ int run_client(StringView session, StringView name, StringView client_init,
{
try
{
Optional<int> stdin_fd;
if (not isatty(0))
{
// move stdin to another fd, and restore tty as stdin
stdin_fd = dup(0);
int tty = open("/dev/tty", O_RDONLY);
dup2(tty, 0);
close(tty);
}
EventManager event_manager;
RemoteClient client{session, name, make_ui(ui_type), getpid(), get_env_vars(),
client_init, std::move(init_coord)};
client_init, std::move(init_coord), stdin_fd};
stdin_fd.map(close);
if (suspend)
raise(SIGTSTP);
while (not client.exit_status() and client.is_ui_ok())

View File

@ -60,11 +60,12 @@ public:
bool operator!=(const Optional& other) const { return !(*this == other); }
template<typename... Args>
void emplace(Args&&... args)
T& emplace(Args&&... args)
{
destruct_ifn();
new (&m_value) T{std::forward<Args>(args)...};
m_valid = true;
return m_value;
}
T& operator*()

View File

@ -261,25 +261,56 @@ public:
return Reader<T>::read(*this);
}
Optional<int> ancillary_fd()
{
auto res = m_ancillary_fd;
m_ancillary_fd.reset();
return res;
}
~MsgReader()
{
m_ancillary_fd.map(close);
}
void reset()
{
m_stream.resize(0);
m_write_pos = 0;
m_read_pos = header_size;
m_ancillary_fd.map(close);
}
private:
void read_from_socket(int sock, size_t size)
{
kak_assert(m_write_pos + size <= m_stream.size());
int res = ::read(sock, m_stream.data() + m_write_pos, size);
iovec io{m_stream.data() + m_write_pos, size};
alignas(cmsghdr) char fdbuf[CMSG_SPACE(sizeof(int))];
msghdr msg{};
msg.msg_iov = &io;
msg.msg_iovlen = 1;
msg.msg_control = fdbuf;
msg.msg_controllen = sizeof(fdbuf);
int res = recvmsg(sock, &msg, MSG_CMSG_CLOEXEC);
if (res <= 0)
throw disconnected{format("socket read failed: {}", strerror(errno))};
m_write_pos += res;
if (cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
cmsg && cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS && cmsg->cmsg_len == CMSG_LEN(sizeof(int)))
{
m_ancillary_fd.map(close);
memcpy(&m_ancillary_fd.emplace(), CMSG_DATA(cmsg), sizeof(int));
}
}
static constexpr uint32_t header_size = sizeof(MessageType) + sizeof(uint32_t);
Vector<char, MemoryDomain::Remote> m_stream;
Optional<int> m_ancillary_fd;
uint32_t m_write_pos = 0;
uint32_t m_read_pos = header_size;
};
@ -398,11 +429,29 @@ private:
RemoteBuffer m_send_buffer;
};
static bool send_data(int fd, RemoteBuffer& buffer)
static bool send_data(int fd, RemoteBuffer& buffer, Optional<int> ancillary_fd = {})
{
while (not buffer.empty() and fd_writable(fd))
{
int res = ::write(fd, buffer.data(), buffer.size());
iovec io{buffer.data(), buffer.size()};
alignas(cmsghdr) char fdbuf[CMSG_SPACE(sizeof(int))];
msghdr msg{};
msg.msg_iov = &io;
msg.msg_iovlen = 1;
if (ancillary_fd)
{
msg.msg_control = fdbuf;
msg.msg_controllen = sizeof(fdbuf);
cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
memcpy(CMSG_DATA(cmsg), &*ancillary_fd, sizeof(int));
}
int res = sendmsg(fd, &msg, 0);
if (res <= 0)
throw disconnected{format("socket write failed: {}", strerror(errno))};
buffer.erase(buffer.begin(), buffer.begin() + res);
@ -592,7 +641,7 @@ bool check_session(StringView session)
RemoteClient::RemoteClient(StringView session, StringView name, std::unique_ptr<UserInterface>&& ui,
int pid, const EnvVarMap& env_vars, StringView init_command,
Optional<BufferCoord> init_coord)
Optional<BufferCoord> init_coord, Optional<int> stdin_fd)
: m_ui(std::move(ui))
{
int sock = connect_to(session);
@ -601,6 +650,7 @@ RemoteClient::RemoteClient(StringView session, StringView name, std::unique_ptr<
MsgWriter msg{m_send_buffer, MessageType::Connect};
msg.write(pid, name, init_command, init_coord, m_ui->dimensions(), env_vars);
}
send_data(sock, m_send_buffer, stdin_fd);
m_ui->set_on_key([this](Key key){
MsgWriter msg(m_send_buffer, MessageType::Key);
@ -750,6 +800,10 @@ private:
auto init_coord = m_reader.read<Optional<BufferCoord>>();
auto dimensions = m_reader.read<DisplayCoord>();
auto env_vars = m_reader.read<HashMap<String, String, MemoryDomain::EnvVars>>();
if (auto stdin_fd = m_reader.ancillary_fd())
create_fifo_buffer(generate_buffer_name("*stdin-{}*"), *stdin_fd, Buffer::Flags::None);
auto* ui = new RemoteUI{sock, dimensions};
ClientManager::instance().create_client(
std::unique_ptr<UserInterface>(ui), pid, std::move(name),

View File

@ -32,7 +32,7 @@ class RemoteClient
public:
RemoteClient(StringView session, StringView name, std::unique_ptr<UserInterface>&& ui,
int pid, const EnvVarMap& env_vars, StringView init_command,
Optional<BufferCoord> init_coord);
Optional<BufferCoord> init_coord, Optional<int> stdin_fd);
bool is_ui_ok() const;
const Optional<int>& exit_status() const { return m_exit_status; }