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