I have an assignment for school where I have to create a shell which can do the following:
read incoming command, parse each part of command
fork child process and execute each command without (< > >> |)
successfully execute each command with <, >, >>
successfully execute each command with |
I am seriously lost... I am new to shell and I have no clue on what to do from here.
My code gives me an error stating segmentation fault (core dumped). Any and all help will be greatly appreciated.
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#define MAX_ARG 10
int main()
{
char line [256];
char prompt[] = "sh2 % ";
char command[256], *args[MAX_ARG];
int pid;
char status;
int i;
/* spit out the prompt */
printf("%s", prompt );
while( fgets(line, sizeof line, stdin) != NULL)
{
/* fgets leaves '\n' in input buffer. ditch it */
line [strlen(line)-1] = '\0';
while (line != NULL)
{
//parse command and arg
args[0] = strtok(line, " "); //grab command
for (i=1; i<MAX_ARG; i++) //grab arguments, to assume max = 10?
{
//if a single command with arguments then set command & argument
//for (i>0)
{
// check to see if the command is 'exit'
if(!strcmp("exit", args[i]))
{
exit(0);
}
{
int p[2];
pipe(p)
if (fork() == 0) //child
{
close (0);
dup(p[0]);
exec("cmd2");
}
else
{
close(1);
close(p[0]);
close(p[1]);
dup(p[1]);
exec("cmd1");
}
close(0);
open("stdout.txt", "r");
if (fork()== 0)
{
exec("cmd3");
}
}
else if (!strcmp(">", args[i]))
open("stderr.txt". "w")
if (fork() == 0)
{
exec("cmd1");
}
}
else if (!strcmp(">>", args[i]))
{
close(1);
open("stdout_stderr.txt", "w");
if (fork() == 0)
{
close(2);
dup(1);
exec("cmd2");
}
}
else
{
pid = fork();
if (pid == 0)
{
status = execvp(command,args);
exit(0);
}
else
{
waitpid(-1);
}
}
}
}
}
}
return 0;
}
You have quite a few things wrong in your program, so it's hard to point to one line and say change this. I think you are trying to do too much at once and not building on a solid base.
In programming you want to start small and build on your progress, your professor did you a favor by lining up the steps:
read incoming command, parse each part of command
fork child process
and execute each command without (< > >> |)
successfully execute
each command with <, >, >>
successfully execute each command with |
Try to get #1 working befor moving on to #2 and as mentioned before using functions is going to help a lot. I would suggest looking at this post http://bytes.com/topic/c/answers/215994-writing-shell-c which will give you a simple shell you could model from. Here is a baseline parser to get you started on #1 (based on the post mentioned)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char line[4096] = {0};
char safeline[4096] = {0};
int done = 0;
int i = 0;
char *s = line;
char **t = NULL;
char *prompt = ">";
char *args = NULL;
char *nl = NULL;
int main(void)
{
while(!done)
{
printf("%s", prompt);
fflush(stdout);
if(NULL == fgets(line, sizeof line, stdin))
{
t = NULL;
done = 1;
}
else
{
nl = strchr(line, '\n');
if(nl != NULL)
{
*nl = '\0';
strcpy(safeline, line);
}
else
{
int ch;
printf("Line too long! Ignored.\n");
while((ch = getchar()) != '\n' && ch != EOF)
{
continue;
}
if(ch == EOF)
{
done = 1;
}
}
args = strchr(line, ' ');
if(args != NULL)
{
*args++ = '\0';
}
if(!done)
{
printf("command - %s : args - %s\n",s, args);
}
}
}
}
Related
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 was writing a simple shell in C to execute external commands like command1 & command2 and here is my code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
static char line[1024];
char *and, *or, *col;
char *arg[1024];
static char* skipwhite(char* s)
{
while (isspace(*s)) ++s;
return s;
}
void run(char *cmd)
{
*and='\0';
int stat;
cmd= skipwhite(cmd);
char *next= strchr(cmd, ' ');
int i=0;
printf("%s \n", cmd);
while(next!=NULL)
{
*next='\0';
arg[i]=cmd;:
i++;
cmd= skipwhite(next+1);
next= strchr(cmd, ' ');
}
//arg[i]='\0';
int shmid = shmget(66, sizeof(int), IPC_CREAT | 0777);
int *status=(int *)shmat(shmid, NULL, 0);
*status=1;
int pid=fork();
if(pid==0)
{
int *v=(int *)shmat(shmid, NULL, 0);
if(execvp(arg[0], arg)==-1)
{
*v=0;
_exit(EXIT_FAILURE);
}
}
else
{
waitpid(pid, &stat, 0);
if(*status==0)
exit(0);
//printf("Trying to execute 2nd command\n");
cmd=and+1;
printf("%s \n", cmd);
and = strchr(cmd, '&');
if(and==NULL)
{
cmd= skipwhite(cmd);
next = strchr(cmd, ' ');
i=0;
while(next!=NULL)
{
*next='\0';
arg[i]=cmd;
i++;
cmd= skipwhite(next+1);
next= strchr(cmd, ' ');
}
arg[i]=cmd;
//arg[i+1]='\0';
if(execvp(arg[0], arg)==-1)
_exit(EXIT_FAILURE);
return;
}
else
run(cmd);
}
}
int main()
{
int status, pid;
printf("SIMPLE SHELL made by me. Type 'exit' or send EOF to exit.\n");
while(1)
{
printf("$> ");
fflush(NULL);
if(!fgets(line, 1024, stdin))
return 0;
char *cmds= line;
if(strcmp(cmds, "exit")==0)
exit(0);
and= strchr(cmds, '&');
or= strchr(cmds, '|');
col= strchr(cmds, ';');
if(and!=NULL)
{
pid=fork();
if(pid==0)
run(cmds);
else
waitpid(pid, &status, 0);
}
}
return 0;
}
My motivation is :
Whenever shell receives an external command, it forks itself, say the new process is 1 and the command is command1 & command2. 1 again forks itself for executing command1 and command2. If command1 is successfully executed, then only a new process for command2 is created else not.
The problem is , the last command is never executed even if it is a correct one. I cannot figure out the problem in the code.
If any information required, please drop a comment below.
In this loop
while(next!=NULL)
{
*next='\0';
arg[i]=cmd;:
i++;
cmd= skipwhite(next+1);
next= strchr(cmd, ' ');
}
//arg[i]='\0';
You copy all parts of the string into arg except the last.
For the last token in your command line you get a valid pointer to cmd but NULL for next if there is no extra space after the token, leaving this token unhandled.
You also commented out the sentinel for the arg array which is required for execv family of functions.
You might rework the loop:
while(cmd && *cmd) {
arg[i++]=cmd;
if (next) {
*next='\0';
cmd = skipwhite(next+1);
next = strchr(cmd, ' ');
}
else {
cmd = NULL;
}
}
arg[i]='\0';
Update regarding extra question:
You have a '\n' at the end of your command because you don't chop it after reading the command line.
Function fgets reads the whole line into the buffer, up to (and including) the the '\n' it the buffer is large enough.
You simply need to remove it immediately after calling fgets:
size_t len = strlen(line);
if (line[len-1] == '\n')
line[len-1] = 0;
or
line[strlen(line)-1] = 0;
The first version also works fine if you enter 1023 characters for your commands where the '\n' cannot be stored in the buffer.
The second version only works fine if you enter less than 1023 characters. For a maximum length command it will kill the last character.
I'm trying to write a shell program in c.
The program needs to have multiple processes created by fork function and be able to print multiple output line in one command.
For example, like linux terminal, if input is "ls ; ps ; pwd ;", the output should be like this.
$./shell
shell> ls ; ps ; pwd ;
(ls output)
(ps output)
(pwd output)
And it should be able to open a file and display the command list and the output that file contains.(batch mode I guess?)
Let's say these command lists are in the batch file.
batch
1 ls
2 ps
3 ls ; pwd ; ps
And the output is
$./shell batch
shell> ls
shell> (ls output)
shell> ps
shell> (ps output)
shell> ls ; pwd ; ps
shell> (ls output)
(ps output)
(pwd output)
Here's the code I wrote
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <string.h>
#include <fcntl.h>
void command(char* myargs[][10], char* buffer);
int tokenizing(char* myargs[][10], char* buffer);
int main(int argc, char* argv[]) {
int fd;
char buffer[200];
char* myargs[10][10];
char* token;
if(argc >= 2) {
if((fd=open(argv[1], O_RDONLY)) == -1)
printf("cannot open file\n");
else {
read(fd, buffer, 200);
printf("%s\n", buffer);
token = strtok(buffer, "\n");
while(token != NULL) {
printf("%s\n", token);
command(myargs, token);
token = strtok(NULL, "\n");
}
return 0;
}
}
while(1) {
printf("prompt> ");
if(fgets(buffer, 200, stdin) == NULL||
strcmp(buffer, "quit\n") == 0)
break;
command(myargs, buffer);
}
return 0;
}
void command(char* myargs[][10], char* buffer) {
int rc = fork();
if(rc < 0) {
fprintf(stderr, "fork failed\n");
} else if(rc == 0) {
int n = tokenizing(myargs, buffer);
for(int i = 0 ; i < n; i++) {
int rc2 = fork();
if(rc2 < 0) {
fprintf(stderr, "for failed\n");
} else if(rc2 == 0) {
execvp(myargs[i][0], myargs[i]);
printf("%s: command not found\n", myargs[i][0]);
exit(0);
} else {
wait(NULL);
}
}
exit(0);
}
else {
wait(NULL);
}
}
int tokenizing(char* myargs[][10], char* buffer) {
int i = 0;
int j = 0;
int k = 0;
char* token;
char* subCommand[10];
token = strtok(buffer, ";\n");
while(token != NULL) {
subCommand[k] = token;
k++;
token = strtok(NULL, ";\n");
}
for(int i = 0; i < k; i++) {
token = strtok(subCommand[i], " \n");
while(token != NULL) {
myargs[i][j] = token;
j++;
token = strtok(NULL, " \n");
}
myargs[i][j] = NULL;
j=0;
}
}
This code works fine but has some issues. When this code runs with a batch file, I struggle with some errors.
When the program is executed, I think the output should be like the above image file - as far as I know.
But frequently the program comes out with some weird command line that I didn't even type. These results just happen alternatively.
In addition, if you see 'ps' lists, you can see two shell programs are running.
Can you guys please help me solve these problems?
'strings' in C need a NUL terminator. Calling strng functions like printf("%s....") and strtok() on char arrays that are not securely NUL-terminated results in undefined behaviour.
read() returns a value. You can use it to load a terminator into 'buffer'. To be super-safe, you should attempt to read only [buffer size -1] chars to ensure that there will always be enough space for the terminator, eg:
buffer[read(fd, buffer, 199)]='\0';
School project to make our own shell and facing segmentation fault issues.
Can someone help?
Edit: kind of running but "quit" doesn't trigger the exit and execvp can't run properly (says : "no such file or directory" & "or ls:invalid option -- ' "
void interactive_mode();
void batch_mode(char *path);
void parse(char *str,char *delimiter, char **args);
int execute(char **args);
int main(int argc, char *argv[]) {
if(strcmp(argv[1], "-b") == 0) {
batch_mode(argv[2]);
}
else if(strcmp(argv[1], "-i") == 0) {
interactive_mode();
}
}
I suspect the segmentation fault problem derives from the interactive and batch modes code
void interactive_mode() {
int quit_flag;
char str[512];
char *commands[128];
char *args[128];
quit_flag = 0;
while(1) {
printf("whatever> ");
if (fgets(str,512,stdin) == NULL) {
exit(0); //error reading
}
int i = 0;
parse(str,";",commands); //split the string into commands eg "ls -a ; ls -l" -> "ls -a ","ls -l"
while (commands[i]!=NULL) {
parse(commands[i]," ",args); //split commands into arguments eg "ls -l" -> "ls","-l"
i++;
quit_flag = execute(args);
}
if (quit_flag == 1)
exit(1);
}
}
Trying to read from file:
void batch_mode(char *path) {
FILE *fp;
char str[512];
char *commands[128];
char *args[128];
int res;
fp = fopen(path,"r");
if (fp == NULL) {
perror("Error opening file");
exit(4); //file not open
}
while(1) {
if (fgets(str, 512, fp) == NULL)
break;
int i = 0;
parse(str, ";", commands);
while (commands[i] != NULL) {
parse(commands[i], " ", args);
i++;
res = execute(args);
}
}
fclose(fp);
printf("whatever>Press Any Key to Continue\n");
getchar();
}
Parsing strings:
void parse(char *str, char *delimiter, char **args) {
char *pch;
int i = 0;
pch = strtok(str,delimiter);
while (pch != NULL) {
args[i] = pch;
i++;
pch = strtok(NULL, delimiter);
}
args[i] = NULL;
}
Executing with fork:
int execute(char **args) {
char path[50];
pid_t pid;
int status;
if(strcmp(args[0],"quit")==0) return 1; //exited by quit
strcpy(path,"/bin/");
strcat(path,args[0]);
if ((pid = fork()) < 0) {
perror("fork failed");
exit(2);
}
else if (pid == 0) {
if(execvp(path, args) < 0) {
perror("execvp failed");
exit(3);
}
}
else {
while (wait(&status) != pid) /* wait for completion */
;
}
}
Please help?
Have you tried to print all your variables argv, str, commands ... To make sure all is going on as expected there ? It could also help you locate in your script where you segfault. It will take you time, sorry I have no quick answer. For starterprintf("%d", __LINE__) would print the line number you are at, don't forget to add an explicit statement.
Otherwise you could use gdb. There is quite a learning curve, but it could be useful for another project.
I have the following problem: In my code, here in line 83, I have this: check = wait(NULL);
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include <sys/stat.h>
#include <sys/types.h>
//---------------------------------
//Function: parse_cmdline(const char* cmdline)
//This function takes the input from stdin
//and returns it as array of strings.
//If stdin is /bin/ls -l /usr/include
//the function will return ["/bin/ls","-l","/usr/include"]
//---------------------------------
char** parse_cmdline(const char* cmdline) {
int count, word_count = 0;
char** line_parsed, line_return;
char *pch, *cmdline_copy = (char*)malloc(sizeof(char)*(strlen(cmdline)+1));
strcpy(cmdline_copy, cmdline);
pch = strtok(cmdline_copy," \n\t\r");
while (pch != NULL) {
++word_count;
pch = strtok(NULL, " \n\t\r");
}
line_parsed = (char**)malloc((word_count+1)*sizeof(char*));
count = 0;
strcpy(cmdline_copy, cmdline);
pch = strtok(cmdline_copy," \n\t\r");
while (pch != NULL) {
line_parsed[count] = (char*)malloc((strlen(pch) + 1)*sizeof(char));
strcpy(line_parsed[count], pch);
++count;
pch = strtok(NULL," \n\t\r");
}
line_parsed[count] = NULL;
free(cmdline_copy);
return line_parsed;
}
int main() {
int count = 0, check;
size_t size;
char* line;
char** cmdline;
while(1) {
check = 0;
printf("$Monkey Eats:< ");
getline(&line, &size, stdin);
cmdline = parse_cmdline(line);
pid_t pid = fork();
if (pid == -1) {
perror("fork");
return -1;
} else if(pid == 0) {
struct stat _stat;
stat(cmdline[0],&_stat);
if(_stat.st_mode & S_IXUSR){
execvp(cmdline[0], cmdline);
}else fprintf(stderr,"%s: Permission denied!\n",cmdline[0]);
perror("");
exit(1);
}else {
check = wait(NULL);
}
count = 0;
while(cmdline[count] != NULL) {
free(cmdline[count]);
++count;
}
free(cmdline);
}
return 0;
}
It makes me a problem. When I run it and when I type a command I have the following message:
$Monkey Eats:< ls
ls: Permission denied!
No such file or directory
If I have only wait(NULL); the program runs normally without a problem. Can somebody tell me what is the problem? Thank you :)
The problem is trying to run ls. execvp() doesn't know where ls is. Try running /bin/ls as your command.
The problem is: stat(cmdline[0],&_stat); - the return code is not checked. What if file not found ? The program continues, and finds that _stat.st_mode & S_IXUSR is 0 (randomly).
However you may test the program as is with "/bin/ls" as input..