]> git.eshelyaron.com Git - emacs.git/commitdiff
When renaming a file, ask only if EEXIST or ENOSYS
authorPaul Eggert <eggert@cs.ucla.edu>
Wed, 2 Aug 2017 08:53:46 +0000 (01:53 -0700)
committerPaul Eggert <eggert@cs.ucla.edu>
Wed, 2 Aug 2017 08:59:32 +0000 (01:59 -0700)
* src/fileio.c (Frename_file): Avoid calling Ffile_directory_p
more than once on FILE.  Use renameat_noreplace, so that we can
ask the user (and unlink and retry) only if this fails with errno
== EEXIST or ENOSYS.  This avoids the need to ask the user for
permission to do an operation that will fail anyway.  Simplify
computation of ok_if_already_exists for subsidiary functions.
* src/filelock.c (rename_lock_file): Prefer renameat_noreplace
if it works, as this avoids the need to link and unlink.
* src/lisp.h (renameat_noreplace): New decl.
* src/sysdep.c [HAVE_LINUX_FS_H]: Include linux/fs.h and sys/syscall.h.
(renameat_noreplace): New function.

src/fileio.c
src/filelock.c
src/lisp.h
src/sysdep.c

index 96c5639a09607fae8e6675b6ddb8c1d25f216b20..0264c9fa1d810e538cfbcb189a49c6242781d339 100644 (file)
@@ -2311,6 +2311,7 @@ This is what happens in interactive use with M-x.  */)
 {
   Lisp_Object handler;
   Lisp_Object encoded_file, encoded_newname, symlink_target;
+  int dirp = -1;
 
   symlink_target = encoded_file = encoded_newname = Qnil;
   CHECK_STRING (file);
@@ -2324,8 +2325,8 @@ This is what happens in interactive use with M-x.  */)
       && (NILP (Ffile_name_case_insensitive_p (file))
          || NILP (Fstring_equal (Fdowncase (file), Fdowncase (newname)))))
     {
-      Lisp_Object fname = (NILP (Ffile_directory_p (file))
-                          ? file : Fdirectory_file_name (file));
+      dirp = !NILP (Ffile_directory_p (file));
+      Lisp_Object fname = dirp ? Fdirectory_file_name (file) : file;
       newname = Fexpand_file_name (Ffile_name_nondirectory (fname), newname);
     }
   else
@@ -2343,47 +2344,55 @@ This is what happens in interactive use with M-x.  */)
   encoded_file = ENCODE_FILE (file);
   encoded_newname = ENCODE_FILE (newname);
 
-  /* If the filesystem is case-insensitive and the file names are
-     identical but for the case, don't ask for confirmation: they
-     simply want to change the letter-case of the file name.  */
-  if ((!(file_name_case_insensitive_p (SSDATA (encoded_file)))
-       || NILP (Fstring_equal (Fdowncase (file), Fdowncase (newname))))
-      && ((NILP (ok_if_already_exists) || INTEGERP (ok_if_already_exists))))
-    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)
+  if (renameat_noreplace (AT_FDCWD, SSDATA (encoded_file),
+                         AT_FDCWD, SSDATA (encoded_newname))
+      == 0)
+    return Qnil;
+  int rename_errno = errno;
+
+  if (rename_errno == EEXIST || rename_errno == ENOSYS)
     {
-      int rename_errno = errno;
-      if (rename_errno == EXDEV)
-       {
-          ptrdiff_t count;
-          symlink_target = Ffile_symlink_p (file);
-          if (! NILP (symlink_target))
-            Fmake_symbolic_link (symlink_target, newname,
-                                 NILP (ok_if_already_exists) ? Qnil : Qt);
-         else if (!NILP (Ffile_directory_p (file)))
-           call4 (Qcopy_directory, file, newname, Qt, Qnil);
-         else
-           /* We have already prompted if it was an integer, so don't
-              have copy-file prompt again.  */
-           Fcopy_file (file, newname,
-                       NILP (ok_if_already_exists) ? Qnil : Qt,
-                       Qt, Qt, Qt);
+      /* If the filesystem is case-insensitive and the file names are
+        identical but for the case, don't ask for confirmation: they
+        simply want to change the letter-case of the file name.  */
+      if ((NILP (ok_if_already_exists) || INTEGERP (ok_if_already_exists))
+         && (! file_name_case_insensitive_p (SSDATA (encoded_file))
+             || NILP (Fstring_equal (Fdowncase (file), Fdowncase (newname)))))
+       barf_or_query_if_file_exists (newname, rename_errno == EEXIST,
+                                     "rename to it",
+                                     INTEGERP (ok_if_already_exists), false);
+      if (rename (SSDATA (encoded_file), SSDATA (encoded_newname)) == 0)
+       return Qnil;
+      rename_errno = errno;
+      /* Don't prompt again.  */
+      ok_if_already_exists = Qt;
+    }
+  else if (!NILP (ok_if_already_exists))
+    ok_if_already_exists = Qt;
 
-         count = SPECPDL_INDEX ();
-         specbind (Qdelete_by_moving_to_trash, Qnil);
+  if (rename_errno != EXDEV)
+    report_file_errno ("Renaming", list2 (file, newname), rename_errno);
 
-         if (!NILP (Ffile_directory_p (file)) && NILP (symlink_target))
-           call2 (Qdelete_directory, file, Qt);
-         else
-           Fdelete_file (file, Qnil);
-         unbind_to (count, Qnil);
-       }
+  symlink_target = Ffile_symlink_p (file);
+  if (!NILP (symlink_target))
+    Fmake_symbolic_link (symlink_target, newname, ok_if_already_exists);
+  else
+    {
+      if (dirp < 0)
+       dirp = !NILP (Ffile_directory_p (file));
+      if (dirp)
+       call4 (Qcopy_directory, file, newname, Qt, Qnil);
       else
-       report_file_errno ("Renaming", list2 (file, newname), rename_errno);
+       Fcopy_file (file, newname, ok_if_already_exists, Qt, Qt, Qt);
     }
 
-  return Qnil;
+  ptrdiff_t count = SPECPDL_INDEX ();
+  specbind (Qdelete_by_moving_to_trash, Qnil);
+  if (dirp && NILP (symlink_target))
+    call2 (Qdelete_directory, file, Qt);
+  else
+    Fdelete_file (file, Qnil);
+  return unbind_to (count, Qnil);
 }
 
 DEFUN ("add-name-to-file", Fadd_name_to_file, Sadd_name_to_file, 2, 3,
index bfa1d63d8339a6b67426bf18d0bbfc2af35e977a..dd8cb28c42550ecd530a0e3837969420bdeaeb0b 100644 (file)
@@ -339,6 +339,9 @@ rename_lock_file (char const *old, char const *new, bool force)
     {
       struct stat st;
 
+      int r = renameat_noreplace (AT_FDCWD, old, AT_FDCWD, new);
+      if (! (r < 0 && errno == ENOSYS))
+       return r;
       if (link (old, new) == 0)
        return unlink (old) == 0 || errno == ENOENT ? 0 : -1;
       if (errno != ENOSYS && errno != LINKS_MIGHT_NOT_WORK)
index cffaf954b3baa72efd0e937bf9de8aa2a806958c..4de6fc85ec1cc9d122529f275af1b4f44563e3f0 100644 (file)
@@ -4298,13 +4298,15 @@ extern ptrdiff_t emacs_write (int, void const *, ptrdiff_t);
 extern ptrdiff_t emacs_write_sig (int, void const *, ptrdiff_t);
 extern ptrdiff_t emacs_write_quit (int, void const *, ptrdiff_t);
 extern void emacs_perror (char const *);
+extern int renameat_noreplace (int, char const *, int, char const *);
+extern int str_collate (Lisp_Object, Lisp_Object, Lisp_Object, Lisp_Object);
 
-extern void unlock_all_files (void);
+/* Defined in filelock.c.  */
 extern void lock_file (Lisp_Object);
 extern void unlock_file (Lisp_Object);
+extern void unlock_all_files (void);
 extern void unlock_buffer (struct buffer *);
 extern void syms_of_filelock (void);
-extern int str_collate (Lisp_Object, Lisp_Object, Lisp_Object, Lisp_Object);
 
 /* Defined in sound.c.  */
 extern void syms_of_sound (void);
index db99f53299c0f2a614f2b20f7e44a03842edf03d..22446b25d16d55fd282ba3b371b043b32d072703 100644 (file)
@@ -37,6 +37,11 @@ along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
 #include "sysselect.h"
 #include "blockinput.h"
 
+#ifdef HAVE_LINUX_FS_H
+# include <linux/fs.h>
+# include <sys/syscall.h>
+#endif
+
 #if defined DARWIN_OS || defined __FreeBSD__
 # include <sys/sysctl.h>
 #endif
@@ -2678,6 +2683,21 @@ set_file_times (int fd, const char *filename,
   timespec[1] = mtime;
   return fdutimens (fd, filename, timespec);
 }
+
+/* Rename directory SRCFD's entry SRC to directory DSTFD's entry DST.
+   This is like renameat except that it fails if DST already exists,
+   or if this operation is not supported atomically.  Return 0 if
+   successful, -1 (setting errno) otherwise.  */
+int
+renameat_noreplace (int srcfd, char const *src, int dstfd, char const *dst)
+{
+#ifdef SYS_renameat2
+  return syscall (SYS_renameat2, srcfd, src, dstfd, dst, RENAME_NOREPLACE);
+#else
+  errno = ENOSYS;
+  return -1;
+#endif
+}
 \f
 /* Like strsignal, except async-signal-safe, and this function typically
    returns a string in the C locale rather than the current locale.  */