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;
28
29#[cfg(unix)]
30const RAW_AT_FDCWD: i32 = libc::AT_FDCWD;
31#[cfg(not(unix))]
32const RAW_AT_FDCWD: i32 = -100;
33
34/// Sentinel for the current working directory
35///
36/// This value can be passed to system calls named "*at" such as
37/// [`fstatat`](super::Fstat::fstatat).
38pub const AT_FDCWD: Fd = Fd(RAW_AT_FDCWD);
39
40/// Metadata of a file contained in a directory
41///
42/// `DirEntry` objects are enumerated by a [`Dir`] implementor.
43#[derive(Clone, Copy, Debug)]
44#[non_exhaustive]
45pub struct DirEntry<'a> {
46    /// Filename
47    pub name: &'a UnixStr,
48}
49
50/// Trait for enumerating directory entries
51///
52/// An implementor of `Dir` may retain a file descriptor (or any other resource
53/// alike) to access the underlying system and obtain entry information. The
54/// file descriptor is released when the implementor object is dropped.
55pub trait Dir: Debug {
56    /// Returns the next directory entry.
57    fn next(&mut self) -> Result<Option<DirEntry<'_>>>;
58}
59
60#[cfg(unix)]
61type RawModeDef = libc::mode_t;
62#[cfg(not(unix))]
63type RawModeDef = u32;
64
65/// Raw file permission bits type
66///
67/// This is a type alias for the raw file permission bits type `mode_t` declared
68/// in the [`libc`] crate. The exact representation of this type is
69/// platform-dependent while POSIX requires the type to be an integer. On
70/// non-Unix platforms, this type is hard-coded to `u32`.
71///
72/// File permission bits are usually wrapped in the [`Mode`] type for better type
73/// safety, so this type is not used directly in most cases.
74pub type RawMode = RawModeDef;
75
76/// File permission bits
77///
78/// This type implements the new type pattern for the raw file permission bits
79/// type [`RawMode`]. The advantage of using this type is that it is more
80/// type-safe than using the raw integer value directly.
81///
82/// This type only defines the permission bits and does not include the file
83/// type bits (e.g., regular file, directory, symbolic link, etc.). The file
84/// types are represented by the [`FileType`] enum.
85#[derive(Copy, Clone, Eq, Hash, PartialEq)]
86#[repr(transparent)]
87pub struct Mode(RawMode);
88
89bitflags! {
90    impl Mode: RawMode {
91        /// User read permission (`0o400`)
92        const USER_READ = 0o400;
93        /// User write permission (`0o200`)
94        const USER_WRITE = 0o200;
95        /// User execute permission (`0o100`)
96        const USER_EXEC = 0o100;
97        /// User read, write, and execute permissions (`0o700`)
98        const USER_ALL = 0o700;
99        /// Group read permission (`0o040`)
100        const GROUP_READ = 0o040;
101        /// Group write permission (`0o020`)
102        const GROUP_WRITE = 0o020;
103        /// Group execute permission (`0o010`)
104        const GROUP_EXEC = 0o010;
105        /// Group read, write, and execute permissions (`0o070`)
106        const GROUP_ALL = 0o070;
107        /// Other read permission (`0o004`)
108        const OTHER_READ = 0o004;
109        /// Other write permission (`0o002`)
110        const OTHER_WRITE = 0o002;
111        /// Other execute permission (`0o001`)
112        const OTHER_EXEC = 0o001;
113        /// Other read, write, and execute permissions (`0o007`)
114        const OTHER_ALL = 0o007;
115        /// All read permission (`0o444`)
116        const ALL_READ = 0o444;
117        /// All write permission (`0o222`)
118        const ALL_WRITE = 0o222;
119        /// All execute permission (`0o111`)
120        const ALL_EXEC = 0o111;
121        /// All combinations of (user, group, other) × (read, write, execute)
122        ///
123        /// Note that this is equivalent to `Mode::USER_ALL | Mode::GROUP_ALL |
124        /// Mode::OTHER_ALL` and does not include the sticky bit, the
125        /// set-user-ID bit, or the set-group-ID bit.
126        const ALL_9 = 0o777;
127        /// Set-user-ID bit (`0o4000`)
128        const SET_USER_ID = 0o4000;
129        /// Set-group-ID bit (`0o2000`)
130        const SET_GROUP_ID = 0o2000;
131        /// Sticky bit (`0o1000`)
132        const STICKY = 0o1000;
133    }
134}
135
136impl Debug for Mode {
137    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138        write!(f, "Mode({:#o})", self.0)
139    }
140}
141
142/// The default mode is `0o644`, not `0o000`.
143impl Default for Mode {
144    fn default() -> Mode {
145        Mode(0o644)
146    }
147}
148
149/// File type
150#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
151#[non_exhaustive]
152pub enum FileType {
153    /// Regular file
154    Regular,
155    /// Directory
156    Directory,
157    /// Symbolic link
158    Symlink,
159    /// Pipe
160    Fifo,
161    /// Block special device file
162    BlockDevice,
163    /// Character special device file
164    CharacterDevice,
165    /// Socket
166    Socket,
167    /// Other file type, including unknown file types
168    Other,
169}
170
171/// File status
172///
173/// This type is a collection of file status information. It is similar to the
174/// `stat` structure defined in the POSIX standard, but it is simplified and
175/// does not include all fields of the `stat` structure.
176#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
177#[non_exhaustive]
178pub struct Stat {
179    /// Device ID
180    pub dev: u64,
181    /// Inode number
182    pub ino: u64,
183    /// Access permissions
184    ///
185    /// Note that this field does not include the file type bits.
186    /// The file type is stored in the `type` field.
187    pub mode: Mode,
188    /// File type
189    pub r#type: FileType,
190    /// Number of hard links
191    pub nlink: u64,
192    /// User ID of the file owner
193    pub uid: Uid,
194    /// Group ID of the file owner
195    pub gid: Gid,
196    /// Length of the file in bytes
197    pub size: u64,
198    // TODO: atime, mtime, ctime, (birthtime)
199}
200
201impl Stat {
202    /// Returns the device ID and inode number as a tuple
203    ///
204    /// This method is useful for testing whether two `Stat` objects refer to
205    /// the same file.
206    #[inline]
207    #[must_use]
208    pub const fn identity(&self) -> (u64, u64) {
209        (self.dev, self.ino)
210    }
211}
212
213/// Trait for retrieving file metadata
214///
215/// See also [`IsExecutableFile`].
216pub trait Fstat {
217    /// Retrieves metadata of a file.
218    ///
219    /// This method wraps the [`fstat` system
220    /// call](https://pubs.opengroup.org/onlinepubs/9799919799/functions/fstat.html).
221    /// It takes a file descriptor and returns a `Stat` object containing the
222    /// file metadata.
223    fn fstat(&self, fd: Fd) -> Result<Stat>;
224
225    /// Retrieves metadata of a file.
226    ///
227    /// This method wraps the [`fstatat` system
228    /// call](https://pubs.opengroup.org/onlinepubs/9799919799/functions/fstatat.html).
229    /// It takes a directory file descriptor, a file path, and a flag indicating
230    /// whether to follow symbolic links. It returns a `Stat` object containing
231    /// the file metadata. The file path is interpreted relative to the
232    /// directory represented by the directory file descriptor.
233    fn fstatat(&self, dir_fd: Fd, path: &CStr, follow_symlinks: bool) -> Result<Stat>;
234
235    /// Whether there is a directory at the specified path.
236    #[must_use]
237    fn is_directory(&self, path: &CStr) -> bool {
238        self.fstatat(AT_FDCWD, path, /* follow_symlinks */ true)
239            .is_ok_and(|stat| stat.r#type == FileType::Directory)
240    }
241
242    /// Tests if a file descriptor is a pipe.
243    #[must_use]
244    fn fd_is_pipe(&self, fd: Fd) -> bool {
245        self.fstat(fd)
246            .is_ok_and(|stat| stat.r#type == FileType::Fifo)
247    }
248}
249
250/// Trait for checking if a file is executable
251///
252/// This trait declares the `is_executable_file` method, which checks whether a
253/// filepath points to an executable regular file. This trait is separate from
254/// the [`Fstat`] trait because the implementation depends on the `faccessat`
255/// system call.
256pub trait IsExecutableFile {
257    /// Whether there is an executable regular file at the specified path.
258    #[must_use]
259    fn is_executable_file(&self, path: &CStr) -> bool;
260}
261
262/// File access mode of open file descriptions
263#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
264#[non_exhaustive]
265pub enum OfdAccess {
266    /// Open for reading only
267    ReadOnly,
268    /// Open for writing only
269    WriteOnly,
270    /// Open for reading and writing
271    ReadWrite,
272    /// Open for executing only (non-directory files)
273    Exec,
274    /// Open for searching only (directories)
275    Search,
276}
277
278/// Options for opening file descriptors
279///
280/// A set of `OpenFlag` values can be passed to [`open`] to configure how the
281/// file descriptor is opened. Some of the flags become the attributes of the
282/// open file description created by the `open` function.
283///
284/// [`open`]: Open::open
285#[derive(Debug, EnumSetType, Hash)]
286#[non_exhaustive]
287pub enum OpenFlag {
288    /// Always write to the end of the file
289    Append,
290    /// Close the file descriptor upon execution of an exec family function
291    CloseOnExec,
292    /// Create the file if it does not exist
293    Create,
294    /// Fail if the file is not a directory
295    Directory,
296    /// Atomically create the file if it does not exist
297    Exclusive,
298    /// Do not make the opened terminal the controlling terminal for the process
299    NoCtty,
300    /// Do not follow symbolic links
301    NoFollow,
302    /// Open the file in non-blocking mode
303    NonBlock,
304    /// Wait until the written data is physically stored on the underlying
305    /// storage device on each write
306    Sync,
307    /// Truncate the file to zero length
308    Truncate,
309}
310
311/// Trait for opening files
312pub trait Open {
313    /// Opens a file descriptor.
314    ///
315    /// This is a thin wrapper around the [`open` system
316    /// call](https://pubs.opengroup.org/onlinepubs/9799919799/functions/open.html).
317    fn open(
318        &self,
319        path: &CStr,
320        access: OfdAccess,
321        flags: EnumSet<OpenFlag>,
322        mode: Mode,
323    ) -> Result<Fd>;
324
325    /// Opens a file descriptor associated with an anonymous temporary file.
326    ///
327    /// This function works similarly to the `O_TMPFILE` flag specified to the
328    /// `open` function.
329    fn open_tmpfile(&self, parent_dir: &Path) -> Result<Fd>;
330
331    /// Opens a directory for enumerating entries.
332    ///
333    /// This is a thin wrapper around the [`fdopendir` system
334    /// function](https://pubs.opengroup.org/onlinepubs/9799919799/functions/fdopendir.html).
335    fn fdopendir(&self, fd: Fd) -> Result<impl Dir + use<Self>>;
336
337    /// Opens a directory for enumerating entries.
338    ///
339    /// This is a thin wrapper around the [`opendir` system
340    /// function](https://pubs.opengroup.org/onlinepubs/9799919799/functions/fdopendir.html).
341    fn opendir(&self, path: &CStr) -> Result<impl Dir + use<Self>>;
342}
343
344/// Trait for seeking within file descriptors
345pub trait Seek {
346    /// Moves the position of the open file description.
347    ///
348    /// This is a thin wrapper around the [`lseek` system
349    /// call](https://pubs.opengroup.org/onlinepubs/9799919799/functions/lseek.html).
350    /// If successful, returns the new position from the beginning of the file.
351    fn lseek(&self, fd: Fd, position: SeekFrom) -> Result<u64>;
352}
353
354/// Trait for getting and setting the file creation mask
355pub trait Umask {
356    /// Gets and sets the file creation mode mask.
357    ///
358    /// This is a thin wrapper around the [`umask` system
359    /// call](https://pubs.opengroup.org/onlinepubs/9799919799/functions/umask.html).
360    /// It sets the mask to the given value and returns the previous mask.
361    ///
362    /// You cannot tell the current mask without setting a new one. If you only
363    /// want to get the current mask, you need to set it back to the original
364    /// value after getting it.
365    fn umask(&self, new_mask: Mode) -> Mode;
366}
367
368/// Trait for getting the current working directory
369///
370/// See also [`Chdir`].
371pub trait GetCwd {
372    /// Returns the current working directory path.
373    fn getcwd(&self) -> Result<PathBuf>;
374}
375
376/// Trait for changing the current working directory
377///
378/// See also [`GetCwd`].
379pub trait Chdir {
380    /// Changes the working directory.
381    fn chdir(&self, path: &CStr) -> Result<()>;
382}