Skip to main content

yash_builtin/cd/
chdir.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2023 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//! Part of the cd built-in that invokes the underlying system call
18
19use crate::common::report::prepare_report_message_and_divert;
20use std::borrow::Cow;
21use std::ffi::CString;
22use std::ffi::NulError;
23use thiserror::Error;
24use yash_env::Env;
25use yash_env::path::Path;
26use yash_env::semantics::Field;
27use yash_env::source::Location;
28use yash_env::source::pretty::{Report, ReportType, Snippet};
29#[cfg(doc)]
30use yash_env::stack::Stack;
31use yash_env::system::{Chdir, Errno, Fcntl, Isatty, Write};
32
33/// Error invoking the underlying system call
34#[derive(Debug, Clone, Eq, Error, PartialEq)]
35pub enum Error {
36    /// The target path contains a nul byte.
37    #[error("path contains a nul byte")]
38    NulByteInPath,
39
40    /// Error from the underlying system call
41    #[error(transparent)]
42    SystemError(#[from] Errno),
43}
44
45impl From<NulError> for Error {
46    fn from(_: NulError) -> Self {
47        Self::NulByteInPath
48    }
49}
50
51pub fn chdir<T: Chdir>(env: &mut Env<T>, path: &Path) -> Result<(), Error> {
52    let c_path = CString::new(path.as_unix_str().as_bytes())?;
53    Ok(env.system.chdir(&c_path)?)
54}
55
56/// Creates a message that describes the failure.
57///
58/// This function expects
59/// [`env.stack.current_builtin()`](Stack::current_builtin) to return `Some(_)`.
60/// If it returns `None`, the function will annotate the message with a dummy
61/// location.
62///
63/// See [`prepare_report_message_and_divert`] for the second return value.
64#[must_use = "returned message should be printed"]
65pub fn failure_message<S: Isatty>(
66    env: &Env<S>,
67    operand: Option<&Field>,
68    path: &Path,
69    error: &Error,
70) -> (String, yash_env::semantics::Result) {
71    let location = operand
72        .or_else(|| env.stack.current_builtin().map(|builtin| &builtin.name))
73        .map(|field| Cow::Borrowed(&field.origin))
74        .unwrap_or_else(|| Cow::Owned(Location::dummy("")));
75    let label = Cow::Owned(format!("{path:?}: {error}"));
76    let mut report = Report::new();
77    report.r#type = ReportType::Error;
78    report.title = "cannot change the working directory".into();
79    report.snippets = Snippet::with_primary_span(&location, label);
80    prepare_report_message_and_divert(env, report)
81}
82
83/// Prints an error message to the standard error.
84///
85/// This function constructs a message with [`failure_message`] and prints it
86/// with [`Concurrent::print_error`].
87///
88/// [`Concurrent::print_error`]: yash_env::system::Concurrent::print_error
89pub async fn report_failure<S>(
90    env: &mut Env<S>,
91    operand: Option<&Field>,
92    path: &Path,
93    error: &Error,
94) -> crate::Result
95where
96    S: Fcntl + Isatty + Write,
97{
98    let (message, divert) = failure_message(env, operand, path, error);
99    env.system.print_error(&message).await;
100    crate::Result::with_exit_status_and_divert(super::EXIT_STATUS_CHDIR_ERROR, divert)
101}