]> git.eshelyaron.com Git - emacs.git/commitdiff
Do not munge contents of local symbolic links
authorPaul Eggert <eggert@cs.ucla.edu>
Sun, 27 Aug 2017 01:36:38 +0000 (18:36 -0700)
committerPaul Eggert <eggert@cs.ucla.edu>
Sun, 27 Aug 2017 01:36:38 +0000 (18:36 -0700)
This lets Emacs deal with arbitrary local symlinks without
mishandling their contents (Bug#28156).  For example,
(progn (shell-command "ln -fs '~' 'x'") (rename-file "x" "/tmp/x"))
now consistently creates a symbolic link from '/tmp/x' to '~'.
Formerly, it did that only if the working directory was on the
same filesystem as /tmp; otherwise, it expanded the '~' to
the user's home directory.
* lisp/dired.el (dired-get-filename): Use files--name-absolute-system-p
instead of rolling our own code.
* lisp/files.el (files--name-absolute-system-p): New function.
(file-truename, file-chase-links): Use it to avoid mishandling
symlink contents that begin with ~.
(copy-directory, move-file-to-trash):
Use concat rather than expand-file-name, to avoid mishandling
symlink contents that begin with ~.
* src/fileio.c (Fmake_symbolic_link): Do not expand leading "~" in the
target unless interactive.  Strip leading "/:" if interactive.
(emacs_readlinkat): Do not prepend "/:" to the link target if
it starts with "/" and contains ":" before NUL.
* test/src/fileio-tests.el (try-link): Rename from try-char,
and accept a string instead of a char.  All uses changed.
(fileio-tests--symlink-failure): Also test leading ~, and "/:",
to test the new behavior.

doc/emacs/files.texi
doc/lispref/files.texi
etc/NEWS
lisp/dired.el
lisp/files.el
src/fileio.c
test/src/fileio-tests.el

index 9195bc47efe1026b80d80a7cd2167e868efff850..fa1f9e53165c06915ba01d97d2d1ab7040ad3a8c 100644 (file)
@@ -1611,8 +1611,12 @@ attempts to open file @var{new} will refer to whatever file is named
 @var{target} at the time the opening is done, or will get an error if
 the name @var{target} is nonexistent at that time.  This command does
 not expand the argument @var{target}, so that it allows you to specify
-a relative name as the target of the link.  On MS-Windows, this
-command works only on MS Windows Vista and later.  On remote systems,
+a relative name as the target of the link.  However, this command
+does expand leading @samp{~} in @var{target} so that you can easily
+specify home directories, and strips leading @samp{/:} so that you can
+specify relative names beginning with literal @samp{~} or @samp{/:}.
+@xref{Quoted File Names}.  On MS-Windows, this command works only on
+MS Windows Vista and later.  When @var{new} is remote,
 it works depending on the system type.
 
 @node Misc File Ops
index f701d683703f1987cb25d669c59c9ad8d0779295..06466c9bba8bfc53d7ee99ab8d892a193891173b 100644 (file)
@@ -1726,14 +1726,17 @@ default file permissions (see @code{set-default-file-modes} below), if
 SELinux context are not copied over in either case.
 @end deffn
 
-@deffn Command make-symbolic-link filename newname &optional ok-if-already-exists
+@deffn Command make-symbolic-link target newname &optional ok-if-already-exists
 @pindex ln
 @kindex file-already-exists
-This command makes a symbolic link to @var{filename}, named
+This command makes a symbolic link to @var{target}, named
 @var{newname}.  This is like the shell command @samp{ln -s
-@var{filename} @var{newname}}.  The @var{filename} argument
+@var{target} @var{newname}}.  The @var{target} argument
 is treated only as a string; it need not name an existing file.
-If @var{filename} is a relative file name, the resulting symbolic link
+If @var{ok-if-already-exists} is an integer, indicating interactive
+use, then leading @samp{~} is expanded and leading @samp{/:} is
+stripped in the @var{target} string.
+If @var{target} is a relative file name, the resulting symbolic link
 is interpreted relative to the directory containing the symbolic link.
 @xref{Relative File Names}.
 
index 02de66b355f5f3f29a6476b93fa2a390d61af4f5..d53e0d25f7834ca704a1f493bac260b5ef6f832e 100644 (file)
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -1227,6 +1227,30 @@ that does not process CRLF.  For example, it defaults to utf-8-unix
 instead of to utf-8.  Before this change, Emacs would sometimes
 mishandle file names containing these control characters.
 
++++
+** 'file-attributes', 'file-symlink-p' and 'make-symbolic-link' no
+longer quietly mutate the target of a local symbolic link, so that
+Emacs can access and copy them reliably regardless of their contents.
+The following changes are involved.
+
+*** 'file-attributes' and 'file-symlink-p' no longer prepend "/:" to
+symbolic links whose targets begin with "/" and contain ":".  For
+example, if a symbolic link "x" has a target "/y:z", (file-symlink-p
+"x") now returns "/y:z" rather than "/:/y:z".
+
+*** 'make-symbolic-link' no longer looks for file name handlers when
+creating a local symbolic link.  For example, (make-symbolic-link
+"/y:z" "x") now creates a symlink to "/y:z" instead of failing.
+
+*** 'make-symbolic-link' now expands a link target with leading "~"
+only when the optional third arg is an integer, as when invoked
+interactively.  For example, (make-symbolic-link "~y" "x") now creates
+a link with target the literal string "~y"; to get the old behavior,
+use (make-symbolic-link (expand-file-name "~y") "x").  To avoid this
+expansion in interactive use, you can now prefix the link target with
+"/:".  For example, (make-symbolic-link "/:~y" "x" 1) now creates a
+link to literal "~y".
+
 +++
 ** Module functions are now implemented slightly differently; in
 particular, the function 'internal--module-call' has been removed.
index 0455f3d137817fd564df777ff5ff258631f7183e..ff62183f091fbd34b1b8fb2d05b1b97e5174ad6e 100644 (file)
@@ -2332,10 +2332,7 @@ Otherwise, an error occurs in these cases."
          (if (and enable-multibyte-characters
                   (not (multibyte-string-p file)))
              (setq file (string-to-multibyte file)))))
-    (and file (file-name-absolute-p file)
-        ;; A relative file name can start with ~.
-        ;; Don't treat it as absolute in this context.
-        (not (eq (aref file 0) ?~))
+    (and file (files--name-absolute-system-p file)
         (setq already-absolute t))
     (cond
      ((null file)
index ca3b055d7a6235aa91d88ca21f953bb0888df366..872fc46e87ad2a322ab0fd05d3e6e2f1a2051362 100644 (file)
@@ -1146,6 +1146,13 @@ accessible."
        (funcall handler 'file-local-copy file)
       nil)))
 
+(defun files--name-absolute-system-p (file)
+  "Return non-nil if FILE is an absolute name to the operating system.
+This is like `file-name-absolute-p', except that it returns nil for
+names beginning with `~'."
+  (and (file-name-absolute-p file)
+       (not (eq (aref file 0) ?~))))
+
 (defun file-truename (filename &optional counter prev-dirs)
   "Return the truename of FILENAME.
 If FILENAME is not absolute, first expands it against `default-directory'.
@@ -1247,9 +1254,9 @@ containing it, until no links are left at any level.
                    ;; since target might look like foo/../bar where foo
                    ;; is itself a link.  Instead, we handle . and .. above.
                    (setq filename
-                         (if (file-name-absolute-p target)
-                             target
-                           (concat dir target))
+                         (concat (if (files--name-absolute-system-p target)
+                                     "/:" dir)
+                                 target)
                          done nil)
                  ;; No, we are done!
                  (setq done t))))))))
@@ -1284,7 +1291,10 @@ it means chase no more than that many links and then stop."
                 (directory-file-name (file-name-directory newname))))
          ;; Now find the parent of that dir.
          (setq newname (file-name-directory newname)))
-       (setq newname (expand-file-name tem (file-name-directory newname)))
+       (setq newname (concat (if (files--name-absolute-system-p tem)
+                                 "/:"
+                               (file-name-directory newname))
+                             tem))
        (setq count (1+ count))))
     newname))
 
@@ -5504,10 +5514,10 @@ directly into NEWNAME instead."
            ;; If NEWNAME is an existing directory and COPY-CONTENTS
            ;; is nil, copy into NEWNAME/[DIRECTORY-BASENAME].
            ((not copy-contents)
-            (setq newname (expand-file-name
+            (setq newname (concat
+                           (file-name-as-directory newname)
                            (file-name-nondirectory
-                            (directory-file-name directory))
-                           newname))
+                            (directory-file-name directory))))
             (and (file-exists-p newname)
                  (not (file-directory-p newname))
                  (error "Cannot overwrite non-directory %s with a directory"
@@ -5519,7 +5529,8 @@ directly into NEWNAME instead."
               ;; We do not want to copy "." and "..".
               (directory-files directory 'full
                                directory-files-no-dot-files-regexp))
-       (let ((target (expand-file-name (file-name-nondirectory file) newname))
+       (let ((target (concat (file-name-as-directory newname)
+                             (file-name-nondirectory file)))
              (filetype (car (file-attributes file))))
          (cond
           ((eq filetype t)       ; Directory but not a symlink.
@@ -7149,8 +7160,8 @@ Otherwise, trash FILENAME using the freedesktop.org conventions,
         ;; If `trash-directory' is non-nil, move the file there.
         (let* ((trash-dir   (expand-file-name trash-directory))
                (fn          (directory-file-name (expand-file-name filename)))
-               (new-fn      (expand-file-name (file-name-nondirectory fn)
-                                              trash-dir)))
+               (new-fn      (concat (file-name-as-directory trash-dir)
+                                    (file-name-nondirectory fn))))
           ;; We can't trash a parent directory of trash-directory.
           (if (string-prefix-p fn trash-dir)
               (error "Trash directory `%s' is a subdirectory of `%s'"
index fa694249cb7e1f664d114d62ac1a44165c84a17a..bbd1a4ef69cb94534c9ebc8a1f57735287b84b09 100644 (file)
@@ -2413,7 +2413,8 @@ DEFUN ("make-symbolic-link", Fmake_symbolic_link, Smake_symbolic_link, 2, 3,
 Both args must be strings.
 Signal a `file-already-exists' error if a file LINKNAME already exists
 unless optional third argument OK-IF-ALREADY-EXISTS is non-nil.
-An integer third arg means request confirmation if LINKNAME already exists.
+An integer third arg means request confirmation if LINKNAME already
+exists, and expand leading "~" or strip leading "/:" in TARGET.
 This happens for interactive use with M-x.  */)
   (Lisp_Object target, Lisp_Object linkname, Lisp_Object ok_if_already_exists)
 {
@@ -2421,21 +2422,15 @@ This happens for interactive use with M-x.  */)
   Lisp_Object encoded_target, encoded_linkname;
 
   CHECK_STRING (target);
-  /* If the link target has a ~, we must expand it to get
-     a truly valid file name.  Otherwise, do not expand;
-     we want to permit links to relative file names.  */
-  if (SREF (target, 0) == '~')
-    target = Fexpand_file_name (target, Qnil);
-
+  if (INTEGERP (ok_if_already_exists))
+    {
+      if (SREF (target, 0) == '~')
+       target = Fexpand_file_name (target, Qnil);
+      else if (SREF (target, 0) == '/' && SREF (target, 1) == ':')
+       target = Fsubstring_no_properties (target, make_number (2), Qnil);
+    }
   linkname = expand_cp_target (target, linkname);
 
-  /* If the file name has special constructs in it,
-     call the corresponding file handler.  */
-  handler = Ffind_file_name_handler (target, Qmake_symbolic_link);
-  if (!NILP (handler))
-    return call4 (handler, Qmake_symbolic_link, target,
-                 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);
@@ -2633,11 +2628,6 @@ emacs_readlinkat (int fd, char const *filename)
     return Qnil;
 
   val = build_unibyte_string (buf);
-  if (buf[0] == '/' && strchr (buf, ':'))
-    {
-      AUTO_STRING (slash_colon, "/:");
-      val = concat2 (slash_colon, val);
-    }
   if (buf != readlink_buf)
     xfree (buf);
   val = DECODE_FILE (val);
index 2ef1b553ab4d792baba1acd0ce736d8e52ea2c52..5103d2f21e6102782a17a9f57bbe56bff5d4fed7 100644 (file)
 
 (require 'ert)
 
-(defun try-char (char link)
-  (let ((target (string char)))
-    (make-symbolic-link target link)
-    (let* ((read-link (file-symlink-p link))
-           (failure (unless (string-equal target read-link)
-                      (list 'string-equal target read-link))))
-      (delete-file link)
-      failure)))
+(defun try-link (target link)
+  (make-symbolic-link target link)
+  (let* ((read-link (file-symlink-p link))
+         (failure (unless (string-equal target read-link)
+                    (list 'string-equal target read-link))))
+    (delete-file link)
+    failure))
 
 (defun fileio-tests--symlink-failure ()
   (let* ((dir (make-temp-file "fileio" t))
@@ -36,9 +35,9 @@
               (char 0))
           (while (and (not failure) (< char 127))
             (setq char (1+ char))
-            (unless (= char ?~)
-              (setq failure (try-char char link))))
-          failure)
+            (setq failure (try-link (string char) link)))
+          (or failure
+              (try-link "/:" link)))
       (delete-directory dir t))))
 
 (ert-deftest fileio-tests--odd-symlink-chars ()