How to synchronize read and write in the pipe in C? - c

Complete C beginner here.
I am trying to write some strings from the child process and read the strings in the parent process. But it looks like I haven't implemented the read and write properly. So my parent just reads the first string it gets. Below is my code
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <stdbool.h>
#include <ctype.h>
/* Function declaration */
bool isNumeric(char* str);
int main(int argc, char* argv[]) {
// Check if input is recieved
if (argc == 1) {
printf("Input not received!\n");
exit(1);
}
// Check if the input is an int
if (isNumeric(argv[1]) == 0) {
printf("Input is not an Integer!\n");
exit(1);
}
// Initialize pipe
int fd[2];
pid_t childpid;
pipe(fd);
childpid = fork();
if (childpid == -1) {
printf("fork failed");
exit(1);
}
else if (childpid > 0) {
char string[100];
close(fd[1]);
printf("PARENT START\n");
while (read(fd[0], string, sizeof(string)) > 0) {
printf("%s\n", string);
}
printf("PARENT END\n");
close(fd[0]);
}
else {
close(fd[0]);
char string[100];
string[0] = '\0';
printf("CHILD START\n");
for (int i = 0; i < 5; i++) {
sprintf(string, "%d", i);
write(fd[1], string, strlen(string)+1);
}
printf("CHILD END\n");
close(fd[1]);
exit(0);
}
}
The output is just
PARENT START
CHILD START
CHILD END
0
PARENT END
My expected output is
PARENT START
CHILD START
CHILD END
0
1
2
3
4
PARENT END
I spent hours trying to synchronize the process, but I couldn't figure out how to fix the problem.

You ignore the return value of read, so the call to printf stops at the first zero byte. You send the messages delimited by zero bytes. Where's the code to find the zero bytes in the received data and extract the messages from the pipe?
You have code to send a message. It separates the messages with a terminating zero byte. Where's the code to receive a message, searching the incoming stream of data for zero bytes and passing on the data prior to it as a message?
Here's some ugly, inefficient code to receive a message. It checks the incoming stream of bytes for the terminating zero byte. It returns 0 on end of file, negative on error and 1 on success.:
int recvMessage (int fd, char* buf, int len)
{
while (len > 0)
{
int r = read(fd, buf, 1);
if (r <= 0) // pipe closed or error
return r;
if (*buf == 0) // we received a terminating zero byte
return 1;
buf++;
len--;
}
return -2; // message larger than buffer
}

Five things you need to learn:
sizeof(string) doesn't return the length of a string, much less one you haven't read in yet! It always returns 100 in this case.
write is not guaranteed to write the number of bytes provided as its third argument. You need to call write repeatedly until the entire buffer you want to write has been written.
read is not guaranteed to read the number of bytes provided as its third argument. You need to call read repeatedly until you've read the desired number of bytes.
In this case, you don't want to read a specific number of bytes. You want to read until one of the character you've read is a NUL.
Unless you pass 1 for read's third parameter, you might end up reading too much. This is not a problem; you just need to factor that in the next time you read.
Writer
Replace
write(fd[1], string, strlen(string)+1);
with
char *p = string;
size_t n = strlen(string) + 1;
while (n > 0) {
ssize_t rv = write(fd[1], p, n);
if (rv == -1) {
perror("write");
exit(1);
}
n -= rv;
p += rv;
}
Reader
Replace
char string[100];
while (read(fd[0], string, sizeof(string)) > 0) {
...
}
with
#define BLOCK_SIZE 100
char *string = NULL;
size_t size = 0;
size_t len = 0;
while (1) {
// Increase the buffer size if necessary.
if (size < len + BLOCK_SIZE) {
char *tmp = realloc(string, len + BLOCK_SIZE);
if (!tmp) {
perror("realloc");
exit(1);
}
string = tmp;
}
// Read from the pipe.
ssize_t rv = read(fd[0], string+len, BLOCK_SIZE);
if (rv == -1) {
perror("read");
exit(1);
}
// Handle EOF
if (rv == 0)
break;
len += rv;
// Check if we've received a message (or even more than one).
while (1) {
for (size_t i=0; i<len; ++i) {
if (!string[i]) {
// Handle a message.
...
len -= i+1;
memmove(string, string+i+1, len);
break;
}
}
}
}
// Handle a partial message.
if (len) {
fprintf(stderr, "Premature EOF");
exit(1);
}
free(string);

Related

Scanf through pipe lock

I have an exercise where I need to interact with a C program through pipe.
I have the following source, which I can't modify.
#include <stdio.h>
#include <stdlib.h>
int main()
{
int number;
int answer;
number = rand() % 100;
printf("Print the double of the number %d\n", number);
scanf("%d", &answer);
if(number * 2 == answer)
printf("Success\n");
else
printf("Error\n");
}
I tried to interact with this program with this code
#include <unistd.h>
#include <stdio.h>
int main(int argc, char **argv, char **env)
{
int STDIN_PIPE[2];
int STDOUT_PIPE[2];
pipe(STDIN_PIPE);
pipe(STDOUT_PIPE);
pid_t pid = fork();
if(pid == 0)
{
char *path = "/path/to/binary";
char *args[2];
args[0] = path;
args[1] = NULL;
close(STDIN_PIPE[1]);
close(STDOUT_PIPE[0]);
dup2(STDIN_PIPE[0], STDIN_FILENO);
dup2(STDOUT_PIPE[1], STDOUT_FILENO);
execve(path, args, env);
}
else
{
char buf[128];
close(STDIN_PIPE[0]);
close(STDOUT_PIPE[1]);
while(read(STDOUT_PIPE[0], buf, 1))
write(1, buf, 1);
}
}
But when I run it, it falls in an infinite loop without printing nothing.
I have fixed a number of issues in your code, added a lot of error checks and completed it so that the end goal is reached.
In the child process, srand() must be called to initialize the random number generator or you always get the same value.
The in the child process, you must flush(stdout) after printing the question so that it is really written to the pipe.
And finally, scanf() return value must be checked.
In the main process, I added a lot of error checks. And I write a readLine function to - guess what - read a line from the pipe. A line is terminated by the end-of-line character \n.
There is still room for some enhancements...
I tested my code using Visual Studio Code configured for gcc and running under Ubuntu 20.04.
Here is the child process source:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
int number;
int answer;
time_t t;
srand((unsigned)time(&t));
number = rand() % 100;
printf("Print the double of the number %d\n", number);
fflush(stdout);
int n = scanf("%d", &answer);
if (n != 1) {
printf("Invalid input\n");
return 1;
}
if ((number * 2) == answer) {
printf("Success\n");
return 0;
}
printf("Error %d is not 2 * %d\n", answer, number);
return 1;
}
And here is the main process source:
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
int readLine(int fd, char *buf, int bufSize);
int main(int argc, char **argv, char **env)
{
int STDIN_PIPE[2];
int STDOUT_PIPE[2];
if (pipe(STDIN_PIPE))
{
perror("pipe(STDIN_PIPE)");
return 1;
}
if (pipe(STDOUT_PIPE)) {
perror("pipe(STDOUT_PIPE)");
return 1;
}
pid_t pid = fork();
if (pid == 0)
{
char *path = "../Child/Child"; // Path to child process, adapt to your environment
char *args[2];
args[0] = path;
args[1] = NULL;
if (dup2(STDIN_PIPE[0], STDIN_FILENO) == -1) {
perror("dup2(STDIN) failed");
return 1;
}
if (dup2(STDOUT_PIPE[1], STDOUT_FILENO) == -1) {
perror("dup2(STDIN) failed");
return 1;
}
// Close all pipe ends
close(STDIN_PIPE[0]); // Close read end of STDIN_PIPE
close(STDIN_PIPE[1]); // Write end of STDIN_PIPE
close(STDOUT_PIPE[0]); // Read end of STDOUT_PIPE
close(STDOUT_PIPE[1]); // Close write end of STDOUT_PIPE
if (execve(path, args, env) == -1) {
perror("execve failed");
return 1;
}
}
else
{
char buf[128];
int bufSize = sizeof(buf) / sizeof(buf[0]);
int i;
// Read the question asked by child process
if (readLine(STDOUT_PIPE[0], buf, bufSize) < 0) {
printf("readLine failed.\n");
return 1;
}
// We receive something like "Print the double of the number 83"
printf("Child process question is \"%s\".\n", buf);
// Extract the number at end of string
i = strlen(buf) - 1;
while ((i >= 0) && isdigit(buf[i]))
i--;
int value = atoi(buf + i + 1);
// Write our answer to write end of STDIN_PIPE
char answer[128];
int answerSize = sizeof(answer) / sizeof(answer[0]);
int answerLen = snprintf(answer, answerSize, "%d\n", value * 2);
printf("Our answer is \"%d\".\n", value * 2);
if (write(STDIN_PIPE[1], answer, answerLen) != answerLen) {
printf("write failed.\n");
return 1;
}
// Read the response (success or failure) sent by child process
if (readLine(STDOUT_PIPE[0], buf, bufSize) < 0) {
printf("readLine failed.\n");
return 1;
}
if (strcasecmp(buf, "Success") == 0)
printf("Child process returned success.\n");
else
printf("Child process returned failure.\n");
// Close all pipe ends
close(STDIN_PIPE[0]); // Close read end of STDIN_PIPE
close(STDIN_PIPE[1]); // Write end of STDIN_PIPE
close(STDOUT_PIPE[0]); // Read end of STDOUT_PIPE
close(STDOUT_PIPE[1]); // Close write end of STDOUT_PIPE
}
return 0;
}
// Read a line from file descriptor
// A line is any characters until \n is received or EOF
// \n is not kept
// Return the number of characters read or <0 if error:
// -1 => Input buffer overflow
// -2 => read() failed and errno has the error
int readLine(int fd, char *buf, int bufSize)
{
int i = 0;
while (1)
{
// Check if enough room in the buffer
if (i >= bufSize) {
printf("Input buffer overflow\n");
return -1;
}
// Read one character from the pipe
ssize_t n = read(fd, buf + i, 1);
if (n == -1)
{
perror("read() failed");
return -2;
}
if (n == 0)
{
// EOF received, that's OK
return i;
}
// NUL terminate the buffer
buf[i + 1] = 0;
// Check for end of line character
if (buf[i] == '\n') {
buf[i] = 0; // Remove ending \n
return i;
}
i++;
}
}

Unable to process the pipe function

Unable to process the pipe function where a give pipes in which one process sends a string message to a second process, and the second process reverses the case of each character in the message and sends it back to the first process.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>
#include <stdbool.h>
// Parent: reads from P1_READ, writes on P1_WRITE
// Child: reads from P2_READ, writes on P2_WRITE
#define P1_READ 0
#define P2_WRITE 1
#define P2_READ 2
#define P1_WRITE 3
// the total number of pipe *pairs* we need
#define NUM_PIPES 2
/*
toggleString accepts an a pointer to char array, allocates size for the
string to be toggled,
copys the argument into a string, loops through the string and for every
uppercase character
we set it to its lower case counterpart and vice versa, returning the
toggled string
*/
char *toggleString(char *argv){
int i; /* Declare counter */
char *str = malloc(sizeof(argv[1])); /* Declare array sizeof input */
strcpy(str, argv); /* Copy String to char array */
for(i=0;str[i]!='\0';i++) { //Loop through length of string
if(str[i]>='A'&&str[i]<='Z'){ //if the array at i is uppercase
str[i]+=32; //Make it lower case
} else if (str[i]>='a'&&str[i]<='z') {// if the array at i is lowercase
str[i]-=32; //Make it uppercase
}
}
return str;
}
/*
int inputValidation accept and integer (number of arugments) and a
pointer to the cmd line input array
We check to see if the command line input contains the minimal number of
arugments and check to see
whether or not the user input contains at least one reversible haracter,
if all goes well we return 0
*/
int inputValidation(int argc, char *argv[]){
int i; //Declare counter variable
bool c = false; //Declare boolean flag using imported <stdbool.h>
char str[strlen(argv[1])]; //Declare str
strcpy(str, argv[1]); //copy argument into str
if (argc != 2) { // check to see if we have enough arguments to
continue
// Prompt user of correct usage
fprintf(stderr, "\nUsage: %s <string> or <'string 1, string 2', ...,
string n'> for multiple strings\n", argv[0]);
exit(EXIT_FAILURE); //Exit on improper input
} else {
//loop through our string
for(i=0;i<strlen(str);i++) {
//if any any char is a reversible character
if(isalpha((int) str[i])){
c = true; //set the flag to true
}
}
if(c == false){ //If flag is false input does not contain any
reversible charachters
printf("\nSorry, The string you entered did NOT contain any
Alphabetical Characters\nRun me again, with at least 1 Alphabetical
character\n\n");
exit(EXIT_FAILURE); //Exit on improper input
}
return (0);
}
}
/*
Main takes input from command line, calls input validation to make sure of
proper input,
then creates the pipes we will need and the forks the child process, Parent
and Child
execute they're respective code
*/
int main(int argc, char *argv[]) {
assert(argc>1);
int fd[2*NUM_PIPES]; //Declare int[] of file descriptors
int len, i; //Declare length and integer for count
pid_t pid; //Declare process id
char parent[strlen(argv[1])]; //Declare Parent array
char child[strlen(argv[1])]; //Declare Child array
if(inputValidation(argc, argv) == 0) /* Check for proper input */
strcpy(parent, argv[1]);
// create all the descriptor pairs we need
for (i=0; i<NUM_PIPES; ++i)
{
if (pipe(fd+(i*2)) < 0)
{
perror("Failed to allocate pipes");
exit(EXIT_FAILURE);
}
}
// fork() returns 0 for child process, child-pid for parent process.
if ((pid = fork()) < 0)
{
perror("Failed to fork process");
return EXIT_FAILURE;
}
//////////////////////////////Childs Code
BEGINS//////////////////////////////////
// if the pid is zero, this is the child process
if (pid == 0)
{
// Child. Start by closing descriptors we
// don't need in this process
close(fd[P1_READ]);
close(fd[P1_WRITE]);
// used for output
pid = getpid();
// wait for parent to send us a value
len = read(fd[P2_READ], &child, len);
if (len < 0)
{
perror("Child: Failed to read data from pipe");
exit(EXIT_FAILURE);
}
else if (len == 0)
{
// not an error, but certainly unexpected
fprintf(stderr, "Child: Read EOF from pipe");
}
else
{
// report pid to console
printf("Child(%d): Recieved Message\n\nChild(%d): Toggling Case and
Sending to Parent\n",pid, pid);
// send the message to toggleString and write it to pipe//
if (write(fd[P2_WRITE], toggleString(child), strlen(child)) < 0)
{
perror("Child: Failed to write response value");
exit(EXIT_FAILURE);
}
}
// finished. close remaining descriptors.
close(fd[P2_READ]);
close(fd[P2_WRITE]);
return EXIT_SUCCESS;
}
//child code ends///
//////////////////////////////Parent Code
BEGINS//////////////////////////////////
// Parent. close unneeded descriptors
close(fd[P2_READ]);
close(fd[P2_WRITE]);
// used for output
pid = getpid();
// send a value to the child
printf("\nParent(%d): Sending %s to Child\n\n", pid, argv[1]);
if (write(fd[P1_WRITE], argv[1], strlen(argv[1])) != strlen(argv[1]))
{
perror("Parent: Failed to send value to child ");
exit(EXIT_FAILURE);
}
// now wait for a response
len = read(fd[P1_READ], &parent, strlen(parent));
if (len < 0)
{
perror("Parent: failed to read value from pipe");
exit(EXIT_FAILURE);
}
else if (len == 0)
{
// not an error, but certainly unexpected
fprintf(stderr, "Parent(%d): Read EOF from pipe", pid);
}
else
{
// report what we received
printf("\nParent(%d): Received %s from Child\n\n", pid, parent);
}
// close down remaining descriptors
close(fd[P1_READ]);
close(fd[P1_WRITE]);
// wait for child termination
wait(NULL);
return EXIT_SUCCESS;
}
//////////////////////////////Parent Code
ENDS//////////////////////////////////
This works:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>
#include <stdbool.h>
#define P1_READ 0
#define P2_WRITE 1
#define P2_READ 2
#define P1_WRITE 3
#define NUM_PIPES 2
static
char *toggleString(char *argv)
{
int i;
char *str = malloc(strlen(argv) + 1); /* Key Fix */
strcpy(str, argv);
for (i = 0; str[i] != '\0'; i++)
{
if (str[i] >= 'A' && str[i] <= 'Z')
{
str[i] += 32;
}
else if (str[i] >= 'a' && str[i] <= 'z')
{
str[i] -= 32;
}
}
return str;
}
static
int inputValidation(int argc, char *argv[])
{
bool c = false;
char str[strlen(argv[1])];
strcpy(str, argv[1]);
if (argc != 2)
{
fprintf(stderr, "\nUsage: %s <string> or <'string 1, string 2', ..., string n'> for multiple strings\n", argv[0]);
exit(EXIT_FAILURE);
}
else
{
for (size_t i = 0; i < strlen(str); i++)
{
if (isalpha((int)str[i]))
{
c = true;
}
}
if (c == false)
{
printf("\nSorry, The string you entered did NOT contain any"
" Alphabetical Characters\nRun me again, with at least 1 Alphabetical"
" character\n\n");
exit(EXIT_FAILURE);
}
return(0);
}
}
int main(int argc, char *argv[])
{
assert(argc > 1);
int fd[2 * NUM_PIPES];
int len, i;
pid_t pid;
char parent[strlen(argv[1])];
char child[strlen(argv[1])];
if (inputValidation(argc, argv) == 0)
strcpy(parent, argv[1]);
for (i = 0; i < NUM_PIPES; ++i)
{
if (pipe(fd + (i * 2)) < 0)
{
perror("Failed to allocate pipes");
exit(EXIT_FAILURE);
}
}
if ((pid = fork()) < 0)
{
perror("Failed to fork process");
exit(EXIT_FAILURE);
}
if (pid == 0)
{
close(fd[P1_READ]);
close(fd[P1_WRITE]);
pid = getpid();
len = read(fd[P2_READ], child, sizeof(child));
if (len < 0)
{
perror("Child: Failed to read data from pipe");
exit(EXIT_FAILURE);
}
else if (len == 0)
{
fprintf(stderr, "Child: Read EOF from pipe\n");
}
else
{
child[len] = '\0';
printf("Child(%d): Received Message [%s]\nChild(%d): Toggling Case and Sending to Parent\n", pid, child, pid);
char *toggled = toggleString(child);
printf("Child(%d): Sending [%s]\n", pid, toggled);
if (write(fd[P2_WRITE], toggled, len) < 0)
{
perror("Child: Failed to write response value");
exit(EXIT_FAILURE);
}
free(toggled);
}
close(fd[P2_READ]);
close(fd[P2_WRITE]);
return EXIT_SUCCESS;
}
close(fd[P2_READ]);
close(fd[P2_WRITE]);
pid = getpid();
printf("\nParent(%d): Sending [%s] to Child\n\n", pid, argv[1]);
len = strlen(argv[1]);
if (write(fd[P1_WRITE], argv[1], len) != len)
{
perror("Parent: Failed to send value to child");
exit(EXIT_FAILURE);
}
len = read(fd[P1_READ], parent, sizeof(parent));
if (len < 0)
{
perror("Parent: failed to read value from pipe");
exit(EXIT_FAILURE);
}
else if (len == 0)
{
fprintf(stderr, "Parent(%d): Read EOF from pipe\n", pid);
}
else
{
parent[len] = '\0';
printf("\nParent(%d): Received [%s] from Child\n\n", pid, parent);
}
close(fd[P1_READ]);
close(fd[P1_WRITE]);
wait(NULL);
return EXIT_SUCCESS;
}
It was painful extracting your code from your comments, and the split over multiple line strings, and so on. The toggleString() function was broken — allocating 1 byte and then copying a string over that. The other code was not careful about null-terminating strings and handling them. These are basically the problems diagnosed in the comments.
Sample run:
$ pp53 'AbSoLuTeLy GlOrIoUs'
Parent(5209): Sending [AbSoLuTeLy GlOrIoUs] to Child
Child(5210): Received Message [AbSoLuTeLy GlOrIoUs]
Child(5210): Toggling Case and Sending to Parent
Child(5210): Sending [aBsOlUtElY gLoRiOuS]
Parent(5209): Received [aBsOlUtElY gLoRiOuS] from Child
$

Check if unix pipe closed without writing anything?

Basically I have a parent process that forks a child and feeds it it's stdin through a pipe. The child process can terminate in one of two cases:
the write end of the pipe is closed by the parent, meaning it reached the end of stdin thus receiving an EOF,
or it receives a certain input through the pipe(-1 in this case) and exits
My parent code looks roughly like this:
close(pi[0]); // close input end
signal(SIGPIPE, SIG_IGN); // do not handle SIGPIPE
char buffer;
int ok = 1;
while(ok && read(STDIN_FILENO, &buffer, 1) > 0) {
int b_written = write(pi[1], &buffer, 1);
if(b_written == -1) {
if(errno == EPIPE) ok = 0;
else perror("pipe write"); // some other error
}
}
As you can see, I check whether the read end of a pipe is closed by checking for errno == EPIPE. However this means that the read loop does one extra iteration before closing. How could I possibly poll to see if the pipe is closed without necessarily writing something to it?
This snippet will check if the other end of a writable pipe is closed using poll(2). This works on Linux -- I'm not sure about other OSes or what POSIX says.
#include <poll.h>
#include <stdbool.h>
#include <stdio.h>
#include <unistd.h>
bool is_pipe_closed(int fd) {
struct pollfd pfd = {
.fd = fd,
.events = POLLOUT,
};
if (poll(&pfd, 1, 1) < 0) {
return false;
}
return pfd.revents & POLLERR;
}
The child could send a signal, such as SIGUSR1 when it detects it has finished. Parent could set a flag to when it receives SIGUSR1 signal, and check this flag before trying to read input. But I am not absolutely sure SIGUSR1 could not be received after checking the flag ans before reading input from stdin). So I prefer to use a control pipe, each time child know it will be able to read one more data it write a 1 in this control pipe. The result could be something like that:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/wait.h>
#define STOP_VALUE 100
#define SIZE_STDIN_BUFFER 1024
static char can_read_more = 1;
static int handle_child(int *p_child_input_stream, int *p_control_stream)
{
int pipefd[2][2];
pid_t fk;
if (pipe(pipefd[0]) < 0) // Pipe to read input from
{
perror("pipe");
return -1;
}
if (pipe(pipefd[1]) < 0) // Pipe to notifiate parent input can be processed
{
perror("pipe");
close(pipefd[0][0]);
close(pipefd[0][1]);
return -1;
}
if ((fk = fork()) < 0)
{
perror("fork");
close(pipefd[0][0]);
close(pipefd[0][1]);
close(pipefd[1][0]);
close(pipefd[1][1]);
return -1;
}
if (fk == 0)
{
close(pipefd[0][1]);
close(pipefd[1][0]);
write(pipefd[1][1], &can_read_more, sizeof(char)); // sizeof(char) == 1
ssize_t nb_read = 0;
char buffer;
while (nb_read >= 0)
{
nb_read = read(pipefd[0][0], &buffer, sizeof(char));
if (nb_read > 0)
{
printf("0x%02x\n", (unsigned int) buffer);
if (buffer == STOP_VALUE)
{
nb_read = -1;
}
else
{
write(pipefd[1][1], &can_read_more, sizeof(char));
}
}
}
close(pipefd[0][0]);
close(pipefd[1][1]);
exit(0);
}
close(pipefd[0][0]);
close(pipefd[1][1]);
*p_child_input_stream = pipefd[0][1];
*p_control_stream = pipefd[1][0];
return 0;
}
int main()
{
int child_input_stream;
int control_stream;
if (handle_child(&child_input_stream, &control_stream) < 0)
{
return 1;
}
char stdin_buffer[SIZE_STDIN_BUFFER];
char buffer;
int ok = 1;
int child_available_input = 0;
while(ok)
{
while (child_available_input <= 0 && ok)
{
ssize_t nb_control = read(control_stream, &buffer, sizeof(char));
if (nb_control > 0)
{
child_available_input += buffer;
}
else
{
fprintf(stderr, "End of child reading its input detected.\n");
ok = 0;
}
}
if (ok)
{
if (fgets(stdin_buffer, SIZE_STDIN_BUFFER, stdin) == NULL)
{
ok = 0;
}
else
{
if (stdin_buffer[strlen(stdin_buffer) - 1] == '\n')
{
stdin_buffer[strlen(stdin_buffer) - 1] = '\0';
}
char dummy;
int input;
if (sscanf(stdin_buffer, "%d%c", &input, &dummy) == 1)
{
buffer = (char) input;
write(child_input_stream, &buffer, sizeof(char));
child_available_input--;
}
}
}
}
return 0;
}

How to pass an integer between childprocesses and parent

I have a program that is supposed to count the chars in a txt file and use a child process to count the chars and then print out the amount. The parent just feeds the lines in the file to the child in a while loop. The program works in that sense that it can open the file, read it line by line and then if the child process prints the amount it is correct.
But now I want to modify it so that the child process passes back the amount and the parent writes out the nr of chars instead. But when I try to do it the program gives me 1153416175 instead of the real number of chars in the file, and also it just get stuck and I have to kill it. Why is this happening?
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#define MAXLINE 100
int main(int argc, char *argv[]) {
int fds[2]; /* file descriptors */
int child; /* child process */
int n; /* size of line in file */
int count, alphacount, total=0; /* used in loop for counting chars in line */
int read_bytes; /* amount of bytes read in read function */
char line[MAXLINE];
FILE *fp;
char *str;
if (argc < 2) {
printf("Format: './rakna <filename>'.\nThe file should have the .txt extension.\n");
exit(1);
} else {
if (pipe(fds) < 0) {
perror("Can't open pipe\n");
exit(1);
}
child = fork();
if (child==-1) {
perror("fork error");
exit(1);
}
/* child that reads chars and returns amount */
if(child == 0)
{
/* close(fds[1]); child process close input of pipe Used to be before ..*/
/* count chars through read */
while (read_bytes = read(fds[0], line, sizeof(line)) > 0) {
/* check if line is received */
if (read_bytes < 0) {
perror("Can't read line");
exit(1);
}
/* count chars in the line */
else {
count=0;
alphacount=0;
while (line[count] != '\0')
{
/* counting chars from 'a' to 'z' */
if (line[count] >= 'a' && line[count] <= 'z')
alphacount++;
/* counting chars from 'A' to 'Z' */
else if (line[count] >= 'A' && line[count] <= 'Z')
alphacount++;
count++;
}
/* adding the nr of chars in line to total */
total += alphacount;
}
}
write(fds[1], &total, sizeof(total)); /* passing total nr of chars to parent */
close(fds[0]); /* closing output part of pipe */
exit(0); /* ending child process */
}
/* parent that sends chars to child-reader and writes out amount */
else
{
/* close(fds[0]); Parent process close output of pipe */
fp = fopen(argv[1], "r");
if (fp == 0) {
perror("Could not read the file.\n");
exit(1);
}
while (fgets(line, MAXLINE, fp) != NULL) {
n = strlen(line);
if (n >= 0) {
line[n]='\0';
if (write(fds[1], line, n) != n) {
perror("write error in pipe");
exit(1);
}
}
else {
perror("problem with fgets");
exit(1);
}
}
int nrofchars;
read_bytes = read(fds[0], &nrofchars, sizeof(nrofchars));
printf("The file has %d nr of chars between a-z\n", nrofchars); //<-- Here it f**ks up, it gives me 1153416175
close(fds[1]); /* closing input part of pipe */
wait(NULL); /* waits for child to read end of file */
}
return 0;
}
}
A pipe is NOT two-way communications, it is unidirectional, fd[0] is the read-end and fd[1] is the write-end.
Your code is using one pipe as bidirectional (you commented out the close of FDs assuming it would become bidirectional) and that's why you are getting the undefined behaviour.
If you want bi-directional you need two pipes :-) OR you can use socketpair(2) to create bidirectional IPC fds.
If you need more help just drop a comment.

monitor bytes in stdin, stdout, and stderr in C

I need to count how many bytes are being sent to a child process through stdin, and how many bytes a child process is writing to stdout and stderr. The child process calls execvp, so I have no way to monitor those stats from within the process itself. My current tactic involves creating 3 additional child processes, one each to monitor each of the std streams through pipes (or in the case of stdin, just reading from stdin).
This tactic seems really frail at best, and I'm doing something strange which makes it so that the processes monitoring stdout/err cannot read from their respective ends of the pipes (and makes them hang indefinitely). Code below.
This creates the three helper child processes, and should allow them to count the stats:
void controles(struct fds *des)
{
int ex[2];
int err[2];
int n_in = 0;
int c_in;
int n_ex = 0;
int c_ex;
int n_err = 0;
int c_err;
pipe(ex);
pipe(err);
/*has two fields, for the write end of the stdout pipe and the stderr pipe. */
des->err = err[1];
des->ex = ex[1];
switch (fork()) {
case 0: /*stdin */
while (read(0, &c_in, 1) == 1)
n_in++;
if (n_in > 0)
printf("%d bytes to stdin\n", n_in);
exit(n_in);
default:
break;
}
switch (fork()) {
case 0: /*stdout */
close(ex[1]);
/*pretty sure this is wrong */
while (read(ex[0], &c_ex, 1) == 1) {
n_ex++;
write(1, &c_ex, 1);
}
if (n_ex > 0)
printf("%d bytes to stdout\n", n_ex);
close(ex[0]);
exit(n_ex);
default:
close(ex[0]);
}
switch (fork()) {
case 0: /*error */
close(err[1]);
/*also probably have a problem here */
while (read(err[0], &c_err, 1) == 1) {
n_err++;
write(2, &c_err, 1);
}
if (n_err > 0)
printf("%d bytes to stderr\n", n_err);
close(err[0]);
exit(n_err);
default:
close(err[0]);
}
}
and this is a code fragment (within the child process) which sets up the two fd's from the fds struct so that the child process should write to the pipe instead of stdin/stderr.
dup2(des.ex, 1);
dup2(des.err, 2);
close(des.ex); close(des.err); /*Is this right?*/
execvp(opts->exec, opts->options); /*sure this is working fine*/
I'm lost, any help would be appreciated.
I think your code could be improved by breaking things apart a little; the accounting and copying routines are all basically the same task, and if you choose to continue down the road with multiple processes, can be written simply:
void handle_fd_pair(char *name, int in, int out) {
char buf[1024];
int count = 0, n;
char fn[PATH_MAX];
snprintf(fn, PATH_MAX - 1, "/tmp/%s_count", name);
fn[PATH_MAX-1] = '\0';
FILE *output = fopen(fn, "w");
/* handle error */
while((n = read(in, buf, 1024)) > 0) {
count+=n;
writen(out, buf, n); /* see below */
}
fprintf(output, "%s copied %d bytes\n", name, count);
fclose(output);
}
Rather than one-char-at-a-time, which is inefficient for moderate amounts of data, we can handle partial writes with the writen() function from the Advanced Programming in the Unix Environment source code:
ssize_t /* Write "n" bytes to a descriptor */
writen(int fd, const void *ptr, size_t n)
{
size_t nleft;
ssize_t nwritten;
nleft = n;
while (nleft > 0) {
if ((nwritten = write(fd, ptr, nleft)) < 0) {
if (nleft == n)
return(-1); /* error, return -1 */
else
break; /* error, return amount written so far */
} else if (nwritten == 0) {
break;
}
nleft -= nwritten;
ptr += nwritten;
}
return(n - nleft); /* return >= 0 */
}
With the helper in place, I think the rest can go more easily. Fork a
new child for each stream, and give the in[0] read-end, out[1] and
err[1] write-ends of the pipes to the child.
All those close() calls in each child are pretty ugly, but trying to
write a little wrapper around an array of all the fds, and exempting the
ones passed in as arguments, also seems like trouble.
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#ifndef PATH_MAX
#define PATH_MAX 128
#endif
void handle_fd_pair(char *name, int in, int out) {
char buf[1024];
int count = 0, n;
char fn[PATH_MAX];
snprintf(fn, PATH_MAX - 1, "/tmp/%s_count", name);
fn[PATH_MAX-1] = '\0';
FILE *output = fopen(fn, "w");
/* handle error */
while((n = read(in, buf, 1024)) > 0) {
count+=n;
writen(out, buf, n); /* see below */
}
fprintf(output, "%s copied %d bytes\n", name, count);
fclose(output);
}
int main(int argc, char* argv[]) {
int in[2], out[2], err[2];
pid_t c1, c2, c3;
pipe(in);
pipe(out);
pipe(err);
if ((c1 = fork()) < 0) {
perror("can't fork first child");
exit(1);
} else if (c1 == 0) {
close(in[0]);
close(out[0]);
close(out[1]);
close(err[0]);
close(err[1]);
handle_fd_pair("stdin", 0, in[1]);
exit(0);
}
if ((c2 = fork()) < 0) {
perror("can't fork second child");
exit(1);
} else if (c2 == 0) {
close(in[0]);
close(in[1]);
close(out[1]);
close(err[0]);
close(err[1]);
handle_fd_pair("stdout", out[0], 1);
exit(0);
}
if ((c3 = fork()) < 0) {
perror("can't fork third child");
exit(1);
} else if (c3 == 0) {
close(in[0]);
close(in[1]);
close(out[0]);
close(out[1]);
close(err[1]);
handle_fd_pair("stderr", err[0], 2);
exit(0);
}
/* parent falls through to here, no children */
close(in[1]);
close(out[0]);
close(err[0]);
close(0);
close(1);
close(2);
dup2(in[0], 0);
dup2(out[1], 1);
dup2(err[1], 2);
system(argv[1]);
exit(1); /* can't reach */
}
It seems to work for toy applications anyway :)
$ ./dup cat
hello
hello
$ ls -l *count
-rw-r--r-- 1 sarnold sarnold 22 2011-05-26 17:41 stderr_count
-rw-r--r-- 1 sarnold sarnold 21 2011-05-26 17:41 stdin_count
-rw-r--r-- 1 sarnold sarnold 22 2011-05-26 17:41 stdout_count
$ cat *count
stderr copied 0 bytes
stdin copied 6 bytes
stdout copied 6 bytes
I think it is worth pointing out that you could also implement this
program with only one process, and use select(2) to determine which
file descriptors need reading and writing.
Overall, I think you're on the right track.
One problem is that in your stderr and stdout handlers, which should be picking bytes off the pipe and writing to the real stderr/stdout, you're writing back to the same pipe.
It would also be helpful to see how you start the child processes. You gave a code fragment to close the real stderr and then dup2 the pipe fd back to stderr's fd, but you probably want this in the parent process (after fork and before exec) so that you don't need to modify the source code to the child process. You should be able to do this generically all from the parent.

Resources