Skip to content
Advertisement

When reading from /dev/tty, what is happening in input and keyboard buffer?

I am playing with the following two code snippets

// Snippet1 in C
#include <stdio.h>

int main(){
    FILE * fp = fopen("/dev/tty", "r");

    int c = getc(fp);
    printf("%d", c);
}
// Snippet2 in Rust
use std::io::Read;
use std::fs;

fn main() {
    let mut f: fs::File = fs::File::open("/dev/tty").unwrap();
    let mut buf: [u8;1] = [0];

    f.read_exact(&mut buf).unwrap();
    print!("byte: {}", buf[0]);
}

What the above code wants to do is to read a byte from the user keyboard and then print it to stdout. The confusing thing is two snippets have different behaviors:

➜  playground gcc main.c -o main
➜  playground ./main
a                                  # input a and press Enter
97%
➜  playground cargo run -q
a                                  # input a and press Enter
byte: 97%                                                                ➜  playground
➜  playground

I am sorry about the format of the above code, I don’t know how to place the prompt at the start of a new line:(

Please note, there are two shell prompts ➜ playground after the execution of Rust code?

I am guessing the Enter is sent to the input buffer as if I have pressed it after execution.

If I know what is actually happening in the buffer, I will find out the cause of this distinction, so I am wondering what’s going on there?

BTW, my environment:

➜  playground rustc --version
rustc 1.57.0 (f1edd0429 2021-11-29)
➜  playground gcc --version
gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

If my question is not allowed, feel free to ask me to delete it:) Thanks in advance:)

Advertisement

Answer

Note that in C, FILE* and the corresponding functions are buffered, so the C program reads the key and newline characters and puts them in its buffer, whereas your Rust code doesn’t use buffering. As a consequence the newline character is still in the kernel buffer for the TTY when your Rust program finishes and so the newline is seen by your shell, whereas the newline has been removed from the kernel buffer by your C program.

You should get the same behaviour as your Rust program with this unbuffered C code:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

int main() {
    int fd = open ("/dev/tty", O_RDONLY);

    char c;
    read (fd, &c, 1);
    printf("%d", c);
}

or the same behaviour as your C program with this buffered Rust code:

use std::io::{ BufReader, Read };
use std::fs;

fn main() {
    let mut f = BufReader::new (fs::File::open("/dev/tty").unwrap());
    let mut buf: [u8;1] = [0];

    f.read_exact(&mut buf).unwrap();
    print!("byte: {}", buf[0]);
}
User contributions licensed under: CC BY-SA
7 People found this is helpful
Advertisement