Can you fool isatty AND log stdout and stderr separately?

拥有回忆 提交于 2019-12-02 20:09:36

Like this?

% ./challenge.py >stdout 2>stderr
% cat stdout 
This is a real tty :)
standard output data
% cat stderr 
standard error data

Because I cheated a little bit. ;-)

% echo $LD_PRELOAD
/home/karol/preload.so

Like so...

% gcc preload.c -shared -o preload.so -fPIC

I feel dirty now, but it was fun. :D

% cat preload.c
#include <stdlib.h>

int isatty(int fd) {
    if(fd == 2 || fd == 1) {
        return 1;
    }
    return 0;
}

char* ttyname(int fd) {
    static char* fake_name = "/dev/fake";
    if(fd == 2 || fd == 1) {
        return fake_name;
    }
    return NULL;
}

For a simpler use-case (e.g. development testing), use strace (linux) or dtruss (OSX). Of course that won't work in privileged process.

Here's a sample, you can distinguish stdout fd1 from stderr fd2:

$ strace -ewrite python2 test.py
[snip]
write(1, "This is a real tty :)\n", 22This is a real tty :)
) = 22
write(2, "standard error data", 19standard error data)     = 19
write(1, "standard output data", 20standard output data)    = 20
+++ exited with 0 +++

In the sample above you see each standard xxx data doubled, because you can't redirect stdout/stderr. You can, however ask strace to save its output to a file.

On a theoretical side, if stdout and stderr refer to the same terminal, you can only distinguish between the 2 while still in the context of your process, either in user mode (LD_PRELOAD), or kernel space (ptrace interface that strace tool uses). Once the data hits actual device, real of pseudo, the distinction is lost.

You can always allocate Pseudo-TTY, that's what screen does.

In Python you'd access it using pty.openpty()

This "master" code passes your test:

import subprocess, pty, os

m, s = pty.openpty()
fm = os.fdopen(m, "rw")
p = subprocess.Popen(["python2", "test.py"], stdin=s, stdout=s, stderr=s)
p.communicate()
os.close(s)
print fm.read()

Of course if you want to distinguish between stdin/out/err, your "slave" process will see different PYT names:

inp = pty.openpty()
oup = pty.openpty()
erp = pty.openpty()

subprocess.Popen([command, args], stdin=inp[1], stdout=uop[1], stderr=erp[1])
$ PYTHONPATH=/tmp/python:$PYTHONPATH ./challenge.py
$ cat stdout
This is a real tty :)
standard output data

$ cat stderr
standard error data

Because this script imports the os module, I've cheated by creating my own os module in /tmp/python and prepending /tmp/python to sys.path.

os.py

import sys

sys.path.remove('/tmp/python')
this_module = sys.modules['os']
del sys.modules['os']

import os
globals().update(vars(os))


class File(file):
    isatty = lambda self: True

sys.stdout = File('stdout', 'w')
sys.stderr = File('stderr', 'w')

isatty = lambda fd: True
ttyname = lambda fd: '/dev/fake'

sys.modules['os'] = this_module

When you can use the script command:

$ script --return -c "[executable string]" >stdout 2>stderr
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!