diff rust/chg/src/attachio.rs @ 39973:7a0ffdd4af78

rust-chg: add future that handles "attachio" request This is the sequence to send client-side stdio and pager stdin to the server.
author Yuya Nishihara <yuya@tcha.org>
date Mon, 24 Sep 2018 16:59:12 +0900
parents
children ce088b38f92b
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/chg/src/attachio.rs	Mon Sep 24 16:59:12 2018 +0900
@@ -0,0 +1,97 @@
+// Copyright 2018 Yuya Nishihara <yuya@tcha.org>
+//
+// This software may be used and distributed according to the terms of the
+// GNU General Public License version 2 or any later version.
+
+//! Functions to send client-side fds over the command server channel.
+
+use futures::{Async, Future, Poll};
+use std::io;
+use std::os::unix::io::AsRawFd;
+use tokio_hglib::{Client, Connection};
+use tokio_hglib::codec::ChannelMessage;
+use tokio_hglib::protocol::MessageLoop;
+
+use super::message;
+use super::procutil;
+
+/// Future to send client-side fds over the command server channel.
+///
+/// This works as follows:
+/// 1. Client sends "attachio" request.
+/// 2. Server sends back 1-byte input request.
+/// 3. Client sends fds with 1-byte dummy payload in response.
+/// 4. Server returns the number of the fds received.
+///
+/// If the stderr is omitted, it will be redirected to the stdout. This
+/// allows us to attach the pager stdin to both stdout and stderr, and
+/// dispose of the client-side handle once attached.
+#[must_use = "futures do nothing unless polled"]
+pub struct AttachIo<C, I, O, E>
+    where C: Connection,
+{
+    msg_loop: MessageLoop<C>,
+    stdin: I,
+    stdout: O,
+    stderr: Option<E>,
+}
+
+impl<C, I, O, E> AttachIo<C, I, O, E>
+    where C: Connection + AsRawFd,
+          I: AsRawFd,
+          O: AsRawFd,
+          E: AsRawFd,
+{
+    pub fn with_client(client: Client<C>, stdin: I, stdout: O, stderr: Option<E>)
+                       -> AttachIo<C, I, O, E> {
+        let msg_loop = MessageLoop::start(client, b"attachio");
+        AttachIo { msg_loop, stdin, stdout, stderr }
+    }
+}
+
+impl<C, I, O, E> Future for AttachIo<C, I, O, E>
+    where C: Connection + AsRawFd,
+          I: AsRawFd,
+          O: AsRawFd,
+          E: AsRawFd,
+{
+    type Item = Client<C>;
+    type Error = io::Error;
+
+    fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
+        loop {
+            let (client, msg) = try_ready!(self.msg_loop.poll());
+            match msg {
+                ChannelMessage::Data(b'r', data) => {
+                    let fd_cnt = message::parse_result_code(data)?;
+                    if fd_cnt == 3 {
+                        return Ok(Async::Ready(client));
+                    } else {
+                        return Err(io::Error::new(io::ErrorKind::InvalidData,
+                                                  "unexpected attachio result"));
+                    }
+                }
+                ChannelMessage::Data(..) => {
+                    // just ignore data sent to uninteresting (optional) channel
+                    self.msg_loop = MessageLoop::resume(client);
+                }
+                ChannelMessage::InputRequest(1) => {
+                    // this may fail with EWOULDBLOCK in theory, but the
+                    // payload is quite small, and the send buffer should
+                    // be empty so the operation will complete immediately
+                    let sock_fd = client.as_raw_fd();
+                    let ifd = self.stdin.as_raw_fd();
+                    let ofd = self.stdout.as_raw_fd();
+                    let efd = self.stderr.as_ref().map_or(ofd, |f| f.as_raw_fd());
+                    procutil::send_raw_fds(sock_fd, &[ifd, ofd, efd])?;
+                    self.msg_loop = MessageLoop::resume(client);
+                }
+                ChannelMessage::InputRequest(..) | ChannelMessage::LineRequest(..) |
+                ChannelMessage::SystemRequest(..) => {
+                    return Err(io::Error::new(io::ErrorKind::InvalidData,
+                                              "unsupported request while attaching io"));
+                }
+            }
+        }
+    }
+}