(command-line-1): Pass arg to other-window.
[bpt/emacs.git] / lisp / ange-ftp.el
CommitLineData
c8472948 1;;; ange-ftp.el --- transparent FTP support for GNU Emacs
2f7ea155 2
46421cea 3;;; Copyright (C) 1989, 1990, 1991, 1992, 1993 Free Software Foundation, Inc.
c8472948 4;;; ??? Waiting for papers from several people.
2f7ea155 5;;;
9d9c912e
ER
6;; Author: Andy Norman (ange@hplb.hpl.hp.com)
7;; Keywords: comm
2f7ea155
RS
8;;;
9;;; This program is free software; you can redistribute it and/or modify
10;;; it under the terms of the GNU General Public License as published by
46421cea 11;;; the Free Software Foundation; either version 2, or (at your option)
2f7ea155
RS
12;;; any later version.
13;;;
14;;; This program is distributed in the hope that it will be useful,
15;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17;;; GNU General Public License for more details.
18;;;
19;;; A copy of the GNU General Public License can be obtained from this
20;;; program's author (send electronic mail to ange@hplb.hpl.hp.com) or from
21;;; the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA
22;;; 02139, USA.
23
c8472948 24;;; Commentary:
2f7ea155
RS
25;;;
26;;; This package attempts to make accessing files and directories using FTP
27;;; from within GNU Emacs as simple and transparent as possible. A subset of
28;;; the common file-handling routines are extended to interact with FTP.
29
2f7ea155
RS
30;;; Usage:
31;;;
32;;; Some of the common GNU Emacs file-handling operations have been made
33;;; FTP-smart. If one of these routines is given a filename that matches
d0bc419e
RS
34;;; '/user@host:name' then it will spawn an FTP process connecting to machine
35;;; 'host' as account 'user' and perform its operation on the file 'name'.
2f7ea155
RS
36;;;
37;;; For example: if find-file is given a filename of:
38;;;
39;;; /ange@anorman:/tmp/notes
40;;;
d0bc419e 41;;; then ange-ftp spawns an FTP process, connect to the host 'anorman' as
2f7ea155
RS
42;;; user 'ange', get the file '/tmp/notes' and pop up a buffer containing the
43;;; contents of that file as if it were on the local filesystem. If ange-ftp
d0bc419e 44;;; needs a password to connect then it reads one in the echo area.
2f7ea155
RS
45
46;;; Extended filename syntax:
47;;;
d0bc419e 48;;; The default extended filename syntax is '/user@host:name', where the
2f7ea155 49;;; 'user@' part may be omitted. This syntax can be customised to a certain
d0bc419e 50;;; extent by changing ange-ftp-name-format. There are limitations.
2f7ea155 51;;;
d0bc419e 52;;; If the user part is omitted then ange-ftp generates a default user
2f7ea155
RS
53;;; instead whose value depends on the variable ange-ftp-default-user.
54
55;;; Passwords:
56;;;
d0bc419e
RS
57;;; A password is required for each host/user pair. Ange-ftp reads passwords
58;;; as needed. You can also specify a password with ange-ftp-set-passwd, or
59;;; in a *valid* ~/.netrc file.
2f7ea155
RS
60
61;;; Passwords for user "anonymous":
62;;;
55badf85
RS
63;;; Passwords for the user "anonymous" (or "ftp") are handled
64;;; specially. The variable `ange-ftp-generate-anonymous-password'
65;;; controls what happens: if the value of this variable is a string,
66;;; then this is used as the password; if non-nil (the default), then
67;;; a password is created from the name of the user and the hostname
68;;; of the machine on which GNU Emacs is running; if nil then the user
69;;; is prompted for a password as normal.
2f7ea155
RS
70
71;;; "Dumb" UNIX hosts:
72;;;
73;;; The FTP servers on some UNIX machines have problems if the 'ls' command is
74;;; used.
75;;;
76;;; The routine ange-ftp-add-dumb-unix-host can be called to tell ange-ftp to
77;;; limit itself to the DIR command and not 'ls' for a given UNIX host. Note
78;;; that this change will take effect for the current GNU Emacs session only.
79;;; See below for a discussion of non-UNIX hosts. If a large number of
80;;; machines with similar hostnames have this problem then it is easier to set
81;;; the value of ange-ftp-dumb-unix-host-regexp in your .emacs file. ange-ftp
82;;; is unable to automatically recognize dumb unix hosts.
83
84;;; File name completion:
85;;;
86;;; Full file-name completion is supported on UNIX, VMS, CMS, and MTS hosts.
87;;; To do filename completion, ange-ftp needs a listing from the remote host.
88;;; Therefore, for very slow connections, it might not save any time.
89
90;;; FTP processes:
91;;;
92;;; When ange-ftp starts up an FTP process, it leaves it running for speed
93;;; purposes. Some FTP servers will close the connection after a period of
94;;; time, but ange-ftp should be able to quietly reconnect the next time that
95;;; the process is needed.
96;;;
d0bc419e
RS
97;;; Killing the "*ftp user@host*" buffer also kills the ftp process.
98;;; This should not cause ange-ftp any grief.
2f7ea155
RS
99
100;;; Binary file transfers:
101;;;
d0bc419e
RS
102;;; By default ange-ftp transfers files in ASCII mode. If a file being
103;;; transferred matches the value of ange-ftp-binary-file-name-regexp then
104;;; binary mode is used for that transfer.
2f7ea155
RS
105
106;;; Account passwords:
107;;;
108;;; Some FTP servers require an additional password which is sent by the
109;;; ACCOUNT command. ange-ftp partially supports this by allowing the user to
110;;; specify an account password by either calling ange-ftp-set-account, or by
111;;; specifying an account token in the .netrc file. If the account password
112;;; is set by either of these methods then ange-ftp will issue an ACCOUNT
113;;; command upon starting the FTP process.
114
115;;; Preloading:
116;;;
117;;; ange-ftp can be preloaded, but must be put in the site-init.el file and
118;;; not the site-load.el file in order for the documentation strings for the
119;;; functions being overloaded to be available.
120
121;;; Status reports:
122;;;
123;;; Most ange-ftp commands that talk to the FTP process output a status
124;;; message on what they are doing. In addition, ange-ftp can take advantage
125;;; of the FTP client's HASH command to display the status of transferring
126;;; files and listing directories. See the documentation for the variables
127;;; ange-ftp-{ascii,binary}-hash-mark-size, ange-ftp-send-hash and
128;;; ange-ftp-process-verbose for more details.
129
130;;; Gateways:
131;;;
132;;; Sometimes it is neccessary for the FTP process to be run on a different
133;;; machine than the machine running GNU Emacs. This can happen when the
134;;; local machine has restrictions on what hosts it can access.
135;;;
136;;; ange-ftp has support for running the ftp process on a different (gateway)
137;;; machine. The way it works is as follows:
138;;;
139;;; 1) Set the variable 'ange-ftp-gateway-host' to the name of a machine
140;;; that doesn't have the access restrictions.
141;;;
142;;; 2) Set the variable 'ange-ftp-local-host-regexp' to a regular expression
143;;; that matches hosts that can be contacted from running a local ftp
144;;; process, but fails to match hosts that can't be accessed locally. For
145;;; example:
146;;;
147;;; "\\.hp\\.com$\\|^[^.]*$"
148;;;
149;;; will match all hosts that are in the .hp.com domain, or don't have an
150;;; explicit domain in their name, but will fail to match hosts with
151;;; explicit domains or that are specified by their ip address.
152;;;
153;;; 3) Using NFS and symlinks, make sure that there is a shared directory with
154;;; the *same* name between the local machine and the gateway machine.
155;;; This directory is neccessary for temporary files created by ange-ftp.
156;;;
157;;; 4) Set the variable 'ange-ftp-gateway-tmp-name-template' to the name of
158;;; this directory plus an identifying filename prefix. For example:
159;;;
160;;; "/nfs/hplose/ange/ange-ftp"
161;;;
162;;; where /nfs/hplose/ange is a directory that is shared between the
163;;; gateway machine and the local machine.
164;;;
165;;; The simplest way of getting a ftp process running on the gateway machine
166;;; is if you can spawn a remote shell using either 'rsh' or 'remsh'. If you
167;;; can't do this for some reason such as security then points 7 onwards will
168;;; discuss an alternative approach.
169;;;
170;;; 5) Set the variable ange-ftp-gateway-program to the name of the remote
171;;; shell process such as 'remsh' or 'rsh' if the default isn't correct.
172;;;
173;;; 6) Set the variable ange-ftp-gateway-program-interactive to nil if it
174;;; isn't already. This tells ange-ftp that you are using a remote shell
175;;; rather than logging in using telnet or rlogin.
176;;;
177;;; That should be all you need to allow ange-ftp to spawn a ftp process on
178;;; the gateway machine. If you have to use telnet or rlogin to get to the
179;;; gateway machine then follow the instructions below.
180;;;
181;;; 7) Set the variable ange-ftp-gateway-program to the name of the program
182;;; that lets you log onto the gateway machine. This may be something like
183;;; telnet or rlogin.
184;;;
185;;; 8) Set the variable ange-ftp-gateway-prompt-pattern to a regular
186;;; expression that matches the prompt you get when you login to the
187;;; gateway machine. Be very specific here; this regexp must not match
188;;; *anything* in your login banner except this prompt.
189;;; shell-prompt-pattern is far too general as it appears to match some
190;;; login banners from Sun machines. For example:
191;;;
192;;; "^$*$ *"
193;;;
194;;; 9) Set the variable ange-ftp-gateway-program-interactive to 't' to let
195;;; ange-ftp know that it has to "hand-hold" the login to the gateway
196;;; machine.
197;;;
198;;; 10) Set the variable ange-ftp-gateway-setup-term-command to a UNIX command
199;;; that will put the pty connected to the gateway machine into a
200;;; no-echoing mode, and will strip off carriage-returns from output from
201;;; the gateway machine. For example:
202;;;
203;;; "stty -onlcr -echo"
204;;;
205;;; will work on HP-UX machines, whereas:
206;;;
207;;; "stty -echo nl"
208;;;
209;;; appears to work for some Sun machines.
210;;;
211;;; That's all there is to it.
212
213;;; Smart gateways:
214;;;
215;;; If you have a "smart" ftp program that allows you to issue commands like
216;;; "USER foo@bar" which do nice proxy things, then look at the variables
217;;; ange-ftp-smart-gateway and ange-ftp-smart-gateway-port.
218
219;;; Tips for using ange-ftp:
220;;;
221;;; 1. For dired to work on a host which marks symlinks with a trailing @ in
222;;; an ls -alF listing, you need to (setq dired-ls-F-marks-symlinks t).
223;;; Most UNIX systems do not do this, but ULTRIX does. If you think that
224;;; there is a chance you might connect to an ULTRIX machine (such as
225;;; prep.ai.mit.edu), then set this variable accordingly. This will have
226;;; the side effect that dired will have problems with symlinks whose names
227;;; end in an @. If you get youself into this situation then editing
228;;; dired's ls-switches to remove "F", will temporarily fix things.
229;;;
230;;; 2. If you know that you are connecting to a certain non-UNIX machine
231;;; frequently, and ange-ftp seems to be unable to guess its host-type,
232;;; then setting the appropriate host-type regexp
233;;; (ange-ftp-vms-host-regexp, ange-ftp-mts-host-regexp, or
234;;; ange-ftp-cms-host-regexp) accordingly should help. Also, please report
235;;; ange-ftp's inability to recognize the host-type as a bug.
236;;;
237;;; 3. For slow connections, you might get "listing unreadable" error
238;;; messages, or get an empty buffer for a file that you know has something
239;;; in it. The solution is to increase the value of ange-ftp-retry-time.
240;;; Its default value is 5 which is plenty for reasonable connections.
241;;; However, for some transatlantic connections I set this to 20.
242;;;
243;;; 4. Beware of compressing files on non-UNIX hosts. Ange-ftp will do it by
244;;; copying the file to the local machine, compressing it there, and then
245;;; sending it back. Binary file transfers between machines of different
246;;; architectures can be a risky business. Test things out first on some
247;;; test files. See "Bugs" below. Also, note that ange-ftp copies files by
248;;; moving them through the local machine. Again, be careful when doing
249;;; this with binary files on non-Unix machines.
250;;;
251;;; 5. Beware that dired over ftp will use your setting of dired-no-confirm
252;;; (list of dired commands for which confirmation is not asked). You
253;;; might want to reconsider your setting of this variable, because you
254;;; might want confirmation for more commands on remote direds than on
255;;; local direds. For example, I strongly recommend that you not include
256;;; compress and uncompress in this list. If there is enough demand it
257;;; might be a good idea to have an alist ange-ftp-dired-no-confirm of
258;;; pairs ( TYPE . LIST ), where TYPE is an operating system type and LIST
259;;; is a list of commands for which confirmation would be suppressed. Then
260;;; remote dired listings would take their (buffer-local) value of
261;;; dired-no-confirm from this alist. Who votes for this?
262
263;;; ---------------------------------------------------------------------
264;;; Non-UNIX support:
265;;; ---------------------------------------------------------------------
266
267;;; VMS support:
268;;;
d0bc419e 269;;; Ange-ftp has full support for VMS hosts. It
2f7ea155
RS
270;;; should be able to automatically recognize any VMS machine. However, if it
271;;; fails to do this, you can use the command ange-ftp-add-vms-host. As well,
272;;; you can set the variable ange-ftp-vms-host-regexp in your .emacs file. We
273;;; would be grateful if you would report any failures to automatically
274;;; recognize a VMS host as a bug.
275;;;
276;;; Filename Syntax:
277;;;
278;;; For ease of *implementation*, the user enters the VMS filename syntax in a
279;;; UNIX-y way. For example:
280;;; PUB$:[ANONYMOUS.SDSCPUB.NEXT]README.TXT;1
281;;; would be entered as:
282;;; /PUB$$:/ANONYMOUS/SDSCPUB/NEXT/README.TXT;1
283;;; i.e. to log in as anonymous on ymir.claremont.edu and grab the file:
284;;; [.CSV.POLICY]RULES.MEM
285;;; you would type:
286;;; C-x C-f /anonymous@ymir.claremont.edu:CSV/POLICY/RULES.MEM
287;;;
288;;; A legal VMS filename is of the form: FILE.TYPE;##
289;;; where FILE can be up to 39 characters
290;;; TYPE can be up to 39 characters
291;;; ## is a version number (an integer between 1 and 32,767)
292;;; Valid characters in FILE and TYPE are A-Z 0-9 _ - $
293;;; $ cannot begin a filename, and - cannot be used as the first or last
294;;; character.
295;;;
296;;; Tips:
297;;; 1. Although VMS is not case sensitive, EMACS running under UNIX is.
298;;; Therefore, to access a VMS file, you must enter the filename with upper
299;;; case letters.
300;;; 2. To access the latest version of file under VMS, you use the filename
301;;; without the ";" and version number. You should always edit the latest
302;;; version of a file. If you want to edit an earlier version, copy it to a
303;;; new file first. This has nothing to do with ange-ftp, but is simply
304;;; good VMS operating practice. Therefore, to edit FILE.TXT;3 (say 3 is
305;;; latest version), do C-x C-f /ymir.claremont.edu:FILE.TXT. If you
306;;; inadvertently do C-x C-f /ymir.claremont.edu:FILE.TXT;3, you will find
307;;; that VMS will not allow you to save the file because it will refuse to
308;;; overwrite FILE.TXT;3, but instead will want to create FILE.TXT;4, and
309;;; attach the buffer to this file. To get out of this situation, M-x
310;;; write-file /ymir.claremont.edu:FILE.TXT will attach the buffer to
d0bc419e 311;;; latest version of the file. For this reason, in dired "f"
2f7ea155
RS
312;;; (dired-find-file), always loads the file sans version, whereas "v",
313;;; (dired-view-file), always loads the explicit version number. The
314;;; reasoning being that it reasonable to view old versions of a file, but
315;;; not to edit them.
316;;; 3. EMACS has a feature in which it does environment variable substitution
317;;; in filenames. Therefore, to enter a $ in a filename, you must quote it
d0bc419e 318;;; by typing $$.
2f7ea155
RS
319
320;;; MTS support:
321;;;
d0bc419e 322;;; Ange-ftp has full support for hosts running
2f7ea155
RS
323;;; the Michigan terminal system. It should be able to automatically
324;;; recognize any MTS machine. However, if it fails to do this, you can use
325;;; the command ange-ftp-add-mts-host. As well, you can set the variable
326;;; ange-ftp-mts-host-regexp in your .emacs file. We would be grateful if you
327;;; would report any failures to automatically recognize a MTS host as a bug.
328;;;
329;;; Filename syntax:
330;;;
331;;; MTS filenames are entered in a UNIX-y way. For example, if your account
332;;; was YYYY, the file FILE in the account XXXX: on mtsg.ubc.ca would be
333;;; entered as
334;;; /YYYY@mtsg.ubc.ca:/XXXX:/FILE
335;;; In other words, MTS accounts are treated as UNIX directories. Of course,
336;;; to access a file in another account, you must have access permission for
337;;; it. If FILE were in your own account, then you could enter it in a
d0bc419e 338;;; relative name fashion as
2f7ea155
RS
339;;; /YYYY@mtsg.ubc.ca:FILE
340;;; MTS filenames can be up to 12 characters. Like UNIX, the structure of the
341;;; filename does not contain a TYPE (i.e. it can have as many "."'s as you
342;;; like.) MTS filenames are always in upper case, and hence be sure to enter
343;;; them as such! MTS is not case sensitive, but an EMACS running under UNIX
344;;; is.
345
346;;; CMS support:
347;;;
d0bc419e 348;;; Ange-ftp has full support for hosts running
2f7ea155
RS
349;;; CMS. It should be able to automatically recognize any CMS machine.
350;;; However, if it fails to do this, you can use the command
351;;; ange-ftp-add-cms-host. As well, you can set the variable
352;;; ange-ftp-cms-host-regexp in your .emacs file. We would be grateful if you
353;;; would report any failures to automatically recognize a CMS host as a bug.
354;;;
355;;; Filename syntax:
356;;;
357;;; CMS filenames are entered in a UNIX-y way. In otherwords, minidisks are
358;;; treated as UNIX directories. For example to access the file READ.ME in
359;;; minidisk *.311 on cuvmb.cc.columbia.edu, you would enter
360;;; /anonymous@cuvmb.cc.columbia.edu:/*.311/READ.ME
361;;; If *.301 is the default minidisk for this account, you could access
362;;; FOO.BAR on this minidisk as
363;;; /anonymous@cuvmb.cc.columbia.edu:FOO.BAR
364;;; CMS filenames are of the form FILE.TYPE, where both FILE and TYPE can be
365;;; up to 8 characters. Again, beware that CMS filenames are always upper
366;;; case, and hence must be entered as such.
367;;;
368;;; Tips:
369;;; 1. CMS machines, with the exception of anonymous accounts, nearly always
370;;; need an account password. To have ange-ftp send an account password,
371;;; you can either include it in your .netrc file, or use
372;;; ange-ftp-set-account.
373;;; 2. Ange-ftp cannot send "write passwords" for a minidisk. Hopefully, we
374;;; can fix this.
375;;;
376;;; ------------------------------------------------------------------
377;;; Bugs:
378;;; ------------------------------------------------------------------
379;;;
380;;; 1. Umask problems:
381;;; Be warned that files created by using ange-ftp will take account of the
382;;; umask of the ftp daemon process rather than the umask of the creating
383;;; user. This is particulary important when logging in as the root user.
384;;; The way that I tighten up the ftp daemon's umask under HP-UX is to make
385;;; sure that the umask is changed to 027 before I spawn /etc/inetd. I
386;;; suspect that there is something similar on other systems.
387;;;
388;;; 2. Some combinations of FTP clients and servers break and get out of sync
389;;; when asked to list a non-existent directory. Some of the ai.mit.edu
390;;; machines cause this problem for some FTP clients. Using
391;;; ange-ftp-kill-process can be used to restart the ftp process, which
392;;; should get things back in synch.
393;;;
394;;; 3. Ange-ftp does not check to make sure that when creating a new file,
395;;; you provide a valid filename for the remote operating system.
396;;; If you do not, then the remote FTP server will most likely
397;;; translate your filename in some way. This may cause ange-ftp to
398;;; get confused about what exactly is the name of the file. The
399;;; most common causes of this are using lower case filenames on systems
400;;; which support only upper case, and using filenames which are too
401;;; long.
402;;;
403;;; 4. Null (blank) passwords confuse both ange-ftp and some FTP daemons.
404;;;
405;;; 5. Ange-ftp likes to use pty's to talk to its FTP processes. If GNU Emacs
406;;; for some reason creates a FTP process that only talks via pipes then
407;;; ange-ftp won't be getting the information it requires at the time that
408;;; it wants it since pipes flush at different times to pty's. One
409;;; disgusting way around this problem is to talk to the FTP process via
410;;; rlogin which does the 'right' things with pty's.
411;;;
412;;; 6. For CMS support, we send too many cd's. Since cd's are cheap, I haven't
413;;; worried about this too much. Eventually, we should have some caching
414;;; of the current minidisk.
415;;;
416;;; 7. Some CMS machines do not assign a default minidisk when you ftp them as
417;;; anonymous. It is then necessary to guess a valid minidisk name, and cd
418;;; to it. This is (understandably) beyond ange-ftp.
419;;;
420;;; 8. Remote to remote copying of files on non-Unix machines can be risky.
421;;; Depending on the variable ange-ftp-binary-file-name-regexp, ange-ftp
422;;; will use binary mode for the copy. Between systems of different
423;;; architecture, this still may not be enough to guarantee the integrity
424;;; of binary files. Binary file transfers from VMS machines are
425;;; particularly problematical. Should ange-ftp-binary-file-name-regexp be
426;;; an alist of OS type, regexp pairs?
427;;;
428;;; 9. The code to do compression of files over ftp is not as careful as it
429;;; should be. It deletes the old remote version of the file, before
430;;; actually checking if the local to remote transfer of the compressed
431;;; file succeeds. Of course to delete the original version of the file
432;;; after transferring the compressed version back is also dangerous,
433;;; because some OS's have severe restrictions on the length of filenames,
434;;; and when the compressed version is copied back the "-Z" or ".Z" may be
435;;; truncated. Then, ange-ftp would delete the only remaining version of
436;;; the file. Maybe ange-ftp should make backups when it compresses files
437;;; (of course, the backup "~" could also be truncated off, sigh...).
438;;; Suggestions?
439;;;
440
441;;; 10. If a dir listing is attempted for an empty directory on (at least
442;;; some) VMS hosts, an ftp error is given. This is really an ftp bug, and
443;;; I don't know how to get ange-ftp work to around it.
444;;;
445;;; 11. Bombs on filenames that start with a space. Deals well with filenames
446;;; containing spaces, but beware that the remote ftpd may not like them
447;;; much.
448;;;
d0bc419e
RS
449;;; 12. The dired support for non-Unix-like systems does not currently work.
450;;; It needs to be reimplemented by modifying the parse-...-listing
451;;; functions to convert the directory listing to ls -l format.
2f7ea155
RS
452;;;
453;;; 13. The famous @ bug. As mentioned above in TIPS, ULTRIX marks symlinks
454;;; with a trailing @ in a ls -alF listing. In order to account for this
455;;; ange-ftp looks to chop trailing @'s off of symlink names when it is
456;;; parsing a listing with the F switch. This will cause ange-ftp to
457;;; incorrectly get the name of a symlink on a non-ULTRIX host if its name
458;;; ends in an @. ange-ftp will correct itself if you take F out of the
459;;; dired ls switches (C-u s will allow you to edit the switches). The
460;;; dired buffer will be automatically reverted, which will allow ange-ftp
461;;; to fix its files hashtable. A cookie to anyone who can think of a
462;;; fast, sure-fire way to recognize ULTRIX over ftp.
463
464;;; If you find any bugs or problems with this package, PLEASE either e-mail
465;;; the above author, or send a message to the ange-ftp-lovers mailing list
466;;; below. Ideas and constructive comments are especially welcome.
467
468;;; ange-ftp-lovers:
469;;;
470;;; ange-ftp has its own mailing list modestly called ange-ftp-lovers. All
471;;; users of ange-ftp are welcome to subscribe (see below) and to discuss
472;;; aspects of ange-ftp. New versions of ange-ftp are posted periodically to
473;;; the mailing list.
474;;;
475;;; To [un]subscribe to ange-ftp-lovers, or to report mailer problems with the
476;;; list, please mail one of the following addresses:
477;;;
478;;; ange-ftp-lovers-request@anorman.hpl.hp.com
479;;; or
480;;; ange-ftp-lovers-request%anorman.hpl.hp.com@hplb.hpl.hp.com
481;;;
482;;; Please don't forget the -request part.
483;;;
484;;; For mail to be posted directly to ange-ftp-lovers, send to one of the
485;;; following addresses:
486;;;
487;;; ange-ftp-lovers@anorman.hpl.hp.com
488;;; or
489;;; ange-ftp-lovers%anorman.hpl.hp.com@hplb.hpl.hp.com
490;;;
491;;; Alternatively, there is a mailing list that only gets announcements of new
492;;; ange-ftp releases. This is called ange-ftp-lovers-announce, and can be
493;;; subscribed to by e-mailing to the -request address as above. Please make
494;;; it clear in the request which mailing list you wish to join.
495
496;;; The latest version of ange-ftp can usually be obtained via anonymous ftp
497;;; from:
498;;; alpha.gnu.ai.mit.edu:ange-ftp/ange-ftp.tar.Z
499;;; or:
500;;; ugle.unit.no:/pub/gnu/emacs-lisp/ange-ftp.tar.Z
501;;; or:
502;;; archive.cis.ohio-state.edu:pub/gnu/emacs/elisp-archive/packages/ange-ftp.tar.Z
503
504;;; The archives for ange-ftp-lovers can be found via anonymous ftp under:
505;;;
506;;; ftp.reed.edu:pub/mailing-lists/ange-ftp/
507\f
508;;; -----------------------------------------------------------
509;;; Technical information on this package:
510;;; -----------------------------------------------------------
511
d0bc419e
RS
512;;; ange-ftp works by putting a handler on file-name-handler-alist
513;;; which is called by many primitives, and a few non-primitives,
514;;; whenever they see a file name of the appropriate sort.
2f7ea155
RS
515
516;;; Checklist for adding non-UNIX support for TYPE
517;;;
518;;; The following functions may need TYPE versions:
519;;; (not all functions will be needed for every OS)
520;;;
d0bc419e
RS
521;;; ange-ftp-fix-name-for-TYPE
522;;; ange-ftp-fix-dir-name-for-TYPE
2f7ea155
RS
523;;; ange-ftp-TYPE-host
524;;; ange-ftp-TYPE-add-host
525;;; ange-ftp-parse-TYPE-listing
526;;; ange-ftp-TYPE-delete-file-entry
527;;; ange-ftp-TYPE-add-file-entry
528;;; ange-ftp-TYPE-file-name-as-directory
d0bc419e
RS
529;;; ange-ftp-TYPE-make-compressed-filename
530;;; ange-ftp-TYPE-file-name-sans-versions
2f7ea155
RS
531;;;
532;;; Variables:
533;;;
534;;; ange-ftp-TYPE-host-regexp
535;;; May need to add TYPE to ange-ftp-dumb-host-types
536;;;
537;;; Check the following functions for OS dependent coding:
538;;;
539;;; ange-ftp-host-type
540;;; ange-ftp-guess-host-type
541;;; ange-ftp-allow-child-lookup
2f7ea155
RS
542
543;;; Host type conventions:
544;;;
545;;; The function ange-ftp-host-type and the variable ange-ftp-dired-host-type
546;;; (mostly) follow the following conventions for remote host types. At
547;;; least, I think that future code should try to follow these conventions,
548;;; and the current code should eventually be made compliant.
549;;;
550;;; nil = local host type, whatever that is (probably unix).
551;;; Think nil as in "not a remote host". This value is used by
552;;; ange-ftp-dired-host-type for local buffers.
553;;;
554;;; t = a remote host of unknown type. Think t is in true, it's remote.
555;;; Currently, 'unix is used as the default remote host type.
556;;; Maybe we should use t.
557;;;
558;;; 'type = a remote host of TYPE type.
559;;;
560;;; 'type:list = a remote host of TYPE type, using a specialized ftp listing
561;;; program called list. This is currently only used for Unix
562;;; dl (descriptive listings), when ange-ftp-dired-host-type
563;;; is set to 'unix:dl.
564
565;;; Bug report codes:
566;;;
567;;; Because of their naive faith in this code, there are certain situations
568;;; which the writers of this program believe could never happen. However,
d0bc419e 569;;; being realists they have put calls to `error' in the program at these
2f7ea155
RS
570;;; points. These errors provide a code, which is an integer, greater than 1.
571;;; To aid debugging. the error codes, and the functions in which they reside
572;;; are listed below.
573;;;
574;;; 1: See ange-ftp-ls
575;;;
576\f
577;;; -----------------------------------------------------------
578;;; Hall of fame:
579;;; -----------------------------------------------------------
580;;;
581;;; Thanks to Roland McGrath for improving the filename syntax handling,
582;;; for suggesting many enhancements and for numerous cleanups to the code.
583;;;
584;;; Thanks to Jamie Zawinski for bugfixes and for ideas such as gateways.
585;;;
586;;; Thanks to Ken Laprade for improved .netrc parsing, password reading, and
587;;; dired / shell auto-loading.
588;;;
d0bc419e 589;;; Thanks to Sebastian Kremer for dired support and for many ideas and
2f7ea155
RS
590;;; bugfixes.
591;;;
592;;; Thanks to Joe Wells for bugfixes, the original non-UNIX system support,
593;;; VOS support, and hostname completion.
594;;;
595;;; Thanks to Nakagawa Takayuki for many good ideas, filename-completion, help
596;;; with file-name expansion, efficiency worries, stylistic concerns and many
597;;; bugfixes.
598;;;
599;;; Thanks to Sandy Rutherford who re-wrote most of ange-ftp to support VMS,
600;;; MTS, CMS and UNIX-dls. Sandy also added dired-support for non-UNIX OS and
601;;; auto-recognition of the host type.
602;;;
603;;; Thanks to Dave Smith who wrote the info file for ange-ftp.
604;;;
605;;; Finally, thanks to Keith Waclena, Mark D. Baushke, Terence Kelleher, Ping
606;;; Zhou, Edward Vielmetti, Jack Repenning, Mike Balenger, Todd Kaufmann,
607;;; Kjetil Svarstad, Tom Wurgler, Linus Tolke, Niko Makila, Carl Edman, Bill
608;;; Trost, Dave Brennan, Dan Jacobson, Andy Scott, Steve Anderson, Sanjay
609;;; Mathur, the folks on the ange-ftp-lovers mailing list and many others
610;;; whose names I've forgotten who have helped to debug and fix problems with
611;;; ange-ftp.el.
612\f
c8472948
ER
613
614;;; Code:
d0bc419e
RS
615(require 'comint)
616
2f7ea155
RS
617;;;; ------------------------------------------------------------
618;;;; User customization variables.
619;;;; ------------------------------------------------------------
620
d0bc419e 621(defvar ange-ftp-name-format
2f7ea155 622 '("^/\\(\\([^@/:]*\\)@\\)?\\([^@/:]*\\):\\(.*\\)" . (3 2 4))
d0bc419e
RS
623 "*Format of a fully expanded remote file name.
624This is a list of the form \(REGEXP HOST USER NAME\),
625where REGEXP is a regular expression matching
626the full remote name, and HOST, USER, and NAME are the numbers of
2f7ea155
RS
627parenthesized expressions in REGEXP for the components (in that order).")
628
629;; ange-ftp-multi-skip-msgs should only match ###-, where ### is one of
630;; the number codes corresponding to ange-ftp-good-msgs or ange-ftp-fatal-msgs.
631;; Otherwise, ange-ftp will go into multi-skip mode, and never come out.
632
633(defvar ange-ftp-multi-msgs
634 "^220-\\|^230-\\|^226\\|^25.-\\|^221-\\|^200-\\|^530-\\|^4[25]1-"
635 "*Regular expression matching messages from the ftp process that start
636a multiline reply.")
637
638(defvar ange-ftp-good-msgs
639 "^220 \\|^230 \\|^226 \\|^25. \\|^221 \\|^200 \\|^[Hh]ash mark"
640 "*Regular expression matching messages from the ftp process that indicate
641that the action that was initiated has completed successfully.")
642
643;; CMS and the odd VMS machine say 200 Port rather than 200 PORT.
644;; Also CMS machines use a multiline 550- reply to say that you
645;; don't have write permission. ange-ftp gets into multi-line skip
646;; mode and hangs. Have it ignore 550- instead. It will then barf
647;; when it gets the 550 line, as it should.
648
649(defvar ange-ftp-skip-msgs
650 (concat "^200 \\(PORT\\|Port\\) \\|^331 \\|^150 \\|^350 \\|^[0-9]+ bytes \\|"
651 "^Connected \\|^$\\|^Remote system\\|^Using\\|^ \\|Password:\\|"
652 "^local:\\|^Trying\\|^125 \\|^550-\\|^221 .*oodbye")
653 "*Regular expression matching messages from the ftp process that can be
654ignored.")
655
656(defvar ange-ftp-fatal-msgs
657 (concat "^ftp: \\|^Not connected\\|^530 \\|^4[25]1 \\|rcmd: \\|"
658 "^No control connection\\|unknown host\\|^lost connection")
659 "*Regular expression matching messages from the FTP process that indicate
660something has gone drastically wrong attempting the action that was
661initiated and that the FTP process should (or already has) been killed.")
662
663(defvar ange-ftp-gateway-fatal-msgs
664 "No route to host\\|Connection closed\\|No such host\\|Login incorrect"
665 "*Regular expression matching messages from the rlogin / telnet process that
666indicates that logging in to the gateway machine has gone wrong.")
667
668(defvar ange-ftp-xfer-size-msgs
669 "^150 .* connection for .* (\\([0-9]+\\) bytes)"
670 "*Regular expression used to determine the number of bytes in a FTP transfer.")
671
672(defvar ange-ftp-tmp-name-template "/tmp/ange-ftp"
673 "*Template used to create temporary files.")
674
675(defvar ange-ftp-gateway-tmp-name-template "/tmp/ange-ftp"
676 "*Template used to create temporary files when ftp-ing through a gateway.
677Files starting with this prefix need to be accessible from BOTH the local
678machine and the gateway machine, and need to have the SAME name on both
679machines, that is, /tmp is probably NOT what you want, since that is rarely
680cross-mounted.")
681
682(defvar ange-ftp-netrc-filename "~/.netrc"
683 "*File in .netrc format to search for passwords.")
684
685(defvar ange-ftp-disable-netrc-security-check nil
686 "*If non-nil avoid checking permissions on the .netrc file.")
687
688(defvar ange-ftp-default-user nil
d0bc419e 689 "*User name to use when none is specied in a file name.
2f7ea155
RS
690If nil, then the name under which the user is logged in is used.
691If non-nil but not a string, the user is prompted for the name.")
692
693(defvar ange-ftp-default-password nil
694 "*Password to use when the user is the same as ange-ftp-default-user.")
695
696(defvar ange-ftp-default-account nil
697 "*Account password to use when the user is the same as ange-ftp-default-user.")
698
55badf85 699(defvar ange-ftp-generate-anonymous-password t
2f7ea155
RS
700 "*If t, use a password of user@host when logging in as the anonymous user.
701If a string then use that as the password.
702If nil then prompt the user for a password.")
703
704(defvar ange-ftp-dumb-unix-host-regexp nil
705 "*If non-nil, if the host being ftp'd to matches this regexp then the FTP
706process uses the \'dir\' command to get directory information.")
707
708(defvar ange-ftp-binary-file-name-regexp
46421cea 709 (concat "\\.[zZ]$\\|\\.lzh$\\|\\.arc$\\|\\.zip$\\|\\.zoo$\\|\\.tar$\\|"
2f7ea155 710 "\\.dvi$\\|\\.ps$\\|\\.elc$\\|TAGS$\\|\\.gif$\\|"
46421cea 711 "\\.EXE\\(;[0-9]+\\)?$\\|\\.[zZ]-part-..$")
2f7ea155
RS
712 "*If a file matches this regexp then it is transferred in binary mode.")
713
714(defvar ange-ftp-gateway-host nil
715 "*Name of host to use as gateway machine when local FTP isn't possible.")
716
717(defvar ange-ftp-local-host-regexp ".*"
718 "*If a host being FTP'd to matches this regexp then the ftp process is started
719locally, otherwise the FTP process is started on \`ange-ftp-gateway-host\'
720instead.")
721
722(defvar ange-ftp-gateway-program-interactive nil
723 "*If non-nil then the gateway program is expected to connect to the gateway
724machine and eventually give a shell prompt. Both telnet and rlogin do something
725like this.")
726
727(defvar ange-ftp-gateway-program (if (eq system-type 'hpux) "remsh" "rsh")
728 "*Name of program to spawn a shell on the gateway machine. Valid candidates
729are rsh (remsh on hp-ux), telnet and rlogin. See also the gateway variable
730above.")
731
732(defvar ange-ftp-gateway-prompt-pattern "^[^#$%>;]*[#$%>;] *"
733 "*Regexp used to detect that the logging-in sequence is completed on the
734gateway machine and that the shell is now awaiting input. Make this regexp as
735strict as possible; it shouldn't match *anything* at all except the user's
736initial prompt. The above string will fail under most SUN-3's since it
737matches the login banner.")
738
739(defvar ange-ftp-gateway-setup-term-command
740 (if (eq system-type 'hpux)
741 "stty -onlcr -echo\n"
742 "stty -echo nl\n")
743 "*Command to use after logging in to the gateway machine to stop the terminal
744echoing each command and to strip out trailing ^M characters.")
745
746(defvar ange-ftp-smart-gateway nil
747 "*If the gateway FTP is smart enough to use proxy server, then don't bother
748telnetting etc, just issue a user@host command instead.")
749
750(defvar ange-ftp-smart-gateway-port "21"
751 "*Port on gateway machine to use when smart gateway is in operation.")
752
753(defvar ange-ftp-send-hash t
754 "*If non-nil, send the HASH command to the FTP client.")
755
756(defvar ange-ftp-binary-hash-mark-size nil
757 "*Default size, in bytes, between hash-marks when transferring a binary file.
758If NIL, this variable will be locally overridden if the FTP client outputs a
759suitable response to the HASH command. If non-NIL then this value takes
760precedence over the local value.")
761
762(defvar ange-ftp-ascii-hash-mark-size 1024
763 "*Default size, in bytes, between hash-marks when transferring an ASCII file.
764This variable is buffer-local and will be locally overridden if the FTP client
765outputs a suitable response to the HASH command.")
766
767(defvar ange-ftp-process-verbose t
768 "*If non-NIL then be chatty about interaction with the FTP process.")
769
770(defvar ange-ftp-ftp-program-name "ftp"
771 "*Name of FTP program to run.")
772
773(defvar ange-ftp-gateway-ftp-program-name "ftp"
774 "*Name of FTP program to run on gateway machine.
775Some AT&T folks claim to use something called `pftp' here.")
776
777(defvar ange-ftp-ftp-program-args '("-i" "-n" "-g" "-v")
778 "*A list of arguments passed to the FTP program when started.")
779
780(defvar ange-ftp-nslookup-program nil
781 "*If non-NIL then a string naming nslookup program." )
782
783(defvar ange-ftp-make-backup-files ()
784 "*A list of operating systems for which ange-ftp will make Emacs backup
785files files on the remote host. For example, '\(unix\) makes sense, but
786'\(unix vms\) or '\(vms\) would be silly, since vms makes its own backups.")
787
788(defvar ange-ftp-retry-time 5
789 "*Number of seconds to wait before retrying if a file or listing
790doesn't arrive. This might need to be increased for very slow connections.")
791
792(defvar ange-ftp-auto-save 0
793 "If 1, allows ange-ftp files to be auto-saved.
794If 0, suppresses auto-saving of ange-ftp files.
795Don't use any other value.")
796\f
797;;;; ------------------------------------------------------------
798;;;; Hash table support.
799;;;; ------------------------------------------------------------
800
801(require 'backquote)
802
803(defun ange-ftp-make-hashtable (&optional size)
804 "Make an obarray suitable for use as a hashtable.
805SIZE, if supplied, should be a prime number."
806 (make-vector (or size 31) 0))
807
808(defun ange-ftp-map-hashtable (fun tbl)
809 "Call FUNCTION on each key and value in HASHTABLE."
810 (mapatoms
811 (function
812 (lambda (sym)
813 (funcall fun (get sym 'key) (get sym 'val))))
814 tbl))
815
816(defmacro ange-ftp-make-hash-key (key)
817 "Convert KEY into a suitable key for a hashtable."
818 (` (if (stringp (, key))
819 (, key)
820 (prin1-to-string (, key)))))
821
822(defun ange-ftp-get-hash-entry (key tbl)
823 "Return the value associated with KEY in HASHTABLE."
824 (let ((sym (intern-soft (ange-ftp-make-hash-key key) tbl)))
825 (and sym (get sym 'val))))
826
827(defun ange-ftp-put-hash-entry (key val tbl)
828 "Record an association between KEY and VALUE in HASHTABLE."
829 (let ((sym (intern (ange-ftp-make-hash-key key) tbl)))
830 (put sym 'val val)
831 (put sym 'key key)))
832
833(defun ange-ftp-del-hash-entry (key tbl)
834 "Copy all symbols except KEY in HASHTABLE and return modified hashtable."
835 (let* ((len (length tbl))
836 (new-tbl (ange-ftp-make-hashtable len))
837 (i (1- len)))
838 (ange-ftp-map-hashtable
839 (function
840 (lambda (k v)
841 (or (equal k key)
842 (ange-ftp-put-hash-entry k v new-tbl))))
843 tbl)
844 (while (>= i 0)
845 (aset tbl i (aref new-tbl i))
846 (setq i (1- i)))
847 tbl))
848
849(defun ange-ftp-hash-entry-exists-p (key tbl)
850 "Return whether there is an association for KEY in TABLE."
851 (intern-soft (ange-ftp-make-hash-key key) tbl))
852
853(defun ange-ftp-hash-table-keys (tbl)
c8fa98cc 854 "Return a sorted list of all the active keys in TABLE, as strings."
2f7ea155
RS
855 (sort (all-completions "" tbl)
856 (function string-lessp)))
857\f
858;;;; ------------------------------------------------------------
859;;;; Internal variables.
860;;;; ------------------------------------------------------------
861
e70fd492 862(defconst ange-ftp-version "$Revision: 1.18 $")
2f7ea155
RS
863
864(defvar ange-ftp-data-buffer-name " *ftp data*"
865 "Buffer name to hold directory listing data received from ftp process.")
866
867(defvar ange-ftp-netrc-modtime nil
868 "Last modified time of the netrc file from file-attributes.")
869
870(defvar ange-ftp-user-hashtable (ange-ftp-make-hashtable)
871 "Hash table holding associations between HOST, USER pairs.")
872
873(defvar ange-ftp-passwd-hashtable (ange-ftp-make-hashtable)
874 "Mapping between a HOST, USER pair and a PASSWORD for them.")
875
876(defvar ange-ftp-account-hashtable (ange-ftp-make-hashtable)
877 "Mapping between a HOST, USER pair and a ACCOUNT password for them.")
878
879(defvar ange-ftp-files-hashtable (ange-ftp-make-hashtable 97)
880 "Hash table for storing directories and their respective files.")
881
882(defvar ange-ftp-ls-cache-lsargs nil
883 "Last set of args used by ange-ftp-ls.")
884
885(defvar ange-ftp-ls-cache-file nil
886 "Last file passed to ange-ftp-ls.")
887
888(defvar ange-ftp-ls-cache-res nil
889 "Last result returned from ange-ftp-ls.")
890
891(defconst ange-ftp-expand-dir-hashtable (ange-ftp-make-hashtable))
892
893(defconst ange-ftp-expand-dir-regexp "^5.0 \\([^: ]+\\):")
894
895;; These are local variables in each FTP process buffer.
896(defvar ange-ftp-hash-mark-unit nil)
897(defvar ange-ftp-hash-mark-count nil)
898(defvar ange-ftp-xfer-size nil)
899(defvar ange-ftp-process-string nil)
900(defvar ange-ftp-process-result-line nil)
901(defvar ange-ftp-process-busy nil)
902(defvar ange-ftp-process-result nil)
903(defvar ange-ftp-process-multi-skip nil)
904(defvar ange-ftp-process-msg nil)
905(defvar ange-ftp-process-continue nil)
906(defvar ange-ftp-last-percent nil)
907
908;; These variables are bound by one function and examined by another.
909;; Leave them void globally for error checking.
910(defvar ange-ftp-this-file)
911(defvar ange-ftp-this-dir)
912(defvar ange-ftp-this-user)
913(defvar ange-ftp-this-host)
d0bc419e 914(defvar ange-ftp-this-msg)
2f7ea155
RS
915(defvar ange-ftp-completion-ignored-pattern)
916(defvar ange-ftp-trample-marker)
917\f
918;; New error symbols.
919(put 'ftp-error 'error-conditions '(ftp-error file-error error))
920;; (put 'ftp-error 'error-message "FTP error")
921\f
922;;; ------------------------------------------------------------
923;;; Match-data support (stolen from Kyle I think)
924;;; ------------------------------------------------------------
925
926(defmacro ange-ftp-save-match-data (&rest body)
927 "Execute the BODY forms, restoring the global value of the match data.
d0bc419e 928Also makes matching case-sensitive within BODY."
2f7ea155
RS
929 (let ((original (make-symbol "match-data"))
930 case-fold-search)
931 (list
932 'let (list (list original '(match-data)))
933 (list 'unwind-protect
934 (cons 'progn body)
935 (list 'store-match-data original)))))
936
937(put 'ange-ftp-save-match-data 'lisp-indent-hook 0)
938(put 'ange-ftp-save-match-data 'edebug-form-hook '(&rest form))
939\f
940;;; ------------------------------------------------------------
941;;; Enhanced message support.
942;;; ------------------------------------------------------------
943
944(defun ange-ftp-message (fmt &rest args)
d0bc419e
RS
945 "Display message in echo area, but indicate if truncated.
946Args are as in `message': a format string, plus arguments to be formatted."
2f7ea155
RS
947 (let ((msg (apply (function format) fmt args))
948 (max (window-width (minibuffer-window))))
949 (if (>= (length msg) max)
950 (setq msg (concat "> " (substring msg (- 3 max)))))
951 (message "%s" msg)))
952
953(defun ange-ftp-abbreviate-filename (file &optional new)
d0bc419e
RS
954 "Abbreviate the file name FILE relative to the default-directory.
955If the optional parameter NEW is given and the non-directory parts match,
956only return the directory part of FILE."
2f7ea155
RS
957 (ange-ftp-save-match-data
958 (if (and default-directory
959 (string-match (concat "^"
960 (regexp-quote default-directory)
961 ".") file))
962 (setq file (substring file (1- (match-end 0)))))
963 (if (and new
964 (string-equal (file-name-nondirectory file)
965 (file-name-nondirectory new)))
966 (setq file (file-name-directory file)))
967 (or file "./")))
968\f
969;;;; ------------------------------------------------------------
970;;;; User / Host mapping support.
971;;;; ------------------------------------------------------------
972
973(defun ange-ftp-set-user (host user)
974 "For a given HOST, set or change the default USER."
975 (interactive "sHost: \nsUser: ")
976 (ange-ftp-put-hash-entry host user ange-ftp-user-hashtable))
977
978(defun ange-ftp-get-user (host)
979 "Given a HOST, return the default USER."
980 (ange-ftp-parse-netrc)
981 (let ((user (ange-ftp-get-hash-entry host ange-ftp-user-hashtable)))
982 (or user
983 (prog1
984 (setq user
985 (cond ((stringp ange-ftp-default-user)
986 ;; We have a default name. Use it.
987 ange-ftp-default-user)
988 (ange-ftp-default-user
989 ;; Ask the user.
990 (let ((enable-recursive-minibuffers t))
991 (read-string (format "User for %s: " host)
992 (user-login-name))))
993 ;; Default to the user's login name.
994 (t
995 (user-login-name))))
996 (ange-ftp-set-user host user)))))
997\f
998;;;; ------------------------------------------------------------
999;;;; Password support.
1000;;;; ------------------------------------------------------------
1001
1002(defun ange-ftp-read-passwd (prompt &optional default)
1003 "Read a password, echoing `.' for each character typed.
1004End with RET, LFD, or ESC. DEL or C-h rubs out. C-u kills line.
1005Optional DEFAULT is password to start with."
1006 (let ((pass (if default default ""))
1007 (c 0)
1008 (echo-keystrokes 0)
1009 (cursor-in-echo-area t))
1010 (while (progn (message "%s%s"
1011 prompt
1012 (make-string (length pass) ?.))
1013 (setq c (read-char))
1014 (and (/= c ?\r) (/= c ?\n) (/= c ?\e)))
1015 (if (= c ?\C-u)
1016 (setq pass "")
1017 (if (and (/= c ?\b) (/= c ?\177))
1018 (setq pass (concat pass (char-to-string c)))
1019 (if (> (length pass) 0)
1020 (setq pass (substring pass 0 -1))))))
1021 (message "")
2495e5f4 1022 (ange-ftp-repaint-minibuffer)
2f7ea155
RS
1023 pass))
1024
1025(defmacro ange-ftp-generate-passwd-key (host user)
1026 (` (concat (, host) "/" (, user))))
1027
1028(defmacro ange-ftp-lookup-passwd (host user)
1029 (` (ange-ftp-get-hash-entry (ange-ftp-generate-passwd-key (, host) (, user))
1030 ange-ftp-passwd-hashtable)))
1031
1032(defun ange-ftp-set-passwd (host user passwd)
1033 "For a given HOST and USER, set or change the associated PASSWORD."
1034 (interactive (list (read-string "Host: ")
1035 (read-string "User: ")
1036 (ange-ftp-read-passwd "Password: ")))
1037 (ange-ftp-put-hash-entry (ange-ftp-generate-passwd-key host user)
1038 passwd
1039 ange-ftp-passwd-hashtable))
1040
1041(defun ange-ftp-get-host-with-passwd (user)
1042 "Given a USER, return a host we know the password for."
1043 (ange-ftp-parse-netrc)
1044 (catch 'found-one
1045 (ange-ftp-map-hashtable
1046 (function (lambda (host val)
1047 (if (ange-ftp-lookup-passwd host user)
1048 (throw 'found-one host))))
1049 ange-ftp-user-hashtable)
1050 (ange-ftp-save-match-data
1051 (ange-ftp-map-hashtable
1052 (function
1053 (lambda (key value)
1054 (if (string-match "^[^/]*\\(/\\).*$" key)
1055 (let ((host (substring key 0 (match-beginning 1))))
1056 (if (and (string-equal user (substring key (match-end 1)))
1057 value)
1058 (throw 'found-one host))))))
1059 ange-ftp-passwd-hashtable))
1060 nil))
1061
1062(defun ange-ftp-get-passwd (host user)
1063 "Return the password for specified HOST and USER, asking user if necessary."
1064 (ange-ftp-parse-netrc)
1065
1066 ;; look up password in the hash table first; user might have overriden the
1067 ;; defaults.
1068 (cond ((ange-ftp-lookup-passwd host user))
1069
1070 ;; see if default user and password set from the .netrc file.
1071 ((and (stringp ange-ftp-default-user)
1072 ange-ftp-default-password
1073 (string-equal user ange-ftp-default-user))
1074 ange-ftp-default-password)
1075
1076 ;; anonymous ftp password is handled specially since there is an
1077 ;; unwritten rule about how that is used on the Internet.
1078 ((and (or (string-equal user "anonymous")
1079 (string-equal user "ftp"))
1080 ange-ftp-generate-anonymous-password)
1081 (if (stringp ange-ftp-generate-anonymous-password)
1082 ange-ftp-generate-anonymous-password
1083 (concat (user-login-name) "@" (system-name))))
1084
1085 ;; see if same user has logged in to other hosts; if so then prompt
1086 ;; with the password that was used there.
1087 (t
1088 (let* ((other (ange-ftp-get-host-with-passwd user))
1089 (passwd (if other
1090
1091 ;; found another machine with the same user.
1092 ;; Try that account.
1093 (ange-ftp-read-passwd
1094 (format "passwd for %s@%s (same as %s@%s): "
1095 user host user other)
1096 (ange-ftp-lookup-passwd other user))
1097
1098 ;; I give up. Ask the user for the password.
1099 (ange-ftp-read-passwd
1100 (format "Password for %s@%s: " user host)))))
1101 (ange-ftp-set-passwd host user passwd)
1102 passwd))))
1103\f
1104;;;; ------------------------------------------------------------
1105;;;; Account support
1106;;;; ------------------------------------------------------------
1107
1108;; Account passwords must be either specified in the .netrc file, or set
1109;; manually by calling ange-ftp-set-account. For the moment, ange-ftp doesn't
1110;; check to see whether the FTP process is actually prompting for an account
1111;; password.
1112
1113(defun ange-ftp-set-account (host user account)
1114 "For a given HOST and USER, set or change the associated ACCOUNT password."
1115 (interactive (list (read-string "Host: ")
1116 (read-string "User: ")
1117 (ange-ftp-read-passwd "Account password: ")))
1118 (ange-ftp-put-hash-entry (ange-ftp-generate-passwd-key host user)
1119 account
1120 ange-ftp-account-hashtable))
1121
1122(defun ange-ftp-get-account (host user)
1123 "Given a HOST and USER, return the FTP account."
1124 (ange-ftp-parse-netrc)
1125 (or (ange-ftp-get-hash-entry (ange-ftp-generate-passwd-key host user)
1126 ange-ftp-account-hashtable)
1127 (and (stringp ange-ftp-default-user)
1128 (string-equal user ange-ftp-default-user)
1129 ange-ftp-default-account)))
1130\f
1131;;;; ------------------------------------------------------------
1132;;;; ~/.netrc support
1133;;;; ------------------------------------------------------------
1134
1135(defun ange-ftp-chase-symlinks (file)
c8fa98cc 1136 "Return the filename that FILE references, following all symbolic links."
2f7ea155
RS
1137 (let (temp)
1138 (while (setq temp (ange-ftp-real-file-symlink-p file))
1139 (setq file
1140 (if (file-name-absolute-p temp)
1141 temp
1142 (concat (file-name-directory file) temp)))))
1143 file)
1144
1145(defun ange-ftp-parse-netrc-token (token limit)
1146 "Move along current line looking for the value of the TOKEN.
1147Valid separators between TOKEN and its value are commas and
1148whitespace. Second arg LIMIT is a limit for the search."
1149 (if (search-forward token limit t)
1150 (let (beg)
1151 (skip-chars-forward ", \t\r\n" limit)
1152 (if (eq (following-char) ?\") ;quoted token value
1153 (progn (forward-char 1)
1154 (setq beg (point))
1155 (skip-chars-forward "^\"" limit)
1156 (forward-char 1)
1157 (buffer-substring beg (1- (point))))
1158 (setq beg (point))
1159 (skip-chars-forward "^, \t\r\n" limit)
1160 (buffer-substring beg (point))))))
1161
1162(defun ange-ftp-parse-netrc-group ()
1163 "Extract the values for the tokens \`machine\', \`login\', \`password\'
1164and \`account\' in the current buffer. If successful, record the information
1165found."
1166 (beginning-of-line)
1167 (let ((start (point))
1168 (end (progn (re-search-forward "machine\\|default"
1169 (point-max) 'end 2) (point)))
1170 machine login password account)
1171 (goto-char start)
1172 (setq machine (ange-ftp-parse-netrc-token "machine" end)
1173 login (ange-ftp-parse-netrc-token "login" end)
1174 password (ange-ftp-parse-netrc-token "password" end)
1175 account (ange-ftp-parse-netrc-token "account" end))
1176 (if (and machine login)
1177 ;; found a `machine` token.
1178 (progn
1179 (ange-ftp-set-user machine login)
1180 (ange-ftp-set-passwd machine login password)
1181 (and account
1182 (ange-ftp-set-account machine login account)))
1183 (goto-char start)
1184 (if (search-forward "default" end t)
1185 ;; found a `default' token
1186 (progn
1187 (setq login (ange-ftp-parse-netrc-token "login" end)
1188 password (ange-ftp-parse-netrc-token "password" end)
1189 account (ange-ftp-parse-netrc-token "account" end))
1190 (and login
1191 (setq ange-ftp-default-user login))
1192 (and password
1193 (setq ange-ftp-default-password password))
1194 (and account
1195 (setq ange-ftp-default-account account)))))
1196 (goto-char end)))
1197
1198(defun ange-ftp-parse-netrc ()
c8fa98cc
CZ
1199 "Read in ~/.netrc, if one exists.
1200If ~/.netrc file exists and has the correct permissions then extract the
2f7ea155
RS
1201\`machine\', \`login\', \`password\' and \`account\' information from within."
1202
1203 ;; We set this before actually doing it to avoid the possibility
1204 ;; of an infinite loop if ange-ftp-netrc-filename is an FTP file.
1205 (interactive)
1206 (let* ((file (ange-ftp-chase-symlinks
1207 (ange-ftp-real-expand-file-name ange-ftp-netrc-filename)))
1208 (attr (ange-ftp-real-file-attributes file)))
1209 (if (and attr ; file exists.
1210 (not (equal (nth 5 attr) ange-ftp-netrc-modtime))) ; file changed
1211 (ange-ftp-save-match-data
1212 (if (or ange-ftp-disable-netrc-security-check
1213 (and (eq (nth 2 attr) (user-uid)) ; Same uids.
1214 (string-match ".r..------" (nth 8 attr))))
1215 (save-excursion
1216 ;; we are cheating a bit here. I'm trying to do the equivalent
1217 ;; of find-file on the .netrc file, but then nuke it afterwards.
1218 ;; with the bit of logic below we should be able to have
1219 ;; encrypted .netrc files.
1220 (set-buffer (generate-new-buffer "*ftp-.netrc*"))
1221 (ange-ftp-real-insert-file-contents file)
1222 (setq buffer-file-name file)
1223 (setq default-directory (file-name-directory file))
1224 (normal-mode t)
1225 (mapcar 'funcall find-file-hooks)
1226 (setq buffer-file-name nil)
1227 (goto-char (point-min))
1228 (while (not (eobp))
1229 (ange-ftp-parse-netrc-group))
1230 (kill-buffer (current-buffer)))
1231 (ange-ftp-message "%s either not owned by you or badly protected."
1232 ange-ftp-netrc-filename)
1233 (sit-for 1))
1234 (setq ange-ftp-netrc-modtime (nth 5 attr))))))
1235
1236(defun ange-ftp-generate-root-prefixes ()
1237 "Return a list of prefixes of the form 'user@host:' to be used when
1238completion is done in the root directory."
1239 (ange-ftp-parse-netrc)
1240 (ange-ftp-save-match-data
1241 (let (res)
1242 (ange-ftp-map-hashtable
1243 (function
1244 (lambda (key value)
1245 (if (string-match "^[^/]*\\(/\\).*$" key)
1246 (let ((host (substring key 0 (match-beginning 1)))
1247 (user (substring key (match-end 1))))
1248 (setq res (cons (list (concat user "@" host ":"))
1249 res))))))
1250 ange-ftp-passwd-hashtable)
1251 (ange-ftp-map-hashtable
1252 (function (lambda (host user)
1253 (setq res (cons (list (concat host ":"))
1254 res))))
1255 ange-ftp-user-hashtable)
1256 (or res (list nil)))))
1257\f
1258;;;; ------------------------------------------------------------
d0bc419e 1259;;;; Remote file name syntax support.
2f7ea155
RS
1260;;;; ------------------------------------------------------------
1261
d0bc419e
RS
1262(defmacro ange-ftp-ftp-name-component (n ns name)
1263 "Extract the Nth ftp file name component from NS."
2f7ea155
RS
1264 (` (let ((elt (nth (, n) (, ns))))
1265 (if (match-beginning elt)
d0bc419e
RS
1266 (substring (, name) (match-beginning elt) (match-end elt))))))
1267
1268(defvar ange-ftp-ftp-name-arg "")
1269(defvar ange-ftp-ftp-name-res nil)
1270
1271(defun ange-ftp-ftp-name (name)
1272 "Parse NAME according to `ange-ftp-name-format' (which see).
1273Returns a list (HOST USER NAME), or nil if NAME does not match the format."
1274 (if (string-equal name ange-ftp-ftp-name-arg)
1275 ange-ftp-ftp-name-res
1276 (setq ange-ftp-ftp-name-arg name
1277 ange-ftp-ftp-name-res
2f7ea155 1278 (ange-ftp-save-match-data
d0bc419e
RS
1279 (if (string-match (car ange-ftp-name-format) name)
1280 (let* ((ns (cdr ange-ftp-name-format))
1281 (host (ange-ftp-ftp-name-component 0 ns name))
1282 (user (ange-ftp-ftp-name-component 1 ns name))
1283 (name (ange-ftp-ftp-name-component 2 ns name)))
2f7ea155
RS
1284 (if (zerop (length user))
1285 (setq user (ange-ftp-get-user host)))
d0bc419e 1286 (list host user name))
2f7ea155
RS
1287 nil)))))
1288
d0bc419e
RS
1289(defun ange-ftp-replace-name-component (fullname name)
1290 "Take a FULLNAME that matches according to ange-ftp-name-format and
1291replace the name component with NAME."
2f7ea155 1292 (ange-ftp-save-match-data
d0bc419e
RS
1293 (if (string-match (car ange-ftp-name-format) fullname)
1294 (let* ((ns (cdr ange-ftp-name-format))
2f7ea155 1295 (elt (nth 2 ns)))
d0bc419e
RS
1296 (concat (substring fullname 0 (match-beginning elt))
1297 name
1298 (substring fullname (match-end elt)))))))
2f7ea155
RS
1299\f
1300;;;; ------------------------------------------------------------
1301;;;; Miscellaneous utils.
1302;;;; ------------------------------------------------------------
1303
1304;; (setq ange-ftp-tmp-keymap (make-sparse-keymap))
1305;; (define-key ange-ftp-tmp-keymap "\C-m" 'exit-minibuffer)
1306
2495e5f4
JB
1307(defun ange-ftp-repaint-minibuffer ()
1308 "Clear any existing minibuffer message; let the minibuffer contents show."
1309 (message nil))
2f7ea155
RS
1310
1311(defun ange-ftp-ftp-process-buffer (host user)
1312 "Return the name of the buffer that collects output from the ftp process
1313connected to the given HOST and USER pair."
1314 (concat "*ftp " user "@" host "*"))
1315
1316(defun ange-ftp-error (host user msg)
1317 "Display the last chunk of output from the ftp process for the given HOST
1318USER pair, and signal an error including MSG in the text."
1319 (let ((cur (selected-window))
1320 (pop-up-windows t))
1321 (pop-to-buffer
1322 (get-buffer-create
1323 (ange-ftp-ftp-process-buffer host user)))
1324 (goto-char (point-max))
1325 (select-window cur))
1326 (signal 'ftp-error (list (format "FTP Error: %s" msg))))
1327
1328(defun ange-ftp-set-buffer-mode ()
e70fd492 1329 "Set correct modes for the current buffer if visiting a remote file."
2f7ea155 1330 (if (and (stringp buffer-file-name)
d0bc419e 1331 (ange-ftp-ftp-name buffer-file-name))
2f7ea155 1332 (progn
e70fd492
RS
1333 (make-local-variable 'make-backup-files)
1334 (setq make-backup-files ange-ftp-make-backup-files)
26aaadf3 1335 (auto-save-mode ange-ftp-auto-save))))
2f7ea155
RS
1336
1337(defun ange-ftp-kill-ftp-process (buffer)
090e4588 1338 "Kill the FTP process associated with BUFFER.
c8fa98cc 1339If the BUFFER's visited filename or default-directory is an ftp filename
2f7ea155
RS
1340then kill the related ftp process."
1341 (interactive "bKill FTP process associated with buffer: ")
1342 (if (null buffer)
1343 (setq buffer (current-buffer)))
1344 (let ((file (or (buffer-file-name) default-directory)))
1345 (if file
d0bc419e 1346 (let ((parsed (ange-ftp-ftp-name (expand-file-name file))))
2f7ea155
RS
1347 (if parsed
1348 (let ((host (nth 0 parsed))
1349 (user (nth 1 parsed)))
1350 (kill-buffer (ange-ftp-ftp-process-buffer host user))))))))
1351
1352(defun ange-ftp-quote-string (string)
1353 "Quote any characters in STRING that may confuse the ftp process."
1354 (apply (function concat)
1355 (mapcar (function
1356 (lambda (char)
1357 (if (or (<= char ? )
1358 (> char ?\~)
1359 (= char ?\")
1360 (= char ?\\))
1361 (vector ?\\ char)
1362 (vector char))))
1363 string)))
1364
1365(defun ange-ftp-barf-if-not-directory (directory)
1366 (or (file-directory-p directory)
1367 (signal 'file-error
1368 (list "Opening directory"
1369 (if (file-exists-p directory)
1370 "not a directory"
1371 "no such file or directory")
1372 directory))))
1373\f
1374;;;; ------------------------------------------------------------
1375;;;; FTP process filter support.
1376;;;; ------------------------------------------------------------
1377
1378(defun ange-ftp-process-handle-line (line proc)
1379 "Look at the given LINE from the ftp process PROC. Try to catagorize it
1380into one of four categories: good, skip, fatal, or unknown."
1381 (cond ((string-match ange-ftp-xfer-size-msgs line)
1382 (setq ange-ftp-xfer-size
1383 (ash (string-to-int (substring line
1384 (match-beginning 1)
1385 (match-end 1)))
1386 -10)))
1387 ((string-match ange-ftp-skip-msgs line)
1388 t)
1389 ((string-match ange-ftp-good-msgs line)
1390 (setq ange-ftp-process-busy nil
1391 ange-ftp-process-result t
1392 ange-ftp-process-result-line line))
1393 ((string-match ange-ftp-fatal-msgs line)
1394 (delete-process proc)
1395 (setq ange-ftp-process-busy nil
1396 ange-ftp-process-result-line line))
1397 ((string-match ange-ftp-multi-msgs line)
1398 (setq ange-ftp-process-multi-skip t))
1399 (ange-ftp-process-multi-skip
1400 t)
1401 (t
1402 (setq ange-ftp-process-busy nil
1403 ange-ftp-process-result-line line))))
1404
1405(defun ange-ftp-process-log-string (proc str)
1406 "For a given PROCESS, log the given STRING at the end of its
1407associated buffer."
1408 (let ((old-buffer (current-buffer)))
1409 (unwind-protect
1410 (let (moving)
1411 (set-buffer (process-buffer proc))
1412 (setq moving (= (point) (process-mark proc)))
1413 (save-excursion
1414 ;; Insert the text, moving the process-marker.
1415 (goto-char (process-mark proc))
1416 (insert str)
1417 (set-marker (process-mark proc) (point)))
1418 (if moving (goto-char (process-mark proc))))
1419 (set-buffer old-buffer))))
1420
1421(defun ange-ftp-set-xfer-size (host user bytes)
1422 "Set the size of the next FTP transfer in bytes."
1423 (let ((proc (ange-ftp-get-process host user)))
1424 (if proc
1425 (let ((buf (process-buffer proc)))
1426 (if buf
1427 (save-excursion
1428 (set-buffer buf)
1429 (setq ange-ftp-xfer-size (ash bytes -10))))))))
1430
1431(defun ange-ftp-process-handle-hash (str)
1432 "Remove hash marks from STRING and display count so far."
1433 (setq str (concat (substring str 0 (match-beginning 0))
1434 (substring str (match-end 0)))
1435 ange-ftp-hash-mark-count (+ (- (match-end 0)
1436 (match-beginning 0))
1437 ange-ftp-hash-mark-count))
1438 (and ange-ftp-process-msg
1439 ange-ftp-process-verbose
1440 (not (eq (selected-window) (minibuffer-window)))
1441 (not (boundp 'search-message)) ;screws up isearch otherwise
1442 (not cursor-in-echo-area) ;screws up y-or-n-p otherwise
1443 (let ((kbytes (ash (* ange-ftp-hash-mark-unit
1444 ange-ftp-hash-mark-count)
1445 -6)))
1446 (if (zerop ange-ftp-xfer-size)
1447 (ange-ftp-message "%s...%dk" ange-ftp-process-msg kbytes)
1448 (let ((percent (/ (* 100 kbytes) ange-ftp-xfer-size)))
1449 ;; cut out the redisplay of identical %-age messages.
1450 (if (not (eq percent ange-ftp-last-percent))
1451 (progn
1452 (setq ange-ftp-last-percent percent)
1453 (ange-ftp-message "%s...%d%%" ange-ftp-process-msg percent)))))))
1454 str)
1455
1456(defun ange-ftp-call-cont (cont result line)
1457 "Call the function specified by CONT. CONT can be either a function or a
1458list of a function and some args. The first two parameters passed to the
1459function will be RESULT and LINE. The remaining args will be taken from CONT
1460if a list was passed."
1461 (if cont
1462 (if (and (listp cont)
1463 (not (eq (car cont) 'lambda)))
1464 (apply (car cont) result line (cdr cont))
1465 (funcall cont result line))))
1466
1467(defun ange-ftp-process-filter (proc str)
1468 "Build up a complete line of output from the ftp PROCESS and pass it
1469on to ange-ftp-process-handle-line to deal with."
1470 (let ((buffer (process-buffer proc))
1471 (old-buffer (current-buffer)))
1472
1473 ;; see if the buffer is still around... it could have been deleted.
1474 (if (buffer-name buffer)
1475 (unwind-protect
1476 (ange-ftp-save-match-data
1477 (set-buffer (process-buffer proc))
1478
1479 ;; handle hash mark printing
1480 (and ange-ftp-hash-mark-unit
1481 ange-ftp-process-busy
1482 (string-match "^#+$" str)
1483 (setq str (ange-ftp-process-handle-hash str)))
1484 (ange-ftp-process-log-string proc str)
1485 (if ange-ftp-process-busy
1486 (progn
1487 (setq ange-ftp-process-string (concat ange-ftp-process-string
1488 str))
1489
1490 ;; if we gave an empty password to the USER command earlier
1491 ;; then we should send a null password now.
1492 (if (string-match "Password: *$" ange-ftp-process-string)
1493 (send-string proc "\n"))))
1494 (while (and ange-ftp-process-busy
1495 (string-match "\n" ange-ftp-process-string))
1496 (let ((line (substring ange-ftp-process-string
1497 0
1498 (match-beginning 0))))
1499 (setq ange-ftp-process-string (substring ange-ftp-process-string
1500 (match-end 0)))
1501 (while (string-match "^ftp> *" line)
1502 (setq line (substring line (match-end 0))))
1503 (ange-ftp-process-handle-line line proc)))
1504
1505 ;; has the ftp client finished? if so then do some clean-up
1506 ;; actions.
1507 (if (not ange-ftp-process-busy)
1508 (progn
1509 ;; reset the xfer size
1510 (setq ange-ftp-xfer-size 0)
1511
1512 ;; issue the "done" message since we've finished.
1513 (if (and ange-ftp-process-msg
1514 ange-ftp-process-verbose
1515 ange-ftp-process-result)
1516 (progn
1517 (ange-ftp-message "%s...done" ange-ftp-process-msg)
2495e5f4 1518 (ange-ftp-repaint-minibuffer)
2f7ea155
RS
1519 (setq ange-ftp-process-msg nil)))
1520
1521 ;; is there a continuation we should be calling? if so,
1522 ;; we'd better call it, making sure we only call it once.
1523 (if ange-ftp-process-continue
1524 (let ((cont ange-ftp-process-continue))
1525 (setq ange-ftp-process-continue nil)
1526 (ange-ftp-call-cont cont
1527 ange-ftp-process-result
1528 ange-ftp-process-result-line))))))
1529 (set-buffer old-buffer)))))
1530
1531(defun ange-ftp-process-sentinel (proc str)
1532 "When ftp process changes state, nuke all file-entries in cache."
1533 (ange-ftp-save-match-data
1534 (let ((name (process-name proc)))
1535 (if (string-match "\\*ftp \\([^@]+\\)@\\([^*]+\\)*" name)
1536 (let ((user (substring name (match-beginning 1) (match-end 1)))
1537 (host (substring name (match-beginning 2) (match-end 2))))
1538 (ange-ftp-wipe-file-entries host user))))
1539 (setq ange-ftp-ls-cache-file nil)))
1540\f
1541;;;; ------------------------------------------------------------
1542;;;; Gateway support.
1543;;;; ------------------------------------------------------------
1544
1545(defun ange-ftp-use-gateway-p (host)
1546 "Returns whether to access this host via a normal (non-smart) gateway."
1547 ;; yes, I know that I could simplify the following expression, but it is
1548 ;; clearer (to me at least) this way.
1549 (and (not ange-ftp-smart-gateway)
1550 (ange-ftp-save-match-data
1551 (not (string-match ange-ftp-local-host-regexp host)))))
1552
1553(defun ange-ftp-use-smart-gateway-p (host)
1554 "Returns whether to access this host via a smart gateway."
1555 (and ange-ftp-smart-gateway
1556 (ange-ftp-save-match-data
1557 (not (string-match ange-ftp-local-host-regexp host)))))
1558
1559\f
1560;;; ------------------------------------------------------------
1561;;; Temporary file location and deletion...
1562;;; ------------------------------------------------------------
1563
1564(defvar ange-ftp-tmp-name-files ())
1565(defvar ange-ftp-tmp-name-hashtable (ange-ftp-make-hashtable 10))
1566(defvar ange-ftp-pid nil)
1567
1568(defun ange-ftp-get-pid ()
1569 "Half-hearted attempt to get the current process's id."
1570 (setq ange-ftp-pid (substring (make-temp-name "") 1)))
1571
1572(defun ange-ftp-make-tmp-name (host)
1573 "This routine will return the name of a new file."
1574 (let* ((template (if (ange-ftp-use-gateway-p host)
1575 ange-ftp-gateway-tmp-name-template
1576 ange-ftp-tmp-name-template))
1577 (pid (or ange-ftp-pid (ange-ftp-get-pid)))
1578 (start ?a)
1579 file entry)
1580 (while
1581 (progn
1582 (setq file (format "%s%c%s" template start pid))
1583 (setq entry (intern file ange-ftp-tmp-name-hashtable))
1584 (or (memq entry ange-ftp-tmp-name-files)
1585 (ange-ftp-real-file-exists-p file)))
1586 (if (> (setq start (1+ start)) ?z)
1587 (progn
1588 (setq template (concat template "X"))
1589 (setq start ?a))))
1590 (setq ange-ftp-tmp-name-files
1591 (cons entry ange-ftp-tmp-name-files))
1592 file))
1593
1594(defun ange-ftp-del-tmp-name (temp)
1595 (setq ange-ftp-tmp-name-files
1596 (delq (intern temp ange-ftp-tmp-name-hashtable)
1597 ange-ftp-tmp-name-files))
1598 (condition-case ()
1599 (ange-ftp-real-delete-file temp)
1600 (error nil)))
1601\f
1602;;;; ------------------------------------------------------------
1603;;;; Interactive gateway program support.
1604;;;; ------------------------------------------------------------
1605
1606(defvar ange-ftp-gwp-running t)
1607(defvar ange-ftp-gwp-status nil)
1608
1609(defun ange-ftp-gwp-sentinel (proc str)
1610 (setq ange-ftp-gwp-running nil))
1611
1612(defun ange-ftp-gwp-filter (proc str)
1613 (ange-ftp-save-match-data
1614 (ange-ftp-process-log-string proc str)
1615 (cond ((string-match "login: *$" str)
1616 (send-string proc
1617 (concat
1618 (let ((ange-ftp-default-user t))
1619 (ange-ftp-get-user ange-ftp-gateway-host))
1620 "\n")))
1621 ((string-match "Password: *$" str)
1622 (send-string proc
1623 (concat
1624 (ange-ftp-get-passwd ange-ftp-gateway-host
1625 (ange-ftp-get-user
1626 ange-ftp-gateway-host))
1627 "\n")))
1628 ((string-match ange-ftp-gateway-fatal-msgs str)
1629 (delete-process proc)
1630 (setq ange-ftp-gwp-running nil))
1631 ((string-match ange-ftp-gateway-prompt-pattern str)
1632 (setq ange-ftp-gwp-running nil
1633 ange-ftp-gwp-status t)))))
1634
1635(defun ange-ftp-gwp-start (host user name args)
1636 "Login to the gateway machine and fire up an ftp process."
1637 (let* ((gw-user (ange-ftp-get-user ange-ftp-gateway-host))
1638 (proc (start-process name name
1639 ange-ftp-gateway-program
1640 ange-ftp-gateway-host))
1641 (ftp (mapconcat (function identity) args " ")))
1642 (process-kill-without-query proc)
1643 (set-process-sentinel proc (function ange-ftp-gwp-sentinel))
1644 (set-process-filter proc (function ange-ftp-gwp-filter))
1645 (set-marker (process-mark proc) (point))
1646 (setq ange-ftp-gwp-running t
1647 ange-ftp-gwp-status nil)
1648 (ange-ftp-message "Connecting to gateway %s..." ange-ftp-gateway-host)
1649 (while ange-ftp-gwp-running ;perform login sequence
1650 (accept-process-output proc))
1651 (if (not ange-ftp-gwp-status)
1652 (ange-ftp-error host user "unable to login to gateway"))
1653 (ange-ftp-message "Connecting to gateway %s...done" ange-ftp-gateway-host)
1654 (setq ange-ftp-gwp-running t
1655 ange-ftp-gwp-status nil)
1656 (process-send-string proc ange-ftp-gateway-setup-term-command)
1657 (while ange-ftp-gwp-running ;zap ^M's and double echoing.
1658 (accept-process-output proc))
1659 (if (not ange-ftp-gwp-status)
1660 (ange-ftp-error host user "unable to set terminal modes on gateway"))
1661 (setq ange-ftp-gwp-running t
1662 ange-ftp-gwp-status nil)
1663 (process-send-string proc (concat "exec " ftp "\n")) ;spawn ftp process
1664 proc))
1665\f
1666;;;; ------------------------------------------------------------
1667;;;; Support for sending commands to the ftp process.
1668;;;; ------------------------------------------------------------
1669
1670(defun ange-ftp-raw-send-cmd (proc cmd &optional msg cont nowait)
1671 "Low-level routine to send the given ftp CMD to the ftp PROCESS.
1672MSG is an optional message to output before and after the command.
1673If CONT is non-NIL then it is either a function or a list of function and
1674some arguments. The function will be called when the ftp command has completed.
1675If CONT is NIL then this routine will return \( RESULT . LINE \) where RESULT
1676is whether the command was successful, and LINE is the line from the FTP
1677process that caused the command to complete.
1678If NOWAIT is given then the routine will return immediately the command has
1679been queued with no result. CONT will still be called, however."
1680 (if (memq (process-status proc) '(run open))
1681 (save-excursion
1682 (set-buffer (process-buffer proc))
1683 (while ange-ftp-process-busy
c7a464d0
RS
1684 ;; This is a kludge to let user quit in case ftp gets hung.
1685 ;; It matters because this function can be called from the filter.
1686 ;; It is bad to allow quitting in a filter, but getting hung
1687 ;; is worse. By binding quit-flag to nil, we might avoid
1688 ;; most of the probability of getting screwed because the user
1689 ;; wants to quit some command.
1690 (let ((quit-flag nil)
1691 (inhibit-quit nil))
1692 (accept-process-output)))
2f7ea155
RS
1693 (setq ange-ftp-process-string ""
1694 ange-ftp-process-result-line ""
1695 ange-ftp-process-busy t
1696 ange-ftp-process-result nil
1697 ange-ftp-process-multi-skip nil
1698 ange-ftp-process-msg msg
1699 ange-ftp-process-continue cont
1700 ange-ftp-hash-mark-count 0
1701 ange-ftp-last-percent -1
1702 cmd (concat cmd "\n"))
1703 (and msg ange-ftp-process-verbose (ange-ftp-message "%s..." msg))
1704 (goto-char (point-max))
d0bc419e 1705 (move-marker comint-last-input-start (point))
2f7ea155
RS
1706 ;; don't insert the password into the buffer on the USER command.
1707 (ange-ftp-save-match-data
1708 (if (string-match "^user \"[^\"]*\"" cmd)
1709 (insert (substring cmd 0 (match-end 0)) " Turtle Power!\n")
1710 (insert cmd)))
d0bc419e 1711 (move-marker comint-last-input-end (point))
2f7ea155
RS
1712 (send-string proc cmd)
1713 (set-marker (process-mark proc) (point))
1714 (if nowait
1715 nil
1716 ;; hang around for command to complete
1717 (while ange-ftp-process-busy
c7a464d0
RS
1718 ;; This is a kludge to let user quit in case ftp gets hung.
1719 ;; It matters because this function can be called from the filter.
1720 (let ((quit-flag nil)
1721 (inhibit-quit nil))
1722 (accept-process-output proc)))
2f7ea155
RS
1723 (if cont
1724 nil ;cont has already been called
1725 (cons ange-ftp-process-result ange-ftp-process-result-line))))))
1726
1727(defun ange-ftp-nslookup-host (host)
1728 "Attempt to resolve the given HOSTNAME using nslookup if possible."
1729 (interactive "sHost: ")
1730 (if ange-ftp-nslookup-program
1731 (let ((proc (start-process " *nslookup*" " *nslookup*"
1732 ange-ftp-nslookup-program host))
1733 (res host))
1734 (process-kill-without-query proc)
1735 (save-excursion
1736 (set-buffer (process-buffer proc))
1737 (while (memq (process-status proc) '(run open))
1738 (accept-process-output proc))
1739 (goto-char (point-min))
1740 (if (re-search-forward "Name:.*\nAddress: *\\(.*\\)$" nil t)
1741 (setq res (buffer-substring (match-beginning 1)
1742 (match-end 1))))
1743 (kill-buffer (current-buffer)))
1744 res)
1745 host))
1746
1747(defun ange-ftp-start-process (host user name)
1748 "Spawn a new ftp process ready to connect to machine HOST and give it NAME.
1749If HOST is only ftp-able through a gateway machine then spawn a shell
1750on the gateway machine to do the ftp instead."
1751 (let* ((use-gateway (ange-ftp-use-gateway-p host))
1752 (ftp-prog (if use-gateway
1753 ange-ftp-gateway-ftp-program-name
1754 ange-ftp-ftp-program-name))
1755 (args (append (list ftp-prog) ange-ftp-ftp-program-args))
1756 proc)
1757 (if use-gateway
1758 (if ange-ftp-gateway-program-interactive
1759 (setq proc (ange-ftp-gwp-start host user name args))
1760 (setq proc (apply 'start-process name name
1761 (append (list ange-ftp-gateway-program
1762 ange-ftp-gateway-host)
1763 args))))
1764 (setq proc (apply 'start-process name name args)))
1765 (process-kill-without-query proc)
2f7ea155
RS
1766 (save-excursion
1767 (set-buffer (process-buffer proc))
d0bc419e 1768 (ange-ftp-mode))
2f7ea155
RS
1769 (set-process-sentinel proc (function ange-ftp-process-sentinel))
1770 (set-process-filter proc (function ange-ftp-process-filter))
1771 (accept-process-output proc) ;wait for ftp startup message
1772 proc))
1773
d0bc419e
RS
1774(defun ange-ftp-mode ()
1775 (interactive)
1776 (comint-mode)
1777 (setq major-mode 'ange-ftp-mode)
1778 (setq mode-name "Ange-ftp")
2f7ea155 1779 (let ((proc (get-buffer-process (current-buffer))))
2f7ea155
RS
1780 (goto-char (point-max))
1781 (set-marker (process-mark proc) (point))
1782 (make-local-variable 'ange-ftp-process-string)
1783 (setq ange-ftp-process-string "")
1784 (make-local-variable 'ange-ftp-process-busy)
1785 (make-local-variable 'ange-ftp-process-result)
1786 (make-local-variable 'ange-ftp-process-msg)
1787 (make-local-variable 'ange-ftp-process-multi-skip)
1788 (make-local-variable 'ange-ftp-process-result-line)
1789 (make-local-variable 'ange-ftp-process-continue)
1790 (make-local-variable 'ange-ftp-hash-mark-count)
1791 (make-local-variable 'ange-ftp-binary-hash-mark-size)
1792 (make-local-variable 'ange-ftp-ascii-hash-mark-size)
1793 (make-local-variable 'ange-ftp-hash-mark-unit)
1794 (make-local-variable 'ange-ftp-xfer-size)
1795 (make-local-variable 'ange-ftp-last-percent)
1796 (setq ange-ftp-hash-mark-count 0)
1797 (setq ange-ftp-xfer-size 0)
1798 (setq ange-ftp-process-result-line "")))
1799
1800(defun ange-ftp-smart-login (host user pass account proc)
1801 "Connect to the FTP-server on HOST as USER using PASSWORD and ACCOUNT.
1802PROC is the FTP-client's process. This routine uses the smart-gateway
1803host specified in ``ange-ftp-gateway-host''."
1804 (let ((result (ange-ftp-raw-send-cmd
1805 proc
1806 (format "open %s %s"
1807 (ange-ftp-nslookup-host ange-ftp-gateway-host)
1808 ange-ftp-smart-gateway-port)
1809 (format "Opening FTP connection to %s via %s"
1810 host
1811 ange-ftp-gateway-host))))
1812 (or (car result)
1813 (ange-ftp-error host user
1814 (concat "OPEN request failed: "
1815 (cdr result))))
1816 (setq result (ange-ftp-raw-send-cmd
1817 proc (format "user \"%s\"@%s %s %s"
1818 user
1819 (ange-ftp-nslookup-host host)
1820 pass
1821 account)
1822 (format "Logging in as user %s@%s"
1823 user host)))
1824 (or (car result)
1825 (progn
1826 (ange-ftp-set-passwd host user nil) ; reset password
1827 (ange-ftp-set-account host user nil) ; reset account
1828 (ange-ftp-error host user
1829 (concat "USER request failed: "
1830 (cdr result)))))))
1831
1832(defun ange-ftp-normal-login (host user pass account proc)
1833 "Connect to the FTP-server on HOST as USER using PASSWORD and ACCOUNT.
1834PROC is the process to the FTP-client."
1835 (let ((result (ange-ftp-raw-send-cmd
1836 proc
1837 (format "open %s" (ange-ftp-nslookup-host host))
1838 (format "Opening FTP connection to %s" host))))
1839 (or (car result)
1840 (ange-ftp-error host user
1841 (concat "OPEN request failed: "
1842 (cdr result))))
1843 (setq result (ange-ftp-raw-send-cmd
1844 proc
1845 (format "user \"%s\" %s %s" user pass account)
1846 (format "Logging in as user %s@%s" user host)))
1847 (or (car result)
1848 (progn
1849 (ange-ftp-set-passwd host user nil) ;reset password.
1850 (ange-ftp-set-account host user nil) ;reset account.
1851 (ange-ftp-error host user
1852 (concat "USER request failed: "
1853 (cdr result)))))))
1854
1855(defvar ange-ftp-hash-mark-msgs
1856 "[hH]ash mark [^0-9]*\\([0-9]+\\)"
1857 "*Regexp matching the FTP client's output upon doing a HASH command.")
1858
1859(defun ange-ftp-guess-hash-mark-size (proc)
1860 (if ange-ftp-send-hash
1861 (save-excursion
1862 (set-buffer (process-buffer proc))
1863 (let* ((status (ange-ftp-raw-send-cmd proc "hash"))
1864 (result (car status))
1865 (line (cdr status)))
1866 (ange-ftp-save-match-data
1867 (if (string-match ange-ftp-hash-mark-msgs line)
1868 (let ((size (string-to-int
1869 (substring line
1870 (match-beginning 1)
1871 (match-end 1)))))
1872 (setq ange-ftp-ascii-hash-mark-size size
1873 ange-ftp-hash-mark-unit (ash size -4))
1874
1875 ;; if a default value for this is set, use that value.
1876 (or ange-ftp-binary-hash-mark-size
1877 (setq ange-ftp-binary-hash-mark-size size)))))))))
1878
1879(defun ange-ftp-get-process (host user)
1880 "Return the process object for a FTP process connected to HOST and
1881logged in as USER. Create a new process if needed."
1882 (let* ((name (ange-ftp-ftp-process-buffer host user))
1883 (proc (get-process name)))
1884 (if (and proc (memq (process-status proc) '(run open)))
1885 proc
1886 (let ((pass (ange-ftp-quote-string
1887 (ange-ftp-get-passwd host user)))
1888 (account (ange-ftp-quote-string
1889 (ange-ftp-get-account host user))))
1890 ;; grab a suitable process.
1891 (setq proc (ange-ftp-start-process host user name))
1892
1893 ;; login to FTP server.
1894 (if (ange-ftp-use-smart-gateway-p host)
1895 (ange-ftp-smart-login host user pass account proc)
1896 (ange-ftp-normal-login host user pass account proc))
1897
1898 ;; Tell client to send back hash-marks as progress. It isn't usually
1899 ;; fatal if this command fails.
1900 (ange-ftp-guess-hash-mark-size proc)
1901
1902 ;; Guess at the host type.
1903 (ange-ftp-guess-host-type host user)
1904
1905 ;; Run any user-specified hooks. Note that proc, host and user are
1906 ;; dynamically bound at this point.
1907 (run-hooks 'ange-ftp-process-startup-hook))
1908 proc)))
1909
1910;; Variables for caching host and host-type
1911(defvar ange-ftp-host-cache nil)
1912(defvar ange-ftp-host-type-cache nil)
1913
1914;; If ange-ftp-host-type is called with the optional user
1915;; argument, it will attempt to guess the host type by connecting
1916;; as user, if necessary. For efficiency, I have tried to give this
1917;; optional second argument only when necessary. Have I missed any calls
1918;; to ange-ftp-host-type where it should have been supplied?
1919
1920(defun ange-ftp-host-type (host &optional user)
1921 "Return a symbol which represents the type of the HOST given.
1922If the optional argument USER is given, attempts to guess the
1923host-type by logging in as USER."
1924 (if (eq host ange-ftp-host-cache)
1925 ange-ftp-host-type-cache
1926 ;; Trigger an ftp connection, in case we need to guess at the host type.
1927 (if (and user (ange-ftp-get-process host user) (eq host ange-ftp-host-cache))
1928 ange-ftp-host-type-cache
1929 (setq ange-ftp-host-cache host
1930 ange-ftp-host-type-cache
1931 (cond ((ange-ftp-dumb-unix-host host)
1932 'dumb-unix)
d0bc419e
RS
1933;; ((and (fboundp 'ange-ftp-vos-host)
1934;; (ange-ftp-vos-host host))
1935;; 'vos)
2f7ea155
RS
1936 ((and (fboundp 'ange-ftp-vms-host)
1937 (ange-ftp-vms-host host))
1938 'vms)
1939 ((and (fboundp 'ange-ftp-mts-host)
1940 (ange-ftp-mts-host host))
1941 'mts)
1942 ((and (fboundp 'ange-ftp-cms-host)
1943 (ange-ftp-cms-host host))
1944 'cms)
1945 (t
1946 'unix))))))
1947
1948;; It would be nice to abstract the functions ange-ftp-TYPE-host and
1949;; ange-ftp-add-TYPE-host. The trick is to abstract these functions
1950;; without sacrificing speed. Also, having separate variables
1951;; ange-ftp-TYPE-regexp is more user friendly then requiring the user to
1952;; set an alist to indicate that a host is of a given type. Even with
1953;; automatic host type recognition, setting a regexp is still a good idea
1954;; (for efficiency) if you log into a particular non-UNIX host frequently.
1955
d0bc419e 1956(defvar ange-ftp-fix-name-func-alist nil
2f7ea155 1957 "Association list of \( TYPE \. FUNC \) pairs, where FUNC is a routine
d0bc419e 1958which can change a UNIX file name into a name more suitable for a host of type
2f7ea155
RS
1959TYPE.")
1960
d0bc419e 1961(defvar ange-ftp-fix-dir-name-func-alist nil
2f7ea155 1962 "Association list of \( TYPE \. FUNC \) pairs, where FUNC is a routine
d0bc419e 1963which can change UNIX directory name into a directory name more suitable
2f7ea155
RS
1964for a host of type TYPE.")
1965
1966;; *** Perhaps the sense of this variable should be inverted, since there
1967;; *** is only 1 host type that can take ls-style listing options.
1968(defvar ange-ftp-dumb-host-types '(dumb-unix)
1969 "List of host types that can't take UNIX ls-style listing options.")
1970
1971(defun ange-ftp-send-cmd (host user cmd &optional msg cont nowait)
1972 "Find an ftp process connected to HOST logged in as USER and send it CMD.
1973MSG is an optional status message to be output before and after issuing the
1974command.
1975See the documentation for ange-ftp-raw-send-cmd for a description of CONT
1976and NOWAIT."
d0bc419e 1977 ;; Handle conversion to remote file name syntax and remote ls option
2f7ea155
RS
1978 ;; capability.
1979 (let ((cmd0 (car cmd))
1980 (cmd1 (nth 1 cmd))
d0bc419e 1981 cmd2 cmd3 host-type fix-name-func)
2f7ea155
RS
1982
1983 (cond
1984
1985 ;; pwd case (We don't care what host-type.)
1986 ((null cmd1))
1987
d0bc419e 1988 ;; cmd == 'dir "remote-name" "local-name" "ls-switches"
2f7ea155
RS
1989 ((progn
1990 (setq cmd2 (nth 2 cmd)
1991 host-type (ange-ftp-host-type host user))
1992 ;; This will trigger an FTP login, if one doesn't exist
1993 (eq cmd0 'dir))
1994 (setq cmd1 (funcall
d0bc419e 1995 (or (cdr (assq host-type ange-ftp-fix-dir-name-func-alist))
2f7ea155
RS
1996 'identity)
1997 cmd1)
1998 cmd3 (nth 3 cmd))
1999 ;; Need to deal with the HP-UX ftp bug. This should also allow
2000 ;; us to resolve symlinks to directories on SysV machines. (Sebastian will
2001 ;; be happy.)
2002 (and (eq host-type 'unix)
2003 (string-match "/$" cmd1)
2004 (not (string-match "R" cmd3))
2005 (setq cmd1 (concat cmd1 ".")))
2006 ;; If the remote ls can take switches, put them in
2007 (or (memq host-type ange-ftp-dumb-host-types)
2008 (setq cmd0 'ls
2009 cmd1 (format "\"%s %s\"" cmd3 cmd1))))
2010
d0bc419e 2011 ;; First argument is the remote name
2f7ea155 2012 ((let ((ange-ftp-this-user user)
d0bc419e
RS
2013 (ange-ftp-this-host host)
2014 (ange-ftp-this-msg msg))
2015 (setq fix-name-func (or (cdr (assq host-type
2016 ange-ftp-fix-name-func-alist))
2017 'identity))
2f7ea155 2018 (memq cmd0 '(get delete mkdir rmdir cd)))
d0bc419e 2019 (setq cmd1 (funcall fix-name-func cmd1)))
2f7ea155 2020
d0bc419e 2021 ;; Second argument is the remote name
2f7ea155 2022 ((memq cmd0 '(append put chmod))
d0bc419e 2023 (setq cmd2 (funcall fix-name-func cmd2)))
2f7ea155 2024
d0bc419e 2025 ;; Both arguments are remote names
2f7ea155 2026 ((eq cmd0 'rename)
d0bc419e
RS
2027 (setq cmd1 (funcall fix-name-func cmd1)
2028 cmd2 (funcall fix-name-func cmd2))))
2f7ea155
RS
2029
2030 ;; Turn the command into one long string
2031 (setq cmd0 (symbol-name cmd0))
2032 (setq cmd (concat cmd0
2033 (and cmd1 (concat " " cmd1))
2034 (and cmd2 (concat " " cmd2))))
2035
2036 ;; Actually send the resulting command.
2037 (let (afsc-result
2038 afsc-line)
2039 (ange-ftp-raw-send-cmd
2040 (ange-ftp-get-process host user)
2041 cmd
2042 msg
2043 (list
2044 (function (lambda (result line host user
2045 cmd msg cont nowait)
2046 (or cont
2047 (setq afsc-result result
2048 afsc-line line))
2049 (if result
2050 (ange-ftp-call-cont cont result line)
2051 (ange-ftp-raw-send-cmd
2052 (ange-ftp-get-process host user)
2053 cmd
2054 msg
2055 (list
2056 (function (lambda (result line cont)
2057 (or cont
2058 (setq afsc-result result
2059 afsc-line line))
2060 (ange-ftp-call-cont cont result line)))
2061 cont)
2062 nowait))))
2063 host user cmd msg cont nowait)
2064 nowait)
2065
2066 (if nowait
2067 nil
2068 (if cont
2069 nil
2070 (cons afsc-result afsc-line))))))
2071
2072;; It might be nice to message users about the host type identified,
2073;; but there is so much other messaging going on, it would not be
2074;; seen. No point in slowing things down just so users can read
2075;; a host type message.
2076
d0bc419e 2077(defconst ange-ftp-cms-name-template
2f7ea155
RS
2078 (concat
2079 "^[-A-Z0-9$*][-A-Z0-9$*]?[-A-Z0-9$*]?[-A-Z0-9$*]?[-A-Z0-9$*]?"
2080 "[-A-Z0-9$*]?[-A-Z0-9$*]?[-A-Z0-9$*]?\\.[0-9][0-9][0-9A-Z]$"))
d0bc419e 2081(defconst ange-ftp-vms-name-template
2f7ea155 2082 "^[-A-Z0-9_$]+:\\[[-A-Z0-9_$]+\\(\\.[-A-Z0-9_$]+\\)*\\]$")
d0bc419e 2083(defconst ange-ftp-mts-name-template
2f7ea155
RS
2084 "^[A-Z0-9._][A-Z0-9._][A-Z0-9._][A-Z0-9._]:$")
2085
2086(defun ange-ftp-guess-host-type (host user)
2087 "Guess at the the host type of HOST by doing a pwd, and examining
2088the directory syntax."
2089 (let ((host-type (ange-ftp-host-type host))
2090 (key (concat host "/" user "/~")))
2091 (if (eq host-type 'unix)
2092 ;; Note that ange-ftp-host-type returns unix as the default value.
2093 (ange-ftp-save-match-data
2094 (let* ((result (ange-ftp-get-pwd host user))
2095 (dir (car result))
d0bc419e 2096 fix-name-func)
2f7ea155
RS
2097 (cond ((null dir)
2098 (message "Warning! Unable to get home directory")
2099 (sit-for 1)
2100 (if (string-match
2101 "^450 No current working directory defined$"
2102 (cdr result))
2103
2104 ;; We'll assume that if pwd bombs with this
2105 ;; error message, then it's CMS.
2106 (progn
2107 (ange-ftp-add-cms-host host)
2108 (setq ange-ftp-host-cache host
2109 ange-ftp-host-type-cache 'cms))))
2110
2111 ;; try for VMS
d0bc419e 2112 ((string-match ange-ftp-vms-name-template dir)
2f7ea155
RS
2113 (ange-ftp-add-vms-host host)
2114 ;; The add-host functions clear the host type cache.
2115 ;; Therefore, need to set the cache afterwards.
2116 (setq ange-ftp-host-cache host
2117 ange-ftp-host-type-cache 'vms))
2118
2119 ;; try for MTS
d0bc419e 2120 ((string-match ange-ftp-mts-name-template dir)
2f7ea155
RS
2121 (ange-ftp-add-mts-host host)
2122 (setq ange-ftp-host-cache host
2123 ange-ftp-host-type-cache 'mts))
2124
2125 ;; try for CMS
d0bc419e 2126 ((string-match ange-ftp-cms-name-template dir)
2f7ea155
RS
2127 (ange-ftp-add-cms-host host)
2128 (setq ange-ftp-host-cache host
2129 ange-ftp-host-type-cache 'cms))
2130
2131 ;; assume UN*X
2132 (t
2133 (setq ange-ftp-host-cache host
2134 ange-ftp-host-type-cache 'unix)))
2135
2136 ;; Now that we have done a pwd, might as well put it in
2137 ;; the expand-dir hashtable.
2138 (let ((ange-ftp-this-user user)
2139 (ange-ftp-this-host host))
d0bc419e
RS
2140 (setq fix-name-func (cdr (assq ange-ftp-host-type-cache
2141 ange-ftp-fix-name-func-alist)))
2142 (if fix-name-func
2143 (setq dir (funcall fix-name-func dir 'reverse))))
2f7ea155
RS
2144 (ange-ftp-put-hash-entry key dir
2145 ange-ftp-expand-dir-hashtable))))
2146
2147 ;; In the special case of CMS make sure that know the
2148 ;; expansion of the home minidisk now, because we will
2149 ;; be doing a lot of cd's.
2150 (if (and (eq host-type 'cms)
2151 (not (ange-ftp-hash-entry-exists-p
2152 key ange-ftp-expand-dir-hashtable)))
2153 (let ((dir (car (ange-ftp-get-pwd host user))))
2154 (if dir
2155 (ange-ftp-put-hash-entry key (concat "/" dir)
2156 ange-ftp-expand-dir-hashtable)
2157 (message "Warning! Unable to get home directory")
2158 (sit-for 1))))))
2159
2160\f
2161;;;; ------------------------------------------------------------
2162;;;; Remote file and directory listing support.
2163;;;; ------------------------------------------------------------
2164
2165(defun ange-ftp-dumb-unix-host (host)
2166 "Returns whether HOST's FTP server doesn't like \'ls\' or \'dir\' commands
2167to take switch arguments."
2168 (and ange-ftp-dumb-unix-host-regexp
2169 (ange-ftp-save-match-data
2170 (string-match ange-ftp-dumb-unix-host-regexp host))))
2171
2172(defun ange-ftp-add-dumb-unix-host (host)
2173 "Interactively adds a given HOST to ange-ftp-dumb-unix-host-regexp."
2174 (interactive
2175 (list (read-string "Host: "
68f5eb5a 2176 (let ((name (or (buffer-file-name) default-directory)))
d0bc419e 2177 (and name (car (ange-ftp-ftp-name name)))))))
2f7ea155
RS
2178 (if (not (ange-ftp-dumb-unix-host host))
2179 (setq ange-ftp-dumb-unix-host-regexp
2180 (concat "^" (regexp-quote host) "$"
2181 (and ange-ftp-dumb-unix-host-regexp "\\|")
2182 ange-ftp-dumb-unix-host-regexp)
2183 ange-ftp-host-cache nil)))
2184
2185(defvar ange-ftp-parse-list-func-alist nil
2186 "Association list of \( TYPE \. FUNC \) pairs. The FUNC is a routine
2187which can parse the output from a DIR listing for a host of type TYPE.")
2188
2189;; With no-error nil, this function returns:
d0bc419e 2190;; an error if file is not an ange-ftp-name
2f7ea155
RS
2191;; (This should never happen.)
2192;; an error if either the listing is unreadable or there is an ftp error.
2193;; the listing (a string), if everything works.
2194;;
2195;; With no-error t, it returns:
d0bc419e 2196;; an error if not an ange-ftp-name
2f7ea155
RS
2197;; error if listing is unreable (most likely caused by a slow connection)
2198;; nil if ftp error (this is because although asking to list a nonexistent
2199;; directory on a remote unix machine usually (except
2200;; maybe for dumb hosts) returns an ls error, but no
2201;; ftp error, if the same is done on a VMS machine,
2202;; an ftp error is returned. Need to trap the error
2203;; so we can go on and try to list the parent.)
2204;; the listing, if everything works.
2205
d0bc419e
RS
2206;; If WILDCARD is non-nil, then this implements the guts of insert-directory
2207;; in the wildcard case. Then we make a relative directory listing
2208;; of FILE within the directory specified by `default-directory'.
2209
2210(defun ange-ftp-ls (file lsargs parse &optional no-error wildcard)
2f7ea155
RS
2211 "Return the output of an `DIR' or `ls' command done over ftp.
2212FILE is the full name of the remote file, LSARGS is any args to pass to the
2213`ls' command, and PARSE specifies that the output should be parsed and stored
2214away in the internal cache."
2215 ;; If parse is t, we assume that file is a directory. i.e. we only parse
2216 ;; full directory listings.
2217 (let* ((ange-ftp-this-file (ange-ftp-expand-file-name file))
d0bc419e 2218 (parsed (ange-ftp-ftp-name ange-ftp-this-file)))
2f7ea155
RS
2219 (if parsed
2220 (let* ((host (nth 0 parsed))
2221 (user (nth 1 parsed))
d0bc419e 2222 (name (ange-ftp-quote-string (nth 2 parsed)))
2f7ea155
RS
2223 (key (directory-file-name ange-ftp-this-file))
2224 (host-type (ange-ftp-host-type host user))
2225 (dumb (memq host-type ange-ftp-dumb-host-types))
2226 result
2227 temp
2228 lscmd parse-func)
d0bc419e
RS
2229 (if (string-equal name "")
2230 (setq name
2f7ea155
RS
2231 (ange-ftp-real-file-name-as-directory
2232 (ange-ftp-expand-dir host user "~"))))
2233 (if (and ange-ftp-ls-cache-file
2234 (string-equal key ange-ftp-ls-cache-file)
2235 ;; Don't care about lsargs for dumb hosts.
2236 (or dumb (string-equal lsargs ange-ftp-ls-cache-lsargs)))
2237 ange-ftp-ls-cache-res
2238 (setq temp (ange-ftp-make-tmp-name host))
d0bc419e
RS
2239 (if wildcard
2240 (progn
2241 (ange-ftp-cd host user (file-name-directory name))
2242 (setq lscmd (list 'dir file temp lsargs)))
2243 (setq lscmd (list 'dir name temp lsargs)))
2f7ea155
RS
2244 (unwind-protect
2245 (if (car (setq result (ange-ftp-send-cmd
2246 host
2247 user
2248 lscmd
2249 (format "Listing %s"
2250 (ange-ftp-abbreviate-filename
2251 ange-ftp-this-file)))))
2252 (save-excursion
2253 (set-buffer (get-buffer-create
2254 ange-ftp-data-buffer-name))
2255 (erase-buffer)
2256 (if (ange-ftp-real-file-readable-p temp)
2257 (ange-ftp-real-insert-file-contents temp)
2258 (sleep-for ange-ftp-retry-time)
2259 ;wait for file to possibly appear
2260 (if (ange-ftp-real-file-readable-p temp)
2261 ;; Try again.
2262 (ange-ftp-real-insert-file-contents temp)
2263 (ange-ftp-error host user
2264 (format
2265 "list data file %s not readable"
2266 temp))))
2267 (if parse
2268 (ange-ftp-set-files
2269 ange-ftp-this-file
2270 (if (setq
2271 parse-func
2272 (cdr (assq host-type
2273 ange-ftp-parse-list-func-alist)))
2274 (funcall parse-func)
2275 (ange-ftp-parse-dired-listing lsargs))))
2276 (setq ange-ftp-ls-cache-file key
2277 ange-ftp-ls-cache-lsargs lsargs
2278 ; For dumb hosts-types this is
2279 ; meaningless but harmless.
2280 ange-ftp-ls-cache-res (buffer-string))
2281 ;; (kill-buffer (current-buffer))
2282 ange-ftp-ls-cache-res)
2283 (if no-error
2284 nil
2285 (ange-ftp-error host user
2286 (concat "DIR failed: " (cdr result)))))
2287 (ange-ftp-del-tmp-name temp))))
2288 (error "Should never happen. Please report. Bug ref. no.: 1"))))
2289\f
2290;;;; ------------------------------------------------------------
2291;;;; Directory information caching support.
2292;;;; ------------------------------------------------------------
2293
2294(defconst ange-ftp-date-regexp
2295 (concat
2296 " \\(Jan\\|Feb\\|Mar\\|Apr\\|May\\|Jun\\|Jul\\|Aug\\|Sep\\|Oct"
2297 "\\|Nov\\|Dec\\) +[0-3]?[0-9] "))
2298
2299(defvar ange-ftp-add-file-entry-alist nil
2300 "Association list of pairs \( TYPE \. FUNC \), where FUNC
2301is a function to be used to add a file entry for the OS TYPE. The
2302main reason for this alist is to deal with file versions in VMS.")
2303
2304(defvar ange-ftp-delete-file-entry-alist nil
2305 "Association list of pairs \( TYPE \. FUNC \), where FUNC
2306is a function to be used to delete a file entry for the OS TYPE.
2307The main reason for this alist is to deal with file versions in
2308VMS.")
2309
d0bc419e
RS
2310(defun ange-ftp-add-file-entry (name &optional dir-p)
2311 "Add a file entry for file NAME, if its directory info exists."
2f7ea155 2312 (funcall (or (cdr (assq (ange-ftp-host-type
d0bc419e 2313 (car (ange-ftp-ftp-name name)))
2f7ea155
RS
2314 ange-ftp-add-file-entry-alist))
2315 'ange-ftp-internal-add-file-entry)
d0bc419e 2316 name dir-p)
2f7ea155
RS
2317 (setq ange-ftp-ls-cache-file nil))
2318
d0bc419e
RS
2319(defun ange-ftp-delete-file-entry (name &optional dir-p)
2320 "Delete the file entry for file NAME, if its directory info exists."
2f7ea155 2321 (funcall (or (cdr (assq (ange-ftp-host-type
d0bc419e 2322 (car (ange-ftp-ftp-name name)))
2f7ea155
RS
2323 ange-ftp-delete-file-entry-alist))
2324 'ange-ftp-internal-delete-file-entry)
d0bc419e 2325 name dir-p)
2f7ea155
RS
2326 (setq ange-ftp-ls-cache-file nil))
2327
2328(defmacro ange-ftp-parse-filename ()
2329 ;;Extract the filename from the current line of a dired-like listing.
2330 (` (let ((eol (progn (end-of-line) (point))))
2331 (beginning-of-line)
2332 (if (re-search-forward ange-ftp-date-regexp eol t)
2333 (progn
2334 (skip-chars-forward " ")
2335 (skip-chars-forward "^ " eol)
2336 (skip-chars-forward " " eol)
2337 ;; We bomb on filenames starting with a space.
2338 (buffer-substring (point) eol))))))
2339
2340;; This deals with the F switch. Should also do something about
2341;; unquoting names obtained with the SysV b switch and the GNU Q
2342;; switch. See Sebastian's dired-get-filename.
2343
2344(defmacro ange-ftp-ls-parser ()
2345 ;; Note that switches is dynamically bound.
2346 ;; Meant to be called by ange-ftp-parse-dired-listing
2347 (` (let ((tbl (ange-ftp-make-hashtable))
2348 (used-F (and (stringp switches)
2349 (string-match "F" switches)))
2350 file-type symlink directory file)
2351 (while (setq file (ange-ftp-parse-filename))
2352 (beginning-of-line)
2353 (skip-chars-forward "\t 0-9")
2354 (setq file-type (following-char)
2355 directory (eq file-type ?d))
2356 (if (eq file-type ?l)
2357 (if (string-match " -> " file)
2358 (setq symlink (substring file (match-end 0))
2359 file (substring file 0 (match-beginning 0)))
2360 ;; Shouldn't happen
2361 (setq symlink ""))
2362 (setq symlink nil))
2363 ;; Only do a costly regexp search if the F switch was used.
2364 (if (and used-F
2365 (not (string-equal file ""))
2366 (looking-at
2367 ".[-r][-w]\\([^ ]\\)[-r][-w]\\([^ ]\\)[-r][-w]\\([^ ]\\)"))
2368 (let ((socket (eq file-type ?s))
2369 (executable
2370 (and (not symlink) ; x bits don't mean a thing for symlinks
2371 (string-match "[xst]"
2372 (concat
2373 (buffer-substring
2374 (match-beginning 1)
2375 (match-end 1))
2376 (buffer-substring
2377 (match-beginning 2)
2378 (match-end 2))
2379 (buffer-substring
2380 (match-beginning 3)
2381 (match-end 3)))))))
2382 ;; Some ls's with the F switch mark symlinks with an @ (ULTRIX)
2383 ;; and others don't. (sigh...) Beware, that some Unix's don't
2384 ;; seem to believe in the F-switch
2385 (if (or (and symlink (string-match "@$" file))
2386 (and directory (string-match "/$" file))
2387 (and executable (string-match "*$" file))
2388 (and socket (string-match "=$" file)))
2389 (setq file (substring file 0 -1)))))
2390 (ange-ftp-put-hash-entry file (or symlink directory) tbl)
2391 (forward-line 1))
2392 (ange-ftp-put-hash-entry "." t tbl)
2393 (ange-ftp-put-hash-entry ".." t tbl)
2394 tbl)))
2395
2396;;; The dl stuff for descriptive listings
2397
2398(defvar ange-ftp-dl-dir-regexp nil
2399 "Regexp matching directories which are listed in dl format. This regexp
2400shouldn't be anchored with a trailing $ so that it will match subdirectories
2401as well.")
2402
2403(defun ange-ftp-add-dl-dir (dir)
c8fa98cc 2404 "Interactively adds a DIR to ange-ftp-dl-dir-regexp."
2f7ea155
RS
2405 (interactive
2406 (list (read-string "Directory: "
68f5eb5a 2407 (let ((name (or (buffer-file-name) default-directory)))
d0bc419e 2408 (and name (ange-ftp-ftp-name name)
2f7ea155
RS
2409 (file-name-directory name))))))
2410 (if (not (and ange-ftp-dl-dir-regexp
2411 (string-match ange-ftp-dl-dir-regexp dir)))
2412 (setq ange-ftp-dl-dir-regexp
2413 (concat "^" (regexp-quote dir)
2414 (and ange-ftp-dl-dir-regexp "\\|")
2415 ange-ftp-dl-dir-regexp))))
2416
2417(defmacro ange-ftp-dl-parser ()
2418 ;; Parse the current buffer, which is assumed to be a descriptive
2419 ;; listing, and return a hashtable.
2420 (` (let ((tbl (ange-ftp-make-hashtable)))
2421 (while (not (eobp))
2422 (ange-ftp-put-hash-entry
2423 (buffer-substring (point)
2424 (progn
2425 (skip-chars-forward "^ /\n")
2426 (point)))
2427 (eq (following-char) ?/)
2428 tbl)
2429 (forward-line 1))
2430 (ange-ftp-put-hash-entry "." t tbl)
2431 (ange-ftp-put-hash-entry ".." t tbl)
2432 tbl)))
2433
2434(defun ange-ftp-parse-dired-listing (&optional switches)
2435 "Parse the current buffer which is assumed to be in a dired-like listing
2436format, and return a hashtable as the result. If the listing is not really
2437a listing, then return nil."
2438 (ange-ftp-save-match-data
2439 (cond
2440 ((looking-at "^total [0-9]+$")
2441 (forward-line 1)
2442 (ange-ftp-ls-parser))
2443 ((looking-at "[^\n]+\\( not found\\|: Not a directory\\)\n\\'")
2444 ;; It's an ls error message.
2445 nil)
2446 ((eobp) ; i.e. (zerop (buffer-size))
2447 ;; This could be one of:
2448 ;; (1) An Ultrix ls error message
2449 ;; (2) A listing with the A switch of an empty directory
2450 ;; on a machine which doesn't give a total line.
2451 ;; (3) The twilight zone.
2452 ;; We'll assume (1) for now.
2453 nil)
2454 ((re-search-forward ange-ftp-date-regexp nil t)
2455 (beginning-of-line)
2456 (ange-ftp-ls-parser))
2457 ((re-search-forward "^[^ \n\t]+ +\\([0-9]+\\|-\\|=\\) " nil t)
2458 ;; It's a dl listing (I hope).
2459 ;; file is bound by the call to ange-ftp-ls
2460 (ange-ftp-add-dl-dir ange-ftp-this-file)
2461 (beginning-of-line)
2462 (ange-ftp-dl-parser))
2463 (t nil))))
2464
2465(defun ange-ftp-set-files (directory files)
2466 "For a given DIRECTORY, set or change the associated FILES hashtable."
2467 (and files (ange-ftp-put-hash-entry (file-name-as-directory directory)
2468 files ange-ftp-files-hashtable)))
2469
2470(defun ange-ftp-get-files (directory &optional no-error)
2471 "Given a given DIRECTORY, return a hashtable of file entries.
2472This will give an error or return nil, depending on the value of
2473NO-ERROR, if a listing for DIRECTORY cannot be obtained."
2474 (setq directory (file-name-as-directory directory)) ;normalize
2475 (or (ange-ftp-get-hash-entry directory ange-ftp-files-hashtable)
2476 (ange-ftp-save-match-data
2477 (and (ange-ftp-ls directory
2478 ;; This is an efficiency hack. We try to
2479 ;; anticipate what sort of listing dired
2480 ;; might want, and cache just such a listing.
2481 (if (and (boundp 'dired-actual-switches)
2482 (stringp dired-actual-switches)
2483 ;; We allow the A switch, which lists
2484 ;; all files except "." and "..".
2485 ;; This is OK because we manually
2486 ;; insert these entries
2487 ;; in the hash table.
2488 (string-match
2489 "[aA]" dired-actual-switches)
2490 (string-match
2491 "l" dired-actual-switches)
2492 (not (string-match
2493 "R" dired-actual-switches)))
2494 dired-actual-switches
2495 (if (and (boundp 'dired-listing-switches)
2496 (stringp dired-listing-switches)
2497 (string-match
2498 "[aA]" dired-listing-switches)
2499 (string-match
2500 "l" dired-listing-switches)
2501 (not (string-match
2502 "R" dired-listing-switches)))
2503 dired-listing-switches
2504 "-al"))
2505 t no-error)
2506 (ange-ftp-get-hash-entry
2507 directory ange-ftp-files-hashtable)))))
2508
d0bc419e
RS
2509(defmacro ange-ftp-get-file-part (name)
2510 "Given NAME, return the file part that can be used for looking up the
2f7ea155 2511file's entry in a hashtable."
d0bc419e 2512 (` (let ((file (file-name-nondirectory (, name))))
2f7ea155
RS
2513 (if (string-equal file "")
2514 "."
2515 file))))
2516
2517(defmacro ange-ftp-allow-child-lookup (dir file)
2518 "Return whether ange-ftp-file-entry-p and ange-ftp-get-file-entry are
d0bc419e 2519allowed to determine if NAME is a sub-directory by listing it directly,
2f7ea155
RS
2520rather than listing its parent directory. This is used for efficiency so
2521that a wasted listing is not done:
25221. When looking for a .dired file in dired-x.el.
25232. The syntax of FILE and DIR make it impossible that FILE could be a valid
2524 subdirectory. This is of course an OS dependent judgement."
2525 (` (not
2526 (let* ((efile (, file)) ; expand once.
2527 (edir (, dir))
d0bc419e 2528 (parsed (ange-ftp-ftp-name edir))
2f7ea155
RS
2529 (host-type (ange-ftp-host-type
2530 (car parsed))))
2531 (or
68f5eb5a
RS
2532;;; This variable seems not to exist in Emacs 19 -- rms.
2533;;; ;; Deal with dired
2534;;; (and (boundp 'dired-local-variables-file)
2535;;; (stringp dired-local-variables-file)
2536;;; (string-equal dired-local-variables-file efile))
2f7ea155
RS
2537 ;; No dots in dir names in vms.
2538 (and (eq host-type 'vms)
2539 (string-match "\\." efile))
2540 ;; No subdirs in mts of cms.
2541 (and (memq host-type '(mts cms))
2542 (not (string-equal "/" (nth 2 parsed)))))))))
2543
d0bc419e
RS
2544(defun ange-ftp-file-entry-p (name)
2545 "Given NAME, return whether there is a file entry for it."
2546 (let* ((name (directory-file-name name))
2547 (dir (file-name-directory name))
2f7ea155 2548 (ent (ange-ftp-get-hash-entry dir ange-ftp-files-hashtable))
d0bc419e 2549 (file (ange-ftp-get-file-part name)))
2f7ea155
RS
2550 (if ent
2551 (ange-ftp-hash-entry-exists-p file ent)
2552 (or (and (ange-ftp-allow-child-lookup dir file)
d0bc419e 2553 (setq ent (ange-ftp-get-files name t))
2f7ea155
RS
2554 ;; Try a child lookup. i.e. try to list file as a
2555 ;; subdirectory of dir. This is a good idea because
2556 ;; we may not have read permission for file's parent. Also,
2557 ;; people tend to work down directory trees anyway. We use
2558 ;; no-error ;; because if file does not exist as a subdir.,
2559 ;; then dumb hosts will give an ftp error. Smart unix hosts
2560 ;; will simply send back the ls
2561 ;; error message.
2562 (ange-ftp-get-hash-entry "." ent))
2563 ;; Child lookup failed. Try the parent. If this bombs,
2564 ;; we are at wits end -- signal an error.
2565 ;; Problem: If this signals an error, the error message
2566 ;; may not have a lot to do with what went wrong.
2567 (ange-ftp-hash-entry-exists-p file
2568 (ange-ftp-get-files dir))))))
2569
d0bc419e 2570(defun ange-ftp-get-file-entry (name)
c8fa98cc
CZ
2571 "Given NAME, return the given file entry.
2572The entry will be either t for a directory, nil for a normal file,
2573or a string for a symlink. If the file isn't in the hashtable,
2574this also returns nil."
d0bc419e
RS
2575 (let* ((name (directory-file-name name))
2576 (dir (file-name-directory name))
2f7ea155 2577 (ent (ange-ftp-get-hash-entry dir ange-ftp-files-hashtable))
d0bc419e 2578 (file (ange-ftp-get-file-part name)))
2f7ea155
RS
2579 (if ent
2580 (ange-ftp-get-hash-entry file ent)
2581 (or (and (ange-ftp-allow-child-lookup dir file)
d0bc419e 2582 (setq ent (ange-ftp-get-files name t))
2f7ea155
RS
2583 (ange-ftp-get-hash-entry "." ent))
2584 ;; i.e. it's a directory by child lookup
2585 (ange-ftp-get-hash-entry file
2586 (ange-ftp-get-files dir))))))
2587
d0bc419e 2588(defun ange-ftp-internal-delete-file-entry (name &optional dir-p)
2f7ea155
RS
2589 (if dir-p
2590 (progn
d0bc419e
RS
2591 (setq name (file-name-as-directory name))
2592 (ange-ftp-del-hash-entry name ange-ftp-files-hashtable)
2593 (setq name (directory-file-name name))))
2f7ea155
RS
2594 ;; Note that file-name-as-directory followed by directory-file-name
2595 ;; serves to canonicalize directory file names to their unix form.
2596 ;; i.e. in VMS, FOO.DIR -> FOO/ -> FOO
d0bc419e 2597 (let ((files (ange-ftp-get-hash-entry (file-name-directory name)
2f7ea155
RS
2598 ange-ftp-files-hashtable)))
2599 (if files
d0bc419e 2600 (ange-ftp-del-hash-entry (ange-ftp-get-file-part name)
2f7ea155
RS
2601 files))))
2602
d0bc419e 2603(defun ange-ftp-internal-add-file-entry (name &optional dir-p)
2f7ea155 2604 (and dir-p
d0bc419e
RS
2605 (setq name (directory-file-name name)))
2606 (let ((files (ange-ftp-get-hash-entry (file-name-directory name)
2f7ea155
RS
2607 ange-ftp-files-hashtable)))
2608 (if files
d0bc419e 2609 (ange-ftp-put-hash-entry (ange-ftp-get-file-part name)
2f7ea155
RS
2610 dir-p
2611 files))))
2612
2613(defun ange-ftp-wipe-file-entries (host user)
2614 "Replace the file entry information hashtable with one that doesn't have any
2615entries for the given HOST, USER pair."
2616 (let ((new-tbl (ange-ftp-make-hashtable (length ange-ftp-files-hashtable))))
2617 (ange-ftp-map-hashtable
2618 (function
2619 (lambda (key val)
d0bc419e 2620 (let ((parsed (ange-ftp-ftp-name key)))
2f7ea155
RS
2621 (if parsed
2622 (let ((h (nth 0 parsed))
2623 (u (nth 1 parsed)))
2624 (or (and (equal host h) (equal user u))
2625 (ange-ftp-put-hash-entry key val new-tbl)))))))
2626 ange-ftp-files-hashtable)
2627 (setq ange-ftp-files-hashtable new-tbl)))
2628\f
2629;;;; ------------------------------------------------------------
2630;;;; File transfer mode support.
2631;;;; ------------------------------------------------------------
2632
2633(defun ange-ftp-set-binary-mode (host user)
2634 "Tell the ftp process for the given HOST & USER to switch to binary mode."
2635 (let ((result (ange-ftp-send-cmd host user '(type "binary"))))
2636 (if (not (car result))
2637 (ange-ftp-error host user (concat "BINARY failed: " (cdr result)))
2638 (save-excursion
2639 (set-buffer (process-buffer (ange-ftp-get-process host user)))
2640 (setq ange-ftp-hash-mark-unit (ash ange-ftp-binary-hash-mark-size -4))))))
2641
2642(defun ange-ftp-set-ascii-mode (host user)
2643 "Tell the ftp process for the given HOST & USER to switch to ascii mode."
2644 (let ((result (ange-ftp-send-cmd host user '(type "ascii"))))
2645 (if (not (car result))
2646 (ange-ftp-error host user (concat "ASCII failed: " (cdr result)))
2647 (save-excursion
2648 (set-buffer (process-buffer (ange-ftp-get-process host user)))
2649 (setq ange-ftp-hash-mark-unit (ash ange-ftp-ascii-hash-mark-size -4))))))
2650\f
2651(defun ange-ftp-cd (host user dir)
2652 (let ((result (ange-ftp-send-cmd host user (list 'cd dir) "Doing CD")))
2653 (or (car result)
2654 (ange-ftp-error host user (concat "CD failed: " (cdr result))))))
2655
2656(defun ange-ftp-get-pwd (host user)
2657 "Attempts to get the current working directory for the given HOST/USER pair.
2658Returns \( DIR . LINE \) where DIR is either the directory or NIL if not found,
2659and LINE is the relevant success or fail line from the FTP-client."
2660 (let* ((result (ange-ftp-send-cmd host user '(pwd) "Getting PWD"))
2661 (line (cdr result))
2662 dir)
2663 (if (car result)
2664 (ange-ftp-save-match-data
2665 (and (or (string-match "\"\\([^\"]*\\)\"" line)
2666 (string-match " \\([^ ]+\\) " line)) ; stone-age VMS servers!
2667 (setq dir (substring line
2668 (match-beginning 1)
2669 (match-end 1))))))
2670 (cons dir line)))
2671\f
2672;;; ------------------------------------------------------------
2673;;; expand-file-name and friends...which currently don't work
2674;;; ------------------------------------------------------------
2675
2676(defun ange-ftp-expand-dir (host user dir)
2677 "Return the result of doing a PWD in the current FTP session to machine HOST
2678logged in as user USER and cd'd to directory DIR."
2679 (let* ((host-type (ange-ftp-host-type host user))
2680 ;; It is more efficient to call ange-ftp-host-type
2681 ;; before binding res, because ange-ftp-host-type sometimes
2682 ;; adds to the info in the expand-dir-hashtable.
d0bc419e
RS
2683 (fix-name-func
2684 (cdr (assq host-type ange-ftp-fix-name-func-alist)))
2f7ea155
RS
2685 (key (concat host "/" user "/" dir))
2686 (res (ange-ftp-get-hash-entry key ange-ftp-expand-dir-hashtable)))
2687 (or res
2688 (progn
2689 (or
2690 (string-equal user "anonymous")
2691 (string-equal user "ftp")
2692 (not (eq host-type 'unix))
2693 (let* ((ange-ftp-good-msgs (concat ange-ftp-expand-dir-regexp
2694 "\\|"
2695 ange-ftp-good-msgs))
2696 (result (ange-ftp-send-cmd host user
2697 (list 'get dir "/dev/null")
2698 (format "expanding %s" dir)))
2699 (line (cdr result)))
2700 (setq res
2701 (if (string-match ange-ftp-expand-dir-regexp line)
2702 (substring line
2703 (match-beginning 1)
2704 (match-end 1))))))
2705 (or res
2706 (if (string-equal dir "~")
2707 (setq res (car (ange-ftp-get-pwd host user)))
2708 (let ((home (ange-ftp-expand-dir host user "~")))
2709 (unwind-protect
2710 (and (ange-ftp-cd host user dir)
2711 (setq res (car (ange-ftp-get-pwd host user))))
2712 (ange-ftp-cd host user home)))))
2713 (if res
2714 (let ((ange-ftp-this-user user)
2715 (ange-ftp-this-host host))
d0bc419e
RS
2716 (if fix-name-func
2717 (setq res (funcall fix-name-func res 'reverse)))
2f7ea155
RS
2718 (ange-ftp-put-hash-entry
2719 key res ange-ftp-expand-dir-hashtable)))
2720 res))))
2721
2722(defun ange-ftp-canonize-filename (n)
2723 "Take a string and short-circuit //, /. and /.."
2724 (if (string-match ".+//" n) ;don't upset Apollo users
2725 (setq n (substring n (1- (match-end 0)))))
d0bc419e 2726 (let ((parsed (ange-ftp-ftp-name n)))
2f7ea155
RS
2727 (if parsed
2728 (let ((host (car parsed))
2729 (user (nth 1 parsed))
d0bc419e 2730 (name (nth 2 parsed)))
2f7ea155 2731
d0bc419e
RS
2732 ;; See if remote name is absolute. If so then just expand it and
2733 ;; replace the name component of the overall name.
2734 (cond ((string-match "^/" name)
2735 name)
2f7ea155 2736
d0bc419e 2737 ;; Name starts with ~ or ~user. Resolve that part of the name
2f7ea155 2738 ;; making it absolute then re-expand it.
d0bc419e
RS
2739 ((string-match "^~[^/]*" name)
2740 (let* ((tilda (substring name
2f7ea155
RS
2741 (match-beginning 0)
2742 (match-end 0)))
d0bc419e 2743 (rest (substring name (match-end 0)))
2f7ea155
RS
2744 (dir (ange-ftp-expand-dir host user tilda)))
2745 (if dir
d0bc419e 2746 (setq name (concat dir rest))
2f7ea155
RS
2747 (error "User \"%s\" is not known"
2748 (substring tilda 1)))))
2749
d0bc419e 2750 ;; relative name. Tack on homedir and re-expand.
2f7ea155
RS
2751 (t
2752 (let ((dir (ange-ftp-expand-dir host user "~")))
2753 (if dir
d0bc419e 2754 (setq name (concat
2f7ea155 2755 (ange-ftp-real-file-name-as-directory dir)
d0bc419e 2756 name))
2f7ea155
RS
2757 (error "Unable to obtain CWD")))))
2758
d0bc419e 2759 (setq name (ange-ftp-real-expand-file-name name))
2f7ea155
RS
2760
2761 ;; see if hit real expand-file-name bug... this will probably annoy
2762 ;; some Apollo people. I'll wait until they shout, however.
d0bc419e
RS
2763 (if (string-match "^//" name)
2764 (setq name (substring name 1)))
2f7ea155 2765
d0bc419e
RS
2766 ;; Now substitute the expanded name back into the overall filename.
2767 (ange-ftp-replace-name-component n name))
2f7ea155 2768
d0bc419e 2769 ;; non-ange-ftp name. Just expand normally.
2f7ea155
RS
2770 (if (eq (string-to-char n) ?/)
2771 (ange-ftp-real-expand-file-name n)
2772 (ange-ftp-real-expand-file-name
2773 (ange-ftp-real-file-name-nondirectory n)
2774 (ange-ftp-real-file-name-directory n))))))
2775
2776(defun ange-ftp-expand-file-name (name &optional default)
2777 "Documented as original."
2778 (ange-ftp-save-match-data
2779 (if (eq (string-to-char name) ?/)
2780 (while (cond ((string-match ".+//" name) ;don't upset Apollo users
2781 (setq name (substring name (1- (match-end 0)))))
2782 ((string-match "/~" name)
2783 (setq name (substring name (1- (match-end 0))))))))
2784 (cond ((eq (string-to-char name) ?~)
2785 (ange-ftp-real-expand-file-name name))
2786 ((eq (string-to-char name) ?/)
2787 (ange-ftp-canonize-filename name))
2788 ((zerop (length name))
2789 (ange-ftp-canonize-filename (or default default-directory)))
2790 ((ange-ftp-canonize-filename
2791 (concat (file-name-as-directory (or default default-directory))
2792 name))))))
2793\f
2794;;; These are problems--they are currently not enabled.
2795
2796(defvar ange-ftp-file-name-as-directory-alist nil
2797 "Association list of \( TYPE \. FUNC \) pairs, where
2798FUNC converts a filename to a directory name for the operating
2799system TYPE.")
2800
2801(defun ange-ftp-file-name-as-directory (name)
2802 "Documented as original."
d0bc419e 2803 (let ((parsed (ange-ftp-ftp-name name)))
2f7ea155
RS
2804 (if parsed
2805 (if (string-equal (nth 2 parsed) "")
2806 name
2807 (funcall (or (cdr (assq
2808 (ange-ftp-host-type (car parsed))
2809 ange-ftp-file-name-as-directory-alist))
2810 'ange-ftp-real-file-name-as-directory)
2811 name))
2812 (ange-ftp-real-file-name-as-directory name))))
2813
2814(defun ange-ftp-file-name-directory (name)
2815 "Documented as original."
d0bc419e 2816 (let ((parsed (ange-ftp-ftp-name name)))
2f7ea155 2817 (if parsed
d0bc419e 2818 (let ((filename (nth 2 parsed)))
2f7ea155 2819 (if (ange-ftp-save-match-data
d0bc419e 2820 (string-match "^~[^/]*$" filename))
2f7ea155 2821 name
d0bc419e 2822 (ange-ftp-replace-name-component
2f7ea155 2823 name
d0bc419e 2824 (ange-ftp-real-file-name-directory filename))))
2f7ea155
RS
2825 (ange-ftp-real-file-name-directory name))))
2826
2827(defun ange-ftp-file-name-nondirectory (name)
2828 "Documented as original."
d0bc419e 2829 (let ((parsed (ange-ftp-ftp-name name)))
2f7ea155 2830 (if parsed
8feebd07 2831 (let ((filename (nth 2 parsed)))
2f7ea155 2832 (if (ange-ftp-save-match-data
8feebd07 2833 (string-match "^~[^/]*$" filename))
2f7ea155 2834 ""
d0bc419e 2835 (ange-ftp-real-file-name-nondirectory name)))
2f7ea155
RS
2836 (ange-ftp-real-file-name-nondirectory name))))
2837
2838(defun ange-ftp-directory-file-name (dir)
2839 "Documented as original."
d0bc419e 2840 (let ((parsed (ange-ftp-ftp-name dir)))
2f7ea155 2841 (if parsed
d0bc419e 2842 (ange-ftp-replace-name-component
2f7ea155
RS
2843 dir
2844 (ange-ftp-real-directory-file-name (nth 2 parsed)))
2845 (ange-ftp-real-directory-file-name dir))))
2846
2847\f
2848;;; Hooks that handle Emacs primitives.
2849
d0bc419e 2850;; Returns non-nil if should transfer FILE in binary mode.
2f7ea155 2851(defun ange-ftp-binary-file (file)
2f7ea155
RS
2852 (ange-ftp-save-match-data
2853 (string-match ange-ftp-binary-file-name-regexp file)))
2854
2855(defun ange-ftp-write-region (start end filename &optional append visit)
2f7ea155 2856 (setq filename (expand-file-name filename))
d0bc419e 2857 (let ((parsed (ange-ftp-ftp-name filename)))
2f7ea155
RS
2858 (if parsed
2859 (let* ((host (nth 0 parsed))
2860 (user (nth 1 parsed))
d0bc419e 2861 (name (ange-ftp-quote-string (nth 2 parsed)))
2f7ea155
RS
2862 (temp (ange-ftp-make-tmp-name host))
2863 (binary (ange-ftp-binary-file filename))
2864 (cmd (if append 'append 'put))
2865 (abbr (ange-ftp-abbreviate-filename filename)))
2866 (unwind-protect
2867 (progn
2868 (let ((executing-macro t)
2869 (filename (buffer-file-name))
2870 (mod-p (buffer-modified-p)))
2871 (unwind-protect
2872 (ange-ftp-real-write-region start end temp nil visit)
2873 ;; cleanup forms
2874 (setq buffer-file-name filename)
2875 (set-buffer-modified-p mod-p)))
2876 (if binary
2877 (ange-ftp-set-binary-mode host user))
2878
2879 ;; tell the process filter what size the transfer will be.
2880 (let ((attr (file-attributes temp)))
2881 (if attr
2882 (ange-ftp-set-xfer-size host user (nth 7 attr))))
2883
2884 ;; put or append the file.
2885 (let ((result (ange-ftp-send-cmd host user
d0bc419e 2886 (list cmd temp name)
2f7ea155
RS
2887 (format "Writing %s" abbr))))
2888 (or (car result)
2889 (signal 'ftp-error
2890 (list
2891 "Opening output file"
2892 (format "FTP Error: \"%s\"" (cdr result))
2893 filename)))))
2894 (ange-ftp-del-tmp-name temp)
2895 (if binary
2896 (ange-ftp-set-ascii-mode host user)))
2897 (if (eq visit t)
2898 (progn
2899 (ange-ftp-set-buffer-mode)
2900 (setq buffer-file-name filename)
2901 (set-buffer-modified-p nil)))
2902 (ange-ftp-message "Wrote %s" abbr)
2903 (ange-ftp-add-file-entry filename))
2904 (ange-ftp-real-write-region start end filename append visit))))
2905
2906(defun ange-ftp-insert-file-contents (filename &optional visit)
2f7ea155
RS
2907 (barf-if-buffer-read-only)
2908 (setq filename (expand-file-name filename))
d0bc419e 2909 (let ((parsed (ange-ftp-ftp-name filename)))
2f7ea155
RS
2910 (if parsed
2911 (progn
2912 (if visit
2913 (setq buffer-file-name filename))
2914 (if (or (file-exists-p filename)
2915 (progn
2916 (setq ange-ftp-ls-cache-file nil)
2917 (ange-ftp-del-hash-entry (file-name-directory filename)
2918 ange-ftp-files-hashtable)
2919 (file-exists-p filename)))
2920 (let* ((host (nth 0 parsed))
2921 (user (nth 1 parsed))
d0bc419e 2922 (name (ange-ftp-quote-string (nth 2 parsed)))
2f7ea155
RS
2923 (temp (ange-ftp-make-tmp-name host))
2924 (binary (ange-ftp-binary-file filename))
2925 (abbr (ange-ftp-abbreviate-filename filename))
2926 size)
2927 (unwind-protect
2928 (progn
2929 (if binary
2930 (ange-ftp-set-binary-mode host user))
2931 (let ((result (ange-ftp-send-cmd host user
d0bc419e 2932 (list 'get name temp)
2f7ea155
RS
2933 (format "Retrieving %s" abbr))))
2934 (or (car result)
2935 (signal 'ftp-error
2936 (list
2937 "Opening input file"
2938 (format "FTP Error: \"%s\"" (cdr result))
2939 filename))))
2940 (if (or (ange-ftp-real-file-readable-p temp)
2941 (sleep-for ange-ftp-retry-time)
2942 ;; Wait for file to hopefully appear.
2943 (ange-ftp-real-file-readable-p temp))
2944 (setq
2945 size
2946 (nth 1 (ange-ftp-real-insert-file-contents temp
2947 visit)))
2948 (signal 'ftp-error
2949 (list
2950 "Opening input file:"
2951 (format
2952 "FTP Error: %s not arrived or readable"
2953 filename)))))
2954 (if binary
2955 (ange-ftp-set-ascii-mode host user))
2956 (ange-ftp-del-tmp-name temp))
2957 (if visit
2958 (setq buffer-file-name filename))
2959 (list filename size))
2960 (signal 'file-error
2961 (list
2962 "Opening input file"
2963 filename))))
2964 (ange-ftp-real-insert-file-contents filename visit))))
2965
2f7ea155
RS
2966(defun ange-ftp-expand-symlink (file dir)
2967 (if (file-name-absolute-p file)
d0bc419e 2968 (ange-ftp-replace-name-component dir file)
2f7ea155
RS
2969 (expand-file-name file dir)))
2970
2971(defun ange-ftp-file-symlink-p (file)
2f7ea155
RS
2972 ;; call ange-ftp-expand-file-name rather than the normal
2973 ;; expand-file-name to stop loops when using a package that
2974 ;; redefines both file-symlink-p and expand-file-name.
2975 (setq file (ange-ftp-expand-file-name file))
d0bc419e 2976 (if (ange-ftp-ftp-name file)
2f7ea155
RS
2977 (let ((file-ent
2978 (ange-ftp-get-hash-entry
2979 (ange-ftp-get-file-part file)
2980 (ange-ftp-get-files (file-name-directory file)))))
2981 (if (stringp file-ent)
2982 (if (file-name-absolute-p file-ent)
d0bc419e 2983 (ange-ftp-replace-name-component
2f7ea155
RS
2984 (file-name-directory file) file-ent)
2985 file-ent)))
2986 (ange-ftp-real-file-symlink-p file)))
2987
d0bc419e
RS
2988(defun ange-ftp-file-exists-p (name)
2989 (setq name (expand-file-name name))
2990 (if (ange-ftp-ftp-name name)
2991 (if (ange-ftp-file-entry-p name)
2992 (let ((file-ent (ange-ftp-get-file-entry name)))
2f7ea155
RS
2993 (if (stringp file-ent)
2994 (file-exists-p
2995 (ange-ftp-expand-symlink file-ent
2996 (file-name-directory
d0bc419e 2997 (directory-file-name name))))
2f7ea155 2998 t)))
d0bc419e 2999 (ange-ftp-real-file-exists-p name)))
2f7ea155 3000
d0bc419e
RS
3001(defun ange-ftp-file-directory-p (name)
3002 (setq name (expand-file-name name))
3003 (if (ange-ftp-ftp-name name)
3004 ;; We do a file-name-as-directory on name here because some
2f7ea155
RS
3005 ;; machines (VMS) use a .DIR to indicate the filename associated
3006 ;; with a directory. This needs to be canonicalized.
3007 (let ((file-ent (ange-ftp-get-file-entry
d0bc419e 3008 (ange-ftp-file-name-as-directory name))))
2f7ea155
RS
3009 (if (stringp file-ent)
3010 (file-directory-p
3011 (ange-ftp-expand-symlink file-ent
3012 (file-name-directory
d0bc419e 3013 (directory-file-name name))))
2f7ea155 3014 file-ent))
d0bc419e 3015 (ange-ftp-real-file-directory-p name)))
2f7ea155
RS
3016
3017(defun ange-ftp-directory-files (directory &optional full match
3018 &rest v19-args)
2f7ea155 3019 (setq directory (expand-file-name directory))
d0bc419e 3020 (if (ange-ftp-ftp-name directory)
2f7ea155
RS
3021 (progn
3022 (ange-ftp-barf-if-not-directory directory)
3023 (let ((tail (ange-ftp-hash-table-keys
3024 (ange-ftp-get-files directory)))
3025 files f)
3026 (setq directory (file-name-as-directory directory))
3027 (ange-ftp-save-match-data
3028 (while tail
3029 (setq f (car tail)
3030 tail (cdr tail))
3031 (if (or (not match) (string-match match f))
3032 (setq files
3033 (cons (if full (concat directory f) f) files)))))
3034 (nreverse files)))
3035 (apply 'ange-ftp-real-directory-files directory full match v19-args)))
3036
3037(defun ange-ftp-file-attributes (file)
2f7ea155 3038 (setq file (expand-file-name file))
d0bc419e 3039 (let ((parsed (ange-ftp-ftp-name file)))
2f7ea155
RS
3040 (if parsed
3041 (let ((part (ange-ftp-get-file-part file))
3042 (files (ange-ftp-get-files (file-name-directory file))))
3043 (if (ange-ftp-hash-entry-exists-p part files)
3044 (let ((host (nth 0 parsed))
3045 (user (nth 1 parsed))
d0bc419e 3046 (name (nth 2 parsed))
2f7ea155
RS
3047 (dirp (ange-ftp-get-hash-entry part files)))
3048 (list (if (and (stringp dirp) (file-name-absolute-p dirp))
3049 (ange-ftp-expand-symlink dirp
3050 (file-name-directory file))
3051 dirp) ;0 file type
3052 -1 ;1 link count
3053 -1 ;2 uid
3054 -1 ;3 gid
3055 '(0 0) ;4 atime
3056 '(0 0) ;5 mtime
3057 '(0 0) ;6 ctime
3058 -1 ;7 size
3059 (concat (if (stringp dirp) "l" (if dirp "d" "-"))
3060 "?????????") ;8 mode
3061 nil ;9 gid weird
3062 ;; Hack to give remote files a unique "inode number".
3063 ;; It's actually the sum of the characters in its name.
3064 (apply '+ (nconc (mapcar 'identity host)
3065 (mapcar 'identity user)
3066 (mapcar 'identity
d0bc419e 3067 (directory-file-name name))))
2f7ea155
RS
3068 -1 ;11 device number [v19 only]
3069 ))))
3070 (ange-ftp-real-file-attributes file))))
3071
3072(defun ange-ftp-file-writable-p (file)
2f7ea155 3073 (setq file (expand-file-name file))
d0bc419e 3074 (if (ange-ftp-ftp-name file)
2f7ea155
RS
3075 (or (file-exists-p file) ;guess here for speed
3076 (file-directory-p (file-name-directory file)))
3077 (ange-ftp-real-file-writable-p file)))
3078
3079(defun ange-ftp-file-readable-p (file)
2f7ea155 3080 (setq file (expand-file-name file))
d0bc419e 3081 (if (ange-ftp-ftp-name file)
2f7ea155
RS
3082 (file-exists-p file)
3083 (ange-ftp-real-file-readable-p file)))
3084
3085(defun ange-ftp-delete-file (file)
2f7ea155
RS
3086 (interactive "fDelete file: ")
3087 (setq file (expand-file-name file))
d0bc419e 3088 (let ((parsed (ange-ftp-ftp-name file)))
2f7ea155
RS
3089 (if parsed
3090 (let* ((host (nth 0 parsed))
3091 (user (nth 1 parsed))
d0bc419e 3092 (name (ange-ftp-quote-string (nth 2 parsed)))
2f7ea155
RS
3093 (abbr (ange-ftp-abbreviate-filename file))
3094 (result (ange-ftp-send-cmd host user
d0bc419e 3095 (list 'delete name)
2f7ea155
RS
3096 (format "Deleting %s" abbr))))
3097 (or (car result)
3098 (signal 'ftp-error
3099 (list
3100 "Removing old name"
3101 (format "FTP Error: \"%s\"" (cdr result))
3102 file)))
3103 (ange-ftp-delete-file-entry file))
3104 (ange-ftp-real-delete-file file))))
3105
3106(defun ange-ftp-verify-visited-file-modtime (buf)
2f7ea155 3107 (let ((name (buffer-file-name buf)))
d0bc419e 3108 (if (and (stringp name) (ange-ftp-ftp-name name))
2f7ea155
RS
3109 t
3110 (ange-ftp-real-verify-visited-file-modtime buf))))
2f7ea155
RS
3111\f
3112;;;; ------------------------------------------------------------
3113;;;; File copying support... totally re-written 6/24/92.
3114;;;; ------------------------------------------------------------
3115
3116(defun ange-ftp-barf-or-query-if-file-exists (absname querystring interactive)
3117 (if (file-exists-p absname)
3118 (if (not interactive)
3119 (signal 'file-already-exists (list absname))
3120 (if (not (yes-or-no-p (format "File %s already exists; %s anyway? "
3121 absname querystring)))
3122 (signal 'file-already-exists (list absname))))))
3123
3124;; async local copy commented out for now since I don't seem to get
3125;; the process sentinel called for some processes.
3126;;
3127;; (defun ange-ftp-copy-file-locally (filename newname ok-if-already-exists
3128;; keep-date cont)
3129;; "Kludge to copy a local file and call a continuation when the copy
3130;; finishes."
3131;; ;; check to see if we can overwrite
3132;; (if (or (not ok-if-already-exists)
3133;; (numberp ok-if-already-exists))
3134;; (ange-ftp-barf-or-query-if-file-exists newname "copy to it"
3135;; (numberp ok-if-already-exists)))
3136;; (let ((proc (start-process " *copy*"
3137;; (generate-new-buffer "*copy*")
3138;; "cp"
3139;; filename
3140;; newname))
3141;; res)
3142;; (set-process-sentinel proc (function ange-ftp-copy-file-locally-sentinel))
3143;; (process-kill-without-query proc)
3144;; (save-excursion
3145;; (set-buffer (process-buffer proc))
3146;; (make-variable-buffer-local 'copy-cont)
3147;; (setq copy-cont cont))))
3148;;
3149;; (defun ange-ftp-copy-file-locally-sentinel (proc status)
3150;; (save-excursion
3151;; (set-buffer (process-buffer proc))
3152;; (let ((cont copy-cont)
3153;; (result (buffer-string)))
3154;; (unwind-protect
3155;; (if (and (string-equal status "finished\n")
3156;; (zerop (length result)))
3157;; (ange-ftp-call-cont cont t nil)
3158;; (ange-ftp-call-cont cont
3159;; nil
3160;; (if (zerop (length result))
3161;; (substring status 0 -1)
3162;; (substring result 0 -1))))
3163;; (kill-buffer (current-buffer))))))
3164
3165;; this is the extended version of ange-ftp-copy-file-internal that works
3166;; asyncronously if asked nicely.
3167(defun ange-ftp-copy-file-internal (filename newname ok-if-already-exists
3168 keep-date &optional msg cont nowait)
3169 (setq filename (expand-file-name filename)
3170 newname (expand-file-name newname))
3171
3172 ;; canonicalize newname if a directory.
3173 (if (file-directory-p newname)
3174 (setq newname (expand-file-name (file-name-nondirectory filename) newname)))
3175
d0bc419e
RS
3176 (let ((f-parsed (ange-ftp-ftp-name filename))
3177 (t-parsed (ange-ftp-ftp-name newname)))
2f7ea155
RS
3178
3179 ;; local file to local file copy?
3180 (if (and (not f-parsed) (not t-parsed))
3181 (progn
3182 (ange-ftp-real-copy-file filename newname ok-if-already-exists
3183 keep-date)
3184 (if cont
3185 (ange-ftp-call-cont cont t "Copied locally")))
3186 ;; one or both files are remote.
3187 (let* ((f-host (and f-parsed (nth 0 f-parsed)))
3188 (f-user (and f-parsed (nth 1 f-parsed)))
d0bc419e 3189 (f-name (and f-parsed (ange-ftp-quote-string (nth 2 f-parsed))))
2f7ea155
RS
3190 (f-abbr (ange-ftp-abbreviate-filename filename))
3191 (t-host (and t-parsed (nth 0 t-parsed)))
3192 (t-user (and t-parsed (nth 1 t-parsed)))
d0bc419e 3193 (t-name (and t-parsed (ange-ftp-quote-string (nth 2 t-parsed))))
2f7ea155
RS
3194 (t-abbr (ange-ftp-abbreviate-filename newname filename))
3195 (binary (or (ange-ftp-binary-file filename)
3196 (ange-ftp-binary-file newname)))
3197 temp1
3198 temp2)
3199
3200 ;; check to see if we can overwrite
3201 (if (or (not ok-if-already-exists)
3202 (numberp ok-if-already-exists))
3203 (ange-ftp-barf-or-query-if-file-exists newname "copy to it"
3204 (numberp ok-if-already-exists)))
3205
3206 ;; do the copying.
3207 (if f-parsed
3208
3209 ;; filename was remote.
3210 (progn
3211 (if (or (ange-ftp-use-gateway-p f-host)
3212 t-parsed)
3213 ;; have to use intermediate file if we are getting via
3214 ;; gateway machine or we are doing a remote to remote copy.
3215 (setq temp1 (ange-ftp-make-tmp-name f-host)))
3216
3217 (if binary
3218 (ange-ftp-set-binary-mode f-host f-user))
3219
3220 (ange-ftp-send-cmd
3221 f-host
3222 f-user
d0bc419e 3223 (list 'get f-name (or temp1 newname))
2f7ea155
RS
3224 (or msg
3225 (if (and temp1 t-parsed)
3226 (format "Getting %s" f-abbr)
3227 (format "Copying %s to %s" f-abbr t-abbr)))
3228 (list (function ange-ftp-cf1)
3229 filename newname binary msg
d0bc419e
RS
3230 f-parsed f-host f-user f-name f-abbr
3231 t-parsed t-host t-user t-name t-abbr
2f7ea155
RS
3232 temp1 temp2 cont nowait)
3233 nowait))
3234
3235 ;; filename wasn't remote. newname must be remote. call the
3236 ;; function which does the remainder of the copying work.
3237 (ange-ftp-cf1 t nil
3238 filename newname binary msg
d0bc419e
RS
3239 f-parsed f-host f-user f-name f-abbr
3240 t-parsed t-host t-user t-name t-abbr
2f7ea155
RS
3241 nil nil cont nowait))))))
3242
3243;; next part of copying routine.
3244(defun ange-ftp-cf1 (result line
3245 filename newname binary msg
d0bc419e
RS
3246 f-parsed f-host f-user f-name f-abbr
3247 t-parsed t-host t-user t-name t-abbr
2f7ea155
RS
3248 temp1 temp2 cont nowait)
3249 (if line
3250 ;; filename must have been remote, and we must have just done a GET.
3251 (unwind-protect
3252 (or result
3253 ;; GET failed for some reason. Clean up and get out.
3254 (progn
3255 (and temp1 (ange-ftp-del-tmp-name temp1))
3256 (or cont
3257 (signal 'ftp-error (list "Opening input file"
3258 (format "FTP Error: \"%s\"" line)
3259 filename)))))
3260 ;; cleanup
3261 (if binary
3262 (ange-ftp-set-ascii-mode f-host f-user))))
3263
3264 (if result
3265 ;; We now have to copy either temp1 or filename to newname.
3266 (if t-parsed
3267
3268 ;; newname was remote.
3269 (progn
3270 (if (ange-ftp-use-gateway-p t-host)
3271 (setq temp2 (ange-ftp-make-tmp-name t-host)))
3272
3273 ;; make sure data is moved into the right place for the
3274 ;; outgoing transfer. gateway temporary files complicate
3275 ;; things nicely.
3276 (if temp1
3277 (if temp2
3278 (if (string-equal temp1 temp2)
3279 (setq temp1 nil)
3280 (ange-ftp-real-copy-file temp1 temp2 t))
3281 (setq temp2 temp1 temp1 nil))
3282 (if temp2
3283 (ange-ftp-real-copy-file filename temp2 t)))
3284
3285 (if binary
3286 (ange-ftp-set-binary-mode t-host t-user))
3287
3288 ;; tell the process filter what size the file is.
3289 (let ((attr (file-attributes (or temp2 filename))))
3290 (if attr
3291 (ange-ftp-set-xfer-size t-host t-user (nth 7 attr))))
3292
3293 (ange-ftp-send-cmd
3294 t-host
3295 t-user
d0bc419e 3296 (list 'put (or temp2 filename) t-name)
2f7ea155
RS
3297 (or msg
3298 (if (and temp2 f-parsed)
3299 (format "Putting %s" newname)
3300 (format "Copying %s to %s" f-abbr t-abbr)))
3301 (list (function ange-ftp-cf2)
3302 newname t-host t-user binary temp1 temp2 cont)
3303 nowait))
3304
3305 ;; newname wasn't remote.
3306 (ange-ftp-cf2 t nil newname t-host t-user binary temp1 temp2 cont))
3307
3308 ;; first copy failed, tell caller
3309 (ange-ftp-call-cont cont result line)))
3310
3311;; last part of copying routine.
3312(defun ange-ftp-cf2 (result line newname t-host t-user binary temp1 temp2 cont)
3313 (unwind-protect
3314 (if line
3315 ;; result from doing a local to remote copy.
3316 (unwind-protect
3317 (progn
3318 (or result
3319 (or cont
3320 (signal 'ftp-error
3321 (list "Opening output file"
3322 (format "FTP Error: \"%s\"" line)
3323 newname))))
3324
3325 (ange-ftp-add-file-entry newname))
3326
3327 ;; cleanup.
3328 (if binary
3329 (ange-ftp-set-ascii-mode t-host t-user)))
3330
3331 ;; newname was local.
3332 (if temp1
3333 (ange-ftp-real-copy-file temp1 newname t)))
3334
3335 ;; clean up
3336 (and temp1 (ange-ftp-del-tmp-name temp1))
3337 (and temp2 (ange-ftp-del-tmp-name temp2))
3338 (ange-ftp-call-cont cont result line)))
3339
3340(defun ange-ftp-copy-file (filename newname &optional ok-if-already-exists
3341 keep-date)
2f7ea155
RS
3342 (interactive "fCopy file: \nFCopy %s to file: \np")
3343 (ange-ftp-copy-file-internal filename
3344 newname
3345 ok-if-already-exists
3346 keep-date
3347 nil
3348 nil
3349 (interactive-p)))
3350\f
3351;;;; ------------------------------------------------------------
3352;;;; File renaming support.
3353;;;; ------------------------------------------------------------
3354
3355(defun ange-ftp-rename-remote-to-remote (filename newname f-parsed t-parsed
3356 binary)
3357 "Rename remote file FILE to remote file NEWNAME."
3358 (let ((f-host (nth 0 f-parsed))
3359 (f-user (nth 1 f-parsed))
3360 (t-host (nth 0 t-parsed))
3361 (t-user (nth 1 t-parsed)))
3362 (if (and (string-equal f-host t-host)
3363 (string-equal f-user t-user))
d0bc419e
RS
3364 (let* ((f-name (ange-ftp-quote-string (nth 2 f-parsed)))
3365 (t-name (ange-ftp-quote-string (nth 2 t-parsed)))
3366 (cmd (list 'rename f-name t-name))
2f7ea155
RS
3367 (fabbr (ange-ftp-abbreviate-filename filename))
3368 (nabbr (ange-ftp-abbreviate-filename newname filename))
3369 (result (ange-ftp-send-cmd f-host f-user cmd
3370 (format "Renaming %s to %s"
3371 fabbr
3372 nabbr))))
3373 (or (car result)
3374 (signal 'ftp-error
3375 (list
3376 "Renaming"
3377 (format "FTP Error: \"%s\"" (cdr result))
3378 filename
3379 newname)))
3380 (ange-ftp-add-file-entry newname)
3381 (ange-ftp-delete-file-entry filename))
3382 (ange-ftp-copy-file-internal filename newname t nil)
3383 (delete-file filename))))
3384
3385(defun ange-ftp-rename-local-to-remote (filename newname)
c8fa98cc 3386 "Rename local FILENAME to remote file NEWNAME."
2f7ea155
RS
3387 (let* ((fabbr (ange-ftp-abbreviate-filename filename))
3388 (nabbr (ange-ftp-abbreviate-filename newname filename))
3389 (msg (format "Renaming %s to %s" fabbr nabbr)))
3390 (ange-ftp-copy-file-internal filename newname t nil msg)
3391 (let (ange-ftp-process-verbose)
3392 (delete-file filename))))
3393
3394(defun ange-ftp-rename-remote-to-local (filename newname)
c8fa98cc 3395 "Rename remote file FILENAME to local file NEWNAME."
2f7ea155
RS
3396 (let* ((fabbr (ange-ftp-abbreviate-filename filename))
3397 (nabbr (ange-ftp-abbreviate-filename newname filename))
3398 (msg (format "Renaming %s to %s" fabbr nabbr)))
3399 (ange-ftp-copy-file-internal filename newname t nil msg)
3400 (let (ange-ftp-process-verbose)
3401 (delete-file filename))))
3402
3403(defun ange-ftp-rename-file (filename newname &optional ok-if-already-exists)
2f7ea155
RS
3404 (interactive "fRename file: \nFRename %s to file: \np")
3405 (setq filename (expand-file-name filename))
3406 (setq newname (expand-file-name newname))
d0bc419e
RS
3407 (let* ((f-parsed (ange-ftp-ftp-name filename))
3408 (t-parsed (ange-ftp-ftp-name newname))
2f7ea155
RS
3409 (binary (if (or f-parsed t-parsed) (ange-ftp-binary-file filename))))
3410 (if (and (or f-parsed t-parsed)
3411 (or (not ok-if-already-exists)
3412 (numberp ok-if-already-exists)))
3413 (ange-ftp-barf-or-query-if-file-exists
3414 newname
3415 "rename to it"
3416 (numberp ok-if-already-exists)))
3417 (if f-parsed
3418 (if t-parsed
3419 (ange-ftp-rename-remote-to-remote filename newname f-parsed
3420 t-parsed binary)
3421 (ange-ftp-rename-remote-to-local filename newname))
3422 (if t-parsed
3423 (ange-ftp-rename-local-to-remote filename newname)
3424 (ange-ftp-real-rename-file filename newname ok-if-already-exists)))))
3425\f
3426;;;; ------------------------------------------------------------
3427;;;; File name completion support.
3428;;;; ------------------------------------------------------------
3429
d0bc419e
RS
3430;; If the file entry SYM is a symlink, returns whether its file exists.
3431;; Note that `ange-ftp-this-dir' is used as a free variable.
2f7ea155 3432(defun ange-ftp-file-entry-active-p (sym)
2f7ea155
RS
3433 (let ((val (get sym 'val)))
3434 (or (not (stringp val))
3435 (file-exists-p (ange-ftp-expand-symlink val ange-ftp-this-dir)))))
3436
d0bc419e
RS
3437;; If the file entry is not a directory (nor a symlink pointing to a directory)
3438;; returns whether the file (or file pointed to by the symlink) is ignored
3439;; by completion-ignored-extensions.
3440;; Note that `ange-ftp-this-dir' and `ange-ftp-completion-ignored-pattern'
3441;; are used as free variables.
2f7ea155 3442(defun ange-ftp-file-entry-not-ignored-p (sym)
2f7ea155
RS
3443 (let ((val (get sym 'val))
3444 (symname (symbol-name sym)))
3445 (if (stringp val)
3446 (let ((file (ange-ftp-expand-symlink val ange-ftp-this-dir)))
3447 (or (file-directory-p file)
3448 (and (file-exists-p file)
3449 (not (string-match ange-ftp-completion-ignored-pattern
3450 symname)))))
3451 (or val ; is a directory name
3452 (not (string-match ange-ftp-completion-ignored-pattern symname))))))
3453
3454(defun ange-ftp-file-name-all-completions (file dir)
2f7ea155 3455 (let ((ange-ftp-this-dir (expand-file-name dir)))
d0bc419e 3456 (if (ange-ftp-ftp-name ange-ftp-this-dir)
2f7ea155
RS
3457 (progn
3458 (ange-ftp-barf-if-not-directory ange-ftp-this-dir)
3459 (setq ange-ftp-this-dir
3460 (ange-ftp-real-file-name-as-directory ange-ftp-this-dir))
3461 (let* ((tbl (ange-ftp-get-files ange-ftp-this-dir))
3462 (completions
3463 (all-completions file tbl
3464 (function ange-ftp-file-entry-active-p))))
3465
3466 ;; see whether each matching file is a directory or not...
3467 (mapcar
3468 (function
3469 (lambda (file)
3470 (let ((ent (ange-ftp-get-hash-entry file tbl)))
3471 (if (and ent
3472 (or (not (stringp ent))
3473 (file-directory-p
3474 (ange-ftp-expand-symlink ent
3475 ange-ftp-this-dir))))
3476 (concat file "/")
3477 file))))
3478 completions)))
3479
3480 (if (string-equal "/" ange-ftp-this-dir)
3481 (nconc (all-completions file (ange-ftp-generate-root-prefixes))
3482 (ange-ftp-real-file-name-all-completions file
3483 ange-ftp-this-dir))
3484 (ange-ftp-real-file-name-all-completions file ange-ftp-this-dir)))))
3485
3486(defun ange-ftp-file-name-completion (file dir)
2f7ea155 3487 (let ((ange-ftp-this-dir (expand-file-name dir)))
d0bc419e 3488 (if (ange-ftp-ftp-name ange-ftp-this-dir)
2f7ea155
RS
3489 (progn
3490 (ange-ftp-barf-if-not-directory ange-ftp-this-dir)
3491 (if (equal file "")
3492 ""
3493 (setq ange-ftp-this-dir
3494 (ange-ftp-real-file-name-as-directory ange-ftp-this-dir)) ;real?
3495 (let* ((tbl (ange-ftp-get-files ange-ftp-this-dir))
3496 (ange-ftp-completion-ignored-pattern
3497 (mapconcat (function
3498 (lambda (s) (if (stringp s)
3499 (concat (regexp-quote s) "$")
3500 "/"))) ; / never in filename
3501 completion-ignored-extensions
3502 "\\|")))
3503 (ange-ftp-save-match-data
3504 (or (ange-ftp-file-name-completion-1
3505 file tbl ange-ftp-this-dir
3506 (function ange-ftp-file-entry-not-ignored-p))
3507 (ange-ftp-file-name-completion-1
3508 file tbl ange-ftp-this-dir
3509 (function ange-ftp-file-entry-active-p)))))))
3510
3511 (if (string-equal "/" ange-ftp-this-dir)
3512 (try-completion
3513 file
3514 (nconc (ange-ftp-generate-root-prefixes)
3515 (mapcar 'list
3516 (ange-ftp-real-file-name-all-completions file "/"))))
3517 (ange-ftp-real-file-name-completion file ange-ftp-this-dir)))))
3518
3519
3520(defun ange-ftp-file-name-completion-1 (file tbl dir predicate)
2f7ea155
RS
3521 (let ((bestmatch (try-completion file tbl predicate)))
3522 (if bestmatch
3523 (if (eq bestmatch t)
3524 (if (file-directory-p (expand-file-name file dir))
3525 (concat file "/")
3526 t)
3527 (if (and (eq (try-completion bestmatch tbl predicate) t)
3528 (file-directory-p
3529 (expand-file-name bestmatch dir)))
3530 (concat bestmatch "/")
3531 bestmatch)))))
3532
2f7ea155
RS
3533;; Put these lines uncommmented in your .emacs if you want C-r to refresh
3534;; ange-ftp's cache whilst doing filename completion.
3535;;
3536;;(define-key minibuffer-local-completion-map "\C-r" 'ange-ftp-re-read-dir)
3537;;(define-key minibuffer-local-must-match-map "\C-r" 'ange-ftp-re-read-dir)
3538
d0bc419e
RS
3539;; Force a re-read of the directory DIR. If DIR is omitted then it defaults
3540;; to the directory part of the contents of the current buffer.
2f7ea155 3541(defun ange-ftp-re-read-dir (&optional dir)
2f7ea155
RS
3542 (interactive)
3543 (if dir
3544 (setq dir (expand-file-name dir))
3545 (setq dir (file-name-directory (expand-file-name (buffer-string)))))
d0bc419e 3546 (if (ange-ftp-ftp-name dir)
2f7ea155
RS
3547 (progn
3548 (setq ange-ftp-ls-cache-file nil)
3549 (ange-ftp-del-hash-entry dir ange-ftp-files-hashtable)
3550 (ange-ftp-get-files dir t))))
3551\f
d0bc419e
RS
3552(defun ange-ftp-make-directory (dir)
3553 (interactive (list (expand-file-name (read-file-name "Make directory: "))))
3554 (if (file-exists-p dir)
3555 (error "Cannot make directory %s: file already exists" dir)
3556 (let ((parsed (ange-ftp-ftp-name dir)))
3557 (if parsed
3558 (let* ((host (nth 0 parsed))
3559 (user (nth 1 parsed))
3560 ;; Some ftp's on unix machines (at least on Suns)
3561 ;; insist that mkdir take a filename, and not a
3562 ;; directory-name name as an arg. Argh!! This is a bug.
3563 ;; Non-unix machines will probably always insist
3564 ;; that mkdir takes a directory-name as an arg
3565 ;; (as the ftp man page says it should).
3566 (name (ange-ftp-quote-string
3567 (if (eq (ange-ftp-host-type host) 'unix)
3568 (ange-ftp-real-directory-file-name (nth 2 parsed))
3569 (ange-ftp-real-file-name-as-directory
3570 (nth 2 parsed)))))
3571 (abbr (ange-ftp-abbreviate-filename dir))
3572 (result (ange-ftp-send-cmd host user
3573 (list 'mkdir name)
3574 (format "Making directory %s"
3575 abbr))))
3576 (or (car result)
3577 (ange-ftp-error host user
3578 (format "Could not make directory %s: %s"
3579 dir
3580 (cdr result))))
3581 (ange-ftp-add-file-entry dir t))
3582 (ange-ftp-real-make-directory dir)))))
3583
3584(defun ange-ftp-delete-directory (dir)
3585 (if (file-directory-p dir)
3586 (let ((parsed (ange-ftp-ftp-name dir)))
3587 (if parsed
3588 (let* ((host (nth 0 parsed))
3589 (user (nth 1 parsed))
3590 ;; Some ftp's on unix machines (at least on Suns)
3591 ;; insist that rmdir take a filename, and not a
3592 ;; directory-name name as an arg. Argh!! This is a bug.
3593 ;; Non-unix machines will probably always insist
3594 ;; that rmdir takes a directory-name as an arg
3595 ;; (as the ftp man page says it should).
3596 (name (ange-ftp-quote-string
3597 (if (eq (ange-ftp-host-type host) 'unix)
3598 (ange-ftp-real-directory-file-name
3599 (nth 2 parsed))
3600 (ange-ftp-real-file-name-as-directory
3601 (nth 2 parsed)))))
3602 (abbr (ange-ftp-abbreviate-filename dir))
3603 (result (ange-ftp-send-cmd host user
3604 (list 'rmdir name)
3605 (format "Removing directory %s"
3606 abbr))))
3607 (or (car result)
3608 (ange-ftp-error host user
3609 (format "Could not remove directory %s: %s"
3610 dir
3611 (cdr result))))
3612 (ange-ftp-delete-file-entry dir t))
3613 (ange-ftp-real-delete-directory dir)))
3614 (error "Not a directory: %s" dir)))
3615\f
8feebd07 3616;; Make a local copy of FILE and return its name.
d0bc419e 3617
8feebd07 3618(defun ange-ftp-file-local-copy (file)
d0bc419e
RS
3619 (let* ((fn1 (expand-file-name file))
3620 (pa1 (ange-ftp-ftp-name fn1)))
3621 (if pa1
3622 (let* ((tmp1 (ange-ftp-make-tmp-name (car pa1)))
3623 (bin1 (ange-ftp-binary-file fn1)))
3624 (ange-ftp-copy-file-internal fn1 tmp1 t nil
3625 (format "Getting %s" fn1))
8feebd07 3626 tmp1))))
d0bc419e 3627\f
9ca74466
JB
3628;; Calculate default-unhandled-directory for a given ange-ftp buffer.
3629(defun ange-ftp-unhandled-file-name-directory (filename)
3630 (file-name-directory ange-ftp-tmp-name-template))
3631
3632\f
d0bc419e
RS
3633;; Need the following functions for making filenames of compressed
3634;; files, because some OS's (unlike UNIX) do not allow a filename to
3635;; have two extensions.
3636
3637(defvar ange-ftp-make-compressed-filename-alist nil
3638 "Alist of host-type-specific functions to process file names for compression.
3639Each element has the form (TYPE . FUNC).
3640FUNC should take one argument, a file name, and return a list
3641of the form (COMPRESSING NEWNAME).
3642COMPRESSING should be t if the specified file should be compressed,
3643and nil if it should be uncompressed (that is, if it is a compressed file).
3644NEWNAME should be the name to give the new compressed or uncompressed file.")
3645
3646(defun ange-ftp-dired-compress-file (name)
3647 (let ((parsed (ange-ftp-ftp-name name))
3648 conversion-func)
3649 (if (and parsed
3650 (setq conversion-func
3651 (cdr (assq (ange-ftp-host-type (car parsed))
3652 ange-ftp-make-compressed-filename-alist))))
3653 (let* ((decision
3654 (ange-ftp-save-match-data (funcall conversion-func name)))
3655 (compressing (car decision))
3656 (newfile (nth 1 decision)))
3657 (if compressing
3658 (ange-ftp-compress name newfile)
3659 (ange-ftp-uncompress name newfile)))
3660 (let (file-name-handler-alist)
e70fd492 3661 (dired-compress-file name)))))
d0bc419e
RS
3662
3663;; Copy FILE to this machine, compress it, and copy out to NFILE.
3664(defun ange-ftp-compress (file nfile)
3665 (let* ((parsed (ange-ftp-ftp-name file))
3666 (tmp1 (ange-ftp-make-tmp-name (car parsed)))
3667 (tmp2 (ange-ftp-make-tmp-name (car parsed)))
3668 (abbr (ange-ftp-abbreviate-filename file))
3669 (nabbr (ange-ftp-abbreviate-filename nfile))
3670 (msg1 (format "Getting %s" abbr))
3671 (msg2 (format "Putting %s" nabbr)))
3672 (unwind-protect
3673 (progn
3674 (ange-ftp-copy-file-internal file tmp1 t nil msg1)
3675 (and ange-ftp-process-verbose
3676 (ange-ftp-message "Compressing %s..." abbr))
3677 (call-process-region (point)
3678 (point)
3679 shell-file-name
3680 nil
3681 t
3682 nil
3683 "-c"
3684 (format "compress -f -c < %s > %s" tmp1 tmp2))
3685 (and ange-ftp-process-verbose
3686 (ange-ftp-message "Compressing %s...done" abbr))
3687 (if (zerop (buffer-size))
3688 (progn
3689 (let (ange-ftp-process-verbose)
3690 (delete-file file))
3691 (ange-ftp-copy-file-internal tmp2 nfile t nil msg2))))
3692 (ange-ftp-del-tmp-name tmp1)
3693 (ange-ftp-del-tmp-name tmp2))))
3694
3695;; Copy FILE to this machine, uncompress it, and copy out to NFILE.
3696(defun ange-ftp-uncompress (file nfile)
3697 (let* ((parsed (ange-ftp-ftp-name file))
3698 (tmp1 (ange-ftp-make-tmp-name (car parsed)))
3699 (tmp2 (ange-ftp-make-tmp-name (car parsed)))
3700 (abbr (ange-ftp-abbreviate-filename file))
3701 (nabbr (ange-ftp-abbreviate-filename nfile))
3702 (msg1 (format "Getting %s" abbr))
3703 (msg2 (format "Putting %s" nabbr))
3704;; ;; Cheap hack because of problems with binary file transfers from
3705;; ;; VMS hosts.
3706;; (gbinary (not (eq 'vms (ange-ftp-host-type (car parsed)))))
3707 )
3708 (unwind-protect
3709 (progn
3710 (ange-ftp-copy-file-internal file tmp1 t nil msg1)
3711 (and ange-ftp-process-verbose
3712 (ange-ftp-message "Uncompressing %s..." abbr))
3713 (call-process-region (point)
3714 (point)
3715 shell-file-name
3716 nil
3717 t
3718 nil
3719 "-c"
3720 (format "uncompress -c < %s > %s" tmp1 tmp2))
3721 (and ange-ftp-process-verbose
3722 (ange-ftp-message "Uncompressing %s...done" abbr))
3723 (if (zerop (buffer-size))
3724 (progn
3725 (let (ange-ftp-process-verbose)
3726 (delete-file file))
3727 (ange-ftp-copy-file-internal tmp2 nfile t nil msg2))))
3728 (ange-ftp-del-tmp-name tmp1)
3729 (ange-ftp-del-tmp-name tmp2))))
3730\f
2f7ea155
RS
3731;;; Define the handler for special file names
3732;;; that causes ange-ftp to be invoked.
3733
26aaadf3
RS
3734;;;###autoload
3735(defun ange-ftp-hook-function (operation &rest args)
3736 (let ((fn (get operation 'ange-ftp)))
3737 (if fn (apply fn args)
3738 (let (file-name-handler-alist)
3739 (apply operation args)))))
3740
3741;;;###autoload
6067ebef 3742(or (assoc "^/[^/:]+:" file-name-handler-alist)
26aaadf3 3743 (setq file-name-handler-alist
6067ebef 3744 (cons '("^/[^/:]+:" . ange-ftp-hook-function)
26aaadf3
RS
3745 file-name-handler-alist)))
3746
3747;;; The above two forms are sufficient to cause this file to be loaded
3748;;; if the user ever uses a file name with a colon in it.
3749
3750;;; This sets the mode
3751(or (memq 'ange-ftp-set-buffer-mode find-file-hooks)
3752 (setq find-file-hooks
3753 (cons 'ange-ftp-set-buffer-mode find-file-hooks)))
3754
3755;;; Now say where to find the handlers for particular operations.
2f7ea155
RS
3756
3757(put 'file-name-directory 'ange-ftp 'ange-ftp-file-name-directory)
3758(put 'file-name-nondirectory 'ange-ftp 'ange-ftp-file-name-nondirectory)
3759(put 'file-name-as-directory 'ange-ftp 'ange-ftp-file-name-as-directory)
3760(put 'directory-file-name 'ange-ftp 'ange-ftp-directory-file-name)
3761(put 'expand-file-name 'ange-ftp 'ange-ftp-expand-file-name)
2f7ea155
RS
3762(put 'make-directory 'ange-ftp 'ange-ftp-make-directory)
3763(put 'delete-directory 'ange-ftp 'ange-ftp-delete-directory)
3764(put 'insert-file-contents 'ange-ftp 'ange-ftp-insert-file-contents)
3765(put 'directory-files 'ange-ftp 'ange-ftp-directory-files)
3766(put 'file-directory-p 'ange-ftp 'ange-ftp-file-directory-p)
3767(put 'file-writable-p 'ange-ftp 'ange-ftp-file-writable-p)
3768(put 'file-readable-p 'ange-ftp 'ange-ftp-file-readable-p)
3769(put 'file-symlink-p 'ange-ftp 'ange-ftp-file-symlink-p)
3770(put 'delete-file 'ange-ftp 'ange-ftp-delete-file)
3771(put 'read-file-name-internal 'ange-ftp 'ange-ftp-read-file-name-internal)
3772(put 'verify-visited-file-modtime 'ange-ftp
3773 'ange-ftp-verify-visited-file-modtime)
3774(put 'file-exists-p 'ange-ftp 'ange-ftp-file-exists-p)
3775(put 'write-region 'ange-ftp 'ange-ftp-write-region)
3776(put 'backup-buffer 'ange-ftp 'ange-ftp-backup-buffer)
3777(put 'copy-file 'ange-ftp 'ange-ftp-copy-file)
3778(put 'rename-file 'ange-ftp 'ange-ftp-rename-file)
3779(put 'file-attributes 'ange-ftp 'ange-ftp-file-attributes)
3780(put 'file-name-all-completions 'ange-ftp 'ange-ftp-file-name-all-completions)
3781(put 'file-name-completion 'ange-ftp 'ange-ftp-file-name-completion)
c3554e95 3782(put 'insert-directory 'ange-ftp 'ange-ftp-insert-directory)
8feebd07 3783(put 'file-local-copy 'ange-ftp 'ange-ftp-file-local-copy)
9ca74466
JB
3784(put 'unhandled-file-name-directory 'ange-ftp
3785 'ange-ftp-unhandled-file-name-directory)
d0bc419e
RS
3786(put 'file-name-sans-versions 'ange-ftp 'ange-ftp-file-name-sans-versions)
3787(put 'dired-uncache 'ange-ftp 'ange-ftp-dired-uncache)
3788(put 'dired-compress-file 'ange-ftp 'ange-ftp-dired-compress-file)
243e5206
RS
3789
3790;; Turn off truename processing to save time.
3791;; Treat each name as its own truename.
3792(put 'file-truename 'ange-ftp 'identity)
3793
3794;; Turn off RCS/SCCS processing to save time.
3795;; This returns nil for any file name as argument.
3796(put 'vc-registered 'ange-ftp 'null)
26aaadf3
RS
3797\f
3798;;; Define ways of getting at unmodified Emacs primitives,
3799;;; turning off our handler.
2f7ea155 3800
2f7ea155
RS
3801(defun ange-ftp-real-file-name-directory (&rest args)
3802 (let (file-name-handler-alist)
3803 (apply 'file-name-directory args)))
3804(defun ange-ftp-real-file-name-nondirectory (&rest args)
3805 (let (file-name-handler-alist)
3806 (apply 'file-name-nondirectory args)))
3807(defun ange-ftp-real-file-name-as-directory (&rest args)
3808 (let (file-name-handler-alist)
3809 (apply 'file-name-as-directory args)))
3810(defun ange-ftp-real-directory-file-name (&rest args)
3811 (let (file-name-handler-alist)
3812 (apply 'directory-file-name args)))
3813(defun ange-ftp-real-expand-file-name (&rest args)
3814 (let (file-name-handler-alist)
3815 (apply 'expand-file-name args)))
3816(defun ange-ftp-real-make-directory (&rest args)
3817 (let (file-name-handler-alist)
3818 (apply 'make-directory args)))
3819(defun ange-ftp-real-delete-directory (&rest args)
3820 (let (file-name-handler-alist)
3821 (apply 'delete-directory args)))
3822(defun ange-ftp-real-insert-file-contents (&rest args)
3823 (let (file-name-handler-alist)
3824 (apply 'insert-file-contents args)))
3825(defun ange-ftp-real-directory-files (&rest args)
3826 (let (file-name-handler-alist)
3827 (apply 'directory-files args)))
3828(defun ange-ftp-real-file-directory-p (&rest args)
3829 (let (file-name-handler-alist)
3830 (apply 'file-directory-p args)))
3831(defun ange-ftp-real-file-writable-p (&rest args)
3832 (let (file-name-handler-alist)
3833 (apply 'file-writable-p args)))
3834(defun ange-ftp-real-file-readable-p (&rest args)
3835 (let (file-name-handler-alist)
3836 (apply 'file-readable-p args)))
3837(defun ange-ftp-real-file-symlink-p (&rest args)
3838 (let (file-name-handler-alist)
3839 (apply 'file-symlink-p args)))
3840(defun ange-ftp-real-delete-file (&rest args)
3841 (let (file-name-handler-alist)
3842 (apply 'delete-file args)))
3843(defun ange-ftp-real-read-file-name-internal (&rest args)
3844 (let (file-name-handler-alist)
3845 (apply 'read-file-name-internal args)))
3846(defun ange-ftp-real-verify-visited-file-modtime (&rest args)
3847 (let (file-name-handler-alist)
3848 (apply 'verify-visited-file-modtime args)))
3849(defun ange-ftp-real-file-exists-p (&rest args)
3850 (let (file-name-handler-alist)
3851 (apply 'file-exists-p args)))
3852(defun ange-ftp-real-write-region (&rest args)
3853 (let (file-name-handler-alist)
3854 (apply 'write-region args)))
3855(defun ange-ftp-real-backup-buffer (&rest args)
3856 (let (file-name-handler-alist)
3857 (apply 'backup-buffer args)))
3858(defun ange-ftp-real-copy-file (&rest args)
3859 (let (file-name-handler-alist)
3860 (apply 'copy-file args)))
3861(defun ange-ftp-real-rename-file (&rest args)
3862 (let (file-name-handler-alist)
3863 (apply 'rename-file args)))
3864(defun ange-ftp-real-file-attributes (&rest args)
3865 (let (file-name-handler-alist)
3866 (apply 'file-attributes args)))
3867(defun ange-ftp-real-file-name-all-completions (&rest args)
3868 (let (file-name-handler-alist)
3869 (apply 'file-name-all-completions args)))
3870(defun ange-ftp-real-file-name-completion (&rest args)
3871 (let (file-name-handler-alist)
3872 (apply 'file-name-completion args)))
c3554e95
RS
3873(defun ange-ftp-real-insert-directory (&rest args)
3874 (let (file-name-handler-alist)
3875 (apply 'insert-directory args)))
d0bc419e
RS
3876(defun ange-ftp-real-file-name-sans-versions (&rest args)
3877 (let (file-name-handler-alist)
3878 (apply 'file-name-sans-versions args)))
3879(defun ange-ftp-real-shell-command (&rest args)
3880 (let (file-name-handler-alist)
3881 (apply 'shell-command args)))
2f7ea155 3882\f
d0bc419e
RS
3883;; Here we support using dired on remote hosts.
3884;; I have turned off the support for using dired on foreign directory formats.
3885;; That involves too many unclean hooks.
3886;; It would be cleaner to support such operations by
3887;; converting the foreign directory format to something dired can understand;
3888;; something close to ls -l output.
3889;; The logical place to do this is in the functions ange-ftp-parse-...-listing.
3890
3891;; Some of the old dired hooks would still be needed even if this is done.
3892;; I have preserved (and modernized) those hooks.
3893;; So the format conversion should be all that is needed.
2f7ea155 3894
c3554e95 3895(defun ange-ftp-insert-directory (file switches &optional wildcard full)
d0bc419e
RS
3896 (let ((short (ange-ftp-abbreviate-filename file))
3897 (parsed (ange-ftp-ftp-name file)))
c3554e95 3898 (if parsed
d0bc419e
RS
3899 (insert
3900 (if wildcard
3901 (let ((default-directory (file-name-directory file)))
3902 (ange-ftp-ls (file-name-nondirectory file) switches nil nil t))
3903 (ange-ftp-ls file switches full)))
c3554e95 3904 (ange-ftp-real-insert-directory file switches wildcard full))))
2f7ea155 3905
d0bc419e
RS
3906(defun ange-ftp-dired-uncache (dir)
3907 (if (ange-ftp-ftp-name (expand-file-name dir)))
2f7ea155 3908 (setq ange-ftp-ls-cache-file nil))
2f7ea155 3909
c3554e95
RS
3910(defvar ange-ftp-sans-version-alist nil
3911 "Alist of mapping host type into function to remove file version numbers.")
2f7ea155 3912
c3554e95 3913(defun ange-ftp-file-name-sans-versions (file keep-backup-version)
c3554e95 3914 (setq file (ange-ftp-abbreviate-filename file))
d0bc419e 3915 (let ((parsed (ange-ftp-ftp-name file))
c3554e95
RS
3916 host-type func)
3917 (if parsed
3918 (setq host-type (ange-ftp-host-type (car parsed))
d0bc419e 3919 func (cdr (assq (ange-ftp-host-type (car parsed))
c3554e95
RS
3920 ange-ftp-sans-version-alist))))
3921 (if func (funcall func file keep-backup-version)
3922 (ange-ftp-real-file-name-sans-versions file keep-backup-version))))
2f7ea155 3923
2f7ea155
RS
3924(defvar ange-ftp-remote-shell-file-name
3925 (if (memq system-type '(hpux usg-unix-v)) ; hope that's right
3926 "remsh"
3927 "rsh")
d0bc419e 3928 "Name of command to run a remote shell, for ange-ftp.")
2f7ea155 3929
d0bc419e
RS
3930;;; This doesn't work yet; a new hook needs to be created.
3931;;; Maybe the new hook should be in call-process.
3932(defun ange-ftp-shell-command (command)
3933 (let* ((parsed (ange-ftp-ftp-name default-directory))
2f7ea155
RS
3934 (host (nth 0 parsed))
3935 (user (nth 1 parsed))
d0bc419e 3936 (name (nth 2 parsed)))
2f7ea155 3937 (if (not parsed)
d0bc419e
RS
3938 (ange-ftp-real-shell-command command)
3939 (if (> (length name) 0) ; else it's $HOME
3940 (setq command (concat "cd " name "; " command)))
2f7ea155
RS
3941 (setq command
3942 (format "%s %s \"%s\"" ; remsh -l USER does not work well
3943 ; on a hp-ux machine I tried
3944 ange-ftp-remote-shell-file-name host command))
3945 (ange-ftp-message "Remote command '%s' ..." command)
3946 ;; Cannot call ange-ftp-real-dired-run-shell-command here as it
3947 ;; would prepend "cd default-directory" --- which bombs because
d0bc419e
RS
3948 ;; default-directory is in ange-ftp syntax for remote file names.
3949 (ange-ftp-real-shell-command command))))
2f7ea155 3950
d0bc419e 3951;;; Thisis not hooked up yet.
2f7ea155 3952(defun ange-ftp-dired-call-process (program discard &rest arguments)
2f7ea155
RS
3953 ;; PROGRAM is always one of those below in the cond in dired.el.
3954 ;; The ARGUMENTS are (nearly) always files.
d0bc419e 3955 (if (ange-ftp-ftp-name default-directory)
2f7ea155
RS
3956 ;; Can't use ange-ftp-dired-host-type here because the current
3957 ;; buffer is *dired-check-process output*
3958 (condition-case oops
d0bc419e 3959 (cond ((equal "chmod" program)
2f7ea155
RS
3960 (ange-ftp-call-chmod arguments))
3961 ;; ((equal "chgrp" program))
3962 ;; ((equal dired-chown-program program))
3963 (t (error "Unknown remote command: %s" program)))
3964 (ftp-error (insert (format "%s: %s, %s\n"
3965 (nth 1 oops)
3966 (nth 2 oops)
3967 (nth 3 oops))))
3968 (error (insert (format "%s\n" (nth 1 oops)))))
3969 (apply 'call-process program nil (not discard) nil arguments)))
3970
d0bc419e 3971;;; This currently does not work; it is never called.
2f7ea155
RS
3972(defun ange-ftp-call-chmod (args)
3973 (if (< (length args) 2)
3974 (error "ange-ftp-call-chmod: missing mode and/or filename: %s" args))
3975 (let ((mode (car args)))
3976 (mapcar
3977 (function
3978 (lambda (file)
3979 (setq file (expand-file-name file))
d0bc419e 3980 (let ((parsed (ange-ftp-ftp-name file)))
2f7ea155
RS
3981 (if parsed
3982 (let* ((host (nth 0 parsed))
3983 (user (nth 1 parsed))
d0bc419e 3984 (name (ange-ftp-quote-string (nth 2 parsed)))
2f7ea155
RS
3985 (abbr (ange-ftp-abbreviate-filename file))
3986 (result (ange-ftp-send-cmd host user
d0bc419e 3987 (list 'chmod mode name)
2f7ea155
RS
3988 (format "doing chmod %s"
3989 abbr))))
3990 (or (car result)
3991 (ange-ftp-error host user
3992 (format "chmod: %s: \"%s\""
3993 file
3994 (cdr result)))))))))
3995 (cdr args)))
3996 (setq ange-ftp-ls-cache-file nil)) ;stop confusing dired
2f7ea155 3997\f
d0bc419e
RS
3998;;; This is turned off because it has nothing properly to do
3999;;; with dired. It could be reasonable to adapt this to
4000;;; replace ange-ftp-copy-file.
4001
4002;;;;; ------------------------------------------------------------
4003;;;;; Noddy support for async copy-file within dired.
4004;;;;; ------------------------------------------------------------
4005
4006;;(defun ange-ftp-dired-copy-file (from to ok-flag &optional cont nowait)
4007;; "Documented as original."
4008;; (dired-handle-overwrite to)
4009;; (ange-ftp-copy-file-internal from to ok-flag dired-copy-preserve-time nil
4010;; cont nowait))
4011
4012;;(defun ange-ftp-dired-do-create-files (op-symbol file-creator operation arg
4013;; &optional marker-char op1
4014;; how-to)
4015;; "Documented as original."
4016;; ;; we need to let ange-ftp-dired-create-files know that we indirectly
4017;; ;; called it rather than somebody else.
4018;; (let ((ange-ftp-dired-do-create-files t)) ; tell who caller is
4019;; (ange-ftp-real-dired-do-create-files op-symbol file-creator operation
4020;; arg marker-char op1 how-to)))
4021
4022;;(defun ange-ftp-dired-create-files (file-creator operation fn-list name-constructor
4023;; &optional marker-char)
4024;; "Documented as original."
4025;; (if (and (boundp 'ange-ftp-dired-do-create-files)
4026;; ;; called from ange-ftp-dired-do-create-files?
4027;; ange-ftp-dired-do-create-files
4028;; ;; any files worth copying?
4029;; fn-list
4030;; ;; we only support async copy-file at the mo.
4031;; (eq file-creator 'dired-copy-file)
4032;; ;; it is only worth calling the alternative function for remote files
4033;; ;; as we tie ourself in recursive knots otherwise.
4034;; (or (ange-ftp-ftp-name (car fn-list))
4035;; ;; we can only call the name constructor for dired-do-create-files
4036;; ;; since the one for regexps starts prompting here, there and
4037;; ;; everywhere.
4038;; (ange-ftp-ftp-name (funcall name-constructor (car fn-list)))))
4039;; ;; use the process-filter driven routine rather than the iterative one.
4040;; (ange-ftp-dcf-1 file-creator
4041;; operation
4042;; fn-list
4043;; name-constructor
4044;; (and (boundp 'target) target) ;dynamically bound
4045;; marker-char
4046;; (current-buffer)
4047;; nil ;overwrite-query
4048;; nil ;overwrite-backup-query
4049;; nil ;failures
4050;; nil ;skipped
4051;; 0 ;success-count
4052;; (length fn-list) ;total
4053;; )
4054;; ;; normal case... use the interative routine... much cheaper.
4055;; (ange-ftp-real-dired-create-files file-creator operation fn-list
4056;; name-constructor marker-char)))
4057
4058;;(defun ange-ftp-dcf-1 (file-creator operation fn-list name-constructor
4059;; target marker-char buffer overwrite-query
4060;; overwrite-backup-query failures skipped
4061;; success-count total)
4062;; (let ((old-buf (current-buffer)))
4063;; (unwind-protect
4064;; (progn
4065;; (set-buffer buffer)
4066;; (if (null fn-list)
4067;; (ange-ftp-dcf-3 failures operation total skipped
4068;; success-count buffer)
2f7ea155 4069
d0bc419e
RS
4070;; (let* ((from (car fn-list))
4071;; (to (funcall name-constructor from)))
4072;; (if (equal to from)
4073;; (progn
4074;; (setq to nil)
4075;; (dired-log "Cannot %s to same file: %s\n"
4076;; (downcase operation) from)))
4077;; (if (not to)
4078;; (ange-ftp-dcf-1 file-creator
4079;; operation
4080;; (cdr fn-list)
4081;; name-constructor
4082;; target
4083;; marker-char
4084;; buffer
4085;; overwrite-query
4086;; overwrite-backup-query
4087;; failures
4088;; (cons (dired-make-relative from) skipped)
4089;; success-count
4090;; total)
4091;; (let* ((overwrite (file-exists-p to))
4092;; (overwrite-confirmed ; for dired-handle-overwrite
4093;; (and overwrite
4094;; (let ((help-form '(format "\
4095;;Type SPC or `y' to overwrite file `%s',
4096;;DEL or `n' to skip to next,
4097;;ESC or `q' to not overwrite any of the remaining files,
4098;;`!' to overwrite all remaining files with no more questions." to)))
4099;; (dired-query 'overwrite-query
4100;; "Overwrite `%s'?" to))))
4101;; ;; must determine if FROM is marked before file-creator
4102;; ;; gets a chance to delete it (in case of a move).
4103;; (actual-marker-char
4104;; (cond ((integerp marker-char) marker-char)
4105;; (marker-char (dired-file-marker from)) ; slow
4106;; (t nil))))
4107;; (condition-case err
4108;; (funcall file-creator from to overwrite-confirmed
4109;; (list (function ange-ftp-dcf-2)
4110;; nil ;err
4111;; file-creator operation fn-list
4112;; name-constructor
4113;; target
4114;; marker-char actual-marker-char
4115;; buffer to from
4116;; overwrite
4117;; overwrite-confirmed
4118;; overwrite-query
4119;; overwrite-backup-query
4120;; failures skipped success-count
4121;; total)
4122;; t)
4123;; (file-error ; FILE-CREATOR aborted
4124;; (ange-ftp-dcf-2 nil ;result
4125;; nil ;line
4126;; err
4127;; file-creator operation fn-list
4128;; name-constructor
4129;; target
4130;; marker-char actual-marker-char
4131;; buffer to from
4132;; overwrite
4133;; overwrite-confirmed
4134;; overwrite-query
4135;; overwrite-backup-query
4136;; failures skipped success-count
4137;; total))))))))
4138;; (set-buffer old-buf))))
4139
4140;;(defun ange-ftp-dcf-2 (result line err
4141;; file-creator operation fn-list
4142;; name-constructor
4143;; target
4144;; marker-char actual-marker-char
4145;; buffer to from
4146;; overwrite
4147;; overwrite-confirmed
4148;; overwrite-query
4149;; overwrite-backup-query
4150;; failures skipped success-count
4151;; total)
4152;; (let ((old-buf (current-buffer)))
4153;; (unwind-protect
4154;; (progn
4155;; (set-buffer buffer)
4156;; (if (or err (not result))
4157;; (progn
4158;; (setq failures (cons (dired-make-relative from) failures))
4159;; (dired-log "%s `%s' to `%s' failed:\n%s\n"
4160;; operation from to (or err line)))
4161;; (if overwrite
4162;; ;; If we get here, file-creator hasn't been aborted
4163;; ;; and the old entry (if any) has to be deleted
4164;; ;; before adding the new entry.
4165;; (dired-remove-file to))
4166;; (setq success-count (1+ success-count))
4167;; (message "%s: %d of %d" operation success-count total)
4168;; (dired-add-file to actual-marker-char))
2f7ea155 4169
d0bc419e
RS
4170;; (ange-ftp-dcf-1 file-creator operation (cdr fn-list)
4171;; name-constructor
4172;; target
4173;; marker-char
4174;; buffer
4175;; overwrite-query
4176;; overwrite-backup-query
4177;; failures skipped success-count
4178;; total))
4179;; (set-buffer old-buf))))
4180
4181;;(defun ange-ftp-dcf-3 (failures operation total skipped success-count
4182;; buffer)
4183;; (let ((old-buf (current-buffer)))
4184;; (unwind-protect
4185;; (progn
4186;; (set-buffer buffer)
4187;; (cond
4188;; (failures
4189;; (dired-log-summary
4190;; (message "%s failed for %d of %d file%s %s"
4191;; operation (length failures) total
4192;; (dired-plural-s total) failures)))
4193;; (skipped
4194;; (dired-log-summary
4195;; (message "%s: %d of %d file%s skipped %s"
4196;; operation (length skipped) total
4197;; (dired-plural-s total) skipped)))
4198;; (t
4199;; (message "%s: %s file%s."
4200;; operation success-count (dired-plural-s success-count))))
4201;; (dired-move-to-filename))
4202;; (set-buffer old-buf))))
2f7ea155
RS
4203\f
4204;;;; -----------------------------------------------
4205;;;; Unix Descriptive Listing (dl) Support
4206;;;; -----------------------------------------------
4207
d0bc419e
RS
4208;; This is turned off because nothing uses it currently
4209;; and because I don't understand what it's supposed to be for. --rms.
4210
4211;;(defconst ange-ftp-dired-dl-re-dir
4212;; "^. [^ /]+/[ \n]"
4213;; "Regular expression to use to search for dl directories.")
4214
4215;;(or (assq 'unix:dl ange-ftp-dired-re-dir-alist)
4216;; (setq ange-ftp-dired-re-dir-alist
4217;; (cons (cons 'unix:dl ange-ftp-dired-dl-re-dir)
4218;; ange-ftp-dired-re-dir-alist)))
4219
4220;;(defun ange-ftp-dired-dl-move-to-filename (&optional raise-error eol)
4221;; "In dired, move to the first character of the filename on this line."
4222;; ;; This is the Unix dl version.
4223;; (or eol (setq eol (progn (end-of-line) (point))))
4224;; (let (case-fold-search)
4225;; (beginning-of-line)
4226;; (if (looking-at ". [^ ]+ +\\([0-9]+\\|-\\|=\\) ")
4227;; (goto-char (+ (point) 2))
4228;; (if raise-error
4229;; (error "No file on this line")
4230;; nil))))
4231
4232;;(or (assq 'unix:dl ange-ftp-dired-move-to-filename-alist)
4233;; (setq ange-ftp-dired-move-to-filename-alist
4234;; (cons '(unix:dl . ange-ftp-dired-dl-move-to-filename)
4235;; ange-ftp-dired-move-to-filename-alist)))
4236
4237;;(defun ange-ftp-dired-dl-move-to-end-of-filename (&optional no-error eol)
4238;; ;; Assumes point is at beginning of filename.
4239;; ;; So, it should be called only after (dired-move-to-filename t).
4240;; ;; On failure, signals an error or returns nil.
4241;; ;; This is the Unix dl version.
4242;; (let ((opoint (point))
4243;; case-fold-search hidden)
4244;; (or eol (setq eol (save-excursion (end-of-line) (point))))
4245;; (setq hidden (and selective-display
4246;; (save-excursion
4247;; (search-forward "\r" eol t))))
4248;; (if hidden
4249;; (if no-error
4250;; nil
4251;; (error
4252;; (substitute-command-keys
4253;; "File line is hidden, type \\[dired-hide-subdir] to unhide")))
4254;; (skip-chars-forward "^ /" eol)
4255;; (if (eq opoint (point))
4256;; (if no-error
4257;; nil
4258;; (error "No file on this line"))
4259;; (point)))))
4260
4261;;(or (assq 'unix:dl ange-ftp-dired-move-to-end-of-filename-alist)
4262;; (setq ange-ftp-dired-move-to-end-of-filename-alist
4263;; (cons '(unix:dl . ange-ftp-dired-dl-move-to-end-of-filename)
4264;; ange-ftp-dired-move-to-end-of-filename-alist)))
2f7ea155
RS
4265\f
4266;;;; ------------------------------------------------------------
4267;;;; VOS support (VOS support is probably broken,
4268;;;; but I don't know anything about VOS.)
4269;;;; ------------------------------------------------------------
4270;
d0bc419e
RS
4271;(defun ange-ftp-fix-name-for-vos (name &optional reverse)
4272; (setq name (copy-sequence name))
2f7ea155
RS
4273; (let ((from (if reverse ?\> ?\/))
4274; (to (if reverse ?\/ ?\>))
d0bc419e 4275; (i (1- (length name))))
2f7ea155 4276; (while (>= i 0)
d0bc419e
RS
4277; (if (= (aref name i) from)
4278; (aset name i to))
2f7ea155 4279; (setq i (1- i)))
d0bc419e 4280; name))
2f7ea155 4281;
d0bc419e
RS
4282;(or (assq 'vos ange-ftp-fix-name-func-alist)
4283; (setq ange-ftp-fix-name-func-alist
4284; (cons '(vos . ange-ftp-fix-name-for-vos)
4285; ange-ftp-fix-name-func-alist)))
2f7ea155
RS
4286;
4287;(or (memq 'vos ange-ftp-dumb-host-types)
4288; (setq ange-ftp-dumb-host-types
4289; (cons 'vos ange-ftp-dumb-host-types)))
4290;
d0bc419e
RS
4291;(defun ange-ftp-fix-dir-name-for-vos (dir-name)
4292; (ange-ftp-fix-name-for-vos
4293; (concat dir-name
4294; (if (eq ?/ (aref dir-name (1- (length dir-name))))
2f7ea155
RS
4295; "" "/")
4296; "*")))
4297;
d0bc419e
RS
4298;(or (assq 'vos ange-ftp-fix-dir-name-func-alist)
4299; (setq ange-ftp-fix-dir-name-func-alist
4300; (cons '(vos . ange-ftp-fix-dir-name-for-vos)
4301; ange-ftp-fix-dir-name-func-alist)))
2f7ea155
RS
4302;
4303;(defvar ange-ftp-vos-host-regexp nil
4304; "If a host matches this regexp then it is assumed to be running VOS.")
4305;
4306;(defun ange-ftp-vos-host (host)
4307; (and ange-ftp-vos-host-regexp
4308; (ange-ftp-save-match-data
4309; (string-match ange-ftp-vos-host-regexp host))))
4310;
4311;(defun ange-ftp-parse-vos-listing ()
4312; "Parse the current buffer which is assumed to be in VOS list -all
4313;format, and return a hashtable as the result."
4314; (let ((tbl (ange-ftp-make-hashtable))
4315; (type-list
4316; '(("^Files: [0-9]+ +Blocks: [0-9]+\n+" nil 40)
4317; ("^Dirs: [0-9]+\n+" t 30)))
4318; type-regexp type-is-dir type-col file)
4319; (goto-char (point-min))
4320; (ange-ftp-save-match-data
4321; (while type-list
4322; (setq type-regexp (car (car type-list))
4323; type-is-dir (nth 1 (car type-list))
4324; type-col (nth 2 (car type-list))
4325; type-list (cdr type-list))
4326; (if (re-search-forward type-regexp nil t)
4327; (while (eq (char-after (point)) ? )
4328; (move-to-column type-col)
4329; (setq file (buffer-substring (point)
4330; (progn
4331; (end-of-line 1)
4332; (point))))
4333; (ange-ftp-put-hash-entry file type-is-dir tbl)
4334; (forward-line 1))))
4335; (ange-ftp-put-hash-entry "." 'vosdir tbl)
4336; (ange-ftp-put-hash-entry ".." 'vosdir tbl))
4337; tbl))
4338;
4339;(or (assq 'vos ange-ftp-parse-list-func-alist)
4340; (setq ange-ftp-parse-list-func-alist
4341; (cons '(vos . ange-ftp-parse-vos-listing)
4342; ange-ftp-parse-list-func-alist)))
4343\f
4344;;;; ------------------------------------------------------------
4345;;;; VMS support.
4346;;;; ------------------------------------------------------------
4347
d0bc419e
RS
4348;; Convert NAME from UNIX-ish to VMS. If REVERSE given then convert from VMS
4349;; to UNIX-ish.
4350(defun ange-ftp-fix-name-for-vms (name &optional reverse)
2f7ea155
RS
4351 (ange-ftp-save-match-data
4352 (if reverse
d0bc419e 4353 (if (string-match "^\\([^:]+:\\)?\\(\\[.*\\]\\)?\\([^][]*\\)$" name)
2f7ea155
RS
4354 (let (drive dir file)
4355 (if (match-beginning 1)
d0bc419e 4356 (setq drive (substring name
2f7ea155
RS
4357 (match-beginning 1)
4358 (match-end 1))))
4359 (if (match-beginning 2)
4360 (setq dir
d0bc419e 4361 (substring name (match-beginning 2) (match-end 2))))
2f7ea155
RS
4362 (if (match-beginning 3)
4363 (setq file
d0bc419e 4364 (substring name (match-beginning 3) (match-end 3))))
2f7ea155
RS
4365 (and dir
4366 (setq dir (apply (function concat)
4367 (mapcar (function
4368 (lambda (char)
4369 (if (= char ?.)
4370 (vector ?/)
4371 (vector char))))
4372 (substring dir 1 -1)))))
4373 (concat (and drive
4374 (concat "/" drive "/"))
4375 dir (and dir "/")
4376 file))
d0bc419e 4377 (error "name %s didn't match" name))
2f7ea155 4378 (let (drive dir file tmp)
d0bc419e
RS
4379 (if (string-match "^/[^:]+:/" name)
4380 (setq drive (substring name 1
2f7ea155 4381 (1- (match-end 0)))
d0bc419e
RS
4382 name (substring name (match-end 0))))
4383 (setq tmp (file-name-directory name))
2f7ea155
RS
4384 (if tmp
4385 (setq dir (apply (function concat)
4386 (mapcar (function
4387 (lambda (char)
4388 (if (= char ?/)
4389 (vector ?.)
4390 (vector char))))
4391 (substring tmp 0 -1)))))
d0bc419e 4392 (setq file (file-name-nondirectory name))
2f7ea155
RS
4393 (concat drive
4394 (and dir (concat "[" (if drive nil ".") dir "]"))
4395 file)))))
4396
d0bc419e
RS
4397;; (ange-ftp-fix-name-for-vms "/PUB$:/ANONYMOUS/SDSCPUB/NEXT/Readme.txt;1")
4398;; (ange-ftp-fix-name-for-vms "/PUB$:[ANONYMOUS.SDSCPUB.NEXT]Readme.txt;1" t)
2f7ea155 4399
d0bc419e
RS
4400(or (assq 'vms ange-ftp-fix-name-func-alist)
4401 (setq ange-ftp-fix-name-func-alist
4402 (cons '(vms . ange-ftp-fix-name-for-vms)
4403 ange-ftp-fix-name-func-alist)))
2f7ea155
RS
4404
4405(or (memq 'vms ange-ftp-dumb-host-types)
4406 (setq ange-ftp-dumb-host-types
4407 (cons 'vms ange-ftp-dumb-host-types)))
4408
4409;; It is important that this function barf for directories for which we know
4410;; that we cannot possibly get a directory listing, such as "/" and "/DEV:/".
4411;; This is because it saves an unnecessary FTP error, or possibly the listing
4412;; might succeed, but give erroneous info. This last case is particularly
4413;; likely for OS's (like MTS) for which we need to use a wildcard in order
4414;; to list a directory.
4415
d0bc419e
RS
4416;; Convert name from UNIX-ish to VMS ready for a DIRectory listing.
4417(defun ange-ftp-fix-dir-name-for-vms (dir-name)
2f7ea155
RS
4418 ;; Should there be entries for .. -> [-] and . -> [] below. Don't
4419 ;; think so, because expand-filename should have already short-circuited
4420 ;; them.
d0bc419e 4421 (cond ((string-equal dir-name "/")
2f7ea155 4422 (error "Cannot get listing for fictitious \"/\" directory."))
d0bc419e 4423 ((string-match "^/[-A-Z0-9_$]+:/$" dir-name)
2f7ea155 4424 (error "Cannot get listing for device."))
d0bc419e 4425 ((ange-ftp-fix-name-for-vms dir-name))))
2f7ea155 4426
d0bc419e
RS
4427(or (assq 'vms ange-ftp-fix-dir-name-func-alist)
4428 (setq ange-ftp-fix-dir-name-func-alist
4429 (cons '(vms . ange-ftp-fix-dir-name-for-vms)
4430 ange-ftp-fix-dir-name-func-alist)))
2f7ea155
RS
4431
4432(defvar ange-ftp-vms-host-regexp nil)
4433
d0bc419e 4434;; Return non-nil if HOST is running VMS.
2f7ea155 4435(defun ange-ftp-vms-host (host)
2f7ea155
RS
4436 (and ange-ftp-vms-host-regexp
4437 (ange-ftp-save-match-data
4438 (string-match ange-ftp-vms-host-regexp host))))
4439
4440;; Because some VMS ftp servers convert filenames to lower case
4441;; we allow a-z in the filename regexp. I'm not too happy about this.
4442
4443(defconst ange-ftp-vms-filename-regexp
4444 (concat
4445 "\\(\\([_A-Za-z0-9$]?\\|[_A-Za-z0-9$][_A-Za-z0-9$---]*\\)\\."
4446 "[_A-Za-z0-9$---]*;+[0-9]*\\)")
4447 "Regular expression to match for a valid VMS file name in Dired buffer.
4448Stupid freaking bug! Position of _ and $ shouldn't matter but they do.
4449Having [A-Z0-9$_] bombs on filename _$$CHANGE_LOG$.TXT$ and $CHANGE_LOG$.TX
4450Other orders of $ and _ seem to all work just fine.")
4451
4452;; These parsing functions are as general as possible because the syntax
4453;; of ftp listings from VMS hosts is a bit erratic. What saves us is that
4454;; the VMS filename syntax is so rigid. If they bomb on a listing in the
4455;; standard VMS Multinet format, then this is a bug. If they bomb on a listing
4456;; from vms.weird.net, then too bad.
4457
d0bc419e 4458;; Extract the next filename from a VMS dired-like listing.
2f7ea155 4459(defun ange-ftp-parse-vms-filename ()
2f7ea155
RS
4460 (if (re-search-forward
4461 ange-ftp-vms-filename-regexp
4462 nil t)
4463 (buffer-substring (match-beginning 0) (match-end 0))))
4464
d0bc419e
RS
4465;; Parse the current buffer which is assumed to be in MultiNet FTP dir
4466;; format, and return a hashtable as the result.
2f7ea155 4467(defun ange-ftp-parse-vms-listing ()
2f7ea155
RS
4468 (let ((tbl (ange-ftp-make-hashtable))
4469 file)
4470 (goto-char (point-min))
4471 (ange-ftp-save-match-data
4472 (while (setq file (ange-ftp-parse-vms-filename))
4473 (if (string-match "\\.\\(DIR\\|dir\\);[0-9]+" file)
4474 ;; deal with directories
4475 (ange-ftp-put-hash-entry
4476 (substring file 0 (match-beginning 0)) t tbl)
4477 (ange-ftp-put-hash-entry file nil tbl)
4478 (if (string-match ";[0-9]+$" file) ; deal with extension
4479 ;; sans extension
4480 (ange-ftp-put-hash-entry
4481 (substring file 0 (match-beginning 0)) nil tbl)))
4482 (forward-line 1))
4483 ;; Would like to look for a "Total" line, or a "Directory" line to
4484 ;; make sure that the listing isn't complete garbage before putting
4485 ;; in "." and "..", but we can't even count on all VAX's giving us
4486 ;; either of these.
4487 (ange-ftp-put-hash-entry "." t tbl)
4488 (ange-ftp-put-hash-entry ".." t tbl))
4489 tbl))
4490
4491(or (assq 'vms ange-ftp-parse-list-func-alist)
4492 (setq ange-ftp-parse-list-func-alist
4493 (cons '(vms . ange-ftp-parse-vms-listing)
4494 ange-ftp-parse-list-func-alist)))
4495
4496;; This version only deletes file entries which have
4497;; explicit version numbers, because that is all VMS allows.
4498
4499;; Can the following two functions be speeded up using file
4500;; completion functions?
4501
d0bc419e 4502(defun ange-ftp-vms-delete-file-entry (name &optional dir-p)
2f7ea155 4503 (if dir-p
d0bc419e 4504 (ange-ftp-internal-delete-file-entry name t)
2f7ea155 4505 (ange-ftp-save-match-data
d0bc419e 4506 (let ((file (ange-ftp-get-file-part name)))
2f7ea155
RS
4507 (if (string-match ";[0-9]+$" file)
4508 ;; In VMS you can't delete a file without an explicit
4509 ;; version number, or wild-card (e.g. FOO;*)
4510 ;; For now, we give up on wildcards.
4511 (let ((files (ange-ftp-get-hash-entry
d0bc419e 4512 (file-name-directory name)
2f7ea155
RS
4513 ange-ftp-files-hashtable)))
4514 (if files
4515 (let* ((root (substring file 0
4516 (match-beginning 0)))
4517 (regexp (concat "^"
4518 (regexp-quote root)
4519 ";[0-9]+$"))
4520 versions)
4521 (ange-ftp-del-hash-entry file files)
4522 ;; Now we need to check if there are any
4523 ;; versions left. If not, then delete the
4524 ;; root entry.
4525 (mapatoms
4526 '(lambda (sym)
4527 (and (string-match regexp (get sym 'key))
4528 (setq versions t)))
4529 files)
4530 (or versions
4531 (ange-ftp-del-hash-entry root files))))))))))
4532
4533(or (assq 'vms ange-ftp-delete-file-entry-alist)
4534 (setq ange-ftp-delete-file-entry-alist
4535 (cons '(vms . ange-ftp-vms-delete-file-entry)
4536 ange-ftp-delete-file-entry-alist)))
4537
d0bc419e 4538(defun ange-ftp-vms-add-file-entry (name &optional dir-p)
2f7ea155 4539 (if dir-p
d0bc419e 4540 (ange-ftp-internal-add-file-entry name t)
2f7ea155 4541 (let ((files (ange-ftp-get-hash-entry
d0bc419e 4542 (file-name-directory name)
2f7ea155
RS
4543 ange-ftp-files-hashtable)))
4544 (if files
d0bc419e 4545 (let ((file (ange-ftp-get-file-part name)))
2f7ea155
RS
4546 (ange-ftp-save-match-data
4547 (if (string-match ";[0-9]+$" file)
4548 (ange-ftp-put-hash-entry
4549 (substring file 0 (match-beginning 0))
4550 nil files)
4551 ;; Need to figure out what version of the file
4552 ;; is being added.
4553 (let ((regexp (concat "^"
4554 (regexp-quote file)
4555 ";\\([0-9]+\\)$"))
4556 (version 0))
4557 (mapatoms
4558 '(lambda (sym)
4559 (let ((name (get sym 'key)))
4560 (and (string-match regexp name)
4561 (setq version
4562 (max version
4563 (string-to-int
4564 (substring name
4565 (match-beginning 1)
4566 (match-end 1))))))))
4567 files)
4568 (setq version (1+ version))
4569 (ange-ftp-put-hash-entry
4570 (concat file ";" (int-to-string version))
4571 nil files))))
4572 (ange-ftp-put-hash-entry file nil files))))))
4573
4574(or (assq 'vms ange-ftp-add-file-entry-alist)
4575 (setq ange-ftp-add-file-entry-alist
4576 (cons '(vms . ange-ftp-vms-add-file-entry)
4577 ange-ftp-add-file-entry-alist)))
4578
4579
4580(defun ange-ftp-add-vms-host (host)
d0bc419e 4581 "Mark HOST as the name of a machine running VMS."
2f7ea155
RS
4582 (interactive
4583 (list (read-string "Host: "
68f5eb5a 4584 (let ((name (or (buffer-file-name) default-directory)))
d0bc419e 4585 (and name (car (ange-ftp-ftp-name name)))))))
2f7ea155
RS
4586 (if (not (ange-ftp-vms-host host))
4587 (setq ange-ftp-vms-host-regexp
4588 (concat "^" (regexp-quote host) "$"
4589 (and ange-ftp-vms-host-regexp "\\|")
4590 ange-ftp-vms-host-regexp)
4591 ange-ftp-host-cache nil)))
4592
4593
4594(defun ange-ftp-vms-file-name-as-directory (name)
4595 (ange-ftp-save-match-data
4596 (if (string-match "\\.\\(DIR\\|dir\\)\\(;[0-9]+\\)?$" name)
4597 (setq name (substring name 0 (match-beginning 0))))
4598 (ange-ftp-real-file-name-as-directory name)))
4599
4600(or (assq 'vms ange-ftp-file-name-as-directory-alist)
4601 (setq ange-ftp-file-name-as-directory-alist
4602 (cons '(vms . ange-ftp-vms-file-name-as-directory)
4603 ange-ftp-file-name-as-directory-alist)))
4604
4605;;; Tree dired support:
4606
4607;; For this code I have borrowed liberally from Sebastian Kremer's
4608;; dired-vms.el
4609
4610
d0bc419e
RS
4611;;;; These regexps must be anchored to beginning of line.
4612;;;; Beware that the ftpd may put the device in front of the filename.
4613
4614;;(defconst ange-ftp-dired-vms-re-exe "^. [^ \t.]+\\.\\(EXE\\|exe\\)[; ]"
4615;; "Regular expression to use to search for VMS executable files.")
4616
4617;;(defconst ange-ftp-dired-vms-re-dir "^. [^ \t.]+\\.\\(DIR\\|dir\\)[; ]"
4618;; "Regular expression to use to search for VMS directories.")
4619
4620;;(or (assq 'vms ange-ftp-dired-re-exe-alist)
4621;; (setq ange-ftp-dired-re-exe-alist
4622;; (cons (cons 'vms ange-ftp-dired-vms-re-exe)
4623;; ange-ftp-dired-re-exe-alist)))
4624
4625;;(or (assq 'vms ange-ftp-dired-re-dir-alist)
4626;; (setq ange-ftp-dired-re-dir-alist
4627;; (cons (cons 'vms ange-ftp-dired-vms-re-dir)
4628;; ange-ftp-dired-re-dir-alist)))
4629
4630;;(defun ange-ftp-dired-vms-insert-headerline (dir)
4631;; ;; VMS inserts a headerline. I would prefer the headerline
4632;; ;; to be in ange-ftp format. This version tries to
4633;; ;; be careful, because we can't count on a headerline
4634;; ;; over ftp, and we wouldn't want to delete anything
4635;; ;; important.
4636;; (save-excursion
4637;; (if (looking-at "^ wildcard ")
4638;; (forward-line 1))
4639;; (if (looking-at "^[ \n\t]*[^\n]+\\][ \t]*\n")
4640;; (delete-region (point) (match-end 0))))
4641;; (ange-ftp-real-dired-insert-headerline dir))
4642
4643;;(or (assq 'vms ange-ftp-dired-insert-headerline-alist)
4644;; (setq ange-ftp-dired-insert-headerline-alist
4645;; (cons '(vms . ange-ftp-dired-vms-insert-headerline)
4646;; ange-ftp-dired-insert-headerline-alist)))
4647
4648;;(defun ange-ftp-dired-vms-move-to-filename (&optional raise-error eol)
4649;; "In dired, move to first char of filename on this line.
4650;;Returns position (point) or nil if no filename on this line."
4651;; ;; This is the VMS version.
4652;; (let (case-fold-search)
4653;; (or eol (setq eol (progn (end-of-line) (point))))
4654;; (beginning-of-line)
4655;; (if (re-search-forward ange-ftp-vms-filename-regexp eol t)
4656;; (goto-char (match-beginning 1))
4657;; (if raise-error
4658;; (error "No file on this line")
4659;; nil))))
4660
4661;;(or (assq 'vms ange-ftp-dired-move-to-filename-alist)
4662;; (setq ange-ftp-dired-move-to-filename-alist
4663;; (cons '(vms . ange-ftp-dired-vms-move-to-filename)
4664;; ange-ftp-dired-move-to-filename-alist)))
4665
4666;;(defun ange-ftp-dired-vms-move-to-end-of-filename (&optional no-error eol)
4667;; ;; Assumes point is at beginning of filename.
4668;; ;; So, it should be called only after (dired-move-to-filename t).
4669;; ;; case-fold-search must be nil, at least for VMS.
4670;; ;; On failure, signals an error or returns nil.
4671;; ;; This is the VMS version.
4672;; (let (opoint hidden case-fold-search)
4673;; (setq opoint (point))
4674;; (or eol (setq eol (save-excursion (end-of-line) (point))))
4675;; (setq hidden (and selective-display
4676;; (save-excursion (search-forward "\r" eol t))))
4677;; (if hidden
4678;; nil
4679;; (re-search-forward ange-ftp-vms-filename-regexp eol t))
4680;; (or no-error
4681;; (not (eq opoint (point)))
4682;; (error
4683;; (if hidden
4684;; (substitute-command-keys
4685;; "File line is hidden, type \\[dired-hide-subdir] to unhide")
4686;; "No file on this line")))
4687;; (if (eq opoint (point))
4688;; nil
4689;; (point))))
4690
4691;;(or (assq 'vms ange-ftp-dired-move-to-end-of-filename-alist)
4692;; (setq ange-ftp-dired-move-to-end-of-filename-alist
4693;; (cons '(vms . ange-ftp-dired-vms-move-to-end-of-filename)
4694;; ange-ftp-dired-move-to-end-of-filename-alist)))
4695
4696;;(defun ange-ftp-dired-vms-between-files ()
4697;; (save-excursion
4698;; (beginning-of-line)
4699;; (or (equal (following-char) 10) ; newline
4700;; (equal (following-char) 9) ; tab
4701;; (progn (forward-char 2)
4702;; (or (looking-at "Total of")
4703;; (equal (following-char) 32))))))
4704
4705;;(or (assq 'vms ange-ftp-dired-between-files-alist)
4706;; (setq ange-ftp-dired-between-files-alist
4707;; (cons '(vms . ange-ftp-dired-vms-between-files)
4708;; ange-ftp-dired-between-files-alist)))
2f7ea155
RS
4709
4710;; Beware! In VMS filenames must be of the form "FILE.TYPE".
4711;; Therefore, we cannot just append a ".Z" to filenames for
4712;; compressed files. Instead, we turn "FILE.TYPE" into
4713;; "FILE.TYPE-Z". Hope that this is a reasonable thing to do.
4714
4715(defun ange-ftp-vms-make-compressed-filename (name &optional reverse)
d0bc419e
RS
4716 (cond
4717 ((string-match "-Z;[0-9]+$" name)
4718 (list nil (substring name 0 (match-beginning 0))))
4719 ((string-match ";[0-9]+$" name)
4720 (list nil (substring name 0 (match-beginning 0))))
4721 ((string-match "-Z$" name)
4722 (list nil (substring name 0 -2)))
4723 (t
4724 (list t
4725 (if (string-match ";[0-9]+$" name)
4726 (concat (substring name 0 (match-beginning 0))
4727 "-Z")
4728 (concat name "-Z"))))))
4729
4730(or (assq 'vms ange-ftp-make-compressed-filename-alist)
4731 (setq ange-ftp-make-compressed-filename-alist
2f7ea155 4732 (cons '(vms . ange-ftp-vms-make-compressed-filename)
d0bc419e
RS
4733 ange-ftp-make-compressed-filename-alist)))
4734
4735;;;; When the filename is too long, VMS will use two lines to list a file
4736;;;; (damn them!) This will confuse dired. To solve this, need to convince
4737;;;; Sebastian to use a function dired-go-to-end-of-file-line, instead of
4738;;;; (forward-line 1). This would require a number of changes to dired.el.
4739;;;; If dired gets confused, revert-buffer will fix it.
4740
4741;;(defun ange-ftp-dired-vms-ls-trim ()
4742;; (goto-char (point-min))
4743;; (let ((case-fold-search nil))
4744;; (re-search-forward ange-ftp-vms-filename-regexp))
4745;; (beginning-of-line)
4746;; (delete-region (point-min) (point))
4747;; (forward-line 1)
4748;; (delete-region (point) (point-max)))
4749
4750
4751;;(or (assq 'vms ange-ftp-dired-ls-trim-alist)
4752;; (setq ange-ftp-dired-ls-trim-alist
4753;; (cons '(vms . ange-ftp-dired-vms-ls-trim)
4754;; ange-ftp-dired-ls-trim-alist)))
4755
4756(defun ange-ftp-vms-sans-version (name)
2f7ea155
RS
4757 (ange-ftp-save-match-data
4758 (if (string-match ";[0-9]+$" name)
4759 (substring name 0 (match-beginning 0))
4760 name)))
4761
d0bc419e
RS
4762(or (assq 'vms ange-ftp-sans-version-alist)
4763 (setq ange-ftp-sans-version-alist
4764 (cons '(vms . ange-ftp-vms-sans-version)
4765 ange-ftp-sans-version-alist)))
4766
4767;;(defvar ange-ftp-file-version-alist)
4768
4769;;;;; The vms version of clean-directory has 2 more optional args
4770;;;;; than the usual dired version. This is so that it can be used by
4771;;;;; ange-ftp-dired-vms-flag-backup-files.
4772
4773;;(defun ange-ftp-dired-vms-clean-directory (keep &optional marker msg)
4774;; "Flag numerical backups for deletion.
4775;;Spares `dired-kept-versions' latest versions, and `kept-old-versions' oldest.
4776;;Positive prefix arg KEEP overrides `dired-kept-versions';
4777;;Negative prefix arg KEEP overrides `kept-old-versions' with KEEP made positive.
4778
4779;;To clear the flags on these files, you can use \\[dired-flag-backup-files]
4780;;with a prefix argument."
4781;;; (interactive "P") ; Never actually called interactively.
4782;; (setq keep (max 1 (if keep (prefix-numeric-value keep) dired-kept-versions)))
4783;; (let ((early-retention (if (< keep 0) (- keep) kept-old-versions))
4784;; ;; late-retention must NEVER be allowed to be less than 1 in VMS!
4785;; ;; This could wipe ALL copies of the file.
4786;; (late-retention (max 1 (if (<= keep 0) dired-kept-versions keep)))
4787;; (action (or msg "Cleaning"))
4788;; (ange-ftp-trample-marker (or marker dired-del-marker))
4789;; (ange-ftp-file-version-alist ()))
4790;; (message (concat action
4791;; " numerical backups (keeping %d late, %d old)...")
4792;; late-retention early-retention)
4793;; ;; Look at each file.
4794;; ;; If the file has numeric backup versions,
4795;; ;; put on ange-ftp-file-version-alist an element of the form
4796;; ;; (FILENAME . VERSION-NUMBER-LIST)
4797;; (dired-map-dired-file-lines (function
4798;; ange-ftp-dired-vms-collect-file-versions))
4799;; ;; Sort each VERSION-NUMBER-LIST,
4800;; ;; and remove the versions not to be deleted.
4801;; (let ((fval ange-ftp-file-version-alist))
4802;; (while fval
4803;; (let* ((sorted-v-list (cons 'q (sort (cdr (car fval)) '<)))
4804;; (v-count (length sorted-v-list)))
4805;; (if (> v-count (+ early-retention late-retention))
4806;; (rplacd (nthcdr early-retention sorted-v-list)
4807;; (nthcdr (- v-count late-retention)
4808;; sorted-v-list)))
4809;; (rplacd (car fval)
4810;; (cdr sorted-v-list)))
4811;; (setq fval (cdr fval))))
4812;; ;; Look at each file. If it is a numeric backup file,
4813;; ;; find it in a VERSION-NUMBER-LIST and maybe flag it for deletion.
4814;; (dired-map-dired-file-lines
4815;; (function
4816;; ange-ftp-dired-vms-trample-file-versions mark))
4817;; (message (concat action " numerical backups...done"))))
4818
4819;;(or (assq 'vms ange-ftp-dired-clean-directory-alist)
4820;; (setq ange-ftp-dired-clean-directory-alist
4821;; (cons '(vms . ange-ftp-dired-vms-clean-directory)
4822;; ange-ftp-dired-clean-directory-alist)))
4823
4824;;(defun ange-ftp-dired-vms-collect-file-versions (fn)
4825;; ;; "If it looks like file FN has versions, return a list of the versions.
4826;; ;;That is a list of strings which are file names.
4827;; ;;The caller may want to flag some of these files for deletion."
4828;;(let ((name (nth 2 (ange-ftp-ftp-name fn))))
4829;; (if (string-match ";[0-9]+$" name)
4830;; (let* ((name (substring name 0 (match-beginning 0)))
4831;; (fn (ange-ftp-replace-name-component fn name)))
4832;; (if (not (assq fn ange-ftp-file-version-alist))
4833;; (let* ((base-versions
4834;; (concat (file-name-nondirectory name) ";"))
4835;; (bv-length (length base-versions))
4836;; (possibilities (file-name-all-completions
4837;; base-versions
4838;; (file-name-directory fn)))
4839;; (versions (mapcar
4840;; '(lambda (arg)
4841;; (if (and (string-match
4842;; "[0-9]+$" arg bv-length)
4843;; (= (match-beginning 0) bv-length))
4844;; (string-to-int (substring arg bv-length))
4845;; 0))
4846;; possibilities)))
4847;; (if versions
4848;; (setq
4849;; ange-ftp-file-version-alist
4850;; (cons (cons fn versions)
4851;; ange-ftp-file-version-alist)))))))))
4852
4853;;(defun ange-ftp-dired-vms-trample-file-versions (fn)
4854;; (let* ((start-vn (string-match ";[0-9]+$" fn))
4855;; base-version-list)
4856;; (and start-vn
4857;; (setq base-version-list ; there was a base version to which
4858;; (assoc (substring fn 0 start-vn) ; this looks like a
4859;; ange-ftp-file-version-alist)) ; subversion
4860;; (not (memq (string-to-int (substring fn (1+ start-vn)))
4861;; base-version-list)) ; this one doesn't make the cut
4862;; (progn (beginning-of-line)
4863;; (delete-char 1)
4864;; (insert ange-ftp-trample-marker)))))
4865
4866;;(defun ange-ftp-dired-vms-flag-backup-files (&optional unflag-p)
4867;; (let ((dired-kept-versions 1)
4868;; (kept-old-versions 0)
4869;; marker msg)
4870;; (if unflag-p
4871;; (setq marker ?\040 msg "Unflagging")
4872;; (setq marker dired-del-marker msg "Cleaning"))
4873;; (ange-ftp-dired-vms-clean-directory nil marker msg)))
4874
4875;;(or (assq 'vms ange-ftp-dired-flag-backup-files-alist)
4876;; (setq ange-ftp-dired-flag-backup-files-alist
4877;; (cons '(vms . ange-ftp-dired-vms-flag-backup-files)
4878;; ange-ftp-dired-flag-backup-files-alist)))
4879
4880;;(defun ange-ftp-dired-vms-backup-diff (&optional switches)
4881;; (let ((file (dired-get-filename 'no-dir))
4882;; bak)
4883;; (if (and (string-match ";[0-9]+$" file)
4884;; ;; Find most recent previous version.
4885;; (let ((root (substring file 0 (match-beginning 0)))
4886;; (ver
4887;; (string-to-int (substring file (1+ (match-beginning 0)))))
4888;; found)
4889;; (setq ver (1- ver))
4890;; (while (and (> ver 0) (not found))
4891;; (setq bak (concat root ";" (int-to-string ver)))
4892;; (and (file-exists-p bak) (setq found t))
4893;; (setq ver (1- ver)))
4894;; found))
4895;; (if switches
4896;; (diff (expand-file-name bak) (expand-file-name file) switches)
4897;; (diff (expand-file-name bak) (expand-file-name file)))
4898;; (error "No previous version found for %s" file))))
4899
4900;;(or (assq 'vms ange-ftp-dired-backup-diff-alist)
4901;; (setq ange-ftp-dired-backup-diff-alist
4902;; (cons '(vms . ange-ftp-dired-vms-backup-diff)
4903;; ange-ftp-dired-backup-diff-alist)))
2f7ea155
RS
4904
4905\f
4906;;;; ------------------------------------------------------------
4907;;;; MTS support
4908;;;; ------------------------------------------------------------
4909
4910
d0bc419e
RS
4911;; Convert NAME from UNIX-ish to MTS. If REVERSE given then convert from
4912;; MTS to UNIX-ish.
4913(defun ange-ftp-fix-name-for-mts (name &optional reverse)
2f7ea155
RS
4914 (ange-ftp-save-match-data
4915 (if reverse
d0bc419e 4916 (if (string-match "^\\([^:]+:\\)?\\(.*\\)$" name)
2f7ea155
RS
4917 (let (acct file)
4918 (if (match-beginning 1)
d0bc419e 4919 (setq acct (substring name 0 (match-end 1))))
2f7ea155 4920 (if (match-beginning 2)
d0bc419e 4921 (setq file (substring name
2f7ea155
RS
4922 (match-beginning 2) (match-end 2))))
4923 (concat (and acct (concat "/" acct "/"))
4924 file))
d0bc419e
RS
4925 (error "name %s didn't match" name))
4926 (if (string-match "^/\\([^:]+:\\)/\\(.*\\)$" name)
4927 (concat (substring name 1 (match-end 1))
4928 (substring name (match-beginning 2) (match-end 2)))
2f7ea155 4929 ;; Let's hope that mts will recognize it anyway.
d0bc419e 4930 name))))
2f7ea155 4931
d0bc419e
RS
4932(or (assq 'mts ange-ftp-fix-name-func-alist)
4933 (setq ange-ftp-fix-name-func-alist
4934 (cons '(mts . ange-ftp-fix-name-for-mts)
4935 ange-ftp-fix-name-func-alist)))
2f7ea155 4936
d0bc419e
RS
4937;; Convert name from UNIX-ish to MTS ready for a DIRectory listing.
4938;; Remember that there are no directories in MTS.
4939(defun ange-ftp-fix-dir-name-for-mts (dir-name)
4940 (if (string-equal dir-name "/")
2f7ea155 4941 (error "Cannot get listing for fictitious \"/\" directory.")
d0bc419e 4942 (let ((dir-name (ange-ftp-fix-name-for-mts dir-name)))
2f7ea155 4943 (cond
d0bc419e 4944 ((string-equal dir-name "")
2f7ea155 4945 "?")
d0bc419e
RS
4946 ((string-match ":$" dir-name)
4947 (concat dir-name "?"))
4948 (dir-name))))) ; It's just a single file.
2f7ea155 4949
d0bc419e
RS
4950(or (assq 'mts ange-ftp-fix-dir-name-func-alist)
4951 (setq ange-ftp-fix-dir-name-func-alist
4952 (cons '(mts . ange-ftp-fix-dir-name-for-mts)
4953 ange-ftp-fix-dir-name-func-alist)))
2f7ea155
RS
4954
4955(or (memq 'mts ange-ftp-dumb-host-types)
4956 (setq ange-ftp-dumb-host-types
4957 (cons 'mts ange-ftp-dumb-host-types)))
4958
4959(defvar ange-ftp-mts-host-regexp nil)
4960
d0bc419e 4961;; Return non-nil if HOST is running MTS.
2f7ea155 4962(defun ange-ftp-mts-host (host)
2f7ea155
RS
4963 (and ange-ftp-mts-host-regexp
4964 (ange-ftp-save-match-data
4965 (string-match ange-ftp-mts-host-regexp host))))
4966
d0bc419e 4967;; Parse the current buffer which is assumed to be in mts ftp dir format.
2f7ea155 4968(defun ange-ftp-parse-mts-listing ()
2f7ea155
RS
4969 (let ((tbl (ange-ftp-make-hashtable)))
4970 (goto-char (point-min))
4971 (ange-ftp-save-match-data
4972 (while (re-search-forward ange-ftp-date-regexp nil t)
4973 (end-of-line)
4974 (skip-chars-backward " ")
4975 (let ((end (point)))
4976 (skip-chars-backward "-A-Z0-9_.!")
4977 (ange-ftp-put-hash-entry (buffer-substring (point) end) nil tbl))
4978 (forward-line 1)))
4979 ;; Don't need to bother with ..
4980 (ange-ftp-put-hash-entry "." t tbl)
4981 tbl))
4982
4983(or (assq 'mts ange-ftp-parse-list-func-alist)
4984 (setq ange-ftp-parse-list-func-alist
4985 (cons '(mts . ange-ftp-parse-mts-listing)
4986 ange-ftp-parse-list-func-alist)))
4987
4988(defun ange-ftp-add-mts-host (host)
d0bc419e 4989 "Mark HOST as the name of a machine running MTS."
2f7ea155
RS
4990 (interactive
4991 (list (read-string "Host: "
68f5eb5a 4992 (let ((name (or (buffer-file-name) default-directory)))
d0bc419e 4993 (and name (car (ange-ftp-ftp-name name)))))))
2f7ea155
RS
4994 (if (not (ange-ftp-mts-host host))
4995 (setq ange-ftp-mts-host-regexp
4996 (concat "^" (regexp-quote host) "$"
4997 (and ange-ftp-mts-host-regexp "\\|")
4998 ange-ftp-mts-host-regexp)
4999 ange-ftp-host-cache nil)))
5000
5001;;; Tree dired support:
5002
d0bc419e
RS
5003;;;; There aren't too many systems left that use MTS. This dired support will
5004;;;; work for the implementation of ftp on mtsg.ubc.ca. I hope other mts systems
5005;;;; implement ftp in the same way. If not, it might be necessary to make the
5006;;;; following more flexible.
5007
5008;;(defun ange-ftp-dired-mts-move-to-filename (&optional raise-error eol)
5009;; "In dired, move to first char of filename on this line.
5010;;Returns position (point) or nil if no filename on this line."
5011;; ;; This is the MTS version.
5012;; (or eol (setq eol (progn (end-of-line) (point))))
5013;; (beginning-of-line)
5014;; (if (re-search-forward
5015;; ange-ftp-date-regexp eol t)
5016;; (progn
5017;; (skip-chars-forward " ") ; Eat blanks after date
5018;; (skip-chars-forward "0-9:" eol) ; Eat time or year
5019;; (skip-chars-forward " " eol) ; one space before filename
5020;; ;; When listing an account other than the users own account it appends
5021;; ;; ACCT: to the beginning of the filename. Skip over this.
5022;; (and (looking-at "[A-Z0-9_.]+:")
5023;; (goto-char (match-end 0)))
5024;; (point))
5025;; (if raise-error
5026;; (error "No file on this line")
5027;; nil)))
5028
5029;;(or (assq 'mts ange-ftp-dired-move-to-filename-alist)
5030;; (setq ange-ftp-dired-move-to-filename-alist
5031;; (cons '(mts . ange-ftp-dired-mts-move-to-filename)
5032;; ange-ftp-dired-move-to-filename-alist)))
5033
5034;;(defun ange-ftp-dired-mts-move-to-end-of-filename (&optional no-error eol)
5035;; ;; Assumes point is at beginning of filename.
5036;; ;; So, it should be called only after (dired-move-to-filename t).
5037;; ;; On failure, signals an error or returns nil.
5038;; ;; This is the MTS version.
5039;; (let (opoint hidden case-fold-search)
5040;; (setq opoint (point)
5041;; eol (save-excursion (end-of-line) (point))
5042;; hidden (and selective-display
5043;; (save-excursion (search-forward "\r" eol t))))
5044;; (if hidden
5045;; nil
5046;; (skip-chars-forward "-A-Z0-9._!" eol))
5047;; (or no-error
5048;; (not (eq opoint (point)))
5049;; (error
5050;; (if hidden
5051;; (substitute-command-keys
5052;; "File line is hidden, type \\[dired-hide-subdir] to unhide")
5053;; "No file on this line")))
5054;; (if (eq opoint (point))
5055;; nil
5056;; (point))))
5057
5058;;(or (assq 'mts ange-ftp-dired-move-to-end-of-filename-alist)
5059;; (setq ange-ftp-dired-move-to-end-of-filename-alist
5060;; (cons '(mts . ange-ftp-dired-mts-move-to-end-of-filename)
5061;; ange-ftp-dired-move-to-end-of-filename-alist)))
2f7ea155
RS
5062\f
5063;;;; ------------------------------------------------------------
5064;;;; CMS support
5065;;;; ------------------------------------------------------------
5066
d0bc419e 5067;; Since CMS doesn't have any full file name syntax, we have to fudge
2f7ea155
RS
5068;; things with cd's. We actually send too many cd's, but is dangerous
5069;; to try to remember the current minidisk, because if the connection
5070;; is closed and needs to be reopened, we will find ourselves back in
5071;; the default minidisk. This is fairly likely since CMS ftp servers
5072;; usually close the connection after 5 minutes of inactivity.
5073
5074;; Have I got the filename character set right?
5075
d0bc419e 5076(defun ange-ftp-fix-name-for-cms (name &optional reverse)
2f7ea155
RS
5077 (ange-ftp-save-match-data
5078 (if reverse
5079 ;; Since we only convert output from a pwd in this direction,
5080 ;; we'll assume that it's a minidisk, and make it into a
5081 ;; directory file name. Note that the expand-dir-hashtable
5082 ;; stores directories without the trailing /. Is this
5083 ;; consistent?
d0bc419e 5084 (concat "/" name)
2f7ea155 5085 (if (string-match "^/\\([-A-Z0-9$*._]+\\)/\\([-A-Z0-9$._]+\\)?$"
d0bc419e
RS
5086 name)
5087 (let ((minidisk (substring name 1 (match-end 1))))
2f7ea155 5088 (if (match-beginning 2)
d0bc419e 5089 (let ((file (substring name (match-beginning 2)
2f7ea155
RS
5090 (match-end 2)))
5091 (cmd (concat "cd " minidisk))
5092
5093 ;; Note that host and user are bound in the call
5094 ;; to ange-ftp-send-cmd
5095 (proc (ange-ftp-get-process ange-ftp-this-host
5096 ange-ftp-this-user)))
5097
5098 ;; Must use ange-ftp-raw-send-cmd here to avoid
5099 ;; an infinite loop.
d0bc419e 5100 (if (car (ange-ftp-raw-send-cmd proc cmd ange-ftp-this-msg))
2f7ea155
RS
5101 file
5102 ;; failed... try ONCE more.
5103 (setq proc (ange-ftp-get-process ange-ftp-this-host
5104 ange-ftp-this-user))
d0bc419e
RS
5105 (let ((result (ange-ftp-raw-send-cmd proc cmd
5106 ange-ftp-this-msg)))
2f7ea155
RS
5107 (if (car result)
5108 file
5109 ;; failed. give up.
5110 (ange-ftp-error ange-ftp-this-host ange-ftp-this-user
5111 (format "cd to minidisk %s failed: %s"
5112 minidisk (cdr result)))))))
5113 ;; return the minidisk
5114 minidisk))
5115 (error "Invalid CMS filename")))))
5116
d0bc419e
RS
5117(or (assq 'cms ange-ftp-fix-name-func-alist)
5118 (setq ange-ftp-fix-name-func-alist
5119 (cons '(cms . ange-ftp-fix-name-for-cms)
5120 ange-ftp-fix-name-func-alist)))
2f7ea155
RS
5121
5122(or (memq 'cms ange-ftp-dumb-host-types)
5123 (setq ange-ftp-dumb-host-types
5124 (cons 'cms ange-ftp-dumb-host-types)))
5125
d0bc419e
RS
5126;; Convert name from UNIX-ish to CMS ready for a DIRectory listing.
5127(defun ange-ftp-fix-dir-name-for-cms (dir-name)
2f7ea155 5128 (cond
d0bc419e 5129 ((string-equal "/" dir-name)
2f7ea155 5130 (error "Cannot get listing for fictitious \"/\" directory."))
d0bc419e
RS
5131 ((string-match "^/\\([-A-Z0-9$*._]+\\)/\\([-A-Z0-9$._]+\\)?$" dir-name)
5132 (let* ((minidisk (substring dir-name (match-beginning 1) (match-end 1)))
2f7ea155 5133 ;; host and user are bound in the call to ange-ftp-send-cmd
d0bc419e 5134 (proc (ange-ftp-get-process ange-ftp-this-host ange-ftp-this-user))
2f7ea155
RS
5135 (cmd (concat "cd " minidisk))
5136 (file (if (match-beginning 2)
5137 ;; it's a single file
d0bc419e 5138 (substring dir-name (match-beginning 2)
2f7ea155
RS
5139 (match-end 2))
5140 ;; use the wild-card
5141 "*")))
5142 (if (car (ange-ftp-raw-send-cmd proc cmd))
5143 file
5144 ;; try again...
d0bc419e
RS
5145 (setq proc (ange-ftp-get-process ange-ftp-this-host
5146 ange-ftp-this-user))
2f7ea155
RS
5147 (let ((result (ange-ftp-raw-send-cmd proc cmd)))
5148 (if (car result)
5149 file
5150 ;; give up
d0bc419e 5151 (ange-ftp-error ange-ftp-this-host ange-ftp-this-user
2f7ea155
RS
5152 (format "cd to minidisk %s failed: "
5153 minidisk (cdr result))))))))
d0bc419e 5154 (t (error "Invalid CMS file name"))))
2f7ea155 5155
d0bc419e
RS
5156(or (assq 'cms ange-ftp-fix-dir-name-func-alist)
5157 (setq ange-ftp-fix-dir-name-func-alist
5158 (cons '(cms . ange-ftp-fix-dir-name-for-cms)
5159 ange-ftp-fix-dir-name-func-alist)))
2f7ea155
RS
5160
5161(defvar ange-ftp-cms-host-regexp nil
5162 "Regular expression to match hosts running the CMS operating system.")
5163
d0bc419e 5164;; Return non-nil if HOST is running CMS.
2f7ea155 5165(defun ange-ftp-cms-host (host)
2f7ea155
RS
5166 (and ange-ftp-cms-host-regexp
5167 (ange-ftp-save-match-data
5168 (string-match ange-ftp-cms-host-regexp host))))
5169
5170(defun ange-ftp-add-cms-host (host)
d0bc419e 5171 "Mark HOST as the name of a CMS host."
2f7ea155
RS
5172 (interactive
5173 (list (read-string "Host: "
68f5eb5a 5174 (let ((name (or (buffer-file-name) default-directory)))
d0bc419e 5175 (and name (car (ange-ftp-ftp-name name)))))))
2f7ea155
RS
5176 (if (not (ange-ftp-cms-host host))
5177 (setq ange-ftp-cms-host-regexp
5178 (concat "^" (regexp-quote host) "$"
5179 (and ange-ftp-cms-host-regexp "\\|")
5180 ange-ftp-cms-host-regexp)
5181 ange-ftp-host-cache nil)))
5182
5183(defun ange-ftp-parse-cms-listing ()
d0bc419e 5184 ;; Parse the current buffer which is assumed to be a CMS directory listing.
2f7ea155
RS
5185 ;; If we succeed in getting a listing, then we will assume that the minidisk
5186 ;; exists. file is bound by the call to ange-ftp-ls. This doesn't work
5187 ;; because ange-ftp doesn't know that the root hashtable has only part of
5188 ;; the info. It will assume that if a minidisk isn't in it, then it doesn't
5189 ;; exist. It would be nice if completion worked for minidisks, as we
5190 ;; discover them.
5191; (let* ((dir-file (directory-file-name file))
5192; (root (file-name-directory dir-file))
5193; (minidisk (ange-ftp-get-file-part dir-file))
5194; (root-tbl (ange-ftp-get-hash-entry root ange-ftp-files-hashtable)))
5195; (if root-tbl
5196; (ange-ftp-put-hash-entry minidisk t root-tbl)
5197; (setq root-tbl (ange-ftp-make-hashtable))
5198; (ange-ftp-put-hash-entry minidisk t root-tbl)
5199; (ange-ftp-put-hash-entry "." t root-tbl)
5200; (ange-ftp-set-files root root-tbl)))
5201 ;; Now do the usual parsing
5202 (let ((tbl (ange-ftp-make-hashtable)))
5203 (goto-char (point-min))
5204 (ange-ftp-save-match-data
5205 (while
5206 (re-search-forward
5207 "^\\([-A-Z0-9$_]+\\) +\\([-A-Z0-9$_]+\\) +[VF] +[0-9]+ " nil t)
5208 (ange-ftp-put-hash-entry
5209 (concat (buffer-substring (match-beginning 1)
5210 (match-end 1))
5211 "."
5212 (buffer-substring (match-beginning 2)
5213 (match-end 2)))
5214 nil tbl)
5215 (forward-line 1))
5216 (ange-ftp-put-hash-entry "." t tbl))
5217 tbl))
5218
5219(or (assq 'cms ange-ftp-parse-list-func-alist)
5220 (setq ange-ftp-parse-list-func-alist
5221 (cons '(cms . ange-ftp-parse-cms-listing)
5222 ange-ftp-parse-list-func-alist)))
5223
d0bc419e
RS
5224;;;;; Tree dired support:
5225
5226;;(defconst ange-ftp-dired-cms-re-exe
5227;; "^. [-A-Z0-9$_]+ +EXEC "
5228;; "Regular expression to use to search for CMS executables.")
5229
5230;;(or (assq 'cms ange-ftp-dired-re-exe-alist)
5231;; (setq ange-ftp-dired-re-exe-alist
5232;; (cons (cons 'cms ange-ftp-dired-cms-re-exe)
5233;; ange-ftp-dired-re-exe-alist)))
5234
5235
5236;;(defun ange-ftp-dired-cms-insert-headerline (dir)
5237;; ;; CMS has no total line, so we insert a blank line for
5238;; ;; aesthetics.
5239;; (insert "\n")
5240;; (forward-char -1)
5241;; (ange-ftp-real-dired-insert-headerline dir))
5242
5243;;(or (assq 'cms ange-ftp-dired-insert-headerline-alist)
5244;; (setq ange-ftp-dired-insert-headerline-alist
5245;; (cons '(cms . ange-ftp-dired-cms-insert-headerline)
5246;; ange-ftp-dired-insert-headerline-alist)))
5247
5248;;(defun ange-ftp-dired-cms-move-to-filename (&optional raise-error eol)
5249;; "In dired, move to the first char of filename on this line."
5250;; ;; This is the CMS version.
5251;; (or eol (setq eol (progn (end-of-line) (point))))
5252;; (let (case-fold-search)
5253;; (beginning-of-line)
5254;; (if (re-search-forward " [-A-Z0-9$_]+ +[-A-Z0-9$_]+ +[VF] +[0-9]+ " eol t)
5255;; (goto-char (1+ (match-beginning 0)))
5256;; (if raise-error
5257;; (error "No file on this line")
5258;; nil))))
5259
5260;;(or (assq 'cms ange-ftp-dired-move-to-filename-alist)
5261;; (setq ange-ftp-dired-move-to-filename-alist
5262;; (cons '(cms . ange-ftp-dired-cms-move-to-filename)
5263;; ange-ftp-dired-move-to-filename-alist)))
5264
5265;;(defun ange-ftp-dired-cms-move-to-end-of-filename (&optional no-error eol)
5266;; ;; Assumes point is at beginning of filename.
5267;; ;; So, it should be called only after (dired-move-to-filename t).
5268;; ;; case-fold-search must be nil, at least for VMS.
5269;; ;; On failure, signals an error or returns nil.
5270;; ;; This is the CMS version.
5271;; (let ((opoint (point))
5272;; case-fold-search hidden)
5273;; (or eol (setq eol (save-excursion (end-of-line) (point))))
5274;; (setq hidden (and selective-display
5275;; (save-excursion
5276;; (search-forward "\r" eol t))))
5277;; (if hidden
5278;; (if no-error
5279;; nil
5280;; (error
5281;; (substitute-command-keys
5282;; "File line is hidden, type \\[dired-hide-subdir] to unhide")))
5283;; (skip-chars-forward "-A-Z0-9$_" eol)
5284;; (skip-chars-forward " " eol)
5285;; (skip-chars-forward "-A-Z0-9$_" eol)
5286;; (if (eq opoint (point))
5287;; (if no-error
5288;; nil
5289;; (error "No file on this line"))
5290;; (point)))))
5291
5292;;(or (assq 'cms ange-ftp-dired-move-to-end-of-filename-alist)
5293;; (setq ange-ftp-dired-move-to-end-of-filename-alist
5294;; (cons '(cms . ange-ftp-dired-cms-move-to-end-of-filename)
5295;; ange-ftp-dired-move-to-end-of-filename-alist)))
2f7ea155
RS
5296
5297(defun ange-ftp-cms-make-compressed-filename (name &optional reverse)
d0bc419e
RS
5298 (if (string-match "-Z$" name)
5299 (list nil (substring name 0 -2))
5300 (list t (concat name "-Z"))))
5301
5302(or (assq 'cms ange-ftp-make-compressed-filename-alist)
5303 (setq ange-ftp-make-compressed-filename-alist
2f7ea155 5304 (cons '(cms . ange-ftp-cms-make-compressed-filename)
d0bc419e
RS
5305 ange-ftp-make-compressed-filename-alist)))
5306
5307;;(defun ange-ftp-dired-cms-get-filename (&optional localp no-error-if-not-filep)
5308;; (let ((name (ange-ftp-real-dired-get-filename localp no-error-if-not-filep)))
5309;; (and name
5310;; (if (string-match "^\\([^ ]+\\) +\\([^ ]+\\)$" name)
5311;; (concat (substring name 0 (match-end 1))
5312;; "."
5313;; (substring name (match-beginning 2) (match-end 2)))
5314;; name))))
5315
5316;;(or (assq 'cms ange-ftp-dired-get-filename-alist)
5317;; (setq ange-ftp-dired-get-filename-alist
5318;; (cons '(cms . ange-ftp-dired-cms-get-filename)
5319;; ange-ftp-dired-get-filename-alist)))
2f7ea155
RS
5320\f
5321;;;; ------------------------------------------------------------
5322;;;; Finally provide package.
5323;;;; ------------------------------------------------------------
5324
5325(provide 'ange-ftp)
c8472948
ER
5326
5327;;; ange-ftp.el ends here