a10/
fs.rs

1//! Filesystem manipulation operations.
2//!
3//! To open a file ([`AsyncFd`]) use [`open_file`] or [`OpenOptions`].
4
5use 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
21/// Flags needed to fill [`Metadata`].
22const 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/// Options used to configure how a file ([`AsyncFd`]) is opened.
31#[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    /// Empty `OpenOptions`, has reading enabled by default.
40    pub const fn new() -> OpenOptions {
41        OpenOptions {
42            flags: libc::O_RDONLY, // NOTE: `O_RDONLY` is 0.
43            mode: 0o666,           // Same as in std lib.
44        }
45    }
46
47    /// Enable read access.
48    ///
49    /// Note that read access is already enabled by default, so this is only
50    /// useful if you called [`OpenOptions::write_only`] and want to enable read
51    /// access as well.
52    #[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        } // Else we're already in read mode.
59        self
60    }
61
62    /// Enable write access.
63    #[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        } // Else we're already in write mode.
69        self
70    }
71
72    /// Only enable write access, disabling read access.
73    #[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    /// Set writing to append only mode.
81    ///
82    /// # Notes
83    ///
84    /// This requires [writing access] to be enabled.
85    ///
86    /// [writing access]: OpenOptions::write
87    #[doc(alias = "O_APPEND")]
88    pub const fn append(mut self) -> Self {
89        self.flags |= libc::O_APPEND;
90        self
91    }
92
93    /// Truncate the file if it exists.
94    #[doc(alias = "O_TRUNC")]
95    pub const fn truncate(mut self) -> Self {
96        self.flags |= libc::O_TRUNC;
97        self
98    }
99
100    /// If the file doesn't exist create it.
101    pub const fn create(mut self) -> Self {
102        self.flags |= libc::O_CREAT;
103        self
104    }
105
106    /// Force a file to be created, failing if a file already exists.
107    ///
108    /// This options implies [`OpenOptions::create`].
109    #[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    /// Write operations on the file will complete according to the requirements
117    /// of synchronized I/O *data* integrity completion.
118    ///
119    /// By the time `write(2)` (and similar) return, the output data has been
120    /// transferred to the underlying hardware, along with any file metadata
121    /// that would be required to retrieve that data (i.e., as though each
122    /// `write(2)` was followed by a call to `fdatasync(2)`).
123    #[doc(alias = "O_DSYNC")]
124    pub const fn data_sync(mut self) -> Self {
125        self.flags |= libc::O_DSYNC;
126        self
127    }
128
129    /// Write operations on the file will complete according to the requirements
130    /// of synchronized I/O *file* integrity completion (by contrast with the
131    /// synchronized I/O data integrity completion provided by
132    /// [`OpenOptions::data_sync`].)
133    ///
134    /// By the time `write(2)` (or similar) returns, the output data and
135    /// associated file metadata have been transferred to the underlying
136    /// hardware (i.e., as though each `write(2)` was followed by a call to
137    /// `fsync(2)`).
138    #[doc(alias = "O_SYNC")]
139    pub const fn sync(mut self) -> Self {
140        self.flags |= libc::O_SYNC;
141        self
142    }
143
144    /// Try to minimize cache effects of the I/O to and from this file.
145    ///
146    /// File I/O is done directly to/from user-space buffers. This uses the
147    /// `O_DIRECT` flag which on its own makes an effort to transfer data
148    /// synchronously, but does not give the guarantees of the `O_SYNC` flag
149    /// ([`OpenOptions::sync`]) that data and necessary metadata are
150    /// transferred. To guarantee synchronous I/O, `O_SYNC` must be used in
151    /// addition to `O_DIRECT`.
152    #[doc(alias = "O_DIRECT")]
153    pub const fn direct(mut self) -> Self {
154        self.flags |= libc::O_DIRECT;
155        self
156    }
157
158    /// Sets the mode bits that a new file will be created with.
159    pub const fn mode(mut self, mode: libc::mode_t) -> Self {
160        self.mode = mode;
161        self
162    }
163
164    /// Create an unnamed temporary regular file. The `dir` argument specifies a
165    /// directory; an unnamed inode will be created in that directory's
166    /// filesystem. Anything written to the resulting file will be lost when the
167    /// last file descriptor is closed, unless the file is given a name.
168    ///
169    /// [`OpenOptions::write`] must be set. The `linkat(2)` system call can be
170    /// used to make the temporary file permanent.
171    #[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    /// Open `path`.
178    #[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/// Open a file in read-only mode.
191#[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/// [`Future`] to [`open`] an asynchronous file ([`AsyncFd`]).
197///
198/// [`open`]: OpenOptions::open
199#[derive(Debug)]
200#[must_use = "`Future`s do nothing unless polled"]
201pub struct Open<D: Descriptor = File> {
202    /// Path used to open the file, need to stay in memory so the kernel can
203    /// access it safely.
204    // SAFETY: because this is not modified by the kernel it doesn't need an
205    // UnsafeCell. It is read-only (as the kernel also has read access).
206    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                // SAFETY: `path` is only removed after the state is set to `Done`.
223                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                        // SAFETY: the open operation ensures that `fd` is valid.
235                        // SAFETY: unwrapped `sq` above already.
236                        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                    // SAFETY: only when the `Future` completed is `self.sq`
269                    // `None`, but in that case `self.path` would also be
270                    // `None`.
271                    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                            // We'll get a canceled completion event if we succeeded, which
278                            // is sufficient to cleanup the operation.
279                            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
294/// File(system) related system calls.
295impl<D: Descriptor> AsyncFd<D> {
296    /// Sync all OS-internal metadata to disk.
297    ///
298    /// # Notes
299    ///
300    /// Any uncompleted writes may not be synced to disk.
301    #[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    /// This function is similar to [`sync_all`], except that it may not
308    /// synchronize file metadata to the filesystem.
309    ///
310    /// This is intended for use cases that must synchronize content, but don’t
311    /// need the metadata on disk. The goal of this method is to reduce disk
312    /// operations.
313    ///
314    /// [`sync_all`]: AsyncFd::sync_all
315    ///
316    /// # Notes
317    ///
318    /// Any uncompleted writes may not be synced to disk.
319    #[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    /// Retrieve metadata about the file.
326    #[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            // SAFETY: all zero values are valid representations.
331            inner: unsafe { zeroed() },
332        });
333        Stat::new(self, metadata, ())
334    }
335
336    /// Predeclare an access pattern for file data.
337    ///
338    /// Announce an intention to access file data in a specific pattern in the
339    /// future, thus allowing the kernel to perform appropriate optimizations.
340    ///
341    /// The advice applies to a (not necessarily existent) region starting at
342    /// offset and extending for len bytes (or until the end of the file if len
343    /// is 0). The advice is not binding; it merely constitutes an expectation
344    /// on behalf of the application.
345    #[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    /// Manipulate file space.
358    ///
359    /// Manipulate the allocated disk space for the file referred for the byte
360    /// range starting at `offset` and continuing for `length` bytes.
361    #[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    /// Truncate the file to `length`.
374    ///
375    /// If the file previously was larger than this size, the extra data is
376    /// lost. If the file previously was shorter, it is extended, and the
377    /// extended part reads as null bytes.
378    #[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
385// SyncData.
386op_future! {
387    fn AsyncFd::sync_all -> (),
388    struct SyncData<'fd> {
389        // Doesn't need any fields.
390    },
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
398// Metadata.
399op_future! {
400    fn AsyncFd::metadata -> Box<Metadata>,
401    struct Stat<'fd> {
402        /// Buffer to write the statx data into.
403        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
416// Advise.
417op_future! {
418    fn AsyncFd::advise -> (),
419    struct Advise<'fd> {
420        // Doesn't need any fields.
421    },
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
432// Allocate.
433op_future! {
434    fn AsyncFd::allocate -> (),
435    struct Allocate<'fd> {
436        // Doesn't need any fields.
437    },
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
448// Truncate.
449op_future! {
450    fn AsyncFd::truncate -> (),
451    struct Truncate<'fd> {
452        // Doesn't need any fields.
453    },
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/// Metadata information about a file.
465///
466/// See [`AsyncFd::metadata`] and [`Stat`].
467#[repr(transparent)]
468pub struct Metadata {
469    inner: libc::statx,
470}
471
472impl Metadata {
473    /// Returns the file type for this metadata.
474    pub const fn file_type(&self) -> FileType {
475        FileType(self.inner.stx_mode)
476    }
477
478    /// Returns `true` if this represents a directory.
479    #[doc(alias = "S_IFDIR")]
480    pub const fn is_dir(&self) -> bool {
481        self.file_type().is_dir()
482    }
483
484    /// Returns `true` if this represents a file.
485    #[doc(alias = "S_IFREG")]
486    pub const fn is_file(&self) -> bool {
487        self.file_type().is_file()
488    }
489
490    /// Returns `true` if this represents a symbolic link.
491    #[doc(alias = "S_IFLNK")]
492    pub const fn is_symlink(&self) -> bool {
493        self.file_type().is_symlink()
494    }
495
496    /// Returns the size of the file, in bytes, this metadata is for.
497    #[allow(clippy::len_without_is_empty)] // Makes no sense.
498    pub const fn len(&self) -> u64 {
499        self.inner.stx_size
500    }
501
502    /// The "preferred" block size for efficient filesystem I/O.
503    pub const fn block_size(&self) -> u32 {
504        self.inner.stx_blksize
505    }
506
507    /// Returns the permissions of the file this metadata is for.
508    pub const fn permissions(&self) -> Permissions {
509        Permissions(self.inner.stx_mode)
510    }
511
512    /// Returns the time this file was last modified.
513    pub fn modified(&self) -> SystemTime {
514        timestamp(&self.inner.stx_mtime)
515    }
516
517    /// Returns the time this file was last accessed.
518    ///
519    /// # Notes
520    ///
521    /// It's possible to disable keeping track of this access time, which makes
522    /// this function return an invalid value.
523    pub fn accessed(&self) -> SystemTime {
524        timestamp(&self.inner.stx_atime)
525    }
526
527    /// Returns the time this file was created.
528    pub fn created(&self) -> SystemTime {
529        timestamp(&self.inner.stx_btime)
530    }
531}
532
533#[allow(clippy::cast_sign_loss)] // Checked.
534fn 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/// A structure representing a type of file with accessors for each file type.
558///
559/// See [`Metadata`].
560#[derive(Copy, Clone)]
561pub struct FileType(u16);
562
563impl FileType {
564    /// Returns `true` if this represents a directory.
565    #[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    /// Returns `true` if this represents a file.
571    #[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    /// Returns `true` if this represents a symbolic link.
577    #[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    /// Returns `true` if this represents a socket.
583    #[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    /// Returns `true` if this represents a block device.
589    #[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    /// Returns `true` if this represents a character device.
595    #[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    /// Returns `true` if this represents a named fifo pipe.
601    #[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/// Access permissions.
649///
650/// See [`Metadata`].
651#[derive(Copy, Clone)]
652pub struct Permissions(u16);
653
654impl Permissions {
655    /// Return `true` if the owner has read permission.
656    #[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    /// Return `true` if the owner has write permission.
662    #[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    /// Return `true` if the owner has execute permission.
668    #[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    /// Return `true` if the group the file belongs to has read permission.
674    #[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    /// Return `true` if the group the file belongs to has write permission.
680    #[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    /// Return `true` if the group the file belongs to has execute permission.
686    #[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    /// Return `true` if others have read permission.
692    #[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    /// Return `true` if others have write permission.
698    #[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    /// Return `true` if others have execute permission.
704    #[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        // Create the same format as `ls(1)` uses.
713        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/// Creates a new, empty directory.
747#[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/// [`Future`] to [create a directory].
757///
758/// [create a directory]: create_dir
759#[derive(Debug)]
760#[must_use = "`Future`s do nothing unless polled"]
761pub struct CreateDir {
762    sq: SubmissionQueue,
763    /// Path used to create the directory, need to stay in memory so the kernel
764    /// can access it safely.
765    // SAFETY: because this is not modified by the kernel it doesn't need an
766    // UnsafeCell. It is read-only (as the kernel also has read access).
767    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            // SAFETY: `path` is only removed after the state is set to `Done`.
778            let path = self.path.as_ref().unwrap();
779            let mode = 0o777; // Same as used by the standard library.
780            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                            // We'll get a canceled completion event if we succeeded, which
824                            // is sufficient to cleanup the operation.
825                            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/// Rename a file or directory to a new name.
839#[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/// [`Future`] to [`rename`] a file.
850#[derive(Debug)]
851#[must_use = "`Future`s do nothing unless polled"]
852pub struct Rename {
853    sq: SubmissionQueue,
854    /// Paths used to rename the file, need to stay in memory so the kernel can
855    /// access it safely.
856    // SAFETY: because this is not modified by the kernel it doesn't need an
857    // UnsafeCell. It is read-only (as the kernel also has read access).
858    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            // SAFETY: `from` and `to` are only removed after the state is set to `Done`.
869            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            // SAFETY: if `from` is `Some`, so is `to` as we extract both or
915            // neither.
916            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                            // We'll get a canceled completion event if we succeeded, which
926                            // is sufficient to cleanup the operation.
927                            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/// Remove a file.
944#[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/// Remove a directory.
956#[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/// [`Future`] to remove a [file] or [directory].
968///
969/// [file]: remove_file
970/// [directory]: remove_dir
971#[derive(Debug)]
972#[must_use = "`Future`s do nothing unless polled"]
973pub struct Delete {
974    sq: SubmissionQueue,
975    /// Paths used to rename the file, need to stay in memory so the kernel can
976    /// access it safely.
977    // SAFETY: because this is not modified by the kernel it doesn't need an
978    // UnsafeCell. It is read-only (as the kernel also has read access).
979    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            // SAFETY: `path` is only removed after the state is set to `Done`.
990            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                            // We'll get a canceled completion event if we succeeded, which
1035                            // is sufficient to cleanup the operation.
1036                            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}