Skip to main content

yash_builtin/
umask.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2024 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//! Umask built-in
18//!
19//! This module implements the [`umask` built-in], which shows or sets the file
20//! mode creation mask.
21//!
22//! [`umask` built-in]: https://magicant.github.io/yash-rs/builtins/umask.html
23
24use crate::common::output;
25use crate::common::report::report_error;
26use yash_env::Env;
27use yash_env::semantics::Field;
28use yash_env::system::{Fcntl, Isatty, Mode, Umask, Write};
29
30pub mod eval;
31pub mod format;
32pub mod symbol;
33pub mod syntax;
34
35/// Interpretation of command-line arguments that determine the behavior of the
36/// `umask` built-in
37#[derive(Clone, Debug, Eq, Hash, PartialEq)]
38pub enum Command {
39    /// Show the current file mode creation mask
40    Show { symbolic: bool },
41    /// Set the file mode creation mask
42    Set(Vec<symbol::Clause>),
43}
44
45impl Command {
46    /// Creates a `Command::Set` variant from a raw mask.
47    ///
48    /// This is a convenience method for creating a `Command::Set` variant from
49    /// a raw mask. The result contains a single clause that sets the mask to
50    /// the given value for all bits.
51    #[must_use]
52    pub fn set_from_raw_mask(mask: u16) -> Self {
53        use symbol::{Action, Clause, Operator, Permission, Who};
54        Self::Set(vec![Clause {
55            who: Who { mask: 0o777 },
56            actions: vec![Action {
57                operator: Operator::Set,
58                permission: Permission::Literal {
59                    // Negate because the Operator applies to the negation of the mask
60                    mask: !mask,
61                    conditional_executable: false,
62                },
63            }],
64        }])
65    }
66
67    /// Executes the `umask` built-in.
68    ///
69    /// Regardless of the command type, this function performs the following steps:
70    ///
71    /// 1. Obtain the current mask from the environment. ([`Umask::umask`])
72    /// 1. Compute a new mask to be set. ([`eval::new_mask`])
73    /// 1. Set the new mask. ([`Umask::umask`])
74    ///
75    /// Returns the string that should be printed to the standard output.
76    pub fn execute<S: Umask>(&self, env: &mut Env<S>) -> String {
77        let current = !env.system.umask(Mode::empty()).bits();
78        let new_mask = eval::new_mask(current as _, self);
79        env.system.umask(Mode::from_bits_retain(!new_mask as _));
80
81        match *self {
82            Self::Show { symbolic: false } => format!("{:03o}\n", !new_mask),
83            Self::Show { symbolic: true } => {
84                let mut output = format::format_symbolic(new_mask);
85                output.push('\n');
86                output
87            }
88            Self::Set(_) => String::new(),
89        }
90    }
91}
92
93/// Entry point of the `umask` built-in
94pub async fn main<S>(env: &mut Env<S>, args: Vec<Field>) -> crate::Result
95where
96    S: Umask + Fcntl + Isatty + Write,
97{
98    match syntax::parse(env, args) {
99        Ok(command) => {
100            let result = command.execute(env);
101            output(env, &result).await
102        }
103        Err(e) => report_error(env, &e).await,
104    }
105}