Skip to main content

zerofs_client/
file.rs

1use crate::error::{ClientResultExt, ZeroFsError};
2use crate::session::{FidGuard, Session};
3use crate::types::{Metadata, SetAttrs};
4use std::sync::Arc;
5use std::sync::atomic::{AtomicBool, Ordering};
6
7/// An open file with concurrent positioned I/O.
8///
9/// Reconnect replays linked fids. Open-unlinked fids are connection-local and
10/// produce `ESTALE` after connection loss.
11pub struct File {
12    session: Arc<Session>,
13    guard: FidGuard,
14    closed: AtomicBool,
15    path: String,
16}
17
18impl std::fmt::Debug for File {
19    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20        f.debug_struct("File")
21            .field("path", &self.path)
22            .field("closed", &self.closed.load(Ordering::Relaxed))
23            .finish_non_exhaustive()
24    }
25}
26
27impl File {
28    pub(crate) fn new(session: Arc<Session>, guard: FidGuard, path: String) -> Arc<Self> {
29        Arc::new(Self {
30            session,
31            guard,
32            closed: AtomicBool::new(false),
33            path,
34        })
35    }
36
37    fn fid(&self) -> Result<u32, ZeroFsError> {
38        if self.closed.load(Ordering::Acquire) {
39            return Err(ZeroFsError::Closed);
40        }
41        Ok(self.guard.fid())
42    }
43
44    /// Largest payload a single `read_at` round trip can return; used by the
45    /// cursor to keep one `poll_read` to one round trip.
46    #[cfg(feature = "tokio-io")]
47    pub(crate) fn max_read_chunk(&self) -> u32 {
48        self.session.client.max_io()
49    }
50
51    /// Read up to `len` bytes at `offset`; a shorter result means EOF. Returns
52    /// [`bytes::Bytes`]: a read served by one round trip comes back with no copy.
53    pub async fn read_at(&self, offset: u64, len: u32) -> Result<bytes::Bytes, ZeroFsError> {
54        self.session
55            .client
56            .read_bytes(self.fid()?, offset, len)
57            .await
58            .ctx(&self.path)
59    }
60
61    /// Write all of `data` at `offset` (any size, chunked internally); errors
62    /// on a short write.
63    pub async fn write_at(&self, offset: u64, data: &[u8]) -> Result<(), ZeroFsError> {
64        self.session
65            .write_all(self.fid()?, offset, data, &self.path)
66            .await
67    }
68
69    /// Current metadata of this open file (fstat).
70    pub async fn metadata(&self) -> Result<Metadata, ZeroFsError> {
71        let stat = self.session.stat_fid(self.fid()?, &self.path).await?;
72        Ok(Metadata::from_stat(&stat))
73    }
74
75    /// Truncate or extend to `size` bytes.
76    pub async fn set_len(&self, size: u64) -> Result<(), ZeroFsError> {
77        self.set_attr(SetAttrs {
78            size: Some(size),
79            ..Default::default()
80        })
81        .await?;
82        Ok(())
83    }
84
85    /// Apply metadata changes through this handle.
86    pub async fn set_attr(&self, attrs: SetAttrs) -> Result<Metadata, ZeroFsError> {
87        let stat = self
88            .session
89            .setattr_fid(self.fid()?, &attrs, &self.path)
90            .await?;
91        Ok(Metadata::from_stat(&stat))
92    }
93
94    /// Flush data and metadata to durable (S3-backed) storage.
95    ///
96    /// Success verifies durability of earlier acknowledged writes on this
97    /// handle. Broken lineage returns [`ZeroFsError::Stale`].
98    pub async fn sync_all(&self) -> Result<(), ZeroFsError> {
99        self.session
100            .client
101            .fsync(self.fid()?, 0)
102            .await
103            .ctx(&self.path)
104    }
105
106    /// Flush file data only. Broken write lineage returns [`ZeroFsError::Stale`].
107    pub async fn sync_data(&self) -> Result<(), ZeroFsError> {
108        self.session
109            .client
110            .fsync(self.fid()?, 1)
111            .await
112            .ctx(&self.path)
113    }
114
115    /// Marks the handle closed. Drop schedules its fid for release. This call is
116    /// idempotent and non-blocking.
117    pub async fn close(&self) {
118        self.closed.store(true, Ordering::Release);
119    }
120
121    /// An independent `AsyncRead + AsyncWrite + AsyncSeek` cursor over this
122    /// file, starting at offset 0. Multiple cursors over one `File` are safe:
123    /// each carries its own position and the underlying I/O is positioned.
124    /// Rust-only sugar (`tokio-io` feature); never crosses FFI.
125    #[cfg(feature = "tokio-io")]
126    pub fn cursor(self: &Arc<File>) -> crate::io::FileCursor {
127        crate::io::FileCursor::new(Arc::clone(self))
128    }
129}