I would write a minimal dynamic library that interposes open()
, handling open("/dev/console", ...)
separately. Then, run the other binary with LD_PRELOAD
environment variable set to point to that library.
At minimum, something like the following libnoconsole.c should work:
#define _POSIX_C_SOURCE 200809L
#define _GNU_SOURCE
#include <unistd.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dlfcn.h>
#include <errno.h>
#ifndef OLD_PATH
#define OLD_PATH "/dev/console"
#endif
#ifndef NEW_PATH
#define NEW_PATH "/tmp/log"
#endif
static int (*original_open)(const char *, int, ...) = NULL;
static int (*original_openat)(int, const char *, int, ...) = NULL;
static int match(const char *a, const char *b)
{
while (*a == *b)
if (!*a) {
return 1;
} else {
a++;
b++;
}
return 0;
}
int open(const char *pathname, int flags, ...)
{
if (!original_open)
original_open = dlsym(RTLD_NEXT, "open");
if (!original_open) {
errno = ENOSYS;
return -1;
}
if (match(pathname, OLD_PATH))
return original_open(NEW_PATH, O_RDWR | O_CREAT, 0600);
if (flags & (O_CREAT | O_TMPFILE)) {
va_list args;
mode_t mode;
va_start(args, flags);
mode = va_arg(args, mode_t);
va_end(args);
return original_open(pathname, flags, mode);
} else
return original_open(pathname, flags);
}
int openat(int dirfd, const char *pathname, int flags, ...)
{
if (!original_openat)
original_openat = dlsym(RTLD_NEXT, "openat");
if (!original_openat) {
errno = ENOSYS;
return -1;
}
if (match(pathname, OLD_PATH))
return original_openat(AT_FDCWD, NEW_PATH, O_RDWR | O_CREAT, 0600);
if (flags & (O_CREAT | O_TMPFILE)) {
va_list args;
mode_t mode;
va_start(args, flags);
mode = va_arg(args, mode_t);
va_end(args);
return original_openat(dirfd, pathname, flags, mode);
} else
return original_openat(dirfd, pathname, flags);
}
Compile it using
gcc -Wall -O2 -c libnoconsole.c
gcc -Wall -O2 -shared -Wl,-soname,libnoconsole.so libnoconsole.o -ldl -o libnoconsole.so
and run your program via
env LD_PRELOAD=$PWD/libnoconsole.so the-other-program
and it should save its output to /tmp/log instead of /dev/console.
The reason for above using the silly match()
instead of !strcmp()
is that strcmp()
wasn't always an async-signal safe function, whereas open()
and openat()
are: it's a small wart to avoid potential issues.