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}