5

I am writing pager pspg. There I have to solve following issue. After reading from stdin I should to reassign stdin from previous reading from pipe to reading from terminal.

I used

freopen("/dev/tty", "r", stdin) 

But it doesn't work, when pager was used from command what was not executed directly

su - someuser -c 'export PAGER=pspg psql somedb'

In this case, I got a error: No such device or address.

I found a workaround - now, the code looks like:

if (freopen("/dev/tty", "r", stdin) == NULL)
{
    /*
     * try to reopen pty.
     * Workaround from:
     * https://cboard.cprogramming.com/c-programming/172533-how-read-pipe-while-keeping-interactive-keyboard-c.html
     */
    if (freopen(ttyname(fileno(stdout)), "r", stdin) == NULL)
    {
        fprintf(stderr, "cannot to reopen stdin: %s\n", strerror(errno));
        exit(1);
    }
}

What is a correct way to detect assigned terminal device in this case?

But this workaround is not correct. It fixed one issue, but next is comming. When someuser is different than current user, then reopen fails with error Permission denied. So this workaround cannot be used for my purposes.

Pavel Stehule
  • 42,331
  • 5
  • 91
  • 94
  • I don't understand your problem `su - user -c 'cat'` read my terminal input and output in my terminal. "previous reading from pipe to reading from terminal.", that don't make sense to me. I telling you that because I suspect a XY problem, what you are trying to do is very strange. – Stargateur Nov 26 '17 at 18:25
  • the pager reads data from stdin, but needs to do switch to /dev/tty to be interactive. The switch is a problem. Normal application doesn't do it, and using stdin, stdout prepared by parent environment. The pager is different - reads data from stdin, but needs to read keyboard too. – Pavel Stehule Nov 26 '17 at 19:43

2 Answers2

2

What less does in this situation is fall back to fd 2 (stderr). If stderr has been redirected away from the tty, it gives up on trying to get keyboard input, and just prints the whole input stream without paging.

The design of su doesn't allow for anything better. The new user is running a command on a tty owned by the original user, and that unpleasant fact can't be entirely hidden.

Here's a nice substitute for su that doesn't have this problem:

ssh -t localhost -l username sh -c 'command'

It has a little more overhead, of course.

2

On the end I used pattern that I found in less pager, but modified for using with ncurses:

First I try to reopen stdin to some tty related device:

if (!isatty(fileno(stdin)))
{
    if (freopen("/dev/tty", "r", stdin) != NULL)
        noatty = false;
    /* when tty is not accessible, try to get tty from stdout */ 
    else if (freopen(ttyname(fileno(stdout)), "r", stdin) != NULL)
        noatty = false;
    else
    {
        /*
         * just ensure stderr is joined to tty, usually when reopen
         * of fileno(stdout) fails - probably due permissions.
         */
        if (!isatty(fileno(stderr)))
        {
            fprintf(stderr, "missing a access to terminal device\n");
            exit(1);
        }
        noatty = true;
        fclose(stdin);
    }
}                   
else
    noatty = false;

When I have not tty and cannot to use stdin, then I am using newterm functions, that allows to specify input stream:

if (noatty)
    /* use stderr like stdin. This is fallback solution used by less */
    newterm(termname(), stdout, stderr);
else
    /* stdin is joined with tty, then use usual initialization */
    initscr();
Pavel Stehule
  • 42,331
  • 5
  • 91
  • 94