You had said [in your top comments] that you had GNU, so fopencookie
for the hooks [I've used it before with success].
Attaching to stdout
may be tricky, but doable.
Note that we have: FILE *stdout;
(i.e. it's [just] a pointer). So, simply setting it to the [newly] opened stream should work.
So, I think you can do, either (1):
FILE *saved_stdout = stdout;
Or (2):
fclose(stdout);
Then, (3):
FILE *fc = fopencookie(...);
setlinebuf(fc); // and whatever else ...
stdout = fc;
You can [probably] adjust the order to suit (e.g. doing fclose
first, etc.)
I had looked for something analogous to freopen
or fdopen
to fit your situation, but I didn't find anything, so doing stdout = ...;
may be the option.
This works fine if you do not have any code that tries to write to fd 1 directly (e.g. write(1,"hello\n",6);
).
Even in that case, there is probably a way.
UPDATE:
Do you know if FILE*stdout is a const? If so, I might need to do something crazy like FILE **p = &stdout and then *p = fopencookie(...)
You were right to be concerned, but not for quite the reason you think. Read on ...
stdout
is writable but ...
Before I posted, I checked stdio.h
, and it has:
extern FILE *stdout; /* Standard output stream. */
If you think about it, stdout
must be writable.
Otherwise, we could never do:
fprintf(stdout,"hello world\n");
fflush(stdout);
Also, if we did a fork
, then [in the child] if we wanted to set up stdout
to go to a logfile, we'd need to be able to do:
freopen("child_logfile","w",stdout);
So, no worries ...
Trust but verify ...
Did I say "no worries"? I may have been premature ;-)
There is an issue.
Here is a sample test program:
#define _GNU_SOURCE
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#if 1 || DEBUG
#define dbgprt(_fmt...) \
do { \
fprintf(stderr,_fmt); \
fflush(stderr); \
} while (0)
#else
#define dbgprt(_fmt...) \
do { } while (0)
#endif
typedef struct {
int ioport;
} uartio_t;
char *arg = "argument";
ssize_t
my_write(void *cookie,const char *buf,size_t len)
{
uartio_t *uart = cookie;
ssize_t err;
dbgprt("my_write: ENTER ioport=%d buf=%p len=%zu\n",
uart->ioport,buf,len);
err = write(uart->ioport,buf,len);
dbgprt("my_write: EXIT err=%zd\n",err);
return err;
}
int
my_close(void *cookie)
{
uartio_t *uart = cookie;
dbgprt("my_close: ioport=%d\n",uart->ioport);
int err = close(uart->ioport);
uart->ioport = -1;
return err;
}
int
main(void)
{
cookie_io_functions_t cookie = {
.write = my_write,
.close = my_close
};
uartio_t uart;
printf("hello\n");
fflush(stdout);
uart.ioport = open("uart",O_WRONLY | O_TRUNC | O_CREAT,0644);
FILE *fc = fopencookie(&uart,"w",cookie);
FILE *saved_stdout = stdout;
stdout = fc;
printf("uart simple printf\n");
fprintf(stdout,"uart fprintf\n");
printf("uart printf with %s\n",arg);
fclose(fc);
stdout = saved_stdout;
printf("world\n");
return 0;
}
Program output:
After compiling, running with:
./uart >out 2>err
This should produce an expected result. But, we get (from head -100 out err uart
):
==> out <==
hello
uart simple printf
world
==> err <==
my_write: ENTER ioport=3 buf=0xa90390 len=39
my_write: EXIT err=39
my_close: ioport=3
==> uart <==
uart fprintf
uart printf with argument
Whoa! What happened? The out
file should just be:
hello
world
And, the uart
file should have three lines instead of two:
uart printf
uart simple printf
uart printf with argument
But, the uart simple printf
line went to out
instead of [the intended] uart
file.
Again, whoa!, what happened?!?!
Explanation:
The program was compiled with gcc
. Recompiling with clang
produces the desired results!
It turns out that gcc
was trying to be too helpful. When compiling, it converted:
printf("uart simple printf\n");
Into:
puts("uart simple printf");
We see that if we disassemble the executable [or compile with -S
and look at the .s
file].
The puts
function [apparently] bypasses stdout
and uses glibc's internal version: _IO_stdout
.
It appears that glibc's puts
is a weak alias to _IO_puts
and that uses _IO_stdout
.
The _IO_*
symbols are not directly accessible. They're what glibc calls "hidden" symbols--available only to glibc.so
itself.
The real fix:
I discovered this after considerable hacking around. Those attempts/fixes are in an appendix below.
It turns out that glibc
defines (e.g.) stdout
as:
FILE *stdout = (FILE *) &_IO_2_1_stdout_;
Internally, glibc
uses that internal name. So, if we change what stdout
points to, it breaks that association.
In actual fact, only _IO_stdout
is hidden. The versioned symbol is global but we have to know the name either from readelf
output or by using some __GLIBC_*
macros (i.e. a bit messy).
So, we need to modify the save/restore code to not change the value in stdout
but memcpy
to/from what stdout
points to.
So, in a way, you were correct. It is [effectively] const
[readonly].
So, for the above sample/test program, when we want to set a new stdout
, we want:
FILE *fc = fopencookie(...);
FILE saved_stdout = *stdout;
*stdout = *fc;
When we want to restore the original:
*fc = *stdout;
fclose(fc);
*stdout = saved_stdout;
So, it really wasn't gcc
that was the issue. The original save/restore we developed was incorrect. But, it was latent. Only when gcc
called puts
did the bug manifest itself.
Personal note: Aha! Now that I got this code working, it seems oddly familiar. I'm having a deja vu experience. I'm pretty sure that I've had to do the same in the past. But, it was so long ago, that I had completely forgotten about it.
Workarounds / fixes that semi-worked but are more complex:
Note: As mentioned, these workarounds are only to show what I tried before finding the simple fix above.
One workaround is to disable gcc
's conversion from printf
to puts
.
The simplest way may be to [as mentioned] compile with clang
. But, some web pages say that clang
does the same thing as gcc
. It does not do the puts
optimization on my version of clang
[for x86_64
]: 7.0.1 -- YMMV
For gcc
...
A simple way is to compile with -fno-builtins
. This fixes the printf->puts
issue but disables [desirable] optimizations for memcpy
, etc. It's also undocumented [AFAICT]
Another way is to force our own version of puts
that calls fputs/fputc
. We'd put that in (e.g.) puts.c
and build and link against it:
#include <stdio.h>
int
puts(const char *str)
{
fputs(str,stdout);
fputc('\n',stdout);
}
When we just did: stdout = fc;
we were deceiving glibc
a bit [actually, glibc was deceiving us a bit] and that has now come back to haunt us.
The "clean" way would be to do freopen
. But, AFAICT, there is no analogous function that works on a cookie stream. There may be one, but I haven't found it.
So, one of the "dirty" methods may be the only way. I think using the "custom" puts
function method above would be the best bet.
Edit: It was after I reread the above "deceiving" sentence that I hit on the simple solution (i.e. It made me dig deeper into glibc
source).