STDIN und STDOUT eines ausgeführten Prozesses umleiten

psi81

Grünschnabel
Hallo zusammen,

ich verzweifle gerade an einer Sache, von der ich dachte, dass sie eigentlich zu den Grundfunktionen der stdio gehören:

Ich versuche, aus meinem C/C++ Programm heraus ein weiteres Programm aufzurufen, und sowohl stdin als auch stdout auf pipes umzulenken, damit ich Zugriff sowohl auf Ein- als auch auf die Ausgabe habe.

Unter Windows gibt es hierfür eine WinAPI Funktion, "CreateProcess", der ich meine Kanäle übergeben kann, das Problem ist nur... ich brauch das ganze für Linux (SuSE 10.1). Weiß jemand Rat?

Gruß
Achim
 
Hi.
Hallo zusammen,

ich verzweifle gerade an einer Sache, von der ich dachte, dass sie eigentlich zu den Grundfunktionen der stdio gehören:

Ich versuche, aus meinem C/C++ Programm heraus ein weiteres Programm aufzurufen, und sowohl stdin als auch stdout auf pipes umzulenken, damit ich Zugriff sowohl auf Ein- als auch auf die Ausgabe habe.
Laut POSIX Standard muß eine Pipe lediglich unidirektional sein - nicht bidirektional. Unter Linux ist das auch der Fall, so das verhindert wird das es zu einem Deadlock kommt.

Unter Windows gibt es hierfür eine WinAPI Funktion, "CreateProcess", der ich meine Kanäle übergeben kann, das Problem ist nur... ich brauch das ganze für Linux (SuSE 10.1). Weiß jemand Rat?

Du kannst das ganze mit fork, exec* und mehreren Pipes unter Linux realisieren:
C:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>

pid_t pid;
int p2c[2], c2p[2];

pipe(p2c);
pipe(c2p);

pid = fork();

if (pid == 0) { /* child */
  dup2(p2c[0], stdin);
  dup2(p2c[1], stdout);

  execvp(...); /* anderes Programm starten */
} else {
  FILE *tochild, *fromchild;

  close(p2c[0]);
  tochild = fdopen(p2c[1], "w");

  close(c2p[1]);
  fromchild = fdopen(c2p[0], "r");  

  /* tochild / fromchild benutzen */  
}
Also so ungefähr sollte das funktionieren. Vor allem Fehlerbehandlung hab ich komplett weggelassen...

Gruß
 
Danach suche ich gerade. Die grds. Idee ist mir klargeworden, danke für die kurze Erläuterung. Hat jemand dafür vielleicht ein komplettes Beispiel?

Gruß
René
 
Ich habe die Lösung inzwischen auch zu 90% (würd ich sagen) verstanden, ich kann mal versuchen, das in Worten wiederzugeben. Danach hänge ich mal ein Beispiel an, wie ich das gelöst habe.

Wenn ich das richtig verstanden habe, und mein Testcode hat zumindest funktioniert, dann funktioniert ein CGI- Call wie folgt:

Zuerst mal erzeuge ich 2 Pipes. Jede Pipe hat logischerweise 2 Handles, nämlich eins am Lese- und eins am Schreibende.

Die eine Pipe ist quasi die Richtung "Parent -> Child", die andere ist die Richtung "Child -> Parent".

Dann wird geforkt.

Im Child wird zunächst STDIN geschlossen (
Code:
close(0)
). Grund dafür ist der, dass der (spätere) CGI- Prozess ja keine Pipe öffnen soll, sondern diese Pipes auf STDIN und STDOUT gemappt werden sollen, damit der Child direkt darüber mit dem Parent kommunizieren können.


Nachdem STDIN geschlossen wurde, kann jetzt das Lese- Handle unserer "Parent->Child" Pipe dupliziert werden:
Code:
dup(p0[0])
.

p0 ist hierbei unsere Parent -> Child Pipe, p1 die umgekehrte Richtung.

Damit wird das Handle auf den gerade geschlossenen Kanal (nämlich STDIN) dupliziert.

Als nächstes machen wir dasselbe mit STDOUT:
Code:
close(1); dup(p1[1])
.

In diesem Fall wurde das Schreibende der anderen Richtung (p1) auf den wiederum zuvor geschlossenen Kanal (STDOUT) gemappt.

Jetzt kann der Childprozess seine nicht benötigten Handles der Pipes schließen, nämlich die Enden, die der Parent braucht.

------------------

Im Parent machen wir dasselbe, nur dass wir in dem Fall nicht noch STDIN und STDOUT umbiegen müssen. Wir öffnen stattdessen einfach die Handles und schließen die überflüssigen, das wars auch schon.

------------------

Die letzte Anweisung im Child- Zweig lautet dann execlp(Programmname, Argumente, NULL). Damit wird das Programm beendet und an dessen Stelle tritt dann das aufgerufene Kommando. Der somit neu erzeugte Prozess kennt weder p0 noch p1, aber STDIN und STDOUT werden automatisch vererbt. Beim Beenden des Prozesses werden diese Handles standardmäßig auch wieder von der Shell geschlossen, so dass da keine offenen Handles zurückbleiben.

-------------------

Beispiel:

Meine CGIComm.h und .cpp:

CGIComm.h

Code:
#ifndef CGICOMM_H
#define CGICOMM_H

#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

typedef struct CGI
{
  FILE *in;
  FILE *out;
  int pid;
};

CGI *OpenCGI(char *Command, char *Args); //CGI- Prozess erzeugen
char* ReadCGI(CGI* cgi); //Vom CGI- Prozess lesen
void WriteCGI(CGI* cgi, char* Data); //An CGI- Prozess senden
void CloseCGI(CGI *cgi); //CGI- Handles parentseitig schließen

int CGICommDirect(char* Command, char* Data, char* Answer, int AnsLen); //Kurzbefehl für alles zusammen

#endif

--------------------------------

CGIComm.cpp

Code:
#include "CGIComm.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <errno.h>

CGI *OpenCGI(char *Command, char *Args)
{
  FILE *f = fopen(Command, "r");
  if (f == NULL)
    return NULL;
  fclose(f);

  CGI *cgi = (CGI*)malloc(sizeof(CGI));
  int p0[2], p1[2];
  pipe(p0);
  pipe(p1);

  int cid = fork();
  if (!cid)
  {
    close(0);
    dup(p0[0]);
    close(1);
    dup(p1[1]);
    close(p0[1]);
    close(p1[0]);

    execlp(Command, Args, NULL);
  }
  else
  {
    cgi->in = fdopen(p0[1], "w");
    close(p0[0]);
    cgi->out = fdopen(p1[0], "r");
    close(p1[1]);
    cgi->pid = cid;
  }
  return cgi;
}

char* ReadCGI(CGI* cgi)
{
  char* Data = NULL;
  if (cgi == NULL)
    return NULL;
  int Len = 0;
  int DataLen = 0;
  char Buffer[128];
  while ((Len = fread(Buffer, 1, 128, cgi->out)))
  {
    char* NData = (char*)malloc(Len + DataLen);
    memcpy(NData, Data, DataLen);
    memcpy(&NData[DataLen], Buffer, Len);
    if (Data != NULL)
      free(Data);
    Data = NData;
    DataLen += Len;
  }
  if (DataLen == 0)
    return NULL;
  char *NData = (char*)malloc(DataLen+1);
  memcpy(NData, Data, DataLen);
  NData[DataLen] = 0;
  if (Data != NULL)
    free(Data);
  Data = NData;
  return Data;
}

void WriteCGI(CGI* cgi, char* Data)
{
  if (cgi == NULL)
    return;
  int Len = strlen(Data)+1;
  fputs(Data, cgi->in);
  fclose(cgi->in);
  cgi->in = NULL;
}

void CloseCGI(CGI *cgi)
{
  if (cgi == NULL)
    return;

  int result;
  waitpid(cgi->pid, &result, 0);

  if (cgi->in != NULL)
    fclose(cgi->in);
  fclose(cgi->out);
  free(cgi);
}


int CGICommDirect(char* Command, char* Data, char* Answer, int AnsLen)
{
  int pfds0[2], pfds1[2];
  FILE *fp_in, *fp_out;

  pipe(pfds0);
  pipe(pfds1);

  int cid = fork();

  if (!cid) {
    close(0);
    dup(pfds1[0]);
    close(1);
    dup(pfds0[1]);
    close(pfds0[0]);
    close(pfds1[1]);
    execlp(Command, NULL, NULL);
  } else {
    fp_in  = fdopen(pfds0[0], "r");
    close(pfds0[1]);
    fp_out = fdopen(pfds1[1], "w");
    close(pfds1[0]);
    fputs(Data, fp_out);
    fclose(fp_out);
    fgets(Answer, AnsLen, fp_in);
    fclose(fp_in);
    wait(NULL);
  }
  return 0;
}

-------------------------------

Bei Fragen fragen :)

Der Code steht übrigens zur freien Verwendung zur Verfügung, ich übernehme allerdings keine Garantie auf Funktionsfähigkeit oder Zweckdienlichkeit (halt die Haftungsauschlussklausel und so...)
 
Hallo,

tut mir leid diesen alten Thread wieder ausgraben zu müssen, aber er beinhaltet genau das, wonach ich suche.

Ich habe allerdings noch eine Frage zum Code von deepthroat. Kann es sein, dass in deinem Code ein kleiner Fehler ist?

Müsste dieser Code
Code:
if (pid == 0) { /* child */
  dup2(p2c[0], stdin);
  dup2(p2c[1], stdout);
nicht eher so
Code:
if (pid == 0) { /* child */
  dup2(p2c[0], stdin);
  dup2(c2p[1], stdout);
sein?

Für was es den Integer (p2c bzw. c2p) jeweils 2 mal gibt, habe ich noch nicht verstanden. Kann das eventuell noch jemand erklären?

Gruß

Ryo
 
Hi.
Ich habe allerdings noch eine Frage zum Code von deepthroat. Kann es sein, dass in deinem Code ein kleiner Fehler ist?

Müsste dieser Code
Code:
if (pid == 0) { /* child */
  dup2(p2c[0], stdin);
  dup2(p2c[1], stdout);
nicht eher so
Code:
if (pid == 0) { /* child */
  dup2(p2c[0], stdin);
  dup2(c2p[1], stdout);
sein?
Ja, da hast du allerdings recht.
Für was es den Integer (p2c bzw. c2p) jeweils 2 mal gibt, habe ich noch nicht verstanden. Kann das eventuell noch jemand erklären?
Was genau meinst du? Warum p2c und c2p ein Array ist? Oder warum p2c und c2p notwendig sind?

Gruß
 
Habe die Funktionsweise von pipe() nun verstanden und weiß, warum du ihm ein Array übergibst.

Nochmal vielen Dank für deinen Code, der hat mir sehr weitergeholfen!

€: Hast du eventuell noch eine Idee, wie ich im Parent-Prozess feststellen kann, wann der Child-Prozess fertig ist? Denn ich möchte ja solange der Child-Prozess lebt im Parent-Prozess die Ausgaben mitlesen und wenn der Child-Prozess beendet ist, soll das Programm beendet werden.

€2: Folgendes Abbruchkriterium scheint wohl zu funktionieren:
Code:
while (readResult > 0);
 
Zuletzt bearbeitet:

Neue Beiträge

Zurück