skip to Main Content

I am using pipes to display file contents. When I comment the wait command (in the last part of the parent process), I expect to see the parent process in the ps command’s output (after a while, because reading is completed and I am still browsing output generated by child process), but I see both parent and child processes in the output.

The following code shows argv1 content. After building the program (for example xpager) it should be executed as xpager filename.

My platform is Ubuntu 22.04.4 LTS.

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/wait.h>

#define BUFSIZE 8192
#define PPAGER  "/usr/bin/less"

int main(int argc, char *argv[]) {
    pid_t   pid;
    int eerrno, n;
    int in, pfd[2];
    char    buf[BUFSIZE];
    char    *pager, *argv0;


    if (argc != 2) {
        printf("usage: %s <pathname>n", argv[0]);
        exit(1);
    }

    if ( (in = open(argv[1], O_RDONLY)) < 0) {
        eerrno = errno;
        printf("Open error. %s(%d)n", strerror(errno), errno);
        exit(eerrno);
    }

    if ( pipe(pfd) < 0) {
        eerrno = errno;
        printf("pipe error. %s(%d)n", strerror(errno), errno);
        exit(eerrno);
    }


    pid = fork();
    switch (pid) {
        case -1:
            eerrno = errno;
            printf("fork error. %s(%d)n", strerror(errno), errno);
            exit(eerrno);

        case 0:
            close(pfd[1]);
            close(in);
            if (STDIN_FILENO != pfd[0]) {
                if (dup2(pfd[0], STDIN_FILENO) < 0) {
                    eerrno = errno;
                    printf("dup2 error. %s(%d)n", strerror(errno), errno);
                    exit(eerrno);
                }
                close(pfd[0]);
            }
            if ((pager=getenv("PPAGER")) == NULL)
                pager = PPAGER;
            if ((argv0=strrchr(pager, '/')) != NULL)
                argv0++;
            else
                argv0=pager; 
            execlp(pager, argv0, argv[1], (char *)0);
        default:
            close(pfd[0]);
            while((n=read(in, buf, BUFSIZE)) > 0) {
                if (write(pfd[1], buf, n) < 0) {
                    eerrno = errno;
                    printf("write errno. %s(%d)n", strerror(errno), errno);
                    kill (pid, 9);
                    exit(eerrno);
                }
            }
            if (n < 0) {
                eerrno = errno;
                printf("read error. %s(%d)n", strerror(errno), errno);
                kill(pid, 9);
                exit(eerrno);
            }
            close(pfd[1]);
        //  wait(NULL);
    }

    exit(0);
}

For easier reading, summarized code is as follow:

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/wait.h>

#define BUFSIZE 8192
#define PPAGER  "/usr/bin/less"

int main(int argc, char *argv[]) {
        pid_t   pid;
        int     n,in, pfd[2];
        char    buf[BUFSIZE];
        char    *pager, *argv0;

        in = open(argv[1], O_RDONLY);
        pipe(pfd);

        pid = fork();
        switch (pid) {
                case 0:
                        close(pfd[1]);
                        dup2(pfd[0], STDIN_FILENO);
                        pager = PPAGER;
                        argv0="less";
                        execlp(pager, argv0, argv[1], (char *)0);
                default:
                        close(pfd[0]);
                        while ((n=read(in, buf, BUFSIZE)) > 0)
                                write(pfd[1], buf, n);
                        close(pfd[1]);
                //      wait(NULL);
        }
        return(0);
   }

2

Answers


  1. I believe the problem is that you are passing the wrong arguments to the exec call. You are setting up the pipes correctly, redirecting the reading end of the child’s pipe to stdin, but you then pass argv[1] to the child (which is the less binary).

    less takes a file to read from as its first argument, so when you pass argv[1] the process just reads directly from that file instead of from the pipe you set up. This causes less to appear to behave correctly, and the parent process gets blocked trying to write to the pipe, since nobody is reading from it.

    Try passing "/dev/stdin" instead of argv[1] to exec and see if that solves your problem. /dev/stdin is a pseudo-file on linux referring to the input stream. Since this is the case, you will also need to pass -f to less to force it to display this file.

    Note that you will most likely still see the parent process running, since less is only reading from the file as needed, as stated in its man page.

    Edit: Instead of redirecting the pipe to stdin, you can also pass the pipe’s file descriptor directly to the process. All open file descriptors can be found at /dev/fd/. Here you will find the files 0, 1 and 2 for stdin, stdout and stderr respectively. Any currently opened file descriptors (e.g. files, pipes etc) can also be found here.

    The pipe’s file descriptor is stored in the integer variable you defined (pfd). So instead of redirecting to stdin, you can do:

    char pipe_fd[32];
    sprintf(pipe_fd, "/dev/fd/%d", pfd[0]);
    execlp(pager, argv0, "-f", pipe_fd, (char *)0);
    
    

    The effect is the same.

    In both cases, when less reaches the EOF, the parent will terminate and less will also be shut down by the the init process (parent of all processes on the system).

    Login or Signup to reply.
  2. Get rid of the filename argument when calling the pager in the child process. When it’s not given a filename, it will read from its standard input, which has been redirected to the pipe:

    So change

    execlp(pager, argv0, argv[1], (char *)0);
    

    to

    execlp(pager, argv0, (char *)0);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search