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