1use 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
23pub 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 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 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 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}