Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cron atty::is(atty::Stream::Stdin) #52

Open
nbari opened this issue Jul 23, 2021 · 13 comments
Open

cron atty::is(atty::Stream::Stdin) #52

nbari opened this issue Jul 23, 2021 · 13 comments

Comments

@nbari
Copy link

nbari commented Jul 23, 2021

I have a program that reads from stdin only when isatty returns false

let input_stdin = !atty::is(atty::Stream::Stdin); 

When running this from a cron it always returns true like if there were something in stdin but there is nothing.

use atty::Stream;

fn main() {
    if atty::is(Stream::Stdout) {
        println!("I'm a terminal");
     } else {
        println!("I'm not");
     }

     let input_stdin = !atty::is(atty::Stream::Stdin); // isatty returns false if there's something in stdin.
     if input_stdin {
         println!("stdin"); // how to prevent this to happen when calling it from a cron
     }

}

Any idea how to prevent this so that I could call my app from a cron job and distinguish when there is something in stdin and not?

@neithernut
Copy link

neithernut commented Jul 24, 2021

What cron-daemon are you using? (And what version?)

@nbari
Copy link
Author

nbari commented Jul 24, 2021

I am using the cron that comes by default in the latest FreeBSD 13 (amd64): https://www.freebsd.org/cgi/man.cgi?cron(8)

@neithernut
Copy link

First of all: isatty doesn't actually tell you whether your process does or can receive input via stdin but whether stdin is connected to a tty. There is no way that I'm aware of that a process can detect whether it may receive any input from stdin (if it is open).

Assuming you do want to know whether the process was spawned from a terminal and not by your cron: isn't your logic backwards?

let input_stdin = !atty::is(atty::Stream::Stdin); // isatty returns false if there's something in stdin.
if input_stdin {
    println!("stdin"); // how to prevent this to happen when calling it from a cron
}

atty::is returns true if stdin is connected to a tty, which should not be the case for a cronjob, so we'd expect input_stdin to be true when run as a cron-job. Or does your program actually expect input only when it is not run from a terminal?

@nbari
Copy link
Author

nbari commented Jul 26, 2021

It has been working "fine" (https://github.com/s3m/s3m/blob/master/src/s3m/options.rs#L211) but I never tested within a cron.

atty::is returns true if stdin is connected to a tty

I have been using it to detect if something is in the stdin so that I can read the input and stream it, but maybe is not the way I should be doing this.

@neithernut
Copy link

I have been using it to detect if something is in the stdin so that I can read the input and stream it, but maybe is not the way I should be doing this.

Yes, that's indeed not how you do this. Tools typically just read until the "end of file" in such situations.

@BurntSushi
Copy link
Contributor

It's not quite that simple. It's pretty typical for tools to change their behavior based on whether they believe stdin is readable or not. Whether stdin is attached to a tty or not is just one part of that calculation.

Here's the logic that ripgrep uses as an example: https://docs.rs/grep-cli/0.1.6/src/grep_cli/lib.rs.html#190-213

Logically speaking: read from stdin if it's believed to be readable, where "believed to be readable" means "it's not a tty and has a file type amenable for reading."

@nbari
Copy link
Author

nbari commented Jul 26, 2021

I will give it a try, thanks for sharing 👍

@nbari
Copy link
Author

nbari commented Aug 3, 2021

From a cron, I still getting like if there is something to read from stdin,

wondering what could be the cons of using something like this:

let mut line = String::new();
std::io::stdin().read_line(&mut line).unwrap();
if line.len() > 0 {
// read from stding
}

@neithernut
Copy link

You don't use a read just for checking for input, there's no point in that. It only complicates your code, since you end up with that extra line you have to incorporate. You just read and handle the result, which you have to do anyway in one way or another. For example, if you really only expected one line, you'd do something like this:

let mut line = String::new();
if std::io::stdin().read_line(&mut line).unwrap() > 0 { // read_line will return Ok(0) at EOF
    // use line
}

Of course, you'll probably want to handle read errors properly instead of just calling Result::unwrap (even if you ultimately still just bail out, but with a more user-friendly error message).

If your input is line-oriented, you can just use the iterator returned by BufRead::lines. Consuming such an iterator (through a for loop, Iterator::try_for_each or similar) will usually give you the behaviour you want.

Still, using the heuristics like the ones linked to by @BurntSushi before the first read does make sense if you want your program to be robust. These heuristics aim at ensuring stdin is "what you expect" and not something "weird" which may exhibit some unexpected behavior. If the check fails, you probably don't want to read from stdin at all but just pretend that it's already at EOF.

Though I have to admit I myself usually don't include such checks in my programs. Or at least I didn't do so in the past.

@nbari
Copy link
Author

nbari commented Aug 3, 2021

The problem I am trying to solve is to prevent reading from stdin when launching my app from a cron when indeed there is nothing in stdin.

my app is a simple s3 uploader that can read from stdin (stream) or just pass the file from the command line (multipart upload):

s3m /path/to/file aws/bucket/file

or pipe something stdin :

 mariabackup --stream=xbstream | s3m aws/bucket/file 

But I notice what when running the app from a cron atty returns that there is something to read from stdin and I end trying to read from stdin but since there is no input 0 I end ignoring the arguments (this because of the logic of the application, in where I give preference to stdin, maybe the easy way is just to add a flat to read from stdin)

Because of this (since I may have some input) if the read length > 0 I could start streaming it, probably there is a way to prevent reading all the time, and find if there is something in stdin (covering cases like when running within a cron) this is what I am trying to figure out, so if have any ideas, hints, please let me know, I would appreciate it

@BurntSushi
Copy link
Contributor

The problem is that your cron implementation is attaching a tty to stdin for some reason, based on what you told us. There is really nothing more to be done here because that is the problem. It's not a problem with this crate.

You're only choice from what I can tell is to either add a flag that forcefully prevents reading from stdin, or somehow modify your heuristic for detecting whether stdin might be readable or not. You haven't really given us much to go on here, other than the fact that your cron daemon is advertising a tty on stdin for subprocesses it executes. (I find that highly unusual. You might consider looking into that issue more deeply, and possibly asking the maintainers why it's happening. Just make sure to provide a minimal reproducible example using libc apis directly.) Otherwise, you could probe at the stdin file descriptor a bit more. For example, what is its file type?

@nbari
Copy link
Author

nbari commented Aug 4, 2021

I will search more about it, how could I test the file type?

@BurntSushi
Copy link
Contributor

https://doc.rust-lang.org/stable/std/fs/struct.FileType.html

Note also the FileTypeExt trait on Unix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants