##// END OF EJS Templates
rust-chg: upgrade to 2018 edition and remove useless extern crates...
Yuya Nishihara -
r45179:1f5ab1a9 default
parent child Browse files
Show More
@@ -1,19 +1,20 b''
1 [package]
1 [package]
2 name = "chg"
2 name = "chg"
3 version = "0.1.0"
3 version = "0.1.0"
4 authors = ["Yuya Nishihara <yuya@tcha.org>"]
4 authors = ["Yuya Nishihara <yuya@tcha.org>"]
5 description = "Client for Mercurial command server with cHg extension"
5 description = "Client for Mercurial command server with cHg extension"
6 license = "GPL-2.0+"
6 license = "GPL-2.0+"
7 edition = "2018"
7
8
8 [dependencies]
9 [dependencies]
9 bytes = "0.4"
10 bytes = "0.4"
10 futures = "0.1"
11 futures = "0.1"
11 libc = "0.2"
12 libc = "0.2"
12 log = { version = "0.4", features = ["std"] }
13 log = { version = "0.4", features = ["std"] }
13 tokio = "0.1"
14 tokio = "0.1"
14 tokio-hglib = "0.2"
15 tokio-hglib = "0.2"
15 tokio-process = "0.2.3"
16 tokio-process = "0.2.3"
16 tokio-timer = "0.2"
17 tokio-timer = "0.2"
17
18
18 [build-dependencies]
19 [build-dependencies]
19 cc = "1.0"
20 cc = "1.0"
@@ -1,9 +1,7 b''
1 extern crate cc;
2
3 fn main() {
1 fn main() {
4 cc::Build::new()
2 cc::Build::new()
5 .warnings(true)
3 .warnings(true)
6 .file("src/sendfds.c")
4 .file("src/sendfds.c")
7 .file("src/sighandlers.c")
5 .file("src/sighandlers.c")
8 .compile("procutil");
6 .compile("procutil");
9 }
7 }
@@ -1,114 +1,114 b''
1 // Copyright 2018 Yuya Nishihara <yuya@tcha.org>
1 // Copyright 2018 Yuya Nishihara <yuya@tcha.org>
2 //
2 //
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::{Async, Future, Poll};
8 use futures::{try_ready, Async, Future, Poll};
9 use std::io;
9 use std::io;
10 use std::os::unix::io::AsRawFd;
10 use std::os::unix::io::AsRawFd;
11 use tokio_hglib::codec::ChannelMessage;
11 use tokio_hglib::codec::ChannelMessage;
12 use tokio_hglib::protocol::MessageLoop;
12 use tokio_hglib::protocol::MessageLoop;
13 use tokio_hglib::{Client, Connection};
13 use tokio_hglib::{Client, Connection};
14
14
15 use super::message;
15 use super::message;
16 use super::procutil;
16 use super::procutil;
17
17
18 /// Future to send client-side fds over the command server channel.
18 /// Future to send client-side fds over the command server channel.
19 ///
19 ///
20 /// This works as follows:
20 /// This works as follows:
21 /// 1. Client sends "attachio" request.
21 /// 1. Client sends "attachio" request.
22 /// 2. Server sends back 1-byte input request.
22 /// 2. Server sends back 1-byte input request.
23 /// 3. Client sends fds with 1-byte dummy payload in response.
23 /// 3. Client sends fds with 1-byte dummy payload in response.
24 /// 4. Server returns the number of the fds received.
24 /// 4. Server returns the number of the fds received.
25 ///
25 ///
26 /// If the stderr is omitted, it will be redirected to the stdout. This
26 /// 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
27 /// allows us to attach the pager stdin to both stdout and stderr, and
28 /// dispose of the client-side handle once attached.
28 /// dispose of the client-side handle once attached.
29 #[must_use = "futures do nothing unless polled"]
29 #[must_use = "futures do nothing unless polled"]
30 pub struct AttachIo<C, I, O, E>
30 pub struct AttachIo<C, I, O, E>
31 where
31 where
32 C: Connection,
32 C: Connection,
33 {
33 {
34 msg_loop: MessageLoop<C>,
34 msg_loop: MessageLoop<C>,
35 stdin: I,
35 stdin: I,
36 stdout: O,
36 stdout: O,
37 stderr: Option<E>,
37 stderr: Option<E>,
38 }
38 }
39
39
40 impl<C, I, O, E> AttachIo<C, I, O, E>
40 impl<C, I, O, E> AttachIo<C, I, O, E>
41 where
41 where
42 C: Connection + AsRawFd,
42 C: Connection + AsRawFd,
43 I: AsRawFd,
43 I: AsRawFd,
44 O: AsRawFd,
44 O: AsRawFd,
45 E: AsRawFd,
45 E: AsRawFd,
46 {
46 {
47 pub fn with_client(
47 pub fn with_client(
48 client: Client<C>,
48 client: Client<C>,
49 stdin: I,
49 stdin: I,
50 stdout: O,
50 stdout: O,
51 stderr: Option<E>,
51 stderr: Option<E>,
52 ) -> AttachIo<C, I, O, E> {
52 ) -> AttachIo<C, I, O, E> {
53 let msg_loop = MessageLoop::start(client, b"attachio");
53 let msg_loop = MessageLoop::start(client, b"attachio");
54 AttachIo {
54 AttachIo {
55 msg_loop,
55 msg_loop,
56 stdin,
56 stdin,
57 stdout,
57 stdout,
58 stderr,
58 stderr,
59 }
59 }
60 }
60 }
61 }
61 }
62
62
63 impl<C, I, O, E> Future for AttachIo<C, I, O, E>
63 impl<C, I, O, E> Future for AttachIo<C, I, O, E>
64 where
64 where
65 C: Connection + AsRawFd,
65 C: Connection + AsRawFd,
66 I: AsRawFd,
66 I: AsRawFd,
67 O: AsRawFd,
67 O: AsRawFd,
68 E: AsRawFd,
68 E: AsRawFd,
69 {
69 {
70 type Item = Client<C>;
70 type Item = Client<C>;
71 type Error = io::Error;
71 type Error = io::Error;
72
72
73 fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
73 fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
74 loop {
74 loop {
75 let (client, msg) = try_ready!(self.msg_loop.poll());
75 let (client, msg) = try_ready!(self.msg_loop.poll());
76 match msg {
76 match msg {
77 ChannelMessage::Data(b'r', data) => {
77 ChannelMessage::Data(b'r', data) => {
78 let fd_cnt = message::parse_result_code(data)?;
78 let fd_cnt = message::parse_result_code(data)?;
79 if fd_cnt == 3 {
79 if fd_cnt == 3 {
80 return Ok(Async::Ready(client));
80 return Ok(Async::Ready(client));
81 } else {
81 } else {
82 return Err(io::Error::new(
82 return Err(io::Error::new(
83 io::ErrorKind::InvalidData,
83 io::ErrorKind::InvalidData,
84 "unexpected attachio result",
84 "unexpected attachio result",
85 ));
85 ));
86 }
86 }
87 }
87 }
88 ChannelMessage::Data(..) => {
88 ChannelMessage::Data(..) => {
89 // just ignore data sent to uninteresting (optional) channel
89 // just ignore data sent to uninteresting (optional) channel
90 self.msg_loop = MessageLoop::resume(client);
90 self.msg_loop = MessageLoop::resume(client);
91 }
91 }
92 ChannelMessage::InputRequest(1) => {
92 ChannelMessage::InputRequest(1) => {
93 // this may fail with EWOULDBLOCK in theory, but the
93 // this may fail with EWOULDBLOCK in theory, but the
94 // payload is quite small, and the send buffer should
94 // payload is quite small, and the send buffer should
95 // be empty so the operation will complete immediately
95 // be empty so the operation will complete immediately
96 let sock_fd = client.as_raw_fd();
96 let sock_fd = client.as_raw_fd();
97 let ifd = self.stdin.as_raw_fd();
97 let ifd = self.stdin.as_raw_fd();
98 let ofd = self.stdout.as_raw_fd();
98 let ofd = self.stdout.as_raw_fd();
99 let efd = self.stderr.as_ref().map_or(ofd, |f| f.as_raw_fd());
99 let efd = self.stderr.as_ref().map_or(ofd, |f| f.as_raw_fd());
100 procutil::send_raw_fds(sock_fd, &[ifd, ofd, efd])?;
100 procutil::send_raw_fds(sock_fd, &[ifd, ofd, efd])?;
101 self.msg_loop = MessageLoop::resume(client);
101 self.msg_loop = MessageLoop::resume(client);
102 }
102 }
103 ChannelMessage::InputRequest(..)
103 ChannelMessage::InputRequest(..)
104 | ChannelMessage::LineRequest(..)
104 | ChannelMessage::LineRequest(..)
105 | ChannelMessage::SystemRequest(..) => {
105 | ChannelMessage::SystemRequest(..) => {
106 return Err(io::Error::new(
106 return Err(io::Error::new(
107 io::ErrorKind::InvalidData,
107 io::ErrorKind::InvalidData,
108 "unsupported request while attaching io",
108 "unsupported request while attaching io",
109 ));
109 ));
110 }
110 }
111 }
111 }
112 }
112 }
113 }
113 }
114 }
114 }
@@ -1,26 +1,15 b''
1 // Copyright 2018 Yuya Nishihara <yuya@tcha.org>
1 // Copyright 2018 Yuya Nishihara <yuya@tcha.org>
2 //
2 //
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 extern crate bytes;
7 #[macro_use]
8 extern crate futures;
9 extern crate libc;
10 #[macro_use]
11 extern crate log;
12 extern crate tokio;
13 extern crate tokio_hglib;
14 extern crate tokio_process;
15 extern crate tokio_timer;
16
17 mod attachio;
6 mod attachio;
18 mod clientext;
7 mod clientext;
19 pub mod locator;
8 pub mod locator;
20 pub mod message;
9 pub mod message;
21 pub mod procutil;
10 pub mod procutil;
22 mod runcommand;
11 mod runcommand;
23 mod uihandler;
12 mod uihandler;
24
13
25 pub use clientext::ChgClientExt;
14 pub use clientext::ChgClientExt;
26 pub use uihandler::{ChgUiHandler, SystemHandler};
15 pub use uihandler::{ChgUiHandler, SystemHandler};
@@ -1,500 +1,501 b''
1 // Copyright 2011, 2018 Yuya Nishihara <yuya@tcha.org>
1 // Copyright 2011, 2018 Yuya Nishihara <yuya@tcha.org>
2 //
2 //
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 //! Utility for locating command-server process.
6 //! Utility for locating command-server process.
7
7
8 use futures::future::{self, Either, Loop};
8 use futures::future::{self, Either, Loop};
9 use log::debug;
9 use std::env;
10 use std::env;
10 use std::ffi::{OsStr, OsString};
11 use std::ffi::{OsStr, OsString};
11 use std::fs::{self, DirBuilder};
12 use std::fs::{self, DirBuilder};
12 use std::io;
13 use std::io;
13 use std::os::unix::ffi::{OsStrExt, OsStringExt};
14 use std::os::unix::ffi::{OsStrExt, OsStringExt};
14 use std::os::unix::fs::{DirBuilderExt, MetadataExt};
15 use std::os::unix::fs::{DirBuilderExt, MetadataExt};
15 use std::path::{Path, PathBuf};
16 use std::path::{Path, PathBuf};
16 use std::process::{self, Command};
17 use std::process::{self, Command};
17 use std::time::Duration;
18 use std::time::Duration;
18 use tokio::prelude::*;
19 use tokio::prelude::*;
19 use tokio_hglib::UnixClient;
20 use tokio_hglib::UnixClient;
20 use tokio_process::{Child, CommandExt};
21 use tokio_process::{Child, CommandExt};
21 use tokio_timer;
22 use tokio_timer;
22
23
23 use super::clientext::ChgClientExt;
24 use super::clientext::ChgClientExt;
24 use super::message::{Instruction, ServerSpec};
25 use super::message::{Instruction, ServerSpec};
25 use super::procutil;
26 use super::procutil;
26
27
27 const REQUIRED_SERVER_CAPABILITIES: &[&str] = &[
28 const REQUIRED_SERVER_CAPABILITIES: &[&str] = &[
28 "attachio",
29 "attachio",
29 "chdir",
30 "chdir",
30 "runcommand",
31 "runcommand",
31 "setenv",
32 "setenv",
32 "setumask2",
33 "setumask2",
33 "validate",
34 "validate",
34 ];
35 ];
35
36
36 /// Helper to connect to and spawn a server process.
37 /// Helper to connect to and spawn a server process.
37 #[derive(Clone, Debug)]
38 #[derive(Clone, Debug)]
38 pub struct Locator {
39 pub struct Locator {
39 hg_command: OsString,
40 hg_command: OsString,
40 hg_early_args: Vec<OsString>,
41 hg_early_args: Vec<OsString>,
41 current_dir: PathBuf,
42 current_dir: PathBuf,
42 env_vars: Vec<(OsString, OsString)>,
43 env_vars: Vec<(OsString, OsString)>,
43 process_id: u32,
44 process_id: u32,
44 base_sock_path: PathBuf,
45 base_sock_path: PathBuf,
45 redirect_sock_path: Option<PathBuf>,
46 redirect_sock_path: Option<PathBuf>,
46 timeout: Duration,
47 timeout: Duration,
47 }
48 }
48
49
49 impl Locator {
50 impl Locator {
50 /// Creates locator capturing the current process environment.
51 /// Creates locator capturing the current process environment.
51 ///
52 ///
52 /// If no `$CHGSOCKNAME` is specified, the socket directory will be
53 /// If no `$CHGSOCKNAME` is specified, the socket directory will be
53 /// created as necessary.
54 /// created as necessary.
54 pub fn prepare_from_env() -> io::Result<Locator> {
55 pub fn prepare_from_env() -> io::Result<Locator> {
55 Ok(Locator {
56 Ok(Locator {
56 hg_command: default_hg_command(),
57 hg_command: default_hg_command(),
57 hg_early_args: Vec::new(),
58 hg_early_args: Vec::new(),
58 current_dir: env::current_dir()?,
59 current_dir: env::current_dir()?,
59 env_vars: env::vars_os().collect(),
60 env_vars: env::vars_os().collect(),
60 process_id: process::id(),
61 process_id: process::id(),
61 base_sock_path: prepare_server_socket_path()?,
62 base_sock_path: prepare_server_socket_path()?,
62 redirect_sock_path: None,
63 redirect_sock_path: None,
63 timeout: default_timeout(),
64 timeout: default_timeout(),
64 })
65 })
65 }
66 }
66
67
67 /// Temporary socket path for this client process.
68 /// Temporary socket path for this client process.
68 fn temp_sock_path(&self) -> PathBuf {
69 fn temp_sock_path(&self) -> PathBuf {
69 let src = self.base_sock_path.as_os_str().as_bytes();
70 let src = self.base_sock_path.as_os_str().as_bytes();
70 let mut buf = Vec::with_capacity(src.len() + 6); // "{src}.{pid}".len()
71 let mut buf = Vec::with_capacity(src.len() + 6); // "{src}.{pid}".len()
71 buf.extend_from_slice(src);
72 buf.extend_from_slice(src);
72 buf.extend_from_slice(format!(".{}", self.process_id).as_bytes());
73 buf.extend_from_slice(format!(".{}", self.process_id).as_bytes());
73 OsString::from_vec(buf).into()
74 OsString::from_vec(buf).into()
74 }
75 }
75
76
76 /// Specifies the arguments to be passed to the server at start.
77 /// Specifies the arguments to be passed to the server at start.
77 pub fn set_early_args<I, P>(&mut self, args: I)
78 pub fn set_early_args<I, P>(&mut self, args: I)
78 where
79 where
79 I: IntoIterator<Item = P>,
80 I: IntoIterator<Item = P>,
80 P: AsRef<OsStr>,
81 P: AsRef<OsStr>,
81 {
82 {
82 self.hg_early_args = args.into_iter().map(|a| a.as_ref().to_owned()).collect();
83 self.hg_early_args = args.into_iter().map(|a| a.as_ref().to_owned()).collect();
83 }
84 }
84
85
85 /// Connects to the server.
86 /// Connects to the server.
86 ///
87 ///
87 /// The server process will be spawned if not running.
88 /// The server process will be spawned if not running.
88 pub fn connect(self) -> impl Future<Item = (Self, UnixClient), Error = io::Error> {
89 pub fn connect(self) -> impl Future<Item = (Self, UnixClient), Error = io::Error> {
89 future::loop_fn((self, 0), |(loc, cnt)| {
90 future::loop_fn((self, 0), |(loc, cnt)| {
90 if cnt < 10 {
91 if cnt < 10 {
91 let fut = loc
92 let fut = loc
92 .try_connect()
93 .try_connect()
93 .and_then(|(loc, client)| {
94 .and_then(|(loc, client)| {
94 client
95 client
95 .validate(&loc.hg_early_args)
96 .validate(&loc.hg_early_args)
96 .map(|(client, instructions)| (loc, client, instructions))
97 .map(|(client, instructions)| (loc, client, instructions))
97 })
98 })
98 .and_then(move |(loc, client, instructions)| {
99 .and_then(move |(loc, client, instructions)| {
99 loc.run_instructions(client, instructions, cnt)
100 loc.run_instructions(client, instructions, cnt)
100 });
101 });
101 Either::A(fut)
102 Either::A(fut)
102 } else {
103 } else {
103 let msg = format!(
104 let msg = format!(
104 concat!(
105 concat!(
105 "too many redirections.\n",
106 "too many redirections.\n",
106 "Please make sure {:?} is not a wrapper which ",
107 "Please make sure {:?} is not a wrapper which ",
107 "changes sensitive environment variables ",
108 "changes sensitive environment variables ",
108 "before executing hg. If you have to use a ",
109 "before executing hg. If you have to use a ",
109 "wrapper, wrap chg instead of hg.",
110 "wrapper, wrap chg instead of hg.",
110 ),
111 ),
111 loc.hg_command
112 loc.hg_command
112 );
113 );
113 Either::B(future::err(io::Error::new(io::ErrorKind::Other, msg)))
114 Either::B(future::err(io::Error::new(io::ErrorKind::Other, msg)))
114 }
115 }
115 })
116 })
116 }
117 }
117
118
118 /// Runs instructions received from the server.
119 /// Runs instructions received from the server.
119 fn run_instructions(
120 fn run_instructions(
120 mut self,
121 mut self,
121 client: UnixClient,
122 client: UnixClient,
122 instructions: Vec<Instruction>,
123 instructions: Vec<Instruction>,
123 cnt: usize,
124 cnt: usize,
124 ) -> io::Result<Loop<(Self, UnixClient), (Self, usize)>> {
125 ) -> io::Result<Loop<(Self, UnixClient), (Self, usize)>> {
125 let mut reconnect = false;
126 let mut reconnect = false;
126 for inst in instructions {
127 for inst in instructions {
127 debug!("instruction: {:?}", inst);
128 debug!("instruction: {:?}", inst);
128 match inst {
129 match inst {
129 Instruction::Exit(_) => {
130 Instruction::Exit(_) => {
130 // Just returns the current connection to run the
131 // Just returns the current connection to run the
131 // unparsable command and report the error
132 // unparsable command and report the error
132 return Ok(Loop::Break((self, client)));
133 return Ok(Loop::Break((self, client)));
133 }
134 }
134 Instruction::Reconnect => {
135 Instruction::Reconnect => {
135 reconnect = true;
136 reconnect = true;
136 }
137 }
137 Instruction::Redirect(path) => {
138 Instruction::Redirect(path) => {
138 if path.parent() != self.base_sock_path.parent() {
139 if path.parent() != self.base_sock_path.parent() {
139 let msg = format!(
140 let msg = format!(
140 "insecure redirect instruction from server: {}",
141 "insecure redirect instruction from server: {}",
141 path.display()
142 path.display()
142 );
143 );
143 return Err(io::Error::new(io::ErrorKind::InvalidData, msg));
144 return Err(io::Error::new(io::ErrorKind::InvalidData, msg));
144 }
145 }
145 self.redirect_sock_path = Some(path);
146 self.redirect_sock_path = Some(path);
146 reconnect = true;
147 reconnect = true;
147 }
148 }
148 Instruction::Unlink(path) => {
149 Instruction::Unlink(path) => {
149 if path.parent() != self.base_sock_path.parent() {
150 if path.parent() != self.base_sock_path.parent() {
150 let msg = format!(
151 let msg = format!(
151 "insecure unlink instruction from server: {}",
152 "insecure unlink instruction from server: {}",
152 path.display()
153 path.display()
153 );
154 );
154 return Err(io::Error::new(io::ErrorKind::InvalidData, msg));
155 return Err(io::Error::new(io::ErrorKind::InvalidData, msg));
155 }
156 }
156 fs::remove_file(path).unwrap_or(()); // may race
157 fs::remove_file(path).unwrap_or(()); // may race
157 }
158 }
158 }
159 }
159 }
160 }
160
161
161 if reconnect {
162 if reconnect {
162 Ok(Loop::Continue((self, cnt + 1)))
163 Ok(Loop::Continue((self, cnt + 1)))
163 } else {
164 } else {
164 Ok(Loop::Break((self, client)))
165 Ok(Loop::Break((self, client)))
165 }
166 }
166 }
167 }
167
168
168 /// Tries to connect to the existing server, or spawns new if not running.
169 /// Tries to connect to the existing server, or spawns new if not running.
169 fn try_connect(self) -> impl Future<Item = (Self, UnixClient), Error = io::Error> {
170 fn try_connect(self) -> impl Future<Item = (Self, UnixClient), Error = io::Error> {
170 let sock_path = self
171 let sock_path = self
171 .redirect_sock_path
172 .redirect_sock_path
172 .as_ref()
173 .as_ref()
173 .unwrap_or(&self.base_sock_path)
174 .unwrap_or(&self.base_sock_path)
174 .clone();
175 .clone();
175 debug!("try connect to {}", sock_path.display());
176 debug!("try connect to {}", sock_path.display());
176 UnixClient::connect(sock_path)
177 UnixClient::connect(sock_path)
177 .then(|res| {
178 .then(|res| {
178 match res {
179 match res {
179 Ok(client) => Either::A(future::ok((self, client))),
180 Ok(client) => Either::A(future::ok((self, client))),
180 Err(_) => {
181 Err(_) => {
181 // Prevent us from being re-connected to the outdated
182 // Prevent us from being re-connected to the outdated
182 // master server: We were told by the server to redirect
183 // master server: We were told by the server to redirect
183 // to redirect_sock_path, which didn't work. We do not
184 // to redirect_sock_path, which didn't work. We do not
184 // want to connect to the same master server again
185 // want to connect to the same master server again
185 // because it would probably tell us the same thing.
186 // because it would probably tell us the same thing.
186 if self.redirect_sock_path.is_some() {
187 if self.redirect_sock_path.is_some() {
187 fs::remove_file(&self.base_sock_path).unwrap_or(());
188 fs::remove_file(&self.base_sock_path).unwrap_or(());
188 // may race
189 // may race
189 }
190 }
190 Either::B(self.spawn_connect())
191 Either::B(self.spawn_connect())
191 }
192 }
192 }
193 }
193 })
194 })
194 .and_then(|(loc, client)| {
195 .and_then(|(loc, client)| {
195 check_server_capabilities(client.server_spec())?;
196 check_server_capabilities(client.server_spec())?;
196 Ok((loc, client))
197 Ok((loc, client))
197 })
198 })
198 .and_then(|(loc, client)| {
199 .and_then(|(loc, client)| {
199 // It's purely optional, and the server might not support this command.
200 // It's purely optional, and the server might not support this command.
200 if client.server_spec().capabilities.contains("setprocname") {
201 if client.server_spec().capabilities.contains("setprocname") {
201 let fut = client
202 let fut = client
202 .set_process_name(format!("chg[worker/{}]", loc.process_id))
203 .set_process_name(format!("chg[worker/{}]", loc.process_id))
203 .map(|client| (loc, client));
204 .map(|client| (loc, client));
204 Either::A(fut)
205 Either::A(fut)
205 } else {
206 } else {
206 Either::B(future::ok((loc, client)))
207 Either::B(future::ok((loc, client)))
207 }
208 }
208 })
209 })
209 .and_then(|(loc, client)| {
210 .and_then(|(loc, client)| {
210 client
211 client
211 .set_current_dir(&loc.current_dir)
212 .set_current_dir(&loc.current_dir)
212 .map(|client| (loc, client))
213 .map(|client| (loc, client))
213 })
214 })
214 .and_then(|(loc, client)| {
215 .and_then(|(loc, client)| {
215 client
216 client
216 .set_env_vars_os(loc.env_vars.iter().cloned())
217 .set_env_vars_os(loc.env_vars.iter().cloned())
217 .map(|client| (loc, client))
218 .map(|client| (loc, client))
218 })
219 })
219 }
220 }
220
221
221 /// Spawns new server process and connects to it.
222 /// Spawns new server process and connects to it.
222 ///
223 ///
223 /// The server will be spawned at the current working directory, then
224 /// The server will be spawned at the current working directory, then
224 /// chdir to "/", so that the server will load configs from the target
225 /// chdir to "/", so that the server will load configs from the target
225 /// repository.
226 /// repository.
226 fn spawn_connect(self) -> impl Future<Item = (Self, UnixClient), Error = io::Error> {
227 fn spawn_connect(self) -> impl Future<Item = (Self, UnixClient), Error = io::Error> {
227 let sock_path = self.temp_sock_path();
228 let sock_path = self.temp_sock_path();
228 debug!("start cmdserver at {}", sock_path.display());
229 debug!("start cmdserver at {}", sock_path.display());
229 Command::new(&self.hg_command)
230 Command::new(&self.hg_command)
230 .arg("serve")
231 .arg("serve")
231 .arg("--cmdserver")
232 .arg("--cmdserver")
232 .arg("chgunix")
233 .arg("chgunix")
233 .arg("--address")
234 .arg("--address")
234 .arg(&sock_path)
235 .arg(&sock_path)
235 .arg("--daemon-postexec")
236 .arg("--daemon-postexec")
236 .arg("chdir:/")
237 .arg("chdir:/")
237 .args(&self.hg_early_args)
238 .args(&self.hg_early_args)
238 .current_dir(&self.current_dir)
239 .current_dir(&self.current_dir)
239 .env_clear()
240 .env_clear()
240 .envs(self.env_vars.iter().cloned())
241 .envs(self.env_vars.iter().cloned())
241 .env("CHGINTERNALMARK", "")
242 .env("CHGINTERNALMARK", "")
242 .spawn_async()
243 .spawn_async()
243 .into_future()
244 .into_future()
244 .and_then(|server| self.connect_spawned(server, sock_path))
245 .and_then(|server| self.connect_spawned(server, sock_path))
245 .and_then(|(loc, client, sock_path)| {
246 .and_then(|(loc, client, sock_path)| {
246 debug!(
247 debug!(
247 "rename {} to {}",
248 "rename {} to {}",
248 sock_path.display(),
249 sock_path.display(),
249 loc.base_sock_path.display()
250 loc.base_sock_path.display()
250 );
251 );
251 fs::rename(&sock_path, &loc.base_sock_path)?;
252 fs::rename(&sock_path, &loc.base_sock_path)?;
252 Ok((loc, client))
253 Ok((loc, client))
253 })
254 })
254 }
255 }
255
256
256 /// Tries to connect to the just spawned server repeatedly until timeout
257 /// Tries to connect to the just spawned server repeatedly until timeout
257 /// exceeded.
258 /// exceeded.
258 fn connect_spawned(
259 fn connect_spawned(
259 self,
260 self,
260 server: Child,
261 server: Child,
261 sock_path: PathBuf,
262 sock_path: PathBuf,
262 ) -> impl Future<Item = (Self, UnixClient, PathBuf), Error = io::Error> {
263 ) -> impl Future<Item = (Self, UnixClient, PathBuf), Error = io::Error> {
263 debug!("try connect to {} repeatedly", sock_path.display());
264 debug!("try connect to {} repeatedly", sock_path.display());
264 let connect = future::loop_fn(sock_path, |sock_path| {
265 let connect = future::loop_fn(sock_path, |sock_path| {
265 UnixClient::connect(sock_path.clone()).then(|res| {
266 UnixClient::connect(sock_path.clone()).then(|res| {
266 match res {
267 match res {
267 Ok(client) => Either::A(future::ok(Loop::Break((client, sock_path)))),
268 Ok(client) => Either::A(future::ok(Loop::Break((client, sock_path)))),
268 Err(_) => {
269 Err(_) => {
269 // try again with slight delay
270 // try again with slight delay
270 let fut = tokio_timer::sleep(Duration::from_millis(10))
271 let fut = tokio_timer::sleep(Duration::from_millis(10))
271 .map(|()| Loop::Continue(sock_path))
272 .map(|()| Loop::Continue(sock_path))
272 .map_err(|err| io::Error::new(io::ErrorKind::Other, err));
273 .map_err(|err| io::Error::new(io::ErrorKind::Other, err));
273 Either::B(fut)
274 Either::B(fut)
274 }
275 }
275 }
276 }
276 })
277 })
277 });
278 });
278
279
279 // waits for either connection established or server failed to start
280 // waits for either connection established or server failed to start
280 connect
281 connect
281 .select2(server)
282 .select2(server)
282 .map_err(|res| res.split().0)
283 .map_err(|res| res.split().0)
283 .timeout(self.timeout)
284 .timeout(self.timeout)
284 .map_err(|err| {
285 .map_err(|err| {
285 err.into_inner().unwrap_or_else(|| {
286 err.into_inner().unwrap_or_else(|| {
286 io::Error::new(
287 io::Error::new(
287 io::ErrorKind::TimedOut,
288 io::ErrorKind::TimedOut,
288 "timed out while connecting to server",
289 "timed out while connecting to server",
289 )
290 )
290 })
291 })
291 })
292 })
292 .and_then(|res| {
293 .and_then(|res| {
293 match res {
294 match res {
294 Either::A(((client, sock_path), server)) => {
295 Either::A(((client, sock_path), server)) => {
295 server.forget(); // continue to run in background
296 server.forget(); // continue to run in background
296 Ok((self, client, sock_path))
297 Ok((self, client, sock_path))
297 }
298 }
298 Either::B((st, _)) => Err(io::Error::new(
299 Either::B((st, _)) => Err(io::Error::new(
299 io::ErrorKind::Other,
300 io::ErrorKind::Other,
300 format!("server exited too early: {}", st),
301 format!("server exited too early: {}", st),
301 )),
302 )),
302 }
303 }
303 })
304 })
304 }
305 }
305 }
306 }
306
307
307 /// Determines the server socket to connect to.
308 /// Determines the server socket to connect to.
308 ///
309 ///
309 /// If no `$CHGSOCKNAME` is specified, the socket directory will be created
310 /// If no `$CHGSOCKNAME` is specified, the socket directory will be created
310 /// as necessary.
311 /// as necessary.
311 fn prepare_server_socket_path() -> io::Result<PathBuf> {
312 fn prepare_server_socket_path() -> io::Result<PathBuf> {
312 if let Some(s) = env::var_os("CHGSOCKNAME") {
313 if let Some(s) = env::var_os("CHGSOCKNAME") {
313 Ok(PathBuf::from(s))
314 Ok(PathBuf::from(s))
314 } else {
315 } else {
315 let mut path = default_server_socket_dir();
316 let mut path = default_server_socket_dir();
316 create_secure_dir(&path)?;
317 create_secure_dir(&path)?;
317 path.push("server");
318 path.push("server");
318 Ok(path)
319 Ok(path)
319 }
320 }
320 }
321 }
321
322
322 /// Determines the default server socket path as follows.
323 /// Determines the default server socket path as follows.
323 ///
324 ///
324 /// 1. `$XDG_RUNTIME_DIR/chg`
325 /// 1. `$XDG_RUNTIME_DIR/chg`
325 /// 2. `$TMPDIR/chg$UID`
326 /// 2. `$TMPDIR/chg$UID`
326 /// 3. `/tmp/chg$UID`
327 /// 3. `/tmp/chg$UID`
327 pub fn default_server_socket_dir() -> PathBuf {
328 pub fn default_server_socket_dir() -> PathBuf {
328 // XDG_RUNTIME_DIR should be ignored if it has an insufficient permission.
329 // XDG_RUNTIME_DIR should be ignored if it has an insufficient permission.
329 // https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
330 // https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
330 if let Some(Ok(s)) = env::var_os("XDG_RUNTIME_DIR").map(check_secure_dir) {
331 if let Some(Ok(s)) = env::var_os("XDG_RUNTIME_DIR").map(check_secure_dir) {
331 let mut path = PathBuf::from(s);
332 let mut path = PathBuf::from(s);
332 path.push("chg");
333 path.push("chg");
333 path
334 path
334 } else {
335 } else {
335 let mut path = env::temp_dir();
336 let mut path = env::temp_dir();
336 path.push(format!("chg{}", procutil::get_effective_uid()));
337 path.push(format!("chg{}", procutil::get_effective_uid()));
337 path
338 path
338 }
339 }
339 }
340 }
340
341
341 /// Determines the default hg command.
342 /// Determines the default hg command.
342 pub fn default_hg_command() -> OsString {
343 pub fn default_hg_command() -> OsString {
343 // TODO: maybe allow embedding the path at compile time (or load from hgrc)
344 // TODO: maybe allow embedding the path at compile time (or load from hgrc)
344 env::var_os("CHGHG")
345 env::var_os("CHGHG")
345 .or(env::var_os("HG"))
346 .or(env::var_os("HG"))
346 .unwrap_or(OsStr::new("hg").to_owned())
347 .unwrap_or(OsStr::new("hg").to_owned())
347 }
348 }
348
349
349 fn default_timeout() -> Duration {
350 fn default_timeout() -> Duration {
350 let secs = env::var("CHGTIMEOUT")
351 let secs = env::var("CHGTIMEOUT")
351 .ok()
352 .ok()
352 .and_then(|s| s.parse().ok())
353 .and_then(|s| s.parse().ok())
353 .unwrap_or(60);
354 .unwrap_or(60);
354 Duration::from_secs(secs)
355 Duration::from_secs(secs)
355 }
356 }
356
357
357 /// Creates a directory which the other users cannot access to.
358 /// Creates a directory which the other users cannot access to.
358 ///
359 ///
359 /// If the directory already exists, tests its permission.
360 /// If the directory already exists, tests its permission.
360 fn create_secure_dir<P>(path: P) -> io::Result<()>
361 fn create_secure_dir<P>(path: P) -> io::Result<()>
361 where
362 where
362 P: AsRef<Path>,
363 P: AsRef<Path>,
363 {
364 {
364 DirBuilder::new()
365 DirBuilder::new()
365 .mode(0o700)
366 .mode(0o700)
366 .create(path.as_ref())
367 .create(path.as_ref())
367 .or_else(|err| {
368 .or_else(|err| {
368 if err.kind() == io::ErrorKind::AlreadyExists {
369 if err.kind() == io::ErrorKind::AlreadyExists {
369 check_secure_dir(path).map(|_| ())
370 check_secure_dir(path).map(|_| ())
370 } else {
371 } else {
371 Err(err)
372 Err(err)
372 }
373 }
373 })
374 })
374 }
375 }
375
376
376 fn check_secure_dir<P>(path: P) -> io::Result<P>
377 fn check_secure_dir<P>(path: P) -> io::Result<P>
377 where
378 where
378 P: AsRef<Path>,
379 P: AsRef<Path>,
379 {
380 {
380 let a = fs::symlink_metadata(path.as_ref())?;
381 let a = fs::symlink_metadata(path.as_ref())?;
381 if a.is_dir() && a.uid() == procutil::get_effective_uid() && (a.mode() & 0o777) == 0o700 {
382 if a.is_dir() && a.uid() == procutil::get_effective_uid() && (a.mode() & 0o777) == 0o700 {
382 Ok(path)
383 Ok(path)
383 } else {
384 } else {
384 Err(io::Error::new(io::ErrorKind::Other, "insecure directory"))
385 Err(io::Error::new(io::ErrorKind::Other, "insecure directory"))
385 }
386 }
386 }
387 }
387
388
388 fn check_server_capabilities(spec: &ServerSpec) -> io::Result<()> {
389 fn check_server_capabilities(spec: &ServerSpec) -> io::Result<()> {
389 let unsupported: Vec<_> = REQUIRED_SERVER_CAPABILITIES
390 let unsupported: Vec<_> = REQUIRED_SERVER_CAPABILITIES
390 .iter()
391 .iter()
391 .cloned()
392 .cloned()
392 .filter(|&s| !spec.capabilities.contains(s))
393 .filter(|&s| !spec.capabilities.contains(s))
393 .collect();
394 .collect();
394 if unsupported.is_empty() {
395 if unsupported.is_empty() {
395 Ok(())
396 Ok(())
396 } else {
397 } else {
397 let msg = format!(
398 let msg = format!(
398 "insufficient server capabilities: {}",
399 "insufficient server capabilities: {}",
399 unsupported.join(", ")
400 unsupported.join(", ")
400 );
401 );
401 Err(io::Error::new(io::ErrorKind::Other, msg))
402 Err(io::Error::new(io::ErrorKind::Other, msg))
402 }
403 }
403 }
404 }
404
405
405 /// Collects arguments which need to be passed to the server at start.
406 /// Collects arguments which need to be passed to the server at start.
406 pub fn collect_early_args<I, P>(args: I) -> Vec<OsString>
407 pub fn collect_early_args<I, P>(args: I) -> Vec<OsString>
407 where
408 where
408 I: IntoIterator<Item = P>,
409 I: IntoIterator<Item = P>,
409 P: AsRef<OsStr>,
410 P: AsRef<OsStr>,
410 {
411 {
411 let mut args_iter = args.into_iter();
412 let mut args_iter = args.into_iter();
412 let mut early_args = Vec::new();
413 let mut early_args = Vec::new();
413 while let Some(arg) = args_iter.next() {
414 while let Some(arg) = args_iter.next() {
414 let argb = arg.as_ref().as_bytes();
415 let argb = arg.as_ref().as_bytes();
415 if argb == b"--" {
416 if argb == b"--" {
416 break;
417 break;
417 } else if argb.starts_with(b"--") {
418 } else if argb.starts_with(b"--") {
418 let mut split = argb[2..].splitn(2, |&c| c == b'=');
419 let mut split = argb[2..].splitn(2, |&c| c == b'=');
419 match split.next().unwrap() {
420 match split.next().unwrap() {
420 b"traceback" => {
421 b"traceback" => {
421 if split.next().is_none() {
422 if split.next().is_none() {
422 early_args.push(arg.as_ref().to_owned());
423 early_args.push(arg.as_ref().to_owned());
423 }
424 }
424 }
425 }
425 b"config" | b"cwd" | b"repo" | b"repository" => {
426 b"config" | b"cwd" | b"repo" | b"repository" => {
426 if split.next().is_some() {
427 if split.next().is_some() {
427 // --<flag>=<val>
428 // --<flag>=<val>
428 early_args.push(arg.as_ref().to_owned());
429 early_args.push(arg.as_ref().to_owned());
429 } else {
430 } else {
430 // --<flag> <val>
431 // --<flag> <val>
431 args_iter.next().map(|val| {
432 args_iter.next().map(|val| {
432 early_args.push(arg.as_ref().to_owned());
433 early_args.push(arg.as_ref().to_owned());
433 early_args.push(val.as_ref().to_owned());
434 early_args.push(val.as_ref().to_owned());
434 });
435 });
435 }
436 }
436 }
437 }
437 _ => {}
438 _ => {}
438 }
439 }
439 } else if argb.starts_with(b"-R") {
440 } else if argb.starts_with(b"-R") {
440 if argb.len() > 2 {
441 if argb.len() > 2 {
441 // -R<val>
442 // -R<val>
442 early_args.push(arg.as_ref().to_owned());
443 early_args.push(arg.as_ref().to_owned());
443 } else {
444 } else {
444 // -R <val>
445 // -R <val>
445 args_iter.next().map(|val| {
446 args_iter.next().map(|val| {
446 early_args.push(arg.as_ref().to_owned());
447 early_args.push(arg.as_ref().to_owned());
447 early_args.push(val.as_ref().to_owned());
448 early_args.push(val.as_ref().to_owned());
448 });
449 });
449 }
450 }
450 }
451 }
451 }
452 }
452
453
453 early_args
454 early_args
454 }
455 }
455
456
456 #[cfg(test)]
457 #[cfg(test)]
457 mod tests {
458 mod tests {
458 use super::*;
459 use super::*;
459
460
460 #[test]
461 #[test]
461 fn collect_early_args_some() {
462 fn collect_early_args_some() {
462 assert!(collect_early_args(&[] as &[&OsStr]).is_empty());
463 assert!(collect_early_args(&[] as &[&OsStr]).is_empty());
463 assert!(collect_early_args(&["log"]).is_empty());
464 assert!(collect_early_args(&["log"]).is_empty());
464 assert_eq!(
465 assert_eq!(
465 collect_early_args(&["log", "-Ra", "foo"]),
466 collect_early_args(&["log", "-Ra", "foo"]),
466 os_string_vec_from(&[b"-Ra"])
467 os_string_vec_from(&[b"-Ra"])
467 );
468 );
468 assert_eq!(
469 assert_eq!(
469 collect_early_args(&["log", "-R", "repo", "", "--traceback", "a"]),
470 collect_early_args(&["log", "-R", "repo", "", "--traceback", "a"]),
470 os_string_vec_from(&[b"-R", b"repo", b"--traceback"])
471 os_string_vec_from(&[b"-R", b"repo", b"--traceback"])
471 );
472 );
472 assert_eq!(
473 assert_eq!(
473 collect_early_args(&["log", "--config", "diff.git=1", "-q"]),
474 collect_early_args(&["log", "--config", "diff.git=1", "-q"]),
474 os_string_vec_from(&[b"--config", b"diff.git=1"])
475 os_string_vec_from(&[b"--config", b"diff.git=1"])
475 );
476 );
476 assert_eq!(
477 assert_eq!(
477 collect_early_args(&["--cwd=..", "--repository", "r", "log"]),
478 collect_early_args(&["--cwd=..", "--repository", "r", "log"]),
478 os_string_vec_from(&[b"--cwd=..", b"--repository", b"r"])
479 os_string_vec_from(&[b"--cwd=..", b"--repository", b"r"])
479 );
480 );
480 assert_eq!(
481 assert_eq!(
481 collect_early_args(&["log", "--repo=r", "--repos", "a"]),
482 collect_early_args(&["log", "--repo=r", "--repos", "a"]),
482 os_string_vec_from(&[b"--repo=r"])
483 os_string_vec_from(&[b"--repo=r"])
483 );
484 );
484 }
485 }
485
486
486 #[test]
487 #[test]
487 fn collect_early_args_orphaned() {
488 fn collect_early_args_orphaned() {
488 assert!(collect_early_args(&["log", "-R"]).is_empty());
489 assert!(collect_early_args(&["log", "-R"]).is_empty());
489 assert!(collect_early_args(&["log", "--config"]).is_empty());
490 assert!(collect_early_args(&["log", "--config"]).is_empty());
490 }
491 }
491
492
492 #[test]
493 #[test]
493 fn collect_early_args_unwanted_value() {
494 fn collect_early_args_unwanted_value() {
494 assert!(collect_early_args(&["log", "--traceback="]).is_empty());
495 assert!(collect_early_args(&["log", "--traceback="]).is_empty());
495 }
496 }
496
497
497 fn os_string_vec_from(v: &[&[u8]]) -> Vec<OsString> {
498 fn os_string_vec_from(v: &[&[u8]]) -> Vec<OsString> {
498 v.iter().map(|s| OsStr::from_bytes(s).to_owned()).collect()
499 v.iter().map(|s| OsStr::from_bytes(s).to_owned()).collect()
499 }
500 }
500 }
501 }
@@ -1,103 +1,97 b''
1 // Copyright 2018 Yuya Nishihara <yuya@tcha.org>
1 // Copyright 2018 Yuya Nishihara <yuya@tcha.org>
2 //
2 //
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 extern crate chg;
7 extern crate futures;
8 extern crate log;
9 extern crate tokio;
10 extern crate tokio_hglib;
11
12 use chg::locator::{self, Locator};
6 use chg::locator::{self, Locator};
13 use chg::procutil;
7 use chg::procutil;
14 use chg::{ChgClientExt, ChgUiHandler};
8 use chg::{ChgClientExt, ChgUiHandler};
15 use futures::sync::oneshot;
9 use futures::sync::oneshot;
16 use std::env;
10 use std::env;
17 use std::io;
11 use std::io;
18 use std::process;
12 use std::process;
19 use std::time::Instant;
13 use std::time::Instant;
20 use tokio::prelude::*;
14 use tokio::prelude::*;
21
15
22 struct DebugLogger {
16 struct DebugLogger {
23 start: Instant,
17 start: Instant,
24 }
18 }
25
19
26 impl DebugLogger {
20 impl DebugLogger {
27 pub fn new() -> DebugLogger {
21 pub fn new() -> DebugLogger {
28 DebugLogger {
22 DebugLogger {
29 start: Instant::now(),
23 start: Instant::now(),
30 }
24 }
31 }
25 }
32 }
26 }
33
27
34 impl log::Log for DebugLogger {
28 impl log::Log for DebugLogger {
35 fn enabled(&self, metadata: &log::Metadata) -> bool {
29 fn enabled(&self, metadata: &log::Metadata) -> bool {
36 metadata.target().starts_with("chg::")
30 metadata.target().starts_with("chg::")
37 }
31 }
38
32
39 fn log(&self, record: &log::Record) {
33 fn log(&self, record: &log::Record) {
40 if self.enabled(record.metadata()) {
34 if self.enabled(record.metadata()) {
41 // just make the output looks similar to chg of C
35 // just make the output looks similar to chg of C
42 let l = format!("{}", record.level()).to_lowercase();
36 let l = format!("{}", record.level()).to_lowercase();
43 let t = self.start.elapsed();
37 let t = self.start.elapsed();
44 writeln!(
38 writeln!(
45 io::stderr(),
39 io::stderr(),
46 "chg: {}: {}.{:06} {}",
40 "chg: {}: {}.{:06} {}",
47 l,
41 l,
48 t.as_secs(),
42 t.as_secs(),
49 t.subsec_micros(),
43 t.subsec_micros(),
50 record.args()
44 record.args()
51 )
45 )
52 .unwrap_or(());
46 .unwrap_or(());
53 }
47 }
54 }
48 }
55
49
56 fn flush(&self) {}
50 fn flush(&self) {}
57 }
51 }
58
52
59 fn main() {
53 fn main() {
60 if env::var_os("CHGDEBUG").is_some() {
54 if env::var_os("CHGDEBUG").is_some() {
61 log::set_boxed_logger(Box::new(DebugLogger::new()))
55 log::set_boxed_logger(Box::new(DebugLogger::new()))
62 .expect("any logger should not be installed yet");
56 .expect("any logger should not be installed yet");
63 log::set_max_level(log::LevelFilter::Debug);
57 log::set_max_level(log::LevelFilter::Debug);
64 }
58 }
65
59
66 // TODO: add loop detection by $CHGINTERNALMARK
60 // TODO: add loop detection by $CHGINTERNALMARK
67
61
68 let code = run().unwrap_or_else(|err| {
62 let code = run().unwrap_or_else(|err| {
69 writeln!(io::stderr(), "chg: abort: {}", err).unwrap_or(());
63 writeln!(io::stderr(), "chg: abort: {}", err).unwrap_or(());
70 255
64 255
71 });
65 });
72 process::exit(code);
66 process::exit(code);
73 }
67 }
74
68
75 fn run() -> io::Result<i32> {
69 fn run() -> io::Result<i32> {
76 let umask = unsafe { procutil::get_umask() }; // not thread safe
70 let umask = unsafe { procutil::get_umask() }; // not thread safe
77 let mut loc = Locator::prepare_from_env()?;
71 let mut loc = Locator::prepare_from_env()?;
78 loc.set_early_args(locator::collect_early_args(env::args_os().skip(1)));
72 loc.set_early_args(locator::collect_early_args(env::args_os().skip(1)));
79 let handler = ChgUiHandler::new();
73 let handler = ChgUiHandler::new();
80 let (result_tx, result_rx) = oneshot::channel();
74 let (result_tx, result_rx) = oneshot::channel();
81 let fut = loc
75 let fut = loc
82 .connect()
76 .connect()
83 .and_then(|(_, client)| client.attach_io(io::stdin(), io::stdout(), io::stderr()))
77 .and_then(|(_, client)| client.attach_io(io::stdin(), io::stdout(), io::stderr()))
84 .and_then(move |client| client.set_umask(umask))
78 .and_then(move |client| client.set_umask(umask))
85 .and_then(|client| {
79 .and_then(|client| {
86 let pid = client.server_spec().process_id.unwrap();
80 let pid = client.server_spec().process_id.unwrap();
87 let pgid = client.server_spec().process_group_id;
81 let pgid = client.server_spec().process_group_id;
88 procutil::setup_signal_handler_once(pid, pgid)?;
82 procutil::setup_signal_handler_once(pid, pgid)?;
89 Ok(client)
83 Ok(client)
90 })
84 })
91 .and_then(|client| client.run_command_chg(handler, env::args_os().skip(1)))
85 .and_then(|client| client.run_command_chg(handler, env::args_os().skip(1)))
92 .map(|(_client, _handler, code)| {
86 .map(|(_client, _handler, code)| {
93 procutil::restore_signal_handler_once()?;
87 procutil::restore_signal_handler_once()?;
94 Ok(code)
88 Ok(code)
95 })
89 })
96 .or_else(|err| Ok(Err(err))) // pass back error to caller
90 .or_else(|err| Ok(Err(err))) // pass back error to caller
97 .map(|res| result_tx.send(res).unwrap());
91 .map(|res| result_tx.send(res).unwrap());
98 tokio::run(fut);
92 tokio::run(fut);
99 result_rx.wait().unwrap_or(Err(io::Error::new(
93 result_rx.wait().unwrap_or(Err(io::Error::new(
100 io::ErrorKind::Other,
94 io::ErrorKind::Other,
101 "no exit code set",
95 "no exit code set",
102 )))
96 )))
103 }
97 }
General Comments 0
You need to be logged in to leave comments. Login now