##// END OF EJS Templates
rust-chg: collect server flags from command arguments...
Yuya Nishihara -
r45172:00ac6065 default
parent child Browse files
Show More
@@ -1,277 +1,386 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::clientext::ChgClientExt;
24 use super::message::ServerSpec;
24 use super::message::ServerSpec;
25 use super::procutil;
25 use super::procutil;
26
26
27 const REQUIRED_SERVER_CAPABILITIES: &[&str] = &["attachio", "chdir", "runcommand", "setenv"];
27 const REQUIRED_SERVER_CAPABILITIES: &[&str] = &["attachio", "chdir", "runcommand", "setenv"];
28
28
29 /// Helper to connect to and spawn a server process.
29 /// Helper to connect to and spawn a server process.
30 #[derive(Clone, Debug)]
30 #[derive(Clone, Debug)]
31 pub struct Locator {
31 pub struct Locator {
32 hg_command: OsString,
32 hg_command: OsString,
33 hg_early_args: Vec<OsString>,
33 current_dir: PathBuf,
34 current_dir: PathBuf,
34 env_vars: Vec<(OsString, OsString)>,
35 env_vars: Vec<(OsString, OsString)>,
35 process_id: u32,
36 process_id: u32,
36 base_sock_path: PathBuf,
37 base_sock_path: PathBuf,
37 timeout: Duration,
38 timeout: Duration,
38 }
39 }
39
40
40 impl Locator {
41 impl Locator {
41 /// Creates locator capturing the current process environment.
42 /// Creates locator capturing the current process environment.
42 ///
43 ///
43 /// If no `$CHGSOCKNAME` is specified, the socket directory will be
44 /// If no `$CHGSOCKNAME` is specified, the socket directory will be
44 /// created as necessary.
45 /// created as necessary.
45 pub fn prepare_from_env() -> io::Result<Locator> {
46 pub fn prepare_from_env() -> io::Result<Locator> {
46 Ok(Locator {
47 Ok(Locator {
47 hg_command: default_hg_command(),
48 hg_command: default_hg_command(),
49 hg_early_args: Vec::new(),
48 current_dir: env::current_dir()?,
50 current_dir: env::current_dir()?,
49 env_vars: env::vars_os().collect(),
51 env_vars: env::vars_os().collect(),
50 process_id: process::id(),
52 process_id: process::id(),
51 base_sock_path: prepare_server_socket_path()?,
53 base_sock_path: prepare_server_socket_path()?,
52 timeout: default_timeout(),
54 timeout: default_timeout(),
53 })
55 })
54 }
56 }
55
57
56 /// Temporary socket path for this client process.
58 /// Temporary socket path for this client process.
57 fn temp_sock_path(&self) -> PathBuf {
59 fn temp_sock_path(&self) -> PathBuf {
58 let src = self.base_sock_path.as_os_str().as_bytes();
60 let src = self.base_sock_path.as_os_str().as_bytes();
59 let mut buf = Vec::with_capacity(src.len() + 6); // "{src}.{pid}".len()
61 let mut buf = Vec::with_capacity(src.len() + 6); // "{src}.{pid}".len()
60 buf.extend_from_slice(src);
62 buf.extend_from_slice(src);
61 buf.extend_from_slice(format!(".{}", self.process_id).as_bytes());
63 buf.extend_from_slice(format!(".{}", self.process_id).as_bytes());
62 OsString::from_vec(buf).into()
64 OsString::from_vec(buf).into()
63 }
65 }
64
66
67 /// Specifies the arguments to be passed to the server at start.
68 pub fn set_early_args<I, P>(&mut self, args: I)
69 where
70 I: IntoIterator<Item = P>,
71 P: AsRef<OsStr>,
72 {
73 self.hg_early_args = args.into_iter().map(|a| a.as_ref().to_owned()).collect();
74 }
75
65 /// Connects to the server.
76 /// Connects to the server.
66 ///
77 ///
67 /// The server process will be spawned if not running.
78 /// The server process will be spawned if not running.
68 pub fn connect(self) -> impl Future<Item = (Self, UnixClient), Error = io::Error> {
79 pub fn connect(self) -> impl Future<Item = (Self, UnixClient), Error = io::Error> {
69 self.try_connect()
80 self.try_connect()
70 }
81 }
71
82
72 /// Tries to connect to the existing server, or spawns new if not running.
83 /// Tries to connect to the existing server, or spawns new if not running.
73 fn try_connect(self) -> impl Future<Item = (Self, UnixClient), Error = io::Error> {
84 fn try_connect(self) -> impl Future<Item = (Self, UnixClient), Error = io::Error> {
74 debug!("try connect to {}", self.base_sock_path.display());
85 debug!("try connect to {}", self.base_sock_path.display());
75 UnixClient::connect(self.base_sock_path.clone())
86 UnixClient::connect(self.base_sock_path.clone())
76 .then(|res| match res {
87 .then(|res| match res {
77 Ok(client) => Either::A(future::ok((self, client))),
88 Ok(client) => Either::A(future::ok((self, client))),
78 Err(_) => Either::B(self.spawn_connect()),
89 Err(_) => Either::B(self.spawn_connect()),
79 })
90 })
80 .and_then(|(loc, client)| {
91 .and_then(|(loc, client)| {
81 check_server_capabilities(client.server_spec())?;
92 check_server_capabilities(client.server_spec())?;
82 Ok((loc, client))
93 Ok((loc, client))
83 })
94 })
84 .and_then(|(loc, client)| {
95 .and_then(|(loc, client)| {
85 client
96 client
86 .set_current_dir(&loc.current_dir)
97 .set_current_dir(&loc.current_dir)
87 .map(|client| (loc, client))
98 .map(|client| (loc, client))
88 })
99 })
89 .and_then(|(loc, client)| {
100 .and_then(|(loc, client)| {
90 client
101 client
91 .set_env_vars_os(loc.env_vars.iter().cloned())
102 .set_env_vars_os(loc.env_vars.iter().cloned())
92 .map(|client| (loc, client))
103 .map(|client| (loc, client))
93 })
104 })
94 }
105 }
95
106
96 /// Spawns new server process and connects to it.
107 /// Spawns new server process and connects to it.
97 ///
108 ///
98 /// The server will be spawned at the current working directory, then
109 /// The server will be spawned at the current working directory, then
99 /// chdir to "/", so that the server will load configs from the target
110 /// chdir to "/", so that the server will load configs from the target
100 /// repository.
111 /// repository.
101 fn spawn_connect(self) -> impl Future<Item = (Self, UnixClient), Error = io::Error> {
112 fn spawn_connect(self) -> impl Future<Item = (Self, UnixClient), Error = io::Error> {
102 let sock_path = self.temp_sock_path();
113 let sock_path = self.temp_sock_path();
103 debug!("start cmdserver at {}", sock_path.display());
114 debug!("start cmdserver at {}", sock_path.display());
104 Command::new(&self.hg_command)
115 Command::new(&self.hg_command)
105 .arg("serve")
116 .arg("serve")
106 .arg("--cmdserver")
117 .arg("--cmdserver")
107 .arg("chgunix")
118 .arg("chgunix")
108 .arg("--address")
119 .arg("--address")
109 .arg(&sock_path)
120 .arg(&sock_path)
110 .arg("--daemon-postexec")
121 .arg("--daemon-postexec")
111 .arg("chdir:/")
122 .arg("chdir:/")
123 .args(&self.hg_early_args)
112 .current_dir(&self.current_dir)
124 .current_dir(&self.current_dir)
113 .env_clear()
125 .env_clear()
114 .envs(self.env_vars.iter().cloned())
126 .envs(self.env_vars.iter().cloned())
115 .env("CHGINTERNALMARK", "")
127 .env("CHGINTERNALMARK", "")
116 .spawn_async()
128 .spawn_async()
117 .into_future()
129 .into_future()
118 .and_then(|server| self.connect_spawned(server, sock_path))
130 .and_then(|server| self.connect_spawned(server, sock_path))
119 .and_then(|(loc, client, sock_path)| {
131 .and_then(|(loc, client, sock_path)| {
120 debug!(
132 debug!(
121 "rename {} to {}",
133 "rename {} to {}",
122 sock_path.display(),
134 sock_path.display(),
123 loc.base_sock_path.display()
135 loc.base_sock_path.display()
124 );
136 );
125 fs::rename(&sock_path, &loc.base_sock_path)?;
137 fs::rename(&sock_path, &loc.base_sock_path)?;
126 Ok((loc, client))
138 Ok((loc, client))
127 })
139 })
128 }
140 }
129
141
130 /// Tries to connect to the just spawned server repeatedly until timeout
142 /// Tries to connect to the just spawned server repeatedly until timeout
131 /// exceeded.
143 /// exceeded.
132 fn connect_spawned(
144 fn connect_spawned(
133 self,
145 self,
134 server: Child,
146 server: Child,
135 sock_path: PathBuf,
147 sock_path: PathBuf,
136 ) -> impl Future<Item = (Self, UnixClient, PathBuf), Error = io::Error> {
148 ) -> impl Future<Item = (Self, UnixClient, PathBuf), Error = io::Error> {
137 debug!("try connect to {} repeatedly", sock_path.display());
149 debug!("try connect to {} repeatedly", sock_path.display());
138 let connect = future::loop_fn(sock_path, |sock_path| {
150 let connect = future::loop_fn(sock_path, |sock_path| {
139 UnixClient::connect(sock_path.clone()).then(|res| {
151 UnixClient::connect(sock_path.clone()).then(|res| {
140 match res {
152 match res {
141 Ok(client) => Either::A(future::ok(Loop::Break((client, sock_path)))),
153 Ok(client) => Either::A(future::ok(Loop::Break((client, sock_path)))),
142 Err(_) => {
154 Err(_) => {
143 // try again with slight delay
155 // try again with slight delay
144 let fut = tokio_timer::sleep(Duration::from_millis(10))
156 let fut = tokio_timer::sleep(Duration::from_millis(10))
145 .map(|()| Loop::Continue(sock_path))
157 .map(|()| Loop::Continue(sock_path))
146 .map_err(|err| io::Error::new(io::ErrorKind::Other, err));
158 .map_err(|err| io::Error::new(io::ErrorKind::Other, err));
147 Either::B(fut)
159 Either::B(fut)
148 }
160 }
149 }
161 }
150 })
162 })
151 });
163 });
152
164
153 // waits for either connection established or server failed to start
165 // waits for either connection established or server failed to start
154 connect
166 connect
155 .select2(server)
167 .select2(server)
156 .map_err(|res| res.split().0)
168 .map_err(|res| res.split().0)
157 .timeout(self.timeout)
169 .timeout(self.timeout)
158 .map_err(|err| {
170 .map_err(|err| {
159 err.into_inner().unwrap_or_else(|| {
171 err.into_inner().unwrap_or_else(|| {
160 io::Error::new(
172 io::Error::new(
161 io::ErrorKind::TimedOut,
173 io::ErrorKind::TimedOut,
162 "timed out while connecting to server",
174 "timed out while connecting to server",
163 )
175 )
164 })
176 })
165 })
177 })
166 .and_then(|res| {
178 .and_then(|res| {
167 match res {
179 match res {
168 Either::A(((client, sock_path), server)) => {
180 Either::A(((client, sock_path), server)) => {
169 server.forget(); // continue to run in background
181 server.forget(); // continue to run in background
170 Ok((self, client, sock_path))
182 Ok((self, client, sock_path))
171 }
183 }
172 Either::B((st, _)) => Err(io::Error::new(
184 Either::B((st, _)) => Err(io::Error::new(
173 io::ErrorKind::Other,
185 io::ErrorKind::Other,
174 format!("server exited too early: {}", st),
186 format!("server exited too early: {}", st),
175 )),
187 )),
176 }
188 }
177 })
189 })
178 }
190 }
179 }
191 }
180
192
181 /// Determines the server socket to connect to.
193 /// Determines the server socket to connect to.
182 ///
194 ///
183 /// If no `$CHGSOCKNAME` is specified, the socket directory will be created
195 /// If no `$CHGSOCKNAME` is specified, the socket directory will be created
184 /// as necessary.
196 /// as necessary.
185 fn prepare_server_socket_path() -> io::Result<PathBuf> {
197 fn prepare_server_socket_path() -> io::Result<PathBuf> {
186 if let Some(s) = env::var_os("CHGSOCKNAME") {
198 if let Some(s) = env::var_os("CHGSOCKNAME") {
187 Ok(PathBuf::from(s))
199 Ok(PathBuf::from(s))
188 } else {
200 } else {
189 let mut path = default_server_socket_dir();
201 let mut path = default_server_socket_dir();
190 create_secure_dir(&path)?;
202 create_secure_dir(&path)?;
191 path.push("server");
203 path.push("server");
192 Ok(path)
204 Ok(path)
193 }
205 }
194 }
206 }
195
207
196 /// Determines the default server socket path as follows.
208 /// Determines the default server socket path as follows.
197 ///
209 ///
198 /// 1. `$XDG_RUNTIME_DIR/chg`
210 /// 1. `$XDG_RUNTIME_DIR/chg`
199 /// 2. `$TMPDIR/chg$UID`
211 /// 2. `$TMPDIR/chg$UID`
200 /// 3. `/tmp/chg$UID`
212 /// 3. `/tmp/chg$UID`
201 pub fn default_server_socket_dir() -> PathBuf {
213 pub fn default_server_socket_dir() -> PathBuf {
202 // XDG_RUNTIME_DIR should be ignored if it has an insufficient permission.
214 // XDG_RUNTIME_DIR should be ignored if it has an insufficient permission.
203 // https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
215 // https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
204 if let Some(Ok(s)) = env::var_os("XDG_RUNTIME_DIR").map(check_secure_dir) {
216 if let Some(Ok(s)) = env::var_os("XDG_RUNTIME_DIR").map(check_secure_dir) {
205 let mut path = PathBuf::from(s);
217 let mut path = PathBuf::from(s);
206 path.push("chg");
218 path.push("chg");
207 path
219 path
208 } else {
220 } else {
209 let mut path = env::temp_dir();
221 let mut path = env::temp_dir();
210 path.push(format!("chg{}", procutil::get_effective_uid()));
222 path.push(format!("chg{}", procutil::get_effective_uid()));
211 path
223 path
212 }
224 }
213 }
225 }
214
226
215 /// Determines the default hg command.
227 /// Determines the default hg command.
216 pub fn default_hg_command() -> OsString {
228 pub fn default_hg_command() -> OsString {
217 // TODO: maybe allow embedding the path at compile time (or load from hgrc)
229 // TODO: maybe allow embedding the path at compile time (or load from hgrc)
218 env::var_os("CHGHG")
230 env::var_os("CHGHG")
219 .or(env::var_os("HG"))
231 .or(env::var_os("HG"))
220 .unwrap_or(OsStr::new("hg").to_owned())
232 .unwrap_or(OsStr::new("hg").to_owned())
221 }
233 }
222
234
223 fn default_timeout() -> Duration {
235 fn default_timeout() -> Duration {
224 let secs = env::var("CHGTIMEOUT")
236 let secs = env::var("CHGTIMEOUT")
225 .ok()
237 .ok()
226 .and_then(|s| s.parse().ok())
238 .and_then(|s| s.parse().ok())
227 .unwrap_or(60);
239 .unwrap_or(60);
228 Duration::from_secs(secs)
240 Duration::from_secs(secs)
229 }
241 }
230
242
231 /// Creates a directory which the other users cannot access to.
243 /// Creates a directory which the other users cannot access to.
232 ///
244 ///
233 /// If the directory already exists, tests its permission.
245 /// If the directory already exists, tests its permission.
234 fn create_secure_dir<P>(path: P) -> io::Result<()>
246 fn create_secure_dir<P>(path: P) -> io::Result<()>
235 where
247 where
236 P: AsRef<Path>,
248 P: AsRef<Path>,
237 {
249 {
238 DirBuilder::new()
250 DirBuilder::new()
239 .mode(0o700)
251 .mode(0o700)
240 .create(path.as_ref())
252 .create(path.as_ref())
241 .or_else(|err| {
253 .or_else(|err| {
242 if err.kind() == io::ErrorKind::AlreadyExists {
254 if err.kind() == io::ErrorKind::AlreadyExists {
243 check_secure_dir(path).map(|_| ())
255 check_secure_dir(path).map(|_| ())
244 } else {
256 } else {
245 Err(err)
257 Err(err)
246 }
258 }
247 })
259 })
248 }
260 }
249
261
250 fn check_secure_dir<P>(path: P) -> io::Result<P>
262 fn check_secure_dir<P>(path: P) -> io::Result<P>
251 where
263 where
252 P: AsRef<Path>,
264 P: AsRef<Path>,
253 {
265 {
254 let a = fs::symlink_metadata(path.as_ref())?;
266 let a = fs::symlink_metadata(path.as_ref())?;
255 if a.is_dir() && a.uid() == procutil::get_effective_uid() && (a.mode() & 0o777) == 0o700 {
267 if a.is_dir() && a.uid() == procutil::get_effective_uid() && (a.mode() & 0o777) == 0o700 {
256 Ok(path)
268 Ok(path)
257 } else {
269 } else {
258 Err(io::Error::new(io::ErrorKind::Other, "insecure directory"))
270 Err(io::Error::new(io::ErrorKind::Other, "insecure directory"))
259 }
271 }
260 }
272 }
261
273
262 fn check_server_capabilities(spec: &ServerSpec) -> io::Result<()> {
274 fn check_server_capabilities(spec: &ServerSpec) -> io::Result<()> {
263 let unsupported: Vec<_> = REQUIRED_SERVER_CAPABILITIES
275 let unsupported: Vec<_> = REQUIRED_SERVER_CAPABILITIES
264 .iter()
276 .iter()
265 .cloned()
277 .cloned()
266 .filter(|&s| !spec.capabilities.contains(s))
278 .filter(|&s| !spec.capabilities.contains(s))
267 .collect();
279 .collect();
268 if unsupported.is_empty() {
280 if unsupported.is_empty() {
269 Ok(())
281 Ok(())
270 } else {
282 } else {
271 let msg = format!(
283 let msg = format!(
272 "insufficient server capabilities: {}",
284 "insufficient server capabilities: {}",
273 unsupported.join(", ")
285 unsupported.join(", ")
274 );
286 );
275 Err(io::Error::new(io::ErrorKind::Other, msg))
287 Err(io::Error::new(io::ErrorKind::Other, msg))
276 }
288 }
277 }
289 }
290
291 /// Collects arguments which need to be passed to the server at start.
292 pub fn collect_early_args<I, P>(args: I) -> Vec<OsString>
293 where
294 I: IntoIterator<Item = P>,
295 P: AsRef<OsStr>,
296 {
297 let mut args_iter = args.into_iter();
298 let mut early_args = Vec::new();
299 while let Some(arg) = args_iter.next() {
300 let argb = arg.as_ref().as_bytes();
301 if argb == b"--" {
302 break;
303 } else if argb.starts_with(b"--") {
304 let mut split = argb[2..].splitn(2, |&c| c == b'=');
305 match split.next().unwrap() {
306 b"traceback" => {
307 if split.next().is_none() {
308 early_args.push(arg.as_ref().to_owned());
309 }
310 }
311 b"config" | b"cwd" | b"repo" | b"repository" => {
312 if split.next().is_some() {
313 // --<flag>=<val>
314 early_args.push(arg.as_ref().to_owned());
315 } else {
316 // --<flag> <val>
317 args_iter.next().map(|val| {
318 early_args.push(arg.as_ref().to_owned());
319 early_args.push(val.as_ref().to_owned());
320 });
321 }
322 }
323 _ => {}
324 }
325 } else if argb.starts_with(b"-R") {
326 if argb.len() > 2 {
327 // -R<val>
328 early_args.push(arg.as_ref().to_owned());
329 } else {
330 // -R <val>
331 args_iter.next().map(|val| {
332 early_args.push(arg.as_ref().to_owned());
333 early_args.push(val.as_ref().to_owned());
334 });
335 }
336 }
337 }
338
339 early_args
340 }
341
342 #[cfg(test)]
343 mod tests {
344 use super::*;
345
346 #[test]
347 fn collect_early_args_some() {
348 assert!(collect_early_args(&[] as &[&OsStr]).is_empty());
349 assert!(collect_early_args(&["log"]).is_empty());
350 assert_eq!(
351 collect_early_args(&["log", "-Ra", "foo"]),
352 os_string_vec_from(&[b"-Ra"])
353 );
354 assert_eq!(
355 collect_early_args(&["log", "-R", "repo", "", "--traceback", "a"]),
356 os_string_vec_from(&[b"-R", b"repo", b"--traceback"])
357 );
358 assert_eq!(
359 collect_early_args(&["log", "--config", "diff.git=1", "-q"]),
360 os_string_vec_from(&[b"--config", b"diff.git=1"])
361 );
362 assert_eq!(
363 collect_early_args(&["--cwd=..", "--repository", "r", "log"]),
364 os_string_vec_from(&[b"--cwd=..", b"--repository", b"r"])
365 );
366 assert_eq!(
367 collect_early_args(&["log", "--repo=r", "--repos", "a"]),
368 os_string_vec_from(&[b"--repo=r"])
369 );
370 }
371
372 #[test]
373 fn collect_early_args_orphaned() {
374 assert!(collect_early_args(&["log", "-R"]).is_empty());
375 assert!(collect_early_args(&["log", "--config"]).is_empty());
376 }
377
378 #[test]
379 fn collect_early_args_unwanted_value() {
380 assert!(collect_early_args(&["log", "--traceback="]).is_empty());
381 }
382
383 fn os_string_vec_from(v: &[&[u8]]) -> Vec<OsString> {
384 v.iter().map(|s| OsStr::from_bytes(s).to_owned()).collect()
385 }
386 }
@@ -1,100 +1,101 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::{self, 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 loc = Locator::prepare_from_env()?;
76 let mut loc = Locator::prepare_from_env()?;
77 loc.set_early_args(locator::collect_early_args(env::args_os().skip(1)));
77 let handler = ChgUiHandler::new();
78 let handler = ChgUiHandler::new();
78 let (result_tx, result_rx) = oneshot::channel();
79 let (result_tx, result_rx) = oneshot::channel();
79 let fut = loc
80 let fut = loc
80 .connect()
81 .connect()
81 .and_then(|(_, client)| client.attach_io(io::stdin(), io::stdout(), io::stderr()))
82 .and_then(|(_, client)| client.attach_io(io::stdin(), io::stdout(), io::stderr()))
82 .and_then(|client| {
83 .and_then(|client| {
83 let pid = client.server_spec().process_id.unwrap();
84 let pid = client.server_spec().process_id.unwrap();
84 let pgid = client.server_spec().process_group_id;
85 let pgid = client.server_spec().process_group_id;
85 procutil::setup_signal_handler_once(pid, pgid)?;
86 procutil::setup_signal_handler_once(pid, pgid)?;
86 Ok(client)
87 Ok(client)
87 })
88 })
88 .and_then(|client| client.run_command_chg(handler, env::args_os().skip(1)))
89 .and_then(|client| client.run_command_chg(handler, env::args_os().skip(1)))
89 .map(|(_client, _handler, code)| {
90 .map(|(_client, _handler, code)| {
90 procutil::restore_signal_handler_once()?;
91 procutil::restore_signal_handler_once()?;
91 Ok(code)
92 Ok(code)
92 })
93 })
93 .or_else(|err| Ok(Err(err))) // pass back error to caller
94 .or_else(|err| Ok(Err(err))) // pass back error to caller
94 .map(|res| result_tx.send(res).unwrap());
95 .map(|res| result_tx.send(res).unwrap());
95 tokio::run(fut);
96 tokio::run(fut);
96 result_rx.wait().unwrap_or(Err(io::Error::new(
97 result_rx.wait().unwrap_or(Err(io::Error::new(
97 io::ErrorKind::Other,
98 io::ErrorKind::Other,
98 "no exit code set",
99 "no exit code set",
99 )))
100 )))
100 }
101 }
General Comments 0
You need to be logged in to leave comments. Login now