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}