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:


Reply via email to