Skip to main content

erofs_component/
pager.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::file::ErofsFile;
6use fuchsia_async as fasync;
7use std::collections::HashMap;
8use std::ops::Range;
9use std::sync::{Arc, Mutex, Weak};
10use zx::sys::zx_page_request_command_t::{ZX_PAGER_VMO_COMPLETE, ZX_PAGER_VMO_READ};
11
12const READAHEAD_ALIGNMENT: u64 = 128 * 1024;
13
14/// Manages the in-memory lifecycle of an active pager-backed file.
15///
16/// To prevent memory leaks, the pager receiver initially holds files as `Weak` references. When a
17/// client checks out a VMO (e.g., via `get_backing_memory`), this is upgraded to `Strong` so the
18/// file and its metadata are not dropped while actively mapped. Once the client closes all VMO
19/// handles, Zircon's `VMO_ZERO_CHILDREN` signal downgrades this back to `Weak`, allowing cleanup
20/// if no other system handles are open.
21pub enum FileHolder {
22    Strong(Arc<ErofsFile>),
23    Weak(Weak<ErofsFile>),
24}
25
26/// A wrapper around Zircon's `zx::Pager`.
27pub struct ErofsPager {
28    pager: Arc<zx::Pager>,
29    registrations: Mutex<HashMap<u64, fasync::ReceiverRegistration<ErofsPacketReceiver>>>,
30}
31
32impl ErofsPager {
33    /// Creates a new ErofsPager.
34    pub fn new() -> Result<Self, zx::Status> {
35        let pager = Arc::new(zx::Pager::create(zx::PagerOptions::empty())?);
36        Ok(Self { pager, registrations: Mutex::new(HashMap::new()) })
37    }
38
39    /// Creates a pager-backed VMO and registers its packet receiver, returning the VMO and its
40    /// receiver registration key.
41    pub fn create_vmo(
42        &self,
43        file: Weak<ErofsFile>,
44        initial_size: u64,
45    ) -> Result<(zx::Vmo, u64), zx::Status> {
46        storage_trace::duration!("storage", "ErofsPager::create_vmo", "size" => initial_size);
47        let receiver = ErofsPacketReceiver {
48            file: Mutex::new(FileHolder::Weak(file)),
49            pager: self.pager.clone(),
50        };
51
52        let registration = fasync::EHandle::local().register_receiver(receiver);
53        let key = registration.key();
54
55        let vmo = self
56            .pager
57            .create_vmo(zx::VmoOptions::empty(), fasync::EHandle::local().port(), key, initial_size)
58            .map_err(|e| {
59                log::error!("self.pager.create_vmo failed: {:?}", e);
60                e
61            })?;
62
63        self.registrations.lock().unwrap().insert(key, registration);
64        Ok((vmo, key))
65    }
66
67    /// Instructs the pager to watch for the `VMO_ZERO_CHILDREN` signal on `vmo` for `key`.
68    ///
69    /// If the file is currently held weakly by the packet receiver, this method upgrades it to a
70    /// `Strong` reference to prevent the file from being deallocated while clients have active
71    /// mappings, and registers the signal wait on `vmo`. Returns `Ok(true)` if a transition to
72    /// `Strong` occurred.
73    pub fn watch_for_zero_children(&self, key: u64, vmo: &zx::Vmo) -> Result<bool, zx::Status> {
74        let registrations = self.registrations.lock().unwrap();
75        let registration = registrations.get(&key).ok_or(zx::Status::BAD_STATE)?;
76        let mut file_holder = registration.receiver().file.lock().unwrap();
77        match &*file_holder {
78            FileHolder::Weak(weak) => {
79                let strong = weak.upgrade().ok_or(zx::Status::BAD_STATE)?;
80                vmo.wait_async(
81                    fasync::EHandle::local().port(),
82                    key,
83                    zx::Signals::VMO_ZERO_CHILDREN,
84                    zx::WaitAsyncOpts::empty(),
85                )?;
86                *file_holder = FileHolder::Strong(strong);
87                Ok(true)
88            }
89            FileHolder::Strong(_) => Ok(false),
90        }
91    }
92
93    /// Unregisters and drops the packet receiver registration for `key`.
94    pub fn unregister(&self, key: u64) {
95        let registration = self.registrations.lock().unwrap().remove(&key);
96        drop(registration);
97    }
98
99    /// Unregisters and discards all active packet receiver registrations.
100    pub fn unregister_all(&self) {
101        let registrations = std::mem::take(&mut *self.registrations.lock().unwrap());
102        for (_, registration) in registrations {
103            let prev = std::mem::replace(
104                &mut *registration.receiver().file.lock().unwrap(),
105                FileHolder::Weak(Weak::new()),
106            );
107            drop(prev);
108            drop(registration);
109        }
110    }
111}
112
113/// Receives pager packets and serves page requests from the kernel.
114pub struct ErofsPacketReceiver {
115    pub(crate) file: Mutex<FileHolder>,
116    pager: Arc<zx::Pager>,
117}
118
119impl ErofsPacketReceiver {
120    fn get_file(&self) -> Result<Arc<ErofsFile>, zx::Status> {
121        let holder = self.file.lock().unwrap();
122        match &*holder {
123            FileHolder::Strong(strong) => Ok(strong.clone()),
124            FileHolder::Weak(weak) => weak.upgrade().ok_or(zx::Status::BAD_STATE),
125        }
126    }
127
128    fn page_in(&self, range: Range<u64>) -> Result<(), zx::Status> {
129        let file = self.get_file()?;
130        storage_trace::duration!(
131            "storage",
132            "ErofsPager::page_in",
133            "nid" => file.node().nid(),
134            "offset" => range.start,
135            "len" => range.end - range.start
136        );
137
138        // Align the read to 128 KiB slots (readahead).
139        let readahead_start = (range.start / READAHEAD_ALIGNMENT) * READAHEAD_ALIGNMENT;
140        let mut readahead_end =
141            ((range.end + READAHEAD_ALIGNMENT - 1) / READAHEAD_ALIGNMENT) * READAHEAD_ALIGNMENT;
142
143        // Clamp to VMO size to avoid supplying pages out of bounds.
144        let vmo_size = file.vmo().get_size()?;
145        readahead_end = std::cmp::min(readahead_end, vmo_size);
146
147        if readahead_end <= readahead_start {
148            return Ok(());
149        }
150
151        let len = readahead_end - readahead_start;
152
153        // TODO(https://fxbug.dev/521911087): Use a pre-allocated mmapped transfer buffer to page
154        // in data instead of allocating a buffer and copying data multiple times.
155        let mut buf = vec![0u8; len as usize];
156        let read_bytes =
157            file.fs().read_file_range(file.node(), readahead_start, &mut buf).map_err(|e| {
158                log::error!("Read EROFS file range failed: {:?}", e);
159                e.to_status()
160            })?;
161
162        if read_bytes < buf.len() {
163            buf[read_bytes..].fill(0);
164        }
165
166        let aux_vmo = zx::Vmo::create(len)?;
167        aux_vmo.write(&buf, 0)?;
168
169        self.pager.supply_pages(file.vmo(), readahead_start..readahead_end, &aux_vmo, 0)?;
170        Ok(())
171    }
172
173    /// Handles the `VMO_ZERO_CHILDREN` signal from Zircon on the pager VMO.
174    ///
175    /// If `num_children` is 0, the `FileHolder` is downgraded from `Strong` to `Weak` to break the
176    /// cyclic reference and allow the file resources to be cleaned up if unused. If a new child
177    /// was created between the signal being emitted and us handling it, we re-register the
178    /// watcher so it will trigger again.
179    fn receive_signal_packet(&self, signals: zx::SignalPacket) {
180        storage_trace::duration!("storage", "ErofsPager::signal_zero_children");
181        assert!(signals.observed().contains(zx::Signals::VMO_ZERO_CHILDREN));
182
183        let mut file_holder = self.file.lock().unwrap();
184        let strong = match &*file_holder {
185            FileHolder::Strong(strong) => strong.clone(),
186            FileHolder::Weak(_) => return,
187        };
188
189        match strong.vmo().info() {
190            Ok(info) => {
191                if info.num_children == 0 {
192                    let weak = FileHolder::Weak(Arc::downgrade(&strong));
193                    *file_holder = weak;
194                } else {
195                    // Re-register the wait.
196                    if let Err(e) = strong.register_zero_children_wait() {
197                        log::error!("Failed to re-register VMO_ZERO_CHILDREN wait: {:?}", e);
198                    }
199                }
200            }
201            Err(e) => {
202                log::error!("Failed to query VMO info: {:?}", e);
203            }
204        }
205    }
206
207    fn receive_pager_packet(&self, contents: zx::PagerPacket) {
208        let command = contents.command();
209        if command == ZX_PAGER_VMO_COMPLETE {
210            storage_trace::duration!("storage", "ErofsPager::vmo_complete");
211            return;
212        }
213
214        let range = contents.range();
215        // If the command is anything except read, fail the request.
216        if command != ZX_PAGER_VMO_READ {
217            if let Ok(file) = self.get_file() {
218                let _ = self.pager.op_range(
219                    zx::PagerOp::Fail(zx::Status::NOT_SUPPORTED),
220                    file.vmo(),
221                    range,
222                );
223            }
224            return;
225        }
226
227        if let Err(e) = self.page_in(range.clone()) {
228            log::error!("Page fault handler failed: {:?}", e);
229            if let Ok(file) = self.get_file() {
230                let _ = self.pager.op_range(zx::PagerOp::Fail(e), file.vmo(), range);
231            }
232        }
233    }
234}
235
236impl fasync::PacketReceiver for ErofsPacketReceiver {
237    fn receive_packet(&self, packet: zx::Packet) {
238        match packet.contents() {
239            zx::PacketContents::Pager(contents) => {
240                self.receive_pager_packet(contents);
241            }
242            zx::PacketContents::SignalOne(signals) => {
243                self.receive_signal_packet(signals);
244            }
245            _ => {}
246        }
247    }
248}