New unwind-protect flavors to better type-check C callbacks.
[bpt/emacs.git] / src / fileio.c
index 89ad339..1b5208e 100644 (file)
@@ -20,7 +20,7 @@ along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
 #include <config.h>
 #include <limits.h>
 #include <fcntl.h>
-#include <stdio.h>
+#include "sysstdio.h"
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <unistd.h>
@@ -36,7 +36,7 @@ along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
 #include <selinux/context.h>
 #endif
 
-#ifdef HAVE_POSIX_ACL
+#ifdef HAVE_ACL_SET_FILE
 #include <sys/acl.h>
 #endif
 
@@ -55,7 +55,6 @@ along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
 #ifdef WINDOWSNT
 #define NOMINMAX 1
 #include <windows.h>
-#include <fcntl.h>
 #include <sys/file.h>
 #include "w32.h"
 #endif /* not WINDOWSNT */
@@ -63,7 +62,6 @@ along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
 #ifdef MSDOS
 #include "msdos.h"
 #include <sys/param.h>
-#include <fcntl.h>
 #endif
 
 #ifdef DOS_NT
@@ -82,6 +80,7 @@ along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
 #endif
 
 #include "systime.h"
+#include <acl.h>
 #include <allocator.h>
 #include <careadlinkat.h>
 #include <stat-time.h>
@@ -147,7 +146,7 @@ static Lisp_Object Qdelete_directory;
 #ifdef WINDOWSNT
 #endif
 
-Lisp_Object Qfile_error;
+Lisp_Object Qfile_error, Qfile_notify_error;
 static Lisp_Object Qfile_already_exists, Qfile_date_error;
 static Lisp_Object Qexcl;
 Lisp_Object Qfile_name_history;
@@ -160,11 +159,18 @@ static bool e_write (int, Lisp_Object, ptrdiff_t, ptrdiff_t,
                     struct coding_system *);
 
 \f
+/* Signal a file-access failure.  STRING describes the failure,
+   NAME the file involved, and ERRORNO the errno value.
+
+   If NAME is neither null nor a pair, package it up as a singleton
+   list before reporting it; this saves report_file_errno's caller the
+   trouble of preserving errno before calling list1.  */
+
 void
-report_file_error (const char *string, Lisp_Object data)
+report_file_errno (char const *string, Lisp_Object name, int errorno)
 {
+  Lisp_Object data = CONSP (name) || NILP (name) ? name : list1 (name);
   Lisp_Object errstring;
-  int errorno = errno;
   char *str;
 
   synchronize_system_messages_locale ();
@@ -197,21 +203,28 @@ report_file_error (const char *string, Lisp_Object data)
       }
 }
 
-Lisp_Object
-close_file_unwind (Lisp_Object fd)
+/* Signal a file-access failure that set errno.  STRING describes the
+   failure, NAME the file involved.  */
+
+void
+report_file_error (char const *string, Lisp_Object name)
 {
-  emacs_close (XFASTINT (fd));
-  return Qnil;
+  report_file_errno (string, name, errno);
+}
+
+void
+close_file_unwind (int fd)
+{
+  emacs_close (fd);
 }
 
 /* Restore point, having saved it as a marker.  */
 
-Lisp_Object
+void
 restore_point_unwind (Lisp_Object location)
 {
   Fgoto_char (location);
   Fset_marker (location, Qnil, Qnil);
-  return Qnil;
 }
 
 \f
@@ -742,7 +755,7 @@ make_temp_name (Lisp_Object prefix, bool base64_p)
               dog-slow, but also useless since eventually nil would
               have to be returned anyway.  */
            report_file_error ("Cannot create temporary name for prefix",
-                              Fcons (prefix, Qnil));
+                              prefix);
          /* not reached */
        }
     }
@@ -775,8 +788,9 @@ probably use `make-temp-file' instead, except in three circumstances:
 DEFUN ("expand-file-name", Fexpand_file_name, Sexpand_file_name, 1, 2, 0,
        doc: /* Convert filename NAME to absolute, and canonicalize it.
 Second arg DEFAULT-DIRECTORY is directory to start with if NAME is relative
-\(does not start with slash or tilde); if DEFAULT-DIRECTORY is nil or missing,
-the current buffer's value of `default-directory' is used.
+\(does not start with slash or tilde); both the directory name and
+a directory's file name are accepted.  If DEFAULT-DIRECTORY is nil or
+missing, the current buffer's value of `default-directory' is used.
 NAME should be a string that is a valid file name for the underlying
 filesystem.
 File name components that are `.' are removed, and
@@ -1850,9 +1864,6 @@ those `/' is discarded.  */)
   error ("Missing \"}\" in environment-variable substitution");
  badvar:
   error ("Substituting nonexistent environment variable \"%s\"", target);
-
-  /* NOTREACHED */
-  return Qnil;
 }
 \f
 /* A slightly faster and more convenient way to get
@@ -1972,7 +1983,7 @@ entries (depending on how Emacs was built).  */)
   security_context_t con;
   int conlength = 0;
 #endif
-#ifdef HAVE_POSIX_ACL
+#ifdef WINDOWSNT
   acl_t acl = NULL;
 #endif
 
@@ -2012,11 +2023,9 @@ entries (depending on how Emacs was built).  */)
 #ifdef WINDOWSNT
   if (!NILP (preserve_extended_attributes))
     {
-#ifdef HAVE_POSIX_ACL
       acl = acl_get_file (SDATA (encoded_file), ACL_TYPE_ACCESS);
-      if (acl == NULL && errno != ENOTSUP)
-       report_file_error ("Getting ACL", Fcons (file, Qnil));
-#endif
+      if (acl == NULL && acl_errno_valid (errno))
+       report_file_error ("Getting ACL", file);
     }
   if (!CopyFile (SDATA (encoded_file),
                 SDATA (encoded_newname),
@@ -2024,11 +2033,8 @@ entries (depending on how Emacs was built).  */)
     {
       /* CopyFile doesn't set errno when it fails.  By far the most
         "popular" reason is that the target is read-only.  */
-      if (GetLastError () == 5)
-       errno = EACCES;
-      else
-       errno = EPERM;
-      report_file_error ("Copying file", Fcons (file, Fcons (newname, Qnil)));
+      report_file_errno ("Copying file", list2 (file, newname),
+                        GetLastError () == 5 ? EACCES : EPERM);
     }
   /* CopyFile retains the timestamp by default.  */
   else if (NILP (keep_time))
@@ -2053,29 +2059,27 @@ entries (depending on how Emacs was built).  */)
       /* Restore original attributes.  */
       SetFileAttributes (filename, attributes);
     }
-#ifdef HAVE_POSIX_ACL
   if (acl != NULL)
     {
       bool fail =
        acl_set_file (SDATA (encoded_newname), ACL_TYPE_ACCESS, acl) != 0;
-      if (fail && errno != ENOTSUP)
-       report_file_error ("Setting ACL", Fcons (newname, Qnil));
+      if (fail && acl_errno_valid (errno))
+       report_file_error ("Setting ACL", newname);
 
       acl_free (acl);
     }
-#endif
 #else /* not WINDOWSNT */
   immediate_quit = 1;
   ifd = emacs_open (SSDATA (encoded_file), O_RDONLY, 0);
   immediate_quit = 0;
 
   if (ifd < 0)
-    report_file_error ("Opening input file", Fcons (file, Qnil));
+    report_file_error ("Opening input file", file);
 
-  record_unwind_protect (close_file_unwind, make_number (ifd));
+  record_unwind_protect_int (close_file_unwind, ifd);
 
   if (fstat (ifd, &st) != 0)
-    report_file_error ("Input file status", Fcons (file, Qnil));
+    report_file_error ("Input file status", file);
 
   if (!NILP (preserve_extended_attributes))
     {
@@ -2084,63 +2088,46 @@ entries (depending on how Emacs was built).  */)
        {
          conlength = fgetfilecon (ifd, &con);
          if (conlength == -1)
-           report_file_error ("Doing fgetfilecon", Fcons (file, Qnil));
+           report_file_error ("Doing fgetfilecon", file);
        }
 #endif
-
-#ifdef HAVE_POSIX_ACL
-      acl = acl_get_fd (ifd);
-      if (acl == NULL && errno != ENOTSUP)
-       report_file_error ("Getting ACL", Fcons (file, Qnil));
-#endif
     }
 
   if (out_st.st_mode != 0
       && st.st_dev == out_st.st_dev && st.st_ino == out_st.st_ino)
-    {
-      errno = 0;
-      report_file_error ("Input and output files are the same",
-                        Fcons (file, Fcons (newname, Qnil)));
-    }
+    report_file_errno ("Input and output files are the same",
+                      list2 (file, newname), 0);
 
   /* We can copy only regular files.  */
   if (!S_ISREG (st.st_mode))
-    {
-      /* Get a better looking error message. */
-      errno = S_ISDIR (st.st_mode) ? EISDIR : EINVAL;
-      report_file_error ("Non-regular file", Fcons (file, Qnil));
-    }
+    report_file_errno ("Non-regular file", file,
+                      S_ISDIR (st.st_mode) ? EISDIR : EINVAL);
 
-#ifdef MSDOS
-  /* System's default file type was set to binary by _fmode in emacs.c.  */
-  ofd = emacs_open (SDATA (encoded_newname),
-                   O_WRONLY | O_TRUNC | O_CREAT
-                   | (NILP (ok_if_already_exists) ? O_EXCL : 0),
-                   S_IREAD | S_IWRITE);
-#else  /* not MSDOS */
   {
-    mode_t new_mask = !NILP (preserve_uid_gid) ? 0600 : 0666;
-    new_mask &= st.st_mode;
+#ifndef MSDOS
+    int new_mask = st.st_mode & (!NILP (preserve_uid_gid) ? 0600 : 0666);
+#else
+    int new_mask = S_IREAD | S_IWRITE;
+#endif
     ofd = emacs_open (SSDATA (encoded_newname),
                      (O_WRONLY | O_TRUNC | O_CREAT
                       | (NILP (ok_if_already_exists) ? O_EXCL : 0)),
                      new_mask);
   }
-#endif /* not MSDOS */
   if (ofd < 0)
-    report_file_error ("Opening output file", Fcons (newname, Qnil));
+    report_file_error ("Opening output file", newname);
 
-  record_unwind_protect (close_file_unwind, make_number (ofd));
+  record_unwind_protect_int (close_file_unwind, ofd);
 
   immediate_quit = 1;
   QUIT;
   while ((n = emacs_read (ifd, buf, sizeof buf)) > 0)
-    if (emacs_write (ofd, buf, n) != n)
-      report_file_error ("I/O error", Fcons (newname, Qnil));
+    if (emacs_write_sig (ofd, buf, n) != n)
+      report_file_error ("Write error", newname);
   immediate_quit = 0;
 
 #ifndef MSDOS
-  /* Preserve the original file modes, and if requested, also its
+  /* Preserve the original file permissions, and if requested, also its
      owner and group.  */
   {
     mode_t mode_mask = 07777;
@@ -2157,8 +2144,16 @@ entries (depending on how Emacs was built).  */)
              mode_mask |= 02000;
          }
       }
-    if (fchmod (ofd, st.st_mode & mode_mask) != 0)
-      report_file_error ("Doing chmod", Fcons (newname, Qnil));
+
+    switch (!NILP (preserve_extended_attributes)
+           ? qcopy_acl (SSDATA (encoded_file), ifd,
+                        SSDATA (encoded_newname), ofd,
+                        st.st_mode & mode_mask)
+           : fchmod (ofd, st.st_mode & mode_mask))
+      {
+      case -2: report_file_error ("Copying permissions from", file);
+      case -1: report_file_error ("Copying permissions to", newname);
+      }
   }
 #endif /* not MSDOS */
 
@@ -2169,23 +2164,12 @@ entries (depending on how Emacs was built).  */)
       bool fail = fsetfilecon (ofd, con) != 0;
       /* See http://debbugs.gnu.org/11245 for ENOTSUP.  */
       if (fail && errno != ENOTSUP)
-       report_file_error ("Doing fsetfilecon", Fcons (newname, Qnil));
+       report_file_error ("Doing fsetfilecon", newname);
 
       freecon (con);
     }
 #endif
 
-#ifdef HAVE_POSIX_ACL
-  if (acl != NULL)
-    {
-      bool fail = acl_set_fd (ofd, acl) != 0;
-      if (fail && errno != ENOTSUP)
-       report_file_error ("Setting ACL", Fcons (newname, Qnil));
-
-      acl_free (acl);
-    }
-#endif
-
   if (!NILP (keep_time))
     {
       EMACS_TIME atime = get_stat_atime (&st);
@@ -2196,7 +2180,7 @@ entries (depending on how Emacs was built).  */)
     }
 
   if (emacs_close (ofd) < 0)
-    report_file_error ("I/O error", Fcons (newname, Qnil));
+    report_file_error ("Write error", newname);
 
   emacs_close (ifd);
 
@@ -2242,7 +2226,7 @@ DEFUN ("make-directory-internal", Fmake_directory_internal,
 #else
   if (mkdir (dir, 0777 & ~auto_saving_dir_umask) != 0)
 #endif
-    report_file_error ("Creating directory", list1 (directory));
+    report_file_error ("Creating directory", directory);
 
   return Qnil;
 }
@@ -2261,7 +2245,7 @@ DEFUN ("delete-directory-internal", Fdelete_directory_internal,
   dir = SSDATA (encoded_dir);
 
   if (rmdir (dir) != 0)
-    report_file_error ("Removing directory", list1 (directory));
+    report_file_error ("Removing directory", directory);
 
   return Qnil;
 }
@@ -2304,7 +2288,7 @@ With a prefix argument, TRASH is nil.  */)
   encoded_file = ENCODE_FILE (filename);
 
   if (unlink (SSDATA (encoded_file)) < 0)
-    report_file_error ("Removing old name", list1 (filename));
+    report_file_error ("Removing old name", filename);
   return Qnil;
 }
 
@@ -2625,7 +2609,11 @@ Use `file-symlink-p' to test for such links.  */)
      call the corresponding file handler.  */
   handler = Ffind_file_name_handler (absname, Qfile_exists_p);
   if (!NILP (handler))
-    return call2 (handler, Qfile_exists_p, absname);
+    {
+      Lisp_Object result = call2 (handler, Qfile_exists_p, absname);
+      errno = 0;
+      return result;
+    }
 
   absname = ENCODE_FILE (absname);
 
@@ -2722,7 +2710,6 @@ If there is no error, returns nil.  */)
   (Lisp_Object filename, Lisp_Object string)
 {
   Lisp_Object handler, encoded_filename, absname;
-  int fd;
 
   CHECK_STRING (filename);
   absname = Fexpand_file_name (filename, Qnil);
@@ -2737,10 +2724,8 @@ If there is no error, returns nil.  */)
 
   encoded_filename = ENCODE_FILE (absname);
 
-  fd = emacs_open (SSDATA (encoded_filename), O_RDONLY, 0);
-  if (fd < 0)
-    report_file_error (SSDATA (string), Fcons (filename, Qnil));
-  emacs_close (fd);
+  if (faccessat (AT_FDCWD, SSDATA (encoded_filename), R_OK, AT_EACCESS) != 0)
+    report_file_error (SSDATA (string), filename);
 
   return Qnil;
 }
@@ -2849,7 +2834,11 @@ searchable directory.  */)
      call the corresponding file handler.  */
   handler = Ffind_file_name_handler (absname, Qfile_accessible_directory_p);
   if (!NILP (handler))
-    return call2 (handler, Qfile_accessible_directory_p, absname);
+    {
+      Lisp_Object r = call2 (handler, Qfile_accessible_directory_p, absname);
+      errno = 0;
+      return r;
+    }
 
   absname = ENCODE_FILE (absname);
   return file_accessible_directory_p (SSDATA (absname)) ? Qt : Qnil;
@@ -2888,7 +2877,7 @@ file_accessible_directory_p (char const *file)
         and it's a safe optimization here.  */
       char *buf = SAFE_ALLOCA (len + 3);
       memcpy (buf, file, len);
-      strcpy (buf + len, "/." + (file[len - 1] == '/'));
+      strcpy (buf + len, &"/."[file[len - 1] == '/']);
       dir = buf;
     }
 
@@ -3071,14 +3060,14 @@ or if Emacs was not compiled with SELinux support.  */)
                  != 0);
           /* See http://debbugs.gnu.org/11245 for ENOTSUP.  */
          if (fail && errno != ENOTSUP)
-           report_file_error ("Doing lsetfilecon", Fcons (absname, Qnil));
+           report_file_error ("Doing lsetfilecon", absname);
 
          context_free (parsed_con);
          freecon (con);
          return fail ? Qnil : Qt;
        }
       else
-       report_file_error ("Doing lgetfilecon", Fcons (absname, Qnil));
+       report_file_error ("Doing lgetfilecon", absname);
     }
 #endif
 
@@ -3095,7 +3084,7 @@ was unable to determine the ACL entries.  */)
 {
   Lisp_Object absname;
   Lisp_Object handler;
-#ifdef HAVE_POSIX_ACL
+#ifdef HAVE_ACL_SET_FILE
   acl_t acl;
   Lisp_Object acl_string;
   char *str;
@@ -3110,7 +3099,7 @@ was unable to determine the ACL entries.  */)
   if (!NILP (handler))
     return call2 (handler, Qfile_acl, absname);
 
-#ifdef HAVE_POSIX_ACL
+#ifdef HAVE_ACL_SET_FILE
   absname = ENCODE_FILE (absname);
 
   acl = acl_get_file (SSDATA (absname), ACL_TYPE_ACCESS);
@@ -3148,7 +3137,7 @@ support.  */)
 {
   Lisp_Object absname;
   Lisp_Object handler;
-#ifdef HAVE_POSIX_ACL
+#ifdef HAVE_ACL_SET_FILE
   Lisp_Object encoded_absname;
   acl_t acl;
   bool fail;
@@ -3162,13 +3151,13 @@ support.  */)
   if (!NILP (handler))
     return call3 (handler, Qset_file_acl, absname, acl_string);
 
-#ifdef HAVE_POSIX_ACL
+#ifdef HAVE_ACL_SET_FILE
   if (STRINGP (acl_string))
     {
       acl = acl_from_text (SSDATA (acl_string));
       if (acl == NULL)
        {
-         report_file_error ("Converting ACL", Fcons (absname, Qnil));
+         report_file_error ("Converting ACL", absname);
          return Qnil;
        }
 
@@ -3177,8 +3166,8 @@ support.  */)
       fail = (acl_set_file (SSDATA (encoded_absname), ACL_TYPE_ACCESS,
                            acl)
              != 0);
-      if (fail && errno != ENOTSUP)
-       report_file_error ("Setting ACL", Fcons (absname, Qnil));
+      if (fail && acl_errno_valid (errno))
+       report_file_error ("Setting ACL", absname);
 
       acl_free (acl);
       return fail ? Qnil : Qt;
@@ -3238,7 +3227,7 @@ symbolic notation, like the `chmod' command from GNU Coreutils.  */)
   encoded_absname = ENCODE_FILE (absname);
 
   if (chmod (SSDATA (encoded_absname), XINT (mode) & 07777) < 0)
-    report_file_error ("Doing chmod", Fcons (absname, Qnil));
+    report_file_error ("Doing chmod", absname);
 
   return Qnil;
 }
@@ -3304,8 +3293,7 @@ Use the current time if TIMESTAMP is nil.  TIMESTAMP is in the format of
         if (file_directory_p (SSDATA (encoded_absname)))
           return Qnil;
 #endif
-        report_file_error ("Setting file times", Fcons (absname, Qnil));
-        return Qnil;
+        report_file_error ("Setting file times", absname);
       }
   }
 
@@ -3362,7 +3350,7 @@ otherwise, if FILE2 does not exist, the answer is t.  */)
   if (stat (SSDATA (absname2), &st2) < 0)
     return Qt;
 
-  return (EMACS_TIME_GT (get_stat_mtime (&st1), get_stat_mtime (&st2))
+  return (EMACS_TIME_LT (get_stat_mtime (&st2), get_stat_mtime (&st1))
          ? Qt : Qnil);
 }
 \f
@@ -3387,7 +3375,7 @@ verify (READ_BUF_SIZE <= INT_MAX);
        o remove all text properties.
        o set back the buffer multibyteness.  */
 
-static Lisp_Object
+static void
 decide_coding_unwind (Lisp_Object unwind_data)
 {
   Lisp_Object multibyte, undo_list, buffer;
@@ -3406,8 +3394,6 @@ decide_coding_unwind (Lisp_Object unwind_data)
   /* Now we are safe to change the buffer's multibyteness directly.  */
   bset_enable_multibyte_characters (current_buffer, multibyte);
   bset_undo_list (current_buffer, undo_list);
-
-  return Qnil;
 }
 
 /* Read from a non-regular file.  STATE is a Lisp_Save_Value
@@ -3505,7 +3491,6 @@ by calling `format-decode', which see.  */)
   EMACS_TIME mtime;
   int fd;
   ptrdiff_t inserted = 0;
-  bool nochange = 0;
   ptrdiff_t how_much;
   off_t beg_offset, end_offset;
   int unprocessed;
@@ -3522,6 +3507,11 @@ by calling `format-decode', which see.  */)
   bool set_coding_system = 0;
   Lisp_Object coding_system;
   bool read_quit = 0;
+  /* If the undo log only contains the insertion, there's no point
+     keeping it.  It's typically when we first fill a file-buffer.  */
+  bool empty_undo_list_p
+    = (!NILP (visit) && NILP (BVAR (current_buffer, undo_list))
+       && BEG == Z);
   Lisp_Object old_Vdeactivate_mark = Vdeactivate_mark;
   bool we_locked_file = 0;
   bool deferred_remove_unwind_protect = 0;
@@ -3567,7 +3557,7 @@ by calling `format-decode', which see.  */)
     {
       save_errno = errno;
       if (NILP (visit))
-       report_file_error ("Opening input file", Fcons (orig_filename, Qnil));
+       report_file_error ("Opening input file", orig_filename);
       mtime = time_error_value (save_errno);
       st.st_size = -1;
       if (!NILP (Vcoding_system_for_read))
@@ -3579,10 +3569,10 @@ by calling `format-decode', which see.  */)
   if (!NILP (replace))
     record_unwind_protect (restore_point_unwind, Fpoint_marker ());
 
-  record_unwind_protect (close_file_unwind, make_number (fd));
+  record_unwind_protect_int (close_file_unwind, fd);
 
   if (fstat (fd, &st) != 0)
-    report_file_error ("Input file status", Fcons (orig_filename, Qnil));
+    report_file_error ("Input file status", orig_filename);
   mtime = get_stat_mtime (&st);
 
   /* This code will need to be changed in order to work on named
@@ -3696,15 +3686,14 @@ by calling `format-decode', which see.  */)
                      int ntail;
                      if (lseek (fd, - (1024 * 3), SEEK_END) < 0)
                        report_file_error ("Setting file position",
-                                          Fcons (orig_filename, Qnil));
+                                          orig_filename);
                      ntail = emacs_read (fd, read_buf + nread, 1024 * 3);
                      nread = ntail < 0 ? ntail : nread + ntail;
                    }
                }
 
              if (nread < 0)
-               error ("IO error reading %s: %s",
-                      SDATA (orig_filename), emacs_strerror (errno));
+               report_file_error ("Read error", orig_filename);
              else if (nread > 0)
                {
                  struct buffer *prev = current_buffer;
@@ -3740,8 +3729,7 @@ by calling `format-decode', which see.  */)
 
                  /* Rewind the file for the actual read done later.  */
                  if (lseek (fd, 0, SEEK_SET) < 0)
-                   report_file_error ("Setting file position",
-                                      Fcons (orig_filename, Qnil));
+                   report_file_error ("Setting file position", orig_filename);
                }
            }
 
@@ -3807,8 +3795,7 @@ by calling `format-decode', which see.  */)
       if (beg_offset != 0)
        {
          if (lseek (fd, beg_offset, SEEK_SET) < 0)
-           report_file_error ("Setting file position",
-                              Fcons (orig_filename, Qnil));
+           report_file_error ("Setting file position", orig_filename);
        }
 
       immediate_quit = 1;
@@ -3821,8 +3808,7 @@ by calling `format-decode', which see.  */)
 
          nread = emacs_read (fd, read_buf, sizeof read_buf);
          if (nread < 0)
-           error ("IO error reading %s: %s",
-                  SSDATA (orig_filename), emacs_strerror (errno));
+           report_file_error ("Read error", orig_filename);
          else if (nread == 0)
            break;
 
@@ -3880,16 +3866,14 @@ by calling `format-decode', which see.  */)
          /* How much can we scan in the next step?  */
          trial = min (curpos, sizeof read_buf);
          if (lseek (fd, curpos - trial, SEEK_SET) < 0)
-           report_file_error ("Setting file position",
-                              Fcons (orig_filename, Qnil));
+           report_file_error ("Setting file position", orig_filename);
 
          total_read = nread = 0;
          while (total_read < trial)
            {
              nread = emacs_read (fd, read_buf + total_read, trial - total_read);
              if (nread < 0)
-               error ("IO error reading %s: %s",
-                      SDATA (orig_filename), emacs_strerror (errno));
+               report_file_error ("Read error", orig_filename);
              else if (nread == 0)
                break;
              total_read += nread;
@@ -3962,7 +3946,7 @@ by calling `format-decode', which see.  */)
 
          /* If display currently starts at beginning of line,
             keep it that way.  */
-         if (XBUFFER (XWINDOW (selected_window)->buffer) == current_buffer)
+         if (XBUFFER (XWINDOW (selected_window)->contents) == current_buffer)
            XWINDOW (selected_window)->start_at_line_beg = !NILP (Fbolp ());
 
          replace_handled = 1;
@@ -4001,8 +3985,7 @@ by calling `format-decode', which see.  */)
         CONVERSION_BUFFER.  */
 
       if (lseek (fd, beg_offset, SEEK_SET) < 0)
-       report_file_error ("Setting file position",
-                          Fcons (orig_filename, Qnil));
+       report_file_error ("Setting file position", orig_filename);
 
       inserted = 0;            /* Bytes put into CONVERSION_BUFFER so far.  */
       unprocessed = 0;         /* Bytes not processed in previous loop.  */
@@ -4040,8 +4023,7 @@ by calling `format-decode', which see.  */)
       deferred_remove_unwind_protect = 1;
 
       if (this < 0)
-       error ("IO error reading %s: %s",
-              SDATA (orig_filename), emacs_strerror (errno));
+       report_file_error ("Read error", orig_filename);
 
       if (unprocessed > 0)
        {
@@ -4071,9 +4053,7 @@ by calling `format-decode', which see.  */)
       if (bufpos == inserted)
        {
          /* Truncate the buffer to the size of the file.  */
-         if (same_at_start == same_at_end)
-           nochange = 1;
-         else
+         if (same_at_start != same_at_end)
            del_range_byte (same_at_start, same_at_end, 0);
          inserted = 0;
 
@@ -4112,7 +4092,7 @@ by calling `format-decode', which see.  */)
 
       /* If display currently starts at beginning of line,
         keep it that way.  */
-      if (XBUFFER (XWINDOW (selected_window)->buffer) == current_buffer)
+      if (XBUFFER (XWINDOW (selected_window)->contents) == current_buffer)
        XWINDOW (selected_window)->start_at_line_beg = !NILP (Fbolp ());
 
       /* Replace the chars that we need to replace,
@@ -4124,6 +4104,7 @@ by calling `format-decode', which see.  */)
        {
          del_range_byte (same_at_start, same_at_end, 0);
          temp = GPT;
+         eassert (same_at_start == GPT_BYTE);
          same_at_start = GPT_BYTE;
        }
       else
@@ -4136,6 +4117,7 @@ by calling `format-decode', which see.  */)
        = buf_bytepos_to_charpos (XBUFFER (conversion_buffer),
                                  same_at_start - BEGV_BYTE
                                  + BUF_BEG_BYTE (XBUFFER (conversion_buffer)));
+      eassert (same_at_start_charpos == temp - (BEGV - BEG));
       inserted_chars
        = (buf_bytepos_to_charpos (XBUFFER (conversion_buffer),
                                   same_at_start + inserted - BEGV_BYTE
@@ -4182,8 +4164,7 @@ by calling `format-decode', which see.  */)
   if (beg_offset != 0 || !NILP (replace))
     {
       if (lseek (fd, beg_offset, SEEK_SET) < 0)
-       report_file_error ("Setting file position",
-                          Fcons (orig_filename, Qnil));
+       report_file_error ("Setting file position", orig_filename);
     }
 
   /* In the following loop, HOW_MUCH contains the total bytes read so
@@ -4222,7 +4203,8 @@ by calling `format-decode', which see.  */)
               to be signaled after decoding the text we read.  */
            nbytes = internal_condition_case_1
              (read_non_regular,
-              make_save_value ("iii", (ptrdiff_t) fd, inserted, trytry),
+              make_save_value (SAVE_TYPE_INT_INT_INT, (ptrdiff_t) fd,
+                               inserted, trytry),
               Qerror, read_non_regular_quit);
 
            if (NILP (nbytes))
@@ -4287,8 +4269,7 @@ by calling `format-decode', which see.  */)
   specpdl_ptr--;
 
   if (how_much < 0)
-    error ("IO error reading %s: %s",
-          SDATA (orig_filename), emacs_strerror (errno));
+    report_file_error ("Read error", orig_filename);
 
   /* Make the text read part of the buffer.  */
   GAP_SIZE -= inserted;
@@ -4419,7 +4400,7 @@ by calling `format-decode', which see.  */)
 
   if (!NILP (visit))
     {
-      if (!EQ (BVAR (current_buffer, undo_list), Qt) && !nochange)
+      if (empty_undo_list_p)
        bset_undo_list (current_buffer, Qnil);
 
       if (NILP (handler))
@@ -4561,7 +4542,7 @@ by calling `format-decode', which see.  */)
          p = XCDR (p);
        }
 
-      if (NILP (visit))
+      if (!empty_undo_list_p)
        {
          bset_undo_list (current_buffer, old_undo);
          if (CONSP (old_undo) && inserted != old_inserted)
@@ -4587,8 +4568,7 @@ by calling `format-decode', which see.  */)
       && EMACS_NSECS (current_buffer->modtime) == NONEXISTENT_MODTIME_NSECS)
     {
       /* If visiting nonexistent file, return nil.  */
-      errno = save_errno;
-      report_file_error ("Opening input file", Fcons (orig_filename, Qnil));
+      report_file_errno ("Opening input file", orig_filename, save_errno);
     }
 
   if (read_quit)
@@ -4603,11 +4583,10 @@ by calling `format-decode', which see.  */)
 \f
 static Lisp_Object build_annotations (Lisp_Object, Lisp_Object);
 
-static Lisp_Object
+static void
 build_annotations_unwind (Lisp_Object arg)
 {
   Vwrite_region_annotation_buffers = arg;
-  return Qnil;
 }
 
 /* Decide the coding-system to encode the data with.  */
@@ -4644,7 +4623,7 @@ This function is for internal use only.  It may prompt the user.  */ )
          && !NILP (Ffboundp (Vselect_safe_coding_system_function)))
        /* Confirm that VAL can surely encode the current region.  */
        val = call5 (Vselect_safe_coding_system_function,
-                    start, end, Fcons (Qt, Fcons (val, Qnil)),
+                    start, end, list2 (Qt, val),
                     Qnil, filename);
     }
   else
@@ -4774,7 +4753,7 @@ This calls `write-region-annotate-functions' at the start, and
   struct stat st;
   EMACS_TIME modtime;
   ptrdiff_t count = SPECPDL_INDEX ();
-  int count1;
+  ptrdiff_t count1;
   Lisp_Object handler;
   Lisp_Object visit_file;
   Lisp_Object annotations;
@@ -4847,7 +4826,7 @@ This calls `write-region-annotate-functions' at the start, and
 
   record_unwind_protect (build_annotations_unwind,
                         Vwrite_region_annotation_buffers);
-  Vwrite_region_annotation_buffers = Fcons (Fcurrent_buffer (), Qnil);
+  Vwrite_region_annotation_buffers = list1 (Fcurrent_buffer ());
   count1 = SPECPDL_INDEX ();
 
   given_buffer = current_buffer;
@@ -4909,29 +4888,27 @@ This calls `write-region-annotate-functions' at the start, and
 
   if (desc < 0)
     {
+      int open_errno = errno;
 #ifdef CLASH_DETECTION
-      save_errno = errno;
       if (!auto_saving) unlock_file (lockname);
-      errno = save_errno;
 #endif /* CLASH_DETECTION */
       UNGCPRO;
-      report_file_error ("Opening output file", Fcons (filename, Qnil));
+      report_file_errno ("Opening output file", filename, open_errno);
     }
 
-  record_unwind_protect (close_file_unwind, make_number (desc));
+  record_unwind_protect_int (close_file_unwind, desc);
 
   if (NUMBERP (append))
     {
       off_t ret = lseek (desc, offset, SEEK_SET);
       if (ret < 0)
        {
+         int lseek_errno = errno;
 #ifdef CLASH_DETECTION
-         save_errno = errno;
          if (!auto_saving) unlock_file (lockname);
-         errno = save_errno;
 #endif /* CLASH_DETECTION */
          UNGCPRO;
-         report_file_error ("Lseek error", Fcons (filename, Qnil));
+         report_file_errno ("Lseek error", filename, lseek_errno);
        }
     }
 
@@ -4963,20 +4940,22 @@ This calls `write-region-annotate-functions' at the start, and
 
   immediate_quit = 0;
 
-#ifdef HAVE_FSYNC
-  /* fsync appears to change the modtime on BSD4.2.
-     Disk full in NFS may be reported here.  */
-  /* mib says that closing the file will try to write as fast as NFS can do
-     it, and that means the fsync here is not crucial for autosave files.  */
-  if (!auto_saving && !write_region_inhibit_fsync && fsync (desc) < 0)
+  /* fsync is not crucial for auto-save files, since they might lose
+     some work anyway.  */
+  if (!auto_saving && !write_region_inhibit_fsync)
     {
-      /* If fsync fails with EINTR, don't treat that as serious.  Also
-        ignore EINVAL which happens when fsync is not supported on this
-        file.  */
-      if (errno != EINTR && errno != EINVAL)
-       ok = 0, save_errno = errno;
+      /* Transfer data and metadata to disk, retrying if interrupted.
+        fsync can report a write failure here, e.g., due to disk full
+        under NFS.  But ignore EINVAL, which means fsync is not
+        supported on this file.  */
+      while (fsync (desc) != 0)
+       if (errno != EINTR)
+         {
+           if (errno != EINVAL)
+             ok = 0, save_errno = errno;
+           break;
+         }
     }
-#endif
 
   modtime = invalid_emacs_time ();
   if (visiting)
@@ -5013,7 +4992,7 @@ This calls `write-region-annotate-functions' at the start, and
       && ! (valid_timestamp_file_system && st.st_dev == timestamp_file_system))
     {
       int desc1 = emacs_open (fn, O_WRONLY | O_BINARY, 0);
-      if (0 <= desc1)
+      if (desc1 >= 0)
        {
          struct stat st1;
          if (fstat (desc1, &st1) == 0
@@ -5082,8 +5061,7 @@ This calls `write-region-annotate-functions' at the start, and
     }
 
   if (! ok)
-    error ("IO error writing %s: %s", SDATA (filename),
-          emacs_strerror (save_errno));
+    report_file_errno ("Write error", filename, save_errno);
 
   if (visiting)
     {
@@ -5327,12 +5305,10 @@ e_write (int desc, Lisp_Object string, ptrdiff_t start, ptrdiff_t end,
 
       if (coding->produced > 0)
        {
-         coding->produced
-           -= emacs_write (desc,
-                           STRINGP (coding->dst_object)
-                           ? SSDATA (coding->dst_object)
-                           : (char *) BYTE_POS_ADDR (coding->dst_pos_byte),
-                           coding->produced);
+         char *buf = (STRINGP (coding->dst_object)
+                      ? SSDATA (coding->dst_object)
+                      : (char *) BYTE_POS_ADDR (coding->dst_pos_byte));
+         coding->produced -= emacs_write_sig (desc, buf, coding->produced);
 
          if (coding->produced)
            return 0;
@@ -5387,37 +5363,19 @@ See Info node `(elisp)Modification Time' for more details.  */)
   return Qnil;
 }
 
-DEFUN ("clear-visited-file-modtime", Fclear_visited_file_modtime,
-       Sclear_visited_file_modtime, 0, 0, 0,
-       doc: /* Clear out records of last mod time of visited file.
-Next attempt to save will certainly not complain of a discrepancy.  */)
-  (void)
-{
-  current_buffer->modtime = make_emacs_time (0, UNKNOWN_MODTIME_NSECS);
-  current_buffer->modtime_size = -1;
-  return Qnil;
-}
-
 DEFUN ("visited-file-modtime", Fvisited_file_modtime,
        Svisited_file_modtime, 0, 0, 0,
        doc: /* Return the current buffer's recorded visited file modification time.
 The value is a list of the form (HIGH LOW USEC PSEC), like the time values that
 `file-attributes' returns.  If the current buffer has no recorded file
 modification time, this function returns 0.  If the visited file
-doesn't exist, HIGH will be -1.
+doesn't exist, return -1.
 See Info node `(elisp)Modification Time' for more details.  */)
   (void)
 {
-  if (EMACS_NSECS (current_buffer->modtime) < 0)
-    {
-      if (EMACS_NSECS (current_buffer->modtime) == NONEXISTENT_MODTIME_NSECS)
-       {
-         /* make_lisp_time won't work here if time_t is unsigned.  */
-         return list4 (make_number (-1), make_number (65535),
-                       make_number (0), make_number (0));
-       }
-      return make_number (0);
-    }
+  int ns = EMACS_NSECS (current_buffer->modtime);
+  if (ns < 0)
+    return make_number (UNKNOWN_MODTIME_NSECS - ns);
   return make_lisp_time (current_buffer->modtime);
 }
 
@@ -5428,12 +5386,22 @@ Useful if the buffer was not read from the file normally
 or if the file itself has been changed for some known benign reason.
 An argument specifies the modification time value to use
 \(instead of that of the visited file), in the form of a list
-\(HIGH LOW USEC PSEC) as returned by `current-time'.  */)
-  (Lisp_Object time_list)
+\(HIGH LOW USEC PSEC) or an integer flag as returned by
+`visited-file-modtime'.  */)
+  (Lisp_Object time_flag)
 {
-  if (!NILP (time_list))
+  if (!NILP (time_flag))
     {
-      current_buffer->modtime = lisp_time_argument (time_list);
+      EMACS_TIME mtime;
+      if (INTEGERP (time_flag))
+       {
+         CHECK_RANGED_INTEGER (time_flag, -1, 0);
+         mtime = make_emacs_time (0, UNKNOWN_MODTIME_NSECS - XINT (time_flag));
+       }
+      else
+       mtime = lisp_time_argument (time_flag);
+
+      current_buffer->modtime = mtime;
       current_buffer->modtime_size = -1;
     }
   else
@@ -5519,11 +5487,18 @@ auto_save_1 (void)
                   Qnil, Qnil);
 }
 
-static Lisp_Object
-do_auto_save_unwind (Lisp_Object arg)  /* used as unwind-protect function */
+struct auto_save_unwind
+{
+  FILE *stream;
+  bool auto_raise;
+};
 
+static void
+do_auto_save_unwind (void *arg)
 {
-  FILE *stream = XSAVE_POINTER (arg, 0);
+  struct auto_save_unwind *p = arg;
+  FILE *stream = p->stream;
+  minibuffer_auto_raise = p->auto_raise;
   auto_saving = 0;
   if (stream != NULL)
     {
@@ -5531,15 +5506,6 @@ do_auto_save_unwind (Lisp_Object arg)  /* used as unwind-protect function */
       fclose (stream);
       unblock_input ();
     }
-  return Qnil;
-}
-
-static Lisp_Object
-do_auto_save_unwind_1 (Lisp_Object value)  /* used as unwind-protect function */
-
-{
-  minibuffer_auto_raise = XINT (value);
-  return Qnil;
 }
 
 static Lisp_Object
@@ -5582,6 +5548,7 @@ A non-nil CURRENT-ONLY argument means save only current buffer.  */)
   ptrdiff_t count = SPECPDL_INDEX ();
   bool orig_minibuffer_auto_raise = minibuffer_auto_raise;
   bool old_message_p = 0;
+  struct auto_save_unwind auto_save_unwind;
   struct gcpro gcpro1, gcpro2;
 
   if (max_specpdl_size < specpdl_size + 40)
@@ -5593,7 +5560,7 @@ A non-nil CURRENT-ONLY argument means save only current buffer.  */)
   if (NILP (no_message))
     {
       old_message_p = push_message ();
-      record_unwind_protect (pop_message_unwind, Qnil);
+      record_unwind_protect_void (pop_message_unwind);
     }
 
   /* Ordinarily don't quit within this function,
@@ -5605,7 +5572,7 @@ A non-nil CURRENT-ONLY argument means save only current buffer.  */)
      point to non-strings reached from Vbuffer_alist.  */
 
   hook = intern ("auto-save-hook");
-  Frun_hooks (1, &hook);
+  safe_run_hooks (hook);
 
   if (STRINGP (Vauto_save_list_file_name))
     {
@@ -5629,13 +5596,12 @@ A non-nil CURRENT-ONLY argument means save only current buffer.  */)
          UNGCPRO;
        }
 
-      stream = fopen (SSDATA (listfile), "w");
+      stream = emacs_fopen (SSDATA (listfile), "w");
     }
 
-  record_unwind_protect (do_auto_save_unwind,
-                        make_save_pointer (stream));
-  record_unwind_protect (do_auto_save_unwind_1,
-                        make_number (minibuffer_auto_raise));
+  auto_save_unwind.stream = stream;
+  auto_save_unwind.auto_raise = minibuffer_auto_raise;
+  record_unwind_protect_ptr (do_auto_save_unwind, &auto_save_unwind);
   minibuffer_auto_raise = 0;
   auto_saving = 1;
   auto_save_error_occurred = 0;
@@ -5817,7 +5783,7 @@ before any other event (mouse or keypress) is handled.  */)
   if ((NILP (last_nonmenu_event) || CONSP (last_nonmenu_event))
       && use_dialog_box
       && use_file_dialog
-      && have_menus_p ())
+      && window_system_available (SELECTED_FRAME ()))
     return Qt;
 #endif
   return Qnil;
@@ -5896,6 +5862,7 @@ syms_of_fileio (void)
   DEFSYM (Qfile_error, "file-error");
   DEFSYM (Qfile_already_exists, "file-already-exists");
   DEFSYM (Qfile_date_error, "file-date-error");
+  DEFSYM (Qfile_notify_error, "file-notify-error");
   DEFSYM (Qexcl, "excl");
 
   DEFVAR_LISP ("file-name-coding-system", Vfile_name_coding_system,
@@ -5934,6 +5901,11 @@ of file names regardless of the current language environment.  */);
   Fput (Qfile_date_error, Qerror_message,
        build_pure_c_string ("Cannot set file date"));
 
+  Fput (Qfile_notify_error, Qerror_conditions,
+       Fpurecopy (list3 (Qfile_notify_error, Qfile_error, Qerror)));
+  Fput (Qfile_notify_error, Qerror_message,
+       build_pure_c_string ("File notification error"));
+
   DEFVAR_LISP ("file-name-handler-alist", Vfile_name_handler_alist,
               doc: /* Alist of elements (REGEXP . HANDLER) for file names handled specially.
 If a file name matches REGEXP, all I/O on that file is done by calling
@@ -6051,13 +6023,28 @@ in the buffer; this is the default behavior, because the auto-save
 file is usually more useful if it contains the deleted text.  */);
   Vauto_save_include_big_deletions = Qnil;
 
-#ifdef HAVE_FSYNC
+  /* fsync can be a significant performance hit.  Often it doesn't
+     suffice to make the file-save operation survive a crash.  For
+     batch scripts, which are typically part of larger shell commands
+     that don't fsync other files, its effect on performance can be
+     significant so its utility is particularly questionable.
+     Hence, for now by default fsync is used only when interactive.
+
+     For more on why fsync often fails to work on today's hardware, see:
+     Zheng M et al. Understanding the robustness of SSDs under power fault.
+     11th USENIX Conf. on File and Storage Technologies, 2013 (FAST '13), 271-84
+     http://www.usenix.org/system/files/conference/fast13/fast13-final80.pdf
+
+     For more on why fsync does not suffice even if it works properly, see:
+     Roche X. Necessary step(s) to synchronize filename operations on disk.
+     Austin Group Defect 672, 2013-03-19
+     http://austingroupbugs.net/view.php?id=672  */
   DEFVAR_BOOL ("write-region-inhibit-fsync", write_region_inhibit_fsync,
               doc: /* Non-nil means don't call fsync in `write-region'.
 This variable affects calls to `write-region' as well as save commands.
-A non-nil value may result in data loss!  */);
-  write_region_inhibit_fsync = 0;
-#endif
+Setting this to nil may avoid data loss if the system loses power or
+the operating system crashes.  */);
+  write_region_inhibit_fsync = noninteractive;
 
   DEFVAR_BOOL ("delete-by-moving-to-trash", delete_by_moving_to_trash,
                doc: /* Specifies whether to use the system's trash can.
@@ -6113,7 +6100,6 @@ This includes interactive calls to `delete-file' and
   defsubr (&Swrite_region);
   defsubr (&Scar_less_than_car);
   defsubr (&Sverify_visited_file_modtime);
-  defsubr (&Sclear_visited_file_modtime);
   defsubr (&Svisited_file_modtime);
   defsubr (&Sset_visited_file_modtime);
   defsubr (&Sdo_auto_save);