Geração de conteúdo de arquivo dinâmico: Satisfação de um 'arquivo aberto' por uma 'execução de processo' [duplicado]

8

Eu tenho um arquivo somente leitura, F .

Um programa, P , do qual não sou autor, precisa ler F .

Eu quero que o conteúdo de F venha de outro programa 'gerador', G , sempre que P tentar ler F (tomando F como um arquivo comum) e não antes.

Eu tentei fazer o seguinte:

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

Agora, quando P é inicializado e tenta ler F , parece que é possível ler a saída gerada por G exatamente como eu desejava. No entanto, pode fazê-lo apenas uma vez, uma vez que o G, afinal, só funciona uma vez! Então, se P tivesse a necessidade de ler F mais tarde em sua execução, acabaria bloqueando o fifo!

Minha pergunta é, diferente da linha de bracketing # 2 acima em algum tipo de loop infinito, existe alguma outra alternativa (elegante) para o acima?

O que eu estou esperando é, alguma forma de registrar um programa 'hook' na chamada de sistema de arquivo aberto, de modo que o arquivo aberto resultaria no lançamento do programa de gancho e na leitura de arquivo no arquivo. leitura da saída do programa de gancho. Obviamente, a suposição aqui é: a leitura acontecerá sequencialmente do arquivo começando ao final do arquivo, e nunca em buscas aleatórias.

    
por Harry 20.05.2013 / 16:16

3 respostas

5

O FUSE + um soft-link (ou um monte de bind) é uma solução, embora eu não o considere "elegante", há muita bagagem. No * BSD você teria a opção mais simples de portalfs , com o qual você poderia resolver o problema com um link simbólico - havia uma porta para o Linux há muitos anos, mas parece ter sido descartada, presumivelmente em favor do FUSE.

Você pode facilmente injetar uma biblioteca para substituir as chamadas de open() / open64() libc necessárias. por exemplo:

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

Compile e execute:

$ 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

Dependendo de como o aplicativo funciona exatamente (chamadas libc), talvez seja necessário manipular open() ou fopen()/fclose() . O acima funciona para cat ou head , mas não sort , pois chama fopen() (é fácil adicionar fopen() / fclose() ao anterior também).

Você provavelmente precisará de mais tratamento de erros e verificação de integridade do que o acima (especialmente com um programa de longa duração, para evitar vazamentos). Este código não manipula corretamente as janelas simultâneas .

Como um pipe e um arquivo têm diferenças óbvias, existe o risco de um mau funcionamento do programa.

Caso contrário, supondo que você tenha o daemon e socat você pode fingir que não tem um loop infinito:

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

Isto tem a pequena desvantagem (que deve ser evidente aqui) do programa provedor começar a escrever e depois bloquear, assim você verá um tempo de atividade antigo, em vez de ser executado sob demanda. Seu provedor precisaria usar E / S sem bloqueio para fornecer dados just-in-time adequadamente. (Um soquete de domínio unix permitiria uma abordagem cliente / servidor mais convencional, mas isso não é o mesmo que um FIFO / named pipe que você pode simplesmente incluir).

Atualize veja também esta última pergunta que cobre o mesmo tópico, embora generalizada para processos / leitores arbitrários ao invés de um específico: Como posso fazer um arquivo especial que executa código quando lido de (note que as respostas somente fifo não serão generalizadas de forma confiável para leituras simultâneas)

    
por 21.05.2013 / 18:51
4

O que você poderia fazer é ter um daemon (vamos chamá-lo de filld , pois ele preenche F cada vez que P precisar).

Quando você o inicia, ele tenta abrir o FIFO (e bloqueia como não há leitor). Cada vez que um leitor chega, ele grava no FIFO o que ele precisa gravar ( fork -ing e exec -ing G por exemplo), fecha o FIFO e reabre-o. Cada vez, consegue abrir, escreve a F . Se receber um SIGPIPE, fecha tudo e bloqueia-se novamente no FIFO.

    
por 20.05.2013 / 16:31
2

Um loop infinito é a razão de pelo menos dois motivos. Ao lado do que você percebeu: Você quer o arquivo atualizado em todos os acessos, não é? Caso contrário, você poderia simplesmente criar um arquivo regular com o conteúdo correto.

Um gancho para abrir * () é possível, mas provavelmente não é trivial. FUSE é o caminho a percorrer. Se você tiver sorte, então já existe um módulo para isso. Caso contrário, você precisa escrever seu próprio módulo, que conecta esse caminho e passa o resto.

    
por 20.05.2013 / 16:32