Segfault from execvp - c

I'm trying to exec input that I get from a socket. I take the message buffer and put it into a char *[] and it is null terminated it works for ls but it won't work with paramaters like ls -la.
char *CMD[msg.c+1];
CMD[msg.c] = NULL;
Here is me parsing and using execvp.
//parse
char *tmp = NULL;
tmp = strtok(msg.v,space);
for(i = 0; i < msg.c; i++){
CMD[i] = tmp;
tmp = strtok(NULL,space);
printf("%s\n",CMD[i]);
}
int fd[2];
pipe(fd);
pid_t pid = fork();
if (pid == 0) {
close(fd[0]);
dup2(fd[1], 1);
msg.c = execvp(CMD[0],CMD);
}

This is probably so late answer that no one cares... To summarise there are some issues in your program, but I do not really know why you got segmentation fault or if you already fixed that.
In the code:
use of strsep(3) instead of obsolete strtok(3)
strdup(3) to be sure no other code modifies msg, if not, then you can remove strdup and free
added some error handling with err(3) to find errors early (earlier).
you cannot do msg.c = execvp(CMD[0], CMD), the program is executing CMD and will never return; to be able to retrieve the exit code, use wait(2) or waitpid(2)
variable length of arrays (char *CMD[msg.c+1]) is allowed in C99, but I do not use it, you can if you want. :)
And here is the lengthy code, probably a lot of bugs, but please tell me and I will try to fix:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <err.h>
#include <sys/types.h>
#include <sys/wait.h>
#define MAX_ARGS 10
int
main(int argc, char *argv[])
{
char *cmd[MAX_ARGS];
char *s;
char buf[256];
char msg[] = "ls -la /";
int n;
int fd[2];
pid_t pid;
int stat;
if ((s = strdup(msg)) == NULL)
err(1, "strdup");
for (n = 0; n < MAX_ARGS && (cmd[n] = strsep(&s, " ")) != NULL; n++)
;
if (pipe(fd) == -1)
err(1, "pipe");
switch ((pid = fork())) {
case -1:
close(fd[0]);
close(fd[1]);
err(1, "fork");
case 0: /* child */
close(fd[0]);
dup2(fd[1], STDOUT_FILENO);
close(fd[1]);
execvp(cmd[0], cmd);
err(1, "execvp");
default: /* parent */
free(s);
close(fd[1]);
while ((n = read(fd[0], buf, sizeof(buf))) > 0)
write(STDOUT_FILENO, buf, n);
waitpid(pid, &stat, 0);
fprintf(stderr, "child(%d) exited with %d\n",
pid, WEXITSTATUS(stat));
}
return (0);
}

Related

Will prefixing the script path with `/usr/bin/env -i` make usage of system() and popen() secure?

I am doing a code review right now and I was blown away by the amount of code that the guy wrote just to execute one script (with hard-coded path and no input arguments) and read the output out of it. (BTW he had a lot of bugs in it.)
I encountered a similar issue before and it was suggested to me that doing pipe/fork/exec manually is "more secure". I am aware of two potential problems:
As system() and popen() execute shell commands, it is possible to slip potentially harmful environment variable values to the program executed this way
Another is that when the command is constructed from user input. I can imagine subshells doing all kinds of harmful things and so on.
I was wondering if suggesting to use popen() instead would be OK in this case. It would greatly simplify the code. The second point is not an issue as there is no user input. By using env -i to clean the environment before executing the script should make the first issue go away:
FILE *fp = popen("/usr/bin/env -i /path/to/some/fancy/script.sh", "r");
/* ... */
Are there any other potential issues I am missing, or is doing the script execution "manually" still worth the effort?
This is technically not your answer to the question on how to call popen() safely, but the answer to the question you should have asked: "How to make a better popen()"
The function child_spawn(argv, env, flags) will set up pipes for communicating with a child process, and spawn the child. It will return a
struct child that holds the child pid and file descriptors for communication.
argv is a NULL terminated string array of command and arguments, while env is a NULL terminated string array of environment variables. If env is NULL, the child will inherit the environment from the parent.
So argv should have the form
const char* argv[] = {"/bin/ls", "-l", NULL};
And env should have the form
const char **env = NULL;
or
const char *env[] =
{
"PATH=/bin:/usr/bin",
"HOME=/tmp",
"SHELL=/bin/sh",
NULL
};
When you are finished with the child process, child_wait() will close the file descriptors associated with the child and wait for it to exit.
To use child_spawn() as a substitute of popen() you call it like this:
struct child c = child_spawn(argv, NULL, CHILD_PIPE_STDOUT);
You may now read from c->fd_out to get the content of the childs stdout.
The constants CHILD_PIPE_STDIN, CHILD_PIPE_STDOUT and CHILD_PIPE_STDERR may be "or"-ed together to have a valid file descriptors in c->fd_in, c->fd_out, c->fd_err
Be aware that if you spawn a child with CHILD_PIPE_STDIN|CHILD_PIPE_STDOUT, there is a risk of deadlock when reading and writing, unless you do non-blocking io.
The function my_system() is an example on how to implement a safer system() using child_spawn()
/*
We have to #define _GNU_SOURCE to get access to `char **environ`
*/
#define _GNU_SOURCE
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/sendfile.h>
#include <stdio.h>
#include <stdlib.h>
#include <error.h>
#include <errno.h>
#include <string.h>
struct child
{
pid_t pid;
int fd_in;
int fd_out;
int fd_err;
};
static void
close_if_valid(int fd)
{
if (fd != -1) close(fd);
}
/*
Closes all file-descriptors for child communication
and waits for child to exit
returns status value from waitpid().
see `man waitpid` on how to interpret that value
*/
int child_wait(struct child *c)
{
close_if_valid(c->fd_in);
close_if_valid(c->fd_out);
close_if_valid(c->fd_err);
int status;
pid_t p = waitpid(c->pid, &status, 0);
if (p == 0)
error(1, errno, "waitpid() failed");
return status;
}
int
dup_if_valid(int fd1, int fd2)
{
if (fd1 != -1 && fd1 != fd2)
return dup2(fd1, fd2);
return fd2;
}
pid_t
child_spawn_fd(const char *const argv[], const char *const env[],
int in, int out, int err)
{
fflush(stdout);
pid_t p = fork();
if (p)
return p;
/***********************
We are now in child
***********************/
/*
Set file descriptors to expected values,
-1 means inherit from parent
*/
if (dup_if_valid(in, 0) == -1)
goto CHILD_ERR;
if (dup_if_valid(out, 1) == -1)
goto CHILD_ERR;
if (dup_if_valid(err, 2) == -1)
goto CHILD_ERR;
/*
close all unneeded file descriptors
This will free resources and keep files and sockets belonging to
the parent from beeing open longer than needed
On *BSD we may call `closefrom(3);`, but this may not exits
on Linux. So we loop over all possible file descriptor numbers.
A better solution, is to look in `/proc/self/fs`
*/
int max_fd = sysconf(_SC_OPEN_MAX);
for (int fd = 3; fd <= max_fd; fd++)
close(fd);
if (env)
environ = (char **)env;
/* Change to execvp if command should be looked up in $PATH */
execv(argv[0], (char * const *)argv);
CHILD_ERR:
_exit(1);
}
#define CHILD_PIPE_STDIN (1 << 0)
#define CHILD_PIPE_STDOUT (1 << 1)
#define CHILD_PIPE_STDERR (1 << 2)
#define READ_END 0
#define WRITE_END 1
struct child
child_spawn(const char * const argv[], const char * const env[], int flags)
{
int in_pipe[2] = {-1, -1};
int out_pipe[2] = {-1, -1};
int err_pipe[2] = {-1, -1};
if (flags & CHILD_PIPE_STDIN)
if (pipe(in_pipe))
error(EXIT_FAILURE, errno, "pipe(in_pipe) failed");
if (flags & CHILD_PIPE_STDOUT)
if (pipe(out_pipe))
error(EXIT_FAILURE, errno, "pipe(out_pipe) failed");
if (flags & CHILD_PIPE_STDERR)
if (pipe(err_pipe))
error(EXIT_FAILURE, errno, "pipe(err_pipe) failed");
pid_t p = child_spawn_fd(argv, env,
in_pipe[READ_END],
out_pipe[WRITE_END],
err_pipe[WRITE_END]);
if (p == -1)
error(EXIT_FAILURE, errno, "fork() failed");
close_if_valid(in_pipe[READ_END]);
close_if_valid(out_pipe[WRITE_END]);
close_if_valid(err_pipe[WRITE_END]);
struct child c =
{
.pid = p,
.fd_in = in_pipe[WRITE_END],
.fd_out = out_pipe[READ_END],
.fd_err = err_pipe[READ_END],
};
return c;
}
/*
Safer implementation of `system()`. It does not invoke shell, and takes
command as NULL terminated list of execuatable and parameters
*/
int
my_system(const char * const argv[])
{
struct child c = child_spawn(argv, NULL, 0);
int status = child_wait(&c);
if (WIFEXITED(status))
return WEXITSTATUS(status);
else
return -1;
}
int
main (int argc, char **argv)
{
printf("Running 'ls -l' using my_system()\n");
printf("---------------------------------\n");
fflush(stdout);
const char * ls_argv[] =
{
"/bin/ls",
"-l",
NULL
};
int e = my_system(ls_argv);
printf("---------\n");
printf("\exit code ---> %d\n", e);
printf("\nRunning 'ls -l' using child_spawn() and reading from stdout\n");
printf("-----------------------------------------------------------\n");
fflush(stdout);
struct child c = child_spawn(ls_argv, NULL, CHILD_PIPE_STDOUT);
/*
Read from the childs stdout and write to current stdout
*/
size_t copied = 0;
while (1)
{
char buff[4096];
ssize_t rlen = read(c.fd_out, buff, 4096);
if (rlen == -1)
error(EXIT_FAILURE, errno, "read() failed");
if (rlen == 0)
break;
size_t written = 0;
while (written < rlen)
{
ssize_t wlen = write(1, buff + written, rlen - written);
if (wlen == -1)
error(EXIT_FAILURE, errno, "write() failed");
written += wlen;
}
copied += written;
}
/* Wait for child to end */
int status = child_wait(&c);
printf("---------\n");
if (WIFEXITED(status))
{
printf(" ---> child exited normally with exit code %d and with %ld bytes copied\n",
WEXITSTATUS(status),
copied);
}
else
printf(" ---> child exited by som other reason than _exit()");
printf("\nWriting to Elmer Fudd filter\n");
const char *quote = "Be very very quiet, I'm hunting rabbits!\n";
printf("Original text: %s", quote);
printf("-----------------------------------------------------------\n");
fflush(stdout);
const char *fudd_filter[] =
{"/bin/sed", "-e" "s/r/w/g", NULL};
struct child c2 = child_spawn(fudd_filter, NULL, CHILD_PIPE_STDIN);
size_t qlen = strlen(quote);
const char *q = quote;
while (qlen)
{
ssize_t wlen = write(c2.fd_in, q, qlen);
if (wlen == -1)
error(EXIT_FAILURE, errno, "write() failed");
q += wlen;
qlen -= wlen;
}
child_wait(&c2);
}

Capturing output from execv() [duplicate]

I'm trying to write a C program that grabs command output and then i'll be passing that to another program.
I'm having an issue, I cant work out how to get the command output and store it. Below is a sample of what I have
if(fork() == 0){
execl("/bin/ls", "ls", "-1", (char *)0);
/* do something with the output here */
}
else{
//*other stuff goes here*
}
so basically im wondering if there is any way i can get the output from the "execl" and pass it to some thing else (e.g. via storing it in some kind of buffer).
Suggestions would be great.
You have to create a pipe from the parent process to the child, using pipe().
Then you must redirect standard ouput (STDOUT_FILENO) and error output (STDERR_FILENO) using dup or dup2 to the pipe, and in the parent process, read from the pipe.
It should work.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define die(e) do { fprintf(stderr, "%s\n", e); exit(EXIT_FAILURE); } while (0);
int main() {
int link[2];
pid_t pid;
char foo[4096];
if (pipe(link)==-1)
die("pipe");
if ((pid = fork()) == -1)
die("fork");
if(pid == 0) {
dup2 (link[1], STDOUT_FILENO);
close(link[0]);
close(link[1]);
execl("/bin/ls", "ls", "-1", (char *)0);
die("execl");
} else {
close(link[1]);
int nbytes = read(link[0], foo, sizeof(foo));
printf("Output: (%.*s)\n", nbytes, foo);
wait(NULL);
}
return 0;
}
Open a pipe, and change stdout to match that pipe.
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int pipes[2];
pipe(pipes); // Create the pipes
dup2(pipes[1],1); // Set the pipe up to standard output
After that, anything which goes to stdout,(such as through printf), comes out pipe[0].
FILE *input = fdopen(pipes[0],"r");
Now you can read the output like a normal file descriptor. For more details, look at this
Thanks Jonathan Leffler, and i optimize the above code for it can't read all response for one time.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#define die(e) do { fprintf(stderr, "%s\n", e); exit(EXIT_FAILURE); } while (0);
int main() {
int link[2];
pid_t pid;
char foo[4096 + 1];
memset(foo, 0, 4096);
if (pipe(link)==-1)
die("pipe");
if ((pid = fork()) == -1)
die("fork");
if(pid == 0) {
dup2 (link[1], STDOUT_FILENO);
close(link[0]);
close(link[1]);
execl("/bin/ls", "ls", "-1", (char *)0);
die("execl");
} else {
close(link[1]);
int nbytes = 0;
std::string totalStr;
while(0 != (nbytes = read(link[0], foo, sizeof(foo)))) {
totalStr = totalStr + foo;
printf("Output: (%.*s)\n", nbytes, foo);
memset(foo, 0, 4096);
}
wait(NULL);
}
return 0;
}
If you want the output in a string (char *), here's an option (for Linux at least):
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/uio.h>
#include <sys/wait.h>
#include <unistd.h>
char* qx(char** cmd, int inc_stderr) {
int stdout_fds[2];
pipe(stdout_fds);
int stderr_fds[2];
if (!inc_stderr) {
pipe(stderr_fds);
}
const pid_t pid = fork();
if (!pid) {
close(stdout_fds[0]);
dup2(stdout_fds[1], 1);
if (inc_stderr) {
dup2(stdout_fds[1], 2);
}
close(stdout_fds[1]);
if (!inc_stderr) {
close(stderr_fds[0]);
dup2(stderr_fds[1], 2);
close(stderr_fds[1]);
}
execvp(*cmd, cmd);
exit(0);
}
close(stdout_fds[1]);
const int buf_size = 4096;
char* out = malloc(buf_size);
int out_size = buf_size;
int i = 0;
do {
const ssize_t r = read(stdout_fds[0], &out[i], buf_size);
if (r > 0) {
i += r;
}
if (out_size - i <= 4096) {
out_size *= 2;
out = realloc(out, out_size);
}
} while (errno == EAGAIN || errno == EINTR);
close(stdout_fds[0]);
if (!inc_stderr) {
close(stderr_fds[1]);
do {
const ssize_t r = read(stderr_fds[0], &out[i], buf_size);
if (r > 0) {
i += r;
}
if (out_size - i <= 4096) {
out_size *= 2;
out = realloc(out, out_size);
}
} while (errno == EAGAIN || errno == EINTR);
close(stderr_fds[0]);
}
int r, status;
do {
r = waitpid(pid, &status, 0);
} while (r == -1 && errno == EINTR);
out[i] = 0;
return out;
}
int main() {
char* argv[3];
argv[0] = "ls";
argv[1] = "-la";
argv[2] = NULL;
char* out = qx(argv, 0);
printf("%s", out);
free(out);
}

Almost done linux shell pipe

Hi i'm trying to build a shell on linux and i'm stuck with the pipelining part.First i take the inputs from the user like "ls | sort" then when i try to run the program it lookls like the commands ls and sort doesnt work
It looks like i've done everything right but it still cant seem to work. can you help please. thanks in advance
include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <fcntl.h>
#include <sys/stat.h>
#define CREATE_FLAGS (O_WRONLY | O_CREAT | O_APPEND)
#define CREATE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
int setup();
int main(int argc, const char * argv[])
{
while(1)
{
printf("333sh: ");
if(setup())
break;
}
return 0;
}
int setup(){
char input [128];
char *arg[32];
int i = 1;
while(fgets(input,128,stdin)!=NULL)
{
arg[0] = strtok(input," \n");
while((arg[i]=strtok(NULL," \n")) != NULL){
i++;
}
if (arg[1]!=NULL && strcmp(arg[1],"|")==0 && arg[2]!=NULL ){
pid_t pid;
int fd[3];
pipe(fd);
pid=fork();
if(pid<0){
printf("fork");
}
else if(pid==0){
pid_t cpid;
cpid=fork();
if(cpid==0){
dup2(fd[2], 1); // Replace stdin with the read end of the pipe
close(fd[0]); // Don't need another copy of the pipe read end hanging about
close(fd[2]);
execvp(arg[0],arg);
}
else if(pid>0){
dup2(fd[0], 0); // Replace stdout with the write end of the pipe
close(fd[0]); //close read from pipe, in parent
close(fd[2]); // Don't need another copy of the pipe write end hanging about
execvp(arg[2], arg);
}
}
else if(pid>0){
waitpid(pid, NULL,0);
}
}
}
}
Your biggest problem is that your argument lists for your commands are malformed (after you've resolved the index 2 vs index 1 issue with the pipe file descriptors diagnosed by Ben Jackson in his answer).
I added a function:
static void dump_args(int pid, char **argv)
{
int i = 0;
fprintf(stderr, "args for %d:\n", pid);
while (*argv != 0)
fprintf(stderr, "%d: [%s]\n", i++, *argv++);
}
and called it just before the calls to execvp(), and the output I got was:
$ ./ns
333sh: ls | sort
args for 29780:
0: [ls]
1: [|]
2: [sort]
ls: sort: No such file or directory
ls: |: No such file or directory
^C
$
The control-C was me interrupting the program. The arguments for each command must be 'the command name' (conventionally, the name of the executable), followed by the remaining arguments and a null pointer.
Your tokenization code is not providing two correct commands.
You also have a problem with which PID you're looking at:
cpid = fork();
if (cpid == 0)
{
dup2(fd[1], 1);
close(fd[0]);
close(fd[1]);
dump_args(getpid(), arg);
execvp(arg[0], arg);
fprintf(stderr, "Failed to exec %s\n", arg[0]);
exit(1);
}
else if (pid > 0) // should be cpid!
{
dup2(fd[0], 0);
close(fd[0]);
close(fd[1]);
dump_args(pid, arg);
execvp(arg[1], arg);
fprintf(stderr, "Failed to exec %s\n", arg[1]);
exit(1);
}
You also need to close the pipe file descriptors in the parent process before waiting.
This code compiles and 'works' for simple x | y command sequences such as ls | sort or ls | sort -r. However, it is far from being a general solution; you'll need to fix your argument parsing code quite a lot before you reach a general solution.
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int setup(void);
int main(void)
{
while (1)
{
printf("333sh: ");
if (setup())
break;
}
return 0;
}
static void dump_args(int pid, char **argv)
{
int i = 0;
fprintf(stderr, "args for %d:\n", pid);
while (*argv != 0)
fprintf(stderr, "%d: [%s]\n", i++, *argv++);
}
int setup(void)
{
char input[128];
char *arg[32];
int i = 1;
while (fgets(input, sizeof(input), stdin) != NULL)
{
arg[0] = strtok(input, " \n");
while ((arg[i] = strtok(NULL, " \n")) != NULL)
{
i++;
}
if (arg[1] != NULL && strcmp(arg[1], "|") == 0 && arg[2] != NULL)
{
pid_t pid;
int fd[2];
arg[1] = NULL;
pipe(fd);
pid = fork();
if (pid < 0)
{
fprintf(stderr, "fork failed\n");
return 1;
}
else if (pid == 0)
{
pid_t cpid = fork();
if (cpid < 0)
{
fprintf(stderr, "fork failed\n");
return 1;
}
else if (cpid == 0)
{
printf("Writer: [%s]\n", arg[0]);
dup2(fd[1], 1);
close(fd[0]);
close(fd[1]);
dump_args(getpid(), arg);
execvp(arg[0], arg);
fprintf(stderr, "Failed to exec %s\n", arg[0]);
exit(1);
}
else
{
printf("Reader: [%s]\n", arg[2]);
assert(cpid > 0);
dup2(fd[0], 0);
close(fd[0]);
close(fd[1]);
dump_args(getpid(), &arg[2]);
execvp(arg[2], &arg[2]);
fprintf(stderr, "Failed to exec %s\n", arg[2]);
exit(1);
}
}
else
{
close(fd[0]);
close(fd[1]);
assert(pid > 0);
while (waitpid(pid, NULL, 0) != -1)
;
}
}
}
return 1;
}
You're using fd[0] and fd[2] but pipe(fd) only sets fd[0] and fd[1].
Couple of immediate problems:
setup() has no return value, but you expect an int
The definition of fgets is:
char * fgets ( char * str, int num, FILE * stream );
Get string from stream
Reads characters from stream and stores them as a C string into str until (num-1) characters have been read or either a newline or the end-of-file is reached, whichever happens first.
A newline character makes fgets stop reading, but it is considered a valid character by the function and included in the string copied to str.
fgets() returns NULL on an error; otherwise it returns a pointer to str. So this seems like a very unsound test condition in your while loop.

Sending C Command Line Arguments Through Pipe

I am trying to send my command line arguments through from the child process to the parent process using a pipe but can't figure out what I'm doing wrong. My code is below. Any help is appreciated. Thanks in advance.
int main(int argc, char argv[])
pid_t child;
int fd[2];
pipe(fd);
if((child = fork() == 0)
{
int len = strlen(argv[1]);
close(fd[0];
write(fd[1], argv[1], len);
exit(0);
}
else //Assuming process won't fail for now
{
char src[10]; //Just using 10 for now, no arguments have more than 10 characters
read(fd[0], src, (strlen(src)));
fprintf(stderr, "%s\n", src);
close(fd[0]);
}
}
You had a bunch of little errors but as far as I can see, believe it or not, this may be your real problem.
read(fd[0], src, (strlen(src)));
My guess is that the first char is null and you are successfully reading 0 bytes.
Change to
read(fd[0], src, (sizeof(src)));
In your larger project make sure you read and write in loops. You are not guaranteed to read or write what you specify.
You may need to close fd[1] inside the else block first.
check this example
#include <sys/wait.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;
if (argc != 2) {
fprintf(stderr, "Usage: %s <string>\n", argv[0]);
exit(EXIT_FAILURE);
}
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);
}
}
You have assumed that fork() will not fail.
But what about pipe()??
Assume both get completed successfully, then closing fds properly is requered.
your if-else blocks should be like this.
if((child = fork() == 0)
{
int len = strlen(argv[1]);
close(fd[0]);//I assume this was your typo. otherwise it would not even get compiled
write(fd[1], argv[1], len);
close(fd[1]);
exit(0);
}
else //Assuming process won't fail for now
{
close(fd[1]);
char src[10]; //Just using 10 for now, no arguments have more than 10 characters
read(fd[0], src, (strlen(src)));
fprintf(stderr, "%s\n", src);
close(fd[0]);
}

UNIX Commands Implemented in C

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.

Resources