]> code.delx.au - gnu-emacs/commitdiff
Plain copy-file no longer chmods an existing destination.
authorPaul Eggert <eggert@cs.ucla.edu>
Sun, 29 Dec 2013 18:18:45 +0000 (10:18 -0800)
committerPaul Eggert <eggert@cs.ucla.edu>
Sun, 29 Dec 2013 18:18:45 +0000 (10:18 -0800)
* doc/lispref/files.texi (Changing Files): Document this.
* etc/NEWS: Document this.
* src/fileio.c (realmask): Now a static var, not a local.
(barf_or_query_if_file_exists): New arg KNOWN_TO_EXIST.
Remove arg STATPTR.  All uses changed.
(Fcopy_file): Do not alter permissions of existing destinations,
unless PRESERVE-PERMISSIONS (renamed from
PRESERVE-EXTENDED-ATTRIBUTES) is non-nil.
Avoid race when testing for existing destinations and for
when input and output files are the same.
If changing the group fails, adjust both default and
preserved permissions so that access is not granted to the
wrong group.
(Fset_default_file_modes, init_fileio): Update realmask.
(Fdefault_file_modes): Use realmask instead of calling umask.

Fixes: debbugs:16133
doc/lispref/ChangeLog
doc/lispref/files.texi
etc/ChangeLog
etc/NEWS
src/ChangeLog
src/fileio.c

index 2ebab6955140730f53dedccfd724d2ae7a675c26..3de4ae598fe0b2a781b3182da639e322fe4b5f8e 100644 (file)
@@ -1,3 +1,8 @@
+2013-12-29  Paul Eggert  <eggert@cs.ucla.edu>
+
+       Plain copy-file no longer chmods an existing destination (Bug#16133).
+       * files.texi (Changing Files): Document this.
+
 2013-12-28  Chong Yidong  <cyd@gnu.org>
 
        * modes.texi (Auto Major Mode): Document interpreter-mode-alist
index c1eae5eefa3e9779d6d83952884dbf2c367fa6ee..01eef350ddfbf0153ef615c83f85813ceb27a576 100644 (file)
@@ -1563,8 +1563,6 @@ some operating systems.)  If setting the time gets an error,
 interactive call, a prefix argument specifies a non-@code{nil} value
 for @var{time}.
 
-This function copies the file modes, too.
-
 If argument @var{preserve-uid-gid} is @code{nil}, we let the operating
 system decide the user and group ownership of the new file (this is
 usually set to the user running Emacs).  If @var{preserve-uid-gid} is
@@ -1572,10 +1570,11 @@ non-@code{nil}, we attempt to copy the user and group ownership of the
 file.  This works only on some operating systems, and only if you have
 the correct permissions to do so.
 
-If the optional argument @var{preserve-extended-attributes} is
-non-@code{nil}, and Emacs has been built with the appropriate support,
-this function attempts to copy the file's extended attributes, such as
-its SELinux context and ACL entries (@pxref{File Attributes}).
+If the optional argument @var{preserve-permissions} is non-@code{nil},
+this function copies the file's permissions, such as its file modes,
+its SELinux context, and ACL entries (@pxref{File Attributes}).
+Otherwise, if the destination is created its file permission bits are
+those of the source, masked by the default file permissions.
 @end deffn
 
 @deffn Command make-symbolic-link filename newname  &optional ok-if-exists
index 8a4b8fb774cd48a199c83ef4d7188118f817f4bc..bde366da14fcf73f73a76e05d26236a78af4d851 100644 (file)
@@ -1,3 +1,8 @@
+2013-12-29  Paul Eggert  <eggert@cs.ucla.edu>
+
+       Plain copy-file no longer chmods an existing destination (Bug#16133).
+       * NEWS: Document this.
+
 2013-12-26  João Távora  <joaotavora@gmail.com>
 
        * NEWS: Describe new features of Electric Pair mode.
index 49a2d8e6c9b086c210f422aaa71d7cebfb7580ba..c1e3784d9f6225cad94392acd3b89371f0313cd6 100644 (file)
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -952,6 +952,12 @@ alist of extended attributes as returned by the new function
 `file-extended-attributes'.  The attributes can be applied to another
 file using `set-file-extended-attributes'.
 
+** By default `copy-file' no longer copies file permission bits to an
+existing destination; and it sets the file permission bits of a newly
+created destination to those of the source, masked by the default file
+permissions.  To copy the file permission bits, pass t as the
+PRESERVE-PERMISSIONS argument of `copy-file'.
+
 +++
 ** `visited-file-modtime' now returns -1 for nonexistent files.
 Formerly it returned a list (-1 LOW USEC PSEC), but this was ambiguous
@@ -1100,8 +1106,8 @@ platforms, and nobody seems to have noticed or cared.
 
 +++
 *** The 6th argument to `copy-file' has been renamed to
-PRESERVE-EXTENDED-ATTRIBUTES as it now handles both SELinux context
-and ACL entries.
+PRESERVE-PERMISSIONS as it now handles ACL entries and the traditional
+Unix file permission bits as well as SELinux context.
 
 +++
 *** The function `file-ownership-preserved-p' now has an optional
index 1ec84a72d2d8d2c56a753be8ab27898c86fee760..e1e9777fa4f3d2e5a50a6ed7af3c4169af5daa22 100644 (file)
@@ -1,3 +1,20 @@
+2013-12-29  Paul Eggert  <eggert@cs.ucla.edu>
+
+       Plain copy-file no longer chmods an existing destination (Bug#16133).
+       * fileio.c (realmask): Now a static var, not a local.
+       (barf_or_query_if_file_exists): New arg KNOWN_TO_EXIST.
+       Remove arg STATPTR.  All uses changed.
+       (Fcopy_file): Do not alter permissions of existing destinations,
+       unless PRESERVE-PERMISSIONS (renamed from
+       PRESERVE-EXTENDED-ATTRIBUTES) is non-nil.
+       Avoid race when testing for existing destinations and for
+       when input and output files are the same.
+       If changing the group fails, adjust both default and
+       preserved permissions so that access is not granted to the
+       wrong group.
+       (Fset_default_file_modes, init_fileio): Update realmask.
+       (Fdefault_file_modes): Use realmask instead of calling umask.
+
 2013-12-28  Paul Eggert  <eggert@cs.ucla.edu>
 
        Fix pipe bug with OS X emacs --daemon (Bug#16262).
index 295d9d748ad53de4ac43c8dd58e8ca0a7d552ead..adf69c6f23446abc5032a2cbc4fc9247657fa328 100644 (file)
@@ -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;
 
@@ -1858,20 +1861,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;
@@ -1880,14 +1879,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);
@@ -1902,15 +1903,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,
@@ -1937,16 +1930,21 @@ 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)
 {
-  struct stat out_st;
   Lisp_Object handler;
   struct gcpro gcpro1, gcpro2, gcpro3, gcpro4;
   ptrdiff_t count = SPECPDL_INDEX ();
   Lisp_Object encoded_file, encoded_newname;
+  bool already_exists = false;
+  mode_t new_mask;
 #if HAVE_LIBSELINUX
   security_context_t con;
   int conlength = 0;
@@ -1981,22 +1979,20 @@ entries (depending on how Emacs was built).  */)
   if (!NILP (handler))
     RETURN_UNGCPRO (call7 (handler, Qcopy_file, file, newname,
                           ok_if_already_exists, keep_time, preserve_uid_gid,
-                          preserve_extended_attributes));
+                          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:
@@ -2022,7 +2018,7 @@ entries (depending on how Emacs was built).  */)
   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 ())
@@ -2034,32 +2030,44 @@ 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);
 
+  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;
   while ((n = emacs_read (ifd, buf, sizeof buf)) > 0)
@@ -2071,26 +2079,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);
@@ -2307,8 +2330,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;
@@ -2387,8 +2410,8 @@ This is what happens in interactive use with M-x.  */)
 
   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)
@@ -2449,8 +2472,8 @@ This happens for interactive use with M-x.  */)
 
   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.  */
@@ -3137,10 +3160,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;
 }
 
@@ -3149,14 +3179,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;
 }
@@ -4697,7 +4720,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);
@@ -5765,6 +5788,9 @@ Fread_file_name (Lisp_Object prompt, Lisp_Object dir, Lisp_Object default_filena
 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