C – How to create a custom file descriptor on Linux

How to create a custom file descriptor on Linux… here is a solution to the problem.

How to create a custom file descriptor on Linux

I would like to create a file whose descriptor will have some customizable behavior. In particular, I would like to create a file descriptor that, when written, precedes each line with the process name and pid (and possibly time), but I can imagine it could be used for other things.

I

don’t want to change writing programs – on the one hand, I want it to work for all programs on my system, even shell/perl/ etc. Scripts, if not impossible to change the source code of everything, would be impractical.

Note that the pipe will not work in this case because the newly created child process shares fd when writing process fork() and its parent process pipe cannot be distinguished on the read side.

There are some methods that do, but I think they’re rather clumsy :

  1. Create a kernel module to create such an FDS. For example, you can open some /dev/customfd and then instruct the module to do some transformations, etc., or send data to user space or sockets, etc.
  2. Use LD_PRELOAD to override the FD manipulation functions and perform these operations on the “special” FD.

However, both methods are very laborious, so I was wondering if there is a better way or any infrastructure like ready-made libraries that would help.

I

prefer solutions that don’t involve kernel changes, but I’m ready to embrace them if necessary.

Just an idea: would FUSE be an answer?

Solution

You have a lot of options, and as you mentioned wrapping the write()/read() function with LD_PRELOAD is a great way to do it.

I recommend that you use unix ptrace(2) to capture the required system calls and pass parameters to your own function.

Example:

#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <linux/user.h>
#include <sys/syscall.h>   /* For SYS_write etc */
int main()
{   pid_t child;
    long orig_eax, eax;
    long params[3];
    int status;
    int insyscall = 0;
    child = fork();
    if(child == 0) {
        ptrace(PTRACE_TRACEME, 0, NULL, NULL);
        execl("/bin/ls", "ls", NULL);
    }
    else {
       while(1) {
          wait(&status);
          if(WIFEXITED(status))
              break;
          orig_eax = ptrace(PTRACE_PEEKUSER,
                     child, 4 * ORIG_EAX, NULL);
          if(orig_eax == SYS_write) {
             if(insyscall == 0) {
                /* Syscall entry */
                insyscall = 1;
                params[0] = ptrace(PTRACE_PEEKUSER,
                                   child, 4 * EBX,
                                   NULL);
                params[1] = ptrace(PTRACE_PEEKUSER,
                                   child, 4 * ECX,
                                   NULL);
                params[2] = ptrace(PTRACE_PEEKUSER,
                                   child, 4 * EDX,
                                   NULL);
                printf("Write called with "
                       "%ld, %ld, %ld\n",
                       params[0], params[1],
                       params[2]);
                }
          else { /* Syscall exit */
                eax = ptrace(PTRACE_PEEKUSER,
                             child, 4 * EAX, NULL);
                    printf("Write returned "
                           "with %ld\n", eax);
                    insyscall = 0;
                }
            }
            ptrace(PTRACE_SYSCALL,
                   child, NULL, NULL);
        }
    }
    return 0;
}

Related Problems and Solutions