rust/chg/src/attachio.rs
author Yuya Nishihara <yuya@tcha.org>
Fri, 10 Apr 2020 21:35:16 +0900
changeset 44688 1f5ab1a9363d
parent 43818 ce088b38f92b
child 44689 6bef9d43cc55
permissions -rw-r--r--
rust-chg: upgrade to 2018 edition and remove useless extern crates Differential Revision: https://phab.mercurial-scm.org/D8399

// 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::{try_ready, Async, Future, Poll};
use std::io;
use std::os::unix::io::AsRawFd;
use tokio_hglib::codec::ChannelMessage;
use tokio_hglib::protocol::MessageLoop;
use tokio_hglib::{Client, Connection};

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",
                    ));
                }
            }
        }
    }
}