Skip to main content

yash_env/system/
file_system.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2021 WATANABE Yuki
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
17//! Items about file systems
18
19use super::{Gid, Result, Uid};
20use crate::io::Fd;
21use crate::path::{Path, PathBuf};
22use crate::str::UnixStr;
23use bitflags::bitflags;
24use enumset::{EnumSet, EnumSetType};
25use std::ffi::CStr;
26use std::fmt::Debug;
27use std::io::SeekFrom;
28use std::rc::Rc;
29
30#[cfg(unix)]
31const RAW_AT_FDCWD: i32 = libc::AT_FDCWD;
32#[cfg(not(unix))]
33const RAW_AT_FDCWD: i32 = -100;
34
35/// Sentinel for the current working directory
36///
37/// This value can be passed to system calls named "*at" such as
38/// [`fstatat`](super::Fstat::fstatat).
39pub const AT_FDCWD: Fd = Fd(RAW_AT_FDCWD);
40
41/// Metadata of a file contained in a directory
42///
43/// `DirEntry` objects are enumerated by a [`Dir`] implementor.
44#[derive(Clone, Copy, Debug)]
45#[non_exhaustive]
46pub struct DirEntry<'a> {
47    /// Filename
48    pub name: &'a UnixStr,
49}
50
51/// Trait for enumerating directory entries
52///
53/// An implementor of `Dir` may retain a file descriptor (or any other resource
54/// alike) to access the underlying system and obtain entry information. The
55/// file descriptor is released when the implementor object is dropped.
56pub trait Dir: Debug {
57    /// Returns the next directory entry.
58    fn next(&mut self) -> Result<Option<DirEntry<'_>>>;
59}
60
61#[cfg(unix)]
62type RawModeDef = libc::mode_t;
63#[cfg(not(unix))]
64type RawModeDef = u32;
65
66/// Raw file permission bits type
67///
68/// This is a type alias for the raw file permission bits type `mode_t` declared
69/// in the [`libc`] crate. The exact representation of this type is
70/// platform-dependent while POSIX requires the type to be an integer. On
71/// non-Unix platforms, this type is hard-coded to `u32`.
72///
73/// File permission bits are usually wrapped in the [`Mode`] type for better type
74/// safety, so this type is not used directly in most cases.
75pub type RawMode = RawModeDef;
76
77/// File permission bits
78///
79/// This type implements the new type pattern for the raw file permission bits
80/// type [`RawMode`]. The advantage of using this type is that it is more
81/// type-safe than using the raw integer value directly.
82///
83/// This type only defines the permission bits and does not include the file
84/// type bits (e.g., regular file, directory, symbolic link, etc.). The file
85/// types are represented by the [`FileType`] enum.
86#[derive(Copy, Clone, Eq, Hash, PartialEq)]
87#[repr(transparent)]
88pub struct Mode(RawMode);
89
90bitflags! {
91    impl Mode: RawMode {
92        /// User read permission (`0o400`)
93        const USER_READ = 0o400;
94        /// User write permission (`0o200`)
95        const USER_WRITE = 0o200;
96        /// User execute permission (`0o100`)
97        const USER_EXEC = 0o100;
98        /// User read, write, and execute permissions (`0o700`)
99        const USER_ALL = 0o700;
100        /// Group read permission (`0o040`)
101        const GROUP_READ = 0o040;
102        /// Group write permission (`0o020`)
103        const GROUP_WRITE = 0o020;
104        /// Group execute permission (`0o010`)
105        const GROUP_EXEC = 0o010;
106        /// Group read, write, and execute permissions (`0o070`)
107        const GROUP_ALL = 0o070;
108        /// Other read permission (`0o004`)
109        const OTHER_READ = 0o004;
110        /// Other write permission (`0o002`)
111        const OTHER_WRITE = 0o002;
112        /// Other execute permission (`0o001`)
113        const OTHER_EXEC = 0o001;
114        /// Other read, write, and execute permissions (`0o007`)
115        const OTHER_ALL = 0o007;
116        /// All read permission (`0o444`)
117        const ALL_READ = 0o444;
118        /// All write permission (`0o222`)
119        const ALL_WRITE = 0o222;
120        /// All execute permission (`0o111`)
121        const ALL_EXEC = 0o111;
122        /// All combinations of (user, group, other) × (read, write, execute)
123        ///
124        /// Note that this is equivalent to `Mode::USER_ALL | Mode::GROUP_ALL |
125        /// Mode::OTHER_ALL` and does not include the sticky bit, the
126        /// set-user-ID bit, or the set-group-ID bit.
127        const ALL_9 = 0o777;
128        /// Set-user-ID bit (`0o4000`)
129        const SET_USER_ID = 0o4000;
130        /// Set-group-ID bit (`0o2000`)
131        const SET_GROUP_ID = 0o2000;
132        /// Sticky bit (`0o1000`)
133        const STICKY = 0o1000;
134    }
135}
136
137impl Debug for Mode {
138    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
139        write!(f, "Mode({:#o})", self.0)
140    }
141}
142
143/// The default mode is `0o644`, not `0o000`.
144impl Default for Mode {
145    fn default() -> Mode {
146        Mode(0o644)
147    }
148}
149
150/// File type
151#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
152#[non_exhaustive]
153pub enum FileType {
154    /// Regular file
155    Regular,
156    /// Directory
157    Directory,
158    /// Symbolic link
159    Symlink,
160    /// Pipe
161    Fifo,
162    /// Block special device file
163    BlockDevice,
164    /// Character special device file
165    CharacterDevice,
166    /// Socket
167    Socket,
168    /// Other file type, including unknown file types
169    Other,
170}
171
172/// Metadata of a file
173///
174/// Implementations of this trait represent metadata of a file, such as its
175/// type, permissions, owner, size, etc. The [`Fstat`] trait provides methods to
176/// retrieve `Stat` objects for files.
177pub trait Stat {
178    /// Device ID
179    #[must_use]
180    fn dev(&self) -> u64;
181    /// Inode number
182    #[must_use]
183    fn ino(&self) -> u64;
184    /// File mode (permission bits)
185    ///
186    /// Note that this field does not include the file type bits.
187    /// Use [`type`](Self::type) to get the file type.
188    /// You can also use [`is_regular_file`](Self::is_regular_file) and other
189    /// similar methods to check the file type.
190    #[must_use]
191    fn mode(&self) -> Mode;
192    /// File type
193    #[must_use]
194    fn r#type(&self) -> FileType;
195    /// Number of hard links
196    #[must_use]
197    fn nlink(&self) -> u64;
198    /// User ID of the file owner
199    #[must_use]
200    fn uid(&self) -> Uid;
201    /// Group ID of the file owner
202    #[must_use]
203    fn gid(&self) -> Gid;
204    /// Size of the file in bytes
205    #[must_use]
206    fn size(&self) -> u64;
207    // TODO: atime, mtime, ctime, (birthtime)
208
209    /// Returns the device ID and inode number as a tuple.
210    ///
211    /// This method is useful for testing whether two `Stat` objects refer to
212    /// the same file.
213    #[inline(always)]
214    #[must_use]
215    fn identity(&self) -> (u64, u64) {
216        (self.dev(), self.ino())
217    }
218
219    /// Whether the file is a regular file
220    #[inline(always)]
221    #[must_use]
222    fn is_regular_file(&self) -> bool {
223        self.r#type() == FileType::Regular
224    }
225    /// Whether the file is a directory
226    #[inline(always)]
227    #[must_use]
228    fn is_directory(&self) -> bool {
229        self.r#type() == FileType::Directory
230    }
231    /// Whether the file is a symbolic link
232    #[inline(always)]
233    #[must_use]
234    fn is_symlink(&self) -> bool {
235        self.r#type() == FileType::Symlink
236    }
237    /// Whether the file is a pipe
238    #[inline(always)]
239    #[must_use]
240    fn is_fifo(&self) -> bool {
241        self.r#type() == FileType::Fifo
242    }
243    /// Whether the file is a block device
244    #[inline(always)]
245    #[must_use]
246    fn is_block_device(&self) -> bool {
247        self.r#type() == FileType::BlockDevice
248    }
249    /// Whether the file is a character device
250    #[inline(always)]
251    #[must_use]
252    fn is_character_device(&self) -> bool {
253        self.r#type() == FileType::CharacterDevice
254    }
255    /// Whether the file is a socket
256    #[inline(always)]
257    #[must_use]
258    fn is_socket(&self) -> bool {
259        self.r#type() == FileType::Socket
260    }
261}
262
263/// Trait for retrieving file metadata
264///
265/// See also [`IsExecutableFile`].
266pub trait Fstat {
267    /// Metadata type returned by [`fstat`](Self::fstat) and [`fstatat`](Self::fstatat)
268    type Stat: Stat + Clone + Debug;
269
270    /// Retrieves metadata of a file.
271    ///
272    /// This method wraps the [`fstat` system
273    /// call](https://pubs.opengroup.org/onlinepubs/9799919799/functions/fstat.html).
274    /// It takes a file descriptor and returns a `Stat` object containing the
275    /// file metadata.
276    fn fstat(&self, fd: Fd) -> Result<Self::Stat>;
277
278    /// Retrieves metadata of a file.
279    ///
280    /// This method wraps the [`fstatat` system
281    /// call](https://pubs.opengroup.org/onlinepubs/9799919799/functions/fstatat.html).
282    /// It takes a directory file descriptor, a file path, and a flag indicating
283    /// whether to follow symbolic links. It returns a `Stat` object containing
284    /// the file metadata. The file path is interpreted relative to the
285    /// directory represented by the directory file descriptor.
286    fn fstatat(&self, dir_fd: Fd, path: &CStr, follow_symlinks: bool) -> Result<Self::Stat>;
287
288    /// Whether there is a directory at the specified path.
289    #[must_use]
290    fn is_directory(&self, path: &CStr) -> bool {
291        self.fstatat(AT_FDCWD, path, /* follow_symlinks */ true)
292            .is_ok_and(|stat| stat.is_directory())
293    }
294
295    /// Tests if a file descriptor is a pipe.
296    #[must_use]
297    fn fd_is_pipe(&self, fd: Fd) -> bool {
298        self.fstat(fd).is_ok_and(|stat| stat.is_fifo())
299    }
300}
301
302/// Delegates the `Fstat` trait to the contained instance of `S`
303impl<S: Fstat> Fstat for Rc<S> {
304    type Stat = S::Stat;
305
306    #[inline]
307    fn fstat(&self, fd: Fd) -> Result<S::Stat> {
308        (self as &S).fstat(fd)
309    }
310    #[inline]
311    fn fstatat(&self, dir_fd: Fd, path: &CStr, follow_symlinks: bool) -> Result<S::Stat> {
312        (self as &S).fstatat(dir_fd, path, follow_symlinks)
313    }
314    #[inline]
315    fn is_directory(&self, path: &CStr) -> bool {
316        (self as &S).is_directory(path)
317    }
318    #[inline]
319    fn fd_is_pipe(&self, fd: Fd) -> bool {
320        (self as &S).fd_is_pipe(fd)
321    }
322}
323
324/// Trait for checking if a file is executable
325///
326/// This trait declares the `is_executable_file` method, which checks whether a
327/// filepath points to an executable regular file. This trait is separate from
328/// the [`Fstat`] trait because the implementation depends on the `faccessat`
329/// system call.
330pub trait IsExecutableFile {
331    /// Whether there is an executable regular file at the specified path.
332    #[must_use]
333    fn is_executable_file(&self, path: &CStr) -> bool;
334}
335
336/// Delegates the `IsExecutableFile` trait to the contained instance of `S`
337impl<S: IsExecutableFile> IsExecutableFile for Rc<S> {
338    #[inline]
339    fn is_executable_file(&self, path: &CStr) -> bool {
340        (self as &S).is_executable_file(path)
341    }
342}
343
344/// File access mode of open file descriptions
345#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
346#[non_exhaustive]
347pub enum OfdAccess {
348    /// Open for reading only
349    ReadOnly,
350    /// Open for writing only
351    WriteOnly,
352    /// Open for reading and writing
353    ReadWrite,
354    /// Open for executing only (non-directory files)
355    Exec,
356    /// Open for searching only (directories)
357    Search,
358}
359
360/// Options for opening file descriptors
361///
362/// A set of `OpenFlag` values can be passed to [`open`] to configure how the
363/// file descriptor is opened. Some of the flags become the attributes of the
364/// open file description created by the `open` function.
365///
366/// [`open`]: Open::open
367#[derive(Debug, EnumSetType, Hash)]
368#[non_exhaustive]
369pub enum OpenFlag {
370    /// Always write to the end of the file
371    Append,
372    /// Close the file descriptor upon execution of an exec family function
373    CloseOnExec,
374    /// Create the file if it does not exist
375    Create,
376    /// Fail if the file is not a directory
377    Directory,
378    /// Atomically create the file if it does not exist
379    Exclusive,
380    /// Do not make the opened terminal the controlling terminal for the process
381    NoCtty,
382    /// Do not follow symbolic links
383    NoFollow,
384    /// Open the file in non-blocking mode
385    NonBlock,
386    /// Wait until the written data is physically stored on the underlying
387    /// storage device on each write
388    Sync,
389    /// Truncate the file to zero length
390    Truncate,
391}
392
393/// Trait for opening files
394pub trait Open {
395    /// Opens a file descriptor.
396    ///
397    /// This is a thin wrapper around the [`open` system
398    /// call](https://pubs.opengroup.org/onlinepubs/9799919799/functions/open.html).
399    ///
400    /// This function returns a future because opening a pipeline or device file
401    /// may block the calling task until another process opens the other end of
402    /// the pipeline or the device file is ready.
403    /// See the [module-level documentation](super) for details.
404    fn open(
405        &self,
406        path: &CStr,
407        access: OfdAccess,
408        flags: EnumSet<OpenFlag>,
409        mode: Mode,
410    ) -> impl Future<Output = Result<Fd>> + use<Self>;
411
412    /// Opens a file descriptor associated with an anonymous temporary file.
413    ///
414    /// This function works similarly to the `O_TMPFILE` flag specified to the
415    /// `open` function.
416    fn open_tmpfile(&self, parent_dir: &Path) -> Result<Fd>;
417
418    /// Opens a directory for enumerating entries.
419    ///
420    /// This is a thin wrapper around the [`fdopendir` system
421    /// function](https://pubs.opengroup.org/onlinepubs/9799919799/functions/fdopendir.html).
422    fn fdopendir(&self, fd: Fd) -> Result<impl Dir + use<Self>>;
423
424    /// Opens a directory for enumerating entries.
425    ///
426    /// This is a thin wrapper around the [`opendir` system
427    /// function](https://pubs.opengroup.org/onlinepubs/9799919799/functions/fdopendir.html).
428    fn opendir(&self, path: &CStr) -> Result<impl Dir + use<Self>>;
429}
430
431/// Delegates the `Open` trait to the contained instance of `S`
432impl<S: Open> Open for Rc<S> {
433    #[inline]
434    fn open(
435        &self,
436        path: &CStr,
437        access: OfdAccess,
438        flags: EnumSet<OpenFlag>,
439        mode: Mode,
440    ) -> impl Future<Output = Result<Fd>> + use<S> {
441        (self as &S).open(path, access, flags, mode)
442    }
443    #[inline]
444    fn open_tmpfile(&self, parent_dir: &Path) -> Result<Fd> {
445        (self as &S).open_tmpfile(parent_dir)
446    }
447    #[inline]
448    fn fdopendir(&self, fd: Fd) -> Result<impl Dir + use<S>> {
449        (self as &S).fdopendir(fd)
450    }
451    #[inline]
452    fn opendir(&self, path: &CStr) -> Result<impl Dir + use<S>> {
453        (self as &S).opendir(path)
454    }
455}
456
457/// Trait for seeking within file descriptors
458pub trait Seek {
459    /// Moves the position of the open file description.
460    ///
461    /// This is a thin wrapper around the [`lseek` system
462    /// call](https://pubs.opengroup.org/onlinepubs/9799919799/functions/lseek.html).
463    /// If successful, returns the new position from the beginning of the file.
464    fn lseek(&self, fd: Fd, position: SeekFrom) -> Result<u64>;
465}
466
467/// Delegates the `Seek` trait to the contained instance of `S`
468impl<S: Seek> Seek for Rc<S> {
469    #[inline]
470    fn lseek(&self, fd: Fd, position: SeekFrom) -> Result<u64> {
471        (self as &S).lseek(fd, position)
472    }
473}
474
475/// Trait for getting and setting the file creation mask
476pub trait Umask {
477    /// Gets and sets the file creation mode mask.
478    ///
479    /// This is a thin wrapper around the [`umask` system
480    /// call](https://pubs.opengroup.org/onlinepubs/9799919799/functions/umask.html).
481    /// It sets the mask to the given value and returns the previous mask.
482    ///
483    /// You cannot tell the current mask without setting a new one. If you only
484    /// want to get the current mask, you need to set it back to the original
485    /// value after getting it.
486    fn umask(&self, new_mask: Mode) -> Mode;
487}
488
489/// Delegates the `Umask` trait to the contained instance of `S`
490impl<S: Umask> Umask for Rc<S> {
491    #[inline]
492    fn umask(&self, new_mask: Mode) -> Mode {
493        (self as &S).umask(new_mask)
494    }
495}
496
497/// Trait for getting the current working directory
498///
499/// See also [`Chdir`].
500pub trait GetCwd {
501    /// Returns the current working directory path.
502    fn getcwd(&self) -> Result<PathBuf>;
503}
504
505/// Delegates the `GetCwd` trait to the contained instance of `S`
506impl<S: GetCwd> GetCwd for Rc<S> {
507    #[inline]
508    fn getcwd(&self) -> Result<PathBuf> {
509        (self as &S).getcwd()
510    }
511}
512
513/// Trait for changing the current working directory
514///
515/// See also [`GetCwd`].
516pub trait Chdir {
517    /// Changes the working directory.
518    fn chdir(&self, path: &CStr) -> Result<()>;
519}
520
521/// Delegates the `Chdir` trait to the contained instance of `S`
522impl<S: Chdir> Chdir for Rc<S> {
523    #[inline]
524    fn chdir(&self, path: &CStr) -> Result<()> {
525        (self as &S).chdir(path)
526    }
527}