I'm trying to learn how to work with fork() to create new processes and pipes to communicate with each process. Let's say I have a list that contains 20 words, and I create 3 processes. Now, I need to distribute the words between the processes using pipes, and the each process will sort the list of words it receives. The way I want to achieve this is like this:
Word1 => Process1
Word2 => Process2
Word3 => Process3
Word4 => Process1
Word5 => Process2
Word6 => Process3
.
.
.
So each process will have a list of words to sort, and eventually I'll use MergeSort to merge all the sorted lists returned by each process. I'm not sure how to use pipes to communicate with each process (e.g. feed each process with a word). Any help that would put me on the right track would be appreciated.
Try this code for size. It uses a fixed number of child processes, but you can change that number by adjusting the enum MAX_KIDS (it was mostly tested with that set at 3; I later changed it to 5 just to make sure).
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
typedef struct Child
{
FILE *fp_to;
FILE *fp_from;
pid_t pid;
} Child;
enum { P_READ, P_WRITE }; /* Read, write descriptor of a pipe */
enum { MAX_LINE = 4096 };
static void be_childish(void);
static void distribute(size_t nkids, Child *kids);
static void err_exit(const char *fmt, ...);
static void merge(size_t nkids, Child *kids);
static void wait_for_kids(size_t nkids, Child *kids);
static int make_kid(Child *kid)
{
int pipe1[2]; /* From parent to child */
int pipe2[2]; /* From child to parent */
if (pipe(pipe1) != 0)
return -1;
if (pipe(pipe2) != 0)
{
close(pipe1[P_READ]);
close(pipe1[P_WRITE]);
return -1;
}
if ((kid->pid = fork()) < 0)
{
close(pipe1[P_READ]);
close(pipe1[P_WRITE]);
close(pipe2[P_READ]);
close(pipe2[P_WRITE]);
return -1;
}
else if (kid->pid == 0)
{
dup2(pipe1[P_READ], STDIN_FILENO);
dup2(pipe2[P_WRITE], STDOUT_FILENO);
close(pipe1[P_READ]);
close(pipe1[P_WRITE]);
close(pipe2[P_READ]);
close(pipe2[P_WRITE]);
/* Reads standard input from parent; writes standard output to parent */
be_childish();
exit(0);
}
else
{
kid->fp_to = fdopen(pipe1[P_WRITE], "w");
kid->fp_from = fdopen(pipe2[P_READ], "r");
close(pipe1[P_READ]);
close(pipe2[P_WRITE]);
return 0;
}
}
int main(void)
{
enum { NUM_KIDS = 5 };
Child kids[NUM_KIDS];
struct sigaction act;
sigfillset(&act.sa_mask);
act.sa_flags = 0;
act.sa_handler = SIG_DFL;
sigaction(SIGCHLD, &act, 0);
for (int i = 0; i < NUM_KIDS; i++)
{
if (make_kid(&kids[i]) != 0)
err_exit("Fault starting child %d\n", i);
}
distribute(NUM_KIDS, kids);
merge(NUM_KIDS, kids);
wait_for_kids(NUM_KIDS, kids);
return(0);
}
static void err_exit(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
exit(1);
}
static int qs_compare(const void *v1, const void *v2)
{
const char *s1 = *(char **)v1;
const char *s2 = *(char **)v2;
return(strcmp(s1, s2));
}
static char *estrdup(const char *str)
{
size_t len = strlen(str) + 1;
char *out = malloc(len);
if (out == 0)
err_exit("Out of memory!\n");
memmove(out, str, len);
return(out);
}
static void be_childish(void)
{
char **lines = 0;
size_t num_lines = 0;
size_t max_lines = 0;
char input[MAX_LINE];
while (fgets(input, sizeof(input), stdin) != 0)
{
if (num_lines >= max_lines)
{
size_t n = (2 * max_lines + 2);
void *space = realloc(lines, n * sizeof(char *));
if (space == 0)
err_exit("Out of memory!\n");
lines = space;
max_lines = n;
}
lines[num_lines++] = estrdup(input);
}
qsort(lines, num_lines, sizeof(char *), qs_compare);
for (size_t i = 0; i < num_lines; i++)
{
if (fputs(lines[i], stdout) == EOF)
err_exit("Short write to parent from %d\n", (int)getpid());
}
exit(0);
}
static void distribute(size_t nkids, Child *kids)
{
char input[MAX_LINE];
size_t n = 0;
while (fgets(input, sizeof(input), stdin) != 0)
{
if (fputs(input, kids[n].fp_to) == EOF)
err_exit("Short write to child %d\n", (int)kids[n].pid);
if (++n >= nkids)
n = 0;
}
/* Close pipes to children - let's them get on with sorting */
for (size_t i = 0; i < nkids; i++)
{
fclose(kids[i].fp_to);
kids[i].fp_to = 0;
}
}
static void read_line(Child *kid, char *buffer, size_t maxlen, int *length)
{
if (fgets(buffer, maxlen, kid->fp_from) != 0)
{
*length = strlen(buffer);
buffer[*length] = '\0';
}
else
{
buffer[0] = '\0';
*length = -1;
fclose(kid->fp_from);
kid->fp_from = 0;
}
}
static int not_all_done(size_t nkids, int *lengths)
{
for (size_t i = 0; i < nkids; i++)
{
if (lengths[i] > 0)
return 1;
}
return 0;
}
static void min_line(size_t nkids, int *len, char **lines, size_t maxlen,
Child *kids, char *output)
{
size_t min_kid = 0;
char *min_str = 0;
for (size_t i = 0; i < nkids; i++)
{
if (len[i] <= 0)
continue;
if (min_str == 0 || strcmp(min_str, lines[i]) > 0)
{
min_str = lines[i];
min_kid = i;
}
}
strcpy(output, min_str);
read_line(&kids[min_kid], lines[min_kid], maxlen, &len[min_kid]);
}
static void merge(size_t nkids, Child *kids)
{
char line_data[nkids][MAX_LINE];
char *lines[nkids];
int len[nkids];
char output[MAX_LINE];
for (size_t i = 0; i < nkids; i++)
lines[i] = line_data[i];
/* Preload first line from each kid */
for (size_t i = 0; i < nkids; i++)
read_line(&kids[i], lines[i], MAX_LINE, &len[i]);
while (not_all_done(nkids, len))
{
min_line(nkids, len, lines, MAX_LINE, kids, output);
fputs(output, stdout);
}
}
static void wait_for_kids(size_t nkids, Child *kids)
{
int pid;
int status;
while ((pid = waitpid(-1, &status, 0)) != -1)
{
for (size_t i = 0; i < nkids; i++)
{
if (pid == kids[i].pid)
kids[i].pid = -1;
}
}
/* This check loop is not really necessary */
for (size_t i = 0; i < nkids; i++)
{
if (kids[i].pid != -1)
err_exit("Child %d died without being tracked\n", (int)kids[i].pid);
}
}
The overall picture is usually:
pid_t pids[3];
int fd[3][2];
int i;
for (i = 0; i < 3; ++i) {
/* create the pipe */
if (pipe(fd[i]) < 0) {
perror("pipe error");
exit(1);
}
/* fork the child */
pid[i] = fork();
if (pid[i] < 0) {
perror("fork error");
} else if (pid[i] > 0) {
/* in parent process */
/* close reading end */
close(fd[i][0]);
} else {
/* in child process */
/* close writing end */
close(fd[i][1]);
/* read from parent */
read(fd[i][0], line, max);
...
}
}
/* in parent process */
char words[100][10] = {...};
int j, child = 0;
/* for all words */
for (j = 0; j < 100; ++j) {
/* write to child */
write(fd[child][1], words[j], strlen(words[j]));
...
++child;
if (child >= 3)
child = 0;
}
Duplicate the pipe part for communicating back from child to parent. Be careful not to deadlock when parent and child are trying to communicate in both directions simultaneously.
There's nothing magical about pipes - they are just a communication medium with two endpoints. The logic goes approximately:
Create 3 pipes and hold onto one endpoint. Fork three times, and get each of those forked children to hold onto the other end of the pipe. The child then goes into a read loop, waiting for input and writing back output. The parent can round-robin all outputs, then do a round-robin read of inputs. This isn't the nicest strategy, but it's by far the simplest. i.e.,
while there is work left to do:
for i in 1..3
write current work unit to pipe[i]
for i in 1..3
read back response from pipe[i]
A given child just looks like this:
while(input = read from pipe)
result = do work on input
write result to pipe
The next step would be doing your read-back in the parent process in an asynchronous, non-blocking manner (possibly using select, or just a busy-wait polling loop). This requires the children to report back which task they are returning a result for, because the ordering could get messy (i.e., you can no longer rely on the first work unit you send being the first response you get). Welcome to the fun world of concurrency bugs.
Given the somewhat underspecified nature of your question, I hope this is somehow useful.
Related
The code I've written finds out the number of words in multiple text files by creating multiple processes with each process being responsible for one file to count its words.
What I want to do is using pipes to find out total number of words in all files.
So the parent should:
creates a pipe between the each child and itself so it can get the number of words from each child
reports the total number of words in all the files by adding the numbers received through pipes
checks the exit status of each child and prints out how that child
exited
also let each child:
sends the number of the words to the parent via the pipe
send 0 as word count through the pipe to the parent if the file does
not exist or any other error happens
returns/exits with 0 if it is successfull in opening the file and
counting the words in that file, returns/exits with 1 if there is an
error (e.g., file does not exist etc.)
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#define MAX_CHAR 100
pid_t getpid(void);
pid_t getppid(void);
char* itoa(int i, char b[]){
char const digit[] = "0123456789";
char* p = b;
if(i<0){
*p++ = '-';
i *= -1;
}
int shifter = i;
do{ //Move to where representation ends
++p;
shifter = shifter/10;
}while(shifter);
*p = '\0';
do{ //Move back, inserting digits as u go
*--p = digit[i%10];
i = i/10;
}while(i);
return b;
}
int countWords(char * fp, int pid) {
FILE * file;
int words = 0;
char word[MAX_CHAR];
//execute this function only if child process of parent, no gradchild is allowed to execute this function!
if (pid == getppid()) {
file = fopen(fp, "r");
if (file == NULL) {
return -1;
}
//find string in the file and count the words.
while (fscanf(file, "%s", word) != EOF) {
words++;
}
return words;
} else {
return -1;
}
return 0;
}
int main(int argc, char * arvg[]) {
//if invalid arguments
if (argc < 2) {
fprintf(stderr, "ERROR: INVALID ARGUMENTS");
exit(-1);
}
int count = 0, pid, ppid, status, totalwords;
int result = -1;
int fd[2];
char string[100];
char readbuffer[80];
int *write_fd = &fd[1];
int *read_fd = &fd[0];
result = pipe(fd);
if(-1 == result){
perror("pipe");
return -1;
}
//creates (argc - 1) child processes using fork()
pid = (int) malloc((argc - 1) * sizeof(int));
//parent pid
ppid = getpid();
//each child process to count the number of words in each file
for (int i = 1; i < argc; i++) {
//child process
pid = fork();
if( pid == -1){
perror("failed to fork");
return -1;
}else if (pid == 0) {
// call a function to count the number of words in file arvg[i]
int words = countWords(arvg[i], ppid);
close(*read_fd);
if (words >= 0) {
printf("Child process pid_%d for %s :number of words is %d\n", i, arvg[i], words);
//I don't know how to write int into the pipe,so below might be wrong
write(*write_fd, words, 1);
return 0;
} else if (words == -1) {
printf("Child process pid_%d for %s :does not exists\n", i, arvg[I]);
//I don't know how to write int into the pipe,so below might be wrong
write(STDOUT_FILENO, words, 1);
exit(1);
}
} else {
close(*write_fd);
//and I have no idea how to read int from pipes
read(*read_fd, &readbuffer, 1);
totalwords += ???
close(*read_fd);
//Wait until all child processes exit/return
if (ppid == getpid()) {
wait( & status);
}
//inspect their exit codes, WEXITSTATUS = return code when child exits
if (WEXITSTATUS(status) == 1) {
count++;
}
}
}
printf("Main process created %d child processes to count words in %d files\n", argc - 1, argc - 1);
printf("Total words is %d", totalwords);
printf("%d files have been counted sucessfully!\n", argc - 1 - count);
printf("%d files did not exist.\n", count);
return 0;
}```
Can someone help me to figure out this? I don't really know how to achieve my goal with pipe.
found some issues with the code. I fixed them for you (however, I would have done the same thing slight differently)
reading and writing int from a pipe is pretty straight forward, just typecast correctly while reading or writing to an int.
malloc to a pid was not necessary. Also malloc returns a pointer and should have been typecasted with (int*)
always add the right includes while using calls. Manual page or reading about the calls while trying to understand the parameters passed and return values is extremely useful.
Enough said, here is your working code
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#define MAX_CHAR 100
pid_t getpid(void);
pid_t getppid(void);
char* itoa(int i, char b[]){
char const digit[] = "0123456789";
char* p = b;
if(i<0){
*p++ = '-';
i *= -1;
}
int shifter = i;
do{ //Move to where representation ends
++p;
shifter = shifter/10;
}while(shifter);
*p = '\0';
do{ //Move back, inserting digits as u go
*--p = digit[i%10];
i = i/10;
}while(i);
return b;
}
int countWords(char * fp, int pid) {
FILE * file;
int words = 0;
char word[MAX_CHAR];
//execute this function only if child process of parent, no gradchild is allowed to execute this function!
if (pid == getppid()) {
file = fopen(fp, "r");
if (file == NULL) {
return -1;
}
//find string in the file and count the words.
while (fscanf(file, "%s", word) != EOF) {
words++;
}
return words;
} else {
return -1;
}
return 0;
}
int main(int argc, char * arvg[]) {
//if invalid arguments
if (argc < 2) {
fprintf(stderr, "ERROR: INVALID ARGUMENTS");
exit(-1);
}
int count = 0, pid, ppid, status, totalwords = 0;
int result = -1;
int fd[2];
char string[100];
char readbuffer[80];
int *write_fd = &fd[1];
int *read_fd = &fd[0];
int recvd = 0;
result = pipe(fd);
if(-1 == result){
perror("pipe");
return -1;
}
//creates (argc - 1) child processes using fork()
//pid = (int) malloc((argc - 1) * sizeof(int));
//parent pid
ppid = getpid();
//each child process to count the number of words in each file
for (int i = 1; i < argc; i++) {
//child process
pid = fork();
if( pid == -1){
perror("failed to fork");
return -1;
}else if (pid == 0) {
printf ("%d child running \n", i);
// call a function to count the number of words in file arvg[i]
int words = countWords(arvg[i], ppid);
close(*read_fd);
if (words >= 0) {
printf("Child process pid_%d for %s :number of words is %d\n", i, arvg[i], words);
//I don't know how to write int into the pipe,so below might be wrong
write(*write_fd, (void *)&words, 1);
return 0;
} else if (words == -1) {
printf("Child process pid_%d for %s :does not exists\n", i, arvg[i]);
//I don't know how to write int into the pipe,so below might be wrong
write(STDOUT_FILENO, (void *)&words, 1);
exit(1);
}
} else {
close(*write_fd);
//and I have no idea how to read int from pipes
read(*read_fd, (void*)&recvd, 1);
totalwords += recvd;
printf("recvd %d \n", totalwords);
close(*read_fd);
//Wait until all child processes exit/return
if (ppid == getpid()) {
wait( & status);
}
//inspect their exit codes, WEXITSTATUS = return code when child exits
if (WEXITSTATUS(status) == 1) {
count++;
}
}
}
printf("Main process created %d child processes to count words in %d files\n", argc - 1, argc - 1);
printf("Total words is %d\n", totalwords);
printf("%d files have been counted sucessfully!\n", argc - 1 - count);
printf("%d files did not exist.\n", count);
return 0;
}
Well, the first time around, I did not focus on the algo. I fixed all of it. The problem is forking in a loop and reading and writing it would lead to wrong results. Moreover, Parent needs to look for EOF to ensure all read has happened. Anyways, Here is the code that should work
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#define MAX_CHAR 100
pid_t getpid(void);
pid_t getppid(void);
char* itoa(int i, char b[]){
char const digit[] = "0123456789";
char* p = b;
if(i<0){
*p++ = '-';
i *= -1;
}
int shifter = i;
do{ //Move to where representation ends
++p;
shifter = shifter/10;
}while(shifter);
*p = '\0';
do{ //Move back, inserting digits as u go
*--p = digit[i%10];
i = i/10;
}while(i);
return b;
}
// count word from file provided
int countWords(char * fp, int pid) {
FILE * file;
int words = 0;
char word[MAX_CHAR];
//execute this function only if child process of parent, no gradchild is allowed to execute this function!
if (pid == getppid()) {
file = fopen(fp, "r");
if (file == NULL) {
return -1;
}
//find string in the file and count the words.
while (fscanf(file, "%s", word) != EOF) {
words++;
}
return words;
} else {
return -1;
}
return 0;
}
//do everything related to child here in this function
void child_process(int write_fd, char *filename, int ppid)
{
// call a function to count the number of words in file argv[i]
printf("counting words of %s\n", filename);
int words = countWords(filename, ppid);
if (words >= 0) {
printf("Child process pid for %s :number of words is %d\n", filename, words);
write(write_fd, (void *)&words, 1);
close(write_fd);
exit(0);
} else if (words == -1) {
printf("Child process pid for %s :does not exist\n", filename);
write(STDOUT_FILENO, (void *)&words, 1);
close(write_fd);
exit(1);
}
return;
}
int main(int argc, char * argv[]) {
//if invalid arguments
if (argc < 2) {
fprintf(stderr, "ERROR: INVALID ARGUMENTS");
exit(-1);
}
int pid = 0;
int ppid = 0;
int totalwords = 0;
int fd[2] = {0};
int write_fd = 0;
int read_fd = 0;
int recvd = 0;
// open a pipe
if(-1 == pipe(fd)){
perror("pipe");
return -1;
}
// assign write_fd and read_fd
write_fd = fd[1];
read_fd = fd[0];
//parent pid
ppid = getpid();
//each child process to count the number of words in each file
pid = fork();
for (int i = 0; i < argc-1; i++)
{
//child process
if (pid == 0) {
close(read_fd);
child_process(write_fd, argv[i+1], ppid);
break;
} else {
pid = fork();
}
}
// don't let child run beyond this point
if (pid == 0) {
exit(0);
}
// parent only code
if (pid > 0)
{
close(write_fd);
while (read(read_fd, (void*)&recvd, 1) > 0)
{
wait(NULL);
totalwords += recvd;
}
close(read_fd);
}
printf("Main process created %d child processes to count words in %d files\n", argc - 1, argc - 1);
printf("Total words is %d\n", totalwords);
printf("%d files have been counted sucessfully!\n", argc - 1);
}
// I have commands in commands[] array
pid_t pid[command_count];
for (int i = 0; i < command_count; i++) {
if ((pid[i]=fork()) == 0) {
printf("--%s\n", commands[i][0]);
execvp(commands[i][0],commands[i]);
_exit(1);
}
if (pid[i] < 0) {
}
}
for (i = 0; i < command_count; i++) {
if (pid[i] > 0) {
int status;
waitpid(pid[i], &status, 0);
}
}
I have the above code and want to run commands at once (paralel) that is in commands array and before each run, want to print the command. For example;
ls | pwd | ls -a
It should print each command name before that command run like
--ls
.. a b.txt
--pwd
/a/a/
--ls -a
*some output*
But it prints like following
--ls
--pwd
--ls -a
.. a b.txt
*some directory as a result of pwd*
*some output*
What could be the reason and how can I fix it?
You will have to use pipe() to create new standard output and optionally standard error file descriptors for each command. Then you can read the pipes in order until each command completes.
Otherwise, because each command is forked into its own process it will run at its own convenience. Text output from commands running all at once and producing output to the same terminal can be mixed up even more than you show here.
Maybe something like
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
struct cmd_data {
const char *cmd;
int fd[2]; // stdout pipe for command
pid_t pid;
int status; // exit status
};
void cmd_launch(struct cmd_data *p, const char *cmd) {
int r;
p->cmd = cmd;
r = pipe(p->fd);
if(r<0) {
perror("pipe");
exit(EXIT_FAILURE);
}
r = fork();
if(r < 0) {
perror("fork");
close(p->fd[0]);
close(p->fd[1]);
} else if( r > 0 ) {
p->pid = r;
close(p->fd[1]);
} else {
close(p->fd[0]);
dup2(p->fd[1], STDOUT_FILENO);
close(p->fd[1]);
r = execlp(cmd, cmd, NULL);
perror("execlp");
exit(EXIT_FAILURE);
}
}
void cmd_join(struct cmd_data *p) {
char buf[4096];
const size_t buflen = sizeof buf;
ssize_t bytes;
printf("-- %s\n", p->cmd);
fflush(stdout);
while( 0 != (bytes = read(p->fd[0], buf, buflen)) ) {
write(STDOUT_FILENO, buf, bytes);
}
close(p->fd[0]);
pid_t r = waitpid(p->pid, &p->status, 0);
if(r<0){
perror("waitpid");
}
printf("-- completed with status %d\n", p->status);
fflush(stdout);
}
int main(int argc, char *argv[]) {
size_t cmd_c = argc - 1;
struct cmd_data *p = calloc(argc, sizeof *p);
size_t i;
for(i = 0; i < cmd_c; ++i) {
cmd_launch(p + i, argv[i + 1]);
}
for(i = 0; i < cmd_c; ++i) {
cmd_join(p + i);
}
free(p);
return EXIT_SUCCESS;
}
I'm creating my own Shell and I successfully got processes to run in the background by using my is_background function to find a &. It was working fine until i tried to implement redirection of standard output. The chk_if_output function is a part of this as well as the if statement if(out[0] == 1) in the process function. Somehow implementing redirection screwed up the way I implemented background process. If I comment out the redirection code it works again. I get a segmentation fault every time I try to run a background process with the redirection code in the program and I can't for the life of me figure out why. I haven't changed any of the background process code.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#define MAX_LINE 80 /* The maximum length command */
int is_background(char *args[], int size){
int background = 0;
if (strcmp(args[size-1 ], "&") == 0){
background = 1;
args[size-1] = NULL;
}
return background;
}
int * chk_if_output(char *args[], int size){
int * out = malloc(2);
out[0] = 0; out[1] = 0;
for (int i = 0; i < size; i++){
if (strcmp(args[i],">") == 0){
out[0] = 1;
out[1] = i;
break;
}
}
return out;
}
void process(char *command, char *params[], int size){
pid_t pid;
int background = is_background(params, size);
int *out = chk_if_output(params, size);
int fd;
int fd2;
pid = fork();
if (pid < 0) {
fprintf(stderr, "Fork Failed\n");
}else if (pid == 0) {
if(out[0] == 1){
for (int i = out[1]; i < size; i++){
params[i] = params[i+1];
}
fd = open(params[out[1]-1],O_RDONLY,0);
dup2(fd,STDIN_FILENO);
close(fd);
fd2 = creat(params[out[1]],0644);
dup2(fd2,STDOUT_FILENO);
close(fd2);
out[0] = 0;
out[1] = 0;
}
execvp(command, params);
}else {
if(background == 1){
waitpid(pid, NULL, 0);
}
background = 0;
}
}
int main(void) {
char *args[MAX_LINE/2 + 1]; /* command line arguments */
int should_run = 1; /* flag to determine when to exit program */
while (should_run) {
char *line;
char *endline;
printf("Leyden_osh>");
fgets(line, MAX_LINE*sizeof line, stdin);
if((endline = strchr(line, '\n')) != NULL){
*endline = '\0';
}
if (strcmp((const char *)line,"exit") == 0){
should_run = 0;
}
int i = 0;
args[i] = strtok(line, " ");
do{
args[++i] = strtok(NULL, " ");
}while(args[i] != NULL);
process(args[0], args, i);
fflush(stdout);
return 0;
}
In the chk_if_output() function, the last element of the array in the loop was NULL.
Fixed it by looping to size -1.
int shell (int argc, char *argv[]) {
char *s = malloc(INPUT_STRING_SIZE+1); /* user input string */
tok_t *t; /* tokens parsed from input */
int lineNum = 0;
int fundex = -1;
pid_t pid = getpid(); /* get current processes PID */
pid_t ppid = getppid(); /* get parents PID */
pid_t cpid, tcpid, cpgid;
init_shell();
printf("%s running as PID %d under %d\n",argv[0],pid,ppid);
lineNum=0;
const int BUF_SIZE = 200;
char buf[BUF_SIZE];
getcwd(buf, BUF_SIZE);
fprintf(stdout, "%d %s: ", lineNum++, buf);
while ((s = freadln(stdin))){
char *ptrA, *ptrB;
ptrA = strstr(s, "<");
ptrB = strstr(s, ">");
if (ptrA) {
*ptrA = '\0';
ptrA++;
}
if (ptrB) {
*ptrB = '\0';
ptrB++;
}
if (ptrA && ptrB) {
} else if (ptrA) {
} else if (ptrB) {
size_t ln = strlen(ptrB) - 1;
if (ptrB[ln] == '\n') // get rid of the trailing newline
ptrB[ln] = '\0';
int newfd; /* new file descriptor */
if ((newfd = open(ptrB, O_CREAT|O_TRUNC|O_WRONLY, S_IRUSR|S_IWUSR|S_IXUSR)) < 0) {
perror("Can't open outfile file\n"); /* open failed */
exit(1);
}
printf("newfd: %d\n", newfd);
dup2(newfd, 1);
printf("Here\n");
}
printf("%s\n", s);
t = getToks(s); /* break the line into tokens */
fundex = lookup(t[0]); /* Is first token a shell literal */
if(fundex >= 0) {
cmd_table[fundex].fun(&t[1]);
} else {
char *bin;
if (get_binary(t[0], &bin) == 0) {
t[0] = bin;
}
pid_t child_pid = fork();
int exit_code;
if (child_pid == 0) {
execv(t[0], &t[0]);
} else {
wait(&exit_code);
}
}
getcwd(buf, BUF_SIZE);
fprintf(stdout, "%d %s: ", lineNum++, buf);
}
return 0;
}
I test out my shell with wc shell.c>testand what happens is I get a blank line on the terminal as if it's waiting for my input. When I hit Enter, it segfaults. If I comment out the dup2 line, the shell works perfectly (but without redirection, of course). I'm on Windows using Cygwin, if that helps.
I had my dup in the wrong place. I needed to dup after I forked, but before I execv'd.
I'm trying to get a better understanding of pipes and processes. I want to implement multiple chained pipes like cat test.txt | sort | uniq -c. I started my code with the cat test.txt, but it isn't working. It compiles, but when I provide a file name in the command line, for example, ./hwk ./test.txt. Nothing returns. Can someone take a look and give me some hints? I want to use loops because I want to be able to add more pipes. I know there's a lot of issues in my code, so I hope someone can give me some guidance on this topic. Thanks.
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#define SIZE 1024
int main (int argc, char **argv)
{
int num_pipe = 1;
int commands = num_pipe + 1; //number of commands is one more than the number of pipes
int fds[num_pipe * 2];
int status;
pid_t pid;
char *str_ptr;
//Pass Command
char *arrayOfCommands[] = {"cat", NULL};
//Setting up pipes
int i;
for (i = 0; i < num_pipe; i++){
if(pipe(fds + i * 2) == -1) {
perror("Error creating pipes");
exit(1);
}
}
int j = 0;
for (i = 0; i < commands - 1; ++i) {
pid = fork();
if (pid == 0) {
if (i < commands) {
if (dup2(fds[j+1], 1) < 0) {
perror("dup2 error");
exit(EXIT_FAILURE);
}
}
if (j != 0) {
if(dup2(fds[j-2], 0) < 0) {
perror("dup2 error");
exit(EXIT_FAILURE);
}
}
for (i = 0; i < 2*num_pipe; i++) {
close(fds[i]);
}
if (execvp(arrayOfCommands[0], arrayOfCommands) < 0) {
perror("Array error");
exit(EXIT_FAILURE);
}
}
else if (pid < 0){
perror("Error");
exit(EXIT_FAILURE);
}
j += 2;
}
for (i = 0; i < 2 * num_pipe; i++){
close(fds[i]);
}
for (i = 0; i < num_pipe + 1; i++) {
wait(&status);
}
return 0;
}
I called this mainly minor adaptation of your program p3.c, compiling it to produce p3. Since there's only one command (cat) being invoked, I juggled things so that it will work correctly. When run as ./p3 p3.c, it prints out the content of the source code.
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
static void err_exit(const char *str);
int main (int argc, char **argv)
{
int num_pipe = 0; // Just cat - no pipes
int commands = num_pipe + 1; // Number of commands is one more than the number of pipes
int fds[num_pipe * 2 + 1]; // Avoid size 0 array
char *arrayOfCommands[3] = { "cat", NULL, NULL};
if (argc != 2)
err_exit("Missing filename argument");
arrayOfCommands[1] = argv[1];
for (int i = 0; i < num_pipe; i++)
{
if (pipe(fds + i * 2) == -1)
err_exit("Error creating pipes");
}
int j = 0;
for (int i = 0; i < commands; ++i)
{
pid_t pid = fork();
if (pid == 0)
{
printf("%d: %s %s\n", (int)getpid(), arrayOfCommands[0], arrayOfCommands[1]);
fflush(stdout);
if (i < commands-1 && dup2(fds[j+1], 1) < 0)
err_exit("dup2 error");
if (j != 0 && dup2(fds[j-2], 0) < 0)
err_exit("dup2 error");
for (i = 0; i < 2*num_pipe; i++)
close(fds[i]);
execvp(arrayOfCommands[0], arrayOfCommands);
err_exit("Array error");
}
else if (pid < 0)
err_exit("Error");
j += 2;
}
for (int i = 0; i < 2 * num_pipe; i++)
close(fds[i]);
for (int i = 0; i < num_pipe + 1; i++)
{
int status;
pid_t pid = wait(&status);
printf("PID %d exited 0x%.4X\n", (int)pid, status);
}
return 0;
}
static void err_exit(const char *str)
{
perror(str);
exit(EXIT_FAILURE);
}
Check that works for you. Then you'll need to work out how you're going to create a second command. Your arrayOfCommands isn't going to help directly. You'll need another array of strings in some shape or form.
An extension to run cat file | rev. The changes are really quite minor. I created a_cat to handle the cat command, a_rev for the rev command, and a_cmds as the array of commands. It was also necessary to fix a loop on i to a loop on k.
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>
static void err_exit(const char *str);
int main (int argc, char **argv)
{
int num_pipe = 1;
int commands = num_pipe + 1; //number of commands is one more than the number of pipes
int fds[num_pipe * 2 + 1]; // Avoid size 0 array
char *a_cat[3] = { "cat", NULL, NULL};
char *a_rev[2] = { "rev", NULL};
char **a_cmds[] = { a_cat, a_rev };
if (argc != 2)
err_exit("Missing filename argument");
a_cat[1] = argv[1];
for (int i = 0; i < num_pipe; i++)
{
if (pipe(fds + i * 2) == -1)
err_exit("Error creating pipes");
}
int j = 0;
for (int i = 0; i < commands; ++i)
{
pid_t pid = fork();
if (pid == 0)
{
printf("%d: %s\n", (int)getpid(), a_cmds[i][0]);
fflush(stdout);
if (i < commands-1 && dup2(fds[j+1], 1) < 0)
err_exit("dup2 error");
if (j != 0 && dup2(fds[j-2], 0) < 0)
err_exit("dup2 error");
for (int k = 0; k < 2*num_pipe; k++)
close(fds[k]);
execvp(a_cmds[i][0], a_cmds[i]);
err_exit("Array error");
}
else if (pid < 0)
err_exit("Error");
j += 2;
}
for (int i = 0; i < 2 * num_pipe; i++)
close(fds[i]);
for (int i = 0; i < num_pipe + 1; i++)
{
int status;
pid_t pid = wait(&status);
printf("PID %d exited 0x%.4X\n", (int)pid, status);
}
return 0;
}
static void err_exit(const char *str)
{
perror(str);
exit(EXIT_FAILURE);
}
You aren't passing your program's command-line arguments through to the "cat" child process. You initialize arrayOfCommands like so -> char *arrayOfCommands[] = {"cat", NULL}; <- then you pass it as-is to the execvp() function as the second argument.
Okay your first problem is that in the line:
execvp(arrayOfCommands[0], arrayOfCommands);
you are using arrayOfCommands but I am not sure how you're populating arrayOfCommands for the case where the text file is not being displayed. I mean are you setting arrayOfCommands like the following earlier in the code:
char *arrayOfCommands[] = {"cat", "./test.txt", NULL};
If I understand you correctly your program is called hwk and for whatever reason you think ./hwk ./test.txt should be parsed but that means you should be parsing argv.
Okay now that that's out of the way let's look at the bigger problem of how you are setting things up.
So when a shell parses out pipes it does there's quite a bit going on. Consider the following:
foo fooparam1 fooparam2 | bar barparam1 | baz bazparam1 bazparam2
The shell uses recursion to solve the problem:
foo fooparam1 fooparam2 | ( bar barparam1 | baz bazparam1 bazparam2 )
So it would look SOMETHING like:
spawn_sub_pipes(const char *str) {
char *cmd = strtok(str, "|");
char *rest = strtok(NULL, "|");
int fds[2];
pipe(fds[]);
int pid = fork();
if ( pid < 0 ) {
perror("pipe error");
exit(-1);
}
if ( pid ) { /* parent is the writer */
close(fds[0]); /* close reading pipe */
dup2(fds[1], 1); /* we attach stdout to the pipe */
}
if ( pid == 0 ) {
close(fds[1]);
dup2(fds[0], 0); /* attach the pipe to stdin */
if ( rest ) { /* fork next children */
spawn_sub_pipes(rest);
}
execvpe(cmd);
}
}
IMPORTANT NOTE
I have just written the above code out without testing it. Get the idea from it but don't use it verbatim.