2023-04-02 01:17:49 +08:00
|
|
|
use crate::builtins::{printf, wait};
|
2023-07-29 19:28:02 +08:00
|
|
|
use crate::common::str2wcstring;
|
2023-06-20 10:28:35 +08:00
|
|
|
use crate::ffi::separation_type_t;
|
2023-04-11 03:19:36 +08:00
|
|
|
use crate::ffi::{self, parser_t, wcstring_list_ffi_t, Repin, RustBuiltin};
|
|
|
|
use crate::wchar::{wstr, WString, L};
|
2023-06-23 02:50:22 +08:00
|
|
|
use crate::wchar_ffi::{c_str, empty_wstring, ToCppWString, WCharFromFFI};
|
2023-02-11 01:22:56 +08:00
|
|
|
use crate::wgetopt::{wgetopter_t, wopt, woption, woption_argument_t};
|
2023-05-15 02:40:18 +08:00
|
|
|
use cxx::{type_id, ExternType};
|
2023-01-16 11:52:08 +08:00
|
|
|
use libc::c_int;
|
2023-08-11 21:26:19 +08:00
|
|
|
use libc::isatty;
|
|
|
|
use libc::STDOUT_FILENO;
|
|
|
|
|
2023-07-29 19:28:02 +08:00
|
|
|
use std::borrow::Cow;
|
|
|
|
use std::fs::File;
|
|
|
|
use std::io::{BufRead, BufReader, Read};
|
|
|
|
use std::os::fd::{FromRawFd, RawFd};
|
2023-01-16 11:52:08 +08:00
|
|
|
use std::pin::Pin;
|
|
|
|
|
2023-07-29 19:28:02 +08:00
|
|
|
pub type BuiltinCmd = fn(&mut parser_t, &mut io_streams_t, &mut [&wstr]) -> Option<c_int>;
|
|
|
|
|
2023-01-16 11:52:08 +08:00
|
|
|
#[cxx::bridge]
|
|
|
|
mod builtins_ffi {
|
|
|
|
extern "C++" {
|
|
|
|
include!("wutil.h");
|
|
|
|
include!("parser.h");
|
|
|
|
include!("builtin.h");
|
|
|
|
|
|
|
|
type wcharz_t = crate::ffi::wcharz_t;
|
2023-04-11 03:19:36 +08:00
|
|
|
type wcstring_list_ffi_t = crate::ffi::wcstring_list_ffi_t;
|
2023-01-16 11:52:08 +08:00
|
|
|
type parser_t = crate::ffi::parser_t;
|
|
|
|
type io_streams_t = crate::ffi::io_streams_t;
|
|
|
|
type RustBuiltin = crate::ffi::RustBuiltin;
|
|
|
|
}
|
|
|
|
extern "Rust" {
|
|
|
|
fn rust_run_builtin(
|
|
|
|
parser: Pin<&mut parser_t>,
|
|
|
|
streams: Pin<&mut io_streams_t>,
|
2023-04-11 03:19:36 +08:00
|
|
|
cpp_args: &wcstring_list_ffi_t,
|
2023-01-16 11:52:08 +08:00
|
|
|
builtin: RustBuiltin,
|
2023-02-06 06:52:58 +08:00
|
|
|
status_code: &mut i32,
|
|
|
|
) -> bool;
|
2023-01-16 11:52:08 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-15 02:40:18 +08:00
|
|
|
unsafe impl ExternType for io_streams_t {
|
|
|
|
type Id = type_id!("io_streams_t");
|
|
|
|
type Kind = cxx::kind::Opaque;
|
|
|
|
}
|
|
|
|
|
2023-02-19 00:13:58 +08:00
|
|
|
/// Error message when too many arguments are supplied to a builtin.
|
|
|
|
pub const BUILTIN_ERR_TOO_MANY_ARGUMENTS: &str = "%ls: too many arguments\n";
|
|
|
|
|
|
|
|
/// Error message when integer expected
|
|
|
|
pub const BUILTIN_ERR_NOT_NUMBER: &str = "%ls: %ls: invalid integer\n";
|
|
|
|
|
2023-06-28 01:05:55 +08:00
|
|
|
pub const BUILTIN_ERR_MISSING_SUBCMD: &str = "%ls: missing subcommand\n";
|
|
|
|
pub const BUILTIN_ERR_INVALID_SUBCMD: &str = "%ls: %ls: invalid subcommand\n";
|
|
|
|
|
2023-06-17 05:38:08 +08:00
|
|
|
/// Error message for unknown switch.
|
|
|
|
pub const BUILTIN_ERR_UNKNOWN: &str = "%ls: %ls: unknown option\n";
|
|
|
|
|
2023-08-18 13:08:31 +08:00
|
|
|
pub const BUILTIN_ERR_MISSING_HELP: &str = "fish: %ls: missing man page\nDocumentation may not be installed.\n`help %ls` will show an online version\n";
|
|
|
|
pub const BUILTIN_ERR_MISSING: &str = "%ls: %ls: option requires an argument\n";
|
|
|
|
|
2023-02-25 04:23:43 +08:00
|
|
|
/// Error messages for unexpected args.
|
|
|
|
pub const BUILTIN_ERR_ARG_COUNT0: &str = "%ls: missing argument\n";
|
2023-03-01 13:05:27 +08:00
|
|
|
pub const BUILTIN_ERR_ARG_COUNT1: &str = "%ls: expected %d arguments; got %d\n";
|
2023-02-25 04:23:43 +08:00
|
|
|
pub const BUILTIN_ERR_ARG_COUNT2: &str = "%ls: %ls: expected %d arguments; got %d\n";
|
|
|
|
pub const BUILTIN_ERR_MIN_ARG_COUNT1: &str = "%ls: expected >= %d arguments; got %d\n";
|
|
|
|
pub const BUILTIN_ERR_MAX_ARG_COUNT1: &str = "%ls: expected <= %d arguments; got %d\n";
|
2023-03-01 13:05:27 +08:00
|
|
|
|
2023-05-15 02:40:18 +08:00
|
|
|
/// Error message for invalid variable name.
|
|
|
|
pub const BUILTIN_ERR_VARNAME: &str = "%ls: %ls: invalid variable name. See `help identifiers`\n";
|
|
|
|
|
2023-02-25 04:23:43 +08:00
|
|
|
/// Error message on invalid combination of options.
|
2023-02-25 04:14:13 +08:00
|
|
|
pub const BUILTIN_ERR_COMBO: &str = "%ls: invalid option combination\n";
|
2023-04-16 17:29:26 +08:00
|
|
|
pub const BUILTIN_ERR_COMBO2: &str = "%ls: invalid option combination, %ls\n";
|
2023-06-28 01:05:55 +08:00
|
|
|
pub const BUILTIN_ERR_COMBO2_EXCLUSIVE: &str = "%ls: %ls %ls: options cannot be used together\n";
|
2023-02-25 04:14:13 +08:00
|
|
|
|
2023-04-09 19:41:35 +08:00
|
|
|
// Return values (`$status` values for fish scripts) for various situations.
|
2023-01-16 11:52:08 +08:00
|
|
|
|
2023-04-09 19:41:35 +08:00
|
|
|
/// The status code used for normal exit in a command.
|
|
|
|
pub const STATUS_CMD_OK: Option<c_int> = Some(0);
|
2023-02-21 01:57:02 +08:00
|
|
|
/// The status code used for failure exit in a command (but not if the args were invalid).
|
|
|
|
pub const STATUS_CMD_ERROR: Option<c_int> = Some(1);
|
2023-04-02 01:17:49 +08:00
|
|
|
/// The status code used for invalid arguments given to a command. This is distinct from valid
|
|
|
|
/// arguments that might result in a command failure. An invalid args condition is something
|
|
|
|
/// like an unrecognized flag, missing or too many arguments, an invalid integer, etc.
|
2023-01-16 11:52:08 +08:00
|
|
|
pub const STATUS_INVALID_ARGS: Option<c_int> = Some(2);
|
|
|
|
|
2023-04-09 19:41:35 +08:00
|
|
|
/// The status code used when a command was not found.
|
|
|
|
pub const STATUS_CMD_UNKNOWN: Option<c_int> = Some(127);
|
|
|
|
|
|
|
|
/// The status code used when an external command can not be run.
|
|
|
|
pub const STATUS_NOT_EXECUTABLE: Option<c_int> = Some(126);
|
|
|
|
|
|
|
|
/// The status code used when a wildcard had no matches.
|
|
|
|
pub const STATUS_UNMATCHED_WILDCARD: Option<c_int> = Some(124);
|
|
|
|
/// The status code used when illegal command name is encountered.
|
|
|
|
pub const STATUS_ILLEGAL_CMD: Option<c_int> = Some(123);
|
|
|
|
/// The status code used when `read` is asked to consume too much data.
|
|
|
|
pub const STATUS_READ_TOO_MUCH: Option<c_int> = Some(122);
|
|
|
|
/// The status code when an expansion fails, for example, "$foo["
|
|
|
|
pub const STATUS_EXPAND_ERROR: Option<c_int> = Some(121);
|
|
|
|
|
2023-01-16 11:52:08 +08:00
|
|
|
/// A wrapper around output_stream_t.
|
|
|
|
pub struct output_stream_t(*mut ffi::output_stream_t);
|
|
|
|
|
|
|
|
impl output_stream_t {
|
|
|
|
/// \return the underlying output_stream_t.
|
|
|
|
fn ffi(&mut self) -> Pin<&mut ffi::output_stream_t> {
|
|
|
|
unsafe { (*self.0).pin() }
|
|
|
|
}
|
|
|
|
|
2023-06-23 02:50:22 +08:00
|
|
|
/// Append a &wstr or WString.
|
2023-01-16 11:52:08 +08:00
|
|
|
pub fn append<Str: AsRef<wstr>>(&mut self, s: Str) -> bool {
|
2023-06-23 02:50:22 +08:00
|
|
|
self.ffi().append(&s.as_ref().into_cpp())
|
2023-01-16 11:52:08 +08:00
|
|
|
}
|
2023-04-02 01:17:49 +08:00
|
|
|
|
2023-08-14 05:14:40 +08:00
|
|
|
/// Append a &wstr or WString with a newline
|
|
|
|
pub fn appendln(&mut self, s: impl Into<WString>) -> bool {
|
|
|
|
let s = s.into() + L!("\n");
|
|
|
|
self.ffi().append(&s.into_cpp())
|
|
|
|
}
|
|
|
|
|
2023-04-02 01:17:49 +08:00
|
|
|
/// Append a char.
|
|
|
|
pub fn append1(&mut self, c: char) -> bool {
|
|
|
|
self.append(wstr::from_char_slice(&[c]))
|
|
|
|
}
|
2023-06-20 10:28:35 +08:00
|
|
|
|
|
|
|
pub fn append_with_separation(
|
|
|
|
&mut self,
|
|
|
|
s: impl AsRef<wstr>,
|
|
|
|
sep: separation_type_t,
|
|
|
|
want_newline: bool,
|
|
|
|
) -> bool {
|
|
|
|
self.ffi()
|
|
|
|
.append_with_separation(&s.as_ref().into_cpp(), sep, want_newline)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn flush_and_check_error(&mut self) -> c_int {
|
|
|
|
self.ffi().flush_and_check_error().into()
|
|
|
|
}
|
2023-01-16 11:52:08 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Convenience wrappers around C++ io_streams_t.
|
|
|
|
pub struct io_streams_t {
|
|
|
|
streams: *mut builtins_ffi::io_streams_t,
|
|
|
|
pub out: output_stream_t,
|
|
|
|
pub err: output_stream_t,
|
2023-04-11 01:49:50 +08:00
|
|
|
pub out_is_redirected: bool,
|
2023-05-15 05:48:17 +08:00
|
|
|
pub err_is_redirected: bool,
|
2023-01-16 11:52:08 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
impl io_streams_t {
|
2023-02-13 00:23:46 +08:00
|
|
|
pub fn new(mut streams: Pin<&mut builtins_ffi::io_streams_t>) -> io_streams_t {
|
2023-01-16 11:52:08 +08:00
|
|
|
let out = output_stream_t(streams.as_mut().get_out().unpin());
|
|
|
|
let err = output_stream_t(streams.as_mut().get_err().unpin());
|
2023-04-11 01:49:50 +08:00
|
|
|
let out_is_redirected = streams.as_mut().get_out_redirected();
|
2023-05-15 05:48:17 +08:00
|
|
|
let err_is_redirected = streams.as_mut().get_err_redirected();
|
2023-01-16 11:52:08 +08:00
|
|
|
let streams = streams.unpin();
|
2023-04-11 01:49:50 +08:00
|
|
|
io_streams_t {
|
|
|
|
streams,
|
|
|
|
out,
|
|
|
|
err,
|
|
|
|
out_is_redirected,
|
2023-05-15 05:48:17 +08:00
|
|
|
err_is_redirected,
|
2023-04-11 01:49:50 +08:00
|
|
|
}
|
2023-01-16 11:52:08 +08:00
|
|
|
}
|
|
|
|
|
2023-02-13 00:23:46 +08:00
|
|
|
pub fn ffi_pin(&mut self) -> Pin<&mut builtins_ffi::io_streams_t> {
|
2023-01-16 11:52:08 +08:00
|
|
|
unsafe { Pin::new_unchecked(&mut *self.streams) }
|
|
|
|
}
|
|
|
|
|
2023-02-13 00:23:46 +08:00
|
|
|
pub fn ffi_ref(&self) -> &builtins_ffi::io_streams_t {
|
2023-01-16 11:52:08 +08:00
|
|
|
unsafe { &*self.streams }
|
|
|
|
}
|
2023-02-25 18:16:55 +08:00
|
|
|
|
2023-08-11 21:26:19 +08:00
|
|
|
pub fn out_is_terminal(&self) -> bool {
|
|
|
|
!self.out_is_redirected && unsafe { isatty(STDOUT_FILENO) == 1 }
|
|
|
|
}
|
|
|
|
|
2023-02-25 18:16:55 +08:00
|
|
|
pub fn stdin_is_directly_redirected(&self) -> bool {
|
|
|
|
self.ffi_ref().ffi_stdin_is_directly_redirected()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn stdin_fd(&self) -> Option<RawFd> {
|
|
|
|
let ret = self.ffi_ref().ffi_stdin_fd().0;
|
|
|
|
|
|
|
|
if ret < 0 {
|
|
|
|
None
|
|
|
|
} else {
|
|
|
|
Some(ret)
|
|
|
|
}
|
|
|
|
}
|
2023-01-16 11:52:08 +08:00
|
|
|
}
|
|
|
|
|
2023-05-15 02:40:18 +08:00
|
|
|
/// Helper function to convert Vec<WString> to Vec<&wstr>, truncating on NUL.
|
|
|
|
/// We truncate on NUL for backwards-compatibility reasons.
|
|
|
|
/// This used to be passed as c-strings (`wchar_t *`),
|
|
|
|
/// and so we act like it for now.
|
|
|
|
pub fn truncate_args_on_nul(args: &[WString]) -> Vec<&wstr> {
|
|
|
|
args.iter()
|
|
|
|
.map(|s| match s.chars().position(|c| c == '\0') {
|
|
|
|
Some(i) => &s[..i],
|
|
|
|
None => &s[..],
|
|
|
|
})
|
|
|
|
.collect()
|
|
|
|
}
|
|
|
|
|
2023-01-16 11:52:08 +08:00
|
|
|
fn rust_run_builtin(
|
|
|
|
parser: Pin<&mut parser_t>,
|
|
|
|
streams: Pin<&mut builtins_ffi::io_streams_t>,
|
2023-04-11 03:19:36 +08:00
|
|
|
cpp_args: &wcstring_list_ffi_t,
|
2023-01-16 11:52:08 +08:00
|
|
|
builtin: RustBuiltin,
|
2023-02-06 06:52:58 +08:00
|
|
|
status_code: &mut i32,
|
|
|
|
) -> bool {
|
2023-04-11 03:19:36 +08:00
|
|
|
let storage: Vec<WString> = cpp_args.from_ffi();
|
2023-05-15 02:40:18 +08:00
|
|
|
let mut args: Vec<&wstr> = truncate_args_on_nul(&storage);
|
2023-01-16 11:52:08 +08:00
|
|
|
let streams = &mut io_streams_t::new(streams);
|
2023-02-06 06:52:58 +08:00
|
|
|
|
|
|
|
match run_builtin(parser.unpin(), streams, args.as_mut_slice(), builtin) {
|
|
|
|
None => false,
|
|
|
|
Some(status) => {
|
|
|
|
*status_code = status;
|
|
|
|
true
|
|
|
|
}
|
|
|
|
}
|
2023-01-16 11:52:08 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn run_builtin(
|
|
|
|
parser: &mut parser_t,
|
|
|
|
streams: &mut io_streams_t,
|
|
|
|
args: &mut [&wstr],
|
|
|
|
builtin: RustBuiltin,
|
|
|
|
) -> Option<c_int> {
|
|
|
|
match builtin {
|
2023-02-25 00:00:05 +08:00
|
|
|
RustBuiltin::Abbr => super::abbr::abbr(parser, streams, args),
|
2023-06-17 05:38:08 +08:00
|
|
|
RustBuiltin::Argparse => super::argparse::argparse(parser, streams, args),
|
2023-03-01 06:42:12 +08:00
|
|
|
RustBuiltin::Bg => super::bg::bg(parser, streams, args),
|
2023-02-25 03:21:27 +08:00
|
|
|
RustBuiltin::Block => super::block::block(parser, streams, args),
|
2023-04-16 17:29:26 +08:00
|
|
|
RustBuiltin::Builtin => super::builtin::builtin(parser, streams, args),
|
2023-04-23 17:43:57 +08:00
|
|
|
RustBuiltin::Cd => super::cd::cd(parser, streams, args),
|
2023-02-21 01:57:02 +08:00
|
|
|
RustBuiltin::Contains => super::contains::contains(parser, streams, args),
|
2023-04-15 00:12:46 +08:00
|
|
|
RustBuiltin::Command => super::command::command(parser, streams, args),
|
2023-08-19 05:18:52 +08:00
|
|
|
RustBuiltin::Count => super::count::count(parser, streams, args),
|
2023-02-06 05:08:32 +08:00
|
|
|
RustBuiltin::Echo => super::echo::echo(parser, streams, args),
|
2023-02-11 01:19:22 +08:00
|
|
|
RustBuiltin::Emit => super::emit::emit(parser, streams, args),
|
2023-02-19 00:13:58 +08:00
|
|
|
RustBuiltin::Exit => super::exit::exit(parser, streams, args),
|
2023-08-09 02:12:05 +08:00
|
|
|
RustBuiltin::Functions => super::functions::functions(parser, streams, args),
|
2023-04-15 19:40:38 +08:00
|
|
|
RustBuiltin::Math => super::math::math(parser, streams, args),
|
2023-07-29 19:28:02 +08:00
|
|
|
RustBuiltin::Path => super::path::path(parser, streams, args),
|
2023-03-01 13:05:27 +08:00
|
|
|
RustBuiltin::Pwd => super::pwd::pwd(parser, streams, args),
|
2023-02-19 05:06:05 +08:00
|
|
|
RustBuiltin::Random => super::random::random(parser, streams, args),
|
2023-03-06 10:38:41 +08:00
|
|
|
RustBuiltin::Realpath => super::realpath::realpath(parser, streams, args),
|
2023-02-19 00:13:58 +08:00
|
|
|
RustBuiltin::Return => super::r#return::r#return(parser, streams, args),
|
2023-05-30 07:39:44 +08:00
|
|
|
RustBuiltin::SetColor => super::set_color::set_color(parser, streams, args),
|
2023-06-28 01:05:55 +08:00
|
|
|
RustBuiltin::Status => super::status::status(parser, streams, args),
|
2023-06-20 10:28:35 +08:00
|
|
|
RustBuiltin::String => super::string::string(parser, streams, args),
|
2023-05-21 08:46:27 +08:00
|
|
|
RustBuiltin::Test => super::test::test(parser, streams, args),
|
2023-02-25 04:14:13 +08:00
|
|
|
RustBuiltin::Type => super::r#type::r#type(parser, streams, args),
|
2023-01-16 11:52:08 +08:00
|
|
|
RustBuiltin::Wait => wait::wait(parser, streams, args),
|
2023-04-02 01:17:49 +08:00
|
|
|
RustBuiltin::Printf => printf::printf(parser, streams, args),
|
2023-01-16 11:52:08 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Covers of these functions that take care of the pinning, etc.
|
|
|
|
// These all return STATUS_INVALID_ARGS.
|
|
|
|
pub fn builtin_missing_argument(
|
|
|
|
parser: &mut parser_t,
|
|
|
|
streams: &mut io_streams_t,
|
|
|
|
cmd: &wstr,
|
|
|
|
opt: &wstr,
|
|
|
|
print_hints: bool,
|
|
|
|
) {
|
|
|
|
ffi::builtin_missing_argument(
|
|
|
|
parser.pin(),
|
|
|
|
streams.ffi_pin(),
|
|
|
|
c_str!(cmd),
|
|
|
|
c_str!(opt),
|
|
|
|
print_hints,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn builtin_unknown_option(
|
|
|
|
parser: &mut parser_t,
|
|
|
|
streams: &mut io_streams_t,
|
|
|
|
cmd: &wstr,
|
|
|
|
opt: &wstr,
|
|
|
|
print_hints: bool,
|
|
|
|
) {
|
2023-02-05 05:28:30 +08:00
|
|
|
ffi::builtin_unknown_option(
|
2023-01-16 11:52:08 +08:00
|
|
|
parser.pin(),
|
|
|
|
streams.ffi_pin(),
|
|
|
|
c_str!(cmd),
|
|
|
|
c_str!(opt),
|
|
|
|
print_hints,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn builtin_print_help(parser: &mut parser_t, streams: &io_streams_t, cmd: &wstr) {
|
|
|
|
ffi::builtin_print_help(
|
|
|
|
parser.pin(),
|
|
|
|
streams.ffi_ref(),
|
|
|
|
c_str!(cmd),
|
|
|
|
empty_wstring(),
|
|
|
|
);
|
|
|
|
}
|
2023-02-11 01:22:56 +08:00
|
|
|
|
2023-02-19 00:13:58 +08:00
|
|
|
pub fn builtin_print_error_trailer(parser: &mut parser_t, streams: &mut io_streams_t, cmd: &wstr) {
|
|
|
|
ffi::builtin_print_error_trailer(parser.pin(), streams.err.ffi(), c_str!(cmd));
|
|
|
|
}
|
|
|
|
|
2023-02-11 01:22:56 +08:00
|
|
|
pub struct HelpOnlyCmdOpts {
|
|
|
|
pub print_help: bool,
|
|
|
|
pub optind: usize,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl HelpOnlyCmdOpts {
|
|
|
|
pub fn parse(
|
|
|
|
args: &mut [&wstr],
|
|
|
|
parser: &mut parser_t,
|
|
|
|
streams: &mut io_streams_t,
|
|
|
|
) -> Result<Self, Option<c_int>> {
|
|
|
|
let cmd = args[0];
|
|
|
|
let print_hints = true;
|
|
|
|
|
|
|
|
const shortopts: &wstr = L!("+:h");
|
|
|
|
const longopts: &[woption] = &[wopt(L!("help"), woption_argument_t::no_argument, 'h')];
|
|
|
|
|
|
|
|
let mut print_help = false;
|
|
|
|
let mut w = wgetopter_t::new(shortopts, longopts, args);
|
|
|
|
while let Some(c) = w.wgetopt_long() {
|
|
|
|
match c {
|
|
|
|
'h' => {
|
|
|
|
print_help = true;
|
|
|
|
}
|
|
|
|
':' => {
|
|
|
|
builtin_missing_argument(
|
|
|
|
parser,
|
|
|
|
streams,
|
|
|
|
cmd,
|
|
|
|
args[w.woptind - 1],
|
|
|
|
print_hints,
|
|
|
|
);
|
|
|
|
return Err(STATUS_INVALID_ARGS);
|
|
|
|
}
|
|
|
|
'?' => {
|
|
|
|
builtin_unknown_option(parser, streams, cmd, args[w.woptind - 1], print_hints);
|
|
|
|
return Err(STATUS_INVALID_ARGS);
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
panic!("unexpected retval from wgetopter::wgetopt_long()");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(HelpOnlyCmdOpts {
|
|
|
|
print_help,
|
|
|
|
optind: w.woptind,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2023-07-29 19:28:02 +08:00
|
|
|
|
|
|
|
#[derive(PartialEq)]
|
|
|
|
pub enum SplitBehavior {
|
|
|
|
Newline,
|
|
|
|
/// The default behavior of the -z or --null-in switch,
|
|
|
|
/// Automatically start splitting on NULL if one appears in the first PATH_MAX bytes.
|
|
|
|
/// Otherwise on newline
|
|
|
|
InferNull,
|
|
|
|
Null,
|
|
|
|
Never,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A helper type for extracting arguments from either argv or stdin.
|
|
|
|
pub struct Arguments<'args, 'iter> {
|
|
|
|
/// The list of arguments passed to the string builtin.
|
|
|
|
args: &'iter [&'args wstr],
|
|
|
|
/// If using argv, index of the next argument to return.
|
|
|
|
argidx: &'iter mut usize,
|
|
|
|
split_behavior: SplitBehavior,
|
|
|
|
/// Buffer to store what we read with the BufReader
|
|
|
|
/// Is only here to avoid allocating every time
|
|
|
|
buffer: Vec<u8>,
|
|
|
|
/// If not using argv, we read with a buffer
|
|
|
|
reader: Option<BufReader<File>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Drop for Arguments<'_, '_> {
|
|
|
|
fn drop(&mut self) {
|
|
|
|
if let Some(r) = self.reader.take() {
|
|
|
|
// we should not close stdin
|
|
|
|
std::mem::forget(r.into_inner());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'args, 'iter> Arguments<'args, 'iter> {
|
|
|
|
pub fn new(
|
|
|
|
args: &'iter [&'args wstr],
|
|
|
|
argidx: &'iter mut usize,
|
|
|
|
streams: &mut io_streams_t,
|
|
|
|
chunk_size: usize,
|
|
|
|
) -> Self {
|
|
|
|
let reader = streams.stdin_is_directly_redirected().then(|| {
|
|
|
|
let stdin_fd = streams
|
|
|
|
.stdin_fd()
|
|
|
|
.filter(|&fd| fd >= 0)
|
|
|
|
.expect("should have a valid fd");
|
|
|
|
// safety: this should be a valid fd, and already open
|
|
|
|
let fd = unsafe { File::from_raw_fd(stdin_fd) };
|
|
|
|
BufReader::with_capacity(chunk_size, fd)
|
|
|
|
});
|
|
|
|
|
|
|
|
Arguments {
|
|
|
|
args,
|
|
|
|
argidx,
|
|
|
|
split_behavior: SplitBehavior::Newline,
|
|
|
|
buffer: Vec::new(),
|
|
|
|
reader,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn with_split_behavior(mut self, split_behavior: SplitBehavior) -> Self {
|
|
|
|
self.split_behavior = split_behavior;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_arg_stdin(&mut self) -> Option<(Cow<'args, wstr>, bool)> {
|
|
|
|
use SplitBehavior::*;
|
|
|
|
let reader = self.reader.as_mut().unwrap();
|
|
|
|
|
|
|
|
if self.split_behavior == InferNull {
|
|
|
|
// we must determine if the first `PATH_MAX` bytes contains a null.
|
|
|
|
// we intentionally do not consume the buffer here
|
|
|
|
// the contents will be returned again later
|
|
|
|
let b = reader.fill_buf().ok()?;
|
|
|
|
if b.contains(&b'\0') {
|
|
|
|
self.split_behavior = Null;
|
|
|
|
} else {
|
|
|
|
self.split_behavior = Newline;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// NOTE: C++ wrongly commented that read_blocked retries for EAGAIN
|
|
|
|
let num_bytes: usize = match self.split_behavior {
|
|
|
|
Newline => reader.read_until(b'\n', &mut self.buffer),
|
|
|
|
Null => reader.read_until(b'\0', &mut self.buffer),
|
|
|
|
Never => reader.read_to_end(&mut self.buffer),
|
|
|
|
_ => unreachable!(),
|
|
|
|
}
|
|
|
|
.ok()?;
|
|
|
|
|
|
|
|
// to match behaviour of earlier versions
|
|
|
|
if num_bytes == 0 {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
|
|
|
// assert!(num_bytes == self.buffer.len());
|
|
|
|
let (end, want_newline) = match (&self.split_behavior, self.buffer.last().unwrap()) {
|
|
|
|
// remove the newline — consumers do not expect it
|
|
|
|
(Newline, b'\n') => (num_bytes - 1, true),
|
|
|
|
// we are missing a trailing newline!
|
|
|
|
(Newline, _) => (num_bytes, false),
|
|
|
|
// consumers do not expect to deal with the null
|
|
|
|
// "want_newline" is not currently relevant for Null
|
|
|
|
(Null, b'\0') => (num_bytes - 1, false),
|
|
|
|
// we are missing a null!
|
|
|
|
(Null, _) => (num_bytes, false),
|
|
|
|
(Never, _) => (num_bytes, false),
|
|
|
|
_ => unreachable!(),
|
|
|
|
};
|
|
|
|
|
|
|
|
let parsed = str2wcstring(&self.buffer[..end]);
|
|
|
|
|
|
|
|
let retval = Some((Cow::Owned(parsed), want_newline));
|
|
|
|
self.buffer.clear();
|
|
|
|
retval
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'args> Iterator for Arguments<'args, '_> {
|
|
|
|
// second is want_newline
|
|
|
|
// If not set, we have consumed all of stdin and its last line is missing a newline character.
|
|
|
|
// This is an edge case -- we expect text input, which is conventionally terminated by a
|
|
|
|
// newline character. But if it isn't, we use this to avoid creating one out of thin air,
|
|
|
|
// to not corrupt input data.
|
|
|
|
type Item = (Cow<'args, wstr>, bool);
|
|
|
|
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
|
|
if self.reader.is_some() {
|
|
|
|
return self.get_arg_stdin();
|
|
|
|
}
|
|
|
|
|
|
|
|
if *self.argidx >= self.args.len() {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
let retval = (Cow::Borrowed(self.args[*self.argidx]), true);
|
|
|
|
*self.argidx += 1;
|
|
|
|
return Some(retval);
|
|
|
|
}
|
|
|
|
}
|