动态文件内容生成:通过“流程执行”满足“文件打开”

Har*_*rry 7 linux process files

我有一个只读文件,F.

程序,P的,我不是作者,需要读取F

我希望 的内容F来自另一个“生成器”程序,G每当P尝试读取F(假设F为普通文件)而不是更早时。

我尝试执行以下操作:

$ mkfifo /well-known/path/to/F    # line #1
$ G > /well-known/path/to/F       # line #2
Run Code Online (Sandbox Code Playgroud)

现在,当P启动并尝试读取时F,它似乎能够G像我希望的那样读取由生成的输出。但是,它只能执行一次,因为 G 毕竟只能运行一次!所以,如果P在执行过程中需要F再次读取,它最终会阻塞在先进先出!

我的问题是,除了在某种无限循环中将上面的第 2 行括起来之外,还有其他(优雅的)替代方案吗?

我希望的是,某种方式将“钩子”程序注册到文件打开系统调用中,这样文件打开将导致钩子程序的启动和文件读取钩程序输出。显然这里的假设是:读取将从文件开始到文件结束顺序发生,并且永远不会随机查找。

mr.*_*tic 6

FUSE + 软链接(或绑定安装)是一种解决方案,虽然我不会认为它“优雅”,但有很多包袱。在 *BSD 上,您可以使用更简单的选项portalfs,您可以使用符号链接解决问题——多年前有一个到 Linux 的端口,但它似乎已被删除,大概是为了支持 FUSE。

您可以很容易地注入一个库来覆盖它所做的所需的open()/ open64()libc 调用。例如:

#define _GNU_SOURCE
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <dlfcn.h>
#include <stdarg.h>

//  gcc -Wall -rdynamic -fPIC -nostartfiles -shared -ldl -Wl,-soname,open64 \
//       -o open64.so open64.c

#define DEBUG 1
#define dfprintf(fmt, ...) \
    do { if (DEBUG) fprintf(stderr, "[%14s#%04d:%8s()] " fmt, \
          __FILE__, __LINE__, __func__, __VA_ARGS__); } while (0)

typedef int open64_f(const char *pathname, int flags, ...);
typedef int close_f(int fd);
static  open64_f   *real_open64;
static  close_f    *real_close;

static FILE *mypipe=NULL;
static int mypipefd=-1;

//void __attribute__((constructor)) my_init()
void _init()
{
    char **pprog=dlsym(RTLD_NEXT, "program_invocation_name");
    dfprintf("It's alive! argv[0]=%s\n",*pprog);

    real_open64 = dlsym(RTLD_NEXT, "open64");
    dfprintf("Hook %p open64()\n",(void *)real_open64);
    if (!real_open64) printf("error: %s\n",dlerror());

    real_close = dlsym(RTLD_NEXT, "close");
    dfprintf("Hook %p close()\n",(void *)real_close);
    if (!real_close) printf("error: %s\n",dlerror());
}

int open64(const char *pathname, int flags, ...)
{
    mode_t tmpmode=0;
    va_list ap;
    va_start(ap, flags);
    if (flags & O_CREAT) tmpmode=va_arg(ap,mode_t);
    va_end(ap);

    dfprintf("open64(%s,%i,%o)\n",pathname,flags,tmpmode);

    if (!strcmp(pathname,"/etc/passwd")) {
        mypipe=popen("/usr/bin/uptime","r");
        mypipefd=fileno(mypipe);
        dfprintf("  popen()=%p fd=%i\n",mypipe,mypipefd);
        return mypipefd;
    } else {
        return real_open64(pathname,flags,tmpmode);
    }
}

int close(int fd)
{
    int rc;
    dfprintf("close(%i)\n",fd);
    if (fd==mypipefd) {
        rc=pclose(mypipe); // pclose() returns wait4() status
        mypipe=NULL; mypipefd=-1;
        return (rc==-1) ? -1 : 0;
    } else  {
        return real_close(fd);
    }
}
Run Code Online (Sandbox Code Playgroud)

编译并运行:

$ gcc -Wall -rdynamic -fPIC -nostartfiles -shared -ldl -Wl,-soname,open64   \
    -o open64.so open64.c 
$ LD_PRELOAD=`pwd`/open64.so cat /etc/passwd
19:55:36 up 1110 days,  9:19, 55 users,  load average: 0.53, 0.33, 0.29
Run Code Online (Sandbox Code Playgroud)

根据应用程序的具体工作方式(libc 调用),您可能需要处理open()fopen()/fclose()替代。以上适用于cator head,但不是sort因为它调用了fopen()(将fopen()/添加fclose()到上面也很简单)。

您可能需要比上述更多的错误处理和健全性检查(特别是对于长时间运行的程序,以避免泄漏)。此代码不能正确处理并发打开

由于管道和文件存在明显差异,因此存在程序出现故障的风险。

否则,假设您有daemonsocat,您可以假装没有无限循环:

daemon -r -- socat -u EXEC:/usr/bin/uptime PIPE:/tmp/uptime    
Run Code Online (Sandbox Code Playgroud)

这具有提供程序开始写入然后阻塞的轻微缺点(在这里应该很明显),因此您会看到旧的正常运行时间,而不是按需运行。您的提供者需要使用非阻塞 I/O 才能正确提供即时数据。(unix 域套接字将允许更传统的客户端/服务器方法,但这与您可以直接插入的 FIFO/命名管道不同。)


更新另见后面的问题,它涵盖了相同的主题,虽然泛化到任意进程/读取器而不是特定的: 如何制作一个在读取时执行代码的特殊文件 (请注意,仅 fifo 的答案不会可靠地概括并发读取)