Mustafa Chelik
Mustafa Chelik

Reputation: 2184

Can't change child process's pipe buffer size

I want to run tcpdump for 5 seconds and grab its stdout output. The problem is that when I read in non-blocking mode, everything works as expected. But since I don't want to get stuck in read, I use non-blocking read. When I use non-blocking read, I get out tcpdump's stdout every 4096 bytes. I searched many times and tried many things and still no luck.

Please tell me what I'm doing wrong. How can I read with non-blocking mode from child process's stdout without waiting for its buffer to fill?

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

#define PIPE_READ 0
#define PIPE_WRITE 1

// Function to set a file descriptor to non-blocking mode
void set_nonblocking(int fd) {
    int flags = fcntl(fd, F_GETFL, 0);
    if (flags == -1) {
        perror("fcntl F_GETFL");
        exit(1);
    }
    if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
        perror("fcntl F_SETFL");
        exit(1);
    }
}

int main() {
    int stdout_pipe[2], stderr_pipe[2];
    pid_t pid;

    // Create two pipes: one for stdout, one for stderr
    if (pipe(stdout_pipe) == -1) {
        perror("pipe stdout");
        exit(1);
    }
    if (pipe(stderr_pipe) == -1) {
        perror("pipe stderr");
        exit(1);
    }

    // Fork the process
    pid = fork();

    if (pid == -1) {
        // Error during fork
        perror("fork");
        exit(1);
    }

    if (pid == 0) {  // Child process
        // Close the unused ends of the pipes in the child
        close(stdout_pipe[PIPE_READ]);
        close(stderr_pipe[PIPE_READ]);

        // Set stdout and stderr to be unbuffered
        setvbuf(stdout, NULL, _IONBF, 0);  // Unbuffered stdout
        setvbuf(stderr, NULL, _IONBF, 0);  // Unbuffered stderr

        // Redirect stdout and stderr to the pipes
        dup2(stdout_pipe[PIPE_WRITE], STDOUT_FILENO);
        dup2(stderr_pipe[PIPE_WRITE], STDERR_FILENO);

        // Close the pipe write ends since they're now redirected
        close(stdout_pipe[PIPE_WRITE]);
        close(stderr_pipe[PIPE_WRITE]);

        // Child runs tcpdump (for example)
        execlp("timeout", "timeout", "5s", "tcpdump", "src", "port", "41972", (char *)NULL);

        // If execlp fails
        perror("execlp");
        exit(1);
    } else {  // Parent process
        char buffer[256];
        int status;
        ssize_t nbytes;

        // Close the unused ends of the pipes in the parent
        close(stdout_pipe[PIPE_WRITE]);
        close(stderr_pipe[PIPE_WRITE]);

        // Set both pipe file descriptors to non-blocking
        set_nonblocking(stdout_pipe[PIPE_READ]);
        set_nonblocking(stderr_pipe[PIPE_READ]);

        // Parent process reads from the pipes in non-blocking mode
        printf("Parent is reading from child's stdout:\n");
        while (1) {
            // Try to read from stdout pipe
            nbytes = read(stdout_pipe[PIPE_READ], buffer, sizeof(buffer) - 1);
            if (nbytes > 0) {
                buffer[nbytes] = '\0';  // Null-terminate the string
                printf("%s", buffer);
            } else if (nbytes == 0) {
                // EOF reached (child process closed its end)
                break;
            } else if (errno != EAGAIN && errno != EWOULDBLOCK) {
                // Handle other errors
                perror("read stdout");
                break;
            }

            // Try to read from stderr pipe
            nbytes = read(stderr_pipe[PIPE_READ], buffer, sizeof(buffer) - 1);
            if (nbytes > 0) {
                buffer[nbytes] = '\0';  // Null-terminate the string
                printf("%s", buffer);
            } else if (nbytes == 0) {
                // EOF reached (child process closed its end)
                break;
            } else if (errno != EAGAIN && errno != EWOULDBLOCK) {
                // Handle other errors
                perror("read stderr");
                break;
            }

            // Sleep for a short time to avoid tight loop (can adjust as needed)
            usleep(100000); // sleep for 100ms
        }

        // Wait for the child process to exit
        waitpid(pid, &status, 0);

        // Close the pipe read ends in the parent
        close(stdout_pipe[PIPE_READ]);
        close(stderr_pipe[PIPE_READ]);
    }

    return 0;
}

Upvotes: 1

Views: 46

Answers (0)

Related Questions