Skip to main content

erofs_component/
directory.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 crate::volume::ErofsVolume;
7use erofs::{DirectoryNode, FileType, Node};
8use fidl_fuchsia_io as fio;
9use fuchsia_sync::Mutex;
10use std::sync::Arc;
11use vfs::directory::dirents_sink::{self, AppendResult};
12use vfs::directory::entry::{DirectoryEntry, EntryInfo, GetEntryInfo, OpenRequest};
13use vfs::directory::entry_container::{Directory, DirectoryWatcher};
14use vfs::directory::immutable::connection::ImmutableConnection;
15use vfs::directory::traversal_position::TraversalPosition;
16use vfs::directory::watchers::Watchers;
17use vfs::directory::watchers::event_producers::{SingleNameEventProducer, StaticVecEventProducer};
18use vfs::execution_scope::ExecutionScope;
19use vfs::path::Path;
20use vfs::{CreationMode, ObjectRequestRef, ProtocolsExt};
21
22/// A directory in the EROFS filesystem.
23pub struct ErofsDirectory {
24    volume: Arc<ErofsVolume>,
25    node: DirectoryNode,
26    watchers: Mutex<Watchers>,
27}
28
29fn check_open_flags(flags: fio::Flags, exists: bool) -> Result<(), zx::Status> {
30    match flags.creation_mode() {
31        CreationMode::Never => Ok(()),
32        CreationMode::Always => {
33            if exists {
34                Err(zx::Status::ALREADY_EXISTS)
35            } else {
36                Err(zx::Status::NOT_SUPPORTED)
37            }
38        }
39        CreationMode::AllowExisting => {
40            if exists {
41                Ok(())
42            } else {
43                Err(zx::Status::NOT_SUPPORTED)
44            }
45        }
46        CreationMode::UnnamedTemporary | CreationMode::UnlinkableUnnamedTemporary => {
47            Err(zx::Status::NOT_SUPPORTED)
48        }
49    }
50}
51
52impl ErofsDirectory {
53    pub fn new(volume: Arc<ErofsVolume>, node: DirectoryNode) -> Self {
54        Self { volume, node, watchers: Mutex::new(Watchers::new()) }
55    }
56}
57
58impl DirectoryEntry for ErofsDirectory {
59    fn open_entry(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), zx::Status> {
60        request.open_dir(self)
61    }
62}
63
64impl GetEntryInfo for ErofsDirectory {
65    fn entry_info(&self) -> EntryInfo {
66        EntryInfo::new(self.node.nid(), fio::DirentType::Directory)
67    }
68}
69
70impl vfs::node::Node for ErofsDirectory {
71    async fn get_attributes(
72        &self,
73        requested_attributes: fio::NodeAttributesQuery,
74    ) -> Result<fio::NodeAttributes2, zx::Status> {
75        let mtime = self.node.mtime_ns();
76        Ok(vfs::attributes!(
77            requested_attributes,
78            Mutable {
79                mode: self.node.mode() as u32,
80                uid: self.node.uid(),
81                gid: self.node.gid(),
82                creation_time: mtime,
83                modification_time: mtime,
84                access_time: mtime,
85            },
86            Immutable {
87                protocols: fio::NodeProtocolKinds::DIRECTORY,
88                abilities: fio::Operations::GET_ATTRIBUTES
89                    | fio::Operations::ENUMERATE
90                    | fio::Operations::TRAVERSE,
91                id: self.node.nid(),
92                link_count: self.node.link_count() as u64,
93                change_time: mtime,
94            }
95        ))
96    }
97
98    fn query_filesystem(&self) -> Result<fio::FilesystemInfo, zx::Status> {
99        self.volume.query_filesystem()
100    }
101
102    async fn list_extended_attributes(&self) -> Result<Vec<Vec<u8>>, zx::Status> {
103        self.volume.fs().list_xattrs(&self.node).map_err(|e| e.to_status())
104    }
105
106    async fn get_extended_attribute(&self, name: Vec<u8>) -> Result<Vec<u8>, zx::Status> {
107        self.volume
108            .fs()
109            .get_xattr(&self.node, &name)
110            .map_err(|e| e.to_status())?
111            .ok_or(zx::Status::NOT_FOUND)
112    }
113}
114
115impl Directory for ErofsDirectory {
116    fn open(
117        self: Arc<Self>,
118        scope: ExecutionScope,
119        mut path: Path,
120        flags: fio::Flags,
121        object_request: ObjectRequestRef<'_>,
122    ) -> Result<(), zx::Status> {
123        let (name, _) = match path.next_with_ref() {
124            (path_ref, Some(name)) => (name, path_ref),
125            (_, None) => {
126                // We are opening this directory itself.
127                check_open_flags(flags, true)?;
128                object_request
129                    .take()
130                    .create_connection_sync::<ImmutableConnection<_>, _>(scope, self, flags);
131                return Ok(());
132            }
133        };
134
135        // Lookup the child in the EROFS image.
136        let child_node_opt = self.volume.fs().lookup(&self.node, name).map_err(|e| {
137            log::error!("Lookup failed for '{}': {:?}", name, e);
138            e.to_status()
139        })?;
140
141        let child_node = match child_node_opt {
142            Some(node) => node,
143            None => {
144                if path.is_empty() {
145                    check_open_flags(flags, false)?;
146                }
147                return Err(zx::Status::NOT_FOUND);
148            }
149        };
150
151        if path.is_empty() {
152            check_open_flags(flags, true)?;
153        }
154
155        // Delegate the remaining path traversal to the child.
156        match child_node {
157            Node::Directory(dir_node) => {
158                let child_dir = Arc::new(ErofsDirectory::new(self.volume.clone(), dir_node));
159                child_dir.open(scope, path, flags, object_request)
160            }
161            Node::File(file_node) => {
162                if !path.is_empty() {
163                    return Err(zx::Status::NOT_DIR);
164                }
165                let child_file = ErofsFile::new(self.volume.clone(), file_node)?;
166                vfs::file::serve(child_file, scope, &flags, object_request)
167            }
168        }
169    }
170
171    async fn read_dirents(
172        &self,
173        pos: &TraversalPosition,
174        sink: Box<dyn dirents_sink::Sink>,
175    ) -> Result<(TraversalPosition, Box<dyn dirents_sink::Sealed>), zx::Status> {
176        let mut entry_offset = match pos {
177            TraversalPosition::Start => 0,
178            TraversalPosition::Index(index) => *index,
179            TraversalPosition::End => return Ok((TraversalPosition::End, sink.seal())),
180            _ => return Err(zx::Status::NOT_SUPPORTED),
181        };
182
183        let mut sink = sink;
184        let mut buffer = vec![erofs::DirectoryEntry::default(); 16];
185
186        loop {
187            let filled = self
188                .volume
189                .fs()
190                .read_directory(&self.node, entry_offset as usize, &mut buffer)
191                .map_err(|e| {
192                    log::error!("Read directory failed at offset {}: {:?}", entry_offset, e);
193                    e.to_status()
194                })?;
195
196            for i in 0..filled {
197                let entry = &buffer[i];
198                if entry.name == ".." {
199                    entry_offset += 1;
200                    continue;
201                }
202                let dirent_type = match entry.file_type {
203                    FileType::RegFile => fio::DirentType::File,
204                    FileType::Dir => fio::DirentType::Directory,
205                    FileType::Symlink => fio::DirentType::Symlink,
206                    _ => fio::DirentType::Unknown,
207                };
208
209                let entry_info = EntryInfo::new(entry.nid, dirent_type);
210                match sink.append(&entry_info, &entry.name) {
211                    AppendResult::Ok(new_sink) => {
212                        sink = new_sink;
213                        entry_offset += 1;
214                    }
215                    AppendResult::Sealed(sealed) => {
216                        return Ok((TraversalPosition::Index(entry_offset), sealed));
217                    }
218                }
219            }
220
221            if filled < buffer.len() {
222                break;
223            }
224        }
225
226        Ok((TraversalPosition::End, sink.seal()))
227    }
228
229    fn register_watcher(
230        self: Arc<Self>,
231        scope: ExecutionScope,
232        mask: fio::WatchMask,
233        watcher: DirectoryWatcher,
234    ) -> Result<(), zx::Status> {
235        let mut entry_names = vec![];
236        let mut entry_offset = 0;
237        let mut buffer = vec![erofs::DirectoryEntry::default(); 16];
238        loop {
239            let filled =
240                self.volume.fs().read_directory(&self.node, entry_offset, &mut buffer).map_err(
241                    |e| {
242                        log::error!(
243                            "Watcher read directory failed at offset {}: {:?}",
244                            entry_offset,
245                            e
246                        );
247                        e.to_status()
248                    },
249                )?;
250            if filled == 0 {
251                break;
252            }
253            for i in 0..filled {
254                let name = &buffer[i].name;
255                if name != ".." {
256                    entry_names.push(name.clone());
257                }
258            }
259            entry_offset += filled;
260        }
261
262        let mut names = StaticVecEventProducer::existing(entry_names);
263
264        let mut watchers = self.watchers.lock();
265        let controller = watchers.add(scope, self.clone(), mask, watcher);
266        controller.send_event(&mut names);
267        controller.send_event(&mut SingleNameEventProducer::idle());
268
269        Ok(())
270    }
271
272    fn unregister_watcher(self: Arc<Self>, key: usize) {
273        self.watchers.lock().remove(key);
274    }
275}