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