Skip to content
Open
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
3 changes: 1 addition & 2 deletions adb_cli/src/handlers/local_commands.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::{fs::File, io::Write};

use adb_client::ADBServerDevice;
use adb_client::{ADBListItemType, ADBServerDevice};
use anyhow::{Result, anyhow};

use crate::models::LocalDeviceCommand;
Expand All @@ -21,7 +21,6 @@ pub fn handle_local_commands(

Ok(())
}
LocalDeviceCommand::List { path } => Ok(device.list(path)?),
LocalDeviceCommand::Logcat { path } => {
let writer: Box<dyn Write> = if let Some(path) = path {
let f = File::create(path)?;
Expand Down
21 changes: 20 additions & 1 deletion adb_cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ mod models;
mod utils;

use adb_client::{
ADBDeviceExt, ADBServer, ADBServerDevice, ADBTcpDevice, ADBUSBDevice, MDNSDiscoveryService,
ADBDeviceExt, ADBListItemType, ADBServer, ADBServerDevice, ADBTcpDevice, ADBUSBDevice,
MDNSDiscoveryService,
};

#[cfg(any(target_os = "linux", target_os = "macos"))]
Expand Down Expand Up @@ -159,6 +160,24 @@ fn main() -> Result<()> {
device.framebuffer(&path)?;
log::info!("Successfully dumped framebuffer at path {path}");
}
DeviceCommands::List { path } => {
let dirs = device.list(&path)?;
for dir in dirs {
let list_item_type = match dir.item_type {
ADBListItemType::File => "File".to_string(),
ADBListItemType::Directory => "Directory".to_string(),
ADBListItemType::Symlink => "Symlink".to_string(),
};
log::info!(
"type: {}, name: {}, time: {}, size: {}, permissions: {:#o}",
list_item_type,
dir.name,
dir.time,
dir.size,
dir.permissions
);
}
}
}

Ok(())
Expand Down
21 changes: 17 additions & 4 deletions adb_cli/src/models/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,23 @@ use super::RebootTypeCommand;
#[derive(Parser, Debug)]
pub enum DeviceCommands {
/// Spawn an interactive shell or run a list of commands on the device
Shell { commands: Vec<String> },
Shell {
commands: Vec<String>,
},
/// Pull a file from device
Pull { source: String, destination: String },
Pull {
source: String,
destination: String,
},
/// Push a file on device
Push { filename: String, path: String },
Push {
filename: String,
path: String,
},
/// Stat a file on device
Stat { path: String },
Stat {
path: String,
},
/// Run an activity on device specified by the intent
Run {
/// The package whose activity is to be invoked
Expand Down Expand Up @@ -43,4 +53,7 @@ pub enum DeviceCommands {
/// Framebuffer image destination path
path: String,
},
List {
path: String,
},
}
2 changes: 0 additions & 2 deletions adb_cli/src/models/local.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ pub enum LocalCommand {
pub enum LocalDeviceCommand {
/// List available server features.
HostFeatures,
/// List a directory on device
List { path: String },
/// Get logs of device
Logcat {
/// Path to output file (created if not exists)
Expand Down
5 changes: 4 additions & 1 deletion adb_client/src/adb_device_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::path::Path;
use image::{ImageBuffer, ImageFormat, Rgba};

use crate::models::AdbStatResponse;
use crate::{RebootType, Result};
use crate::{ADBListItem, RebootType, Result};

/// Trait representing all features available on both [`crate::ADBServerDevice`] and [`crate::ADBUSBDevice`]
pub trait ADBDeviceExt {
Expand All @@ -24,6 +24,9 @@ pub trait ADBDeviceExt {
/// Push `stream` to `path` on the device.
fn push(&mut self, stream: &mut dyn Read, path: &dyn AsRef<str>) -> Result<()>;

/// List the items in a directory on the device
fn list(&mut self, path: &dyn AsRef<str>) -> Result<Vec<ADBListItem>>;

/// Reboot the device using given reboot type
fn reboot(&mut self, reboot_type: RebootType) -> Result<()>;

Expand Down
4 changes: 4 additions & 0 deletions adb_client/src/device/adb_message_device_commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,8 @@ impl<T: ADBMessageTransport> ADBDeviceExt for ADBMessageDevice<T> {
fn framebuffer_inner(&mut self) -> Result<image::ImageBuffer<image::Rgba<u8>, Vec<u8>>> {
self.framebuffer_inner()
}

fn list(&mut self, path: &dyn AsRef<str>) -> Result<Vec<crate::ADBListItem>> {
self.list(path)
}
}
4 changes: 4 additions & 0 deletions adb_client/src/device/adb_tcp_device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ impl ADBDeviceExt for ADBTcpDevice {
fn framebuffer_inner(&mut self) -> Result<image::ImageBuffer<image::Rgba<u8>, Vec<u8>>> {
self.inner.framebuffer_inner()
}

fn list(&mut self, path: &dyn AsRef<str>) -> Result<Vec<crate::ADBListItem>> {
self.inner.list(path)
}
}

impl Drop for ADBTcpDevice {
Expand Down
4 changes: 4 additions & 0 deletions adb_client/src/device/adb_usb_device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,10 @@ impl ADBDeviceExt for ADBUSBDevice {
fn framebuffer_inner(&mut self) -> Result<image::ImageBuffer<image::Rgba<u8>, Vec<u8>>> {
self.inner.framebuffer_inner()
}

fn list(&mut self, path: &dyn AsRef<str>) -> Result<Vec<crate::ADBListItem>> {
self.inner.list(path)
}
}

impl Drop for ADBUSBDevice {
Expand Down
174 changes: 174 additions & 0 deletions adb_client/src/device/commands/list.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
use crate::{
ADBListItem, ADBListItemType, ADBMessageTransport, Result, RustADBError,
device::{
ADBTransportMessage, MessageCommand, MessageSubcommand,
adb_message_device::ADBMessageDevice,
},
};
use byteorder::ByteOrder;
use byteorder::LittleEndian;
use std::str;

impl<T: ADBMessageTransport> ADBMessageDevice<T> {
/// List the entries in the given directory on the device.
/// note: path uses internal file paths, so Documents is at /storage/emulated/0/Documents
pub(crate) fn list<A: AsRef<str>>(&mut self, path: A) -> Result<Vec<ADBListItem>> {
self.begin_synchronization()?;

let output = self.handle_list(path);

self.end_transaction()?;
output
}

/// Request amount of bytes from transport, potentially across payloads
///
/// This automatically request a new payload by sending back "Okay" and waiting for the next payload
/// It reads the request bytes across the existing payload, and if there is not enough bytes left,
/// reads the rest from the next payload
///
/// Current index
/// ┼───────────────┼ Requested
/// ┌─────────────┐
/// ┌───────────────┼───────┐ │
/// └───────────────────────┘
/// Current └─────┘
/// payload Wanted in
/// Next payload
fn read_bytes_from_transport(
requested_bytes: &usize,
current_index: &mut usize,
transport: &mut T,
payload: &mut Vec<u8>,
local_id: &u32,
remote_id: &u32,
) -> Result<Vec<u8>> {
if *current_index + requested_bytes <= payload.len() {
// if there is enough bytes in this payload
// Copy from existing payload
let slice = &payload[*current_index..*current_index + requested_bytes];
*current_index += requested_bytes;
Ok(slice.to_vec())
} else {
// Read the rest of the existing payload, then continue with the next message
let mut slice = Vec::new();
let bytes_read_from_existing_payload = payload.len() - *current_index;
slice.extend_from_slice(
&payload[*current_index..*current_index + bytes_read_from_existing_payload],
);

// Request the next message
let send_message =
ADBTransportMessage::new(MessageCommand::Okay, *local_id, *remote_id, &[]);
transport.write_message(send_message)?;
// Read the new message
*payload = transport.read_message()?.into_payload();
let bytes_read_from_new_payload = requested_bytes - bytes_read_from_existing_payload;
slice.extend_from_slice(&payload[..bytes_read_from_new_payload]);
*current_index = bytes_read_from_new_payload;
Ok(slice)
}
}

fn handle_list<A: AsRef<str>>(&mut self, path: A) -> Result<Vec<ADBListItem>> {
// TODO: use LIS2 to support files over 2.14 GB in size.
// SEE: https://github.com/cstyan/adbDocumentation?tab=readme-ov-file#adb-list
let local_id = self.get_local_id()?;
let remote_id = self.get_remote_id()?;
{
let mut len_buf = Vec::from([0_u8; 4]);
LittleEndian::write_u32(&mut len_buf, path.as_ref().len() as u32);

let subcommand_data = MessageSubcommand::List; //.with_arg(path.len() as u32);

let mut serialized_message =
bincode::serialize(&subcommand_data).map_err(|_e| RustADBError::ConversionError)?;

serialized_message.append(&mut len_buf);
let mut path_bytes: Vec<u8> = Vec::from(path.as_ref().as_bytes());
serialized_message.append(&mut path_bytes);

let message = ADBTransportMessage::new(
MessageCommand::Write,
local_id,
remote_id,
&serialized_message,
);
self.send_and_expect_okay(message)?;
}

let mut list_items = Vec::new();

let transport = self.get_transport_mut();
let mut payload = transport.read_message()?.into_payload();
let mut current_index = 0;
loop {
// Loop though the response for all the entries
const STATUS_CODE_LENGTH_IN_BYTES: usize = 4;
let status_code = Self::read_bytes_from_transport(
&STATUS_CODE_LENGTH_IN_BYTES,
&mut current_index,
transport,
&mut payload,
&local_id,
&remote_id,
)?;
match str::from_utf8(&status_code)? {
"DENT" => {
// Read the file mode, size, mod time and name length in one go, since all their sizes are predictable
const U32_SIZE_IN_BYTES: usize = 4;
const SIZE_OF_METADATA: usize = U32_SIZE_IN_BYTES * 4;
let metadata = Self::read_bytes_from_transport(
&SIZE_OF_METADATA,
&mut current_index,
transport,
&mut payload,
&local_id,
&remote_id,
)?;
let mode = metadata[..U32_SIZE_IN_BYTES].to_vec();
let size = metadata[U32_SIZE_IN_BYTES..2 * U32_SIZE_IN_BYTES].to_vec();
let time = metadata[2 * U32_SIZE_IN_BYTES..3 * U32_SIZE_IN_BYTES].to_vec();
let name_len = metadata[3 * U32_SIZE_IN_BYTES..4 * U32_SIZE_IN_BYTES].to_vec();

let mode = LittleEndian::read_u32(&mode);
let size = LittleEndian::read_u32(&size);
let time = LittleEndian::read_u32(&time);
let name_len = LittleEndian::read_u32(&name_len) as usize;
// Read the file name, since it requires the length from the name_len
let name_buf = Self::read_bytes_from_transport(
&name_len,
&mut current_index,
transport,
&mut payload,
&local_id,
&remote_id,
)?;
let name = String::from_utf8(name_buf)?;

// First 9 bits are the file permissions
let permissions = mode & 0b111111111;
// Bits 14 to 16 are the file type
let item_type = match (mode >> 13) & 0b111 {
0b010 => ADBListItemType::Directory,
0b100 => ADBListItemType::File,
0b101 => ADBListItemType::Symlink,
type_code => return Err(RustADBError::UnknownFileMode(type_code)),
};
let entry = ADBListItem {
item_type,
name,
time,
size,
permissions,
};
list_items.push(entry);
}
"DONE" => {
return Ok(list_items);
}
x => log::error!("Got an unknown response {}", x),
}
}
}
}
1 change: 1 addition & 0 deletions adb_client/src/device/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod framebuffer;
mod install;
mod list;
mod pull;
mod push;
mod reboot;
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 @@ -120,6 +120,9 @@ pub enum RustADBError {
/// An unknown transport has been provided
#[error("unknown transport: {0}")]
UnknownTransport(String),
/// An unknown file mode was encountered in list
#[error("Unknown file mode {0}")]
UnknownFileMode(u32),
}

impl<T> From<std::sync::PoisonError<T>> for RustADBError {
Expand Down
2 changes: 1 addition & 1 deletion adb_client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub use device::{ADBTcpDevice, ADBUSBDevice};
pub use emulator_device::ADBEmulatorDevice;
pub use error::{Result, RustADBError};
pub use mdns::*;
pub use models::{AdbStatResponse, RebootType};
pub use models::{ADBListItem, ADBListItemType, AdbStatResponse, RebootType};
pub use server::*;
pub use server_device::ADBServerDevice;
pub use transports::*;
25 changes: 25 additions & 0 deletions adb_client/src/models/list_info.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
/// A list entry on the remote device
pub struct ADBListItem {
/// The name of the file, not the path
pub name: String,
/// The unix time stamp of when it was last modified
pub time: u32,
/// The unix mode of the file, used for permissions and special bits
pub permissions: u32,
/// The size of the file
pub size: u32,
/// The type of item this is, file, directory or symlink
pub item_type: ADBListItemType,
}

#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
/// The different types of item that the list item can be
pub enum ADBListItemType {
/// The entry is a file
File,
/// The entry is a directory
Directory,
/// The entry is a symlink
Symlink,
}
2 changes: 2 additions & 0 deletions adb_client/src/models/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod adb_server_command;
mod adb_stat_response;
mod framebuffer_info;
mod host_features;
mod list_info;
mod reboot_type;
mod sync_command;

Expand All @@ -11,5 +12,6 @@ pub(crate) use adb_server_command::AdbServerCommand;
pub use adb_stat_response::AdbStatResponse;
pub(crate) use framebuffer_info::{FrameBufferInfoV1, FrameBufferInfoV2};
pub use host_features::HostFeatures;
pub use list_info::{ADBListItem, ADBListItemType};
pub use reboot_type::RebootType;
pub use sync_command::SyncCommand;
4 changes: 4 additions & 0 deletions adb_client/src/server_device/adb_server_device_commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,8 @@ impl ADBDeviceExt for ADBServerDevice {
fn framebuffer_inner(&mut self) -> Result<image::ImageBuffer<image::Rgba<u8>, Vec<u8>>> {
self.framebuffer_inner()
}

fn list(&mut self, path: &dyn AsRef<str>) -> Result<Vec<crate::ADBListItem>> {
self.list(path)
}
}
Loading