1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5 #[macro_use]
6 extern crate log;
7 extern crate once_cell;
8 extern crate regex;
9 extern crate tempfile;
10 extern crate walkdir;
11
12 pub mod adb;
13 pub mod shell;
14
15 #[cfg(test)]
16 pub mod test;
17
18 use once_cell::sync::Lazy;
19 use regex::Regex;
20 use std::collections::BTreeMap;
21 use std::convert::TryFrom;
22 use std::fmt;
23 use std::fs::File;
24 use std::io::{self, Read, Write};
25 use std::iter::FromIterator;
26 use std::net::TcpStream;
27 use std::num::{ParseIntError, TryFromIntError};
28 use std::path::{Path, PathBuf};
29 use std::str::{FromStr, Utf8Error};
30 use std::time::{Duration, SystemTime};
31 use uuid::Uuid;
32 use walkdir::WalkDir;
33
34 use crate::adb::{DeviceSerial, SyncCommand};
35
36 pub type Result<T> = std::result::Result<T, DeviceError>;
37
38 static SYNC_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"[^A-Za-z0-9_@%+=:,./-]").unwrap());
39
40 #[derive(Debug, Clone, Copy, PartialEq)]
41 pub enum AndroidStorageInput {
42 Auto,
43 App,
44 Internal,
45 Sdcard,
46 }
47
48 impl FromStr for AndroidStorageInput {
49 type Err = DeviceError;
50
from_str(s: &str) -> Result<Self>51 fn from_str(s: &str) -> Result<Self> {
52 match s {
53 "auto" => Ok(AndroidStorageInput::Auto),
54 "app" => Ok(AndroidStorageInput::App),
55 "internal" => Ok(AndroidStorageInput::Internal),
56 "sdcard" => Ok(AndroidStorageInput::Sdcard),
57 _ => Err(DeviceError::InvalidStorage),
58 }
59 }
60 }
61
62 impl Default for AndroidStorageInput {
default() -> Self63 fn default() -> Self {
64 AndroidStorageInput::Auto
65 }
66 }
67
68 #[derive(Debug, Clone, Copy, PartialEq)]
69 pub enum AndroidStorage {
70 App,
71 Internal,
72 Sdcard,
73 }
74
75 #[derive(Debug)]
76 pub enum DeviceError {
77 Adb(String),
78 FromInt(TryFromIntError),
79 InvalidStorage,
80 Io(io::Error),
81 MissingPackage,
82 MultipleDevices,
83 ParseInt(ParseIntError),
84 UnknownDevice(String),
85 Utf8(Utf8Error),
86 WalkDir(walkdir::Error),
87 }
88
89 impl fmt::Display for DeviceError {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result90 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
91 match *self {
92 DeviceError::Adb(ref message) => message.fmt(f),
93 DeviceError::FromInt(ref int) => int.fmt(f),
94 DeviceError::InvalidStorage => write!(f, "Invalid storage"),
95 DeviceError::Io(ref error) => error.fmt(f),
96 DeviceError::MissingPackage => write!(f, "Missing package"),
97 DeviceError::MultipleDevices => write!(f, "Multiple Android devices online"),
98 DeviceError::ParseInt(ref error) => error.fmt(f),
99 DeviceError::UnknownDevice(ref serial) => {
100 write!(f, "Unknown Android device with serial '{}'", serial)
101 }
102 DeviceError::Utf8(ref error) => error.fmt(f),
103 DeviceError::WalkDir(ref error) => error.fmt(f),
104 }
105 }
106 }
107
108 impl From<io::Error> for DeviceError {
from(value: io::Error) -> DeviceError109 fn from(value: io::Error) -> DeviceError {
110 DeviceError::Io(value)
111 }
112 }
113
114 impl From<ParseIntError> for DeviceError {
from(value: ParseIntError) -> DeviceError115 fn from(value: ParseIntError) -> DeviceError {
116 DeviceError::ParseInt(value)
117 }
118 }
119
120 impl From<TryFromIntError> for DeviceError {
from(value: TryFromIntError) -> DeviceError121 fn from(value: TryFromIntError) -> DeviceError {
122 DeviceError::FromInt(value)
123 }
124 }
125
126 impl From<Utf8Error> for DeviceError {
from(value: Utf8Error) -> DeviceError127 fn from(value: Utf8Error) -> DeviceError {
128 DeviceError::Utf8(value)
129 }
130 }
131
132 impl From<walkdir::Error> for DeviceError {
from(value: walkdir::Error) -> DeviceError133 fn from(value: walkdir::Error) -> DeviceError {
134 DeviceError::WalkDir(value)
135 }
136 }
137
encode_message(payload: &str) -> Result<String>138 fn encode_message(payload: &str) -> Result<String> {
139 let hex_length = u16::try_from(payload.len()).map(|len| format!("{:0>4X}", len))?;
140
141 Ok(format!("{}{}", hex_length, payload))
142 }
143
parse_device_info(line: &str) -> Option<DeviceInfo>144 fn parse_device_info(line: &str) -> Option<DeviceInfo> {
145 // Turn "serial\tdevice key1:value1 key2:value2 ..." into a `DeviceInfo`.
146 let mut pairs = line.split_whitespace();
147 let serial = pairs.next();
148 let state = pairs.next();
149 if let (Some(serial), Some("device")) = (serial, state) {
150 let info: BTreeMap<String, String> = pairs
151 .filter_map(|pair| {
152 let mut kv = pair.split(':');
153 if let (Some(k), Some(v), None) = (kv.next(), kv.next(), kv.next()) {
154 Some((k.to_owned(), v.to_owned()))
155 } else {
156 None
157 }
158 })
159 .collect();
160
161 Some(DeviceInfo {
162 serial: serial.to_owned(),
163 info,
164 })
165 } else {
166 None
167 }
168 }
169
170 /// Reads the payload length of a host message from the stream.
read_length<R: Read>(stream: &mut R) -> Result<usize>171 fn read_length<R: Read>(stream: &mut R) -> Result<usize> {
172 let mut bytes: [u8; 4] = [0; 4];
173 stream.read_exact(&mut bytes)?;
174
175 let response = std::str::from_utf8(&bytes)?;
176
177 Ok(usize::from_str_radix(&response, 16)?)
178 }
179
180 /// Reads the payload length of a device message from the stream.
read_length_little_endian(reader: &mut dyn Read) -> Result<usize>181 fn read_length_little_endian(reader: &mut dyn Read) -> Result<usize> {
182 let mut bytes: [u8; 4] = [0; 4];
183 reader.read_exact(&mut bytes)?;
184
185 let n: usize = (bytes[0] as usize)
186 + ((bytes[1] as usize) << 8)
187 + ((bytes[2] as usize) << 16)
188 + ((bytes[3] as usize) << 24);
189
190 Ok(n)
191 }
192
193 /// Writes the payload length of a device message to the stream.
write_length_little_endian(writer: &mut dyn Write, n: usize) -> Result<usize>194 fn write_length_little_endian(writer: &mut dyn Write, n: usize) -> Result<usize> {
195 let mut bytes = [0; 4];
196 bytes[0] = (n & 0xFF) as u8;
197 bytes[1] = ((n >> 8) & 0xFF) as u8;
198 bytes[2] = ((n >> 16) & 0xFF) as u8;
199 bytes[3] = ((n >> 24) & 0xFF) as u8;
200
201 writer.write(&bytes[..]).map_err(DeviceError::Io)
202 }
203
read_response(stream: &mut TcpStream, has_output: bool, has_length: bool) -> Result<Vec<u8>>204 fn read_response(stream: &mut TcpStream, has_output: bool, has_length: bool) -> Result<Vec<u8>> {
205 let mut bytes: [u8; 1024] = [0; 1024];
206
207 stream.read_exact(&mut bytes[0..4])?;
208
209 if &bytes[0..4] != SyncCommand::Okay.code() {
210 let n = bytes.len().min(read_length(stream)?);
211 stream.read_exact(&mut bytes[0..n])?;
212
213 let message = std::str::from_utf8(&bytes[0..n]).map(|s| format!("adb error: {}", s))?;
214
215 return Err(DeviceError::Adb(message));
216 }
217
218 let mut response = Vec::new();
219
220 if has_output {
221 stream.read_to_end(&mut response)?;
222
223 if response.len() >= 4 && &response[0..4] == SyncCommand::Okay.code() {
224 // Sometimes the server produces OKAYOKAY. Sometimes there is a transport OKAY and
225 // then the underlying command OKAY. This is straight from `chromedriver`.
226 response = response.split_off(4);
227 }
228
229 if response.len() >= 4 && &response[0..4] == SyncCommand::Fail.code() {
230 // The server may even produce OKAYFAIL, which means the underlying
231 // command failed. First split-off the `FAIL` and length of the message.
232 response = response.split_off(8);
233
234 let message = std::str::from_utf8(&*response).map(|s| format!("adb error: {}", s))?;
235
236 return Err(DeviceError::Adb(message));
237 }
238
239 if has_length {
240 if response.len() >= 4 {
241 let message = response.split_off(4);
242 let slice: &mut &[u8] = &mut &*response;
243
244 let n = read_length(slice)?;
245 warn!(
246 "adb server response contained hexstring length {} and message length was {} \
247 and message was {:?}",
248 n,
249 message.len(),
250 std::str::from_utf8(&message)?
251 );
252
253 return Ok(message);
254 } else {
255 return Err(DeviceError::Adb(format!(
256 "adb server response did not contain expected hexstring length: {:?}",
257 std::str::from_utf8(&response)?
258 )));
259 }
260 }
261 }
262
263 Ok(response)
264 }
265
266 /// Detailed information about an ADB device.
267 #[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
268 pub struct DeviceInfo {
269 pub serial: DeviceSerial,
270 pub info: BTreeMap<String, String>,
271 }
272
273 /// Represents a connection to an ADB host, which multiplexes the connections to
274 /// individual devices.
275 #[derive(Debug)]
276 pub struct Host {
277 /// The TCP host to connect to. Defaults to `"localhost"`.
278 pub host: Option<String>,
279 /// The TCP port to connect to. Defaults to `5037`.
280 pub port: Option<u16>,
281 /// Optional TCP read timeout duration. Defaults to 2s.
282 pub read_timeout: Option<Duration>,
283 /// Optional TCP write timeout duration. Defaults to 2s.
284 pub write_timeout: Option<Duration>,
285 }
286
287 impl Default for Host {
default() -> Host288 fn default() -> Host {
289 Host {
290 host: Some("localhost".to_string()),
291 port: Some(5037),
292 read_timeout: Some(Duration::from_secs(2)),
293 write_timeout: Some(Duration::from_secs(2)),
294 }
295 }
296 }
297
298 impl Host {
299 /// Searches for available devices, and selects the one as specified by `device_serial`.
300 ///
301 /// If multiple devices are online, and no device has been specified,
302 /// the `ANDROID_SERIAL` environment variable can be used to select one.
device_or_default<T: AsRef<str>>( self, device_serial: Option<&T>, storage: AndroidStorageInput, ) -> Result<Device>303 pub fn device_or_default<T: AsRef<str>>(
304 self,
305 device_serial: Option<&T>,
306 storage: AndroidStorageInput,
307 ) -> Result<Device> {
308 let serials: Vec<String> = self
309 .devices::<Vec<_>>()?
310 .into_iter()
311 .map(|d| d.serial)
312 .collect();
313
314 if let Some(ref serial) = device_serial
315 .map(|v| v.as_ref().to_owned())
316 .or_else(|| std::env::var("ANDROID_SERIAL").ok())
317 {
318 if !serials.contains(serial) {
319 return Err(DeviceError::UnknownDevice(serial.clone()));
320 }
321
322 return Device::new(self, serial.to_owned(), storage);
323 }
324
325 if serials.len() > 1 {
326 return Err(DeviceError::MultipleDevices);
327 }
328
329 if let Some(ref serial) = serials.first() {
330 return Device::new(self, serial.to_owned().to_string(), storage);
331 }
332
333 Err(DeviceError::Adb("No Android devices are online".to_owned()))
334 }
335
connect(&self) -> Result<TcpStream>336 pub fn connect(&self) -> Result<TcpStream> {
337 let stream = TcpStream::connect(format!(
338 "{}:{}",
339 self.host.clone().unwrap_or_else(|| "localhost".to_owned()),
340 self.port.unwrap_or(5037)
341 ))?;
342 stream.set_read_timeout(self.read_timeout)?;
343 stream.set_write_timeout(self.write_timeout)?;
344 Ok(stream)
345 }
346
execute_command( &self, command: &str, has_output: bool, has_length: bool, ) -> Result<String>347 pub fn execute_command(
348 &self,
349 command: &str,
350 has_output: bool,
351 has_length: bool,
352 ) -> Result<String> {
353 let mut stream = self.connect()?;
354
355 stream.write_all(encode_message(command)?.as_bytes())?;
356 let bytes = read_response(&mut stream, has_output, has_length)?;
357 // TODO: should we assert no bytes were read?
358
359 let response = std::str::from_utf8(&bytes)?;
360
361 Ok(response.to_owned())
362 }
363
execute_host_command( &self, host_command: &str, has_length: bool, has_output: bool, ) -> Result<String>364 pub fn execute_host_command(
365 &self,
366 host_command: &str,
367 has_length: bool,
368 has_output: bool,
369 ) -> Result<String> {
370 self.execute_command(&format!("host:{}", host_command), has_output, has_length)
371 }
372
features<B: FromIterator<String>>(&self) -> Result<B>373 pub fn features<B: FromIterator<String>>(&self) -> Result<B> {
374 let features = self.execute_host_command("features", true, true)?;
375 Ok(features.split(',').map(|x| x.to_owned()).collect())
376 }
377
devices<B: FromIterator<DeviceInfo>>(&self) -> Result<B>378 pub fn devices<B: FromIterator<DeviceInfo>>(&self) -> Result<B> {
379 let response = self.execute_host_command("devices-l", true, true)?;
380
381 let infos: B = response.lines().filter_map(parse_device_info).collect();
382
383 Ok(infos)
384 }
385 }
386
387 /// Represents an ADB device.
388 #[derive(Debug)]
389 pub struct Device {
390 /// ADB host that controls this device.
391 pub host: Host,
392
393 /// Serial number uniquely identifying this ADB device.
394 pub serial: DeviceSerial,
395
396 /// adb running as root
397 pub adbd_root: bool,
398
399 /// Flag for rooted device
400 pub is_rooted: bool,
401
402 /// "su 0" command available
403 pub su_0_root: bool,
404
405 /// "su -c" command available
406 pub su_c_root: bool,
407
408 pub run_as_package: Option<String>,
409
410 pub storage: AndroidStorage,
411
412 /// Cache intermediate tempfile name used in pushing via run_as.
413 pub tempfile: PathBuf,
414 }
415
416 impl Device {
new(host: Host, serial: DeviceSerial, storage: AndroidStorageInput) -> Result<Device>417 pub fn new(host: Host, serial: DeviceSerial, storage: AndroidStorageInput) -> Result<Device> {
418 let mut device = Device {
419 host,
420 serial,
421 adbd_root: false,
422 is_rooted: false,
423 run_as_package: None,
424 storage: AndroidStorage::App,
425 su_c_root: false,
426 su_0_root: false,
427 tempfile: PathBuf::from("/data/local/tmp"),
428 };
429 device
430 .tempfile
431 .push(Uuid::new_v4().to_hyphenated().to_string());
432
433 // check for rooted devices
434 let uid_check = |id: String| id.contains("uid=0");
435 device.adbd_root = device
436 .execute_host_shell_command("id")
437 .map_or(false, uid_check);
438 device.su_0_root = device
439 .execute_host_shell_command("su 0 id")
440 .map_or(false, uid_check);
441 device.su_c_root = device
442 .execute_host_shell_command("su -c id")
443 .map_or(false, uid_check);
444 device.is_rooted = device.adbd_root || device.su_0_root || device.su_c_root;
445
446 device.storage = match storage {
447 AndroidStorageInput::App => AndroidStorage::App,
448 AndroidStorageInput::Internal => AndroidStorage::Internal,
449 AndroidStorageInput::Sdcard => AndroidStorage::Sdcard,
450 AndroidStorageInput::Auto => AndroidStorage::Sdcard,
451 };
452
453 if device.is_rooted {
454 debug!("Device is rooted");
455
456 // Set Permissive=1 if we have root.
457 device.execute_host_shell_command("setenforce permissive")?;
458 } else {
459 debug!("Device is unrooted");
460 }
461
462 Ok(device)
463 }
464
clear_app_data(&self, package: &str) -> Result<bool>465 pub fn clear_app_data(&self, package: &str) -> Result<bool> {
466 self.execute_host_shell_command(&format!("pm clear {}", package))
467 .map(|v| v.contains("Success"))
468 }
469
create_dir(&self, path: &Path) -> Result<()>470 pub fn create_dir(&self, path: &Path) -> Result<()> {
471 debug!("Creating {}", path.display());
472
473 let enable_run_as = self.enable_run_as_for_path(&path);
474 self.execute_host_shell_command_as(&format!("mkdir -p {}", path.display()), enable_run_as)?;
475
476 Ok(())
477 }
478
chmod(&self, path: &Path, mask: &str, recursive: bool) -> Result<()>479 pub fn chmod(&self, path: &Path, mask: &str, recursive: bool) -> Result<()> {
480 let enable_run_as = self.enable_run_as_for_path(&path);
481
482 let recursive = match recursive {
483 true => " -R",
484 false => "",
485 };
486
487 self.execute_host_shell_command_as(
488 &format!("chmod {} {} {}", recursive, mask, path.display()),
489 enable_run_as,
490 )?;
491
492 Ok(())
493 }
494
execute_host_command( &self, command: &str, has_output: bool, has_length: bool, ) -> Result<String>495 pub fn execute_host_command(
496 &self,
497 command: &str,
498 has_output: bool,
499 has_length: bool,
500 ) -> Result<String> {
501 let mut stream = self.host.connect()?;
502
503 let switch_command = format!("host:transport:{}", self.serial);
504 debug!("execute_host_command: >> {:?}", &switch_command);
505 stream.write_all(encode_message(&switch_command)?.as_bytes())?;
506 let _bytes = read_response(&mut stream, false, false)?;
507 debug!("execute_host_command: << {:?}", _bytes);
508 // TODO: should we assert no bytes were read?
509
510 debug!("execute_host_command: >> {:?}", &command);
511 stream.write_all(encode_message(&command)?.as_bytes())?;
512 let bytes = read_response(&mut stream, has_output, has_length)?;
513
514 let response = std::str::from_utf8(&bytes)?;
515 debug!("execute_host_command: << {:?}", response);
516
517 // Unify new lines by removing possible carriage returns
518 Ok(response.replace("\r\n", "\n"))
519 }
520
enable_run_as_for_path(&self, path: &Path) -> bool521 pub fn enable_run_as_for_path(&self, path: &Path) -> bool {
522 match &self.run_as_package {
523 Some(package) => {
524 let mut p = PathBuf::from("/data/data/");
525 p.push(package);
526 path.starts_with(p)
527 }
528 None => false,
529 }
530 }
531
execute_host_shell_command(&self, shell_command: &str) -> Result<String>532 pub fn execute_host_shell_command(&self, shell_command: &str) -> Result<String> {
533 self.execute_host_shell_command_as(shell_command, false)
534 }
535
execute_host_shell_command_as( &self, shell_command: &str, enable_run_as: bool, ) -> Result<String>536 pub fn execute_host_shell_command_as(
537 &self,
538 shell_command: &str,
539 enable_run_as: bool,
540 ) -> Result<String> {
541 // We don't want to duplicate su invocations.
542 if shell_command.starts_with("su") {
543 return self.execute_host_command(&format!("shell:{}", shell_command), true, false);
544 }
545
546 let has_outer_quotes = shell_command.starts_with('"') && shell_command.ends_with('"')
547 || shell_command.starts_with('\'') && shell_command.ends_with('\'');
548
549 if self.adbd_root {
550 return self.execute_host_command(&format!("shell:{}", shell_command), true, false);
551 }
552
553 if self.su_0_root {
554 return self.execute_host_command(
555 &format!("shell:su 0 {}", shell_command),
556 true,
557 false,
558 );
559 }
560
561 if self.su_c_root {
562 if has_outer_quotes {
563 return self.execute_host_command(
564 &format!("shell:su -c {}", shell_command),
565 true,
566 false,
567 );
568 }
569
570 if SYNC_REGEX.is_match(shell_command) {
571 let arg: &str = &shell_command.replace("'", "'\"'\"'")[..];
572 return self.execute_host_command(&format!("shell:su -c '{}'", arg), true, false);
573 }
574
575 return self.execute_host_command(
576 &format!("shell:su -c \"{}\"", shell_command),
577 true,
578 false,
579 );
580 }
581
582 // Execute command as package
583 if enable_run_as {
584 let run_as_package = self
585 .run_as_package
586 .as_ref()
587 .ok_or(DeviceError::MissingPackage)?;
588
589 if has_outer_quotes {
590 return self.execute_host_command(
591 &format!("shell:run-as {} {}", run_as_package, shell_command),
592 true,
593 false,
594 );
595 }
596
597 if SYNC_REGEX.is_match(shell_command) {
598 let arg: &str = &shell_command.replace("'", "'\"'\"'")[..];
599 return self.execute_host_command(
600 &format!("shell:run-as {} {}", run_as_package, arg),
601 true,
602 false,
603 );
604 }
605
606 return self.execute_host_command(
607 &format!("shell:run-as {} \"{}\"", run_as_package, shell_command),
608 true,
609 false,
610 );
611 }
612
613 self.execute_host_command(&format!("shell:{}", shell_command), true, false)
614 }
615
is_app_installed(&self, package: &str) -> Result<bool>616 pub fn is_app_installed(&self, package: &str) -> Result<bool> {
617 self.execute_host_shell_command(&format!("pm path {}", package))
618 .map(|v| v.contains("package:"))
619 }
620
launch<T: AsRef<str>>( &self, package: &str, activity: &str, am_start_args: &[T], ) -> Result<bool>621 pub fn launch<T: AsRef<str>>(
622 &self,
623 package: &str,
624 activity: &str,
625 am_start_args: &[T],
626 ) -> Result<bool> {
627 let mut am_start = format!("am start -W -n {}/{}", package, activity);
628
629 for arg in am_start_args {
630 am_start.push(' ');
631 if SYNC_REGEX.is_match(arg.as_ref()) {
632 am_start.push_str(&format!("\"{}\"", &shell::escape(arg.as_ref())));
633 } else {
634 am_start.push_str(&shell::escape(arg.as_ref()));
635 };
636 }
637
638 self.execute_host_shell_command(&am_start)
639 .map(|v| v.contains("Complete"))
640 }
641
force_stop(&self, package: &str) -> Result<()>642 pub fn force_stop(&self, package: &str) -> Result<()> {
643 debug!("Force stopping Android package: {}", package);
644 self.execute_host_shell_command(&format!("am force-stop {}", package))
645 .and(Ok(()))
646 }
647
forward_port(&self, local: u16, remote: u16) -> Result<u16>648 pub fn forward_port(&self, local: u16, remote: u16) -> Result<u16> {
649 let command = format!(
650 "host-serial:{}:forward:tcp:{};tcp:{}",
651 self.serial, local, remote
652 );
653 let response = self.host.execute_command(&command, true, false)?;
654
655 if local == 0 {
656 Ok(response.parse::<u16>()?)
657 } else {
658 Ok(local)
659 }
660 }
661
kill_forward_port(&self, local: u16) -> Result<()>662 pub fn kill_forward_port(&self, local: u16) -> Result<()> {
663 let command = format!("killforward:tcp:{}", local);
664 self.execute_host_command(&command, true, false).and(Ok(()))
665 }
666
kill_forward_all_ports(&self) -> Result<()>667 pub fn kill_forward_all_ports(&self) -> Result<()> {
668 self.execute_host_command(&"killforward-all".to_owned(), false, false)
669 .and(Ok(()))
670 }
671
reverse_port(&self, remote: u16, local: u16) -> Result<u16>672 pub fn reverse_port(&self, remote: u16, local: u16) -> Result<u16> {
673 let command = format!("reverse:forward:tcp:{};tcp:{}", remote, local);
674 let response = self.execute_host_command(&command, true, false)?;
675
676 if remote == 0 {
677 Ok(response.parse::<u16>()?)
678 } else {
679 Ok(remote)
680 }
681 }
682
kill_reverse_port(&self, remote: u16) -> Result<()>683 pub fn kill_reverse_port(&self, remote: u16) -> Result<()> {
684 let command = format!("reverse:killforward:tcp:{}", remote);
685 self.execute_host_command(&command, true, true).and(Ok(()))
686 }
687
kill_reverse_all_ports(&self) -> Result<()>688 pub fn kill_reverse_all_ports(&self) -> Result<()> {
689 let command = "reverse:killforward-all".to_owned();
690 self.execute_host_command(&command, false, false)
691 .and(Ok(()))
692 }
693
path_exists(&self, path: &Path, enable_run_as: bool) -> Result<bool>694 pub fn path_exists(&self, path: &Path, enable_run_as: bool) -> Result<bool> {
695 self.execute_host_shell_command_as(format!("ls {}", path.display()).as_str(), enable_run_as)
696 .map(|path| !path.contains("No such file or directory"))
697 }
698
push(&self, buffer: &mut dyn Read, dest: &Path, mode: u32) -> Result<()>699 pub fn push(&self, buffer: &mut dyn Read, dest: &Path, mode: u32) -> Result<()> {
700 // Implement the ADB protocol to send a file to the device.
701 // The protocol consists of the following steps:
702 // * Send "host:transport" command with device serial
703 // * Send "sync:" command to initialize file transfer
704 // * Send "SEND" command with name and mode of the file
705 // * Send "DATA" command one or more times for the file content
706 // * Send "DONE" command to indicate end of file transfer
707
708 let enable_run_as = self.enable_run_as_for_path(&dest.to_path_buf());
709 let dest1 = match enable_run_as {
710 true => self.tempfile.as_path(),
711 false => Path::new(dest),
712 };
713
714 // If the destination directory does not exist, adb will
715 // create it and any necessary ancestors however it will not
716 // set the directory permissions to 0o777. In addition,
717 // Android 9 (P) has a bug in its push implementation which
718 // will cause a push which creates directories to fail with
719 // the error `secure_mkdirs failed: Operation not
720 // permitted`. We can work around this by creating the
721 // destination directories prior to the push. Collect the
722 // ancestors of the destination directory which do not yet
723 // exist so we can create them and adjust their permissions
724 // prior to performing the push.
725 let mut current = dest.parent();
726 let mut leaf: Option<&Path> = None;
727 let mut root: Option<&Path> = None;
728
729 while let Some(path) = current {
730 if self.path_exists(path, enable_run_as)? {
731 break;
732 }
733 if leaf.is_none() {
734 leaf = Some(path);
735 }
736 root = Some(path);
737 current = path.parent();
738 }
739
740 if let Some(path) = leaf {
741 self.create_dir(&path)?;
742 }
743
744 if let Some(path) = root {
745 self.chmod(&path, "777", true)?;
746 }
747
748 let mut stream = self.host.connect()?;
749
750 let message = encode_message(&format!("host:transport:{}", self.serial))?;
751 stream.write_all(message.as_bytes())?;
752 let _bytes = read_response(&mut stream, false, true)?;
753
754 let message = encode_message("sync:")?;
755 stream.write_all(message.as_bytes())?;
756 let _bytes = read_response(&mut stream, false, true)?;
757
758 stream.write_all(SyncCommand::Send.code())?;
759 let args_ = format!("{},{}", dest1.display(), mode);
760 let args = args_.as_bytes();
761 write_length_little_endian(&mut stream, args.len())?;
762 stream.write_all(args)?;
763
764 // Use a 32KB buffer to transfer the file contents
765 // TODO: Maybe adjust to maxdata (256KB)
766 let mut buf = [0; 32 * 1024];
767
768 loop {
769 let len = buffer.read(&mut buf)?;
770
771 if len == 0 {
772 break;
773 }
774
775 stream.write_all(SyncCommand::Data.code())?;
776 write_length_little_endian(&mut stream, len)?;
777 stream.write_all(&buf[0..len])?;
778 }
779
780 // https://android.googlesource.com/platform/system/core/+/master/adb/SYNC.TXT#66
781 //
782 // When the file is transferred a sync request "DONE" is sent, where length is set
783 // to the last modified time for the file. The server responds to this last
784 // request (but not to chunk requests) with an "OKAY" sync response (length can
785 // be ignored).
786 let time: u32 = ((SystemTime::now().duration_since(SystemTime::UNIX_EPOCH))
787 .unwrap()
788 .as_secs()
789 & 0xFFFF_FFFF) as u32;
790
791 stream.write_all(SyncCommand::Done.code())?;
792 write_length_little_endian(&mut stream, time as usize)?;
793
794 // Status.
795 stream.read_exact(&mut buf[0..4])?;
796
797 if &buf[0..4] == SyncCommand::Okay.code() {
798 if enable_run_as {
799 // Use cp -a to preserve the permissions set by push.
800 let result = self.execute_host_shell_command_as(
801 format!("cp -aR {} {}", dest1.display(), dest.display()).as_str(),
802 enable_run_as,
803 );
804 if self.remove(dest1).is_err() {
805 debug!("Failed to remove {}", dest1.display());
806 }
807 result?;
808 }
809 Ok(())
810 } else if &buf[0..4] == SyncCommand::Fail.code() {
811 if enable_run_as && self.remove(dest1).is_err() {
812 debug!("Failed to remove {}", dest1.display());
813 }
814 let n = buf.len().min(read_length_little_endian(&mut stream)?);
815
816 stream.read_exact(&mut buf[0..n])?;
817
818 let message = std::str::from_utf8(&buf[0..n])
819 .map(|s| format!("adb error: {}", s))
820 .unwrap_or_else(|_| "adb error was not utf-8".into());
821
822 Err(DeviceError::Adb(message))
823 } else {
824 if self.remove(dest1).is_err() {
825 debug!("Failed to remove {}", dest1.display());
826 }
827 Err(DeviceError::Adb("FAIL (unknown)".to_owned()))
828 }
829 }
830
push_dir(&self, source: &Path, dest_dir: &Path, mode: u32) -> Result<()>831 pub fn push_dir(&self, source: &Path, dest_dir: &Path, mode: u32) -> Result<()> {
832 debug!("Pushing {} to {}", source.display(), dest_dir.display());
833
834 let walker = WalkDir::new(source).follow_links(false).into_iter();
835
836 for entry in walker {
837 let entry = entry?;
838 let path = entry.path();
839
840 if !entry.metadata()?.is_file() {
841 continue;
842 }
843
844 let mut file = File::open(path)?;
845
846 let mut dest = dest_dir.to_path_buf();
847 dest.push(
848 path.strip_prefix(source)
849 .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?,
850 );
851
852 self.push(&mut file, &dest, mode)?;
853 }
854
855 Ok(())
856 }
857
remove(&self, path: &Path) -> Result<()>858 pub fn remove(&self, path: &Path) -> Result<()> {
859 debug!("Deleting {}", path.display());
860
861 self.execute_host_shell_command_as(
862 &format!("rm -rf {}", path.display()),
863 self.enable_run_as_for_path(&path),
864 )?;
865
866 Ok(())
867 }
868 }
869