Merge from emacs--devo--0
[bpt/emacs.git] / lisp / net / ange-ftp.el
index 1115b18..3fa7510 100644 (file)
@@ -1,7 +1,7 @@
 ;;; ange-ftp.el --- transparent FTP support for GNU Emacs
 
 ;;; ange-ftp.el --- transparent FTP support for GNU Emacs
 
-;; Copyright (C) 1989,90,91,92,93,94,95,96,98, 2000, 2001
-;;  Free Software Foundation, Inc.
+;; Copyright (C) 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1998,
+;;   2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007 Free Software Foundation, Inc.
 
 ;; Author: Andy Norman (ange@hplb.hpl.hp.com)
 ;; Maintainer: FSF
 
 ;; Author: Andy Norman (ange@hplb.hpl.hp.com)
 ;; Maintainer: FSF
@@ -21,8 +21,8 @@
 
 ;; You should have received a copy of the GNU General Public License
 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
 
 ;; You should have received a copy of the GNU General Public License
 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
-;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
-;; Boston, MA 02111-1307, USA.
+;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+;; Boston, MA 02110-1301, USA.
 
 ;;; Commentary:
 
 
 ;;; Commentary:
 
 ;; ange-ftp-bs2000-special-prefix because names starting with # or @
 ;; are reserved for temporary files.
 ;; This is especially important for auto-save files.
 ;; ange-ftp-bs2000-special-prefix because names starting with # or @
 ;; are reserved for temporary files.
 ;; This is especially important for auto-save files.
-;; Valid file generations are ending with ([+|-|*]0-9...) . 
+;; Valid file generations are ending with ([+|-|*]0-9...) .
 ;; File generations are not supported yet!
 ;; A filename must at least contain one character (A-Z) and cannot be longer
 ;; than 41 characters.
 ;; File generations are not supported yet!
 ;; A filename must at least contain one character (A-Z) and cannot be longer
 ;; than 41 characters.
 ;; aspects of ange-ftp.  New versions of ange-ftp are posted periodically to
 ;; the mailing list.
 
 ;; aspects of ange-ftp.  New versions of ange-ftp are posted periodically to
 ;; the mailing list.
 
-;; [The following information about lists may be obsolete.]
-
 ;; To [un]subscribe to ange-ftp-lovers, or to report mailer problems with the
 ;; list, please mail one of the following addresses:
 ;;
 ;; To [un]subscribe to ange-ftp-lovers, or to report mailer problems with the
 ;; list, please mail one of the following addresses:
 ;;
-;;     ange-ftp-lovers-request@anorman.hpl.hp.com
-;; or
-;;     ange-ftp-lovers-request%anorman.hpl.hp.com@hplb.hpl.hp.com
+;;     ange-ftp-lovers-request@hplb.hpl.hp.com
 ;;
 ;; Please don't forget the -request part.
 ;;
 ;; For mail to be posted directly to ange-ftp-lovers, send to one of the
 ;; following addresses:
 ;;
 ;;
 ;; Please don't forget the -request part.
 ;;
 ;; For mail to be posted directly to ange-ftp-lovers, send to one of the
 ;; following addresses:
 ;;
-;;     ange-ftp-lovers@anorman.hpl.hp.com
-;; or
-;;     ange-ftp-lovers%anorman.hpl.hp.com@hplb.hpl.hp.com
+;;     ange-ftp-lovers@hplb.hpl.hp.com
 ;;
 ;; Alternatively, there is a mailing list that only gets announcements of new
 ;; ange-ftp releases.  This is called ange-ftp-lovers-announce, and can be
 ;; subscribed to by e-mailing to the -request address as above.  Please make
 ;; it clear in the request which mailing list you wish to join.
 ;;
 ;; Alternatively, there is a mailing list that only gets announcements of new
 ;; ange-ftp releases.  This is called ange-ftp-lovers-announce, and can be
 ;; subscribed to by e-mailing to the -request address as above.  Please make
 ;; it clear in the request which mailing list you wish to join.
-
-;; The archives for ange-ftp-lovers can be found via anonymous ftp under:
-;;
-;;     ftp.reed.edu:pub/mailing-lists/ange-ftp/
 \f
 ;; -----------------------------------------------------------
 ;; Technical information on this package:
 \f
 ;; -----------------------------------------------------------
 ;; Technical information on this package:
   :prefix "ange-ftp-")
 
 (defcustom ange-ftp-name-format
   :prefix "ange-ftp-")
 
 (defcustom ange-ftp-name-format
-  '("^/\\(\\([^@/:]*\\)@\\)?\\([^@/:]*[^@/:.]\\):\\(.*\\)" . (3 2 4))
+  '("\\`/\\(\\([^/:]*\\)@\\)?\\([^@/:]*[^@/:.]\\):\\(.*\\)" . (3 2 4))
   "*Format of a fully expanded remote file name.
 
 This is a list of the form \(REGEXP HOST USER NAME\),
   "*Format of a fully expanded remote file name.
 
 This is a list of the form \(REGEXP HOST USER NAME\),
@@ -704,7 +694,7 @@ where REGEXP is a regular expression matching
 the full remote name, and HOST, USER, and NAME are the numbers of
 parenthesized expressions in REGEXP for the components (in that order)."
   :group 'ange-ftp
 the full remote name, and HOST, USER, and NAME are the numbers of
 parenthesized expressions in REGEXP for the components (in that order)."
   :group 'ange-ftp
-  :type '(list regexp
+  :type '(list (regexp  :tag "Name regexp")
               (integer :tag "Host group")
               (integer :tag "User group")
               (integer :tag "Name group")))
               (integer :tag "Host group")
               (integer :tag "User group")
               (integer :tag "Name group")))
@@ -738,6 +728,7 @@ parenthesized expressions in REGEXP for the components (in that order)."
          "^Data connection \\|"
          "^local:\\|^Trying\\|^125 \\|^550-\\|^221 .*oodbye\\|"
           "^500 .*AUTH \\(KERBEROS\\|GSSAPI\\)\\|^KERBEROS\\|"
          "^Data connection \\|"
          "^local:\\|^Trying\\|^125 \\|^550-\\|^221 .*oodbye\\|"
           "^500 .*AUTH \\(KERBEROS\\|GSSAPI\\)\\|^KERBEROS\\|"
+         "^530 Please login with USER and PASS\\|" ; non kerberised vsFTPd
          "^22[789] .*[Pp]assive\\|^200 EPRT\\|^500 .*EPRT")
   "*Regular expression matching ftp messages that can be ignored."
   :group 'ange-ftp
          "^22[789] .*[Pp]assive\\|^200 EPRT\\|^500 .*EPRT")
   "*Regular expression matching ftp messages that can be ignored."
   :group 'ange-ftp
@@ -752,6 +743,21 @@ These mean that the FTP process should (or already has) been killed."
   :group 'ange-ftp
   :type 'regexp)
 
   :group 'ange-ftp
   :type 'regexp)
 
+(defcustom ange-ftp-potential-error-msgs
+  ;; On Mac OS X we sometimes get things like:
+  ;;
+  ;;     ftp> open ftp.nluug.nl
+  ;;     Trying 2001:610:1:80aa:192:87:102:36...
+  ;;     ftp: connect to address 2001:610:1:80aa:192:87:102:36: No route to host
+  ;;     Trying 192.87.102.36...
+  ;;     Connected to ftp.nluug.nl.
+  "^ftp: connect to address .*: No route to host"
+  "*Regular expression matching ftp messages that can indicate serious errors.
+These mean that something went wrong, but they may be followed by more
+messages indicating that the error was somehow corrected."
+  :group 'ange-ftp
+  :type 'regexp)
+
 (defcustom ange-ftp-gateway-fatal-msgs
   "No route to host\\|Connection closed\\|No such host\\|Login incorrect"
   "*Regular expression matching login failure messages from rlogin/telnet."
 (defcustom ange-ftp-gateway-fatal-msgs
   "No route to host\\|Connection closed\\|No such host\\|Login incorrect"
   "*Regular expression matching login failure messages from rlogin/telnet."
@@ -857,10 +863,11 @@ If nil, prompt the user for a password."
                 string))
 
 (defcustom ange-ftp-binary-file-name-regexp
                 string))
 
 (defcustom ange-ftp-binary-file-name-regexp
-  (concat "\\.[zZ]$\\|\\.lzh$\\|\\.arc$\\|\\.zip$\\|\\.zoo$\\|\\.tar$\\|"
-         "\\.dvi$\\|\\.ps$\\|\\.elc$\\|TAGS$\\|\\.gif$\\|"
-         "\\.EXE\\(;[0-9]+\\)?$\\|\\.[zZ]-part-..$\\|\\.gz$\\|"
-         "\\.taz$\\|\\.tgz$")
+  (concat "TAGS\\'\\|\\.\\(?:"
+          (eval-when-compile
+            (regexp-opt '("z" "Z" "lzh" "arc" "zip" "zoo" "tar" "dvi"
+                          "ps" "elc" "gif" "gz" "taz" "tgz")))
+         "\\|EXE\\(;[0-9]+\\)?\\|[zZ]-part-..\\)\\'")
   "*If a file matches this regexp then it is transferred in binary mode."
   :group 'ange-ftp
   :type 'regexp)
   "*If a file matches this regexp then it is transferred in binary mode."
   :group 'ange-ftp
   :type 'regexp)
@@ -916,10 +923,11 @@ This command should stop the terminal from echoing each command, and
 arrange to strip out trailing ^M characters.")
 
 (defcustom ange-ftp-smart-gateway nil
 arrange to strip out trailing ^M characters.")
 
 (defcustom ange-ftp-smart-gateway nil
-  "*Non-nil means the ftp gateway and/or the gateway ftp program is smart.
+  "*Non-nil says the ftp gateway (proxy) or gateway ftp program is smart.
 
 Don't bother telnetting, etc., already connected to desired host transparently,
 
 Don't bother telnetting, etc., already connected to desired host transparently,
-or just issue a user@host command in case \`ange-ftp-gateway-host\' is non-nil."
+or just issue a user@host command in case \`ange-ftp-gateway-host\' is non-nil.
+See also `ange-ftp-smart-gateway-port'."
   :group 'ange-ftp
   :type 'boolean)
 
   :group 'ange-ftp
   :type 'boolean)
 
@@ -997,7 +1005,7 @@ Don't use any other value."
                 (const :tag "Allow" 1)))
 
 (defcustom ange-ftp-try-passive-mode nil
                 (const :tag "Allow" 1)))
 
 (defcustom ange-ftp-try-passive-mode nil
-  "It t, try to use passive mode in ftp, if the client program supports it."
+  "If t, try to use passive mode in ftp, if the client program supports it."
   :group 'ange-ftp
   :type 'boolean
   :version "21.1")
   :group 'ange-ftp
   :type 'boolean
   :version "21.1")
@@ -1012,7 +1020,7 @@ or nil meaning don't change it."
   :type '(repeat (cons regexp (choice (const :tag "On" "on")
                                      (const :tag "Off" "off")
                                      (const :tag "Don't change" nil))))
   :type '(repeat (cons regexp (choice (const :tag "On" "on")
                                      (const :tag "Off" "off")
                                      (const :tag "Don't change" nil))))
-  :version "21.4")
+  :version "22.1")
 \f
 ;;;; ------------------------------------------------------------
 ;;;; Hash table support.
 \f
 ;;;; ------------------------------------------------------------
 ;;;; Hash table support.
@@ -1020,60 +1028,16 @@ or nil meaning don't change it."
 
 (require 'backquote)
 
 
 (require 'backquote)
 
-(defun ange-ftp-make-hashtable (&optional size)
-  "Make an obarray suitable for use as a hashtable.
-SIZE, if supplied, should be a prime number."
-  (make-vector (or size 31) 0))
-
-(defun ange-ftp-map-hashtable (fun tbl)
-  "Call FUNCTION on each key and value in HASHTABLE."
-  (mapatoms
-   (function
-    (lambda (sym)
-      (funcall fun (get sym 'key) (get sym 'val))))
-   tbl))
-
-(defmacro ange-ftp-make-hash-key (key)
-  "Convert KEY into a suitable key for a hashtable."
-  `(if (stringp ,key)
-       ,key
-     (prin1-to-string ,key)))
-
-(defun ange-ftp-get-hash-entry (key tbl)
-  "Return the value associated with KEY in HASHTABLE."
-  (let ((sym (intern-soft (ange-ftp-make-hash-key key) tbl)))
-    (and sym (get sym 'val))))
-
-(defun ange-ftp-put-hash-entry (key val tbl)
-  "Record an association between KEY and VALUE in HASHTABLE."
-  (let ((sym (intern (ange-ftp-make-hash-key key) tbl)))
-    (put sym 'val val)
-    (put sym 'key key)))
-
-(defun ange-ftp-del-hash-entry (key tbl)
-  "Copy all symbols except KEY in HASHTABLE and return modified hashtable."
-  (let* ((len (length tbl))
-        (new-tbl (ange-ftp-make-hashtable len))
-        (i (1- len)))
-    (ange-ftp-map-hashtable
-     (function
-      (lambda (k v)
-       (or (equal k key)
-           (ange-ftp-put-hash-entry k v new-tbl))))
-     tbl)
-    (while (>= i 0)
-      (aset tbl i (aref new-tbl i))
-      (setq i (1- i)))
-    tbl))
-
 (defun ange-ftp-hash-entry-exists-p (key tbl)
   "Return whether there is an association for KEY in TABLE."
 (defun ange-ftp-hash-entry-exists-p (key tbl)
   "Return whether there is an association for KEY in TABLE."
-  (intern-soft (ange-ftp-make-hash-key key) tbl))
+  (and tbl (not (eq (gethash key tbl 'unknown) 'unknown))))
 
 (defun ange-ftp-hash-table-keys (tbl)
   "Return a sorted list of all the active keys in TABLE, as strings."
 
 (defun ange-ftp-hash-table-keys (tbl)
   "Return a sorted list of all the active keys in TABLE, as strings."
-  (sort (all-completions "" tbl)
-       (function string-lessp)))
+  ;; (let ((keys nil))
+  ;;   (maphash (lambda (k v) (push k keys)) tbl)
+  ;;   (sort keys 'string-lessp))
+  (sort (all-completions "" tbl) 'string-lessp))
 \f
 ;;;; ------------------------------------------------------------
 ;;;; Internal variables.
 \f
 ;;;; ------------------------------------------------------------
 ;;;; Internal variables.
@@ -1085,20 +1049,20 @@ SIZE, if supplied, should be a prime number."
 (defvar ange-ftp-netrc-modtime nil
   "Last modified time of the netrc file from file-attributes.")
 
 (defvar ange-ftp-netrc-modtime nil
   "Last modified time of the netrc file from file-attributes.")
 
-(defvar ange-ftp-user-hashtable (ange-ftp-make-hashtable)
+(defvar ange-ftp-user-hashtable (make-hash-table :test 'equal)
   "Hash table holding associations between HOST, USER pairs.")
 
   "Hash table holding associations between HOST, USER pairs.")
 
-(defvar ange-ftp-passwd-hashtable (ange-ftp-make-hashtable)
+(defvar ange-ftp-passwd-hashtable (make-hash-table :test 'equal)
   "Mapping between a HOST, USER pair and a PASSWORD for them.
 All HOST values should be in lower case.")
 
   "Mapping between a HOST, USER pair and a PASSWORD for them.
 All HOST values should be in lower case.")
 
-(defvar ange-ftp-account-hashtable (ange-ftp-make-hashtable)
+(defvar ange-ftp-account-hashtable (make-hash-table :test 'equal)
   "Mapping between a HOST, USER pair and a ACCOUNT password for them.")
 
   "Mapping between a HOST, USER pair and a ACCOUNT password for them.")
 
-(defvar ange-ftp-files-hashtable (ange-ftp-make-hashtable 97)
+(defvar ange-ftp-files-hashtable (make-hash-table :test 'equal :size 97)
   "Hash table for storing directories and their respective files.")
 
   "Hash table for storing directories and their respective files.")
 
-(defvar ange-ftp-inodes-hashtable (ange-ftp-make-hashtable 97)
+(defvar ange-ftp-inodes-hashtable (make-hash-table :test 'equal :size 97)
   "Hash table for storing file names and their \"inode numbers\".")
 
 (defvar ange-ftp-next-inode-number 1
   "Hash table for storing file names and their \"inode numbers\".")
 
 (defvar ange-ftp-next-inode-number 1
@@ -1113,7 +1077,7 @@ All HOST values should be in lower case.")
 (defvar ange-ftp-ls-cache-res nil
   "Last result returned from ange-ftp-ls.")
 
 (defvar ange-ftp-ls-cache-res nil
   "Last result returned from ange-ftp-ls.")
 
-(defconst ange-ftp-expand-dir-hashtable (ange-ftp-make-hashtable))
+(defconst ange-ftp-expand-dir-hashtable (make-hash-table :test 'equal))
 
 (defconst ange-ftp-expand-dir-regexp "^5.0 \\([^: ]+\\):")
 
 
 (defconst ange-ftp-expand-dir-regexp "^5.0 \\([^: ]+\\):")
 
@@ -1123,6 +1087,7 @@ All HOST values should be in lower case.")
 (defvar ange-ftp-xfer-size nil)
 (defvar ange-ftp-process-string nil)
 (defvar ange-ftp-process-result-line nil)
 (defvar ange-ftp-xfer-size nil)
 (defvar ange-ftp-process-string nil)
 (defvar ange-ftp-process-result-line nil)
+(defvar ange-ftp-pending-error-line nil)
 (defvar ange-ftp-process-busy nil)
 (defvar ange-ftp-process-result nil)
 (defvar ange-ftp-process-multi-skip nil)
 (defvar ange-ftp-process-busy nil)
 (defvar ange-ftp-process-result nil)
 (defvar ange-ftp-process-multi-skip nil)
@@ -1151,7 +1116,7 @@ All HOST values should be in lower case.")
 (defun ange-ftp-message (fmt &rest args)
   "Display message in echo area, but indicate if truncated.
 Args are as in `message': a format string, plus arguments to be formatted."
 (defun ange-ftp-message (fmt &rest args)
   "Display message in echo area, but indicate if truncated.
 Args are as in `message': a format string, plus arguments to be formatted."
-  (let ((msg (apply (function format) fmt args))
+  (let ((msg (apply 'format fmt args))
        (max (window-width (minibuffer-window))))
     (if noninteractive
        msg
        (max (window-width (minibuffer-window))))
     (if noninteractive
        msg
@@ -1166,7 +1131,7 @@ If the optional parameter NEW is given and the non-directory parts match,
 only return the directory part of FILE."
   (save-match-data
     (if (and default-directory
 only return the directory part of FILE."
   (save-match-data
     (if (and default-directory
-            (string-match (concat "^"
+            (string-match (concat "\\`"
                                   (regexp-quote default-directory)
                                   ".") file))
        (setq file (substring file (1- (match-end 0)))))
                                   (regexp-quote default-directory)
                                   ".") file))
        (setq file (substring file (1- (match-end 0)))))
@@ -1183,12 +1148,12 @@ only return the directory part of FILE."
 (defun ange-ftp-set-user (host user)
   "For a given HOST, set or change the default USER."
   (interactive "sHost: \nsUser: ")
 (defun ange-ftp-set-user (host user)
   "For a given HOST, set or change the default USER."
   (interactive "sHost: \nsUser: ")
-  (ange-ftp-put-hash-entry host user ange-ftp-user-hashtable))
+  (puthash host user ange-ftp-user-hashtable))
 
 (defun ange-ftp-get-user (host)
   "Given a HOST, return the default USER."
   (ange-ftp-parse-netrc)
 
 (defun ange-ftp-get-user (host)
   "Given a HOST, return the default USER."
   (ange-ftp-parse-netrc)
-  (let ((user (ange-ftp-get-hash-entry host ange-ftp-user-hashtable)))
+  (let ((user (gethash host ange-ftp-user-hashtable)))
     (or user
        (prog1
            (setq user
     (or user
        (prog1
            (setq user
@@ -1214,36 +1179,33 @@ only return the directory part of FILE."
   `(concat (downcase ,host) "/" ,user))
 
 (defmacro ange-ftp-lookup-passwd (host user)
   `(concat (downcase ,host) "/" ,user))
 
 (defmacro ange-ftp-lookup-passwd (host user)
-  `(ange-ftp-get-hash-entry (ange-ftp-generate-passwd-key ,host ,user)
-                            ange-ftp-passwd-hashtable))
+  `(gethash (ange-ftp-generate-passwd-key ,host ,user)
+           ange-ftp-passwd-hashtable))
 
 (defun ange-ftp-set-passwd (host user passwd)
   "For a given HOST and USER, set or change the associated PASSWORD."
   (interactive (list (read-string "Host: ")
                     (read-string "User: ")
                     (read-passwd "Password: ")))
 
 (defun ange-ftp-set-passwd (host user passwd)
   "For a given HOST and USER, set or change the associated PASSWORD."
   (interactive (list (read-string "Host: ")
                     (read-string "User: ")
                     (read-passwd "Password: ")))
-  (ange-ftp-put-hash-entry (ange-ftp-generate-passwd-key host user)
-                          passwd
-                          ange-ftp-passwd-hashtable))
+  (puthash (ange-ftp-generate-passwd-key host user)
+          passwd ange-ftp-passwd-hashtable))
 
 (defun ange-ftp-get-host-with-passwd (user)
   "Given a USER, return a host we know the password for."
   (ange-ftp-parse-netrc)
   (catch 'found-one
 
 (defun ange-ftp-get-host-with-passwd (user)
   "Given a USER, return a host we know the password for."
   (ange-ftp-parse-netrc)
   (catch 'found-one
-    (ange-ftp-map-hashtable
-     (function (lambda (host val)
-                (if (ange-ftp-lookup-passwd host user)
-                    (throw 'found-one host))))
+    (maphash
+     (lambda (host val)
+       (if (ange-ftp-lookup-passwd host user) (throw 'found-one host)))
      ange-ftp-user-hashtable)
     (save-match-data
      ange-ftp-user-hashtable)
     (save-match-data
-      (ange-ftp-map-hashtable
-       (function
-       (lambda (key value)
-         (if (string-match "^[^/]*\\(/\\).*$" key)
-             (let ((host (substring key 0 (match-beginning 1))))
-               (if (and (string-equal user (substring key (match-end 1)))
-                        value)
-                   (throw 'found-one host))))))
+      (maphash
+       (lambda (key value)
+        (if (string-match "\\`[^/]*\\(/\\).*\\'" key)
+            (let ((host (substring key 0 (match-beginning 1))))
+              (if (and (string-equal user (substring key (match-end 1)))
+                       value)
+                  (throw 'found-one host)))))
        ange-ftp-passwd-hashtable))
     nil))
 
        ange-ftp-passwd-hashtable))
     nil))
 
@@ -1310,15 +1272,14 @@ only return the directory part of FILE."
   (interactive (list (read-string "Host: ")
                     (read-string "User: ")
                     (read-passwd "Account password: ")))
   (interactive (list (read-string "Host: ")
                     (read-string "User: ")
                     (read-passwd "Account password: ")))
-  (ange-ftp-put-hash-entry (ange-ftp-generate-passwd-key host user)
-                          account
-                          ange-ftp-account-hashtable))
+  (puthash (ange-ftp-generate-passwd-key host user)
+          account ange-ftp-account-hashtable))
 
 (defun ange-ftp-get-account (host user)
   "Given a HOST and USER, return the FTP account."
   (ange-ftp-parse-netrc)
 
 (defun ange-ftp-get-account (host user)
   "Given a HOST and USER, return the FTP account."
   (ange-ftp-parse-netrc)
-  (or (ange-ftp-get-hash-entry (ange-ftp-generate-passwd-key host user)
-                              ange-ftp-account-hashtable)
+  (or (gethash (ange-ftp-generate-passwd-key host user)
+              ange-ftp-account-hashtable)
       (and (stringp ange-ftp-default-user)
           (string-equal user ange-ftp-default-user)
           ange-ftp-default-account)
       (and (stringp ange-ftp-default-user)
           (string-equal user ange-ftp-default-user)
           ange-ftp-default-account)
@@ -1337,6 +1298,8 @@ only return the directory part of FILE."
       (setq file
            (if (file-name-absolute-p temp)
                temp
       (setq file
            (if (file-name-absolute-p temp)
                temp
+             ;; Wouldn't `expand-file-name' be better than `concat' ?
+             ;; It would fail when `a/b/..' != `a', tho.  --Stef
              (concat (file-name-directory file) temp)))))
   file)
 
              (concat (file-name-directory file) temp)))))
   file)
 
@@ -1424,17 +1387,17 @@ only return the directory part of FILE."
          (if (or ange-ftp-disable-netrc-security-check
                  (and (eq (nth 2 attr) (user-uid)) ; Same uids.
                       (string-match ".r..------" (nth 8 attr))))
          (if (or ange-ftp-disable-netrc-security-check
                  (and (eq (nth 2 attr) (user-uid)) ; Same uids.
                       (string-match ".r..------" (nth 8 attr))))
-             (save-excursion
+             (with-current-buffer
                ;; we are cheating a bit here.  I'm trying to do the equivalent
                ;; of find-file on the .netrc file, but then nuke it afterwards.
                ;; with the bit of logic below we should be able to have
                ;; encrypted .netrc files.
                ;; we are cheating a bit here.  I'm trying to do the equivalent
                ;; of find-file on the .netrc file, but then nuke it afterwards.
                ;; with the bit of logic below we should be able to have
                ;; encrypted .netrc files.
-               (set-buffer (generate-new-buffer "*ftp-.netrc*"))
+                  (generate-new-buffer "*ftp-.netrc*")
                (ange-ftp-real-insert-file-contents file)
                (setq buffer-file-name file)
                (setq default-directory (file-name-directory file))
                (normal-mode t)
                (ange-ftp-real-insert-file-contents file)
                (setq buffer-file-name file)
                (setq default-directory (file-name-directory file))
                (normal-mode t)
-               (mapcar 'funcall find-file-hooks)
+               (run-hooks 'find-file-hook)
                (setq buffer-file-name nil)
                (goto-char (point-min))
                (skip-chars-forward " \t\r\n")
                (setq buffer-file-name nil)
                (goto-char (point-min))
                (skip-chars-forward " \t\r\n")
@@ -1453,17 +1416,15 @@ only return the directory part of FILE."
   (ange-ftp-parse-netrc)
   (save-match-data
     (let (res)
   (ange-ftp-parse-netrc)
   (save-match-data
     (let (res)
-      (ange-ftp-map-hashtable
-       (function
-       (lambda (key value)
-         (if (string-match "^[^/]*\\(/\\).*$" key)
-             (let ((host (substring key 0 (match-beginning 1)))
-                   (user (substring key (match-end 1))))
-               (push (concat user "@" host ":") res)))))
+      (maphash
+       (lambda (key value)
+        (if (string-match "\\`[^/]*\\(/\\).*\\'" key)
+            (let ((host (substring key 0 (match-beginning 1)))
+                  (user (substring key (match-end 1))))
+              (push (concat user "@" host ":") res))))
        ange-ftp-passwd-hashtable)
        ange-ftp-passwd-hashtable)
-      (ange-ftp-map-hashtable
-       (function (lambda (host user)
-                  (push (concat host ":") res)))
+      (maphash
+       (lambda (host user) (push (concat host ":") res))
        ange-ftp-user-hashtable)
       (or res (list nil)))))
 \f
        ange-ftp-user-hashtable)
       (or res (list nil)))))
 \f
@@ -1474,8 +1435,7 @@ only return the directory part of FILE."
 (defmacro ange-ftp-ftp-name-component (n ns name)
   "Extract the Nth ftp file name component from NS."
   `(let ((elt (nth ,n ,ns)))
 (defmacro ange-ftp-ftp-name-component (n ns name)
   "Extract the Nth ftp file name component from NS."
   `(let ((elt (nth ,n ,ns)))
-    (if (match-beginning elt)
-        (substring ,name (match-beginning elt) (match-end elt)))))
+     (match-string elt ,name)))
 
 (defvar ange-ftp-ftp-name-arg "")
 (defvar ange-ftp-ftp-name-res nil)
 
 (defvar ange-ftp-ftp-name-arg "")
 (defvar ange-ftp-ftp-name-res nil)
@@ -1528,14 +1488,15 @@ only return the directory part of FILE."
 ;; Display the last chunk of output from the ftp process for the given HOST
 ;; USER pair, and signal an error including MSG in the text.
 (defun ange-ftp-error (host user msg)
 ;; Display the last chunk of output from the ftp process for the given HOST
 ;; USER pair, and signal an error including MSG in the text.
 (defun ange-ftp-error (host user msg)
-  (let ((cur (selected-window))
-       (pop-up-windows t))
-    (pop-to-buffer
-     (get-buffer-create
-      (ange-ftp-ftp-process-buffer host user)))
-    (goto-char (point-max))
-    (select-window cur))
-  (signal 'ftp-error (list (format "FTP Error: %s" msg))))
+  (save-excursion  ;; Prevent pop-to-buffer from changing current buffer.
+    (let ((cur (selected-window))
+         (pop-up-windows t))
+      (pop-to-buffer
+       (get-buffer-create
+       (ange-ftp-ftp-process-buffer host user)))
+      (goto-char (point-max))
+      (select-window cur))
+    (signal 'ftp-error (list (format "FTP Error: %s" msg)))))
 
 (defun ange-ftp-set-buffer-mode ()
   "Set correct modes for the current buffer if visiting a remote file."
 
 (defun ange-ftp-set-buffer-mode ()
   "Set correct modes for the current buffer if visiting a remote file."
@@ -1552,7 +1513,7 @@ then kill the related ftp process."
       (setq buffer (current-buffer))
     (setq buffer (get-buffer buffer)))
   (let ((file (or (buffer-file-name buffer)
       (setq buffer (current-buffer))
     (setq buffer (get-buffer buffer)))
   (let ((file (or (buffer-file-name buffer)
-                 (save-excursion (set-buffer buffer) default-directory))))
+                 (with-current-buffer buffer default-directory))))
     (if file
        (let ((parsed (ange-ftp-ftp-name (expand-file-name file))))
          (if parsed
     (if file
        (let ((parsed (ange-ftp-ftp-name (expand-file-name file))))
          (if parsed
@@ -1562,19 +1523,18 @@ then kill the related ftp process."
 
 (defun ange-ftp-quote-string (string)
   "Quote any characters in STRING that may confuse the ftp process."
 
 (defun ange-ftp-quote-string (string)
   "Quote any characters in STRING that may confuse the ftp process."
-  (apply (function concat)
-        (mapcar (function
-                 ;; This is said to be wrong; ftp is said to
-                 ;; need quoting only for ", and that by doubling it.
-                 ;; But experiment says this kind of quoting is correct
-                 ;; when talking to ftp on GNU/Linux systems.
-                  (lambda (char)
-                    (if (or (<= char ? )
-                            (> char ?\~)
-                            (= char ?\")
-                            (= char ?\\))
-                        (vector ?\\ char)
-                      (vector char))))
+  (apply 'concat
+        (mapcar (lambda (char)
+                  ;; This is said to be wrong; ftp is said to
+                  ;; need quoting only for ", and that by doubling it.
+                  ;; But experiment says this kind of quoting is correct
+                  ;; when talking to ftp on GNU/Linux systems.
+                  (if (or (<= char ? )
+                          (> char ?\~)
+                          (= char ?\")
+                          (= char ?\\))
+                      (vector ?\\ char)
+                    (vector char)))
                 string)))
 
 (defun ange-ftp-barf-if-not-directory (directory)
                 string)))
 
 (defun ange-ftp-barf-if-not-directory (directory)
@@ -1596,15 +1556,14 @@ Try to categorize it into one of four categories:
 good, skip, fatal, or unknown."
   (cond ((string-match ange-ftp-xfer-size-msgs line)
         (setq ange-ftp-xfer-size
 good, skip, fatal, or unknown."
   (cond ((string-match ange-ftp-xfer-size-msgs line)
         (setq ange-ftp-xfer-size
-              (/ (string-to-number (substring line
-                                              (match-beginning 1)
-                                              (match-end 1)))
+              (/ (string-to-number (match-string 1 line))
                  1024)))
        ((string-match ange-ftp-skip-msgs line)
         t)
        ((string-match ange-ftp-good-msgs line)
         (setq ange-ftp-process-busy nil
               ange-ftp-process-result t
                  1024)))
        ((string-match ange-ftp-skip-msgs line)
         t)
        ((string-match ange-ftp-good-msgs line)
         (setq ange-ftp-process-busy nil
               ange-ftp-process-result t
+               ange-ftp-pending-error-line nil
               ange-ftp-process-result-line line))
        ;; Check this before checking for errors.
        ;; Otherwise the last line of these three seems to be an error:
               ange-ftp-process-result-line line))
        ;; Check this before checking for errors.
        ;; Otherwise the last line of these three seems to be an error:
@@ -1613,11 +1572,17 @@ good, skip, fatal, or unknown."
        ;; 230-"ftp.stsci.edu: unknown host", the new IP address will be...
        ((string-match ange-ftp-multi-msgs line)
         (setq ange-ftp-process-multi-skip t))
        ;; 230-"ftp.stsci.edu: unknown host", the new IP address will be...
        ((string-match ange-ftp-multi-msgs line)
         (setq ange-ftp-process-multi-skip t))
+       ((string-match ange-ftp-potential-error-msgs line)
+         ;; This looks like an error, but we have to keep reading the output
+         ;; to see if it was fixed or not.  E.g. it may indicate that IPv6
+         ;; failed, but maybe a subsequent IPv4 fallback succeeded.
+         (set (make-local-variable 'ange-ftp-pending-error-line) line)
+         t)
        ((string-match ange-ftp-fatal-msgs line)
         (delete-process proc)
         (setq ange-ftp-process-busy nil
               ange-ftp-process-result-line line))
        ((string-match ange-ftp-fatal-msgs line)
         (delete-process proc)
         (setq ange-ftp-process-busy nil
               ange-ftp-process-result-line line))
-       (ange-ftp-process-multi-skip
+        (ange-ftp-process-multi-skip
         t)
        (t
         (setq ange-ftp-process-busy nil
         t)
        (t
         (setq ange-ftp-process-busy nil
@@ -1629,8 +1594,7 @@ good, skip, fatal, or unknown."
     (if proc
        (let ((buf (process-buffer proc)))
          (if buf
     (if proc
        (let ((buf (process-buffer proc)))
          (if buf
-             (save-excursion
-               (set-buffer buf)
+             (with-current-buffer buf
                (setq ange-ftp-xfer-size
                      ;; For very large files, BYTES can be a float.
                      (if (integerp bytes)
                (setq ange-ftp-xfer-size
                      ;; For very large files, BYTES can be a float.
                      (if (integerp bytes)
@@ -1653,14 +1617,13 @@ good, skip, fatal, or unknown."
        (let ((kbytes (ash (* ange-ftp-hash-mark-unit
                             ange-ftp-hash-mark-count)
                          -6)))
        (let ((kbytes (ash (* ange-ftp-hash-mark-unit
                             ange-ftp-hash-mark-count)
                          -6)))
-       (if (zerop ange-ftp-xfer-size)
-          (ange-ftp-message "%s...%dk" ange-ftp-process-msg kbytes)
-        (let ((percent (/ (* 100 kbytes) ange-ftp-xfer-size)))
-          ;; cut out the redisplay of identical %-age messages.
-          (if (not (eq percent ange-ftp-last-percent))
-              (progn
-                (setq ange-ftp-last-percent percent)
-                (ange-ftp-message "%s...%d%%" ange-ftp-process-msg percent)))))))
+        (if (zerop ange-ftp-xfer-size)
+            (ange-ftp-message "%s...%dk" ange-ftp-process-msg kbytes)
+          (let ((percent (/ (* 100 kbytes) ange-ftp-xfer-size)))
+            ;; cut out the redisplay of identical %-age messages.
+            (unless (eq percent ange-ftp-last-percent)
+              (setq ange-ftp-last-percent percent)
+              (ange-ftp-message "%s...%d%%" ange-ftp-process-msg percent))))))
   str)
 
 ;; Call the function specified by CONT.  CONT can be either a function
   str)
 
 ;; Call the function specified by CONT.  CONT can be either a function
@@ -1679,79 +1642,82 @@ good, skip, fatal, or unknown."
 ;; on to ange-ftp-process-handle-line to deal with.
 
 (defun ange-ftp-process-filter (proc str)
 ;; on to ange-ftp-process-handle-line to deal with.
 
 (defun ange-ftp-process-filter (proc str)
-  (let ((buffer (process-buffer proc))
-       (old-buffer (current-buffer)))
-
-    ;; Eliminate nulls.
-    (while (string-match "\000+" str)
-      (setq str (replace-match "" nil nil str)))
-
-    ;; see if the buffer is still around... it could have been deleted.
-    (if (buffer-name buffer)
-       (unwind-protect
-           (progn
-             (set-buffer (process-buffer proc))
-
-             ;; handle hash mark printing
-             (and ange-ftp-process-busy
-                  (string-match "^#+$" str)
-                  (setq str (ange-ftp-process-handle-hash str)))
-             (comint-output-filter proc str)
-             ;; Replace STR by the result of the comint processing.
-             (setq str (buffer-substring comint-last-output-start
-                                         (process-mark proc)))
-             (if ange-ftp-process-busy
-                 (progn
-                   (setq ange-ftp-process-string (concat ange-ftp-process-string
-                                                         str))
-
-                   ;; if we gave an empty password to the USER command earlier
-                   ;; then we should send a null password now.
-                   (if (string-match "Password: *$" ange-ftp-process-string)
-                       (send-string proc "\n"))))
-             (while (and ange-ftp-process-busy
-                         (string-match "\n" ange-ftp-process-string))
-               (let ((line (substring ange-ftp-process-string
-                                      0
-                                      (match-beginning 0))))
-                 (setq ange-ftp-process-string (substring ange-ftp-process-string
-                                                          (match-end 0)))
-                 (while (string-match "^ftp> *" line)
-                   (setq line (substring line (match-end 0))))
-                 (ange-ftp-process-handle-line line proc)))
-
-             ;; has the ftp client finished?  if so then do some clean-up
-             ;; actions.
-             (if (not ange-ftp-process-busy)
-                 (progn
-                   ;; reset the xfer size
-                   (setq ange-ftp-xfer-size 0)
-
-                   ;; issue the "done" message since we've finished.
-                   (if (and ange-ftp-process-msg
-                            ange-ftp-process-verbose
-                            ange-ftp-process-result)
-                       (progn
-                         (ange-ftp-message "%s...done" ange-ftp-process-msg)
-                         (ange-ftp-repaint-minibuffer)
-                         (setq ange-ftp-process-msg nil)))
-
-                   ;; is there a continuation we should be calling?  if so,
-                   ;; we'd better call it, making sure we only call it once.
-                   (if ange-ftp-process-continue
-                       (let ((cont ange-ftp-process-continue))
-                         (setq ange-ftp-process-continue nil)
-                         (ange-ftp-call-cont cont
-                                             ange-ftp-process-result
-                                             ange-ftp-process-result-line))))))
-         (set-buffer old-buffer)))))
+  ;; Eliminate nulls.
+  (while (string-match "\000+" str)
+    (setq str (replace-match "" nil nil str)))
+
+  ;; see if the buffer is still around... it could have been deleted.
+  (when (buffer-live-p (process-buffer proc))
+    (with-current-buffer (process-buffer proc)
+
+      ;; handle hash mark printing
+      (and ange-ftp-process-busy
+           (string-match "^#+$" str)
+           (setq str (ange-ftp-process-handle-hash str)))
+      (comint-output-filter proc str)
+      ;; Replace STR by the result of the comint processing.
+      (setq str (buffer-substring comint-last-output-start
+                                  (process-mark proc)))
+      (if ange-ftp-process-busy
+          (progn
+            (setq ange-ftp-process-string (concat ange-ftp-process-string
+                                                  str))
+
+            ;; if we gave an empty password to the USER command earlier
+            ;; then we should send a null password now.
+            (if (string-match "Password: *$" ange-ftp-process-string)
+                (process-send-string proc "\n"))))
+      (while (and ange-ftp-process-busy
+                  (string-match "\n" ange-ftp-process-string))
+        (let ((line (substring ange-ftp-process-string
+                               0
+                               (match-beginning 0)))
+              (seen-prompt nil))
+          (setq ange-ftp-process-string (substring ange-ftp-process-string
+                                                   (match-end 0)))
+          (while (string-match "\\`ftp> *" line)
+            (setq seen-prompt t)
+            (setq line (substring line (match-end 0))))
+          (if (not (and seen-prompt ange-ftp-pending-error-line))
+              (ange-ftp-process-handle-line line proc)
+            ;; If we've seen a potential error message and it
+            ;; hasn't been cancelled by a good message before
+            ;; seeing a propt, then the error was real.
+            (delete-process proc)
+            (setq ange-ftp-process-busy nil
+                  ange-ftp-process-result-line ange-ftp-pending-error-line))))
+
+      ;; has the ftp client finished?  if so then do some clean-up
+      ;; actions.
+      (if (not ange-ftp-process-busy)
+          (progn
+            ;; reset the xfer size
+            (setq ange-ftp-xfer-size 0)
+
+            ;; issue the "done" message since we've finished.
+            (if (and ange-ftp-process-msg
+                     ange-ftp-process-verbose
+                     ange-ftp-process-result)
+                (progn
+                  (ange-ftp-message "%s...done" ange-ftp-process-msg)
+                  (ange-ftp-repaint-minibuffer)
+                  (setq ange-ftp-process-msg nil)))
+
+            ;; is there a continuation we should be calling?  if so,
+            ;; we'd better call it, making sure we only call it once.
+            (if ange-ftp-process-continue
+                (let ((cont ange-ftp-process-continue))
+                  (setq ange-ftp-process-continue nil)
+                  (ange-ftp-call-cont cont
+                                      ange-ftp-process-result
+                                      ange-ftp-process-result-line))))))))
 
 (defun ange-ftp-process-sentinel (proc str)
   "When ftp process changes state, nuke all file-entries in cache."
   (let ((name (process-name proc)))
     (if (string-match "\\*ftp \\([^@]+\\)@\\([^*]+\\)\\*" name)
 
 (defun ange-ftp-process-sentinel (proc str)
   "When ftp process changes state, nuke all file-entries in cache."
   (let ((name (process-name proc)))
     (if (string-match "\\*ftp \\([^@]+\\)@\\([^*]+\\)\\*" name)
-       (let ((user (substring name (match-beginning 1) (match-end 1)))
-             (host (substring name (match-beginning 2) (match-end 2))))
+       (let ((user (match-string 1 name))
+             (host (match-string 2 name)))
          (ange-ftp-wipe-file-entries host user))))
   (setq ange-ftp-ls-cache-file nil))
 \f
          (ange-ftp-wipe-file-entries host user))))
   (setq ange-ftp-ls-cache-file nil))
 \f
@@ -1781,8 +1747,8 @@ good, skip, fatal, or unknown."
 (defun ange-ftp-make-tmp-name (host)
   "This routine will return the name of a new file."
   (make-temp-file (if (ange-ftp-use-gateway-p host)
 (defun ange-ftp-make-tmp-name (host)
   "This routine will return the name of a new file."
   (make-temp-file (if (ange-ftp-use-gateway-p host)
-                      ange-ftp-gateway-tmp-name-template
-                    ange-ftp-tmp-name-template)))
+                     ange-ftp-gateway-tmp-name-template
+                   ange-ftp-tmp-name-template)))
 
 (defalias 'ange-ftp-del-tmp-name 'delete-file)
 \f
 
 (defalias 'ange-ftp-del-tmp-name 'delete-file)
 \f
@@ -1798,23 +1764,22 @@ good, skip, fatal, or unknown."
 
 (defun ange-ftp-gwp-filter (proc str)
   (comint-output-filter proc str)
 
 (defun ange-ftp-gwp-filter (proc str)
   (comint-output-filter proc str)
-  (save-excursion
-    (set-buffer (process-buffer proc))
+  (with-current-buffer (process-buffer proc)
     ;; Replace STR by the result of the comint processing.
     (setq str (buffer-substring comint-last-output-start (process-mark proc))))
   (cond ((string-match "login: *$" str)
     ;; Replace STR by the result of the comint processing.
     (setq str (buffer-substring comint-last-output-start (process-mark proc))))
   (cond ((string-match "login: *$" str)
-        (send-string proc
-                     (concat
-                      (let ((ange-ftp-default-user t))
-                        (ange-ftp-get-user ange-ftp-gateway-host))
-                      "\n")))
+        (process-send-string proc
+                              (concat
+                               (let ((ange-ftp-default-user t))
+                                 (ange-ftp-get-user ange-ftp-gateway-host))
+                               "\n")))
        ((string-match "Password: *$" str)
        ((string-match "Password: *$" str)
-        (send-string proc
-                     (concat
-                      (ange-ftp-get-passwd ange-ftp-gateway-host
-                                           (ange-ftp-get-user
-                                            ange-ftp-gateway-host))
-                      "\n")))
+        (process-send-string proc
+                              (concat
+                               (ange-ftp-get-passwd ange-ftp-gateway-host
+                                                    (ange-ftp-get-user
+                                                     ange-ftp-gateway-host))
+                               "\n")))
        ((string-match ange-ftp-gateway-fatal-msgs str)
         (delete-process proc)
         (setq ange-ftp-gwp-running nil))
        ((string-match ange-ftp-gateway-fatal-msgs str)
         (delete-process proc)
         (setq ange-ftp-gwp-running nil))
@@ -1824,20 +1789,18 @@ good, skip, fatal, or unknown."
 
 (defun ange-ftp-gwp-start (host user name args)
   "Login to the gateway machine and fire up an ftp process."
 
 (defun ange-ftp-gwp-start (host user name args)
   "Login to the gateway machine and fire up an ftp process."
-  (let* ((gw-user (ange-ftp-get-user ange-ftp-gateway-host))
-        ;; It would be nice to make process-connection-type nil,
+  (let* (;; It would be nice to make process-connection-type nil,
         ;; but that doesn't work: ftp never responds.
         ;; Can anyone find a fix for that?
         (proc (let ((process-connection-type t))
                 (start-process name name
                                ange-ftp-gateway-program
                                ange-ftp-gateway-host)))
         ;; but that doesn't work: ftp never responds.
         ;; Can anyone find a fix for that?
         (proc (let ((process-connection-type t))
                 (start-process name name
                                ange-ftp-gateway-program
                                ange-ftp-gateway-host)))
-        (ftp (mapconcat (function identity) args " ")))
-    (process-kill-without-query proc)
-    (set-process-sentinel proc (function ange-ftp-gwp-sentinel))
-    (set-process-filter proc (function ange-ftp-gwp-filter))
-    (save-excursion
-      (set-buffer (process-buffer proc))
+        (ftp (mapconcat 'identity args " ")))
+    (set-process-query-on-exit-flag proc nil)
+    (set-process-sentinel proc 'ange-ftp-gwp-sentinel)
+    (set-process-filter proc 'ange-ftp-gwp-filter)
+    (with-current-buffer (process-buffer proc)
       (goto-char (point-max))
       (set-marker (process-mark proc) (point)))
     (setq ange-ftp-gwp-running t
       (goto-char (point-max))
       (set-marker (process-mark proc) (point)))
     (setq ange-ftp-gwp-running t
@@ -1893,11 +1856,11 @@ been queued with no result.  CONT will still be called, however."
        (move-marker comint-last-input-start (point))
        ;; don't insert the password into the buffer on the USER command.
        (save-match-data
        (move-marker comint-last-input-start (point))
        ;; don't insert the password into the buffer on the USER command.
        (save-match-data
-         (if (string-match "^user \"[^\"]*\"" cmd)
+         (if (string-match "\\`user \"[^\"]*\"" cmd)
              (insert (substring cmd 0 (match-end 0)) " Turtle Power!\n")
            (insert cmd)))
        (move-marker comint-last-input-end (point))
              (insert (substring cmd 0 (match-end 0)) " Turtle Power!\n")
            (insert cmd)))
        (move-marker comint-last-input-end (point))
-       (send-string proc cmd)
+       (process-send-string proc cmd)
        (set-marker (process-mark proc) (point))
        (if nowait
            nil
        (set-marker (process-mark proc) (point))
        (if nowait
            nil
@@ -1942,15 +1905,13 @@ been queued with no result.  CONT will still be called, however."
                    (start-process " *nslookup*" " *nslookup*"
                                   ange-ftp-nslookup-program host)))
            (res host))
                    (start-process " *nslookup*" " *nslookup*"
                                   ange-ftp-nslookup-program host)))
            (res host))
-       (process-kill-without-query proc)
-       (save-excursion
-         (set-buffer (process-buffer proc))
+       (set-process-query-on-exit-flag proc nil)
+       (with-current-buffer (process-buffer proc)
          (while (memq (process-status proc) '(run open))
            (accept-process-output proc))
          (goto-char (point-min))
          (if (re-search-forward "Name:.*\nAddress: *\\(.*\\)$" nil t)
          (while (memq (process-status proc) '(run open))
            (accept-process-output proc))
          (goto-char (point-min))
          (if (re-search-forward "Name:.*\nAddress: *\\(.*\\)$" nil t)
-             (setq res (buffer-substring (match-beginning 1)
-                                         (match-end 1))))
+             (setq res (match-string 1)))
          (kill-buffer (current-buffer)))
        res)
     host))
          (kill-buffer (current-buffer)))
        res)
     host))
@@ -1981,10 +1942,10 @@ on the gateway machine to do the ftp instead."
     ;; but that doesn't work: ftp never responds.
     ;; Can anyone find a fix for that?
     (let ((process-connection-type t)
     ;; but that doesn't work: ftp never responds.
     ;; Can anyone find a fix for that?
     (let ((process-connection-type t)
-         (process-environment process-environment)
+         ;; Copy this so we don't alter it permanently.
+         (process-environment (copy-tree process-environment))
          (buffer (get-buffer-create name)))
          (buffer (get-buffer-create name)))
-      (save-excursion
-       (set-buffer buffer)
+      (with-current-buffer buffer
        (internal-ange-ftp-mode))
       ;; This tells GNU ftp not to output any fancy escape sequences.
       (setenv "TERM" "dumb")
        (internal-ange-ftp-mode))
       ;; This tells GNU ftp not to output any fancy escape sequences.
       (setenv "TERM" "dumb")
@@ -1996,13 +1957,12 @@ on the gateway machine to do the ftp instead."
                                            ange-ftp-gateway-host)
                                      args))))
        (setq proc (apply 'start-process name name args))))
                                            ange-ftp-gateway-host)
                                      args))))
        (setq proc (apply 'start-process name name args))))
-    (save-excursion
-      (set-buffer (process-buffer proc))
+    (with-current-buffer (process-buffer proc)
       (goto-char (point-max))
       (set-marker (process-mark proc) (point)))
       (goto-char (point-max))
       (set-marker (process-mark proc) (point)))
-    (process-kill-without-query proc)
-    (set-process-sentinel proc (function ange-ftp-process-sentinel))
-    (set-process-filter proc (function ange-ftp-process-filter))
+    (set-process-query-on-exit-flag proc nil)
+    (set-process-sentinel proc 'ange-ftp-process-sentinel)
+    (set-process-filter proc 'ange-ftp-process-filter)
     ;; On Windows, the standard ftp client buffers its output (because
     ;; stdout is a pipe handle) so the startup message may never appear:
     ;; `accept-process-output' at this point would hang indefinitely.
     ;; On Windows, the standard ftp client buffers its output (because
     ;; stdout is a pipe handle) so the startup message may never appear:
     ;; `accept-process-output' at this point would hang indefinitely.
@@ -2026,35 +1986,34 @@ on the gateway machine to do the ftp instead."
 
 \\{comint-mode-map}"
   (interactive)
 
 \\{comint-mode-map}"
   (interactive)
-  (comint-mode)
+  (delay-mode-hooks (comint-mode))
   (setq major-mode 'internal-ange-ftp-mode)
   (setq mode-name "Internal Ange-ftp")
   (setq major-mode 'internal-ange-ftp-mode)
   (setq mode-name "Internal Ange-ftp")
-  (let ((proc (get-buffer-process (current-buffer))))
-    (make-local-variable 'ange-ftp-process-string)
-    (setq ange-ftp-process-string "")
-    (make-local-variable 'ange-ftp-process-busy)
-    (make-local-variable 'ange-ftp-process-result)
-    (make-local-variable 'ange-ftp-process-msg)
-    (make-local-variable 'ange-ftp-process-multi-skip)
-    (make-local-variable 'ange-ftp-process-result-line)
-    (make-local-variable 'ange-ftp-process-continue)
-    (make-local-variable 'ange-ftp-hash-mark-count)
-    (make-local-variable 'ange-ftp-binary-hash-mark-size)
-    (make-local-variable 'ange-ftp-ascii-hash-mark-size)
-    (make-local-variable 'ange-ftp-hash-mark-unit)
-    (make-local-variable 'ange-ftp-xfer-size)
-    (make-local-variable 'ange-ftp-last-percent)
-    (setq ange-ftp-hash-mark-count 0)
-    (setq ange-ftp-xfer-size 0)
-    (setq ange-ftp-process-result-line "")
-
-    (setq comint-prompt-regexp "^ftp> ")
-    (make-local-variable 'comint-password-prompt-regexp)
-    ;; This is a regexp that can't match anything.
-    ;; ange-ftp has its own ways of handling passwords.
-    (setq comint-password-prompt-regexp "^a\\'z")
-    (make-local-variable 'paragraph-start)
-    (setq paragraph-start comint-prompt-regexp)))
+  (make-local-variable 'ange-ftp-process-string)
+  (setq ange-ftp-process-string "")
+  (make-local-variable 'ange-ftp-process-busy)
+  (make-local-variable 'ange-ftp-process-result)
+  (make-local-variable 'ange-ftp-process-msg)
+  (make-local-variable 'ange-ftp-process-multi-skip)
+  (make-local-variable 'ange-ftp-process-result-line)
+  (make-local-variable 'ange-ftp-process-continue)
+  (make-local-variable 'ange-ftp-hash-mark-count)
+  (make-local-variable 'ange-ftp-binary-hash-mark-size)
+  (make-local-variable 'ange-ftp-ascii-hash-mark-size)
+  (make-local-variable 'ange-ftp-hash-mark-unit)
+  (make-local-variable 'ange-ftp-xfer-size)
+  (make-local-variable 'ange-ftp-last-percent)
+  (setq ange-ftp-hash-mark-count 0)
+  (setq ange-ftp-xfer-size 0)
+  (setq ange-ftp-process-result-line "")
+  (setq comint-prompt-regexp "^ftp> ")
+  (make-local-variable 'comint-password-prompt-regexp)
+  ;; This is a regexp that can't match anything.
+  ;; ange-ftp has its own ways of handling passwords.
+  (setq comint-password-prompt-regexp "\\`a\\`")
+  (make-local-variable 'paragraph-start)
+  (setq paragraph-start comint-prompt-regexp)
+  (run-mode-hooks 'internal-ange-ftp-mode-hook))
 
 (defcustom ange-ftp-raw-login nil
   "*Use raw ftp commands for login, if account password is not nil.
 
 (defcustom ange-ftp-raw-login nil
   "*Use raw ftp commands for login, if account password is not nil.
@@ -2100,7 +2059,7 @@ host specified in `ange-ftp-gateway-host'."
 PROC is the process to the FTP-client.  HOST may have an optional
 suffix of the form #PORT to specify a non-default port"
   (save-match-data
 PROC is the process to the FTP-client.  HOST may have an optional
 suffix of the form #PORT to specify a non-default port"
   (save-match-data
-    (string-match "^\\([^#]+\\)\\(#\\([0-9]+\\)\\)?\\'" host)
+    (string-match "\\`\\([^#]+\\)\\(#\\([0-9]+\\)\\)?\\'" host)
     (let* ((nshost (ange-ftp-nslookup-host (match-string 1 host)))
           (port (match-string 3 host))
           (result (ange-ftp-raw-send-cmd
     (let* ((nshost (ange-ftp-nslookup-host (match-string 1 host)))
           (port (match-string 3 host))
           (result (ange-ftp-raw-send-cmd
@@ -2151,7 +2110,7 @@ suffix of the form #PORT to specify a non-default port"
                ange-ftp-skip-msgs skip)))
       (or (car result)
          (progn
                ange-ftp-skip-msgs skip)))
       (or (car result)
          (progn
-           (ange-ftp-set-passwd host user nil) ;reset password.
+           (ange-ftp-set-passwd host user nil) ;reset password.
            (ange-ftp-set-account host user nil) ;reset account.
            (ange-ftp-error host user
                            (concat "USER request failed: "
            (ange-ftp-set-account host user nil) ;reset account.
            (ange-ftp-error host user
                            (concat "USER request failed: "
@@ -2164,17 +2123,12 @@ suffix of the form #PORT to specify a non-default port"
 
 (defun ange-ftp-guess-hash-mark-size (proc)
   (if ange-ftp-send-hash
 
 (defun ange-ftp-guess-hash-mark-size (proc)
   (if ange-ftp-send-hash
-      (save-excursion
-       (set-buffer (process-buffer proc))
+      (with-current-buffer (process-buffer proc)
        (let* ((status (ange-ftp-raw-send-cmd proc "hash"))
        (let* ((status (ange-ftp-raw-send-cmd proc "hash"))
-              (result (car status))
               (line (cdr status)))
          (save-match-data
            (if (string-match ange-ftp-hash-mark-msgs line)
               (line (cdr status)))
          (save-match-data
            (if (string-match ange-ftp-hash-mark-msgs line)
-               (let ((size (string-to-int
-                           (substring line
-                                      (match-beginning 1)
-                                      (match-end 1)))))
+               (let ((size (string-to-number (match-string 1 line))))
                  (setq ange-ftp-ascii-hash-mark-size size
                        ange-ftp-hash-mark-unit (ash size -4))
 
                  (setq ange-ftp-ascii-hash-mark-size size
                        ange-ftp-hash-mark-unit (ash size -4))
 
@@ -2182,6 +2136,8 @@ suffix of the form #PORT to specify a non-default port"
                  (or ange-ftp-binary-hash-mark-size
                      (setq ange-ftp-binary-hash-mark-size size)))))))))
 
                  (or ange-ftp-binary-hash-mark-size
                      (setq ange-ftp-binary-hash-mark-size size)))))))))
 
+(defvar ange-ftp-process-startup-hook nil)
+
 (defun ange-ftp-get-process (host user)
   "Return an FTP subprocess connected to HOST and logged in as USER.
 Create a new process if needed."
 (defun ange-ftp-get-process (host user)
   "Return an FTP subprocess connected to HOST and logged in as USER.
 Create a new process if needed."
@@ -2222,7 +2178,9 @@ Create a new process if needed."
 
        ;; Run any user-specified hooks.  Note that proc, host and user are
        ;; dynamically bound at this point.
 
        ;; Run any user-specified hooks.  Note that proc, host and user are
        ;; dynamically bound at this point.
-       (run-hooks 'ange-ftp-process-startup-hook))
+       (let ((ange-ftp-this-user user)
+             (ange-ftp-this-host host))
+         (run-hooks 'ange-ftp-process-startup-hook)))
       proc)))
 
 (defun ange-ftp-passive-mode (proc on-or-off)
       proc)))
 
 (defun ange-ftp-passive-mode (proc on-or-off)
@@ -2341,10 +2299,18 @@ and NOWAIT."
       ;; resolve symlinks to directories on SysV machines. (Sebastian will
       ;; be happy.)
       (and (eq host-type 'unix)
       ;; resolve symlinks to directories on SysV machines. (Sebastian will
       ;; be happy.)
       (and (eq host-type 'unix)
-          (string-match "/$" cmd1)
+          (string-match "/\\'" cmd1)
           (not (string-match "R" cmd3))
           (setq cmd1 (concat cmd1 ".")))
 
           (not (string-match "R" cmd3))
           (setq cmd1 (concat cmd1 ".")))
 
+      ;; Using "ls -flags foo" has several problems:
+      ;; - if foo is a symlink, we may get a single line showing the symlink
+      ;;   rather than the listing of the directory it points to.
+      ;; - if "foo" has spaces, the parsing of the command may be done wrong.
+      ;; - some version of netbsd's ftpd only accept a single argument after
+      ;;   `ls', which can either be the directory or the flags.
+      ;; So to work around those problems, we use "cd foo; ls -flags".
+
       ;; If the dir name contains a space, some ftp servers will
       ;; refuse to list it.  We instead change directory to the
       ;; directory in question and ls ".".
       ;; If the dir name contains a space, some ftp servers will
       ;; refuse to list it.  We instead change directory to the
       ;; directory in question and ls ".".
@@ -2358,7 +2324,19 @@ and NOWAIT."
       (unless (memq host-type ange-ftp-dumb-host-types)
        (setq cmd0 'ls)
        ;; We cd and then use `ls' with no directory argument.
       (unless (memq host-type ange-ftp-dumb-host-types)
        (setq cmd0 'ls)
        ;; We cd and then use `ls' with no directory argument.
-       ;; This works around a misfeature of some versions of netbsd ftpd.
+       ;; This works around a misfeature of some versions of netbsd ftpd
+       ;; where `ls' can only take one argument: either one set of flags
+       ;; or a file/directory name.
+       ;; If we're trying to `ls' a single file, this fails since we
+       ;; can't cd to a file.  We can't fix this problem here, tho, because
+       ;; at this point we don't know whether the argument is a file or
+       ;; a directory.  Such an `ls' is only ever used (apparently) from
+       ;; `insert-directory' when the `full-directory-p' argument is nil
+       ;; (which seems to only be used by dired when updating its display
+       ;; after operating on a set of files).  So we've changed
+       ;; `ange-ftp-insert-directory' such that in this case it gets
+       ;; a full listing of the directory and extracting the line
+       ;; corresponding to the requested file.
        (unless (equal cmd1 ".")
          (setq result (ange-ftp-cd host user (nth 1 cmd) 'noerror)))
        (setq cmd1 cmd3)))
        (unless (equal cmd1 ".")
          (setq result (ange-ftp-cd host user (nth 1 cmd) 'noerror)))
        (setq cmd1 cmd3)))
@@ -2516,8 +2494,7 @@ Works by doing a pwd and examining the directory syntax."
                                             ange-ftp-fix-name-func-alist)))
              (if fix-name-func
                  (setq dir (funcall fix-name-func dir 'reverse))))
                                             ange-ftp-fix-name-func-alist)))
              (if fix-name-func
                  (setq dir (funcall fix-name-func dir 'reverse))))
-           (ange-ftp-put-hash-entry key dir
-                                    ange-ftp-expand-dir-hashtable))))
+           (puthash key dir ange-ftp-expand-dir-hashtable))))
 
     ;; In the special case of CMS make sure that know the
     ;; expansion of the home minidisk now, because we will
 
     ;; In the special case of CMS make sure that know the
     ;; expansion of the home minidisk now, because we will
@@ -2527,8 +2504,7 @@ Works by doing a pwd and examining the directory syntax."
                   key ange-ftp-expand-dir-hashtable)))
        (let ((dir (car (ange-ftp-get-pwd host user))))
          (if dir
                   key ange-ftp-expand-dir-hashtable)))
        (let ((dir (car (ange-ftp-get-pwd host user))))
          (if dir
-             (ange-ftp-put-hash-entry key (concat "/" dir)
-                                      ange-ftp-expand-dir-hashtable)
+             (puthash key (concat "/" dir) ange-ftp-expand-dir-hashtable)
            (message "Warning! Unable to get home directory")
            (sit-for 1))))))
 
            (message "Warning! Unable to get home directory")
            (sit-for 1))))))
 
@@ -2594,6 +2570,8 @@ which can parse the output from a DIR listing for a host of type TYPE.")
 FILE is the full name of the remote file, LSARGS is any args to pass to the
 `ls' command, and PARSE specifies that the output should be parsed and stored
 away in the internal cache."
 FILE is the full name of the remote file, LSARGS is any args to pass to the
 `ls' command, and PARSE specifies that the output should be parsed and stored
 away in the internal cache."
+  (when (string-match "^--dired\\s-+" lsargs)
+    (setq lsargs (replace-match "" nil t lsargs)))
   ;; If parse is t, we assume that file is a directory. i.e. we only parse
   ;; full directory listings.
   (let* ((ange-ftp-this-file (ange-ftp-expand-file-name file))
   ;; If parse is t, we assume that file is a directory. i.e. we only parse
   ;; full directory listings.
   (let* ((ange-ftp-this-file (ange-ftp-expand-file-name file))
@@ -2611,7 +2589,7 @@ away in the internal cache."
          (if (string-equal name "")
              (setq name
                    (ange-ftp-real-file-name-as-directory
          (if (string-equal name "")
              (setq name
                    (ange-ftp-real-file-name-as-directory
-                         (ange-ftp-expand-dir host user "~"))))
+                    (ange-ftp-expand-dir host user "~"))))
          (if (and ange-ftp-ls-cache-file
                   (string-equal key ange-ftp-ls-cache-file)
                   ;; Don't care about lsargs for dumb hosts.
          (if (and ange-ftp-ls-cache-file
                   (string-equal key ange-ftp-ls-cache-file)
                   ;; Don't care about lsargs for dumb hosts.
@@ -2621,7 +2599,7 @@ away in the internal cache."
            (if wildcard
                (progn
                  (ange-ftp-cd host user (file-name-directory name))
            (if wildcard
                (progn
                  (ange-ftp-cd host user (file-name-directory name))
-                 (setq lscmd (list 'dir file temp lsargs)))
+                 (setq lscmd (list 'ls file temp lsargs)))
              (setq lscmd (list 'dir name temp lsargs)))
            (unwind-protect
                (if (car (setq result (ange-ftp-send-cmd
              (setq lscmd (list 'dir name temp lsargs)))
            (unwind-protect
                (if (car (setq result (ange-ftp-send-cmd
@@ -2631,9 +2609,8 @@ away in the internal cache."
                                       (format "Listing %s"
                                               (ange-ftp-abbreviate-filename
                                                ange-ftp-this-file)))))
                                       (format "Listing %s"
                                               (ange-ftp-abbreviate-filename
                                                ange-ftp-this-file)))))
-                   (save-excursion
-                     (set-buffer (get-buffer-create
-                                  ange-ftp-data-buffer-name))
+                   (with-current-buffer (get-buffer-create
+                                          ange-ftp-data-buffer-name)
                      (erase-buffer)
                      (if (ange-ftp-real-file-readable-p temp)
                          (ange-ftp-real-insert-file-contents temp)
                      (erase-buffer)
                      (if (ange-ftp-real-file-readable-p temp)
                          (ange-ftp-real-insert-file-contents temp)
@@ -2689,31 +2666,6 @@ away in the internal cache."
 ;;;; Directory information caching support.
 ;;;; ------------------------------------------------------------
 
 ;;;; Directory information caching support.
 ;;;; ------------------------------------------------------------
 
-(defconst ange-ftp-date-regexp
-  (let* ((l "\\([A-Za-z]\\|[^\0-\177]\\)")
-        ;; In some locales, month abbreviations are as short as 2 letters,
-        ;; and they can be padded on the right with spaces.
-        ;; weiand: changed: month ends with . or , or .,
-;;old   (month (concat l l "+ *"))
-        (month (concat l l "+[.]?,? *"))
-        ;; Recognize any non-ASCII character.
-        ;; The purpose is to match a Kanji character.
-        (k "[^\0-\177]")
-        (s " ")
-        (mm "[ 0-1][0-9]")
-        ;; weiand: changed: day ends with .
-;;old   (dd "[ 0-3][0-9]")
-        (dd "[ 0-3][0-9][.]?")
-        (western (concat "\\(" month s dd "\\|" dd s month "\\)"))
-        (japanese (concat mm k s dd k)))
-        ;; Require the previous column to end in a digit.
-        ;; This avoids recognizing `1 may 1997' as a date in the line:
-        ;; -r--r--r--   1 may      1997        1168 Oct 19 16:49 README
-    (concat "[0-9]" s "\\(" western "\\|" japanese "\\)" s))
-  "Regular expression to match up to the column before the file name in a
-directory listing.  This regular expression is designed to recognize dates
-regardless of the language.")
-
 (defvar ange-ftp-add-file-entry-alist nil
   "Alist saying how to add file entries on certain OS types.
 Association list of pairs \( TYPE \. FUNC \), where FUNC
 (defvar ange-ftp-add-file-entry-alist nil
   "Alist saying how to add file entries on certain OS types.
 Association list of pairs \( TYPE \. FUNC \), where FUNC
@@ -2748,65 +2700,59 @@ The main reason for this alist is to deal with file versions in VMS.")
   ;;Extract the filename from the current line of a dired-like listing.
   `(let ((eol (progn (end-of-line) (point))))
      (beginning-of-line)
   ;;Extract the filename from the current line of a dired-like listing.
   `(let ((eol (progn (end-of-line) (point))))
      (beginning-of-line)
-     (if (re-search-forward ange-ftp-date-regexp eol t)
-         (progn
-           (skip-chars-forward " ")
-           (skip-chars-forward "^ " eol)
-           (skip-chars-forward " " eol)
-           ;; We bomb on filenames starting with a space.
-           (buffer-substring (point) eol)))))
+     (if (re-search-forward directory-listing-before-filename-regexp eol t)
+        (buffer-substring (point) eol))))
 
 ;; This deals with the F switch. Should also do something about
 ;; unquoting names obtained with the SysV b switch and the GNU Q
 ;; switch. See Sebastian's dired-get-filename.
 
 
 ;; This deals with the F switch. Should also do something about
 ;; unquoting names obtained with the SysV b switch and the GNU Q
 ;; switch. See Sebastian's dired-get-filename.
 
-(defmacro ange-ftp-ls-parser ()
-  ;; Note that switches is dynamically bound.
+(defun ange-ftp-ls-parser (switches)
   ;; Meant to be called by ange-ftp-parse-dired-listing
   ;; Meant to be called by ange-ftp-parse-dired-listing
-  `(let ((tbl (ange-ftp-make-hashtable))
-         (used-F (and (stringp switches)
-                      (string-match "F" switches)))
-         file-type symlink directory file)
-     (while (setq file (ange-ftp-parse-filename))
-       (beginning-of-line)
-       (skip-chars-forward "\t 0-9")
-       (setq file-type (following-char)
-             directory (eq file-type ?d))
-       (if (eq file-type ?l)
-           (if (string-match " -> " file)
-               (setq symlink (substring file (match-end 0))
-                     file (substring file 0 (match-beginning 0)))
-             ;; Shouldn't happen
-            (setq symlink ""))
-         (setq symlink nil))
-       ;; Only do a costly regexp search if the F switch was used.
-       (if (and used-F
-                (not (string-equal file ""))
-                (looking-at
-                 ".[-r][-w]\\([^ ]\\)[-r][-w]\\([^ ]\\)[-r][-w]\\([^ ]\\)"))
-           (let ((socket (eq file-type ?s))
-                 (executable
-                  (and (not symlink) ; x bits don't mean a thing for symlinks
-                       (string-match
-                        "[xst]"
-                        (concat (buffer-substring
-                                 (match-beginning 1) (match-end 1))
-                                (buffer-substring
-                                 (match-beginning 2) (match-end 2))
-                                (buffer-substring
-                                 (match-beginning 3) (match-end 3)))))))
-             ;; Some ls's with the F switch mark symlinks with an @ (ULTRIX)
-             ;; and others don't. (sigh...) Beware, that some Unix's don't
-             ;; seem to believe in the F-switch
-             (if (or (and symlink (string-match "@$" file))
-                     (and directory (string-match "/$" file))
-                     (and executable (string-match "*$" file))
-                     (and socket (string-match "=$" file)))
-                 (setq file (substring file 0 -1)))))
-       (ange-ftp-put-hash-entry file (or symlink directory) tbl)
-       (forward-line 1))
-    (ange-ftp-put-hash-entry "." t tbl)
-    (ange-ftp-put-hash-entry ".." t tbl)
+  (let ((tbl (make-hash-table :test 'equal))
+       (used-F (and (stringp switches)
+                    (string-match "F" switches)))
+       file-type symlink directory file)
+    (while (setq file (ange-ftp-parse-filename))
+      (beginning-of-line)
+      (skip-chars-forward "\t 0-9")
+      (setq file-type (following-char)
+           directory (eq file-type ?d))
+      (if (eq file-type ?l)
+         (let ((end (string-match " -> " file)))
+           (if end
+               ;; Sometimes `ls' appends a @ at the end of the target.
+               (setq symlink (substring file (match-end 0)
+                                        (string-match "@\\'" file))
+                     file (substring file 0 end))
+             ;; Shouldn't happen
+             (setq symlink "")))
+       (setq symlink nil))
+      ;; Only do a costly regexp search if the F switch was used.
+      (if (and used-F
+              (not (string-equal file ""))
+              (looking-at
+               ".[-r][-w]\\([^ ]\\)[-r][-w]\\([^ ]\\)[-r][-w]\\([^ ]\\)"))
+         (let ((socket (eq file-type ?s))
+               (executable
+                (and (not symlink) ; x bits don't mean a thing for symlinks
+                     (string-match
+                      "[xst]"
+                      (concat (match-string 1)
+                              (match-string 2)
+                              (match-string 3))))))
+           ;; Some ls's with the F switch mark symlinks with an @ (ULTRIX)
+           ;; and others don't. (sigh...) Beware, that some Unix's don't
+           ;; seem to believe in the F-switch
+           (if (or (and symlink (string-match "@\\'" file))
+                   (and directory (string-match "/\\'" file))
+                   (and executable (string-match "*\\'" file))
+                   (and socket (string-match "=\\'" file)))
+               (setq file (substring file 0 -1)))))
+      (puthash file (or symlink directory) tbl)
+      (forward-line 1))
+    (puthash "." t tbl)
+    (puthash ".." t tbl)
     tbl))
 
 ;;; The dl stuff for descriptive listings
     tbl))
 
 ;;; The dl stuff for descriptive listings
@@ -2833,9 +2779,9 @@ match subdirectories as well.")
 (defmacro ange-ftp-dl-parser ()
   ;; Parse the current buffer, which is assumed to be a descriptive
   ;; listing, and return a hashtable.
 (defmacro ange-ftp-dl-parser ()
   ;; Parse the current buffer, which is assumed to be a descriptive
   ;; listing, and return a hashtable.
-  `(let ((tbl (ange-ftp-make-hashtable)))
+  `(let ((tbl (make-hash-table :test 'equal)))
      (while (not (eobp))
      (while (not (eobp))
-       (ange-ftp-put-hash-entry
+       (puthash
         (buffer-substring (point)
                           (progn
                             (skip-chars-forward "^ /\n")
         (buffer-substring (point)
                           (progn
                             (skip-chars-forward "^ /\n")
@@ -2843,9 +2789,9 @@ match subdirectories as well.")
         (eq (following-char) ?/)
         tbl)
        (forward-line 1))
         (eq (following-char) ?/)
         tbl)
        (forward-line 1))
-    (ange-ftp-put-hash-entry "." t tbl)
-    (ange-ftp-put-hash-entry ".." t tbl)
-    tbl))
+     (puthash "." t tbl)
+     (puthash ".." t tbl)
+     tbl))
 
 ;; Parse the current buffer which is assumed to be in a dired-like listing
 ;; format, and return a hashtable as the result. If the listing is not really
 
 ;; Parse the current buffer which is assumed to be in a dired-like listing
 ;; format, and return a hashtable as the result. If the listing is not really
@@ -2858,7 +2804,7 @@ match subdirectories as well.")
       (forward-line 1)
       ;; Some systems put in a blank line here.
       (if (eolp) (forward-line 1))
       (forward-line 1)
       ;; Some systems put in a blank line here.
       (if (eolp) (forward-line 1))
-      (ange-ftp-ls-parser))
+      (ange-ftp-ls-parser switches))
      ((looking-at "[^\n]+\\( not found\\|: Not a directory\\)\n\\'")
       ;; It's an ls error message.
       nil)
      ((looking-at "[^\n]+\\( not found\\|: Not a directory\\)\n\\'")
       ;; It's an ls error message.
       nil)
@@ -2870,9 +2816,9 @@ match subdirectories as well.")
       ;; (3) The twilight zone.
       ;; We'll assume (1) for now.
       nil)
       ;; (3) The twilight zone.
       ;; We'll assume (1) for now.
       nil)
-     ((re-search-forward ange-ftp-date-regexp nil t)
+     ((re-search-forward directory-listing-before-filename-regexp nil t)
       (beginning-of-line)
       (beginning-of-line)
-      (ange-ftp-ls-parser))
+      (ange-ftp-ls-parser switches))
      ((re-search-forward "^[^ \n\t]+ +\\([0-9]+\\|-\\|=\\) " nil t)
       ;; It's a dl listing (I hope).
       ;; file is bound by the call to ange-ftp-ls
      ((re-search-forward "^[^ \n\t]+ +\\([0-9]+\\|-\\|=\\) " nil t)
       ;; It's a dl listing (I hope).
       ;; file is bound by the call to ange-ftp-ls
@@ -2883,15 +2829,15 @@ match subdirectories as well.")
 
 (defun ange-ftp-set-files (directory files)
   "For a given DIRECTORY, set or change the associated FILES hashtable."
 
 (defun ange-ftp-set-files (directory files)
   "For a given DIRECTORY, set or change the associated FILES hashtable."
-  (and files (ange-ftp-put-hash-entry (file-name-as-directory directory)
-                                     files ange-ftp-files-hashtable)))
+  (and files (puthash (file-name-as-directory directory)
+                     files ange-ftp-files-hashtable)))
 
 (defun ange-ftp-get-files (directory &optional no-error)
   "Given a given DIRECTORY, return a hashtable of file entries.
 This will give an error or return nil, depending on the value of
 NO-ERROR, if a listing for DIRECTORY cannot be obtained."
   (setq directory (file-name-as-directory directory)) ;normalize
 
 (defun ange-ftp-get-files (directory &optional no-error)
   "Given a given DIRECTORY, return a hashtable of file entries.
 This will give an error or return nil, depending on the value of
 NO-ERROR, if a listing for DIRECTORY cannot be obtained."
   (setq directory (file-name-as-directory directory)) ;normalize
-  (or (ange-ftp-get-hash-entry directory ange-ftp-files-hashtable)
+  (or (gethash directory ange-ftp-files-hashtable)
       (save-match-data
        (and (ange-ftp-ls directory
                          ;; This is an efficiency hack. We try to
       (save-match-data
        (and (ange-ftp-ls directory
                          ;; This is an efficiency hack. We try to
@@ -2922,15 +2868,14 @@ NO-ERROR, if a listing for DIRECTORY cannot be obtained."
                                dired-listing-switches
                              "-al"))
                          t no-error)
                                dired-listing-switches
                              "-al"))
                          t no-error)
-            (ange-ftp-get-hash-entry
-             directory ange-ftp-files-hashtable)))))
+            (gethash directory ange-ftp-files-hashtable)))))
 
 ;; Given NAME, return the file part that can be used for looking up the
 ;; file's entry in a hashtable.
 (defmacro ange-ftp-get-file-part (name)
   `(let ((file (file-name-nondirectory ,name)))
      (if (string-equal file "")
 
 ;; Given NAME, return the file part that can be used for looking up the
 ;; file's entry in a hashtable.
 (defmacro ange-ftp-get-file-part (name)
   `(let ((file (file-name-nondirectory ,name)))
      (if (string-equal file "")
-        "."
+        "."
        file)))
 
 ;; Return whether ange-ftp-file-entry-p and ange-ftp-get-file-entry are
        file)))
 
 ;; Return whether ange-ftp-file-entry-p and ange-ftp-get-file-entry are
@@ -2941,6 +2886,7 @@ NO-ERROR, if a listing for DIRECTORY cannot be obtained."
 ;; 2. The syntax of FILE and DIR make it impossible that FILE could be a valid
 ;;     subdirectory. This is of course an OS dependent judgement.
 
 ;; 2. The syntax of FILE and DIR make it impossible that FILE could be a valid
 ;;     subdirectory. This is of course an OS dependent judgement.
 
+(defvar dired-local-variables-file)
 (defmacro ange-ftp-allow-child-lookup (dir file)
   `(not
     (let* ((efile ,file)               ; expand once.
 (defmacro ange-ftp-allow-child-lookup (dir file)
   `(not
     (let* ((efile ,file)               ; expand once.
@@ -2967,7 +2913,7 @@ NO-ERROR, if a listing for DIRECTORY cannot be obtained."
   "Given NAME, return whether there is a file entry for it."
   (let* ((name (directory-file-name name))
         (dir (file-name-directory name))
   "Given NAME, return whether there is a file entry for it."
   (let* ((name (directory-file-name name))
         (dir (file-name-directory name))
-        (ent (ange-ftp-get-hash-entry dir ange-ftp-files-hashtable))
+        (ent (gethash dir ange-ftp-files-hashtable))
         (file (ange-ftp-get-file-part name)))
     (if ent
        (ange-ftp-hash-entry-exists-p file ent)
         (file (ange-ftp-get-file-part name)))
     (if ent
        (ange-ftp-hash-entry-exists-p file ent)
@@ -2981,13 +2927,10 @@ NO-ERROR, if a listing for DIRECTORY cannot be obtained."
               ;; then dumb hosts will give an ftp error. Smart unix hosts
               ;; will simply send back the ls
               ;; error message.
               ;; then dumb hosts will give an ftp error. Smart unix hosts
               ;; will simply send back the ls
               ;; error message.
-              (ange-ftp-get-hash-entry "." ent))
+              (gethash "." ent))
          ;; Child lookup failed, so try the parent.
          ;; Child lookup failed, so try the parent.
-         (let ((table (ange-ftp-get-files dir)))
-           ;; If the dir doesn't exist, don't use it as a hash table.
-           (and table
-                (ange-ftp-hash-entry-exists-p file
-                                              table)))))))
+         (ange-ftp-hash-entry-exists-p
+          file (ange-ftp-get-files dir 'no-error))))))
 
 (defun ange-ftp-get-file-entry (name)
   "Given NAME, return the given file entry.
 
 (defun ange-ftp-get-file-entry (name)
   "Given NAME, return the given file entry.
@@ -2996,53 +2939,49 @@ or a string for a symlink. If the file isn't in the hashtable,
 this also returns nil."
   (let* ((name (directory-file-name name))
         (dir (file-name-directory name))
 this also returns nil."
   (let* ((name (directory-file-name name))
         (dir (file-name-directory name))
-        (ent (ange-ftp-get-hash-entry dir ange-ftp-files-hashtable))
+        (ent (gethash dir ange-ftp-files-hashtable))
         (file (ange-ftp-get-file-part name)))
     (if ent
         (file (ange-ftp-get-file-part name)))
     (if ent
-       (ange-ftp-get-hash-entry file ent)
+       (gethash file ent)
       (or (and (ange-ftp-allow-child-lookup dir file)
               (setq ent (ange-ftp-get-files name t))
       (or (and (ange-ftp-allow-child-lookup dir file)
               (setq ent (ange-ftp-get-files name t))
-              (ange-ftp-get-hash-entry "." ent))
-              ;; i.e. it's a directory by child lookup
-         (ange-ftp-get-hash-entry file
-                                  (ange-ftp-get-files dir))))))
+              (gethash "." ent))
+         ;; i.e. it's a directory by child lookup
+         (and (setq ent (ange-ftp-get-files dir t))
+              (gethash file ent))))))
 
 (defun ange-ftp-internal-delete-file-entry (name &optional dir-p)
 
 (defun ange-ftp-internal-delete-file-entry (name &optional dir-p)
-  (if dir-p
-      (progn
-       (setq name (file-name-as-directory name))
-       (ange-ftp-del-hash-entry name ange-ftp-files-hashtable)
-       (setq name (directory-file-name name))))
+  (when dir-p
+    (setq name (file-name-as-directory name))
+    (remhash name ange-ftp-files-hashtable)
+    (setq name (directory-file-name name)))
   ;; Note that file-name-as-directory followed by directory-file-name
   ;; serves to canonicalize directory file names to their unix form.
   ;; i.e. in VMS, FOO.DIR -> FOO/ -> FOO
   ;; Note that file-name-as-directory followed by directory-file-name
   ;; serves to canonicalize directory file names to their unix form.
   ;; i.e. in VMS, FOO.DIR -> FOO/ -> FOO
-  (let ((files (ange-ftp-get-hash-entry (file-name-directory name)
-                                       ange-ftp-files-hashtable)))
+  (let ((files (gethash (file-name-directory name) ange-ftp-files-hashtable)))
     (if files
     (if files
-       (ange-ftp-del-hash-entry (ange-ftp-get-file-part name)
-                                files))))
+       (remhash (ange-ftp-get-file-part name) files))))
 
 (defun ange-ftp-internal-add-file-entry (name &optional dir-p)
   (and dir-p
        (setq name (directory-file-name name)))
 
 (defun ange-ftp-internal-add-file-entry (name &optional dir-p)
   (and dir-p
        (setq name (directory-file-name name)))
-  (let ((files (ange-ftp-get-hash-entry (file-name-directory name)
-                                       ange-ftp-files-hashtable)))
+  (let ((files (gethash (file-name-directory name) ange-ftp-files-hashtable)))
     (if files
     (if files
-       (ange-ftp-put-hash-entry (ange-ftp-get-file-part name)
-                                dir-p
-                                files))))
+       (puthash (ange-ftp-get-file-part name) dir-p files))))
 
 (defun ange-ftp-wipe-file-entries (host user)
   "Get rid of entry for HOST, USER pair from file entry information hashtable."
 
 (defun ange-ftp-wipe-file-entries (host user)
   "Get rid of entry for HOST, USER pair from file entry information hashtable."
-  (let ((new-tbl (ange-ftp-make-hashtable (length ange-ftp-files-hashtable))))
-    (ange-ftp-map-hashtable
+  (let ((new-tbl (make-hash-table :test 'equal
+                                 :size (hash-table-size
+                                        ange-ftp-files-hashtable))))
+    (maphash
      (lambda (key val)
        (let ((parsed (ange-ftp-ftp-name key)))
          (if parsed
              (let ((h (nth 0 parsed))
                    (u (nth 1 parsed)))
                (or (and (equal host h) (equal user u))
      (lambda (key val)
        (let ((parsed (ange-ftp-ftp-name key)))
          (if parsed
              (let ((h (nth 0 parsed))
                    (u (nth 1 parsed)))
                (or (and (equal host h) (equal user u))
-                   (ange-ftp-put-hash-entry key val new-tbl))))))
+                   (puthash key val new-tbl))))))
      ange-ftp-files-hashtable)
     (setq ange-ftp-files-hashtable new-tbl)))
 \f
      ange-ftp-files-hashtable)
     (setq ange-ftp-files-hashtable new-tbl)))
 \f
@@ -3055,8 +2994,7 @@ this also returns nil."
   (let ((result (ange-ftp-send-cmd host user '(type "binary"))))
     (if (not (car result))
        (ange-ftp-error host user (concat "BINARY failed: " (cdr result)))
   (let ((result (ange-ftp-send-cmd host user '(type "binary"))))
     (if (not (car result))
        (ange-ftp-error host user (concat "BINARY failed: " (cdr result)))
-      (save-excursion
-       (set-buffer (process-buffer (ange-ftp-get-process host user)))
+      (with-current-buffer (process-buffer (ange-ftp-get-process host user))
        (and ange-ftp-binary-hash-mark-size
             (setq ange-ftp-hash-mark-unit
                   (ash ange-ftp-binary-hash-mark-size -4)))))))
        (and ange-ftp-binary-hash-mark-size
             (setq ange-ftp-hash-mark-unit
                   (ash ange-ftp-binary-hash-mark-size -4)))))))
@@ -3066,8 +3004,7 @@ this also returns nil."
   (let ((result (ange-ftp-send-cmd host user '(type "ascii"))))
     (if (not (car result))
        (ange-ftp-error host user (concat "ASCII failed: " (cdr result)))
   (let ((result (ange-ftp-send-cmd host user '(type "ascii"))))
     (if (not (car result))
        (ange-ftp-error host user (concat "ASCII failed: " (cdr result)))
-      (save-excursion
-       (set-buffer (process-buffer (ange-ftp-get-process host user)))
+      (with-current-buffer (process-buffer (ange-ftp-get-process host user))
        (and ange-ftp-ascii-hash-mark-size
             (setq ange-ftp-hash-mark-unit
                   (ash ange-ftp-ascii-hash-mark-size -4)))))))
        (and ange-ftp-ascii-hash-mark-size
             (setq ange-ftp-hash-mark-unit
                   (ash ange-ftp-ascii-hash-mark-size -4)))))))
@@ -3088,10 +3025,8 @@ and LINE is the relevant success or fail line from the FTP-client."
     (if (car result)
        (save-match-data
          (and (or (string-match "\"\\([^\"]*\\)\"" line)
     (if (car result)
        (save-match-data
          (and (or (string-match "\"\\([^\"]*\\)\"" line)
-                  (string-match " \\([^ ]+\\) " line)) ; stone-age VMS servers!
-              (setq dir (substring line
-                                   (match-beginning 1)
-                                   (match-end 1))))))
+                  (string-match " \\([^ ]+\\) " line)) ; stone-age VMS servers!
+              (setq dir (match-string 1 line)))))
     (cons dir line)))
 \f
 ;;; ------------------------------------------------------------
     (cons dir line)))
 \f
 ;;; ------------------------------------------------------------
@@ -3109,7 +3044,7 @@ logged in as user USER and cd'd to directory DIR."
         (fix-name-func
          (cdr (assq host-type ange-ftp-fix-name-func-alist)))
         (key (concat host "/" user "/" dir))
         (fix-name-func
          (cdr (assq host-type ange-ftp-fix-name-func-alist)))
         (key (concat host "/" user "/" dir))
-        (res (ange-ftp-get-hash-entry key ange-ftp-expand-dir-hashtable)))
+        (res (gethash key ange-ftp-expand-dir-hashtable)))
     (or res
        (progn
          (or
     (or res
        (progn
          (or
@@ -3125,9 +3060,7 @@ logged in as user USER and cd'd to directory DIR."
                  (line (cdr result)))
             (setq res
                   (if (string-match ange-ftp-expand-dir-regexp line)
                  (line (cdr result)))
             (setq res
                   (if (string-match ange-ftp-expand-dir-regexp line)
-                      (substring line
-                                 (match-beginning 1)
-                                 (match-end 1))))))
+                      (match-string 1 line)))))
          (or res
              (if (string-equal dir "~")
                  (setq res (car (ange-ftp-get-pwd host user)))
          (or res
              (if (string-equal dir "~")
                  (setq res (car (ange-ftp-get-pwd host user)))
@@ -3141,8 +3074,7 @@ logged in as user USER and cd'd to directory DIR."
                    (ange-ftp-this-host host))
                (if fix-name-func
                    (setq res (funcall fix-name-func res 'reverse)))
                    (ange-ftp-this-host host))
                (if fix-name-func
                    (setq res (funcall fix-name-func res 'reverse)))
-               (ange-ftp-put-hash-entry
-                key res ange-ftp-expand-dir-hashtable)))
+               (puthash key res ange-ftp-expand-dir-hashtable)))
          res))))
 
 (defun ange-ftp-canonize-filename (n)
          res))))
 
 (defun ange-ftp-canonize-filename (n)
@@ -3157,20 +3089,24 @@ logged in as user USER and cd'd to directory DIR."
 
          ;; See if remote name is absolute.  If so then just expand it and
          ;; replace the name component of the overall name.
 
          ;; See if remote name is absolute.  If so then just expand it and
          ;; replace the name component of the overall name.
-         (cond ((string-match "^/" name)
+         (cond ((string-match "\\`/" name)
                 name)
 
                ;; Name starts with ~ or ~user.  Resolve that part of the name
                ;; making it absolute then re-expand it.
                 name)
 
                ;; Name starts with ~ or ~user.  Resolve that part of the name
                ;; making it absolute then re-expand it.
-               ((string-match "^~[^/]*" name)
-                (let* ((tilda (substring name
-                                         (match-beginning 0)
-                                         (match-end 0)))
+               ((string-match "\\`~[^/]*" name)
+                (let* ((tilda (match-string 0 name))
                        (rest (substring name (match-end 0)))
                        (dir (ange-ftp-expand-dir host user tilda)))
                   (if dir
                        (rest (substring name (match-end 0)))
                        (dir (ange-ftp-expand-dir host user tilda)))
                   (if dir
-                      (setq name (if (string-equal dir "/")
-                                     rest (concat dir rest)))
+                       ;; C-x d /ftp:anonymous@ftp.gnu.org:~/ RET
+                       ;; seems to cause `rest' to sometimes be empty.
+                       ;; Maybe it's an error for `rest' to be empty here,
+                       ;; but until we figure this out, this quick fix
+                       ;; seems to do the trick.
+                      (setq name (cond ((string-equal rest "") dir)
+                                       ((string-equal dir "/") rest)
+                                       (t (concat dir rest))))
                     (error "User \"%s\" is not known"
                            (substring tilda 1)))))
 
                     (error "User \"%s\" is not known"
                            (substring tilda 1)))))
 
@@ -3184,19 +3120,18 @@ logged in as user USER and cd'd to directory DIR."
                     (error "Unable to obtain CWD")))))
 
          ;; If name starts with //, preserve that, for apollo system.
                     (error "Unable to obtain CWD")))))
 
          ;; If name starts with //, preserve that, for apollo system.
-         (if (not (string-match "^//" name))
-             (progn
-               (if (not (eq system-type 'windows-nt))
-                   (setq name (ange-ftp-real-expand-file-name name))
-                 ;; Windows UNC default dirs do not make sense for ftp.
-                 (if (string-match "^//" default-directory)
-                     (setq name (ange-ftp-real-expand-file-name name "c:/"))
-                   (setq name (ange-ftp-real-expand-file-name name)))
-                 ;; Strip off possible drive specifier.
-                 (if (string-match "^[a-zA-Z]:" name)
-                     (setq name (substring name 2))))
-               (if (string-match "^//" name)
-                   (setq name (substring name 1)))))
+         (unless (string-match "\\`//" name)
+            (if (not (eq system-type 'windows-nt))
+                (setq name (ange-ftp-real-expand-file-name name))
+              ;; Windows UNC default dirs do not make sense for ftp.
+              (setq name (if (string-match "\\`//" default-directory)
+                             (ange-ftp-real-expand-file-name name "c:/")
+                           (ange-ftp-real-expand-file-name name)))
+              ;; Strip off possible drive specifier.
+              (if (string-match "\\`[a-zA-Z]:" name)
+                  (setq name (substring name 2))))
+            (if (string-match "\\`//" name)
+                (setq name (substring name 1))))
 
          ;; Now substitute the expanded name back into the overall filename.
          (ange-ftp-replace-name-component n name))
 
          ;; Now substitute the expanded name back into the overall filename.
          (ange-ftp-replace-name-component n name))
@@ -3209,7 +3144,7 @@ logged in as user USER and cd'd to directory DIR."
         (ange-ftp-real-file-name-directory n))))))
 
 (defun ange-ftp-expand-file-name (name &optional default)
         (ange-ftp-real-file-name-directory n))))))
 
 (defun ange-ftp-expand-file-name (name &optional default)
-  "Documented as original."
+  "Documented as `expand-file-name'."
   (save-match-data
     (setq default (or default default-directory))
     (cond ((eq (string-to-char name) ?~)
   (save-match-data
     (setq default (or default default-directory))
     (cond ((eq (string-to-char name) ?~)
@@ -3220,8 +3155,8 @@ logged in as user USER and cd'd to directory DIR."
                (eq (string-to-char name) ?\\))
           (ange-ftp-canonize-filename name))
          ((and (eq system-type 'windows-nt)
                (eq (string-to-char name) ?\\))
           (ange-ftp-canonize-filename name))
          ((and (eq system-type 'windows-nt)
-               (or (string-match "^[a-zA-Z]:" name)
-                   (string-match "^[a-zA-Z]:" default)))
+               (or (string-match "\\`[a-zA-Z]:" name)
+                   (string-match "\\`[a-zA-Z]:" default)))
           (ange-ftp-real-expand-file-name name default))
          ((zerop (length name))
           (ange-ftp-canonize-filename default))
           (ange-ftp-real-expand-file-name name default))
          ((zerop (length name))
           (ange-ftp-canonize-filename default))
@@ -3254,7 +3189,7 @@ system TYPE.")
     (if parsed
        (let ((filename (nth 2 parsed)))
          (if (save-match-data
     (if parsed
        (let ((filename (nth 2 parsed)))
          (if (save-match-data
-               (string-match "^~[^/]*$" filename))
+               (string-match "\\`~[^/]*\\'" filename))
              name
            (ange-ftp-replace-name-component
             name
              name
            (ange-ftp-replace-name-component
             name
@@ -3267,7 +3202,7 @@ system TYPE.")
     (if parsed
        (let ((filename (nth 2 parsed)))
          (if (save-match-data
     (if parsed
        (let ((filename (nth 2 parsed)))
          (if (save-match-data
-               (string-match "^~[^/]*$" filename))
+               (string-match "\\`~[^/]*\\'" filename))
              ""
            (ange-ftp-real-file-name-nondirectory filename)))
       (ange-ftp-real-file-name-nondirectory name))))
              ""
            (ange-ftp-real-file-name-nondirectory filename)))
       (ange-ftp-real-file-name-nondirectory name))))
@@ -3277,8 +3212,8 @@ system TYPE.")
   (let ((parsed (ange-ftp-ftp-name dir)))
     (if parsed
        (ange-ftp-replace-name-component
   (let ((parsed (ange-ftp-ftp-name dir)))
     (if parsed
        (ange-ftp-replace-name-component
-          dir
-          (ange-ftp-real-directory-file-name (nth 2 parsed)))
+        dir
+        (ange-ftp-real-directory-file-name (nth 2 parsed)))
       (ange-ftp-real-directory-file-name dir))))
 
 \f
       (ange-ftp-real-directory-file-name dir))))
 
 \f
@@ -3314,17 +3249,17 @@ system TYPE.")
               (coding-system-used last-coding-system-used))
          (unwind-protect
              (progn
               (coding-system-used last-coding-system-used))
          (unwind-protect
              (progn
-               (let ((executing-kbd-macro t)
-                     (filename (buffer-file-name))
+               (let ((filename (buffer-file-name))
                      (mod-p (buffer-modified-p)))
                  (unwind-protect
                      (progn
                      (mod-p (buffer-modified-p)))
                  (unwind-protect
                      (progn
-                       (ange-ftp-real-write-region start end temp nil visit)
+                       (ange-ftp-real-write-region start end temp nil
+                                                   (or visit 'quiet))
                        (setq coding-system-used last-coding-system-used))
                    ;; cleanup forms
                    (setq coding-system-used last-coding-system-used)
                    (setq buffer-file-name filename)
                        (setq coding-system-used last-coding-system-used))
                    ;; cleanup forms
                    (setq coding-system-used last-coding-system-used)
                    (setq buffer-file-name filename)
-                   (set-buffer-modified-p mod-p)))
+                   (restore-buffer-modified-p mod-p)))
                (if binary
                    (ange-ftp-set-binary-mode host user))
 
                (if binary
                    (ange-ftp-set-binary-mode host user))
 
@@ -3369,8 +3304,8 @@ system TYPE.")
          (if (or (file-exists-p filename)
                  (progn
                    (setq ange-ftp-ls-cache-file nil)
          (if (or (file-exists-p filename)
                  (progn
                    (setq ange-ftp-ls-cache-file nil)
-                   (ange-ftp-del-hash-entry (file-name-directory filename)
-                                            ange-ftp-files-hashtable)
+                   (remhash (file-name-directory filename)
+                            ange-ftp-files-hashtable)
                    (file-exists-p filename)))
              (let* ((host (nth 0 parsed))
                     (user (nth 1 parsed))
                    (file-exists-p filename)))
              (let* ((host (nth 0 parsed))
                     (user (nth 1 parsed))
@@ -3433,9 +3368,14 @@ system TYPE.")
       (ange-ftp-real-insert-file-contents filename visit beg end replace))))
 
 (defun ange-ftp-expand-symlink (file dir)
       (ange-ftp-real-insert-file-contents filename visit beg end replace))))
 
 (defun ange-ftp-expand-symlink (file dir)
-  (if (file-name-absolute-p file)
-      (ange-ftp-replace-name-component dir file)
-    (expand-file-name file dir)))
+  (let ((res (if (file-name-absolute-p file)
+                (ange-ftp-replace-name-component dir file)
+              (expand-file-name file dir))))
+    (if (file-symlink-p res)
+       (ange-ftp-expand-symlink
+        (ange-ftp-get-file-entry res)
+        (file-name-directory (directory-file-name res)))
+      res)))
 
 (defun ange-ftp-file-symlink-p (file)
   ;; call ange-ftp-expand-file-name rather than the normal
 
 (defun ange-ftp-file-symlink-p (file)
   ;; call ange-ftp-expand-file-name rather than the normal
@@ -3443,15 +3383,17 @@ system TYPE.")
   ;; redefines both file-symlink-p and expand-file-name.
   (setq file (ange-ftp-expand-file-name file))
   (if (ange-ftp-ftp-name file)
   ;; redefines both file-symlink-p and expand-file-name.
   (setq file (ange-ftp-expand-file-name file))
   (if (ange-ftp-ftp-name file)
-      (let ((file-ent
-            (ange-ftp-get-hash-entry
-             (ange-ftp-get-file-part file)
-             (ange-ftp-get-files (file-name-directory file)))))
-       (if (stringp file-ent)
-           (if (file-name-absolute-p file-ent)
-               (ange-ftp-replace-name-component
-                     (file-name-directory file) file-ent)
-             file-ent)))
+      (condition-case nil
+         (let ((ent (ange-ftp-get-files (file-name-directory file))))
+           (and ent
+                (stringp (setq ent
+                               (gethash (ange-ftp-get-file-part file) ent)))
+                ent))
+       ;; If we can't read the parent directory, just assume
+       ;; this file is not a symlink.
+       ;; This makes it possible to access a directory that
+       ;; whose parent is not readable.
+       (file-error nil))
     (ange-ftp-real-file-symlink-p file)))
 
 (defun ange-ftp-file-exists-p (name)
     (ange-ftp-real-file-symlink-p file)))
 
 (defun ange-ftp-file-exists-p (name)
@@ -3476,7 +3418,9 @@ system TYPE.")
       (let ((file-ent (ange-ftp-get-file-entry
                       (ange-ftp-file-name-as-directory name))))
        (if (stringp file-ent)
       (let ((file-ent (ange-ftp-get-file-entry
                       (ange-ftp-file-name-as-directory name))))
        (if (stringp file-ent)
-           (file-directory-p
+           ;; Calling file-directory-p doesn't work because ange-ftp
+           ;; is temporarily disabled for this operation.
+           (ange-ftp-file-directory-p
             (ange-ftp-expand-symlink file-ent
                                      (file-name-directory
                                       (directory-file-name name))))
             (ange-ftp-expand-symlink file-ent
                                      (file-name-directory
                                       (directory-file-name name))))
@@ -3503,7 +3447,7 @@ system TYPE.")
          (nreverse files)))
     (apply 'ange-ftp-real-directory-files directory full match v19-args)))
 
          (nreverse files)))
     (apply 'ange-ftp-real-directory-files directory full match v19-args)))
 
-(defun ange-ftp-file-attributes (file)
+(defun ange-ftp-file-attributes (file &optional id-format)
   (setq file (expand-file-name file))
   (let ((parsed (ange-ftp-ftp-name file)))
     (if parsed
   (setq file (expand-file-name file))
   (let ((parsed (ange-ftp-ftp-name file)))
     (if parsed
@@ -3513,13 +3457,12 @@ system TYPE.")
              (let ((host (nth 0 parsed))
                    (user (nth 1 parsed))
                    (name (nth 2 parsed))
              (let ((host (nth 0 parsed))
                    (user (nth 1 parsed))
                    (name (nth 2 parsed))
-                   (dirp (ange-ftp-get-hash-entry part files))
-                   (inode (ange-ftp-get-hash-entry
-                           file ange-ftp-inodes-hashtable)))
+                   (dirp (gethash part files))
+                   (inode (gethash file ange-ftp-inodes-hashtable)))
                (unless inode
                  (setq inode ange-ftp-next-inode-number
                        ange-ftp-next-inode-number (1+ inode))
                (unless inode
                  (setq inode ange-ftp-next-inode-number
                        ange-ftp-next-inode-number (1+ inode))
-                 (ange-ftp-put-hash-entry file inode ange-ftp-inodes-hashtable))
+                 (puthash file inode ange-ftp-inodes-hashtable))
                (list (if (and (stringp dirp) (file-name-absolute-p dirp))
                          (ange-ftp-expand-symlink dirp
                                                   (file-name-directory file))
                (list (if (and (stringp dirp) (file-name-absolute-p dirp))
                          (ange-ftp-expand-symlink dirp
                                                   (file-name-directory file))
@@ -3537,7 +3480,9 @@ system TYPE.")
                      inode             ;10 "inode number".
                      -1                ;11 device number [v19 only]
                      ))))
                      inode             ;10 "inode number".
                      -1                ;11 device number [v19 only]
                      ))))
-      (ange-ftp-real-file-attributes file))))
+      (if id-format
+         (ange-ftp-real-file-attributes file id-format)
+       (ange-ftp-real-file-attributes file)))))
 
 (defun ange-ftp-file-newer-than-file-p (f1 f2)
   (let ((f1-parsed (ange-ftp-ftp-name f1))
 
 (defun ange-ftp-file-newer-than-file-p (f1 f2)
   (let ((f1-parsed (ange-ftp-ftp-name f1))
@@ -3661,14 +3606,13 @@ Value is (0 0) if the modification time cannot be determined."
 ;;                          filename
 ;;                          newname))
 ;;     res)
 ;;                          filename
 ;;                          newname))
 ;;     res)
-;;     (set-process-sentinel proc (function ange-ftp-copy-file-locally-sentinel))
+;;     (set-process-sentinel proc 'ange-ftp-copy-file-locally-sentinel)
 ;;     (process-kill-without-query proc)
 ;;     (with-current-buffer (process-buffer proc)
 ;;       (set (make-local-variable 'copy-cont) cont))))
 ;;
 ;; (defun ange-ftp-copy-file-locally-sentinel (proc status)
 ;;     (process-kill-without-query proc)
 ;;     (with-current-buffer (process-buffer proc)
 ;;       (set (make-local-variable 'copy-cont) cont))))
 ;;
 ;; (defun ange-ftp-copy-file-locally-sentinel (proc status)
-;;   (save-excursion
-;;     (set-buffer (process-buffer proc))
+;;   (with-current-buffer (process-buffer proc)
 ;;     (let ((cont copy-cont)
 ;;       (result (buffer-string)))
 ;;       (unwind-protect
 ;;     (let ((cont copy-cont)
 ;;       (result (buffer-string)))
 ;;       (unwind-protect
@@ -3749,7 +3693,7 @@ Value is (0 0) if the modification time cannot be determined."
                   (if (and temp1 t-parsed)
                       (format "Getting %s" f-abbr)
                     (format "Copying %s to %s" f-abbr t-abbr)))
                   (if (and temp1 t-parsed)
                       (format "Getting %s" f-abbr)
                     (format "Copying %s to %s" f-abbr t-abbr)))
-              (list (function ange-ftp-cf1)
+              (list 'ange-ftp-cf1
                     filename newname binary msg
                     f-parsed f-host f-user f-name f-abbr
                     t-parsed t-host t-user t-name t-abbr
                     filename newname binary msg
                     f-parsed f-host f-user f-name f-abbr
                     t-parsed t-host t-user t-name t-abbr
@@ -3827,7 +3771,7 @@ Value is (0 0) if the modification time cannot be determined."
                 (if (and temp2 f-parsed)
                     (format "Putting %s" newname)
                   (format "Copying %s to %s" f-abbr t-abbr)))
                 (if (and temp2 f-parsed)
                     (format "Putting %s" newname)
                   (format "Copying %s to %s" f-abbr t-abbr)))
-            (list (function ange-ftp-cf2)
+            (list 'ange-ftp-cf2
                   newname t-host t-user binary temp1 temp2 cont)
             nowait))
 
                   newname t-host t-user binary temp1 temp2 cont)
             nowait))
 
@@ -3902,7 +3846,7 @@ E.g.,
          (and verbose-p (format "%s --> %s" from-file to-file))
          (list 'ange-ftp-copy-files-async verbose-p (cdr files))
          t))
          (and verbose-p (format "%s --> %s" from-file to-file))
          (list 'ange-ftp-copy-files-async verbose-p (cdr files))
          t))
-      (message "%s: done" 'ange-ftp-copy-files-async)))
+    (message "%s: done" 'ange-ftp-copy-files-async)))
 
 \f
 ;;;; ------------------------------------------------------------
 
 \f
 ;;;; ------------------------------------------------------------
@@ -3982,35 +3926,26 @@ E.g.,
 ;;;; File name completion support.
 ;;;; ------------------------------------------------------------
 
 ;;;; File name completion support.
 ;;;; ------------------------------------------------------------
 
-;; If the file entry SYM is a symlink, returns whether its file exists.
-;; Note that `ange-ftp-this-dir' is used as a free variable.
-(defun ange-ftp-file-entry-active-p (sym)
-  (let ((val (get sym 'val)))
-    (or (not (stringp val))
-       (file-exists-p (ange-ftp-expand-symlink val ange-ftp-this-dir)))))
-
 ;; If the file entry is not a directory (nor a symlink pointing to a directory)
 ;; returns whether the file (or file pointed to by the symlink) is ignored
 ;; by completion-ignored-extensions.
 ;; Note that `ange-ftp-this-dir' and `ange-ftp-completion-ignored-pattern'
 ;; are used as free variables.
 ;; If the file entry is not a directory (nor a symlink pointing to a directory)
 ;; returns whether the file (or file pointed to by the symlink) is ignored
 ;; by completion-ignored-extensions.
 ;; Note that `ange-ftp-this-dir' and `ange-ftp-completion-ignored-pattern'
 ;; are used as free variables.
-(defun ange-ftp-file-entry-not-ignored-p (sym)
-  (let ((val (get sym 'val))
-       (symname (symbol-name sym)))
-    (if (stringp val)
-       (let ((file (ange-ftp-expand-symlink val ange-ftp-this-dir)))
-         (or (file-directory-p file)
-             (and (file-exists-p file)
-                  (not (string-match ange-ftp-completion-ignored-pattern
-                                     symname)))))
-      (or val ; is a directory name
-         (not (string-match ange-ftp-completion-ignored-pattern symname))))))
+(defun ange-ftp-file-entry-not-ignored-p (symname val)
+  (if (stringp val)
+      (let ((file (ange-ftp-expand-symlink val ange-ftp-this-dir)))
+       (or (file-directory-p file)
+           (and (file-exists-p file)
+                (not (string-match ange-ftp-completion-ignored-pattern
+                                   symname)))))
+    (or val                            ; is a directory name
+       (not (string-match ange-ftp-completion-ignored-pattern symname)))))
 
 (defun ange-ftp-root-dir-p (dir)
   ;; Maybe we should use something more like
   ;; (equal dir (file-name-directory (directory-file-name dir)))  -stef
   (or (and (eq system-type 'windows-nt)
 
 (defun ange-ftp-root-dir-p (dir)
   ;; Maybe we should use something more like
   ;; (equal dir (file-name-directory (directory-file-name dir)))  -stef
   (or (and (eq system-type 'windows-nt)
-          (string-match "^[a-zA-Z]:[/\\]$" dir))
+          (string-match "\\`[a-zA-Z]:[/\\]\\'" dir))
       (string-equal "/" dir)))
 
 (defun ange-ftp-file-name-all-completions (file dir)
       (string-equal "/" dir)))
 
 (defun ange-ftp-file-name-all-completions (file dir)
@@ -4021,21 +3956,19 @@ E.g.,
          (setq ange-ftp-this-dir
                (ange-ftp-real-file-name-as-directory ange-ftp-this-dir))
          (let* ((tbl (ange-ftp-get-files ange-ftp-this-dir))
          (setq ange-ftp-this-dir
                (ange-ftp-real-file-name-as-directory ange-ftp-this-dir))
          (let* ((tbl (ange-ftp-get-files ange-ftp-this-dir))
-                (completions
-                 (all-completions file tbl
-                                  (function ange-ftp-file-entry-active-p))))
+                (completions (all-completions file tbl)))
 
            ;; see whether each matching file is a directory or not...
            (mapcar
              (lambda (file)
 
            ;; see whether each matching file is a directory or not...
            (mapcar
              (lambda (file)
-               (let ((ent (ange-ftp-get-hash-entry file tbl)))
+               (let ((ent (gethash file tbl)))
                  (if (and ent
                           (or (not (stringp ent))
                               (file-directory-p
                                (ange-ftp-expand-symlink ent
                                                         ange-ftp-this-dir))))
                      (concat file "/")
                  (if (and ent
                           (or (not (stringp ent))
                               (file-directory-p
                                (ange-ftp-expand-symlink ent
                                                         ange-ftp-this-dir))))
                      (concat file "/")
-                    file)))
+                  file)))
             completions)))
 
       (if (ange-ftp-root-dir-p ange-ftp-this-dir)
             completions)))
 
       (if (ange-ftp-root-dir-p ange-ftp-this-dir)
@@ -4044,7 +3977,7 @@ E.g.,
                                                          ange-ftp-this-dir))
        (ange-ftp-real-file-name-all-completions file ange-ftp-this-dir)))))
 
                                                          ange-ftp-this-dir))
        (ange-ftp-real-file-name-all-completions file ange-ftp-this-dir)))))
 
-(defun ange-ftp-file-name-completion (file dir)
+(defun ange-ftp-file-name-completion (file dir &optional predicate)
   (let ((ange-ftp-this-dir (expand-file-name dir)))
     (if (ange-ftp-ftp-name ange-ftp-this-dir)
        (progn
   (let ((ange-ftp-this-dir (expand-file-name dir)))
     (if (ange-ftp-ftp-name ange-ftp-this-dir)
        (progn
@@ -4056,28 +3989,32 @@ E.g.,
            (let* ((tbl (ange-ftp-get-files ange-ftp-this-dir))
                   (ange-ftp-completion-ignored-pattern
                    (mapconcat (lambda (s) (if (stringp s)
            (let* ((tbl (ange-ftp-get-files ange-ftp-this-dir))
                   (ange-ftp-completion-ignored-pattern
                    (mapconcat (lambda (s) (if (stringp s)
-                                               (concat (regexp-quote s) "$")
-                                            "/")) ; / never in filename
+                                          (concat (regexp-quote s) "$")
+                                        "/")) ; / never in filename
                               completion-ignored-extensions
                               "\\|")))
              (save-match-data
                (or (ange-ftp-file-name-completion-1
                     file tbl ange-ftp-this-dir
                               completion-ignored-extensions
                               "\\|")))
              (save-match-data
                (or (ange-ftp-file-name-completion-1
                     file tbl ange-ftp-this-dir
-                    (function ange-ftp-file-entry-not-ignored-p))
+                    'ange-ftp-file-entry-not-ignored-p)
                    (ange-ftp-file-name-completion-1
                    (ange-ftp-file-name-completion-1
-                    file tbl ange-ftp-this-dir
-                    (function ange-ftp-file-entry-active-p)))))))
+                    file tbl ange-ftp-this-dir))))))
 
       (if (ange-ftp-root-dir-p ange-ftp-this-dir)
          (try-completion
           file
           (nconc (ange-ftp-generate-root-prefixes)
                  (ange-ftp-real-file-name-all-completions
 
       (if (ange-ftp-root-dir-p ange-ftp-this-dir)
          (try-completion
           file
           (nconc (ange-ftp-generate-root-prefixes)
                  (ange-ftp-real-file-name-all-completions
-                  file ange-ftp-this-dir)))
-       (ange-ftp-real-file-name-completion file ange-ftp-this-dir)))))
+                  file ange-ftp-this-dir))
+          predicate)
+       (if predicate
+           (ange-ftp-real-file-name-completion
+            file ange-ftp-this-dir predicate)
+         (ange-ftp-real-file-name-completion
+          file ange-ftp-this-dir))))))
 
 
 
 
-(defun ange-ftp-file-name-completion-1 (file tbl dir predicate)
+(defun ange-ftp-file-name-completion-1 (file tbl dir &optional predicate)
   (let ((bestmatch (try-completion file tbl predicate)))
     (if bestmatch
        (if (eq bestmatch t)
   (let ((bestmatch (try-completion file tbl predicate)))
     (if bestmatch
        (if (eq bestmatch t)
@@ -4113,7 +4050,7 @@ directory, so that Emacs will know its current contents."
   (if (ange-ftp-ftp-name dir)
       (progn
        (setq ange-ftp-ls-cache-file nil)
   (if (ange-ftp-ftp-name dir)
       (progn
        (setq ange-ftp-ls-cache-file nil)
-       (ange-ftp-del-hash-entry dir ange-ftp-files-hashtable)
+       (remhash dir ange-ftp-files-hashtable)
        (ange-ftp-get-files dir t))))
 \f
 (defun ange-ftp-make-directory (dir &optional parents)
        (ange-ftp-get-files dir t))))
 \f
 (defun ange-ftp-make-directory (dir &optional parents)
@@ -4170,11 +4107,11 @@ directory, so that Emacs will know its current contents."
                               (nth 2 parsed))
                            (ange-ftp-real-file-name-as-directory
                             (nth 2 parsed)))))
                               (nth 2 parsed))
                            (ange-ftp-real-file-name-as-directory
                             (nth 2 parsed)))))
-                 (abbr (ange-ftp-abbreviate-filename dir))
-                 (result (ange-ftp-send-cmd host user
-                                            (list 'rmdir name)
-                                            (format "Removing directory %s"
-                                                    abbr))))
+                  (abbr (ange-ftp-abbreviate-filename dir))
+                  (result (ange-ftp-send-cmd host user
+                                             (list 'rmdir name)
+                                             (format "Removing directory %s"
+                                                     abbr))))
              (or (car result)
                  (ange-ftp-error host user
                                  (format "Could not remove directory %s: %s"
              (or (car result)
                  (ange-ftp-error host user
                                  (format "Could not remove directory %s: %s"
@@ -4195,6 +4132,16 @@ directory, so that Emacs will know its current contents."
                                       (format "Getting %s" fn1))
          tmp1))))
 
                                       (format "Getting %s" fn1))
          tmp1))))
 
+(defun ange-ftp-file-remote-p (file &optional connected)
+  (and (or (not connected)
+          (let* ((parsed (ange-ftp-ftp-name file))
+                 (host (nth 0 parsed))
+                 (user (nth 1 parsed))
+                 (proc (get-process (ange-ftp-ftp-process-buffer host user))))
+            (and proc (processp proc)
+                 (memq (process-status proc) '(run open)))))
+       (ange-ftp-replace-name-component file "")))
+
 (defun ange-ftp-load (file &optional noerror nomessage nosuffix)
   (if (ange-ftp-ftp-name file)
       (let ((tryfiles (if nosuffix
 (defun ange-ftp-load (file &optional noerror nomessage nosuffix)
   (if (ange-ftp-ftp-name file)
       (let ((tryfiles (if nosuffix
@@ -4337,42 +4284,42 @@ NEWNAME should be the name to give the new compressed or uncompressed file.")
     (if fn (save-match-data (apply fn args))
       (ange-ftp-run-real-handler operation args))))
 
     (if fn (save-match-data (apply fn args))
       (ange-ftp-run-real-handler operation args))))
 
-
-;;; This regexp takes care of real ange-ftp file names (with a slash
-;;; and colon).
-;;; Don't allow the host name to end in a period--some systems use /.:
-;;;###autoload
-(or (assoc "^/[^/:]*[^/:.]:" file-name-handler-alist)
-    (setq file-name-handler-alist
-         (cons '("^/[^/:]*[^/:.]:" . ange-ftp-hook-function)
-               file-name-handler-alist)))
-
-;;; This regexp recognizes absolute filenames with only one component,
-;;; for the sake of hostname completion.
-;;;###autoload
-(or (assoc "^/[^/:]*\\'" file-name-handler-alist)
-    (setq file-name-handler-alist
-         (cons '("^/[^/:]*\\'" . ange-ftp-completion-hook-function)
-               file-name-handler-alist)))
-
-;;; This regexp recognizes absolute filenames with only one component
-;;; on Windows, for the sake of hostname completion.
-;;; NB. Do not mark this as autoload, because it is very common to
-;;; do completions in the root directory of drives on Windows.
-(and (memq system-type '(ms-dos windows-nt))
-     (or (assoc "^[a-zA-Z]:/[^/:]*\\'" file-name-handler-alist)
-        (setq file-name-handler-alist
-              (cons '("^[a-zA-Z]:/[^/:]*\\'" .
-                      ange-ftp-completion-hook-function)
-                    file-name-handler-alist))))
+;; The following code is commented out because Tramp now deals with
+;; Ange-FTP filenames, too.
+
+;;-;;; This regexp takes care of real ange-ftp file names (with a slash
+;;-;;; and colon).
+;;-;;; Don't allow the host name to end in a period--some systems use /.:
+;;-;;;###autoload
+;;-(or (assoc "^/[^/:]*[^/:.]:" file-name-handler-alist)
+;;-    (setq file-name-handler-alist
+;;-      (cons '("^/[^/:]*[^/:.]:" . ange-ftp-hook-function)
+;;-            file-name-handler-alist)))
+;;-
+;;-;;; This regexp recognizes absolute filenames with only one component,
+;;-;;; for the sake of hostname completion.
+;;-;;;###autoload
+;;-(or (assoc "^/[^/:]*\\'" file-name-handler-alist)
+;;-    (setq file-name-handler-alist
+;;-      (cons '("^/[^/:]*\\'" . ange-ftp-completion-hook-function)
+;;-            file-name-handler-alist)))
+;;-
+;;-;;; This regexp recognizes absolute filenames with only one component
+;;-;;; on Windows, for the sake of hostname completion.
+;;-;;; NB. Do not mark this as autoload, because it is very common to
+;;-;;; do completions in the root directory of drives on Windows.
+;;-(and (memq system-type '(ms-dos windows-nt))
+;;-     (or (assoc "^[a-zA-Z]:/[^/:]*\\'" file-name-handler-alist)
+;;-     (setq file-name-handler-alist
+;;-           (cons '("^[a-zA-Z]:/[^/:]*\\'" .
+;;-                   ange-ftp-completion-hook-function)
+;;-                 file-name-handler-alist))))
 
 ;;; The above two forms are sufficient to cause this file to be loaded
 ;;; if the user ever uses a file name with a colon in it.
 
 ;;; This sets the mode
 
 ;;; The above two forms are sufficient to cause this file to be loaded
 ;;; if the user ever uses a file name with a colon in it.
 
 ;;; This sets the mode
-(or (memq 'ange-ftp-set-buffer-mode find-file-hooks)
-    (setq find-file-hooks
-         (cons 'ange-ftp-set-buffer-mode find-file-hooks)))
+(add-hook 'find-file-hook 'ange-ftp-set-buffer-mode)
 
 ;;; Now say where to find the handlers for particular operations.
 
 
 ;;; Now say where to find the handlers for particular operations.
 
@@ -4403,6 +4350,7 @@ NEWNAME should be the name to give the new compressed or uncompressed file.")
 (put 'file-name-completion 'ange-ftp 'ange-ftp-file-name-completion)
 (put 'insert-directory 'ange-ftp 'ange-ftp-insert-directory)
 (put 'file-local-copy 'ange-ftp 'ange-ftp-file-local-copy)
 (put 'file-name-completion 'ange-ftp 'ange-ftp-file-name-completion)
 (put 'insert-directory 'ange-ftp 'ange-ftp-insert-directory)
 (put 'file-local-copy 'ange-ftp 'ange-ftp-file-local-copy)
+(put 'file-remote-p 'ange-ftp 'ange-ftp-file-remote-p)
 (put 'unhandled-file-name-directory 'ange-ftp
      'ange-ftp-unhandled-file-name-directory)
 (put 'file-name-sans-versions 'ange-ftp 'ange-ftp-file-name-sans-versions)
 (put 'unhandled-file-name-directory 'ange-ftp
      'ange-ftp-unhandled-file-name-directory)
 (put 'file-name-sans-versions 'ange-ftp 'ange-ftp-file-name-sans-versions)
@@ -4419,7 +4367,10 @@ NEWNAME should be the name to give the new compressed or uncompressed file.")
 ;; This returns nil for any file name as argument.
 (put 'vc-registered 'ange-ftp 'null)
 
 ;; This returns nil for any file name as argument.
 (put 'vc-registered 'ange-ftp 'null)
 
-(put 'dired-call-process 'ange-ftp 'ange-ftp-dired-call-process)
+;; We can handle process-file in a restricted way (just for chown).
+;; Nothing possible for start-file-process.
+(put 'process-file 'ange-ftp 'ange-ftp-process-file)
+(put 'start-file-process 'ange-ftp 'ignore)
 (put 'shell-command 'ange-ftp 'ange-ftp-shell-command)
 \f
 ;;; Define ways of getting at unmodified Emacs primitives,
 (put 'shell-command 'ange-ftp 'ange-ftp-shell-command)
 \f
 ;;; Define ways of getting at unmodified Emacs primitives,
@@ -4507,22 +4458,42 @@ NEWNAME should be the name to give the new compressed or uncompressed file.")
 ;; I have preserved (and modernized) those hooks.
 ;; So the format conversion should be all that is needed.
 
 ;; I have preserved (and modernized) those hooks.
 ;; So the format conversion should be all that is needed.
 
+;; When called from dired, SWITCHES may start with "--dired".
+;; `ange-ftp-ls' handles this.
+
 (defun ange-ftp-insert-directory (file switches &optional wildcard full)
 (defun ange-ftp-insert-directory (file switches &optional wildcard full)
-  (let ((short (ange-ftp-abbreviate-filename file))
-       (parsed (ange-ftp-ftp-name (expand-file-name file)))
-       tem)
-    (if parsed
-       (if (and (not wildcard)
-                (setq tem (file-symlink-p (directory-file-name file))))
-           (ange-ftp-insert-directory
-            (ange-ftp-replace-name-component file tem)
-            switches wildcard full)
-         (insert
-          (if wildcard
-              (let ((default-directory (file-name-directory file)))
-                (ange-ftp-ls (file-name-nondirectory file) switches nil nil t))
-            (ange-ftp-ls file switches full))))
-      (ange-ftp-real-insert-directory file switches wildcard full))))
+  (if (not (ange-ftp-ftp-name (expand-file-name file)))
+      (ange-ftp-real-insert-directory file switches wildcard full)
+    ;; We used to follow symlinks on `file' here.  Apparently it was done
+    ;; because some FTP servers react to "ls foo" by listing the symlink foo
+    ;; rather than the directory it points to.  Now that ange-ftp-ls uses
+    ;; "cd foo; ls" instead, this is not necesssary any more.
+    (insert
+     (cond
+      (wildcard
+       (let ((default-directory (file-name-directory file)))
+         (ange-ftp-ls (file-name-nondirectory file) switches nil nil t)))
+      (full
+       (ange-ftp-ls file switches 'parse))
+      (t
+       ;; If `full' is nil we're going to do `ls' for a single file.
+       ;; Problem is that for various reasons, ange-ftp-ls needs to cd and
+       ;; then do an ls of current dir, which obviously won't work if we
+       ;; want to ls a file.  So instead, we get a full listing of the
+       ;; parent directory and extract the line corresponding to `file'.
+       (when (string-match "d\\'" switches)
+         ;; Remove "d" which dired added to `switches'.
+         (setq switches (substring switches 0 (match-beginning 0))))
+       (let* ((dirlist (ange-ftp-ls (or (file-name-directory file) ".")
+                                    switches nil))
+              (filename (file-name-nondirectory (directory-file-name file)))
+              (case-fold-search nil))
+         ;; FIXME: This presumes a particular output format, which is
+         ;; basically Unix.
+         (if (string-match (concat "^.+[^ ] " (regexp-quote filename)
+                                   "\\( -> .*\\)?[@/*=]?\n") dirlist)
+             (match-string 0 dirlist)
+           "")))))))
 
 (defun ange-ftp-dired-uncache (dir)
   (if (ange-ftp-ftp-name (expand-file-name dir))
 
 (defun ange-ftp-dired-uncache (dir)
   (if (ange-ftp-ftp-name (expand-file-name dir))
@@ -4534,11 +4505,8 @@ NEWNAME should be the name to give the new compressed or uncompressed file.")
 (defun ange-ftp-file-name-sans-versions (file keep-backup-version)
   (let* ((short (ange-ftp-abbreviate-filename file))
         (parsed (ange-ftp-ftp-name short))
 (defun ange-ftp-file-name-sans-versions (file keep-backup-version)
   (let* ((short (ange-ftp-abbreviate-filename file))
         (parsed (ange-ftp-ftp-name short))
-        host-type func)
-    (if parsed
-       (setq host-type (ange-ftp-host-type (car parsed))
-             func (cdr (assq (ange-ftp-host-type (car parsed))
-                             ange-ftp-sans-version-alist))))
+        (func (if parsed (cdr (assq (ange-ftp-host-type (car parsed))
+                                     ange-ftp-sans-version-alist)))))
     (if func (funcall func file keep-backup-version)
       (ange-ftp-real-file-name-sans-versions file keep-backup-version))))
 
     (if func (funcall func file keep-backup-version)
       (ange-ftp-real-file-name-sans-versions file keep-backup-version))))
 
@@ -4565,8 +4533,8 @@ NEWNAME should be the name to give the new compressed or uncompressed file.")
       ;; default-directory is in ange-ftp syntax for remote file names.
       (ange-ftp-real-shell-command command output-buffer error-buffer))))
 
       ;; default-directory is in ange-ftp syntax for remote file names.
       (ange-ftp-real-shell-command command output-buffer error-buffer))))
 
-;;; This is the handler for call-process.
-(defun ange-ftp-dired-call-process (program discard &rest arguments)
+;;; This is the handler for process-file.
+(defun ange-ftp-process-file (program infile buffer display &rest arguments)
   ;; PROGRAM is always one of those below in the cond in dired.el.
   ;; The ARGUMENTS are (nearly) always files.
   (if (ange-ftp-ftp-name default-directory)
   ;; PROGRAM is always one of those below in the cond in dired.el.
   ;; The ARGUMENTS are (nearly) always files.
   (if (ange-ftp-ftp-name default-directory)
@@ -4579,17 +4547,14 @@ NEWNAME should be the name to give the new compressed or uncompressed file.")
                ;; ((equal dired-chown-program program))
                (t (error "Unknown remote command: %s" program)))
        (ftp-error (insert (format "%s: %s, %s\n"
                ;; ((equal dired-chown-program program))
                (t (error "Unknown remote command: %s" program)))
        (ftp-error (insert (format "%s: %s, %s\n"
-                                   (nth 1 oops)
-                                   (nth 2 oops)
-                                   (nth 3 oops)))
+                                  (nth 1 oops)
+                                  (nth 2 oops)
+                                  (nth 3 oops)))
                   ;; Caller expects nonzero value to mean failure.
                   1)
        (error (insert (format "%s\n" (nth 1 oops)))
               1))
                   ;; Caller expects nonzero value to mean failure.
                   1)
        (error (insert (format "%s\n" (nth 1 oops)))
               1))
-    (apply 'call-process program nil (not discard) nil arguments)))
-
-(defvar ange-ftp-remote-shell "rsh"
-  "Remote shell to use for chmod, if FTP server rejects the `chmod' command.")
+    (apply 'call-process program infile buffer display arguments)))
 
 ;; Handle an attempt to run chmod on a remote file
 ;; by using the ftp chmod command.
 
 ;; Handle an attempt to run chmod on a remote file
 ;; by using the ftp chmod command.
@@ -4615,15 +4580,15 @@ NEWNAME should be the name to give the new compressed or uncompressed file.")
                                                        abbr))))
                (or (car result)
                    (call-process
                                                        abbr))))
                (or (car result)
                    (call-process
-                    ange-ftp-remote-shell
+                    remote-shell-program
                     nil t nil host dired-chmod-program mode name))))))
      rest))
   (setq ange-ftp-ls-cache-file nil)    ;Stop confusing Dired.
   0)
 \f
                     nil t nil host dired-chmod-program mode name))))))
      rest))
   (setq ange-ftp-ls-cache-file nil)    ;Stop confusing Dired.
   0)
 \f
-;;; This is turned off because it has nothing properly to do
-;;; with dired.  It could be reasonable to adapt this to
-;;; replace ange-ftp-copy-file.
+;; This is turned off because it has nothing properly to do
+;; with dired.  It could be reasonable to adapt this to
+;; replace ange-ftp-copy-file.
 
 ;;;;; ------------------------------------------------------------
 ;;;;; Noddy support for async copy-file within dired.
 
 ;;;;; ------------------------------------------------------------
 ;;;;; Noddy support for async copy-file within dired.
@@ -4685,10 +4650,7 @@ NEWNAME should be the name to give the new compressed or uncompressed file.")
 ;;                    target marker-char buffer overwrite-query
 ;;                    overwrite-backup-query failures skipped
 ;;                    success-count total)
 ;;                    target marker-char buffer overwrite-query
 ;;                    overwrite-backup-query failures skipped
 ;;                    success-count total)
-;;  (let ((old-buf (current-buffer)))
-;;    (unwind-protect
-;;     (progn
-;;       (set-buffer buffer)
+;;  (with-current-buffer buffer
 ;;       (if (null fn-list)
 ;;           (ange-ftp-dcf-3 failures operation total skipped
 ;;                           success-count buffer)
 ;;       (if (null fn-list)
 ;;           (ange-ftp-dcf-3 failures operation total skipped
 ;;                           success-count buffer)
@@ -4732,7 +4694,7 @@ NEWNAME should be the name to give the new compressed or uncompressed file.")
 ;;                            (t nil))))
 ;;               (condition-case err
 ;;                   (funcall file-creator from to overwrite-confirmed
 ;;                            (t nil))))
 ;;               (condition-case err
 ;;                   (funcall file-creator from to overwrite-confirmed
-;;                            (list (function ange-ftp-dcf-2)
+;;                            (list 'ange-ftp-dcf-2
 ;;                                  nil        ;err
 ;;                                  file-creator operation fn-list
 ;;                                  name-constructor
 ;;                                  nil        ;err
 ;;                                  file-creator operation fn-list
 ;;                                  name-constructor
@@ -4760,8 +4722,7 @@ NEWNAME should be the name to give the new compressed or uncompressed file.")
 ;;                                  overwrite-query
 ;;                                  overwrite-backup-query
 ;;                                  failures skipped success-count
 ;;                                  overwrite-query
 ;;                                  overwrite-backup-query
 ;;                                  failures skipped success-count
-;;                                  total))))))))
-;;      (set-buffer old-buf))))
+;;                                  total)))))))))
 
 ;;(defun ange-ftp-dcf-2 (result line err
 ;;                           file-creator operation fn-list
 
 ;;(defun ange-ftp-dcf-2 (result line err
 ;;                           file-creator operation fn-list
@@ -4775,10 +4736,7 @@ NEWNAME should be the name to give the new compressed or uncompressed file.")
 ;;                           overwrite-backup-query
 ;;                           failures skipped success-count
 ;;                           total)
 ;;                           overwrite-backup-query
 ;;                           failures skipped success-count
 ;;                           total)
-;;  (let ((old-buf (current-buffer)))
-;;    (unwind-protect
-;;     (progn
-;;       (set-buffer buffer)
+;;  (with-current-buffer buffer
 ;;       (if (or err (not result))
 ;;           (progn
 ;;             (setq failures (cons (dired-make-relative from) failures))
 ;;       (if (or err (not result))
 ;;           (progn
 ;;             (setq failures (cons (dired-make-relative from) failures))
@@ -4801,15 +4759,11 @@ NEWNAME should be the name to give the new compressed or uncompressed file.")
 ;;                       overwrite-query
 ;;                       overwrite-backup-query
 ;;                       failures skipped success-count
 ;;                       overwrite-query
 ;;                       overwrite-backup-query
 ;;                       failures skipped success-count
-;;                       total))
-;;      (set-buffer old-buf))))
+;;                       total)))
 
 ;;(defun ange-ftp-dcf-3 (failures operation total skipped success-count
 ;;                             buffer)
 
 ;;(defun ange-ftp-dcf-3 (failures operation total skipped success-count
 ;;                             buffer)
-;;  (let ((old-buf (current-buffer)))
-;;    (unwind-protect
-;;     (progn
-;;       (set-buffer buffer)
+;;  (with-current-buffer buffer
 ;;       (cond
 ;;        (failures
 ;;         (dired-log-summary
 ;;       (cond
 ;;        (failures
 ;;         (dired-log-summary
@@ -4824,8 +4778,7 @@ NEWNAME should be the name to give the new compressed or uncompressed file.")
 ;;        (t
 ;;         (message "%s: %s file%s."
 ;;                  operation success-count (dired-plural-s success-count))))
 ;;        (t
 ;;         (message "%s: %s file%s."
 ;;                  operation success-count (dired-plural-s success-count))))
-;;       (dired-move-to-filename))
-;;      (set-buffer old-buf))))
+;;       (dired-move-to-filename)))
 \f
 ;;;; -----------------------------------------------
 ;;;; Unix Descriptive Listing (dl) Support
 \f
 ;;;; -----------------------------------------------
 ;;;; Unix Descriptive Listing (dl) Support
@@ -4956,10 +4909,10 @@ NEWNAME should be the name to give the new compressed or uncompressed file.")
 ;                                         (progn
 ;                                           (end-of-line 1)
 ;                                           (point))))
 ;                                         (progn
 ;                                           (end-of-line 1)
 ;                                           (point))))
-;            (ange-ftp-put-hash-entry file type-is-dir tbl)
+;            (puthash file type-is-dir tbl)
 ;            (forward-line 1))))
 ;            (forward-line 1))))
-;      (ange-ftp-put-hash-entry "." 'vosdir tbl)
-;      (ange-ftp-put-hash-entry ".." 'vosdir tbl))
+;      (puthash "." 'vosdir tbl)
+;      (puthash ".." 'vosdir tbl))
 ;    tbl))
 ;
 ;(or (assq 'vos ange-ftp-parse-list-func-alist)
 ;    tbl))
 ;
 ;(or (assq 'vos ange-ftp-parse-list-func-alist)
@@ -4976,18 +4929,11 @@ NEWNAME should be the name to give the new compressed or uncompressed file.")
 (defun ange-ftp-fix-name-for-vms (name &optional reverse)
   (save-match-data
     (if reverse
 (defun ange-ftp-fix-name-for-vms (name &optional reverse)
   (save-match-data
     (if reverse
-       (if (string-match "^\\([^:]+:\\)?\\(\\[.*\\]\\)?\\([^][]*\\)$" name)
+       (if (string-match "\\`\\([^:]+:\\)?\\(\\[.*\\]\\)?\\([^][]*\\)\\'" name)
            (let (drive dir file)
            (let (drive dir file)
-             (if (match-beginning 1)
-                 (setq drive (substring name
-                                        (match-beginning 1)
-                                        (match-end 1))))
-             (if (match-beginning 2)
-                 (setq dir
-                       (substring name (match-beginning 2) (match-end 2))))
-             (if (match-beginning 3)
-                 (setq file
-                       (substring name (match-beginning 3) (match-end 3))))
+             (setq drive (match-string 1 name))
+             (setq dir (match-string 2 name))
+             (setq file (match-string 3 name))
              (and dir
                   (setq dir (subst-char-in-string
                               ?/ ?. (substring dir 1 -1) t)))
              (and dir
                   (setq dir (subst-char-in-string
                               ?/ ?. (substring dir 1 -1) t)))
@@ -4997,7 +4943,7 @@ NEWNAME should be the name to give the new compressed or uncompressed file.")
                      file))
          (error "name %s didn't match" name))
       (let (drive dir file tmp)
                      file))
          (error "name %s didn't match" name))
       (let (drive dir file tmp)
-       (if (string-match "^/[^:]+:/" name)
+       (if (string-match "\\`/[^:]+:/" name)
            (setq drive (substring name 1
                                   (1- (match-end 0)))
                  name (substring name (match-end 0))))
            (setq drive (substring name 1
                                   (1- (match-end 0)))
                  name (substring name (match-end 0))))
@@ -5035,7 +4981,7 @@ NEWNAME should be the name to give the new compressed or uncompressed file.")
   ;; them.
   (cond ((string-equal dir-name "/")
         (error "Cannot get listing for fictitious \"/\" directory"))
   ;; them.
   (cond ((string-equal dir-name "/")
         (error "Cannot get listing for fictitious \"/\" directory"))
-       ((string-match "^/[-A-Z0-9_$]+:/$" dir-name)
+       ((string-match "\\`/[-A-Z0-9_$]+:/\\'" dir-name)
         (error "Cannot get listing for device"))
        ((ange-ftp-fix-name-for-vms dir-name))))
 
         (error "Cannot get listing for device"))
        ((ange-ftp-fix-name-for-vms dir-name))))
 
@@ -5073,40 +5019,36 @@ Other orders of $ and _ seem to all work just fine.")
 ;; Extract the next filename from a VMS dired-like listing.
 (defun ange-ftp-parse-vms-filename ()
   (if (re-search-forward
 ;; Extract the next filename from a VMS dired-like listing.
 (defun ange-ftp-parse-vms-filename ()
   (if (re-search-forward
-        ange-ftp-vms-filename-regexp
-        nil t)
-       (buffer-substring (match-beginning 0) (match-end 0))))
+       ange-ftp-vms-filename-regexp
+       nil t)
+      (match-string 0)))
 
 ;; Parse the current buffer which is assumed to be in MultiNet FTP dir
 ;; format, and return a hashtable as the result.
 (defun ange-ftp-parse-vms-listing ()
 
 ;; Parse the current buffer which is assumed to be in MultiNet FTP dir
 ;; format, and return a hashtable as the result.
 (defun ange-ftp-parse-vms-listing ()
-  (let ((tbl (ange-ftp-make-hashtable))
+  (let ((tbl (make-hash-table :test 'equal))
        file)
     (goto-char (point-min))
     (save-match-data
       (while (setq file (ange-ftp-parse-vms-filename))
        (if (string-match "\\.\\(DIR\\|dir\\);[0-9]+" file)
            ;; deal with directories
        file)
     (goto-char (point-min))
     (save-match-data
       (while (setq file (ange-ftp-parse-vms-filename))
        (if (string-match "\\.\\(DIR\\|dir\\);[0-9]+" file)
            ;; deal with directories
-           (ange-ftp-put-hash-entry
-            (substring file 0 (match-beginning 0)) t tbl)
-         (ange-ftp-put-hash-entry file nil tbl)
-         (if (string-match ";[0-9]+$" file) ; deal with extension
+           (puthash (substring file 0 (match-beginning 0)) t tbl)
+         (puthash file nil tbl)
+         (if (string-match ";[0-9]+\\'" file) ; deal with extension
              ;; sans extension
              ;; sans extension
-             (ange-ftp-put-hash-entry
-              (substring file 0 (match-beginning 0)) nil tbl)))
+             (puthash (substring file 0 (match-beginning 0)) nil tbl)))
        (forward-line 1))
       ;; Would like to look for a "Total" line, or a "Directory" line to
       ;; make sure that the listing isn't complete garbage before putting
       ;; in "." and "..", but we can't even count on all VAX's giving us
       ;; either of these.
        (forward-line 1))
       ;; Would like to look for a "Total" line, or a "Directory" line to
       ;; make sure that the listing isn't complete garbage before putting
       ;; in "." and "..", but we can't even count on all VAX's giving us
       ;; either of these.
-          (ange-ftp-put-hash-entry "." t tbl)
-          (ange-ftp-put-hash-entry ".." t tbl))
+      (puthash "." t tbl)
+      (puthash ".." t tbl))
     tbl))
 
     tbl))
 
-(or (assq 'vms ange-ftp-parse-list-func-alist)
-    (setq ange-ftp-parse-list-func-alist
-         (cons '(vms . ange-ftp-parse-vms-listing)
-               ange-ftp-parse-list-func-alist)))
+(add-to-list 'ange-ftp-parse-list-func-alist
+            '(vms . ange-ftp-parse-vms-listing))
 
 ;; This version only deletes file entries which have
 ;; explicit version numbers, because that is all VMS allows.
 
 ;; This version only deletes file entries which have
 ;; explicit version numbers, because that is all VMS allows.
@@ -5119,13 +5061,12 @@ Other orders of $ and _ seem to all work just fine.")
       (ange-ftp-internal-delete-file-entry name t)
     (save-match-data
       (let ((file (ange-ftp-get-file-part name)))
       (ange-ftp-internal-delete-file-entry name t)
     (save-match-data
       (let ((file (ange-ftp-get-file-part name)))
-       (if (string-match ";[0-9]+$" file)
+       (if (string-match ";[0-9]+\\'" file)
            ;; In VMS you can't delete a file without an explicit
            ;; version number, or wild-card (e.g. FOO;*)
            ;; For now, we give up on wildcards.
            ;; In VMS you can't delete a file without an explicit
            ;; version number, or wild-card (e.g. FOO;*)
            ;; For now, we give up on wildcards.
-           (let ((files (ange-ftp-get-hash-entry
-                         (file-name-directory name)
-                         ange-ftp-files-hashtable)))
+           (let ((files (gethash (file-name-directory name)
+                                 ange-ftp-files-hashtable)))
              (if files
                  (let* ((root (substring file 0
                                          (match-beginning 0)))
              (if files
                  (let* ((root (substring file 0
                                          (match-beginning 0)))
@@ -5133,17 +5074,17 @@ Other orders of $ and _ seem to all work just fine.")
                                         (regexp-quote root)
                                         ";[0-9]+$"))
                         versions)
                                         (regexp-quote root)
                                         ";[0-9]+$"))
                         versions)
-                   (ange-ftp-del-hash-entry file files)
+                   (remhash file files)
                    ;; Now we need to check if there are any
                    ;; versions left. If not, then delete the
                    ;; root entry.
                    ;; Now we need to check if there are any
                    ;; versions left. If not, then delete the
                    ;; root entry.
-                   (mapatoms
-                    (lambda (sym)
-                      (and (string-match regexp (get sym 'key))
+                   (maphash
+                    (lambda (key val)
+                      (and (string-match regexp key)
                            (setq versions t)))
                     files)
                    (or versions
                            (setq versions t)))
                     files)
                    (or versions
-                       (ange-ftp-del-hash-entry root files))))))))))
+                       (remhash root files))))))))))
 
 (or (assq 'vms ange-ftp-delete-file-entry-alist)
     (setq ange-ftp-delete-file-entry-alist
 
 (or (assq 'vms ange-ftp-delete-file-entry-alist)
     (setq ange-ftp-delete-file-entry-alist
@@ -5153,38 +5094,31 @@ Other orders of $ and _ seem to all work just fine.")
 (defun ange-ftp-vms-add-file-entry (name &optional dir-p)
   (if dir-p
       (ange-ftp-internal-add-file-entry name t)
 (defun ange-ftp-vms-add-file-entry (name &optional dir-p)
   (if dir-p
       (ange-ftp-internal-add-file-entry name t)
-    (let ((files (ange-ftp-get-hash-entry
-                 (file-name-directory name)
-                 ange-ftp-files-hashtable)))
+    (let ((files (gethash (file-name-directory name)
+                         ange-ftp-files-hashtable)))
       (if files
          (let ((file (ange-ftp-get-file-part name)))
            (save-match-data
       (if files
          (let ((file (ange-ftp-get-file-part name)))
            (save-match-data
-             (if (string-match ";[0-9]+$" file)
-                 (ange-ftp-put-hash-entry
-                  (substring file 0 (match-beginning 0))
-                  nil files)
+             (if (string-match ";[0-9]+\\'" file)
+                 (puthash (substring file 0 (match-beginning 0)) nil files)
                ;; Need to figure out what version of the file
                ;; is being added.
                (let ((regexp (concat "^"
                                      (regexp-quote file)
                                      ";\\([0-9]+\\)$"))
                      (version 0))
                ;; Need to figure out what version of the file
                ;; is being added.
                (let ((regexp (concat "^"
                                      (regexp-quote file)
                                      ";\\([0-9]+\\)$"))
                      (version 0))
-                 (mapatoms
-                  (lambda (sym)
-                    (let ((name (get sym 'key)))
-                      (and (string-match regexp name)
-                           (setq version
-                                 (max version
-                                      (string-to-int
-                                       (substring name
-                                                  (match-beginning 1)
-                                                  (match-end 1))))))))
+                 (maphash
+                  (lambda (name val)
+                    (and (string-match regexp name)
+                         (setq version
+                               (max version
+                                    (string-to-number (match-string 1 name))))))
                   files)
                  (setq version (1+ version))
                   files)
                  (setq version (1+ version))
-                 (ange-ftp-put-hash-entry
+                 (puthash
                   (concat file ";" (int-to-string version))
                   nil files))))
                   (concat file ";" (int-to-string version))
                   nil files))))
-           (ange-ftp-put-hash-entry file nil files))))))
+           (puthash file nil files))))))
 
 (or (assq 'vms ange-ftp-add-file-entry-alist)
     (setq ange-ftp-add-file-entry-alist
 
 (or (assq 'vms ange-ftp-add-file-entry-alist)
     (setq ange-ftp-add-file-entry-alist
@@ -5208,7 +5142,7 @@ Other orders of $ and _ seem to all work just fine.")
 
 (defun ange-ftp-vms-file-name-as-directory (name)
   (save-match-data
 
 (defun ange-ftp-vms-file-name-as-directory (name)
   (save-match-data
-    (if (string-match "\\.\\(DIR\\|dir\\)\\(;[0-9]+\\)?$" name)
+    (if (string-match "\\.\\(DIR\\|dir\\)\\(;[0-9]+\\)?\\'" name)
        (setq name (substring name 0 (match-beginning 0))))
     (ange-ftp-real-file-name-as-directory name)))
 
        (setq name (substring name 0 (match-beginning 0))))
     (ange-ftp-real-file-name-as-directory name)))
 
@@ -5329,15 +5263,15 @@ Other orders of $ and _ seem to all work just fine.")
 
 (defun ange-ftp-vms-make-compressed-filename (name &optional reverse)
   (cond
 
 (defun ange-ftp-vms-make-compressed-filename (name &optional reverse)
   (cond
-   ((string-match "-Z;[0-9]+$" name)
+   ((string-match "-Z;[0-9]+\\'" name)
     (list nil (substring name 0 (match-beginning 0))))
     (list nil (substring name 0 (match-beginning 0))))
-   ((string-match ";[0-9]+$" name)
+   ((string-match ";[0-9]+\\'" name)
     (list nil (substring name 0 (match-beginning 0))))
     (list nil (substring name 0 (match-beginning 0))))
-   ((string-match "-Z$" name)
+   ((string-match "-Z\\'" name)
     (list nil (substring name 0 -2)))
    (t
     (list t
     (list nil (substring name 0 -2)))
    (t
     (list t
-         (if (string-match ";[0-9]+$" name)
+         (if (string-match ";[0-9]+\\'" name)
              (concat (substring name 0 (match-beginning 0))
                      "-Z")
            (concat name "-Z"))))))
              (concat (substring name 0 (match-beginning 0))
                      "-Z")
            (concat name "-Z"))))))
@@ -5370,7 +5304,7 @@ Other orders of $ and _ seem to all work just fine.")
 
 (defun ange-ftp-vms-sans-version (name &rest args)
   (save-match-data
 
 (defun ange-ftp-vms-sans-version (name &rest args)
   (save-match-data
-    (if (string-match ";[0-9]+$" name)
+    (if (string-match ";[0-9]+\\'" name)
        (substring name 0 (match-beginning 0))
       name)))
 
        (substring name 0 (match-beginning 0))
       name)))
 
@@ -5409,8 +5343,7 @@ Other orders of $ and _ seem to all work just fine.")
 ;;    ;; If the file has numeric backup versions,
 ;;    ;; put on ange-ftp-file-version-alist an element of the form
 ;;    ;; (FILENAME . VERSION-NUMBER-LIST)
 ;;    ;; If the file has numeric backup versions,
 ;;    ;; put on ange-ftp-file-version-alist an element of the form
 ;;    ;; (FILENAME . VERSION-NUMBER-LIST)
-;;    (dired-map-dired-file-lines (function
-;;                              ange-ftp-dired-vms-collect-file-versions))
+;;    (dired-map-dired-file-lines 'ange-ftp-dired-vms-collect-file-versions)
 ;;    ;; Sort each VERSION-NUMBER-LIST,
 ;;    ;; and remove the versions not to be deleted.
 ;;    (let ((fval ange-ftp-file-version-alist))
 ;;    ;; Sort each VERSION-NUMBER-LIST,
 ;;    ;; and remove the versions not to be deleted.
 ;;    (let ((fval ange-ftp-file-version-alist))
@@ -5427,8 +5360,7 @@ Other orders of $ and _ seem to all work just fine.")
 ;;    ;; Look at each file.  If it is a numeric backup file,
 ;;    ;; find it in a VERSION-NUMBER-LIST and maybe flag it for deletion.
 ;;    (dired-map-dired-file-lines
 ;;    ;; Look at each file.  If it is a numeric backup file,
 ;;    ;; find it in a VERSION-NUMBER-LIST and maybe flag it for deletion.
 ;;    (dired-map-dired-file-lines
-;;     (function
-;;      ange-ftp-dired-vms-trample-file-versions mark))
+;;     'ange-ftp-dired-vms-trample-file-versions mark)
 ;;    (message (concat action " numerical backups...done"))))
 
 ;;(or (assq 'vms ange-ftp-dired-clean-directory-alist)
 ;;    (message (concat action " numerical backups...done"))))
 
 ;;(or (assq 'vms ange-ftp-dired-clean-directory-alist)
@@ -5528,19 +5460,15 @@ Other orders of $ and _ seem to all work just fine.")
 (defun ange-ftp-fix-name-for-mts (name &optional reverse)
   (save-match-data
     (if reverse
 (defun ange-ftp-fix-name-for-mts (name &optional reverse)
   (save-match-data
     (if reverse
-       (if (string-match "^\\([^:]+:\\)?\\(.*\\)$" name)
+       (if (string-match "\\`\\([^:]+:\\)?\\(.*\\)\\'" name)
            (let (acct file)
            (let (acct file)
-             (if (match-beginning 1)
-                 (setq acct (substring name 0 (match-end 1))))
-             (if (match-beginning 2)
-                 (setq file (substring name
-                                       (match-beginning 2) (match-end 2))))
+             (setq acct (match-string 1 name))
+             (setq file (match-string 2 name))
              (concat (and acct (concat "/" acct "/"))
                      file))
          (error "name %s didn't match" name))
              (concat (and acct (concat "/" acct "/"))
                      file))
          (error "name %s didn't match" name))
-      (if (string-match "^/\\([^:]+:\\)/\\(.*\\)$" name)
-         (concat (substring name 1 (match-end 1))
-                 (substring name (match-beginning 2) (match-end 2)))
+      (if (string-match "\\`/\\([^:]+:\\)/\\(.*\\)\\'" name)
+         (concat (match-string 1 name) (match-string 2 name))
        ;; Let's hope that mts will recognize it anyway.
        name))))
 
        ;; Let's hope that mts will recognize it anyway.
        name))))
 
@@ -5558,7 +5486,7 @@ Other orders of $ and _ seem to all work just fine.")
       (cond
        ((string-equal dir-name "")
        "?")
       (cond
        ((string-equal dir-name "")
        "?")
-       ((string-match ":$" dir-name)
+       ((string-match ":\\'" dir-name)
        (concat dir-name "?"))
        (dir-name))))) ; It's just a single file.
 
        (concat dir-name "?"))
        (dir-name))))) ; It's just a single file.
 
@@ -5581,24 +5509,22 @@ Other orders of $ and _ seem to all work just fine.")
 
 ;; Parse the current buffer which is assumed to be in mts ftp dir format.
 (defun ange-ftp-parse-mts-listing ()
 
 ;; Parse the current buffer which is assumed to be in mts ftp dir format.
 (defun ange-ftp-parse-mts-listing ()
-  (let ((tbl (ange-ftp-make-hashtable)))
+  (let ((tbl (make-hash-table :test 'equal)))
     (goto-char (point-min))
     (save-match-data
     (goto-char (point-min))
     (save-match-data
-      (while (re-search-forward ange-ftp-date-regexp nil t)
+      (while (re-search-forward directory-listing-before-filename-regexp nil t)
        (end-of-line)
        (skip-chars-backward " ")
        (let ((end (point)))
          (skip-chars-backward "-A-Z0-9_.!")
        (end-of-line)
        (skip-chars-backward " ")
        (let ((end (point)))
          (skip-chars-backward "-A-Z0-9_.!")
-         (ange-ftp-put-hash-entry (buffer-substring (point) end) nil tbl))
+         (puthash (buffer-substring (point) end) nil tbl))
        (forward-line 1)))
        (forward-line 1)))
-      ;; Don't need to bother with ..
-    (ange-ftp-put-hash-entry "." t tbl)
+    ;; Don't need to bother with ..
+    (puthash "." t tbl)
     tbl))
 
     tbl))
 
-(or (assq 'mts ange-ftp-parse-list-func-alist)
-    (setq ange-ftp-parse-list-func-alist
-         (cons '(mts . ange-ftp-parse-mts-listing)
-               ange-ftp-parse-list-func-alist)))
+(add-to-list 'ange-ftp-parse-list-func-alist
+            '(mts . ange-ftp-parse-mts-listing))
 
 (defun ange-ftp-add-mts-host (host)
   "Mark HOST as the name of a machine running MTS."
 
 (defun ange-ftp-add-mts-host (host)
   "Mark HOST as the name of a machine running MTS."
@@ -5697,12 +5623,11 @@ Other orders of $ and _ seem to all work just fine.")
        ;; stores directories without the trailing /. Is this
        ;; consistent?
        (concat "/" name)
        ;; stores directories without the trailing /. Is this
        ;; consistent?
        (concat "/" name)
-      (if (string-match "^/\\([-A-Z0-9$*._]+\\)/\\([-A-Z0-9$._]+\\)?$"
+      (if (string-match "\\`/\\([-A-Z0-9$*._]+\\)/\\([-A-Z0-9$._]+\\)?\\'"
                        name)
                        name)
-         (let ((minidisk (substring name 1 (match-end 1))))
+         (let ((minidisk (match-string 1 name)))
            (if (match-beginning 2)
            (if (match-beginning 2)
-               (let ((file (substring name (match-beginning 2)
-                                      (match-end 2)))
+               (let ((file (match-string 2 name))
                      (cmd (concat "cd " minidisk))
 
                      ;; Note that host and user are bound in the call
                      (cmd (concat "cd " minidisk))
 
                      ;; Note that host and user are bound in the call
@@ -5743,15 +5668,14 @@ Other orders of $ and _ seem to all work just fine.")
   (cond
    ((string-equal "/" dir-name)
     (error "Cannot get listing for fictitious \"/\" directory"))
   (cond
    ((string-equal "/" dir-name)
     (error "Cannot get listing for fictitious \"/\" directory"))
-   ((string-match "^/\\([-A-Z0-9$*._]+\\)/\\([-A-Z0-9$._]+\\)?$" dir-name)
-    (let* ((minidisk (substring dir-name (match-beginning 1) (match-end 1)))
+   ((string-match "\\`/\\([-A-Z0-9$*._]+\\)/\\([-A-Z0-9$._]+\\)?\\'" dir-name)
+    (let* ((minidisk (match-string 1 dir-name))
           ;; host and user are bound in the call to ange-ftp-send-cmd
           (proc (ange-ftp-get-process ange-ftp-this-host ange-ftp-this-user))
           (cmd (concat "cd " minidisk))
           (file (if (match-beginning 2)
                     ;; it's a single file
           ;; host and user are bound in the call to ange-ftp-send-cmd
           (proc (ange-ftp-get-process ange-ftp-this-host ange-ftp-this-user))
           (cmd (concat "cd " minidisk))
           (file (if (match-beginning 2)
                     ;; it's a single file
-                    (substring dir-name (match-beginning 2)
-                               (match-end 2))
+                    (match-string 2 dir-name)
                   ;; use the wild-card
                   "*")))
       (if (car (ange-ftp-raw-send-cmd proc cmd))
                   ;; use the wild-card
                   "*")))
       (if (car (ange-ftp-raw-send-cmd proc cmd))
@@ -5808,33 +5732,25 @@ Other orders of $ and _ seem to all work just fine.")
 ;       (minidisk (ange-ftp-get-file-part dir-file))
 ;       (root-tbl (ange-ftp-get-hash-entry root ange-ftp-files-hashtable)))
 ;    (if root-tbl
 ;       (minidisk (ange-ftp-get-file-part dir-file))
 ;       (root-tbl (ange-ftp-get-hash-entry root ange-ftp-files-hashtable)))
 ;    (if root-tbl
-;      (ange-ftp-put-hash-entry minidisk t root-tbl)
+;      (puthash minidisk t root-tbl)
 ;      (setq root-tbl (ange-ftp-make-hashtable))
 ;      (setq root-tbl (ange-ftp-make-hashtable))
-;      (ange-ftp-put-hash-entry minidisk t root-tbl)
-;      (ange-ftp-put-hash-entry "." t root-tbl)
+;      (puthash minidisk t root-tbl)
+;      (puthash "." t root-tbl)
 ;      (ange-ftp-set-files root root-tbl)))
   ;; Now do the usual parsing
 ;      (ange-ftp-set-files root root-tbl)))
   ;; Now do the usual parsing
-  (let ((tbl (ange-ftp-make-hashtable)))
+  (let ((tbl (make-hash-table :test 'equal)))
     (goto-char (point-min))
     (save-match-data
       (while
          (re-search-forward
           "^\\([-A-Z0-9$_]+\\) +\\([-A-Z0-9$_]+\\) +[VF] +[0-9]+ " nil t)
     (goto-char (point-min))
     (save-match-data
       (while
          (re-search-forward
           "^\\([-A-Z0-9$_]+\\) +\\([-A-Z0-9$_]+\\) +[VF] +[0-9]+ " nil t)
-       (ange-ftp-put-hash-entry
-        (concat (buffer-substring (match-beginning 1)
-                                  (match-end 1))
-                "."
-                (buffer-substring (match-beginning 2)
-                                  (match-end 2)))
-        nil tbl)
+       (puthash (concat (match-string 1) "." (match-string 2)) nil tbl)
        (forward-line 1))
        (forward-line 1))
-      (ange-ftp-put-hash-entry "." t tbl))
+      (puthash "." t tbl))
     tbl))
 
     tbl))
 
-(or (assq 'cms ange-ftp-parse-list-func-alist)
-    (setq ange-ftp-parse-list-func-alist
-         (cons '(cms . ange-ftp-parse-cms-listing)
-               ange-ftp-parse-list-func-alist)))
+(add-to-list 'ange-ftp-parse-list-func-alist
+            '(cms . ange-ftp-parse-cms-listing))
 
 ;;;;; Tree dired support:
 
 
 ;;;;; Tree dired support:
 
@@ -5910,7 +5826,7 @@ Other orders of $ and _ seem to all work just fine.")
 ;;             ange-ftp-dired-move-to-end-of-filename-alist)))
 
 (defun ange-ftp-cms-make-compressed-filename (name &optional reverse)
 ;;             ange-ftp-dired-move-to-end-of-filename-alist)))
 
 (defun ange-ftp-cms-make-compressed-filename (name &optional reverse)
-  (if (string-match "-Z$" name)
+  (if (string-match "-Z\\'" name)
       (list nil (substring name 0 -2))
     (list t (concat name "-Z"))))
 
       (list nil (substring name 0 -2))
     (list t (concat name "-Z"))))
 
@@ -5948,14 +5864,14 @@ Other orders of $ and _ seem to all work just fine.")
    "^\\(" ange-ftp-bs2000-filename-pubset-regexp "\\)?"
    "\\(" ange-ftp-bs2000-filename-username-regexp "\\)?"
    "\\(" ange-ftp-bs2000-short-filename-regexp "\\)?")
    "^\\(" ange-ftp-bs2000-filename-pubset-regexp "\\)?"
    "\\(" ange-ftp-bs2000-filename-username-regexp "\\)?"
    "\\(" ange-ftp-bs2000-short-filename-regexp "\\)?")
-"Regular expression used in ange-ftp-fix-name-for-bs2000.")
+  "Regular expression used in ange-ftp-fix-name-for-bs2000.")
 
 (defconst ange-ftp-bs2000-fix-name-regexp
   (concat
    "/?\\(" ange-ftp-bs2000-filename-pubset-regexp "/\\)?"
    "\\(\\$[A-Z0-9]*/\\)?"
    "\\(" ange-ftp-bs2000-short-filename-regexp "\\)?")
 
 (defconst ange-ftp-bs2000-fix-name-regexp
   (concat
    "/?\\(" ange-ftp-bs2000-filename-pubset-regexp "/\\)?"
    "\\(\\$[A-Z0-9]*/\\)?"
    "\\(" ange-ftp-bs2000-short-filename-regexp "\\)?")
-"Regular expression used in ange-ftp-fix-name-for-bs2000.")
+  "Regular expression used in ange-ftp-fix-name-for-bs2000.")
 
 (defcustom ange-ftp-bs2000-special-prefix
   "X"
 
 (defcustom ange-ftp-bs2000-special-prefix
   "X"
@@ -6015,12 +5931,7 @@ Other orders of $ and _ seem to all work just fine.")
              (and userid (concat userid "."))
              ;; change every '/' in filename to a '.', normally not neccessary
              (and filename
              (and userid (concat userid "."))
              ;; change every '/' in filename to a '.', normally not neccessary
              (and filename
-                  (apply (function concat)
-                         (mapcar (function (lambda (char)
-                                             (if (= char ?/)
-                                                 (vector ?.)
-                                               (vector char))))
-                                 filename))))))
+                  (subst-char-in-string ?/ ?. filename)))))
        ;; Let's hope that BS2000 recognize this anyway:
        name))))
 
        ;; Let's hope that BS2000 recognize this anyway:
        name))))
 
@@ -6072,8 +5983,6 @@ Other orders of $ and _ seem to all work just fine.")
                    ange-ftp-bs2000-host-regexp)
            ange-ftp-host-cache nil)))
 
                    ange-ftp-bs2000-host-regexp)
            ange-ftp-host-cache nil)))
 
-(defvar ange-ftp-bs2000-posix-hook-installed nil)
-
 (defun ange-ftp-add-bs2000-posix-host (host)
   "Mark HOST as the name of a machine running BS2000 with POSIX subsystem."
   (interactive
 (defun ange-ftp-add-bs2000-posix-host (host)
   "Mark HOST as the name of a machine running BS2000 with POSIX subsystem."
   (interactive
@@ -6087,9 +5996,7 @@ Other orders of $ and _ seem to all work just fine.")
                    ange-ftp-bs2000-posix-host-regexp)
            ange-ftp-host-cache nil))
   ;; Install CD hook to cd to posix on connecting:
                    ange-ftp-bs2000-posix-host-regexp)
            ange-ftp-host-cache nil))
   ;; Install CD hook to cd to posix on connecting:
-  (and (not ange-ftp-bs2000-posix-hook-installed)
-       (add-hook 'ange-ftp-process-startup-hook 'ange-ftp-bs2000-cd-to-posix)
-       (setq ange-ftp-bs2000-posix-hook-installed t))
+  (add-hook 'ange-ftp-process-startup-hook 'ange-ftp-bs2000-cd-to-posix)
   host)
 
 (defconst ange-ftp-bs2000-filename-regexp
   host)
 
 (defconst ange-ftp-bs2000-filename-regexp
@@ -6111,53 +6018,51 @@ Other orders of $ and _ seem to all work just fine.")
 ;; Extract the next filename from a BS2000 dired-like listing.
 (defun ange-ftp-parse-bs2000-filename ()
   (if (re-search-forward ange-ftp-bs2000-filename-regexp nil t)
 ;; Extract the next filename from a BS2000 dired-like listing.
 (defun ange-ftp-parse-bs2000-filename ()
   (if (re-search-forward ange-ftp-bs2000-filename-regexp nil t)
-       (buffer-substring (match-beginning 2) (match-end 2))))
+      (match-string 2)))
 
 ;; Parse the current buffer which is assumed to be in (some) BS2000 FTP dir
 ;; format, and return a hashtable as the result.
 (defun ange-ftp-parse-bs2000-listing ()
 
 ;; Parse the current buffer which is assumed to be in (some) BS2000 FTP dir
 ;; format, and return a hashtable as the result.
 (defun ange-ftp-parse-bs2000-listing ()
-  (let ((tbl (ange-ftp-make-hashtable))
+  (let ((tbl (make-hash-table :test 'equal))
        pubset
        file)
     ;; get current pubset
     (goto-char (point-min))
     (if (re-search-forward ange-ftp-bs2000-filename-pubset-regexp nil t)
        pubset
        file)
     ;; get current pubset
     (goto-char (point-min))
     (if (re-search-forward ange-ftp-bs2000-filename-pubset-regexp nil t)
-       (setq pubset (buffer-substring (match-beginning 0) (match-end 0))))
+       (setq pubset (match-string 0)))
     ;; add files to hashtable
     (goto-char (point-min))
     (save-match-data
       (while (setq file (ange-ftp-parse-bs2000-filename))
     ;; add files to hashtable
     (goto-char (point-min))
     (save-match-data
       (while (setq file (ange-ftp-parse-bs2000-filename))
-       (ange-ftp-put-hash-entry file nil tbl)))
+       (puthash file nil tbl)))
     ;; add . and ..
     ;; add . and ..
-    (ange-ftp-put-hash-entry "." t tbl)
-    (ange-ftp-put-hash-entry ".." t tbl)
+    (puthash "." t tbl)
+    (puthash ".." t tbl)
     ;; add all additional pubsets, if not listing one of them
     (if (not (member pubset ange-ftp-bs2000-additional-pubsets))
     ;; add all additional pubsets, if not listing one of them
     (if (not (member pubset ange-ftp-bs2000-additional-pubsets))
-       (mapcar (function (lambda (pubset)
-                           (ange-ftp-put-hash-entry pubset t tbl)))
+       (mapcar (lambda (pubset) (puthash pubset t tbl))
                ange-ftp-bs2000-additional-pubsets))
     tbl))
 
                ange-ftp-bs2000-additional-pubsets))
     tbl))
 
-(or (assq 'bs2000 ange-ftp-parse-list-func-alist)
-    (setq ange-ftp-parse-list-func-alist
-         (cons '(bs2000 . ange-ftp-parse-bs2000-listing)
-               ange-ftp-parse-list-func-alist)))
+(add-to-list 'ange-ftp-parse-list-func-alist
+            '(bs2000 . ange-ftp-parse-bs2000-listing))
 
 (defun ange-ftp-bs2000-cd-to-posix ()
   "cd to POSIX subsystem if the current host matches
 
 (defun ange-ftp-bs2000-cd-to-posix ()
   "cd to POSIX subsystem if the current host matches
-ange-ftp-bs2000-posix-host-regexp.  All BS2000 hosts with POSIX subsystem
-MUST BE EXPLICITLY SET with ange-ftp-add-bs2000-posix-host for they cannot
+`ange-ftp-bs2000-posix-host-regexp'.  All BS2000 hosts with POSIX subsystem
+MUST BE EXPLICITLY SET with `ange-ftp-add-bs2000-posix-host' for they cannot
 be recognized automatically (they are all valid BS2000 hosts too)."
 be recognized automatically (they are all valid BS2000 hosts too)."
-  (if (and host (ange-ftp-bs2000-posix-host host))
+  (if (and ange-ftp-this-host (ange-ftp-bs2000-posix-host ange-ftp-this-host))
       (progn
        ;; change to POSIX:
 ;      (ange-ftp-raw-send-cmd proc "cd %POSIX")
       (progn
        ;; change to POSIX:
 ;      (ange-ftp-raw-send-cmd proc "cd %POSIX")
-       (ange-ftp-cd host user "%POSIX")
+       (ange-ftp-cd ange-ftp-this-host ange-ftp-this-user "%POSIX")
        ;; put new home directory in the expand-dir hashtable.
        ;; put new home directory in the expand-dir hashtable.
-       ;; `host' and `user' are bound in ange-ftp-get-process.
-       (ange-ftp-put-hash-entry (concat host "/" user "/~")
-                                (car (ange-ftp-get-pwd host user))
-                                ange-ftp-expand-dir-hashtable))))
+       ;; `ange-ftp-this-host' and `ange-ftp-this-user' are bound in
+       ;; ange-ftp-get-process.
+       (puthash (concat ange-ftp-this-host "/" ange-ftp-this-user "/~")
+                (car (ange-ftp-get-pwd ange-ftp-this-host ange-ftp-this-user))
+                ange-ftp-expand-dir-hashtable))))
 
 ;; Not available yet:
 ;; ange-ftp-bs2000-delete-file-entry
 
 ;; Not available yet:
 ;; ange-ftp-bs2000-delete-file-entry
@@ -6172,4 +6077,5 @@ be recognized automatically (they are all valid BS2000 hosts too)."
 
 (provide 'ange-ftp)
 
 
 (provide 'ange-ftp)
 
+;; arch-tag: 2987ef88-cb56-4ec1-87a9-79132572e316
 ;;; ange-ftp.el ends here
 ;;; ange-ftp.el ends here