Fix crash when ':write -method replace' fails to create tempfile

If a user attempts to save a file without write permission for the
containing directory, with writemethod set as 'replace' or an explicit
':write -method replace' command, kak crashes with "terminating due to
uncaught exception of type Kakoune:runtime_error". (Note this doesn't
happen with a forced write, which fails earlier when it tries to enable
u+w permission.)

Don't raise another exception when already bailing out with a runtime
error for failing to create a temporary file or open the existing file.
Instead, make a best-efforts attempt to restore the file permissions
before raising the first exception, and only report the runtime chmod
exception if that step fails on the non-error path.
This commit is contained in:
Chris Webb 2023-11-26 17:50:32 +00:00
parent 990e92a5f3
commit 05bbdb27c9

View File

@ -351,16 +351,16 @@ void write_buffer_to_file(Buffer& buffer, StringView filename,
if (force and ::chmod(zfilename, st.st_mode | S_IWUSR) < 0)
throw runtime_error(format("unable to change file permissions: {}", strerror(errno)));
auto restore_mode = on_scope_end([&]{
if ((force or replace) and ::chmod(zfilename, st.st_mode) < 0)
throw runtime_error(format("unable to restore file permissions: {}", strerror(errno)));
});
char temp_filename[PATH_MAX];
const int fd = replace ? open_temp_file(filename, temp_filename)
: open(zfilename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd == -1)
throw file_access_error(filename, strerror(errno));
{
auto saved_errno = errno;
if (force)
::chmod(zfilename, st.st_mode);
throw file_access_error(filename, strerror(saved_errno));
}
{
auto close_fd = on_scope_end([fd]{ close(fd); });
@ -370,7 +370,14 @@ void write_buffer_to_file(Buffer& buffer, StringView filename,
}
if (replace and rename(temp_filename, zfilename) != 0)
{
if (force)
::chmod(zfilename, st.st_mode);
throw runtime_error("replacing file failed");
}
if ((force or replace) and ::chmod(zfilename, st.st_mode) < 0)
throw runtime_error(format("unable to restore file permissions: {}", strerror(errno)));
if ((buffer.flags() & Buffer::Flags::File) and
real_path(filename) == real_path(buffer.name()))