Hello community, here is the log from the commit of package tilde for openSUSE:Factory checked in at 2019-08-23 11:09:42 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/tilde (Old) and /work/SRC/openSUSE:Factory/.tilde.new.7948 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "tilde" Fri Aug 23 11:09:42 2019 rev:8 rq:725410 version:1.0.1 Changes: -------- --- /work/SRC/openSUSE:Factory/tilde/tilde.changes 2019-01-08 12:31:35.932068206 +0100 +++ /work/SRC/openSUSE:Factory/.tilde.new.7948/tilde.changes 2019-08-23 11:09:43.762458490 +0200 @@ -1,0 +2,7 @@ +Thu Aug 22 19:41:51 UTC 2019 - Jan Engelhardt <[email protected]> + +- Update to new upstream release 1.0.1 + * This release rewrites the file writing code such that ownership + and permissions are never accidentally changed. + +------------------------------------------------------------------- Old: ---- tilde-1.0.0.tar.bz2 New: ---- tilde-1.0.1.tar.bz2 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ tilde.spec ++++++ --- /var/tmp/diff_new_pack.DE4RAw/_old 2019-08-23 11:09:44.262458428 +0200 +++ /var/tmp/diff_new_pack.DE4RAw/_new 2019-08-23 11:09:44.266458428 +0200 @@ -17,17 +17,16 @@ Name: tilde -Version: 1.0.0 +Version: 1.0.1 Release: 0 Summary: A text editor for the terminal License: GPL-3.0-only Group: Development/Libraries/C and C++ -Url: http://os.ghalkes.nl/t3/libt3widget.html +URL: https://os.ghalkes.nl/t3/libt3widget.html #Freecode-URL: http://freecode.com/projects/tilde #Git-Clone: git://github.com/gphalkes/tilde -Source: http://os.ghalkes.nl/dist/%name-%version.tar.bz2 -BuildRoot: %{_tmppath}/%{name}-%{version}-build +Source: https://os.ghalkes.nl/dist/%name-%version.tar.bz2 BuildRequires: c++_compiler BuildRequires: gettext-devel BuildRequires: libacl-devel @@ -48,7 +47,7 @@ File menu can be accessed by pressing Alt-F. %prep -%setup -q +%autosetup -p1 %build %configure --docdir="%_docdir/%name" @@ -58,11 +57,10 @@ %make_install %files -%defattr(-,root,root) %_bindir/tilde %_docdir/%name/ %_datadir/%name/ %_mandir/man1/tilde.1* -%doc COPYING +%license COPYING %changelog ++++++ tilde-1.0.0.tar.bz2 -> tilde-1.0.1.tar.bz2 ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tilde-1.0.0/Changelog new/tilde-1.0.1/Changelog --- old/tilde-1.0.0/Changelog 2018-11-25 16:13:18.000000000 +0100 +++ new/tilde-1.0.1/Changelog 2019-05-10 10:10:49.000000000 +0200 @@ -1,3 +1,8 @@ +Version 1.0.1: + Bug fixes: + - Writing of files has been completely re-written to ensure correct + preservation of permissions and ownerships of the file. + Version 1.0.0: This release is the first based on libt3widget 1.0.0. It does not provide new features. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tilde-1.0.0/Makefile.in new/tilde-1.0.1/Makefile.in --- old/tilde-1.0.0/Makefile.in 2018-11-25 16:13:18.000000000 +0100 +++ new/tilde-1.0.1/Makefile.in 2019-05-10 10:10:49.000000000 +0200 @@ -55,7 +55,7 @@ SILENTCXX=@echo '[CXX]' $< ; SILENTLD=@echo '[LD]' $@ ; -OBJECTS=src/filestate.o src/log.o src/filebuffer.o src/util.o src/option.o src/main.o src/dialogs/optionsdialog.o src/dialogs/attributesdialog.o src/dialogs/selectbufferdialog.o src/filewrapper.o src/dialogs/encodingdialog.o src/openfiles.o src/fileautocompleter.o src/dialogs/openrecentdialog.o src/dialogs/characterdetailsdialog.o src/dialogs/highlightdialog.o src/fileline.o src/fileeditwindow.o +OBJECTS=src/filestate.o src/log.o src/copy_file.o src/filebuffer.o src/util.o src/option.o src/main.o src/dialogs/optionsdialog.o src/dialogs/attributesdialog.o src/dialogs/selectbufferdialog.o src/filewrapper.o src/dialogs/encodingdialog.o src/openfiles.o src/fileautocompleter.o src/dialogs/openrecentdialog.o src/dialogs/characterdetailsdialog.o src/dialogs/highlightdialog.o src/fileline.o src/fileeditwindow.o all: src/tilde diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tilde-1.0.0/config.pkg new/tilde-1.0.1/config.pkg --- old/tilde-1.0.0/config.pkg 2018-11-25 16:13:18.000000000 +0100 +++ new/tilde-1.0.1/config.pkg 2019-05-10 10:10:49.000000000 +0200 @@ -45,10 +45,6 @@ checkfunction_internal test_compile_cxx "$@" } -test_link_cxx_with_flags() { - test_link_cxx "$@" "CXXFLAGS=$CXXFLAGS" -} - config() { has_support_cxx11 clean_cxx @@ -87,7 +83,7 @@ return 0; } EOF - pkgconfig libt3widget/1.0.0 LIBT3WIDGET test_link_cxx_with_flags || \ + pkgconfig libt3widget/1.0.0 LIBT3WIDGET test_link_cxx || \ error "!! Can not find libt3widget. libt3widget is required to compile tilde." clean_cxx @@ -138,7 +134,6 @@ fd = creat("path", S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); stat("path", &statbuf); fchmod(fd, 0644); - fchown(fd, 0, 0); fsync(fd); ftruncate(fd, 5); off_t offset = lseek(fd, 0, SEEK_CUR); @@ -158,7 +153,6 @@ checkfunction "creat" 'int fd = creat("path", S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);' "<sys/types.h>" "<sys/stat.h>" "<fcntl.h>" checkfunction "stat" 'struct stat statbuf; stat("path", &statbuf);' "<sys/types.h>" "<sys/stat.h>" "<unistd.h>" checkfunction "fchmod" 'int fd; fchmod(fd, 0);' "<sys/stat.h>" - checkfunction "fchown" 'int fd; fchown(fd, 0, 0);' "<unistd.h>" checkfunction "fsync" 'int fd; fsync(fd);' "<unistd.h>" checkfunction "ftruncate" 'int fd; ftrucnate(fd, 5);' "<unistd.h>" "<sys/types.h>" checkfunction "lseek" 'off_t offset = lseek(fd, 0, SEEK_CUR);' "<sys/types.h>" "<unistd.h>" @@ -173,30 +167,58 @@ clean_cxx cat > .configcxx.cc <<EOF -#include <stdlib.h> -#include <attr/libattr.h> +#include <fcntl.h> int main(int argc, char *argv[]) { - attr_copy_file(argv[1], argv[2], NULL, NULL); + posix_fallocate(1, 0, 10); } EOF - if test_link_cxx "libattr" TESTLIBS=-lattr ; then - CONFIGFLAGS="${CONFIGFLAGS} -DHAS_LIBATTR" - CONFIGLIBS="${CONFIGLIBS} -lattr" + if test_link_cxx "posix_fallocate" ; then + CONFIGFLAGS="${CONFIGFLAGS} -DHAS_POSIX_FALLOCATE" fi clean_cxx cat > .configcxx.cc <<EOF -#include <stdlib.h> -#include <acl/libacl.h> +#ifndef __linux__ +#error sendfile should only be used on linux, as different platforms have different semantics +#endif +#include <sys/sendfile.h> + +int main(int argc, char *argv[]) { + off_t offset = 0; + sendfile(1, 2, &offset, 10); +} +EOF + if test_link_cxx "Linux sendfile" ; then + CONFIGFLAGS="${CONFIGFLAGS} -DHAS_SENDFILE" + fi + + clean_cxx + cat > .configcxx.cc <<EOF +#define _GNU_SOURCE +#include <unistd.h> + +int main(int argc, char *argv[]) { + off_t in, out; + copy_file_range(1, &in, 2, &out, 10, 0); +} +EOF + if test_link_cxx "copy_file_range" ; then + CONFIGFLAGS="${CONFIGFLAGS} -DHAS_COPY_FILE_RANGE" + fi + + clean_cxx + cat > .configcxx.cc <<EOF +#include <linux/fs.h> +#include <sys/ioctl.h> int main(int argc, char *argv[]) { - perm_copy_file(argv[1], argv[2], NULL); + int fd; + ioctl(1, FICLONE, fd); } EOF - if test_link_cxx "libacl" TESTLIBS=-lacl ; then - CONFIGFLAGS="${CONFIGFLAGS} -DHAS_LIBACL" - CONFIGLIBS="${CONFIGLIBS} -lacl" + if test_link_cxx "ficlone" ; then + CONFIGFLAGS="${CONFIGFLAGS} -DHAS_FICLONE" fi create_makefile "CONFIGFLAGS=${CONFIGFLAGS} ${LIBTRANSCRIPT_FLAGS} ${LIBT3WIDGET_FLAGS} ${LIBT3CONFIG_FLAGS} ${LIBT3HIGHLIGHT_FLAGS}" \ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tilde-1.0.0/configure new/tilde-1.0.1/configure --- old/tilde-1.0.0/configure 2018-11-25 16:13:18.000000000 +0100 +++ new/tilde-1.0.1/configure 2019-05-10 10:10:49.000000000 +0200 @@ -260,7 +260,7 @@ check_message "Checking for $1... " shift - if test_make "$@" .configcxx.o >> config.log 2>&1 ; then + if test_make CXXFLAGS="$CXXFLAGS" "$@" .configcxx.o >> config.log 2>&1 ; then check_message_result "yes" true else @@ -276,7 +276,7 @@ check_message "Checking for $1... " shift - if test_make "$@" .configcxx >> config.log 2>&1 ; then + if test_make CXXFLAGS="$CXXFLAGS" "$@" .configcxx >> config.log 2>&1 ; then check_message_result "yes" true else diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tilde-1.0.0/man/tilde.1 new/tilde-1.0.1/man/tilde.1 --- old/tilde-1.0.0/man/tilde.1 2018-11-25 16:13:18.000000000 +0100 +++ new/tilde-1.0.1/man/tilde.1 2019-05-10 10:10:49.000000000 +0200 @@ -1,5 +1,5 @@ .\" Generated by manscript from tilde.1.txt -.TH "TILDE" "1" "2018/03/18" "1.0.0" "An intuitive terminal text editor" +.TH "TILDE" "1" "2018/03/18" "1.0.1" "An intuitive terminal text editor" .SH NAME tilde \- an intuitive text editor for the console/terminal .SH SYNOPSIS diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tilde-1.0.0/src/copy_file.cc new/tilde-1.0.1/src/copy_file.cc --- old/tilde-1.0.0/src/copy_file.cc 1970-01-01 01:00:00.000000000 +0100 +++ new/tilde-1.0.1/src/copy_file.cc 2019-05-10 10:10:49.000000000 +0200 @@ -0,0 +1,143 @@ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include "tilde/copy_file.h" + +#include <limits> +#include <sys/stat.h> +#include <sys/types.h> +#include <t3widget/util.h> +#include <unistd.h> + +using namespace t3widget; + +static bool rewind_files(int src_fd, int dest_fd) { + if (lseek(src_fd, 0, SEEK_SET) == (off_t)-1) { + return false; + } + if (lseek(dest_fd, 0, SEEK_SET) == (off_t)-1) { + return false; + } + return true; +} + +#if defined(HAS_SENDFILE) && defined(__linux__) +#include <sys/sendfile.h> + +int copy_file_by_sendfile(int src_fd, int dest_fd, size_t bytes_to_copy) { + if (!rewind_files(src_fd, dest_fd)) { + return errno; + } + + ssize_t result; + do { + result = sendfile(dest_fd, src_fd, nullptr, bytes_to_copy); + if (result < 0) { + if (errno == EAGAIN) { + continue; + } + } + if (result < 0) { + return errno; + } + bytes_to_copy -= result; + } while (bytes_to_copy > 0); + return 0; +} +#else +#if defined(TILDE_UNITTEST) && defined(__linux__) +#error Please define HAS_SENDFILE in unit tests +#endif +int copy_file_by_sendfile(int, int, size_t) { return ENOTSUP; } +#endif + +#if defined(HAS_COPY_FILE_RANGE) +int copy_file_by_copy_file_range(int src_fd, int dest_fd, size_t bytes_to_copy) { + if (!rewind_files(src_fd, dest_fd)) { + return errno; + } + + ssize_t result; + do { + result = copy_file_range(src_fd, nullptr, dest_fd, nullptr, bytes_to_copy, 0); + if (result < 0) { + if (errno == EAGAIN) { + continue; + } + } + if (result < 0) { + return errno; + } + bytes_to_copy -= result; + } while (bytes_to_copy > 0); + return 0; +} +#else +#if defined(TILDE_UNITTEST) && defined(__linux__) +#error Please define HAS_COPY_FILE_RANGE in unit tests +#endif +int copy_file_by_copy_file_range(int, int, size_t) { return ENOTSUP; } +#endif + +#if defined(HAS_FICLONE) +#include <linux/fs.h> +#include <sys/ioctl.h> + +int copy_file_by_ficlone(int src_fd, int dest_fd) { + if (ioctl(dest_fd, FICLONE, src_fd) == -1) { + return errno; + } + return 0; +} +#else +#if defined(TILDE_UNITTEST) && defined(__linux__) +#error Please define HAS_FICLONE in unit tests +#endif +int copy_file_by_ficlone(int, int) { return ENOTSUP; } +#endif + +int copy_file_by_read_write(int src_fd, int dest_fd) { + if (!rewind_files(src_fd, dest_fd)) { + return errno; + } + // Copy in chunks of 32K. This aims to balance memory use vs. number of operations. + char buffer[32768]; + while (true) { + ssize_t read_bytes = nosig_read(src_fd, buffer, sizeof(buffer)); + if (read_bytes < 0) { + return errno; + } else if (read_bytes == 0) { + return 0; + } + + ssize_t written_bytes = nosig_write(dest_fd, buffer, read_bytes); + if (written_bytes < 0) { + return errno; + } + } +} + +int copy_file(int src_fd, int dest_fd) { + int result; + + result = copy_file_by_ficlone(src_fd, dest_fd); + if (result == 0) { + return result; + } + + struct stat statbuf; + if (fstat(src_fd, &statbuf) < 0) { + return errno; + } + // FIXME: these routines may fail if the file changed in between and are now shorter! + result = copy_file_by_copy_file_range(src_fd, dest_fd, statbuf.st_size); + if (result != ENOTSUP) { + return result; + } + result = copy_file_by_sendfile(src_fd, dest_fd, statbuf.st_size); + if (result != ENOTSUP) { + return result; + } + return copy_file_by_read_write(src_fd, dest_fd); +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tilde-1.0.0/src/copy_file.h new/tilde-1.0.1/src/copy_file.h --- old/tilde-1.0.0/src/copy_file.h 1970-01-01 01:00:00.000000000 +0100 +++ new/tilde-1.0.1/src/copy_file.h 2019-05-10 10:10:49.000000000 +0200 @@ -0,0 +1,16 @@ +#ifndef COPY_FILE_H_ +#define COPY_FILE_H_ + +#include <cstddef> + +// Copy file by different methods. The files need not be at the starting position. The postion +// after copy is undefined. +int copy_file_by_sendfile(int src_fd, int dest_fd, size_t bytes_to_copy); +int copy_file_by_copy_file_range(int src_fd, int dest_fd, size_t bytes_to_copy); +int copy_file_by_ficlone(int src_fd, int dest_fd); +int copy_file_by_read_write(int src_fd, int dest_fd); + +// Generic copy routine which will try to copy the file using one of the methods above. +int copy_file(int src_fd, int dest_fd); + +#endif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tilde-1.0.0/src/dialogs/attributesdialog.cc new/tilde-1.0.1/src/dialogs/attributesdialog.cc --- old/tilde-1.0.0/src/dialogs/attributesdialog.cc 2018-11-25 16:13:18.000000000 +0100 +++ new/tilde-1.0.1/src/dialogs/attributesdialog.cc 2019-05-10 10:10:49.000000000 +0200 @@ -96,7 +96,7 @@ ADD_ATTRIBUTE_ENTRY("Number", NUMBER, number_line); ADD_ATTRIBUTE_ENTRY("String", STRING, string_line); ADD_ATTRIBUTE_ENTRY("String escape", STRING_ESCAPE, string_escape_line); - ADD_ATTRIBUTE_ENTRY("Miscelaneous", MISC, misc_line); + ADD_ATTRIBUTE_ENTRY("Miscellaneous", MISC, misc_line); ADD_ATTRIBUTE_ENTRY("Variable", VARIABLE, variable_line); ADD_ATTRIBUTE_ENTRY("Error", ERROR, error_line); ADD_ATTRIBUTE_ENTRY("Addition", ADDITION, addition_line); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tilde-1.0.0/src/dialogs/highlightdialog.cc new/tilde-1.0.1/src/dialogs/highlightdialog.cc --- old/tilde-1.0.0/src/dialogs/highlightdialog.cc 2018-11-25 16:13:18.000000000 +0100 +++ new/tilde-1.0.1/src/dialogs/highlightdialog.cc 2019-05-10 10:10:49.000000000 +0200 @@ -98,11 +98,24 @@ return; } - if ((highlight = t3_highlight_load(names.get()[idx - 1].lang_file, map_highlight, nullptr, - T3_HIGHLIGHT_UTF8 | T3_HIGHLIGHT_USE_PATH, &error)) == - nullptr) { + if ((highlight = + t3_highlight_load(names.get()[idx - 1].lang_file, map_highlight, nullptr, + T3_HIGHLIGHT_UTF8 | T3_HIGHLIGHT_USE_PATH | T3_HIGHLIGHT_VERBOSE_ERROR, + &error)) == nullptr) { std::string message(_("Error loading highlighting patterns: ")); + if (error.file_name) { + std::string file_location; + std::string file_name = convert_lang_codeset(error.file_name, true); + printf_into(&file_location, "%s:%d: ", file_name.c_str(), error.line_number); + message += file_location; + free(error.file_name); + } message += t3_highlight_strerror(error.error); + if (error.extra) { + message += ": "; + message += error.extra; + free(error.extra); + } error_dialog->set_message(message); error_dialog->center_over(this); error_dialog->show(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tilde-1.0.0/src/filebuffer.cc new/tilde-1.0.1/src/filebuffer.cc --- old/tilde-1.0.0/src/filebuffer.cc 2018-11-25 16:13:18.000000000 +0100 +++ new/tilde-1.0.1/src/filebuffer.cc 2019-05-10 10:10:49.000000000 +0200 @@ -14,13 +14,8 @@ #include <cstring> #include <fcntl.h> #include <unistd.h> -#ifdef HAS_LIBATTR -#include <attr/libattr.h> -#endif -#ifdef HAS_LIBACL -#include <acl/libacl.h> -#endif +#include "tilde/copy_file.h" #include "tilde/filebuffer.h" #include "tilde/fileline.h" #include "tilde/filestate.h" @@ -221,10 +216,55 @@ } switch (state->state) { - case save_as_process_t::INITIAL: + case save_as_process_t::INITIAL: { + if (strip_spaces.is_valid() ? strip_spaces.value() : option.strip_spaces) { + do_strip_spaces(); + } + + transcript_error_t error; + if (!state->encoding.empty()) { + if ((state->conversion_handle = transcript_open_converter( + state->encoding.c_str(), TRANSCRIPT_UTF8, 0, &error)) == nullptr) { + return rw_result_t(rw_result_t::CONVERSION_OPEN_ERROR); + } + encoding = state->encoding; + } else if (encoding != "UTF-8") { + if ((state->conversion_handle = transcript_open_converter(encoding.c_str(), TRANSCRIPT_UTF8, + 0, &error)) == nullptr) { + return rw_result_t(rw_result_t::CONVERSION_OPEN_ERROR); + } + } else { + state->conversion_handle = nullptr; + } + state->wrapper = t3widget::make_unique<file_write_wrapper_t>(-1, state->conversion_handle); + state->i = 0; + state->state = save_as_process_t::OPEN_FILE; + } + // FALLTHROUGH + case save_as_process_t::OPEN_FILE: { + if (state->conversion_handle) { + transcript_from_unicode_reset(state->conversion_handle); + } + try { + for (; state->i < size(); state->i++) { + if (state->i != 0) { + state->wrapper->write("\n", 1); + } + const std::string &data = get_line_data(state->i).get_data(); + /* At this point the wrapper is initialized with -1 as fd, thus it is not writing + anything. However, it will catch conversion errors, and this will result in asking + the user what to do. */ + state->wrapper->write(data.data(), data.size()); + } + } catch (rw_result_t error) { + return error; + } + state->computed_length = state->wrapper->written_size(); + if (state->name.empty()) { if (name.empty()) { - PANIC(); + // FIXME: this should return some result instead of crashing! + return rw_result_t(rw_result_t::INTERNAL_ERROR); } state->save_name = name.c_str(); } else { @@ -234,106 +274,131 @@ state->real_name = canonicalize_path(state->save_name); if (state->real_name.empty()) { if (errno != ENOENT) { - return rw_result_t(rw_result_t::ERRNO_ERROR, errno); + return rw_result_t(rw_result_t::ERRNO_ERROR_FILE_UNTOUCHED, errno); } state->real_name = state->save_name; } - /* FIXME: to avoid race conditions, it is probably better to try open with O_CREAT|O_EXCL - However, this may cause issues with NFS, which is known to have isues with this. */ - if (stat(state->real_name.c_str(), &state->file_info) < 0) { - if (errno == ENOENT) { - if ((state->fd = creat(state->real_name.c_str(), CREATE_MODE)) < 0) { - return rw_result_t(rw_result_t::ERRNO_ERROR, errno); + /* This attempts to avoid race conditions, by trying to open with O_CREAT|O_EXCL. This is + known to have issues on NFSv3, but it's the best we can do. */ + if ((state->fd = open(state->real_name.c_str(), O_CREAT | O_EXCL | O_RDWR, 0666)) < 0) { + struct stat file_info; + if ((state->fd = open(state->real_name.c_str(), O_RDWR)) >= 0) { + state->state = save_as_process_t::CREATE_BACKUP; + if (!state->name.empty()) { + return rw_result_t(rw_result_t::FILE_EXISTS); + } + } else if ((state->readonly_fd = open(state->real_name.c_str(), O_RDONLY)) >= 0) { + if (fstat(state->readonly_fd, &file_info) == 0 && file_info.st_uid == geteuid() && + (file_info.st_mode & S_IRUSR) && !(file_info.st_mode & S_IWUSR)) { + state->state = save_as_process_t::CHANGE_MODE; + state->original_mode = file_info.st_mode & 07777; + state->readonly_dev = file_info.st_dev; + state->readonly_ino = file_info.st_ino; + return rw_result_t(rw_result_t::READ_ONLY_FILE); + } else { + close(state->readonly_fd); + state->readonly_fd = -1; + return rw_result_t(rw_result_t::ERRNO_ERROR_FILE_UNTOUCHED, EACCES); } } else { - return rw_result_t(rw_result_t::ERRNO_ERROR, errno); + return rw_result_t(rw_result_t::ERRNO_ERROR_FILE_UNTOUCHED, errno); + } + } + } + if (0) { + /* The code here is only reachable through the case label. This is an ugly hack to prevent + executing this code in the normal path, but allow continuing execution after this block + once the file mode is changed. */ + case save_as_process_t::CHANGE_MODE: + if (fchmod(state->readonly_fd, state->original_mode.value() | S_IWUSR)) { + return rw_result_t(rw_result_t::ERRNO_ERROR_FILE_UNTOUCHED); + } + if ((state->fd = open(state->real_name.c_str(), O_RDWR)) < 0) { + int saved_errno = errno; + fchmod(state->readonly_fd, state->original_mode.value()); + return rw_result_t(rw_result_t::ERRNO_ERROR_FILE_UNTOUCHED, saved_errno); + } + struct stat file_info; + if (fstat(state->fd, &file_info) == 0 && (file_info.st_dev != state->readonly_dev || + file_info.st_ino != state->readonly_ino)) { + return rw_result_t(rw_result_t::RACE_ON_FILE); + } + close(state->readonly_fd); + state->readonly_fd = -1; + } + // FALLTHROUGH + case save_as_process_t::CREATE_BACKUP: { + // If the creation of the backup file fails, the user either aborts or allows continuation + // without completing the backup. Thus the next state is always WRITING. + state->state = save_as_process_t::WRITING; + std::string temp_name_str = state->real_name; + + if (option.make_backup) { + temp_name_str += "~"; + if ((state->backup_fd = open(temp_name_str.c_str(), O_CREAT | O_TRUNC | O_WRONLY, 0600)) < + 0) { + return rw_result_t(rw_result_t::BACKUP_FAILED, errno); } } else { - state->state = save_as_process_t::ALLOW_OVERWRITE; - if (!state->name.empty()) { - return rw_result_t(rw_result_t::FILE_EXISTS); - } - /* Note that we have a new case in the middle of the else statement - here. It is an ugly hack, but it does prevent a lot of code - duplication and other hacks. */ - case save_as_process_t::ALLOW_OVERWRITE: - state->state = save_as_process_t::ALLOW_OVERWRITE_READONLY; - if (access(state->save_name, W_OK) != 0) { - return rw_result_t(rw_result_t::FILE_EXISTS_READONLY); - } - case save_as_process_t::ALLOW_OVERWRITE_READONLY: - std::string temp_name_str = state->real_name; - - if ((idx = temp_name_str.rfind('/')) == std::string::npos) { - idx = 0; - } else { - idx++; - } + if ((idx = temp_name_str.rfind('/')) == std::string::npos) { + idx = 0; + } else { + idx++; + } - temp_name_str.erase(idx); - try { - temp_name_str.append(".tildeXXXXXX"); - } catch (std::bad_alloc &ba) { - return rw_result_t(rw_result_t::ERRNO_ERROR, ENOMEM); - } + temp_name_str.erase(idx); + temp_name_str.append("tilde-backup-XXXXXX"); - /* Unfortunately, we can't pass the c_str result to mkstemp as we are not allowed - to change that string. So we'll just have to copy it into a vector :-( */ - std::vector<char> temp_name(temp_name_str.begin(), temp_name_str.end()); - // Ensure nul termination. - temp_name.push_back(0); - /* Attempt to create a temporary file. If this fails, just write the file - directly. The latter has some risk (e.g. file truncation due to full disk, - or corruption due to computer crashes), but these are so small that it is - worth permitting this if we can't create the temporary file. */ - if (geteuid() == state->file_info.st_uid && - (state->fd = mkstemp(temp_name.data())) >= 0) { - state->temp_name = temp_name.data(); -// Preserve ownership and attributes -#ifdef HAS_LIBATTR - attr_copy_file(state->real_name.c_str(), state->temp_name.c_str(), nullptr, nullptr); -#endif -#ifdef HAS_LIBACL - perm_copy_file(state->real_name.c_str(), state->temp_name.c_str(), nullptr); -#endif - fchmod(state->fd, state->file_info.st_mode); - fchown(state->fd, -1, state->file_info.st_gid); - fchown(state->fd, state->file_info.st_uid, -1); - } else { - state->temp_name.clear(); - if ((state->fd = open(state->real_name.c_str(), O_WRONLY | O_CREAT, CREATE_MODE)) < 0) { - return rw_result_t(rw_result_t::ERRNO_ERROR, errno); - } - } - } - - { - transcript_t *handle; - transcript_error_t error; - if (!state->encoding.empty()) { - if ((handle = transcript_open_converter(state->encoding.c_str(), TRANSCRIPT_UTF8, 0, - &error)) == nullptr) { - return rw_result_t(rw_result_t::CONVERSION_OPEN_ERROR); - } - encoding = state->encoding; - } else if (encoding != "UTF-8") { - if ((handle = transcript_open_converter(encoding.c_str(), TRANSCRIPT_UTF8, 0, &error)) == - nullptr) { - return rw_result_t(rw_result_t::CONVERSION_OPEN_ERROR); - } + /* Unfortunately, we can't pass the c_str result to mkstemp as we are not allowed to change + that string. So we'll just have to copy it into a vector :-( */ + std::vector<char> temp_name(temp_name_str.begin(), temp_name_str.end()); + // Ensure nul termination. + temp_name.push_back(0); + if ((state->backup_fd = mkstemp(temp_name.data())) >= 0) { + state->temp_name = temp_name.data(); } else { - handle = nullptr; - } - // FIXME: if the new fails, the handle will remain open! - // FIXME: if the new fails, this will abort with an exception! This should not happen! - state->wrapper = new file_write_wrapper_t(state->fd, handle); - state->i = 0; - state->state = save_as_process_t::WRITING; + return rw_result_t(errno == ENOSPC ? rw_result_t::ERRNO_ERROR_FILE_UNTOUCHED + : rw_result_t::BACKUP_FAILED, + errno); + } + } + int error = copy_file(state->fd, state->backup_fd); + if (error != 0) { + return rw_result_t( + errno == ENOSPC ? rw_result_t::ERRNO_ERROR_FILE_UNTOUCHED : rw_result_t::BACKUP_FAILED, + error); + } + if (fsync(state->backup_fd) < 0 || close(state->backup_fd) < 0) { + return rw_result_t( + errno == ENOSPC ? rw_result_t::ERRNO_ERROR_FILE_UNTOUCHED : rw_result_t::BACKUP_FAILED, + errno); } + state->backup_saved = true; + state->backup_fd = -1; + } + // FALLTHROUGH case save_as_process_t::WRITING: { - if (strip_spaces.is_valid() ? strip_spaces.value() : option.strip_spaces) { - do_strip_spaces(); +#ifdef HAS_POSIX_FALLOCATE + // Use posix_fallocate to attempt to pre-allocate the required size of the file. If the call + // fails with ENOSPC or EFBIG, stop writing and report an error to the user. All other error + // codes are ignored. + if (posix_fallocate(state->fd, 0, state->computed_length) < 0 && + (errno == ENOSPC || errno == EFBIG)) { + // We want the backup to be removed (if it exists), and we didn't change anything, so we + // close the file here and set the fd to -1. + close(state->fd); + state->fd = -1; + return rw_result_t(rw_result_t::ERRNO_ERROR_FILE_UNTOUCHED); + } +#endif + int conversion_flags = state->wrapper->conversion_flags(); + state->wrapper = + t3widget::make_unique<file_write_wrapper_t>(state->fd, state->conversion_handle); + state->wrapper->add_conversion_flags(conversion_flags); + state->i = 0; + if (lseek(state->fd, 0, SEEK_SET) < 0) { + return rw_result_t(rw_result_t::ERRNO_ERROR); } try { for (; state->i < size(); state->i++) { @@ -344,30 +409,39 @@ state->wrapper->write(data.data(), data.size()); } } catch (rw_result_t error) { + // Don't attempt to retry imprecise conversions, as they should have been caught earlier. + // Also, restarting the conversion may append the current line to an already partially + // written line. + if (error == rw_result_t::CONVERSION_IMPRECISE) { + return rw_result_t(rw_result_t::CONVERSION_ERROR); + } return error; } - // If the file is being overwritten, truncate it to the written size. - if (state->temp_name.empty()) { - off_t curr_pos = lseek(state->fd, 0, SEEK_CUR); - if (curr_pos >= 0) { - ftruncate(state->fd, curr_pos); - } + // Truncate it to the written size. + int result; + while ((result = ftruncate(state->fd, state->wrapper->written_size())) < 0 && + errno == EINTR) { + } + if (result < 0) { + return rw_result_t(rw_result_t::ERRNO_ERROR); + } + if (fsync(state->fd) < 0) { + return rw_result_t(rw_result_t::ERRNO_ERROR); + } + /* Perform fchmod instead of chmod on the file name, to ensure that we actually change the + mode on the file we are interested in. However, we only want to report a problem after + cleaning up the rest, as it is more of an advisory nature. */ + int fchmod_errno = 0; + if (state->original_mode.is_valid() && fchmod(state->fd, state->original_mode.value()) < 0) { + fchmod_errno = errno; } - fsync(state->fd); - close(state->fd); - state->fd = -1; - if (!state->temp_name.empty()) { - if (option.make_backup) { - std::string backup_name = state->real_name; - backup_name += '~'; - unlink(backup_name.c_str()); - link(state->real_name.c_str(), backup_name.c_str()); - } - if (rename(state->temp_name.c_str(), state->real_name.c_str()) < 0) { - return rw_result_t(rw_result_t::ERRNO_ERROR, errno); - } + state->original_mode.reset(); + + if (close(state->fd) < 0) { + return rw_result_t(rw_result_t::ERRNO_ERROR); } + state->fd = -1; if (!state->name.empty()) { name = state->name; @@ -375,10 +449,13 @@ name_line.set_text(converted_name); } set_undo_mark(); + if (fchmod_errno != 0) { + return rw_result_t(rw_result_t::MODE_RESET_FAILED, fchmod_errno); + } break; } default: - PANIC(); + return rw_result_t(rw_result_t::INTERNAL_ERROR); } return rw_result_t(rw_result_t::SUCCESS); } @@ -751,8 +828,9 @@ } set_cursor_pos(new_pos); } else { - // FIXME: this causes the cursor position to be recorded incorrectly in the undo information. - // although one could argue this is to some extent better as it shows the actual edit. + // FIXME: this causes the cursor position to be recorded incorrectly in the undo + // information. although one could argue this is to some extent better as it shows the + // actual edit. set_cursor_pos(0); insert_block(line_comment); set_cursor_pos(saved_cursor.pos + line_comment.size()); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tilde-1.0.0/src/filestate.cc new/tilde-1.0.1/src/filestate.cc --- old/tilde-1.0.0/src/filestate.cc 2018-11-25 16:13:18.000000000 +0100 +++ new/tilde-1.0.1/src/filestate.cc 2019-05-10 10:10:49.000000000 +0200 @@ -27,7 +27,9 @@ wrapper(nullptr), encoding("UTF-8"), fd(-1), - buffer_used(true) {} + buffer_used(true) { + set_up_connections(); +} load_process_t::load_process_t(const callback_t &cb, const char *name, const char *_encoding, bool missing_ok) @@ -38,7 +40,26 @@ wrapper(nullptr), encoding(_encoding == nullptr ? "UTF-8" : _encoding), fd(-1), - buffer_used(true) {} + buffer_used(true) { + set_up_connections(); +} + +void load_process_t::set_up_connections() { + connections.push_back(continue_abort_dialog->connect_activate([this] { run(); }, 0)); + connections.push_back(continue_abort_dialog->connect_activate([this] { abort(); }, 1)); + connections.push_back(continue_abort_dialog->connect_closed([this] { abort(); })); + + connections.push_back(preserve_bom_dialog->connect_activate([this] { preserve_bom(); }, 0)); + connections.push_back(preserve_bom_dialog->connect_activate([this] { remove_bom(); }, 1)); + connections.push_back(preserve_bom_dialog->connect_closed([this] { preserve_bom(); })); + + connections.push_back( + open_file_dialog->connect_file_selected(bind_front(&load_process_t::file_selected, this))); + connections.push_back(open_file_dialog->connect_closed([this] { abort(); })); + + connections.push_back( + encoding_dialog->connect_activate(bind_front(&load_process_t::encoding_selected, this))); +} void load_process_t::abort() { delete file; @@ -51,18 +72,12 @@ rw_result_t rw_result; if (state == SELECT_FILE) { - connections.push_back( - open_file_dialog->connect_file_selected(bind_front(&load_process_t::file_selected, this))); - connections.push_back(open_file_dialog->connect_closed([this] { abort(); })); open_file_dialog->reset(); open_file_dialog->show(); - connections.push_back( - encoding_dialog->connect_activate(bind_front(&load_process_t::encoding_selected, this))); encoding_dialog->set_encoding(encoding.c_str()); return false; } - disconnect(); switch ((rw_result = file->load(this))) { case rw_result_t::SUCCESS: result = true; @@ -92,18 +107,12 @@ break; case rw_result_t::CONVERSION_IMPRECISE: printf_into(&message, "Conversion from encoding %s is irreversible", file->get_encoding()); - connections.push_back(continue_abort_dialog->connect_activate([this] { run(); }, 0)); - connections.push_back(continue_abort_dialog->connect_activate([this] { abort(); }, 1)); - connections.push_back(continue_abort_dialog->connect_closed([this] { abort(); })); continue_abort_dialog->set_message(message); continue_abort_dialog->show(); return false; case rw_result_t::CONVERSION_ILLEGAL: printf_into(&message, "Conversion from encoding %s encountered illegal characters", file->get_encoding()); - connections.push_back(continue_abort_dialog->connect_activate([this] { run(); }, 0)); - connections.push_back(continue_abort_dialog->connect_activate([this] { abort(); }, 1)); - connections.push_back(continue_abort_dialog->connect_closed([this] { abort(); })); continue_abort_dialog->set_message(message); continue_abort_dialog->show(); return false; @@ -114,9 +123,6 @@ error_dialog->show(); break; case rw_result_t::BOM_FOUND: - connections.push_back(preserve_bom_dialog->connect_activate([this] { preserve_bom(); }, 0)); - connections.push_back(preserve_bom_dialog->connect_activate([this] { remove_bom(); }, 1)); - connections.push_back(preserve_bom_dialog->connect_closed([this] { preserve_bom(); })); preserve_bom_dialog->show(); return false; default: @@ -180,52 +186,71 @@ : stepped_process_t(cb), state(SELECT_FILE), file(_file), - allow_highlight_change(_allow_highlight_change), - highlight_changed(false), - save_name(nullptr), - fd(-1), - wrapper(nullptr) {} + allow_highlight_change(_allow_highlight_change) { + connections.push_back(continue_abort_dialog->connect_activate([this] { run(); }, 0)); + connections.push_back(continue_abort_dialog->connect_activate([this] { abort(); }, 1)); + connections.push_back(continue_abort_dialog->connect_closed([this] { abort(); })); + + connections.push_back( + save_as_dialog->connect_file_selected(bind_front(&save_as_process_t::file_selected, this))); + connections.push_back(save_as_dialog->connect_closed([this] { abort(); })); + + connections.push_back( + encoding_dialog->connect_activate(bind_front(&save_as_process_t::encoding_selected, this))); +} bool save_as_process_t::step() { std::string message; rw_result_t rw_result; if (state == SELECT_FILE) { - connections.push_back( - save_as_dialog->connect_file_selected(bind_front(&save_as_process_t::file_selected, this))); - connections.push_back(save_as_dialog->connect_closed([this] { abort(); })); - save_as_dialog->set_from_file(file->get_name()); save_as_dialog->show(); - connections.push_back( - encoding_dialog->connect_activate(bind_front(&save_as_process_t::encoding_selected, this))); encoding_dialog->set_encoding(file->get_encoding()); return false; } - disconnect(); switch ((rw_result = file->save(this))) { case rw_result_t::SUCCESS: result = true; break; case rw_result_t::FILE_EXISTS: printf_into(&message, "File '%s' already exists", name.c_str()); - connections.push_back(continue_abort_dialog->connect_activate([this] { run(); }, 0)); - connections.push_back(continue_abort_dialog->connect_activate([this] { abort(); }, 1)); - connections.push_back(continue_abort_dialog->connect_closed([this] { abort(); })); continue_abort_dialog->set_message(message); continue_abort_dialog->show(); return false; - case rw_result_t::FILE_EXISTS_READONLY: - printf_into(&message, "File '%s' is readonly", save_name); - connections.push_back(continue_abort_dialog->connect_activate([this] { run(); }, 0)); - connections.push_back(continue_abort_dialog->connect_activate([this] { abort(); }, 1)); - connections.push_back(continue_abort_dialog->connect_closed([this] { abort(); })); + case rw_result_t::BACKUP_FAILED: + printf_into( + &message, + "Could not create a backup file: %s.\n\nThis could result in data loss if Tilde is " + "unable to complete writing the file", + strerror(rw_result.get_errno_error())); continue_abort_dialog->set_message(message); continue_abort_dialog->show(); return false; case rw_result_t::ERRNO_ERROR: - printf_into(&message, "Could not save file: %s", strerror(rw_result.get_errno_error())); + case rw_result_t::ERRNO_ERROR_FILE_UNTOUCHED: + printf_into(&message, "Could not save file: %s.", strerror(rw_result.get_errno_error())); + if (rw_result == rw_result_t::ERRNO_ERROR_FILE_UNTOUCHED) { + message.append( + "\n\nThe original file has not been touched. Save the current buffer to another " + "location to ensure its contents are preserved!"); + } else if (backup_saved) { + message.append("\n\nThe original contents of the file can still be retrieved from "); + // FIXME: the file names probably needs to be converted from some other character set. + if (!temp_name.empty()) { + message.append(temp_name); + } else { + message.append(name); + message.append("~"); + } + message.append( + ". Save the current buffer to another location to ensure its contents are preserved!"); + } else { + message.append( + "\n\nThe original file contents are lost. Save the current buffer to some other " + "location to ensure its contents are preserved!"); + } error_dialog->set_message(message); error_dialog->show(); break; @@ -243,15 +268,40 @@ encoding = file->get_encoding(); } i++; - printf_into(&message, "Conversion into encoding %s is irreversible", encoding.c_str()); - connections.push_back(continue_abort_dialog->connect_activate([this] { run(); }, 0)); - connections.push_back(continue_abort_dialog->connect_activate([this] { abort(); }, 1)); - connections.push_back(continue_abort_dialog->connect_closed([this] { abort(); })); + printf_into(&message, + "Conversion into encoding %s is irreversible\n\nThe loaded buffer will continue " + "to hold the original text, but the on-disk version will differ.", + encoding.c_str()); + continue_abort_dialog->set_message(message); + continue_abort_dialog->show(); + return false; + case rw_result_t::READ_ONLY_FILE: + printf_into(&message, "File %s is read-only", save_name); continue_abort_dialog->set_message(message); continue_abort_dialog->show(); return false; + case rw_result_t::MODE_RESET_FAILED: + printf_into( + &message, + "The file %s was written successfully, but changing back the mode bits on the file " + "failed: %s", + name.c_str(), strerror(rw_result.get_errno_error())); + error_dialog->set_message(message); + error_dialog->show(); + break; + case rw_result_t::RACE_ON_FILE: + printf_into(&message, + "Opening file '%s' after changing the mode opened a different file. The file was " + "not written.", + name.c_str()); + break; default: - PANIC(); + printf_into(&message, + "An unknown error occurred during saving. The file has not been saved and may be " + "damaged!"); + error_dialog->set_message(message); + error_dialog->show(); + break; } return true; } @@ -270,15 +320,28 @@ void save_as_process_t::encoding_selected(const std::string *_encoding) { encoding = *_encoding; } save_as_process_t::~save_as_process_t() { + if (backup_fd >= 0) { + close(backup_fd); + } + if (readonly_fd >= 0) { + if (original_mode.is_valid()) { + fchmod(fd, original_mode.value()); + original_mode.reset(); + } + close(readonly_fd); + } if (fd >= 0) { - close(fd); - if (temp_name.empty()) { - unlink(name.c_str()); - } else { - unlink(temp_name.c_str()); + if (original_mode.is_valid()) { + fchmod(fd, original_mode.value()); } + close(fd); + } else if (!temp_name.empty()) { + // Remove the backup file (not the ~ backup, but the temporary file we may have created). + unlink(temp_name.c_str()); + } + if (conversion_handle) { + transcript_close_converter(conversion_handle); } - delete wrapper; } void save_as_process_t::execute(const callback_t &cb, file_buffer_t *_file) { @@ -304,6 +367,10 @@ if (!_file->is_modified()) { state = CLOSE; } + connections.push_back(close_confirm_dialog->connect_activate([this] { do_save(); }, 0)); + connections.push_back(close_confirm_dialog->connect_activate([this] { dont_save(); }, 1)); + connections.push_back(close_confirm_dialog->connect_activate([this] { abort(); }, 2)); + connections.push_back(close_confirm_dialog->connect_closed([this] { abort(); })); } bool close_process_t::step() { @@ -318,7 +385,6 @@ } } - disconnect(); if (state == CLOSE) { recent_files.push_front(file); /* Can't delete the file_buffer_t here, because on switching buffers the @@ -331,10 +397,6 @@ std::string message; printf_into(&message, "Save changes to '%s'", file->get_name().empty() ? "(Untitled)" : file->get_name().c_str()); - connections.push_back(close_confirm_dialog->connect_activate([this] { do_save(); }, 0)); - connections.push_back(close_confirm_dialog->connect_activate([this] { dont_save(); }, 1)); - connections.push_back(close_confirm_dialog->connect_activate([this] { abort(); }, 2)); - connections.push_back(close_confirm_dialog->connect_closed([this] { abort(); })); close_confirm_dialog->set_message(message); close_confirm_dialog->show(); } else { @@ -360,7 +422,12 @@ const file_buffer_t *close_process_t::get_file_buffer_ptr() { return file; } exit_process_t::exit_process_t(const callback_t &cb) - : stepped_process_t(cb), iter(open_files.begin()) {} + : stepped_process_t(cb), iter(open_files.begin()) { + connections.push_back(close_confirm_dialog->connect_activate([this] { do_save(); }, 0)); + connections.push_back(close_confirm_dialog->connect_activate([this] { dont_save(); }, 1)); + connections.push_back(close_confirm_dialog->connect_activate([this] { abort(); }, 2)); + connections.push_back(close_confirm_dialog->connect_closed([this] { abort(); })); +} bool exit_process_t::step() { for (; iter != open_files.end(); iter++) { @@ -368,10 +435,6 @@ std::string message; printf_into(&message, "Save changes to '%s'", (*iter)->get_name().empty() ? "(Untitled)" : (*iter)->get_name().c_str()); - connections.push_back(close_confirm_dialog->connect_activate([this] { do_save(); }, 0)); - connections.push_back(close_confirm_dialog->connect_activate([this] { dont_save(); }, 1)); - connections.push_back(close_confirm_dialog->connect_activate([this] { abort(); }, 2)); - connections.push_back(close_confirm_dialog->connect_closed([this] { abort(); })); close_confirm_dialog->set_message(message); close_confirm_dialog->show(); return false; @@ -403,13 +466,14 @@ void exit_process_t::execute(const callback_t &cb) { (new exit_process_t(cb))->run(); } -open_recent_process_t::open_recent_process_t(const callback_t &cb) : load_process_t(cb) {} +open_recent_process_t::open_recent_process_t(const callback_t &cb) : load_process_t(cb) { + connections.push_back(open_recent_dialog->connect_file_selected( + bind_front(&open_recent_process_t::recent_file_selected, this))); + connections.push_back(open_recent_dialog->connect_closed([this] { abort(); })); +} bool open_recent_process_t::step() { if (state == SELECT_FILE) { - connections.push_back(open_recent_dialog->connect_file_selected( - bind_front(&open_recent_process_t::recent_file_selected, this))); - connections.push_back(open_recent_dialog->connect_closed([this] { abort(); })); open_recent_dialog->show(); return false; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tilde-1.0.0/src/filestate.h new/tilde-1.0.1/src/filestate.h --- old/tilde-1.0.0/src/filestate.h 2018-11-25 16:13:18.000000000 +0100 +++ new/tilde-1.0.1/src/filestate.h 2019-05-10 10:10:49.000000000 +0200 @@ -13,6 +13,7 @@ */ #ifndef FILESTATE_H #define FILESTATE_H +#include <cerrno> #include <string> #include <sys/stat.h> #include <sys/types.h> @@ -34,14 +35,19 @@ enum stop_reason_t { SUCCESS, FILE_EXISTS, - FILE_EXISTS_READONLY, + READ_ONLY_FILE, + BACKUP_FAILED, ERRNO_ERROR, + ERRNO_ERROR_FILE_UNTOUCHED, CONVERSION_OPEN_ERROR, CONVERSION_IMPRECISE, CONVERSION_ERROR, CONVERSION_ILLEGAL, CONVERSION_TRUNCATED, BOM_FOUND, + MODE_RESET_FAILED, + INTERNAL_ERROR, + RACE_ON_FILE, }; private: @@ -53,7 +59,7 @@ public: rw_result_t() = default; - explicit rw_result_t(stop_reason_t _reason) : reason(_reason) {} + explicit rw_result_t(stop_reason_t _reason) : reason(_reason), errno_error(errno) {} rw_result_t(stop_reason_t _reason, int _errno_error) : reason(_reason), errno_error(_errno_error) {} rw_result_t(stop_reason_t _reason, transcript_error_t _transcript_error) @@ -92,6 +98,8 @@ void remove_bom(); public: + void set_up_connections(); + virtual file_buffer_t *get_file_buffer(); static void execute(const callback_t &cb); static void execute(const callback_t &cb, const char *name, const char *encoding = nullptr, @@ -102,23 +110,30 @@ friend class file_buffer_t; protected: - enum { SELECT_FILE, INITIAL, ALLOW_OVERWRITE, ALLOW_OVERWRITE_READONLY, WRITING }; - int state; + enum { SELECT_FILE, INITIAL, OPEN_FILE, CHANGE_MODE, CREATE_BACKUP, WRITING }; + int state = SELECT_FILE; file_buffer_t *file; std::string name; std::string encoding; bool allow_highlight_change; - bool highlight_changed; + bool highlight_changed = false; // State for save file_buffer_t::save function - const char *save_name; + const char *save_name = nullptr; std::string real_name; std::string temp_name; - int fd; + int fd = -1; + int backup_fd = -1; + int readonly_fd = -1; + dev_t readonly_dev; + ino_t readonly_ino; + bool backup_saved = false; + off_t computed_length = 0; + optional<mode_t> original_mode; text_pos_t i; - file_write_wrapper_t *wrapper; - struct stat file_info; + transcript_t *conversion_handle = nullptr; + std::unique_ptr<file_write_wrapper_t> wrapper = nullptr; save_as_process_t(const callback_t &cb, file_buffer_t *_file, bool _allow_highlight_change = true); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tilde-1.0.0/src/filewrapper.cc new/tilde-1.0.1/src/filewrapper.cc --- old/tilde-1.0.0/src/filewrapper.cc 2018-11-25 16:13:18.000000000 +0100 +++ new/tilde-1.0.1/src/filewrapper.cc 2019-05-10 10:10:49.000000000 +0200 @@ -135,10 +135,11 @@ // FIXME: check return value nfc_output.reset(reinterpret_cast<char *>(u8_normalize( UNINORM_NFC, reinterpret_cast<const uint8_t *>(buffer), bytes, nullptr, &nfc_output_len))); - if (handle == nullptr) { - if (nosig_write(fd, nfc_output.get(), nfc_output_len) < 0) { + if (handle_ == nullptr) { + if (fd_ >= 0 && nosig_write(fd_, nfc_output.get(), nfc_output_len) < 0) { throw rw_result_t(rw_result_t::ERRNO_ERROR, errno); } + written_size_ += nfc_output_len; return; } @@ -148,8 +149,8 @@ while (buffer < buffer_end) { transcript_buffer_ptr = transcript_buffer; - switch (transcript_from_unicode(handle, &buffer, buffer_end, &transcript_buffer_ptr, - transcript_buffer_end, conversion_flags)) { + switch (transcript_from_unicode(handle_, &buffer, buffer_end, &transcript_buffer_ptr, + transcript_buffer_end, conversion_flags_)) { case TRANSCRIPT_SUCCESS: ASSERT(buffer == buffer_end); break; @@ -157,19 +158,21 @@ break; case TRANSCRIPT_FALLBACK: case TRANSCRIPT_UNASSIGNED: - case TRANSCRIPT_PRIVATE_USE: imprecise = true; - conversion_flags |= TRANSCRIPT_ALLOW_FALLBACK | TRANSCRIPT_SUBST_UNASSIGNED; + conversion_flags_ |= TRANSCRIPT_ALLOW_FALLBACK | TRANSCRIPT_SUBST_UNASSIGNED; break; case TRANSCRIPT_INCOMPLETE: + case TRANSCRIPT_PRIVATE_USE: default: throw rw_result_t(rw_result_t::CONVERSION_ERROR); } if (transcript_buffer_ptr > transcript_buffer) { - conversion_flags &= ~TRANSCRIPT_FILE_START; - if (nosig_write(fd, transcript_buffer, transcript_buffer_ptr - transcript_buffer) < 0) { + conversion_flags_ &= ~TRANSCRIPT_FILE_START; + if (fd_ >= 0 && + nosig_write(fd_, transcript_buffer, transcript_buffer_ptr - transcript_buffer) < 0) { throw rw_result_t(rw_result_t::ERRNO_ERROR, errno); } + written_size_ += transcript_buffer_ptr - transcript_buffer; } } @@ -177,9 +180,3 @@ throw rw_result_t(rw_result_t::CONVERSION_IMPRECISE); } } - -file_write_wrapper_t::~file_write_wrapper_t() { - if (handle != nullptr) { - transcript_close_converter(handle); - } -} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tilde-1.0.0/src/filewrapper.h new/tilde-1.0.1/src/filewrapper.h --- old/tilde-1.0.0/src/filewrapper.h 2018-11-25 16:13:18.000000000 +0100 +++ new/tilde-1.0.1/src/filewrapper.h 2019-05-10 10:10:49.000000000 +0200 @@ -77,16 +77,32 @@ class file_write_wrapper_t { private: - int fd, conversion_flags; - transcript_t *handle; + int fd_, conversion_flags_; + transcript_t *handle_; + off_t written_size_ = 0; public: - explicit file_write_wrapper_t(int _fd, transcript_t *_handle = nullptr) - : fd(_fd), - conversion_flags(TRANSCRIPT_FILE_START | TRANSCRIPT_ALLOW_PRIVATE_USE), - handle(_handle) {} - ~file_write_wrapper_t(); + explicit file_write_wrapper_t(int fd, transcript_t *handle = nullptr) + : fd_(fd), + conversion_flags_(TRANSCRIPT_FILE_START | TRANSCRIPT_ALLOW_PRIVATE_USE), + handle_(handle) { + if (handle_) { + transcript_from_unicode_reset(handle_); + } + } void write(const char *buffer, size_t bytes); + + // Get the state of the conversion flags. This may have changed from the initial setting by + // imprecise conversions. + int conversion_flags() const { return conversion_flags_; } + // Adds to the state of the conversion flags, based on the input flags. This ignores the flags + // that indicate the start or end of the conversion. + void add_conversion_flags(int conversion_flags) { + conversion_flags_ |= + conversion_flags & (TRANSCRIPT_ALLOW_FALLBACK | TRANSCRIPT_SUBST_UNASSIGNED); + } + + off_t written_size() const { return written_size_; } }; #endif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tilde-1.0.0/src/main.cc new/tilde-1.0.1/src/main.cc --- old/tilde-1.0.0/src/main.cc 2018-11-25 16:13:18.000000000 +0100 +++ new/tilde-1.0.1/src/main.cc 2019-05-10 10:10:49.000000000 +0200 @@ -230,7 +230,7 @@ about_dialog->set_max_text_height(13); about_dialog->set_message( // clang-format off - "Tilde - The intuitive text editor\n\nVersion 1.0.0\n" + "Tilde - The intuitive text editor\n\nVersion 1.0.1\n" "Copyright (c) 2011-2018 G.P. Halkes\n\n" // @copyright "The Tilde text editor is licensed under the GNU General Public License version 3. " "You should have received a copy of the GNU General Public License along with this program. " diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tilde-1.0.0/src/option.cc new/tilde-1.0.1/src/option.cc --- old/tilde-1.0.0/src/option.cc 2018-11-25 16:13:18.000000000 +0100 +++ new/tilde-1.0.1/src/option.cc 2019-05-10 10:10:49.000000000 +0200 @@ -421,7 +421,7 @@ static void print_version() { printf( - "Tilde version 1.0.0\n" + "Tilde version 1.0.1\n" "Copyright (c) 2011-2018 G.P. Halkes\n" // @copyright "Tilde is licensed under the GNU General Public License version 3\n"); printf( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tilde-1.0.0/src/util.cc new/tilde-1.0.1/src/util.cc --- old/tilde-1.0.0/src/util.cc 2018-11-25 16:13:18.000000000 +0100 +++ new/tilde-1.0.1/src/util.cc 2019-05-10 10:10:49.000000000 +0200 @@ -53,20 +53,18 @@ void stepped_process_t::abort() { done(false); } -void stepped_process_t::disconnect() { - for (t3widget::connection_t &iter : connections) { - iter.disconnect(); - } - connections.clear(); -} - void stepped_process_t::done(bool _result) { result = _result; done_cb(this); delete this; } -stepped_process_t::~stepped_process_t() { disconnect(); } +stepped_process_t::~stepped_process_t() { + for (t3widget::connection_t &iter : connections) { + iter.disconnect(); + } + connections.clear(); +} bool stepped_process_t::get_result() { return result; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tilde-1.0.0/src/util.h new/tilde-1.0.1/src/util.h --- old/tilde-1.0.0/src/util.h 2018-11-25 16:13:18.000000000 +0100 +++ new/tilde-1.0.1/src/util.h 2019-05-10 10:10:49.000000000 +0200 @@ -93,7 +93,6 @@ virtual bool step() = 0; void run(); void abort(); - void disconnect(); void done(bool _result); public:
