Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion adb_cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ fn run_command(mut device: Box<dyn ADBDeviceExt>, command: DeviceCommands) -> AD
device.shell(&mut std::io::stdin(), Box::new(std::io::stdout()))?;
}
} else {
device.shell_command(&commands.join(" "), &mut std::io::stdout())?;
device.shell_command(&commands.join(" "), Some(&mut std::io::stdout()), None)?;
}
}
DeviceCommands::Pull {
Expand Down
1 change: 1 addition & 0 deletions adb_cli/src/models/adb_cli_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ impl From<adb_client::RustADBError> for ADBCliError {
| RustADBError::FramebufferConversionError
| RustADBError::UnimplementedFramebufferImageVersion(_)
| RustADBError::IOError(_)
| RustADBError::ADBShellV2ParseError(_)
| RustADBError::ADBRequestFailed(_)
| RustADBError::UnknownDeviceState(_)
| RustADBError::Utf8StrError(_)
Expand Down
12 changes: 9 additions & 3 deletions adb_client/src/adb_device_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@ use crate::{RebootType, Result};
/// Trait representing all features available on ADB devices.
pub trait ADBDeviceExt {
/// Runs command in a shell on the device, and write its output and error streams into output.
fn shell_command(&mut self, command: &dyn AsRef<str>, output: &mut dyn Write) -> Result<()>;
fn shell_command(
&mut self,
command: &dyn AsRef<str>,
stdout: Option<&mut dyn Write>,
stderr: Option<&mut dyn Write>,
) -> Result<Option<u8>>;

/// Starts an interactive shell session on the device.
/// Input data is read from reader and write to writer.
Expand Down Expand Up @@ -49,14 +54,15 @@ pub trait ADBDeviceExt {
activity: &dyn AsRef<str>,
) -> Result<Vec<u8>> {
let mut output = Vec::new();
self.shell_command(
let _status = self.shell_command(
&format!(
"am start {}/{}.{}",
package.as_ref(),
package.as_ref(),
activity.as_ref()
),
&mut output,
Some(&mut output),
None,
)?;

Ok(output)
Expand Down
3 changes: 3 additions & 0 deletions adb_client/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ pub enum RustADBError {
/// Indicates that an error occurred with I/O.
#[error(transparent)]
IOError(#[from] std::io::Error),
/// Indicates that an error occurred during ADB shell v2 parsing.
#[error("ADB shell v2 parsing error: {0}")]
ADBShellV2ParseError(String),
/// Indicates that an error occurred when sending ADB request.
#[error("ADB request failed - {0}")]
ADBRequestFailed(String),
Expand Down
9 changes: 7 additions & 2 deletions adb_client/src/message_devices/adb_message_device_commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,13 @@ use std::{

impl<T: ADBMessageTransport> ADBDeviceExt for ADBMessageDevice<T> {
#[inline]
fn shell_command(&mut self, command: &dyn AsRef<str>, output: &mut dyn Write) -> Result<()> {
self.shell_command(command, output)
fn shell_command(
&mut self,
command: &dyn AsRef<str>,
stdout: Option<&mut dyn Write>,
stderr: Option<&mut dyn Write>,
) -> Result<Option<u8>> {
self.shell_command(command, stdout, stderr)
}

#[inline]
Expand Down
12 changes: 8 additions & 4 deletions adb_client/src/message_devices/commands/shell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ impl<T: ADBMessageTransport> ADBMessageDevice<T> {
pub(crate) fn shell_command(
&mut self,
command: &dyn AsRef<str>,
output: &mut dyn Write,
) -> Result<()> {
mut stdout: Option<&mut dyn Write>,
_stderr: Option<&mut dyn Write>,
) -> Result<Option<u8>> {
let mut session = self.open_session(&ADBLocalCommand::ShellCommand(
command.as_ref().to_string(),
Vec::new(),
Expand All @@ -27,10 +28,13 @@ impl<T: ADBMessageTransport> ADBMessageDevice<T> {
if message.header().command() == MessageCommand::Clse {
break;
}
output.write_all(&message.into_payload())?;
// should this just write for ::Write messages?
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since I'm not familiar with when this message transport is used, I wasn't entirely that I was updating this correctly

if let Some(ref mut stdout) = stdout {
stdout.write_all(&message.into_payload())?;
}
}

Ok(())
Ok(None)
}

/// Starts an interactive shell session on the device.
Expand Down
9 changes: 7 additions & 2 deletions adb_client/src/message_devices/tcp/adb_tcp_device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,13 @@ impl ADBTcpDevice {

impl ADBDeviceExt for ADBTcpDevice {
#[inline]
fn shell_command(&mut self, command: &dyn AsRef<str>, output: &mut dyn Write) -> Result<()> {
self.inner.shell_command(command, output)
fn shell_command(
&mut self,
command: &dyn AsRef<str>,
stdout: Option<&mut dyn Write>,
stderr: Option<&mut dyn Write>,
) -> Result<Option<u8>> {
self.inner.shell_command(command, stdout, stderr)
}

#[inline]
Expand Down
2 changes: 1 addition & 1 deletion adb_client/src/message_devices/usb/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use adb_client::{usb::ADBUSBDevice, ADBDeviceExt};
let vendor_id = 0x04e8;
let product_id = 0x6860;
let mut device = ADBUSBDevice::new(vendor_id, product_id).expect("cannot find device");
device.shell_command(&"df -h", &mut std::io::stdout());
device.shell_command(&"df -h", Some(&mut std::io::stdout()), None);
```

## Push a file to the device
Expand Down
9 changes: 7 additions & 2 deletions adb_client/src/message_devices/usb/adb_usb_device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,8 +236,13 @@ impl ADBUSBDevice {

impl ADBDeviceExt for ADBUSBDevice {
#[inline]
fn shell_command(&mut self, command: &dyn AsRef<str>, output: &mut dyn Write) -> Result<()> {
self.inner.shell_command(command, output)
fn shell_command(
&mut self,
command: &dyn AsRef<str>,
stdout: Option<&mut dyn Write>,
stderr: Option<&mut dyn Write>,
) -> Result<Option<u8>> {
self.inner.shell_command(command, stdout, stderr)
}

#[inline]
Expand Down
2 changes: 1 addition & 1 deletion adb_client/src/server_device/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use adb_client::{server::ADBServer, ADBDeviceExt};

let mut server = ADBServer::default();
let mut device = server.get_device().expect("cannot get device");
device.shell_command(&"df -h", &mut std::io::stdout());
device.shell_command(&"df -h", Some(&mut std::io::stdout()), None);
```

## Push a file to the device
Expand Down
115 changes: 107 additions & 8 deletions adb_client/src/server_device/adb_server_device_commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ use std::{
path::Path,
};

use byteorder::ReadBytesExt;

use crate::{
ADBDeviceExt, ADBListItemType, Result, RustADBError,
models::{ADBCommand, ADBLocalCommand, AdbStatResponse, HostFeatures, RemountInfo},
Expand All @@ -12,8 +14,36 @@ use super::ADBServerDevice;

const BUFFER_SIZE: usize = 65535;

#[derive(Eq, PartialEq)]
enum ShellChannel {
Stdout,
Stderr,
ExitStatus,
}

impl TryFrom<u8> for ShellChannel {
type Error = std::io::Error;

fn try_from(value: u8) -> std::result::Result<Self, Self::Error> {
match value {
1 => Ok(ShellChannel::Stdout),
2 => Ok(ShellChannel::Stderr),
3 => Ok(ShellChannel::ExitStatus),
_ => Err(std::io::Error::new(
ErrorKind::InvalidData,
"Invalid channel",
)),
}
}
}

impl ADBDeviceExt for ADBServerDevice {
fn shell_command(&mut self, command: &dyn AsRef<str>, output: &mut dyn Write) -> Result<()> {
fn shell_command(
&mut self,
command: &dyn AsRef<str>,
mut stdout: Option<&mut dyn Write>,
mut stderr: Option<&mut dyn Write>,
) -> Result<Option<u8>> {
let supported_features = self.host_features()?;
if !supported_features.contains(&HostFeatures::ShellV2)
&& !supported_features.contains(&HostFeatures::Cmd)
Expand Down Expand Up @@ -44,17 +74,86 @@ impl ADBDeviceExt for ADBServerDevice {
args,
)))?;

// Now decode the shell v2 protocol packets, reference:
// https://android.googlesource.com/platform/packages/modules/adb/+/refs/heads/main/shell_protocol.h

let mut exit = None;
let mut input = std::io::BufReader::new(self.transport.get_raw_connection()?);

let mut buffer = vec![0; BUFFER_SIZE].into_boxed_slice();
loop {
match self.transport.get_raw_connection()?.read(&mut buffer) {
Ok(size) => {
if size == 0 {
return Ok(());
// 1 byte of channel
// 4 bytes of payload size
let mut pckt_metadata = vec![0; 5];
if let Err(err) = input.read_exact(&mut pckt_metadata) {
match err.kind() {
ErrorKind::UnexpectedEof | ErrorKind::BrokenPipe => return Ok(None),
_ => return Err(RustADBError::IOError(err)),
}
}

let (channel, payload_size) = {
let channel = pckt_metadata[0];
let payload_size = u32::from_le_bytes(pckt_metadata[1..5].try_into()?) as usize;
(ShellChannel::try_from(channel)?, payload_size)
};

if payload_size == 0 {
continue;
}

match channel {
ShellChannel::Stdout | ShellChannel::Stderr => {
let mut remainder = payload_size;
while remainder > 0 {
let to_read = std::cmp::min(remainder, BUFFER_SIZE);
match input.read(&mut buffer[0..to_read]) {
Ok(size) => {
if size == 0 {
return Ok(exit);
}

match channel {
ShellChannel::Stdout => {
if let Some(stdout) = stdout.as_mut() {
stdout.write_all(&buffer[..size])?;
}
}
ShellChannel::Stderr => {
// first stderr if existing, else a merged output into stdout
if let Some(writer) = stderr.as_mut() {
writer.write_all(&buffer[..size])?;
} else if let Some(writer) = stdout.as_mut() {
writer.write_all(&buffer[..size])?;
}
}
ShellChannel::ExitStatus => {
// unreachable
}
}

remainder -= size;
}
Err(e) => {
return Err(RustADBError::IOError(e));
}
}
}
output.write_all(&buffer[..size])?;
}
Err(e) => {
return Err(RustADBError::IOError(e));
ShellChannel::ExitStatus => {
if payload_size != 1 {
return Err(RustADBError::ADBShellV2ParseError(format!(
"Spurious exit status packet with size of {payload_size} (should be 1)"
)));
}

match input.read_u8() {
Ok(status) => exit = Some(status),
Err(err) => match err.kind() {
ErrorKind::UnexpectedEof | ErrorKind::BrokenPipe => return Ok(None),
_ => return Err(RustADBError::IOError(err)),
},
}
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion adb_client/src/server_device/commands/logcat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ impl<W: Write> Write for LogFilter<W> {
impl ADBServerDevice {
/// Get logs from device
pub fn get_logs<W: Write>(&mut self, output: W) -> Result<()> {
self.shell_command(&"exec logcat", &mut LogFilter::new(output))
let _status = self.shell_command(&"exec logcat", Some(&mut LogFilter::new(output)), None);
Ok(())
}
}
2 changes: 1 addition & 1 deletion pyadb_client/src/adb_server_device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ impl PyADBServerDevice {
/// Run shell commands on device and return the output (stdout + stderr merged)
pub fn shell_command(&mut self, command: &str) -> Result<Vec<u8>> {
let mut output = Vec::new();
self.0.shell_command(&command, &mut output)?;
self.0.shell_command(&command, Some(&mut output), None)?;
Ok(output)
}

Expand Down
2 changes: 1 addition & 1 deletion pyadb_client/src/adb_usb_device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ impl PyADBUSBDevice {
/// Run shell commands on device and return the output (stdout + stderr merged)
pub fn shell_command(&mut self, command: &str) -> Result<Vec<u8>> {
let mut output = Vec::new();
self.0.shell_command(&command, &mut output)?;
self.0.shell_command(&command, Some(&mut output), None)?;
Ok(output)
}

Expand Down
Loading