mirror of
https://github.com/jkcoxson/idevice.git
synced 2026-03-02 06:26:15 +01:00
open an owned file in AFC (#44)
* ability to open an owned file Signed-off-by: Abdullah Al-Banna <abdu.albanna@proton.me> * get the inner afc of an owned file without closing the file Signed-off-by: Abdullah Al-Banna <abdu.albanna@proton.me> --------- Signed-off-by: Abdullah Al-Banna <abdu.albanna@proton.me>
This commit is contained in:
committed by
GitHub
parent
c60f0d102b
commit
9e8abb7d37
@@ -4,21 +4,31 @@ use std::{io::SeekFrom, marker::PhantomPinned, pin::Pin};
|
||||
|
||||
use tokio::io::{AsyncRead, AsyncSeek, AsyncWrite};
|
||||
|
||||
use super::inner_file::InnerFileDescriptor;
|
||||
use crate::IdeviceError;
|
||||
use crate::{
|
||||
IdeviceError,
|
||||
afc::{
|
||||
AfcClient,
|
||||
inner_file::{InnerFileDescriptor, OwnedInnerFileDescriptor},
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FileDescriptor<'a> {
|
||||
inner: Pin<Box<InnerFileDescriptor<'a>>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct OwnedFileDescriptor {
|
||||
inner: Pin<Box<OwnedInnerFileDescriptor>>,
|
||||
}
|
||||
|
||||
impl<'a> FileDescriptor<'a> {
|
||||
/// create a new FileDescriptor from a raw fd
|
||||
///
|
||||
/// # Safety
|
||||
/// make sure the fd is an opened file, and that you got it from a previous
|
||||
/// FileDescriptor via `as_raw_fd()` method
|
||||
pub unsafe fn new(client: &'a mut super::AfcClient, fd: u64, path: String) -> Self {
|
||||
pub unsafe fn new(client: &'a mut AfcClient, fd: u64, path: String) -> Self {
|
||||
Self {
|
||||
inner: Box::pin(InnerFileDescriptor {
|
||||
client,
|
||||
@@ -30,20 +40,54 @@ impl<'a> FileDescriptor<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_raw_fd(&self) -> u64 {
|
||||
self.inner.fd
|
||||
}
|
||||
}
|
||||
impl FileDescriptor<'_> {
|
||||
/// Returns the current cursor position for the file
|
||||
pub async fn seek_tell(&mut self) -> Result<u64, IdeviceError> {
|
||||
self.inner.as_mut().seek_tell().await
|
||||
}
|
||||
|
||||
/// Closes the file descriptor
|
||||
pub async fn close(self) -> Result<(), IdeviceError> {
|
||||
self.inner.close().await
|
||||
}
|
||||
}
|
||||
|
||||
impl OwnedFileDescriptor {
|
||||
/// create a new OwnedFileDescriptor from a raw fd
|
||||
///
|
||||
/// # Safety
|
||||
/// make sure the fd is an opened file, and that you got it from a previous
|
||||
/// OwnedFileDescriptor via `as_raw_fd()` method
|
||||
pub unsafe fn new(client: AfcClient, fd: u64, path: String) -> Self {
|
||||
Self {
|
||||
inner: Box::pin(OwnedInnerFileDescriptor {
|
||||
client,
|
||||
fd,
|
||||
path,
|
||||
pending_fut: None,
|
||||
_m: PhantomPinned,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Closes the file descriptor
|
||||
pub async fn close(self) -> Result<AfcClient, IdeviceError> {
|
||||
self.inner.close().await
|
||||
}
|
||||
|
||||
/// gets the owned afc
|
||||
///
|
||||
/// # Safety
|
||||
/// this get's the afc out without closing, if you want to get the afc and close the file, use
|
||||
/// `.close()`
|
||||
pub unsafe fn get_inner_afc(self) -> AfcClient {
|
||||
self.inner.get_inner_afc()
|
||||
}
|
||||
}
|
||||
|
||||
crate::impl_to_structs!(FileDescriptor<'_>, OwnedFileDescriptor; {
|
||||
pub fn as_raw_fd(&self) -> u64 {
|
||||
self.inner.fd
|
||||
}
|
||||
|
||||
/// Returns the current cursor position for the file
|
||||
pub async fn seek_tell(&mut self) -> Result<u64, IdeviceError> {
|
||||
self.inner.as_mut().seek_tell().await
|
||||
}
|
||||
|
||||
/// Reads the entire contents of the file
|
||||
///
|
||||
@@ -60,9 +104,9 @@ impl FileDescriptor<'_> {
|
||||
pub async fn write_entire(&mut self, bytes: &[u8]) -> Result<(), IdeviceError> {
|
||||
self.inner.as_mut().write(bytes).await
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
impl AsyncRead for FileDescriptor<'_> {
|
||||
crate::impl_trait_to_structs!(AsyncRead for FileDescriptor<'_>, OwnedFileDescriptor; {
|
||||
fn poll_read(
|
||||
mut self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
@@ -71,9 +115,9 @@ impl AsyncRead for FileDescriptor<'_> {
|
||||
let inner = self.inner.as_mut();
|
||||
inner.poll_read(cx, buf)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
impl AsyncWrite for FileDescriptor<'_> {
|
||||
crate::impl_trait_to_structs!(AsyncWrite for FileDescriptor<'_>, OwnedFileDescriptor; {
|
||||
fn poll_write(
|
||||
mut self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
@@ -98,9 +142,9 @@ impl AsyncWrite for FileDescriptor<'_> {
|
||||
let inner = self.inner.as_mut();
|
||||
inner.poll_shutdown(cx)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
impl AsyncSeek for FileDescriptor<'_> {
|
||||
crate::impl_trait_to_structs!(AsyncSeek for FileDescriptor<'_>, OwnedFileDescriptor; {
|
||||
fn start_seek(mut self: Pin<&mut Self>, position: SeekFrom) -> std::io::Result<()> {
|
||||
let this = self.inner.as_mut();
|
||||
this.start_seek(position)
|
||||
@@ -113,4 +157,4 @@ impl AsyncSeek for FileDescriptor<'_> {
|
||||
let this = self.inner.as_mut();
|
||||
this.poll_complete(cx)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -5,11 +5,13 @@ use std::{io::SeekFrom, pin::Pin};
|
||||
use futures::{FutureExt, future::BoxFuture};
|
||||
use tokio::io::{AsyncRead, AsyncSeek, AsyncWrite};
|
||||
|
||||
use crate::IdeviceError;
|
||||
|
||||
use super::{
|
||||
opcode::AfcOpcode,
|
||||
packet::{AfcPacket, AfcPacketHeader},
|
||||
use crate::{
|
||||
IdeviceError,
|
||||
afc::{
|
||||
AfcClient, MAGIC,
|
||||
opcode::AfcOpcode,
|
||||
packet::{AfcPacket, AfcPacketHeader},
|
||||
},
|
||||
};
|
||||
|
||||
/// Maximum transfer size for file operations (1MB)
|
||||
@@ -32,19 +34,31 @@ pub(crate) enum PendingResult {
|
||||
Bytes(Vec<u8>),
|
||||
}
|
||||
|
||||
type OwnedBoxFuture = Pin<Box<dyn Future<Output = Result<PendingResult, IdeviceError>> + Send>>;
|
||||
|
||||
/// Handle for an open file on the device.
|
||||
/// Call close before dropping
|
||||
pub(crate) struct InnerFileDescriptor<'a> {
|
||||
pub(crate) client: &'a mut super::AfcClient,
|
||||
pub(crate) client: &'a mut AfcClient,
|
||||
pub(crate) fd: u64,
|
||||
pub(crate) path: String,
|
||||
|
||||
pub(crate) pending_fut: Option<BoxFuture<'a, Result<PendingResult, IdeviceError>>>,
|
||||
|
||||
pub(crate) _m: std::marker::PhantomPinned,
|
||||
}
|
||||
|
||||
impl InnerFileDescriptor<'_> {
|
||||
/// Handle for an owned open file on the device.
|
||||
/// Call close before dropping
|
||||
pub(crate) struct OwnedInnerFileDescriptor {
|
||||
pub(crate) client: AfcClient,
|
||||
pub(crate) fd: u64,
|
||||
pub(crate) path: String,
|
||||
|
||||
pub(crate) pending_fut: Option<OwnedBoxFuture>,
|
||||
pub(crate) _m: std::marker::PhantomPinned,
|
||||
}
|
||||
|
||||
crate::impl_to_structs!(InnerFileDescriptor<'_>, OwnedInnerFileDescriptor; {
|
||||
/// Generic helper to send an AFC packet and read the response
|
||||
pub async fn send_packet(
|
||||
self: Pin<&mut Self>,
|
||||
@@ -57,7 +71,7 @@ impl InnerFileDescriptor<'_> {
|
||||
|
||||
let header_len = header_payload.len() as u64 + AfcPacketHeader::LEN;
|
||||
let header = AfcPacketHeader {
|
||||
magic: super::MAGIC,
|
||||
magic: MAGIC,
|
||||
entire_len: header_len + payload.len() as u64,
|
||||
header_payload_len: header_len,
|
||||
packet_num: this.client.package_number,
|
||||
@@ -115,15 +129,6 @@ impl InnerFileDescriptor<'_> {
|
||||
self.as_mut().seek_tell().await
|
||||
}
|
||||
|
||||
/// Closes the file descriptor
|
||||
pub async fn close(mut self: Pin<Box<Self>>) -> Result<(), IdeviceError> {
|
||||
let header_payload = self.fd.to_le_bytes().to_vec();
|
||||
|
||||
self.as_mut()
|
||||
.send_packet(AfcOpcode::FileClose, header_payload, Vec::new())
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reads n size of contents from the file
|
||||
///
|
||||
@@ -188,7 +193,7 @@ impl InnerFileDescriptor<'_> {
|
||||
|
||||
fn store_pending_read(mut self: Pin<&mut Self>, buf_rem: usize) {
|
||||
unsafe {
|
||||
let this = self.as_mut().get_unchecked_mut() as *mut InnerFileDescriptor;
|
||||
let this = self.as_mut().get_unchecked_mut() as *mut Self;
|
||||
|
||||
let fut = Some(
|
||||
// SAFETY: we already know that self is pinned
|
||||
@@ -204,7 +209,7 @@ impl InnerFileDescriptor<'_> {
|
||||
|
||||
fn store_pending_seek(mut self: Pin<&mut Self>, position: std::io::SeekFrom) {
|
||||
unsafe {
|
||||
let this = self.as_mut().get_unchecked_mut() as *mut InnerFileDescriptor;
|
||||
let this = self.as_mut().get_unchecked_mut() as *mut Self;
|
||||
|
||||
let fut = Some(
|
||||
Pin::new_unchecked(&mut *this)
|
||||
@@ -221,7 +226,7 @@ impl InnerFileDescriptor<'_> {
|
||||
unsafe {
|
||||
let this = self.as_mut().get_unchecked_mut();
|
||||
|
||||
let this = this as *mut InnerFileDescriptor;
|
||||
let this = this as *mut Self;
|
||||
|
||||
// move the entire buffer into the future so we don't have to store it somewhere
|
||||
let pined_this = Pin::new_unchecked(&mut *this);
|
||||
@@ -232,7 +237,7 @@ impl InnerFileDescriptor<'_> {
|
||||
(&mut *this).pending_fut = Some(fut);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
impl<'a> InnerFileDescriptor<'a> {
|
||||
fn get_or_init_read_fut(
|
||||
@@ -268,9 +273,63 @@ impl<'a> InnerFileDescriptor<'a> {
|
||||
self.as_mut().get_unchecked_mut().pending_fut.take();
|
||||
}
|
||||
}
|
||||
|
||||
/// Closes the file descriptor
|
||||
pub async fn close(mut self: Pin<Box<Self>>) -> Result<(), IdeviceError> {
|
||||
let header_payload = self.fd.to_le_bytes().to_vec();
|
||||
|
||||
self.as_mut()
|
||||
.send_packet(AfcOpcode::FileClose, header_payload, Vec::new())
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for InnerFileDescriptor<'_> {
|
||||
impl OwnedInnerFileDescriptor {
|
||||
fn get_or_init_read_fut(mut self: Pin<&mut Self>, buf_rem: usize) -> &mut OwnedBoxFuture {
|
||||
if self.as_ref().pending_fut.is_none() {
|
||||
self.as_mut().store_pending_read(buf_rem);
|
||||
}
|
||||
|
||||
unsafe { self.get_unchecked_mut().pending_fut.as_mut().unwrap() }
|
||||
}
|
||||
|
||||
fn get_or_init_write_fut(mut self: Pin<&mut Self>, buf: &'_ [u8]) -> &mut OwnedBoxFuture {
|
||||
if self.as_ref().pending_fut.is_none() {
|
||||
self.as_mut().store_pending_write(buf);
|
||||
}
|
||||
|
||||
unsafe { self.get_unchecked_mut().pending_fut.as_mut().unwrap() }
|
||||
}
|
||||
|
||||
fn get_seek_fut(self: Pin<&mut Self>) -> Option<&mut OwnedBoxFuture> {
|
||||
unsafe { self.get_unchecked_mut().pending_fut.as_mut() }
|
||||
}
|
||||
|
||||
fn remove_pending_fut(mut self: Pin<&mut Self>) {
|
||||
unsafe {
|
||||
self.as_mut().get_unchecked_mut().pending_fut.take();
|
||||
}
|
||||
}
|
||||
|
||||
/// Closes the file descriptor
|
||||
pub async fn close(mut self: Pin<Box<Self>>) -> Result<AfcClient, IdeviceError> {
|
||||
let header_payload = self.fd.to_le_bytes().to_vec();
|
||||
|
||||
self.as_mut()
|
||||
.send_packet(AfcOpcode::FileClose, header_payload, Vec::new())
|
||||
.await?;
|
||||
|
||||
// we don't need it to be pinned anymore
|
||||
Ok(unsafe { Pin::into_inner_unchecked(self) }.client)
|
||||
}
|
||||
|
||||
pub fn get_inner_afc(self: Pin<Box<Self>>) -> AfcClient {
|
||||
unsafe { Pin::into_inner_unchecked(self).client }
|
||||
}
|
||||
}
|
||||
|
||||
crate::impl_trait_to_structs!(AsyncRead for InnerFileDescriptor<'_>, OwnedInnerFileDescriptor; {
|
||||
fn poll_read(
|
||||
mut self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
@@ -293,9 +352,9 @@ impl AsyncRead for InnerFileDescriptor<'_> {
|
||||
|
||||
std::task::Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
impl AsyncWrite for InnerFileDescriptor<'_> {
|
||||
crate::impl_trait_to_structs!(AsyncWrite for InnerFileDescriptor<'_>, OwnedInnerFileDescriptor; {
|
||||
fn poll_write(
|
||||
mut self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
@@ -329,9 +388,10 @@ impl AsyncWrite for InnerFileDescriptor<'_> {
|
||||
) -> std::task::Poll<Result<(), std::io::Error>> {
|
||||
std::task::Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncSeek for InnerFileDescriptor<'_> {
|
||||
});
|
||||
|
||||
crate::impl_trait_to_structs!(AsyncSeek for InnerFileDescriptor<'_>, OwnedInnerFileDescriptor; {
|
||||
fn start_seek(self: Pin<&mut Self>, position: SeekFrom) -> std::io::Result<()> {
|
||||
self.store_pending_seek(position);
|
||||
|
||||
@@ -356,7 +416,7 @@ impl AsyncSeek for InnerFileDescriptor<'_> {
|
||||
_ => unreachable!("a non seek future was stored, this shouldn't happen"),
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
impl std::fmt::Debug for InnerFileDescriptor<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
@@ -368,18 +428,31 @@ impl std::fmt::Debug for InnerFileDescriptor<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for OwnedInnerFileDescriptor {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("OwnedInnerFileDescriptor")
|
||||
.field("client", &self.client)
|
||||
.field("fd", &self.fd)
|
||||
.field("path", &self.path)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::Arc;
|
||||
|
||||
use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt};
|
||||
|
||||
use crate::usbmuxd::{UsbmuxdAddr, UsbmuxdConnection};
|
||||
use crate::{
|
||||
IdeviceService as _,
|
||||
afc::opcode::AfcFopenMode,
|
||||
usbmuxd::{UsbmuxdAddr, UsbmuxdConnection},
|
||||
};
|
||||
|
||||
use super::super::*;
|
||||
use super::*;
|
||||
|
||||
async fn make_client() -> super::super::AfcClient {
|
||||
async fn make_client() -> AfcClient {
|
||||
let mut u = UsbmuxdConnection::default()
|
||||
.await
|
||||
.expect("failed to connect to usbmuxd");
|
||||
|
||||
23
idevice/src/services/afc/inner_file_impl_macro.rs
Normal file
23
idevice/src/services/afc/inner_file_impl_macro.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
#[macro_export]
|
||||
macro_rules! impl_to_structs {
|
||||
(
|
||||
$( $name:ident $(<$li:lifetime>)? ),+;
|
||||
$body:tt
|
||||
) => {
|
||||
$(
|
||||
impl $name $(<$li>)? $body
|
||||
)+
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! impl_trait_to_structs {
|
||||
(
|
||||
$trit:ident for $( $name:ident $(<$li:lifetime>)? ),+;
|
||||
$body:tt
|
||||
) => {
|
||||
$(
|
||||
impl $trit for $name $(<$li>)? $body
|
||||
)+
|
||||
};
|
||||
}
|
||||
@@ -6,16 +6,20 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use errors::AfcError;
|
||||
use file::FileDescriptor;
|
||||
use opcode::{AfcFopenMode, AfcOpcode};
|
||||
use packet::{AfcPacket, AfcPacketHeader};
|
||||
use tracing::warn;
|
||||
|
||||
use crate::{Idevice, IdeviceError, IdeviceService, obf};
|
||||
use crate::{
|
||||
Idevice, IdeviceError, IdeviceService,
|
||||
afc::file::{FileDescriptor, OwnedFileDescriptor},
|
||||
obf,
|
||||
};
|
||||
|
||||
pub mod errors;
|
||||
pub mod file;
|
||||
mod inner_file;
|
||||
mod inner_file_impl_macro;
|
||||
pub mod opcode;
|
||||
pub mod packet;
|
||||
|
||||
@@ -415,6 +419,51 @@ impl AfcClient {
|
||||
Ok(unsafe { FileDescriptor::new(self, fd, path) })
|
||||
}
|
||||
|
||||
/// Opens an owned file on the device
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `path` - Path to the file to open
|
||||
/// * `mode` - Opening mode (read, write, etc.)
|
||||
///
|
||||
/// # Returns
|
||||
/// A `OwnedFileDescriptor` struct for the opened file
|
||||
pub async fn open_owned(
|
||||
mut self,
|
||||
path: impl Into<String>,
|
||||
mode: AfcFopenMode,
|
||||
) -> Result<OwnedFileDescriptor, IdeviceError> {
|
||||
let path = path.into();
|
||||
let mut header_payload = (mode as u64).to_le_bytes().to_vec();
|
||||
header_payload.extend(path.as_bytes());
|
||||
let header_len = header_payload.len() as u64 + AfcPacketHeader::LEN;
|
||||
|
||||
let header = AfcPacketHeader {
|
||||
magic: MAGIC,
|
||||
entire_len: header_len, // it's the same since the payload is empty for this
|
||||
header_payload_len: header_len,
|
||||
packet_num: self.package_number,
|
||||
operation: AfcOpcode::FileOpen,
|
||||
};
|
||||
self.package_number += 1;
|
||||
|
||||
let packet = AfcPacket {
|
||||
header,
|
||||
header_payload,
|
||||
payload: Vec::new(),
|
||||
};
|
||||
|
||||
self.send(packet).await?;
|
||||
let res = self.read().await?;
|
||||
if res.header_payload.len() < 8 {
|
||||
warn!("Header payload fd is less than 8 bytes");
|
||||
return Err(IdeviceError::UnexpectedResponse);
|
||||
}
|
||||
let fd = u64::from_le_bytes(res.header_payload[..8].try_into().unwrap());
|
||||
|
||||
// we know it's a valid fd
|
||||
Ok(unsafe { OwnedFileDescriptor::new(self, fd, path) })
|
||||
}
|
||||
|
||||
/// Creates a hard or symbolic link
|
||||
///
|
||||
/// # Arguments
|
||||
|
||||
Reference in New Issue
Block a user