2010-10-07 28 views
18

Je suis en train d'implémenter des piping sur un système de fichiers simulé en C++ (avec surtout du C). Il doit exécuter des commandes dans le shell hôte, mais effectuer lui-même la tuyauterie sur le système de fichiers simulé.Peut popen() faire des tuyaux bidirectionnels comme pipe() + fork()?

je pourrais y parvenir avec les pipe(), fork() et system() appels système, mais je préfère utiliser popen() (qui gère la création d'un tuyau, bifurquer un processus, et en passant une commande à la coquille). Cela peut ne pas être possible parce que (je pense) je dois pouvoir écrire du processus parent du tuyau, lire sur la fin du processus enfant, écrire la sortie de l'enfant, et enfin lire cette sortie du parent. La page de manuel pour popen() sur mon système indique qu'un canal bidirectionnel est possible, mais mon code doit être exécuté sur un système dont l'ancienne version ne prend en charge que les canalisations unidirectionnelles.

Avec les appels séparés ci-dessus, je peux ouvrir/fermer des tuyaux pour y parvenir. Est-ce possible avec popen()?

Pour un exemple trivial, pour exécuter ls -l | grep .txt | grep cmds je dois:

  • Ouvrir un tuyau et un processus pour exécuter ls -l sur l'hôte; lire la sortie arrière
  • redirigez la sortie de ls -l retour à mon simulateur
  • Ouvrir un tuyau et un processus pour exécuter grep .txt sur l'hôte sur la sortie canalisée de ls -l
  • redirigez la sortie de ce retour au simulateur (coincé ici)
  • Ouvrir un tuyau et un processus pour exécuter grep cmds sur l'hôte sur la sortie de canalisée grep .txt
  • redirigez la sortie de ce retour au simulateur et l'imprimer

homme Popen

De Mac OS X:

La fonction popen() 'ouvre' un processus en créant un tuyau bidirectionnel, bifurquer, et en invoquant la coquille. Tous les flux ouverts par les appels popen() précédents dans le processus parent sont fermés dans le nouveau processus enfant. Historiquement, popen() a été implémenté avec un tuyau unidirectionnel; par conséquent, de nombreuses implémentations de popen() seulement permettent à l'argument de mode de spécifier lecture ou écriture, pas les deux. Parce que popen() est maintenant implémenté en utilisant un tube bidirectionnel , l'argument de mode peut demander un flux de données bidirectionnel. L'argument mode est un pointeur vers une chaîne terminée par un zéro qui doit être 'r' pour la lecture, 'w' pour l'écriture ou 'r +' pour la lecture et l'écriture.

+3

Ignore ce mauvais manuel, ce qui fait paraître comme unidirectionalité de tuyaux est un comportement hérité. Le comportement de votre implémentation est non standard et ne sera probablement jamais pris en charge dans d'autres environnements. Configurez simplement les tuyaux et la fourche et exécutez-vous, et tout sera heureux. –

+2

Tout ce fil est fait de victoire, toutes les réponses et la question méritent +1. –

+0

Je viens de faire un ** homme popen ** sur Ubuntu 13.04 et il dit: 'Puisqu'un tube est par définition unidirectionnel, l'argument type peut spécifier uniquement la lecture ou l'écriture, pas les deux; le flux qui en résulte est en lecture seule ou en écriture seule. Je suis surpris que ce soit le "plus vieux" popen ... – sager89

Répondre

10

Vous semblez avoir répondu à votre propre question. Si votre code doit fonctionner sur un ancien système qui ne prend pas en charge les canaux bidirectionnels d'ouverture, vous ne pourrez pas utiliser popen (du moins pas celui fourni).

La vraie question serait sur les capacités exactes des systèmes plus anciens en question. En particulier, leur pipe prend-il en charge la création de tuyaux bidirectionnels? S'ils ont un pipe qui peut créer un tuyau bidirectionnel, mais qui ne le fait pas, alors j'écrirais le flux principal du code pour utiliser popen avec un tuyau bidirectionnel, et fournir une implémentation de popen qui peut utiliser un tuyau bidirectionnel qui est compilé dans un utilisé si nécessaire.

Si vous avez besoin de soutenir les systèmes assez vieux que pipe ne supporte que les tuyaux unidirectionnels, alors vous êtes un peu coincé à l'utilisation pipe, fork, dup2, etc., vous-même. Je serais probablement encore envelopper dans une fonction qui fonctionne presque comme une version moderne de popen, mais au lieu de retourner une poignée de fichier, remplit une petite structure avec deux poignées de fichier, un pour stdin de l'enfant, l'autre pour le stdout de l'enfant.

+0

Je pense que vous m'avez aidé à réaliser que ma question était vraiment, "Est la capacité bidirectionnelle" commun "?", et il semble que ce ne soit pas. Merci pour la bonne réponse. –

+0

+1 pour le popen pour une direction + pipe() pour l'autre. Cela m'évite de réécrire la plupart des instructions cout, et je n'ai qu'à changer de cin à la place. – Cardin

8

POSIX prévoit que l'appel popen() n'a pas été conçu pour fournir une communication bidirectionnelle:

L'argument de sélection sur popen() est une chaîne qui spécifie le mode I/O:

  1. Si le mode est r, lorsque le processus enfant est démarré, son descripteur de fichier STDOUT_FILENO doit être l'extrémité inscriptible du canal, et le descripteur de fichier fileno (flux) dans le processus appelant, où flux est le pointeur retourné par popen() , doit être la fin lisible de la tuyau.
  2. Si mode est w, lorsque le processus enfant est démarré, son descripteur de fichier STDIN_FILENO doit être l'extrémité lisible du canal, et le descripteur de fichier fileno (stream) dans le processus appelant, où stream est le pointeur retourné par popen (), doit être l'extrémité inscriptible du tuyau.
  3. Si le mode a une autre valeur, le résultat n'est pas spécifié.

Tout code portable ne fera aucune hypothèse au-delà de cela. Le BSD popen() est similaire à ce que votre question décrit.

De plus, les tuyaux sont différents des sockets et chaque descripteur de fichier de tuyauterie est unidirectionnel. Vous devez créer deux tuyaux, un configuré pour chaque direction.

19

Je suggère d'écrire votre propre fonction pour faire la tuyauterie/forking/système pour vous. Vous pourriez avoir la fonction lancer un processus et retour des descripteurs de fichiers en lecture/écriture, comme dans ...

typedef void pfunc_t (int rfd, int wfd); 

pid_t pcreate(int fds[2], pfunc_t pfunc) { 
    /* Spawn a process from pfunc, returning it's pid. The fds array passed will 
    * be filled with two descriptors: fds[0] will read from the child process, 
    * and fds[1] will write to it. 
    * Similarly, the child process will receive a reading/writing fd set (in 
    * that same order) as arguments. 
    */ 
    pid_t pid; 
    int pipes[4]; 

    /* Warning: I'm not handling possible errors in pipe/fork */ 

    pipe(&pipes[0]); /* Parent read/child write pipe */ 
    pipe(&pipes[2]); /* Child read/parent write pipe */ 

    if ((pid = fork()) > 0) { 
     /* Parent process */ 
     fds[0] = pipes[0]; 
     fds[1] = pipes[3]; 

     close(pipes[1]); 
     close(pipes[2]); 

     return pid; 

    } else { 
     close(pipes[0]); 
     close(pipes[3]); 

     pfunc(pipes[2], pipes[1]); 

     exit(0); 
    } 

    return -1; /* ? */ 
} 

Vous pouvez ajouter toutes les fonctionnalités que vous avez besoin là-dedans.

+0

C'est merveilleux! Merci GRAND STYLE! –

1

Pas besoin de créer deux tubes et de perdre un filtre dans chaque processus. Utilisez simplement une socket à la place.https://stackoverflow.com/a/25177958/894520

+1

Pouvez-vous s'il vous plaît ajouter plus de détails pourquoi un filedescriptor pour chaque processus serait un «gaspillage»? Je veux dire ce que ça coûte? Quoi de moins cher avec la socket de domaine? –

+0

Je ne suis pas non plus convaincu qu'une prise est nécessaire dans ce cas. Mais si c'est en effet mieux que des pipes, j'aimerais savoir comment et pourquoi. –

0

Dans l'un des netresolve backends je parle à un script et donc je dois écrire à son stdin et lire son stdout. La fonction suivante exécute une commande avec stdin et stdout redirigés vers un canal. Vous pouvez l'utiliser et l'adapter à votre goût.

static bool 
start_subprocess(char *const command[], int *pid, int *infd, int *outfd) 
{ 
    int p1[2], p2[2]; 

    if (!pid || !infd || !outfd) 
     return false; 

    if (pipe(p1) == -1) 
     goto err_pipe1; 
    if (pipe(p2) == -1) 
     goto err_pipe2; 
    if ((*pid = fork()) == -1) 
     goto err_fork; 

    if (*pid) { 
     /* Parent process. */ 
     *infd = p1[1]; 
     *outfd = p2[0]; 
     close(p1[0]); 
     close(p2[1]); 
     return true; 
    } else { 
     /* Child process. */ 
     dup2(p1[0], 0); 
     dup2(p2[1], 1); 
     close(p1[0]); 
     close(p1[1]); 
     close(p2[0]); 
     close(p2[1]); 
     execvp(*command, command); 
     /* Error occured. */ 
     fprintf(stderr, "error running %s: %s", *command, strerror(errno)); 
     abort(); 
    } 

err_fork: 
    close(p2[1]); 
    close(p2[0]); 
err_pipe2: 
    close(p1[1]); 
    close(p1[0]); 
err_pipe1: 
    return false; 
} 

https://github.com/crossdistro/netresolve/blob/master/backends/exec.c#L46

(j'ai utilisé le même code dans popen simultaneous read and write)