Skip to main content

vfs/
file.rs

1// Copyright 2019 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Module holding different kinds of files and their building blocks.
6
7use crate::execution_scope::ExecutionScope;
8use crate::node::Node;
9use crate::object_request::ObjectRequestRef;
10#[cfg(test)]
11use crate::object_request::ToObjectRequest;
12use crate::protocols::ProtocolsExt;
13use flex_fuchsia_io as fio;
14use std::future::{Future, ready};
15use std::sync::Arc;
16use zx_status::Status;
17
18/// File nodes backed by VMOs.
19#[cfg(not(feature = "fdomain"))]
20pub mod vmo;
21
22#[cfg(feature = "fdomain")]
23pub mod simple;
24
25mod common;
26
27pub mod connection;
28
29pub use connection::{FidlIoConnection, RawIoConnection};
30
31#[cfg(not(feature = "fdomain"))]
32pub use connection::{GetVmo, StreamIoConnection};
33
34/// Creates a new read-only `SimpleFile` with the specified `content`.
35///
36/// ## Examples
37/// ```
38/// // Using static data:
39/// let from_str = read_only("str");
40/// let from_bytes = read_only(b"bytes");
41/// // Using owned data:
42/// let from_string = read_only(String::from("owned"));
43/// let from_vec = read_only(vec![0u8; 2]);
44/// ```
45#[cfg(feature = "fdomain")]
46pub fn read_only(content: impl AsRef<[u8]>) -> Arc<simple::SimpleFile> {
47    simple::SimpleFile::read_only(content)
48}
49
50#[cfg(not(feature = "fdomain"))]
51pub use vmo::read_only;
52
53/// FileOptions include options that are relevant after the file has been opened. Flags like
54/// `TRUNCATE`, which only applies during open time, are not included.
55#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
56pub struct FileOptions {
57    pub rights: fio::Operations,
58    pub is_append: bool,
59    #[cfg(fuchsia_api_level_at_least = "HEAD")]
60    pub is_linkable: bool,
61}
62
63impl FileOptions {
64    /// Converts to `StreamOptions`.
65    #[cfg(target_os = "fuchsia")]
66    pub fn to_stream_options(&self) -> zx::StreamOptions {
67        let mut options = zx::StreamOptions::empty();
68        if self.rights.contains(fio::Operations::READ_BYTES) {
69            options |= zx::StreamOptions::MODE_READ;
70        }
71        if self.rights.contains(fio::Operations::WRITE_BYTES) {
72            options |= zx::StreamOptions::MODE_WRITE;
73        }
74        if self.is_append {
75            options |= zx::StreamOptions::MODE_APPEND;
76        }
77        options
78    }
79
80    pub(crate) fn to_io1(&self) -> fio::OpenFlags {
81        let mut flags = fio::OpenFlags::empty();
82        if self.rights.contains(fio::Operations::READ_BYTES) {
83            flags |= fio::OpenFlags::RIGHT_READABLE;
84        }
85        if self.rights.contains(fio::Operations::WRITE_BYTES) {
86            flags |= fio::OpenFlags::RIGHT_WRITABLE;
87        }
88        if self.rights.contains(fio::Operations::EXECUTE) {
89            flags |= fio::OpenFlags::RIGHT_EXECUTABLE;
90        }
91        if self.is_append {
92            flags |= fio::OpenFlags::APPEND;
93        }
94        flags
95    }
96}
97
98impl From<&FileOptions> for fio::Flags {
99    fn from(options: &FileOptions) -> Self {
100        // There is 1:1 mapping between `fio::Operations` and `fio::Flags`.
101        let mut flags = fio::Flags::from_bits_truncate(options.rights.bits());
102        if options.is_append {
103            flags |= fio::Flags::FILE_APPEND;
104        }
105        flags | fio::Flags::PROTOCOL_FILE
106    }
107}
108
109#[derive(Default, PartialEq)]
110pub enum SyncMode {
111    /// Used when the Sync fuchsia.io method is used.
112    #[default]
113    Normal,
114
115    /// Used when the connection is about to be closed. Typically this will involve flushing data
116    /// from caches, but performance is a consideration, so it should only perform what might be
117    /// necessary for closing the file. If anything *must* happen when a file is closed, it must be
118    /// implemented in the `Node::close` function, not here; a call to sync with this mode is not
119    /// guaranteed and not implementing/supporting it should have no effect on correctness. If
120    /// `Node::close` needs to flush data in an async context, it has to spawn a task.  Supporting
121    /// this mode means that in most cases there's no need to spawn a task because there should be
122    /// nothing that needs to be flushed (but it must check). This will only be called if the
123    /// connection has write permissions; a connection that only has read permissions should not
124    /// have made any changes that need flushing.
125    PreClose,
126}
127
128/// Trait used for all files.
129pub trait File: Node {
130    /// Capabilities:
131    fn readable(&self) -> bool {
132        true
133    }
134    fn writable(&self) -> bool {
135        false
136    }
137    fn executable(&self) -> bool {
138        false
139    }
140
141    /// Called when the file is going to be accessed, typically by a new connection.
142    /// Flags is the same as the flags passed to `flex_fuchsia_io.Node/Open`.
143    /// The following flags are handled by the connection and do not need to be handled inside
144    /// open():
145    /// * OPEN_FLAG_TRUNCATE - A call to truncate() will be made immediately after open().
146    /// * OPEN_FLAG_DESCRIBE - The OnOpen event is sent before any other requests are received from
147    /// the file's client.
148    fn open_file(&self, options: &FileOptions) -> impl Future<Output = Result<(), Status>> + Send;
149
150    /// Truncate the file to `length`.
151    /// If there are pending attributes to update (see `update_attributes`), they should also be
152    /// flushed at this time. Otherwise, no attributes should be updated, other than size as needed.
153    fn truncate(&self, length: u64) -> impl Future<Output = Result<(), Status>> + Send;
154
155    /// Get a VMO representing this file.
156    /// If not supported by the underlying filesystem, should return Err(NOT_SUPPORTED).
157    #[cfg(target_os = "fuchsia")]
158    fn get_backing_memory(
159        &self,
160        _flags: fio::VmoFlags,
161    ) -> impl Future<Output = Result<zx::Vmo, Status>> + Send {
162        ready(Err(Status::NOT_SUPPORTED))
163    }
164
165    /// Get the size of this file.
166    /// This is used to calculate seek offset relative to the end.
167    fn get_size(&self) -> impl Future<Output = Result<u64, Status>> + Send;
168
169    /// Set the mutable attributes of this file based on the values in `attributes`. If the file
170    /// does not support updating *all* of the specified attributes, implementations should fail
171    /// with `ZX_ERR_NOT_SUPPORTED`.
172    fn update_attributes(
173        &self,
174        attributes: fio::MutableNodeAttributes,
175    ) -> impl Future<Output = Result<(), Status>> + Send;
176
177    /// Preallocate disk space for this range.
178    #[cfg(fuchsia_api_level_at_least = "HEAD")]
179    fn allocate(
180        &self,
181        _offset: u64,
182        _length: u64,
183        _mode: fio::AllocateMode,
184    ) -> impl Future<Output = Result<(), Status>> + Send {
185        ready(Err(Status::NOT_SUPPORTED))
186    }
187
188    /// Set the merkle tree and the descriptor for this file and mark the file as fsverity-enabled.
189    #[cfg(fuchsia_api_level_at_least = "HEAD")]
190    fn enable_verity(
191        &self,
192        _options: fio::VerificationOptions,
193    ) -> impl Future<Output = Result<(), Status>> + Send {
194        ready(Err(Status::NOT_SUPPORTED))
195    }
196
197    /// Sync this file's contents to the storage medium (probably disk).
198    /// This does not necessarily guarantee that the file will be completely written to disk once
199    /// the call returns. It merely guarantees that any changes to the file have been propagated
200    /// to the next layer in the storage stack.
201    fn sync(&self, mode: SyncMode) -> impl Future<Output = Result<(), Status>> + Send;
202}
203
204// Trait for handling reads and writes to a file. Files that support Streams should handle reads and
205// writes via a Pager instead of implementing this trait.
206pub trait FileIo: Send + Sync {
207    /// Read at most |buffer.len()| bytes starting at |offset| into |buffer|. The function may read
208    /// less than |count| bytes and still return success, in which case read_at returns the number
209    /// of bytes read into |buffer|.
210    fn read_at(
211        &self,
212        offset: u64,
213        buffer: &mut [u8],
214    ) -> impl Future<Output = Result<u64, Status>> + Send;
215
216    /// Write |content| starting at |offset|, returning the number of bytes that were successfully
217    /// written.
218    ///
219
220    /// If there are pending attributes to update (see `update_attributes`), they should also be
221    /// flushed at this time. Otherwise, no attributes should be updated, other than size as needed.
222    fn write_at(
223        &self,
224        offset: u64,
225        content: &[u8],
226    ) -> impl Future<Output = Result<u64, Status>> + Send;
227
228    /// Appends |content| returning, if successful, the number of bytes written, and the file offset
229    /// after writing.  Implementations should make the writes atomic, so in the event that multiple
230    /// requests to append are in-flight, it should appear that the two writes are applied in
231    /// sequence.
232    ///
233    /// If there are pending attributes to update (see `update_attributes`), they should also be
234    /// flushed at this time. Otherwise, no attributes should be updated, other than size as needed.
235    fn append(&self, content: &[u8]) -> impl Future<Output = Result<(u64, u64), Status>> + Send;
236}
237
238/// Trait for dispatching read, write, and seek FIDL requests for a given connection. The
239/// implementer of this trait is responsible for maintaining the per connection state.
240///
241/// Files that support Streams should handle reads and writes via a Pager instead of implementing
242/// this trait.
243pub trait RawFileIoConnection: Send + Sync {
244    /// Reads at most `count` bytes from the file starting at the connection's seek offset and
245    /// advances the seek offset.
246    fn read(&self, count: u64) -> impl Future<Output = Result<Vec<u8>, Status>> + Send;
247
248    /// Reads `count` bytes from the file starting at `offset`.
249    fn read_at(
250        &self,
251        offset: u64,
252        count: u64,
253    ) -> impl Future<Output = Result<Vec<u8>, Status>> + Send;
254
255    /// Writes `data` to the file starting at the connect's seek offset and advances the seek
256    /// offset. If the connection is in append mode then the seek offset is moved to the end of the
257    /// file before writing. Returns the number of bytes written.
258    fn write(&self, data: &[u8]) -> impl Future<Output = Result<u64, Status>> + Send;
259
260    /// Writes `data` to the file starting at `offset`. Returns the number of bytes written.
261    fn write_at(
262        &self,
263        offset: u64,
264        data: &[u8],
265    ) -> impl Future<Output = Result<u64, Status>> + Send;
266
267    /// Modifies the connection's seek offset. Returns the connections new seek offset.
268    fn seek(
269        &self,
270        offset: i64,
271        origin: fio::SeekOrigin,
272    ) -> impl Future<Output = Result<u64, Status>> + Send;
273
274    /// Notifies the `IoOpHandler` that the flags of the connection have changed.
275    fn set_flags(&self, flags: fio::Flags) -> Result<(), Status>;
276}
277
278pub trait FileLike: Node {
279    fn open(
280        self: Arc<Self>,
281        scope: ExecutionScope,
282        options: FileOptions,
283        object_request: ObjectRequestRef<'_>,
284    ) -> Result<(), Status>;
285}
286
287/// Helper to open a file or node as required.
288pub fn serve(
289    file: Arc<impl FileLike>,
290    scope: ExecutionScope,
291    protocols: &impl ProtocolsExt,
292    object_request: ObjectRequestRef<'_>,
293) -> Result<(), Status> {
294    if protocols.is_node() {
295        let options = protocols.to_node_options(file.entry_info().type_())?;
296        file.open_as_node(scope, options, object_request)
297    } else {
298        file.open(scope, protocols.to_file_options()?, object_request)
299    }
300}
301
302/// Serve the provided file object on a new execution scope with `flags`.
303#[cfg(test)]
304#[cfg(not(feature = "fdomain"))]
305pub fn serve_proxy(file: Arc<impl FileLike>, flags: fio::Flags) -> fio::FileProxy {
306    #[cfg(feature = "fdomain")]
307    let scope = crate::execution_scope::ExecutionScope::new(flex_local::local_client_empty());
308    #[cfg(not(feature = "fdomain"))]
309    let scope = crate::execution_scope::ExecutionScope::new();
310
311    #[cfg(feature = "fdomain")]
312    let (proxy, server) = {
313        let client = scope.domain();
314        client.create_proxy::<fio::FileMarker>()
315    };
316    #[cfg(not(feature = "fdomain"))]
317    let (proxy, server) = fidl::endpoints::create_proxy::<fio::FileMarker>();
318
319    let request = flags.to_object_request(server);
320
321    request.handle(|request| serve(file, scope, &flags, request));
322    proxy
323}
324
325/// Serve the provided file object on a new execution scope with `flags`.
326#[cfg(test)]
327#[cfg(feature = "fdomain")]
328pub fn serve_proxy(file: Arc<impl FileLike>, flags: fio::Flags) -> fio::FileProxy {
329    let client = fdomain_local::local_client_empty();
330    let (proxy, server) = client.create_proxy::<fio::FileMarker>();
331    let request = flags.to_object_request(server);
332    request.handle(|request| serve(file, ExecutionScope::new(client.clone()), &flags, request));
333    proxy
334}
335
336#[cfg(test)]
337pub fn serve_proxy_with_scope(
338    file: Arc<impl FileLike>,
339    flags: fio::Flags,
340    scope: &ExecutionScope,
341) -> fio::FileProxy {
342    #[cfg(feature = "fdomain")]
343    let client = scope.domain();
344    #[cfg(feature = "fdomain")]
345    let (proxy, server) = client.create_proxy::<fio::FileMarker>();
346    #[cfg(not(feature = "fdomain"))]
347    let (proxy, server) = fidl::endpoints::create_proxy::<fio::FileMarker>();
348
349    let request = flags.to_object_request(server);
350    request.handle(|request| serve(file, scope.clone(), &flags, request));
351    proxy
352}