tiny_std/
fs.rs

1#[cfg(feature = "alloc")]
2use alloc::string::String;
3#[cfg(feature = "alloc")]
4use alloc::vec::Vec;
5use core::mem::MaybeUninit;
6
7use rusl::error::Errno;
8pub use rusl::platform::Mode;
9use rusl::platform::{Dirent, OpenFlags, Stat, NULL_BYTE};
10use rusl::string::strlen::{buf_strlen, strlen};
11use rusl::string::unix_str::UnixStr;
12use rusl::unistd::UnlinkFlags;
13
14use crate::error::Error;
15use crate::error::Result;
16use crate::io::{Read, Write};
17use crate::unix::fd::{AsRawFd, BorrowedFd, OwnedFd, RawFd};
18
19#[cfg(test)]
20mod test;
21
22pub struct File(OwnedFd);
23
24impl File {
25    /// Opens a file with default options
26    /// # Errors
27    /// Operating system errors ond finding and reading files
28    #[inline]
29    pub fn open(path: &UnixStr) -> Result<Self> {
30        Self::open_with_options(path, OpenOptions::new().read(true))
31    }
32
33    #[inline]
34    fn open_with_options(path: &UnixStr, opts: &OpenOptions) -> Result<Self> {
35        let flags =
36            OpenFlags::O_CLOEXEC | opts.get_access_mode()? | opts.get_creation_mode()? | opts.flags;
37        let fd = rusl::unistd::open_mode(path, flags, opts.mode)?;
38        Ok(File(OwnedFd(fd)))
39    }
40
41    fn open_at(dir_fd: RawFd, path: &UnixStr) -> Result<Self> {
42        let mut opts = OpenOptions::new();
43        opts.read(true);
44        let flags =
45            OpenFlags::O_CLOEXEC | opts.get_access_mode()? | opts.get_creation_mode()? | opts.flags;
46        let fd = rusl::unistd::open_at_mode(dir_fd, path, flags, opts.mode)?;
47        Ok(File(OwnedFd(fd)))
48    }
49
50    /// Create a File from a raw `fd`
51    /// # Safety
52    /// The fd is valid and is not duplicated.
53    /// Duplication is bad since the `fd` will be closed when this `File` is dropped
54    #[must_use]
55    pub const unsafe fn from_raw_fd(fd: RawFd) -> Self {
56        Self(OwnedFd::from_raw(fd))
57    }
58
59    /// Set this `File` to be non-blocking.
60    /// This will result in for example read expecting a certain number of bytes
61    /// to fail with `EAGAIN` if that data isn't available.
62    /// # Errors
63    /// Errors making the underlying syscalls
64    #[inline]
65    pub fn set_nonblocking(&self) -> Result<()> {
66        self.0.set_nonblocking()
67    }
68
69    /// Get file metadata for this open `File`
70    /// # Errors
71    /// Os errors making the stat-syscall
72    #[inline]
73    pub fn metadata(&self) -> Result<Metadata> {
74        let stat = rusl::unistd::stat_fd(self.as_raw_fd())?;
75        Ok(Metadata(stat))
76    }
77
78    /// Copies `src` to `dest`, can be used to move files.
79    /// Will overwrite anything currently at `dest`.
80    /// Returns a handle to the new file.
81    /// # Errors
82    /// Os errors relating to file access
83    #[expect(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
84    pub fn copy(&self, dest: &UnixStr) -> Result<Self> {
85        let this_metadata = self.metadata()?;
86        let dest = OpenOptions::new()
87            .create(true)
88            .write(true)
89            .mode(this_metadata.mode())
90            .open(dest)?;
91        let mut offset = 0;
92        // We don't have to care about sign loss on the st_size, it's always positive.
93        let mut remaining = this_metadata.0.st_size as u64 - offset;
94        while remaining > 0 {
95            let w = rusl::unistd::copy_file_range(
96                self.as_raw_fd(),
97                offset,
98                dest.as_raw_fd(),
99                offset,
100                remaining as usize,
101            )?;
102            if w == 0 {
103                return Ok(dest);
104            }
105            offset += w as u64;
106            remaining = this_metadata.0.st_size as u64 - offset;
107        }
108        Ok(dest)
109    }
110}
111
112impl File {
113    #[inline]
114    pub(crate) fn into_inner(self) -> OwnedFd {
115        self.0
116    }
117}
118
119impl AsRawFd for File {
120    #[inline]
121    fn as_raw_fd(&self) -> RawFd {
122        self.0.as_raw_fd()
123    }
124}
125
126impl Read for File {
127    #[inline]
128    fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
129        Ok(rusl::unistd::read(self.0 .0, buf)?)
130    }
131}
132
133/// Reads a file into a newly allocated vec
134/// # Errors
135/// Os errors relating to file access and reading
136#[cfg(feature = "alloc")]
137pub fn read(path: &UnixStr) -> Result<Vec<u8>> {
138    let mut file = File::open(path)?;
139    let mut bytes = Vec::new();
140    file.read_to_end(&mut bytes)?;
141    Ok(bytes)
142}
143
144/// Reads a file into a newly allocated vec
145/// # Errors
146/// Os errors relating to file access and reading as well as utf8 conversion errors
147#[cfg(feature = "alloc")]
148pub fn read_to_string(path: &UnixStr) -> Result<String> {
149    let mut file = File::open(path)?;
150    let mut string = String::new();
151    file.read_to_string(&mut string)?;
152    Ok(string)
153}
154
155/// Attempts to write the entire contents of `buf` into the path specified at `path`.
156/// If no file exists at `path` one will be created.
157/// If a file exists at `path` it will be overwritten.
158/// Use `File` and open with `append` to append to a file.
159/// # Errors
160/// Os errors relating to file creation or writing, such as permissions errors.
161pub fn write(path: &UnixStr, buf: &[u8]) -> Result<()> {
162    let mut file = OpenOptions::new()
163        .write(true)
164        .create(true)
165        .truncate(true)
166        .open(path)?;
167    file.write_all(buf)?;
168    Ok(())
169}
170#[derive(Debug, Clone)]
171pub struct Metadata(Stat);
172
173impl Metadata {
174    #[inline]
175    #[must_use]
176    pub fn is_dir(&self) -> bool {
177        Mode::from(self.0.st_mode) & Mode::S_IFMT == Mode::S_IFDIR
178    }
179
180    #[inline]
181    #[must_use]
182    pub fn is_file(&self) -> bool {
183        Mode::from(self.0.st_mode) & Mode::S_IFMT == Mode::S_IFREG
184    }
185
186    #[inline]
187    #[must_use]
188    pub fn is_symlink(&self) -> bool {
189        Mode::from(self.0.st_mode) & Mode::S_IFMT == Mode::S_IFLNK
190    }
191
192    #[inline]
193    #[must_use]
194    pub fn mode(&self) -> Mode {
195        Mode::from(self.0.st_mode)
196    }
197
198    #[inline]
199    #[must_use]
200    #[expect(clippy::len_without_is_empty, clippy::cast_sign_loss)]
201    pub fn len(&self) -> u64 {
202        self.0.st_size as u64
203    }
204}
205
206/// Reads metadata at path
207/// # Errors
208/// Os errors relating to file access
209#[inline]
210pub fn metadata(path: &UnixStr) -> Result<Metadata> {
211    let res = rusl::unistd::stat(path)?;
212    Ok(Metadata(res))
213}
214
215/// Renames `src` to `dest`, can be used to move files or directories.
216/// Will overwrite anything currently at `dest`.
217/// # Errors
218/// Os errors relating to file access
219#[inline]
220pub fn rename(src: &UnixStr, dest: &UnixStr) -> Result<()> {
221    rusl::unistd::rename(src, dest)?;
222    Ok(())
223}
224
225/// Copies the file at `src`, to the `dest`, overwriting anything at `dest`.
226/// Returns a handle to the new file.
227/// # Errors
228/// See [`File::copy`]
229#[inline]
230pub fn copy_file(src: &UnixStr, dest: &UnixStr) -> Result<File> {
231    let src_file = File::open(src)?;
232    src_file.copy(dest)
233}
234
235/// Checks if anything exists at the provided path.
236/// Will false-negative if the path is empty.
237/// # Errors
238/// Os errors relating to file access
239pub fn exists(path: &UnixStr) -> Result<bool> {
240    match rusl::unistd::stat(path) {
241        Ok(_) => Ok(true),
242        Err(e) => {
243            if matches!(e.code, Some(Errno::ENOENT)) {
244                return Ok(false);
245            }
246            Err(Error::from(e))
247        }
248    }
249}
250
251/// Tries to remove a file from the filesystem at the specified path
252/// # Errors
253/// OS errors relating to file access/permissions
254#[inline]
255pub fn remove_file(path: &UnixStr) -> Result<()> {
256    rusl::unistd::unlink(path)?;
257    Ok(())
258}
259
260/// Tries to create a directory at the specified path
261/// # Errors
262/// OS errors relating to file access/permissions
263#[inline]
264pub fn create_dir(path: &UnixStr) -> Result<()> {
265    create_dir_mode(path, Mode::from(0o755))
266}
267
268/// Create a directory with the given mode
269/// # Errors
270/// OS errors relating to file access/permissions
271#[inline]
272pub fn create_dir_mode(path: &UnixStr, mode: Mode) -> Result<()> {
273    rusl::unistd::mkdir(path, mode)?;
274    Ok(())
275}
276
277/// Tries to create a directory at the specified path
278/// # Errors
279/// OS errors relating to file access/permissions
280/// If working without an allocator, the maximum path length is 512 bytes
281#[inline]
282pub fn create_dir_all(path: &UnixStr) -> Result<()> {
283    // To make it simple we'll just stack alloc an uninit 512 array for the path.
284    // Kind of a travesty, but needs to be like this to work without an allocator
285    const NO_ALLOC_MAX_LEN: usize = 512;
286    const EMPTY: [MaybeUninit<u8>; NO_ALLOC_MAX_LEN] = [MaybeUninit::uninit(); NO_ALLOC_MAX_LEN];
287    unsafe {
288        // Could check if we haven't got any slashes at all and just run the pointer straight through
289        // without possibly having to add an extra indirection buffer here.
290        let ptr = path.as_ptr();
291        let len = strlen(ptr);
292        if len == 0 {
293            return Err(Error::no_code(
294                "Can't create a directory with an empty name",
295            ));
296        }
297        #[cfg(feature = "alloc")]
298        if len > NO_ALLOC_MAX_LEN {
299            let mut owned: Vec<u8> = Vec::with_capacity(len);
300            ptr.copy_to(owned.as_mut_ptr(), len);
301            write_all_sub_paths(owned.as_mut_slice(), ptr)?;
302            return Ok(());
303        }
304        #[cfg(not(feature = "alloc"))]
305        if len > NO_ALLOC_MAX_LEN {
306            return Err(Error::no_code(
307                "Supplied path larger than 512 without an allocator present",
308            ));
309        }
310        let mut copied = EMPTY;
311        ptr.copy_to(copied.as_mut_ptr().cast(), len);
312        // Make into slice, we know the actual slice-length is len + 1
313        let initialized_section = copied[..len].as_mut_ptr().cast();
314        let buf: &mut [u8] = core::slice::from_raw_parts_mut(initialized_section, len);
315        write_all_sub_paths(buf, ptr)?;
316    }
317    Ok(())
318}
319
320#[inline]
321unsafe fn write_all_sub_paths(
322    buf: &mut [u8],
323    raw: *const u8,
324) -> core::result::Result<(), rusl::Error> {
325    let len = buf.len();
326    let mut it = 1;
327    loop {
328        // Iterate down
329        let ind = len - it;
330        if ind == 0 {
331            break;
332        }
333
334        let byte = buf[ind];
335        if byte == b'/' {
336            // Swap slash for null termination to make a valid path
337            buf[ind] = NULL_BYTE;
338
339            return match rusl::unistd::mkdir(
340                UnixStr::from_bytes_unchecked(&buf[..=ind]),
341                Mode::from(0o755),
342            ) {
343                // Successfully wrote, traverse down
344                Ok(()) => {
345                    // Replace the null byte to make a valid path concatenation
346                    buf[ind] = b'/';
347                    for i in ind + 1..len {
348                        // Found next
349                        if buf[i] == b'/' {
350                            // Swap slash for null termination to make a valid path
351                            buf[i] = NULL_BYTE;
352                            rusl::unistd::mkdir(
353                                UnixStr::from_bytes_unchecked(&buf[..=i]),
354                                Mode::from(0o755),
355                            )?;
356                            // Swap back to continue down
357                            buf[i] = b'/';
358                        }
359                    }
360                    // if we end on a slash we don't have to write the last part
361                    if unsafe { raw.add(len - 1).read() } == b'/' {
362                        return Ok(());
363                    }
364                    // We know the actual length is len + 1 and null terminated, try write full
365                    rusl::unistd::mkdir(
366                        UnixStr::from_bytes_unchecked(core::slice::from_raw_parts(raw, len + 1)),
367                        Mode::from(0o755),
368                    )?;
369                    Ok(())
370                }
371                Err(e) => {
372                    if let Some(code) = e.code {
373                        if code == Errno::ENOENT {
374                            it += 1;
375                            // Put slash back, only way we end up here is if we tried to write
376                            // previously replacing the slash with a null-byte
377                            buf[ind] = b'/';
378                            continue;
379                        } else if code == Errno::EEXIST {
380                            return Ok(());
381                        }
382                    }
383                    Err(e)
384                }
385            };
386        }
387        it += 1;
388    }
389    Ok(())
390}
391
392pub struct Directory(OwnedFd);
393
394impl Directory {
395    /// Opens a directory for reading
396    /// # Errors
397    /// OS errors relating to file access/permissions
398    #[inline]
399    pub fn open(path: &UnixStr) -> Result<Directory> {
400        let fd = rusl::unistd::open(path, OpenFlags::O_CLOEXEC | OpenFlags::O_RDONLY)?;
401        Ok(Directory(OwnedFd(fd)))
402    }
403
404    #[inline]
405    fn open_at(dir_fd: RawFd, path: &UnixStr) -> Result<Directory> {
406        let fd = rusl::unistd::open_at(dir_fd, path, OpenFlags::O_CLOEXEC | OpenFlags::O_RDONLY)?;
407        Ok(Directory(OwnedFd(fd)))
408    }
409
410    /// Will try to read a directory into a 512 byte buffer.
411    /// The iterator will try to keep requesting entries until the directory is EOF or produces an error.
412    /// Best case number of syscalls to drain the interator is 2.
413    /// Worst case, assuming each entity has the linux-max name of 256 bytes is
414    /// `n + 1` when `n` is the number of files.
415    #[must_use]
416    pub fn read<'a>(&self) -> ReadDir<'a> {
417        let buf = [0u8; 512];
418        ReadDir {
419            fd: BorrowedFd::new(self.0 .0),
420            filled_buf: buf,
421            offset: 0,
422            read_size: 0,
423            eod: false,
424        }
425    }
426
427    /// Removes all children of this directory.
428    /// Potentially destructive.
429    /// # Errors
430    /// Os errors relating to permissions
431    pub fn remove_all(&self) -> Result<()> {
432        for sub_dir in self.read() {
433            let sub_dir = sub_dir?;
434            if FileType::Directory == sub_dir.file_type() {
435                if sub_dir.is_relative_reference() {
436                    continue;
437                }
438                let fname = sub_dir.file_unix_name()?;
439                let next = Self::open_at(self.0 .0, fname)?;
440                next.remove_all()?;
441                rusl::unistd::unlink_at(self.0 .0, fname, UnlinkFlags::at_removedir())?;
442            } else {
443                rusl::unistd::unlink_at(
444                    self.0 .0,
445                    sub_dir.file_unix_name()?,
446                    UnlinkFlags::empty(),
447                )?;
448            }
449        }
450        Ok(())
451    }
452}
453
454pub struct ReadDir<'a> {
455    fd: BorrowedFd<'a>,
456    // Maximum
457    filled_buf: [u8; 512],
458    offset: usize,
459    read_size: usize,
460    eod: bool,
461}
462
463impl<'a> Iterator for ReadDir<'a> {
464    type Item = Result<DirEntry<'a>>;
465
466    fn next(&mut self) -> Option<Self::Item> {
467        if self.read_size == self.offset {
468            if self.eod {
469                return None;
470            }
471            match rusl::unistd::get_dents(self.fd.fd, &mut self.filled_buf) {
472                Ok(read) => {
473                    if read == 0 {
474                        self.eod = true;
475                        return None;
476                    }
477                    self.read_size = read;
478                    // Luckily we don't read any partials, so no shift-back the buffer
479                    self.offset = 0;
480                }
481                Err(e) => {
482                    self.eod = true;
483                    return Some(Err(e.into()));
484                }
485            }
486        }
487        unsafe {
488            Dirent::try_from_bytes(&self.filled_buf[self.offset..]).map(|de| {
489                self.offset += de.d_reclen as usize;
490                Ok(DirEntry {
491                    inner: de,
492                    fd: self.fd,
493                })
494            })
495        }
496    }
497}
498
499#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
500pub enum FileType {
501    Fifo,
502    CharDevice,
503    Directory,
504    BlockDevice,
505    RegularFile,
506    Symlink,
507    Socket,
508    Unknown,
509}
510
511pub struct DirEntry<'a> {
512    inner: Dirent,
513    fd: BorrowedFd<'a>,
514}
515
516impl DirEntry<'_> {
517    /// If the dir entry is `.` or `..`
518    #[must_use]
519    #[inline]
520    pub fn is_relative_reference(&self) -> bool {
521        &self.inner.d_name[..2] == b".\0" || &self.inner.d_name[..3] == b"..\0"
522    }
523
524    #[must_use]
525    pub fn file_type(&self) -> FileType {
526        match self.inner.d_type {
527            rusl::platform::DirType::DT_FIFO => FileType::Fifo,
528            rusl::platform::DirType::DT_CHR => FileType::CharDevice,
529            rusl::platform::DirType::DT_DIR => FileType::Directory,
530            rusl::platform::DirType::DT_BLK => FileType::BlockDevice,
531            rusl::platform::DirType::DT_REG => FileType::RegularFile,
532            rusl::platform::DirType::DT_LNK => FileType::Symlink,
533            rusl::platform::DirType::DT_SOCK => FileType::Socket,
534            _ => FileType::Unknown,
535        }
536    }
537
538    /// Gets the utf8 filename of the entity
539    /// # Errors
540    /// The file name is not null terminated, or that null terminated name is not utf8
541    pub fn file_name(&self) -> Result<&str> {
542        let len = buf_strlen(&self.inner.d_name)?;
543        // Safety:
544        // We just checked the len
545        let as_str = unsafe { core::str::from_utf8(self.inner.d_name.get_unchecked(..len)) }
546            .map_err(|_| Error::no_code("File name not utf8"))?;
547        Ok(as_str)
548    }
549
550    /// Gets the unixstr file name of the entity
551    /// # Errors
552    /// The file name is not null terminated
553    pub fn file_unix_name(&self) -> Result<&UnixStr> {
554        let len = buf_strlen(&self.inner.d_name)?;
555        unsafe {
556            // Safety: The non-null terminated len is one less than the null terminated len
557            // ie. we just did a range check.
558            let tgt = self.inner.d_name.get_unchecked(..=len);
559            // Safety: `&UnixStr` and `&[u8]` have the same layout
560            Ok(&*(core::ptr::from_ref::<[u8]>(tgt) as *const UnixStr))
561        }
562    }
563
564    /// Opens this entity's file in read only mode
565    /// # Errors
566    /// This entity is not a file, check `file_type(&self)` first to be sure
567    /// Os errors relating to permissions
568    #[inline]
569    pub fn open_file(&self) -> Result<File> {
570        let ft = self.file_type();
571        if ft == FileType::RegularFile || ft == FileType::CharDevice {
572            Ok(File::open_at(self.fd.fd, self.file_unix_name()?)?)
573        } else {
574            Err(Error::no_code("Tried to open non-file as file"))
575        }
576    }
577
578    /// Opens this entity's directory
579    /// # Errors
580    /// The entity is nota directory, check `file_type(&self)` first to be sure
581    /// Os errors relating to permissions
582    #[inline]
583    pub fn open_dir(&self) -> Result<Directory> {
584        if self.file_type() == FileType::Directory {
585            Ok(Directory::open_at(self.fd.fd, self.file_unix_name()?)?)
586        } else {
587            Err(Error::no_code("Tried to open non-file as file"))
588        }
589    }
590}
591
592/// Tries to remove a directory from the filesystem at the specified path
593/// # Errors
594/// OS errors relating to file access/permissions
595#[inline]
596pub fn remove_dir(path: &UnixStr) -> Result<()> {
597    rusl::unistd::unlink_flags(path, UnlinkFlags::at_removedir())?;
598    Ok(())
599}
600
601/// Tries to recursively remove a directory and its contents from the filesystem at the specified path.
602/// Potentially very destructive
603/// # Errors
604/// Os errors relating to file access/permissions
605pub fn remove_dir_all(path: &UnixStr) -> Result<()> {
606    let dir = Directory::open(path)?;
607    dir.remove_all()?;
608    remove_dir(path)
609}
610
611impl Write for File {
612    #[inline]
613    fn write(&mut self, buf: &[u8]) -> Result<usize> {
614        Ok(rusl::unistd::write(self.0 .0, buf)?)
615    }
616
617    #[inline]
618    fn flush(&mut self) -> Result<()> {
619        Ok(())
620    }
621}
622
623#[derive(Clone, Debug)]
624#[expect(clippy::struct_excessive_bools)]
625pub struct OpenOptions {
626    read: bool,
627    write: bool,
628    append: bool,
629    truncate: bool,
630    create: bool,
631    create_new: bool,
632    flags: OpenFlags,
633    mode: Mode,
634}
635
636impl Default for OpenOptions {
637    #[inline]
638    fn default() -> Self {
639        Self::new()
640    }
641}
642
643impl OpenOptions {
644    #[must_use]
645    pub fn new() -> OpenOptions {
646        OpenOptions {
647            // generic
648            read: false,
649            write: false,
650            append: false,
651            truncate: false,
652            create: false,
653            create_new: false,
654            // system-specific
655            flags: OpenFlags::empty(),
656            mode: Mode::from(0o0_000_666),
657        }
658    }
659
660    pub fn read(&mut self, read: bool) -> &mut Self {
661        self.read = read;
662        self
663    }
664    pub fn write(&mut self, write: bool) -> &mut Self {
665        self.write = write;
666        self
667    }
668    pub fn append(&mut self, append: bool) -> &mut Self {
669        self.append = append;
670        self
671    }
672    pub fn truncate(&mut self, truncate: bool) -> &mut Self {
673        self.truncate = truncate;
674        self
675    }
676    pub fn create(&mut self, create: bool) -> &mut Self {
677        self.create = create;
678        self
679    }
680    pub fn create_new(&mut self, create_new: bool) -> &mut Self {
681        self.create_new = create_new;
682        self
683    }
684
685    pub fn custom_flags(&mut self, flags: OpenFlags) -> &mut Self {
686        self.flags = flags;
687        self
688    }
689    pub fn mode(&mut self, mode: Mode) -> &mut Self {
690        self.mode = mode;
691        self
692    }
693    /// Opens a file with `self` as provided options
694    /// # Errors
695    /// See `File::open_with_options`
696    #[inline]
697    pub fn open(&self, path: &UnixStr) -> Result<File> {
698        File::open_with_options(path, self)
699    }
700
701    fn get_access_mode(&self) -> Result<OpenFlags> {
702        match (self.read, self.write, self.append) {
703            (true, false, false) => Ok(OpenFlags::O_RDONLY),
704            (false, true, false) => Ok(OpenFlags::O_WRONLY),
705            (true, true, false) => Ok(OpenFlags::O_RDWR),
706            (false, _, true) => Ok(OpenFlags::O_WRONLY | OpenFlags::O_APPEND),
707            (true, _, true) => Ok(OpenFlags::O_RDWR | OpenFlags::O_APPEND),
708            (false, false, false) => Err(Error::no_code(
709                "Bad OpenOptions, no access mode (read, write, append)",
710            )),
711        }
712    }
713
714    fn get_creation_mode(&self) -> crate::error::Result<OpenFlags> {
715        match (self.write, self.append) {
716            (true, false) => {}
717            (false, false) => {
718                if self.truncate || self.create || self.create_new {
719                    return Err(Error::no_code("Bad OpenOptions, used truncate, create, or create_new without access mode write or append"));
720                }
721            }
722            (_, true) => {
723                if self.truncate && !self.create_new {
724                    return Err(Error::no_code(
725                        "Bad OpenOptions, used truncate without create_new with access mode append",
726                    ));
727                }
728            }
729        }
730
731        Ok(match (self.create, self.truncate, self.create_new) {
732            (false, false, false) => OpenFlags::empty(),
733            (true, false, false) => OpenFlags::O_CREAT,
734            (false, true, false) => OpenFlags::O_TRUNC,
735            (true, true, false) => OpenFlags::O_CREAT | OpenFlags::O_TRUNC,
736            (_, _, true) => OpenFlags::O_CREAT | OpenFlags::O_EXCL,
737        })
738    }
739}