* src/fileio.c (Fsubstitute_in_file_name): Use substitute-env-in-file-name.
[bpt/emacs.git] / src / fileio.c
index f207212..a80145a 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>
@@ -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
@@ -145,10 +143,12 @@ static Lisp_Object Qcopy_directory;
 /* Lisp function for recursively deleting directories.  */
 static Lisp_Object Qdelete_directory;
 
+static Lisp_Object Qsubstitute_env_in_file_name;
+
 #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;
@@ -161,11 +161,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 ();
@@ -198,21 +205,37 @@ 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.  When invoking this function, take
+   care to not use arguments such as build_string ("foo") that involve
+   side effects that may set errno.  */
+
+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);
+}
+
+void
+fclose_unwind (void *arg)
+{
+  FILE *stream = arg;
+  fclose (stream);
 }
 
 /* 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;
+  unchain_marker (XMARKER (location));
 }
 
 \f
@@ -345,8 +368,7 @@ Given a Unix syntax file name, returns a string ending in slash.  */)
     }
 
 #ifdef DOS_NT
-  beg = alloca (SBYTES (filename) + 1);
-  memcpy (beg, SSDATA (filename), SBYTES (filename) + 1);
+  beg = xlispstrdupa (filename);
 #else
   beg = SSDATA (filename);
 #endif
@@ -484,6 +506,10 @@ get a current directory to run processes in.  */)
   return Ffile_name_directory (filename);
 }
 
+/* Maximum number of bytes that DST will be longer than SRC
+   in file_name_as_directory.  This occurs when SRCLEN == 0.  */
+enum { file_name_as_directory_slop = 2 };
+
 /* Convert from file name SRC of length SRCLEN to directory name in
    DST.  MULTIBYTE non-zero means the file name in SRC is a multibyte
    string.  On UNIX, just make sure there is a terminating /.  Return
@@ -501,14 +527,10 @@ file_name_as_directory (char *dst, const char *src, ptrdiff_t srclen,
       return 2;
     }
 
-  strcpy (dst, src);
-
+  memcpy (dst, src, srclen);
   if (!IS_DIRECTORY_SEP (dst[srclen - 1]))
-    {
-      dst[srclen] = DIRECTORY_SEP;
-      dst[srclen + 1] = '\0';
-      srclen++;
-    }
+    dst[srclen++] = DIRECTORY_SEP;
+  dst[srclen] = 0;
 #ifdef DOS_NT
   dostounix_filename (dst, multibyte);
 #endif
@@ -527,7 +549,8 @@ For a Unix-syntax file name, just appends a slash.  */)
 {
   char *buf;
   ptrdiff_t length;
-  Lisp_Object handler;
+  Lisp_Object handler, val;
+  USE_SAFE_ALLOCA;
 
   CHECK_STRING (file);
   if (NILP (file))
@@ -549,10 +572,12 @@ For a Unix-syntax file name, just appends a slash.  */)
   if (!NILP (Vw32_downcase_file_names))
     file = Fdowncase (file);
 #endif
-  buf = alloca (SBYTES (file) + 10);
+  buf = SAFE_ALLOCA (SBYTES (file) + file_name_as_directory_slop + 1);
   length = file_name_as_directory (buf, SSDATA (file), SBYTES (file),
                                   STRING_MULTIBYTE (file));
-  return make_specified_string (buf, -1, length, STRING_MULTIBYTE (file));
+  val = make_specified_string (buf, -1, length, STRING_MULTIBYTE (file));
+  SAFE_FREE ();
+  return val;
 }
 \f
 /* Convert from directory name SRC of length SRCLEN to file name in
@@ -564,18 +589,17 @@ static ptrdiff_t
 directory_file_name (char *dst, char *src, ptrdiff_t srclen, bool multibyte)
 {
   /* Process as Unix format: just remove any final slash.
-     But leave "/" unchanged; do not change it to "".  */
-  strcpy (dst, src);
-  if (srclen > 1
-      && IS_DIRECTORY_SEP (dst[srclen - 1])
+     But leave "/" and "//" unchanged.  */
+  while (srclen > 1
 #ifdef DOS_NT
-      && !IS_ANY_SEP (dst[srclen - 2])
+        && !IS_ANY_SEP (src[srclen - 2])
 #endif
-      )
-    {
-      dst[srclen - 1] = 0;
-      srclen--;
-    }
+        && IS_DIRECTORY_SEP (src[srclen - 1])
+        && ! (srclen == 2 && IS_DIRECTORY_SEP (src[0])))
+    srclen--;
+
+  memcpy (dst, src, srclen);
+  dst[srclen] = 0;
 #ifdef DOS_NT
   dostounix_filename (dst, multibyte);
 #endif
@@ -593,7 +617,8 @@ In Unix-syntax, this function just removes the final slash.  */)
 {
   char *buf;
   ptrdiff_t length;
-  Lisp_Object handler;
+  Lisp_Object handler, val;
+  USE_SAFE_ALLOCA;
 
   CHECK_STRING (directory);
 
@@ -616,10 +641,12 @@ In Unix-syntax, this function just removes the final slash.  */)
   if (!NILP (Vw32_downcase_file_names))
     directory = Fdowncase (directory);
 #endif
-  buf = alloca (SBYTES (directory) + 20);
+  buf = SAFE_ALLOCA (SBYTES (directory) + 1);
   length = directory_file_name (buf, SSDATA (directory), SBYTES (directory),
                                STRING_MULTIBYTE (directory));
-  return make_specified_string (buf, -1, length, STRING_MULTIBYTE (directory));
+  val = make_specified_string (buf, -1, length, STRING_MULTIBYTE (directory));
+  SAFE_FREE ();
+  return val;
 }
 
 static const char make_temp_name_tbl[64] =
@@ -743,7 +770,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 */
        }
     }
@@ -776,8 +803,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
@@ -816,6 +844,7 @@ filesystem tree, not (expand-file-name ".."  dirname).  */)
   Lisp_Object handler, result, handled_name;
   bool multibyte;
   Lisp_Object hdir;
+  USE_SAFE_ALLOCA;
 
   CHECK_STRING (name);
 
@@ -922,8 +951,7 @@ filesystem tree, not (expand-file-name ".."  dirname).  */)
 #endif
 
   /* Make a local copy of nm[] to protect it from GC in DECODE_FILE below.  */
-  nm = alloca (SBYTES (name) + 1);
-  memcpy (nm, SSDATA (name), SBYTES (name) + 1);
+  nm = xlispstrdupa (name);
 
 #ifdef DOS_NT
   /* Note if special escape prefix is present, but remove for now.  */
@@ -991,11 +1019,11 @@ filesystem tree, not (expand-file-name ".."  dirname).  */)
                  || (p[2] == '.' && (IS_DIRECTORY_SEP (p[3])
                                      || p[3] == 0))))
            lose = 1;
-         /* We want to replace multiple `/' in a row with a single
-            slash.  */
-         else if (p > nm
-                  && IS_DIRECTORY_SEP (p[0])
-                  && IS_DIRECTORY_SEP (p[1]))
+         /* Replace multiple slashes with a single one, except
+            leave leading "//" alone.  */
+         else if (IS_DIRECTORY_SEP (p[0])
+                  && IS_DIRECTORY_SEP (p[1])
+                  && (p != nm || IS_DIRECTORY_SEP (p[2])))
            lose = 1;
          p++;
        }
@@ -1078,10 +1106,11 @@ filesystem tree, not (expand-file-name ".."  dirname).  */)
       else                     /* ~user/filename */
        {
          char *o, *p;
-         for (p = nm; *p && (!IS_DIRECTORY_SEP (*p)); p++);
-         o = alloca (p - nm + 1);
+         for (p = nm; *p && !IS_DIRECTORY_SEP (*p); p++)
+           continue;
+         o = SAFE_ALLOCA (p - nm + 1);
          memcpy (o, nm, p - nm);
-         o [p - nm] = 0;
+         o[p - nm] = 0;
 
          block_input ();
          pw = getpwnam (o + 1);
@@ -1197,7 +1226,8 @@ filesystem tree, not (expand-file-name ".."  dirname).  */)
          if (!IS_DIRECTORY_SEP (nm[0]))
            {
              ptrdiff_t newlen = strlen (newdir);
-             char *tmp = alloca (newlen + strlen (nm) + 2);
+             char *tmp = alloca (newlen + file_name_as_directory_slop
+                                 + strlen (nm) + 1);
              file_name_as_directory (tmp, newdir, newlen, multibyte);
              strcat (tmp, nm);
              nm = tmp;
@@ -1251,31 +1281,18 @@ filesystem tree, not (expand-file-name ".."  dirname).  */)
 
   if (newdir)
     {
-      /* Get rid of any slash at the end of newdir, unless newdir is
-        just / or // (an incomplete UNC name).  */
+      /* Ignore any slash at the end of newdir, unless newdir is
+        just "/" or "//".  */
       length = strlen (newdir);
-      tlen = length + 1;
-      if (length > 1 && IS_DIRECTORY_SEP (newdir[length - 1])
-#ifdef WINDOWSNT
-         && !(length == 2 && IS_DIRECTORY_SEP (newdir[0]))
-#endif
-         )
-       {
-         char *temp = alloca (length);
-         memcpy (temp, newdir, length - 1);
-         temp[length - 1] = 0;
-         length--;
-         newdir = temp;
-       }
+      while (length > 1 && IS_DIRECTORY_SEP (newdir[length - 1])
+            && ! (length == 2 && IS_DIRECTORY_SEP (newdir[0])))
+       length--;
     }
   else
-    {
-      length = 0;
-      tlen = 0;
-    }
+    length = 0;
 
   /* Now concatenate the directory and name to new space in the stack frame.  */
-  tlen += strlen (nm) + 1;
+  tlen = length + file_name_as_directory_slop + strlen (nm) + 1;
 #ifdef DOS_NT
   /* Reserve space for drive specifier and escape prefix, since either
      or both may need to be inserted.  (The Microsoft x86 compiler
@@ -1283,7 +1300,7 @@ filesystem tree, not (expand-file-name ".."  dirname).  */)
   target = alloca (tlen + 4);
   target += 4;
 #else  /* not DOS_NT */
-  target = alloca (tlen);
+  target = SAFE_ALLOCA (tlen);
 #endif /* not DOS_NT */
   *target = 0;
 
@@ -1300,7 +1317,10 @@ filesystem tree, not (expand-file-name ".."  dirname).  */)
          if (!(drive && nm[0] && IS_DIRECTORY_SEP (newdir[0])
                && newdir[1] == '\0'))
 #endif
-           strcpy (target, newdir);
+           {
+             memcpy (target, newdir, length);
+             target[length] = 0;
+           }
        }
       else
        file_name_as_directory (target, newdir, length, multibyte);
@@ -1360,8 +1380,9 @@ filesystem tree, not (expand-file-name ".."  dirname).  */)
              ++o;
            p += 3;
          }
-       else if (p > target && IS_DIRECTORY_SEP (p[1]))
-         /* Collapse multiple `/' in a row.  */
+       else if (IS_DIRECTORY_SEP (p[1])
+                && (p != target || IS_DIRECTORY_SEP (p[2])))
+         /* Collapse multiple "/", except leave leading "//" alone.  */
          p++;
        else
          {
@@ -1409,11 +1430,12 @@ filesystem tree, not (expand-file-name ".."  dirname).  */)
     {
       handled_name = call3 (handler, Qexpand_file_name,
                            result, default_directory);
-      if (STRINGP (handled_name))
-       return handled_name;
-      error ("Invalid handler in `file-name-handler-alist'");
+      if (STRINGP (handled_name))
+       error ("Invalid handler in `file-name-handler-alist'");
+      result = handled_name;
     }
 
+  SAFE_FREE ();
   return result;
 }
 
@@ -1644,10 +1666,8 @@ If `//' appears, everything up to and including the first of
 those `/' is discarded.  */)
   (Lisp_Object filename)
 {
-  char *nm, *s, *p, *o, *x, *endp;
-  char *target = NULL;
-  ptrdiff_t total = 0;
-  bool substituted = 0;
+  char *nm, *p, *x, *endp;
+  bool substituted = false;
   bool multibyte;
   char *xnm;
   Lisp_Object handler;
@@ -1671,8 +1691,7 @@ those `/' is discarded.  */)
   /* Always work on a copy of the string, in case GC happens during
      decode of environment variables, causing the original Lisp_String
      data to be relocated.  */
-  nm = alloca (SBYTES (filename) + 1);
-  memcpy (nm, SDATA (filename), SBYTES (filename) + 1);
+  nm = xlispstrdupa (filename);
 
 #ifdef DOS_NT
   dostounix_filename (nm, multibyte);
@@ -1689,66 +1708,19 @@ those `/' is discarded.  */)
     return Fsubstitute_in_file_name
       (make_specified_string (p, -1, endp - p, multibyte));
 
-  /* See if any variables are substituted into the string
-     and find the total length of their values in `total'.  */
+  /* See if any variables are substituted into the string.  */
 
-  for (p = nm; p != endp;)
-    if (*p != '$')
-      p++;
-    else
-      {
-       p++;
-       if (p == endp)
-         goto badsubst;
-       else if (*p == '$')
-         {
-           /* "$$" means a single "$".  */
-           p++;
-           total -= 1;
-           substituted = 1;
-           continue;
-         }
-       else if (*p == '{')
-         {
-           o = ++p;
-           p = memchr (p, '}', endp - p);
-           if (! p)
-             goto missingclose;
-           s = p;
-         }
-       else
-         {
-           o = p;
-           while (p != endp && (c_isalnum (*p) || *p == '_')) p++;
-           s = p;
-         }
-
-       /* Copy out the variable name.  */
-       target = alloca (s - o + 1);
-       memcpy (target, o, s - o);
-       target[s - o] = 0;
-#ifdef DOS_NT
-       strupr (target); /* $home == $HOME etc.  */
-#endif /* DOS_NT */
-
-       /* Get variable value.  */
-       o = egetenv (target);
-       if (o)
-         {
-           /* Don't try to guess a maximum length - UTF8 can use up to
-              four bytes per character.  This code is unlikely to run
-              in a situation that requires performance, so decoding the
-              env variables twice should be acceptable. Note that
-              decoding may cause a garbage collect.  */
-           Lisp_Object orig, decoded;
-           orig = build_unibyte_string (o);
-           decoded = DECODE_FILE (orig);
-           total += SBYTES (decoded);
-           substituted = 1;
-         }
-       else if (*p == '}')
-         goto badvar;
-      }
+  if (!NILP (Ffboundp (Qsubstitute_env_in_file_name)))
+    {
+      Lisp_Object name
+       = (!substituted ? filename
+          : make_specified_string (nm, -1, endp - nm, multibyte));
+      Lisp_Object tmp = call1 (Qsubstitute_env_in_file_name, name);
+      CHECK_STRING (tmp);
+      if (!EQ (tmp, name))
+       substituted = true;
+      filename = tmp;
+    }
 
   if (!substituted)
     {
@@ -1759,73 +1731,9 @@ those `/' is discarded.  */)
       return filename;
     }
 
-  /* If substitution required, recopy the string and do it.  */
-  /* Make space in stack frame for the new copy.  */
-  xnm = alloca (SBYTES (filename) + total + 1);
-  x = xnm;
-
-  /* Copy the rest of the name through, replacing $ constructs with values.  */
-  for (p = nm; *p;)
-    if (*p != '$')
-      *x++ = *p++;
-    else
-      {
-       p++;
-       if (p == endp)
-         goto badsubst;
-       else if (*p == '$')
-         {
-           *x++ = *p++;
-           continue;
-         }
-       else if (*p == '{')
-         {
-           o = ++p;
-           p = memchr (p, '}', endp - p);
-           if (! p)
-             goto missingclose;
-           s = p++;
-         }
-       else
-         {
-           o = p;
-           while (p != endp && (c_isalnum (*p) || *p == '_')) p++;
-           s = p;
-         }
-
-       /* Copy out the variable name.  */
-       target = alloca (s - o + 1);
-       memcpy (target, o, s - o);
-       target[s - o] = 0;
-
-       /* Get variable value.  */
-       o = egetenv (target);
-       if (!o)
-         {
-           *x++ = '$';
-           strcpy (x, target); x+= strlen (target);
-         }
-       else
-         {
-           Lisp_Object orig, decoded;
-           ptrdiff_t orig_length, decoded_length;
-           orig_length = strlen (o);
-           orig = make_unibyte_string (o, orig_length);
-           decoded = DECODE_FILE (orig);
-           decoded_length = SBYTES (decoded);
-           memcpy (x, SDATA (decoded), decoded_length);
-           x += decoded_length;
-
-           /* If environment variable needed decoding, return value
-              needs to be multibyte.  */
-           if (decoded_length != orig_length
-               || memcmp (SDATA (decoded), o, orig_length))
-             multibyte = 1;
-         }
-      }
-
-  *x = 0;
-
+  xnm = SSDATA (filename);
+  x = xnm + SBYTES (filename);
+  
   /* If /~ or // appears, discard everything through first slash.  */
   while ((p = search_embedded_absfilename (xnm, x)) != NULL)
     /* This time we do not start over because we've already expanded envvars
@@ -1843,14 +1751,9 @@ those `/' is discarded.  */)
     }
   else
 #endif
-  return make_specified_string (xnm, -1, x - xnm, multibyte);
-
- badsubst:
-  error ("Bad format environment-variable substitution");
- missingclose:
-  error ("Missing \"}\" in environment-variable substitution");
- badvar:
-  error ("Substituting nonexistent environment variable \"%s\"", target);
+  return (xnm == SSDATA (filename)
+         ? filename
+         : make_specified_string (xnm, -1, x - xnm, multibyte));
 }
 \f
 /* A slightly faster and more convenient way to get
@@ -2012,7 +1915,7 @@ entries (depending on how Emacs was built).  */)
     {
       acl = acl_get_file (SDATA (encoded_file), ACL_TYPE_ACCESS);
       if (acl == NULL && acl_errno_valid (errno))
-       report_file_error ("Getting ACL", Fcons (file, Qnil));
+       report_file_error ("Getting ACL", file);
     }
   if (!CopyFile (SDATA (encoded_file),
                 SDATA (encoded_newname),
@@ -2020,16 +1923,13 @@ 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))
     {
-      EMACS_TIME now;
+      struct timespec now;
       DWORD attributes;
       char * filename;
 
@@ -2038,7 +1938,7 @@ entries (depending on how Emacs was built).  */)
       /* Ensure file is writable while its modified time is set.  */
       attributes = GetFileAttributes (filename);
       SetFileAttributes (filename, attributes & ~FILE_ATTRIBUTE_READONLY);
-      now = current_emacs_time ();
+      now = current_timespec ();
       if (set_file_times (-1, filename, now, now))
        {
          /* Restore original attributes.  */
@@ -2054,7 +1954,7 @@ entries (depending on how Emacs was built).  */)
       bool fail =
        acl_set_file (SDATA (encoded_newname), ACL_TYPE_ACCESS, acl) != 0;
       if (fail && acl_errno_valid (errno))
-       report_file_error ("Setting ACL", Fcons (newname, Qnil));
+       report_file_error ("Setting ACL", newname);
 
       acl_free (acl);
     }
@@ -2064,12 +1964,12 @@ entries (depending on how Emacs was built).  */)
   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))
     {
@@ -2078,53 +1978,42 @@ 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
     }
 
   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
@@ -2152,8 +2041,8 @@ entries (depending on how Emacs was built).  */)
                         st.st_mode & mode_mask)
            : fchmod (ofd, st.st_mode & mode_mask))
       {
-      case -2: report_file_error ("Copying permissions from", list1 (file));
-      case -1: report_file_error ("Copying permissions to", list1 (newname));
+      case -2: report_file_error ("Copying permissions from", file);
+      case -1: report_file_error ("Copying permissions to", newname);
       }
   }
 #endif /* not MSDOS */
@@ -2165,7 +2054,7 @@ 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);
     }
@@ -2173,15 +2062,15 @@ entries (depending on how Emacs was built).  */)
 
   if (!NILP (keep_time))
     {
-      EMACS_TIME atime = get_stat_atime (&st);
-      EMACS_TIME mtime = get_stat_mtime (&st);
+      struct timespec atime = get_stat_atime (&st);
+      struct timespec mtime = get_stat_mtime (&st);
       if (set_file_times (ofd, SSDATA (encoded_newname), atime, mtime))
        xsignal2 (Qfile_date_error,
                  build_string ("Cannot set file date"), newname);
     }
 
   if (emacs_close (ofd) < 0)
-    report_file_error ("I/O error", Fcons (newname, Qnil));
+    report_file_error ("Write error", newname);
 
   emacs_close (ifd);
 
@@ -2227,7 +2116,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;
 }
@@ -2246,7 +2135,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;
 }
@@ -2289,7 +2178,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;
 }
 
@@ -2371,7 +2260,8 @@ This is what happens in interactive use with M-x.  */)
                                  INTEGERP (ok_if_already_exists), 0, 0);
   if (rename (SSDATA (encoded_file), SSDATA (encoded_newname)) < 0)
     {
-      if (errno == EXDEV)
+      int rename_errno = errno;
+      if (rename_errno == EXDEV)
        {
           ptrdiff_t count;
           symlink_target = Ffile_symlink_p (file);
@@ -2397,7 +2287,7 @@ This is what happens in interactive use with M-x.  */)
          unbind_to (count, Qnil);
        }
       else
-       report_file_error ("Renaming", list2 (file, newname));
+       report_file_errno ("Renaming", list2 (file, newname), rename_errno);
     }
   UNGCPRO;
   return Qnil;
@@ -2451,7 +2341,10 @@ This is what happens in interactive use with M-x.  */)
 
   unlink (SSDATA (newname));
   if (link (SSDATA (encoded_file), SSDATA (encoded_newname)) < 0)
-    report_file_error ("Adding new name", list2 (file, newname));
+    {
+      int link_errno = errno;
+      report_file_errno ("Adding new name", list2 (file, newname), link_errno);
+    }
 
   UNGCPRO;
   return Qnil;
@@ -2510,6 +2403,7 @@ This happens for interactive use with M-x.  */)
   if (symlink (SSDATA (encoded_filename), SSDATA (encoded_linkname)) < 0)
     {
       /* If we didn't complain already, silently delete existing file.  */
+      int symlink_errno;
       if (errno == EEXIST)
        {
          unlink (SSDATA (encoded_linkname));
@@ -2527,7 +2421,9 @@ This happens for interactive use with M-x.  */)
                    build_string ("Symbolic links are not supported"));
        }
 
-      report_file_error ("Making symbolic link", list2 (filename, linkname));
+      symlink_errno = errno;
+      report_file_errno ("Making symbolic link", list2 (filename, linkname),
+                        symlink_errno);
     }
   UNGCPRO;
   return Qnil;
@@ -2610,7 +2506,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);
 
@@ -2707,7 +2607,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);
@@ -2722,10 +2621,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;
 }
@@ -2834,7 +2731,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;
@@ -3056,14 +2957,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
 
@@ -3153,7 +3054,7 @@ support.  */)
       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;
        }
 
@@ -3163,7 +3064,7 @@ support.  */)
                            acl)
              != 0);
       if (fail && acl_errno_valid (errno))
-       report_file_error ("Setting ACL", Fcons (absname, Qnil));
+       report_file_error ("Setting ACL", absname);
 
       acl_free (acl);
       return fail ? Qnil : Qt;
@@ -3223,7 +3124,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;
 }
@@ -3269,7 +3170,7 @@ Use the current time if TIMESTAMP is nil.  TIMESTAMP is in the format of
 {
   Lisp_Object absname, encoded_absname;
   Lisp_Object handler;
-  EMACS_TIME t = lisp_time_argument (timestamp);
+  struct timespec t = lisp_time_argument (timestamp);
 
   absname = Fexpand_file_name (filename, BVAR (current_buffer, directory));
 
@@ -3289,7 +3190,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));
+        report_file_error ("Setting file times", absname);
       }
   }
 
@@ -3346,7 +3247,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 (timespec_cmp (get_stat_mtime (&st2), get_stat_mtime (&st1)) < 0
          ? Qt : Qnil);
 }
 \f
@@ -3371,7 +3272,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;
@@ -3390,8 +3291,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
@@ -3448,13 +3347,13 @@ file_offset (Lisp_Object val)
 }
 
 /* Return a special time value indicating the error number ERRNUM.  */
-static EMACS_TIME
+static struct timespec
 time_error_value (int errnum)
 {
   int ns = (errnum == ENOENT || errnum == EACCES || errnum == ENOTDIR
            ? NONEXISTENT_MODTIME_NSECS
            : UNKNOWN_MODTIME_NSECS);
-  return make_emacs_time (0, ns);
+  return make_timespec (0, ns);
 }
 
 DEFUN ("insert-file-contents", Finsert_file_contents, Sinsert_file_contents,
@@ -3486,10 +3385,9 @@ by calling `format-decode', which see.  */)
   (Lisp_Object filename, Lisp_Object visit, Lisp_Object beg, Lisp_Object end, Lisp_Object replace)
 {
   struct stat st;
-  EMACS_TIME mtime;
+  struct timespec mtime;
   int fd;
   ptrdiff_t inserted = 0;
-  bool nochange = 0;
   ptrdiff_t how_much;
   off_t beg_offset, end_offset;
   int unprocessed;
@@ -3506,9 +3404,14 @@ 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;
+  ptrdiff_t fd_index;
 
   if (current_buffer->base_buffer && ! NILP (visit))
     error ("Cannot do file visiting in an indirect buffer");
@@ -3551,7 +3454,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))
@@ -3559,14 +3462,15 @@ by calling `format-decode', which see.  */)
       goto notfound;
     }
 
+  fd_index = SPECPDL_INDEX ();
+  record_unwind_protect_int (close_file_unwind, fd);
+
   /* Replacement should preserve point as it preserves markers.  */
   if (!NILP (replace))
     record_unwind_protect (restore_point_unwind, Fpoint_marker ());
 
-  record_unwind_protect (close_file_unwind, make_number (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
@@ -3680,15 +3584,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;
@@ -3724,8 +3627,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);
                }
            }
 
@@ -3791,8 +3693,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;
@@ -3805,8 +3706,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;
 
@@ -3841,7 +3741,8 @@ by calling `format-decode', which see.  */)
       if (same_at_start - BEGV_BYTE == end_offset - beg_offset)
        {
          emacs_close (fd);
-         specpdl_ptr--;
+         clear_unwind_protect (fd_index);
+
          /* Truncate the buffer to the size of the file.  */
          del_range_1 (same_at_start, same_at_end, 0, 0);
          goto handled;
@@ -3864,16 +3765,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;
@@ -3985,8 +3884,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.  */
@@ -4016,16 +3914,10 @@ by calling `format-decode', which see.  */)
            memcpy (read_buf, coding.carryover, unprocessed);
        }
       UNGCPRO;
-      emacs_close (fd);
-
-      /* We should remove the unwind_protect calling
-        close_file_unwind, but other stuff has been added the stack,
-        so defer the removal till we reach the `handled' label.  */
-      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);
+      emacs_close (fd);
+      clear_unwind_protect (fd_index);
 
       if (unprocessed > 0)
        {
@@ -4055,9 +3947,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;
 
@@ -4108,6 +3998,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
@@ -4120,6 +4011,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
@@ -4166,8 +4058,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
@@ -4206,8 +4097,7 @@ 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 (SAVE_TYPE_INT_INT_INT, (ptrdiff_t) fd,
-                               inserted, trytry),
+              make_save_int_int_int (fd, inserted, trytry),
               Qerror, read_non_regular_quit);
 
            if (NILP (nbytes))
@@ -4267,13 +4157,10 @@ by calling `format-decode', which see.  */)
     Vdeactivate_mark = Qt;
 
   emacs_close (fd);
-
-  /* Discard the unwind protect for closing the file.  */
-  specpdl_ptr--;
+  clear_unwind_protect (fd_index);
 
   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;
@@ -4397,14 +4284,9 @@ by calling `format-decode', which see.  */)
 
  handled:
 
-  if (deferred_remove_unwind_protect)
-    /* If requested above, discard the unwind protect for closing the
-       file.  */
-    specpdl_ptr--;
-
   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))
@@ -4546,7 +4428,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)
@@ -4569,11 +4451,10 @@ by calling `format-decode', which see.  */)
     }
 
   if (!NILP (visit)
-      && EMACS_NSECS (current_buffer->modtime) == NONEXISTENT_MODTIME_NSECS)
+      && current_buffer->modtime.tv_nsec == 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)
@@ -4588,11 +4469,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.  */
@@ -4629,7 +4509,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
@@ -4747,25 +4627,39 @@ This does code conversion according to the value of
 
 This calls `write-region-annotate-functions' at the start, and
 `write-region-post-annotation-function' at the end.  */)
-  (Lisp_Object start, Lisp_Object end, Lisp_Object filename, Lisp_Object append, Lisp_Object visit, Lisp_Object lockname, Lisp_Object mustbenew)
+  (Lisp_Object start, Lisp_Object end, Lisp_Object filename, Lisp_Object append,
+   Lisp_Object visit, Lisp_Object lockname, Lisp_Object mustbenew)
+{
+  return write_region (start, end, filename, append, visit, lockname, mustbenew,
+                      -1);
+}
+
+/* Like Fwrite_region, except that if DESC is nonnegative, it is a file
+   descriptor for FILENAME, so do not open or close FILENAME.  */
+
+Lisp_Object
+write_region (Lisp_Object start, Lisp_Object end, Lisp_Object filename,
+             Lisp_Object append, Lisp_Object visit, Lisp_Object lockname,
+             Lisp_Object mustbenew, int desc)
 {
-  int desc;
   int open_flags;
   int mode;
   off_t offset IF_LINT (= 0);
+  bool open_and_close_file = desc < 0;
   bool ok;
   int save_errno = 0;
   const char *fn;
   struct stat st;
-  EMACS_TIME modtime;
+  struct timespec modtime;
   ptrdiff_t count = SPECPDL_INDEX ();
-  int count1;
+  ptrdiff_t count1 IF_LINT (= 0);
   Lisp_Object handler;
   Lisp_Object visit_file;
   Lisp_Object annotations;
   Lisp_Object encoded_filename;
   bool visiting = (EQ (visit, Qt) || STRINGP (visit));
   bool quietly = !NILP (visit);
+  bool file_locked = 0;
   struct gcpro gcpro1, gcpro2, gcpro3, gcpro4, gcpro5;
   struct buffer *given_buffer;
   struct coding_system coding;
@@ -4832,8 +4726,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);
-  count1 = SPECPDL_INDEX ();
+  Vwrite_region_annotation_buffers = list1 (Fcurrent_buffer ());
 
   given_buffer = current_buffer;
 
@@ -4872,8 +4765,11 @@ This calls `write-region-annotate-functions' at the start, and
     coding.mode |= CODING_MODE_SELECTIVE_DISPLAY;
 
 #ifdef CLASH_DETECTION
-  if (!auto_saving)
-    lock_file (lockname);
+  if (open_and_close_file && !auto_saving)
+    {
+      lock_file (lockname);
+      file_locked = 1;
+    }
 #endif /* CLASH_DETECTION */
 
   encoded_filename = ENCODE_FILE (filename);
@@ -4890,33 +4786,36 @@ This calls `write-region-annotate-functions' at the start, and
   mode = auto_saving ? auto_save_mode_bits : 0666;
 #endif
 
-  desc = emacs_open (fn, open_flags, mode);
-
-  if (desc < 0)
+  if (open_and_close_file)
     {
+      desc = emacs_open (fn, open_flags, mode);
+      if (desc < 0)
+       {
+         int open_errno = errno;
 #ifdef CLASH_DETECTION
-      save_errno = errno;
-      if (!auto_saving) unlock_file (lockname);
-      errno = save_errno;
+         if (file_locked)
+           unlock_file (lockname);
 #endif /* CLASH_DETECTION */
-      UNGCPRO;
-      report_file_error ("Opening output file", Fcons (filename, Qnil));
-    }
+         UNGCPRO;
+         report_file_errno ("Opening output file", filename, open_errno);
+       }
 
-  record_unwind_protect (close_file_unwind, make_number (desc));
+      count1 = SPECPDL_INDEX ();
+      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;
+         if (file_locked)
+           unlock_file (lockname);
 #endif /* CLASH_DETECTION */
          UNGCPRO;
-         report_file_error ("Lseek error", Fcons (filename, Qnil));
+         report_file_errno ("Lseek error", filename, lseek_errno);
        }
     }
 
@@ -4948,9 +4847,9 @@ This calls `write-region-annotate-functions' at the start, and
 
   immediate_quit = 0;
 
-  /* fsync is not crucial for auto-save files, since they might lose
-     some work anyway.  */
-  if (!auto_saving && !write_region_inhibit_fsync)
+  /* fsync is not crucial for temporary files.  Nor for auto-save
+     files, since they might lose some work anyway.  */
+  if (open_and_close_file && !auto_saving && !write_region_inhibit_fsync)
     {
       /* Transfer data and metadata to disk, retrying if interrupted.
         fsync can report a write failure here, e.g., due to disk full
@@ -4965,7 +4864,7 @@ This calls `write-region-annotate-functions' at the start, and
          }
     }
 
-  modtime = invalid_emacs_time ();
+  modtime = invalid_timespec ();
   if (visiting)
     {
       if (fstat (desc, &st) == 0)
@@ -4974,12 +4873,15 @@ This calls `write-region-annotate-functions' at the start, and
        ok = 0, save_errno = errno;
     }
 
-  /* NFS can report a write failure now.  */
-  if (emacs_close (desc) < 0)
-    ok = 0, save_errno = errno;
+  if (open_and_close_file)
+    {
+      /* NFS can report a write failure now.  */
+      if (emacs_close (desc) < 0)
+       ok = 0, save_errno = errno;
 
-  /* Discard the unwind protect for close_file_unwind.  */
-  specpdl_ptr = specpdl + count1;
+      /* Discard the unwind protect for close_file_unwind.  */
+      specpdl_ptr = specpdl + count1;
+    }
 
   /* Some file systems have a bug where st_mtime is not updated
      properly after a write.  For example, CIFS might not see the
@@ -4996,7 +4898,7 @@ This calls `write-region-annotate-functions' at the start, and
      unlikely and a similar race between the last write and the fstat
      above cannot possibly be closed anyway.  */
 
-  if (EMACS_TIME_VALID_P (modtime)
+  if (timespec_valid_p (modtime)
       && ! (valid_timestamp_file_system && st.st_dev == timestamp_file_system))
     {
       int desc1 = emacs_open (fn, O_WRONLY | O_BINARY, 0);
@@ -5018,11 +4920,11 @@ This calls `write-region-annotate-functions' at the start, and
              bool use_heuristic
                = ((open_flags & (O_EXCL | O_TRUNC)) != 0
                   && st.st_size != 0
-                  && EMACS_NSECS (modtime) % 100 != 0);
+                  && modtime.tv_nsec % 100 != 0);
 
-             EMACS_TIME modtime1 = get_stat_mtime (&st1);
+             struct timespec modtime1 = get_stat_mtime (&st1);
              if (use_heuristic
-                 && EMACS_TIME_EQ (modtime, modtime1)
+                 && timespec_cmp (modtime, modtime1) == 0
                  && st.st_size == st1.st_size)
                {
                  timestamp_file_system = st.st_dev;
@@ -5055,22 +4957,21 @@ This calls `write-region-annotate-functions' at the start, and
   unbind_to (count, Qnil);
 
 #ifdef CLASH_DETECTION
-  if (!auto_saving)
+  if (file_locked)
     unlock_file (lockname);
 #endif /* CLASH_DETECTION */
 
   /* Do this before reporting IO error
      to avoid a "file has changed on disk" warning on
      next attempt to save.  */
-  if (EMACS_TIME_VALID_P (modtime))
+  if (timespec_valid_p (modtime))
     {
       current_buffer->modtime = modtime;
       current_buffer->modtime_size = st.st_size;
     }
 
   if (! ok)
-    error ("IO error writing %s: %s", SDATA (filename),
-          emacs_strerror (save_errno));
+    report_file_errno ("Write error", filename, save_errno);
 
   if (visiting)
     {
@@ -5100,13 +5001,12 @@ This calls `write-region-annotate-functions' at the start, and
   return Qnil;
 }
 \f
-Lisp_Object merge (Lisp_Object, Lisp_Object, Lisp_Object);
-
 DEFUN ("car-less-than-car", Fcar_less_than_car, Scar_less_than_car, 2, 2, 0,
        doc: /* Return t if (car A) is numerically less than (car B).  */)
   (Lisp_Object a, Lisp_Object b)
 {
-  return Flss (Fcar (a), Fcar (b));
+  Lisp_Object args[2] = { Fcar (a), Fcar (b), };
+  return Flss (2, args);
 }
 
 /* Build the complete list of annotations appropriate for writing out
@@ -5247,6 +5147,10 @@ a_write (int desc, Lisp_Object string, ptrdiff_t pos,
   return 1;
 }
 
+/* Maximum number of characters that the next
+   function encodes per one loop iteration.  */
+
+enum { E_WRITE_MAX = 8 * 1024 * 1024 };
 
 /* Write text in the range START and END into descriptor DESC,
    encoding them with coding system CODING.  If STRING is nil, START
@@ -5273,9 +5177,16 @@ e_write (int desc, Lisp_Object string, ptrdiff_t start, ptrdiff_t end,
          coding->src_multibyte = SCHARS (string) < SBYTES (string);
          if (CODING_REQUIRE_ENCODING (coding))
            {
-             encode_coding_object (coding, string,
-                                   start, string_char_to_byte (string, start),
-                                   end, string_char_to_byte (string, end), Qt);
+             ptrdiff_t nchars = min (end - start, E_WRITE_MAX);
+
+             /* Avoid creating huge Lisp string in encode_coding_object.  */
+             if (nchars == E_WRITE_MAX)
+               coding->raw_destination = 1;
+
+             encode_coding_object
+               (coding, string, start, string_char_to_byte (string, start),
+                start + nchars, string_char_to_byte (string, start + nchars),
+                Qt);
            }
          else
            {
@@ -5292,8 +5203,15 @@ e_write (int desc, Lisp_Object string, ptrdiff_t start, ptrdiff_t end,
          coding->src_multibyte = (end - start) < (end_byte - start_byte);
          if (CODING_REQUIRE_ENCODING (coding))
            {
-             encode_coding_object (coding, Fcurrent_buffer (),
-                                   start, start_byte, end, end_byte, Qt);
+             ptrdiff_t nchars = min (end - start, E_WRITE_MAX);
+
+             /* Likewise.  */
+             if (nchars == E_WRITE_MAX)
+               coding->raw_destination = 1;
+
+             encode_coding_object
+               (coding, Fcurrent_buffer (), start, start_byte,
+                start + nchars, CHAR_TO_BYTE (start + nchars), Qt);
            }
          else
            {
@@ -5314,13 +5232,19 @@ 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 = (coding->raw_destination ? (char *) coding->destination
+                      : (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->raw_destination)
+           {
+             /* We're responsible for freeing this, see
+                encode_coding_object to check why.  */
+             xfree (coding->destination);
+             coding->raw_destination = 0;
+           }
          if (coding->produced)
            return 0;
        }
@@ -5342,7 +5266,7 @@ See Info node `(elisp)Modification Time' for more details.  */)
   struct stat st;
   Lisp_Object handler;
   Lisp_Object filename;
-  EMACS_TIME mtime;
+  struct timespec mtime;
 
   if (NILP (buf))
     b = current_buffer;
@@ -5353,7 +5277,7 @@ See Info node `(elisp)Modification Time' for more details.  */)
     }
 
   if (!STRINGP (BVAR (b, filename))) return Qt;
-  if (EMACS_NSECS (b->modtime) == UNKNOWN_MODTIME_NSECS) return Qt;
+  if (b->modtime.tv_nsec == UNKNOWN_MODTIME_NSECS) return Qt;
 
   /* If the file name has special constructs in it,
      call the corresponding file handler.  */
@@ -5367,43 +5291,26 @@ See Info node `(elisp)Modification Time' for more details.  */)
   mtime = (stat (SSDATA (filename), &st) == 0
           ? get_stat_mtime (&st)
           : time_error_value (errno));
-  if (EMACS_TIME_EQ (mtime, b->modtime)
+  if (timespec_cmp (mtime, b->modtime) == 0
       && (b->modtime_size < 0
          || st.st_size == b->modtime_size))
     return Qt;
   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 list4i (-1, 65535, 0, 0);
-       }
-      return make_number (0);
-    }
+  int ns = current_buffer->modtime.tv_nsec;
+  if (ns < 0)
+    return make_number (UNKNOWN_MODTIME_NSECS - ns);
   return make_lisp_time (current_buffer->modtime);
 }
 
@@ -5414,12 +5321,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);
+      struct timespec mtime;
+      if (INTEGERP (time_flag))
+       {
+         CHECK_RANGED_INTEGER (time_flag, -1, 0);
+         mtime = make_timespec (0, UNKNOWN_MODTIME_NSECS - XINT (time_flag));
+       }
+      else
+       mtime = lisp_time_argument (time_flag);
+
+      current_buffer->modtime = mtime;
       current_buffer->modtime_size = -1;
     }
   else
@@ -5505,11 +5422,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)
     {
@@ -5517,15 +5441,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
@@ -5568,6 +5483,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)
@@ -5579,7 +5495,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,
@@ -5591,7 +5507,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))
     {
@@ -5615,13 +5531,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;
@@ -5635,9 +5550,8 @@ A non-nil CURRENT-ONLY argument means save only current buffer.  */)
      couldn't handle some ange-ftp'd file.  */
 
   for (do_handled_files = 0; do_handled_files < 2; do_handled_files++)
-    for (tail = Vbuffer_alist; CONSP (tail); tail = XCDR (tail))
+    FOR_EACH_LIVE_BUFFER (tail, buf)
       {
-       buf = XCDR (XCAR (tail));
        b = XBUFFER (buf);
 
        /* Record all the buffers that have auto save mode
@@ -5680,12 +5594,12 @@ A non-nil CURRENT-ONLY argument means save only current buffer.  */)
                || NILP (Ffind_file_name_handler (BVAR (b, auto_save_file_name),
                                                  Qwrite_region))))
          {
-           EMACS_TIME before_time = current_emacs_time ();
-           EMACS_TIME after_time;
+           struct timespec before_time = current_timespec ();
+           struct timespec after_time;
 
            /* If we had a failure, don't try again for 20 minutes.  */
            if (b->auto_save_failure_time > 0
-               && EMACS_SECS (before_time) - b->auto_save_failure_time < 1200)
+               && before_time.tv_sec - b->auto_save_failure_time < 1200)
              continue;
 
            set_buffer_internal (b);
@@ -5718,12 +5632,12 @@ A non-nil CURRENT-ONLY argument means save only current buffer.  */)
            XSETFASTINT (BVAR (current_buffer, save_length), Z - BEG);
            set_buffer_internal (old);
 
-           after_time = current_emacs_time ();
+           after_time = current_timespec ();
 
            /* If auto-save took more than 60 seconds,
               assume it was an NFS failure that got a timeout.  */
-           if (EMACS_SECS (after_time) - EMACS_SECS (before_time) > 60)
-             b->auto_save_failure_time = EMACS_SECS (after_time);
+           if (after_time.tv_sec - before_time.tv_sec > 60)
+             b->auto_save_failure_time = after_time.tv_sec;
          }
       }
 
@@ -5882,6 +5796,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,
@@ -5920,6 +5835,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
@@ -6072,6 +5992,7 @@ This includes interactive calls to `delete-file' and
   DEFSYM (Qmove_file_to_trash, "move-file-to-trash");
   DEFSYM (Qcopy_directory, "copy-directory");
   DEFSYM (Qdelete_directory, "delete-directory");
+  DEFSYM (Qsubstitute_env_in_file_name, "substitute-env-in-file-name");
 
   defsubr (&Sfind_file_name_handler);
   defsubr (&Sfile_name_directory);
@@ -6114,7 +6035,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);