changeset 32446:aab90b31807c

Added better remote directory support to Eshell, as well as a few bug fixes. See the ChangeLog.
author John Wiegley <johnw@newartisans.com>
date Fri, 13 Oct 2000 09:02:39 +0000
parents 05513a882886
children cfae1c82d702
files lisp/ChangeLog lisp/eshell/em-glob.el lisp/eshell/em-ls.el lisp/eshell/em-pred.el lisp/eshell/em-rebind.el lisp/eshell/em-unix.el lisp/eshell/esh-arg.el lisp/eshell/esh-mode.el lisp/eshell/esh-util.el
diffstat 9 files changed, 324 insertions(+), 98 deletions(-) [+]
line wrap: on
line diff
--- a/lisp/ChangeLog	Fri Oct 13 08:21:20 2000 +0000
+++ b/lisp/ChangeLog	Fri Oct 13 09:02:39 2000 +0000
@@ -1,3 +1,82 @@
+2000-10-13  John Wiegley  <johnw@gnu.org>
+
+	* eshell/esh-util.el: Added a global form which declares an
+	autoload for `parse-time-string', if that function is not already
+	defined, and if parse-time.el is available on the user's system.
+
+	* eshell/em-ls.el (eshell-ls-applicable): Extended this function
+	to be aware of ange-ftp user info.
+	(eshell-do-ls): Bind `ange-cache'.  Also, use
+	`eshell-file-attributes'.
+	(eshell-ls-annotate): Use `eshell-file-attributes'.
+	(eshell-ls-file): Made the user-id printing code a bit smarter.
+
+	* eshell/esh-util.el (eshell-ange-ls-uids): Added variable, to
+	allow identification of alias user ids in remote directories.
+	It's manual, but there's no other way to know when the current
+	user on the local machine, is also the owning user on the remote
+	machine.
+	(fboundp): Bind `ange-cache'.
+	(eshell-directory-files-and-attributes): Re-organized the logic a
+	bit to use `eshell-file-attributes' instead of `file-attributes'.
+	The former is more sensitive to directories that are read via FTP,
+	and knows how to use ange-ftp to determine full attribute
+	information, instead of just the name and last modtime.
+	(eshell-current-ange-uids): Return the current user id when in a
+	remote directory.
+	(eshell-parse-ange-ls): Parse a full directory listing that has
+	been returned by ange-ftp.
+	(eshell-file-attributes): This beefed up version of
+	`file-attributes' is only special if the user is currently in a
+	remote directory, in which case it does a lot of work to find out
+	what the real attributes of a file are, as they appear on the
+	remote machine.  This makes usage of remote directories (i.e.,
+	ange-ftp pathnames) much more useful.  You can now use Eshell as a
+	full-fledged FTP client, with much more manipulation ability than
+	most other clients.
+
+	* eshell/em-unix.el (eshell-du-prefer-over-ange): Added a new
+	variable, which means that Eshell's du should always be preferred
+	in remote directories.
+	(eshell-shuffle-files): Use `eshell-file-attributes', rather than
+	just `file-attributes'.
+	(eshell-mvcp-template): Bind `ange-cache', to improve performance
+	when reading remote directories.  This is an Eshell-specific
+	variable (not part of ange-ftp).
+	(eshell/ln): Bind `ange-cache'.
+	(eshell/du): Added some extra logic for determining when to use
+	Eshell's du (which is slow), and when to use the external version
+	(which may or may not exist).
+
+	* eshell/em-rebind.el (eshell-delchar-or-maybe-eof): Call
+	`eshell-interactive-process', rather than using
+	`get-buffer-process', since backgrounded processes don't count in
+	the context of this function's logic.
+
+	* eshell/esh-arg.el (eshell-parse-double-quote): Moved a call to
+	`forward-char', so that null strings are parsed correctly.
+
+2000-09-10  John Wiegley  <johnw@gnu.org>
+
+	* eshell/em-pred.el (eshell-pred-file-type,
+	eshell-pred-file-links, eshell-pred-file-size): Use
+	`eshell-file-attributes'.  This is more correct over ange-ftp.
+
+	* eshell/em-glob.el (eshell-extended-glob): Bind `ange-cache', so
+	that remote file globbing is more efficient.
+
+	* eshell/em-ls.el (eshell-ls-dir): Use `expand-file-name' when
+	gathering the files and attributes within a directory.
+
+	* eshell/em-unix.el (eshell/cat): If any of the files passed on
+	the command line is a special file (not a regular file, directory
+	or symlink), always attempt to call the external version of cat.
+
+2000-09-06  John Wiegley  <johnw@gnu.org>
+
+	* eshell/esh-mode.el (eshell-find-tag): Corrections to the
+	Eshell-friendly version of find-tag.
+
 2000-10-13  Miles Bader  <miles@lsi.nec.co.jp>
 
 	* image-file.el (image-file-name-extensions) 
@@ -347,6 +426,8 @@
 	* files.el (set-auto-mode): Ignore unknown -*- mode -*- rather than
 	raise an error.  This way it can still default to a sane value.
 
+2000-10-06  Stefan Monnier  <monnier@cs.yale.edu>
+
 	* startup.el (fancy-splash-screens): Use local rather than global map.
 	Don't use `update-menu-bindings' any more.
 	Get rid of assumptions about keymap representation.
@@ -1362,9 +1443,6 @@
 
 2000-09-16  Andrew Innes  <andrewi@gnu.org>
 
-	* makefile.nt (compile-files): No need to make .elc files
-	read-only, since they aren't under VC now.
-
 	* makefile.w32-in (compile-files-CMD): No need to make .elc files
 	read-only, since they aren't under VC now.
 
--- a/lisp/eshell/em-glob.el	Fri Oct 13 08:21:20 2000 +0000
+++ b/lisp/eshell/em-glob.el	Fri Oct 13 09:02:39 2000 +0000
@@ -243,7 +243,7 @@
 
    (INCLUDE-REGEXP EXCLUDE-REGEXP (PRED-FUNC-LIST) (MOD-FUNC-LIST))"
   (let ((paths (eshell-split-path glob))
-	matches message-shown)
+	matches message-shown ange-cache)
     (unwind-protect
 	(if (and (cdr paths)
 		 (file-name-absolute-p (car paths)))
--- a/lisp/eshell/em-ls.el	Fri Oct 13 08:21:20 2000 +0000
+++ b/lisp/eshell/em-ls.el	Fri Oct 13 09:02:39 2000 +0000
@@ -192,9 +192,15 @@
   "Test whether, for ATTRS, the user UID can do what corresponds to INDEX.
 This is really just for efficiency, to avoid having to stat the file
 yet again."
-  `(if (= (user-uid) (nth 2 ,attrs))
-       (not (eq (aref (nth 8 ,attrs) ,index) ?-))
-     (,(eval func) ,file)))
+  `(if (numberp (nth 2 ,attrs))
+       (if (= (user-uid) (nth 2 ,attrs))
+ 	   (not (eq (aref (nth 8 ,attrs) ,index) ?-))
+ 	 (,(eval func) ,file))
+     (not (eq (aref (nth 8 ,attrs)
+ 		    (+ ,index (if (member (nth 2 ,attrs)
+ 					  (eshell-current-ange-uids))
+ 				  0 6)))
+ 	      ?-))))
 
 (defcustom eshell-ls-highlight-alist nil
   "*This alist correlates test functions to color.
@@ -265,7 +271,8 @@
   (defvar show-all)
   (defvar show-recursive)
   (defvar show-size)
-  (defvar sort-method))
+  (defvar sort-method)
+  (defvar ange-cache))
 
 (defun eshell-do-ls (&rest args)
   "Implementation of \"ls\" in Lisp, passing ARGS."
@@ -328,7 +335,7 @@
      (setq listing-style 'by-columns))
    (unless args
      (setq args (list ".")))
-   (let ((eshell-ls-exclude-regexp eshell-ls-exclude-regexp))
+   (let ((eshell-ls-exclude-regexp eshell-ls-exclude-regexp) ange-cache)
      (when ignore-pattern
        (unless (eshell-using-module 'eshell-glob)
 	 (error (concat "-I option requires that `eshell-glob'"
@@ -347,7 +354,7 @@
 				(file-name-absolute-p arg))
 			   (expand-file-name arg)
 			 arg)
-		       (file-attributes arg)))) args)
+		       (eshell-file-attributes arg)))) args)
       t (expand-file-name default-directory)))
    (funcall flush-func)))
 
@@ -379,7 +386,7 @@
 		   (file-name-directory
 		    (expand-file-name (car fileinfo))))))
       (setq attr
-	    (file-attributes
+	    (eshell-file-attributes
 	     (let ((target (if dir
 			       (expand-file-name (cadr fileinfo) dir)
 			     (cadr fileinfo))))
@@ -425,16 +432,22 @@
 		 "%s%4d %-8s %-8s "
 		 (or (nth 8 attrs) "??????????")
 		 (or (nth 1 attrs) 0)
-		 (or (and (not numeric-uid-gid)
-			  (nth 2 attrs)
-			  (eshell-substring
-			   (user-login-name (nth 2 attrs)) 8))
+		 (or (let ((user (nth 2 attrs)))
+		       (and (not numeric-uid-gid)
+			    user
+			    (eshell-substring
+			     (if (numberp user)
+				 (user-login-name user)
+			       user) 8)))
 		     (nth 2 attrs)
 		     "")
-		 (or (and (not numeric-uid-gid)
-			  (nth 3 attrs)
-			  (eshell-substring
-			   (eshell-group-name (nth 3 attrs)) 8))
+		 (or (let ((group (nth 3 attrs)))
+		       (and (not numeric-uid-gid)
+			    group
+			    (eshell-substring
+			     (if (numberp group)
+				 (eshell-group-name group)
+			       group) 8)))
 		     (nth 3 attrs)
 		     ""))
 		(let* ((str (eshell-ls-printable-size (nth 7 attrs)))
--- a/lisp/eshell/em-pred.el	Fri Oct 13 08:21:20 2000 +0000
+++ b/lisp/eshell/em-pred.el	Fri Oct 13 09:02:39 2000 +0000
@@ -464,7 +464,7 @@
 	(forward-char)
       (setq type ?%)))
   `(lambda (file)
-     (let ((attrs (file-attributes (directory-file-name file))))
+     (let ((attrs (eshell-file-attributes (directory-file-name file))))
        (if attrs
 	   (memq (aref (nth 8 attrs) 0)
 		 ,(if (eq type ?%)
@@ -489,7 +489,7 @@
     (setq amount (string-to-number (match-string 0)))
     (goto-char (match-end 0))
     `(lambda (file)
-       (let ((attrs (file-attributes file)))
+       (let ((attrs (eshell-file-attributes file)))
 	 (if attrs
 	     (,(if (eq qual ?-)
 		   '<
@@ -518,7 +518,7 @@
     (setq amount (* (string-to-number (match-string 0)) quantum))
     (goto-char (match-end 0))
     `(lambda (file)
-       (let ((attrs (file-attributes file)))
+       (let ((attrs (eshell-file-attributes file)))
 	 (if attrs
 	     (,(if (eq qual ?-)
 		   '<
--- a/lisp/eshell/em-rebind.el	Fri Oct 13 08:21:20 2000 +0000
+++ b/lisp/eshell/em-rebind.el	Fri Oct 13 09:02:39 2000 +0000
@@ -232,7 +232,7 @@
 Sends an EOF only if point is at the end of the buffer and there is no
 input."
   (interactive "p")
-  (let ((proc (get-buffer-process (current-buffer))))
+  (let ((proc (eshell-interactive-process)))
     (if (eobp)
 	(cond
 	 ((/= (point) eshell-last-output-end)
--- a/lisp/eshell/em-unix.el	Fri Oct 13 08:21:20 2000 +0000
+++ b/lisp/eshell/em-unix.el	Fri Oct 13 09:02:39 2000 +0000
@@ -122,6 +122,12 @@
   :type 'boolean
   :group 'eshell-unix)
 
+(defcustom eshell-du-prefer-over-ange nil
+  "*Use Eshell's du in ange-ftp remote directories.
+Otherwise, Emacs will attempt to use rsh to invoke du the machine."
+  :type 'boolean
+  :group 'eshell-unix)
+
 (require 'esh-opt)
 
 ;;; Functions:
@@ -296,7 +302,7 @@
   "Shuffle around some filesystem entries, using FUNC to do the work."
   (if (null target)
       (error "%s: missing destination file" command))
-  (let ((attr-target (file-attributes target))
+  (let ((attr-target (eshell-file-attributes target))
 	(is-dir (or (file-directory-p target)
 		    (and preview (not eshell-warn-dot-directories))))
 	attr)
@@ -315,8 +321,10 @@
        ((and attr-target
 	     (or (not (eshell-under-windows-p))
 		 (eq system-type 'ms-dos))
-	     (setq attr (file-attributes (car files)))
+	     (setq attr (eshell-file-attributes (car files)))
+	     (nth 10 attr-target) (nth 10 attr)
 	     (= (nth 10 attr-target) (nth 10 attr))
+	     (nth 11 attr-target) (nth 11 attr)
 	     (= (nth 11 attr-target) (nth 11 attr)))
 	(eshell-error (format "%s: `%s' and `%s' are the same file\n"
 			      command (car files) target)))
@@ -339,10 +347,10 @@
 		(let (eshell-warn-dot-directories)
 		  (if (and (not deep)
 			   (eq func 'rename-file)
-			   (= (nth 11 (file-attributes
+			   (= (nth 11 (eshell-file-attributes
 				       (file-name-directory
 					(expand-file-name source))))
-			      (nth 11 (file-attributes
+			      (nth 11 (eshell-file-attributes
 				       (file-name-directory
 					(expand-file-name target))))))
 		      (apply 'eshell-funcalln func source target args)
@@ -415,7 +423,7 @@
 		     (or (not no-dereference)
 			 (not (file-symlink-p (car args)))))))
        (eshell-shorthand-tar-command ,command args)
-     (let (target)
+     (let (target ange-cache)
        (if (> (length args) 1)
 	   (progn
 	     (setq target (car (last args)))
@@ -508,7 +516,7 @@
 more than one TARGET, the last argument must be a directory;  create links
 in DIRECTORY to each TARGET.  Create hard links by default, symbolic links
 with '--symbolic'.  When creating hard links, each TARGET must exist.")
-   (let (target no-dereference)
+   (let (target no-dereference ange-cache)
      (if (> (length args) 1)
 	 (progn
 	   (setq target (car (last args)))
@@ -525,10 +533,24 @@
    nil))
 
 (defun eshell/cat (&rest args)
-  "Implementation of cat in Lisp."
-  (if eshell-in-pipeline-p
-      (throw 'eshell-replace-command
-	     (eshell-parse-command "*cat" (eshell-flatten-list args)))
+  "Implementation of cat in Lisp.
+If in a pipeline, or the file is not a regular file, directory or
+symlink, then revert to the system's definition of cat."
+  (setq args (eshell-flatten-list args))
+  (if (or eshell-in-pipeline-p
+	  (catch 'special
+	    (eshell-for arg args
+	      (unless (let ((attrs (eshell-file-attributes arg)))
+			(and attrs (memq (aref (nth 8 attrs) 0)
+					 '(?d ?l ?-))))
+		(throw 'special t)))))
+      (let ((ext-cat (eshell-search-path "cat")))
+	(if ext-cat
+	    (throw 'eshell-replace-command
+		   (eshell-parse-command ext-cat args))
+	  (if eshell-in-pipeline-p
+	      (error "Eshell's `cat' does not work in pipelines")
+	    (error "Eshell's `cat' cannot display one of the files given"))))
     (eshell-init-print-buffer)
     (eshell-eval-using-options
      "cat" args
@@ -772,61 +794,69 @@
 
 (defun eshell/du (&rest args)
   "Implementation of \"du\" in Lisp, passing ARGS."
-  (if (eshell-search-path "du")
-      (throw 'eshell-replace-command
-	     (eshell-parse-command "*du" (eshell-flatten-list args)))
-    (eshell-eval-using-options
-     "du" args
-     '((?a "all" nil show-all
-	   "write counts for all files, not just directories")
-       (nil "block-size" t block-size
-	    "use SIZE-byte blocks (i.e., --block-size SIZE)")
-       (?b "bytes" nil by-bytes
-	   "print size in bytes")
-       (?c "total" nil grand-total
-	   "produce a grand total")
-       (?d "max-depth" t max-depth
-	   "display data only this many levels of data")
-       (?h "human-readable" 1024 human-readable
-	   "print sizes in human readable format")
-       (?H "is" 1000 human-readable
-	   "likewise, but use powers of 1000 not 1024")
-       (?k "kilobytes" 1024 block-size
-	   "like --block-size 1024")
-       (?L "dereference" nil dereference-links
-	   "dereference all symbolic links")
-       (?m "megabytes" 1048576 block-size
-	   "like --block-size 1048576")
-       (?s "summarize" 0 max-depth
-	   "display only a total for each argument")
-       (?x "one-file-system" nil only-one-filesystem
-	   "skip directories on different filesystems")
-       (nil "help" nil nil
-	    "show this usage screen")
-       :external "du"
-       :usage "[OPTION]... FILE...
+  (setq args (if args
+		 (eshell-flatten-list args)
+	       '(".")))
+  (let ((ext-du (eshell-search-path "du")))
+    (if (and ext-du
+	     (not (catch 'have-ange-path
+		    (eshell-for arg args
+		      (if (eq (find-file-name-handler (expand-file-name arg)
+						      'directory-files)
+			      'ange-ftp-hook-function)
+			  (throw 'have-ange-path t))))))
+	(throw 'eshell-replace-command
+	       (eshell-parse-command ext-du args))
+      (eshell-eval-using-options
+       "du" args
+       '((?a "all" nil show-all
+	     "write counts for all files, not just directories")
+	 (nil "block-size" t block-size
+	      "use SIZE-byte blocks (i.e., --block-size SIZE)")
+	 (?b "bytes" nil by-bytes
+	     "print size in bytes")
+	 (?c "total" nil grand-total
+	     "produce a grand total")
+	 (?d "max-depth" t max-depth
+	     "display data only this many levels of data")
+	 (?h "human-readable" 1024 human-readable
+	     "print sizes in human readable format")
+	 (?H "is" 1000 human-readable
+	     "likewise, but use powers of 1000 not 1024")
+	 (?k "kilobytes" 1024 block-size
+	     "like --block-size 1024")
+	 (?L "dereference" nil dereference-links
+	     "dereference all symbolic links")
+	 (?m "megabytes" 1048576 block-size
+	     "like --block-size 1048576")
+	 (?s "summarize" 0 max-depth
+	     "display only a total for each argument")
+	 (?x "one-file-system" nil only-one-filesystem
+	     "skip directories on different filesystems")
+	 (nil "help" nil nil
+	      "show this usage screen")
+	 :external "du"
+	 :usage "[OPTION]... FILE...
 Summarize disk usage of each FILE, recursively for directories.")
-     (unless by-bytes
-       (setq block-size (or block-size 1024)))
-     (if (and max-depth (stringp max-depth))
-	 (setq max-depth (string-to-int max-depth)))
-     ;; filesystem support means nothing under Windows
-     (if (eshell-under-windows-p)
-	 (setq only-one-filesystem nil))
-     (unless args
-       (setq args '(".")))
-     (let ((size 0.0))
-       (while args
-	 (if only-one-filesystem
-	     (setq only-one-filesystem
-		   (nth 11 (file-attributes
-			    (file-name-as-directory (car args))))))
-	 (setq size (+ size (eshell-du-sum-directory
-			     (directory-file-name (car args)) 0)))
-	 (setq args (cdr args)))
-       (if grand-total
-	   (eshell-print (concat (eshell-du-size-string size)
-				 "total\n")))))))
+       (unless by-bytes
+	 (setq block-size (or block-size 1024)))
+       (if (and max-depth (stringp max-depth))
+	   (setq max-depth (string-to-int max-depth)))
+       ;; filesystem support means nothing under Windows
+       (if (eshell-under-windows-p)
+	   (setq only-one-filesystem nil))
+       (let ((size 0.0) ange-cache)
+	 (while args
+	   (if only-one-filesystem
+	       (setq only-one-filesystem
+		     (nth 11 (eshell-file-attributes
+			      (file-name-as-directory (car args))))))
+	   (setq size (+ size (eshell-du-sum-directory
+			       (directory-file-name (car args)) 0)))
+	   (setq args (cdr args)))
+	 (if grand-total
+	     (eshell-print (concat (eshell-du-size-string size)
+				   "total\n"))))))))
 
 (defvar eshell-time-start nil)
 
--- a/lisp/eshell/esh-arg.el	Fri Oct 13 08:21:20 2000 +0000
+++ b/lisp/eshell/esh-arg.el	Fri Oct 13 09:02:39 2000 +0000
@@ -328,13 +328,13 @@
 (defun eshell-parse-double-quote ()
   "Parse a double quoted string, which allows for variable interpolation."
   (when (eq (char-after) ?\")
-    (forward-char)
     (let* ((end (eshell-find-delimiter ?\" ?\" nil nil t))
 	   (eshell-current-quoted t))
       (if (not end)
 	  (throw 'eshell-incomplete ?\")
 	(prog1
 	    (save-restriction
+	      (forward-char)
 	      (narrow-to-region (point) end)
 	      (list 'eshell-escape-arg
 		    (eshell-parse-argument)))
--- a/lisp/eshell/esh-mode.el	Fri Oct 13 08:21:20 2000 +0000
+++ b/lisp/eshell/esh-mode.el	Fri Oct 13 09:02:39 2000 +0000
@@ -524,8 +524,9 @@
   (interactive)
   (require 'etags)
   (let ((inhibit-read-only t)
-	(no-default (eobp)))
-    (setq tagname (find-tag-interactive "Find tag: " no-default))
+	(no-default (eobp))
+	(find-tag-default-function 'ignore))
+    (setq tagname (car (find-tag-interactive "Find tag: ")))
     (find-tag tagname next-p regexp-p)))
 
 (defun eshell-move-argument (limit func property arg)
--- a/lisp/eshell/esh-util.el	Fri Oct 13 08:21:20 2000 +0000
+++ b/lisp/eshell/esh-util.el	Fri Oct 13 09:02:39 2000 +0000
@@ -86,6 +86,15 @@
   :type 'regexp
   :group 'eshell-util)
 
+(defcustom eshell-ange-ls-uids nil
+  "*List of user/host/id strings, used to determine remote ownership."
+  :type '(list (cons :tag "Host/User Pair"
+		     (string :tag "Hostname")
+		     (repeat (cons :tag "User/UID List"
+				   (string :tag "Username")
+				   (repeat :tag "UIDs" string)))))
+  :group 'eshell-util)
+
 ;;; Internal Variables:
 
 (defvar eshell-group-names nil
@@ -558,28 +567,123 @@
 (unless (fboundp 'directory-files-and-attributes)
   (defun directory-files-and-attributes (dir &optional full match nosort)
     (documentation 'directory-files)
-    (let* ((dir (expand-file-name dir))
-	   (default-directory dir))
+    (let ((dir (expand-file-name dir)) ange-cache)
       (mapcar
        (function
 	(lambda (file)
-	  (cons file (file-attributes file))))
+	  (cons file (eshell-file-attributes (expand-file-name file dir)))))
        (directory-files dir full match nosort)))))
 
+(eval-when-compile
+  (defvar ange-cache))
+
 (defun eshell-directory-files-and-attributes (dir &optional full match nosort)
   "Make sure to use the handler for `directory-file-and-attributes'."
-  (let ((dfh (find-file-name-handler dir 'directory-files)))
+  (let* ((dir (expand-file-name dir))
+	 (dfh (find-file-name-handler dir 'directory-files)))
     (if (not dfh)
 	(directory-files-and-attributes dir full match nosort)
-      (let* ((files (funcall dfh 'directory-files dir full match nosort))
-	     (fah (find-file-name-handler dir 'file-attributes))
-	     (default-directory (expand-file-name dir)))
+      (let ((files (funcall dfh 'directory-files dir full match nosort))
+	    (fah (find-file-name-handler dir 'file-attributes)))
 	(mapcar
 	 (function
 	  (lambda (file)
-	    (cons file (funcall fah 'file-attributes file))))
+	    (cons file (if fah
+			   (eshell-file-attributes
+			    (expand-file-name file dir))
+			 (file-attributes (expand-file-name file dir))))))
 	 files)))))
 
+(defun eshell-current-ange-uids ()
+  (if (string-match "/\\([^@]+\\)@\\([^:]+\\):" default-directory)
+      (let* ((host (match-string 2 default-directory))
+	     (user (match-string 1 default-directory))
+	     (host-users (assoc host eshell-ange-ls-uids)))
+	(when host-users
+	  (setq host-users (cdr host-users))
+	  (cdr (assoc user host-users))))))
+
+;; Add an autoload for parse-time-string
+(if (and (not (fboundp 'parse-time-string))
+	 (locate-library "parse-time"))
+    (autoload 'parse-time-string "parse-time"))
+
+(defun eshell-parse-ange-ls (dir)
+  (let (entry)
+    (with-temp-buffer
+      (insert (ange-ftp-ls dir "-la" nil))
+      (goto-char (point-min))
+      (if (looking-at "^total [0-9]+$")
+	  (forward-line 1))
+      ;; Some systems put in a blank line here.
+      (if (eolp) (forward-line 1))
+      (while (looking-at
+	      `,(concat "\\([dlscb-][rwxst-]+\\)"
+			"\\s-*" "\\([0-9]+\\)" "\\s-+"
+			"\\(\\S-+\\)" "\\s-+"
+			"\\(\\S-+\\)" "\\s-+"
+			"\\([0-9]+\\)" "\\s-+" "\\(.*\\)"))
+	(let* ((perms (match-string 1))
+	       (links (string-to-number (match-string 2)))
+	       (user (match-string 3))
+	       (group (match-string 4))
+	       (size (string-to-number (match-string 5)))
+	       (mtime
+		(if (fboundp 'parse-time-string)
+		    (let ((moment (parse-time-string
+				   (match-string 6))))
+		      (if (nth 0 moment)
+			  (setcar (nthcdr 5 moment)
+				  (nth 5 (decode-time (current-time))))
+			(setcar (nthcdr 0 moment) 0)
+			(setcar (nthcdr 1 moment) 0)
+			(setcar (nthcdr 2 moment) 0))
+		      (apply 'encode-time moment))
+		  (ange-ftp-file-modtime (expand-file-name name dir))))
+	       (name (ange-ftp-parse-filename))
+	       symlink)
+	  (if (string-match "\\(.+\\) -> \\(.+\\)" name)
+	      (setq symlink (match-string 2 name)
+		    name (match-string 1 name)))
+	  (setq entry
+		(cons
+		 (cons name
+		       (list (if (eq (aref perms 0) ?d)
+				 t
+			       symlink)
+			     links user group
+			     nil mtime nil
+			     size perms nil nil)) entry)))
+	(forward-line)))
+    entry))
+
+(defun eshell-file-attributes (file)
+  "Return the attributes of FILE, playing tricks if it's over ange-ftp."
+  (let* ((file (expand-file-name file))
+	 (handler (find-file-name-handler file 'file-attributes))
+	 entry)
+    (if (not handler)
+	(file-attributes file)
+      (if (eq (find-file-name-handler (file-name-directory file)
+				      'directory-files)
+	      'ange-ftp-hook-function)
+	  (let ((base (file-name-nondirectory file))
+		(dir (file-name-directory file)))
+	    (if (boundp 'ange-cache)
+		(setq entry (cdr (assoc base (cdr (assoc dir ange-cache))))))
+	    (unless entry
+	      (setq entry (eshell-parse-ange-ls dir))
+	      (if (boundp 'ange-cache)
+		  (setq ange-cache
+			(cons (cons dir entry)
+			      ange-cache)))
+	      (if entry
+		  (let ((fentry (assoc base (cdr entry))))
+		    (if fentry
+			(setq entry (cdr fentry))
+		      (setq entry nil)))))))
+      (or entry (funcall handler 'file-attributes file)))))
+
 (defun eshell-copy-list (list)
   "Return a copy of a list, which may be a dotted list.
 The elements of the list are not copied, just the list structure itself."