use dynwind_begin and dynwind_end
[bpt/emacs.git] / src / fileio.c
index 02bde86..e000890 100644 (file)
@@ -1,6 +1,6 @@
 /* File IO for GNU Emacs.
 
-Copyright (C) 1985-1988, 1993-2013 Free Software Foundation, Inc.
+Copyright (C) 1985-1988, 1993-2014 Free Software Foundation, Inc.
 
 This file is part of GNU Emacs.
 
@@ -95,6 +95,9 @@ along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
 /* True during writing of auto-save files.  */
 static bool auto_saving;
 
+/* Emacs's real umask.  */
+static mode_t realmask;
+
 /* Nonzero umask during creation of auto-save directories.  */
 static mode_t auto_saving_dir_umask;
 
@@ -270,6 +273,14 @@ close_file_unwind (int fd)
   emacs_close (fd);
 }
 
+void
+close_file_ptr_unwind (void *fdp)
+{
+  int fd = *((int *) fdp);
+  if (fd >= 0)
+    emacs_close (fd);
+}
+
 void
 fclose_unwind (void *arg)
 {
@@ -277,6 +288,14 @@ fclose_unwind (void *arg)
   fclose (stream);
 }
 
+void
+fclose_ptr_unwind (void *arg)
+{
+  FILE *stream = *((void **) arg);
+  if (stream)
+    fclose (stream);
+}
+
 /* Restore point, having saved it as a marker.  */
 
 void
@@ -987,7 +1006,7 @@ filesystem tree, not (expand-file-name ".."  dirname).  */)
        {
          unsigned char *p = SDATA (name);
 
-         while (*p && ASCII_BYTE_P (*p))
+         while (*p && ASCII_CHAR_P (*p))
            p++;
          if (*p == '\0')
            {
@@ -1044,10 +1063,9 @@ filesystem tree, not (expand-file-name ".."  dirname).  */)
     nm++;
 
   /* Discard any previous drive specifier if nm is now in UNC format.  */
-  if (IS_DIRECTORY_SEP (nm[0]) && IS_DIRECTORY_SEP (nm[1]))
-    {
-      drive = 0;
-    }
+  if (IS_DIRECTORY_SEP (nm[0]) && IS_DIRECTORY_SEP (nm[1])
+      && !IS_DIRECTORY_SEP (nm[2]))
+    drive = 0;
 #endif /* WINDOWSNT */
 #endif /* DOS_NT */
 
@@ -1168,9 +1186,8 @@ filesystem tree, not (expand-file-name ".."  dirname).  */)
              tem = build_string (newdir_utf8);
            }
          else
-#else
-           tem = build_string (newdir);
 #endif
+           tem = build_string (newdir);
          if (multibyte && !STRING_MULTIBYTE (tem))
            {
              hdir = DECODE_FILE (tem);
@@ -1259,7 +1276,8 @@ filesystem tree, not (expand-file-name ".."  dirname).  */)
       && !IS_DIRECTORY_SEP (nm[0])
 #endif
 #ifdef WINDOWSNT
-      && !(IS_DIRECTORY_SEP (nm[0]) && IS_DIRECTORY_SEP (nm[1]))
+      && !(IS_DIRECTORY_SEP (nm[0]) && IS_DIRECTORY_SEP (nm[1])
+          && !IS_DIRECTORY_SEP (nm[2]))
 #endif
       && !newdir)
     {
@@ -1284,7 +1302,8 @@ filesystem tree, not (expand-file-name ".."  dirname).  */)
             && IS_DEVICE_SEP (newdir[1]) && IS_DIRECTORY_SEP (newdir[2]))
 #ifdef WINDOWSNT
          /* Detect Windows file names in UNC format.  */
-         && ! (IS_DIRECTORY_SEP (newdir[0]) && IS_DIRECTORY_SEP (newdir[1]))
+         && ! (IS_DIRECTORY_SEP (newdir[0]) && IS_DIRECTORY_SEP (newdir[1])
+               && !IS_DIRECTORY_SEP (newdir[2]))
 #endif
          )
        {
@@ -1344,7 +1363,8 @@ filesystem tree, not (expand-file-name ".."  dirname).  */)
       if (IS_DIRECTORY_SEP (nm[0]) && collapse_newdir)
        {
 #ifdef WINDOWSNT
-         if (IS_DIRECTORY_SEP (newdir[0]) && IS_DIRECTORY_SEP (newdir[1]))
+         if (IS_DIRECTORY_SEP (newdir[0]) && IS_DIRECTORY_SEP (newdir[1])
+             && !IS_DIRECTORY_SEP (newdir[2]))
            {
              char *adir = strcpy (alloca (strlen (newdir) + 1), newdir);
              char *p = adir + 2;
@@ -1859,20 +1879,16 @@ expand_and_dir_to_file (Lisp_Object filename, Lisp_Object defdir)
 }
 \f
 /* Signal an error if the file ABSNAME already exists.
-   If INTERACTIVE, ask the user whether to proceed,
-   and bypass the error if the user says to go ahead.
+   If KNOWN_TO_EXIST, the file is known to exist.
    QUERYSTRING is a name for the action that is being considered
    to alter the file.
-
-   *STATPTR is used to store the stat information if the file exists.
-   If the file does not exist, STATPTR->st_mode is set to 0.
-   If STATPTR is null, we don't store into it.
-
+   If INTERACTIVE, ask the user whether to proceed,
+   and bypass the error if the user says to go ahead.
    If QUICK, ask for y or n, not yes or no.  */
 
 static void
-barf_or_query_if_file_exists (Lisp_Object absname, const char *querystring,
-                             bool interactive, struct stat *statptr,
+barf_or_query_if_file_exists (Lisp_Object absname, bool known_to_exist,
+                             const char *querystring, bool interactive,
                              bool quick)
 {
   Lisp_Object tem, encoded_filename;
@@ -1881,14 +1897,16 @@ barf_or_query_if_file_exists (Lisp_Object absname, const char *querystring,
 
   encoded_filename = ENCODE_FILE (absname);
 
-  /* `stat' is a good way to tell whether the file exists,
-     regardless of what access permissions it has.  */
-  if (lstat (SSDATA (encoded_filename), &statbuf) >= 0)
+  if (! known_to_exist && lstat (SSDATA (encoded_filename), &statbuf) == 0)
     {
       if (S_ISDIR (statbuf.st_mode))
        xsignal2 (Qfile_error,
                  build_string ("File is a directory"), absname);
+      known_to_exist = true;
+    }
 
+  if (known_to_exist)
+    {
       if (! interactive)
        xsignal2 (Qfile_already_exists,
                  build_string ("File already exists"), absname);
@@ -1903,15 +1921,7 @@ barf_or_query_if_file_exists (Lisp_Object absname, const char *querystring,
       if (NILP (tem))
        xsignal2 (Qfile_already_exists,
                  build_string ("File already exists"), absname);
-      if (statptr)
-       *statptr = statbuf;
-    }
-  else
-    {
-      if (statptr)
-       statptr->st_mode = 0;
     }
-  return;
 }
 
 DEFUN ("copy-file", Fcopy_file, Scopy_file, 2, 6,
@@ -1938,18 +1948,18 @@ A prefix arg makes KEEP-TIME non-nil.
 If PRESERVE-UID-GID is non-nil, we try to transfer the
 uid and gid of FILE to NEWNAME.
 
-If PRESERVE-EXTENDED-ATTRIBUTES is non-nil, we try to copy additional
-attributes of FILE to NEWNAME, such as its SELinux context and ACL
-entries (depending on how Emacs was built).  */)
-  (Lisp_Object file, Lisp_Object newname, Lisp_Object ok_if_already_exists, Lisp_Object keep_time, Lisp_Object preserve_uid_gid, Lisp_Object preserve_extended_attributes)
+If PRESERVE-PERMISSIONS is non-nil, copy permissions of FILE to NEWNAME;
+this includes the file modes, along with ACL entries and SELinux
+context if present.  Otherwise, if NEWNAME is created its file
+permission bits are those of FILE, masked by the default file
+permissions.  */)
+  (Lisp_Object file, Lisp_Object newname, Lisp_Object ok_if_already_exists,
+   Lisp_Object keep_time, Lisp_Object preserve_uid_gid,
+   Lisp_Object preserve_permissions)
 {
-  int ifd, ofd;
-  int n;
-  char buf[16 * 1024];
-  struct stat st, out_st;
   Lisp_Object handler;
   struct gcpro gcpro1, gcpro2, gcpro3, gcpro4;
-  ptrdiff_t count = SPECPDL_INDEX ();
+  dynwind_begin ();
   Lisp_Object encoded_file, encoded_newname;
 #if HAVE_LIBSELINUX
   security_context_t con;
@@ -1957,6 +1967,13 @@ entries (depending on how Emacs was built).  */)
 #endif
 #ifdef WINDOWSNT
   int result;
+#else
+  bool already_exists = false;
+  mode_t new_mask;
+  int ifd, ofd;
+  int n;
+  char buf[16 * 1024];
+  struct stat st;
 #endif
 
   encoded_file = encoded_newname = Qnil;
@@ -1977,25 +1994,23 @@ entries (depending on how Emacs was built).  */)
   /* Likewise for output file name.  */
   if (NILP (handler))
     handler = Ffind_file_name_handler (newname, Qcopy_file);
-  if (!NILP (handler))
-    RETURN_UNGCPRO (call7 (handler, Qcopy_file, file, newname,
-                          ok_if_already_exists, keep_time, preserve_uid_gid,
-                          preserve_extended_attributes));
+  if (!NILP (handler)) {
+    dynwind_end ();
+    return call7 (handler, Qcopy_file, file, newname, ok_if_already_exists, keep_time, preserve_uid_gid, preserve_permissions);
+  }
 
   encoded_file = ENCODE_FILE (file);
   encoded_newname = ENCODE_FILE (newname);
 
+#ifdef WINDOWSNT
   if (NILP (ok_if_already_exists)
       || INTEGERP (ok_if_already_exists))
-    barf_or_query_if_file_exists (newname, "copy to it",
-                                 INTEGERP (ok_if_already_exists), &out_st, 0);
-  else if (stat (SSDATA (encoded_newname), &out_st) < 0)
-    out_st.st_mode = 0;
+    barf_or_query_if_file_exists (newname, false, "copy to it",
+                                 INTEGERP (ok_if_already_exists), false);
 
-#ifdef WINDOWSNT
   result = w32_copy_file (SSDATA (encoded_file), SSDATA (encoded_newname),
                          !NILP (keep_time), !NILP (preserve_uid_gid),
-                         !NILP (preserve_extended_attributes));
+                         !NILP (preserve_permissions));
   switch (result)
     {
     case -1:
@@ -2016,12 +2031,12 @@ entries (depending on how Emacs was built).  */)
   if (ifd < 0)
     report_file_error ("Opening input file", file);
 
-  record_unwind_protect_int (close_file_unwind, ifd);
+  record_unwind_protect_int_1 (close_file_unwind, ifd, false);
 
   if (fstat (ifd, &st) != 0)
     report_file_error ("Input file status", file);
 
-  if (!NILP (preserve_extended_attributes))
+  if (!NILP (preserve_permissions))
     {
 #if HAVE_LIBSELINUX
       if (is_selinux_enabled ())
@@ -2033,31 +2048,43 @@ entries (depending on how Emacs was built).  */)
 #endif
     }
 
-  if (out_st.st_mode != 0
-      && st.st_dev == out_st.st_dev && st.st_ino == out_st.st_ino)
-    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))
     report_file_errno ("Non-regular file", file,
                       S_ISDIR (st.st_mode) ? EISDIR : EINVAL);
 
-  {
 #ifndef MSDOS
-    int new_mask = st.st_mode & (!NILP (preserve_uid_gid) ? 0600 : 0666);
+  new_mask = st.st_mode & (!NILP (preserve_uid_gid) ? 0700 : 0777);
 #else
-    int new_mask = S_IREAD | S_IWRITE;
+  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);
-  }
+
+  ofd = emacs_open (SSDATA (encoded_newname), O_WRONLY | O_CREAT | O_EXCL,
+                   new_mask);
+  if (ofd < 0 && errno == EEXIST)
+    {
+      if (NILP (ok_if_already_exists) || INTEGERP (ok_if_already_exists))
+       barf_or_query_if_file_exists (newname, true, "copy to it",
+                                     INTEGERP (ok_if_already_exists), false);
+      already_exists = true;
+      ofd = emacs_open (SSDATA (encoded_newname), O_WRONLY, 0);
+    }
   if (ofd < 0)
     report_file_error ("Opening output file", newname);
 
-  record_unwind_protect_int (close_file_unwind, ofd);
+  record_unwind_protect_int_1 (close_file_unwind, ofd, false);
+
+  if (already_exists)
+    {
+      struct stat out_st;
+      if (fstat (ofd, &out_st) != 0)
+       report_file_error ("Output file status", newname);
+      if (st.st_dev == out_st.st_dev && st.st_ino == out_st.st_ino)
+       report_file_errno ("Input and output files are the same",
+                          list2 (file, newname), 0);
+      if (ftruncate (ofd, 0) != 0)
+       report_file_error ("Truncating output file", newname);
+    }
 
   immediate_quit = 1;
   QUIT;
@@ -2070,26 +2097,41 @@ entries (depending on how Emacs was built).  */)
   /* Preserve the original file permissions, and if requested, also its
      owner and group.  */
   {
-    mode_t mode_mask = 07777;
+    mode_t preserved_permissions = st.st_mode & 07777;
+    mode_t default_permissions = st.st_mode & 0777 & ~realmask;
     if (!NILP (preserve_uid_gid))
       {
        /* Attempt to change owner and group.  If that doesn't work
           attempt to change just the group, as that is sometimes allowed.
           Adjust the mode mask to eliminate setuid or setgid bits
-          that are inappropriate if the owner and group are wrong.  */
+          or group permissions bits that are inappropriate if the
+          owner or group are wrong.  */
        if (fchown (ofd, st.st_uid, st.st_gid) != 0)
          {
-           mode_mask &= ~06000;
            if (fchown (ofd, -1, st.st_gid) == 0)
-             mode_mask |= 02000;
+             preserved_permissions &= ~04000;
+           else
+             {
+               preserved_permissions &= ~06000;
+
+               /* Copy the other bits to the group bits, since the
+                  group is wrong.  */
+               preserved_permissions &= ~070;
+               preserved_permissions |= (preserved_permissions & 7) << 3;
+               default_permissions &= ~070;
+               default_permissions |= (default_permissions & 7) << 3;
+             }
          }
       }
 
-    switch (!NILP (preserve_extended_attributes)
+    switch (!NILP (preserve_permissions)
            ? qcopy_acl (SSDATA (encoded_file), ifd,
                         SSDATA (encoded_newname), ofd,
-                        st.st_mode & mode_mask)
-           : fchmod (ofd, st.st_mode & mode_mask))
+                        preserved_permissions)
+           : (already_exists
+              || (new_mask & ~realmask) == default_permissions)
+           ? 0
+           : fchmod (ofd, default_permissions))
       {
       case -2: report_file_error ("Copying permissions from", file);
       case -1: report_file_error ("Copying permissions to", newname);
@@ -2134,8 +2176,7 @@ entries (depending on how Emacs was built).  */)
 #endif /* MSDOS */
 #endif /* not WINDOWSNT */
 
-  /* Discard the unwind protects.  */
-  specpdl_ptr = specpdl + count;
+  dynwind_end ();
 
   UNGCPRO;
   return Qnil;
@@ -2292,8 +2333,7 @@ This is what happens in interactive use with M-x.  */)
   if (NILP (handler))
     handler = Ffind_file_name_handler (newname, Qrename_file);
   if (!NILP (handler))
-    RETURN_UNGCPRO (call4 (handler, Qrename_file,
-                          file, newname, ok_if_already_exists));
+    return call4 (handler, Qrename_file, file, newname, ok_if_already_exists);
 
   encoded_file = ENCODE_FILE (file);
   encoded_newname = ENCODE_FILE (newname);
@@ -2306,8 +2346,8 @@ This is what happens in interactive use with M-x.  */)
 #endif
   if (NILP (ok_if_already_exists)
       || INTEGERP (ok_if_already_exists))
-    barf_or_query_if_file_exists (newname, "rename to it",
-                                 INTEGERP (ok_if_already_exists), 0, 0);
+    barf_or_query_if_file_exists (newname, false, "rename to it",
+                                 INTEGERP (ok_if_already_exists), false);
   if (rename (SSDATA (encoded_file), SSDATA (encoded_newname)) < 0)
     {
       int rename_errno = errno;
@@ -2327,14 +2367,14 @@ This is what happens in interactive use with M-x.  */)
                        NILP (ok_if_already_exists) ? Qnil : Qt,
                        Qt, Qt, Qt);
 
-         count = SPECPDL_INDEX ();
+         dynwind_begin ();
          specbind (Qdelete_by_moving_to_trash, Qnil);
 
          if (!NILP (Ffile_directory_p (file)) && NILP (symlink_target))
            call2 (Qdelete_directory, file, Qt);
          else
            Fdelete_file (file, Qnil);
-         unbind_to (count, Qnil);
+         dynwind_end ();
        }
       else
        report_file_errno ("Renaming", list2 (file, newname), rename_errno);
@@ -2371,23 +2411,21 @@ This is what happens in interactive use with M-x.  */)
      call the corresponding file handler.  */
   handler = Ffind_file_name_handler (file, Qadd_name_to_file);
   if (!NILP (handler))
-    RETURN_UNGCPRO (call4 (handler, Qadd_name_to_file, file,
-                          newname, ok_if_already_exists));
+    return call4 (handler, Qadd_name_to_file, file, newname, ok_if_already_exists);
 
   /* If the new name has special constructs in it,
      call the corresponding file handler.  */
   handler = Ffind_file_name_handler (newname, Qadd_name_to_file);
   if (!NILP (handler))
-    RETURN_UNGCPRO (call4 (handler, Qadd_name_to_file, file,
-                          newname, ok_if_already_exists));
+    return call4 (handler, Qadd_name_to_file, file, newname, ok_if_already_exists);
 
   encoded_file = ENCODE_FILE (file);
   encoded_newname = ENCODE_FILE (newname);
 
   if (NILP (ok_if_already_exists)
       || INTEGERP (ok_if_already_exists))
-    barf_or_query_if_file_exists (newname, "make it a new name",
-                                 INTEGERP (ok_if_already_exists), 0, 0);
+    barf_or_query_if_file_exists (newname, false, "make it a new name",
+                                 INTEGERP (ok_if_already_exists), false);
 
   unlink (SSDATA (newname));
   if (link (SSDATA (encoded_file), SSDATA (encoded_newname)) < 0)
@@ -2433,23 +2471,21 @@ This happens for interactive use with M-x.  */)
      call the corresponding file handler.  */
   handler = Ffind_file_name_handler (filename, Qmake_symbolic_link);
   if (!NILP (handler))
-    RETURN_UNGCPRO (call4 (handler, Qmake_symbolic_link, filename,
-                          linkname, ok_if_already_exists));
+    return call4 (handler, Qmake_symbolic_link, filename, linkname, ok_if_already_exists);
 
   /* If the new link name has special constructs in it,
      call the corresponding file handler.  */
   handler = Ffind_file_name_handler (linkname, Qmake_symbolic_link);
   if (!NILP (handler))
-    RETURN_UNGCPRO (call4 (handler, Qmake_symbolic_link, filename,
-                          linkname, ok_if_already_exists));
+    return call4 (handler, Qmake_symbolic_link, filename, linkname, ok_if_already_exists);
 
   encoded_filename = ENCODE_FILE (filename);
   encoded_linkname = ENCODE_FILE (linkname);
 
   if (NILP (ok_if_already_exists)
       || INTEGERP (ok_if_already_exists))
-    barf_or_query_if_file_exists (linkname, "make it a link",
-                                 INTEGERP (ok_if_already_exists), 0, 0);
+    barf_or_query_if_file_exists (linkname, false, "make it a link",
+                                 INTEGERP (ok_if_already_exists), false);
   if (symlink (SSDATA (encoded_filename), SSDATA (encoded_linkname)) < 0)
     {
       /* If we didn't complain already, silently delete existing file.  */
@@ -2520,7 +2556,9 @@ Use `file-symlink-p' to test for such links.  */)
 
 DEFUN ("file-executable-p", Ffile_executable_p, Sfile_executable_p, 1, 1, 0,
        doc: /* Return t if FILENAME can be executed by you.
-For a directory, this means you can access files in that directory.  */)
+For a directory, this means you can access files in that directory.
+\(It is generally better to use `file-accessible-directory-p' for that
+purpose, though.)  */)
   (Lisp_Object filename)
 {
   Lisp_Object absname;
@@ -2656,8 +2694,7 @@ DEFUN ("file-symlink-p", Ffile_symlink_p, Sfile_symlink_p, 1, 1, 0,
 The value is the link target, as a string.
 Otherwise it returns nil.
 
-This function returns t when given the name of a symlink that
-points to a nonexistent file.  */)
+This function does not check whether the link target exists.  */)
   (Lisp_Object filename)
 {
   Lisp_Object handler;
@@ -2884,7 +2921,7 @@ or if SELinux is disabled, or if Emacs lacks SELinux support.  */)
     }
 #endif
 
-  return Flist (sizeof (values) / sizeof (values[0]), values);
+  return Flist (ARRAYELTS (values), values);
 }
 \f
 DEFUN ("set-file-selinux-context", Fset_file_selinux_context,
@@ -2986,6 +3023,9 @@ was unable to determine the ACL entries.  */)
   acl_t acl;
   Lisp_Object acl_string;
   char *str;
+# ifndef HAVE_ACL_TYPE_EXTENDED
+  acl_type_t ACL_TYPE_EXTENDED = ACL_TYPE_ACCESS;
+# endif
 #endif
 
   absname = expand_and_dir_to_file (filename,
@@ -3000,7 +3040,7 @@ was unable to determine the ACL entries.  */)
 #ifdef HAVE_ACL_SET_FILE
   absname = ENCODE_FILE (absname);
 
-  acl = acl_get_file (SSDATA (absname), ACL_TYPE_ACCESS);
+  acl = acl_get_file (SSDATA (absname), ACL_TYPE_EXTENDED);
   if (acl == NULL)
     return Qnil;
 
@@ -3136,10 +3176,17 @@ The argument MODE should be an integer; only the low 9 bits are used.
 This setting is inherited by subprocesses.  */)
   (Lisp_Object mode)
 {
+  mode_t oldrealmask, oldumask, newumask;
   CHECK_NUMBER (mode);
+  oldrealmask = realmask;
+  newumask = ~ XINT (mode) & 0777;
 
-  umask ((~ XINT (mode)) & 0777);
+  block_input ();
+  realmask = newumask;
+  oldumask = umask (newumask);
+  unblock_input ();
 
+  eassert (oldumask == oldrealmask);
   return Qnil;
 }
 
@@ -3148,14 +3195,7 @@ DEFUN ("default-file-modes", Fdefault_file_modes, Sdefault_file_modes, 0, 0, 0,
 The value is an integer.  */)
   (void)
 {
-  mode_t realmask;
   Lisp_Object value;
-
-  block_input ();
-  realmask = umask (0);
-  umask (realmask);
-  unblock_input ();
-
   XSETINT (value, (~ realmask) & 0777);
   return value;
 }
@@ -3392,7 +3432,6 @@ by calling `format-decode', which see.  */)
   ptrdiff_t how_much;
   off_t beg_offset, end_offset;
   int unprocessed;
-  ptrdiff_t count = SPECPDL_INDEX ();
   struct gcpro gcpro1, gcpro2, gcpro3, gcpro4, gcpro5;
   Lisp_Object handler, val, insval, orig_filename, old_undo;
   Lisp_Object p;
@@ -3412,7 +3451,8 @@ by calling `format-decode', which see.  */)
        && BEG == Z);
   Lisp_Object old_Vdeactivate_mark = Vdeactivate_mark;
   bool we_locked_file = 0;
-  ptrdiff_t fd_index;
+
+  dynwind_begin ();
 
   if (current_buffer->base_buffer && ! NILP (visit))
     error ("Cannot do file visiting in an indirect buffer");
@@ -3463,8 +3503,7 @@ by calling `format-decode', which see.  */)
       goto notfound;
     }
 
-  fd_index = SPECPDL_INDEX ();
-  record_unwind_protect_int (close_file_unwind, fd);
+  record_unwind_protect_ptr (close_file_ptr_unwind, &fd);
 
   /* Replacement should preserve point as it preserves markers.  */
   if (!NILP (replace))
@@ -3595,10 +3634,10 @@ by calling `format-decode', which see.  */)
                report_file_error ("Read error", orig_filename);
              else if (nread > 0)
                {
-                 struct buffer *prev = current_buffer;
                  Lisp_Object workbuf;
                  struct buffer *buf;
 
+                  dynwind_begin ();
                  record_unwind_current_buffer ();
 
                  workbuf = Fget_buffer_create (build_string (" *code-converting-work*"));
@@ -3620,11 +3659,8 @@ by calling `format-decode', which see.  */)
                  TEMP_SET_PT_BOTH (BEG, BEG_BYTE);
                  coding_system = call2 (Vset_auto_coding_function,
                                         filename, make_number (nread));
-                 set_buffer_internal (prev);
 
-                 /* Discard the unwind protect for recovering the
-                     current buffer.  */
-                 specpdl_ptr--;
+                  dynwind_end ();
 
                  /* Rewind the file for the actual read done later.  */
                  if (lseek (fd, 0, SEEK_SET) < 0)
@@ -3742,7 +3778,7 @@ by calling `format-decode', which see.  */)
       if (same_at_start - BEGV_BYTE == end_offset - beg_offset)
        {
          emacs_close (fd);
-         clear_unwind_protect (fd_index);
+          fd = -1;
 
          /* Truncate the buffer to the size of the file.  */
          del_range_1 (same_at_start, same_at_end, 0, 0);
@@ -3876,12 +3912,12 @@ by calling `format-decode', which see.  */)
       unsigned char *decoded;
       ptrdiff_t temp;
       ptrdiff_t this = 0;
-      ptrdiff_t this_count = SPECPDL_INDEX ();
       bool multibyte
        = ! NILP (BVAR (current_buffer, enable_multibyte_characters));
       Lisp_Object conversion_buffer;
       struct gcpro gcpro1;
 
+      dynwind_begin ();
       conversion_buffer = code_conversion_save (1, multibyte);
 
       /* First read the whole file, performing code conversion into
@@ -3921,7 +3957,7 @@ by calling `format-decode', which see.  */)
       if (this < 0)
        report_file_error ("Read error", orig_filename);
       emacs_close (fd);
-      clear_unwind_protect (fd_index);
+      fd = -1;
 
       if (unprocessed > 0)
        {
@@ -3960,7 +3996,7 @@ by calling `format-decode', which see.  */)
            }
          inserted = 0;
 
-         unbind_to (this_count, Qnil);
+          dynwind_end ();
          goto handled;
        }
 
@@ -4040,7 +4076,7 @@ by calling `format-decode', which see.  */)
       /* Set point before the inserted characters.  */
       SET_PT_BOTH (temp, same_at_start);
 
-      unbind_to (this_count, Qnil);
+      dynwind_end ();
 
       goto handled;
     }
@@ -4053,14 +4089,12 @@ by calling `format-decode', which see.  */)
 
   if (NILP (visit) && total > 0)
     {
-#ifdef CLASH_DETECTION
       if (!NILP (BVAR (current_buffer, file_truename))
          /* Make binding buffer-file-name to nil effective.  */
          && !NILP (BVAR (current_buffer, filename))
          && SAVE_MODIFF >= MODIFF)
        we_locked_file = 1;
-#endif /* CLASH_DETECTION */
-      prepare_to_modify_buffer (GPT, GPT, NULL);
+      prepare_to_modify_buffer (PT, PT, NULL);
     }
 
   move_gap_both (PT, PT_BYTE);
@@ -4159,17 +4193,15 @@ by calling `format-decode', which see.  */)
 
   if (inserted == 0)
     {
-#ifdef CLASH_DETECTION
       if (we_locked_file)
        unlock_file (BVAR (current_buffer, file_truename));
-#endif
       Vdeactivate_mark = old_Vdeactivate_mark;
     }
   else
     Vdeactivate_mark = Qt;
 
   emacs_close (fd);
-  clear_unwind_protect (fd_index);
+  fd = -1;
 
   if (how_much < 0)
     report_file_error ("Read error", orig_filename);
@@ -4207,7 +4239,7 @@ by calling `format-decode', which see.  */)
             care of marker adjustment.  By this way, we can run Lisp
             program safely before decoding the inserted text.  */
          Lisp_Object unwind_data;
-         ptrdiff_t count1 = SPECPDL_INDEX ();
+          dynwind_begin ();
 
          unwind_data = Fcons (BVAR (current_buffer, enable_multibyte_characters),
                               Fcons (BVAR (current_buffer, undo_list),
@@ -4234,7 +4266,7 @@ by calling `format-decode', which see.  */)
              if (CONSP (coding_system))
                coding_system = XCAR (coding_system);
            }
-         unbind_to (count1, Qnil);
+          dynwind_end ();
          inserted = Z_BYTE - BEG_BYTE;
        }
 
@@ -4311,14 +4343,12 @@ by calling `format-decode', which see.  */)
       SAVE_MODIFF = MODIFF;
       BUF_AUTOSAVE_MODIFF (current_buffer) = MODIFF;
       XSETFASTINT (BVAR (current_buffer, save_length), Z - BEG);
-#ifdef CLASH_DETECTION
       if (NILP (handler))
        {
          if (!NILP (BVAR (current_buffer, file_truename)))
            unlock_file (BVAR (current_buffer, file_truename));
          unlock_file (filename);
        }
-#endif /* CLASH_DETECTION */
       if (not_regular)
        xsignal2 (Qfile_error,
                  build_string ("not a regular file"), orig_filename);
@@ -4343,8 +4373,8 @@ by calling `format-decode', which see.  */)
   if (inserted > 0)
     {
       /* Don't run point motion or modification hooks when decoding.  */
-      ptrdiff_t count1 = SPECPDL_INDEX ();
       ptrdiff_t old_inserted = inserted;
+      dynwind_begin ();
       specbind (Qinhibit_point_motion_hooks, Qt);
       specbind (Qinhibit_modification_hooks, Qt);
 
@@ -4459,7 +4489,7 @@ by calling `format-decode', which see.  */)
           Otherwise start with an empty undo_list.  */
        bset_undo_list (current_buffer, EQ (old_undo, Qt) ? Qt : Qnil);
 
-      unbind_to (count1, Qnil);
+      dynwind_end ();
     }
 
   if (!NILP (visit)
@@ -4472,7 +4502,11 @@ by calling `format-decode', which see.  */)
   /* We made a lot of deletions and insertions above, so invalidate
      the newline cache for the entire region of the inserted
      characters.  */
-  if (current_buffer->newline_cache)
+  if (current_buffer->base_buffer && current_buffer->base_buffer->newline_cache)
+    invalidate_region_cache (current_buffer->base_buffer,
+                             current_buffer->base_buffer->newline_cache,
+                             PT - BEG, Z - PT - inserted);
+  else if (current_buffer->newline_cache)
     invalidate_region_cache (current_buffer,
                              current_buffer->newline_cache,
                              PT - BEG, Z - PT - inserted);
@@ -4484,7 +4518,8 @@ by calling `format-decode', which see.  */)
   if (NILP (val))
     val = list2 (orig_filename, make_number (inserted));
 
-  RETURN_UNGCPRO (unbind_to (count, val));
+  dynwind_end ();
+  return val;
 }
 \f
 static Lisp_Object build_annotations (Lisp_Object, Lisp_Object);
@@ -4671,8 +4706,6 @@ write_region (Lisp_Object start, Lisp_Object end, Lisp_Object filename,
   const char *fn;
   struct stat st;
   struct timespec modtime;
-  ptrdiff_t count = SPECPDL_INDEX ();
-  ptrdiff_t count1 IF_LINT (= 0);
   Lisp_Object handler;
   Lisp_Object visit_file;
   Lisp_Object annotations;
@@ -4696,7 +4729,7 @@ write_region (Lisp_Object start, Lisp_Object end, Lisp_Object filename,
   filename = Fexpand_file_name (filename, Qnil);
 
   if (!NILP (mustbenew) && !EQ (mustbenew, Qexcl))
-    barf_or_query_if_file_exists (filename, "overwrite", 1, 0, 1);
+    barf_or_query_if_file_exists (filename, false, "overwrite", true, true);
 
   if (STRINGP (visit))
     visit_file = Fexpand_file_name (visit, Qnil);
@@ -4731,6 +4764,7 @@ write_region (Lisp_Object start, Lisp_Object end, Lisp_Object filename,
       return val;
     }
 
+  dynwind_begin ();
   record_unwind_protect (save_restriction_restore, save_restriction_save ());
 
   /* Special kludge to simplify auto-saving.  */
@@ -4784,13 +4818,11 @@ write_region (Lisp_Object start, Lisp_Object end, Lisp_Object filename,
   if (!STRINGP (start) && !NILP (BVAR (current_buffer, selective_display)))
     coding.mode |= CODING_MODE_SELECTIVE_DISPLAY;
 
-#ifdef CLASH_DETECTION
   if (open_and_close_file && !auto_saving)
     {
       lock_file (lockname);
       file_locked = 1;
     }
-#endif /* CLASH_DETECTION */
 
   encoded_filename = ENCODE_FILE (filename);
   fn = SSDATA (encoded_filename);
@@ -4812,16 +4844,14 @@ write_region (Lisp_Object start, Lisp_Object end, Lisp_Object filename,
       if (desc < 0)
        {
          int open_errno = errno;
-#ifdef CLASH_DETECTION
          if (file_locked)
            unlock_file (lockname);
-#endif /* CLASH_DETECTION */
          UNGCPRO;
          report_file_errno ("Opening output file", filename, open_errno);
        }
 
-      count1 = SPECPDL_INDEX ();
-      record_unwind_protect_int (close_file_unwind, desc);
+      dynwind_begin ();
+      record_unwind_protect_int_1 (close_file_unwind, desc, false);
     }
 
   if (NUMBERP (append))
@@ -4830,10 +4860,8 @@ write_region (Lisp_Object start, Lisp_Object end, Lisp_Object filename,
       if (ret < 0)
        {
          int lseek_errno = errno;
-#ifdef CLASH_DETECTION
          if (file_locked)
            unlock_file (lockname);
-#endif /* CLASH_DETECTION */
          UNGCPRO;
          report_file_errno ("Lseek error", filename, lseek_errno);
        }
@@ -4899,8 +4927,7 @@ write_region (Lisp_Object start, Lisp_Object end, Lisp_Object filename,
       if (emacs_close (desc) < 0)
        ok = 0, save_errno = errno;
 
-      /* Discard the unwind protect for close_file_unwind.  */
-      specpdl_ptr = specpdl + count1;
+      dynwind_end ();
     }
 
   /* Some file systems have a bug where st_mtime is not updated
@@ -4974,12 +5001,10 @@ write_region (Lisp_Object start, Lisp_Object end, Lisp_Object filename,
        = XCDR (Vwrite_region_annotation_buffers);
     }
 
-  unbind_to (count, Qnil);
+  dynwind_end ();
 
-#ifdef CLASH_DETECTION
   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
@@ -5025,7 +5050,9 @@ 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)
 {
-  Lisp_Object args[2] = { Fcar (a), Fcar (b), };
+  Lisp_Object args[2];
+  args[0] = Fcar (a);
+  args[1] = Fcar (b);
   return Flss (2, args);
 }
 
@@ -5500,7 +5527,7 @@ A non-nil CURRENT-ONLY argument means save only current buffer.  */)
   int do_handled_files;
   Lisp_Object oquit;
   FILE *stream = NULL;
-  ptrdiff_t count = SPECPDL_INDEX ();
+  dynwind_begin ();
   bool orig_minibuffer_auto_raise = minibuffer_auto_raise;
   bool old_message_p = 0;
   struct auto_save_unwind auto_save_unwind;
@@ -5683,7 +5710,7 @@ A non-nil CURRENT-ONLY argument means save only current buffer.  */)
   Vquit_flag = oquit;
 
   /* This restores the message-stack status.  */
-  unbind_to (count, Qnil);
+  dynwind_end ();
   return Qnil;
 }
 
@@ -5743,27 +5770,12 @@ before any other event (mouse or keypress) is handled.  */)
   return Qnil;
 }
 
-Lisp_Object
-Fread_file_name (Lisp_Object prompt, Lisp_Object dir, Lisp_Object default_filename, Lisp_Object mustmatch, Lisp_Object initial, Lisp_Object predicate)
-{
-  struct gcpro gcpro1;
-  Lisp_Object args[7];
-
-  GCPRO1 (default_filename);
-  args[0] = intern ("read-file-name");
-  args[1] = prompt;
-  args[2] = dir;
-  args[3] = default_filename;
-  args[4] = mustmatch;
-  args[5] = initial;
-  args[6] = predicate;
-  RETURN_UNGCPRO (Ffuncall (7, args));
-}
-
-\f
 void
 init_fileio (void)
 {
+  realmask = umask (0);
+  umask (realmask);
+
   valid_timestamp_file_system = 0;
 
   /* fsync can be a significant performance hit.  Often it doesn't
@@ -5788,6 +5800,8 @@ init_fileio (void)
 void
 syms_of_fileio (void)
 {
+#include "fileio.x"
+
   DEFSYM (Qoperations, "operations");
   DEFSYM (Qexpand_file_name, "expand-file-name");
   DEFSYM (Qsubstitute_in_file_name, "substitute-in-file-name");
@@ -6007,7 +6021,7 @@ file is usually more useful if it contains the deleted text.  */);
               doc: /* Non-nil means don't call fsync in `write-region'.
 This variable affects calls to `write-region' as well as save commands.
 Setting this to nil may avoid data loss if the system loses power or
-the operating system crashes.  */);
+the operating system crashes.  By default, it is non-nil in batch mode.  */);
   write_region_inhibit_fsync = 0; /* See also `init_fileio' above.  */
 
   DEFVAR_BOOL ("delete-by-moving-to-trash", delete_by_moving_to_trash,
@@ -6023,58 +6037,4 @@ This includes interactive calls to `delete-file' and
   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);
-  defsubr (&Sfile_name_nondirectory);
-  defsubr (&Sunhandled_file_name_directory);
-  defsubr (&Sfile_name_as_directory);
-  defsubr (&Sdirectory_file_name);
-  defsubr (&Smake_temp_name);
-  defsubr (&Sexpand_file_name);
-  defsubr (&Ssubstitute_in_file_name);
-  defsubr (&Scopy_file);
-  defsubr (&Smake_directory_internal);
-  defsubr (&Sdelete_directory_internal);
-  defsubr (&Sdelete_file);
-  defsubr (&Srename_file);
-  defsubr (&Sadd_name_to_file);
-  defsubr (&Smake_symbolic_link);
-  defsubr (&Sfile_name_absolute_p);
-  defsubr (&Sfile_exists_p);
-  defsubr (&Sfile_executable_p);
-  defsubr (&Sfile_readable_p);
-  defsubr (&Sfile_writable_p);
-  defsubr (&Saccess_file);
-  defsubr (&Sfile_symlink_p);
-  defsubr (&Sfile_directory_p);
-  defsubr (&Sfile_accessible_directory_p);
-  defsubr (&Sfile_regular_p);
-  defsubr (&Sfile_modes);
-  defsubr (&Sset_file_modes);
-  defsubr (&Sset_file_times);
-  defsubr (&Sfile_selinux_context);
-  defsubr (&Sfile_acl);
-  defsubr (&Sset_file_acl);
-  defsubr (&Sset_file_selinux_context);
-  defsubr (&Sset_default_file_modes);
-  defsubr (&Sdefault_file_modes);
-  defsubr (&Sfile_newer_than_file_p);
-  defsubr (&Sinsert_file_contents);
-  defsubr (&Schoose_write_coding_system);
-  defsubr (&Swrite_region);
-  defsubr (&Scar_less_than_car);
-  defsubr (&Sverify_visited_file_modtime);
-  defsubr (&Svisited_file_modtime);
-  defsubr (&Sset_visited_file_modtime);
-  defsubr (&Sdo_auto_save);
-  defsubr (&Sset_buffer_auto_saved);
-  defsubr (&Sclear_buffer_auto_save_failure);
-  defsubr (&Srecent_auto_save_p);
-
-  defsubr (&Snext_read_file_uses_dialog_p);
-
-#ifdef HAVE_SYNC
-  defsubr (&Sunix_sync);
-#endif
 }