Change release version from 21.4 to 22.1 throughout.
[bpt/emacs.git] / lisp / net / tramp-smb.el
CommitLineData
4007ba5b
KG
1;;; tramp-smb.el --- Tramp access functions for SMB servers -*- coding: iso-8859-1; -*-
2
5ec2cc41 3;; Copyright (C) 2002, 2003, 2004 Free Software Foundation, Inc.
4007ba5b
KG
4
5;; Author: Michael Albinus <Michael.Albinus@alcatel.de>
6;; Keywords: comm, processes
7
8;; This file is part of GNU Emacs.
9
10;; GNU Emacs is free software; you can redistribute it and/or modify
11;; it under the terms of the GNU General Public License as published by
12;; the Free Software Foundation; either version 2, or (at your option)
13;; any later version.
14
15;; GNU Emacs is distributed in the hope that it will be useful,
16;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18;; GNU General Public License for more details.
19
20;; You should have received a copy of the GNU General Public License
21;; along with GNU Emacs; see the file COPYING. If not, write to the
22;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23;; Boston, MA 02111-1307, USA.
24
25;;; Commentary:
26
27;; Access functions for SMB servers like SAMBA or M$ Windows from Tramp.
28
29;;; Code:
30
31(require 'tramp)
32
33;; Pacify byte-compiler
34(eval-when-compile
35 (require 'cl)
36 (require 'custom)
37 ;; Emacs 19.34 compatibility hack -- is this needed?
38 (or (>= emacs-major-version 20)
39 (load "cl-seq")))
40
38c65fca
KG
41;; Avoid byte-compiler warnings if the byte-compiler supports this.
42;; Currently, XEmacs supports this.
43(eval-when-compile
44 (when (fboundp 'byte-compiler-options)
45 (let (unused-vars) ; Pacify Emacs byte-compiler
46 (defalias 'warnings 'identity) ; Pacify Emacs byte-compiler
47 (byte-compiler-options (warnings (- unused-vars))))))
48
49;; XEmacs byte-compiler raises warning abouts `last-coding-system-used'.
50(eval-when-compile
51 (unless (boundp 'last-coding-system-used)
52 (defvar last-coding-system-used nil)))
53
4007ba5b
KG
54;; Define SMB method ...
55(defcustom tramp-smb-method "smb"
56 "*Method to connect SAMBA and M$ SMB servers."
57 :group 'tramp
58 :type 'string)
59
60;; ... and add it to the method list.
61(add-to-list 'tramp-methods (cons tramp-smb-method nil))
62
63;; Add a default for `tramp-default-method-alist'. Rule: If there is
64;; a domain in USER, it must be the SMB method.
65(add-to-list 'tramp-default-method-alist
5ec2cc41 66 (list "" "%" tramp-smb-method))
4007ba5b
KG
67
68;; Add completion function for SMB method.
69(tramp-set-completion-function
70 tramp-smb-method
71 '((tramp-parse-netrc "~/.netrc")))
72
73(defcustom tramp-smb-program "smbclient"
74 "*Name of SMB client to run."
75 :group 'tramp
76 :type 'string)
77
5ec2cc41 78(defconst tramp-smb-prompt "^smb: \\S-+> \\|^\\s-+Server\\s-+Comment$"
4007ba5b
KG
79 "Regexp used as prompt in smbclient.")
80
81(defconst tramp-smb-errors
82 (mapconcat
83 'identity
84 '(; Connection error
85 "Connection to \\S-+ failed"
86 ; Samba
4007ba5b 87 "ERRDOS"
5ec2cc41 88 "ERRSRV"
4007ba5b
KG
89 "ERRbadfile"
90 "ERRbadpw"
91 "ERRfilexists"
92 "ERRnoaccess"
93 "ERRnomem"
94 "ERRnosuchshare"
95 ; Windows NT 4.0, Windows 5.0 (Windows 2000), Windows 5.1 (Windows XP)
96 "NT_STATUS_ACCESS_DENIED"
5ec2cc41 97 "NT_STATUS_ACCOUNT_LOCKED_OUT"
4007ba5b
KG
98 "NT_STATUS_BAD_NETWORK_NAME"
99 "NT_STATUS_CANNOT_DELETE"
100 "NT_STATUS_LOGON_FAILURE"
5ec2cc41 101 "NT_STATUS_NETWORK_ACCESS_DENIED"
4007ba5b
KG
102 "NT_STATUS_NO_SUCH_FILE"
103 "NT_STATUS_OBJECT_NAME_INVALID"
104 "NT_STATUS_OBJECT_NAME_NOT_FOUND"
5ec2cc41
KG
105 "NT_STATUS_SHARING_VIOLATION"
106 "NT_STATUS_WRONG_PASSWORD")
4007ba5b
KG
107 "\\|")
108 "Regexp for possible error strings of SMB servers.
109Used instead of analyzing error codes of commands.")
110
111(defvar tramp-smb-share nil
112 "Holds the share name for the current buffer.
113This variable is local to each buffer.")
114(make-variable-buffer-local 'tramp-smb-share)
115
116(defvar tramp-smb-share-cache nil
117 "Caches the share names accessible to host related to the current buffer.
118This variable is local to each buffer.")
119(make-variable-buffer-local 'tramp-smb-share-cache)
120
8daea7fc
KG
121(defvar tramp-smb-inodes nil
122 "Keeps virtual inodes numbers for SMB files.")
123
4007ba5b
KG
124;; New handlers should be added here.
125(defconst tramp-smb-file-name-handler-alist
126 '(
127 ;; `access-file' performed by default handler
128 (add-name-to-file . tramp-smb-handle-copy-file) ;; we're on Windows, honey.
129 ;; `byte-compiler-base-file-name' performed by default handler
130 (copy-file . tramp-smb-handle-copy-file)
131 (delete-directory . tramp-smb-handle-delete-directory)
132 (delete-file . tramp-smb-handle-delete-file)
133 ;; `diff-latest-backup-file' performed by default handler
8daea7fc 134 (directory-file-name . tramp-handle-directory-file-name)
4007ba5b
KG
135 (directory-files . tramp-smb-handle-directory-files)
136 (directory-files-and-attributes . tramp-smb-handle-directory-files-and-attributes)
137 (dired-call-process . tramp-smb-not-handled)
138 (dired-compress-file . tramp-smb-not-handled)
139 ;; `dired-uncache' performed by default handler
140 ;; `expand-file-name' not necessary because we cannot expand "~/"
141 (file-accessible-directory-p . tramp-smb-handle-file-directory-p)
142 (file-attributes . tramp-smb-handle-file-attributes)
143 (file-directory-p . tramp-smb-handle-file-directory-p)
144 (file-executable-p . tramp-smb-handle-file-exists-p)
145 (file-exists-p . tramp-smb-handle-file-exists-p)
146 (file-local-copy . tramp-smb-handle-file-local-copy)
19a87064 147 (file-remote-p . tramp-handle-file-remote-p)
4007ba5b
KG
148 (file-modes . tramp-handle-file-modes)
149 (file-name-all-completions . tramp-smb-handle-file-name-all-completions)
150 ;; `file-name-as-directory' performed by default handler
151 (file-name-completion . tramp-handle-file-name-completion)
152 (file-name-directory . tramp-handle-file-name-directory)
153 (file-name-nondirectory . tramp-handle-file-name-nondirectory)
154 ;; `file-name-sans-versions' performed by default handler
155 (file-newer-than-file-p . tramp-smb-handle-file-newer-than-file-p)
156 (file-ownership-preserved-p . tramp-smb-not-handled)
157 (file-readable-p . tramp-smb-handle-file-exists-p)
158 (file-regular-p . tramp-handle-file-regular-p)
159 (file-symlink-p . tramp-smb-not-handled)
160 ;; `file-truename' performed by default handler
161 (file-writable-p . tramp-smb-handle-file-writable-p)
38c65fca 162 (find-backup-file-name . tramp-handle-find-backup-file-name)
4007ba5b
KG
163 ;; `find-file-noselect' performed by default handler
164 ;; `get-file-buffer' performed by default handler
165 (insert-directory . tramp-smb-handle-insert-directory)
166 (insert-file-contents . tramp-handle-insert-file-contents)
167 (load . tramp-handle-load)
168 (make-directory . tramp-smb-handle-make-directory)
169 (make-directory-internal . tramp-smb-handle-make-directory-internal)
170 (make-symbolic-link . tramp-smb-not-handled)
171 (rename-file . tramp-smb-handle-rename-file)
172 (set-file-modes . tramp-smb-not-handled)
173 (set-visited-file-modtime . tramp-smb-not-handled)
174 (shell-command . tramp-smb-not-handled)
01917a18 175 (substitute-in-file-name . tramp-smb-handle-substitute-in-file-name)
4007ba5b
KG
176 (unhandled-file-name-directory . tramp-handle-unhandled-file-name-directory)
177 (vc-registered . tramp-smb-not-handled)
178 (verify-visited-file-modtime . tramp-smb-not-handled)
179 (write-region . tramp-smb-handle-write-region)
180)
181 "Alist of handler functions for Tramp SMB method.
182Operations not mentioned here will be handled by the default Emacs primitives.")
183
184(defun tramp-smb-file-name-p (filename)
185 "Check if it's a filename for SMB servers."
186 (let ((v (tramp-dissect-file-name filename)))
187 (string=
188 (tramp-find-method
189 (tramp-file-name-multi-method v)
190 (tramp-file-name-method v)
191 (tramp-file-name-user v)
192 (tramp-file-name-host v))
193 tramp-smb-method)))
194
195(defun tramp-smb-file-name-handler (operation &rest args)
196 "Invoke the SMB related OPERATION.
197First arg specifies the OPERATION, second arg is a list of arguments to
198pass to the OPERATION."
199 (let ((fn (assoc operation tramp-smb-file-name-handler-alist)))
200 (if fn
201 (if (eq (cdr fn) 'tramp-smb-not-handled)
202 (apply (cdr fn) operation args)
203 (save-match-data (apply (cdr fn) args)))
204 (tramp-run-real-handler operation args))))
205
206(add-to-list 'tramp-foreign-file-name-handler-alist
207 (cons 'tramp-smb-file-name-p 'tramp-smb-file-name-handler))
208
209
210;; File name primitives
211
212(defun tramp-smb-not-handled (operation &rest args)
213 "Default handler for all functions which are disrecarded."
214 (tramp-message 10 "Won't be handled: %s %s" operation args)
215 nil)
216
217(defun tramp-smb-handle-copy-file
218 (filename newname &optional ok-if-already-exists keep-date)
219 "Like `copy-file' for tramp files.
220KEEP-DATE is not handled in case NEWNAME resides on an SMB server."
221 (setq filename (expand-file-name filename)
222 newname (expand-file-name newname))
223
224 (let ((tmpfile (file-local-copy filename)))
225
226 (if tmpfile
227 ;; remote filename
228 (rename-file tmpfile newname ok-if-already-exists)
229
230 ;; remote newname
231 (when (file-directory-p newname)
232 (setq newname (expand-file-name
233 (file-name-nondirectory filename) newname)))
234 (when (and (not ok-if-already-exists)
235 (file-exists-p newname))
236 (error "copy-file: file %s already exists" newname))
237
238; (with-parsed-tramp-file-name newname nil
7432277c 239 (let (user host localname)
4007ba5b 240 (with-parsed-tramp-file-name newname l
7432277c 241 (setq user l-user host l-host localname l-localname))
4007ba5b 242 (save-excursion
7432277c
KG
243 (let ((share (tramp-smb-get-share localname))
244 (file (tramp-smb-get-localname localname t)))
4007ba5b
KG
245 (unless share
246 (error "Target `%s' must contain a share name" filename))
247 (tramp-smb-maybe-open-connection user host share)
248 (tramp-message-for-buffer
249 nil tramp-smb-method user host
250 5 "Copying file %s to file %s..." filename newname)
251 (if (tramp-smb-send-command
252 user host (format "put %s \"%s\"" filename file))
253 (tramp-message-for-buffer
254 nil tramp-smb-method user host
255 5 "Copying file %s to file %s...done" filename newname)
256 (error "Cannot copy `%s'" filename))))))))
257
258(defun tramp-smb-handle-delete-directory (directory)
259 "Like `delete-directory' for tramp files."
260 (setq directory (directory-file-name (expand-file-name directory)))
261 (unless (file-exists-p directory)
262 (error "Cannot delete non-existing directory `%s'" directory))
263; (with-parsed-tramp-file-name directory nil
7432277c 264 (let (user host localname)
4007ba5b 265 (with-parsed-tramp-file-name directory l
7432277c 266 (setq user l-user host l-host localname l-localname))
4007ba5b 267 (save-excursion
7432277c
KG
268 (let ((share (tramp-smb-get-share localname))
269 (dir (tramp-smb-get-localname (file-name-directory localname) t))
270 (file (file-name-nondirectory localname)))
4007ba5b
KG
271 (tramp-smb-maybe-open-connection user host share)
272 (if (and
273 (tramp-smb-send-command user host (format "cd \"%s\"" dir))
274 (tramp-smb-send-command user host (format "rmdir \"%s\"" file)))
275 ;; Go Home
276 (tramp-smb-send-command user host (format "cd \\"))
277 ;; Error
278 (tramp-smb-send-command user host (format "cd \\"))
279 (error "Cannot delete directory `%s'" directory))))))
280
281(defun tramp-smb-handle-delete-file (filename)
282 "Like `delete-file' for tramp files."
283 (setq filename (expand-file-name filename))
284 (unless (file-exists-p filename)
285 (error "Cannot delete non-existing file `%s'" filename))
286; (with-parsed-tramp-file-name filename nil
7432277c 287 (let (user host localname)
4007ba5b 288 (with-parsed-tramp-file-name filename l
7432277c 289 (setq user l-user host l-host localname l-localname))
4007ba5b 290 (save-excursion
7432277c
KG
291 (let ((share (tramp-smb-get-share localname))
292 (dir (tramp-smb-get-localname (file-name-directory localname) t))
293 (file (file-name-nondirectory localname)))
4007ba5b
KG
294 (unless (file-exists-p filename)
295 (error "Cannot delete non-existing file `%s'" filename))
296 (tramp-smb-maybe-open-connection user host share)
297 (if (and
298 (tramp-smb-send-command user host (format "cd \"%s\"" dir))
299 (tramp-smb-send-command user host (format "rm \"%s\"" file)))
300 ;; Go Home
301 (tramp-smb-send-command user host (format "cd \\"))
302 ;; Error
303 (tramp-smb-send-command user host (format "cd \\"))
c951aecb 304 (error "Cannot delete file `%s'" filename))))))
4007ba5b
KG
305
306(defun tramp-smb-handle-directory-files
307 (directory &optional full match nosort)
308 "Like `directory-files' for tramp files."
309 (setq directory (directory-file-name (expand-file-name directory)))
310; (with-parsed-tramp-file-name directory nil
7432277c 311 (let (user host localname)
4007ba5b 312 (with-parsed-tramp-file-name directory l
7432277c 313 (setq user l-user host l-host localname l-localname))
4007ba5b 314 (save-excursion
7432277c
KG
315 (let* ((share (tramp-smb-get-share localname))
316 (file (tramp-smb-get-localname localname nil))
4007ba5b
KG
317 (entries (tramp-smb-get-file-entries user host share file)))
318 ;; Just the file names are needed
319 (setq entries (mapcar 'car entries))
320 ;; Discriminate with regexp
321 (when match
322 (setq entries
323 (delete nil
324 (mapcar (lambda (x) (when (string-match match x) x))
325 entries))))
7432277c 326 ;; Make absolute localnames if necessary
4007ba5b
KG
327 (when full
328 (setq entries
329 (mapcar (lambda (x)
330 (concat (file-name-as-directory directory) x))
331 entries)))
332 ;; Sort them if necessary
333 (unless nosort (setq entries (sort entries 'string-lessp)))
334 ;; That's it
335 entries))))
336
337(defun tramp-smb-handle-directory-files-and-attributes
c951aecb 338 (directory &optional full match nosort id-format)
4007ba5b
KG
339 "Like `directory-files-and-attributes' for tramp files."
340 (mapcar
341 (lambda (x)
c951aecb 342 ;; We cannot call `file-attributes' for backward compatibility reasons.
bf247b6e 343 ;; Its optional parameter ID-FORMAT is introduced with Emacs 22.1.
c951aecb
KG
344 (cons x (tramp-smb-handle-file-attributes
345 (if full x (concat (file-name-as-directory directory) x)) id-format)))
4007ba5b 346 (directory-files directory full match nosort)))
bf247b6e 347
c951aecb
KG
348(defun tramp-smb-handle-file-attributes (filename &optional id-format)
349 "Like `file-attributes' for tramp files."
4007ba5b 350; (with-parsed-tramp-file-name filename nil
7432277c 351 (let (user host localname)
4007ba5b 352 (with-parsed-tramp-file-name filename l
7432277c 353 (setq user l-user host l-host localname l-localname))
4007ba5b 354 (save-excursion
7432277c
KG
355 (let* ((share (tramp-smb-get-share localname))
356 (file (tramp-smb-get-localname localname nil))
4007ba5b
KG
357 (entries (tramp-smb-get-file-entries user host share file))
358 (entry (and entries
8daea7fc 359 (assoc (file-name-nondirectory file) entries)))
c951aecb
KG
360 (uid (if (and id-format (equal id-format 'string)) "nobody" -1))
361 (gid (if (and id-format (equal id-format 'string)) "nogroup" -1))
8daea7fc
KG
362 (inode (tramp-smb-get-inode share file))
363 (device (tramp-get-device nil tramp-smb-method user host)))
364
4007ba5b
KG
365 ; check result
366 (when entry
367 (list (and (string-match "d" (nth 1 entry))
368 t) ;0 file type
369 -1 ;1 link count
c951aecb
KG
370 uid ;2 uid
371 gid ;3 gid
8daea7fc 372 '(0 0) ;4 atime
4007ba5b 373 (nth 3 entry) ;5 mtime
8daea7fc 374 '(0 0) ;6 ctime
4007ba5b
KG
375 (nth 2 entry) ;7 size
376 (nth 1 entry) ;8 mode
377 nil ;9 gid weird
8daea7fc
KG
378 inode ;10 inode number
379 device)))))) ;11 file system number
4007ba5b
KG
380
381(defun tramp-smb-handle-file-directory-p (filename)
382 "Like `file-directory-p' for tramp files."
383; (with-parsed-tramp-file-name filename nil
7432277c 384 (let (user host localname)
4007ba5b 385 (with-parsed-tramp-file-name filename l
7432277c 386 (setq user l-user host l-host localname l-localname))
4007ba5b 387 (save-excursion
7432277c
KG
388 (let* ((share (tramp-smb-get-share localname))
389 (file (tramp-smb-get-localname localname nil))
4007ba5b
KG
390 (entries (tramp-smb-get-file-entries user host share file))
391 (entry (and entries
392 (assoc (file-name-nondirectory file) entries))))
393 (and entry
394 (string-match "d" (nth 1 entry))
395 t)))))
396
397(defun tramp-smb-handle-file-exists-p (filename)
398 "Like `file-exists-p' for tramp files."
399; (with-parsed-tramp-file-name filename nil
7432277c 400 (let (user host localname)
4007ba5b 401 (with-parsed-tramp-file-name filename l
7432277c 402 (setq user l-user host l-host localname l-localname))
4007ba5b 403 (save-excursion
7432277c
KG
404 (let* ((share (tramp-smb-get-share localname))
405 (file (tramp-smb-get-localname localname nil))
4007ba5b
KG
406 (entries (tramp-smb-get-file-entries user host share file)))
407 (and entries
408 (member (file-name-nondirectory file) (mapcar 'car entries))
409 t)))))
410
411(defun tramp-smb-handle-file-local-copy (filename)
412 "Like `file-local-copy' for tramp files."
413 (with-parsed-tramp-file-name filename nil
414 (save-excursion
7432277c
KG
415 (let ((share (tramp-smb-get-share localname))
416 (file (tramp-smb-get-localname localname t))
4007ba5b
KG
417 (tmpfil (tramp-make-temp-file)))
418 (unless (file-exists-p filename)
419 (error "Cannot make local copy of non-existing file `%s'" filename))
420 (tramp-message-for-buffer
421 nil tramp-smb-method user host
422 5 "Fetching %s to tmp file %s..." filename tmpfil)
423 (tramp-smb-maybe-open-connection user host share)
424 (if (tramp-smb-send-command
425 user host (format "get \"%s\" %s" file tmpfil))
426 (tramp-message-for-buffer
427 nil tramp-smb-method user host
428 5 "Fetching %s to tmp file %s...done" filename tmpfil)
429 (error "Cannot make local copy of file `%s'" filename))
430 tmpfil))))
431
432;; This function should return "foo/" for directories and "bar" for
433;; files.
434(defun tramp-smb-handle-file-name-all-completions (filename directory)
435 "Like `file-name-all-completions' for tramp files."
436; (with-parsed-tramp-file-name directory nil
7432277c 437 (let (user host localname)
4007ba5b 438 (with-parsed-tramp-file-name directory l
7432277c 439 (setq user l-user host l-host localname l-localname))
4007ba5b
KG
440 (save-match-data
441 (save-excursion
7432277c
KG
442 (let* ((share (tramp-smb-get-share localname))
443 (file (tramp-smb-get-localname localname nil))
4007ba5b
KG
444 (entries (tramp-smb-get-file-entries user host share file)))
445
446 (all-completions
447 filename
448 (mapcar
449 (lambda (x)
450 (list
451 (if (string-match "d" (nth 1 x))
452 (file-name-as-directory (nth 0 x))
453 (nth 0 x))))
454 entries)))))))
455
456(defun tramp-smb-handle-file-newer-than-file-p (file1 file2)
457 "Like `file-newer-than-file-p' for tramp files."
458 (cond
459 ((not (file-exists-p file1)) nil)
460 ((not (file-exists-p file2)) t)
461 (t (tramp-smb-time-less-p (file-attributes file2)
462 (file-attributes file1)))))
463
464(defun tramp-smb-handle-file-writable-p (filename)
465 "Like `file-writable-p' for tramp files."
5ec2cc41
KG
466 (if (not (file-exists-p filename))
467 (let ((dir (file-name-directory filename)))
468 (and (file-exists-p dir)
469 (file-writable-p dir)))
470; (with-parsed-tramp-file-name filename nil
471 (let (user host localname)
472 (with-parsed-tramp-file-name filename l
473 (setq user l-user host l-host localname l-localname))
474 (save-excursion
475 (let* ((share (tramp-smb-get-share localname))
476 (file (tramp-smb-get-localname localname nil))
477 (entries (tramp-smb-get-file-entries user host share file))
478 (entry (and entries
479 (assoc (file-name-nondirectory file) entries))))
480 (and share entry
481 (string-match "w" (nth 1 entry))
482 t))))))
4007ba5b
KG
483
484(defun tramp-smb-handle-insert-directory
485 (filename switches &optional wildcard full-directory-p)
486 "Like `insert-directory' for tramp files.
487WILDCARD and FULL-DIRECTORY-P are not handled."
488 (setq filename (expand-file-name filename))
489 (when (file-directory-p filename)
490 ;; This check is a little bit strange, but in `dired-add-entry'
491 ;; this function is called with a non-directory ...
492 (setq filename (file-name-as-directory filename)))
493; (with-parsed-tramp-file-name filename nil
7432277c 494 (let (user host localname)
4007ba5b 495 (with-parsed-tramp-file-name filename l
7432277c 496 (setq user l-user host l-host localname l-localname))
4007ba5b 497 (save-match-data
7432277c
KG
498 (let* ((share (tramp-smb-get-share localname))
499 (file (tramp-smb-get-localname localname nil))
4007ba5b
KG
500 (entries (tramp-smb-get-file-entries user host share file)))
501
502 ;; Delete dummy "" entry, useless entries
bf247b6e 503 (setq entries
4007ba5b
KG
504 (if (file-directory-p filename)
505 (delq (assoc "" entries) entries)
506 ;; We just need the only and only entry FILENAME.
507 (list (assoc (file-name-nondirectory filename) entries))))
508
509 ;; Sort entries
510 (setq entries
511 (sort
512 entries
513 (lambda (x y)
514 (if (string-match "t" switches)
515 ; sort by date
516 (tramp-smb-time-less-p (nth 3 y) (nth 3 x))
517 ; sort by name
518 (string-lessp (nth 0 x) (nth 0 y))))))
519
520 ;; Print entries
521 (mapcar
522 (lambda (x)
523 (insert
524 (format
525 "%10s %3d %-8s %-8s %8s %s %s\n"
526 (nth 1 x) ; mode
527 1 "nobody" "nogroup"
528 (nth 2 x) ; size
529 (format-time-string
530 (if (tramp-smb-time-less-p
531 (tramp-smb-time-subtract (current-time) (nth 3 x))
532 tramp-smb-half-a-year)
533 "%b %e %R"
534 "%b %e %Y")
535 (nth 3 x)) ; date
536 (nth 0 x))) ; file name
537 (forward-line)
538 (beginning-of-line))
539 entries)))))
540
541(defun tramp-smb-handle-make-directory (dir &optional parents)
542 "Like `make-directory' for tramp files."
543 (setq dir (directory-file-name (expand-file-name dir)))
544 (unless (file-name-absolute-p dir)
545 (setq dir (concat default-directory dir)))
546; (with-parsed-tramp-file-name dir nil
7432277c 547 (let (user host localname)
4007ba5b 548 (with-parsed-tramp-file-name dir l
7432277c 549 (setq user l-user host l-host localname l-localname))
4007ba5b 550 (save-match-data
7432277c 551 (let* ((share (tramp-smb-get-share localname))
4007ba5b
KG
552 (ldir (file-name-directory dir)))
553 ;; Make missing directory parts
554 (when (and parents share (not (file-directory-p ldir)))
555 (make-directory ldir parents))
556 ;; Just do it
557 (when (file-directory-p ldir)
8daea7fc 558 (make-directory-internal dir))
4007ba5b
KG
559 (unless (file-directory-p dir)
560 (error "Couldn't make directory %s" dir))))))
561
562(defun tramp-smb-handle-make-directory-internal (directory)
563 "Like `make-directory-internal' for tramp files."
564 (setq directory (directory-file-name (expand-file-name directory)))
565 (unless (file-name-absolute-p directory)
c951aecb 566 (setq directory (concat default-directory directory)))
4007ba5b 567; (with-parsed-tramp-file-name directory nil
7432277c 568 (let (user host localname)
4007ba5b 569 (with-parsed-tramp-file-name directory l
7432277c 570 (setq user l-user host l-host localname l-localname))
4007ba5b 571 (save-match-data
7432277c
KG
572 (let* ((share (tramp-smb-get-share localname))
573 (file (tramp-smb-get-localname localname nil)))
4007ba5b
KG
574 (when (file-directory-p (file-name-directory directory))
575 (tramp-smb-maybe-open-connection user host share)
576 (tramp-smb-send-command user host (format "mkdir \"%s\"" file)))
577 (unless (file-directory-p directory)
578 (error "Couldn't make directory %s" directory))))))
579
580(defun tramp-smb-handle-rename-file
581 (filename newname &optional ok-if-already-exists)
582 "Like `rename-file' for tramp files."
583 (setq filename (expand-file-name filename)
584 newname (expand-file-name newname))
585
586 (let ((tmpfile (file-local-copy filename)))
587
588 (if tmpfile
589 ;; remote filename
590 (rename-file tmpfile newname ok-if-already-exists)
591
592 ;; remote newname
593 (when (file-directory-p newname)
594 (setq newname (expand-file-name
595 (file-name-nondirectory filename) newname)))
596 (when (and (not ok-if-already-exists)
597 (file-exists-p newname))
598 (error "rename-file: file %s already exists" newname))
599
600; (with-parsed-tramp-file-name newname nil
7432277c 601 (let (user host localname)
4007ba5b 602 (with-parsed-tramp-file-name newname l
7432277c 603 (setq user l-user host l-host localname l-localname))
4007ba5b 604 (save-excursion
7432277c
KG
605 (let ((share (tramp-smb-get-share localname))
606 (file (tramp-smb-get-localname localname t)))
4007ba5b
KG
607 (tramp-smb-maybe-open-connection user host share)
608 (tramp-message-for-buffer
609 nil tramp-smb-method user host
610 5 "Copying file %s to file %s..." filename newname)
611 (if (tramp-smb-send-command
612 user host (format "put %s \"%s\"" filename file))
613 (tramp-message-for-buffer
614 nil tramp-smb-method user host
615 5 "Copying file %s to file %s...done" filename newname)
616 (error "Cannot rename `%s'" filename)))))))
617
618 (delete-file filename))
619
01917a18
MA
620(defun tramp-smb-handle-substitute-in-file-name (filename)
621 "Like `handle-substitute-in-file-name' for tramp files.
622Catches errors for shares like \"C$/\", which are common in Microsoft Windows."
623 (condition-case nil
624 (tramp-run-real-handler 'substitute-in-file-name (list filename))
625 (error filename)))
626
4007ba5b
KG
627(defun tramp-smb-handle-write-region
628 (start end filename &optional append visit lockname confirm)
629 "Like `write-region' for tramp files."
630 (unless (eq append nil)
631 (error "Cannot append to file using tramp (`%s')" filename))
632 (setq filename (expand-file-name filename))
633 ;; XEmacs takes a coding system as the seventh argument, not `confirm'
634 (when (and (not (featurep 'xemacs))
635 confirm (file-exists-p filename))
636 (unless (y-or-n-p (format "File %s exists; overwrite anyway? "
637 filename))
638 (error "File not overwritten")))
639; (with-parsed-tramp-file-name filename nil
7432277c 640 (let (user host localname)
4007ba5b 641 (with-parsed-tramp-file-name filename l
7432277c 642 (setq user l-user host l-host localname l-localname))
4007ba5b 643 (save-excursion
7432277c
KG
644 (let ((share (tramp-smb-get-share localname))
645 (file (tramp-smb-get-localname localname t))
4007ba5b
KG
646 (curbuf (current-buffer))
647 ;; We use this to save the value of `last-coding-system-used'
648 ;; after writing the tmp file. At the end of the function,
649 ;; we set `last-coding-system-used' to this saved value.
650 ;; This way, any intermediary coding systems used while
651 ;; talking to the remote shell or suchlike won't hose this
652 ;; variable. This approach was snarfed from ange-ftp.el.
653 coding-system-used
654 tmpfil)
655 ;; Write region into a tmp file.
656 (setq tmpfil (tramp-make-temp-file))
657 ;; We say `no-message' here because we don't want the visited file
658 ;; modtime data to be clobbered from the temp file. We call
659 ;; `set-visited-file-modtime' ourselves later on.
660 (tramp-run-real-handler
661 'write-region
662 (if confirm ; don't pass this arg unless defined for backward compat.
663 (list start end tmpfil append 'no-message lockname confirm)
664 (list start end tmpfil append 'no-message lockname)))
665 ;; Now, `last-coding-system-used' has the right value. Remember it.
666 (when (boundp 'last-coding-system-used)
667 (setq coding-system-used last-coding-system-used))
668
669 (tramp-smb-maybe-open-connection user host share)
670 (tramp-message-for-buffer
671 nil tramp-smb-method user host
672 5 "Writing tmp file %s to file %s..." tmpfil filename)
673 (if (tramp-smb-send-command
674 user host (format "put %s \"%s\"" tmpfil file))
675 (tramp-message-for-buffer
676 nil tramp-smb-method user host
677 5 "Writing tmp file %s to file %s...done" tmpfil filename)
678 (error "Cannot write `%s'" filename))
679
680 (delete-file tmpfil)
681 (unless (equal curbuf (current-buffer))
682 (error "Buffer has changed from `%s' to `%s'"
683 curbuf (current-buffer)))
684 (when (eq visit t)
685 (set-visited-file-modtime))
686 ;; Make `last-coding-system-used' have the right value.
687 (when (boundp 'last-coding-system-used)
688 (setq last-coding-system-used coding-system-used))))))
689
690
691;; Internal file name functions
692
7432277c
KG
693(defun tramp-smb-get-share (localname)
694 "Returns the share name of LOCALNAME."
4007ba5b 695 (save-match-data
7432277c
KG
696 (when (string-match "^/?\\([^/]+\\)/" localname)
697 (match-string 1 localname))))
4007ba5b 698
7432277c
KG
699(defun tramp-smb-get-localname (localname convert)
700 "Returns the file name of LOCALNAME.
4007ba5b
KG
701If CONVERT is non-nil exchange \"/\" by \"\\\\\"."
702 (save-match-data
7432277c 703 (let ((res localname))
4007ba5b
KG
704
705 (setq
706 res (if (string-match "^/?[^/]+/\\(.*\\)" res)
707 (if convert
708 (mapconcat
709 (lambda (x) (if (equal x ?/) "\\" (char-to-string x)))
710 (match-string 1 res) "")
711 (match-string 1 res))
712 (if (string-match "^/?\\([^/]+\\)$" res)
713 (match-string 1 res)
714 "")))
715
716 ;; Sometimes we have discarded `substitute-in-file-name'
717 (when (string-match "\\(\\$\\$\\)\\(/\\|$\\)" res)
718 (setq res (replace-match "$" nil nil res 1)))
719
720 res)))
721
722;; Share names of a host are cached. It is very unlikely that the
723;; shares do change during connection.
7432277c
KG
724(defun tramp-smb-get-file-entries (user host share localname)
725 "Read entries which match LOCALNAME.
4007ba5b 726Either the shares are listed, or the `dir' command is executed.
7432277c
KG
727Only entries matching the localname are returned.
728Result is a list of (LOCALNAME MODE SIZE MONTH DAY TIME YEAR)."
4007ba5b
KG
729 (save-excursion
730 (save-match-data
7432277c
KG
731 (let ((base (or (and (> (length localname) 0)
732 (string-match "\\([^/]+\\)$" localname)
733 (regexp-quote (match-string 1 localname)))
4007ba5b
KG
734 ""))
735 res entry)
736 (set-buffer (tramp-get-buffer nil tramp-smb-method user host))
737 (if (and (not share) tramp-smb-share-cache)
738 ;; Return cached shares
739 (setq res tramp-smb-share-cache)
740 ;; Read entries
741 (tramp-smb-maybe-open-connection user host share)
742 (when share
743 (tramp-smb-send-command
744 user host
745 (format "dir %s"
7432277c 746 (if (zerop (length localname)) "" (concat "\"" localname "*\"")))))
4007ba5b
KG
747 (goto-char (point-min))
748 ;; Loop the listing
749 (unless (re-search-forward tramp-smb-errors nil t)
750 (while (not (eobp))
751 (setq entry (tramp-smb-read-file-entry share))
752 (forward-line)
753 (when entry (add-to-list 'res entry))))
754 (unless share
755 ;; Cache share entries
756 (setq tramp-smb-share-cache res)))
757
4007ba5b 758 ;; Add directory itself
5ec2cc41
KG
759 (add-to-list 'res '("" "drwxrwxrwx" 0 (0 0)))
760
761 ;; There's a very strange error (debugged with XEmacs 21.4.14)
762 ;; If there's no short delay, it returns nil. No idea about
763 (when (featurep 'xemacs) (sleep-for 0.01))
4007ba5b
KG
764
765 ;; Check for matching entries
766 (delq nil (mapcar
767 (lambda (x) (and (string-match base (nth 0 x)) x))
768 res))))))
769
770;; Return either a share name (if SHARE is nil), or a file name
771;;
772;; If shares are listed, the following format is expected
773;;
774;; \s-\{8,8} - leading spaces
775;; \S-\(.*\S-\)\s-* - share name, 14 char
776;; \s- - space delimeter
777;; \S-+\s-* - type, 8 char, "Disk " expected
778;; \(\s-\{2,2\}.*\)? - space delimeter, comment
779;;
780;; Entries provided by smbclient DIR aren't fully regular.
781;; They should have the format
782;;
783;; \s-\{2,2} - leading spaces
b1a2b924
KG
784;; \S-\(.*\S-\)\s-* - file name, 30 chars, left bound
785;; \s-+[ADHRSV]* - permissions, 7 chars, right bound
4007ba5b 786;; \s- - space delimeter
b1a2b924 787;; \s-+[0-9]+ - size, 8 chars, right bound
4007ba5b
KG
788;; \s-\{2,2\} - space delimeter
789;; \w\{3,3\} - weekday
790;; \s- - space delimeter
b1a2b924
KG
791;; \w\{3,3\} - month
792;; \s- - space delimeter
4007ba5b
KG
793;; [ 19][0-9] - day
794;; \s- - space delimeter
795;; [0-9]\{2,2\}:[0-9]\{2,2\}:[0-9]\{2,2\} - time
796;; \s- - space delimeter
797;; [0-9]\{4,4\} - year
798;;
b1a2b924
KG
799;; samba/src/client.c (http://samba.org/doxygen/samba/client_8c-source.html)
800;; has function display_finfo:
801;;
802;; d_printf(" %-30s%7.7s %8.0f %s",
803;; finfo->name,
804;; attrib_string(finfo->mode),
805;; (double)finfo->size,
806;; asctime(LocalTime(&t)));
807;;
808;; in Samba 1.9, there's the following code:
809;;
810;; DEBUG(0,(" %-30s%7.7s%10d %s",
811;; CNV_LANG(finfo->name),
812;; attrib_string(finfo->mode),
813;; finfo->size,
814;; asctime(LocalTime(&t))));
815;;
4007ba5b
KG
816;; Problems:
817;; * Modern regexp constructs, like spy groups and counted repetitions, aren't
818;; available in older Emacsen.
819;; * The length of constructs (file name, size) might exceed the default.
820;; * File names might contain spaces.
821;; * Permissions might be empty.
822;;
823;; So we try to analyze backwards.
824(defun tramp-smb-read-file-entry (share)
825 "Parse entry in SMB output buffer.
826If SHARE is result, entries are of type dir. Otherwise, shares are listed.
7432277c 827Result is the list (LOCALNAME MODE SIZE MTIME)."
4007ba5b 828 (let ((line (buffer-substring (point) (tramp-point-at-eol)))
7432277c 829 localname mode size month day hour min sec year mtime)
4007ba5b
KG
830
831 (if (not share)
832
833 ; Read share entries
834 (when (string-match "^\\s-+\\(\\S-+\\)\\s-+Disk" line)
7432277c 835 (setq localname (match-string 1 line)
4007ba5b
KG
836 mode "dr-xr-xr-x"
837 size 0))
838
839 ; Real listing
840 (block nil
841
842 ;; year
843 (if (string-match "\\([0-9]+\\)$" line)
844 (setq year (string-to-number (match-string 1 line))
845 line (substring line 0 -5))
846 (return))
847
848 ;; time
849 (if (string-match "\\([0-9]+\\):\\([0-9]+\\):\\([0-9]+\\)$" line)
850 (setq hour (string-to-number (match-string 1 line))
851 min (string-to-number (match-string 2 line))
852 sec (string-to-number (match-string 3 line))
853 line (substring line 0 -9))
854 (return))
855
856 ;; day
857 (if (string-match "\\([0-9]+\\)$" line)
858 (setq day (string-to-number (match-string 1 line))
859 line (substring line 0 -3))
860 (return))
861
862 ;; month
863 (if (string-match "\\(\\w+\\)$" line)
864 (setq month (match-string 1 line)
865 line (substring line 0 -4))
866 (return))
867
868 ;; weekday
869 (if (string-match "\\(\\w+\\)$" line)
870 (setq line (substring line 0 -5))
871 (return))
872
873 ;; size
874 (if (string-match "\\([0-9]+\\)$" line)
b1a2b924
KG
875 (let ((length (- (max 10 (1+ (length (match-string 1 line)))))))
876 (setq size (string-to-number (match-string 1 line)))
877 (when (string-match "\\([ADHRSV]+\\)" (substring line length))
878 (setq length (+ length (match-end 0))))
879 (setq line (substring line 0 length)))
4007ba5b
KG
880 (return))
881
b1a2b924
KG
882 ;; mode: ARCH, DIR, HIDDEN, RONLY, SYSTEM, VOLID
883 (if (string-match "\\([ADHRSV]+\\)?$" line)
4007ba5b 884 (setq
b1a2b924 885 mode (or (match-string 1 line) "")
4007ba5b
KG
886 mode (save-match-data (format
887 "%s%s"
888 (if (string-match "D" mode) "d" "-")
889 (mapconcat
890 (lambda (x) "") " "
891 (concat "r" (if (string-match "R" mode) "-" "w") "x"))))
b1a2b924 892 line (substring line 0 -7))
4007ba5b
KG
893 (return))
894
7432277c 895 ;; localname
b1a2b924 896 (if (string-match "^\\s-+\\(\\S-\\(.*\\S-\\)?\\)\\s-*$" line)
7432277c 897 (setq localname (match-string 1 line))
4007ba5b
KG
898 (return))))
899
7432277c 900 (when (and localname mode size)
4007ba5b
KG
901 (setq mtime
902 (if (and sec min hour day month year)
903 (encode-time
904 sec min hour day
905 (cdr (assoc (downcase month) tramp-smb-parse-time-months))
906 year)
907 '(0 0)))
7432277c 908 (list localname mode size mtime))))
4007ba5b 909
8daea7fc
KG
910;; Inodes don't exist for SMB files. Therefore we must generate virtual ones.
911;; Used in `find-buffer-visiting'.
912;; The method applied might be not so efficient (Ange-FTP uses hashes). But
913;; performance isn't the major issue given that file transfer will take time.
914
915(defun tramp-smb-get-inode (share file)
916 "Returns the virtual inode number.
917If it doesn't exist, generate a new one."
918 (let ((string (concat share "/" (directory-file-name file))))
919 (unless (assoc string tramp-smb-inodes)
920 (add-to-list 'tramp-smb-inodes
921 (list string (length tramp-smb-inodes))))
922 (nth 1 (assoc string tramp-smb-inodes))))
923
4007ba5b
KG
924
925;; Connection functions
926
927(defun tramp-smb-send-command (user host command)
928 "Send the COMMAND to USER at HOST (logged into an SMB session).
929Erases temporary buffer before sending the command. Returns nil if
930there has been an error message from smbclient."
931 (save-excursion
932 (set-buffer (tramp-get-buffer nil tramp-smb-method user host))
933 (erase-buffer)
934 (tramp-send-command nil tramp-smb-method user host command nil t)
935 (tramp-smb-wait-for-output user host)))
936
937(defun tramp-smb-maybe-open-connection (user host share)
938 "Maybe open a connection to HOST, logging in as USER, using `tramp-smb-program'.
939Does not do anything if a connection is already open, but re-opens the
940connection if a previous connection has died for some reason."
5ec2cc41
KG
941 (let ((process-connection-type tramp-process-connection-type)
942 (p (get-buffer-process
4007ba5b
KG
943 (tramp-get-buffer nil tramp-smb-method user host))))
944 (save-excursion
945 (set-buffer (tramp-get-buffer nil tramp-smb-method user host))
946 ;; Check whether it is still the same share
947 (unless (and p (processp p) (string-equal tramp-smb-share share))
948 (when (and p (processp p))
949 (delete-process p)
950 (setq p nil)))
951 ;; If too much time has passed since last command was sent, look
952 ;; whether process is still alive. If it isn't, kill it.
953 (when (and tramp-last-cmd-time
954 (> (tramp-time-diff (current-time) tramp-last-cmd-time) 60)
955 p (processp p) (memq (process-status p) '(run open)))
956 (unless (and p (processp p) (memq (process-status p) '(run open)))
957 (delete-process p)
958 (setq p nil))))
959 (unless (and p (processp p) (memq (process-status p) '(run open)))
960 (when (and p (processp p))
961 (delete-process p))
962 (tramp-smb-open-connection user host share))))
963
964(defun tramp-smb-open-connection (user host share)
965 "Open a connection using `tramp-smb-program'.
966This starts the command `smbclient //HOST/SHARE -U USER', then waits
967for a remote password prompt. It queries the user for the password,
968then sends the password to the remote host.
969
970Domain names in USER and port numbers in HOST are acknowledged."
971
972 (save-match-data
973 (let* ((buffer (tramp-get-buffer nil tramp-smb-method user host))
974 (real-user user)
975 (real-host host)
976 domain port args)
977
978 ; Check for domain ("user%domain") and port ("host#port")
979 (when (and user (string-match "\\(.+\\)%\\(.+\\)" user))
980 (setq real-user (or (match-string 1 user) user)
981 domain (match-string 2 user)))
982
983 (when (and host (string-match "\\(.+\\)#\\(.+\\)" host))
984 (setq real-host (or (match-string 1 host) host)
985 port (match-string 2 host)))
986
987 (if share
988 (setq args (list (concat "//" real-host "/" share)))
989 (setq args (list "-L" real-host )))
990
991 (if real-user
992 (setq args (append args (list "-U" real-user)))
993 (setq args (append args (list "-N"))))
994
995 (when domain (setq args (append args (list "-W" domain))))
996 (when port (setq args (append args (list "-p" port))))
997
998 ; OK, let's go
999 (tramp-pre-connection nil tramp-smb-method user host)
1000 (tramp-message 7 "Opening connection for //%s@%s/%s..."
1001 user host (or share ""))
1002
1003 (let* ((default-directory (tramp-temporary-file-directory))
1004 ;; If we omit the conditional here, then we would use
1005 ;; `undecided-dos' in some cases. With the conditional,
1006 ;; we use nil in these cases. Which one is right?
1007 (coding-system-for-read (unless (and (not (featurep 'xemacs))
1008 (> emacs-major-version 20))
1009 tramp-dos-coding-system))
1010 (p (apply #'start-process (buffer-name buffer) buffer
1011 tramp-smb-program args)))
1012
1013 (tramp-message 9 "Started process %s" (process-command p))
19a87064 1014 (tramp-set-process-query-on-exit-flag p nil)
4007ba5b 1015 (set-buffer buffer)
5ec2cc41 1016 (setq tramp-smb-share share)
4007ba5b
KG
1017
1018 ; send password
1019 (when real-user
1020 (let ((pw-prompt "Password:"))
1021 (tramp-message 9 "Sending password")
07dfe738 1022 (tramp-enter-password p pw-prompt user host)))
4007ba5b
KG
1023
1024 (unless (tramp-smb-wait-for-output user host)
5ec2cc41 1025 (tramp-clear-passwd user host)
4007ba5b
KG
1026 (error "Cannot open connection //%s@%s/%s"
1027 user host (or share "")))))))
1028
1029;; We don't use timeouts. If needed, the caller shall wrap around.
1030(defun tramp-smb-wait-for-output (user host)
1031 "Wait for output from smbclient command.
4007ba5b 1032Returns nil if an error message has appeared."
5ec2cc41
KG
1033 (let ((proc (get-buffer-process (current-buffer)))
1034 (found (progn (goto-char (point-min))
1035 (re-search-forward tramp-smb-prompt nil t)))
1036 (err (progn (goto-char (point-min))
1037 (re-search-forward tramp-smb-errors nil t))))
1038
1039 ;; Algorithm: get waiting output. See if last line contains
1040 ;; tramp-smb-prompt sentinel or tramp-smb-errors strings.
1041 ;; If not, wait a bit and again get waiting output.
1042 (while (and (not found) (not err))
1043
1044 ;; Accept pending output.
1045 (accept-process-output proc)
1046
1047 ;; Search for prompt.
4007ba5b 1048 (goto-char (point-min))
5ec2cc41
KG
1049 (setq found (re-search-forward tramp-smb-prompt nil t))
1050
1051 ;; Search for errors.
1052 (goto-char (point-min))
1053 (setq err (re-search-forward tramp-smb-errors nil t)))
1054
1055 ;; Add output to debug buffer if appropriate.
1056 (when tramp-debug-buffer
1057 (append-to-buffer
1058 (tramp-get-debug-buffer nil tramp-smb-method user host)
1059 (point-min) (point-max)))
1060
1061 ;; Return value is whether no error message has appeared.
1062 (not err)))
4007ba5b
KG
1063
1064
1065;; Snarfed code from time-date.el and parse-time.el
1066
1067(defconst tramp-smb-half-a-year '(241 17024)
1068"Evaluated by \"(days-to-time 183)\".")
1069
1070(defconst tramp-smb-parse-time-months '(("jan" . 1) ("feb" . 2) ("mar" . 3)
1071 ("apr" . 4) ("may" . 5) ("jun" . 6)
1072 ("jul" . 7) ("aug" . 8) ("sep" . 9)
1073 ("oct" . 10) ("nov" . 11) ("dec" . 12))
1074"Alist mapping month names to integers.")
1075
1076(defun tramp-smb-time-less-p (t1 t2)
1077 "Say whether time value T1 is less than time value T2."
1078 (unless t1 (setq t1 '(0 0)))
1079 (unless t2 (setq t2 '(0 0)))
1080 (or (< (car t1) (car t2))
1081 (and (= (car t1) (car t2))
1082 (< (nth 1 t1) (nth 1 t2)))))
1083
1084(defun tramp-smb-time-subtract (t1 t2)
1085 "Subtract two time values.
1086Return the difference in the format of a time value."
1087 (unless t1 (setq t1 '(0 0)))
1088 (unless t2 (setq t2 '(0 0)))
1089 (let ((borrow (< (cadr t1) (cadr t2))))
1090 (list (- (car t1) (car t2) (if borrow 1 0))
1091 (- (+ (if borrow 65536 0) (cadr t1)) (cadr t2)))))
1092
1093
4007ba5b
KG
1094(provide 'tramp-smb)
1095
1096;;; TODO:
1097
1098;; * Provide a local smb.conf. The default one might not be readable.
1099;; * Error handling in case password is wrong.
1100;; * Read password from "~/.netrc".
4007ba5b
KG
1101;; * Return more comprehensive file permission string. Think whether it is
1102;; possible to implement `set-file-modes'.
1103;; * Handle WILDCARD and FULL-DIRECTORY-P in
1104;; `tramp-smb-handle-insert-directory'.
1105;; * Handle links (FILENAME.LNK).
1106;; * Maybe local tmp files should have the same extension like the original
1107;; files. Strange behaviour with jka-compr otherwise?
1108;; * Copy files in dired from SMB to another method doesn't work.
1109;; * Try to remove the inclusion of dummy "" directory. Seems to be at
1110;; several places, especially in `tramp-smb-handle-insert-directory'.
1111;; * Provide variables for debug.
1112;; * (RMS) Use unwind-protect to clean up the state so as to make the state
1113;; regular again.
1114
ab5796a9 1115;;; arch-tag: fcc9dbec-7503-4d73-b638-3c8aa59575f5
4007ba5b 1116;;; tramp-smb.el ends here