fish-shell/fish-rust/src/builtins/realpath.rs

143 lines
4.3 KiB
Rust
Raw Normal View History

2023-03-06 10:38:41 +08:00
//! Implementation of the realpath builtin.
2023-03-08 14:40:47 +08:00
use errno::errno;
2023-03-06 10:38:41 +08:00
use libc::c_int;
use crate::{
ffi::parser_t,
path::path_apply_working_directory,
wchar::{wstr, WExt, L},
wchar_ffi::WCharFromFFI,
wgetopt::{wgetopter_t, wopt, woption, woption_argument_t::no_argument},
wutil::{normalize_path, wgettext_fmt, wrealpath},
};
use super::shared::{
builtin_missing_argument, builtin_print_help, builtin_unknown_option, io_streams_t,
BUILTIN_ERR_ARG_COUNT1, STATUS_CMD_ERROR, STATUS_CMD_OK, STATUS_INVALID_ARGS,
};
#[derive(Default)]
struct Options {
print_help: bool,
no_symlinks: bool,
}
const short_options: &wstr = L!("+:hs");
const long_options: &[woption] = &[
wopt(L!("no-symlinks"), no_argument, 's'),
wopt(L!("help"), no_argument, 'h'),
];
fn parse_options(
args: &mut [&wstr],
parser: &mut parser_t,
streams: &mut io_streams_t,
) -> Result<(Options, usize), Option<c_int>> {
let cmd = args[0];
let mut opts = Options::default();
let mut w = wgetopter_t::new(short_options, long_options, args);
while let Some(c) = w.wgetopt_long() {
match c {
's' => opts.no_symlinks = true,
'h' => opts.print_help = true,
':' => {
builtin_missing_argument(parser, streams, cmd, args[w.woptind - 1], false);
return Err(STATUS_INVALID_ARGS);
}
'?' => {
builtin_unknown_option(parser, streams, cmd, args[w.woptind - 1], false);
return Err(STATUS_INVALID_ARGS);
}
_ => panic!("unexpected retval from wgetopt_long"),
}
}
Ok((opts, w.woptind))
}
/// An implementation of the external realpath command. Doesn't support any options.
/// In general scripts shouldn't invoke this directly. They should just use `realpath` which
/// will fallback to this builtin if an external command cannot be found.
pub fn realpath(
parser: &mut parser_t,
streams: &mut io_streams_t,
args: &mut [&wstr],
) -> Option<c_int> {
let cmd = args[0];
let (opts, optind) = match parse_options(args, parser, streams) {
Ok((opts, optind)) => (opts, optind),
Err(err @ Some(_)) if err != STATUS_CMD_OK => return err,
Err(err) => panic!("Illogical exit code from parse_options(): {err:?}"),
};
if opts.print_help {
builtin_print_help(parser, streams, cmd);
return STATUS_CMD_OK;
}
// TODO: allow arbitrary args. `realpath *` should print many paths
if optind + 1 != args.len() {
streams.err.append(wgettext_fmt!(
BUILTIN_ERR_ARG_COUNT1,
cmd,
0,
args.len() - 1
));
return STATUS_INVALID_ARGS;
}
let arg = args[optind];
if !opts.no_symlinks {
if let Some(real_path) = wrealpath(arg) {
streams.out.append(real_path);
} else {
2023-03-08 14:40:47 +08:00
let errno = errno();
if errno.0 != 0 {
// realpath() just couldn't do it. Report the error and make it clear
// this is an error from our builtin, not the system's realpath.
streams.err.append(wgettext_fmt!(
"builtin %ls: %ls: %s\n",
cmd,
arg,
2023-03-08 14:40:47 +08:00
errno.to_string()
));
} else {
// Who knows. Probably a bug in our wrealpath() implementation.
streams
.err
.append(wgettext_fmt!("builtin %ls: Invalid arg: %ls\n", cmd, arg));
}
2023-03-06 10:38:41 +08:00
return STATUS_CMD_ERROR;
}
} else {
// We need to get the *physical* pwd here.
let realpwd = wrealpath(&parser.vars1().get_pwd_slash().from_ffi());
if let Some(realpwd) = realpwd {
let absolute_arg = if arg.starts_with(L!("/")) {
arg.to_owned()
} else {
path_apply_working_directory(arg, &realpwd)
};
streams.out.append(normalize_path(&absolute_arg, false));
} else {
streams.err.append(wgettext_fmt!(
"builtin %ls: realpath failed: %s\n",
cmd,
2023-03-08 14:40:47 +08:00
errno().to_string()
));
2023-03-06 10:38:41 +08:00
return STATUS_CMD_ERROR;
}
}
streams.out.append(L!("\n"));
STATUS_CMD_OK
}