I have 2 processes (an 'ls' process and a 'grep'). I'm using pipe to communicate between both of them. But the grep process is unable to read from the pipe. Could you help me figure out why so?
Here is my code
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
int pipe_fd[2];
int main()
{
pid_t p1,p2;
char *prog1_argv[4];
char *prog2_argv[2];
/* Build argument list */
prog1_argv[0] = "ls";
prog1_argv[1] = "-l";
prog1_argv[2] = "/";
prog1_argv[3] = NULL;
prog2_argv[0] = "grep";
prog2_argv[1] = "s";
prog2_argv[1] = NULL;
if (pipe(pipe_fd) < 0)
{
printf ("pipe failed");
}
p1 = fork();
if(p1 == 0)
{
printf("in child\n");
close(pipe_fd[0]);
if(dup2(pipe_fd[1],1)<0)
{
printf("dup failed:%d\n",errno);
}
close(pipe_fd[1]);
if(execvp (prog1_argv[0], prog1_argv)<0)
printf("exec failed");
}
if(p1>0)
{
printf("im in parent\n");
waitpid(p1,NULL,0);
printf("parent: child exited. Now test the pipe\n");
close(pipe_fd[1]);
if(dup2(pipe_fd[0],0)<0)
{
printf("dup failed:%d\n",errno);
}
close(pipe_fd[0]);
if(execvp (prog2_argv[0], prog2_argv)<0)
printf("exec failed");
}
}
Fundamentally, you should not be waiting for the ls to die before running the grep.
The ls command might generate so much data that it can't all be stored in the pipe, so the ls command will block until the other process reads from the pipe, but the other process is waiting for ls to complete before it tries to read anything from the pipe. This is a deadlock.
Also, by waiting like that, you enforce serial execution, which throws away the benefits of multiple cores.
There are a number of minor improvements you should make. There are various points at which you report errors. Errors should be reported on the standard error stream (stderr), not on stdout. You should also ensure the program does not continue after at least some of those errors.
You don't have to test the return value from any of the exec*() system calls. If the function returns, it failed. And again, you should ensure that the process exits after that. In this program, it doesn't matter that the child continues; in many programs, not exiting would lead to chaos (two processes trying to read standard input at the same time, for example).
There's no need for pipe_fd to be a global variable. Do make sure all your messages end with a newline, please. You didn't include <sys/wait.h> so you were working without a prototype in scope for the waitpid() function — that's generally a bad idea. You should set your compiler to fussy so it demands that every function has a prototype in scope before it is used or defined. You can initialize the argument lists in the definitions:
char *prog1_argv[] = { "ls", "-l", "/", NULL };
char *prog2_argv[] = { "grep", "s", NULL };
This has the crucial beneficial side-effect of not zapping prog_argv2[1] with a NULL pointer (as noted by Matthias in his answer. I also removed the sizes of the arrays; the second one was dimensioned at 2 and needed to be 3, but when you initialize like this, the compiler does the counting.
One thing you did correctly that was important to do correctly is ensure that the pipe file descriptors were all closed.
This works correctly for me:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
int main(void)
{
pid_t p1;
int pipe_fd[2];
char *prog1_argv[] = { "ls", "-l", "/", NULL };
char *prog2_argv[] = { "grep", "s", 0 };
if (pipe(pipe_fd) < 0)
{
fprintf(stderr, "pipe failed:%d\n", errno);
exit(1);
}
p1 = fork();
if (p1 == 0)
{
printf("In child\n");
close(pipe_fd[0]);
if (dup2(pipe_fd[1], 1) < 0)
{
fprintf(stderr, "dup failed:%d\n", errno);
exit(1);
}
close(pipe_fd[1]);
execvp(prog1_argv[0], prog1_argv);
fprintf(stderr, "exec failed:%d\n", errno);
exit(1);
}
if (p1 > 0)
{
printf("In parent\n");
close(pipe_fd[1]);
if (dup2(pipe_fd[0], 0) < 0)
{
fprintf(stderr, "dup failed:%d\n", errno);
exit(1);
}
close(pipe_fd[0]);
execvp(prog2_argv[0], prog2_argv);
fprintf(stderr, "exec failed:%d\n", errno);
exit(1);
}
fprintf(stderr, "Fork failed:%d\n", errno);
return(1);
}
You override your grep's argument. Try:
int main()
{
pid_t p1,p2;
char *prog1_argv[4];
char *prog2_argv[3];
/* Build argument list */
prog1_argv[0] = "ls";
prog1_argv[1] = "-l";
prog1_argv[2] = "/";
prog1_argv[3] = NULL;
prog2_argv[0] = "grep";
prog2_argv[1] = "s";
prog2_argv[2] = NULL;
// ...
Related
In the code below, I am simply trying to send a file via stdin to a child process which will exec the cat OS command. The code compiles fine. Here is how I call it from the command line:
$ ./uniquify < words.txt
However, when I run it I get a seg fault error. I am really having a hard time understanding how the flow if information is supposed to work through pipes to children. I am trying to make the code as simple as possible, so I can understand it, but it is not yet making sense. Any help would be appreciated.
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#define NUM_CHILDREN 2
int main(int argc, char *argv[])
{
pid_t catPid;
int writeFds[NUM_CHILDREN];
int catFds[2];
int c = 0;
FILE *writeToChildren[NUM_CHILDREN];
//create a pipe
(void) pipe(catFds);
if ((catPid = fork()) < 0) {
perror("cat fork failed");
exit(1);
}
//this is the child case
if (catPid == 0) {
//close the write end of the pipe
close(catFds[1]);
//close stdin?
close(0);
//duplicate the read side of the pipe
dup(catFds[0]);
//exec cat
execl("/bin/cat", "cat", (char *) 0);
perror("***** exec of cat failed");
exit(20);
}
else { //this is the parent case
//close the read end of the pipe
close(catFds[0]);
int p[2];
//create a pipe
pipe(p);
writeToChildren[c] = fdopen(p[1], "w");
} //only the the parent continues from here
//close file descriptor so the cat child can exit
close(catFds[1]);
char words[NUM_CHILDREN][50];
//read through the input file two words at a time
while (fscanf(stdin, "%s %s", words[0], words[1]) != EOF) {
//loop twice passing one of the words to each rev child
for (c = 0; c < NUM_CHILDREN; c++) {
fprintf(writeToChildren[c], "%s\n", words[c]);
}
}
//close all FILEs and fds by sending and EOF
for (c = 0; c < NUM_CHILDREN; c++) {
fclose(writeToChildren[c]);
close(writeFds[c]);
}
int status = 0;
//wait on all children
for (c = 0; c < (NUM_CHILDREN + 1); c++) {
wait(&status);
}
return 0;
}
Since your question seems to be about understanding how pipes and forks work, I hope below programs can help you. Please notice that this is for illustration only. It wouldn't qualify for commercial implementation, but I wanted to keep it short!
You can compile the two programs as follows:
cc pipechild.c -o pipechild
cc pipeparent.c -o pipeparent
Then execute with ./pipeparent
pipeparent.c source
/* pipeparent.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#define MESSAGE "HELLO!\n"
#define INBUFSIZE 80
#define RD 0 // Read end of pipe
#define WR 1 // Write end of pipe
int main(void)
{
int ptocpipe[2]; // Parent-to-child pipe
int ctoppipe[2]; // Chile-to-parent pipe
pid_t childpid; // Process ID of child
char inbuf[80]; // Input from child
int rd; // read() return
int rdup; // dup():ed stdin for child
int wdup; // dup():ed stdout for child
char *eol; // End of line
// Create pipe for writing to child
if (pipe(ptocpipe) < 0) {
fprintf(stderr, "pipe(ptocpipe) failed!\n");
return 2;
}
// Create pipe for writing back to parent
if (pipe(ctoppipe) < 0) {
fprintf(stderr, "pipe(ctoppipe) failed!\n");
return 2;
}
// Verify that one of the pipes are working by filling it first
// in one end and then reading it from the other. The OS will
// buffer the contents for us. Note, this is not at all necessary,
// it's just to illustrate how it works!
write(ptocpipe[WR], MESSAGE, strlen(MESSAGE));
read(ptocpipe[RD], inbuf, INBUFSIZE);
if (strlen(inbuf) != strlen(MESSAGE)) {
fprintf(stderr, "Failed to flush the toilet!\n");
return 6;
} else {
printf("Wrote to myself: %s", inbuf);
}
// Next, we want to launch some interactive program which
// replies with exactly one line to each line we send to it,
// until it gets tired and returns EOF to us.
// First, we must clone ourselves by using fork(). Then the
// child process must be replaced by the interactive program.
// Problem is: How do we cheat the program to read its stdin
// from us, and send its stdout back to us?
switch (childpid = fork()) {
case -1: // Error
fprintf(stderr, "Parent: fork() failed!\n");
return 3;
case 0: // Child process
// Close the ends we don't need. If not, we might
// write back to ourselves!
close(ptocpipe[WR]);
close(ctoppipe[RD]);
// Close stdin
close(0);
// Create a "new stdin", which WILL be 0 (zero)
if ((rdup = dup(ptocpipe[RD])) < 0) {
fprintf(stderr, "Failed dup(stdin)\n");
return 4;
}
// Close stdout
close(1);
// Create a "new stdout", which WILL be 1 (one)
if ((wdup = dup(ctoppipe[WR])) < 0) {
fprintf(stderr, "Failed dup(stdout)\n");
return 5;
}
// For debugging, verify stdin and stdout
fprintf(stderr, "rdup: %d, wdup %d\n", rdup, wdup);
// Overload current process by the interactive
// child process which we want to execute.
execlp("./pipechild", "pipechild", (char *) NULL);
// Getting here means we failed to launch the child
fprintf(stderr, "Parent: execl() failed!\n");
return 4;
}
// This code is executed by the parent only!
// Close the ends we don't need, to avoid writing back to ourself
close(ptocpipe[RD]);
close(ctoppipe[WR]);
// Write one line to the child and expect a reply, or EOF.
do {
write(ptocpipe[WR], MESSAGE, strlen(MESSAGE));
if ((rd = read(ctoppipe[RD], inbuf, INBUFSIZE)) > 0) {
// Chop off ending EOL
if ((eol = rindex(inbuf, '\n')) != NULL)
*eol = '\0';
printf("Parent: Read \"%s\" from child.\n", inbuf);
}
} while (rd > 0);
fprintf(stderr, "Parent: Child done!\n");
return 0;
}
pipechild.c source
/* pipechild.c
* Note - This is only for illustration purpose!
* To be stable, we should catch/ignore signals,
* and use select() to read.
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <strings.h>
#include <string.h>
#define MAXCOUNT 5 // Maximum input lines toread
#define INBUFSIZE 80 // Buffer size
int main(void)
{
char buff[INBUFSIZE];
int remains = MAXCOUNT;
pid_t mypid;
char *eol;
mypid = getpid(); // Process-ID
fprintf(stderr, "Child %d: Started!\n", mypid);
// For each line read, write one tostdout.
while (fgets(buff, INBUFSIZE, stdin) && remains--) {
// Chop off ending EOL
if ((eol = rindex(buff, '\n')) != NULL)
*eol = '\0';
// Debug to console
fprintf(stderr, "Child %d: I got %s. %d remains.\n",
mypid, buff, 1 + remains);
// Reply to parent
sprintf(buff, "Child %d: %d remains\n", mypid, 1 + remains);
write(1, buff, strlen(buff));
}
fprintf(stderr, "Child %d: I'm done!\n", mypid);
return 0;
}
I have a simple setup for a fork and pipe that I have used before. But this time around I'm getting a SIGPIPE in my write call. Here's the code
int fd[2];
int pid;
if (pipe(fd) == -1) {
perror("pipe init error");
exit(1);
}
// signal(SIGPIPE, SIG_IGN);
if ((pid = fork()) < -1) {
perror("fork error"); exit(1);
}
// parent
else if (pid > 0) {
close(fd[0]);
write(fd[1], "WHAT", MAXWORD); //SIGPIPE here
close(fd[1]);
int status;
wait(&status);
}
// child
else {
close(fd[1]);
// void foo(char *dirname, int in, int out);
// foo takes a path, reads from fd 'in' and outputs to 'fd' out
foo("./some/path", fd[0], 1);
close(fd[0]);
}
Here's function foo:
void foo(char *dirname, int in, int out){
int string_length;
char word[MAXWORD];
// to get rid of \n
char* sep;
sep = malloc(sizeof(char));
// read from piped stdin until it's closed
while ((string_length = read(in, word, MAXWORD)) > 0){
// get rid of \n
sep = strchr(word, '\n');
*sep = '\0';
printf("THe word is: %s\n", word);
}
}
If you get SIGPIPE when you write on a pipe, it means there is no process that can read from the pipe: neither the current process (you've close the read end of the pipe — which is good; you'd be deadlocked instead of dead if you'd not closed it) nor the other (child) process.
Since you've not shown what the function foo() does, we can't tell you any more about what's wrong.
Now that foo() has been added, it is not clear what's up. There are issues, but most are not show stoppers.
Argument dirname is unused.
Argument out is unused.
You leak the memory allocated to sep in the loop.
You do not ensure that the string read from the pipe is null terminated. This could lead to crashes, which in turn would lead to writes failing.
I suspect item 4 is the immediately critical issue; the others are more matters of tidiness.
I note that in the main code, you have:
write(fd[1], "WHAT", MAXWORD); //SIGPIPE here
Unless MAXWORD is either 4 or 5, you are on a losing path; you should only write 4 or 5 characters.
Combined with the read()...the read will attempt to read MAXWORD bytes but might get fewer. However, there's no sign that the data written contains a newline, so the search for a newline in the input is not going to work reliably. However, that problem should manifest itself after the pipe was successfully written too, not before.
I note that the variable int fd_parent_write_word[2]; is unused and the code uses variable int fd[2] without declaring it.
It is a nuisance when what you get to analyze is not an SSCCE (Short, Self-Contained, Correct Example). It is so much easier when the test case has been reduced to a simple program that can be compiled and run with the submitter confident that the problem reproduces with it.
This SSCCE code compiles cleanly and runs OK:
#include <assert.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
enum { MAXWORD = 5 };
static void foo(int in);
static void he_who_pays_the_piper(int signum)
{
assert(signum == SIGPIPE);
const char msg[] = "Received signal SIGPIPE\n";
write(2, msg, sizeof(msg)-1);
exit(1);
}
int main(void)
{
int fd[2];
int pid;
if (pipe(fd) == -1) {
perror("pipe init error");
exit(1);
}
signal(SIGPIPE, he_who_pays_the_piper);
if ((pid = fork()) < -1) {
perror("fork error"); exit(1);
}
else if (pid > 0) {
close(fd[0]);
write(fd[1], "WHAT", MAXWORD); //SIGPIPE here
close(fd[1]);
int status;
pid = wait(&status);
printf("Got status 0x%04X from %d\n", status, pid);
}
else {
close(fd[1]);
foo(fd[0]);
close(fd[0]);
}
return 0;
}
static void foo(int in)
{
int string_length;
char word[MAXWORD];
while ((string_length = read(in, word, MAXWORD)) > 0)
printf("The word is: %.*s\n", string_length, word);
}
Example output:
The word is: WHAT
Got status 0x0000 from 49458
Note that this works because the '\0' at the end of the string WHAT is written to the pipe, and read from the pipe. Most usually, you do not write the strings including the trailing '\0'.
Hi I need a little help with parallel download program.
Currently, it is downloading the same file in parallel instead of downloading multiple files at the same time.
Something is wrong with the fork and fgets, not sure how to fix them. Thank you.
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
FILE *file; /*declare the file pointer*/
#define LINE_MAX 1000
char line [LINE_MAX];
//Parent process
int main()
{
pid_t pid;
file= fopen ("urls.txt", "rt"); /*open file and read it*/
if(!file)
{
perror("fopen");
exit(-1);
}
int numberOfChildren = 0;
while (!feof (file)) {
memset (line,'\0',1000);
char *urlPtr;
while (!feof (file))
{
urlPtr= fgets (line,LINE_MAX, file);
if(urlPtr)
{
int lineLen = strlen(urlPtr);
urlPtr[lineLen-1] = '\0';
pid = fork();
++numberOfChildren;
if (pid == 0) { /* child process */
execlp("/usr/bin/wget", "wget", urlPtr, NULL);
}
else if (pid < 0) { /* error occurred */
fprintf(stderr, "Fork Failed");
exit(-1);
}
}
}
while (numberOfChildren>0) { /* parent process */
/* parent will wait for the child to complete */
wait (NULL);
--numberOfChildren;
printf ("Child Complete");
}
}
fclose (file); /*close file command*/
return 0;
}
You have the fork() check outside the URL reading loop. You first read lots of URLs and spawn a lot of children, and then do the pid check. Try
while (!feof (file))
{
urlPtr= fgets (line,LINE_MAX, file);
pid = fork();
if (pid == 0) { /* child process */
execlp("/usr/bin/wget", "wget", urlPtr, NULL);
}
else if (pid < 0) { /* error occurred */
fprintf(stderr, "Fork Failed");
exit(-1);
}
++numberOfChildren;
}
You should put a diagnostic print and exit after the execlp() (but in the child code after the if). You should probably also close the input file before you execute wget; the program doesn't need it open. No huge harm done this time, but it's good to be tidy. Your parent probably shouldn't exit just because one child failed to fork(); you have other children, in general, that you should wait for. You might stop processing the file at that point, though. And you should definitely forget about feof(); use while (fgets(line, sizeof(line), file) != 0), though that means you don't need urlPtr. The memset() is superfluous; fgets() initializes the string correctly.
Adaptation of code in question
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
FILE *file; /*declare the file pointer*/
#define LINE_MAX 1000
char line [LINE_MAX];
//Parent process
int main(void)
{
pid_t pid;
file = fopen("urls.txt", "rt"); /*open file and read it*/
if (!file)
{
perror("fopen");
exit(-1);
}
int numberOfChildren = 0;
memset(line,'\0',1000);
char *urlPtr;
while (!feof(file))
{
urlPtr= fgets(line, sizeof(line), file);
if (urlPtr)
{
int lineLen = strlen(urlPtr);
urlPtr[lineLen-1] = '\0';
pid = fork();
++numberOfChildren;
if (pid == 0)
{ /* child process */
execlp("/usr/bin/wget", "wget", urlPtr, NULL);
fprintf(stderr, "%d: wget failed\n", (int)getpid());
exit(1);
}
else if (pid < 0)
{ /* error occurred */
fprintf(stderr, "Fork Failed\n");
exit(-1);
}
else
printf("%d: %s\n", (int)pid, urlPtr);
}
}
/* JL: Moved block of code */
while (numberOfChildren>0)
{ /* parent process */
/* parent will wait for the child to complete */
int status;
int corpse = wait(&status);
--numberOfChildren;
printf("Child %d Complete (0x%04X)\n", corpse, status);
}
fclose(file); /*close file command*/
return 0;
}
Note that a while (!feof(file)) loop has been removed, but there is more unnecessary code that could go. Given data file
ftp://ftp.iana.org/tz/releases/tzcode2012f.tar.gz
ftp://ftp.iana.org/tz/releases/tzdata2012f.tar.gz
The code above works fetching the two files in parallel.
Alternative code
I like to use functions, even for relatively short stretches of code that are used once. Hence the be_childish() function added below. The error reporting is a bit tedious to write out, but that is no excuse for not doing it.
I briefly introduced a minimal function that does error reporting, based on an elaborate library of my own, but it would only be used twice in this code (for the file open error and after execlp() returns, which always and unconditionally indicates failure), but decided to leave it out. I have functions such as err_setarg0(), err_error(), err_remark() and err_usage() and using those would reduce each error report to a single line (and some more complex functions that could be told to include the PID automatically, etc). To me, it is worth having such a library as it makes error checking much, much simpler and therefore less painful and less likely to be skimped on.
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
static void be_childish(const char *urlPtr)
{
const char *wget = "/usr/bin/wget";
char *nl = strchr(urlPtr, '\n');
if (nl != 0)
*nl = '\0';
printf("%d: %s\n", (int)getpid(), urlPtr);
execlp(wget, "wget", urlPtr, NULL);
fprintf(stderr, "%d: Failed to execute %s\n", (int)getpid(), wget);
exit(EXIT_FAILURE);
}
int main(int argc, char **argv)
{
FILE *file;
char line [1024];
pid_t pid;
const char *name = "urls.txt";
int rc = EXIT_SUCCESS;
if (argc == 2)
name = argv[1];
else if (argc > 2)
{
fprintf(stderr, "Usage: %s [filename]\n", argv[0]);
exit(EXIT_FAILURE);
}
file = fopen(name, "rt"); /* Undefined behaviour per POSIX */
int numberOfChildren = 0;
if (file == 0)
{
fprintf(stderr, "Failed to open file %s\n", name);
exit(EXIT_FAILURE);
}
while (fgets(line, sizeof(line), file) != 0)
{
if ((pid = fork()) == 0)
{
fclose(file);
be_childish(line);
}
else if (pid < 0)
{
fprintf(stderr, "Fork Failed");
rc = EXIT_FAILURE;
break;
}
++numberOfChildren;
}
fclose(file);
/* Parent waits for the children to complete */
while (numberOfChildren > 0)
{
int status;
const char *result = "OK";
pid = wait(&status);
--numberOfChildren;
if (status != 0)
{
result = "Failed";
rc = EXIT_FAILURE;
}
printf("Child %d %s\n", pid, result);
}
return rc;
}
Note that the code takes a file name on the command line, defaulting to your "urls.txt". The "rt" open mode is not a POSIX or standard C mode; it will likely work, but "r" is sufficient to open a text file on all systems ("rb" to open a binary file works on all systems too, and is POSIX and standard C compliant). It reports which child process is processing each file listed. It reports the status (success or failure) of each child; it's own exit status is only success if all the children were successful.
You could probably control the verboseness from the command line. You might also want to keep a record of which child was processing each file so that you could report on files successfully downloaded, rather than on the processes which the user doesn't care about, really. That complicates the processing since you need to make a copy of each URL as you read it.
Note that you do need to trim the newlines off the end of the string (URL) before passing it to wget.
This code now tested (after adding the newline amendment), and it produced two files. The screen display is a bit of a mess; that's because each copy of wget thinks it is the sole user:
80334: ftp://ftp.iana.org/tz/releases/tzcode2012f.tar.gz
80335: ftp://ftp.iana.org/tz/releases/tzdata2012f.tar.gz
--2012-09-23 19:19:44-- ftp://ftp.iana.org/tz/releases/tzcode2012f.tar.gz
=> “tzcode2012f.tar.gz”
Resolving ftp.iana.org... --2012-09-23 19:19:44-- ftp://ftp.iana.org/tz/releases/tzdata2012f.tar.gz
=> “tzdata2012f.tar.gz”
Resolving ftp.iana.org... 192.0.32.8192.0.32.8, , 2620:0:2d0:200::82620:0:2d0:200::8
Connecting to ftp.iana.org|192.0.32.8|:21... Connecting to ftp.iana.org|192.0.32.8|:21... connected.
Logging in as anonymous ... connected.
Logging in as anonymous ... Logged in!
==> SYST ... Logged in!
==> SYST ... done. ==> PWD ... done. ==> PWD ... done.
==> TYPE I ... done.
==> TYPE I ... done. ==> CWD (1) /tz/releases ... done. ==> CWD (1) /tz/releases ... done.
==> SIZE tzdata2012f.tar.gz ... done.
==> SIZE tzcode2012f.tar.gz ... 206404
==> PASV ... 135543
==> PASV ... done. ==> RETR tzdata2012f.tar.gz ... done. ==> RETR tzcode2012f.tar.gz ... done.
Length: 206404 (202K) (unauthoritative)
0% [ ] 0 --.-K/s done.
Length: 135543 (132K) (unauthoritative)
100%[==============================================================================>] 135,543 72.7K/s in 1.8s
100%[==============================================================================>] 206,404 81.4K/s in 2.5s
2012-09-23 19:19:48 (72.7 KB/s) - “tzcode2012f.tar.gz” saved [135543]
Child 80334 OK
2012-09-23 19:19:48 (81.4 KB/s) - “tzdata2012f.tar.gz” saved [206404]
Child 80335 OK
For my Operating Systems class I have an assignment due that is built onto a previous assignment. Unfortunately my previous project doesn't work correctly in addition to me not knowing where I need to start for the next project. The code which I have below is suppose to mimic a simple UNIX/Linux shell with some additional commands that cannot be performed with execvp: background processing via the ampersand operator, the 'jobs' shell command: list the pids of all living child processes (i.e. not ones that have terminated), "reaping" of "zombie" processes, and the 'cd' shell command: change the shell's working directory.
I believe, everything but the "jobs" command, and "cd" command work, but I'm not sure why these two don't.
The next assignment is to add some I/O redirection in the form of "mysh$ cmd arg1 arg2 argN > file.out" which I don't know where to even really begin...
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <wait.h>
#include <signal.h>
#include <sys/types.h>
int main(int argc, char **argv) {
char bBuffer[BUFSIZ], *pArgs[10], *aPtr = NULL, *sPtr;
int jobs[100];
int jobList = 0;
int background;
ssize_t rBytes;
int aCount;
pid_t pid;
int status;
while(!feof(stdin)) {
pid = waitpid(-1, &status, WNOHANG);
if (pid > 0)
printf("waitpid reaped child pid %d\n", pid);
write(1, "\e[1;31mmyBash \e[1;32m# \e[0m", 27);
rBytes = read(0, bBuffer, BUFSIZ-1);
if(rBytes == -1) {
perror("read");
exit(1);
}
bBuffer[rBytes-1] = '\0';
if(!strcasecmp(bBuffer, "exit")){
exit(0);
}
sPtr = bBuffer;
aCount = 0;
do {
aPtr = strsep(&sPtr, " ");
pArgs[aCount++] = aPtr;
} while(aPtr);
background = (strcmp(pArgs[aCount-2], "&") == 0);
if (background)
pArgs[aCount-2] = NULL;
if (strlen(pArgs[0]) > 1) {
pid = fork();
if (pid == -1) {
perror("fork");
exit(1);
} else if (pid == 0) {
jobs[jobList] = pid;
jobList++;
if(!strcasecmp(pArgs[0], "jobs")){
for(int i; i<jobList; i++) {
if(kill(jobs[i],0)==0){
printf(jobs[i]);
}
printf("these are jobs\n");
exit(1);
}
if(!strcasecmp(pArgs[0], "cd")){
int ret;
if (!pArgs[1])
strcpy(bBuffer, "pwd");
ret = chdir(pArgs[1]);
strcpy(bBuffer, "pwd");
exit(1);
}
fclose(stdin);
fopen("/dev/null", "r");
execvp(pArgs[0], pArgs);
exit(1);
} else if (!background) {
pid = waitpid(pid, &status, 0);
if (pid > 0)
printf("waitpid reaped child pid %d\n", pid);
}
}
}
return 0;
}
First you;ll want to parse your line and detect that you need to redirect to a file. So let;s say you use strsep or whatever and you found out output is going to file.out or input is coming from file.in.
At this point you want to redirect output using dup / dup2. For example, to redirect STDOUT:
int
do_redirect(int fileno, const char *name)
{
int newfd;
switch (fileno) {
case STDOUT_FILENO:
newfd = open(name, O_WRONLY | O_CREAT, S_IRUSR | S_IRUSR);
break;
}
if (newfd == -1) {
perror("open");
return -1;
}
return dup2(fileno, newfd);
}
/* ... */
pid = fork();
do_redirect(STDOUT_FILENO, name);
Things to note:
I didn't test the code - it might not even compile
I didn't do much error-checking - you should (the way I did for open)
You need to implement STDIN_FILENO redirection on your own
Note how I used a separate function, your main is WAY to large as it is
Your code has something like 7 levels of indentation - ever heard about arrow code ?
Since this is homework, I will not give you code directly.
dup, dup2 and freopen are good to look at for input/output redirection.
fork for starting a concurrent process (ampersand)
You are on the right track using waitpid to reap child processes.
I've just started working with UNIX FIFOs, and I discovered something while experimenting with my first FIFO program. The program works this way: after creating the FIFO, two processes are started using the fork() function. The child process reads what the father passes to him through the FIFO, and prints it on the screen. The data exchanged is the string specified as an argument. The question is: in the father section, if I forget to close the input side of the FIFO (meaning that I exclude the close(fd) line) the program would just hang, even if the data between the processes is exchanged correctly. Otherwise, everything works fine and the program terminates withouth hanging. Can someone please explain me why?
Thanks for your patience. Here is the code of the main function:
int main(int argc, char *argv[])
{
if(argc != 2)
{
printf("An argument must be specified\n");
return -1;
}
int ret = mkfifo("./fifo.txt", 0644);
char buf;
if(ret < 0)
{
perror("Error creating FIFO");
return -1;
}
pid_t pid = fork();
if(pid < 0)
{
perror("Error creating child process");
return -1;
}
if(pid == 0) /* child */
{
int fd = open("./fifo.txt", O_RDONLY); /* opens the fifo in reading mode */
while(read(fd, &buf, 1) > 0)
{
write(STDOUT_FILENO, &buf, 1);
}
write(STDOUT_FILENO, "\n", 1);
close(fd);
return 0;
}
else /* father */
{
int fd = open("./fifo.txt", O_WRONLY); /* opens the fifo in writing mode */
write(fd, argv[1], strlen(argv[1]));
close(fd);
waitpid(pid, NULL, 0);
return 0;
}
}
read(2) blocks until there are characters available or the channel is closed at the other end. The father process must close the pipe in order for the last child read() to return. If you omit the close(fd) in the father, the child will block in the read() until the father exits (closing the pipe automatically) but father will hang in waitpid() until the child exits.
First things first: there are several issues with the code you posted.
There are no #include directives, hence no prototypes in scope for any of the functions you call. C89 requires prototypes for variadic functions such as printf(); C99 requires prototypes for all functions. Both C89 and C99 require declarations in scope for O_RDONLY, O_WRONLY, STDOUT_FILENO and NULL.
-1 is not an allowed return value for main().
C89 does not allow mixing declarations and statements.
A minor nit: the usual nomenclature is "parent and child", not "father and child".
I have modified your program to correct this issue and improve readability:
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int
main(int argc, char *argv[])
{
if (argc != 2) {
printf("An argument must be specified\n");
return 1;
}
int ret = mkfifo("./fifo.txt", 0644);
char buf;
if (ret < 0) {
perror("Error creating FIFO");
return 1;
}
pid_t pid = fork();
if (pid < 0) {
perror("Error creating child process");
return 1;
}
if (pid == 0) { /* child */
int fd = open("./fifo.txt", O_RDONLY); /* opens the fifo in reading mode */
while(read(fd, &buf, 1) > 0) {
write(STDOUT_FILENO, &buf, 1);
}
write(STDOUT_FILENO, "\n", 1);
close(fd);
return 0;
} else { /* parent */
int fd = open("./fifo.txt", O_WRONLY); /* opens the fifo in writing mode */
write(fd, argv[1], strlen(argv[1]));
close(fd);
waitpid(pid, NULL, 0);
return 0;
}
}
But most importantly, you did not mention what operating system and compiler you are using.
I am unable to reproduce the issue, and I suspect it may be related to one of the issues listed above.