Scanf through pipe lock - c

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++;
}
}

Related

how to use pipe and execvp in c via linux

I'm trying to use pipe command and I can't understand how to.
I've a lot of versions but I can't make it work.
first of all the hierarchy:
main prog - nicecmp - that will execute the child prog and print the result
child prog - loopcmp - that will execute his child prog and get the returned value and send it back to the parent in nicecmp.
loopcmp's childs - lencmp/lexcmp - both prog will be executed in loopcmp and return value between -1 to 2. (100% works)
shortly, I need to create a pipe and a new process that will run new program (loopcmp - added in the end of the code) using execvp, and I need to print the res of the loopcmp in the parent.
I can send it directly from the prog that I executed and I can use WEXITSTATUS in the child after the end of the loopcmp.
what's the right way to do so (from the progrem execution or after that I've returned from the loopcmp)
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#define LINELEN (80)
#define READFROM ("./loopcmp")
typedef enum { eLexcmp, eLencmp, eNumOfCmp } eCmpstr;
const char* cmpstr[eNumOfCmp] = { "./lexcmp", "./lencmp" };
int lencmp(const char *str1, const char *str2);
int lexcmp(const char *str1, const char *str2);
char *mygets(char *buf, int len);
int mygeti();
int main(int argc, char *argv[])
{
char str1[LINELEN + 1];
char str2[LINELEN + 1];
int index, rc, status, res;
int pfd[2];/* Pipe file descriptors */
if (pipe(pfd) == -1) /* Create pipe */
exit(-2); // pipe failed !
char* myargs[4];
myargs[0]=strdup(READFROM);
while (1)
{
printf("Please enter first string:\n");
if (mygets(str1, LINELEN) == NULL)
break;
printf("Please enter second string:\n");
if (mygets(str2, LINELEN) == NULL)
break;
myargs[2] = strdup(str1);
myargs[3] = strdup(str2);
do {
printf("Please choose:\n");
for (int i = 0; i < eNumOfCmp; i++)
printf("%d - %s\n", i, cmpstr[i]);
index = mygeti();
} while ((index < 0) || (index >= eNumOfCmp));
myargs[1] = strdup(cmpstr[index]);
rc = fork();
if (rc < 0) // fork failed !
{
printf("fork failed\n");
return -2;
}
else if (rc == 0) { // child !
if (close(pfd[1]) == -1) /* Write end is unused */
exit(-2);
/* Duplicate stdin on read end of pipe; close duplicated descriptor */
if (pfd[0] != STDIN_FILENO) { /* Defensive check */
if (dup2(pfd[0], STDIN_FILENO) == -1)
exit(-2);
if (close(pfd[0]) == -1)
exit(-2);
}
execvp(myargs[0],myargs);
}
else { // parent
if (close(pfd[1]) == -1) /* Write end is unused */
exit(-2);
/* Duplicate stdin on read end of pipe; close duplicated descriptor */
if (pfd[0] != STDIN_FILENO) { /* Defensive check */
if (dup2(pfd[0], STDIN_FILENO) == -1)
exit(-2);
if (close(pfd[0]) == -1)
exit(-2);
}
read(pfd[0], &res, sizeof(int));
printf("%d\n", res);
if (close(pfd[0]) == -1)
exit(-2);
}
}
return 0;
}
loopcmp ->
int main(int argc, char *argv[])
{
int status,rc,res = 0;
if (argc != 4)
{
return -1;
}
char* myargs[3];
for(int i=0;i<3;i++){
myargs[i]=argv[i+1];
}
rc = fork();
if (rc < 0) //fork failed
{
return -2;
}
else if (rc == 0) //I'm the child
{
if(execvp(myargs[1], myargs)==-1)
return -2;
}
else // parent
{
wait(&status);
res = WEXITSTATUS(status);
if(res ==254) // invalid file path ! (254== -2)
return -2 ;
}
write(fileno(stdout),&res,sizeof(int));
return res;
}

How to synchronize read and write in the pipe in 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);

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;
}

Can I use STDIN for IPC?

Can I use standard input for interprocess communication? I wrote the following gnu c code as an experiment, but the program hangs waiting for input after printing the character defined as val. Neither a newline nor fflush in the sending process seem to alleviate the problem.
#include <unistd.h>
#include <stdio.h>
int main(void) {
char val = '!';
int proc = fork();
if (proc < 0)
return -1;
if (proc == 0) {
write(0, &val, 1);
return 0;
}
else {
char ch[2] = { 0 };
read(0, ch, 1);
printf("%s\n", ch);
return 0;
}
return -2;
}
You can use pipe for IPC. Now if you want to use STDIN_FILENO and STDOUT_FILENO it would look like this:
#include <unistd.h>
#include <stdio.h>
int main(void) {
char val = '!';
int filedes[2];
pipe(filedes);
int proc = fork();
if (proc < 0)
return -1;
if (proc == 0) {
close(1);
dup(filedes[1]);
close(filedes[0]);
close(filedes[1]);
write(1, &val, 1);
return 0;
}
else {
char ch[2] = { 0 };
close(0);
dup(filedes[0]);
close(filedes[0]);
close(filedes[1]);
read(0, ch, 1);
printf("%s\n", ch);
return 0;
}
return -2;
}
Combination close(x) and dup(filedes[x]) closes STDOUT/STDIN makes copy of filedes[x] into first available descriptor, what you just closed. As suggested by Jonathan example is now closing both filedes ends and without any doubts is using STDIN/STDOUT.

Resources