Search code examples
bashpipebufferpetalinux

How to unbuffer stdout of legacy running binary without stdbuf and similar tools


I want to monitor the realtime output of a program that I will start. I am trying to do this by redirecting the output of the program to a pipe and then reading the pipe from a monitoring script.

./program >> apipe

then from the monitoring script

cat apipe

However due to the buffer in >> there is no output. Anyway I can disable this buffer? I am running on a barebones embedded system (petalinux) so I don't have access to unbuffer, script, or stdbuf to help me out.

I have tried the scripts on another platform where unbuffer is available it works as I expect.

Any way I can configure this buffer, or use another binary to redirect?

Edit: I do not have access to the source code of the command I am trying to run. It is a legacy binary.


Solution

  • If you don't have access to stdbuf, you might as well simulate it and unbuffer the stdout manually with gdb (assuming obviously you have access to gdb).

    Let's take a look at how stdbuf actually operates. The stdbuf GNU coreutils command basically only injects libstdbuf in the user program by setting LD_PRELOAD environment variable. (Irrelevant, but for the record, options are passed via _STDBUF_E/_STDBUF_I/_STDBUF_O env vars.)

    Then, when the libstdbuf is run, it calls setvbuf libc function (which in turn executes the underlaying syscall) on appropriate file descriptors (stdin/stdout/stderr), with the appropriate mode (fully buffered, line buffered, or unbuffered).

    Declaration for setvbuf is in stdio.h, available with man 3 setvbuf:

    #include <stdio.h>
    
    int setvbuf(FILE *stream, char *buf, int mode, size_t size);
    

    Values for mode are: _IONBF, _IOLBF, _IOFBF, as defined in stdio.h. We are here only interested in the unbuffered mode: _IONBF. It has a value of 2 (you can check your /usr/include/stdio.h).

    Unbuffer script

    So, to unbuffer a stdout for some process, we just need to call:

    setvbuf(stdout, NULL, _IONBF, 0)
    

    We can easily do that with gdb. Let's make a script we can call, unbuffer-stdout.sh:

    #!/bin/bash
    # usage: unbuffer-stdout.sh PID
    
    gdb --pid "$1" -ex "call setvbuf(stdout, 0, 2, 0)" --batch
    

    Then, we can call it like:

    $ ./unbuffer-stdout.sh "$(pgrep -f my-program-name)"
    

    (You'll probably need sudo to run it as root.)

    Testing

    We can use this simple Python program with buffered standard output (if not called with -u, and with unset PYTHONUNBUFFERED), writer.py:

    #!/usr/bin/python
    import sys, time
    
    while True:
        sys.stdout.write("output")
        time.sleep(0.5)
    

    Run it with:

    $ ./writer.py >/tmp/output &
    $ tailf /tmp/output
    

    and observe no output appears until we run:

    $ sudo ./unbuffer-stdout.sh "$(pgrep -f writer.py)"