The answer was to write a Bash extension in C which takes advantage of two things:
a) Bash does not fork(2) when it executes an extension so it's in-process;
b) dup2(2)
so it's possible to do funky things with the file descriptors and get away with it.
Alas on Win32 Cygwin's bash does not load this extension (MinGW does) and dup2(2) is just borked. Blame M$ for designing a braindead C library and OS.
Here's the code:
-ulianov#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <asm/fcntl.h>
#include <errno.h>
#include "builtins.h"
#include "shell.h"
// Compile on Linux/x86:
// gcc -I/tmp/bash-3.2 -I/tmp/bash-3.2/include fdmangle.c -fpic -c
// ld -x -Bshareable -o fdmangle.so fdmangle.o
// Use in shell scripts:
// enable -f ./fdmangle.so fdmangle
// fdmangle 2 stderr
extern char **make_builtin_argv(); // Bash-ism
static int verbose = 0;
static int fdmangle_main(int argc, char **argv)
{
if(argc < 3) {
return 1;
}
int n = 1;
if(!strcmp(argv[1], "-v")) { verbose = 1; n++; }
if(verbose && argc < 4) {
return 1;
}
const int fdn = atoi(argv[n]);
const char* file = argv[n+1];
if(verbose) fprintf(stderr, "fdmangle %d -> %s\n", fdn, file);
int flags = O_CREAT | O_WRONLY | O_APPEND;
#ifdef __unix__
flags |= O_NOFOLLOW;
#endif
int fd = open(file, flags, 0640);
if(fd < 0) {
fprintf(stderr, "Cannot open for writing %s: %d (%s)\n", file, errno, sys_errlist[errno]);
}
dup2(fd, fdn);
return 0;
}
static int fdmangle_builtin(WORD_LIST *list)
{
char **v=NULL;
int c=0, r=0;
v = make_builtin_argv(list, &c);
r = fdmangle_main(c, v);
free(v);
return r;
}
static char* fdmangle_doc[] = {
"File descriptor mangling",
(char *)0
};
struct builtin fdmangle_struct = {
"fdmangle",
fdmangle_builtin,
BUILTIN_ENABLED,
fdmangle_doc,
"fdmangle [-v] fd file",
0
};