如何在linux上创建自定义文件描述符?

时间:2022-01-24 13:16:48

I would like to create a file whose descriptor would have some customizable behavior. In particular, I'd like to create a file descriptor, which, when written to, would prefix every line, with name of the process and pid (and maybe time), but I can imagine it can be useful to do other things.

我想创建一个文件,它的描述符将具有一些可定制的行为。特别地,我想创建一个文件描述符,当写入时,它将在每一行前面加上进程名和pid(可能还有时间),但是我可以想象,它可以用于其他事情。

I don't want to alter the writing program - for one thing, I want it to work for all programs on my system, even shell/perl/etc. scripts, and it would be impractical if not impossible to change the source code of everything.

我不想改变编写程序——首先,我想让它适用于我系统中的所有程序,甚至shell/perl/等等。脚本,如果不可能改变所有的源代码,那将是不切实际的。

Note that pipes wouldn't do in this case, because when the writing process fork()s, the newly created child shares the fd and is indistinguishable from its parent by the reading end of the pipe.

注意,在这种情况下管道不会这样做,因为当写入过程fork()s时,新创建的子节点共享fd,并且在管道的读取端与父节点之间无法区分。

There are approaches which would do, but I think they are rather clumsy:

有一些方法可以做到这一点,但我认为它们相当笨拙:

  1. Create a kernel module that will create such fds. For example, you could open some /dev/customfd and then instruct the module to do some transformation etc. or send data to userspace or socket etc.
  2. 创建一个将创建此类fds的内核模块。例如,您可以打开一些/dev/customfd,然后指示模块进行一些转换等等,或者将数据发送到userspace或socket等。
  3. Use LD_PRELOAD that will override the fd manipulation functions and do these kinds of things on the "special" fd.
  4. 使用LD_PRELOAD来覆盖fd操作函数,并在“特殊”fd上执行这些操作。

However, both of these approaches are quite laborious, so I would like to know if there is a better way, or any infrastructure (like off-the-shelf libraries) that would help.

然而,这两种方法都非常麻烦,所以我想知道是否有更好的方法,或者有什么基础设施(比如现成的库)会有帮助。

I'd prefer a solution which doesn't involve kernel changes, but I'm ready to accept them if necessary.

我更喜欢不涉及内核更改的解决方案,但如果需要,我准备接受它们。

Just an idea: would FUSE be an answer?

一个想法:导火索会是一个答案吗?

1 个解决方案

#1


4  

You have a lot of options , as you mentioned using the LD_PRELOAD wrapping the write()/read() functions is a good approach.

您有很多选项,正如您所提到的,使用LD_PRELOAD包装write()/read()函数是一种很好的方法。

I recommend you to use unix ptrace(2) to caught the desired system call and pass the arguments to your own function.

我建议您使用unix ptrace(2)捕获所需的系统调用,并将参数传递给您自己的函数。

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;
}

#1


4  

You have a lot of options , as you mentioned using the LD_PRELOAD wrapping the write()/read() functions is a good approach.

您有很多选项,正如您所提到的,使用LD_PRELOAD包装write()/read()函数是一种很好的方法。

I recommend you to use unix ptrace(2) to caught the desired system call and pass the arguments to your own function.

我建议您使用unix ptrace(2)捕获所需的系统调用,并将参数传递给您自己的函数。

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;
}