Function: tramp-do-copy-or-rename-file-out-of-band

tramp-do-copy-or-rename-file-out-of-band is a byte-compiled function defined in tramp-sh.el.gz.

Signature

(tramp-do-copy-or-rename-file-out-of-band OP FILENAME NEWNAME OK-IF-ALREADY-EXISTS KEEP-DATE)

Documentation

Invoke scp program to copy.

The method used must be an out-of-band method.

Source Code

;; Defined in /usr/src/emacs/lisp/net/tramp-sh.el.gz
(defun tramp-do-copy-or-rename-file-out-of-band
    (op filename newname ok-if-already-exists keep-date)
  "Invoke `scp' program to copy.
The method used must be an out-of-band method."
  (let* ((t1 (tramp-tramp-file-p filename))
	 (t2 (tramp-tramp-file-p newname))
	 (orig-vec (tramp-dissect-file-name (if t1 filename newname)))
	 copy-program copy-args copy-env copy-keep-date listener spec
	 options source target remote-copy-program remote-copy-args p)

    (with-parsed-tramp-file-name (if t1 filename newname) nil
      (if (and t1 t2)

	  ;; Both are Tramp files.  We shall optimize it when the
	  ;; methods for FILENAME and NEWNAME are the same.
	  (let* ((dir-flag (file-directory-p filename))
		 (tmpfile (tramp-compat-make-temp-file localname dir-flag)))
	    (if dir-flag
		(setq tmpfile
		      (expand-file-name
		       (file-name-nondirectory newname) tmpfile)))
	    (unwind-protect
		(progn
		  (tramp-do-copy-or-rename-file-out-of-band
		   op filename tmpfile ok-if-already-exists keep-date)
		  (tramp-do-copy-or-rename-file-out-of-band
		   'rename tmpfile newname ok-if-already-exists keep-date))
	      ;; Save exit.
	      (ignore-errors
		(if dir-flag
		    (delete-directory
		     (expand-file-name ".." tmpfile) 'recursive)
		  (delete-file tmpfile)))))

	;; Check which ones of source and target are Tramp files.
	(setq source (funcall
		      (if (and (string-equal method "rsync")
			       (file-directory-p filename)
			       (not (file-exists-p newname)))
			  #'file-name-as-directory
			#'identity)
		      (if t1
			  (tramp-make-copy-program-file-name v)
			(tramp-compat-file-name-unquote filename)))
	      target (if t2
			 (tramp-make-copy-program-file-name v)
		       (tramp-compat-file-name-unquote newname)))

	;; Check for user.  There might be an interactive setting.
	(setq user (or (tramp-file-name-user v)
		       (tramp-get-connection-property v "login-as" nil)))

	;; Check for listener port.
	(when (tramp-get-method-parameter v 'tramp-remote-copy-args)
	  (setq listener (number-to-string (+ 50000 (random 10000))))
	  (while
	      (zerop (tramp-call-process v "nc" nil nil nil "-z" host listener))
	    (setq listener (number-to-string (+ 50000 (random 10000))))))

	;; Compose copy command.
	(setq options
	      (format-spec
	       (tramp-ssh-controlmaster-options v)
	       (format-spec-make
		?t (tramp-get-connection-property
		    (tramp-get-connection-process v) "temp-file" "")))
	      spec (list
		    ?h (or host "") ?u (or user "") ?p (or port "")
		    ?r listener ?c options ?k (if keep-date " " "")
                    ?n (concat "2>" (tramp-get-remote-null-device v))
		    ?x (tramp-scp-strict-file-name-checking v)
		    ?y (tramp-scp-force-scp-protocol v))
	      copy-program (tramp-get-method-parameter v 'tramp-copy-program)
	      copy-keep-date (tramp-get-method-parameter
			      v 'tramp-copy-keep-date)
	      copy-args
	      ;; " " has either been a replacement of "%k" (when
	      ;; keep-date argument is non-nil), or a replacement for
	      ;; the whole keep-date sublist.
	      (delete " " (apply #'tramp-expand-args v 'tramp-copy-args spec))
	      ;; `tramp-ssh-controlmaster-options' is a string instead
	      ;; of a list.  Unflatten it.
	      copy-args
	      (tramp-compat-flatten-tree
	       (mapcar
		(lambda (x) (if (tramp-compat-string-search " " x)
                                (split-string x) x))
		copy-args))
	      copy-env (apply #'tramp-expand-args v 'tramp-copy-env spec)
	      remote-copy-program
	      (tramp-get-method-parameter v 'tramp-remote-copy-program)
	      remote-copy-args
	      (apply #'tramp-expand-args v 'tramp-remote-copy-args spec))

	;; Check for local copy program.
	(unless (executable-find copy-program)
	  (tramp-error
	   v 'file-error "Cannot find local copy program: %s" copy-program))

	;; Install listener on the remote side.  The prompt must be
	;; consumed later on, when the process does not listen anymore.
	(when remote-copy-program
	  (unless (with-tramp-connection-property
		      v (concat "remote-copy-program-" remote-copy-program)
		    (tramp-find-executable
		     v remote-copy-program (tramp-get-remote-path v)))
	    (tramp-error
	     v 'file-error
	     "Cannot find remote listener: %s" remote-copy-program))
	  (setq remote-copy-program
		(mapconcat
		 #'identity
		 (append
		  (list remote-copy-program) remote-copy-args
		  (list (if t1 (concat "<" source) (concat ">" target)) "&"))
		 " "))
	  (tramp-send-command v remote-copy-program)
	  (with-timeout
	      (60 (tramp-error
		   v 'file-error
		   "Listener process not running on remote host: `%s'"
		   remote-copy-program))
	    (tramp-send-command v (format "netstat -l | grep -q :%s" listener))
	    (while (not (tramp-send-command-and-check v nil))
	      (tramp-send-command
	       v (format "netstat -l | grep -q :%s" listener)))))

	(with-temp-buffer
	  (unwind-protect
	      ;; The default directory must be remote.
	      (let ((default-directory
		      (file-name-directory (if t1 filename newname)))
		    (process-environment (copy-sequence process-environment)))
		;; Set the transfer process properties.
		(tramp-set-connection-property
		 v "process-name" (buffer-name (current-buffer)))
		(tramp-set-connection-property
		 v "process-buffer" (current-buffer))
		(when copy-env
		  (tramp-message
		   orig-vec 6 "%s=\"%s\""
		   (car copy-env) (string-join (cdr copy-env) " "))
		  (setenv (car copy-env) (string-join (cdr copy-env) " ")))
		(setq
		 copy-args
		 (append
		  copy-args
		  (if remote-copy-program
		      (list (if t1 (concat ">" target) (concat "<" source)))
		    (list source target)))
		 ;; Use an asynchronous process.  By this, password
		 ;; can be handled.  We don't set a timeout, because
		 ;; the copying of large files can last longer than 60
		 ;; secs.
		 p (let ((default-directory tramp-compat-temporary-file-directory))
		     (apply
		      #'start-process
		      (tramp-get-connection-name v)
		      (tramp-get-connection-buffer v)
		      copy-program copy-args)))
		(tramp-message orig-vec 6 "%s" (string-join (process-command p) " "))
		(process-put p 'vector orig-vec)
		(process-put p 'adjust-window-size-function #'ignore)
		(set-process-query-on-exit-flag p nil)

		;; We must adapt `tramp-local-end-of-line' for
		;; sending the password.
		(let ((tramp-local-end-of-line tramp-rsh-end-of-line))
		  (tramp-process-actions
		   p v nil tramp-actions-copy-out-of-band)))

	    ;; Reset the transfer process properties.
	    (tramp-flush-connection-property v "process-name")
	    (tramp-flush-connection-property v "process-buffer")
	    ;; Clear the remote prompt.
	    (when (and remote-copy-program
		       (not (tramp-send-command-and-check v nil)))
	      ;; Houston, we have a problem!  Likely, the listener is
	      ;; still running, so let's clear everything (but the
	      ;; cached password).
	      (tramp-cleanup-connection v 'keep-debug 'keep-password))))

	;; Handle KEEP-DATE argument.
	(when (and keep-date (not copy-keep-date))
	  (tramp-compat-set-file-times
	   newname
	   (tramp-compat-file-attribute-modification-time
	    (file-attributes filename))
	   (unless ok-if-already-exists 'nofollow)))

	;; Set the mode.
	(unless (and keep-date copy-keep-date)
	  (ignore-errors
	    (set-file-modes newname (tramp-default-file-modes filename)))))

      ;; If the operation was `rename', delete the original file.
      (unless (eq op 'copy)
	(if (file-regular-p filename)
	    (delete-file filename)
	  (delete-directory filename 'recursive))))))