Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reading from `forkpty` child process: `ls /` output yields `EIO`

Tags:

c

pty

ipc

execvp

I'm trying to use forkpty to execvp a ls /, and then from the parent process read its output and write to stdout.

I got it working, and running it under Valgrind shows no errors either.

EDIT: Valgrind shows no memory errors. The last read still yields EIO under Valgrind.

However, the last read always returns -1 and sets errno to EIO. I've been researching and reading the manual pages, but I still don't understand exactly why that happens:

  EIO    I/O  error.  This will happen for example when the process is in
         a background process group, tries to read from  its  controlling
         terminal,  and  either it is ignoring or blocking SIGTTIN or its
         process group is orphaned.  It may also occur when  there  is  a
         low-level I/O error while reading from a disk or tape.

I've also noticed that if I catch SIGCHLD signals I can know when ls has exited, but if I list a bigger directory such as /usr/bin then I get that signal for the ls child process even when there are many reads following that succeed.

Here's an example output:

$ ./a.out
bin    dev         initrd.img.old  libx32      opt   sbin  usr
boot   etc         lib             lost+found  proc  srv   var
build  home        lib32           media       root  sys   vmlinuz
core   initrd.img  lib64           mnt         run   tmp   vmlinuz.old
# read: Input/output error

Am I missing something obvious?

#include <pty.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

int main(int argc, char* argv[]) {
    int master_fd;
    pid_t child_pid = forkpty(&master_fd, NULL, NULL, NULL);

    if (child_pid == -1) {
        perror("# forkpty");
        return EXIT_FAILURE;
    }
    else if (!child_pid) { /* CHILD */
        char* ls_argv[] = {
            "ls",
            "/",
            NULL,
        };

        execvp(ls_argv[0], ls_argv);
        perror("# execvp");
        abort();
    }
    else { /* PARENT */
        uint8_t buffer[BUFSIZ];
        ssize_t read_count;

        while ((read_count = read(master_fd, buffer, BUFSIZ)) > 0) {
            uint8_t* ptr = buffer;
            size_t ptr_len = (size_t) read_count;

            while (ptr_len > 0) {
                ssize_t write_count = write(STDOUT_FILENO, ptr, ptr_len);

                if (write_count == -1) {
                    perror("# write");
                    return EXIT_FAILURE;
                }

                ptr += write_count;
                ptr_len -= write_count;
            }
        }

        if (read_count == -1) {
            perror("# read");
            return EXIT_FAILURE;
        }

        if (close(master_fd) == -1) {
            perror("# close");
            return EXIT_FAILURE;
        }

        int child_status;

        if (waitpid(child_pid, &child_status, 0) == -1) {
            perror("# waitpid");
            return EXIT_FAILURE;
        }

        if (!WIFEXITED(child_status)) {
            fprintf(stderr, "# main: subprocess did not exit normally\n");
            return EXIT_FAILURE;
        }

        return child_status;
    }
}
like image 497
Márcio Avatar asked Oct 30 '22 10:10

Márcio


1 Answers

There seems to be three parts to the question:

a) Is EIO a valid error in this case (on the last read)?

b) Why is a SIGCHLD being received much ahead of the expected I/O completion,

c) Why doesn't the error happen with Valgrind?

a) The last read is expected to fail since the read is similar to the one on a broken pipe with the child process having exited. EOF seems to be a more meaningful error as compared to EIO.( It may also occur when there is a low-level I/O error while reading from a disk or tape.)

b) The CPU processing being much ahead of I/O caused this. Having received the SIGCHLD (and having set a flag to indicate the child has exited) the parent process should be expecting the read to fail once it's done reading information that the child wrote into the slave-pty and hence safely ignore the EIO.

c) I am not sure about this. While Valgrind might have slowed up things a bit giving enough time for I/O processing the final read would still be expected to fail (return -1), in this case with EIO.

like image 68
Prashanth Sriram Avatar answered Nov 15 '22 07:11

Prashanth Sriram