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:
Abdullah Al-Banna
2025-12-09 22:58:09 +03:00
committed by GitHub
parent c60f0d102b
commit 9e8abb7d37
4 changed files with 242 additions and 53 deletions

View File

@@ -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)
}
}
});

View File

@@ -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::{
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");

View 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
)+
};
}

View File

@@ -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