I'm currently trying to implement a c shim which sits between the open function from the c standard library and a program.
the shim should transparently write all file paths being opened to a log within a directory defined by the environment variable IO_SHIM_PREFIX
I have this working relatively well, but in order to achieve it I had to fake the fcntl.h header guards, and I think that there must be a better way.
If I include fcntl.h directly I get the following error
gcc -shared -fPIC -o shim.so src/shim.c -ldl
src/shim.c:47:5: error: conflicting types for ‘open’
47 | int open(const char *pathname, int flags){
| ^~~~
In file included from src/shim.c:16:
/usr/include/fcntl.h:168:12: note: previous declaration of ‘open’ was here
168 | extern int open (const char *__file, int __oflag, ...) __nonnull ((1));
| ^~~~
make: *** [Makefile:3: all] Error 1
I'm guessing it has something to do with the __nonnull ((1))
part at the end but I'm not much of a c programmer and I dont understand it.
Makefile
CC=gcc
all: src/shim.c
$(CC) -shared -fPIC -o shim.so src/shim.c -ldl
src/shim.c
// required for RTLD_NEXT
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <unistd.h>
#include <math.h>
#include <string.h>
// this bypasses the header guard in bits/fcntl.h
// its a terrible idea, but I don't know the right way to do this.
#define _FCNTL_H
#include <bits/fcntl.h>
#undef _FCNTL_H
typedef int (*open_fn_ptr)(const char*, int);
void write_to_log(const char* prefix, const char* pathname, open_fn_ptr original_open){
pid_t pid = getpid();
pid_t tid = gettid();
char* pattern;
if (prefix[strlen(prefix) -1] == '/'){
pattern = "%s%d_%d.log";
}
else{
pattern = "%s/%d_%d.log";
}
int path_len = snprintf(NULL, 0, pattern, prefix, pid, tid);
char* log_filepath = (char*) malloc(sizeof(char) * path_len);
sprintf(log_filepath, pattern, prefix, pid, tid);
int fd = original_open(log_filepath, O_WRONLY | O_APPEND | O_CREAT);
write(fd, pathname, strlen(pathname));
write(fd, "\n", sizeof(char));
close(fd);
free(log_filepath);
}
int open(const char *pathname, int flags){
// acquire a pointer to the original implementation of open.
open_fn_ptr original_open = dlsym(RTLD_NEXT, "open");
const char* prefix = getenv("IO_SHIM_PREFIX");
if (prefix != NULL) {
write_to_log(prefix, pathname, original_open);
}
return original_open(pathname, flags);
}
Usage example
IO_SHIM_PREFIX=`realpath .` LD_PRELOAD=./shim.so brave-browser
open
also take a variable argument list. Theoretically, you could just ignore it any extra args, but actually you should check ifflags
containO_CREAT
orO_TMPFILE
and if so, read amode
argument from va_args and pass it on too. \$\endgroup\$, ...);
takes a variable length argument list. This question probably should have been asked on stack overflow. \$\endgroup\$strace --trace=open
serves your needs? \$\endgroup\$