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 行括起来之外,还有其他(优雅的)替代方案吗?
我希望的是,某种方式将“钩子”程序注册到文件打开系统调用中,这样文件打开将导致钩子程序的启动和文件读取钩程序输出。显然这里的假设是:读取将从文件开始到文件结束顺序发生,并且永远不会随机查找。
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()到上面也很简单)。
您可能需要比上述更多的错误处理和健全性检查(特别是对于长时间运行的程序,以避免泄漏)。此代码不能正确处理并发打开。
由于管道和文件存在明显差异,因此存在程序出现故障的风险。
否则,假设您有daemon和socat,您可以假装没有无限循环:
daemon -r -- socat -u EXEC:/usr/bin/uptime PIPE:/tmp/uptime
Run Code Online (Sandbox Code Playgroud)
这具有提供程序开始写入然后阻塞的轻微缺点(在这里应该很明显),因此您会看到旧的正常运行时间,而不是按需运行。您的提供者需要使用非阻塞 I/O 才能正确提供即时数据。(unix 域套接字将允许更传统的客户端/服务器方法,但这与您可以直接插入的 FIFO/命名管道不同。)
更新另见后面的问题,它涵盖了相同的主题,虽然泛化到任意进程/读取器而不是特定的: 如何制作一个在读取时执行代码的特殊文件 (请注意,仅 fifo 的答案不会可靠地概括并发读取)