From 40e3614cf458a982c783e899143bef81736faa27 Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Tue, 20 Jul 2021 22:06:57 +1000 Subject: [PATCH] Prevent overwriting existing file in :write Add a -force (equivalent to w!) switch that enables overwriting. --- doc/pages/commands.asciidoc | 6 +++++- src/commands.cc | 10 ++++++++-- src/file.cc | 7 +++++++ src/file.hh | 1 + test/run | 6 +++--- 5 files changed, 24 insertions(+), 6 deletions(-) diff --git a/doc/pages/commands.asciidoc b/doc/pages/commands.asciidoc index 84ba0493..3a1ec818 100644 --- a/doc/pages/commands.asciidoc +++ b/doc/pages/commands.asciidoc @@ -58,13 +58,17 @@ of the file onto the filesystem Otherwise, does nothing. -*write[!]* [-sync] [-method ] []:: +*write[!]* [-force] [-sync] [-method ] []:: *alias* w + write buffer to or use its name if filename is not given. If the file is write-protected, its permissions are temporarily changed to allow saving the buffer and restored afterwards when the write! command is used. + *-force*::: + Equivalent to `!`, allow overwiting existing files if `` + is given and set permissions temporarily if necessary. + *-sync*::: Synchronise the filesystem after the write diff --git a/src/commands.cc b/src/commands.cc index 3a805756..99859127 100644 --- a/src/commands.cc +++ b/src/commands.cc @@ -460,6 +460,7 @@ const ParameterDesc write_params{ { { "sync", { false, "force the synchronization of the file onto the filesystem" } }, { "method", { true, "explicit writemethod (replace|overwrite)" } }, + { "force", { false, "Allow overwriting existing file with explicit filename" } }, }, ParameterDesc::Flags::SwitchesOnlyAtStart, 0, 1 }; @@ -488,7 +489,12 @@ void do_write_buffer(Context& context, Optional filename, WriteFlags fla (not filename or real_path(*filename) == buffer.name())) throw runtime_error("cannot overwrite the buffer when in readonly mode"); - auto effective_filename = not filename ? buffer.name() : parse_filename(*filename); + auto effective_filename = filename ? parse_filename(*filename) : buffer.name(); + if (filename and not (flags & WriteFlags::Force) and + real_path(effective_filename) != buffer.name() and + regular_file_exists(effective_filename)) + throw runtime_error("cannot overwrite existing file without -force"); + auto method = write_method.value_or_compute([&] { return context.options()["writemethod"].get(); }); context.hooks().run_hook(Hook::BufWritePre, effective_filename, context); @@ -502,7 +508,7 @@ void write_buffer(const ParametersParser& parser, Context& context, const ShellC return do_write_buffer(context, parser.positional_count() > 0 ? parser[0] : Optional{}, (parser.get_switch("sync") ? WriteFlags::Sync : WriteFlags::None) | - (force ? WriteFlags::Force : WriteFlags::None), + (parser.get_switch("force") or force ? WriteFlags::Force : WriteFlags::None), parser.get_switch("method").map(parse_write_method)); } diff --git a/src/file.cc b/src/file.cc index 89dee9a3..4c7658be 100644 --- a/src/file.cc +++ b/src/file.cc @@ -247,6 +247,13 @@ bool file_exists(StringView filename) return stat(filename.zstr(), &st) == 0; } +bool regular_file_exists(StringView filename) +{ + struct stat st; + return stat(filename.zstr(), &st) == 0 and + (st.st_mode & S_IFMT) == S_IFREG; +} + void write(int fd, StringView data) { const char* ptr = data.data(); diff --git a/src/file.hh b/src/file.hh index aa32b01d..849b14f4 100644 --- a/src/file.hh +++ b/src/file.hh @@ -81,6 +81,7 @@ void write_buffer_to_backup_file(Buffer& buffer); String find_file(StringView filename, StringView buf_dir, ConstArrayView paths); bool file_exists(StringView filename); +bool regular_file_exists(StringView filename); Vector list_files(StringView directory); diff --git a/test/run b/test/run index 762453fc..492fd6f6 100755 --- a/test/run +++ b/test/run @@ -16,7 +16,7 @@ main() { try %{ source rc } hook global RuntimeError .+ %{ echo -debug -- error: %val{hook_param} - eval -buffer *debug* write debug + eval -buffer *debug* write -force debug quit! } try %{ exec -with-maps -with-hooks "%sh{cat cmd}" } @@ -151,14 +151,14 @@ show_diff() { finished_commands() { printf %s 'eval -client client0 %{ - eval -buffer *debug* write debug + eval -buffer *debug* write -force debug nop %sh{ ' for env_var in $env_vars; do printf 'printf %%s\\\\n "$%s" >%s\n' "$env_var" "$env_var" done printf %s ' } - write out + write -force out quit! } '