libslack(coproc) - coprocess module
#include <slack/std.h> #include <slack/coproc.h> pid_t coproc_open(int *to, int *from, int *err, const char *cmd, char * const *argv, char * const *envv, void (*action)(void *data), void *data); int coproc_close(pid_t pid, int *to, int *from, int *err); pid_t coproc_pty_open(int *masterfd, char *slavename, size_t slavenamesize, const struct termios *slave_termios, const struct winsize *slave_winsize, const char *cmd, char * const *argv, char * const *envv, void (*action)(void *data), void *data); int coproc_pty_close(pid_t pid, int *masterfd, const char *slavename);
This module contains functions for creating coprocesses that use either pipes or pseudo terminals for communication.
pid_t coproc_open(int *to, int *from, int *err, const char *cmd, char * const *argv, char * const *envv, void (*action)(void *data), void *data)
Starts a coprocess. cmd
is the name of the process or a shell command.
argv
is the command line argument vector to be passed to execve(2).
envv
is the environment variable vector to be passed to execve(2). If
envv
is null
, the current environment is used. If cmd
is the name
of a program, argv
must not be null
. If cmd
contains shell
metacharacters, it will executed by sh -c
and argv
must be null
.
This provides some protection from unintentionally invoking sh -c
. If
cmd
does not contain any shell metacharacters, but does contain a slash
character (/
), it is passed directly to execve(2). If it doesn't
contain a slash character, we search for the executable in the directories
specified by the PATH
variable. If the PATH
variable is not set, a
default path is used: /bin:/usr/bin
for root; :/bin:/usr/bin
for other
users. If permission is denied for a file (execve(2) returns EACCES
),
then searching continues. If the header of a file isn't recognised
(execve(2) returns ENOEXEC
), then /bin/sh
will be executed with
cmd
as its first argument. This is done so that shell scripts without a
#!
line can be used. If this attempt fails, no further searching is done.
Communication with the coprocess occurs over pipes. Data written to *to
can be read from the standard input of the coprocess. Data written to the
standard output or standard error of the coprocess may be read from *from
and *err
respectively. If the function pointer action
is not null
,
it is invoked in the child process between the calls to fork(2) and
execve(2). Specifically, it is invoked before the pipes are duplicated
onto stdin
, stdout
and stderr
. data is passed as the argument to
action. This is useful when you need to prevent the coprocess from
inheriting certain process attributes. It can be used to ignore signals, set
default signal handlers, modify the signal mask and close files. On success,
returns the process id of the coprocess. On error, returns -1
with
errno
set appropriately.
Note: That this can only be used with coprocesses that do not buffer I/O or that explicitly set line buffering (or no buffering) with setbuf(3) or setvbuf(3). If a potential coprocess uses standard I/O and you don't have access to the source code, you will need to use coproc_pty_open(3) instead.
Note: If cmd does contain shell metacharacters, make sure that the application provides the command to execute. If the command comes from outside the application, do not trust it. Verify that it is safe to execute.
int coproc_close(pid_t pid, int *to, int *from, int *err)
Closes the coprocess referred to by pid
which must have been obtained
from coproc_open(3). *to
, *from
and *err
will be closed and set
to -1
if they are not already -1
. The current process will then wait
for the coprocess to terminate by calling waitpid(2). On success, returns
the status of the child process as determined by waitpid(2). On error,
returns -1
with errno
set appropriately. Note: If waitpid(2) is
interrupted by a signal, coproc_close(3) will return -1
with errno
set to EINTR
. The caller has to call coproc_close(3) (or just
waitpid(2)) again until it succeeds (or a real error occurs).
pid_t coproc_pty_open(int *masterfd, char *slavename, size_t slavenamesize, const struct termios *slave_termios, const struct winsize *slave_winsize, const char *cmd, char * const *argv, char * const *envv, void (*action)(void *data), void *data)
Equivalent to coproc_open(3) except that communication with the coprocess
occurs over a pseudo terminal. This is useful when the coprocess uses
standard I/O and you don't have the source code. Standard I/O is fully
buffered unless connected to a terminal. *masterfd
is set to the master
side of a pseudo terminal. Data written to *masterfd
can be read from the
standard input of the coprocess. Data written to the standard output or
error of the coprocess can be read from *masterfd
. The device name of the
slave side of the pseudo terminal is stored in the buffer pointed to by
slavename
which must be able to store at least 64 bytes. slavenamesize
is the size of the buffer pointed to by slavename
. No more than
slavenamesize
bytes will be written into the buffer pointed to by
slavename
including the terminating nul
byte. If slave_termios
is
not null, it is passed to tcsetattr(3) with the command TCSANOW
to set
the terminal attributes of the slave device. If slave_winsize
is not
null, it is passed to ioctl(2) with the command TIOCSWINSZ
to set the
window size of the slave device. On success, returns 0
. On error, returns
-1
with errno
set appropriately.
int coproc_pty_close(pid_t pid, int *masterfd, const char *slavename)
Closes the coprocess referred to by pid
which must have been obtained
from coproc_pty_open(3). The slave side of the pseudo terminal is
released with pty_release(3) and *masterfd
is closed and set to -1
if it is not already -1
. The current process will then wait for the
coprocess to terminate by calling waitpid(2). On success, returns the
status of the child process as determined by waitpid(2). On error,
returns -1
with errno
set appropriately. Note: If waitpid(2) is
interrupted by a signal, coproc_close(3) will return -1
with errno
set to EINTR
. The caller has to call coproc_close(3) (or just
waitpid(2)) again until it succeeds (or a real error occurs).
Additional errors may be generated and returned from the underlying system calls. See their manual pages.
EINVAL
Invalid arguments were passed to coproc_open(3), coproc_close(3), coproc_pty_open(3) or coproc_pty_close(3).
MT-Safe (coproc_pty_open(3) is MT-Safe iff the pseudo(3) module is MT-Safe).
The following examples add two numbers from the command line using dc as a coprocess in four different ways.
This version uses pipes and does not use sh -c
.
#include <slack/std.h> #include <slack/coproc.h> int main(int ac, char **av) { if (ac == 3) { char *argv[2] = { "dc", NULL }; int to, from, err, status; char buf[BUFSIZ]; ssize_t bytes; pid_t pid; // Start the coprocess (without using sh -c) if ((pid = coproc_open(&to, &from, &err, "dc", argv, NULL, NULL, NULL)) == (pid_t)-1) { fprintf(stderr, "coproc_open(dc) failed: %s\n", strerror(errno)); return EXIT_FAILURE; } // Send it input and read its output snprintf(buf, BUFSIZ, "%s %s + p\n", av[1], av[2]); write(to, buf, strlen(buf)); bytes = read(from, buf, BUFSIZ); printf("%*.*s", bytes, bytes, buf); // Stop the coprocess (it's ok if you close to, from and/or err beforehand) while ((status = coproc_close(pid, &to, &from, &err)) == -1 && errno == EINTR) {} if (status == -1) { fprintf(stderr, "coproc_close(dc) failed: %s\n", strerror(errno)); return EXIT_FAILURE; } // Evaluate its exit status if (WIFSIGNALED(status)) { fprintf(stderr, "dc was killed by signal %d\n", WTERMSIG(status)); return EXIT_FAILURE; } if (WIFEXITED(status) && WEXITSTATUS(status)) { fprintf(stderr, "dc was killed by signal %d\n", WEXITSTATUS(status)); return EXIT_FAILURE; } } return EXIT_SUCCESS; }
This version uses pipes and sh -c
.
#include <slack/std.h> #include <slack/coproc.h> int main(int ac, char **av) { if (ac == 3) { int to, from, err, status; char buf[BUFSIZ]; ssize_t bytes; pid_t pid; // Start the coprocess (using sh -c) if ((pid = coproc_open(&to, &from, &err, "dc 2>&1", NULL, NULL, NULL, NULL)) == (pid_t)-1) { fprintf(stderr, "coproc_open(dc) failed: %s\n", strerror(errno)); return EXIT_FAILURE; } // Send it input and read its output snprintf(buf, BUFSIZ, "%s %s + p\n", av[1], av[2]); write(to, buf, strlen(buf)); bytes = read(from, buf, BUFSIZ); printf("%*.*s", bytes, bytes, buf); // Stop the coprocess (it's ok if you close to, from and/or err beforehand) while ((status = coproc_close(pid, &to, &from, &err)) == -1 && errno == EINTR) {} if (status == -1) { fprintf(stderr, "coproc_close(dc) failed: %s\n", strerror(errno)); return EXIT_FAILURE; } // Evaluate its exit status if (WIFSIGNALED(status)) { fprintf(stderr, "dc was killed by signal %d\n", WTERMSIG(status)); return EXIT_FAILURE; } if (WIFEXITED(status) && WEXITSTATUS(status)) { fprintf(stderr, "dc was killed by signal %d\n", WEXITSTATUS(status)); return EXIT_FAILURE; } } return EXIT_SUCCESS; }
This version uses a pseudo terminal and does not use sh -c
.
#include <slack/std.h> #include <slack/coproc.h> int tty_noecho(int fd) { struct termios attr[1]; if (tcgetattr(fd, attr) == -1) return -1; attr->c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL); #ifdef ONLCR attr->c_oflag &= ~ONLCR; #endif return tcsetattr(fd, TCSANOW, attr); } int main(int ac, char **av) { if (ac == 3) { char *argv[2] = { "dc", NULL }; struct termios attr[1]; char eof = CEOF; int masterfd, status; char slavename[64]; char buf[BUFSIZ]; ssize_t bytes; pid_t pid; // Start the coprocess (without using sh -c) if ((pid = coproc_pty_open(&masterfd, slavename, 64, NULL, NULL, "dc", argv, NULL, NULL, NULL)) == (pid_t)-1) { fprintf(stderr, "coproc_pty_open(dc) failed: %s\n", strerror(errno)); return EXIT_FAILURE; } // Turn off echo so we don't read back what we are about to write if (tty_noecho(masterfd) == -1) fprintf(stderr, "tty_noecho(masterfd) failed: %s\n", strerror(errno)); // Send it input and eof and read its output snprintf(buf, BUFSIZ, "%s %s + p\n", av[1], av[2]); write(masterfd, buf, strlen(buf)); if (tcgetattr(masterfd, attr) != -1) eof = attr->c_cc[VEOF]; write(masterfd, &eof, 1); while ((bytes = read(masterfd, buf, BUFSIZ)) > 0) printf("%*.*s", bytes, bytes, buf); if (bytes == -1 && errno != EIO) fprintf(stderr, "read(masterfd) failed: %s\n", strerror(errno)); // Stop the coprocess (masterfd must not be closed beforehand) while ((status = coproc_pty_close(pid, &masterfd, slavename)) == -1 && errno == EINTR) {} if (status == -1) { fprintf(stderr, "coproc_pty_close(dc) failed: %s\n", strerror(errno)); return EXIT_FAILURE; } // Evaluate its exit status if (WIFSIGNALED(status)) { fprintf(stderr, "dc was killed by signal %d\n", WTERMSIG(status)); return EXIT_FAILURE; } if (WIFEXITED(status) && WEXITSTATUS(status)) { fprintf(stderr, "dc was killed by signal %d\n", WEXITSTATUS(status)); return EXIT_FAILURE; } } return EXIT_SUCCESS; }
This version uses a pseudo terminal and sh -c
.
#include <slack/std.h> #include <slack/coproc.h> int tty_noecho(int fd) { struct termios attr[1]; if (tcgetattr(fd, attr) == -1) return -1; attr->c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL); #ifdef ONLCR attr->c_oflag &= ~ONLCR; #endif return tcsetattr(fd, TCSANOW, attr); } int main(int ac, char **av) { if (ac == 3) { int masterfd, status; char slavename[64]; char buf[BUFSIZ]; struct termios attr[1]; char eof = CEOF; ssize_t bytes; pid_t pid; // Start the coprocess (without using sh -c) if ((pid = coproc_pty_open(&masterfd, slavename, 64, NULL, NULL, "dc 2>&1", NULL, NULL, NULL, NULL)) == (pid_t)-1) { fprintf(stderr, "coproc_pty_open(dc) failed: %s\n", strerror(errno)); return EXIT_FAILURE; } // Turn off echo so we don't read back what we are about to write if (tty_noecho(masterfd) == -1) fprintf(stderr, "tty_noecho(masterfd) failed: %s\n", strerror(errno)); // Send it input and eof and read its output snprintf(buf, BUFSIZ, "%s %s + p\n", av[1], av[2]); write(masterfd, buf, strlen(buf)); if (tcgetattr(masterfd, attr) != -1) eof = attr->c_cc[VEOF]; write(masterfd, &eof, 1); while ((bytes = read(masterfd, buf, BUFSIZ)) > 0) printf("%*.*s", bytes, bytes, buf); if (bytes == -1 && errno != EIO) fprintf(stderr, "read(masterfd) failed: %s\n", strerror(errno)); // Stop the coprocess (masterfd must not be closed beforehand) while ((status = coproc_pty_close(pid, &masterfd, slavename)) == -1 && errno == EINTR) {} if (status == -1) { fprintf(stderr, "coproc_pty_close(dc) failed: %s\n", strerror(errno)); return EXIT_FAILURE; } // Evaluate its exit status if (WIFSIGNALED(status)) { fprintf(stderr, "dc was killed by signal %d\n", WTERMSIG(status)); return EXIT_FAILURE; } if (WIFEXITED(status) && WEXITSTATUS(status)) { fprintf(stderr, "dc was killed by signal %d\n", WEXITSTATUS(status)); return EXIT_FAILURE; } } return EXIT_SUCCESS; }
libslack(3), execve(2), system(3), popen(3), waitpid(2), sh(1), pseudo(3>
20100612 raf <raf@raf.org>