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.ino() as u64, 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        Ok(vfs::immutable_attributes!(
76            requested_attributes,
77            Immutable {
78                protocols: fio::NodeProtocolKinds::DIRECTORY,
79                abilities: fio::Operations::GET_ATTRIBUTES
80                    | fio::Operations::ENUMERATE
81                    | fio::Operations::TRAVERSE,
82                id: self.node.ino() as u64,
83            }
84        ))
85    }
86}
87
88impl Directory for ErofsDirectory {
89    fn open(
90        self: Arc<Self>,
91        scope: ExecutionScope,
92        mut path: Path,
93        flags: fio::Flags,
94        object_request: ObjectRequestRef<'_>,
95    ) -> Result<(), zx::Status> {
96        let (name, _) = match path.next_with_ref() {
97            (path_ref, Some(name)) => (name, path_ref),
98            (_, None) => {
99                // We are opening this directory itself.
100                check_open_flags(flags, true)?;
101                object_request
102                    .take()
103                    .create_connection_sync::<ImmutableConnection<_>, _>(scope, self, flags);
104                return Ok(());
105            }
106        };
107
108        // Lookup the child in the EROFS image.
109        let child_node_opt = self.volume.fs().lookup(&self.node, name).map_err(|e| {
110            log::error!("Lookup failed for '{}': {:?}", name, e);
111            e.to_status()
112        })?;
113
114        let child_node = match child_node_opt {
115            Some(node) => node,
116            None => {
117                if path.is_empty() {
118                    check_open_flags(flags, false)?;
119                }
120                return Err(zx::Status::NOT_FOUND);
121            }
122        };
123
124        if path.is_empty() {
125            check_open_flags(flags, true)?;
126        }
127
128        // Delegate the remaining path traversal to the child.
129        match child_node {
130            Node::Directory(dir_node) => {
131                let child_dir = Arc::new(ErofsDirectory::new(self.volume.clone(), dir_node));
132                child_dir.open(scope, path, flags, object_request)
133            }
134            Node::File(file_node) => {
135                if !path.is_empty() {
136                    return Err(zx::Status::NOT_DIR);
137                }
138                let child_file = ErofsFile::new(self.volume.clone(), file_node)?;
139                vfs::file::serve(child_file, scope, &flags, object_request)
140            }
141        }
142    }
143
144    async fn read_dirents(
145        &self,
146        pos: &TraversalPosition,
147        sink: Box<dyn dirents_sink::Sink>,
148    ) -> Result<(TraversalPosition, Box<dyn dirents_sink::Sealed>), zx::Status> {
149        let mut entry_offset = match pos {
150            TraversalPosition::Start => 0,
151            TraversalPosition::Index(index) => *index,
152            TraversalPosition::End => return Ok((TraversalPosition::End, sink.seal())),
153            _ => return Err(zx::Status::NOT_SUPPORTED),
154        };
155
156        let mut sink = sink;
157        let mut buffer = vec![erofs::DirectoryEntry::default(); 16];
158
159        loop {
160            let filled = self
161                .volume
162                .fs()
163                .read_directory(&self.node, entry_offset as usize, &mut buffer)
164                .map_err(|e| {
165                    log::error!("Read directory failed at offset {}: {:?}", entry_offset, e);
166                    e.to_status()
167                })?;
168
169            for i in 0..filled {
170                let entry = &buffer[i];
171                if entry.name == ".." {
172                    entry_offset += 1;
173                    continue;
174                }
175                let dirent_type = match entry.file_type {
176                    FileType::RegFile => fio::DirentType::File,
177                    FileType::Dir => fio::DirentType::Directory,
178                    FileType::Symlink => fio::DirentType::Symlink,
179                    _ => fio::DirentType::Unknown,
180                };
181
182                // We have to go parse the child inode entry to find the ino.
183                let child = self.volume.fs().node(entry.nid).map_err(|e| {
184                    log::error!("Failed to lookup child node {} for ino: {:?}", entry.nid, e);
185                    e.to_status()
186                })?;
187                let ino = child.ino();
188
189                let entry_info = EntryInfo::new(ino as u64, dirent_type);
190                match sink.append(&entry_info, &entry.name) {
191                    AppendResult::Ok(new_sink) => {
192                        sink = new_sink;
193                        entry_offset += 1;
194                    }
195                    AppendResult::Sealed(sealed) => {
196                        return Ok((TraversalPosition::Index(entry_offset), sealed));
197                    }
198                }
199            }
200
201            if filled < buffer.len() {
202                break;
203            }
204        }
205
206        Ok((TraversalPosition::End, sink.seal()))
207    }
208
209    fn register_watcher(
210        self: Arc<Self>,
211        scope: ExecutionScope,
212        mask: fio::WatchMask,
213        watcher: DirectoryWatcher,
214    ) -> Result<(), zx::Status> {
215        let mut entry_names = vec![];
216        let mut entry_offset = 0;
217        let mut buffer = vec![erofs::DirectoryEntry::default(); 16];
218        loop {
219            let filled =
220                self.volume.fs().read_directory(&self.node, entry_offset, &mut buffer).map_err(
221                    |e| {
222                        log::error!(
223                            "Watcher read directory failed at offset {}: {:?}",
224                            entry_offset,
225                            e
226                        );
227                        e.to_status()
228                    },
229                )?;
230            if filled == 0 {
231                break;
232            }
233            for i in 0..filled {
234                let name = &buffer[i].name;
235                if name != ".." {
236                    entry_names.push(name.clone());
237                }
238            }
239            entry_offset += filled;
240        }
241
242        let mut names = StaticVecEventProducer::existing(entry_names);
243
244        let mut watchers = self.watchers.lock();
245        let controller = watchers.add(scope, self.clone(), mask, watcher);
246        controller.send_event(&mut names);
247        controller.send_event(&mut SingleNameEventProducer::idle());
248
249        Ok(())
250    }
251
252    fn unregister_watcher(self: Arc<Self>, key: usize) {
253        self.watchers.lock().remove(key);
254    }
255}