Domanda

Ho molti problemi a costruire un logger "intermediario" - l'intenzione è di posizionarlo sul percorso sopra un elemento in / usr / bin e catturare tutto ciò che va e viene dall'applicazione. (L'app Black Box di terze parti non riesce per qualche motivo FTP.) Una volta eseguito, l'intermediario eseguirà il fork, reindirizzerà stdout e stdin su / da pipe di cui il genitore ha il controllo, quindi eseguirà il programma in / usr / bin. (Hardcoded; sì, lo so, sto male.)

Tuttavia, una volta eseguito poll (), le cose diventano strane. Perdo il controllo del mio file di log, il sondaggio sul tubo di output del bambino genera un errore, cani e gatti iniziano a vivere insieme, eccetera.

Qualcuno può far luce su questo?

Ecco quello che ho attualmente ... Il sondaggio () in questione è contrassegnato da commenti non rientrati per facilità di localizzazione.

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <poll.h>
#include <time.h>
#include <sys/types.h>
#include <fcntl.h>

#define MAX_STR_LEN 1024
static int directionFlag; /* 0 = input, 1 = output */
static int eofFlag;

/* Splits the next char from the stream inFile, with extra
information logged if directionFlag swaps */
void logChar(int inFilDes, int outFilDes, FILE *logFile, int direction)
{
    char inChar = 0;
    if(read(inFilDes, &inChar, sizeof(char)) > 0)
    {

        if(direction != directionFlag)
        {
            directionFlag = direction;
            if(direction)
            {
                fprintf(logFile, "\nOUTPUT: ");
            } else {
                fprintf(logFile, "\nINPUT: ");
            }
        }

        write(outFilDes, &inChar, sizeof(char));
        fputc(inChar, stderr);
        fputc(inChar, logFile);
    } else {
        eofFlag = 1;
    }
    return;
}

int main(int argc, char* argv[])
{
    pid_t pid;

    int childInPipe[2];
    int childOutPipe[2];

    eofFlag = 0;

    /* [0] is input, [1] is output*/

    if(pipe(childInPipe) < 0 || pipe(childOutPipe) < 0) {
        fprintf(stderr,"Pipe error; aborting\n");
            exit(1);
    }

    if((pid = fork()) == -1){
        fprintf(stderr,"Fork error; aborting\n");
        exit(1);
    }

    if(pid)
    {
        /*Parent process*/

        int i;
        int errcode;
        time_t rawtime;
        struct tm * timeinfo;
        time(&rawtime);
        timeinfo=localtime(&rawtime);

        struct pollfd pollArray[2] = {
            { .fd = 0, .events = POLLIN, .revents = 0 },
            { .fd = childOutPipe[0], .events = POLLIN, .revents = 0 }
        };
        /* Yet again, 0 = input, 1 = output */

        nfds_t nfds = sizeof(struct pollfd[2]);

        close(childInPipe[0]);
        close(childOutPipe[1]);

        /* We don't want to change around the streams for this one,
        as we will be logging everything - and I do mean everything */

        FILE *logFile;
        if(!(logFile = fopen("/opt/middleman/logfile.txt", "a"))) {
            fprintf(stderr, "fopen fail on /opt/middleman/logfile.txt\n");
            exit(1);
        }

        fprintf(logFile, "Commandline: ");

        for(i=0; i < argc; i++)
        {
            fprintf(logFile, "%s ", argv[i]);
        }
        fprintf(logFile, "\nTIMESTAMP: %s\n", asctime(timeinfo));

        while(!eofFlag)
        {

// RIGHT HERE is where things go to pot
            errcode = poll(pollArray, nfds, 1);
// All following fprintf(logfile)s do nothing
            if(errcode < 0) {
                fprintf(stderr, "POLL returned with error %d!", errcode);
                eofFlag = 1;
            }
            if((pollArray[0].revents && POLLERR) & errno != EAGAIN ) {
                fprintf(stderr, "POLL on input has thrown an exception!\n");
                fprintf(stderr, "ERRNO value: %d\n", errno);
                fprintf(logFile, "POLL on input has thrown an exception!\n");
                eofFlag = 1;
            } else if(pollArray[0].revents && POLLIN) {
                logChar(pollArray[0].fd, childInPipe[1], logFile, 0);
            } else if((pollArray[1].revents && POLLERR) & errno != EAGAIN ) {
                fprintf(stderr, "POLL on output has thrown an exception!\n");
                fprintf(stderr, "ERRNO value: %d\n", errno);
                fprintf(logFile, "POLL on output has thrown an exception!\n");
                eofFlag = 1;
            } else if(pollArray[1].revents && POLLIN) {
                logChar(pollArray[1].fd, 1, logFile, 1);
            }

        }

        fclose(logFile);

    }
    else
    {
        /*Child process; switch streams and execute application*/
        int i;
        int catcherr = 0;
        char stmt[MAX_STR_LEN] = "/usr/bin/";

        close(childInPipe[1]);
        close(childOutPipe[0]);

        strcat(stmt, argv[0]);

        if(dup2(childInPipe[0],0) < 0) {
            fprintf(stderr, "dup2 threw error %d on childInPipe[0] to stdin!\n", errno);
        }
//      close(childInPipe[0]);

        if(dup2(childOutPipe[1],1) < 0)
        {
            fprintf(stderr, "dup2 threw error %d on childInPipe[1] to stdout!\n", errno);
        }

        /* Arguments need to be in a different format for execv */
        char* args[argc+1];
        for(i = 0; i < argc; i++)
        {
            args[i] = argv[i];
        }
        args[i] = (char *)0;

        fprintf(stderr, "Child setup complete, executing %s\n", stmt);
        fprintf(stdout, "Child setup complete, executing %s\n", stmt);

        if(execv(stmt, args) == -1) {
            fprintf(stderr, "execvP error!\n");
            exit(1);
        }
    }
    return 0;
}


MODIFICA 23/06/09 12:20

Dopo le correzioni, ho tentato di eseguire "banner" attraverso questo programma, ed ecco l'output che ottengo ...

Child setup complete, executing /usr/bin/banner
POLL on output has thrown an exception!
ERRNO value: 0

Il file di registro ha il seguente:

Commandline: banner testing 
TIMESTAMP: Tue Jun 23 11:21:00 2009

Il motivo per cui ERRNO ha uno 0 è perché poll () ritorna bene; è il pollArray [1] .revents che è tornato con un errore, il che significa che il polling di ChildOutPipe [0] ha un errore. logChar (), per quanto ne so, non viene mai chiamato.

Proverò a dividere il sondaggio () in due diverse chiamate.


Va bene, il momento in cui scrivo () - anche su stdin, che non ritorna con un messaggio di errore - uccide la mia capacità di scrivere nel file di registro. Inoltre, ho scoperto che il ciclo while () viene eseguito più volte prima che il poll di output ritorni con un errore sulla pipe. Sto diventando sempre più convinto che il sondaggio () sia semplicemente una causa persa.
Ogni tentativo di scrivere su logFile fallisce dopo il poll (), persino un poll () riuscito, con errno impostato su & Quot; Numero file errato & Quot ;. Questo davvero non dovrebbe succedere. Onestamente non riesco a vedere in che modo influirebbe sulla gestione dei miei file.
Ok, a quanto pare sono un idiota. Grazie per avermi chiarito; Supponevo che nfds fosse una dimensione in byte, non in una matrice. È stato risolto, e voilà! Non sta più uccidendo il mio logFile handle.

È stato utile?

Soluzione

I veri problemi:

1 ° (ma minore) problema

struct pollfd pollArray[2] = {{0, POLLIN, 0}, {childOutPipe[0], POLLIN, 0}};

Stai formulando ipotesi ingiustificate sull'ordine e sui contenuti di 'struct pollfd'. Tutto lo standard dice che contiene (almeno) tre membri; non dice nulla sull'ordine in cui appaiono.

  

L'intestazione definisce la struttura pollfd, che comprende almeno i seguenti membri:

int    fd       The following descriptor being polled. 
short  events   The input event flags (see below). 
short  revents  The output event flags (see below). 

Dato che stai usando C99, usa la notazione di inizializzazione sicura:

    struct pollfd pollArray[2] =
    {
        { .fd = 0,               .events = POLLIN, .revents = 0 },
        { .fd = childOutPipe[0], .events = POLLIN, .revents = 0 },
    };

È possibile sostituire lo 0 per l'input standard con FILENO_STDIN da <fcntl.h>.

2 ° (il maggiore) problema

    nfds_t nfds = sizeof(pollArray);

La dimensione dell'array di polling è probabilmente di 16 (byte) - sulla maggior parte ma non su tutte le macchine (32-bit e 64-bit). È necessaria la dimensione dell'array di polling (che è 2). Questo è il motivo per cui si scatena l'inferno; il sistema sta osservando i rifiuti e si sta confondendo.

Indirizzare un commento :

Per trovare la dimensione di una matrice definita nel file o nella funzione locale (ma non un parametro di matrice passato in una funzione, né una matrice definita in un altro file), utilizzare una variante della macro:

#define DIM(x) (sizeof(x)/sizeof(*(x)))

Questo nome si rifà all'uso di BASIC in un passato oscuro e distante; altri nomi che ho visto sono NELEMS o ARRAY_SIZE o DIMENSION (tornando a Fortran IV) e sono sicuro che ce ne sono molti altri.

Quello che sta succedendo è che, poiché non si imposta nfds su 2, la chiamata di sistema sta leggendo i dati dopo l'array struct pollfd effettivo e sta provando a creare un capo o una coda di cose che non è revents. In particolare, probabilmente sta scrivendo in quello che hai detto che è il campo FILE * di una riga nell'array poll(), ma lo spazio effettivo è il registro errcode, quindi è completamente rovinato. Allo stesso modo per altre variabili locali. In altre parole, hai un buffer overflow dello stack, noto anche come Stack Overflow, un nome che dovrebbe essere leggermente familiare. Ma succede perché l'hai programmato.

Fix:

    nfds_t nfds = DIM(pollArray);

3o problema (medio grado)

   poll(pollArray, nfds, 1);
   if (errcode < 0) {

Il risultato di #include <errno.h> non viene salvato e alla variabile errno non viene mai assegnato un valore, tuttavia si controlla quale sia il valore immediatamente dopo. Il codice corretto dovrebbe probabilmente leggere:

errcode = poll(pollArray, nfds, 1);
if (errcode < 0)
{
    fprintf(stderr, "POLL returned with error %d!\n", errcode);
    eofFlag = 1;
}

Nota il carattere di nuova riga aggiunto al messaggio di errore: ne hai bisogno. Oppure:

if (poll(pollArray, nfds, 1) < 0)
{
    int errnum = errno;
    fprintf(stderr, "POLL returned with error (%d: %s)\n",
            errnum, strerror(errnum));
    eofFlag = 1;
}

Nel secondo caso, aggiungi "stderr" all'elenco delle intestazioni. Il salvataggio del valore di ENOTTY lo preserva dalle modifiche apportate dalle chiamate di funzione, ma è possibile testare in modo affidabile select() solo quando una funzione (chiamata di sistema) non è riuscita. Anche le chiamate di funzione riuscite possono lasciare sort diverso da zero. (Ad esempio, su alcuni sistemi, se <=> non va su un terminale, il valore di <=> dopo una chiamata I / O è <=>, anche se la chiamata nel suo insieme ha avuto esito positivo.)


Ruminazioni precedenti

Alcuni pensieri precedenti su quale potrebbe essere il problema; Penso che ci siano ancora alcune informazioni utili quaggiù.

Sospetto che il tuo problema sia che <=> 'danneggi' l'insieme dei descrittori sottoposti a polling e devi ricostruirlo su ogni ciclo. (Dopo aver controllato la pagina di manuale in Open Group , sembra che <=> non abbia i problemi di cui <=> soffre .) Questo è certamente un problema con la relativa <=> chiamata di sistema.

Il tuo codice figlio non sta chiudendo tutti i descrittori di file quando dovrebbe: hai commentato uno 'close () `e ne manca un altro. Quando il bambino ha terminato di collegare le pipe all'input e all'output standard, non si desidera che i descrittori di file non supportati siano ancora aperti; i processi non sono in grado di rilevare correttamente EOF.

Commenti simili possono essere applicati nel genitore.

Inoltre, tieni presente che potrebbe essere necessario che il processo di invio invii più pacchetti di dati al figlio prima che appaia qualcosa sull'output standard del figlio. Come caso estremo, considera '<=>'; che legge tutti i suoi dati prima di generare qualsiasi output. Mi preoccupo del codice di cambio di direzione, quindi, anche senon ho digerito completamente quello che fa. Di per sé, il cambio di direzione è innocuo - scrive semplicemente la nuova direzione quando inizia a scrivere nella direzione opposta dall'ultima volta.

Più seriamente, non usare letture e scritture di caratteri singoli; leggere i buffer di dimensioni sensibili pieni. Le dimensioni sensibili potrebbero essere quasi tutte le potenze di due tra 256 e 8192; puoi scegliere altre dimensioni in libertà (la dimensione del buffer del tubo potrebbe essere una buona dimensione da scegliere). La gestione di più personaggi alla volta migliorerà notevolmente le prestazioni.


Il modo in cui ho risolto problemi simili è avere due processi che eseguono il monitoraggio, uno per l'input standard e l'altro per l'output standard - o equivalenti. Ciò significa che non ho bisogno di usare <=> (o <=>). Il processo che gestisce l'input standard legge e blocca in attesa di ulteriori informazioni; quando arriva qualcosa, lo registra e lo scrive nell'input standard del bambino. Allo stesso modo per il processo che gestisce l'output standard.

Posso estrarre il codice che funziona con le pipe se ne hai bisogno (vedi il mio profilo). L'ho guardato un anno o due fa (hmmm; ultime modifiche nel 2005 in effetti, anche se l'ho ricompilato nel 2007) ed era ancora funzionante (è stato scritto intorno al 1989). Ho anche un codice che funziona su socket anziché su pipe. Avrebbero bisogno di qualche adattamento per soddisfare le tue esigenze; erano piuttosto specializzati (e la versione pipe, in particolare, è a conoscenza di un protocollo di database client-server e tenta di gestire pacchetti completi di informazioni).

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top