;; <https://android.stackexchange.com/questions/226638/how-to-use-multibyte-file-names-in-adb-shell/232379#232379>
;; mksh uses UTF-8 internally, but is currently limited to the
;; BMP (basic multilingua plane), which means U+0000 to
- ;; U+FFFD. If you want to use SMP codepoints (U-00010000 to
+ ;; U+FFFD. If you want to use SMP codepoints (U-00010000 to
;; U-0010FFFD) on the input line, you currently have to disable
;; the UTF-8 mode (sorry).
(tramp-adb-execute-adb-command vec "shell" command)
(unless (memq op '(copy rename))
(error "Unknown operation `%s', must be `copy' or `rename'" op))
- (setq filename (file-truename filename))
+ ;; We cannot use `file-truename', this would fail for symlinks with
+ ;; non-existing target.
+ (setq filename (expand-file-name filename))
(if (file-directory-p filename)
(progn
(copy-directory filename newname keep-date t)
method '(("smb" . "smb-share")
("davs" . "dav")
("nextcloud" . "dav")
- ("afp". "afp-volume")
+ ("afp" . "afp-volume")
("gdrive" . "google-drive")))
method)
tramp-gvfs-mounttypes)
(progn
(copy-directory filename newname keep-date t)
(when (eq op 'rename) (delete-directory filename 'recursive)))
+ (if (file-symlink-p filename)
+ (progn
+ (make-symbolic-link
+ (file-symlink-p filename) newname ok-if-already-exists)
+ (when (eq op 'rename) (delete-file filename)))
+
+ ;; FIXME: This should be optimized. Computing `file-attributes'
+ ;; checks already, whether the file exists.
+ (let ((t1 (tramp-tramp-file-p filename))
+ (t2 (tramp-tramp-file-p newname))
+ (length (or (file-attribute-size
+ (file-attributes (file-truename filename)))
+ ;; `filename' doesn't exist, for example due
+ ;; to non-existent symlink target.
+ 0))
+ (file-times (file-attribute-modification-time
+ (file-attributes filename)))
+ (file-modes (tramp-default-file-modes filename))
+ (msg-operation (if (eq op 'copy) "Copying" "Renaming"))
+ copy-keep-date)
+
+ (with-parsed-tramp-file-name (if t1 filename newname) nil
+ (tramp-barf-if-file-missing v filename
+ (when (and (not ok-if-already-exists) (file-exists-p newname))
+ (tramp-error v 'file-already-exists newname))
+ (when (and (file-directory-p newname)
+ (not (directory-name-p newname)))
+ (tramp-error v 'file-error "File is a directory %s" newname))
- ;; FIXME: This should be optimized. Computing `file-attributes'
- ;; checks already, whether the file exists.
- (let ((t1 (tramp-tramp-file-p filename))
- (t2 (tramp-tramp-file-p newname))
- (length (file-attribute-size
- (file-attributes (file-truename filename))))
- (file-times (file-attribute-modification-time
- (file-attributes filename)))
- (file-modes (tramp-default-file-modes filename))
- (msg-operation (if (eq op 'copy) "Copying" "Renaming"))
- copy-keep-date)
-
- (with-parsed-tramp-file-name (if t1 filename newname) nil
- (unless length
- (tramp-error v 'file-missing filename))
- (tramp-barf-if-file-missing v filename
- (when (and (not ok-if-already-exists) (file-exists-p newname))
- (tramp-error v 'file-already-exists newname))
- (when (and (file-directory-p newname)
- (not (directory-name-p newname)))
- (tramp-error v 'file-error "File is a directory %s" newname))
-
- (with-tramp-progress-reporter
- v 0 (format "%s %s to %s" msg-operation filename newname)
+ (with-tramp-progress-reporter
+ v 0 (format "%s %s to %s" msg-operation filename newname)
- (cond
- ;; Both are Tramp files.
- ((and t1 t2)
- (with-parsed-tramp-file-name filename v1
- (with-parsed-tramp-file-name newname v2
- (cond
- ;; Shortcut: if method, host, user are the same for
- ;; both files, we invoke `cp' or `mv' on the remote
- ;; host directly.
- ((tramp-equal-remote filename newname)
- (setq copy-keep-date
- (or (eq op 'rename) keep-date preserve-uid-gid))
- (tramp-do-copy-or-rename-file-directly
- op filename newname
- ok-if-already-exists keep-date preserve-uid-gid))
-
- ;; Try out-of-band operation.
- ((and
- (tramp-method-out-of-band-p v1 length)
- (tramp-method-out-of-band-p v2 length))
- (setq copy-keep-date
- (tramp-get-method-parameter v 'tramp-copy-keep-date))
- (tramp-do-copy-or-rename-file-out-of-band
- op filename newname ok-if-already-exists keep-date))
-
- ;; No shortcut was possible. So we copy the file
- ;; first. If the operation was `rename', we go
- ;; back and delete the original file (if the copy
- ;; was successful). The approach is simple-minded:
- ;; we create a new buffer, insert the contents of
- ;; the source file into it, then write out the
- ;; buffer to the target file. The advantage is
- ;; that it doesn't matter which file name handlers
- ;; are used for the source and target file.
- (t
- (tramp-do-copy-or-rename-file-via-buffer
- op filename newname ok-if-already-exists keep-date))))))
-
- ;; One file is a Tramp file, the other one is local.
- ((or t1 t2)
(cond
- ;; Fast track on local machine.
- ((tramp-local-host-p v)
- (setq copy-keep-date
- (or (eq op 'rename) keep-date preserve-uid-gid))
- (tramp-do-copy-or-rename-file-directly
- op filename newname
- ok-if-already-exists keep-date preserve-uid-gid))
-
- ;; If the Tramp file has an out-of-band method, the
- ;; corresponding copy-program can be invoked.
- ((tramp-method-out-of-band-p v length)
- (setq copy-keep-date
- (tramp-get-method-parameter v 'tramp-copy-keep-date))
- (tramp-do-copy-or-rename-file-out-of-band
- op filename newname ok-if-already-exists keep-date))
+ ;; Both are Tramp files.
+ ((and t1 t2)
+ (with-parsed-tramp-file-name filename v1
+ (with-parsed-tramp-file-name newname v2
+ (cond
+ ;; Shortcut: if method, host, user are the same
+ ;; for both files, we invoke `cp' or `mv' on the
+ ;; remote host directly.
+ ((tramp-equal-remote filename newname)
+ (setq copy-keep-date
+ (or (eq op 'rename) keep-date preserve-uid-gid))
+ (tramp-do-copy-or-rename-file-directly
+ op filename newname
+ ok-if-already-exists keep-date preserve-uid-gid))
+
+ ;; Try out-of-band operation.
+ ((and
+ (tramp-method-out-of-band-p v1 length)
+ (tramp-method-out-of-band-p v2 length))
+ (setq copy-keep-date
+ (tramp-get-method-parameter v 'tramp-copy-keep-date))
+ (tramp-do-copy-or-rename-file-out-of-band
+ op filename newname ok-if-already-exists keep-date))
+
+ ;; No shortcut was possible. So we copy the file
+ ;; first. If the operation was `rename', we go
+ ;; back and delete the original file (if the copy
+ ;; was successful). The approach is simple-minded:
+ ;; we create a new buffer, insert the contents of
+ ;; the source file into it, then write out the
+ ;; buffer to the target file. The advantage is
+ ;; that it doesn't matter which file name handlers
+ ;; are used for the source and target file.
+ (t
+ (tramp-do-copy-or-rename-file-via-buffer
+ op filename newname ok-if-already-exists keep-date))))))
+
+ ;; One file is a Tramp file, the other one is local.
+ ((or t1 t2)
+ (cond
+ ;; Fast track on local machine.
+ ((tramp-local-host-p v)
+ (setq copy-keep-date
+ (or (eq op 'rename) keep-date preserve-uid-gid))
+ (tramp-do-copy-or-rename-file-directly
+ op filename newname
+ ok-if-already-exists keep-date preserve-uid-gid))
+
+ ;; If the Tramp file has an out-of-band method, the
+ ;; corresponding copy-program can be invoked.
+ ((tramp-method-out-of-band-p v length)
+ (setq copy-keep-date
+ (tramp-get-method-parameter v 'tramp-copy-keep-date))
+ (tramp-do-copy-or-rename-file-out-of-band
+ op filename newname ok-if-already-exists keep-date))
+
+ ;; Use the inline method via a Tramp buffer.
+ (t (tramp-do-copy-or-rename-file-via-buffer
+ op filename newname ok-if-already-exists keep-date))))
+
+ (t
+ ;; One of them must be a Tramp file.
+ (error "Tramp implementation says this cannot happen")))
+
+ ;; In case of `rename', we must flush the cache of the source file.
+ (when (and t1 (eq op 'rename))
+ (with-parsed-tramp-file-name filename v1
+ (tramp-flush-file-properties v1 v1-localname)))
+
+ ;; NEWNAME has wrong cached values.
+ (when t2
+ (with-parsed-tramp-file-name newname v2
+ (tramp-flush-file-properties v2 v2-localname)))
- ;; Use the inline method via a Tramp buffer.
- (t (tramp-do-copy-or-rename-file-via-buffer
- op filename newname ok-if-already-exists keep-date))))
+ ;; Handle `preserve-extended-attributes'. We ignore
+ ;; possible errors, because ACL strings could be
+ ;; incompatible.
+ (when-let* ((attributes (and preserve-extended-attributes
+ (file-extended-attributes filename))))
+ (ignore-errors
+ (set-file-extended-attributes newname attributes)))
- (t
- ;; One of them must be a Tramp file.
- (error "Tramp implementation says this cannot happen")))
-
- ;; In case of `rename', we must flush the cache of the source file.
- (when (and t1 (eq op 'rename))
- (with-parsed-tramp-file-name filename v1
- (tramp-flush-file-properties v1 v1-localname)))
-
- ;; NEWNAME has wrong cached values.
- (when t2
- (with-parsed-tramp-file-name newname v2
- (tramp-flush-file-properties v2 v2-localname)))
-
- ;; Handle `preserve-extended-attributes'. We ignore
- ;; possible errors, because ACL strings could be
- ;; incompatible.
- (when-let ((attributes (and preserve-extended-attributes
- (file-extended-attributes filename))))
- (ignore-errors
- (set-file-extended-attributes newname attributes)))
-
- ;; KEEP-DATE handling.
- (when (and keep-date (not copy-keep-date))
- (set-file-times
- newname file-times (unless ok-if-already-exists 'nofollow)))
-
- ;; Set the mode.
- (unless (and keep-date copy-keep-date)
- (set-file-modes newname file-modes))))))))
+ ;; KEEP-DATE handling.
+ (when (and keep-date (not copy-keep-date))
+ (set-file-times
+ newname file-times (unless ok-if-already-exists 'nofollow)))
+
+ ;; Set the mode.
+ (unless (and keep-date copy-keep-date)
+ (set-file-modes newname file-modes)))))))))
(defun tramp-do-copy-or-rename-file-via-buffer
(op filename newname _ok-if-already-exists _keep-date)
;; character to read. When a process does
;; not read from stdin, like magit, it
;; should set a timeout
- ;; instead. See`tramp-pipe-stty-settings'.
+ ;; instead. See `tramp-pipe-stty-settings'.
;; (Bug#62093)
;; FIXME: Shall we rather use "stty raw"?
(tramp-send-command
(lambda (x) (not (tramp-get-file-property vec x "file-directory-p")))
remote-path))))))
-;; The PIPE_BUF in POSIX [1] can be as low as 512 [2]. Here are the values
+;; The PIPE_BUF in POSIX [1] can be as low as 512 [2]. Here are the values
;; on various platforms:
;; - 512 on macOS, FreeBSD, NetBSD, OpenBSD, MirBSD, native Windows.
;; - 4 KiB on Linux, OSF/1, Cygwin, Haiku.
"Read from server failed, maybe it closed the connection"
"Call timed out: server did not respond"
(: (+ (not blank)) ": command not found")
+ (: (+ (not blank)) " does not exist")
"Server doesn't support UNIX CIFS calls"
(| ;; Samba.
"ERRDOS"
PRESERVE-UID-GID and PRESERVE-EXTENDED-ATTRIBUTES are completely ignored."
(setq filename (expand-file-name filename)
newname (expand-file-name newname))
- (with-tramp-progress-reporter
- (tramp-dissect-file-name
- (if (tramp-tramp-file-p filename) filename newname))
- 0 (format "Copying %s to %s" filename newname)
- (if (file-directory-p filename)
- (copy-directory filename newname keep-date 'parents 'copy-contents)
+ (with-parsed-tramp-file-name
+ (if (tramp-tramp-file-p filename) filename newname) nil
+ (with-tramp-progress-reporter
+ v 0 (format "Copying %s to %s" filename newname)
+
+ (if (file-directory-p filename)
+ (copy-directory filename newname keep-date 'parents 'copy-contents)
+
+ (tramp-barf-if-file-missing v filename
+ ;; `file-local-copy' returns a file name also for a local
+ ;; file with `jka-compr-handler', so we cannot trust its
+ ;; result as indication for a remote file name.
+ (if-let* ((tmpfile
+ (and (tramp-tramp-file-p filename)
+ (file-local-copy filename))))
+ ;; Remote filename.
+ (condition-case err
+ (rename-file tmpfile newname ok-if-already-exists)
+ ((error quit)
+ (delete-file tmpfile)
+ (signal (car err) (cdr err))))
+
+ ;; Remote newname.
+ (when (and (file-directory-p newname)
+ (directory-name-p newname))
+ (setq newname
+ (expand-file-name
+ (file-name-nondirectory filename) newname)))
+
+ (when (and (not ok-if-already-exists) (file-exists-p newname))
+ (tramp-error v 'file-already-exists newname))
+ (when (and (file-directory-p newname)
+ (not (directory-name-p newname)))
+ (tramp-error v 'file-error "File is a directory %s" newname))
+
+ (unless (tramp-smb-get-share v)
+ (tramp-error
+ v 'file-error "Target `%s' must contain a share name" newname))
+ (unless (tramp-smb-send-command
+ v (format "put %s %s"
+ (tramp-smb-shell-quote-argument filename)
+ (tramp-smb-shell-quote-localname v)))
+ (tramp-error
+ v 'file-error "Cannot copy `%s' to `%s'" filename newname))
- (unless (file-exists-p filename)
- (tramp-error
- (tramp-dissect-file-name
- (if (tramp-tramp-file-p filename) filename newname))
- 'file-missing filename))
-
- ;; `file-local-copy' returns a file name also for a local file
- ;; with `jka-compr-handler', so we cannot trust its result as
- ;; indication for a remote file name.
- (if-let ((tmpfile
- (and (tramp-tramp-file-p filename) (file-local-copy filename))))
- ;; Remote filename.
- (condition-case err
- (rename-file tmpfile newname ok-if-already-exists)
- ((error quit)
- (delete-file tmpfile)
- (signal (car err) (cdr err))))
-
- ;; Remote newname.
- (when (and (file-directory-p newname)
- (directory-name-p newname))
- (setq newname
- (expand-file-name (file-name-nondirectory filename) newname)))
-
- (with-parsed-tramp-file-name newname nil
- (when (and (not ok-if-already-exists) (file-exists-p newname))
- (tramp-error v 'file-already-exists newname))
- (when (and (file-directory-p newname)
- (not (directory-name-p newname)))
- (tramp-error v 'file-error "File is a directory %s" newname))
-
- (unless (tramp-smb-get-share v)
- (tramp-error
- v 'file-error "Target `%s' must contain a share name" newname))
- (unless (tramp-smb-send-command
- v (format "put %s %s"
- (tramp-smb-shell-quote-argument filename)
- (tramp-smb-shell-quote-localname v)))
- (tramp-error
- v 'file-error "Cannot copy `%s' to `%s'" filename newname))
-
- ;; When newname did exist, we have wrong cached values.
- (when (tramp-tramp-file-p newname)
- (with-parsed-tramp-file-name newname v2
- (tramp-flush-file-properties v2 v2-localname))))))
-
- ;; KEEP-DATE handling.
- (when keep-date
- (set-file-times
- newname
- (file-attribute-modification-time (file-attributes filename))
- (unless ok-if-already-exists 'nofollow)))))
+ ;; When newname did exist, we have wrong cached values.
+ (when (tramp-tramp-file-p newname)
+ (with-parsed-tramp-file-name newname v2
+ (tramp-flush-file-properties v2 v2-localname))))))
+
+ ;; KEEP-DATE handling.
+ (when keep-date
+ (set-file-times
+ newname
+ (file-attribute-modification-time (file-attributes filename))
+ (unless ok-if-already-exists 'nofollow))))))
(defun tramp-smb-handle-delete-directory (directory &optional recursive trash)
"Like `delete-directory' for Tramp files."
(with-parsed-tramp-file-name
(if (tramp-tramp-file-p filename) filename newname) nil
- (unless (file-exists-p filename)
- (tramp-error v 'file-missing filename))
- (when (and (not ok-if-already-exists) (file-exists-p newname))
- (tramp-error v 'file-already-exists newname))
- (when (and (file-directory-p newname)
- (not (directory-name-p newname)))
- (tramp-error v 'file-error "File is a directory %s" newname))
+ (tramp-barf-if-file-missing v filename
+ (when (and (not ok-if-already-exists) (file-exists-p newname))
+ (tramp-error v 'file-already-exists newname))
+ (when (and (file-directory-p newname)
+ (not (directory-name-p newname)))
+ (tramp-error v 'file-error "File is a directory %s" newname))
- (with-tramp-progress-reporter
- v 0 (format "Renaming %s to %s" filename newname)
-
- (if (and (not (file-exists-p newname))
- (tramp-equal-remote filename newname)
- (string-equal
- (tramp-smb-get-share (tramp-dissect-file-name filename))
- (tramp-smb-get-share (tramp-dissect-file-name newname))))
- ;; We can rename directly.
- (with-parsed-tramp-file-name filename v1
- (with-parsed-tramp-file-name newname v2
-
- ;; We must also flush the cache of the directory, because
- ;; `file-attributes' reads the values from there.
- (tramp-flush-file-properties v1 v1-localname)
- (tramp-flush-file-properties v2 v2-localname)
- (unless (tramp-smb-get-share v2)
- (tramp-error
- v2 'file-error
- "Target `%s' must contain a share name" newname))
- (unless (tramp-smb-send-command
- v2 (format "rename %s %s"
- (tramp-smb-shell-quote-localname v1)
- (tramp-smb-shell-quote-localname v2)))
- (tramp-error v2 'file-error "Cannot rename `%s'" filename))))
-
- ;; We must rename via copy.
- (copy-file
- filename newname ok-if-already-exists 'keep-time 'preserve-uid-gid)
- (if (file-directory-p filename)
- (delete-directory filename 'recursive)
- (delete-file filename))))))
+ (with-tramp-progress-reporter
+ v 0 (format "Renaming %s to %s" filename newname)
+
+ (if (and (not (file-exists-p newname))
+ (tramp-equal-remote filename newname)
+ (string-equal
+ (tramp-smb-get-share (tramp-dissect-file-name filename))
+ (tramp-smb-get-share (tramp-dissect-file-name newname))))
+ ;; We can rename directly.
+ (with-parsed-tramp-file-name filename v1
+ (with-parsed-tramp-file-name newname v2
+
+ ;; We must also flush the cache of the directory, because
+ ;; `file-attributes' reads the values from there.
+ (tramp-flush-file-properties v1 v1-localname)
+ (tramp-flush-file-properties v2 v2-localname)
+ (unless (tramp-smb-get-share v2)
+ (tramp-error
+ v2 'file-error
+ "Target `%s' must contain a share name" newname))
+ (unless (tramp-smb-send-command
+ v2 (format "rename %s %s"
+ (tramp-smb-shell-quote-localname v1)
+ (tramp-smb-shell-quote-localname v2)))
+ (tramp-error v2 'file-error "Cannot rename `%s'" filename))))
+
+ ;; We must rename via copy.
+ (copy-file
+ filename newname ok-if-already-exists 'keep-time 'preserve-uid-gid)
+ (if (file-directory-p filename)
+ (delete-directory filename 'recursive)
+ (delete-file filename)))))))
(defun tramp-smb-action-set-acl (proc vec)
"Set ACL data."
(unless (memq op '(copy rename))
(error "Unknown operation `%s', must be `copy' or `rename'" op))
- (setq filename (file-truename filename))
(if (file-directory-p filename)
(progn
(copy-directory filename newname keep-date t)
(when (eq op 'rename) (delete-directory filename 'recursive)))
-
- ;; FIXME: This should be optimized. Computing `file-attributes'
- ;; checks already, whether the file exists.
- (let ((t1 (tramp-sudoedit-file-name-p filename))
- (t2 (tramp-sudoedit-file-name-p newname))
- (file-times (file-attribute-modification-time
- (file-attributes filename)))
- (file-modes (tramp-default-file-modes filename))
- (attributes (and preserve-extended-attributes
- (file-extended-attributes filename)))
- (sudoedit-operation
- (cond
- ((and (eq op 'copy) preserve-uid-gid) '("cp" "-f" "-p"))
- ((eq op 'copy) '("cp" "-f"))
- ((eq op 'rename) '("mv" "-f"))))
- (msg-operation (if (eq op 'copy) "Copying" "Renaming")))
-
- (with-parsed-tramp-file-name (if t1 filename newname) nil
- (tramp-barf-if-file-missing v filename
- (when (and (not ok-if-already-exists) (file-exists-p newname))
- (tramp-error v 'file-already-exists newname))
- (when (and (file-directory-p newname)
- (not (directory-name-p newname)))
- (tramp-error v 'file-error "File is a directory %s" newname))
-
- (if (or (and (tramp-tramp-file-p filename) (not t1))
- (and (tramp-tramp-file-p newname) (not t2)))
- ;; We cannot copy or rename directly.
- (let ((tmpfile (tramp-compat-make-temp-file filename)))
- (if (eq op 'copy)
- (copy-file filename tmpfile t)
- (rename-file filename tmpfile t))
- (rename-file tmpfile newname ok-if-already-exists))
-
- ;; Direct action.
- (with-tramp-progress-reporter
- v 0 (format "%s %s to %s" msg-operation filename newname)
- (unless (tramp-sudoedit-send-command
- v sudoedit-operation
- (tramp-unquote-file-local-name filename)
- (tramp-unquote-file-local-name newname))
- (tramp-error
- v 'file-error
- "Error %s `%s' `%s'" msg-operation filename newname))))
-
- ;; When `newname' is local, we must change the ownership to
- ;; the local user.
- (unless (tramp-tramp-file-p newname)
- (tramp-set-file-uid-gid
- (concat (file-remote-p filename) newname)
- (tramp-get-local-uid 'integer)
- (tramp-get-local-gid 'integer)))
-
- ;; Set the time and mode. Mask possible errors.
- (when keep-date
- (ignore-errors
- (set-file-times
- newname file-times (unless ok-if-already-exists 'nofollow))
- (set-file-modes newname file-modes)))
-
- ;; Handle `preserve-extended-attributes'. We ignore possible
- ;; errors, because ACL strings could be incompatible.
- (when attributes
- (ignore-errors
- (set-file-extended-attributes newname attributes)))
-
- (when (and t1 (eq op 'rename))
- (with-parsed-tramp-file-name filename v1
- (tramp-flush-file-properties v1 v1-localname)))
-
- (when t2
- (with-parsed-tramp-file-name newname v2
- (tramp-flush-file-properties v2 v2-localname))))))))
+ (if (file-symlink-p filename)
+ (progn
+ (make-symbolic-link
+ (file-symlink-p filename) newname ok-if-already-exists)
+ (when (eq op 'rename) (delete-file filename)))
+
+ ;; FIXME: This should be optimized. Computing `file-attributes'
+ ;; checks already, whether the file exists.
+ (let ((t1 (tramp-sudoedit-file-name-p filename))
+ (t2 (tramp-sudoedit-file-name-p newname))
+ (file-times (file-attribute-modification-time
+ (file-attributes filename)))
+ (file-modes (tramp-default-file-modes filename))
+ (attributes (and preserve-extended-attributes
+ (file-extended-attributes filename)))
+ (sudoedit-operation
+ (cond
+ ((and (eq op 'copy) preserve-uid-gid) '("cp" "-f" "-p"))
+ ((eq op 'copy) '("cp" "-f"))
+ ((eq op 'rename) '("mv" "-f"))))
+ (msg-operation (if (eq op 'copy) "Copying" "Renaming")))
+
+ (with-parsed-tramp-file-name (if t1 filename newname) nil
+ (tramp-barf-if-file-missing v filename
+ (when (and (not ok-if-already-exists) (file-exists-p newname))
+ (tramp-error v 'file-already-exists newname))
+ (when (and (file-directory-p newname)
+ (not (directory-name-p newname)))
+ (tramp-error v 'file-error "File is a directory %s" newname))
+
+ (if (or (and (tramp-tramp-file-p filename) (not t1))
+ (and (tramp-tramp-file-p newname) (not t2)))
+ ;; We cannot copy or rename directly.
+ (let ((tmpfile (tramp-compat-make-temp-file filename)))
+ (if (eq op 'copy)
+ (copy-file filename tmpfile t)
+ (rename-file filename tmpfile t))
+ (rename-file tmpfile newname ok-if-already-exists))
+
+ ;; Direct action.
+ (with-tramp-progress-reporter
+ v 0 (format "%s %s to %s" msg-operation filename newname)
+ (unless (tramp-sudoedit-send-command
+ v sudoedit-operation
+ (tramp-unquote-file-local-name filename)
+ (tramp-unquote-file-local-name newname))
+ (tramp-error
+ v 'file-error
+ "Error %s `%s' `%s'" msg-operation filename newname))))
+
+ ;; When `newname' is local, we must change the ownership
+ ;; to the local user.
+ (unless (tramp-tramp-file-p newname)
+ (tramp-set-file-uid-gid
+ (concat (file-remote-p filename) newname)
+ (tramp-get-local-uid 'integer)
+ (tramp-get-local-gid 'integer)))
+
+ ;; Set the time and mode. Mask possible errors.
+ (when keep-date
+ (ignore-errors
+ (set-file-times
+ newname file-times (unless ok-if-already-exists 'nofollow))
+ (set-file-modes newname file-modes)))
+
+ ;; Handle `preserve-extended-attributes'. We ignore possible
+ ;; errors, because ACL strings could be incompatible.
+ (when attributes
+ (ignore-errors
+ (set-file-extended-attributes newname attributes)))
+
+ (when (and t1 (eq op 'rename))
+ (with-parsed-tramp-file-name filename v1
+ (tramp-flush-file-properties v1 v1-localname)))
+
+ (when t2
+ (with-parsed-tramp-file-name newname v2
+ (tramp-flush-file-properties v2 v2-localname)))))))))
(defun tramp-sudoedit-handle-copy-file
(filename newname &optional ok-if-already-exists keep-date
`(condition-case ,err
(progn ,@body)
(error
- (if (not (file-exists-p ,filename))
+ (if (not (or (file-exists-p ,filename) (file-symlink-p ,filename)))
(tramp-error ,vec 'file-missing ,filename)
(signal (car ,err) (cdr ,err)))))))
(when (tramp-connectable-p ,filename)
(with-parsed-tramp-file-name (expand-file-name ,filename) nil
(with-tramp-file-property v localname "file-exists-p"
- ;; Examine `file-attributes' cache to see if request can
- ;; be satisfied without remote operation.
- (if (tramp-file-property-p v localname "file-attributes")
- (not
- (null (tramp-get-file-property v localname "file-attributes")))
- ,@body))))))
+ (cond
+ ;; Examine `file-attributes' cache to see if request can
+ ;; be satisfied without remote operation.
+ ((and-let*
+ (((tramp-file-property-p v localname "file-attributes"))
+ (fa (tramp-get-file-property v localname "file-attributes"))
+ ((not (stringp (car fa)))))))
+ ;; Symlink to a non-existing target counts as nil.
+ ((file-symlink-p ,filename)
+ (file-exists-p (file-truename ,filename)))
+ (t ,@body)))))))
(defmacro tramp-skeleton-file-local-copy (filename &rest body)
"Skeleton for `tramp-*-handle-file-local-copy'.
;; We cannot add "file-attributes", "file-executable-p",
;; "file-ownership-preserved-p", "file-readable-p",
;; "file-writable-p".
- '("file-directory-p" "file-exists-p" "file-symlinkp" "file-truename")
+ '("file-directory-p" "file-exists-p" "file-symlink-p" "file-truename")
(tramp-flush-file-properties v localname))
(condition-case err
(progn ,@body)
tramp-error-show-message-timeout nil
tramp-persistency-file-name nil
tramp-verbose 0
- vc-handled-backends nil)
+ vc-handled-backends (unless noninteractive vc-handled-backends))
(defconst tramp-test-name-prefix "tramp-test"
"Prefix to use for temporary test files.")
(dolist (quoted (if (tramp--test-expensive-test-p) '(nil t) '(nil)))
(let ((tmp-name1 (tramp--test-make-temp-name nil quoted))
(tmp-name2 (tramp--test-make-temp-name nil quoted))
- (tmp-name3 (tramp--test-make-temp-name 'local quoted)))
+ (tmp-name3 (tramp--test-make-temp-name 'local quoted))
+ (tmp-name4
+ (file-name-nondirectory (tramp--test-make-temp-name 'local quoted))))
(dolist (source-target
`(;; Copy on remote side.
(,tmp-name1 . ,tmp-name2)
(,tmp-name1 . ,tmp-name3)
;; Copy from local side to remote side.
(,tmp-name3 . ,tmp-name1)))
- (let ((source (car source-target))
- (target (cdr source-target)))
+ (let* ((source (car source-target))
+ (source-link
+ (expand-file-name tmp-name4 (file-name-directory source)))
+ (target (cdr source-target))
+ (target-link
+ (expand-file-name tmp-name4 (file-name-directory target))))
;; Copy simple file.
(unwind-protect
(ignore-errors (delete-file source))
(ignore-errors (delete-file target)))
+ ;; Copy symlinked file.
+ (unwind-protect
+ (tramp--test-ignore-make-symbolic-link-error
+ (write-region "foo" nil source-link)
+ (should (file-exists-p source-link))
+ (make-symbolic-link tmp-name4 source)
+ (should (file-exists-p source))
+ (should (string-equal (file-symlink-p source) tmp-name4))
+ (copy-file source target)
+ ;; Some backends like tramp-gvfs.el do not create the
+ ;; link on the target.
+ (when (file-symlink-p target)
+ (should (string-equal (file-symlink-p target) tmp-name4))))
+
+ ;; Cleanup.
+ (ignore-errors (delete-file source))
+ (ignore-errors (delete-file source-link))
+ (ignore-errors (delete-file target))
+ (ignore-errors (delete-file target-link)))
+
;; Copy file to directory.
(unwind-protect
;; This doesn't work on FTP.
(dolist (quoted (if (tramp--test-expensive-test-p) '(nil t) '(nil)))
(let ((tmp-name1 (tramp--test-make-temp-name nil quoted))
(tmp-name2 (tramp--test-make-temp-name nil quoted))
- (tmp-name3 (tramp--test-make-temp-name 'local quoted)))
+ (tmp-name3 (tramp--test-make-temp-name 'local quoted))
+ (tmp-name4
+ (file-name-nondirectory (tramp--test-make-temp-name 'local quoted))))
(dolist (source-target
`(;; Rename on remote side.
(,tmp-name1 . ,tmp-name2)
(,tmp-name1 . ,tmp-name3)
;; Rename from local side to remote side.
(,tmp-name3 . ,tmp-name1)))
- (let ((source (car source-target))
- (target (cdr source-target)))
+ (let* ((source (car source-target))
+ (source-link
+ (expand-file-name tmp-name4 (file-name-directory source)))
+ (target (cdr source-target))
+ (target-link
+ (expand-file-name tmp-name4 (file-name-directory target))))
;; Rename simple file.
(unwind-protect
(ignore-errors (delete-file source))
(ignore-errors (delete-file target)))
+ ;; Rename symlinked file.
+ (unwind-protect
+ (tramp--test-ignore-make-symbolic-link-error
+ (write-region "foo" nil source-link)
+ (should (file-exists-p source-link))
+ (make-symbolic-link tmp-name4 source)
+ (should (file-exists-p source))
+ (should (string-equal (file-symlink-p source) tmp-name4))
+ (rename-file source target)
+ (should-not (file-exists-p source))
+ ;; Some backends like tramp-gvfs.el do not create the
+ ;; link on the target.
+ (when (file-symlink-p target)
+ (should (string-equal (file-symlink-p target) tmp-name4))))
+
+ ;; Cleanup.
+ (ignore-errors (delete-file source))
+ (ignore-errors (delete-file source-link))
+ (ignore-errors (delete-file target))
+ (ignore-errors (delete-file target-link)))
+
;; Rename file to directory.
(unwind-protect
(progn
(if quoted #'file-name-quote #'identity)
(file-attribute-type attr))
(file-remote-p (file-truename tmp-name1) 'localname)))
+ (delete-file tmp-name2)
+
+ ;; A non-existent link target makes the file unaccessible.
+ (make-symbolic-link "error" tmp-name2)
+ (should (file-symlink-p tmp-name2))
+ (should-error
+ (access-file tmp-name2 "error")
+ :type 'file-missing)
+ ;; `file-ownership-preserved-p' should return t for
+ ;; symlinked files to a non-existing target.
+ (when test-file-ownership-preserved-p
+ (should (file-ownership-preserved-p tmp-name2 'group)))
(delete-file tmp-name2))
;; Check, that "//" in symlinks are handled properly.
(make-symbolic-link tmp-name1 tmp-name2)
(should (file-symlink-p tmp-name1))
(should (file-symlink-p tmp-name2))
- (should-not (file-regular-p tmp-name1))
- (should-not (file-regular-p tmp-name2))
(should-error
- (file-truename tmp-name1)
+ (file-regular-p tmp-name1)
:type 'file-error)
(should-error
- (file-truename tmp-name2)
+ (file-regular-p tmp-name2)
:type 'file-error))))
;; Cleanup.
(if quoted #'file-name-quote #'identity)
(file-attribute-type (file-attributes file3)))
(file-remote-p (file-truename file1) 'localname)))
- ;; Check file contents.
- (with-temp-buffer
- (insert-file-contents file3)
- (should (string-equal (buffer-string) elt)))
(delete-file file3))))
;; Check file names.