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