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:
parent
2104af0771
commit
60154300f9
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
14
src/main.cc
14
src/main.cc
|
@ -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())
|
||||
|
|
|
@ -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*()
|
||||
|
|
|
@ -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,14 +429,32 @@ 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());
|
||||
if (res <= 0)
|
||||
throw disconnected{format("socket write failed: {}", strerror(errno))};
|
||||
buffer.erase(buffer.begin(), buffer.begin() + res);
|
||||
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);
|
||||
}
|
||||
return buffer.empty();
|
||||
}
|
||||
|
@ -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),
|
||||
|
|
|
@ -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; }
|
||||
|
|
Loading…
Reference in New Issue
Block a user