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