1use crate::mm::ProtectionFlags;
6use crate::task::dynamic_thread_spawner::SpawnRequestBuilder;
7use crate::task::{CurrentTask, FullCredentials, Kernel, LockedAndTask};
8use crate::vfs::buffers::{VecInputBuffer, VecOutputBuffer};
9use crate::vfs::{
10 DirectoryEntryType, DirentSink, FileHandle, FileObject, FsStr, FsString, LookupContext,
11 NamespaceNode, RenameFlags, SeekTarget, UnlinkKind,
12};
13use fidl::HandleBased;
14use fidl::endpoints::{ClientEnd, ServerEnd};
15use fidl_fuchsia_io as fio;
16use fuchsia_runtime::UtcInstant;
17use futures::future::BoxFuture;
18use itertools::Either;
19use starnix_logging::{log_error, track_stub};
20use starnix_sync::{Locked, Unlocked};
21use starnix_types::convert::IntoFidl as _;
22use starnix_uapi::device_type::DeviceType;
23use starnix_uapi::errors::Errno;
24use starnix_uapi::file_mode::{AccessCheck, FileMode};
25use starnix_uapi::open_flags::OpenFlags;
26use starnix_uapi::vfs::ResolveFlags;
27use starnix_uapi::{errno, error, from_status_like_fdio, ino_t, off_t};
28use std::sync::Arc;
29use vfs::directory::mutable::connection::MutableConnection;
30use vfs::directory::{self};
31use vfs::{
32 ObjectRequestRef, ProtocolsExt, ToObjectRequest, attributes, execution_scope, file, path,
33};
34
35pub fn serve_file(
37 current_task: &CurrentTask,
38 file: &FileObject,
39 credentials: FullCredentials,
40) -> Result<(ClientEnd<fio::NodeMarker>, execution_scope::ExecutionScope), Errno> {
41 let (client_end, server_end) = fidl::endpoints::create_endpoints::<fio::NodeMarker>();
42 let scope = serve_file_at(server_end, current_task, file, credentials)?;
43 Ok((client_end, scope))
44}
45
46pub fn serve_file_at(
47 server_end: ServerEnd<fio::NodeMarker>,
48 current_task: &CurrentTask,
49 file: &FileObject,
50 credentials: FullCredentials,
51) -> Result<execution_scope::ExecutionScope, Errno> {
52 let kernel = current_task.kernel();
53 let open_flags = file.flags();
54 let starnix_file =
55 StarnixNodeConnection::new(&kernel, file.weak_handle.upgrade().unwrap(), credentials);
56 let scope = execution_scope::ExecutionScope::new();
57 kernel.kthreads.spawn_future({
58 let scope = scope.clone();
59 async move || {
60 let fidl_flags: fio::OpenFlags = open_flags.into_fidl();
61 if starnix_file.is_dir() {
62 fidl_flags.to_object_request(server_end).handle(|object_request| {
63 object_request.take().create_connection_sync::<MutableConnection<_>, _>(
64 scope.clone(),
65 starnix_file,
66 fidl_flags,
67 );
68 Ok(())
69 });
70 } else {
71 fidl_flags.to_object_request(server_end).handle(|object_request| {
72 object_request.take().create_connection_sync::<file::RawIoConnection<_>, _>(
73 scope.clone(),
74 starnix_file,
75 fidl_flags,
76 );
77 Ok(())
78 });
79 }
80 scope.wait().await;
81 }
82 });
83 Ok(scope)
84}
85
86#[async_trait::async_trait(?Send)]
87trait Work: Send + 'static {
88 async fn run(
89 self: Box<Self>,
90 locked: &mut Locked<Unlocked>,
91 current_task: &CurrentTask,
92 file: &FileHandle,
93 );
94}
95
96struct EitherSender<R>(Either<futures::channel::oneshot::Sender<R>, std::sync::mpsc::Sender<R>>);
97
98impl<R> From<futures::channel::oneshot::Sender<R>> for EitherSender<R> {
99 fn from(v: futures::channel::oneshot::Sender<R>) -> Self {
100 Self(Either::Left(v))
101 }
102}
103
104impl<R> From<std::sync::mpsc::Sender<R>> for EitherSender<R> {
105 fn from(v: std::sync::mpsc::Sender<R>) -> Self {
106 Self(Either::Right(v))
107 }
108}
109
110impl<R> EitherSender<R> {
111 async fn send(self, r: R) {
112 match self.0 {
113 Either::Left(s) => {
114 let _ = s.send(r);
115 }
116 Either::Right(s) => {
117 let _ = s.send(r);
118 }
119 }
120 }
121}
122
123struct WorkWrapper<R, F>
124where
125 R: Send + 'static,
126 F: AsyncFnOnce(&mut Locked<Unlocked>, &CurrentTask, &FileHandle) -> R + Send + 'static,
127{
128 f: F,
129 sender: EitherSender<R>,
130}
131
132#[async_trait::async_trait(?Send)]
133impl<R, F> Work for WorkWrapper<R, F>
134where
135 R: Send + 'static,
136 F: AsyncFnOnce(&mut Locked<Unlocked>, &CurrentTask, &FileHandle) -> R + Send + 'static,
137{
138 async fn run(
139 self: Box<Self>,
140 locked: &mut Locked<Unlocked>,
141 current_task: &CurrentTask,
142 file: &FileHandle,
143 ) {
144 let f: F = self.f;
145 let r = f(locked, current_task, file).await;
146 self.sender.send(r).await;
147 }
148}
149
150async fn handle_file(
151 locked_and_task: LockedAndTask<'_>,
152 credentials: FullCredentials,
153 file: FileHandle,
154 receiver: std::sync::mpsc::Receiver<Box<dyn Work>>,
155) {
156 locked_and_task
158 .current_task()
159 .override_creds_async(credentials.clone(), async || {
160 let file = match file.name.open(
162 &mut locked_and_task.unlocked(),
163 locked_and_task.current_task(),
164 file.flags(),
165 AccessCheck::skip(),
166 ) {
167 Ok(file) => file,
168 Err(e) => {
169 log_error!("Unable to reopen file: {e:?}");
170 return;
171 }
172 };
173 while let Ok(w) = receiver.recv() {
174 w.run(&mut locked_and_task.unlocked(), locked_and_task.current_task(), &file).await;
175 }
176 })
177 .await;
178}
179
180fn to_open_flags(flags: &impl ProtocolsExt) -> OpenFlags {
181 let rights = flags.rights().unwrap_or_default();
182 let mut open_flags = if rights.contains(fio::Operations::WRITE_BYTES) {
183 if rights.contains(fio::Operations::READ_BYTES) {
184 OpenFlags::RDWR
185 } else {
186 OpenFlags::WRONLY
187 }
188 } else {
189 OpenFlags::RDONLY
190 };
191
192 if flags.create_directory() {
193 open_flags |= OpenFlags::DIRECTORY;
194 }
195
196 match flags.creation_mode() {
197 vfs::CreationMode::Always => open_flags |= OpenFlags::CREAT | OpenFlags::EXCL,
198 vfs::CreationMode::AllowExisting => open_flags |= OpenFlags::CREAT,
199 vfs::CreationMode::UnnamedTemporary => open_flags |= OpenFlags::TMPFILE,
200 vfs::CreationMode::UnlinkableUnnamedTemporary => {
201 open_flags |= OpenFlags::TMPFILE | OpenFlags::EXCL
202 }
203 vfs::CreationMode::Never => {}
204 };
205
206 if flags.is_truncate() {
207 open_flags |= OpenFlags::TRUNC;
208 }
209
210 if flags.is_append() {
211 open_flags |= OpenFlags::APPEND;
212 }
213
214 open_flags
215}
216
217#[derive(Clone)]
230struct StarnixNodeConnection {
231 is_dir: bool,
232 credentials: FullCredentials,
233 work_sender: std::sync::mpsc::Sender<Box<dyn Work>>,
234}
235
236fn lookup_parent(
237 locked: &mut Locked<Unlocked>,
238 current_task: &CurrentTask,
239 file: &FileObject,
240 path: path::Path,
241) -> Result<(NamespaceNode, FsString), Errno> {
242 let (node, name) = current_task.lookup_parent(
243 locked,
244 &mut LookupContext::default(),
245 &file.name,
246 path.as_str().into(),
247 )?;
248 Ok((node, name.to_owned()))
249}
250
251impl StarnixNodeConnection {
252 fn new(kernel: &Kernel, file: FileHandle, credentials: FullCredentials) -> Arc<Self> {
253 let (work_sender, receiver) = std::sync::mpsc::channel();
254 let is_dir = file.node().is_dir();
255 let closure = {
256 let credentials = credentials.clone();
257 async move |locked_and_task: LockedAndTask<'_>| {
258 handle_file(locked_and_task, credentials, file, receiver).await;
259 }
260 };
261 let req = SpawnRequestBuilder::new().with_async_closure(closure).build();
262 kernel.kthreads.spawner().spawn_from_request(req);
263 Arc::new(Self { is_dir, credentials, work_sender })
264 }
265
266 fn spawn_task<R, E, F>(&self, f: F) -> Result<R, Errno>
267 where
268 R: Send + 'static,
269 E: Send + 'static,
270 F: AsyncFnOnce(&mut Locked<Unlocked>, &CurrentTask, &FileHandle) -> Result<R, E>
271 + Send
272 + 'static,
273 Errno: From<E>,
274 {
275 let (sender, receiver) = std::sync::mpsc::channel();
276 self.work_sender
277 .send(Box::new(WorkWrapper { f, sender: sender.into() }))
278 .map_err(|_| errno!(EIO))?;
279 Ok(receiver.recv().map_err(|_| errno!(EIO))??)
280 }
281
282 async fn spawn_task_async<R, E, F>(&self, f: F) -> Result<R, Errno>
283 where
284 R: Send + 'static,
285 E: Send + 'static,
286 F: AsyncFnOnce(&mut Locked<Unlocked>, &CurrentTask, &FileHandle) -> Result<R, E>
287 + Send
288 + 'static,
289 Errno: From<E>,
290 {
291 let (sender, receiver) = futures::channel::oneshot::channel();
292 self.work_sender
293 .send(Box::new(WorkWrapper { f, sender: sender.into() }))
294 .map_err(|_| errno!(EIO))?;
295 Ok(receiver.await.map_err(|_| errno!(EIO))??)
296 }
297
298 fn is_dir(&self) -> bool {
299 self.is_dir
300 }
301
302 fn lookup_parent(&self, path: path::Path) -> Result<(NamespaceNode, FsString), Errno> {
303 self.spawn_task(async move |locked, current_task, file| {
304 lookup_parent(locked, current_task, file, path)
305 })
306 }
307
308 fn reopen(&self, flags: &impl ProtocolsExt) -> Result<Arc<Self>, Errno> {
311 let credentials = self.credentials.clone();
312 let flags = to_open_flags(flags);
313 self.spawn_task(async move |locked, current_task, file| {
314 let file = file.name.open(locked, current_task, flags, AccessCheck::default())?;
315 Ok(StarnixNodeConnection::new(¤t_task.kernel(), file, credentials))
316 })
317 }
318
319 fn directory_read_dirents<'a>(
321 &'a self,
322 pos: &'a directory::traversal_position::TraversalPosition,
323 sink: Box<dyn directory::dirents_sink::Sink>,
324 ) -> Result<
325 (
326 directory::traversal_position::TraversalPosition,
327 Box<dyn directory::dirents_sink::Sealed>,
328 ),
329 Errno,
330 > {
331 let pos = pos.clone();
332 self.spawn_task(async move |locked, current_task, file| {
333 struct DirentSinkAdapter<'a> {
334 sink: Option<directory::dirents_sink::AppendResult>,
335 offset: &'a mut off_t,
336 }
337 impl<'a> DirentSinkAdapter<'a> {
338 fn append(
339 &mut self,
340 entry: &directory::entry::EntryInfo,
341 name: &str,
342 ) -> Result<(), Errno> {
343 let sink = self.sink.take();
344 self.sink = match sink {
345 s @ Some(directory::dirents_sink::AppendResult::Sealed(_)) => {
346 self.sink = s;
347 return error!(ENOSPC);
348 }
349 Some(directory::dirents_sink::AppendResult::Ok(sink)) => {
350 Some(sink.append(entry, name))
351 }
352 None => return error!(ENOTSUP),
353 };
354 Ok(())
355 }
356 }
357 impl<'a> DirentSink for DirentSinkAdapter<'a> {
358 fn add(
359 &mut self,
360 inode_num: ino_t,
361 offset: off_t,
362 entry_type: DirectoryEntryType,
363 name: &FsStr,
364 ) -> Result<(), Errno> {
365 if name != ".." {
367 if let Some(dirent_type) =
369 fio::DirentType::from_primitive(entry_type.bits())
370 {
371 let entry_info =
372 directory::entry::EntryInfo::new(inode_num, dirent_type);
373 self.append(&entry_info, &String::from_utf8_lossy(name))?
374 }
375 }
376 *self.offset = offset;
377 Ok(())
378 }
379 fn offset(&self) -> off_t {
380 *self.offset
381 }
382 }
383 let offset = match pos {
384 directory::traversal_position::TraversalPosition::Start => 0,
385 directory::traversal_position::TraversalPosition::Index(v) => v as i64,
386 directory::traversal_position::TraversalPosition::End => {
387 return Ok((
388 directory::traversal_position::TraversalPosition::End,
389 sink.seal(),
390 ));
391 }
392 _ => return error!(EINVAL),
393 };
394 if *file.offset.lock() != offset {
395 file.seek(locked, current_task, SeekTarget::Set(offset))?;
396 }
397 let mut file_offset = file.offset.lock();
398 let mut dirent_sink = DirentSinkAdapter {
399 sink: Some(directory::dirents_sink::AppendResult::Ok(sink)),
400 offset: &mut file_offset,
401 };
402 file.readdir(locked, current_task, &mut dirent_sink)?;
403 match dirent_sink.sink {
404 Some(directory::dirents_sink::AppendResult::Sealed(seal)) => {
405 Ok((directory::traversal_position::TraversalPosition::End, seal))
406 }
407 Some(directory::dirents_sink::AppendResult::Ok(sink)) => Ok((
408 directory::traversal_position::TraversalPosition::Index(*file_offset as u64),
409 sink.seal(),
410 )),
411 None => error!(ENOTSUP),
412 }
413 })
414 }
415
416 fn directory_entry_open(
418 self: Arc<Self>,
419 scope: execution_scope::ExecutionScope,
420 flags: impl ProtocolsExt,
421 path: path::Path,
422 object_request: ObjectRequestRef<'_>,
423 ) -> Result<(), zx::Status> {
424 if self.is_dir() {
425 if path.is_dot() {
426 let dir = self.reopen(&flags)?;
428 object_request
429 .take()
430 .create_connection_sync::<MutableConnection<_>, _>(scope, dir, flags);
431 return Ok(());
432 }
433
434 let starnix_file = self.spawn_task({
436 let credentials = self.credentials.clone();
437 let create_directory = flags.creation_mode() != vfs::common::CreationMode::Never
438 && flags.create_directory();
439 let open_flags = to_open_flags(&flags);
440 async move |locked, current_task, file| {
441 let (node, name) = lookup_parent(locked, current_task, file, path)?;
442 let file = match current_task.open_namespace_node_at(
443 locked,
444 node.clone(),
445 name.as_ref(),
446 open_flags,
447 FileMode::ALLOW_ALL,
448 ResolveFlags::empty(),
449 AccessCheck::default(),
450 ) {
451 Err(e) if e == errno!(EISDIR) && create_directory => {
452 let mode = current_task
453 .fs()
454 .apply_umask(FileMode::from_bits(0o777) | FileMode::IFDIR);
455 let name = node.create_node(
456 locked,
457 ¤t_task,
458 name.as_ref(),
459 mode,
460 DeviceType::NONE,
461 )?;
462 name.open(
463 locked,
464 ¤t_task,
465 open_flags & !(OpenFlags::CREAT | OpenFlags::EXCL),
466 AccessCheck::skip(),
467 )?
468 }
469 f => f?,
470 };
471 Ok(StarnixNodeConnection::new(¤t_task.kernel(), file, credentials))
472 }
473 })?;
474
475 return starnix_file.directory_entry_open(
476 scope,
477 flags,
478 path::Path::dot(),
479 object_request,
480 );
481 }
482
483 if !path.is_dot() {
485 return Err(zx::Status::NOT_DIR);
486 }
487 let file = self.reopen(&flags)?;
488 object_request
489 .take()
490 .create_connection_sync::<file::RawIoConnection<_>, _>(scope, file, flags);
491 Ok(())
492 }
493
494 fn get_attributes(
495 &self,
496 requested_attributes: fio::NodeAttributesQuery,
497 ) -> fio::NodeAttributes2 {
498 self.spawn_task(async move |_, _, file| {
499 let info = file.node().info();
500
501 #[allow(clippy::unnecessary_cast)]
503 let link_count = info.link_count as u64;
504
505 let (protocols, abilities) = if info.mode.contains(FileMode::IFDIR) {
506 (
507 fio::NodeProtocolKinds::DIRECTORY,
508 fio::Operations::GET_ATTRIBUTES
509 | fio::Operations::UPDATE_ATTRIBUTES
510 | fio::Operations::ENUMERATE
511 | fio::Operations::TRAVERSE
512 | fio::Operations::MODIFY_DIRECTORY,
513 )
514 } else {
515 (
516 fio::NodeProtocolKinds::FILE,
517 fio::Operations::GET_ATTRIBUTES
518 | fio::Operations::UPDATE_ATTRIBUTES
519 | fio::Operations::READ_BYTES
520 | fio::Operations::WRITE_BYTES,
521 )
522 };
523
524 Ok(attributes!(
525 requested_attributes,
526 Mutable {
527 creation_time: info.time_status_change.into_nanos() as u64,
528 modification_time: info.time_modify.into_nanos() as u64,
529 mode: info.mode.bits(),
530 uid: info.uid,
531 gid: info.gid,
532 rdev: info.rdev.bits(),
533 },
534 Immutable {
535 protocols: protocols,
536 abilities: abilities,
537 content_size: info.size as u64,
538 storage_size: info.storage_size() as u64,
539 link_count: link_count,
540 id: file.fs.dev_id.bits(),
541 }
542 ))
543 })
544 .expect("spawn_task")
545 }
546
547 fn update_attributes(&self, attributes: fio::MutableNodeAttributes) {
548 let _ = self.spawn_task(async move |_, _, file| {
549 file.node().update_info(|info| {
550 if let Some(time) = attributes.creation_time {
551 info.time_status_change = UtcInstant::from_nanos(time as i64);
552 }
553 if let Some(time) = attributes.modification_time {
554 info.time_modify = UtcInstant::from_nanos(time as i64);
555 }
556 if let Some(mode) = attributes.mode {
557 info.mode = FileMode::from_bits(mode);
558 }
559 if let Some(uid) = attributes.uid {
560 info.uid = uid;
561 }
562 if let Some(gid) = attributes.gid {
563 info.gid = gid;
564 }
565 if let Some(rdev) = attributes.rdev {
566 info.rdev = DeviceType::from_bits(rdev);
567 }
568 });
569 Ok(())
570 });
571 }
572}
573
574impl vfs::node::Node for StarnixNodeConnection {
575 async fn get_attributes(
576 &self,
577 requested_attributes: fio::NodeAttributesQuery,
578 ) -> Result<fio::NodeAttributes2, zx::Status> {
579 Ok(StarnixNodeConnection::get_attributes(self, requested_attributes))
580 }
581}
582
583impl directory::entry::GetEntryInfo for StarnixNodeConnection {
584 fn entry_info(&self) -> directory::entry::EntryInfo {
585 let dirent_type =
586 if self.is_dir() { fio::DirentType::Directory } else { fio::DirentType::File };
587 directory::entry::EntryInfo::new(0, dirent_type)
588 }
589}
590
591impl directory::entry_container::Directory for StarnixNodeConnection {
592 fn open(
593 self: Arc<Self>,
594 scope: execution_scope::ExecutionScope,
595 path: path::Path,
596 flags: fio::Flags,
597 object_request: ObjectRequestRef<'_>,
598 ) -> Result<(), zx::Status> {
599 self.directory_entry_open(scope, flags, path, object_request)
600 }
601
602 async fn read_dirents(
603 &self,
604 pos: &directory::traversal_position::TraversalPosition,
605 sink: Box<dyn directory::dirents_sink::Sink>,
606 ) -> Result<
607 (
608 directory::traversal_position::TraversalPosition,
609 Box<dyn directory::dirents_sink::Sealed>,
610 ),
611 zx::Status,
612 > {
613 StarnixNodeConnection::directory_read_dirents(self, pos, sink).map_err(Errno::into)
614 }
615 fn register_watcher(
616 self: Arc<Self>,
617 _scope: execution_scope::ExecutionScope,
618 _mask: fio::WatchMask,
619 _watcher: directory::entry_container::DirectoryWatcher,
620 ) -> Result<(), zx::Status> {
621 track_stub!(TODO("https://fxbug.dev/322875605"), "register directory watcher");
622 Ok(())
623 }
624 fn unregister_watcher(self: Arc<Self>, _key: usize) {}
625}
626
627impl directory::entry_container::MutableDirectory for StarnixNodeConnection {
628 async fn update_attributes(
629 &self,
630 attributes: fio::MutableNodeAttributes,
631 ) -> Result<(), zx::Status> {
632 StarnixNodeConnection::update_attributes(self, attributes);
633 Ok(())
634 }
635 async fn unlink(
636 self: Arc<Self>,
637 name: &str,
638 must_be_directory: bool,
639 ) -> Result<(), zx::Status> {
640 let name = FsString::from(name.to_owned());
641 self.spawn_task_async(async move |locked, current_task, file| {
642 let kind =
643 if must_be_directory { UnlinkKind::Directory } else { UnlinkKind::NonDirectory };
644 file.name.entry.unlink(
645 locked,
646 current_task,
647 &file.name.mount,
648 name.as_ref(),
649 kind,
650 false,
651 )
652 })
653 .await?;
654 Ok(())
655 }
656 async fn sync(&self) -> Result<(), zx::Status> {
657 Ok(())
658 }
659 fn rename(
660 self: Arc<Self>,
661 src_dir: Arc<dyn directory::entry_container::MutableDirectory>,
662 src_name: path::Path,
663 dst_name: path::Path,
664 ) -> BoxFuture<'static, Result<(), zx::Status>> {
665 let this = self.clone();
666 Box::pin(async move {
667 Ok(self
668 .spawn_task_async(async move |locked, current_task, file| {
669 let src_dir = src_dir
670 .into_any()
671 .downcast::<StarnixNodeConnection>()
672 .map_err(|_| errno!(EXDEV))?;
673 let (dst_node, dst_name) =
674 lookup_parent(locked, current_task, &file, dst_name)?;
675 let (src_node, src_name) = if Arc::ptr_eq(&src_dir, &this) {
676 lookup_parent(locked, current_task, &file, src_name)?
677 } else {
678 src_dir.lookup_parent(src_name)?
679 };
680 NamespaceNode::rename(
681 locked,
682 current_task,
683 &src_node,
684 src_name.as_ref(),
685 &dst_node,
686 dst_name.as_ref(),
687 RenameFlags::empty(),
688 )
689 })
690 .await?)
691 })
692 }
693}
694
695impl file::File for StarnixNodeConnection {
696 fn writable(&self) -> bool {
697 true
698 }
699 async fn open_file(&self, _optionss: &file::FileOptions) -> Result<(), zx::Status> {
700 Ok(())
701 }
702 async fn truncate(&self, length: u64) -> Result<(), zx::Status> {
703 Ok(self
704 .spawn_task_async(async move |locked, current_task, file| {
705 file.name.truncate(locked, current_task, length)
706 })
707 .await?)
708 }
709 async fn get_backing_memory(&self, flags: fio::VmoFlags) -> Result<zx::Vmo, zx::Status> {
710 Ok(self
711 .spawn_task_async(async move |locked, current_task, file| {
712 (|| {
713 let mut prot_flags = ProtectionFlags::empty();
714 if flags.contains(fio::VmoFlags::READ) {
715 prot_flags |= ProtectionFlags::READ;
716 }
717 if flags.contains(fio::VmoFlags::WRITE) {
718 prot_flags |= ProtectionFlags::WRITE;
719 }
720 if flags.contains(fio::VmoFlags::EXECUTE) {
721 prot_flags |= ProtectionFlags::EXEC;
722 }
723 let memory = file.get_memory(locked, current_task, None, prot_flags)?;
724 let vmo = memory.as_vmo().ok_or(zx::Status::NOT_SUPPORTED)?;
725 if flags.contains(fio::VmoFlags::PRIVATE_CLONE) {
726 let size = vmo.get_size()?;
727 vmo.create_child(zx::VmoChildOptions::SNAPSHOT_AT_LEAST_ON_WRITE, 0, size)
728 } else {
729 vmo.duplicate_handle(zx::Rights::SAME_RIGHTS)
730 }
731 })()
732 .map_err(|e| from_status_like_fdio!(e))
733 })
734 .await?)
735 }
736
737 async fn get_size(&self) -> Result<u64, zx::Status> {
738 Ok(self
739 .spawn_task_async(async move |_, _, file| Ok(file.node().info().size as u64))
740 .await?)
741 }
742 async fn update_attributes(
743 &self,
744 attributes: fio::MutableNodeAttributes,
745 ) -> Result<(), zx::Status> {
746 StarnixNodeConnection::update_attributes(self, attributes);
747 Ok(())
748 }
749 async fn sync(&self, _mode: file::SyncMode) -> Result<(), zx::Status> {
750 Ok(())
751 }
752}
753
754impl file::RawFileIoConnection for StarnixNodeConnection {
755 async fn read(&self, count: u64) -> Result<Vec<u8>, zx::Status> {
756 Ok(self
757 .spawn_task_async(async move |locked, current_task, file| {
758 let mut data = VecOutputBuffer::new(count as usize);
759 file.read(locked, current_task, &mut data)?;
760 Ok(data.into())
761 })
762 .await?)
763 }
764
765 async fn read_at(&self, offset: u64, count: u64) -> Result<Vec<u8>, zx::Status> {
766 Ok(self
767 .spawn_task_async(async move |locked, current_task, file| -> Result<Vec<u8>, Errno> {
768 let mut data = VecOutputBuffer::new(count as usize);
769 file.read_at(locked, current_task, offset as usize, &mut data)?;
770 Ok(data.into())
771 })
772 .await?)
773 }
774
775 async fn write(&self, content: &[u8]) -> Result<u64, zx::Status> {
776 let mut data = VecInputBuffer::new(content);
777 Ok(self
778 .spawn_task_async(async move |locked, current_task, file| {
779 let written = file.write(locked, current_task, &mut data)?;
780 Ok(written as u64)
781 })
782 .await?)
783 }
784
785 async fn write_at(&self, offset: u64, content: &[u8]) -> Result<u64, zx::Status> {
786 let mut data = VecInputBuffer::new(content);
787 Ok(self
788 .spawn_task_async(async move |locked, current_task, file| {
789 let written = file.write_at(locked, current_task, offset as usize, &mut data)?;
790 Ok(written as u64)
791 })
792 .await?)
793 }
794
795 async fn seek(&self, offset: i64, origin: fio::SeekOrigin) -> Result<u64, zx::Status> {
796 let target = match origin {
797 fio::SeekOrigin::Start => SeekTarget::Set(offset),
798 fio::SeekOrigin::Current => SeekTarget::Cur(offset),
799 fio::SeekOrigin::End => SeekTarget::End(offset),
800 };
801 Ok(self.spawn_task(async move |locked, current_task, file| {
802 let seek_result = file.seek(locked, current_task, target)?;
803 Ok(seek_result as u64)
804 })?)
805 }
806
807 fn set_flags(&self, flags: fio::Flags) -> Result<(), zx::Status> {
808 const SETTABLE_FLAGS_MASK: OpenFlags = OpenFlags::APPEND;
815 let flags = if flags.contains(fio::Flags::FILE_APPEND) {
816 OpenFlags::APPEND
817 } else {
818 OpenFlags::empty()
819 };
820 Ok(self.spawn_task(async move |_, _, file| {
821 file.update_file_flags(flags, SETTABLE_FLAGS_MASK);
822 Ok(())
823 })?)
824 }
825}
826
827#[cfg(test)]
828mod tests {
829 use super::*;
830 use crate::fs::tmpfs::TmpFs;
831 use crate::testing::*;
832 use crate::vfs::{FsString, Namespace};
833 use starnix_uapi::auth::{Capabilities, Credentials};
834 use std::collections::HashSet;
835 use syncio::{Zxio, ZxioOpenOptions, zxio_node_attr_has_t};
836
837 fn assert_directory_content(zxio: &Zxio, content: &[&[u8]]) {
838 let expected = content.iter().map(|&x| FsString::from(x)).collect::<HashSet<_>>();
839 let mut iterator = zxio.create_dirent_iterator().expect("iterator");
840 iterator.rewind().expect("iterator");
841 let found =
842 iterator.map(|x| x.as_ref().expect("dirent").name.clone()).collect::<HashSet<_>>();
843 assert_eq!(found, expected);
844 }
845
846 #[::fuchsia::test]
847 async fn access_file_system() {
848 spawn_kernel_and_run(async |locked, current_task| {
849 let kernel = current_task.kernel();
850 let fs = TmpFs::new_fs(locked, &kernel);
851
852 let file =
853 &fs.root().open_anonymous(locked, current_task, OpenFlags::RDWR).expect("open");
854 let (root_handle, scope) =
855 serve_file(current_task, file, FullCredentials::for_kernel()).expect("serve");
856
857 let fs_dev_id = fs.dev_id;
860 std::thread::spawn(move || {
861 let root_zxio = Zxio::create(root_handle.into_handle()).expect("create");
862
863 assert_directory_content(&root_zxio, &[b"."]);
864 assert_directory_content(&root_zxio, &[b"."]);
866
867 let attrs = root_zxio
868 .attr_get(zxio_node_attr_has_t { id: true, ..Default::default() })
869 .expect("attr_get");
870 assert_eq!(attrs.id, fs_dev_id.bits());
871
872 let mut attrs = syncio::zxio_node_attributes_t::default();
873 attrs.has.creation_time = true;
874 attrs.has.modification_time = true;
875 attrs.creation_time = 0;
876 attrs.modification_time = 42;
877 root_zxio.attr_set(&attrs).expect("attr_set");
878 let attrs = root_zxio
879 .attr_get(zxio_node_attr_has_t {
880 creation_time: true,
881 modification_time: true,
882 ..Default::default()
883 })
884 .expect("attr_get");
885 assert_eq!(attrs.creation_time, 0);
886 assert_eq!(attrs.modification_time, 42);
887
888 assert_eq!(
889 root_zxio
890 .open("foo", fio::PERM_READABLE | fio::PERM_WRITABLE, Default::default())
891 .expect_err("open"),
892 zx::Status::NOT_FOUND
893 );
894 let foo_zxio = root_zxio
895 .open(
896 "foo",
897 fio::PERM_READABLE
898 | fio::PERM_WRITABLE
899 | fio::Flags::FLAG_MAYBE_CREATE
900 | fio::Flags::PROTOCOL_FILE,
901 Default::default(),
902 )
903 .expect("zxio_open");
904 assert_directory_content(&root_zxio, &[b".", b"foo"]);
905
906 assert_eq!(foo_zxio.write(b"hello").expect("write"), 5);
907 assert_eq!(foo_zxio.write_at(2, b"ch").expect("write_at"), 2);
908 let mut buffer = [0; 7];
909 assert_eq!(foo_zxio.read_at(2, &mut buffer).expect("read_at"), 3);
910 assert_eq!(&buffer[..3], b"cho");
911 assert_eq!(foo_zxio.seek(syncio::SeekOrigin::Start, 0).expect("seek"), 0);
912 assert_eq!(foo_zxio.read(&mut buffer).expect("read"), 5);
913 assert_eq!(&buffer[..5], b"hecho");
914
915 let attrs = foo_zxio
916 .attr_get(zxio_node_attr_has_t { id: true, ..Default::default() })
917 .expect("attr_get");
918 assert_eq!(attrs.id, fs_dev_id.bits());
919
920 let mut attrs = syncio::zxio_node_attributes_t::default();
921 attrs.has.creation_time = true;
922 attrs.has.modification_time = true;
923 attrs.creation_time = 0;
924 attrs.modification_time = 42;
925 foo_zxio.attr_set(&attrs).expect("attr_set");
926 let attrs = foo_zxio
927 .attr_get(zxio_node_attr_has_t {
928 creation_time: true,
929 modification_time: true,
930 ..Default::default()
931 })
932 .expect("attr_get");
933 assert_eq!(attrs.creation_time, 0);
934 assert_eq!(attrs.modification_time, 42);
935
936 assert_eq!(
937 root_zxio
938 .open(
939 "bar/baz",
940 fio::Flags::PROTOCOL_DIRECTORY
941 | fio::Flags::FLAG_MAYBE_CREATE
942 | fio::PERM_READABLE
943 | fio::PERM_WRITABLE,
944 Default::default(),
945 )
946 .expect_err("open"),
947 zx::Status::NOT_FOUND
948 );
949
950 let bar_zxio = root_zxio
951 .open(
952 "bar",
953 fio::Flags::PROTOCOL_DIRECTORY
954 | fio::Flags::FLAG_MAYBE_CREATE
955 | fio::PERM_READABLE
956 | fio::PERM_WRITABLE,
957 Default::default(),
958 )
959 .expect("open");
960 let baz_zxio = root_zxio
961 .open(
962 "bar/baz",
963 fio::Flags::PROTOCOL_DIRECTORY
964 | fio::Flags::FLAG_MAYBE_CREATE
965 | fio::PERM_READABLE
966 | fio::PERM_WRITABLE,
967 Default::default(),
968 )
969 .expect("open");
970 assert_directory_content(&root_zxio, &[b".", b"foo", b"bar"]);
971 assert_directory_content(&bar_zxio, &[b".", b"baz"]);
972
973 bar_zxio.rename("baz", &root_zxio, "quz").expect("rename");
974 assert_directory_content(&bar_zxio, &[b"."]);
975 assert_directory_content(&root_zxio, &[b".", b"foo", b"bar", b"quz"]);
976 assert_directory_content(&baz_zxio, &[b"."]);
977 })
978 .join()
979 .expect("join");
980 scope.shutdown();
981 scope.wait().await;
982 std::mem::drop(fs);
984 })
985 .await;
986 }
987
988 #[::fuchsia::test]
989 async fn open() {
990 spawn_kernel_and_run(async |locked, current_task| {
991 let kernel = current_task.kernel();
992 let fs = TmpFs::new_fs(locked, &kernel);
993
994 let file = &fs
995 .root()
996 .open_anonymous(locked, current_task, OpenFlags::RDWR)
997 .expect("open_anonymous failed");
998 let (root_handle, scope) =
999 serve_file(current_task, file, FullCredentials::for_kernel())
1000 .expect("serve_file failed");
1001
1002 std::thread::spawn(move || {
1003 let root_zxio =
1004 Zxio::create(root_handle.into_handle()).expect("zxio create failed");
1005
1006 assert_directory_content(&root_zxio, &[b"."]);
1007 assert_eq!(
1008 root_zxio
1009 .open(
1010 "foo",
1011 fio::PERM_READABLE | fio::PERM_WRITABLE,
1012 ZxioOpenOptions::default()
1013 )
1014 .expect_err("open3 passed unexpectedly"),
1015 zx::Status::NOT_FOUND
1016 );
1017 root_zxio
1018 .open(
1019 "foo",
1020 fio::Flags::PROTOCOL_FILE
1021 | fio::PERM_READABLE
1022 | fio::PERM_WRITABLE
1023 | fio::Flags::FLAG_MUST_CREATE,
1024 ZxioOpenOptions::default(),
1025 )
1026 .expect("open3 failed");
1027 assert_directory_content(&root_zxio, &[b".", b"foo"]);
1028
1029 assert_eq!(
1030 root_zxio
1031 .open(
1032 "bar/baz",
1033 fio::Flags::PROTOCOL_DIRECTORY
1034 | fio::PERM_READABLE
1035 | fio::PERM_WRITABLE
1036 | fio::Flags::FLAG_MUST_CREATE,
1037 ZxioOpenOptions::default()
1038 )
1039 .expect_err("open3 passed unexpectedly"),
1040 zx::Status::NOT_FOUND
1041 );
1042 let bar_zxio = root_zxio
1043 .open(
1044 "bar",
1045 fio::Flags::PROTOCOL_DIRECTORY
1046 | fio::PERM_READABLE
1047 | fio::PERM_WRITABLE
1048 | fio::Flags::FLAG_MUST_CREATE,
1049 ZxioOpenOptions::default(),
1050 )
1051 .expect("open3 failed");
1052 root_zxio
1053 .open(
1054 "bar/baz",
1055 fio::Flags::PROTOCOL_DIRECTORY
1056 | fio::PERM_READABLE
1057 | fio::PERM_WRITABLE
1058 | fio::Flags::FLAG_MUST_CREATE,
1059 ZxioOpenOptions::default(),
1060 )
1061 .expect("open3 failed");
1062 assert_directory_content(&root_zxio, &[b".", b"foo", b"bar"]);
1063 assert_directory_content(&bar_zxio, &[b".", b"baz"]);
1064 })
1065 .join()
1066 .expect("join");
1067 scope.shutdown();
1068 scope.wait().await;
1069
1070 std::mem::drop(fs);
1072 })
1073 .await;
1074 }
1075
1076 #[::fuchsia::test]
1077 async fn use_credentials() {
1078 spawn_kernel_and_run(async |locked, current_task| {
1079 let kernel = current_task.kernel();
1080 let fs = TmpFs::new_fs(locked, &kernel);
1081
1082 let file = &fs
1083 .root()
1084 .open_anonymous(locked, current_task, OpenFlags::RDWR)
1085 .expect("open_anonymous failed");
1086 let ns = Namespace::new(fs);
1088 ns.root()
1089 .open_create_node(
1090 locked,
1091 current_task,
1092 "test".into(),
1093 FileMode::from_bits(0o600) | FileMode::IFREG,
1094 DeviceType::NONE,
1095 OpenFlags::empty(),
1096 )
1097 .expect("open_create_node failed");
1098
1099 let mut user_credentials = FullCredentials::for_kernel();
1100 let mut creds = Credentials::with_ids(0, 0);
1101 creds.fsuid = 1;
1102 creds.cap_effective = Capabilities::empty();
1103 user_credentials.creds = creds.into();
1104
1105 let (root_handle, scope) =
1106 serve_file(current_task, file, user_credentials).expect("serve_file failed");
1107
1108 std::thread::spawn(move || {
1109 let root_zxio =
1110 Zxio::create(root_handle.into_handle()).expect("zxio create failed");
1111
1112 assert_directory_content(&root_zxio, &[b".", b"test"]);
1113 assert_eq!(
1114 root_zxio
1115 .open(
1116 "test",
1117 fio::PERM_READABLE | fio::PERM_WRITABLE,
1118 ZxioOpenOptions::default()
1119 )
1120 .expect_err("open3 passed unexpectedly"),
1121 zx::Status::ACCESS_DENIED
1122 );
1123 })
1124 .join()
1125 .expect("join");
1126 scope.shutdown();
1127 scope.wait().await;
1128
1129 std::mem::drop(ns);
1131 })
1132 .await;
1133 }
1134}