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}