yash_env/io.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//! Type definitions for I/O.
18
19use crate::Env;
20use crate::source::Location;
21use crate::source::pretty::{Report, ReportType, Snippet};
22use crate::system::concurrency::WriteAll;
23use crate::system::{Close, Dup, FdFlag, Isatty};
24use annotate_snippets::Renderer;
25use std::borrow::Cow;
26#[cfg(unix)]
27pub(crate) use std::os::unix::io::RawFd;
28
29#[cfg(not(unix))]
30pub(crate) type RawFd = i32;
31
32/// File descriptor
33///
34/// This is the `newtype` pattern applied to [`RawFd`], which is merely a type
35/// alias.
36#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
37#[repr(transparent)]
38pub struct Fd(pub RawFd);
39
40impl Fd {
41 /// File descriptor for the standard input
42 pub const STDIN: Fd = Fd(0);
43 /// File descriptor for the standard output
44 pub const STDOUT: Fd = Fd(1);
45 /// File descriptor for the standard error
46 pub const STDERR: Fd = Fd(2);
47}
48
49impl From<RawFd> for Fd {
50 fn from(raw_fd: RawFd) -> Fd {
51 Fd(raw_fd)
52 }
53}
54
55impl std::fmt::Display for Fd {
56 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57 self.0.fmt(f)
58 }
59}
60
61/// Minimum file descriptor the shell may occupy for its internal use
62///
63/// POSIX reserves file descriptors below `MIN_INTERNAL_FD` so the user can use
64/// them freely. When the shell needs to open a file descriptor that is
65/// invisible to the user, it should be kept at `MIN_INTERNAL_FD` or above.
66/// (Hint: A typical way to move a file descriptor is to
67/// [`dup`](crate::system::Dup::dup) and [`close`](crate::system::Close::close).
68/// You can also use [`move_fd_internal`].)
69pub const MIN_INTERNAL_FD: Fd = Fd(10);
70
71/// Moves a file descriptor to be at least [`MIN_INTERNAL_FD`].
72///
73/// This is a convenience function that duplicates the given `from` FD to be at
74/// least `MIN_INTERNAL_FD`, and closes the original `from` FD. The new FD will
75/// have the `CLOEXEC` flag set. If `from` is already at least
76/// `MIN_INTERNAL_FD`, this function does nothing and returns `from`.
77///
78/// This function can be used to make sure a file descriptor used by the shell
79/// does not conflict with file descriptors used by the user.
80/// [`MIN_INTERNAL_FD`] is the minimum file descriptor number the shell may use
81/// internally.
82///
83/// Returns the new file descriptor. On error during duplication, the original
84/// `from` FD is still closed, and the error is returned. Errors during closing
85/// the original `from` FD are ignored.
86pub fn move_fd_internal<S>(system: &S, from: Fd) -> crate::system::Result<Fd>
87where
88 S: Dup + Close + ?Sized,
89{
90 if from >= MIN_INTERNAL_FD {
91 return Ok(from);
92 }
93
94 let new = system.dup(from, MIN_INTERNAL_FD, FdFlag::CloseOnExec.into());
95 system.close(from).ok();
96 new
97}
98
99/// Convenience function for converting a report into a string.
100///
101/// The returned string may contain ANSI color escape sequences if the given
102/// `env` allows it. The string will end with a newline.
103///
104/// To print the returned string to the standard error, you can use
105/// [`WriteAll::print_error`].
106#[must_use]
107pub fn report_to_string<S: Isatty>(env: &Env<S>, report: &Report<'_>) -> String {
108 let renderer = if env.should_print_error_in_color() {
109 Renderer::styled()
110 } else {
111 Renderer::plain()
112 };
113 format!("{}\n", renderer.render(&[report.into()]))
114}
115
116/// Convenience function for printing a report.
117///
118/// This function converts the `report` into a string by using
119/// [`report_to_string`], and prints the result to the standard error.
120pub async fn print_report<S: Isatty + WriteAll>(env: &mut Env<S>, report: &Report<'_>) {
121 let report_str = report_to_string(env, report);
122 env.system.print_error(&report_str).await;
123}
124
125/// Convenience function for printing an error message.
126///
127/// This function constructs a temporary [`Report`] based on the given `title`,
128/// `label`, and `location`. The message is printed using [`print_report`].
129pub async fn print_error<S: Isatty + WriteAll>(
130 env: &mut Env<S>,
131 title: Cow<'_, str>,
132 label: Cow<'_, str>,
133 location: &Location,
134) {
135 let mut report = Report::new();
136 report.r#type = ReportType::Error;
137 report.title = title;
138 report.snippets = Snippet::with_primary_span(location, label);
139 print_report(env, &report).await;
140}