From 51ab59cd3624476832272112ae5814fecef12f69 Mon Sep 17 00:00:00 2001 From: Frank LENORMAND Date: Wed, 19 Apr 2017 18:47:07 +0300 Subject: [PATCH] src: Implement a `write!` command This commit allows "forced" writes to a write-protected file, by attempting to temporarily grant the current user write permissions on it. After the buffer has been written, the previous permissions are restored if the file existed, or set to 0644 otherwise. --- README.asciidoc | 6 ++++-- doc/manpages/commands.asciidoc | 7 +++++-- src/commands.cc | 16 +++++++++++++++- src/file.cc | 22 ++++++++++++++++++++-- src/file.hh | 2 +- 5 files changed, 45 insertions(+), 8 deletions(-) diff --git a/README.asciidoc b/README.asciidoc index 03ba5eaa..628ccf88 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -676,8 +676,10 @@ command `q!` has to be used). * `e[dit][!] [ []]`: open buffer on file, go to given line and column. If file is already opened, just switch to this file. use edit! to force reloading. - * `w[rite] []`: write buffer to or use it's name if - filename is not given. + * `w[rite][!] []`: write buffer to or use it's 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. * `w[rite]a[ll]`: write all buffers that are associated to a file. * `q[uit][!]`: exit Kakoune, use quit! to force quitting even if there is some unsaved buffers remaining. diff --git a/doc/manpages/commands.asciidoc b/doc/manpages/commands.asciidoc index 47a2b18a..cb20a832 100644 --- a/doc/manpages/commands.asciidoc +++ b/doc/manpages/commands.asciidoc @@ -24,8 +24,11 @@ command *q!* has to be used). open buffer on file, go to given line and column. If file is already opened, just switch to this file. Use edit! to force reloading -*w[rite]* []:: - write buffer to or use it's name if filename is not given +*w[rite][!]* []:: + write buffer to or use it's 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. *w[rite]a[ll]*:: write all buffers that are associated to a file diff --git a/src/commands.cc b/src/commands.cc index 6143179d..355f560d 100644 --- a/src/commands.cc +++ b/src/commands.cc @@ -309,6 +309,7 @@ const CommandDesc force_edit_cmd = { edit }; +template void write_buffer(const ParametersParser& parser, Context& context, const ShellContext&) { Buffer& buffer = context.buffer(); @@ -326,7 +327,7 @@ void write_buffer(const ParametersParser& parser, Context& context, const ShellC buffer.name() : parse_filename(parser[0]); context.hooks().run_hook("BufWritePre", filename, context); - write_buffer_to_file(buffer, filename); + write_buffer_to_file(buffer, filename, force); context.hooks().run_hook("BufWritePost", filename, context); } @@ -342,6 +343,18 @@ const CommandDesc write_cmd = { write_buffer, }; +const CommandDesc force_write_cmd = { + "write!", + "w!", + "write [filename]: write the current buffer to its file " + "or to [filename] if specified, even when the file is write protected", + single_optional_param, + CommandFlags::None, + CommandHelper{}, + filename_completer, + write_buffer, +}; + void write_all_buffers(Context& context) { // Copy buffer list because hooks might be creating/deleting buffers @@ -2102,6 +2115,7 @@ void register_commands() register_command(edit_cmd); register_command(force_edit_cmd); register_command(write_cmd); + register_command(force_write_cmd); register_command(write_all_cmd); register_command(write_all_quit_cmd); register_command(kill_cmd); diff --git a/src/file.cc b/src/file.cc index bf592689..49fa00e5 100644 --- a/src/file.cc +++ b/src/file.cc @@ -278,9 +278,27 @@ void write_buffer_to_fd(Buffer& buffer, int fd) } } -void write_buffer_to_file(Buffer& buffer, StringView filename) +void write_buffer_to_file(Buffer& buffer, StringView filename, bool force) { - int fd = open(filename.zstr(), O_CREAT | O_WRONLY | O_TRUNC, 0644); + struct stat st; + auto zfilename = filename.zstr(); + + if (force) + { + if (::stat(zfilename, &st) == 0) + { + if (::chmod(zfilename, st.st_mode | S_IWUSR) < 0) + throw runtime_error("couldn't change file permissions"); + } + else + force = false; + } + auto restore_mode = on_scope_end([&]{ + if (force and ::chmod(zfilename, st.st_mode) < 0) + throw runtime_error("couldn't restore file permissions"); + }); + + int fd = open(zfilename, O_CREAT | O_WRONLY | O_TRUNC, 0644); if (fd == -1) throw file_access_error(filename, strerror(errno)); diff --git a/src/file.hh b/src/file.hh index 7cb5f88c..23fbe0a2 100644 --- a/src/file.hh +++ b/src/file.hh @@ -49,7 +49,7 @@ struct MappedFile struct stat st {}; }; -void write_buffer_to_file(Buffer& buffer, StringView filename); +void write_buffer_to_file(Buffer& buffer, StringView filename, bool force = false); void write_buffer_to_fd(Buffer& buffer, int fd); void write_buffer_to_backup_file(Buffer& buffer);