Let's not run before we can walk. Consider the slave program only for now.
You will save a lot of time and effort by working on one problem at a time. The slave can be tested without the master, so it makes a lot of sense to create, and test, it first. Completely.
Originally Posted by
matkuz
1) I don't really need have sa.sa_handler in my program because I can use simply sigwaitforinfo
True.
Originally Posted by
matkuz
In the slave, 2) After that I exec the slave proces
You don't need the fd at all. You can just print to standard output normally.
If you do need to pass the file descriptor number as a parameter, you can use
Code:
FILE *pipeout;
int pipeoutfd;
char dummy;
if (sscanf(argv[1], "%d %c", &pipeoutfd, &dummy) != 1 || pipeoutfd < 0) {
fprintf(stderr, "%s: Invalid file descriptor number.\n", argv[1]);
return EXIT_FAILURE;
}
pipeout = fdopen(pipeoutfd, "wb");
if (!pipeout) {
fprintf(stderr, "%s: Not a writable file descriptor.\n", argv[1]);
return EXIT_FAILURE;
}
/* At this point, use only the 'pipeout' stream,
* similar to stderr (or stdout).
*
* The stream now "owns" the file descriptor.
* Do not use the pipeoutfd directly.
* If the pipeout stream is closed, using
* fclose(pipeout);
* the pipeoutfd file descriptor is closed too.
*
* After printing to the pipeout stream, you can call
* fflush(pipeout);
* to make sure the data is written immediately to the pipe.
* (Otherwise the library might buffer some writes, for
* efficiency.)
*/
and then just fprintf(pipeout, "..."...); (optionally followed by fflush(pipeout); if you want to ensure the data gets written to the pipe now, instead of being buffered for later).
Although the sscanf() does not detect integer overflow (whereas strtol() does), the above does recognize bad parameters, like a number followed by a letter. For full error checking, I like to use a separate helper function that uses strol(), checks for garbage following the number, and that the number fits in an int.
Originally Posted by
matkuz
3) I change code in the master process
Worry about this only after you get the slave working. I'll help you look at this in detail then.
Originally Posted by
matkuz
3A)Where should I close the pipe in child?
You don't.
If you meant where in the master process, after forking a new child process but before executing the child process, there is more detail forthcoming on this. But again, worry about this after you get the slave working correctly.
(To ease your mind: It is easy to fork the slave processes and create the pipes so that the slave processes will only see the write end of each pipe, and that will be the standard output. It does involve a few steps, including fcntl(pipe_read_end, F_SETFD, O_CLOEXEC), to make sure that the read ends of any pipes are not open in the child processes -- they're automatically closed when a child process executes the slave binary).
Consider this example slave program:
Code:
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
const char *strsignalconst(const int signum)
{
static char buffer[16];
switch (signum) {
case SIGHUP: return "SIGHUP";
case SIGINT: return "SIGINT";
case SIGTERM: return "SIGTERM";
case SIGPIPE: return "SIGPIPE";
case SIGUSR1: return "SIGUSR1";
case SIGUSR2: return "SIGUSR2";
case SIGKILL: return "SIGKILL";
default:
snprintf(buffer, sizeof buffer, "%d", signum);
return (const char *)buffer;
}
}
int main(void)
{
const long pid = (long)getpid();
sigset_t mask;
siginfo_t info;
int signum;
/* Mask of signals we wait for. */
sigemptyset(&mask);
sigaddset(&mask, SIGUSR1);
sigaddset(&mask, SIGTERM);
/* If the parent process dies before we do, and we try to write
* to the pipe, we get an error, and a SIGPIPE signal, which by
* default aborts the program. The error is enough for us,
* so we will also block SIGPIPE. */
sigaddset(&mask, SIGPIPE);
/* We block the signals, so that they're not delivered
* unless we specifically wait for them. */
sigprocmask(SIG_BLOCK, &mask, NULL);
/* This slave is now ready to receive the signal. */
fprintf(stderr, "Slave %ld is ready for a SIGUSR1 signal.\n", pid);
fflush(stderr);
while (1) {
/* Clear signal info structure. */
memset(&info, 0, sizeof info);
/* Wait for a signal. */
signum = sigwaitinfo(&mask, &info);
if (signum == -1) {
/* Just interrupted for some reason? */
if (errno == EINTR)
continue;
/* Output the error string and exit. */
fprintf(stderr, "Slave %ld: %s.\n", pid, strerror(errno));
return EXIT_FAILURE;
}
break;
}
/* Print signal information to standard error. */
fprintf(stderr, "Slave %ld exited due to signal %d (%s) from process %ld.\n",
pid, signum, strsignalconst(signum), (long)info.si_pid);
fflush(stderr);
/* Print signal information to standard output. */
fprintf(stdout, "Slave %ld: %s from %ld.\n",
pid, strsignalconst(signum), (long)info.si_pid);
fflush(stdout);
/* If there was an I/O error, return EXIT_FAILURE. */
if (ferror(stdout) || ferror(stderr))
return EXIT_FAILURE;
return EXIT_SUCCESS;
}
You can compile it using e.g.
gcc -Wall -Wextra -O2 slave.c -o slave
To test it, run in one terminal
Code:
bash -c '( for((i=0; i<4; i++)); do sleep .2 ; ./slave & done ; wait )'
or, if you want each slave output to a separate file (simulates a pipe better),
Code:
bash -c '( for((i=0; i<4; i++)); do sleep .2 ; rm -f out.$i ; ./slave > out.$i & done ; wait )'
Look at the process numbers in the Slave # is ready for a SIGUSR1 signal lines. In another terminal, you can use
kill -USR1 #
or for example
kill -TERM #
where the # is a process number. Each slave will report which signal killed it, and which process sent the signal. After all slaves are killed, the bash command in the original terminal will complete.