vfs/file/
vmo.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//! Implementation of a file backed by a VMO buffer shared by all the file connections.
6
7#[cfg(test)]
8mod tests;
9
10use crate::ObjectRequestRef;
11use crate::directory::entry::{DirectoryEntry, EntryInfo, GetEntryInfo, OpenRequest};
12use crate::execution_scope::ExecutionScope;
13use crate::file::common::vmo_flags_to_rights;
14use crate::file::{File, FileLike, FileOptions, GetVmo, StreamIoConnection, SyncMode};
15use crate::node::Node;
16use fidl_fuchsia_io as fio;
17use std::sync::Arc;
18use zx::{self as zx, HandleBased as _, Status, Vmo};
19
20/// Creates a new read-only `VmoFile` with the specified `content`.
21///
22/// ## Panics
23///
24/// This function panics if a VMO could not be created, or if `content` could not be written to the
25/// VMO.
26///
27/// ## Examples
28/// ```
29/// // Using static data:
30/// let from_str = read_only("str");
31/// let from_bytes = read_only(b"bytes");
32/// // Using owned data:
33/// let from_string = read_only(String::from("owned"));
34/// let from_vec = read_only(vec![0u8; 2]);
35/// ```
36pub fn read_only(content: impl AsRef<[u8]>) -> Arc<VmoFile> {
37    VmoFile::from_bytes(content.as_ref())
38}
39
40/// Implementation of a VMO-backed file in a virtual file system.
41pub struct VmoFile {
42    /// Specifies if the file can be opened as executable.
43    executable: bool,
44
45    /// Specifies the inode for this file. Can be [`fio::INO_UNKNOWN`] if not required.
46    inode: u64,
47
48    /// Vmo that backs the file.
49    vmo: Vmo,
50}
51
52unsafe impl Sync for VmoFile {}
53
54impl VmoFile {
55    /// Create a new VmoFile which is backed by an existing Vmo.
56    ///
57    /// # Arguments
58    ///
59    /// * `vmo` - Vmo backing this file object.
60    pub fn new(vmo: zx::Vmo) -> Arc<Self> {
61        Self::new_with_inode(vmo, fio::INO_UNKNOWN)
62    }
63
64    /// Create a new VmoFile with a specified inode value.
65    ///
66    /// # Arguments
67    ///
68    /// * `vmo` - Vmo backing this file object.
69    /// * `inode` - Inode value to report when getting the VmoFile's attributes.
70    pub fn new_with_inode(vmo: zx::Vmo, inode: u64) -> Arc<Self> {
71        Self::new_with_inode_and_executable(vmo, inode, false)
72    }
73
74    /// Create a new VmoFile with specified inode and executable values.
75    ///
76    /// # Arguments
77    ///
78    /// * `vmo` - Vmo backing this file object.
79    /// * `inode` - Inode value to report when getting the VmoFile's attributes.
80    /// * `executable` - If true, allow connections with OpenFlags::PERM_EXECUTE.
81    pub fn new_with_inode_and_executable(vmo: zx::Vmo, inode: u64, executable: bool) -> Arc<Self> {
82        Arc::new(VmoFile { executable, inode, vmo })
83    }
84
85    /// Creates a `VmoFile` with the specified `data`.
86    pub fn from_data(data: impl AsRef<[u8]>) -> Arc<Self> {
87        Self::from_bytes(data.as_ref())
88    }
89
90    fn from_bytes(bytes: &[u8]) -> Arc<Self> {
91        let vmo = Vmo::create(bytes.len().try_into().unwrap()).unwrap();
92        if !bytes.is_empty() {
93            vmo.write(bytes, 0).unwrap();
94        }
95        Self::new(vmo)
96    }
97}
98
99impl FileLike for VmoFile {
100    fn open(
101        self: Arc<Self>,
102        scope: ExecutionScope,
103        options: FileOptions,
104        object_request: ObjectRequestRef<'_>,
105    ) -> Result<(), Status> {
106        StreamIoConnection::create_sync(scope, self, options, object_request.take());
107        Ok(())
108    }
109}
110
111impl GetEntryInfo for VmoFile {
112    fn entry_info(&self) -> EntryInfo {
113        EntryInfo::new(self.inode, fio::DirentType::File)
114    }
115}
116
117impl DirectoryEntry for VmoFile {
118    fn open_entry(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), Status> {
119        request.open_file(self)
120    }
121}
122
123impl Node for VmoFile {
124    async fn get_attributes(
125        &self,
126        requested_attributes: fio::NodeAttributesQuery,
127    ) -> Result<fio::NodeAttributes2, Status> {
128        let content_size = if requested_attributes.intersects(
129            fio::NodeAttributesQuery::CONTENT_SIZE.union(fio::NodeAttributesQuery::STORAGE_SIZE),
130        ) {
131            Some(self.vmo.get_content_size()?)
132        } else {
133            None
134        };
135        let mut abilities = fio::Operations::GET_ATTRIBUTES | fio::Operations::READ_BYTES;
136        if self.executable {
137            abilities |= fio::Operations::EXECUTE
138        }
139        Ok(immutable_attributes!(
140            requested_attributes,
141            Immutable {
142                protocols: fio::NodeProtocolKinds::FILE,
143                abilities: abilities,
144                content_size: content_size,
145                storage_size: content_size,
146                id: self.inode,
147            }
148        ))
149    }
150}
151
152// Required by `StreamIoConnection`.
153impl GetVmo for VmoFile {
154    fn get_vmo(&self) -> &zx::Vmo {
155        &self.vmo
156    }
157}
158
159impl File for VmoFile {
160    fn readable(&self) -> bool {
161        true
162    }
163
164    fn writable(&self) -> bool {
165        false
166    }
167
168    fn executable(&self) -> bool {
169        self.executable
170    }
171
172    async fn open_file(&self, _options: &FileOptions) -> Result<(), Status> {
173        Ok(())
174    }
175
176    async fn truncate(&self, _length: u64) -> Result<(), Status> {
177        Err(Status::NOT_SUPPORTED)
178    }
179
180    async fn get_backing_memory(&self, flags: fio::VmoFlags) -> Result<zx::Vmo, Status> {
181        // Logic here matches fuchsia.io requirements and matches what works for memfs.
182        // Shared requests are satisfied by duplicating an handle, and private shares are
183        // child VMOs.
184        let vmo_rights = vmo_flags_to_rights(flags)
185            | zx::Rights::BASIC
186            | zx::Rights::MAP
187            | zx::Rights::GET_PROPERTY;
188        // Unless private sharing mode is specified, we always default to shared.
189        if flags.contains(fio::VmoFlags::PRIVATE_CLONE) {
190            get_as_private(&self.vmo, vmo_rights)
191        } else {
192            self.vmo.duplicate_handle(vmo_rights)
193        }
194    }
195
196    async fn get_size(&self) -> Result<u64, Status> {
197        Ok(self.vmo.get_content_size()?)
198    }
199
200    async fn update_attributes(
201        &self,
202        _attributes: fio::MutableNodeAttributes,
203    ) -> Result<(), Status> {
204        Err(Status::NOT_SUPPORTED)
205    }
206
207    async fn sync(&self, _mode: SyncMode) -> Result<(), Status> {
208        Ok(())
209    }
210}
211
212fn get_as_private(vmo: &zx::Vmo, mut rights: zx::Rights) -> Result<zx::Vmo, zx::Status> {
213    // SNAPSHOT_AT_LEAST_ON_WRITE removes ZX_RIGHT_EXECUTE even if the parent VMO has it, adding
214    // CHILD_NO_WRITE will ensure EXECUTE is maintained.
215    const CHILD_OPTIONS: zx::VmoChildOptions =
216        zx::VmoChildOptions::SNAPSHOT_AT_LEAST_ON_WRITE.union(zx::VmoChildOptions::NO_WRITE);
217
218    // Allow for the child VMO's content size and name to be changed.
219    rights |= zx::Rights::SET_PROPERTY;
220
221    let size = vmo.get_content_size()?;
222    let new_vmo = vmo.create_child(CHILD_OPTIONS, 0, size)?;
223    new_vmo.replace_handle(rights)
224}