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