Linux

動態文件內容生成:通過“程序執行”滿足“文件打開”

  • March 25, 2015

我有一個只讀文件,F.

一個程序,P我不是作者,需要閱讀F

我希望的內容F來自另一個“生成器”程序,G無論何時P嘗試讀取FF作為普通文件)而不是更早。

我嘗試執行以下操作:

$ mkfifo /well-known/path/to/F    # line #1
$ G > /well-known/path/to/F       # line #2

現在,當P啟動並嘗試讀取F時,它似乎能夠讀取生成的輸出,G就像我希望的那樣。但是,它只能執行一次,因為 G 畢竟只能執行一次!因此,如果P需要在F稍後的執行過程中再次讀取,它最終會阻塞在 fifo 上!

**我的問題是,**除了在某種無限循環中將上面的第 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);
   }
}

編譯並執行:

$ 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

根據應用程序的確切工作方式(libc 呼叫),您可能需要處理open()fopen()/fclose()代替。以上適用於cator head,但不是sort因為它呼叫fopen()了(將fopen()/添加fclose()到上面也很簡單)。

您可能需要比上述更多的錯誤處理和完整性檢查(特別是對於長時間執行的程序,以避免洩漏)。此程式碼無法正確處理並發打開

由於管道和文件有明顯的區別,因此存在程序發生故障的風險。

否則,假設你有daemonsocat你可以假裝你沒有無限循環:

daemon -r -- socat -u EXEC:/usr/bin/uptime PIPE:/tmp/uptime    

這有一個小缺點(這在此處應該很明顯),即提供程序開始編寫然後阻塞,因此您會看到舊的正常執行時間,而不是按需執行。您的提供商需要使用非阻塞 I/O 才能正確提供即時數據。(unix 域套接字將允許更傳統的客戶端/伺服器方法,但這與您可以直接放入的 FIFO/命名管道不同。)


更新另請參閱後面的問題,它涵蓋了相同的主題,雖然概括為任意程序/閱讀器而不是特定的: 如何製作一個特殊的文件,在讀取時執行程式碼 (請注意,僅先進先出的答案不會可靠地概括並發讀取)

引用自:https://unix.stackexchange.com/questions/76462