]> git.eshelyaron.com Git - emacs.git/commitdiff
Added better remote directory support to Eshell, as well as a few bug
authorJohn Wiegley <johnw@newartisans.com>
Fri, 13 Oct 2000 09:02:39 +0000 (09:02 +0000)
committerJohn Wiegley <johnw@newartisans.com>
Fri, 13 Oct 2000 09:02:39 +0000 (09:02 +0000)
fixes.  See the ChangeLog.

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

index 7be8f0c6da3cca1601505fa5066800cc1672448b..7077ad7b58822d9c9107553dbf1ce4c2353d6bc2 100644 (file)
@@ -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) 
        * 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.
 
 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.
 
index 4cb3d1e40ce66473ec395c0375d31f8b0c1d0bc2..ec8c1b2c37aa25de938225b0bf802151ca7dfa7e 100644 (file)
@@ -243,7 +243,7 @@ resulting regular expression."
 
    (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)))
index cd16f049815ca243822b07f31a29621a7161ff3a..77e5d5577dc61c39cafedaeea89ce507e3cc8723 100644 (file)
@@ -192,9 +192,15 @@ really need to stick around for very long."
   "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 @@ instead."
   (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 @@ Sort entries alphabetically across.")
      (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 @@ Sort entries alphabetically across.")
                                (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 @@ name should be displayed as, etc.  Think of it as cooking a FILEINFO."
                   (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 @@ whose cdr is the list of file attributes."
                 "%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)))
index c3e3de5adbd57a3f360f6b41d2e8e5664fafea1d..9708e2d793a223c03bb19b03f936612cbb186418 100644 (file)
@@ -464,7 +464,7 @@ that 'ls -l' will show in the first column of its display. "
        (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 @@ that 'ls -l' will show in the first column of its display. "
     (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 @@ that 'ls -l' will show in the first column of its display. "
     (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 ?-)
                   '<
index 0463a78ffb49c778ddeacde7b40f0eef54d68d7a..4caae2da425278d22ce4fe299f97bf970b381d23 100644 (file)
@@ -232,7 +232,7 @@ lock it at that."
 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)
index 17e110f65edab48cec63e9b106e5c61aec557468..07f14c6e30ad10759e45276b915fa95dc51245b6 100644 (file)
@@ -122,6 +122,12 @@ Otherwise, `rmdir' is required."
   :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 @@ Remove the DIRECTORY(ies), if they are empty.")
   "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 @@ Remove the DIRECTORY(ies), if they are empty.")
        ((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 @@ Remove the DIRECTORY(ies), if they are empty.")
                (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 @@ Remove the DIRECTORY(ies), if they are empty.")
                     (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 @@ Create a link to the specified TARGET with optional LINK_NAME.  If there is
 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 @@ with '--symbolic'.  When creating hard links, each TARGET must exist.")
    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 @@ external command."
 
 (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)
 
index 8364ca9a7f679b2cd82928a410ea88abd9928850..985401fbe37c46ef36c5144f1451f5aa19fc4b06 100644 (file)
@@ -328,13 +328,13 @@ special character that is not itself a backslash."
 (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)))
index 1d79c8af701c7bfad20e667ca2733701f9c0c2dc..4d32da81f05a08c7acbd1b3c21c5d65f164f0715 100644 (file)
@@ -524,8 +524,9 @@ sessions, such as when using `eshell-command'.")
   (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)
index 3d650928c62a07b988b58bd5efa18699a1d8de1c..8441689310706cf1024e08f89fd81eb75916b7ba 100644 (file)
@@ -86,6 +86,15 @@ function `string-to-number'."
   :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 optional argument INPLACE is non-nil, return a new string."
 (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."