How to implement successive pipes in C? - c

I work on a program that downloads the best (marked as '(best)') video format using youtube-dl. It reads a command-line argument then it launches a child process 'youtube-dl -F [url]'. Then it passes the line with '(best)' to a routine that extracts the format and executes, again as a child, 'youtube-dl -f [best format] [url]'. The problem is it works only for the first link. Maybe a child doesn't write to a pipe properly, maybe a parent doesn't read from the pipe. I'm lost. Thanks for your help.
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#define LINE_LEN 255
enum { ERROR=-1, CHILD };
void error(char *msg)
{
fprintf(stderr, "%s: %s\n", msg, strerror(errno));
exit(1);
}
void dl_best(char *format, char *url)
{
char fmt[4];
int status;
pid_t pid;
for (int i = 0; *format != ' '; format++, i++)
fmt[i] = *format;
fmt[3] = '\0';
switch(pid = fork()) {
case ERROR:
error("Failed to pipe in dl_best");
break;
case CHILD:
if (execlp("youtube-dl", "youtube-dl", "-f", fmt, url, NULL) == -1)
error("Failed to execle() in dl_best");
break;
default:
if (waitpid(pid, &status, 0) == -1)
error("Waitpid failed in dl_best()");
break;
}
}
void get_format(char *url)
{
pid_t pid;
int fd[2], status;
char line[LINE_LEN];
if (pipe(fd) == -1)
error("Pipe failed");
if ((pid = fork()) == ERROR) {
error("Failed to create a child precess in get_format()");
} else if (pid == CHILD) {
if (close(fd[0]) == -1)
error("Child failed to close reading pipe");
if (dup2(fd[1],1) == -1)
error("Dup2 failed in get_format()");
if (execlp("youtube-dl", "youtube-dl", "-F", url, NULL) == -1)
error("Failed to execute get_formats");
} else { //parent
if (close(fd[1]) == -1)
error("Parent failed to close writing pipe");
if (dup2(fd[0],0) == -1)
error("Dup2 failed in get_format()");
if (waitpid(pid, &status, 0) == -1)
error("Waitpid failed in get_format()");
while (fgets(line, LINE_LEN, stdin)) {
if (strstr(line, "(best)") != NULL)
dl_best(line, url);
}
if (close(fd[0]) == -1)
error("Parent failed to close reading pipe");
}
}
int main(int argc, char *argv[])
{
//int fd[2], status, argc_cp = argc;
//dl_best("22 ", argv[--argc]);
while (--argc)
get_format(argv[argc]);
return 0;
}

if (dup2(fd[0],0) == -1)
error("Dup2 failed in get_format()");
if (waitpid(pid, &status, 0) == -1)
error("Waitpid failed in get_format()");
while (fgets(line, LINE_LEN, stdin)) {
if (strstr(line, "(best)") != NULL)
dl_best(line, url);
}
This is a common mistake when piping from child to parent in C. With this code, the child will fill up the pipe buffer and then block waiting for the parent to drain the pipe, but the parent won't ever do that because it's blocked waiting for the child to exit, so the overall program will deadlock.
(You won't hit the deadlock for invocations where the child's complete output is smaller than the size of the pipe buffer. This may be why it appears to work for the first command line argument only.) You need to read all of the data produced by the child before you wait for the child to terminate. For this program, that's as simple as moving the waitpid and its conditional below the while loop.
Your repeated replacement of file descriptor 0 may also be running foul of the C99 rule that end-of-file is a sticky condition. (This may also explain why it appears to work for the first command line argument only.) You could address that by calling clearerr after the dup2, but it would be better not to mess with stdin at all. Instead, use fdopen to convert fd[0] into a FILE.
Putting both of those fixes together, your parent-side code in get_format should look something like this:
} else { //parent
if (close(fd[1]) != 0)
error("Parent failed to close writing pipe");
FILE *fp = fdopen(fd[0], "rt");
if (!fp)
error("Parent failed to allocate a FILE");
while (fgets(line, LINE_LEN, fp)) {
if (strstr(line, "(best)") != NULL)
dl_best(line, url);
}
if (fclose(fp) != 0)
error("Parent failed to close reading pipe");
if (waitpid(pid, &status, 0) != pid)
error("Waitpid failed in get_format()");
}
(Note that fd[0] is closed together with fp, by the fclose; it is not necessary (in fact, it would be wrong) to call close on it.)
I would also consider allowing the dl_best child to run asynchronously - that is, have dl_best return the child PID rather than waiting for it itself, and then get_format waits for both children after its while loop - but that's an optimization, not a bugfix.

Related

pipe() is either not writing correctly, not reading correctly, or both

I am trying to get a pipe working between two children where they can communicate with each other, but it doesn't seem to be reading/writing anything.
My output looks like this:
(Parent pid:1)
(First child pid:2)
(Second child pid:3)
What is your sibling's pid? Answer: 0
this is the code that i have for it, but it all looks like it should work.
EDIT: i added some checks to see if the pipe, write, or read were failing, but not getting any of those errors. it seems like the pipe is working, but still shows a 0 for the answer
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
int rc = fork();
int pipeArr[2];
if(pipe(pipeArr) == -1) {
printf("An error ocurred when opening pipe\n");
}
if(rc < 0) {
// fork failed
fprintf(stderr, "fork failed\n");
exit(1);
}
else if(rc == 0) {
// child (new process)
close(pipeArr[0]); // this is the output, not needed here
int pid = (int) getpid();
printf("(First child pid:%d)\n", pid);
if(write(pipeArr[1], &pid, sizeof(int)) == -1) {
printf("An error ocurred when writing to the pipe\n");
}
close(pipeArr[1]); // this is the input, not needed anymore
}
else {
// parent goes down this path (main)
printf("(Parent pid:%d)\n", (int) getpid());
int rc2 = fork();
if(rc2 < 0) {
// fork failed
fprintf(stderr, "fork failed\n");
exit(1);
}
else if(rc2 == 0) {
// child (new process)
close(pipeArr[1]); // this is the input, not needed here
int pid;
if(read(pipeArr[0], &pid, sizeof(int)) == -1) {
printf("An error ocurred when reading from the pipe\n");
}
close(pipeArr[0]); // this is the output, not needed anymore
printf("(Second child pid:%d)\n", (int) getpid());
printf("What is the first child's pid? Answer: %d\n", pid);
}
else {
// nothing needed here
}
}
return 0;
}

pipe function returning random characters

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
int main(void){
//Variables, p[2] for each end of the pipe. nbytes to read pipe return value SUCCESS or FAILURE. pid_t to hold pid of fork process.
// buffer to hold response from the child process.
int p[2], nbytes;
pid_t childpid;
char string[] = "Hello, World!\n";
char buffer[80];
//Declaration of pipe
pipe(p);
//Error handling.
if(((childpid = fork()) == -1) || (pipe(p) == -1))
{
perror("fork");
exit(1);
}
//Child process sends message to paprent.
if(childpid == 0)
{
/* Child process closes up input side of pipe */
close(p[0]);
/* Send "string" through the output side of pipe */
write(p[1], string, (strlen(string)+1));
exit(0);
}
else
{
/* Parent process closes up output side of pipe */
close(p[1]);
/* Read in a string from the pipe */
nbytes = read(p[0], buffer, sizeof(buffer));
printf("Received string: %s", buffer);
}
return(0);
}
Output > Received string: #�=zJ
The point of the exercise is to have a child process send a message through a pipe to the parent process and the parent returns the result. This exact code worked the first time I ran it, but then when I tried to run it a second time it started to return seemingly random characters each time. I tried to copy my buffer to another variable but then it was empty. Is the pipe actually not function the way I think it is? What am I doing wrong?
You first create a pipe with pipe(p); and then you create another with ... || (pipe(p) == -1)) Is that deliberate?
2nd Pipe was causing an issue.
You have:
pipe(p);
//Error handling.
if(((childpid = fork()) == -1) || (pipe(p) == -1))
{
perror("fork");
exit(1);
}
This creates two pipes — one in the line pipe(p); and the second in the condition if(((childpid = fork()) == -1) || (pipe(p) == -1)). This is wasteful at best. Moreover, the second pipe is after the fork(), so the parent and child processes don't access the same pipe any more — you overwrote the one created before the fork() which they do share. Test the result of pipe() before calling fork() and remove the extra condition in the if test:
if (pipe(p) != 0)
{
perror("pipe");
exit(1);
}
if ((childpid = fork()) < 0)
{
perror("fork");
exit(1);
}
Get used to testing for errors and writing appropriate code to handle them. It will be a major part of your life as a C programmer.
Later on in the code, you have:
{
/* Parent process closes up output side of pipe */
close(p[1]);
/* Read in a string from the pipe */
nbytes = read(p[0], buffer, sizeof(buffer));
printf("Received string: %s", buffer);
}
You need to heed the value of nbytes. Since it is an int, you could use:
printf("Received %d bytes: [%.*s]\n", nbytes, nbytes, buffer);
This limits the output to what was read, and reports 0 if that's what it gets. I suppose you should also check for -1 in nbytes before using it in the printf() statement:
if (nbytes < 0)
{
fprintf(stderr, "failed to read from pipe descriptor %d\n", p[0]);
// Or perror("read");
// Should you exit here with a non-zero status?
}
else
printf("Received %d bytes: [%.*s]\n", nbytes, nbytes, buffer);
Note: errors are reported on stderr; perror() does that automatically.
The problem is that you create two pipes when you really only need to check the first for errors:
// Declaration of pipe
if(pipe(p) == -1) { // check for error here
perror("pipe");
exit(1);
}
// Error handling.
if((childpid = fork()) == -1) { // and don't create another pipe here
perror("fork");
exit(1);
}
You should also check the return values from write and read. They may not write or read the full string in one go.

Recursive piping in Unix again

I know this topic came up already several times, but I'm still stuck at one point.
I need to write a program that emulates cmd1 | cmd2 | cmd3 ... piping.
My code is here: http://ideone.com/fedrB8
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
void pipeline( char * ar[], int pos, int in_fd);
void error_exit(const char*);
static int child = 0; /* whether it is a child process relative to main() */
int main(int argc, char * argv[]) {
if(argc < 2){
printf("Usage: %s option (option) ...\n", argv[0]);
exit(1);
}
pipeline(argv, 1, STDIN_FILENO);
return 0;
}
void error_exit(const char *kom){
perror(kom);
(child ? _exit : exit)(EXIT_FAILURE);
}
void pipeline(char *ar[], int pos, int in_fd){
if(ar[pos+1] == NULL){ /*last command */
if(in_fd != STDIN_FILENO){
if(dup2(in_fd, STDIN_FILENO) != -1)
close(in_fd); /*successfully redirected*/
else error_exit("dup2");
}
execlp(ar[pos], ar[pos], NULL);
error_exit("execlp last");
}
else{
int fd[2];
pid_t childpid;
if ((pipe(fd) == -1) || ((childpid = fork()) == -1)) {
error_exit("Failed to setup pipeline");
}
if (childpid == 0){ /* child executes current command */
child = 1;
close(fd[0]);
if (dup2(in_fd, STDIN_FILENO) == -1) /*read from in_fd */
perror("Failed to redirect stdin");
if (dup2(fd[1], STDOUT_FILENO) == -1) /*write to fd[1]*/
perror("Failed to redirect stdout");
else if ((close(fd[1]) == -1) || (close(in_fd) == - 1))
perror("Failed to close extra pipe descriptors");
else {
execlp(ar[pos], ar[pos], NULL);
error_exit("Failed to execlp");
}
}
close(fd[1]); /* parent executes the rest of commands */
close(in_fd);
pipeline(ar, pos+1, fd[0]);
}
}
It works completely fine for up to 3 commands, but when it comes to 4 and more it doesnt any more and after hours of analysing, I still cant get where the problem is.
Example:
./prog ls uniq sort head
gives:
sort: stat failed: -: Bad file descriptor
Not an expert, but it seems the following line is the problem:
((close(fd[1]) == -1) || (close(in_fd) == - 1))
Try not to close in_fd there.
I think, the parent is trying to close the same fd that is closed by child.
When you use dup2() you do not need to close the files as dup2() already closes the file.

How to loop through stdin & pipe output to a child execl command in C?

I have been trying to figure out how to loop through stdin from a file, then send it to a child process who sorts int using execl(). The code below works in that it takes the file & sorts the lines, but I am not seeing the "end of sentence" debug string I have added. Somehow this part of the code is being bypassed. I could use some help understanding the flow of data as it comes in from the file, then gets printed out to the screen.
int main(int argc, char *argv[])
{
pid_t p;
int status;
int fds[2];
FILE *writeToChild;
char word[50];
if(pipe(fds) == -1) {
perror("Error creating pipes");
exit(EXIT_FAILURE);
}
switch(p = fork()) {
case 0: //this is the child process
close(fds[1]); //close the write end of the pipe
execl("/usr/bin/sort", "sort", (char *) 0);
break;
case -1: //failure to fork case
perror("Could not create child");
exit(EXIT_FAILURE);
default: //this is the parent process
close(fds[0]); //close the read end of the pipe
writeToChild = fdopen(fds[1], "w");
wait(&status);
break;
}
while (fscanf(stdin, "%s", word) != EOF) {
//the below isn't being printed. Why?
fprintf(writeToChild, "%s end of sentence\n", word);
}
return 0;
}
Your primary problem is that you have the wait() in the wrong place. You wait for the child to die before you've written anything to it. You also have a secondary problem that don't redirect the read end of the pipe to the sort process's standard input.
You're not closing fds[0] in the child; cleanliness suggests that you should. You do need to fclose(writeToChild) before waiting; the sort won't stop until the parent has closed the pipe to the child.
These changes (and a few other ones) lead to:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
pid_t p;
int status;
int fds[2];
FILE *writeToChild;
char word[50];
if (pipe(fds) == -1)
{
perror("Error creating pipes");
exit(EXIT_FAILURE);
}
switch (p = fork())
{
case 0: //this is the child process
close(fds[1]); //close the write end of the pipe
dup2(fds[0], 0);
close(fds[0]);
execl("/usr/bin/sort", "sort", (char *) 0);
fprintf(stderr, "Failed to exec sort\n");
exit(EXIT_FAILURE);
case -1: //failure to fork case
perror("Could not create child");
exit(EXIT_FAILURE);
default: //this is the parent process
close(fds[0]); //close the read end of the pipe
writeToChild = fdopen(fds[1], "w");
break;
}
if (writeToChild != 0)
{
while (fscanf(stdin, "%49s", word) != EOF)
{
//the below isn't being printed. Why?
fprintf(writeToChild, "%s end of sentence\n", word);
}
fclose(writeToChild);
}
wait(&status);
return 0;
}

Looping an unnamed pipe in C

Ok, I searched for this one but couldn't find it. Apologize if it's already been answered before.
Basically I have a program for class that creates two unnamed pipes and uses them to communicate between the parent and child process. The command is passed from the parent to the child, where the child executes it and returns a Success/Error message to the parent. Parent then prints out the Success/Error message. Easy enough, I have that working. Problem is now I need to loop it until the user gives the "exit" command. I think I need a while loop, but after experimenting with placement the program still only runs once and then exits. Here's what I have. Hopefully this makes sense, I left out the processing portion of the code since that part works (like I said, for a class) but if there's anything that doesn't make sense I'll clarify. Thanks in advance for any help.
while (strcmp(cmd,"exit") != 0)
{
/* Create Pipe P to pass command from the
parent process to the child process and check for errors.*/
pipe(p);
/*Create Pipe Q to pass command from the
child process to the parent process and check for errors. */
pipe(q);
/* Create child process */
pid = fork();
switch(pid){
case -1: /* fork failed */
perror("main: fork");
exit(1);
case 0: /* Child process */
/*****************************************
Stuff being executed in the child process
*****************************************/
default: /* Parent process */
printf ("Choose from the following list of commands.\n");
printf ("display\n");
printf ("chars\n");
printf ("lines\n");
printf ("words\n");
printf ("find\n");
printf ("exit\n");
fgets (cmd,10,stdin);
if ((c = strchr(cmd, '\n')) != NULL)
{
*c = '\0';
}
/**********************************
Pipes being opened and closed for
communication between parent and child
**************************************/
break;
}
return 0;
}
}
You need to create the child before you enter the loop.
You also need to be a lot more careful with the plumbing. The main (parent) process must close the ends of the pipes that it won't use, and the child likewise (noting that the child closes the opposite ends from the parent). Of course, if the child is reading standard input and writing on standard output, then you have to arrange for the pipes to be duplicated to the correct descriptors, and then the child closes all the descriptors returned by the pipe() call.
Try this for size - you'd have to expand be_childish() to do the real work...
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
static void be_childish(int p[2], int q[2]);
static void be_parental(int p[2], int q[2]);
static void err_exit(const char *fmt, ...)
{
int errnum = errno;
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
fprintf(stderr, "\n%d: %s\n", errnum, strerror(errnum));
exit(1);
}
int main(void)
{
int p[2]; /* Pipe to child */
int q[2]; /* Pipe to parent */
pid_t pid;
if (pipe(p) != 0)
err_exit("Failed to create pipe 1");
if (pipe(q) != 0)
err_exit("Failed to create pipe 2");
if ((pid = fork()) < 0)
err_exit("Failed to create child process");
else if (pid == 0)
be_childish(p, q);
else
be_parental(p, q);
return(0);
}
static int prompt(char *buffer, size_t buflen)
{
char *c;
printf("Choose from the following list of commands.\n");
printf("display\n");
printf("chars\n");
printf("lines\n");
printf("words\n");
printf("find\n");
printf("exit\n");
if (fgets(buffer, buflen, stdin) == 0)
return EOF;
if ((c = strchr(buffer, '\n')) != NULL)
*c = '\0';
if (strcmp(buffer, "exit") == 0)
return EOF;
return 0;
}
static void be_parental(int p[2], int q[2])
{
char cmd[10] = "";
if (close(p[0]) != 0 || close(q[1]) != 0)
err_exit("Parent: failed to close pipe");
while (prompt(cmd, sizeof(cmd)) != EOF)
{
char buffer[4096];
ssize_t nbytes;
if (write(p[1], cmd, strlen(cmd)) != (ssize_t)strlen(cmd))
err_exit("Write to child failed");
if ((nbytes = read(q[0], buffer, sizeof(buffer))) < 0)
err_exit("Read from child failed");
if (nbytes == 0)
return;
printf("%s\n", buffer);
}
}
static void be_childish(int p[2], int q[2])
{
char cmd[10] = "";
ssize_t nbytes;
if (close(p[1]) != 0 || close(q[0]) != 0)
err_exit("Child: failed to close pipe");
while ((nbytes = read(p[0], cmd, sizeof(cmd))) > 0)
{
char buffer[4096];
cmd[nbytes] = '\0';
/* Process command */
strcpy(buffer, "Response from child: ");
strcat(buffer, cmd);
if (write(q[1], buffer, strlen(buffer)) != (ssize_t)strlen(buffer))
err_exit("Write to parent failed");
}
}

Resources