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 read
s 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;
}
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With