I need the main prog to get two strings from the user and an argument for the other program, call fork() and then in child process I need to write the strings into pipe and send them to the other program which returns an int which I want to pass to parent so I'm trying to use another pipe for it but every time it stops right after inserting the strings.
So the main program: (EDITED)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/wait.h>
#define LINELEN (80)
char *mygets(char *buf, int len);
int mygeti();
int main(int argc, char *argv[])
{
char *cmpstr[] = {"lexcmp", "lencmp"};
int veclen = sizeof(cmpstr)/sizeof(char *);
char str1[LINELEN + 1];
char str2[LINELEN + 1];
int index;
int pid[2];
int pfd[4][2];
for(int i = 0; i < 4; i++)
{
if(pipe(pfd[i]) < 0)
{
perror("pipe");
return -2;
}
}
pid[0] = fork();
if(pid[0] == 0) // child a
{
close(pfd[0][1]);
close(pfd[2][0]);
dup2(pfd[0][0], STDIN_FILENO);
dup2(pfd[2][1], STDOUT_FILENO);
char *myargs[3];
myargs[0] = "./loopcmp";
myargs[1] = "lexcmp";
myargs[2] = NULL;
if(execvp(myargs[0], myargs) == -1)
{
perror("exec");
return -2;
}
close(pfd[0][0]);
close(pfd[2][1]);
}
else
{
pid[1] = fork();
if(pid[1] == 0) //child b
{
close(pfd[1][1]);
close(pfd[3][0]);
dup2(pfd[1][0], STDIN_FILENO);
dup2(pfd[3][1], STDOUT_FILENO);
char *myargs[3];
myargs[0] = "./loopcmp";
myargs[1] = "lencmp";
myargs[2] = NULL;
if(execvp(myargs[0], myargs) == -1)
{
perror("exec");
return -2;
}
close(pfd[1][0]);
close(pfd[3][1]);
}
else // parent
{
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;
do {
printf("Please choose:\n");
for (int i=0 ; i < veclen ; i++)
printf("%d - %s\n", i, cmpstr[i]);
index = mygeti();
} while ((index < 0) || (index >= veclen));
close(pfd[index][0]);
if(write(pfd[index][1], str1, strlen(str1)) == -1)
{
perror("writeToPipe");
return -2;
}
if(write(pfd[index][1], str2, strlen(str2)) == -1)
{
perror("writeToPipe");
return -2;
}
if(index == 0)
{
close(pfd[2][1]);
char rbuf[1];
while(read(pfd[2][0], &rbuf, 1) > 0)
{
write(STDOUT_FILENO, &rbuf, 1);
}
}
if(index == 1)
{
close(pfd[3][1]);
char rbuf[1];
while(read(pfd[3][0], &rbuf, 1) > 0)
{
write(STDOUT_FILENO, &rbuf, 1);
}
}
}
}
}
return 0;
}
char *mygets(char *buf, int len)
{
char *retval;
retval = fgets(buf, len, stdin);
buf[len] = '\0';
if (buf[strlen(buf) - 1] == 10) /* trim \r */
buf[strlen(buf) - 1] = '\0';
else if (retval)
while (getchar() != '\n'); /* get to eol */
return retval;
}
int mygeti()
{
int ch;
int retval=0;
while(isspace(ch=getchar()));
while(isdigit(ch))
{
retval = retval * 10 + ch - '0';
ch = getchar();
}
while (ch != '\n')
ch = getchar();
return retval;
}
The other program - loopcmp: (Here I shouldn't change anything)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define LINELEN (80)
int lencmp(const char *str1, const char *str2);
int lexcmp(const char *str1, const char *str2);
char *mygets(char *buf, int len);
int main(int argc, char *argv[])
{
int(*cmpfunc)(const char *, const char *) = NULL;
char str1[LINELEN + 1];
char str2[LINELEN + 1];
if (argc != 2)
return -1;
if (!strcmp(argv[1], "lexcmp"))
cmpfunc = lexcmp;
else if (!strcmp(argv[1], "lencmp"))
cmpfunc = lencmp;
else
return -1;
while (1)
{
if (mygets(str1, LINELEN) == NULL)
break;
if (mygets(str2, LINELEN) == NULL)
break;
printf("%d\n", cmpfunc(str1, str2));
fflush(stdout);
}
return 0;
}
int lencmp(const char *str1, const char *str2)
{
int val;
val = strlen(str1) - strlen(str2);
if (val < 0)
return 1;
if (val > 0)
return 2;
return 0;
}
int lexcmp(const char *str1, const char *str2)
{
int val;
val = strcmp(str1, str2);
if (val < 0)
return 1;
if (val > 0)
return 2;
return 0;
}
char *mygets(char *buf, int len)
{
char *retval;
retval = fgets(buf, len, stdin);
buf[len] = '\0';
if (buf[strlen(buf) - 1] == 10) /* trim \r */
buf[strlen(buf) - 1] = '\0';
else if (retval) while (getchar() != '\n'); /* get to eol */
return retval;
}
This is what I get:
Picture
and what I actually need it to print the interger returned from the exec of the child and then start again and get new two strings and so on till the user exits. what am I doing wrong? I can only modify the main program (the first one)
The first thing to do is ensure you are closing all unnecessary file descriptors in each process.
This means anything relating to the lexcmp child process should be closed in the lencmp child process, and vice versa. The parent needs to close the read ends of both "TO" pipes, and the write end of both "FROM" pipes.
Each of these closures should happen exactly once, where appropriate.
As is, in the parent, you are calling close(pfd[index][0]);, close(pfd[2][1]);, and close(pfd[3][1]); in a loop.
After calling dup2, you should immediately close the first argument (the original pipe end). As is, in the the children, you are attempting to close them after execvp is called, which leads into the next issue...
If execvp succeeds, it NEVER returns, as it will completely replace the process image. Anything expected to run after it is really operating in a failure state. So
if(execvp(myargs[0], myargs) == -1)
{
perror("exec");
return -2;
}
could be written as
execvp(myargs[0], myargs)
perror("exec");
return -2;
to the same effect.
Aside: the large if .. else if .. else structure of main is a bit hard to read, and not needed since the body of each if statement results in the child processes being replaced, or exiting on error.
The next issues have to do with deadlocking, which most typically occurs when two intercommunicating processes attempt blocking reads from one another at the same time.
Your child processes expect input in a very specific way: 2 lines at a time, creating a pair of strings. The two write calls, in the form of,
write(pfd[index][1], strX, strlen(strX))
do not write any newlines, thus the children wait forever, never to send any data back, and the parent will wait forever, never receiving any data.
Aside: mygets is severely flawed, in a few ways, including being unable to detect EOF or I/O failures (this function is a SIGSEGV in waiting). One of the more obnoxious failings is that the comment here
if (buf[strlen(buf) - 1] == 10) /* trim \r */
is just plain wrong. ASCII decimal 10 is '\n', the line feed, or newline character. '\r', or carriage return, would be decimal 13. This is why using character constants 'A' instead of integer constants 65 is highly encouraged.
The side effect here, generally speaking, is your strings are stripped of a trailing newline character.
The second deadlock occurs when you go to read the child process' response.
Firstly, this example
char rbuf[1];
while(read(pfd[N][0], &rbuf, 1) > 0)
{
write(STDOUT_FILENO, &rbuf, 1);
}
is malformed. Either remove the & operators, OR change char rbuf[1]; to char rbuf;. Fixing this, and the newline problem from above, will result in the parent process reading data back from the child.
The problem then becomes that a while (read(...) > 0) loop will continuously block execution of the calling process, waiting for more data to be available.
This means another deadlock when the child process has already moved on to trying to read another pair of lines from the parent process.
A simple solution is to attempt a single, reasonably large read in the parent, and rely on the behaviour of fflush(stdout); in the child to flush the pipe to the parent.
Here is a functional -ish example, with minimal changes made. This program still has some problems, such as: the parent process generally has no idea of the status of the child processes, and relying signal propagation (^C) from the terminal to end the process tree gracefully, since loopcmp does not handle EOF (should really discuss this with whoever wrote loopcmp.c / mygets).
Additionally, mygeti is flawed as well, as an invalid input cannot be distinguished from a valid input of 0. It also does not handle EOF, or prevent signed integer overflow.
Some more robust abstraction (functions and structures) around creating child processes would help a lot to clean this up further.
This should help you progress, though.
#define _POSIX_C_SOURCE 200809L
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#define LINELEN (80)
char *mygets(char *buf, int len);
int mygeti();
void close_pipe(int fd[2])
{
close(fd[0]);
close(fd[1]);
}
int main(void)
{
char *cmpstr[] = {"lexcmp", "lencmp"};
int veclen = sizeof(cmpstr)/sizeof(char *);
char str1[LINELEN + 1];
char str2[LINELEN + 1];
int index;
int pid[2];
int pfd[4][2];
/* pfd[0] is TO lexcmp
* pfd[1] is TO lencmp
* pfd[2] is FROM lexcmp
* pfd[3] is FROM lencmp
*/
for(int i = 0; i < 4; i++)
if(pipe(pfd[i]) < 0) {
perror("pipe");
return -2;
}
pid[0] = fork();
if (pid[0] == 0) {
/* child lexcmp */
close_pipe(pfd[1]);
close_pipe(pfd[3]);
close(pfd[0][1]);
close(pfd[2][0]);
dup2(pfd[0][0], STDIN_FILENO);
dup2(pfd[2][1], STDOUT_FILENO);
close(pfd[0][0]);
close(pfd[2][1]);
char *args[] = { "./loopcmp", "lexcmp", NULL };
execvp(*args, args);
perror("exec");
return -2; /* This only returns from the child */
}
pid[1] = fork();
if (pid[1] == 0) {
/* child lencmp */
close_pipe(pfd[0]);
close_pipe(pfd[2]);
close(pfd[1][1]);
close(pfd[3][0]);
dup2(pfd[1][0], STDIN_FILENO);
dup2(pfd[3][1], STDOUT_FILENO);
close(pfd[1][0]);
close(pfd[3][1]);
char *args[] = { "./loopcmp", "lencmp", NULL };
execvp(*args, args);
perror("exec");
return -2; /* This only returns from the child */
}
/* parent */
close(pfd[0][0]);
close(pfd[1][0]);
close(pfd[2][1]);
close(pfd[3][1]);
while (1) {
printf("Please enter first string: ");
if (mygets(str1, LINELEN) == NULL)
break;
printf("Please enter second string: ");
if (mygets(str2, LINELEN) == NULL)
break;
do {
printf("Please choose (");
for (int i=0 ; i < veclen ; i++)
printf(" [%d] %s", i, cmpstr[i]);
printf(" ): ");
index = mygeti();
} while ((index < 0) || (index >= veclen));
if (0 >= dprintf(pfd[index][1], "%s\n%s\n", str1, str2)) {
fprintf(stderr, "Failed to write to child %d\n", index);
perror("dprintf");
return -2;
}
char buf[64];
ssize_t bytes = read(pfd[index + 2][0], buf, sizeof buf - 1);
if (-1 == bytes) {
perror("read from child");
return -2;
}
buf[bytes] = 0;
printf("Result: %s", buf);
}
}
char *mygets(char *buf, int len)
{
char *retval;
retval = fgets(buf, len, stdin);
buf[len] = '\0';
if (buf[strlen(buf) - 1] == 10) /* trim \r */
buf[strlen(buf) - 1] = '\0';
else if (retval)
while (getchar() != '\n'); /* get to eol */
return retval;
}
int mygeti()
{
int ch;
int retval=0;
while(isspace(ch=getchar()));
while(isdigit(ch))
{
retval = retval * 10 + ch - '0';
ch = getchar();
}
while (ch != '\n')
ch = getchar();
return retval;
}
Note the use of dprintf. If not available for whatever reason, just make sure to write a single newline after each string.
Final aside: with the way fgets works, the + 1 to the string buffer sizes are rather meaningless (although they are indirectly required here due to mygets performing its own, poorly designed buf[len] = '\0'). fgets writes at most len - 1 non-null bytes, always leaving room for the null terminating byte, which it places.
Related
I'm writing a program that will read from /etc/passwd and output the username and shell.
For example, here is the first line of the /etc/passwd file:
root:x:0:0:root:/root:/bin/bash
I need to only output the user and the shell. In this instance it would print:
root:/bin/bash
The values are separated by ':' so I just need to print the string before the first ':' and the string after the 6th ':'
Here is the code I have so far:
#include <string.h>
#define BUFFERSIZE 4096
int printf(const char *text, ...);
int main(void) {
int fd;
int buff_size = 1;
char buff[BUFFERSIZE];
int size;
fd = open("/etc/passwd", O_RDONLY);
if (fd < 0) {
printf("Error opening file \n");
return -1;
}
size = strlen(buff - 17);
size = size + 1;
while ((size = read(fd, buff, 1)) > 0) {
buff[1] = '\0';
write(STDOUT_FILENO, buff, size);
}
}
(I am creating prototypes for printf because one of the requirements was to write the program without including <stdio.h> or <stdlib.h>)
Another approach is to use a single loop and a state variable to track the state of where you are in each line based on the number of colons read. The state-variable ncolon does that below. Essentially you read every character and check whether the loop is in a state where you should write the character as output or not. You condition the write on the number of colons, whether you are before the 1st or after the last.
Putting it altogether, you could do:
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main (int argc, char **argv) {
int fd, /* file descriptor */
ofd = STDOUT_FILENO, /* output file descriptor */
ncolon = 0; /* counter - number of colons seen */
/* open file given on command line or read from stdin otherwise */
if ((fd = argc > 1 ? open (argv[1], O_RDONLY) : STDIN_FILENO) == -1) {
return 1;
}
for (;;) { /* loop continually */
unsigned char c; /* storage for character */
int rtn; /* var to save return */
if ((rtn = read (fd, &c, 1)) < 1) { /* validate read of 1 char */
if (rtn == -1) { /* return on error */
return 1;
}
break; /* break read loop on EOF */
}
if (ncolon < 1 || ncolon == 6) { /* if before 1st or after last */
write (ofd, &c, 1); /* output char */
}
if (c == '\n') { /* reset ncolon on newline */
ncolon = 0;
}
else if (c == ':') { /* increment on colon */
ncolon += 1;
}
}
if (fd != STDIN_FILENO) { /* close file */
close (fd);
}
}
Example Use/Output
$ ./read_etc-passwd /etc/passwd
root:/bin/bash
messagebus:/usr/bin/false
systemd-network:/usr/sbin/nologin
systemd-timesync:/usr/sbin/nologin
nobody:/bin/bash
mail:/usr/sbin/nologin
chrony:/usr/sbin/nologin
...
Confirm the Format
$ diff <(./read_etc-passwd /etc/passwd) <(awk -F: '{print $1":"$7}' /etc/passwd)
(no output means program output and awk output were identical)
Your program has undefined behavior when you evaluate strlen(buff - 17). It is unclear why you do this.
You can solve the problem with these simple steps:
read one byte at a time
count the ':' on the line
output the byte if the count is equal to 0 or equal to 6.
reset the count at newline (and print the newline)
Note that read(fd, &b, 1) and write(1, &b, 1) return -1 in case of error or interruption and should be restarted if errno is EINTR.
Here is a modified version:
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(void) {
int fd;
unsigned char b;
int count;
ssize_t ret;
fd = open("/etc/passwd", O_RDONLY);
if (fd < 0) {
write(2, "Error opening /etc/password\n", 28);
return 1;
}
count = 0;
for (;;) {
ret = read(fd, &b, 1);
if (ret == 0) { // end of file
break;
}
if (ret < 0) { // error
if (errno == EINTR)
continue;
write(2, "Read error on /etc/password\n", 28);
return 1;
}
if (b == '\n') {
// reset count, print b
count = 0;
} else
if (b == ':') {
// increment count, print ':' only if count == 1
count = count + 1;
if (count != 1)
continue;
} else
if (count != 0 && count != 6) {
// print b only if count is 0 or 6
continue;
}
for (;;) {
ret = write(1, &b, 1);
if (ret == 1)
break;
if (ret < 0 && errno = EINTR)
continue;
write(2, "Write error\n", 12);
return 1;
}
}
close(fd);
return 0;
}
EDIT: I have made the info here more specific and executed some recommendations from the comments.
I have a shell written in C that works like a charm when used. However, I have some tests written for a function called pipe_exec that causes a bus error. I thought it was originally from strtok in my split function (and it may still be).
The pipe_exec func basically deals with commands with pipes like ls -a | wc -l or something. It always works fine when I'm using the actual shell but with the tests, there's always a bus error if there are any flags involved with the piped commands.
The issue could jut be with my test.
But I have no clue what the issue is. It's tracing back to the strtok in my split function, but it only has a bus issue with the tests and never in any actual equivalent situations.
Any help here is appreciated. Sorry for so much code to look at.
shell_exec_tests.c
static char *args1[20] = {"ls ", " wc"}; // works
static char *args2[20] = {"ls -a", "wc -l"}; // causes bus error
static int a = 0;
static int b = 0;
void test_setup(void)
{
a = pipe_exec(args1);
b = pipe_exec(args2);
}
void test_teardown(void)
{
// nothing
}
MU_TEST(test_check)
{
mu_check(a == EXIT_SUCCESS);
mu_check(b == EXIT_SUCCESS);
}
MU_TEST_SUITE(test_suite)
{
MU_SUITE_CONFIGURE(&test_setup, &test_teardown);
MU_RUN_TEST(test_check);
}
int main()
{
MU_RUN_SUITE(test_suite);
MU_REPORT();
return MU_EXIT_CODE;
}
pipe_exec.c
// make_proc: determine if a process goes to stdout or takes in data from stdin
void make_proc(int in, int out, char **cmd)
{
pid_t rc;
int status;
rc = fork();
if (rc < 0) {
perror("fork");
exit(1);
}
if (rc == 0) {
if (in != STDIN_FILENO) {
dup2(in, STDIN_FILENO);
close(in);
}
if (out != STDOUT_FILENO) {
dup2(out, STDOUT_FILENO);
close(out);
}
execvp(*cmd, cmd);
errmsg(*cmd);
exit(1);
}
waitpid(rc, &status, WUNTRACED);
return;
}
// pipe_exec: loop through each command, connecting each through a pipe
int pipe_exec(char **args)
{
int in, status, return_val;
int pipe_no; // keep track of no. of cmds seperated by pipes
int pfd[2];
pid_t rc;
char **cmd;
return_val = EXIT_SUCCESS;
in = 0;
pipe_no = 0;
while (*args) {
cmd = split(*args, " \t\r\n");
if (!args[1]) {
break;
}
if (pipe(pfd) < 0) {
perror("pipe");
}
make_proc(in, pfd[1], cmd);
close(pfd[1]);
in = pfd[0];
args++;
pipe_no++;
}
// move pointer back
args -= pipe_no;
rc = fork();
if (rc < 0) {
perror("fork");
exit(1);
}
if (rc == 0) {
if (in != 0) dup2(in, STDIN_FILENO);
execvp(*cmd, cmd);
errmsg(*cmd);
return_val = EXIT_FAILURE;
exit(1);
}
waitpid(rc, &status, WUNTRACED);
// pretty sure i need a pipe to get the EXIT_FAILURE from
// the child if the child fails, but for now im just working
// on finding that bus error issue
return return_val;
}
And lastly, my split function:
// trim: trim leading and trailing whitespace on a string
static char *trim(char *str)
{
char *end;
// Trim leading space
while(isspace((unsigned char)*str)) str++;
if(*str == 0) // All spaces?
return str;
// Trim trailing space
end = str + strlen(str) - 1;
while(end > str && isspace((unsigned char)*end)) end--;
// Write new null terminator character
end[1] = '\0';
return str;
}
// split: take a string and break it up into an array of strings based on delim
char **split(char *s, const char *delim)
{
char **split_s;
char *token;
size_t len;
int i;
len = strlen(s);
split_s = calloc(len*2, sizeof(char*));
if (split_s == NULL) {
fprintf(stderr, "split: could not allocate memory\n");
exit(EXIT_FAILURE);
}
i = 0;
token = strtok(s, delim);
while (token != NULL) {
split_s[i] = trim(token);
token = strtok(NULL, delim);
i++;
}
split_s[i] = NULL;
return split_s;
}
I am trying to write a mock shell that saves command line history and overrides the signal action for SIGINT to trigger printing the previous 10 commands entered by the user. As far as I am aware, everything from handling all of the signals to updating cursor and running commands using execvp works fine.
I however am running into 2 problems that I am having a hard time trying to wrap my head around.
Trouble trying to actually copy the contents of the input buffer to my own c-string vector, namely histv. After calling read and storing user input in buf, I try to copy the contents of buf to the location at histv[cursor % MAX_HISTORY] (the reason I am using modulus is because it seems easier to use a kind of circular array instead of handling the case that cursor rises to some number greater than MAX_HISTORY)
When I try running the process as a background process I always get an execvp error, even when the command is valid. I know it's happing after the parent process gets the SIGUSR2 signal and creates a new child to run the commands. So I am assuming it has something to do with what happens to argv after the child process kills itself and raises SIGUSR2
Below is the code for the entire program. It is a bit messy in some spots, but overall it's rather simple. I have used all of memcpy, strcpy, and strncpy to no avail. They all compile and run without errors, but none of them seem to do anything.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/wait.h>
// limits
#define MAX_LINE 80
#define MAX_HISTORY 10
// function headers
void print_history(unsigned int const, char**);
void handler_func(int);
void read_func(char*[], char[], char**, unsigned int const);
int parse_args(char*, char**, size_t);
// globals
volatile sig_atomic_t sig_caught = 0;
void print_history (const unsigned int cursor, char **histv) {
int temp = (cursor > MAX_HISTORY) ? (cursor - MAX_HISTORY) : 0;
puts("\n\nprinting the previous ten commands...");
printf("cursor %d", cursor);
for (int i = 1; temp < cursor; temp++) {
printf("%d%s\n", i++, histv[temp % MAX_HISTORY]);
}
}
void handler_func(int sig)
{
/* update loop control variable */
sig_caught = 1;
}
int main(void)
{
// declare sigaction struct
struct sigaction sigactor;
// initialize sigaction struct
sigactor.sa_handler = handler_func;
sigemptyset(&sigactor.sa_mask);
sigactor.sa_flags = 0;
// set up sigaction for SIGINT
if (sigaction(SIGINT, &sigactor, NULL) == -1) {
perror("siagction() failed");
_exit(EXIT_FAILURE);
}
// set the buffer to no buffering
setvbuf(stdout, NULL, _IONBF, 0);
unsigned int cursor = 0;
/* initlialize history vector */
char **histv = (char**)malloc(sizeof(char*) * MAX_HISTORY);
for (int i = 0; i < MAX_HISTORY; i++)
histv[i] = (char*)malloc(sizeof(char) * MAX_LINE);
// enter shell loop
while (1) {
/* fork process and get child pid */
int cpid;
char *argsv[MAX_LINE/2+1];
char buf[MAX_LINE];
while(!sig_caught) {
cpid = fork();
/* child */
if (cpid == 0) {
read_func(argsv, buf, histv, cursor);
}
/* fork error */
else if (cpid < 0) {
perror("Error forking process");
_exit(EXIT_FAILURE);
}
/* parent process begins here */
else {
/* variable to store status returned from child*/
int cstatus;
/* suspend parent until child exits *
* store return status in cstatus */
waitpid(cpid, &cstatus, 0);
/* get status from child process and check for SIGTERM *
* SIGTERM is raised by child when someone enters '!q' */
switch(WTERMSIG(cstatus))
{
/* user wants to quit */
case SIGTERM:
puts("User issued quit command");
for (int i = 0; i < MAX_HISTORY; i++)
free((void *)histv[i]);
free((void *)histv);
_exit(EXIT_SUCCESS);
/* invalid string length */
case SIGUSR1:
puts("Please enter a valid string");
break;
/* background process */
case SIGUSR2:
cpid = fork();
if (cpid < 0) perror("Error forking process...");
else if (cpid == 0) {
if (execvp(argsv[0], argsv) < 0) {
--cursor;
perror("execvp");
kill(getpid(), SIGUSR1);
}
}
}
if (!sig_caught) cursor++;
}
}// signal loop
kill (cpid, SIGTERM);
print_history(cursor, histv);
fflush(stdout);
sig_caught = 0;
}
}
void read_func(char *argsv[], char buf[], char *histv[], unsigned int const cursor)
{
printf("\nCMD > ");
int background = 0;
size_t length = read(STDIN_FILENO, buf, MAX_LINE);
if (length > 80 || length <= 0) kill(getpid(), SIGUSR1);
/* copy buffer into history and update cursor */
memcpy(histv[cursor % MAX_HISTORY], buf, length);
printf("cursor %d", cursor);
/* parse arguments and return number of arguments */
background = parse_args(buf, argsv, length);
/* user entered quit command or string is invalid */
if (background == -1) kill(getpid(), SIGTERM);
/* signal parent to run process in the background */
if (background == 1) kill(getpid(), SIGUSR2);
/* run command */
if (execvp(argsv[0], argsv) < 0) {
perror("execvp");
_exit(EXIT_FAILURE);
}
}
int parse_args(char buf[], char *argsv[], size_t length)
{
int i, /* loop index for accessing buf array */
start, /* index where beginning of next command parameter is */
ct, /* index of where to place the next parameter into args[] */
bckg; /* background flag */
/* read what the user enters on the command line */
ct = 0;
start = -1;
bckg = 0;
if (buf[0] == '!' && buf[1] == 'q') return -1;
/* examine every character in the buf */
for (i = 0; i < length; i++) {
switch (buf[i]){
case ' ':
case '\t': /* argument separators */
if(start != -1){
argsv[ct] = &buf[start]; /* set up pointer */
ct++;
}
buf[i] = '\0'; /* add a null char; make a C string */
start = -1;
break;
case '\n': /* should be the final char examined */
if (start != -1){
argsv[ct] = &buf[start];
ct++;
}
buf[i] = '\0';
argsv[ct] = NULL; /* no more arguments to this command */
break;
case '&':
bckg = 1;
buf[i] = '\0';
break;
default: /* some other character */
if (start == -1)
start = i;
}
}
argsv[ct] = NULL; /* just in case the input line was > 80 */
return bckg;
}
Side note, the parse_args function was initially given to us and I changed it a bit to work with the rest of program, but for the most part I did not write that function.
Please go easy on me, it's been a long time since I've used C for anything and a lot of what I am doing here took a lot of effort to begin understanding how this program behaves. (~:
So both of the problems was with the fact that the memory wasn't being shared, as o11c described. I fixed the issue by using mmap instead of malloc which in turn simplified my program as I no longer had to handle the memory management. Changes are described below.
char **histv = (char**)malloc(sizeof(char*) * MAX_HISTORY);
for (int i = 0; i < MAX_HISTORY; i++)
histv[i] = (char*)malloc(sizeof(char) * MAX_LINE);
was changed to
char **histv = (char**)mmap(NULL, (sizeof(char*) * MAX_HISTORY), (PROT_READ | PROT_WRITE), (MAP_SHARED | MAP_ANONYMOUS), -1, 0);
for (int i = 0; i < MAX_HISTORY; i++) histv[i] = (char*)mmap(NULL, (sizeof(char) * MAX_LINE), (PROT_READ | PROT_WRITE), (MAP_SHARED | MAP_ANONYMOUS), -1, 0);
While I do not know if this is the best way to do this, it solved my problem.
Also note that I did explicitly unmap the memory using munmap even though it technically should be handled automatically on exit.
I'm trying to write a program which read output of another program and write to the program as input.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
char str[30];
printf("Input string : ");
fflush(stdout);
scanf("%s", &str);
fflush(stdout);
printf("entered string is %s\n", str);
return 0;
}
This program1 is a simple program reading input from stdin and print the string entered.
And here in the program2, I tried to create 2 pipes and execute the program1.
And read the output of program1 and get user input and deliver the string user entered to program1.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
typedef struct pipe_rw
{
pid_t cpid;
int pipe_r[2];
int pipe_w[2];
} RWPIPE;
char *get_user_input(void)
{
char buf[128];
char *input;
char ch;
int n;
int len = 0;
memset(buf, 0x0, 128);
while((ch = fgetc(stdin)) != 0xa)
{
buf[len] = ch;
len++;
}
input = malloc(sizeof(char) * (len));
strncpy(input, buf, (len));
return input;
}
int pclose_rw(RWPIPE *rwp)
{
int status, ret = 0;
if (rwp)
{
if (rwp->cpid > 0)
{
kill(rwp->cpid, SIGTERM);
do {
ret = waitpid(rwp->cpid, &status, WUNTRACED|WCONTINUED);
} while (!WIFEXITED(status) && !WIFSIGNALED(status));
}
close(rwp->pipe_r[0]);
close(rwp->pipe_w[1]);
free(rwp);
}
return ret;
}
RWPIPE *popen_rw(const char *command)
{
RWPIPE *rwp = (RWPIPE *)malloc(sizeof(*rwp));
if (rwp == NULL)
return NULL;
memset(rwp, 0x00, sizeof(*rwp));
if (pipe(rwp->pipe_r) != 0 || pipe(rwp->pipe_w) != 0)
{
free(rwp);
return NULL;
}
rwp->cpid = fork();
if (rwp->cpid == -1)
{
free(rwp);
return NULL;
}
if (rwp->cpid == 0)
{
dup2(rwp->pipe_w[0], STDIN_FILENO);
dup2(rwp->pipe_r[1], STDOUT_FILENO);
close(rwp->pipe_r[0]);
close(rwp->pipe_r[1]);
close(rwp->pipe_w[0]);
close(rwp->pipe_w[1]);
execl(command, command, NULL);
printf("Error: fail to exec command - %s ..\n", command);
exit (1);
}
else
{
close(rwp->pipe_r[1]);
close(rwp->pipe_w[0]);
}
return rwp;
}
ssize_t read_p(RWPIPE *rwp, void *buf, size_t count)
{
return read(rwp->pipe_r[0], buf, count);
}
ssize_t write_p(RWPIPE *rwp, const void *buf, size_t count)
{
return write(rwp->pipe_w[1], buf, count);
}
int main(void)
{
char rbuf[BUFSIZ], wbuf[BUFSIZ];
int ret, len, n = 0;
char *string;
RWPIPE *rwp = popen_rw("./read_write");
if (rwp == NULL)
{
printf("Error: fail to open command ..\n");
return EXIT_FAILURE;
}
while (1)
{
memset(rbuf, 0x00, sizeof(rbuf));
if (read_p(rwp, rbuf, sizeof(rbuf)) < 1)
{
printf("No more input..\n");
break;
}
printf("%s", rbuf);
string = get_user_input();
len = strlen(string);
ret = write_p(rwp, string, len);
if (ret != len)
{
printf("Write %d bytes (expected %d) ..\n", ret, len);
break;
}
printf("end");
}
pclose_rw(rwp);
return EXIT_SUCCESS;
}
If run the program2 reads output of program1 successfully.
And it gets user input but it failed to give the string entered from user to program1.
[root#localhost test_code]# ./rw_pipe
Input string : 1234
^C
Please give me some ideas why it works like this.
Your primary problem is that the data written to the child does not end with a newline, so the child is not aware that the message is complete (it isn't complete) and the child is still busy reading while the parent is waiting for a response — a deadlock.
This code adds some instrumentation and fixes the problem by including the newline in the string read by get_input().
The original program expects two lots of input (one in response to the prompt from read_write, the other in response to the echoed output), but dies from a SIGPIPE when it tries to send the second input to the now-exited child. The code below circumvents that by ignoring SIGPIPE signals, which means that the parent gets a write error instead of being killed by the signal.
There's an unusual control flow between the two programs, and if you made read_write into an iterative program, you'd see that it generates two outputs for a single input. That's not the way it's usually done, of course. Fixing that is outside of the scope of the immediate exercise, though.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
typedef struct pipe_rw
{
pid_t cpid;
int pipe_r[2];
int pipe_w[2];
} RWPIPE;
static char *get_user_input(void)
{
char buf[128];
char *input;
char ch;
size_t len = 0;
while((ch = fgetc(stdin)) != '\n' && ch != EOF && len < sizeof(buf) - 2)
buf[len++] = ch;
buf[len++] = '\n';
buf[len] = '\0';
input = malloc(sizeof(char) * (len + 1));
strncpy(input, buf, (len + 1));
printf("Got: [%s]\n", input);
return input;
}
static int pclose_rw(RWPIPE *rwp)
{
int status, ret = 0;
if (rwp)
{
if (rwp->cpid > 0)
{
kill(rwp->cpid, SIGTERM);
do {
ret = waitpid(rwp->cpid, &status, WUNTRACED|WCONTINUED);
} while (!WIFEXITED(status) && !WIFSIGNALED(status));
}
close(rwp->pipe_r[0]);
close(rwp->pipe_w[1]);
free(rwp);
}
return ret;
}
static RWPIPE *popen_rw(const char *command)
{
RWPIPE *rwp = (RWPIPE *)malloc(sizeof(*rwp));
if (rwp == NULL)
return NULL;
memset(rwp, 0x00, sizeof(*rwp));
if (pipe(rwp->pipe_r) != 0 || pipe(rwp->pipe_w) != 0)
{
free(rwp);
return NULL;
}
rwp->cpid = fork();
if (rwp->cpid == -1)
{
free(rwp);
return NULL;
}
if (rwp->cpid == 0)
{
dup2(rwp->pipe_w[0], STDIN_FILENO);
dup2(rwp->pipe_r[1], STDOUT_FILENO);
close(rwp->pipe_r[0]);
close(rwp->pipe_r[1]);
close(rwp->pipe_w[0]);
close(rwp->pipe_w[1]);
execl(command, command, NULL);
fprintf(stderr, "Error: fail to exec command '%s'.\n", command);
exit (1);
}
else
{
close(rwp->pipe_r[1]);
close(rwp->pipe_w[0]);
}
return rwp;
}
static ssize_t read_p(RWPIPE *rwp, void *buf, size_t count)
{
return read(rwp->pipe_r[0], buf, count);
}
static ssize_t write_p(RWPIPE *rwp, const void *buf, size_t count)
{
return write(rwp->pipe_w[1], buf, count);
}
int main(void)
{
char rbuf[BUFSIZ];
int ret, len;
char *string;
signal(SIGPIPE, SIG_IGN);
RWPIPE *rwp = popen_rw("./read_write");
if (rwp == NULL)
{
printf("Error: fail to open command ..\n");
return EXIT_FAILURE;
}
while (1)
{
memset(rbuf, 0x00, sizeof(rbuf));
if (read_p(rwp, rbuf, sizeof(rbuf)) <= 0)
{
printf("No more input..\n");
break;
}
printf("From child: [%s]\n", rbuf);
string = get_user_input();
len = strlen(string);
printf("Length %d: [%s]\n", len, string);
ret = write_p(rwp, string, len);
if (ret != len)
{
fprintf(stderr, "Write %d bytes (expected %d) ..\n", ret, len);
break;
}
printf("end cycle\n");
}
printf("End of loop\n");
pclose_rw(rwp);
return EXIT_SUCCESS;
}
Sample run
The program is rwpipe53; the input I typed was Ocelot and Grumble.
$ ./rwpipe53
From child: [Input string : ]
Ocelot
Got: [Ocelot
]
Length 7: [Ocelot
]
end cycle
From child: [entered string is Ocelot
]
Grumble
Got: [Grumble
]
Length 8: [Grumble
]
Write -1 bytes (expected 8) ..
End of loop
$
Note how the square brackets (any pair of marker symbols can be used if you prefer) shows where the data starts and ends. I find that a valuable technique when debugging code.
Although my program works correctly in all cases, it doesn't use a pipe to connect the output of the first of two commands to the second when they're separated by a pipe symbol. I wrote the output of the first command to a file, then redirected the standard input of the second command to the file when the process to run that command was run. I need to use a pipe system call to create the pipe and obtain the file descriptors
for the pipe, and then run the two processes at the same time. It is a homework question and I have done 99% of the work but somehow am not able to get the pipe system call working... what I've been trying is that for an input like: Command 1 | Command 2
inside the child process for command 2 I close FD[0] then dup FD[1] and for command 1 close FD[1] then dup FD[1] and close FD[0].
I am hell confused with the file descriptors when using pipe.... I have to use a pipe
Any sort of help is appreciated. Execute function is where I am forking the processes.
Here's my code...
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <regex.h>
/* Global Variables */
extern char **environ; /* Environmental Variables */
char *pathList[10]; /* List of paths from the $PATH */
int pathCount; /* Count of the # of paths in $PATH */
char *pathSet; /* Variable through which $PATH is retrieved */
int hasPipe = 0;
int cmdNo = 0;
/* This function takes the 'finalPath', the full path to executable,argList[],the
full command-line input arguments and argCount, the number of arguments from
command-line as input. It the creates a child process, in turn invokes the
execve() that finally executes the executable in 'finalPath' with the arguments
in 'argText' all stored into the args[] appropriately. Child process also handles
input and output file re-direction.
*/
void execute(char *finalPath, char *argList[], int argCount)
{
int k,fd,ofound,pos,i; /* flags and temporary variables */
pid_t pid; /* process ID */
int status, which;
char msg[100];
char *args[4]; /* argument list for execve() */
int spCase = 0;
ofound = 0;
pos=0;
pid = fork(); /* Creating a new process using fork() */
if (pid == -1) /* Checking for errors in process creation */
{
write(1,"Fork failed.\n",12);
exit(1);
}
/**************************
Checking for parent process
***************************/
if (pid != 0)
{
which = wait(&status);
if (which == -1)
{
write(1,"Wait failed.\n",12);
exit(1);
}
if (status & 0xff)
{ /* Case of abnormal termination */
sprintf(msg,"ERROR: <dShell> # process %d terminated abnormally for reason %d\n",which, status & 0xff);
write(1,msg,strlen(msg));
}
else
{ /* Case of normal termination */
sprintf(msg,"process %d terminated normally with status %d\n",which, (status >> 8) & 0xff);
write(1,msg,strlen(msg));
}
}
/*************************
Checking for child process
**************************/
if (pid == 0)
{
char argText[50];
argText[0] = '\0';
int std_fd;
if(cmdNo==0 && hasPipe)
{
close(1);
std_fd = open("temp.out", O_WRONLY | O_CREAT | O_TRUNC, S_IRWXU);
dup(std_fd);
}
else if(cmdNo==1 && hasPipe)
{
close(0);
std_fd = open("temp.out", O_RDONLY);
dup(std_fd);
}
/* Finding the first re-direction operator */
for( i = 0; i < argCount ; ++i)
{
if( ofound != 1 && ofound != 2)
{
if( strcmp(argList[i],"<") == 0 )
{
fd = open(argList[i+1],O_RDONLY);
if (fd < 0)
{
sprintf(msg,"ERROR: %s could not be opened\n", argList[i+1]);
write(1, msg, strlen(msg));
exit(5);
}
ofound = 1;
strcpy(argText,"\0");
close(0);
dup(fd);
close(fd);
}
else if(strcmp(argList[i],">") == 0)
{
fd = open(argList[i+1],O_CREAT | O_WRONLY, 0777);
pos = i;
ofound = 2;
strcpy(argText,"\0");
if (fd < 0)
{
sprintf(msg,"ERROR: %s could not be opened\n", argList[i+1]);
write(1, msg, strlen(msg));
exit(5);
}
close(1);
dup(fd);
close(fd);
}
}
}
/* If input re-direction operator is found check for an output re-direction along with it */
if(ofound == 1)
{
for( k = 0; k < argCount && ofound != 2; ++k)
{
if( strcmp(argList[k],">") == 0 )
{
fd = open(argList[k+1],O_CREAT | O_WRONLY , 0777);
spCase = 1;
ofound = 2;
strcpy(argText,"\0");
if (fd < 0)
{
sprintf(msg,"ERROR: %s could not be opened\n", argList[k+1]);
write(1, msg, strlen(msg));
exit(5);
}
close(1);
dup(fd);
close(fd);
}
}
}
/* If the re-direction operators are not found */
if( ofound == 0 )
{
for(i = 1; i < argCount; ++i)
{
strcat(argText, argList[i]);
strcat(argText, " ");
}
spCase = 2;
}
/* Case when both arguments and output re-direction operators are found */
if (spCase == 0)
{
if(pos == 0)
{
for( i = 3; i<argCount; ++i)
{
strcat(argText, argList[i]);
strcat(argText," ");
}
}
if(pos == argCount - 2)
{
for( i = 1; i<argCount - 2; ++i)
{
strcat(argText, argList[i]);
strcat(argText," ");
}
}
}
argText[strlen(argText)-1] = '\0'; /*because I added an extra space so trimming that*/
/* Running the execve */
args[0] = finalPath;
if(strlen(argText) == 0) /* checking if argText is populated */
{
args[1] = NULL;
}
else
{
args[1] = argText;
args[2] = NULL;
}
/* Execute command,if it returns that means it failed and need to display error and exit */
execve(args[0], args, environ);
sprintf(msg, "ERROR! execve() failed");
write(1, msg, strlen(msg));
}
}
/*******************************************************************************
This function checks if the path is accessible and continues to execute the
command. If the path does not exist of is not accessible, variable 'retFlag'
is used to return 0 to the calling function.
********************************************************************************/
int checkPath(char *exepath, char *argList[], int argCount, int flag)
{
char *finalPath;
int retFlag = flag;
if(access(exepath,X_OK) == 0)
{
finalPath = exepath;
retFlag = 1;
execute(finalPath,argList,argCount);
return retFlag;
}
else
return retFlag;
}
/**********************************************************************************
This function checks if the first argument is a path and if so calls checkPath().
Else it gets the paths set to the $PATH variable, tokenizes it, pads it with the
first token of input command and calls checkPath(). If the correct path is established,
the variable 'found' is used to kick out of the for loop.
************************************************************************************/
void setPath(char *argList[], int argCount)
{
char *exepath;
char com[50];
char emsg[80];
char *command;
int i,found = 0;
/* Seperating the command if redirection is used */
if( strcmp(argList[0],"<") == 0 || strcmp(argList[0],">") == 0 )
{
command = argList[2];
}
else
command = argList[0];
/* In case of no redirection, storing the commands and arguments into a array */
if(strcmp(command,"#") == 0) /* Checking for comment statements */
{
write(1,"ERROR: No command(s) found. Only comment present/n",48);
}
else
{
if(strstr(command,"/")) /* Checking if the entire path is given as a part of the command */
{
exepath = command;
found = checkPath(exepath,argList,argCount,0);
}
else /* building the path and storing it in 'com' */
{
for(i = 0; i< pathCount && found != 1; i++)
{
sprintf(com,"%s%s%s",pathList[i],"/",command);
exepath = com;
found = checkPath(exepath,argList,argCount,0);
}
}
if(found == 0)
{
sprintf(emsg,"%s%s",command,":COMMAND DOES NOT EXIST");
write(1,emsg,sizeof(emsg));
write(1,"\n",1);
}
}
}
/* Tokenizes commands into words */
void tokens(char *cmdStr)
{
char cmd[100];
strcpy(cmd,cmdStr);
char *result;
char delims[] = " , ";
char *argList[20];
int argCount = 0;
/*Tokenize the individual command into strings */
result = strtok(cmd,delims);
while( result != NULL )
{
argList[argCount] = result;
result = strtok( NULL, delims );
++argCount;
}
setPath(argList,argCount);
}
/* Tokenizes multiple commands into single commands */
void tokenize(char *inputStr)
{
int i,cmdCount = 0;
char *cmdResult;
char *cmdStr[100];
char delimiters[] = "|";
cmdResult = strtok(inputStr, delimiters);
while(cmdResult != NULL)
{
cmdStr[cmdCount]=cmdResult;
cmdResult = strtok(NULL, delimiters);
cmdCount++;
}
if( cmdCount > 1 )
hasPipe = 1;
else
hasPipe = 0;
for( i=0; i<cmdCount ; i++)
{
cmdNo = i%cmdCount;
tokens(cmdStr[i]);
}
}
int main(int argc, char *argv[])
{
char prompt[8]; /* String that stores the personalized prompt */
char *path; /* Temporary variable used for tokenization*/
char ch; /* Temporary variable used in read() */
int chCount; /* # of characters read from the prompt */
int entry; /* return variable of read() */
int flag; /* Flag to go read the next command when newline is found */
regex_t reIgnore;
char pattern[20]="^\\s*$|^#.*";
/* Tokenizing the paths asociated with the $PATH and storing them in a array declared globally */
pathCount = 0;
pathSet = getenv("PATH");
if ( !pathSet)
{
write(1, "ERROR: PATH environment does not exist.\n", 40);
exit(1);
}
path = strtok(pathSet,":");
while(path != NULL)
{
pathList[pathCount] = path;
path = strtok(NULL,":");
++pathCount;
}
/* Checks for blanks and tabs in Step 2 */
if ( regcomp(&reIgnore, pattern, REG_EXTENDED) )
{
write(1, "Error. \n",9);
exit(2);
}
sprintf(prompt,"<dShell> # "); /* Storing the personalized shell prompt into 'prompt' */
/* Reading the input from command line and passing it to tokenize() */
while(1)
{
char inputStr[100]; /* String into which inputs are read into */
chCount = 0;
flag = 0;
hasPipe = 1;
write(1,prompt,strlen(prompt)); /* Printing out the personalized shell prompt */
/* This will read a character 1 by 1 until it reaches the end of file */
entry = read(0,&ch,1);
if(!entry)
exit(0);
/* Reading the input and storing it in inputStr as long as newline is not encountered */
while( entry != 0 && flag == 0 )
{
/* A newline has been found so a new command will need to be executed */
/* The inputStr till this point is sent to tokenize() */
if( ch == '\n' )
{
inputStr[chCount] = '\0';
flag = 1;
if(chCount > 0) {
if(strcmp(inputStr,"exit") == 0)
exit(3);
else
tokenize(inputStr);
}
}
inputStr[chCount] = ch;
chCount++;
if(flag == 0)
entry = read( 0, &ch, 1 );
}
}
}
See the man page for pipe(2). It has this example:
#include <sys/wait.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int
main(int argc, char *argv[])
{
int pipefd[2];
pid_t cpid;
char buf;
assert(argc == 2);
if (pipe(pipefd) == -1) {
perror("pipe");
exit(EXIT_FAILURE);
}
cpid = fork();
if (cpid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (cpid == 0) { /* Child reads from pipe */
close(pipefd[1]); /* Close unused write end */
while (read(pipefd[0], &buf, 1) > 0)
write(STDOUT_FILENO, &buf, 1);
write(STDOUT_FILENO, "\n", 1);
close(pipefd[0]);
_exit(EXIT_SUCCESS);
} else { /* Parent writes argv[1] to pipe */
close(pipefd[0]); /* Close unused read end */
write(pipefd[1], argv[1], strlen(argv[1]));
close(pipefd[1]); /* Reader will see EOF */
wait(NULL); /* Wait for child */
exit(EXIT_SUCCESS);
}
}