Mercurial > hg
comparison rust/rhg/src/main.rs @ 49149:006688e36e12 stable
rhg: use `Command::exec` instead of `Command::status`
`rhg` is supposed to be a transparent executable, using a subprocess defeats
that purpose. See inline comments for more details.
This also introduces the `which` crate to check if the fallback executable
actually exists to help debugging (plain `execve` doesn't give much
information).
The error code 253 is used to signify that the fallback is not found, but may
mean in the future that it is otherwise invalid if we start being more
specific.
Differential Revision: https://phab.mercurial-scm.org/D12578
author | Raphaël Gomès <rgomes@octobus.net> |
---|---|
date | Tue, 19 Apr 2022 12:27:40 +0200 |
parents | f19be290756a |
children | a932cad26d37 |
comparison
equal
deleted
inserted
replaced
49148:db3f8e5cc965 | 49149:006688e36e12 |
---|---|
11 use hg::repo::{Repo, RepoError}; | 11 use hg::repo::{Repo, RepoError}; |
12 use hg::utils::files::{get_bytes_from_os_str, get_path_from_bytes}; | 12 use hg::utils::files::{get_bytes_from_os_str, get_path_from_bytes}; |
13 use hg::utils::SliceExt; | 13 use hg::utils::SliceExt; |
14 use std::collections::HashSet; | 14 use std::collections::HashSet; |
15 use std::ffi::OsString; | 15 use std::ffi::OsString; |
16 use std::os::unix::prelude::CommandExt; | |
16 use std::path::PathBuf; | 17 use std::path::PathBuf; |
17 use std::process::Command; | 18 use std::process::Command; |
18 | 19 |
19 mod blackbox; | 20 mod blackbox; |
20 mod color; | 21 mod color; |
363 } else { | 364 } else { |
364 exit_codes::ABORT | 365 exit_codes::ABORT |
365 } | 366 } |
366 } | 367 } |
367 Err(CommandError::Unsuccessful) => exit_codes::UNSUCCESSFUL, | 368 Err(CommandError::Unsuccessful) => exit_codes::UNSUCCESSFUL, |
368 | |
369 // Exit with a specific code and no error message to let a potential | 369 // Exit with a specific code and no error message to let a potential |
370 // wrapper script fallback to Python-based Mercurial. | 370 // wrapper script fallback to Python-based Mercurial. |
371 Err(CommandError::UnsupportedFeature { .. }) => { | 371 Err(CommandError::UnsupportedFeature { .. }) => { |
372 exit_codes::UNIMPLEMENTED | 372 exit_codes::UNIMPLEMENTED |
373 } | |
374 Err(CommandError::InvalidFallback { .. }) => { | |
375 exit_codes::INVALID_FALLBACK | |
373 } | 376 } |
374 } | 377 } |
375 } | 378 } |
376 | 379 |
377 fn exit( | 380 fn exit( |
413 )); | 416 )); |
414 on_unsupported = OnUnsupported::Abort | 417 on_unsupported = OnUnsupported::Abort |
415 } else { | 418 } else { |
416 log::debug!("falling back (see trace-level log)"); | 419 log::debug!("falling back (see trace-level log)"); |
417 log::trace!("{}", local_to_utf8(message)); | 420 log::trace!("{}", local_to_utf8(message)); |
421 if let Err(err) = which::which(executable_path) { | |
422 exit_no_fallback( | |
423 ui, | |
424 OnUnsupported::Abort, | |
425 Err(CommandError::InvalidFallback { | |
426 path: executable.to_owned(), | |
427 err: err.to_string(), | |
428 }), | |
429 use_detailed_exit_code, | |
430 ) | |
431 } | |
418 // `args` is now `argv[1..]` since we’ve already consumed | 432 // `args` is now `argv[1..]` since we’ve already consumed |
419 // `argv[0]` | 433 // `argv[0]` |
420 let mut command = Command::new(executable_path); | 434 let mut command = Command::new(executable_path); |
421 command.args(args); | 435 command.args(args); |
422 if let Some(initial) = initial_current_dir { | 436 if let Some(initial) = initial_current_dir { |
423 command.current_dir(initial); | 437 command.current_dir(initial); |
424 } | 438 } |
425 let result = command.status(); | 439 // We don't use subprocess because proper signal handling is harder |
426 match result { | 440 // and we don't want to keep `rhg` around after a fallback anyway. |
427 Ok(status) => std::process::exit( | 441 // For example, if `rhg` is run in the background and falls back to |
428 status.code().unwrap_or(exit_codes::ABORT), | 442 // `hg` which, in turn, waits for a signal, we'll get stuck if |
429 ), | 443 // we're doing plain subprocess. |
430 Err(error) => { | 444 // |
431 let _ = ui.write_stderr(&format_bytes!( | 445 // If `exec` returns, we can only assume our process is very broken |
432 b"tried to fall back to a '{}' sub-process but got error {}\n", | 446 // (see its documentation), so only try to forward the error code |
433 executable, format_bytes::Utf8(error) | 447 // when exiting. |
434 )); | 448 let err = command.exec(); |
435 on_unsupported = OnUnsupported::Abort | 449 std::process::exit( |
436 } | 450 err.raw_os_error().unwrap_or(exit_codes::ABORT), |
437 } | 451 ); |
438 } | 452 } |
439 } | 453 } |
440 exit_no_fallback(ui, on_unsupported, result, use_detailed_exit_code) | 454 exit_no_fallback(ui, on_unsupported, result, use_detailed_exit_code) |
441 } | 455 } |
442 | 456 |
469 } | 483 } |
470 OnUnsupported::AbortSilent => {} | 484 OnUnsupported::AbortSilent => {} |
471 OnUnsupported::Fallback { .. } => unreachable!(), | 485 OnUnsupported::Fallback { .. } => unreachable!(), |
472 } | 486 } |
473 } | 487 } |
488 Err(CommandError::InvalidFallback { path, err }) => { | |
489 let _ = ui.write_stderr(&format_bytes!( | |
490 b"abort: invalid fallback '{}': {}\n", | |
491 path, | |
492 err.as_bytes(), | |
493 )); | |
494 } | |
474 } | 495 } |
475 std::process::exit(exit_code(&result, use_detailed_exit_code)) | 496 std::process::exit(exit_code(&result, use_detailed_exit_code)) |
476 } | 497 } |
477 | 498 |
478 macro_rules! subcommands { | 499 macro_rules! subcommands { |