0

My teacher gave us a practice assignment for studying in my Operating Systems class. The assignment was to pipe three processes together and implement the commands in the title all at once. We are only allowed to use these commands when implementing it:

dup2()
one of the exec()
fork()
pipe()
close()

I can pipe two together but I don't know how to do three. Could someone either show me how to do it or at least point me in the right direction?

Here is my code so far:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
        int pfd[2];
        int pfdb[2];
        int pid;

        if (pipe(pfd) == -1) {
                perror("pipe failed");
                exit(-1);
        }
        if ((pid = fork()) < 0) {
                perror("fork failed");
                exit(-2);
        }
        if (pid == 0) {
                close(pfd[1]);
                dup2(pfd[0], 0);
                close(pfd[0]);
                execlp("ps", "ps", "-ef", (char *) 0);
                perror("ps failed");
                exit(-3);
        } 
        else {
                close(pfd[0]);
                dup2(pfd[1], 1);
                close(pfd[1]);
                execlp("grep", "grep", "darrowr", (char *) 0);
                perror("grep failed");
                exit(-4);
        }
        exit(0);

}

Any help would be appreciated. Heck a tutorial on how to complete it would be wondrous!

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
ANIME4154142
  • 3
  • 2
  • 5
  • in order to replicate the semantics, you don't have to pipe anything. (You could do `system("ps -ef | grep USERID | wc")` as well.. Since this is simple text search, you could just read the process table and count the occurrences of the user ID. – The Paramagnetic Croissant Oct 28 '14 at 14:35
  • @JonathanLeffler well, `exec()` is there, so... (my advice wasn't using `system()`, just read the second part of my comment.) – The Paramagnetic Croissant Oct 28 '14 at 15:10
  • I don't think the proposed duplicate is a sufficiently good duplicate; it is dealing with a deadlock problem because of lack of closes, rather than 'how to do the job at all'. The solution is just about sufficient for the pipeline in the question; it is not as complete as it should be for general use. – Jonathan Leffler Oct 28 '14 at 23:02
  • @JonathanLeffler, perhaps so. It seems to me this general question — How do I implement a shell-like pipeline of 3+ processes in C/C++ — has been asked and answered [many](http://stackoverflow.com/q/20056084/132382) [times](http://stackoverflow.com/q/8082932/132382) here. – pilcrow Oct 29 '14 at 14:26
  • 1
    Dammit; I don't like SO removing my carefully written comments! @pilcrow — I said, before SO removed it, something along the lines of: _I agree that there are probably other questions which cover this situation. I like the answer to the second question linked in your second comment as a duplicate ([Connecting n processes with pipes in a shell?](http://stackoverflow.com/questions/8082932/connecting-n-commands-with-pipes-in-a-shell); that makes a good duplicate._ Thank you for doing the researching. – Jonathan Leffler Oct 29 '14 at 14:40

1 Answers1

0

You're going to need 3 processes and 2 pipes to connect them together. You start with 1 process, so you are going to need 2 fork() calls, 2 pipe() calls, and 3 exec*() calls. You have to decide which of the processes the initial process will end up running; it is most likely either the ps or the wc. You can write the code either way, but decide before you start.

The middle process, the grep, is going to need a pipe for its input and a pipe for its output. You could create one pipe and one child process and have it run ps with its output going to a pipe; you then create another pipe and another child process and fix its pipes up before running grep; the original process would have both pipes open and would close most of the file descriptors before running wc.

The key thing with pipes is to make sure you close enough file descriptors. If you duplicate a pipe to standard input or standard output, you should almost always close both of the original file descriptors returned by the pipe() call; in your example, you should close both. And with two pipes, that means there are four descriptors to close.

Working code

Note the use of an error report and exit function; it simplifies error reporting enormously. I have a library of functions that do different error reports; this is a simple implementation of one of those functions. (It's overly simple: it doesn't include the program name in the messages.)

#define _XOPEN_SOURCE 700
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

static void err_syserr(const char *fmt, ...);

int main(void)
{
  int p1[2];
  int p2[2];
  pid_t pid1;
  pid_t pid2;

  if (pipe(p1) == -1)
    err_syserr("failed to create first pipe");
  if ((pid1 = fork()) < 0)
    err_syserr("failed to fork first time");
  if (pid1 == 0)
  {
    dup2(p1[1], STDOUT_FILENO);
    close(p1[0]);
    close(p1[1]);
    execlp("ps", "ps", "-ef", (char *)0);
    err_syserr("failed to exec 'ps'");
  }
  if (pipe(p2) == -1)
    err_syserr("failed to create second pipe");
  if ((pid2 = fork()) < 0)
    err_syserr("failed to fork second time");
  if (pid2 == 0)
  {
    dup2(p1[0], STDIN_FILENO);
    close(p1[0]);
    close(p1[1]);
    dup2(p2[1], STDOUT_FILENO);
    close(p2[0]);
    close(p2[1]);
    execlp("grep", "grep", "root", (char *)0);
    err_syserr("failed to exec 'grep'");
  }
  else
  {
    close(p1[0]);
    close(p1[1]);
    dup2(p2[0], STDIN_FILENO);
    close(p2[0]);
    close(p2[1]);
    execlp("wc", "wc", (char *)0);
    err_syserr("failed to exec 'wc'");
  }
  /*NOTREACHED*/
}

#include <stdarg.h>
#include <errno.h>
#include <string.h>

static void err_syserr(const char *fmt, ...)
{
  int errnum = errno;
  va_list args;
  va_start(args, fmt);
  vfprintf(stderr, fmt, args);
  va_end(args);
  if (errnum != 0)
    fprintf(stderr, " (%d: %s)", errnum, strerror(errnum));
  putc('\n', stderr);
  exit(EXIT_FAILURE);
}

Sample output:

    234    2053   18213

My machine is rather busy running root-owned programs, it seems.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • The thing is I don't know what I need to write for the third part. Like what should I add to the code to make wc work. And what should I add to the code to make the second pipe. And how do I make the second fork? Can you choose whichever one of these questions you think is important and show me the actual code? – ANIME4154142 Oct 28 '14 at 17:51
  • at the very least can you show me where I need to put the third command code? and what I need to write it as like do I make it and else statement or if? – ANIME4154142 Oct 28 '14 at 18:51
  • Thank you very much. I really appreciate it. In fact I'm learning alot. Would the program work though if I took out the stuff after NOTREACHED? – ANIME4154142 Oct 28 '14 at 22:37
  • The close brace after `/*NOTREACHED*/` is the end of the `main()` function; you'll need that. The headers and code after that implement the `err_syserr()` function used liberally in the `main()` function. If you remove it from there, you'll have to either change the calls in the `main()` function or provide it somewhere else. If you like, you can consider the function as a cover for `perror()` plus `exit()`; you could rewrite it as: `void err_syserr(const char *msg, int status) { perror(msg); exit(status); }` and you could modify the calls in my code to use your error messages plus a status. – Jonathan Leffler Oct 28 '14 at 22:41
  • The `/*NOTREACHED*/` comment is stylized and was used with `lint` (back in the days when `lint` was useful because compilers didn't have the cross-module information about function interfaces -- which means before C89 standard compilers became universal, meaning before circa 1995). It indicates that the flow of control in a function does not reach this point (so the fact that there isn't a `return 0;` at the end is harmless. Of course, with C99 or C11, there is an implicit `return 0;` there anyway. – Jonathan Leffler Oct 28 '14 at 22:53
  • Hi one last final question. What is the reason for changing the process with grep in it to an if statement. – ANIME4154142 Oct 30 '14 at 22:10
  • You have three processes; the `if (pid2 == 0)` is true for the child that needs to run `grep`, and false for the parent which needs to run `wc -l`. Note that this code avoids all the issues of waiting for children to die because it simply runs the commands (the exit status of the sequence is the exit status of the `wc` command). In a shell, you'd need the parent to continue after the commands in the pipeline complete, which requires a bit more coding. – Jonathan Leffler Oct 30 '14 at 23:53