3 // This software may be used and distributed according to the terms of the |
3 // This software may be used and distributed according to the terms of the |
4 // GNU General Public License version 2 or any later version. |
4 // GNU General Public License version 2 or any later version. |
5 |
5 |
6 //! Functions to send client-side fds over the command server channel. |
6 //! Functions to send client-side fds over the command server channel. |
7 |
7 |
8 use futures::{try_ready, Async, Future, Poll}; |
|
9 use std::io; |
8 use std::io; |
10 use std::os::unix::io::AsRawFd; |
9 use std::os::unix::io::AsRawFd; |
11 use tokio_hglib::codec::ChannelMessage; |
10 use tokio_hglib::codec::ChannelMessage; |
12 use tokio_hglib::protocol::MessageLoop; |
11 use tokio_hglib::{Connection, Protocol}; |
13 use tokio_hglib::{Client, Connection}; |
|
14 |
12 |
15 use crate::message; |
13 use crate::message; |
16 use crate::procutil; |
14 use crate::procutil; |
17 |
15 |
18 /// Future to send client-side fds over the command server channel. |
16 /// Sends client-side fds over the command server channel. |
19 /// |
17 /// |
20 /// This works as follows: |
18 /// This works as follows: |
21 /// 1. Client sends "attachio" request. |
19 /// 1. Client sends "attachio" request. |
22 /// 2. Server sends back 1-byte input request. |
20 /// 2. Server sends back 1-byte input request. |
23 /// 3. Client sends fds with 1-byte dummy payload in response. |
21 /// 3. Client sends fds with 1-byte dummy payload in response. |
24 /// 4. Server returns the number of the fds received. |
22 /// 4. Server returns the number of the fds received. |
25 /// |
23 /// |
26 /// If the stderr is omitted, it will be redirected to the stdout. This |
24 /// If the stderr is omitted, it will be redirected to the stdout. This |
27 /// allows us to attach the pager stdin to both stdout and stderr, and |
25 /// allows us to attach the pager stdin to both stdout and stderr, and |
28 /// dispose of the client-side handle once attached. |
26 /// dispose of the client-side handle once attached. |
29 #[must_use = "futures do nothing unless polled"] |
27 pub async fn attach_io( |
30 pub struct AttachIo<C, I, O, E> |
28 proto: &mut Protocol<impl Connection + AsRawFd>, |
31 where |
29 stdin: impl AsRawFd, |
32 C: Connection, |
30 stdout: impl AsRawFd, |
33 { |
31 stderr: Option<impl AsRawFd>, |
34 msg_loop: MessageLoop<C>, |
32 ) -> io::Result<()> { |
35 stdin: I, |
33 // TODO: unindent |
36 stdout: O, |
34 { |
37 stderr: Option<E>, |
35 proto.send_command("attachio").await?; |
38 } |
|
39 |
|
40 impl<C, I, O, E> AttachIo<C, I, O, E> |
|
41 where |
|
42 C: Connection + AsRawFd, |
|
43 I: AsRawFd, |
|
44 O: AsRawFd, |
|
45 E: AsRawFd, |
|
46 { |
|
47 pub fn with_client( |
|
48 client: Client<C>, |
|
49 stdin: I, |
|
50 stdout: O, |
|
51 stderr: Option<E>, |
|
52 ) -> AttachIo<C, I, O, E> { |
|
53 let msg_loop = MessageLoop::start(client, b"attachio"); |
|
54 AttachIo { |
|
55 msg_loop, |
|
56 stdin, |
|
57 stdout, |
|
58 stderr, |
|
59 } |
|
60 } |
|
61 } |
|
62 |
|
63 impl<C, I, O, E> Future for AttachIo<C, I, O, E> |
|
64 where |
|
65 C: Connection + AsRawFd, |
|
66 I: AsRawFd, |
|
67 O: AsRawFd, |
|
68 E: AsRawFd, |
|
69 { |
|
70 type Item = Client<C>; |
|
71 type Error = io::Error; |
|
72 |
|
73 fn poll(&mut self) -> Poll<Self::Item, Self::Error> { |
|
74 loop { |
36 loop { |
75 let (client, msg) = try_ready!(self.msg_loop.poll()); |
37 match proto.fetch_response().await? { |
76 match msg { |
|
77 ChannelMessage::Data(b'r', data) => { |
38 ChannelMessage::Data(b'r', data) => { |
78 let fd_cnt = message::parse_result_code(data)?; |
39 let fd_cnt = message::parse_result_code(data)?; |
79 if fd_cnt == 3 { |
40 if fd_cnt == 3 { |
80 return Ok(Async::Ready(client)); |
41 return Ok(()); |
81 } else { |
42 } else { |
82 return Err(io::Error::new( |
43 return Err(io::Error::new( |
83 io::ErrorKind::InvalidData, |
44 io::ErrorKind::InvalidData, |
84 "unexpected attachio result", |
45 "unexpected attachio result", |
85 )); |
46 )); |
86 } |
47 } |
87 } |
48 } |
88 ChannelMessage::Data(..) => { |
49 ChannelMessage::Data(..) => { |
89 // just ignore data sent to uninteresting (optional) channel |
50 // just ignore data sent to uninteresting (optional) channel |
90 self.msg_loop = MessageLoop::resume(client); |
|
91 } |
51 } |
92 ChannelMessage::InputRequest(1) => { |
52 ChannelMessage::InputRequest(1) => { |
93 // this may fail with EWOULDBLOCK in theory, but the |
53 // this may fail with EWOULDBLOCK in theory, but the |
94 // payload is quite small, and the send buffer should |
54 // payload is quite small, and the send buffer should |
95 // be empty so the operation will complete immediately |
55 // be empty so the operation will complete immediately |
96 let sock_fd = client.as_raw_fd(); |
56 let sock_fd = proto.as_raw_fd(); |
97 let ifd = self.stdin.as_raw_fd(); |
57 let ifd = stdin.as_raw_fd(); |
98 let ofd = self.stdout.as_raw_fd(); |
58 let ofd = stdout.as_raw_fd(); |
99 let efd = self.stderr.as_ref().map_or(ofd, |f| f.as_raw_fd()); |
59 let efd = stderr.as_ref().map_or(ofd, |f| f.as_raw_fd()); |
100 procutil::send_raw_fds(sock_fd, &[ifd, ofd, efd])?; |
60 procutil::send_raw_fds(sock_fd, &[ifd, ofd, efd])?; |
101 self.msg_loop = MessageLoop::resume(client); |
|
102 } |
61 } |
103 ChannelMessage::InputRequest(..) |
62 ChannelMessage::InputRequest(..) |
104 | ChannelMessage::LineRequest(..) |
63 | ChannelMessage::LineRequest(..) |
105 | ChannelMessage::SystemRequest(..) => { |
64 | ChannelMessage::SystemRequest(..) => { |
106 return Err(io::Error::new( |
65 return Err(io::Error::new( |