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 {
|
2023-03-08 14:30:48 +08:00
|
|
|
// 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()
|
2023-03-08 14:30:48 +08:00
|
|
|
));
|
|
|
|
} 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 {
|
2023-03-08 14:30:48 +08:00
|
|
|
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-08 14:30:48 +08:00
|
|
|
));
|
2023-03-06 10:38:41 +08:00
|
|
|
return STATUS_CMD_ERROR;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
streams.out.append(L!("\n"));
|
|
|
|
|
|
|
|
STATUS_CMD_OK
|
|
|
|
}
|