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