Skip to content

Commit

Permalink
feat: implement noclobber option (a.k.a. -C)
Browse files Browse the repository at this point in the history
  • Loading branch information
reubeno committed Dec 15, 2024
1 parent 839b803 commit 591d200
Show file tree
Hide file tree
Showing 6 changed files with 63 additions and 14 deletions.
41 changes: 27 additions & 14 deletions brush-core/src/interp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1316,16 +1316,39 @@ pub(crate) async fn setup_redirect(
ast::IoFileRedirectTarget::Filename(f) => {
let mut options = std::fs::File::options();

let mut expanded_fields =
expansion::full_expand_and_split_word(shell, f).await?;

if expanded_fields.len() != 1 {
return Err(error::Error::InvalidRedirection);
}

let expanded_file_path: PathBuf =
shell.get_absolute_path(Path::new(expanded_fields.remove(0).as_str()));

let default_fd_if_unspecified = get_default_fd_for_redirect_kind(kind);
match kind {
ast::IoFileRedirectKind::Read => {
options.read(true);
}
ast::IoFileRedirectKind::Write => {
// TODO: honor noclobber options
options.create(true);
options.write(true);
options.truncate(true);
if shell
.options
.disallow_overwriting_regular_files_via_output_redirection
{
// First check to see if the path points to an existing regular
// file.
if !expanded_file_path.is_file() {
options.create(true);
} else {
options.create_new(true);
}
options.write(true);
} else {
options.create(true);
options.write(true);
options.truncate(true);
}
}
ast::IoFileRedirectKind::Append => {
options.create(true);
Expand All @@ -1352,16 +1375,6 @@ pub(crate) async fn setup_redirect(

fd_num = specified_fd_num.unwrap_or(default_fd_if_unspecified);

let mut expanded_fields =
expansion::full_expand_and_split_word(shell, f).await?;

if expanded_fields.len() != 1 {
return Err(error::Error::InvalidRedirection);
}

let expanded_file_path: PathBuf =
shell.get_absolute_path(Path::new(expanded_fields.remove(0).as_str()));

let opened_file =
options.open(expanded_file_path.as_path()).map_err(|err| {
error::Error::RedirectionFailure(
Expand Down
2 changes: 2 additions & 0 deletions brush-core/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,8 @@ impl RuntimeOptions {
// There's a set of options enabled by default for all shells.
let mut options = Self {
interactive: create_options.interactive,
disallow_overwriting_regular_files_via_output_redirection: create_options
.disallow_overwriting_regular_files_via_output_redirection,
do_not_execute_commands: create_options.do_not_execute_commands,
enable_command_history: create_options.interactive,
enable_job_control: create_options.interactive,
Expand Down
2 changes: 2 additions & 0 deletions brush-core/src/shell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ pub struct CreateOptions {
pub disabled_shopt_options: Vec<String>,
/// Enabled shopt options.
pub enabled_shopt_options: Vec<String>,
/// Disallow overwriting regular files via output redirection.
pub disallow_overwriting_regular_files_via_output_redirection: bool,
/// Do not execute commands.
pub do_not_execute_commands: bool,
/// Whether the shell is interactive.
Expand Down
4 changes: 4 additions & 0 deletions brush-shell/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ pub struct CommandLineArgs {
#[clap(long = "version", action = clap::ArgAction::Version)]
pub version: Option<bool>,

/// Enable noclobber shell option.
#[arg(short = 'C')]
pub disallow_overwriting_regular_files_via_output_redirection: bool,

/// Execute the provided command and then exit.
#[arg(short = 'c', value_name = "COMMAND")]
pub command: Option<String>,
Expand Down
2 changes: 2 additions & 0 deletions brush-shell/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@ async fn instantiate_shell(
let options = brush_interactive::Options {
shell: brush_core::CreateOptions {
disabled_shopt_options: args.disabled_shopt_options.clone(),
disallow_overwriting_regular_files_via_output_redirection: args
.disallow_overwriting_regular_files_via_output_redirection,
enabled_shopt_options: args.enabled_shopt_options.clone(),
do_not_execute_commands: args.do_not_execute_commands,
login: args.login || argv0.as_ref().map_or(false, |a0| a0.starts_with('-')),
Expand Down
26 changes: 26 additions & 0 deletions brush-shell/tests/cases/options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,32 @@ cases:
env | grep newvar
env | grep unexported
- name: "set -C"
ignore_stderr: true
stdin: |
touch existing-file
set -C
echo hi > non-existing-file
echo "Result (non existing): $?"
echo "File contents: $(cat non-existing-file)"
echo
echo hi > /dev/null
echo "Result (device file): $?"
echo
echo hi > existing-file
echo "Result (existing file): $?"
echo "File contents: $(cat existing-file)"
echo
echo hi >| existing-file
echo "Result (clobber): $?"
echo "File contents: $(cat existing-file)"
echo
- name: "set -x"
stdin: |
set -x
Expand Down

0 comments on commit 591d200

Please sign in to comment.