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