summary refs log tree commit diff
path: root/sys_util/src/terminal.rs
blob: 211fb15190a86e7146493abf4dd57dd713286bb6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
// Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use std::mem::zeroed;
use std::io::StdinLock;
use std::os::unix::io::RawFd;

use libc::{fcntl, tcgetattr, tcsetattr, isatty, read, c_int, termios, STDIN_FILENO, TCSANOW,
           ICANON, ECHO, ISIG, O_NONBLOCK, F_GETFL, F_SETFL};

use {Result, errno_result};

fn modify_mode<F: FnOnce(&mut termios)>(fd: RawFd, f: F) -> Result<()> {
    // Safe because we check the return value of isatty.
    if unsafe { isatty(fd) } != 1 {
        return Ok(());
    }

    // The following pair are safe because termios gets totally overwritten by tcgetattr and we
    // check the return result.
    let mut termios: termios = unsafe { zeroed() };
    let ret = unsafe { tcgetattr(fd, &mut termios as *mut _) };
    if ret < 0 {
        return errno_result();
    }
    let mut new_termios = termios;
    f(&mut new_termios);
    // Safe because the syscall will only read the extent of termios and we check the return result.
    let ret = unsafe { tcsetattr(fd, TCSANOW, &new_termios as *const _) };
    if ret < 0 {
        return errno_result();
    }

    Ok(())
}

fn get_flags(fd: RawFd) -> Result<c_int> {
    // Safe because no third parameter is expected and we check the return result.
    let ret = unsafe { fcntl(fd, F_GETFL) };
    if ret < 0 {
        return errno_result();
    }
    Ok(ret)
}


fn set_flags(fd: RawFd, flags: c_int) -> Result<()> {
    // Safe because we supply the third parameter and we check the return result.
    let ret = unsafe { fcntl(fd, F_SETFL, flags) };
    if ret < 0 {
        return errno_result();
    }
    Ok(())
}

/// Trait for file descriptors that are TTYs, according to `isatty(3)`.
///
/// This is marked unsafe because the implementation must promise that the returned RawFd is a valid
/// fd and that the lifetime of the returned fd is at least that of the trait object.
pub unsafe trait Terminal {
    /// Gets the file descriptor of the TTY.
    fn tty_fd(&self) -> RawFd;

    /// Set this terminal's mode to canonical mode (`ICANON | ECHO | ISIG`).
    fn set_canon_mode(&self) -> Result<()> {
        modify_mode(self.tty_fd(), |t| t.c_lflag |= ICANON | ECHO | ISIG)
    }

    /// Set this terminal's mode to raw mode (`!(ICANON | ECHO | ISIG)`).
    fn set_raw_mode(&self) -> Result<()> {
        modify_mode(self.tty_fd(), |t| t.c_lflag &= !(ICANON | ECHO | ISIG))
    }

    /// Sets the non-blocking mode of this terminal's file descriptor.
    ///
    /// If `non_block` is `true`, then `read_raw` will not block. If `non_block` is `false`, then
    /// `read_raw` may block if there is nothing to read.
    fn set_non_block(&self, non_block: bool) -> Result<()> {
        let old_flags = get_flags(self.tty_fd())?;
        let new_flags = if non_block {
            old_flags | O_NONBLOCK
        } else {
            old_flags & !O_NONBLOCK
        };
        if new_flags != old_flags {
            set_flags(self.tty_fd(), new_flags)?
        }
        Ok(())
    }

    /// Reads up to `out.len()` bytes from this terminal without any buffering.
    ///
    /// This may block, depending on if non-blocking was enabled with `set_non_block` or if there
    /// are any bytes to read. If there is at least one byte that is readable, this will not block.
    fn read_raw(&self, out: &mut [u8]) -> Result<usize> {
        // Safe because read will only modify the pointer up to the length we give it and we check
        // the return result.
        let ret = unsafe { read(self.tty_fd(), out.as_mut_ptr() as *mut _, out.len()) };
        if ret < 0 {
            return errno_result();
        }

        Ok(ret as usize)
    }
}

// Safe because we return a genuine terminal fd that never changes and shares our lifetime.
unsafe impl<'a> Terminal for StdinLock<'a> {
    fn tty_fd(&self) -> RawFd {
        STDIN_FILENO
    }
}