1use std::ffi::{CString, OsString};
6use std::future::Future;
7use std::marker::PhantomData;
8use std::mem::zeroed;
9use std::os::unix::ffi::OsStringExt;
10use std::path::PathBuf;
11use std::pin::Pin;
12use std::task::{self, Poll};
13use std::time::{Duration, SystemTime};
14use std::{fmt, io, str};
15
16use crate::extract::Extractor;
17use crate::fd::{AsyncFd, Descriptor, File};
18use crate::op::{op_future, poll_state, OpState};
19use crate::{libc, man_link, Extract, SubmissionQueue};
20
21const METADATA_FLAGS: u32 = libc::STATX_TYPE
23 | libc::STATX_MODE
24 | libc::STATX_SIZE
25 | libc::STATX_BLOCKS
26 | libc::STATX_ATIME
27 | libc::STATX_MTIME
28 | libc::STATX_BTIME;
29
30#[derive(Clone, Debug)]
32#[must_use = "no file is opened until `a10::fs::OpenOptions::open` or `open_temp_file` is called"]
33pub struct OpenOptions {
34 flags: libc::c_int,
35 mode: libc::mode_t,
36}
37
38impl OpenOptions {
39 pub const fn new() -> OpenOptions {
41 OpenOptions {
42 flags: libc::O_RDONLY, mode: 0o666, }
45 }
46
47 #[doc(alias = "O_RDONLY")]
53 #[doc(alias = "O_RDWR")]
54 pub const fn read(mut self) -> Self {
55 if (self.flags & libc::O_ACCMODE) == libc::O_WRONLY {
56 self.flags &= !libc::O_ACCMODE;
57 self.flags |= libc::O_RDWR;
58 } self
60 }
61
62 #[doc(alias = "O_RDWR")]
64 pub const fn write(mut self) -> Self {
65 if (self.flags & libc::O_ACCMODE) == libc::O_RDONLY {
66 self.flags &= !libc::O_ACCMODE;
67 self.flags |= libc::O_RDWR;
68 } self
70 }
71
72 #[doc(alias = "O_WRONLY")]
74 pub const fn write_only(mut self) -> Self {
75 self.flags &= !libc::O_ACCMODE;
76 self.flags |= libc::O_WRONLY;
77 self
78 }
79
80 #[doc(alias = "O_APPEND")]
88 pub const fn append(mut self) -> Self {
89 self.flags |= libc::O_APPEND;
90 self
91 }
92
93 #[doc(alias = "O_TRUNC")]
95 pub const fn truncate(mut self) -> Self {
96 self.flags |= libc::O_TRUNC;
97 self
98 }
99
100 pub const fn create(mut self) -> Self {
102 self.flags |= libc::O_CREAT;
103 self
104 }
105
106 #[doc(alias = "O_EXCL")]
110 #[doc(alias = "O_CREAT")]
111 pub const fn create_new(mut self) -> Self {
112 self.flags |= libc::O_CREAT | libc::O_EXCL;
113 self
114 }
115
116 #[doc(alias = "O_DSYNC")]
124 pub const fn data_sync(mut self) -> Self {
125 self.flags |= libc::O_DSYNC;
126 self
127 }
128
129 #[doc(alias = "O_SYNC")]
139 pub const fn sync(mut self) -> Self {
140 self.flags |= libc::O_SYNC;
141 self
142 }
143
144 #[doc(alias = "O_DIRECT")]
153 pub const fn direct(mut self) -> Self {
154 self.flags |= libc::O_DIRECT;
155 self
156 }
157
158 pub const fn mode(mut self, mode: libc::mode_t) -> Self {
160 self.mode = mode;
161 self
162 }
163
164 #[doc(alias = "O_TMPFILE")]
172 pub fn open_temp_file<D: Descriptor>(mut self, sq: SubmissionQueue, dir: PathBuf) -> Open<D> {
173 self.flags |= libc::O_TMPFILE;
174 self.open(sq, dir)
175 }
176
177 #[doc = man_link!(openat(2))]
179 #[doc(alias = "openat")]
180 pub fn open<D: Descriptor>(self, sq: SubmissionQueue, path: PathBuf) -> Open<D> {
181 Open {
182 path: Some(path_to_cstring(path)),
183 sq: Some(sq),
184 state: OpState::NotStarted((self.flags | D::cloexec_flag(), self.mode)),
185 kind: PhantomData,
186 }
187 }
188}
189
190#[doc = man_link!(openat(2))]
192pub fn open_file(sq: SubmissionQueue, path: PathBuf) -> Open<File> {
193 OpenOptions::new().read().open(sq, path)
194}
195
196#[derive(Debug)]
200#[must_use = "`Future`s do nothing unless polled"]
201pub struct Open<D: Descriptor = File> {
202 path: Option<CString>,
207 sq: Option<SubmissionQueue>,
208 state: OpState<(libc::c_int, libc::mode_t)>,
209 kind: PhantomData<D>,
210}
211
212impl<D: Descriptor + Unpin> Future for Open<D> {
213 type Output = io::Result<AsyncFd<D>>;
214
215 fn poll(mut self: Pin<&mut Self>, ctx: &mut task::Context<'_>) -> Poll<Self::Output> {
216 let op_index = poll_state!(
217 Open,
218 self.state,
219 self.sq.as_ref().unwrap(),
220 ctx,
221 |submission, (flags, mode)| unsafe {
222 let path = self.path.as_ref().unwrap();
224 submission.open_at(libc::AT_FDCWD, path.as_ptr(), flags, mode);
225 D::create_flags(submission);
226 }
227 );
228
229 match self.sq.as_ref().unwrap().poll_op(ctx, op_index) {
230 Poll::Ready(result) => {
231 self.state = OpState::Done;
232 match result {
233 Ok((_, fd)) => Poll::Ready(Ok(unsafe {
234 AsyncFd::from_raw(fd, self.sq.take().unwrap())
237 })),
238 Err(err) => Poll::Ready(Err(err)),
239 }
240 }
241 Poll::Pending => Poll::Pending,
242 }
243 }
244}
245
246impl<D: Descriptor> Extract for Open<D> {}
247
248impl<D: Descriptor + Unpin> Future for Extractor<Open<D>> {
249 type Output = io::Result<(AsyncFd<D>, PathBuf)>;
250
251 fn poll(mut self: Pin<&mut Self>, ctx: &mut task::Context<'_>) -> Poll<Self::Output> {
252 match Pin::new(&mut self.fut).poll(ctx) {
253 Poll::Ready(Ok(file)) => {
254 let path = path_from_cstring(self.fut.path.take().unwrap());
255 Poll::Ready(Ok((file, path)))
256 }
257 Poll::Ready(Err(err)) => Poll::Ready(Err(err)),
258 Poll::Pending => Poll::Pending,
259 }
260 }
261}
262
263impl<D: Descriptor> Drop for Open<D> {
264 fn drop(&mut self) {
265 if let Some(path) = self.path.take() {
266 match self.state {
267 OpState::Running(op_index) => {
268 let sq = self.sq.as_ref().unwrap();
272 let result = sq.cancel_op(
273 op_index,
274 || path,
275 |submission| unsafe {
276 submission.cancel_op(op_index);
277 submission.no_completion_event();
280 },
281 );
282 if let Err(err) = result {
283 log::error!(
284 "dropped a10::Open before completion, attempt to cancel failed: {err}"
285 );
286 }
287 }
288 OpState::NotStarted(_) | OpState::Done => drop(path),
289 }
290 }
291 }
292}
293
294impl<D: Descriptor> AsyncFd<D> {
296 #[doc = man_link!(fsync(2))]
302 #[doc(alias = "fsync")]
303 pub const fn sync_all<'fd>(&'fd self) -> SyncData<'fd, D> {
304 SyncData::new(self, 0)
305 }
306
307 #[doc = man_link!(fsync(2))]
320 #[doc(alias = "fdatasync")]
321 pub const fn sync_data<'fd>(&'fd self) -> SyncData<'fd, D> {
322 SyncData::new(self, libc::IORING_FSYNC_DATASYNC)
323 }
324
325 #[doc = man_link!(statx(2))]
327 #[doc(alias = "statx")]
328 pub fn metadata<'fd>(&'fd self) -> Stat<'fd, D> {
329 let metadata = Box::new(Metadata {
330 inner: unsafe { zeroed() },
332 });
333 Stat::new(self, metadata, ())
334 }
335
336 #[doc = man_link!(posix_fadvise(2))]
346 #[doc(alias = "fadvise")]
347 #[doc(alias = "posix_fadvise")]
348 pub const fn advise<'fd>(
349 &'fd self,
350 offset: u64,
351 length: u32,
352 advice: libc::c_int,
353 ) -> Advise<'fd, D> {
354 Advise::new(self, (offset, length, advice))
355 }
356
357 #[doc = man_link!(fallocate(2))]
362 #[doc(alias = "fallocate")]
363 #[doc(alias = "posix_fallocate")]
364 pub const fn allocate<'fd>(
365 &'fd self,
366 offset: u64,
367 length: u32,
368 mode: libc::c_int,
369 ) -> Allocate<'fd, D> {
370 Allocate::new(self, (offset, length, mode))
371 }
372
373 #[doc = man_link!(ftruncate(2))]
379 #[doc(alias = "ftruncate")]
380 pub const fn truncate<'fd>(&'fd self, length: u64) -> Truncate<'fd, D> {
381 Truncate::new(self, length)
382 }
383}
384
385op_future! {
387 fn AsyncFd::sync_all -> (),
388 struct SyncData<'fd> {
389 },
391 setup_state: flags: u32,
392 setup: |submission, fd, (), flags| unsafe {
393 submission.fsync(fd.fd(), flags);
394 },
395 map_result: |n| Ok(debug_assert!(n == 0)),
396}
397
398op_future! {
400 fn AsyncFd::metadata -> Box<Metadata>,
401 struct Stat<'fd> {
402 metadata: Box<Metadata>,
404 },
405 setup_state: _unused: (),
406 setup: |submission, fd, (metadata,), ()| unsafe {
407 submission.statx_file(fd.fd(), &mut metadata.inner, METADATA_FLAGS);
408 },
409 map_result: |this, (metadata,), n| {
410 debug_assert!(n == 0);
411 debug_assert!(metadata.inner.stx_mask & METADATA_FLAGS == METADATA_FLAGS);
412 Ok(metadata)
413 },
414}
415
416op_future! {
418 fn AsyncFd::advise -> (),
419 struct Advise<'fd> {
420 },
422 setup_state: flags: (u64, u32, libc::c_int),
423 setup: |submission, fd, (), (offset, length, advise)| unsafe {
424 submission.fadvise(fd.fd(), offset, length, advise);
425 },
426 map_result: |this, (), res| {
427 debug_assert!(res == 0);
428 Ok(())
429 },
430}
431
432op_future! {
434 fn AsyncFd::allocate -> (),
435 struct Allocate<'fd> {
436 },
438 setup_state: flags: (u64, u32, libc::c_int),
439 setup: |submission, fd, (), (offset, length, mode)| unsafe {
440 submission.fallocate(fd.fd(), offset, length, mode);
441 },
442 map_result: |this, (), res| {
443 debug_assert!(res == 0);
444 Ok(())
445 },
446}
447
448op_future! {
450 fn AsyncFd::truncate -> (),
451 struct Truncate<'fd> {
452 },
454 setup_state: flags: u64,
455 setup: |submission, fd, (), length| unsafe {
456 submission.ftruncate(fd.fd(), length);
457 },
458 map_result: |this, (), res| {
459 debug_assert!(res == 0);
460 Ok(())
461 },
462}
463
464#[repr(transparent)]
468pub struct Metadata {
469 inner: libc::statx,
470}
471
472impl Metadata {
473 pub const fn file_type(&self) -> FileType {
475 FileType(self.inner.stx_mode)
476 }
477
478 #[doc(alias = "S_IFDIR")]
480 pub const fn is_dir(&self) -> bool {
481 self.file_type().is_dir()
482 }
483
484 #[doc(alias = "S_IFREG")]
486 pub const fn is_file(&self) -> bool {
487 self.file_type().is_file()
488 }
489
490 #[doc(alias = "S_IFLNK")]
492 pub const fn is_symlink(&self) -> bool {
493 self.file_type().is_symlink()
494 }
495
496 #[allow(clippy::len_without_is_empty)] pub const fn len(&self) -> u64 {
499 self.inner.stx_size
500 }
501
502 pub const fn block_size(&self) -> u32 {
504 self.inner.stx_blksize
505 }
506
507 pub const fn permissions(&self) -> Permissions {
509 Permissions(self.inner.stx_mode)
510 }
511
512 pub fn modified(&self) -> SystemTime {
514 timestamp(&self.inner.stx_mtime)
515 }
516
517 pub fn accessed(&self) -> SystemTime {
524 timestamp(&self.inner.stx_atime)
525 }
526
527 pub fn created(&self) -> SystemTime {
529 timestamp(&self.inner.stx_btime)
530 }
531}
532
533#[allow(clippy::cast_sign_loss)] fn timestamp(ts: &libc::statx_timestamp) -> SystemTime {
535 let dur = Duration::new(ts.tv_sec as u64, ts.tv_nsec);
536 if ts.tv_sec.is_negative() {
537 SystemTime::UNIX_EPOCH - dur
538 } else {
539 SystemTime::UNIX_EPOCH + dur
540 }
541}
542
543impl fmt::Debug for Metadata {
544 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
545 f.debug_struct("Metadata")
546 .field("file_type", &self.file_type())
547 .field("len", &self.len())
548 .field("block_size", &self.block_size())
549 .field("permissions", &self.permissions())
550 .field("modified", &self.modified())
551 .field("accessed", &self.accessed())
552 .field("created", &self.created())
553 .finish()
554 }
555}
556
557#[derive(Copy, Clone)]
561pub struct FileType(u16);
562
563impl FileType {
564 #[doc(alias = "S_IFDIR")]
566 pub const fn is_dir(&self) -> bool {
567 (self.0 & libc::S_IFMT as u16) == libc::S_IFDIR as u16
568 }
569
570 #[doc(alias = "S_IFREG")]
572 pub const fn is_file(&self) -> bool {
573 (self.0 & libc::S_IFMT as u16) == libc::S_IFREG as u16
574 }
575
576 #[doc(alias = "S_IFLNK")]
578 pub const fn is_symlink(&self) -> bool {
579 (self.0 & libc::S_IFMT as u16) == libc::S_IFLNK as u16
580 }
581
582 #[doc(alias = "S_IFSOCK")]
584 pub const fn is_socket(&self) -> bool {
585 (self.0 & libc::S_IFMT as u16) == libc::S_IFSOCK as u16
586 }
587
588 #[doc(alias = "S_IFBLK")]
590 pub const fn is_block_device(&self) -> bool {
591 (self.0 & libc::S_IFMT as u16) == libc::S_IFBLK as u16
592 }
593
594 #[doc(alias = "S_IFCHR")]
596 pub const fn is_character_device(&self) -> bool {
597 (self.0 & libc::S_IFMT as u16) == libc::S_IFCHR as u16
598 }
599
600 #[doc(alias = "S_IFIFO")]
602 pub const fn is_named_pipe(&self) -> bool {
603 (self.0 & libc::S_IFMT as u16) == libc::S_IFIFO as u16
604 }
605}
606
607impl fmt::Debug for FileType {
608 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
609 let ty = if f.alternate() {
610 if self.is_dir() {
611 "directory"
612 } else if self.is_file() {
613 "file"
614 } else if self.is_symlink() {
615 "symbolic link"
616 } else if self.is_socket() {
617 "socket"
618 } else if self.is_block_device() {
619 "block device"
620 } else if self.is_character_device() {
621 "character device"
622 } else if self.is_named_pipe() {
623 "named pipe"
624 } else {
625 "unknown"
626 }
627 } else if self.is_dir() {
628 "d"
629 } else if self.is_file() {
630 "-"
631 } else if self.is_symlink() {
632 "l"
633 } else if self.is_socket() {
634 "s"
635 } else if self.is_block_device() {
636 "b"
637 } else if self.is_character_device() {
638 "c"
639 } else if self.is_named_pipe() {
640 "p"
641 } else {
642 "?"
643 };
644 f.debug_tuple("FileType").field(&ty).finish()
645 }
646}
647
648#[derive(Copy, Clone)]
652pub struct Permissions(u16);
653
654impl Permissions {
655 #[doc(alias = "S_IRUSR")]
657 pub const fn owner_can_read(&self) -> bool {
658 self.0 & libc::S_IRUSR as u16 != 0
659 }
660
661 #[doc(alias = "S_IWUSR")]
663 pub const fn owner_can_write(&self) -> bool {
664 self.0 & libc::S_IWUSR as u16 != 0
665 }
666
667 #[doc(alias = "S_IXUSR")]
669 pub const fn owner_can_execute(&self) -> bool {
670 self.0 & libc::S_IXUSR as u16 != 0
671 }
672
673 #[doc(alias = "S_IRGRP")]
675 pub const fn group_can_read(&self) -> bool {
676 self.0 & libc::S_IRGRP as u16 != 0
677 }
678
679 #[doc(alias = "S_IWGRP")]
681 pub const fn group_can_write(&self) -> bool {
682 self.0 & libc::S_IWGRP as u16 != 0
683 }
684
685 #[doc(alias = "S_IXGRP")]
687 pub const fn group_can_execute(&self) -> bool {
688 self.0 & libc::S_IXGRP as u16 != 0
689 }
690
691 #[doc(alias = "S_IROTH")]
693 pub const fn others_can_read(&self) -> bool {
694 self.0 & libc::S_IROTH as u16 != 0
695 }
696
697 #[doc(alias = "S_IWOTH")]
699 pub const fn others_can_write(&self) -> bool {
700 self.0 & libc::S_IWOTH as u16 != 0
701 }
702
703 #[doc(alias = "S_IXOTH")]
705 pub const fn others_can_execute(&self) -> bool {
706 self.0 & libc::S_IXOTH as u16 != 0
707 }
708}
709
710impl fmt::Debug for Permissions {
711 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
712 let mut buf = [b'-'; 9];
714 if self.owner_can_read() {
715 buf[0] = b'r';
716 }
717 if self.owner_can_write() {
718 buf[1] = b'w';
719 }
720 if self.owner_can_execute() {
721 buf[2] = b'x';
722 }
723 if self.group_can_read() {
724 buf[3] = b'r';
725 }
726 if self.group_can_write() {
727 buf[4] = b'w';
728 }
729 if self.group_can_execute() {
730 buf[5] = b'x';
731 }
732 if self.others_can_read() {
733 buf[6] = b'r';
734 }
735 if self.others_can_write() {
736 buf[7] = b'w';
737 }
738 if self.others_can_execute() {
739 buf[8] = b'x';
740 }
741 let permissions = str::from_utf8(&buf).unwrap();
742 f.debug_tuple("Permissions").field(&permissions).finish()
743 }
744}
745
746#[doc = man_link!(mkdirat(2))]
748pub fn create_dir(sq: SubmissionQueue, path: PathBuf) -> CreateDir {
749 CreateDir {
750 sq,
751 path: Some(path_to_cstring(path)),
752 state: OpState::NotStarted(()),
753 }
754}
755
756#[derive(Debug)]
760#[must_use = "`Future`s do nothing unless polled"]
761pub struct CreateDir {
762 sq: SubmissionQueue,
763 path: Option<CString>,
768 state: OpState<()>,
769}
770
771impl Future for CreateDir {
772 type Output = io::Result<()>;
773
774 fn poll(mut self: Pin<&mut Self>, ctx: &mut task::Context<'_>) -> Poll<Self::Output> {
775 #[rustfmt::skip]
776 let op_index = poll_state!(CreateDir, self.state, self.sq, ctx, |submission, ()| unsafe {
777 let path = self.path.as_ref().unwrap();
779 let mode = 0o777; submission.mkdirat(libc::AT_FDCWD, path.as_ptr(), mode);
781 });
782
783 match self.sq.poll_op(ctx, op_index) {
784 Poll::Ready(result) => {
785 self.state = OpState::Done;
786 match result {
787 Ok((_, res)) => Poll::Ready(Ok(debug_assert!(res == 0))),
788 Err(err) => Poll::Ready(Err(err)),
789 }
790 }
791 Poll::Pending => Poll::Pending,
792 }
793 }
794}
795
796impl Extract for CreateDir {}
797
798impl Future for Extractor<CreateDir> {
799 type Output = io::Result<PathBuf>;
800
801 fn poll(mut self: Pin<&mut Self>, ctx: &mut task::Context<'_>) -> Poll<Self::Output> {
802 match Pin::new(&mut self.fut).poll(ctx) {
803 Poll::Ready(Ok(())) => {
804 let path = path_from_cstring(self.fut.path.take().unwrap());
805 Poll::Ready(Ok(path))
806 }
807 Poll::Ready(Err(err)) => Poll::Ready(Err(err)),
808 Poll::Pending => Poll::Pending,
809 }
810 }
811}
812
813impl Drop for CreateDir {
814 fn drop(&mut self) {
815 if let Some(path) = self.path.take() {
816 match self.state {
817 OpState::Running(op_index) => {
818 let result = self.sq.cancel_op(
819 op_index,
820 || path,
821 |submission| unsafe {
822 submission.cancel_op(op_index);
823 submission.no_completion_event();
826 },
827 );
828 if let Err(err) = result {
829 log::error!("dropped a10::CreateDir before completion, attempt to cancel failed: {err}");
830 }
831 }
832 OpState::NotStarted(()) | OpState::Done => drop(path),
833 }
834 }
835 }
836}
837
838#[doc = man_link!(rename(2))]
840pub fn rename(sq: SubmissionQueue, from: PathBuf, to: PathBuf) -> Rename {
841 Rename {
842 sq,
843 from: Some(path_to_cstring(from)),
844 to: Some(path_to_cstring(to)),
845 state: OpState::NotStarted(()),
846 }
847}
848
849#[derive(Debug)]
851#[must_use = "`Future`s do nothing unless polled"]
852pub struct Rename {
853 sq: SubmissionQueue,
854 from: Option<CString>,
859 to: Option<CString>,
860 state: OpState<()>,
861}
862
863impl Future for Rename {
864 type Output = io::Result<()>;
865
866 fn poll(mut self: Pin<&mut Self>, ctx: &mut task::Context<'_>) -> Poll<Self::Output> {
867 let op_index = poll_state!(Rename, self.state, self.sq, ctx, |submission, ()| unsafe {
868 let from = self.from.as_ref().unwrap();
870 let to = self.to.as_ref().unwrap();
871 submission.rename(
872 libc::AT_FDCWD,
873 from.as_ptr(),
874 libc::AT_FDCWD,
875 to.as_ptr(),
876 0,
877 );
878 });
879
880 match self.sq.poll_op(ctx, op_index) {
881 Poll::Ready(result) => {
882 self.state = OpState::Done;
883 match result {
884 Ok((_, res)) => Poll::Ready(Ok(debug_assert!(res == 0))),
885 Err(err) => Poll::Ready(Err(err)),
886 }
887 }
888 Poll::Pending => Poll::Pending,
889 }
890 }
891}
892
893impl Extract for Rename {}
894
895impl Future for Extractor<Rename> {
896 type Output = io::Result<(PathBuf, PathBuf)>;
897
898 fn poll(mut self: Pin<&mut Self>, ctx: &mut task::Context<'_>) -> Poll<Self::Output> {
899 match Pin::new(&mut self.fut).poll(ctx) {
900 Poll::Ready(Ok(())) => {
901 let from = path_from_cstring(self.fut.from.take().unwrap());
902 let to = path_from_cstring(self.fut.to.take().unwrap());
903 Poll::Ready(Ok((from, to)))
904 }
905 Poll::Ready(Err(err)) => Poll::Ready(Err(err)),
906 Poll::Pending => Poll::Pending,
907 }
908 }
909}
910
911impl Drop for Rename {
912 fn drop(&mut self) {
913 if let Some(from) = self.from.take() {
914 let to = self.to.take().unwrap();
917
918 match self.state {
919 OpState::Running(op_index) => {
920 let result = self.sq.cancel_op(
921 op_index,
922 || Box::from((from, to)),
923 |submission| unsafe {
924 submission.cancel_op(op_index);
925 submission.no_completion_event();
928 },
929 );
930 if let Err(err) = result {
931 log::error!("dropped a10::CreateDir before completion, attempt to cancel failed: {err}");
932 }
933 }
934 OpState::NotStarted(()) | OpState::Done => {
935 drop(from);
936 drop(to);
937 }
938 }
939 }
940 }
941}
942
943#[doc = man_link!(unlinkat(2))]
945#[doc(alias = "unlink")]
946#[doc(alias = "unlinkat")]
947pub fn remove_file(sq: SubmissionQueue, path: PathBuf) -> Delete {
948 Delete {
949 sq,
950 path: Some(path_to_cstring(path)),
951 state: OpState::NotStarted(0),
952 }
953}
954
955#[doc = man_link!(unlinkat(2))]
957#[doc(alias = "rmdir")]
958#[doc(alias = "unlinkat")]
959pub fn remove_dir(sq: SubmissionQueue, path: PathBuf) -> Delete {
960 Delete {
961 sq,
962 path: Some(path_to_cstring(path)),
963 state: OpState::NotStarted(libc::AT_REMOVEDIR),
964 }
965}
966
967#[derive(Debug)]
972#[must_use = "`Future`s do nothing unless polled"]
973pub struct Delete {
974 sq: SubmissionQueue,
975 path: Option<CString>,
980 state: OpState<libc::c_int>,
981}
982
983impl Future for Delete {
984 type Output = io::Result<()>;
985
986 fn poll(mut self: Pin<&mut Self>, ctx: &mut task::Context<'_>) -> Poll<Self::Output> {
987 #[rustfmt::skip]
988 let op_index = poll_state!(Delete, self.state, self.sq, ctx, |submission, flags| unsafe {
989 let path = self.path.as_ref().unwrap();
991 submission.unlinkat(libc::AT_FDCWD, path.as_ptr(), flags);
992 });
993
994 match self.sq.poll_op(ctx, op_index) {
995 Poll::Ready(result) => {
996 self.state = OpState::Done;
997 match result {
998 Ok((_, res)) => Poll::Ready(Ok(debug_assert!(res == 0))),
999 Err(err) => Poll::Ready(Err(err)),
1000 }
1001 }
1002 Poll::Pending => Poll::Pending,
1003 }
1004 }
1005}
1006
1007impl Extract for Delete {}
1008
1009impl Future for Extractor<Delete> {
1010 type Output = io::Result<PathBuf>;
1011
1012 fn poll(mut self: Pin<&mut Self>, ctx: &mut task::Context<'_>) -> Poll<Self::Output> {
1013 match Pin::new(&mut self.fut).poll(ctx) {
1014 Poll::Ready(Ok(())) => {
1015 let path = path_from_cstring(self.fut.path.take().unwrap());
1016 Poll::Ready(Ok(path))
1017 }
1018 Poll::Ready(Err(err)) => Poll::Ready(Err(err)),
1019 Poll::Pending => Poll::Pending,
1020 }
1021 }
1022}
1023
1024impl Drop for Delete {
1025 fn drop(&mut self) {
1026 if let Some(path) = self.path.take() {
1027 match self.state {
1028 OpState::Running(op_index) => {
1029 let result = self.sq.cancel_op(
1030 op_index,
1031 || path,
1032 |submission| unsafe {
1033 submission.cancel_op(op_index);
1034 submission.no_completion_event();
1037 },
1038 );
1039 if let Err(err) = result {
1040 log::error!("dropped a10::CreateDir before completion, attempt to cancel failed: {err}");
1041 }
1042 }
1043 OpState::NotStarted(_) | OpState::Done => drop(path),
1044 }
1045 }
1046 }
1047}
1048
1049fn path_to_cstring(path: PathBuf) -> CString {
1050 unsafe { CString::from_vec_unchecked(path.into_os_string().into_vec()) }
1051}
1052
1053fn path_from_cstring(path: CString) -> PathBuf {
1054 OsString::from_vec(path.into_bytes()).into()
1055}