How can I handle exit argument? - c

I am building a simple shell I want to handle the argument of exit. How can I handle it here?
I have already handle exit but I want to handle exit with argument.
Usage: exit status, where status is an integer used to exit the shell
For example
exit 98
exit 1
Present output
sh: No file or directory found
Expected Output
Exit the shell with the specific status code
shell.h
#ifndef SHELL_H
#define SHELL_H
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
void print_prompt(void);
void tok_str(char *, char **);
int add_path(char **cmd);
void print_env(char **);
#endif
shell.c
#include "shell.h"
int main(int argc, char **argv, char **env)
{
char *buf = NULL;
size_t buflen = 0;
char *cmd[20];
do
{
fprintf(stderr, ":) ");
if (getline(&buf, &buflen, stdin) == -1)
{
if (feof(stdin))
{
free(buf);
buf = NULL;
exit(EXIT_SUCCESS);
}
perror("Error occurred");
free(buf);
buf = NULL;
exit(EXIT_FAILURE);
}
if (buf[0] == '\0' || strcmp(buf, "\n") == 0)
{
free(buf);
buf = NULL;
continue;
}
if (strcmp(buf, "exit\n") == 0)
{
free(buf);
buf = NULL;
exit(EXIT_SUCCESS);
}
if (strcmp(buf, "env\n") == 0)
{
print_env(env);
free(buf);
buf = NULL;
continue;
}
tok_str(buf, cmd);
add_path(cmd);
if (fork() == 0)
{
execve(cmd[0], cmd, env);
printf("%s: No such file or directory\n", argv[0]);
exit(EXIT_SUCCESS);
}else
{
wait(NULL);
free(buf);
buf = NULL;
}
} while (1);
exit(EXIT_SUCCESS);
}
utils.c
#include "shell.h"
void tok_str(char *buf, char **cmd)
{
char *ptr;
int i = 0;
ptr = strtok(buf, " \n");
while (ptr)
{
cmd[i] = ptr;
ptr = strtok(NULL, " \n");
i++;
}
cmd[i] = NULL;
}
int add_path(char **cmd)
{
const char *PATH[] = {"/usr/local/bin/ls", "/bin/", "/sbin/", "/usr/bin/", NULL};
unsigned int i = 0;
struct stat st;
while (PATH[i])
{
char s[120] = "";
strcat(s, PATH[i]);
strcat(s, cmd[0]);
if (stat(s, &st) == 0)
{
cmd[0] = s;
return (0);
}
i++;
}
return (-1);
}
void print_env(char **env)
{
int i = 0;
while (env[i])
printf("%s\n", env[i++]);
}

Related

Implementing an exit function on my C shell

I have been trying to implement an exit command on my C shell. I have tried the fork-exec method since it's a system call.
When I run the program, it prompts for the stdin input and when I type in "exit" it returns a "segmentation fault (core dumped)" error.
What am I doing wrong?
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <fcntl.h>
#define ARGVMAX 100
#define LINESIZE 1024
#define EXITCMD "exit"
//makeargv - builds an argv vector from words in a string
int makeargv(char *s, char *argv[ARGVMAX]) {
int ntokens = 0;
if (s == NULL || argv == NULL || ARGVMAX == 0)
return -1;
argv[ntokens] = strtok(s, " \t\n");
while ((argv[ntokens] != NULL) && (ntokens < ARGVMAX)) {
ntokens++;
argv[ntokens] = strtok(NULL, " \t\n");
}
argv[ntokens] = NULL; // it must terminate with NULL
return ntokens;
}
void prompt() {
printf("sish> ");
fflush(stdout); //writes the prompt
}
/****** MAIN ******/
int main() {
char line[LINESIZE];
int wstatus;
while (1) {
prompt();
if (fgets(line, LINESIZE, stdin) == NULL)
break;
// TODO:
if(fgets(line, LINESIZE, strcmp(stdin, EXITCMD )) == 0)
return 0;
signal(SIGINT, SIG_DFL);
if (fork() == 0) exit(execvp(line[0], line));
{
signal(SIGINT, SIG_IGN);
}
wait(&wstatus);
if(WIFEXITED(wstatus))
printf("<%d>", WEXITSTATUS(wstatus));
}
return 0;
}
After reviewing and cleaning the code, I was finally able to implement the exit command.
Here goes the code:
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <fcntl.h>
#define ARGVMAX 100
#define LINESIZE 1024
#define EXITCMD "exit"
//makeargv - build an argv vector from words in a string
int makeargv(char *s, char *argv[ARGVMAX]) {
int ntokens = 0;
if (s == NULL || argv == NULL || ARGVMAX == 0)
return -1;
argv[ntokens] = strtok(s, " \t\n");
while ((argv[ntokens] != NULL) && (ntokens < ARGVMAX)) {
ntokens++;
argv[ntokens] = strtok(NULL, " \t\n");
}
argv[ntokens] = NULL; // it must terminate with NULL
return ntokens;
}
void prompt() {
printf("C Shell >> ");
fflush(stdout); //writes the prompt
}
/****** MAIN ******/
int main() {
char line[LINESIZE];
while (1) {
prompt();
if (fgets(line, LINESIZE, stdin) == NULL)
break;
// TODO:
char *p = strchr(line, '\n');
if (p)
*p = 0;
if(strcmp(line, "exit") == 0)
break;
char *args[] = {line, (char*) 0};
int pid = fork();
if (pid == 0){
execvp(line, args);
perror("Command Error!");
exit(1);
} else {
wait(NULL);
}
return 0;
}
return 0;
}

stdin into execvp() while using fork() and pipe()

So I am trying to read from standard input and then get the input ready so that later on it can be used inside execvp().
What I am implementing here is basically a pipe for some terminal commands.
Here is how an example of my code goes.
input:
ls -s1
sort -n
output:
commands[0]="ls"
commands[1]="-s1"
commands2[0]="��""
commands2[1]="��""
sort: cannot read: t: No such file or directory
Here is my code
# include <stdlib.h>
# include <stdio.h>
# include <unistd.h>
# include <string.h>
# include <sys/wait.h>
# define BUF_SIZE 256
int main()
{
char buffer[BUF_SIZE];
char *commands[5];
char *commands2[5];
int argc = 0;
int argc2 = 0;
fgets(buffer, BUF_SIZE, stdin);
for ( commands[argc] = strtok(buffer, " \t\n");
commands[argc] != NULL;
commands[++argc] = strtok(NULL, " \t\n") ) {
printf("commands[%d]=\"%s\"\n", argc, commands[argc]);
}
commands[argc] = NULL;
fgets(buffer, BUF_SIZE, stdin);
for ( commands2[argc2] = strtok(buffer, " \t\n");
commands2[argc2] != NULL;
commands2[++argc2] = strtok(NULL, " \t\n") ) {
printf("commands2[%d]=\"%s\"\n", argc2, commands2[argc]);
}
commands2[argc2] = NULL;
int my_pipe[2];
if (pipe(my_pipe) == -1)
{
perror("cannot create pipe\n");
}
pid_t my_pid;
my_pid = fork();
if (my_pid < 0)
{
perror("Failed fork\n");
}
if (my_pid > 0)
{
close(my_pipe[1]);
dup2(my_pipe[0], 0);
close(my_pipe[0]);
wait(NULL);
execvp(commands2[0],commands2);
}
else
{
close(my_pipe[0]);
dup2(my_pipe[1], 1);
close(my_pipe[1]);
execvp(commands[0],commands);
}
}
One major problem is that you read the second line over the first in buffer, and the commands[] array contains pointers into buffer too. That's not a recipe for happiness. The simplest fix is to define char buffer2[BUF_SIZE]; and use that in the second fgets() call and for loop.
Using argc in printf("commands2[%d]=\"%s\"\n", argc2, commands2[argc]); is a copy'n'paste bug — it should reference argc2 twice. This helped hide the previous problem.
Note that perror() does not exit; your code blunders on if pipe() fails, or if fork() fails.
The wait() in if (my_pid > 0) is bad; remove it.
If execvp() fails, you should report an error and exit with a non-zero status.
Putting those changes together yields code such as:
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#define BUF_SIZE 256
int main(void)
{
char buffer[BUF_SIZE];
char buffer2[BUF_SIZE];
char *commands[5];
char *commands2[5];
int argc = 0;
int argc2 = 0;
fgets(buffer, BUF_SIZE, stdin);
for (commands[argc] = strtok(buffer, " \t\n");
commands[argc] != NULL;
commands[++argc] = strtok(NULL, " \t\n"))
{
printf("commands[%d]=\"%s\"\n", argc, commands[argc]);
}
commands[argc] = NULL;
fgets(buffer2, BUF_SIZE, stdin);
for (commands2[argc2] = strtok(buffer2, " \t\n");
commands2[argc2] != NULL;
commands2[++argc2] = strtok(NULL, " \t\n"))
{
printf("commands2[%d]=\"%s\"\n", argc2, commands2[argc2]);
}
commands2[argc2] = NULL;
int my_pipe[2];
if (pipe(my_pipe) == -1)
{
perror("cannot create pipe\n");
exit(EXIT_FAILURE);
}
pid_t my_pid = fork();
if (my_pid < 0)
{
perror("Failed fork\n");
exit(EXIT_FAILURE);
}
if (my_pid > 0)
{
close(my_pipe[1]);
dup2(my_pipe[0], 0);
close(my_pipe[0]);
execvp(commands2[0], commands2);
perror(commands2[0]);
exit(EXIT_FAILURE);
}
else
{
close(my_pipe[0]);
dup2(my_pipe[1], 1);
close(my_pipe[1]);
execvp(commands[0], commands);
perror(commands[0]);
exit(EXIT_FAILURE);
}
return EXIT_FAILURE;
}
When I run the program, it produces the appropriate output. Note that the return at the end of main() is actually never reached.

How to use execvp() to execute a command

So I'm trying to create a custom shell for my school project. My method was to create child process, and have that process execute the command using the execvp() function that my professor briefly mentioned in class that we are meant to use. Here's my code, as always, any help is appreciated.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#define MAX_LINE 80
int main(int argc, char *argv[])
{
char *input = (char*)malloc(MAX_LINE*sizeof(char));
int should_run = 1;
while(should_run){
printf("osh>");
fflush(stdout);
pid_t pid;
pid = fork();
if(pid < 0){
printf("error with creating chiled process");
return 0;
}
if(pid == 0){
fgets(input, MAX_LINE, stdin);
char *token = strtok(input," ");
if(execvp(token[0], token) < 0){
printf("Error in execution.");
return(0);
}
//should_run = 0;
}
waitpid(pid, 1, 0);
}
return 0;
}
The prototype of execvp is
int execvp(const char *file, char *const argv[]);
It expects a pointer to char as the first argument, and a NULL-terminated
pointer to an array of char*. You are passing completely wrong arguments.
You are passing a single char as first argument and a char* as the second.
Use execlp instead:
int execlp(const char *file, const char *arg, ...
/* (char *) NULL */);
So
char *token = strtok(input," \n");
if(token == NULL)
{
fprintf(stderr, "only delimiters in line\n");
exit(1);
}
if(execlp(token, token, NULL) < 0){
fprintf(stderr, "Error in execution: %s\n", strerror(errno));
exit(1);
}
Also the convention in UNIX is to print error messages to stderr and a process with an error should
have an exit status other than 0.
As Pablo's states, you are passing the wrong arguments to execvp().
You can consider coding by yourself a function (char **strsplit(char *str, char delim)) which takes a string and split it into smaller pieces, returning an array of strings.
Also don't ignore compiler's warnings, they tell you a lot of things, and I suggest you to compile with gcc -Wall -Wextra -Werror to get almost any possible error in your program.
I tell you this because waitpid() takes as second argument a pointer to integer, to get an update of the status of the forked program. With this status you how the program exited (normally, segf, bus error...), you can use it to print an error if something went wrong.
You can consider using execv() instead (I know I'm going off topic, but you can learn useful things doing this), and find by yourself the correct executable(s).
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <dirent.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#define MAX_LINE 255
char **strsplit(char *str, char delim);
char *strjoin(char const *s1, char const *s2);
int isexec(char *path)
{
struct stat buf;
lstat(path, &buf);
if (S_ISREG(buf.st_mode) && (S_IXUSR & buf.st_mode))
return (1);
return (0);
}
static char *find_exec_readdir(char *paths, char *cmd)
{
DIR *dir;
struct dirent *dirent;
char *exec;
exec = NULL;
if ((dir = opendir(paths)) != NULL)
{
while ((dirent = readdir(dir)) != NULL)
{
if (!strcmp(dirent->d_name, cmd))
{
exec = strdup(dirent->d_name);
break ;
}
}
if (closedir(dir))
dprintf(2, "Failed closing dir.\n");
}
return (exec);
}
char *find_exec(char *cmd, char **paths)
{
char *exec;
char *path;
char *tmp;
int i;
i = -1;
exec = NULL;
path = NULL;
if ((cmd[0] == '.' || cmd[0] == '/'))
{
if (isexec(cmd))
return (strdup(cmd));
return (NULL);
}
while (paths[++i])
if ((exec = find_exec_readdir(paths[i], cmd)) != NULL)
{
tmp = strjoin(paths[i], "/");
path = strjoin(tmp, exec);
free(tmp);
free(exec);
break ;
}
return (path);
}
int handle_return_status(int status)
{
int sig;
int i;
if (!WIFEXITED(status) && WIFSIGNALED(status))
{
sig = WTERMSIG(status);
i = -1;
while (++i <= 13)
{
if (print_signal_error(sig))
{
return (-1);
}
}
dprintf(2, "Process terminated with unknown signal: %d\n", sig, NULL);
return (-1);
}
return (0);
}
int main(int argc, char *argv[])
{
char *input = NULL;
char **command = NULL;
int should_run = 1;
int status = 0;
(void)argc;
(void)argv;
if ((input = (char*)malloc(MAX_LINE*sizeof(char))) == NULL)
return (dprintf(2, "Failed to malloc, abort.\n"));
while(should_run){
printf("osh> ");
fflush(stdout);
pid_t pid;
pid = fork();
if(pid < 0)
return (dprintf(2, "error with creating chiled process\n"));
if(pid == 0){
fgets(input, MAX_LINE, stdin);
command = strsplit(input, ' ');
command[0] = find_exec(command[0], strsplit(getenv("PATH"), ':'));
if(execv(command[0], &command[1]) < 0)
return (dprintf(2, "Error in execution.\n"));
//should_run = 0;
}
waitpid(pid, &status, 0);
handle_ret_status(status);
}
return 0;
}

How can I extend my functionality to also have the cd command?

I'm writing a small shell in C as an exercis to learn about linux and C. Now I can execute custom commands and the exit command, but I can't execute a builtin CD command since I don't know how to split it in two part (the cd command and the name of the directory to CD to).
The desired functionality is that my program should accept the cd command with a parameter that is the directory. I can do it with command-line arguments but I don't know how to do it in its current form. How can it be done?
#include <sys/stat.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <dirent.h>
#include <errno.h>
#include <stdarg.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#define BUFFER_LEN 1024
#define BUFFERSIZE 1024
int mystrcmp(char const *, char const *);
void err_syserr(char *fmt, ...)
{
int errnum = errno;
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
if (errnum != 0)
fprintf(stderr, "(%d: %s)\n", errnum, strerror(errnum));
exit(EXIT_FAILURE);
}
int main() {
char line[BUFFER_LEN];
char* argv[100];
char* path= "/bin/";
char progpath[20];
int argc;
size_t length;
char *token;
int i=0;
int pid;
while(1) {
i = 0;
printf("miniShell>> ");
if(!fgets(line, BUFFER_LEN, stdin)) {
break;
}
length = strlen(line);
if (line[length - 1] == '\n') {
line[length - 1] = '\0';
}
if(strcmp(line, "exit")==0) {
break;
}
if(strcmp(line, "cd")==0) {
/*printf("change directory to %s\n", argv[2]);
chdir(argv[2]);*/
}
token = strtok(line," ");
while(token!=NULL) {
argv[i]=token;
token = strtok(NULL," ");
i++;
}
argv[i]=NULL;
argc=i;
for(i=0; i<argc; i++) {
printf("%s\n", argv[i]);
}
strcpy(progpath, path);
strcat(progpath, argv[0]);
for(i=0; i<strlen(progpath); i++) {
if(progpath[i]=='\n') {
progpath[i]='\0';
}
}
pid= fork();
if(pid==0) {
execvp(progpath,argv);
fprintf(stderr, "Child process could not do execvp\n");
} else {
wait(NULL);
printf("Child exited\n");
}
}
return (0);
}
int mystrcmp(char const *p, char const *q)
{
int i = 0;
for(i = 0; q[i]; i++)
{
if(p[i] != q[i])
return -1;
}
return 0;
}
int cd(char *pth) {
char path[BUFFERSIZE];
char cwd[BUFFERSIZE];
char * return_value;
int other_return;
strcpy(path,pth);
if(pth[0] != '/')
{
return_value = getcwd(cwd,sizeof(cwd));
strcat(cwd,"/");
strcat(cwd,path);
other_return = chdir(cwd);
} else {
other_return = chdir(pth);
}
printf("Spawned foreground process: %d\n", getpid());
return 0;
}
Use strpbrk or strsep to split your input into white-space separated tokens, then use strcmp on the first one and use the remaining ones as arguments.
This answer to a related question has an example, and here are some notes on portability.
I'd suggest to parse your argument with the getopt function from unistd library. It's covered in this question too.
Here is the documentation for getopt command.

Why is my C program crashing the second time?

I've written a small program for a simple shell as an axercise to learn C. Unfortunaltely, the program crashes the second time it is called.
$ ./a.out
miniShell>> ls
ls
a.out digenv.c~ miniShell.c~ test
digenv digenv.c.orig miniShell.c.orig testshell.c
digenv2.c digenv.old.c README.md testshell.c~
digenv2.c~ LICENSE smallshell.c testshell.c.orig
digenv.c miniShell.c smallshell.c.orig
Child exited
miniShell>> ls
ls
Segmentation fault (core dumped)
Can you help me troubleshoot it? The code is:
#include <sys/stat.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <dirent.h>
#include <errno.h>
#include <stdarg.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
int mystrcmp(char const *, char const *);
void err_syserr(char *fmt, ...)
{
int errnum = errno;
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
if (errnum != 0)
fprintf(stderr, "(%d: %s)\n", errnum, strerror(errnum));
exit(EXIT_FAILURE);
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUFFER_LEN 1024
#define BUFFERSIZE 1024
int main() {
char line[BUFFER_LEN];
char* argv[100];
char* path= "/bin/";
char progpath[20];
int argc;
size_t length;
char *token;
int i=0;
int pid;
while(1) {
printf("miniShell>> ");
if(!fgets(line, BUFFER_LEN, stdin)) {
break;
}
length = strlen(line);
if (line[length - 1] == '\n') {
line[length - 1] = '\0';
}
if(strcmp(line, "exit")==0) {
break;
}
token = strtok(line," ");
while(token!=NULL) {
argv[i]=token;
token = strtok(NULL," ");
i++;
}
argv[i]=NULL;
argc=i;
for(i=0; i<argc; i++) {
printf("%s\n", argv[i]);
}
strcpy(progpath, path);
strcat(progpath, argv[0]);
for(i=0; i<strlen(progpath); i++) {
if(progpath[i]=='\n') {
progpath[i]='\0';
}
}
pid= fork();
if(pid==0) {
execvp(progpath,argv);
fprintf(stderr, "Child process could not do execvp\n");
} else {
wait(NULL);
printf("Child exited\n");
}
}
return (0);
}
int mystrcmp(char const *p, char const *q)
{
int i = 0;
for(i = 0; q[i]; i++)
{
if(p[i] != q[i])
return -1;
}
return 0;
}
int cd(char *pth) {
char path[BUFFERSIZE];
char cwd[BUFFERSIZE];
char * return_value;
int other_return;
strcpy(path,pth);
if(pth[0] != '/')
{
return_value = getcwd(cwd,sizeof(cwd));
strcat(cwd,"/");
strcat(cwd,path);
other_return = chdir(cwd);
} else {
other_return = chdir(pth);
}
printf("Spawned foreground process: %d\n", getpid());
return 0;
}
The program is supposed to behave like a small shell that is able to take arbitrary commands and execute them (without using the system call). Can you help me?
The problem is caused by the value of i not being reset to 0 at the start of the top level while loop.
Add the line
i = 0;
just before
printf("miniShell>> ");
and all should be well.

Resources