Commit | Line | Data |
---|---|---|
0ec3f7ea JH |
1 | ;;; tramp-adb.el --- Functions for calling Android Debug Bridge from Tramp |
2 | ||
ba318903 | 3 | ;; Copyright (C) 2011-2014 Free Software Foundation, Inc. |
0ec3f7ea JH |
4 | |
5 | ;; Author: Juergen Hoetzel <juergen@archlinux.org> | |
6 | ;; Keywords: comm, processes | |
7 | ;; Package: tramp | |
8 | ||
9 | ;; This file is part of GNU Emacs. | |
10 | ||
11 | ;; GNU Emacs is free software: you can redistribute it and/or modify | |
12 | ;; it under the terms of the GNU General Public License as published by | |
13 | ;; the Free Software Foundation, either version 3 of the License, or | |
14 | ;; (at your option) any later version. | |
15 | ||
16 | ;; GNU Emacs is distributed in the hope that it will be useful, | |
17 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
19 | ;; GNU General Public License for more details. | |
20 | ||
21 | ;; You should have received a copy of the GNU General Public License | |
22 | ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. | |
23 | ||
24 | ;;; Commentary: | |
25 | ||
779451da MA |
26 | ;; The Android Debug Bridge "adb" must be installed on your local |
27 | ;; machine. If it is not in your $PATH, add the following form into | |
28 | ;; your .emacs: | |
0ec3f7ea | 29 | ;; |
779451da | 30 | ;; (setq tramp-adb-program "/path/to/adb") |
0ec3f7ea JH |
31 | ;; |
32 | ;; Due to security it is not possible to access non-root devices. | |
33 | ||
34 | ;;; Code: | |
35 | ||
36 | (require 'tramp) | |
a36e2d26 | 37 | (require 'time-date) |
0ec3f7ea | 38 | |
b74f0d96 | 39 | ;; Pacify byte-compiler. |
f95527c8 | 40 | (defvar directory-sep-char) |
0ec3f7ea | 41 | |
779451da MA |
42 | (defcustom tramp-adb-program "adb" |
43 | "Name of the Android Debug Bridge program." | |
44 | :group 'tramp | |
51b890ac | 45 | :version "24.4" |
779451da | 46 | :type 'string) |
0ec3f7ea JH |
47 | |
48 | ;;;###tramp-autoload | |
49 | (defconst tramp-adb-method "adb" | |
50 | "*When this method name is used, forward all calls to Android Debug Bridge.") | |
51 | ||
bd8c13f9 | 52 | (defcustom tramp-adb-prompt |
7d11fc27 | 53 | "^\\(?:[[:digit:]]*|?\\)?\\(?:[[:alnum:]]*@[[:alnum:]]*[^#\\$]*\\)?[#\\$][[:space:]]" |
51b890ac | 54 | "Regexp used as prompt in almquist shell." |
0ec3f7ea | 55 | :type 'string |
51b890ac MA |
56 | :version "24.4" |
57 | :group 'tramp) | |
0ec3f7ea | 58 | |
bd8c13f9 JH |
59 | (defconst tramp-adb-ls-date-regexp |
60 | "[[:space:]][0-9]\\{4\\}-[0-9][0-9]-[0-9][0-9][[:space:]][0-9][0-9]:[0-9][0-9][[:space:]]") | |
0ec3f7ea | 61 | |
838cf298 MA |
62 | (defconst tramp-adb-ls-toolbox-regexp |
63 | (concat | |
64 | "^[[:space:]]*\\([-[:alpha:]]+\\)" ; \1 permissions | |
65 | "[[:space:]]*\\([^[:space:]]+\\)" ; \2 username | |
66 | "[[:space:]]+\\([^[:space:]]+\\)" ; \3 group | |
b6cfbcd0 | 67 | "[[:space:]]+\\([[:digit:]]+\\)" ; \4 size |
838cf298 MA |
68 | "[[:space:]]+\\([-[:digit:]]+[[:space:]][:[:digit:]]+\\)" ; \5 date |
69 | "[[:space:]]+\\(.*\\)$")) ; \6 filename | |
70 | ||
0ec3f7ea | 71 | ;;;###tramp-autoload |
b49eebcc MA |
72 | (add-to-list 'tramp-methods |
73 | `(,tramp-adb-method | |
74 | (tramp-tmpdir "/data/local/tmp"))) | |
75 | ||
76 | ;;;###tramp-autoload | |
77 | (add-to-list 'tramp-default-host-alist `(,tramp-adb-method nil "")) | |
0ec3f7ea JH |
78 | |
79 | ;;;###tramp-autoload | |
80 | (eval-after-load 'tramp | |
81 | '(tramp-set-completion-function | |
82 | tramp-adb-method '((tramp-adb-parse-device-names "")))) | |
83 | ||
84 | ;;;###tramp-autoload | |
85 | (add-to-list 'tramp-foreign-file-name-handler-alist | |
86 | (cons 'tramp-adb-file-name-p 'tramp-adb-file-name-handler)) | |
87 | ||
88 | (defconst tramp-adb-file-name-handler-alist | |
a43dc424 MA |
89 | '((access-file . ignore) |
90 | (add-name-to-file . tramp-adb-handle-copy-file) | |
91 | ;; `byte-compiler-base-file-name' performed by default handler. | |
92 | ;; `copy-directory' performed by default handler. | |
93 | (copy-file . tramp-adb-handle-copy-file) | |
94 | (delete-directory . tramp-adb-handle-delete-directory) | |
95 | (delete-file . tramp-adb-handle-delete-file) | |
96 | ;; `diff-latest-backup-file' performed by default handler. | |
97 | (directory-file-name . tramp-handle-directory-file-name) | |
98 | (directory-files . tramp-handle-directory-files) | |
99 | (directory-files-and-attributes | |
100 | . tramp-adb-handle-directory-files-and-attributes) | |
101 | (dired-call-process . ignore) | |
102 | (dired-compress-file . ignore) | |
0ec3f7ea | 103 | (dired-uncache . tramp-handle-dired-uncache) |
a43dc424 MA |
104 | (expand-file-name . tramp-adb-handle-expand-file-name) |
105 | (file-accessible-directory-p . tramp-handle-file-accessible-directory-p) | |
106 | (file-acl . ignore) | |
107 | (file-attributes . tramp-adb-handle-file-attributes) | |
108 | (file-directory-p . tramp-adb-handle-file-directory-p) | |
109 | ;; `file-equal-p' performed by default handler. | |
110 | ;; FIXME: This is too sloppy. | |
111 | (file-executable-p . tramp-handle-file-exists-p) | |
112 | (file-exists-p . tramp-handle-file-exists-p) | |
113 | ;; `file-in-directory-p' performed by default handler. | |
114 | (file-local-copy . tramp-adb-handle-file-local-copy) | |
115 | (file-modes . tramp-handle-file-modes) | |
116 | (file-name-all-completions . tramp-adb-handle-file-name-all-completions) | |
0ec3f7ea JH |
117 | (file-name-as-directory . tramp-handle-file-name-as-directory) |
118 | (file-name-completion . tramp-handle-file-name-completion) | |
0ec3f7ea JH |
119 | (file-name-directory . tramp-handle-file-name-directory) |
120 | (file-name-nondirectory . tramp-handle-file-name-nondirectory) | |
a43dc424 | 121 | ;; `file-name-sans-versions' performed by default handler. |
0ec3f7ea | 122 | (file-newer-than-file-p . tramp-handle-file-newer-than-file-p) |
a43dc424 MA |
123 | (file-notify-add-watch . tramp-handle-file-notify-add-watch) |
124 | (file-notify-rm-watch . tramp-handle-file-notify-rm-watch) | |
125 | (file-ownership-preserved-p . ignore) | |
126 | (file-readable-p . tramp-handle-file-exists-p) | |
0ec3f7ea JH |
127 | (file-regular-p . tramp-handle-file-regular-p) |
128 | (file-remote-p . tramp-handle-file-remote-p) | |
a43dc424 | 129 | (file-selinux-context . ignore) |
0ec3f7ea | 130 | (file-symlink-p . tramp-handle-file-symlink-p) |
a43dc424 | 131 | (file-truename . tramp-adb-handle-file-truename) |
0ec3f7ea | 132 | (file-writable-p . tramp-adb-handle-file-writable-p) |
0ec3f7ea | 133 | (find-backup-file-name . tramp-handle-find-backup-file-name) |
a43dc424 MA |
134 | ;; `find-file-noselect' performed by default handler. |
135 | ;; `get-file-buffer' performed by default handler. | |
c22c1614 | 136 | (insert-directory . tramp-handle-insert-directory) |
0ec3f7ea | 137 | (insert-file-contents . tramp-handle-insert-file-contents) |
a43dc424 | 138 | (load . tramp-handle-load) |
af9ff9e8 | 139 | (make-auto-save-file-name . tramp-handle-make-auto-save-file-name) |
a43dc424 MA |
140 | (make-directory . tramp-adb-handle-make-directory) |
141 | (make-directory-internal . ignore) | |
50bfdd5d | 142 | (make-symbolic-link . tramp-handle-make-symbolic-link) |
a43dc424 MA |
143 | (process-file . tramp-adb-handle-process-file) |
144 | (rename-file . tramp-adb-handle-rename-file) | |
145 | (set-file-acl . ignore) | |
0ec3f7ea | 146 | (set-file-modes . tramp-adb-handle-set-file-modes) |
a43dc424 | 147 | (set-file-selinux-context . ignore) |
62bcf670 | 148 | (set-file-times . tramp-adb-handle-set-file-times) |
a43dc424 | 149 | (set-visited-file-modtime . tramp-handle-set-visited-file-modtime) |
0ec3f7ea | 150 | (shell-command . tramp-adb-handle-shell-command) |
a43dc424 MA |
151 | (start-file-process . tramp-adb-handle-start-file-process) |
152 | (substitute-in-file-name . tramp-handle-substitute-in-file-name) | |
153 | (unhandled-file-name-directory . tramp-handle-unhandled-file-name-directory) | |
154 | (vc-registered . ignore) | |
155 | (verify-visited-file-modtime . tramp-handle-verify-visited-file-modtime) | |
156 | (write-region . tramp-adb-handle-write-region)) | |
0ec3f7ea JH |
157 | "Alist of handler functions for Tramp ADB method.") |
158 | ||
b421decc MA |
159 | ;; It must be a `defsubst' in order to push the whole code into |
160 | ;; tramp-loaddefs.el. Otherwise, there would be recursive autoloading. | |
0ec3f7ea | 161 | ;;;###tramp-autoload |
6ce21463 | 162 | (defsubst tramp-adb-file-name-p (filename) |
0ec3f7ea JH |
163 | "Check if it's a filename for ADB." |
164 | (let ((v (tramp-dissect-file-name filename))) | |
165 | (string= (tramp-file-name-method v) tramp-adb-method))) | |
166 | ||
167 | ;;;###tramp-autoload | |
168 | (defun tramp-adb-file-name-handler (operation &rest args) | |
169 | "Invoke the ADB handler for OPERATION. | |
170 | First arg specifies the OPERATION, second arg is a list of arguments to | |
171 | pass to the OPERATION." | |
b6cfbcd0 | 172 | (let ((fn (assoc operation tramp-adb-file-name-handler-alist))) |
0ec3f7ea JH |
173 | (if fn |
174 | (save-match-data (apply (cdr fn) args)) | |
175 | (tramp-run-real-handler operation args)))) | |
176 | ||
0ec3f7ea | 177 | ;;;###tramp-autoload |
5d89d9d2 | 178 | (defun tramp-adb-parse-device-names (_ignore) |
0ec3f7ea | 179 | "Return a list of (nil host) tuples allowed to access." |
fa550654 MA |
180 | (with-timeout (10) |
181 | (with-temp-buffer | |
67c0a6e6 MA |
182 | ;; `call-process' does not react on timer under MS Windows. |
183 | ;; That's why we use `start-process'. | |
184 | (let ((p (start-process | |
185 | tramp-adb-program (current-buffer) tramp-adb-program "devices")) | |
186 | result) | |
187 | (tramp-compat-set-process-query-on-exit-flag p nil) | |
188 | (while (eq 'run (process-status p)) | |
245aa73e MA |
189 | (accept-process-output p 0.1)) |
190 | (accept-process-output p 0.1) | |
67c0a6e6 MA |
191 | (goto-char (point-min)) |
192 | (while (search-forward-regexp "^\\(\\S-+\\)[[:space:]]+device$" nil t) | |
193 | (add-to-list 'result (list nil (match-string 1)))) | |
194 | result)))) | |
0ec3f7ea JH |
195 | |
196 | (defun tramp-adb-handle-expand-file-name (name &optional dir) | |
197 | "Like `expand-file-name' for Tramp files." | |
198 | ;; If DIR is not given, use DEFAULT-DIRECTORY or "/". | |
199 | (setq dir (or dir default-directory "/")) | |
200 | ;; Unless NAME is absolute, concat DIR and NAME. | |
201 | (unless (file-name-absolute-p name) | |
202 | (setq name (concat (file-name-as-directory dir) name))) | |
203 | ;; If NAME is not a Tramp file, run the real handler. | |
204 | (if (not (tramp-tramp-file-p name)) | |
205 | (tramp-run-real-handler 'expand-file-name (list name nil)) | |
206 | ;; Dissect NAME. | |
207 | (with-parsed-tramp-file-name name nil | |
208 | (unless (tramp-run-real-handler 'file-name-absolute-p (list localname)) | |
209 | (setq localname (concat "/" localname))) | |
210 | ;; Do normal `expand-file-name' (this does "/./" and "/../"). | |
211 | ;; We bind `directory-sep-char' here for XEmacs on Windows, | |
212 | ;; which would otherwise use backslash. `default-directory' is | |
213 | ;; bound, because on Windows there would be problems with UNC | |
214 | ;; shares or Cygwin mounts. | |
215 | (let ((directory-sep-char ?/) | |
216 | (default-directory (tramp-compat-temporary-file-directory))) | |
217 | (tramp-make-tramp-file-name | |
218 | method user host | |
219 | (tramp-drop-volume-letter | |
220 | (tramp-run-real-handler | |
221 | 'expand-file-name (list localname)))))))) | |
222 | ||
223 | (defun tramp-adb-handle-file-directory-p (filename) | |
224 | "Like `file-directory-p' for Tramp files." | |
225 | (car (file-attributes (file-truename filename)))) | |
226 | ||
227 | ;; This is derived from `tramp-sh-handle-file-truename'. Maybe the | |
228 | ;; code could be shared? | |
5d89d9d2 | 229 | (defun tramp-adb-handle-file-truename (filename) |
0ec3f7ea | 230 | "Like `file-truename' for Tramp files." |
245aa73e MA |
231 | (format |
232 | "%s%s" | |
233 | (with-parsed-tramp-file-name (expand-file-name filename) nil | |
234 | (tramp-make-tramp-file-name | |
235 | method user host | |
236 | (with-tramp-file-property v localname "file-truename" | |
237 | (let ((result nil)) ; result steps in reverse order | |
238 | (tramp-message v 4 "Finding true name for `%s'" filename) | |
239 | (let* ((directory-sep-char ?/) | |
240 | (steps (tramp-compat-split-string localname "/")) | |
241 | (localnamedir (tramp-run-real-handler | |
242 | 'file-name-as-directory (list localname))) | |
243 | (is-dir (string= localname localnamedir)) | |
244 | (thisstep nil) | |
245 | (numchase 0) | |
246 | ;; Don't make the following value larger than | |
247 | ;; necessary. People expect an error message in a | |
248 | ;; timely fashion when something is wrong; otherwise | |
249 | ;; they might think that Emacs is hung. Of course, | |
250 | ;; correctness has to come first. | |
251 | (numchase-limit 20) | |
252 | symlink-target) | |
253 | (while (and steps (< numchase numchase-limit)) | |
254 | (setq thisstep (pop steps)) | |
255 | (tramp-message | |
256 | v 5 "Check %s" | |
257 | (mapconcat 'identity | |
258 | (append '("") (reverse result) (list thisstep)) | |
259 | "/")) | |
260 | (setq symlink-target | |
261 | (nth 0 (file-attributes | |
262 | (tramp-make-tramp-file-name | |
263 | method user host | |
264 | (mapconcat 'identity | |
265 | (append '("") | |
266 | (reverse result) | |
267 | (list thisstep)) | |
268 | "/"))))) | |
269 | (cond ((string= "." thisstep) | |
270 | (tramp-message v 5 "Ignoring step `.'")) | |
271 | ((string= ".." thisstep) | |
272 | (tramp-message v 5 "Processing step `..'") | |
273 | (pop result)) | |
274 | ((stringp symlink-target) | |
275 | ;; It's a symlink, follow it. | |
276 | (tramp-message v 5 "Follow symlink to %s" symlink-target) | |
277 | (setq numchase (1+ numchase)) | |
278 | (when (file-name-absolute-p symlink-target) | |
279 | (setq result nil)) | |
280 | ;; If the symlink was absolute, we'll get a string | |
281 | ;; like "/user@host:/some/target"; extract the | |
282 | ;; "/some/target" part from it. | |
283 | (when (tramp-tramp-file-p symlink-target) | |
284 | (unless (tramp-equal-remote filename symlink-target) | |
285 | (tramp-error | |
286 | v 'file-error | |
287 | "Symlink target `%s' on wrong host" symlink-target)) | |
288 | (setq symlink-target localname)) | |
289 | (setq steps | |
290 | (append (tramp-compat-split-string | |
291 | symlink-target "/") | |
292 | steps))) | |
293 | (t | |
294 | ;; It's a file. | |
295 | (setq result (cons thisstep result))))) | |
296 | (when (>= numchase numchase-limit) | |
297 | (tramp-error | |
298 | v 'file-error | |
299 | "Maximum number (%d) of symlinks exceeded" numchase-limit)) | |
300 | (setq result (reverse result)) | |
301 | ;; Combine list to form string. | |
302 | (setq result | |
303 | (if result | |
304 | (mapconcat 'identity (cons "" result) "/") | |
305 | "/")) | |
306 | (when (and is-dir (or (string= "" result) | |
307 | (not (string= (substring result -1) "/")))) | |
308 | (setq result (concat result "/")))) | |
309 | ||
310 | (tramp-message v 4 "True name of `%s' is `%s'" localname result) | |
311 | result)))) | |
312 | ||
313 | ;; Preserve trailing "/". | |
314 | (if (string-equal (file-name-nondirectory filename) "") "/" ""))) | |
0ec3f7ea JH |
315 | |
316 | (defun tramp-adb-handle-file-attributes (filename &optional id-format) | |
317 | "Like `file-attributes' for Tramp files." | |
318 | (unless id-format (setq id-format 'integer)) | |
c22c1614 MA |
319 | (with-parsed-tramp-file-name filename nil |
320 | (with-tramp-file-property | |
321 | v localname (format "file-attributes-%s" id-format) | |
322 | (and | |
323 | (tramp-adb-send-command-and-check | |
324 | v (format "%s -d -l %s" | |
325 | (tramp-adb-get-ls-command v) | |
326 | (tramp-shell-quote-argument localname))) | |
327 | (with-current-buffer (tramp-get-buffer v) | |
328 | (tramp-adb-sh-fix-ls-output) | |
329 | (cdar (tramp-do-parse-file-attributes-with-ls v id-format))))))) | |
838cf298 MA |
330 | |
331 | (defun tramp-do-parse-file-attributes-with-ls (vec &optional id-format) | |
332 | "Parse `file-attributes' for Tramp files using the ls(1) command." | |
333 | (with-current-buffer (tramp-get-buffer vec) | |
334 | (goto-char (point-min)) | |
335 | (let ((file-properties nil)) | |
336 | (while (re-search-forward tramp-adb-ls-toolbox-regexp nil t) | |
337 | (let* ((mod-string (match-string 1)) | |
338 | (is-dir (eq ?d (aref mod-string 0))) | |
339 | (is-symlink (eq ?l (aref mod-string 0))) | |
340 | (uid (match-string 2)) | |
341 | (gid (match-string 3)) | |
342 | (size (string-to-number (match-string 4))) | |
343 | (date (match-string 5)) | |
344 | (name (match-string 6)) | |
345 | (symlink-target | |
346 | (and is-symlink | |
347 | (cadr (split-string name "\\( -> \\|\n\\)"))))) | |
348 | (push (list | |
62bcf670 JH |
349 | (if is-symlink |
350 | (car (split-string name "\\( -> \\|\n\\)")) | |
351 | name) | |
838cf298 MA |
352 | (or is-dir symlink-target) |
353 | 1 ;link-count | |
354 | ;; no way to handle numeric ids in Androids ash | |
355 | (if (eq id-format 'integer) 0 uid) | |
356 | (if (eq id-format 'integer) 0 gid) | |
357 | '(0 0) ; atime | |
358 | (date-to-time date) ; mtime | |
359 | '(0 0) ; ctime | |
360 | size | |
361 | mod-string | |
362 | ;; fake | |
363 | t 1 | |
f4566fe9 | 364 | (tramp-get-device vec)) |
838cf298 MA |
365 | file-properties))) |
366 | file-properties))) | |
367 | ||
368 | (defun tramp-adb-handle-directory-files-and-attributes | |
369 | (directory &optional full match nosort id-format) | |
370 | "Like `directory-files-and-attributes' for Tramp files." | |
371 | (when (file-directory-p directory) | |
372 | (with-parsed-tramp-file-name (expand-file-name directory) nil | |
373 | (with-tramp-file-property | |
f4566fe9 | 374 | v localname (format "directory-files-attributes-%s-%s-%s-%s" |
838cf298 | 375 | full match id-format nosort) |
838cf298 | 376 | (with-current-buffer (tramp-get-buffer v) |
c22c1614 MA |
377 | (when (tramp-adb-send-command-and-check |
378 | v (format "%s -a -l %s" | |
379 | (tramp-adb-get-ls-command v) | |
380 | (tramp-shell-quote-argument localname))) | |
381 | ;; We insert also filename/. and filename/.., because "ls" doesn't. | |
382 | (narrow-to-region (point) (point)) | |
383 | (tramp-adb-send-command | |
384 | v (format "%s -d -a -l %s %s" | |
385 | (tramp-adb-get-ls-command v) | |
386 | (concat (file-name-as-directory localname) ".") | |
387 | (concat (file-name-as-directory localname) ".."))) | |
388 | (widen)) | |
838cf298 | 389 | (tramp-adb-sh-fix-ls-output) |
f4566fe9 MA |
390 | (let ((result (tramp-do-parse-file-attributes-with-ls |
391 | v (or id-format 'integer)))) | |
838cf298 | 392 | (when full |
f4566fe9 MA |
393 | (setq result |
394 | (mapcar | |
395 | (lambda (x) | |
396 | (cons (expand-file-name (car x) directory) (cdr x))) | |
397 | result))) | |
838cf298 | 398 | (unless nosort |
f4566fe9 MA |
399 | (setq result |
400 | (sort result (lambda (x y) (string< (car x) (car y)))))) | |
838cf298 MA |
401 | (delq nil |
402 | (mapcar (lambda (x) | |
403 | (if (or (not match) (string-match match (car x))) | |
404 | x)) | |
405 | result)))))))) | |
0ec3f7ea | 406 | |
bd8c13f9 JH |
407 | (defun tramp-adb-get-ls-command (vec) |
408 | (with-tramp-connection-property vec "ls" | |
409 | (tramp-message vec 5 "Finding a suitable `ls' command") | |
c22c1614 | 410 | (if (tramp-adb-send-command-and-check vec "ls --color=never -al /dev/null") |
bd8c13f9 JH |
411 | ;; On CyanogenMod based system BusyBox is used and "ls" output |
412 | ;; coloring is enabled by default. So we try to disable it | |
413 | ;; when possible. | |
414 | "ls --color=never" | |
415 | "ls"))) | |
416 | ||
0ec3f7ea JH |
417 | (defun tramp-adb--gnu-switches-to-ash |
418 | (switches) | |
419 | "Almquist shell can't handle multiple arguments. | |
420 | Convert (\"-al\") to (\"-a\" \"-l\"). Remove arguments like \"--dired\"." | |
421 | (split-string | |
422 | (apply 'concat | |
423 | (mapcar (lambda (s) | |
af9ff9e8 | 424 | (tramp-compat-replace-regexp-in-string |
0ec3f7ea | 425 | "\\(.\\)" " -\\1" |
af9ff9e8 | 426 | (tramp-compat-replace-regexp-in-string "^-" "" s))) |
0ec3f7ea JH |
427 | ;; FIXME: Warning about removed switches (long and non-dash). |
428 | (delq nil | |
429 | (mapcar | |
bd8c13f9 JH |
430 | (lambda (s) |
431 | (and (not (string-match "\\(^--\\|^[^-]\\)" s)) s)) | |
0ec3f7ea JH |
432 | switches)))))) |
433 | ||
0ec3f7ea | 434 | (defun tramp-adb-sh-fix-ls-output (&optional sort-by-time) |
bd8c13f9 JH |
435 | "Insert dummy 0 in empty size columns. |
436 | Androids \"ls\" command doesn't insert size column for directories: | |
437 | Emacs dired can't find files." | |
0ec3f7ea JH |
438 | (save-excursion |
439 | ;; Insert missing size. | |
440 | (goto-char (point-min)) | |
bd8c13f9 JH |
441 | (while |
442 | (search-forward-regexp | |
443 | "[[:space:]]\\([[:space:]][0-9]\\{4\\}-[0-9][0-9]-[0-9][0-9][[:space:]]\\)" nil t) | |
0ec3f7ea JH |
444 | (replace-match "0\\1" "\\1" nil) |
445 | ;; Insert missing "/". | |
446 | (when (looking-at "[0-9][0-9]:[0-9][0-9][[:space:]]+$") | |
447 | (end-of-line) | |
448 | (insert "/"))) | |
449 | ;; Sort entries. | |
450 | (let* ((lines (split-string (buffer-string) "\n" t)) | |
451 | (sorted-lines | |
452 | (sort | |
453 | lines | |
454 | (if sort-by-time | |
455 | 'tramp-adb-ls-output-time-less-p | |
456 | 'tramp-adb-ls-output-name-less-p)))) | |
457 | (delete-region (point-min) (point-max)) | |
458 | (insert " " (mapconcat 'identity sorted-lines "\n "))) | |
459 | ;; Add final newline. | |
460 | (goto-char (point-max)) | |
461 | (unless (= (point) (line-beginning-position)) | |
462 | (insert "\n")))) | |
463 | ||
464 | ||
465 | (defun tramp-adb-ls-output-time-less-p (a b) | |
466 | "Sort \"ls\" output by time, descending." | |
467 | (let (time-a time-b) | |
468 | (string-match tramp-adb-ls-date-regexp a) | |
469 | (setq time-a (apply 'encode-time (parse-time-string (match-string 0 a)))) | |
470 | (string-match tramp-adb-ls-date-regexp b) | |
471 | (setq time-b (apply 'encode-time (parse-time-string (match-string 0 b)))) | |
a36e2d26 | 472 | (tramp-time-less-p time-b time-a))) |
0ec3f7ea JH |
473 | |
474 | (defun tramp-adb-ls-output-name-less-p (a b) | |
475 | "Sort \"ls\" output by name, ascending." | |
476 | (let (posa posb) | |
c22c1614 | 477 | (string-match directory-listing-before-filename-regexp a) |
0ec3f7ea | 478 | (setq posa (match-end 0)) |
c22c1614 | 479 | (string-match directory-listing-before-filename-regexp b) |
0ec3f7ea JH |
480 | (setq posb (match-end 0)) |
481 | (string-lessp (substring a posa) (substring b posb)))) | |
482 | ||
483 | (defun tramp-adb-handle-make-directory (dir &optional parents) | |
484 | "Like `make-directory' for Tramp files." | |
485 | (setq dir (expand-file-name dir)) | |
486 | (with-parsed-tramp-file-name dir nil | |
487 | (when parents | |
488 | (let ((par (expand-file-name ".." dir))) | |
489 | (unless (file-directory-p par) | |
490 | (make-directory par parents)))) | |
491 | (tramp-adb-barf-unless-okay | |
492 | v (format "mkdir %s" (tramp-shell-quote-argument localname)) | |
493 | "Couldn't make directory %s" dir) | |
245aa73e MA |
494 | (tramp-flush-file-property v (file-name-directory localname)) |
495 | (tramp-flush-directory-property v localname))) | |
0ec3f7ea JH |
496 | |
497 | (defun tramp-adb-handle-delete-directory (directory &optional recursive) | |
498 | "Like `delete-directory' for Tramp files." | |
499 | (setq directory (expand-file-name directory)) | |
500 | (with-parsed-tramp-file-name directory nil | |
501 | (tramp-flush-file-property v (file-name-directory localname)) | |
502 | (tramp-flush-directory-property v localname) | |
503 | (tramp-adb-barf-unless-okay | |
504 | v (format "%s %s" | |
505 | (if recursive "rm -r" "rmdir") | |
506 | (tramp-shell-quote-argument localname)) | |
507 | "Couldn't delete %s" directory))) | |
508 | ||
5d89d9d2 | 509 | (defun tramp-adb-handle-delete-file (filename &optional _trash) |
0ec3f7ea JH |
510 | "Like `delete-file' for Tramp files." |
511 | (setq filename (expand-file-name filename)) | |
512 | (with-parsed-tramp-file-name filename nil | |
513 | (tramp-flush-file-property v (file-name-directory localname)) | |
514 | (tramp-flush-file-property v localname) | |
515 | (tramp-adb-barf-unless-okay | |
516 | v (format "rm %s" (tramp-shell-quote-argument localname)) | |
517 | "Couldn't delete %s" filename))) | |
518 | ||
519 | (defun tramp-adb-handle-file-name-all-completions (filename directory) | |
520 | "Like `file-name-all-completions' for Tramp files." | |
521 | (all-completions | |
522 | filename | |
523 | (with-parsed-tramp-file-name directory nil | |
524 | (with-tramp-file-property v localname "file-name-all-completions" | |
525 | (save-match-data | |
526 | (tramp-adb-send-command | |
c22c1614 | 527 | v (format "%s -a %s" |
bd8c13f9 JH |
528 | (tramp-adb-get-ls-command v) |
529 | (tramp-shell-quote-argument localname))) | |
0ec3f7ea JH |
530 | (mapcar |
531 | (lambda (f) | |
c22c1614 | 532 | (if (file-directory-p (expand-file-name f directory)) |
0ec3f7ea JH |
533 | (file-name-as-directory f) |
534 | f)) | |
535 | (with-current-buffer (tramp-get-buffer v) | |
c22c1614 MA |
536 | (append |
537 | '("." "..") | |
538 | (delq | |
539 | nil | |
540 | (mapcar | |
541 | (lambda (l) (and (not (string-match "^[[:space:]]*$" l)) l)) | |
542 | (split-string (buffer-string) "\n"))))))))))) | |
0ec3f7ea JH |
543 | |
544 | (defun tramp-adb-handle-file-local-copy (filename) | |
545 | "Like `file-local-copy' for Tramp files." | |
546 | (with-parsed-tramp-file-name filename nil | |
547 | (unless (file-exists-p (file-truename filename)) | |
548 | (tramp-error | |
549 | v 'file-error | |
550 | "Cannot make local copy of non-existing file `%s'" filename)) | |
551 | (let ((tmpfile (tramp-compat-make-temp-file filename))) | |
552 | (with-tramp-progress-reporter | |
553 | v 3 (format "Fetching %s to tmp file %s" filename tmpfile) | |
554 | (when (tramp-adb-execute-adb-command v "pull" localname tmpfile) | |
555 | (delete-file tmpfile) | |
556 | (tramp-error | |
557 | v 'file-error "Cannot make local copy of file `%s'" filename)) | |
c22c1614 MA |
558 | (set-file-modes |
559 | tmpfile | |
560 | (logior (or (file-modes filename) 0) | |
561 | (tramp-compat-octal-to-decimal "0400")))) | |
0ec3f7ea JH |
562 | tmpfile))) |
563 | ||
564 | (defun tramp-adb-handle-file-writable-p (filename) | |
565 | "Like `tramp-sh-handle-file-writable-p'. | |
566 | But handle the case, if the \"test\" command is not available." | |
567 | (with-parsed-tramp-file-name filename nil | |
568 | (with-tramp-file-property v localname "file-writable-p" | |
569 | (if (tramp-adb-find-test-command v) | |
570 | (if (file-exists-p filename) | |
c22c1614 MA |
571 | (tramp-adb-send-command-and-check |
572 | v (format "test -w %s" (tramp-shell-quote-argument localname))) | |
0ec3f7ea JH |
573 | (and |
574 | (file-directory-p (file-name-directory filename)) | |
575 | (file-writable-p (file-name-directory filename)))) | |
576 | ||
577 | ;; Missing "test" command on Android < 4. | |
578 | (let ((rw-path "/data/data")) | |
579 | (tramp-message | |
580 | v 5 | |
581 | "Not implemented yet (assuming \"/data/data\" is writable): %s" | |
582 | localname) | |
583 | (and (>= (length localname) (length rw-path)) | |
584 | (string= (substring localname 0 (length rw-path)) | |
585 | rw-path))))))) | |
586 | ||
587 | (defun tramp-adb-handle-write-region | |
588 | (start end filename &optional append visit lockname confirm) | |
589 | "Like `write-region' for Tramp files." | |
590 | (setq filename (expand-file-name filename)) | |
591 | (with-parsed-tramp-file-name filename nil | |
0ec3f7ea JH |
592 | (when (and confirm (file-exists-p filename)) |
593 | (unless (y-or-n-p (format "File %s exists; overwrite anyway? " | |
594 | filename)) | |
595 | (tramp-error v 'file-error "File not overwritten"))) | |
596 | ;; We must also flush the cache of the directory, because | |
597 | ;; `file-attributes' reads the values from there. | |
598 | (tramp-flush-file-property v (file-name-directory localname)) | |
599 | (tramp-flush-file-property v localname) | |
600 | (let* ((curbuf (current-buffer)) | |
601 | (tmpfile (tramp-compat-make-temp-file filename))) | |
c22c1614 MA |
602 | (when (and append (file-exists-p filename)) |
603 | (copy-file filename tmpfile 'ok) | |
604 | (set-file-modes | |
605 | tmpfile | |
606 | (logior (or (file-modes tmpfile) 0) | |
607 | (tramp-compat-octal-to-decimal "0600")))) | |
0ec3f7ea JH |
608 | (tramp-run-real-handler |
609 | 'write-region | |
610 | (list start end tmpfile append 'no-message lockname confirm)) | |
611 | (with-tramp-progress-reporter | |
493ce45c | 612 | v 3 (format "Moving tmp file `%s' to `%s'" tmpfile filename) |
0ec3f7ea JH |
613 | (unwind-protect |
614 | (when (tramp-adb-execute-adb-command v "push" tmpfile localname) | |
493ce45c | 615 | (tramp-error v 'file-error "Cannot write: `%s'" filename)) |
0ec3f7ea JH |
616 | (delete-file tmpfile))) |
617 | ||
a43dc424 MA |
618 | (when (or (eq visit t) (stringp visit)) |
619 | (set-visited-file-modtime)) | |
620 | ||
0ec3f7ea JH |
621 | (unless (equal curbuf (current-buffer)) |
622 | (tramp-error | |
623 | v 'file-error | |
624 | "Buffer has changed from `%s' to `%s'" curbuf (current-buffer)))))) | |
625 | ||
626 | (defun tramp-adb-handle-set-file-modes (filename mode) | |
627 | "Like `set-file-modes' for Tramp files." | |
628 | (with-parsed-tramp-file-name filename nil | |
629 | (tramp-flush-file-property v localname) | |
245aa73e MA |
630 | (tramp-adb-send-command-and-check |
631 | v (format "chmod %s %s" (tramp-compat-decimal-to-octal mode) localname)))) | |
0ec3f7ea | 632 | |
62bcf670 JH |
633 | (defun tramp-adb-handle-set-file-times (filename &optional time) |
634 | "Like `set-file-times' for Tramp files." | |
635 | (with-parsed-tramp-file-name filename nil | |
636 | (tramp-flush-file-property v localname) | |
637 | (let ((time (if (or (null time) (equal time '(0 0))) | |
638 | (current-time) | |
639 | time))) | |
c22c1614 MA |
640 | (tramp-adb-send-command-and-check |
641 | ;; Use shell arithmetic because of Emacs integer size limit. | |
62bcf670 JH |
642 | v (format "touch -t $(( %d * 65536 + %d )) %s" |
643 | (car time) (cadr time) | |
644 | (tramp-shell-quote-argument localname)))))) | |
645 | ||
0ec3f7ea JH |
646 | (defun tramp-adb-handle-copy-file |
647 | (filename newname &optional ok-if-already-exists keep-date | |
5d89d9d2 | 648 | _preserve-uid-gid _preserve-extended-attributes) |
0ec3f7ea | 649 | "Like `copy-file' for Tramp files. |
53b6a8b1 | 650 | PRESERVE-UID-GID and PRESERVE-EXTENDED-ATTRIBUTES are completely ignored." |
0ec3f7ea JH |
651 | (setq filename (expand-file-name filename) |
652 | newname (expand-file-name newname)) | |
653 | ||
654 | (if (file-directory-p filename) | |
a36e2d26 | 655 | (tramp-file-name-handler 'copy-directory filename newname keep-date t) |
0ec3f7ea | 656 | (with-tramp-progress-reporter |
4c1f03ef MA |
657 | (tramp-dissect-file-name |
658 | (if (tramp-tramp-file-p filename) filename newname)) | |
0ec3f7ea JH |
659 | 0 (format "Copying %s to %s" filename newname) |
660 | ||
661 | (let ((tmpfile (file-local-copy filename))) | |
662 | ||
663 | (if tmpfile | |
664 | ;; Remote filename. | |
665 | (condition-case err | |
666 | (rename-file tmpfile newname ok-if-already-exists) | |
667 | ((error quit) | |
668 | (delete-file tmpfile) | |
669 | (signal (car err) (cdr err)))) | |
670 | ||
671 | ;; Remote newname. | |
672 | (when (file-directory-p newname) | |
673 | (setq newname | |
674 | (expand-file-name (file-name-nondirectory filename) newname))) | |
675 | ||
676 | (with-parsed-tramp-file-name newname nil | |
677 | (when (and (not ok-if-already-exists) | |
678 | (file-exists-p newname)) | |
679 | (tramp-error v 'file-already-exists newname)) | |
680 | ||
681 | ;; We must also flush the cache of the directory, because | |
682 | ;; `file-attributes' reads the values from there. | |
683 | (tramp-flush-file-property v (file-name-directory localname)) | |
684 | (tramp-flush-file-property v localname) | |
685 | (when (tramp-adb-execute-adb-command v "push" filename localname) | |
686 | (tramp-error | |
687 | v 'file-error "Cannot copy `%s' `%s'" filename newname)))))) | |
688 | ||
689 | ;; KEEP-DATE handling. | |
690 | (when keep-date | |
691 | (set-file-times newname (nth 5 (file-attributes filename)))))) | |
692 | ||
693 | (defun tramp-adb-handle-rename-file | |
694 | (filename newname &optional ok-if-already-exists) | |
695 | "Like `rename-file' for Tramp files." | |
696 | (setq filename (expand-file-name filename) | |
697 | newname (expand-file-name newname)) | |
698 | ||
c22c1614 MA |
699 | (let ((t1 (tramp-tramp-file-p filename)) |
700 | (t2 (tramp-tramp-file-p newname))) | |
701 | (with-parsed-tramp-file-name (if t1 filename newname) nil | |
702 | (with-tramp-progress-reporter | |
703 | v 0 (format "Renaming %s to %s" filename newname) | |
704 | ||
705 | (if (and t1 t2 | |
706 | (tramp-equal-remote filename newname) | |
707 | (not (file-directory-p filename))) | |
708 | (let ((l1 (tramp-file-name-handler | |
709 | 'file-remote-p filename 'localname)) | |
710 | (l2 (tramp-file-name-handler | |
711 | 'file-remote-p newname 'localname))) | |
712 | (when (and (not ok-if-already-exists) | |
713 | (file-exists-p newname)) | |
714 | (tramp-error v 'file-already-exists newname)) | |
715 | ;; We must also flush the cache of the directory, because | |
716 | ;; `file-attributes' reads the values from there. | |
717 | (tramp-flush-file-property v (file-name-directory l1)) | |
718 | (tramp-flush-file-property v l1) | |
719 | (tramp-flush-file-property v (file-name-directory l2)) | |
720 | (tramp-flush-file-property v l2) | |
721 | ;; Short track. | |
722 | (tramp-adb-barf-unless-okay | |
723 | v (format "mv %s %s" l1 l2) | |
724 | "Error renaming %s to %s" filename newname)) | |
725 | ||
726 | ;; Rename by copy. | |
727 | (copy-file filename newname ok-if-already-exists t t) | |
728 | (delete-file filename)))))) | |
0ec3f7ea JH |
729 | |
730 | (defun tramp-adb-handle-process-file | |
731 | (program &optional infile destination display &rest args) | |
732 | "Like `process-file' for Tramp files." | |
733 | ;; The implementation is not complete yet. | |
734 | (when (and (numberp destination) (zerop destination)) | |
735 | (error "Implementation does not handle immediate return")) | |
736 | ||
737 | (with-parsed-tramp-file-name default-directory nil | |
738 | (let (command input tmpinput stderr tmpstderr outbuf ret) | |
739 | ;; Compute command. | |
740 | (setq command (mapconcat 'tramp-shell-quote-argument | |
741 | (cons program args) " ")) | |
742 | ;; Determine input. | |
743 | (if (null infile) | |
744 | (setq input "/dev/null") | |
745 | (setq infile (expand-file-name infile)) | |
746 | (if (tramp-equal-remote default-directory infile) | |
747 | ;; INFILE is on the same remote host. | |
748 | (setq input (with-parsed-tramp-file-name infile nil localname)) | |
749 | ;; INFILE must be copied to remote host. | |
750 | (setq input (tramp-make-tramp-temp-file v) | |
751 | tmpinput (tramp-make-tramp-file-name method user host input)) | |
752 | (copy-file infile tmpinput t))) | |
753 | (when input (setq command (format "%s <%s" command input))) | |
754 | ||
755 | ;; Determine output. | |
756 | (cond | |
757 | ;; Just a buffer. | |
758 | ((bufferp destination) | |
759 | (setq outbuf destination)) | |
760 | ;; A buffer name. | |
761 | ((stringp destination) | |
762 | (setq outbuf (get-buffer-create destination))) | |
763 | ;; (REAL-DESTINATION ERROR-DESTINATION) | |
764 | ((consp destination) | |
765 | ;; output. | |
766 | (cond | |
767 | ((bufferp (car destination)) | |
768 | (setq outbuf (car destination))) | |
769 | ((stringp (car destination)) | |
770 | (setq outbuf (get-buffer-create (car destination)))) | |
771 | ((car destination) | |
772 | (setq outbuf (current-buffer)))) | |
773 | ;; stderr. | |
774 | (cond | |
775 | ((stringp (cadr destination)) | |
776 | (setcar (cdr destination) (expand-file-name (cadr destination))) | |
777 | (if (tramp-equal-remote default-directory (cadr destination)) | |
778 | ;; stderr is on the same remote host. | |
779 | (setq stderr (with-parsed-tramp-file-name | |
780 | (cadr destination) nil localname)) | |
781 | ;; stderr must be copied to remote host. The temporary | |
782 | ;; file must be deleted after execution. | |
783 | (setq stderr (tramp-make-tramp-temp-file v) | |
784 | tmpstderr (tramp-make-tramp-file-name | |
785 | method user host stderr)))) | |
786 | ;; stderr to be discarded. | |
787 | ((null (cadr destination)) | |
788 | (setq stderr "/dev/null")))) | |
789 | ;; 't | |
790 | (destination | |
791 | (setq outbuf (current-buffer)))) | |
792 | (when stderr (setq command (format "%s 2>%s" command stderr))) | |
793 | ||
794 | ;; Send the command. It might not return in time, so we protect | |
795 | ;; it. Call it in a subshell, in order to preserve working | |
796 | ;; directory. | |
797 | (condition-case nil | |
798 | (progn | |
7d11fc27 MA |
799 | (setq ret 0) |
800 | (tramp-adb-barf-unless-okay | |
801 | v (format "(cd %s; %s)" | |
802 | (tramp-shell-quote-argument localname) command) | |
803 | "") | |
6692a64c | 804 | ;; We should add the output anyway. |
0ec3f7ea JH |
805 | (when outbuf |
806 | (with-current-buffer outbuf | |
807 | (insert-buffer-substring (tramp-get-connection-buffer v))) | |
6692a64c | 808 | (when (and display (get-buffer-window outbuf t)) (redisplay)))) |
0ec3f7ea JH |
809 | ;; When the user did interrupt, we should do it also. We use |
810 | ;; return code -1 as marker. | |
811 | (quit | |
812 | (kill-buffer (tramp-get-connection-buffer v)) | |
813 | (setq ret -1)) | |
814 | ;; Handle errors. | |
815 | (error | |
816 | (kill-buffer (tramp-get-connection-buffer v)) | |
817 | (setq ret 1))) | |
818 | ||
819 | ;; Provide error file. | |
820 | (when tmpstderr (rename-file tmpstderr (cadr destination) t)) | |
821 | ||
822 | ;; Cleanup. We remove all file cache values for the connection, | |
823 | ;; because the remote process could have changed them. | |
824 | (when tmpinput (delete-file tmpinput)) | |
825 | ||
826 | ;; `process-file-side-effects' has been introduced with GNU | |
827 | ;; Emacs 23.2. If set to `nil', no remote file will be changed | |
828 | ;; by `program'. If it doesn't exist, we assume its default | |
829 | ;; value 't'. | |
830 | (unless (and (boundp 'process-file-side-effects) | |
831 | (not (symbol-value 'process-file-side-effects))) | |
832 | (tramp-flush-directory-property v "")) | |
833 | ||
834 | ;; Return exit status. | |
835 | (if (equal ret -1) | |
836 | (keyboard-quit) | |
837 | ret)))) | |
838 | ||
839 | (defun tramp-adb-handle-shell-command | |
840 | (command &optional output-buffer error-buffer) | |
841 | "Like `shell-command' for Tramp files." | |
842 | (let* ((asynchronous (string-match "[ \t]*&[ \t]*\\'" command)) | |
843 | ;; We cannot use `shell-file-name' and `shell-command-switch', | |
844 | ;; they are variables of the local host. | |
845 | (args (list "sh" "-c" (substring command 0 asynchronous))) | |
846 | current-buffer-p | |
847 | (output-buffer | |
848 | (cond | |
849 | ((bufferp output-buffer) output-buffer) | |
850 | ((stringp output-buffer) (get-buffer-create output-buffer)) | |
851 | (output-buffer | |
852 | (setq current-buffer-p t) | |
853 | (current-buffer)) | |
854 | (t (get-buffer-create | |
855 | (if asynchronous | |
856 | "*Async Shell Command*" | |
857 | "*Shell Command Output*"))))) | |
858 | (error-buffer | |
859 | (cond | |
860 | ((bufferp error-buffer) error-buffer) | |
861 | ((stringp error-buffer) (get-buffer-create error-buffer)))) | |
862 | (buffer | |
863 | (if (and (not asynchronous) error-buffer) | |
864 | (with-parsed-tramp-file-name default-directory nil | |
865 | (list output-buffer (tramp-make-tramp-temp-file v))) | |
866 | output-buffer)) | |
867 | (p (get-buffer-process output-buffer))) | |
868 | ||
869 | ;; Check whether there is another process running. Tramp does not | |
870 | ;; support 2 (asynchronous) processes in parallel. | |
871 | (when p | |
872 | (if (yes-or-no-p "A command is running. Kill it? ") | |
873 | (ignore-errors (kill-process p)) | |
95beaef3 | 874 | (tramp-user-error p "Shell command in progress"))) |
0ec3f7ea JH |
875 | |
876 | (if current-buffer-p | |
877 | (progn | |
878 | (barf-if-buffer-read-only) | |
879 | (push-mark nil t)) | |
880 | (with-current-buffer output-buffer | |
881 | (setq buffer-read-only nil) | |
882 | (erase-buffer))) | |
883 | ||
884 | (if (and (not current-buffer-p) (integerp asynchronous)) | |
885 | (prog1 | |
886 | ;; Run the process. | |
887 | (apply 'start-file-process "*Async Shell*" buffer args) | |
888 | ;; Display output. | |
889 | (pop-to-buffer output-buffer) | |
890 | (setq mode-line-process '(":%s")) | |
891 | (shell-mode)) | |
892 | ||
893 | (prog1 | |
894 | ;; Run the process. | |
895 | (apply 'process-file (car args) nil buffer nil (cdr args)) | |
896 | ;; Insert error messages if they were separated. | |
897 | (when (listp buffer) | |
898 | (with-current-buffer error-buffer | |
899 | (insert-file-contents (cadr buffer))) | |
900 | (delete-file (cadr buffer))) | |
901 | (if current-buffer-p | |
902 | ;; This is like exchange-point-and-mark, but doesn't | |
903 | ;; activate the mark. It is cleaner to avoid activation, | |
904 | ;; even though the command loop would deactivate the mark | |
905 | ;; because we inserted text. | |
906 | (goto-char (prog1 (mark t) | |
907 | (set-marker (mark-marker) (point) | |
908 | (current-buffer)))) | |
909 | ;; There's some output, display it. | |
910 | (when (with-current-buffer output-buffer (> (point-max) (point-min))) | |
911 | (if (functionp 'display-message-or-buffer) | |
912 | (tramp-compat-funcall 'display-message-or-buffer output-buffer) | |
913 | (pop-to-buffer output-buffer)))))))) | |
914 | ||
bd8c13f9 | 915 | ;; We use BUFFER also as connection buffer during setup. Because of |
0ec3f7ea JH |
916 | ;; this, its original contents must be saved, and restored once |
917 | ;; connection has been setup. | |
918 | (defun tramp-adb-handle-start-file-process (name buffer program &rest args) | |
919 | "Like `start-file-process' for Tramp files." | |
920 | (with-parsed-tramp-file-name default-directory nil | |
f1f05871 MA |
921 | ;; When PROGRAM is nil, we should provide a tty. This is not |
922 | ;; possible here. | |
923 | (unless (stringp program) | |
924 | (tramp-error v 'file-error "PROGRAM must be a string")) | |
925 | ||
0ec3f7ea | 926 | (let ((command |
f1f05871 MA |
927 | (format "cd %s; %s" |
928 | (tramp-shell-quote-argument localname) | |
929 | (mapconcat 'tramp-shell-quote-argument | |
930 | (cons program args) " "))) | |
0ec3f7ea JH |
931 | (tramp-process-connection-type |
932 | (or (null program) tramp-process-connection-type)) | |
f1f05871 | 933 | (bmp (and (buffer-live-p buffer) (buffer-modified-p buffer))) |
0ec3f7ea JH |
934 | (name1 name) |
935 | (i 0)) | |
f1f05871 MA |
936 | |
937 | (unless buffer | |
938 | ;; BUFFER can be nil. We use a temporary buffer. | |
939 | (setq buffer (generate-new-buffer tramp-temp-buffer-name))) | |
940 | (while (get-process name1) | |
941 | ;; NAME must be unique as process name. | |
942 | (setq i (1+ i) | |
943 | name1 (format "%s<%d>" name i))) | |
944 | (setq name name1) | |
945 | ;; Set the new process properties. | |
946 | (tramp-set-connection-property v "process-name" name) | |
947 | (tramp-set-connection-property v "process-buffer" buffer) | |
948 | ||
949 | (with-current-buffer (tramp-get-connection-buffer v) | |
950 | (unwind-protect | |
951 | ;; We catch this event. Otherwise, `start-process' could | |
952 | ;; be called on the local host. | |
953 | (save-excursion | |
954 | (save-restriction | |
955 | ;; Activate narrowing in order to save BUFFER | |
956 | ;; contents. Clear also the modification time; | |
957 | ;; otherwise we might be interrupted by | |
958 | ;; `verify-visited-file-modtime'. | |
959 | (let ((buffer-undo-list t) | |
960 | (buffer-read-only nil) | |
961 | (mark (point))) | |
962 | (clear-visited-file-modtime) | |
963 | (narrow-to-region (point-max) (point-max)) | |
964 | ;; We call `tramp-adb-maybe-open-connection', in | |
965 | ;; order to cleanup the prompt afterwards. | |
966 | (tramp-adb-maybe-open-connection v) | |
967 | (widen) | |
968 | (delete-region mark (point)) | |
969 | (narrow-to-region (point-max) (point-max)) | |
970 | ;; Send the command. | |
971 | (let ((tramp-adb-prompt (regexp-quote command))) | |
972 | (tramp-adb-send-command v command)) | |
973 | (let ((p (tramp-get-connection-process v))) | |
974 | ;; Set query flag and process marker for this | |
975 | ;; process. We ignore errors, because the process | |
976 | ;; could have finished already. | |
977 | (ignore-errors | |
978 | (tramp-compat-set-process-query-on-exit-flag p t) | |
979 | (set-marker (process-mark p) (point))) | |
980 | ;; Return process. | |
981 | p)))) | |
982 | ||
983 | ;; Save exit. | |
984 | (if (string-match tramp-temp-buffer-name (buffer-name)) | |
985 | (ignore-errors | |
986 | (set-process-buffer (tramp-get-connection-process v) nil) | |
987 | (kill-buffer (current-buffer))) | |
988 | (set-buffer-modified-p bmp)) | |
989 | (tramp-set-connection-property v "process-name" nil) | |
990 | (tramp-set-connection-property v "process-buffer" nil)))))) | |
0ec3f7ea | 991 | |
0ec3f7ea JH |
992 | ;; Helper functions. |
993 | ||
994 | (defun tramp-adb-execute-adb-command (vec &rest args) | |
995 | "Returns nil on success error-output on failure." | |
b49eebcc MA |
996 | (when (> (length (tramp-file-name-host vec)) 0) |
997 | (setq args (append (list "-s" (tramp-file-name-host vec)) args))) | |
0ec3f7ea JH |
998 | (with-temp-buffer |
999 | (prog1 | |
d0853629 | 1000 | (unless |
493ce45c MA |
1001 | (zerop |
1002 | (apply 'tramp-call-process vec tramp-adb-program nil t nil args)) | |
0ec3f7ea | 1003 | (buffer-string)) |
d0853629 | 1004 | (tramp-message vec 6 "%s" (buffer-string))))) |
0ec3f7ea JH |
1005 | |
1006 | (defun tramp-adb-find-test-command (vec) | |
1007 | "Checks, whether the ash has a builtin \"test\" command. | |
1008 | This happens for Android >= 4.0." | |
1009 | (with-tramp-connection-property vec "test" | |
c22c1614 | 1010 | (tramp-adb-send-command-and-check vec "type test"))) |
0ec3f7ea JH |
1011 | |
1012 | ;; Connection functions | |
1013 | ||
1014 | (defun tramp-adb-send-command (vec command) | |
1015 | "Send the COMMAND to connection VEC." | |
1016 | (tramp-adb-maybe-open-connection vec) | |
1017 | (tramp-message vec 6 "%s" command) | |
1018 | (tramp-send-string vec command) | |
1019 | ;; fixme: Race condition | |
1020 | (tramp-adb-wait-for-output (tramp-get-connection-process vec)) | |
1021 | (with-current-buffer (tramp-get-connection-buffer vec) | |
1022 | (save-excursion | |
1023 | (goto-char (point-min)) | |
1024 | ;; We can't use stty to disable echo of command. | |
1025 | (delete-matching-lines (regexp-quote command)) | |
1026 | ;; When the local machine is W32, there are still trailing ^M. | |
1027 | ;; There must be a better solution by setting the correct coding | |
1028 | ;; system, but this requires changes in core Tramp. | |
1029 | (goto-char (point-min)) | |
1030 | (while (re-search-forward "\r+$" nil t) | |
1031 | (replace-match "" nil nil))))) | |
1032 | ||
c22c1614 | 1033 | (defun tramp-adb-send-command-and-check |
0ec3f7ea | 1034 | (vec command) |
33848c48 | 1035 | "Run COMMAND and check its exit status. |
0ec3f7ea JH |
1036 | Sends `echo $?' along with the COMMAND for checking the exit status. If |
1037 | COMMAND is nil, just sends `echo $?'. Returns the exit status found." | |
7d11fc27 MA |
1038 | (tramp-adb-send-command |
1039 | vec (if command | |
1040 | (format "%s; echo tramp_exit_status $?" command) | |
1041 | "echo tramp_exit_status $?")) | |
0ec3f7ea JH |
1042 | (with-current-buffer (tramp-get-connection-buffer vec) |
1043 | (goto-char (point-max)) | |
1044 | (unless (re-search-backward "tramp_exit_status [0-9]+" nil t) | |
1045 | (tramp-error | |
1046 | vec 'file-error "Couldn't find exit status of `%s'" command)) | |
1047 | (skip-chars-forward "^ ") | |
7d11fc27 | 1048 | (prog1 |
c22c1614 | 1049 | (zerop (read (current-buffer))) |
7d11fc27 MA |
1050 | (let (buffer-read-only) |
1051 | (delete-region (match-beginning 0) (point-max)))))) | |
1052 | ||
1053 | (defun tramp-adb-barf-unless-okay (vec command fmt &rest args) | |
1054 | "Run COMMAND, check exit status, throw error if exit status not okay. | |
1055 | FMT and ARGS are passed to `error'." | |
c22c1614 | 1056 | (unless (tramp-adb-send-command-and-check vec command) |
7d11fc27 | 1057 | (apply 'tramp-error vec 'file-error fmt args))) |
0ec3f7ea JH |
1058 | |
1059 | (defun tramp-adb-wait-for-output (proc &optional timeout) | |
1060 | "Wait for output from remote command." | |
1061 | (unless (buffer-live-p (process-buffer proc)) | |
1062 | (delete-process proc) | |
1063 | (tramp-error proc 'file-error "Process `%s' not available, try again" proc)) | |
1064 | (with-current-buffer (process-buffer proc) | |
1065 | (if (tramp-wait-for-regexp proc timeout tramp-adb-prompt) | |
1066 | (let (buffer-read-only) | |
1067 | (goto-char (point-min)) | |
d754b364 JH |
1068 | ;; ADB terminal sends "^H" sequences. |
1069 | (when (re-search-forward "<\b+" (point-at-eol) t) | |
0ec3f7ea JH |
1070 | (forward-line 1) |
1071 | (delete-region (point-min) (point))) | |
1072 | ;; Delete the prompt. | |
d754b364 JH |
1073 | (goto-char (point-min)) |
1074 | (when (re-search-forward tramp-adb-prompt (point-at-eol) t) | |
1075 | (forward-line 1) | |
1076 | (delete-region (point-min) (point))) | |
0ec3f7ea JH |
1077 | (goto-char (point-max)) |
1078 | (re-search-backward tramp-adb-prompt nil t) | |
1079 | (delete-region (point) (point-max))) | |
1080 | (if timeout | |
1081 | (tramp-error | |
1082 | proc 'file-error | |
1083 | "[[Remote adb prompt `%s' not found in %d secs]]" | |
1084 | tramp-adb-prompt timeout) | |
1085 | (tramp-error | |
1086 | proc 'file-error | |
1087 | "[[Remote prompt `%s' not found]]" tramp-adb-prompt))))) | |
1088 | ||
1089 | (defun tramp-adb-maybe-open-connection (vec) | |
1090 | "Maybe open a connection VEC. | |
1091 | Does not do anything if a connection is already open, but re-opens the | |
1092 | connection if a previous connection has died for some reason." | |
35c3d36e | 1093 | (tramp-check-proper-method-and-host vec) |
78fc2530 | 1094 | |
0ec3f7ea | 1095 | (let* ((buf (tramp-get-connection-buffer vec)) |
1a82330c | 1096 | (p (get-buffer-process buf)) |
b49eebcc | 1097 | (host (tramp-file-name-host vec)) |
7d11fc27 | 1098 | (user (tramp-file-name-user vec)) |
c22c1614 | 1099 | devices) |
9a0f9ec3 MA |
1100 | |
1101 | ;; Maybe we know already that "su" is not supported. We cannot | |
1102 | ;; use a connection property, because we have not checked yet | |
1103 | ;; whether it is still the same device. | |
1104 | (when (and user (not (tramp-get-file-property vec "" "su-command-p" t))) | |
1105 | (tramp-error vec 'file-error "Cannot switch to user `%s'" user)) | |
1106 | ||
0ec3f7ea JH |
1107 | (unless |
1108 | (and p (processp p) (memq (process-status p) '(run open))) | |
1109 | (save-match-data | |
1110 | (when (and p (processp p)) (delete-process p)) | |
493ce45c | 1111 | (setq devices (mapcar 'cadr (tramp-adb-parse-device-names nil))) |
1a82330c JH |
1112 | (if (not devices) |
1113 | (tramp-error vec 'file-error "No device connected")) | |
b49eebcc MA |
1114 | (if (and (> (length host) 0) (not (member host devices))) |
1115 | (tramp-error vec 'file-error "Device %s not connected" host)) | |
1116 | (if (and (> (length devices) 1) (zerop (length host))) | |
1a82330c JH |
1117 | (tramp-error |
1118 | vec 'file-error | |
1119 | "Multiple Devices connected: No Host/Device specified")) | |
0ec3f7ea JH |
1120 | (with-tramp-progress-reporter vec 3 "Opening adb shell connection" |
1121 | (let* ((coding-system-for-read 'utf-8-dos) ;is this correct? | |
1122 | (process-connection-type tramp-process-connection-type) | |
b49eebcc MA |
1123 | (args (if (> (length host) 0) |
1124 | (list "-s" host "shell") | |
0ec3f7ea JH |
1125 | (list "shell"))) |
1126 | (p (let ((default-directory | |
1127 | (tramp-compat-temporary-file-directory))) | |
1128 | (apply 'start-process (tramp-get-connection-name vec) buf | |
779451da | 1129 | tramp-adb-program args)))) |
0ec3f7ea JH |
1130 | (tramp-message |
1131 | vec 6 "%s" (mapconcat 'identity (process-command p) " ")) | |
1132 | ;; Wait for initial prompt. | |
fa550654 | 1133 | (tramp-adb-wait-for-output p 30) |
0ec3f7ea JH |
1134 | (unless (eq 'run (process-status p)) |
1135 | (tramp-error vec 'file-error "Terminated!")) | |
4c1f03ef | 1136 | (tramp-set-connection-property p "vector" vec) |
a36e2d26 | 1137 | (tramp-compat-set-process-query-on-exit-flag p nil) |
cdb07539 MA |
1138 | |
1139 | ;; Check whether the properties have been changed. If | |
1140 | ;; yes, this is a strong indication that we must expire all | |
1141 | ;; connection properties. We start again. | |
1142 | (tramp-message vec 5 "Checking system information") | |
1143 | (tramp-adb-send-command | |
1144 | vec "echo \\\"`getprop ro.product.model` `getprop ro.product.version` `getprop ro.build.version.release`\\\"") | |
1145 | (let ((old-getprop | |
1146 | (tramp-get-connection-property vec "getprop" nil)) | |
1147 | (new-getprop | |
1148 | (tramp-set-connection-property | |
1149 | vec "getprop" | |
1150 | (with-current-buffer (tramp-get-connection-buffer vec) | |
1151 | ;; Read the expression. | |
1152 | (goto-char (point-min)) | |
1153 | (read (current-buffer)))))) | |
1154 | (when (and (stringp old-getprop) | |
1155 | (not (string-equal old-getprop new-getprop))) | |
cdb07539 MA |
1156 | (tramp-message |
1157 | vec 3 | |
1158 | "Connection reset, because remote host changed from `%s' to `%s'" | |
1159 | old-getprop new-getprop) | |
6480194c | 1160 | (tramp-cleanup-connection vec t) |
b6cfbcd0 MA |
1161 | (tramp-adb-maybe-open-connection vec))) |
1162 | ||
7d11fc27 MA |
1163 | ;; Change user if indicated. |
1164 | (when user | |
1165 | (tramp-adb-send-command vec (format "su %s" user)) | |
c22c1614 | 1166 | (unless (tramp-adb-send-command-and-check vec nil) |
7d11fc27 | 1167 | (delete-process p) |
9a0f9ec3 MA |
1168 | (tramp-set-file-property vec "" "su-command-p" nil) |
1169 | (tramp-error | |
1170 | vec 'file-error "Cannot switch to user `%s'" user))) | |
7d11fc27 | 1171 | |
b6cfbcd0 MA |
1172 | ;; Set "remote-path" connection property. This is needed |
1173 | ;; for eshell. | |
1174 | (tramp-adb-send-command vec "echo \\\"$PATH\\\"") | |
1175 | (tramp-set-connection-property | |
1176 | vec "remote-path" | |
1177 | (split-string | |
1178 | (with-current-buffer (tramp-get-connection-buffer vec) | |
1179 | ;; Read the expression. | |
1180 | (goto-char (point-min)) | |
1181 | (read (current-buffer))) | |
1182 | ":" 'omit-nulls)))))))) | |
0ec3f7ea | 1183 | |
ce8c5107 MA |
1184 | (add-hook 'tramp-unload-hook |
1185 | (lambda () | |
1186 | (unload-feature 'tramp-adb 'force))) | |
1187 | ||
0ec3f7ea JH |
1188 | (provide 'tramp-adb) |
1189 | ;;; tramp-adb.el ends here |