Skip to main content

erofs_component/
file.rs

1// Copyright 2026 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
5use crate::volume::ErofsVolume;
6use erofs::FileNode;
7use fidl_fuchsia_io as fio;
8use fuchsia_async as fasync;
9use std::sync::Arc;
10use storage_trace::TraceFutureExt as _;
11use vfs::ObjectRequestRef;
12use vfs::directory::entry::{DirectoryEntry, EntryInfo, GetEntryInfo, OpenRequest};
13use vfs::execution_scope::ExecutionScope;
14use vfs::file::connection::GetVmo;
15use vfs::file::{File, FileLike, FileOptions, SyncMode};
16use vfs::node::Node;
17
18/// An implementation of an EROFS file backed by a Zircon Pager VMO.
19pub struct ErofsFile {
20    volume: Arc<ErofsVolume>,
21    node: FileNode,
22    vmo: zx::Vmo,
23    registration_key: u64,
24}
25
26impl Drop for ErofsFile {
27    fn drop(&mut self) {
28        self.volume.pager().unregister(self.registration_key);
29    }
30}
31
32impl ErofsFile {
33    /// Creates a new pager-backed `ErofsFile` using `Arc::new_cyclic` to establish a weak
34    /// self-reference within the registered `ErofsPacketReceiver`. This permits the file to be
35    /// kept alive dynamically when being used by clients, and cleaned up when no longer in use.
36    pub fn new(volume: Arc<ErofsVolume>, node: FileNode) -> Result<Arc<Self>, zx::Status> {
37        let file = Arc::new_cyclic(|weak| {
38            let (vmo, registration_key) = volume
39                .pager()
40                .create_vmo(weak.clone(), node.size())
41                .expect("Failed to create pager VMO");
42            Self { volume, node, vmo, registration_key }
43        });
44        Ok(file)
45    }
46
47    pub fn fs(&self) -> &erofs::ErofsFilesystem {
48        self.volume.fs()
49    }
50
51    pub fn node(&self) -> &FileNode {
52        &self.node
53    }
54
55    pub fn vmo(&self) -> &zx::Vmo {
56        &self.vmo
57    }
58
59    pub(crate) fn register_zero_children_wait(&self) -> Result<(), zx::Status> {
60        self.vmo.wait_async(
61            fasync::EHandle::local().port(),
62            self.registration_key,
63            zx::Signals::VMO_ZERO_CHILDREN,
64            zx::WaitAsyncOpts::empty(),
65        )
66    }
67
68    /// Instructs the pager to watch for the `VMO_ZERO_CHILDREN` signal.
69    ///
70    /// If the VMO is currently held weakly by the packet receiver, this method upgrades it to a
71    /// `Strong` reference to prevent the file from being deallocated while clients have active
72    /// mappings, and registers the signal wait on the VMO. Returns `Ok(true)` if a transition to
73    /// `Strong` occurred.
74    pub fn watch_for_zero_children(&self) -> Result<bool, zx::Status> {
75        self.volume.pager().watch_for_zero_children(self.registration_key, &self.vmo)
76    }
77}
78
79impl DirectoryEntry for ErofsFile {
80    fn open_entry(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), zx::Status> {
81        request.open_file(self)
82    }
83}
84
85impl GetEntryInfo for ErofsFile {
86    fn entry_info(&self) -> EntryInfo {
87        EntryInfo::new(self.node.nid(), fio::DirentType::File)
88    }
89}
90
91impl Node for ErofsFile {
92    async fn get_attributes(
93        &self,
94        requested_attributes: fio::NodeAttributesQuery,
95    ) -> Result<fio::NodeAttributes2, zx::Status> {
96        storage_trace::duration!("storage", "ErofsFile::get_attributes", "nid" => self.node.nid());
97        let mtime = self.node.mtime_ns();
98        let content_size = self.node.size();
99        let storage_size = self.node.storage_size(self.volume.fs().block_size());
100        let selinux_context = self
101            .volume
102            .fs()
103            .get_xattr(&self.node, fio::SELINUX_CONTEXT_NAME.as_bytes())
104            .ok()
105            .flatten()
106            .map(|val| {
107                if val.len() <= fio::MAX_SELINUX_CONTEXT_ATTRIBUTE_LEN as usize {
108                    fio::SelinuxContext::Data(val)
109                } else {
110                    fio::SelinuxContext::UseExtendedAttributes(fio::EmptyStruct {})
111                }
112            });
113        Ok(vfs::attributes!(
114            requested_attributes,
115            Mutable {
116                mode: self.node.mode() as u32,
117                uid: self.node.uid(),
118                gid: self.node.gid(),
119                creation_time: mtime,
120                modification_time: mtime,
121                access_time: mtime,
122                selinux_context: selinux_context,
123            },
124            Immutable {
125                protocols: fio::NodeProtocolKinds::FILE,
126                abilities: fio::Operations::GET_ATTRIBUTES | fio::Operations::READ_BYTES,
127                content_size: content_size,
128                storage_size: storage_size,
129                id: self.node.nid(),
130                link_count: self.node.link_count() as u64,
131                change_time: mtime,
132            }
133        ))
134    }
135
136    async fn list_extended_attributes(&self) -> Result<Vec<Vec<u8>>, zx::Status> {
137        self.volume.fs().list_xattrs(&self.node).map_err(|e| e.to_status())
138    }
139
140    async fn get_extended_attribute(&self, name: Vec<u8>) -> Result<Vec<u8>, zx::Status> {
141        self.volume
142            .fs()
143            .get_xattr(&self.node, &name)
144            .map_err(|e| e.to_status())?
145            .ok_or(zx::Status::NOT_FOUND)
146    }
147}
148
149impl GetVmo for ErofsFile {
150    const PAGER_ON_FIDL_EXECUTOR: bool = true;
151
152    fn get_vmo(&self) -> &zx::Vmo {
153        &self.vmo
154    }
155}
156
157impl File for ErofsFile {
158    fn readable(&self) -> bool {
159        true
160    }
161
162    fn writable(&self) -> bool {
163        false
164    }
165
166    fn executable(&self) -> bool {
167        false
168    }
169
170    async fn open_file(&self, _options: &FileOptions) -> Result<(), zx::Status> {
171        Ok(())
172    }
173
174    async fn truncate(&self, _length: u64) -> Result<(), zx::Status> {
175        Err(zx::Status::NOT_SUPPORTED)
176    }
177
178    async fn get_size(&self) -> Result<u64, zx::Status> {
179        Ok(self.node.size())
180    }
181
182    async fn update_attributes(
183        &self,
184        _attributes: fio::MutableNodeAttributes,
185    ) -> Result<(), zx::Status> {
186        Err(zx::Status::NOT_SUPPORTED)
187    }
188
189    async fn get_backing_memory(&self, flags: fio::VmoFlags) -> Result<zx::Vmo, zx::Status> {
190        storage_trace::duration!(
191            "storage",
192            "ErofsFile::get_backing_memory",
193            "nid" => self.node.nid()
194        );
195        let mut vmo_rights = vmo_flags_to_rights(flags)
196            | zx::Rights::BASIC
197            | zx::Rights::MAP
198            | zx::Rights::GET_PROPERTY;
199
200        let child_vmo = if flags.contains(fio::VmoFlags::PRIVATE_CLONE) {
201            vmo_rights |= zx::Rights::SET_PROPERTY;
202            let mut child_options = zx::VmoChildOptions::SNAPSHOT_AT_LEAST_ON_WRITE;
203            if flags.contains(fio::VmoFlags::WRITE) {
204                child_options |= zx::VmoChildOptions::RESIZABLE;
205                vmo_rights |= zx::Rights::RESIZE;
206            }
207            self.vmo.create_child(child_options, 0, self.node.size())?
208        } else {
209            self.vmo.create_child(zx::VmoChildOptions::REFERENCE, 0, 0)?
210        };
211
212        let child_vmo = child_vmo.replace_handle(vmo_rights)?;
213
214        let _ = self.watch_for_zero_children()?;
215
216        Ok(child_vmo)
217    }
218
219    async fn sync(&self, _mode: SyncMode) -> Result<(), zx::Status> {
220        Ok(())
221    }
222}
223
224impl FileLike for ErofsFile {
225    fn open(
226        self: Arc<Self>,
227        scope: ExecutionScope,
228        options: FileOptions,
229        object_request: ObjectRequestRef<'_>,
230    ) -> Result<(), zx::Status> {
231        let request = object_request.take();
232        let scope_clone = scope.clone();
233        let nid = self.node.nid();
234        scope.spawn(
235            request
236                .handle_async(async move |object_request_ref| {
237                    vfs::file::StreamIoConnection::create(
238                        scope_clone,
239                        self,
240                        options,
241                        object_request_ref,
242                    )
243                    .await
244                })
245                .trace(storage_trace::trace_future_args!(
246                    "storage",
247                    "ErofsFile::open",
248                    "nid" => nid
249                )),
250        );
251        Ok(())
252    }
253}
254
255/// Maps VMO flags to their respective rights.
256fn vmo_flags_to_rights(vmo_flags: fio::VmoFlags) -> zx::Rights {
257    let mut rights = zx::Rights::NONE;
258    if vmo_flags.contains(fio::VmoFlags::READ) {
259        rights |= zx::Rights::READ;
260    }
261    if vmo_flags.contains(fio::VmoFlags::WRITE) {
262        rights |= zx::Rights::WRITE;
263    }
264    if vmo_flags.contains(fio::VmoFlags::EXECUTE) {
265        rights |= zx::Rights::EXECUTE;
266    }
267    rights
268}