##// END OF EJS Templates
rust-chg: move set_current_dir() to Locator...
Yuya Nishihara -
r45163:0a2516ef default
parent child Browse files
Show More
@@ -1,266 +1,272 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 std::env;
9 use std::env;
10 use std::ffi::{OsStr, OsString};
10 use std::ffi::{OsStr, OsString};
11 use std::fs::{self, DirBuilder};
11 use std::fs::{self, DirBuilder};
12 use std::io;
12 use std::io;
13 use std::os::unix::ffi::{OsStrExt, OsStringExt};
13 use std::os::unix::ffi::{OsStrExt, OsStringExt};
14 use std::os::unix::fs::{DirBuilderExt, MetadataExt};
14 use std::os::unix::fs::{DirBuilderExt, MetadataExt};
15 use std::path::{Path, PathBuf};
15 use std::path::{Path, PathBuf};
16 use std::process::{self, Command};
16 use std::process::{self, Command};
17 use std::time::Duration;
17 use std::time::Duration;
18 use tokio::prelude::*;
18 use tokio::prelude::*;
19 use tokio_hglib::UnixClient;
19 use tokio_hglib::UnixClient;
20 use tokio_process::{Child, CommandExt};
20 use tokio_process::{Child, CommandExt};
21 use tokio_timer;
21 use tokio_timer;
22
22
23 use super::clientext::ChgClientExt;
23 use super::message::ServerSpec;
24 use super::message::ServerSpec;
24 use super::procutil;
25 use super::procutil;
25
26
26 const REQUIRED_SERVER_CAPABILITIES: &[&str] = &["attachio", "chdir", "runcommand"];
27 const REQUIRED_SERVER_CAPABILITIES: &[&str] = &["attachio", "chdir", "runcommand"];
27
28
28 /// Helper to connect to and spawn a server process.
29 /// Helper to connect to and spawn a server process.
29 #[derive(Clone, Debug)]
30 #[derive(Clone, Debug)]
30 pub struct Locator {
31 pub struct Locator {
31 hg_command: OsString,
32 hg_command: OsString,
32 current_dir: PathBuf,
33 current_dir: PathBuf,
33 env_vars: Vec<(OsString, OsString)>,
34 env_vars: Vec<(OsString, OsString)>,
34 process_id: u32,
35 process_id: u32,
35 base_sock_path: PathBuf,
36 base_sock_path: PathBuf,
36 timeout: Duration,
37 timeout: Duration,
37 }
38 }
38
39
39 impl Locator {
40 impl Locator {
40 /// Creates locator capturing the current process environment.
41 /// Creates locator capturing the current process environment.
41 ///
42 ///
42 /// If no `$CHGSOCKNAME` is specified, the socket directory will be
43 /// If no `$CHGSOCKNAME` is specified, the socket directory will be
43 /// created as necessary.
44 /// created as necessary.
44 pub fn prepare_from_env() -> io::Result<Locator> {
45 pub fn prepare_from_env() -> io::Result<Locator> {
45 Ok(Locator {
46 Ok(Locator {
46 hg_command: default_hg_command(),
47 hg_command: default_hg_command(),
47 current_dir: env::current_dir()?,
48 current_dir: env::current_dir()?,
48 env_vars: env::vars_os().collect(),
49 env_vars: env::vars_os().collect(),
49 process_id: process::id(),
50 process_id: process::id(),
50 base_sock_path: prepare_server_socket_path()?,
51 base_sock_path: prepare_server_socket_path()?,
51 timeout: default_timeout(),
52 timeout: default_timeout(),
52 })
53 })
53 }
54 }
54
55
55 /// Temporary socket path for this client process.
56 /// Temporary socket path for this client process.
56 fn temp_sock_path(&self) -> PathBuf {
57 fn temp_sock_path(&self) -> PathBuf {
57 let src = self.base_sock_path.as_os_str().as_bytes();
58 let src = self.base_sock_path.as_os_str().as_bytes();
58 let mut buf = Vec::with_capacity(src.len() + 6); // "{src}.{pid}".len()
59 let mut buf = Vec::with_capacity(src.len() + 6); // "{src}.{pid}".len()
59 buf.extend_from_slice(src);
60 buf.extend_from_slice(src);
60 buf.extend_from_slice(format!(".{}", self.process_id).as_bytes());
61 buf.extend_from_slice(format!(".{}", self.process_id).as_bytes());
61 OsString::from_vec(buf).into()
62 OsString::from_vec(buf).into()
62 }
63 }
63
64
64 /// Connects to the server.
65 /// Connects to the server.
65 ///
66 ///
66 /// The server process will be spawned if not running.
67 /// The server process will be spawned if not running.
67 pub fn connect(self) -> impl Future<Item = (Self, UnixClient), Error = io::Error> {
68 pub fn connect(self) -> impl Future<Item = (Self, UnixClient), Error = io::Error> {
68 self.try_connect()
69 self.try_connect()
69 }
70 }
70
71
71 /// Tries to connect to the existing server, or spawns new if not running.
72 /// Tries to connect to the existing server, or spawns new if not running.
72 fn try_connect(self) -> impl Future<Item = (Self, UnixClient), Error = io::Error> {
73 fn try_connect(self) -> impl Future<Item = (Self, UnixClient), Error = io::Error> {
73 debug!("try connect to {}", self.base_sock_path.display());
74 debug!("try connect to {}", self.base_sock_path.display());
74 UnixClient::connect(self.base_sock_path.clone())
75 UnixClient::connect(self.base_sock_path.clone())
75 .then(|res| match res {
76 .then(|res| match res {
76 Ok(client) => Either::A(future::ok((self, client))),
77 Ok(client) => Either::A(future::ok((self, client))),
77 Err(_) => Either::B(self.spawn_connect()),
78 Err(_) => Either::B(self.spawn_connect()),
78 })
79 })
79 .and_then(|(loc, client)| {
80 .and_then(|(loc, client)| {
80 check_server_capabilities(client.server_spec())?;
81 check_server_capabilities(client.server_spec())?;
81 Ok((loc, client))
82 Ok((loc, client))
82 })
83 })
84 .and_then(|(loc, client)| {
85 client
86 .set_current_dir(&loc.current_dir)
87 .map(|client| (loc, client))
88 })
83 }
89 }
84
90
85 /// Spawns new server process and connects to it.
91 /// Spawns new server process and connects to it.
86 ///
92 ///
87 /// The server will be spawned at the current working directory, then
93 /// The server will be spawned at the current working directory, then
88 /// chdir to "/", so that the server will load configs from the target
94 /// chdir to "/", so that the server will load configs from the target
89 /// repository.
95 /// repository.
90 fn spawn_connect(self) -> impl Future<Item = (Self, UnixClient), Error = io::Error> {
96 fn spawn_connect(self) -> impl Future<Item = (Self, UnixClient), Error = io::Error> {
91 let sock_path = self.temp_sock_path();
97 let sock_path = self.temp_sock_path();
92 debug!("start cmdserver at {}", sock_path.display());
98 debug!("start cmdserver at {}", sock_path.display());
93 Command::new(&self.hg_command)
99 Command::new(&self.hg_command)
94 .arg("serve")
100 .arg("serve")
95 .arg("--cmdserver")
101 .arg("--cmdserver")
96 .arg("chgunix")
102 .arg("chgunix")
97 .arg("--address")
103 .arg("--address")
98 .arg(&sock_path)
104 .arg(&sock_path)
99 .arg("--daemon-postexec")
105 .arg("--daemon-postexec")
100 .arg("chdir:/")
106 .arg("chdir:/")
101 .current_dir(&self.current_dir)
107 .current_dir(&self.current_dir)
102 .env_clear()
108 .env_clear()
103 .envs(self.env_vars.iter().cloned())
109 .envs(self.env_vars.iter().cloned())
104 .env("CHGINTERNALMARK", "")
110 .env("CHGINTERNALMARK", "")
105 .spawn_async()
111 .spawn_async()
106 .into_future()
112 .into_future()
107 .and_then(|server| self.connect_spawned(server, sock_path))
113 .and_then(|server| self.connect_spawned(server, sock_path))
108 .and_then(|(loc, client, sock_path)| {
114 .and_then(|(loc, client, sock_path)| {
109 debug!(
115 debug!(
110 "rename {} to {}",
116 "rename {} to {}",
111 sock_path.display(),
117 sock_path.display(),
112 loc.base_sock_path.display()
118 loc.base_sock_path.display()
113 );
119 );
114 fs::rename(&sock_path, &loc.base_sock_path)?;
120 fs::rename(&sock_path, &loc.base_sock_path)?;
115 Ok((loc, client))
121 Ok((loc, client))
116 })
122 })
117 }
123 }
118
124
119 /// Tries to connect to the just spawned server repeatedly until timeout
125 /// Tries to connect to the just spawned server repeatedly until timeout
120 /// exceeded.
126 /// exceeded.
121 fn connect_spawned(
127 fn connect_spawned(
122 self,
128 self,
123 server: Child,
129 server: Child,
124 sock_path: PathBuf,
130 sock_path: PathBuf,
125 ) -> impl Future<Item = (Self, UnixClient, PathBuf), Error = io::Error> {
131 ) -> impl Future<Item = (Self, UnixClient, PathBuf), Error = io::Error> {
126 debug!("try connect to {} repeatedly", sock_path.display());
132 debug!("try connect to {} repeatedly", sock_path.display());
127 let connect = future::loop_fn(sock_path, |sock_path| {
133 let connect = future::loop_fn(sock_path, |sock_path| {
128 UnixClient::connect(sock_path.clone()).then(|res| {
134 UnixClient::connect(sock_path.clone()).then(|res| {
129 match res {
135 match res {
130 Ok(client) => Either::A(future::ok(Loop::Break((client, sock_path)))),
136 Ok(client) => Either::A(future::ok(Loop::Break((client, sock_path)))),
131 Err(_) => {
137 Err(_) => {
132 // try again with slight delay
138 // try again with slight delay
133 let fut = tokio_timer::sleep(Duration::from_millis(10))
139 let fut = tokio_timer::sleep(Duration::from_millis(10))
134 .map(|()| Loop::Continue(sock_path))
140 .map(|()| Loop::Continue(sock_path))
135 .map_err(|err| io::Error::new(io::ErrorKind::Other, err));
141 .map_err(|err| io::Error::new(io::ErrorKind::Other, err));
136 Either::B(fut)
142 Either::B(fut)
137 }
143 }
138 }
144 }
139 })
145 })
140 });
146 });
141
147
142 // waits for either connection established or server failed to start
148 // waits for either connection established or server failed to start
143 connect
149 connect
144 .select2(server)
150 .select2(server)
145 .map_err(|res| res.split().0)
151 .map_err(|res| res.split().0)
146 .timeout(self.timeout)
152 .timeout(self.timeout)
147 .map_err(|err| {
153 .map_err(|err| {
148 err.into_inner().unwrap_or_else(|| {
154 err.into_inner().unwrap_or_else(|| {
149 io::Error::new(
155 io::Error::new(
150 io::ErrorKind::TimedOut,
156 io::ErrorKind::TimedOut,
151 "timed out while connecting to server",
157 "timed out while connecting to server",
152 )
158 )
153 })
159 })
154 })
160 })
155 .and_then(|res| {
161 .and_then(|res| {
156 match res {
162 match res {
157 Either::A(((client, sock_path), server)) => {
163 Either::A(((client, sock_path), server)) => {
158 server.forget(); // continue to run in background
164 server.forget(); // continue to run in background
159 Ok((self, client, sock_path))
165 Ok((self, client, sock_path))
160 }
166 }
161 Either::B((st, _)) => Err(io::Error::new(
167 Either::B((st, _)) => Err(io::Error::new(
162 io::ErrorKind::Other,
168 io::ErrorKind::Other,
163 format!("server exited too early: {}", st),
169 format!("server exited too early: {}", st),
164 )),
170 )),
165 }
171 }
166 })
172 })
167 }
173 }
168 }
174 }
169
175
170 /// Determines the server socket to connect to.
176 /// Determines the server socket to connect to.
171 ///
177 ///
172 /// If no `$CHGSOCKNAME` is specified, the socket directory will be created
178 /// If no `$CHGSOCKNAME` is specified, the socket directory will be created
173 /// as necessary.
179 /// as necessary.
174 fn prepare_server_socket_path() -> io::Result<PathBuf> {
180 fn prepare_server_socket_path() -> io::Result<PathBuf> {
175 if let Some(s) = env::var_os("CHGSOCKNAME") {
181 if let Some(s) = env::var_os("CHGSOCKNAME") {
176 Ok(PathBuf::from(s))
182 Ok(PathBuf::from(s))
177 } else {
183 } else {
178 let mut path = default_server_socket_dir();
184 let mut path = default_server_socket_dir();
179 create_secure_dir(&path)?;
185 create_secure_dir(&path)?;
180 path.push("server");
186 path.push("server");
181 Ok(path)
187 Ok(path)
182 }
188 }
183 }
189 }
184
190
185 /// Determines the default server socket path as follows.
191 /// Determines the default server socket path as follows.
186 ///
192 ///
187 /// 1. `$XDG_RUNTIME_DIR/chg`
193 /// 1. `$XDG_RUNTIME_DIR/chg`
188 /// 2. `$TMPDIR/chg$UID`
194 /// 2. `$TMPDIR/chg$UID`
189 /// 3. `/tmp/chg$UID`
195 /// 3. `/tmp/chg$UID`
190 pub fn default_server_socket_dir() -> PathBuf {
196 pub fn default_server_socket_dir() -> PathBuf {
191 // XDG_RUNTIME_DIR should be ignored if it has an insufficient permission.
197 // XDG_RUNTIME_DIR should be ignored if it has an insufficient permission.
192 // https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
198 // https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
193 if let Some(Ok(s)) = env::var_os("XDG_RUNTIME_DIR").map(check_secure_dir) {
199 if let Some(Ok(s)) = env::var_os("XDG_RUNTIME_DIR").map(check_secure_dir) {
194 let mut path = PathBuf::from(s);
200 let mut path = PathBuf::from(s);
195 path.push("chg");
201 path.push("chg");
196 path
202 path
197 } else {
203 } else {
198 let mut path = env::temp_dir();
204 let mut path = env::temp_dir();
199 path.push(format!("chg{}", procutil::get_effective_uid()));
205 path.push(format!("chg{}", procutil::get_effective_uid()));
200 path
206 path
201 }
207 }
202 }
208 }
203
209
204 /// Determines the default hg command.
210 /// Determines the default hg command.
205 pub fn default_hg_command() -> OsString {
211 pub fn default_hg_command() -> OsString {
206 // TODO: maybe allow embedding the path at compile time (or load from hgrc)
212 // TODO: maybe allow embedding the path at compile time (or load from hgrc)
207 env::var_os("CHGHG")
213 env::var_os("CHGHG")
208 .or(env::var_os("HG"))
214 .or(env::var_os("HG"))
209 .unwrap_or(OsStr::new("hg").to_owned())
215 .unwrap_or(OsStr::new("hg").to_owned())
210 }
216 }
211
217
212 fn default_timeout() -> Duration {
218 fn default_timeout() -> Duration {
213 let secs = env::var("CHGTIMEOUT")
219 let secs = env::var("CHGTIMEOUT")
214 .ok()
220 .ok()
215 .and_then(|s| s.parse().ok())
221 .and_then(|s| s.parse().ok())
216 .unwrap_or(60);
222 .unwrap_or(60);
217 Duration::from_secs(secs)
223 Duration::from_secs(secs)
218 }
224 }
219
225
220 /// Creates a directory which the other users cannot access to.
226 /// Creates a directory which the other users cannot access to.
221 ///
227 ///
222 /// If the directory already exists, tests its permission.
228 /// If the directory already exists, tests its permission.
223 fn create_secure_dir<P>(path: P) -> io::Result<()>
229 fn create_secure_dir<P>(path: P) -> io::Result<()>
224 where
230 where
225 P: AsRef<Path>,
231 P: AsRef<Path>,
226 {
232 {
227 DirBuilder::new()
233 DirBuilder::new()
228 .mode(0o700)
234 .mode(0o700)
229 .create(path.as_ref())
235 .create(path.as_ref())
230 .or_else(|err| {
236 .or_else(|err| {
231 if err.kind() == io::ErrorKind::AlreadyExists {
237 if err.kind() == io::ErrorKind::AlreadyExists {
232 check_secure_dir(path).map(|_| ())
238 check_secure_dir(path).map(|_| ())
233 } else {
239 } else {
234 Err(err)
240 Err(err)
235 }
241 }
236 })
242 })
237 }
243 }
238
244
239 fn check_secure_dir<P>(path: P) -> io::Result<P>
245 fn check_secure_dir<P>(path: P) -> io::Result<P>
240 where
246 where
241 P: AsRef<Path>,
247 P: AsRef<Path>,
242 {
248 {
243 let a = fs::symlink_metadata(path.as_ref())?;
249 let a = fs::symlink_metadata(path.as_ref())?;
244 if a.is_dir() && a.uid() == procutil::get_effective_uid() && (a.mode() & 0o777) == 0o700 {
250 if a.is_dir() && a.uid() == procutil::get_effective_uid() && (a.mode() & 0o777) == 0o700 {
245 Ok(path)
251 Ok(path)
246 } else {
252 } else {
247 Err(io::Error::new(io::ErrorKind::Other, "insecure directory"))
253 Err(io::Error::new(io::ErrorKind::Other, "insecure directory"))
248 }
254 }
249 }
255 }
250
256
251 fn check_server_capabilities(spec: &ServerSpec) -> io::Result<()> {
257 fn check_server_capabilities(spec: &ServerSpec) -> io::Result<()> {
252 let unsupported: Vec<_> = REQUIRED_SERVER_CAPABILITIES
258 let unsupported: Vec<_> = REQUIRED_SERVER_CAPABILITIES
253 .iter()
259 .iter()
254 .cloned()
260 .cloned()
255 .filter(|&s| !spec.capabilities.contains(s))
261 .filter(|&s| !spec.capabilities.contains(s))
256 .collect();
262 .collect();
257 if unsupported.is_empty() {
263 if unsupported.is_empty() {
258 Ok(())
264 Ok(())
259 } else {
265 } else {
260 let msg = format!(
266 let msg = format!(
261 "insufficient server capabilities: {}",
267 "insufficient server capabilities: {}",
262 unsupported.join(", ")
268 unsupported.join(", ")
263 );
269 );
264 Err(io::Error::new(io::ErrorKind::Other, msg))
270 Err(io::Error::new(io::ErrorKind::Other, msg))
265 }
271 }
266 }
272 }
@@ -1,102 +1,100 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;
6 extern crate chg;
7 extern crate futures;
7 extern crate futures;
8 extern crate log;
8 extern crate log;
9 extern crate tokio;
9 extern crate tokio;
10 extern crate tokio_hglib;
10 extern crate tokio_hglib;
11
11
12 use chg::locator::Locator;
12 use chg::locator::Locator;
13 use chg::procutil;
13 use chg::procutil;
14 use chg::{ChgClientExt, ChgUiHandler};
14 use chg::{ChgClientExt, ChgUiHandler};
15 use futures::sync::oneshot;
15 use futures::sync::oneshot;
16 use std::env;
16 use std::env;
17 use std::io;
17 use std::io;
18 use std::process;
18 use std::process;
19 use std::time::Instant;
19 use std::time::Instant;
20 use tokio::prelude::*;
20 use tokio::prelude::*;
21
21
22 struct DebugLogger {
22 struct DebugLogger {
23 start: Instant,
23 start: Instant,
24 }
24 }
25
25
26 impl DebugLogger {
26 impl DebugLogger {
27 pub fn new() -> DebugLogger {
27 pub fn new() -> DebugLogger {
28 DebugLogger {
28 DebugLogger {
29 start: Instant::now(),
29 start: Instant::now(),
30 }
30 }
31 }
31 }
32 }
32 }
33
33
34 impl log::Log for DebugLogger {
34 impl log::Log for DebugLogger {
35 fn enabled(&self, metadata: &log::Metadata) -> bool {
35 fn enabled(&self, metadata: &log::Metadata) -> bool {
36 metadata.target().starts_with("chg::")
36 metadata.target().starts_with("chg::")
37 }
37 }
38
38
39 fn log(&self, record: &log::Record) {
39 fn log(&self, record: &log::Record) {
40 if self.enabled(record.metadata()) {
40 if self.enabled(record.metadata()) {
41 // just make the output looks similar to chg of C
41 // just make the output looks similar to chg of C
42 let l = format!("{}", record.level()).to_lowercase();
42 let l = format!("{}", record.level()).to_lowercase();
43 let t = self.start.elapsed();
43 let t = self.start.elapsed();
44 writeln!(
44 writeln!(
45 io::stderr(),
45 io::stderr(),
46 "chg: {}: {}.{:06} {}",
46 "chg: {}: {}.{:06} {}",
47 l,
47 l,
48 t.as_secs(),
48 t.as_secs(),
49 t.subsec_micros(),
49 t.subsec_micros(),
50 record.args()
50 record.args()
51 )
51 )
52 .unwrap_or(());
52 .unwrap_or(());
53 }
53 }
54 }
54 }
55
55
56 fn flush(&self) {}
56 fn flush(&self) {}
57 }
57 }
58
58
59 fn main() {
59 fn main() {
60 if env::var_os("CHGDEBUG").is_some() {
60 if env::var_os("CHGDEBUG").is_some() {
61 log::set_boxed_logger(Box::new(DebugLogger::new()))
61 log::set_boxed_logger(Box::new(DebugLogger::new()))
62 .expect("any logger should not be installed yet");
62 .expect("any logger should not be installed yet");
63 log::set_max_level(log::LevelFilter::Debug);
63 log::set_max_level(log::LevelFilter::Debug);
64 }
64 }
65
65
66 // TODO: add loop detection by $CHGINTERNALMARK
66 // TODO: add loop detection by $CHGINTERNALMARK
67
67
68 let code = run().unwrap_or_else(|err| {
68 let code = run().unwrap_or_else(|err| {
69 writeln!(io::stderr(), "chg: abort: {}", err).unwrap_or(());
69 writeln!(io::stderr(), "chg: abort: {}", err).unwrap_or(());
70 255
70 255
71 });
71 });
72 process::exit(code);
72 process::exit(code);
73 }
73 }
74
74
75 fn run() -> io::Result<i32> {
75 fn run() -> io::Result<i32> {
76 let current_dir = env::current_dir()?;
77 let loc = Locator::prepare_from_env()?;
76 let loc = Locator::prepare_from_env()?;
78 let handler = ChgUiHandler::new();
77 let handler = ChgUiHandler::new();
79 let (result_tx, result_rx) = oneshot::channel();
78 let (result_tx, result_rx) = oneshot::channel();
80 let fut = loc
79 let fut = loc
81 .connect()
80 .connect()
82 .and_then(|(_, client)| client.set_current_dir(current_dir))
81 .and_then(|(_, client)| client.attach_io(io::stdin(), io::stdout(), io::stderr()))
83 .and_then(|client| client.attach_io(io::stdin(), io::stdout(), io::stderr()))
84 .and_then(|client| {
82 .and_then(|client| {
85 let pid = client.server_spec().process_id.unwrap();
83 let pid = client.server_spec().process_id.unwrap();
86 let pgid = client.server_spec().process_group_id;
84 let pgid = client.server_spec().process_group_id;
87 procutil::setup_signal_handler_once(pid, pgid)?;
85 procutil::setup_signal_handler_once(pid, pgid)?;
88 Ok(client)
86 Ok(client)
89 })
87 })
90 .and_then(|client| client.run_command_chg(handler, env::args_os().skip(1)))
88 .and_then(|client| client.run_command_chg(handler, env::args_os().skip(1)))
91 .map(|(_client, _handler, code)| {
89 .map(|(_client, _handler, code)| {
92 procutil::restore_signal_handler_once()?;
90 procutil::restore_signal_handler_once()?;
93 Ok(code)
91 Ok(code)
94 })
92 })
95 .or_else(|err| Ok(Err(err))) // pass back error to caller
93 .or_else(|err| Ok(Err(err))) // pass back error to caller
96 .map(|res| result_tx.send(res).unwrap());
94 .map(|res| result_tx.send(res).unwrap());
97 tokio::run(fut);
95 tokio::run(fut);
98 result_rx.wait().unwrap_or(Err(io::Error::new(
96 result_rx.wait().unwrap_or(Err(io::Error::new(
99 io::ErrorKind::Other,
97 io::ErrorKind::Other,
100 "no exit code set",
98 "no exit code set",
101 )))
99 )))
102 }
100 }
General Comments 0
You need to be logged in to leave comments. Login now