;;; tramp-adb.el --- Functions for calling Android Debug Bridge from Tramp
-;; Copyright (C) 2011-2012 Free Software Foundation, Inc.
+;; Copyright (C) 2011-2013 Free Software Foundation, Inc.
;; Author: Juergen Hoetzel <juergen@archlinux.org>
;; Keywords: comm, processes
;;; Commentary:
-;; The Android Debug Bridge must be installed on your local machine.
-;; Add the following form into your .emacs:
+;; The Android Debug Bridge "adb" must be installed on your local
+;; machine. If it is not in your $PATH, add the following form into
+;; your .emacs:
;;
-;; (setq tramp-adb-sdk-dir "/path/to/android/sdk")
+;; (setq tramp-adb-program "/path/to/adb")
;;
;; Due to security it is not possible to access non-root devices.
;;; Code:
(require 'tramp)
+(require 'time-date)
(defvar dired-move-to-filename-regexp)
-(defcustom tramp-adb-sdk-dir "~/Android/sdk"
- "Set to the directory containing the Android SDK."
- :type 'string
+(defcustom tramp-adb-program "adb"
+ "Name of the Android Debug Bridge program."
+ :group 'tramp
:version "24.4"
- :group 'tramp)
+ :type 'string)
;;;###tramp-autoload
(defconst tramp-adb-method "adb"
"*When this method name is used, forward all calls to Android Debug Bridge.")
-(defcustom tramp-adb-prompt "^\\(?:[[:alnum:]]*@[[:alnum:]]*[^#\\$]*\\)?[#\\$][[:space:]]"
+(defcustom tramp-adb-prompt
+ "^\\(?:[[:digit:]]*|?\\)?\\(?:[[:alnum:]]*@[[:alnum:]]*[^#\\$]*\\)?[#\\$][[:space:]]"
"Regexp used as prompt in almquist shell."
:type 'string
:version "24.4"
:group 'tramp)
-(defconst tramp-adb-ls-date-regexp "[[:space:]][0-9]\\{4\\}-[0-9][0-9]-[0-9][0-9][[:space:]][0-9][0-9]:[0-9][0-9][[:space:]]")
+(defconst tramp-adb-ls-date-regexp
+ "[[:space:]][0-9]\\{4\\}-[0-9][0-9]-[0-9][0-9][[:space:]][0-9][0-9]:[0-9][0-9][[:space:]]")
+
+(defconst tramp-adb-ls-toolbox-regexp
+ (concat
+ "^[[:space:]]*\\([-[:alpha:]]+\\)" ; \1 permissions
+ "[[:space:]]*\\([^[:space:]]+\\)" ; \2 username
+ "[[:space:]]+\\([^[:space:]]+\\)" ; \3 group
+ "[[:space:]]+\\([[:digit:]]+\\)" ; \4 size
+ "[[:space:]]+\\([-[:digit:]]+[[:space:]][:[:digit:]]+\\)" ; \5 date
+ "[[:space:]]+\\(.*\\)$")) ; \6 filename
+
+;;;###tramp-autoload
+(add-to-list 'tramp-methods
+ `(,tramp-adb-method
+ (tramp-tmpdir "/data/local/tmp")))
;;;###tramp-autoload
-(add-to-list 'tramp-methods `(,tramp-adb-method))
+(add-to-list 'tramp-default-host-alist `(,tramp-adb-method nil ""))
;;;###tramp-autoload
(eval-after-load 'tramp
(file-name-as-directory . tramp-handle-file-name-as-directory)
(file-regular-p . tramp-handle-file-regular-p)
(file-remote-p . tramp-handle-file-remote-p)
+ (file-accessible-directory-p . tramp-handle-file-accessible-directory-p)
(file-directory-p . tramp-adb-handle-file-directory-p)
(file-symlink-p . tramp-handle-file-symlink-p)
;; FIXME: This is too sloppy.
- (file-executable-p . file-exists-p)
- (file-exists-p . tramp-adb-handle-file-exists-p)
+ (file-executable-p . tramp-handle-file-exists-p)
+ (file-exists-p . tramp-handle-file-exists-p)
(file-readable-p . tramp-handle-file-exists-p)
(file-writable-p . tramp-adb-handle-file-writable-p)
(file-local-copy . tramp-adb-handle-file-local-copy)
(expand-file-name . tramp-adb-handle-expand-file-name)
(find-backup-file-name . tramp-handle-find-backup-file-name)
(directory-files . tramp-handle-directory-files)
+ (directory-files-and-attributes
+ . tramp-adb-handle-directory-files-and-attributes)
(make-directory . tramp-adb-handle-make-directory)
(delete-directory . tramp-adb-handle-delete-directory)
(delete-file . tramp-adb-handle-delete-file)
(vc-registered . ignore) ;no vc control files on Android devices
(write-region . tramp-adb-handle-write-region)
(set-file-modes . tramp-adb-handle-set-file-modes)
- (set-file-times . ignore)
+ (set-file-times . tramp-adb-handle-set-file-times)
(copy-file . tramp-adb-handle-copy-file)
(rename-file . tramp-adb-handle-rename-file)
(process-file . tramp-adb-handle-process-file)
(start-file-process . tramp-adb-handle-start-file-process))
"Alist of handler functions for Tramp ADB method.")
+;; It must be a `defsubst' in order to push the whole code into
+;; tramp-loaddefs.el. Otherwise, there would be recursive autoloading.
;;;###tramp-autoload
-(defun tramp-adb-file-name-p (filename)
+(defsubst tramp-adb-file-name-p (filename)
"Check if it's a filename for ADB."
(let ((v (tramp-dissect-file-name filename)))
(string= (tramp-file-name-method v) tramp-adb-method)))
"Invoke the ADB handler for OPERATION.
First arg specifies the OPERATION, second arg is a list of arguments to
pass to the OPERATION."
- (let ((fn (assoc operation tramp-adb-file-name-handler-alist))
- ;; `tramp-default-host's default value is (system-name). Not
- ;; useful for us.
- (tramp-default-host
- (unless (equal (eval (car (get 'tramp-default-host 'standard-value)))
- tramp-default-host)
- tramp-default-host)))
+ (let ((fn (assoc operation tramp-adb-file-name-handler-alist)))
(if fn
(save-match-data (apply (cdr fn) args))
(tramp-run-real-handler operation args))))
-;; This cannot be a constant, because `tramp-adb-sdk-dir' is customizable.
-(defun tramp-adb-program ()
- "The Android Debug Bridge."
- (expand-file-name "platform-tools/adb" tramp-adb-sdk-dir))
-
;;;###tramp-autoload
(defun tramp-adb-parse-device-names (ignore)
"Return a list of (nil host) tuples allowed to access."
- (with-temp-buffer
- (when (zerop (call-process (tramp-adb-program) nil t nil "devices"))
- (let (result)
- (goto-char (point-min))
- (while (search-forward-regexp "^\\(\\S-+\\)[[:space:]]+device$" nil t)
- (add-to-list 'result (list nil (match-string 1))))
- result))))
+ (with-timeout (10)
+ (with-temp-buffer
+ (when (zerop (call-process tramp-adb-program nil t nil "devices"))
+ (let (result)
+ (goto-char (point-min))
+ (while (search-forward-regexp "^\\(\\S-+\\)[[:space:]]+device$" nil t)
+ (add-to-list 'result (list nil (match-string 1))))
+ result)))))
(defun tramp-adb-handle-expand-file-name (name &optional dir)
"Like `expand-file-name' for Tramp files."
(unless id-format (setq id-format 'integer))
(ignore-errors
(with-parsed-tramp-file-name filename nil
- (with-tramp-file-property v localname (format "file-attributes-%s" id-format)
+ (with-tramp-file-property
+ v localname (format "file-attributes-%s" id-format)
+ (tramp-adb-barf-unless-okay
+ v (format "%s -d -l %s"
+ (tramp-adb-get-ls-command v)
+ (tramp-shell-quote-argument localname)) "")
+ (with-current-buffer (tramp-get-buffer v)
+ (tramp-adb-sh-fix-ls-output)
+ (cdar (tramp-do-parse-file-attributes-with-ls v id-format)))))))
+
+(defun tramp-do-parse-file-attributes-with-ls (vec &optional id-format)
+ "Parse `file-attributes' for Tramp files using the ls(1) command."
+ (with-current-buffer (tramp-get-buffer vec)
+ (goto-char (point-min))
+ (let ((file-properties nil))
+ (while (re-search-forward tramp-adb-ls-toolbox-regexp nil t)
+ (let* ((mod-string (match-string 1))
+ (is-dir (eq ?d (aref mod-string 0)))
+ (is-symlink (eq ?l (aref mod-string 0)))
+ (uid (match-string 2))
+ (gid (match-string 3))
+ (size (string-to-number (match-string 4)))
+ (date (match-string 5))
+ (name (match-string 6))
+ (symlink-target
+ (and is-symlink
+ (cadr (split-string name "\\( -> \\|\n\\)")))))
+ (push (list
+ (if is-symlink
+ (car (split-string name "\\( -> \\|\n\\)"))
+ name)
+ (or is-dir symlink-target)
+ 1 ;link-count
+ ;; no way to handle numeric ids in Androids ash
+ (if (eq id-format 'integer) 0 uid)
+ (if (eq id-format 'integer) 0 gid)
+ '(0 0) ; atime
+ (date-to-time date) ; mtime
+ '(0 0) ; ctime
+ size
+ mod-string
+ ;; fake
+ t 1
+ (tramp-get-device vec))
+ file-properties)))
+ file-properties)))
+
+(defun tramp-adb-handle-directory-files-and-attributes
+ (directory &optional full match nosort id-format)
+ "Like `directory-files-and-attributes' for Tramp files."
+ (when (file-directory-p directory)
+ (with-parsed-tramp-file-name (expand-file-name directory) nil
+ (with-tramp-file-property
+ v localname (format "directory-files-attributes-%s-%s-%s-%s"
+ full match id-format nosort)
(tramp-adb-barf-unless-okay
- v (format "ls -d -l %s" (tramp-shell-quote-argument localname)) "")
+ v (format "%s -a -l %s"
+ (tramp-adb-get-ls-command v)
+ (tramp-shell-quote-argument localname)) "")
(with-current-buffer (tramp-get-buffer v)
(tramp-adb-sh-fix-ls-output)
- (let* ((columns (split-string (buffer-string)))
- (mod-string (nth 0 columns))
- (is-dir (eq ?d (aref mod-string 0)))
- (is-symlink (eq ?l (aref mod-string 0)))
- (symlink-target (and is-symlink (cadr (split-string (buffer-string) "\\( -> \\|\n\\)"))))
- (uid (nth 1 columns))
- (gid (nth 2 columns))
- (date (format "%s %s" (nth 4 columns) (nth 5 columns)))
- (size (string-to-number (nth 3 columns))))
- (list
- (or is-dir symlink-target)
- 1 ;link-count
- ;; no way to handle numeric ids in Androids ash
- (if (eq id-format 'integer) 0 uid)
- (if (eq id-format 'integer) 0 gid)
- '(0 0) ; atime
- (date-to-time date) ; mtime
- '(0 0) ; ctime
- size
- mod-string
- ;; fake
- t 1 1)))))))
+ (let ((result (tramp-do-parse-file-attributes-with-ls
+ v (or id-format 'integer))))
+ (when full
+ (setq result
+ (mapcar
+ (lambda (x)
+ (cons (expand-file-name (car x) directory) (cdr x)))
+ result)))
+ (unless nosort
+ (setq result
+ (sort result (lambda (x y) (string< (car x) (car y))))))
+ (delq nil
+ (mapcar (lambda (x)
+ (if (or (not match) (string-match match (car x)))
+ x))
+ result))))))))
+
+(defun tramp-adb-get-ls-command (vec)
+ (with-tramp-connection-property vec "ls"
+ (tramp-message vec 5 "Finding a suitable `ls' command")
+ (if (zerop (tramp-adb-command-exit-status
+ vec "ls --color=never -al /dev/null"))
+ ;; On CyanogenMod based system BusyBox is used and "ls" output
+ ;; coloring is enabled by default. So we try to disable it
+ ;; when possible.
+ "ls --color=never"
+ "ls")))
+
+(defun tramp-adb-get-toolbox (vec)
+ "Get shell toolbox implementation: `toolbox' for original distributions
+or `busybox' for CyanogenMod based distributions"
+ (with-tramp-connection-property vec "toolbox"
+ (tramp-message vec 5 "Checking shell toolbox implementation")
+ (cond
+ ((zerop (tramp-adb-command-exit-status vec "busybox")) 'busybox)
+ ((zerop (tramp-adb-command-exit-status vec "toolbox")) 'toolbox)
+ (t 'unknown))))
(defun tramp-adb--gnu-switches-to-ash
(switches)
;; FIXME: Warning about removed switches (long and non-dash).
(delq nil
(mapcar
- (lambda (s) (and (not (string-match "\\(^--\\|^[^-]\\)" s)) s))
+ (lambda (s)
+ (and (not (string-match "\\(^--\\|^[^-]\\)" s)) s))
switches))))))
(defun tramp-adb-handle-insert-directory
(switch-t (member "-t" switches))
(switches (mapconcat 'identity (remove "-t" switches) " ")))
(tramp-adb-barf-unless-okay
- v (format "ls %s %s" switches name)
+ v (format "%s %s %s" (tramp-adb-get-ls-command v) switches name)
"Cannot insert directory listing: %s" filename)
(unless switch-d
;; We insert also filename/. and filename/.., because "ls" doesn't.
(narrow-to-region (point) (point))
(ignore-errors
(tramp-adb-barf-unless-okay
- v (format "ls -d %s %s %s"
+ v (format "%s -d %s %s %s"
+ (tramp-adb-get-ls-command v)
switches
(concat (file-name-as-directory name) ".")
(concat (file-name-as-directory name) ".."))
(insert-buffer-substring (tramp-get-buffer v))))
(defun tramp-adb-sh-fix-ls-output (&optional sort-by-time)
- "Androids ls command doesn't insert size column for directories: Emacs dired can't find files. Insert dummy 0 in empty size columns."
+ "Insert dummy 0 in empty size columns.
+Androids \"ls\" command doesn't insert size column for directories:
+Emacs dired can't find files."
(save-excursion
;; Insert missing size.
(goto-char (point-min))
- (while (search-forward-regexp "[[:space:]]\\([[:space:]][0-9]\\{4\\}-[0-9][0-9]-[0-9][0-9][[:space:]]\\)" nil t)
+ (while
+ (search-forward-regexp
+ "[[:space:]]\\([[:space:]][0-9]\\{4\\}-[0-9][0-9]-[0-9][0-9][[:space:]]\\)" nil t)
(replace-match "0\\1" "\\1" nil)
;; Insert missing "/".
(when (looking-at "[0-9][0-9]:[0-9][0-9][[:space:]]+$")
(setq time-a (apply 'encode-time (parse-time-string (match-string 0 a))))
(string-match tramp-adb-ls-date-regexp b)
(setq time-b (apply 'encode-time (parse-time-string (match-string 0 b))))
- (time-less-p time-b time-a)))
+ (tramp-time-less-p time-b time-a)))
(defun tramp-adb-ls-output-name-less-p (a b)
"Sort \"ls\" output by name, ascending."
(with-tramp-file-property v localname "file-name-all-completions"
(save-match-data
(tramp-adb-send-command
- v (format "ls %s" (tramp-shell-quote-argument localname)))
+ v (format "%s %s"
+ (tramp-adb-get-ls-command v)
+ (tramp-shell-quote-argument localname)))
(mapcar
(lambda (f)
(if (file-directory-p f)
v (format "chmod %s %s" (tramp-compat-decimal-to-octal mode) localname)
"Error while changing file's mode %s" filename)))
+(defun tramp-adb-handle-set-file-times (filename &optional time)
+ "Like `set-file-times' for Tramp files."
+ (with-parsed-tramp-file-name filename nil
+ (tramp-flush-file-property v localname)
+ (let ((time (if (or (null time) (equal time '(0 0)))
+ (current-time)
+ time)))
+ (tramp-adb-command-exit-status
+ ;; use shell arithmetic because of Emacs integer size limit
+ v (format "touch -t $(( %d * 65536 + %d )) %s"
+ (car time) (cadr time)
+ (tramp-shell-quote-argument localname))))))
+
(defun tramp-adb-handle-copy-file
(filename newname &optional ok-if-already-exists keep-date
- preserve-uid-gid preserve-selinux-context)
+ preserve-uid-gid preserve-extended-attributes)
"Like `copy-file' for Tramp files.
-PRESERVE-UID-GID and PRESERVE-SELINUX-CONTEXT are completely ignored."
+PRESERVE-UID-GID and PRESERVE-EXTENDED-ATTRIBUTES are completely ignored."
(setq filename (expand-file-name filename)
newname (expand-file-name newname))
(if (file-directory-p filename)
- (copy-directory filename newname keep-date t)
+ (tramp-file-name-handler 'copy-directory filename newname keep-date t)
(with-tramp-progress-reporter
(tramp-dissect-file-name (if (file-remote-p filename) filename newname))
0 (format "Copying %s to %s" filename newname)
(tramp-flush-file-property v localname)
;; Short track.
(tramp-adb-barf-unless-okay
- v (format "mv %s %s" (file-remote-p filename 'localname) localname)
+ v (format
+ "mv %s %s"
+ (tramp-file-name-handler 'file-remote-p filename 'localname)
+ localname)
"Error renaming %s to %s" filename newname))
;; Rename by copy.
;; directory.
(condition-case nil
(progn
- (setq ret 0
- ret
- (tramp-adb-barf-unless-okay
- v (format "(cd %s; %s)"
- (tramp-shell-quote-argument localname)
- command)
- ""))
+ (setq ret 0)
+ (tramp-adb-barf-unless-okay
+ v (format "(cd %s; %s)"
+ (tramp-shell-quote-argument localname) command)
+ "")
;; We should show the output anyway.
(when outbuf
(with-current-buffer outbuf
(tramp-compat-funcall 'display-message-or-buffer output-buffer)
(pop-to-buffer output-buffer))))))))
-;; We use BUFFER also as connection buffer during setup. Because of
+;; We use BUFFER also as connection buffer during setup. Because of
;; this, its original contents must be saved, and restored once
;; connection has been setup.
(defun tramp-adb-handle-start-file-process (name buffer program &rest args)
"Like `start-file-process' for Tramp files."
(with-parsed-tramp-file-name default-directory nil
- ;; When PROGRAM is nil, we just provide a tty.
+ ;; When PROGRAM is nil, we should provide a tty. This is not
+ ;; possible here.
+ (unless (stringp program)
+ (tramp-error v 'file-error "PROGRAM must be a string"))
+
(let ((command
- (when (stringp program)
- (format "cd %s; %s"
- (tramp-shell-quote-argument localname)
- (mapconcat 'tramp-shell-quote-argument
- (cons program args) " "))))
+ (format "cd %s; %s"
+ (tramp-shell-quote-argument localname)
+ (mapconcat 'tramp-shell-quote-argument
+ (cons program args) " ")))
(tramp-process-connection-type
(or (null program) tramp-process-connection-type))
(bmp (and (buffer-live-p buffer) (buffer-modified-p buffer)))
(name1 name)
(i 0))
- (unwind-protect
- (save-excursion
- (save-restriction
- (unless buffer
- ;; BUFFER can be nil. We use a temporary buffer.
- (setq buffer (generate-new-buffer tramp-temp-buffer-name)))
- (while (get-process name1)
- ;; NAME must be unique as process name.
- (setq i (1+ i)
- name1 (format "%s<%d>" name i)))
- (setq name name1)
- ;; Set the new process properties.
- (tramp-set-connection-property v "process-name" name)
- (tramp-set-connection-property v "process-buffer" buffer)
- ;; Activate narrowing in order to save BUFFER contents.
- ;; Clear also the modification time; otherwise we might
- ;; be interrupted by `verify-visited-file-modtime'.
- (with-current-buffer (tramp-get-connection-buffer v)
- (let ((buffer-undo-list t))
+
+ (unless buffer
+ ;; BUFFER can be nil. We use a temporary buffer.
+ (setq buffer (generate-new-buffer tramp-temp-buffer-name)))
+ (while (get-process name1)
+ ;; NAME must be unique as process name.
+ (setq i (1+ i)
+ name1 (format "%s<%d>" name i)))
+ (setq name name1)
+ ;; Set the new process properties.
+ (tramp-set-connection-property v "process-name" name)
+ (tramp-set-connection-property v "process-buffer" buffer)
+
+ (with-current-buffer (tramp-get-connection-buffer v)
+ (unwind-protect
+ ;; We catch this event. Otherwise, `start-process' could
+ ;; be called on the local host.
+ (save-excursion
+ (save-restriction
+ ;; Activate narrowing in order to save BUFFER
+ ;; contents. Clear also the modification time;
+ ;; otherwise we might be interrupted by
+ ;; `verify-visited-file-modtime'.
+ (let ((buffer-undo-list t)
+ (buffer-read-only nil)
+ (mark (point)))
(clear-visited-file-modtime)
(narrow-to-region (point-max) (point-max))
- (if command
- ;; Send the command.
- (tramp-adb-send-command v command)
- ;; Open the connection.
- (tramp-adb-maybe-open-connection v))))
- (let ((p (tramp-get-connection-process v)))
- ;; Set sentinel and query flag for this process.
- (tramp-set-connection-property p "vector" v)
- (set-process-sentinel p 'tramp-process-sentinel)
- (tramp-compat-set-process-query-on-exit-flag p t)
- ;; Return process.
- p)))
- ;; Save exit.
- (with-current-buffer (tramp-get-connection-buffer v)
+ ;; We call `tramp-adb-maybe-open-connection', in
+ ;; order to cleanup the prompt afterwards.
+ (tramp-adb-maybe-open-connection v)
+ (widen)
+ (delete-region mark (point))
+ (narrow-to-region (point-max) (point-max))
+ ;; Send the command.
+ (let ((tramp-adb-prompt (regexp-quote command)))
+ (tramp-adb-send-command v command))
+ (let ((p (tramp-get-connection-process v)))
+ ;; Set query flag and process marker for this
+ ;; process. We ignore errors, because the process
+ ;; could have finished already.
+ (ignore-errors
+ (tramp-compat-set-process-query-on-exit-flag p t)
+ (set-marker (process-mark p) (point)))
+ ;; Return process.
+ p))))
+
+ ;; Save exit.
(if (string-match tramp-temp-buffer-name (buffer-name))
- (progn
+ (ignore-errors
(set-process-buffer (tramp-get-connection-process v) nil)
(kill-buffer (current-buffer)))
- (set-buffer-modified-p bmp)))
- (tramp-set-connection-property v "process-name" nil)
- (tramp-set-connection-property v "process-buffer" nil)))))
-
-;; Android < 4 doesn't provide test command.
-
-(defun tramp-adb-handle-file-exists-p (filename)
- "Like `file-exists-p' for Tramp files."
- (with-parsed-tramp-file-name filename nil
- (with-tramp-file-property v localname "file-exists-p"
- (file-attributes filename))))
+ (set-buffer-modified-p bmp))
+ (tramp-set-connection-property v "process-name" nil)
+ (tramp-set-connection-property v "process-buffer" nil))))))
;; Helper functions.
(defun tramp-adb-execute-adb-command (vec &rest args)
"Returns nil on success error-output on failure."
- (when (tramp-file-name-host vec)
+ (when (> (length (tramp-file-name-host vec)) 0)
(setq args (append (list "-s" (tramp-file-name-host vec)) args)))
(with-temp-buffer
(prog1
- (unless (zerop (apply 'call-process (tramp-adb-program) nil t nil args))
+ (unless (zerop (apply 'call-process tramp-adb-program nil t nil args))
(buffer-string))
(tramp-message
vec 6 "%s %s\n%s"
- (tramp-adb-program) (mapconcat 'identity args " ") (buffer-string)))))
+ tramp-adb-program (mapconcat 'identity args " ") (buffer-string)))))
(defun tramp-adb-find-test-command (vec)
"Checks, whether the ash has a builtin \"test\" command.
(while (re-search-forward "\r+$" nil t)
(replace-match "" nil nil)))))
-(defun tramp-adb-barf-unless-okay (vec command fmt &rest args)
- "Run COMMAND, check exit status, throw error if exit status not okay.
-FMT and ARGS are passed to `error'."
- (tramp-adb-send-command vec (format "%s; echo tramp_exit_status $?" command))
- (with-current-buffer (tramp-get-connection-buffer vec)
- (goto-char (point-max))
- (unless (re-search-backward "tramp_exit_status [0-9]+" nil t)
- (tramp-error
- vec 'file-error "Couldn't find exit status of `%s'" command))
- (skip-chars-forward "^ ")
- (unless (zerop (read (current-buffer)))
- (apply 'tramp-error vec 'file-error fmt args))
- (let (buffer-read-only)
- (delete-region (match-beginning 0) (point-max)))))
-
(defun tramp-adb-command-exit-status
(vec command)
"Run COMMAND and return its exit status.
Sends `echo $?' along with the COMMAND for checking the exit status. If
COMMAND is nil, just sends `echo $?'. Returns the exit status found."
- (tramp-adb-send-command vec (format "%s; echo tramp_exit_status $?" command))
+ (tramp-adb-send-command
+ vec (if command
+ (format "%s; echo tramp_exit_status $?" command)
+ "echo tramp_exit_status $?"))
(with-current-buffer (tramp-get-connection-buffer vec)
(goto-char (point-max))
(unless (re-search-backward "tramp_exit_status [0-9]+" nil t)
(tramp-error
vec 'file-error "Couldn't find exit status of `%s'" command))
(skip-chars-forward "^ ")
- (read (current-buffer))))
+ (prog1
+ (read (current-buffer))
+ (let (buffer-read-only)
+ (delete-region (match-beginning 0) (point-max))))))
+
+(defun tramp-adb-barf-unless-okay (vec command fmt &rest args)
+ "Run COMMAND, check exit status, throw error if exit status not okay.
+FMT and ARGS are passed to `error'."
+ (unless (zerop (tramp-adb-command-exit-status vec command))
+ (apply 'tramp-error vec 'file-error fmt args)))
(defun tramp-adb-wait-for-output (proc &optional timeout)
"Wait for output from remote command."
(if (tramp-wait-for-regexp proc timeout tramp-adb-prompt)
(let (buffer-read-only)
(goto-char (point-min))
- (when (re-search-forward tramp-adb-prompt (point-at-eol) t)
+ ;; ADB terminal sends "^H" sequences.
+ (when (re-search-forward "<\b+" (point-at-eol) t)
(forward-line 1)
(delete-region (point-min) (point)))
;; Delete the prompt.
+ (goto-char (point-min))
+ (when (re-search-forward tramp-adb-prompt (point-at-eol) t)
+ (forward-line 1)
+ (delete-region (point-min) (point)))
(goto-char (point-max))
(re-search-backward tramp-adb-prompt nil t)
(delete-region (point) (point-max)))
Does not do anything if a connection is already open, but re-opens the
connection if a previous connection has died for some reason."
(let* ((buf (tramp-get-connection-buffer vec))
- (p (get-buffer-process buf)))
+ (p (get-buffer-process buf))
+ (host (tramp-file-name-host vec))
+ (user (tramp-file-name-user vec))
+ (devices (mapcar 'cadr (tramp-adb-parse-device-names nil))))
(unless
(and p (processp p) (memq (process-status p) '(run open)))
(save-match-data
(when (and p (processp p)) (delete-process p))
+ (if (not devices)
+ (tramp-error vec 'file-error "No device connected"))
+ (if (and (> (length host) 0) (not (member host devices)))
+ (tramp-error vec 'file-error "Device %s not connected" host))
+ (if (and (> (length devices) 1) (zerop (length host)))
+ (tramp-error
+ vec 'file-error
+ "Multiple Devices connected: No Host/Device specified"))
(with-tramp-progress-reporter vec 3 "Opening adb shell connection"
(let* ((coding-system-for-read 'utf-8-dos) ;is this correct?
(process-connection-type tramp-process-connection-type)
- (args (if (tramp-file-name-host vec)
- (list "-s" (tramp-file-name-host vec) "shell")
+ (args (if (> (length host) 0)
+ (list "-s" host "shell")
(list "shell")))
(p (let ((default-directory
(tramp-compat-temporary-file-directory)))
(apply 'start-process (tramp-get-connection-name vec) buf
- (tramp-adb-program) args))))
+ tramp-adb-program args))))
(tramp-message
vec 6 "%s" (mapconcat 'identity (process-command p) " "))
;; Wait for initial prompt.
- (tramp-adb-wait-for-output p)
+ (tramp-adb-wait-for-output p 30)
(unless (eq 'run (process-status p))
(tramp-error vec 'file-error "Terminated!"))
- (set-process-query-on-exit-flag p nil)))))))
+ (tramp-compat-set-process-query-on-exit-flag p nil)
+
+ ;; Check whether the properties have been changed. If
+ ;; yes, this is a strong indication that we must expire all
+ ;; connection properties. We start again.
+ (tramp-message vec 5 "Checking system information")
+ (tramp-adb-send-command
+ vec "echo \\\"`getprop ro.product.model` `getprop ro.product.version` `getprop ro.build.version.release`\\\"")
+ (let ((old-getprop
+ (tramp-get-connection-property vec "getprop" nil))
+ (new-getprop
+ (tramp-set-connection-property
+ vec "getprop"
+ (with-current-buffer (tramp-get-connection-buffer vec)
+ ;; Read the expression.
+ (goto-char (point-min))
+ (read (current-buffer))))))
+ (when (and (stringp old-getprop)
+ (not (string-equal old-getprop new-getprop)))
+ (tramp-cleanup vec)
+ (tramp-message
+ vec 3
+ "Connection reset, because remote host changed from `%s' to `%s'"
+ old-getprop new-getprop)
+ (tramp-adb-maybe-open-connection vec)))
+
+ ;; Change user if indicated.
+ (when user
+ (tramp-adb-send-command vec (format "su %s" user))
+ (unless (zerop (tramp-adb-command-exit-status vec nil))
+ (delete-process p)
+ (tramp-error vec 'file-error "Cannot switch to user %s" user)))
+
+ ;; Set "remote-path" connection property. This is needed
+ ;; for eshell.
+ (tramp-adb-send-command vec "echo \\\"$PATH\\\"")
+ (tramp-set-connection-property
+ vec "remote-path"
+ (split-string
+ (with-current-buffer (tramp-get-connection-buffer vec)
+ ;; Read the expression.
+ (goto-char (point-min))
+ (read (current-buffer)))
+ ":" 'omit-nulls))))))))
(provide 'tramp-adb)
;;; tramp-adb.el ends here