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 {