GNU bug report logs - #10489
24.0.92; dired-do-copy may create infinite directory hierarchy

Previous Next

Package: emacs;

Reported by: michael_heerdegen <at> web.de

Date: Thu, 12 Jan 2012 19:36:01 UTC

Severity: important

Tags: patch

Merged with 11130

Found in version 24.0.92

Done: Chong Yidong <cyd <at> gnu.org>

Bug is archived. No further changes may be made.

Full log


View this message in rfc822 format

From: Thierry Volpiatto <thierry.volpiatto <at> gmail.com>
To: Eli Zaretskii <eliz <at> gnu.org>
Cc: 10489 <at> debbugs.gnu.org
Subject: bug#10489: 24.0.92; dired-do-copy may create infinite directory hierarchy
Date: Fri, 24 Feb 2012 13:18:05 +0100
[Message part 1 (text/plain, inline)]
Eli Zaretskii <eliz <at> gnu.org> writes:

> A better error message would be
>
>   (error "Cannot copy `%s' into its subdirectory `%s'" from to)
Done

Have fixed commented block in `dired-create-files', have a look.

> I don't understand why you use expand-file-name here: file-truename
> does it for you anyway.
Fixed.

> Suggest to modify the doc string as follows:
Done.

Have modified `file-subdir-of-p' according to your advices.
Please have a look. (Tested with success on windows also)

(file-subdir-of-p "/" "/") works now.

> Finally, it looks like this function only works when its two arguments
> already exist; when they don't, it returns nil.  If this is the
> intent, it should be reflected in the doc string.
Fixed docstring.
Fixed `copy-directory' by doing another check of `file-subdir-of-p'
after creation of the non--existing subdir. Thanks for this.

[patch-r118916.patch (text/x-diff, inline)]
# HG changeset patch
# User Thierry Volpiatto <thierry.volpiatto <at> gmail.com>
# Date 1330085238 -3600
# Node ID 3006935d19d27ff609e7f691d436efcdeb3b928f
# Parent  d736ca342d20302be2fcb7e81f1c9e364b759663
Fix bug#10489: 24.0.92; dired-do-copy may create infinite directory hierarchy.

* lisp/files.el (files-equal-p): New, simple equality check between two filename.
(file-subdir-of-p): New, Check if file1 is subdir of file2.
(copy-directory): Return error when trying to copy a directory on itself.

* lisp/dired-aux.el (dired-copy-file-recursive): Same.
(dired-create-files): Modify destination when source is equal to dest when copying files.
Return also when dest is a subdir of source.

diff --git a/lisp/dired-aux.el b/lisp/dired-aux.el
--- a/lisp/dired-aux.el
+++ b/lisp/dired-aux.el
@@ -1264,6 +1264,8 @@
 
 (defun dired-copy-file-recursive (from to ok-flag &optional
 				       preserve-time top recursive)
+  (when (file-subdir-of-p to from)
+    (error "Cannot copy `%s' into its subdirectory `%s'" from to))
   (let ((attrs (file-attributes from)))
     (if (and recursive
 	     (eq t (car attrs))
@@ -1430,10 +1432,30 @@
                   (cond  ((integerp marker-char) marker-char)
                          (marker-char (dired-file-marker from)) ; slow
                          (t nil))))
-	    (when (and (file-directory-p from)
-		       (file-directory-p to)
-		       (eq file-creator 'dired-copy-file))
-	      (setq to (file-name-directory to)))
+            ;; Handle the `dired-copy-file' file-creator specially
+            ;; When copying a directory to another directory or
+            ;; possibly to itself or one of its subdirectories.
+            ;; e.g "~/foo/" => "~/test/"
+            ;; or "~/foo/" =>"~/foo/"
+            ;; or "~/foo/ => ~/foo/bar/")
+            ;; In this case the 'name-constructor' have set the destination
+            ;; TO to "~/test/foo" because the old emacs23 behavior
+            ;; of `copy-directory' was to not create the subdirectory
+            ;; and instead copy the contents.
+            ;; With the new behavior of `copy-directory'
+            ;; (similar to the `cp' shell command) we don't
+            ;; need such a construction of the target directory,
+            ;; so modify the destination TO to "~/test/" instead of "~/test/foo/".
+            (let ((destname (file-name-directory to)))
+              (when (and (file-directory-p from)
+                         (file-directory-p to)
+                         (eq file-creator 'dired-copy-file))
+                (setq to destname))
+              ;; If DESTNAME and FROM are the same directory or
+              ;; If DESTNAME is a subdirectory of FROM, return error.
+              (and (file-subdir-of-p destname from)
+                   (error "Cannot copy `%s' into its subdirectory `%s'"
+                          from to)))
             (condition-case err
                 (progn
                   (funcall file-creator from to dired-overwrite-confirmed)
diff --git a/lisp/files.el b/lisp/files.el
--- a/lisp/files.el
+++ b/lisp/files.el
@@ -4985,6 +4985,34 @@
 		 directory 'full directory-files-no-dot-files-regexp)))
       (delete-directory-internal directory)))))
 
+(defun files-equal-p (file1 file2)
+  "Return non-nil if FILE1 and FILE2 name the same file."
+  (and (equal (file-remote-p file1) (file-remote-p file2))
+       (equal (file-attributes (file-truename file1))
+              (file-attributes (file-truename file2)))))
+
+(defun file-subdir-of-p (dir1 dir2)
+  "Return non-nil if DIR1 is a subdirectory of DIR2.
+Note that a directory is treated by this function as a subdirectory of itself.
+This function only works when its two arguments already exist,
+when they don't, it returns nil."
+  (when (and (not (or (file-remote-p dir1)
+                      (file-remote-p dir2)))
+             (file-directory-p dir1)
+             (file-directory-p dir2))
+    (loop with f1 = (file-truename dir1)
+          with f2 = (file-truename dir2)
+          with ls1 = (or (split-string f1 "/" t) (list "/"))
+          with ls2 = (or (split-string f2 "/" t) (list "/"))
+          for p = (string-match "^/" f1)
+          for i in ls1
+          for j in ls2
+          when (string= i j)
+          concat (if p (concat "/" i) (concat i "/"))
+          into root
+          finally return
+          (files-equal-p (file-truename root) f2))))
+
 (defun copy-directory (directory newname &optional keep-time parents copy-contents)
   "Copy DIRECTORY to NEWNAME.  Both args must be strings.
 This function always sets the file modes of the output files to match
@@ -5011,6 +5039,9 @@
 	    (format "Copy directory %s to: " dir)
 	    default-directory default-directory nil nil)
 	   current-prefix-arg t nil)))
+  (when (file-subdir-of-p newname directory)
+    (error "Cannot copy `%s' into its subdirectory `%s'"
+           directory newname))
   ;; If default-directory is a remote directory, make sure we find its
   ;; copy-directory handler.
   (let ((handler (or (find-file-name-handler directory 'copy-directory)
@@ -5025,7 +5056,12 @@
       (cond ((not (file-directory-p newname))
 	     ;; If NEWNAME is not an existing directory, create it;
 	     ;; that is where we will copy the files of DIRECTORY.
-	     (make-directory newname parents))
+	     (make-directory newname parents)
+             ;; `file-subdir-of-p' doesn't handle non--existing directories,
+             ;; so double check now if NEWNAME is not a subdir of DIRECTORY.
+             (and (file-subdir-of-p newname directory)
+                  (error "Cannot copy `%s' into its subdirectory `%s'"
+                         directory newname)))
 	    ;; If NEWNAME is an existing directory and COPY-CONTENTS
 	    ;; is nil, copy into NEWNAME/[DIRECTORY-BASENAME].
 	    ((not copy-contents)
[Message part 3 (text/plain, inline)]

-- 
  Thierry
Get my Gnupg key:
gpg --keyserver pgp.mit.edu --recv-keys 59F29997 

This bug report was last modified 13 years and 58 days ago.

Previous Next


GNU bug tracking system
Copyright (C) 1999 Darren O. Benham, 1997,2003 nCipher Corporation Ltd, 1994-97 Ian Jackson.