Loop after input of NULL value in simple C shell - c

I'm trying to write a simple C shell. My problem is that I have written the program so that when the user enters a NULL value I've got the shell to exit and stop running. However after using different shells i've realised that the shell continues to loop. Is there anyway to fix this without having to rewrite my code? I'm still quite a novice to C.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#define MAX_CMD_SIZE 512
int getPath(){
printf("PATH = %s\n", getenv("PATH"));
return 0;
}
int setPath(char* arg){
setenv("PATH", arg, 1);
return 0;
}
int setwd() {
char *arg;
arg = getenv("HOME");
chdir(arg);
return 0;
}
int main()
{
char buff[MAX_CMD_SIZE]; //buff used to hold commands the user will type in
char *defaultPath = getenv("PATH");
char *args[50] = {NULL};
char *arg;
int i;
pid_t pid;
setwd();
while(1){
printf(">");
if (fgets(buff, MAX_CMD_SIZE, stdin) == NULL) { //Will exit if no value is typed on pressing enter
setPath(defaultPath);
getPath();
exit(0);
}
arg = strtok(buff, " <|>\n\t");
i = 0;
if (arg == NULL) return -1;
while (arg != NULL){
printf("%s\n", arg);
args[i] = arg;
arg = strtok(NULL, " <|>\n\t");
i++;
}
if (strcmp(args[0], "exit") == 0 && !feof(stdin)){ //Will exit if input is equal to "exit" or ctrl + d
setPath(defaultPath);
getPath();
exit(0);
}
else {
pid = fork();
if (pid < 0){ //Error checking
fprintf(stderr, "Fork Failed\n");
} else if (pid == 0){ //This is the child procsess
execvp(args[0], args);
exit(-1);
} else { //Parent Process
wait(NULL); // Parent will wait for child to complete
}
}
}
return 0;
}

Related

How to make a history command

I am having trouble creating a child process, and I'm not sure if I have the execvp argument right. Is there a way to fix it so it'll pass correctly?
int execute(char* input) {
int i = 0;
char* shell_argv[MAX_CMD_LINE_ARGS];
memset(shell_argv, 0, MAX_CMD_LINE_ARGS * sizeof(char));
//passing pointer of input and element list 128
int shell_argc = parse(input, shell_argv);
int status = 0;
pid_t pid = fork();
if (pid < 0) {
fprintf(stderr, "Fork() failed\n"); } // send to stderr
else if (pid == 0) { // child
// fill in code for execvp(...) <- this is what I'm having trouble with
if (execvp(shell_argv[0], shell_argv) == -1 && strcmp(input, "history") != 0) {
printf("Invalid command\n");
}
} else { // parent ----- don't wait if you are creating a daemon (background) process
while (wait(&status) != pid) { }
}
return 0;
}
There are some errors in your code:
shell_argv is an array of char*, memset length shoud be MAX_CMD_LINE_ARGS * sizeof(char*); or use a simple way char *shell_argv[MAX_CMD_LINE_ARGS] = {0};
I can't find a standard function parse(), maybe you implemented it by your self. I have made a workround to run the code.
memory for the element in shell_argv should be free at the end of founction.
The input argument of execvp is correct. Here is all the code for your reference.
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#include <stdlib.h>
#define MAX_CMD_LINE_ARGS 255
int parse(char *input, char** shell_argv){
shell_argv[0] = strdup("date");
shell_argv[1] = strdup("+%s");
}
int execute(char *input)
{
int i = 0;
char *shell_argv[MAX_CMD_LINE_ARGS];
memset(shell_argv, 0, MAX_CMD_LINE_ARGS * sizeof(char*));
// passing pointer of input and element list 128
int shell_argc = parse(input, shell_argv);
int status = 0;
pid_t pid = fork();
if (pid < 0)
{
fprintf(stderr, "Fork() failed\n");
} // send to stderr
else if (pid == 0)
{ // child
// fill in code for execvp(...) <- this is what I'm having trouble with
if (execvp(shell_argv[0], shell_argv) == -1 && strcmp(input, "history") != 0)
{
printf("Invalid command\n");
}
}
else
{ // parent ----- don't wait if you are creating a daemon (background) process
while (wait(&status) != pid)
{
}
}
for(int i=0;i<MAX_CMD_LINE_ARGS;i++){
if(shell_argv[i] != NULL){
free(shell_argv[i]);
}
}
return 0;
}
int main(){
execute("date +%s");
return 0;
}
Test result:
gxie#ubuntu20:~/test $ gcc main.c
gxie#ubuntu20:~/test $ ./a.out
1663837441

strstr() and exit(0) trouble in C

so I'm working on a simulated linux shell using C (homework), and I have it exiting the program if the user just simply types in "quit".
However, if they throw "exit" somewhere inside a command... "cat file ; exit"
I need it to execute the command like normal, then quit.
I know that I still have to filter the string entered and strip it of the exit, but for now, I'm just simply trying to get it to recognize the substring of "exit" using strstr() and to exit the program.
It just keeps looping if input contains a string with substring "exit" currently.
Thank you.
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#define bSize 1000
void driveLoop();
char *userInput(void);
int main(int argc, char **argv){
driveLoop();
return 0;
}
void driveLoop(void){
char *comTokens[100];
char *tempTokens;
char *command;
char *cd;
char *cdDir;
char *cdTemp;
char cdBuf[bSize];
char checkExit[] = "exit";
for (;;){
printf("> ");
command = userInput();
if (!command)
break;
char *exitPtr = strstr(command, checkExit); // using strstr on the pointer containing the input
int i = 0;
tempTokens = strtok(command, " \t\n");
while (tempTokens && i < 99){
comTokens[i++] = tempTokens;
tempTokens = strtok(NULL, "\t\n");
}
if (strcmp(comTokens[0], "exit") == 0){ // this exit is working normally
exit(0);
}
if (strcmp(comTokens[0], "cd") == 0){
cd = getcwd(cdBuf, sizeof(cdBuf));
cdDir = strcat(cd, "/");
cdTemp = strcat(cdDir, comTokens[1]);
continue;
}
comTokens[i] = NULL;
pid_t cFork = fork();
if (cFork == (pid_t) - 1){
perror("fork");
}
else if (cFork == 0){
execvp(comTokens[0], comTokens);
perror("exec");
if (exitPtr != NULL){ // here's where I'm calling the exit
exit(0); // nothing happens though. It just keeps looping.
}
}
else {
int status;
waitpid(cFork, &status, 0);
}
}
}
char *userInput(void){
char *input = NULL;
size_t size = 0;
getline(&input, &size, stdin);
return input;
}
else if (cFork == 0){
execvp(comTokens[0], comTokens);
perror("exec");
if (exitPtr != NULL){ // here's where I'm calling the exit
exit(0); // nothing happens though. It just keeps looping.
}
}
execvp doesn't return if it succeeds, so your if will normally never be executed. (And if execvp does return, you probably want to exit the child unconditionally, preferably with _exit().)
What you really want is for the parent process to exit. So you probably wanted to put this code in the parent branch of the fork:
else {
int status;
waitpid(cFork, &status, 0);
// add it here
if (exitPtr != NULL){
exit(0);
}
}

Address out of bounds in C, problems with making an ls command

I am trying to make a some what shell in C but I am having problems with making the ls command. mkdir, and cd work fine but with ls it gives me
"Address out of bounds segmentation error"
Hope somebody can help me. Here's my code.
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <readline/readline.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
printf("\033[1;33mWelcome To Crisp Bacon Shell\n");
while (1) {
printf("\033[0m%s $", hostname);
input = readline("");
command = get_input(input);
child_pid = fork();
if (child_pid < 0) {
perror("Fork failed");
exit(1);
}else if (child_pid == 0) {
/* Never returns if the call is successful */
execvp(command[0], command);
printf("This won't be printed if execvp is successul\n");
} else {
waitpid(child_pid, &stat_loc, WUNTRACED);
}
free(input);
free(command);
}
return 0;
}
char **get_input(char *input) {
char **command = malloc(8 * sizeof(char *));
char *separator = " ";
char *parsed;
int index = 0;
parsed = strtok(input, separator);
while (parsed != NULL) {
command[index] = parsed;
index++;
parsed = strtok(NULL, separator);
}
command[index] = NULL;
return command;
}
The only thing I understand it has something to do with memory and references or pointers but I tried changing everything from & refrencing to pointers and it just gave me more errors what do I do?
There were many undeclared variables in your code snippets. You also need to fetch the hostname, it isn't a global variable. It's also a best practice to declare your functions before using them.
This works fine:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <readline/readline.h>
#include <unistd.h>
#include <sys/wait.h>
char **get_input(char *input) {
char **command = malloc(8 * sizeof(char *));
char *separator = " ";
int index = 0;
char *parsed = strtok(input, separator);
while (parsed != NULL && index < 8) { // you need to make sure the index does not overflow the array
command[index] = parsed;
index++;
parsed = strtok(NULL, separator);
}
command[index] = NULL;
return command;
}
int main() {
printf("\033[1;33mWelcome To Crisp Bacon Shell\n");
while (1) {
// hostname does not exist, you need to fetch it
char hostname[1024];
gethostname(hostname, 1023); // POSIX only
printf("\033[0m%s $", hostname);
char *input = readline(NULL);
char **command = get_input(input);
pid_t child_pid = fork();
if (child_pid < 0) {
perror("Fork failed");
exit(1);
} else if (child_pid == 0) {
/* Never returns if the call is successful */
execvp(command[0], command);
printf("This won't be printed if execvp is successul\n");
} else {
waitpid(child_pid, NULL, WUNTRACED); // since you don't use the middle argument, no need to point to valid data
}
free(input);
free(command);
}
return 0;
}

SHELL with PIPE communication BETWEEN 2 child processes with the same parent process

I want to create a UNIX based shell that supports commands with 1 pipe.
For example, the command ls | wc
I have wrote the following code but when I try an input like the one above, the program actes weird and can't figure out where the fault is.
To be more specific, it appears to fork() properly, then exec() with the proper parameters but only the command before the pipe (ls) exits properly. The command after the pipe (wc) never exits actually and as a result there is no output from the program.
I 've put some printf to help me figure out where is the fault in the code.
Functions fetch_input and string_tokenizer are tested and work fine.
The fault must be somewhere below the point pointed in the code.
Any suggestion to help me find what is the fault would be appreciated.
Thanks in advance.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#define ANSI_COLOR_GREEN "\x1b[32m"
#define ANSI_COLOR_CYAN "\x1b[36m"
#define ANSI_COLOR_RESET "\x1b[0m"
char * fetch_input(char * buffer);
char * trim(char * string);
char ** string_tokenizer(char * string, char c);
int main(int arc, char * argv[]){
char *input, *init_input, **commands, **com1, **com2;
pid_t fork_pid_1, fork_pid_2, ret_pid;
int stat,i, fd[2];
init_input = (char *)malloc(sizeof(char)*256);
input = init_input;
input = fetch_input(input);
while(strcmp(input,"exit")!=0){
commands = string_tokenizer(input,'|');
com1 = string_tokenizer(commands[0],' ');
if(commands[1]==0){
fork_pid_1 = fork();
if(fork_pid_1<0){
printf("Fork Error!\n");
_exit(1);
}
else if(fork_pid_1==0){
execvp(com1[0],com1);
printf("Exec Error!\n");
_exit(1);
}
else{
ret_pid=wait(&stat);
}
}
else{ //the fault is probably somewhere below that point
if(pipe(fd)<0){ _exit(1);}
fork_pid_1 = fork();
if(fork_pid_1<0){
printf("Fork Error!\n");
_exit(1);
}
else if(fork_pid_1==0){
printf("First command, ready to exec...(pid:%d)\n",getpid());
close(fd[0]);
dup2(fd[1],1);
close(fd[1]);
execvp(com1[0],com1);
printf("Exec Error!\n");
_exit(1);
}
else{
com2 = string_tokenizer(commands[1],' ');
fork_pid_2=fork();
if(fork_pid_2<0){
printf("Fork Error!\n");
_exit(1);
}
else if(fork_pid_2==0){
printf("Second command, ready to exec...(pid:%d)\n",getpid());
close(fd[1]);
dup2(fd[0],0);
close(fd[0]);
execvp(com2[0],com2);
printf("Exec Error!\n");
_exit(1);
}
else{
printf("Now we 're in the parent process...(p_pid:%d)\n",getpid());
while( (ret_pid=waitpid(-1,&stat,0)) >0 ){
printf("Child process (%d) exited with status:%d\n",ret_pid,stat);
}
}
}
}
input = init_input;
input = fetch_input(input);
}
return 0;
}
char * fetch_input(char * buffer){
int i, sum;
do{
printf(ANSI_COLOR_CYAN "$" ANSI_COLOR_RESET);
fflush(stdin);
fgets(buffer,256,stdin);
if(buffer[strlen(buffer)-1]=='\n'){
buffer[strlen(buffer)-1]='\0';
}
sum=0;
for(i=0;i<strlen(buffer);i++){
if(buffer[i]==' '){sum++;}
}
}while(strlen(buffer)==sum);
buffer = trim(buffer);
return buffer;
}
char * trim(char * string){
int i;
i=strlen(string);
while(string[i-1]==' '){
i--;
}
*(string+i)='\0';
while(isspace(*string)){string++;}
return string;
}
char ** string_tokenizer(char * string, char c){
int j=0,k,i,done,found,first,last;
char ** ret, *str;
str=string;
if( (str==0) || (strlen(str)==0) ) return NULL;
ret = (char **)malloc(sizeof(char*));
do{
done=0;
found=0;
i=0;
first=-1;
last=-1;
while( (str[i]!='\0') && (done==0) ){
if( (str[i]==c) && (found==0) ){
i++;
}
else if( (str[i]!=c) && (found==0) ){
found=1;
first=i;
i++;
}
else if( (str[i]!=c) && (found!=0) ){
i++;
}
else if( (str[i]==c) && (found!=0) ){
done=1;
last=i;
}
}
if(done!=0){
*(ret+j) = (char *)malloc(sizeof(char)*(last-first+1));
for(k=first;k<last;k++){
*(*(ret+j)+(k-first)) = str[k];
}
*(*(ret+j)+(k-first)) = '\0';
*(ret+j) = trim(*(ret+j));
j++;
str=str+last;
}
}while(done!=0);
if( (done==0) && (found==0) ){
*(ret+j)=NULL;
}
else if( (done==0) && (found!=0) ){
*(ret+j) = (char *)malloc(sizeof(char)*(i-first+1));
for(k=first;k<i;k++){
*(*(ret+j)+(k-first)) = str[k];
}
*(*(ret+j)+(k-first)) = '\0';
*(ret+j)=trim(*(ret+j));
*(ret+j+1) = NULL;
}
return ret;
}

Implementing a simple shell in c - wc command not working

I am implementing a simple shell in c for a class. There are a number of requirements but the one thing that I am concerned about is this sequence of commands:
ls > test
wc < test
which will output the results of the ls command to the file test and the wc
command will then count the number of words, bytes, characters (or something
like that) in that file.
Anyway, the first command works and the test file is successfully created with
the expected content. The wc command doesn't work however. It triggers the error associated with the execv statement "Command can't be executed. My input redirection works as a command like:
grep test < test works perfectly. My question is, Why doesn't my shell recognize the wc command?
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <fcntl.h>
void parse(char buffer[2048], char *arguments[512]){
//these characters(space, tab, new line, return)represent white space
//that separate words
char * delim = " \t\r\n\f";
char * token;
int count = 0;
//Finds the first word in buffer
token = strtok (buffer, delim);
//While a token still exists
while (token != NULL){
//if token is not empty it is added to arguments
if (strlen (token) > 0){
arguments[count]=token;
count++;
}
//Find the next token.
token = strtok (NULL, delim);
arguments[count+1]=NULL;
}
int checkInput(char *arguments[512]){
int loc = 0;
int count = 0;
while (arguments[count]!=NULL){
if (strcmp(arguments[count],"<")==0){
loc = count;
}
count++;
}
return loc;
}
void redirectInput(int input,char *arguments[512]){
int in;
int in2;
char*inFile = arguments[input+1];
in = open(inFile, O_RDONLY);
if (in < 0){
perror("Error Opening File");
exit(1);
}
in2 = dup2(in, 0);
if (in2 < 0){
perror("Error redirecting stdin");
exit(1);
}
close(in);
}
int checkOutput(char *arguments[512]){
int loc = 0;
int count = 0;
while (arguments[count]!=NULL){
if (strcmp(arguments[count],">")==0){
loc = count;
}
count++;
}
return loc;
}
void redirectOutput(int output,char *arguments[512]){
int out;
int out2;
char*outFile=arguments[output+1];
out = open(outFile, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IRGRP | S_IWGRP | S_IWUSR);
if (out < 0){
perror("Error Opening File");
exit(1);
}
out2 = dup2(out, 1);
if (out2 < 0){
perror("Error redirecting stdout");
exit(1);
}
close(out);
}
int main(int argc, char **argv){
//buffer is to hold the commands that the user will type in
char buffer[512];
char buffer2[512];
//bin/program_name is the arguments to pass to execv
//if we want to run ls, "/bin/ls" is required to be passed to execv()
char* path = "/bin/";
char * arguments[512];
char * args_copy[512];
//This will be the final path to the program is passed to execv
char prog[512];
char directory[512];
pid_t pid,w;
int status;
int isValid;
int input;
int output;
getcwd(directory,sizeof(directory));
while(1){
isValid = 0;
while(!isValid){
//print the prompt
printf(":");
fflush(stdout);
//get input
fgets(buffer, 512, stdin);
if(buffer[0]!='\n' && buffer[0] != '#'){
isValid=1;
}
}
strcpy(buffer2, buffer);
parse(buffer2, args_copy);
//Handle the builtin functions without before forking
if (strcmp(args_copy[0],"exit")==0){
exit(0);
}
else if (strcmp(args_copy[0],"status")==0){
printf("exit value %d\n",WEXITSTATUS(status));
}else if( strcmp(args_copy[0],"cd") == 0 ){
if(args_copy[1]==NULL){
chdir(directory);
}
else{
chdir(args_copy[1]);
}
}
else{
//fork!
pid = fork();
//Error checking to see if fork works
if (pid < 0) {
perror("fork");
exit(EXIT_FAILURE);
}
//If pid !=0 then it's the parent
if(pid!=0){
do {
w = waitpid(pid, &status, WUNTRACED | WCONTINUED);
if (w == -1) {
perror("waitpid");
exit(EXIT_FAILURE);
}
if (WIFEXITED(status)) {
}
else if (WIFSIGNALED(status)) {
printf("killed by signal %d\n", WTERMSIG(status));
}
else if (WIFSTOPPED(status)) {
printf("stopped by signal %d\n", WSTOPSIG(status));
}
else if (WIFCONTINUED(status)) {
printf("continued\n");
}
}while (!WIFEXITED(status) && !WIFSIGNALED(status));
}
else{
//parse the user input into an array of strings(arguments)
parse(buffer, arguments);
input = checkInput(arguments);
if (input){
redirectInput(input,arguments);
arguments[input]=NULL;
}
output = checkOutput(arguments);
if (output){
redirectOutput(output,arguments);
arguments[output]=NULL;
}
//First we copy a /bin/ to prog
strcpy(prog, path);
//Then we concancate the program name to /bin/
//If the program name is ls, then it'll be /bin/ls
strcat(prog, arguments[0]);
//pass the prepared arguments to execv and we're done!
int rv=execv(prog, arguments);
if (rv == -1) {
perror("Command can't execute");
exit(EXIT_FAILURE);
}
}
}
}
return 0;
}
This happens because wc is /usr/bin/wc, not /bin/wc.
You can use execvp instead of execv to automatically search through $PATH for your executable. In that case, you would not add /bin/ to the path.

Resources