How can I make a dummy shell take no arguments in C? - c

I have a dummy shell program that takes arguments. However, I want it to take no arguments and instead provide a prompt to let the user input the name of executable program and parameters.
For example:
$dummyshell
>(executable program and parameters go here)
Here is the code I have so far:
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#define BUFFER_SIZE 1<<16
#define ARRAY_SIZE 1<<16
void parseCmdArgs(char *buffer, char** cmdArgs,
size_t cmdArgsSize, size_t *nargs)
{
char *bufCmdArgs[cmdArgsSize];
char **temp;
char *buf;
size_t n, p;
cmdArgs[0], buf, bufCmdArgs[0] = buffer;
for(temp=bufCmdArgs; (*temp=strsep(&buf, " \n\t")) != NULL ;){
if ((*temp != '\0') && (++temp >= &bufCmdArgs[cmdArgsSize]))
break;
}
for (p=n=0; bufCmdArgs[n]!=NULL; n++){
if(strlen(bufCmdArgs[n])>0)
cmdArgs[p++]=bufCmdArgs[n];
}
*nargs=p;
cmdArgs[p]=NULL;
}
int main(int argc, char *argv[], char *envp[]){
char buffer[BUFFER_SIZE];
char *args[ARRAY_SIZE];
char hflag = 'N';
int *retStatus;
size_t nargs;
pid_t pid;
while(1){
printf("$dummyshell ");
fgets(buffer, BUFFER_SIZE, stdin);
parseCmdArgs(buffer, args, ARRAY_SIZE, &nargs);
if (nargs==0)
continue;
if (!strcmp(args[0], "help"))
{
printf("cat cd (absolute path references only\n");
printf("exit\n");
printf("help history\n");
printf("jobs kill\n");
printf("ls more\n");
printf("ps pwd\n");
continue;
}
if (!strcmp(args[0], "exit" ))
exit(0);
pid = fork();
if (pid){
pid = wait(retStatus);
}
else {
if( execvp(args[0], args)) {
puts(strerror(errno));
exit(127);
}
}
}
return 0;
}

What do you think this line does?
cmdArgs[0], buf, bufCmdArgs[0] = buffer;
It actually evaluates the uninitialized cmdArgs[0] and throws it away; then it evaluates the uninitialized buf and throws that away, and finally assigns to bufCmdArgs[0]. If you want to assign to all three variables, use assignment operators in place of the comma operator:
cmdArgs[0] = buf = bufCmdArgs[0] = buffer;
Neither hflag nor the arguments to main() are used; get rid of them (int main(void)). You should #include <sys/wait.h>; you should change retStatus to a simple int and then pass &retStatus to wait(). Failing that, you need to initialize retStatus so it points to an int that can be assigned to.
With those changes, the code runs OK – sort of. It executed ls -l correctly. However, control-D (EOF) to exit failed horribly (I got to see the plain ls listing of my directory quite a lot before I was able to interrupt it.
Separately, you'll also eventually realize that both the commands cd and exit (and probably history too) require special attention; they are shell built-ins for a good reason. That, however, comes later.
Do you know of a way to modify my program so that if the user types a '&' symbol at the end of the program and its parameters, the dummy shell will run the program in the background rather than the foreground?
Yes, I do know of ways to do that. Basically, you don't do the wait() (and when you do wait(), you check whether the dead process you collect information on is the one you expected, or whether it is one of your shell's background processes.
Semi-working code
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#define BUFFER_SIZE 1<<16
#define ARRAY_SIZE 1<<16
static void parseCmdArgs(char *buffer, char** cmdArgs,
size_t cmdArgsSize, size_t *nargs)
{
char *bufCmdArgs[cmdArgsSize];
char **temp;
char *buf;
size_t n, p;
cmdArgs[0] = buf = bufCmdArgs[0] = buffer;
for(temp=bufCmdArgs; (*temp=strsep(&buf, " \n\t")) != NULL ;){
if ((*temp != '\0') && (++temp >= &bufCmdArgs[cmdArgsSize]))
break;
}
for (p=n=0; bufCmdArgs[n]!=NULL; n++){
if(strlen(bufCmdArgs[n])>0)
cmdArgs[p++]=bufCmdArgs[n];
}
*nargs=p;
cmdArgs[p]=NULL;
}
//int main(int argc, char *argv[], char *envp[]){
int main(void)
{
char buffer[BUFFER_SIZE];
char *args[ARRAY_SIZE];
int retStatus;
size_t nargs;
pid_t pid;
while(1){
printf("$dummyshell ");
fgets(buffer, BUFFER_SIZE, stdin);
parseCmdArgs(buffer, args, ARRAY_SIZE, &nargs);
if (nargs==0)
continue;
if (!strcmp(args[0], "help"))
{
printf("cat cd (absolute path references only\n");
printf("exit\n");
printf("help history\n");
printf("jobs kill\n");
printf("ls more\n");
printf("ps pwd\n");
continue;
}
if (!strcmp(args[0], "exit" ))
exit(0);
pid = fork();
if (pid){
pid = wait(&retStatus);
}
else {
if( execvp(args[0], args)) {
puts(strerror(errno));
exit(127);
}
}
}
return 0;
}
You might note that error messages should be reported on standard error, not standard output (so puts(strerror(errno)) should be fprintf(stderr, "%s\n", strerror(errno)); because fputs() doesn't add a newline but puts() does).

Related

Simple shell with fork and exec

I am trying to make a simple shell running any command from PATH, lets say ls or pwd du gedit etc. I am having trouble with exec.I require that if i enter space nothing happens and if i type exit it terminates.Any help is appreciated
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <string.h>
#define BUFFER 1024
int main() {
char line[BUFFER];
char* args[100];
char* path = "";
char program[20];
while(1){
printf("$ ");
if(!fgets(line, BUFFER, stdin))
break;
size_t length = strlen(line);
if (line[length - 1] == '\n')
line[length - 1] = '\0';
if(strcmp(line, "exit")==0) break;
strcpy(program,path);
strcat(program,line);
int pid= fork(); //fork child
if(pid==0){ //Child
execlp(program,line,(char *)NULL);
}else{ //Parent
wait(NULL);
}
}
}
You have 2 fgets() call. Remove the first one fgets(line, BUFFER, stdin);.
fgets() will read in the newline if there's space in buffer. You need to remove it because when you input exit, you'll actually input exit\n and there's no command as /bin/exit\n.
The below code demonstrates removing newline character:
if(!fgets(line, BUFFER, stdin))
break;
char *p = strchr(line, '\n');
if (p) *p = 0;
Your usage is wrong. Check the manual of execl. You need to pass the arguments: execl(program, line, (char *)NULL);
Notice the cast of last argument of NULL. In case, NULL is defined as 0 then the cast becomes necessary because execl is a variadic function.
A modified example using execvp:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <string.h>
#define BUFFER 1024
int main(void) {
char line[BUFFER];
while(1) {
printf("$ ");
if(!fgets(line, BUFFER, stdin)) break;
char *p = strchr(line, '\n');
if (p) *p = 0;
if(strcmp(line, "exit")==0) break;
char *args[] = {line, (char*)0};
int pid= fork(); //fork child
if(pid==0) { //Child
execvp(line, args);
perror("exec");
exit(1);
} else { //Parent
wait(NULL);
}
}
return 0;
}
Hi See my correction below and see my comment as well.
int main() {
char line[BUFFER];
char* args[100];
char* path = "/bin/";
char program[20];
char command[50];
while(1){
printf("$ ");
if(!fgets(line, BUFFER, stdin))
break;
memset(command,0,sizeof(command));
if(strncmp(line, "exit", (strlen(line)-1))==0) break;
strncpy(command,line,(strlen(line)-1));
strcpy(program, path);
strcat(program,command);
int pid= fork(); //fork child
if(pid==0){ //Child
execl(program,command,NULL);
exit(0);// you must exit from the child because now you are inside while loop of child. Otherwise you have to type exit twice to exit from the application. Because your while loop also became the part of every child and from the child again it will call fork and create a child again
}else{
wait(NULL);
}
}
}
Also to support all command execution see the function execl how you need to pass the parameters in it. Accordingly you need to split your command and create the parameter list properly for execl.

How to add '&' functionality to run program in background in dummy shell?

I know I have to fork() but what do I do after that? Also I know I have to skip a wait() call at some point but how do I implement that? When I type '&' after a command it says "cannot access &: No such file or directory". The dummy shell should return a prompt for users immediately for more command input if the & is typed after a program and its parameters (Because the program will be running in the background). How do I accomplish this?
Here is the code I'm pretty sure needs to be changed:
pid = fork();
if (pid){
wait(&retStatus);
}
else {
if( execvp(args[0], args)) {
fprintf(stderr, "%s\n", strerror(errno));
exit(127);
}
}
Here is all of my code:
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#define BUFFER_SIZE 1<<16
#define ARRAY_SIZE 1<<16
static void parseCmdArgs(char *buffer, char** cmdArgs,
size_t cmdArgsSize, size_t *nargs)
{
char *bufCmdArgs[cmdArgsSize];
char **temp;
char *buf;
size_t n, p;
cmdArgs[0] = buf = bufCmdArgs[0] = buffer;
for(temp=bufCmdArgs; (*temp=strsep(&buf, " \n\t")) != NULL ;){
if ((*temp != '\0') && (++temp >= &bufCmdArgs[cmdArgsSize]))
break;
}
for (p=n=0; bufCmdArgs[n]!=NULL; n++){
if(strlen(bufCmdArgs[n])>0)
cmdArgs[p++]=bufCmdArgs[n];
}
*nargs=p;
cmdArgs[p]=NULL;
}
//int main(int argc, char *argv[], char *envp[]){
int main(void)
{
char buffer[BUFFER_SIZE];
char *args[ARRAY_SIZE];
int retStatus;
size_t nargs;
pid_t pid;
printf("$dummyshell\n");
while(1){
printf("> ");
fgets(buffer, BUFFER_SIZE, stdin);
parseCmdArgs(buffer, args, ARRAY_SIZE, &nargs);
if (nargs==0)
continue;
if (!strcmp(args[0], "help"))
{
printf("cat cd (absolute path references only\n");
printf("exit\n");
printf("help history\n");
printf("jobs kill\n");
printf("ls more\n");
printf("ps pwd\n");
continue;
}
if (!strcmp(args[0], "exit" ))
exit(0);
pid = fork();
if (pid){
wait(&retStatus);
}
else {
if( execvp(args[0], args)) {
fprintf(stderr, "%s\n", strerror(errno));
exit(127);
}
}
/* pid = fork();
if (pid == 0)
setpgrp();
else if (pid)
pid = wait(&retStatus);
else {
if (execvp(args[0], args)){
fprintf(stderr, "%s\n", strerror(errno));
exit(127);
}
}*/
}
return 0;
}

How can I make my dummy shell run a program in the background?

I want the user to be able to type and input a program and its parameters but if the user types a "&" at the end, the dummyshell should run the program in the background. In this case, the dummyshell will return immediately a prompt to users (for further command inputs). I know I have to use wait() but I'm not sure how to use it in order to run a program in the background.
For example when program runs in foreground:
$ dummyshell
> HelloWorld
...
>
For example when program runs in background:
$ dummyshell
> HelloWorld &
>
This is the code I'm working with so far:
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#define BUFFER_SIZE 1<<16
#define ARRAY_SIZE 1<<16
static void parseCmdArgs(char *buffer, char** cmdArgs,
size_t cmdArgsSize, size_t *nargs)
{
char *bufCmdArgs[cmdArgsSize];
char **temp;
char *buf;
size_t n, p;
cmdArgs[0] = buf = bufCmdArgs[0] = buffer;
for(temp=bufCmdArgs; (*temp=strsep(&buf, " \n\t")) != NULL ;){
if ((*temp != '\0') && (++temp >= &bufCmdArgs[cmdArgsSize]))
break;
}
for (p=n=0; bufCmdArgs[n]!=NULL; n++){
if(strlen(bufCmdArgs[n])>0)
cmdArgs[p++]=bufCmdArgs[n];
}
*nargs=p;
cmdArgs[p]=NULL;
}
//int main(int argc, char *argv[], char *envp[]){
int main(void)
{
char buffer[BUFFER_SIZE];
char *args[ARRAY_SIZE];
int retStatus;
size_t nargs;
pid_t pid;
while(1){
printf("$dummyshell ");
fgets(buffer, BUFFER_SIZE, stdin);
parseCmdArgs(buffer, args, ARRAY_SIZE, &nargs);
if (nargs==0)
continue;
if (!strcmp(args[0], "help"))
{
printf("cat cd (absolute path references only\n");
printf("exit\n");
printf("help history\n");
printf("jobs kill\n");
printf("ls more\n");
printf("ps pwd\n");
continue;
}
if (!strcmp(args[0], "exit" ))
exit(0);
pid = fork();
if (pid){
pid = wait(&retStatus);
}
else {
if( execvp(args[0], args)) {
fprintf(stderr, "%s\n", strerror(errno));
exit(127);
}
}
}
return 0;
}

How do I detect if a user types '&' after program and parameters when using dummy shell?

I have a dummy shell and I want it to be able to be able to run whatever program the user enters in the background if the user types an '&' at the end. However, I'm not sure how to check if the user types an '&' at the end. I already tried: printf("%c\n", args[ARRAY_SIZE-1]) but that doesn't seem to print anything. I did in order to see if I can access the '&'. How can I go about doing this?
The code I'm working with is below:
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#define BUFFER_SIZE 1<<16
#define ARRAY_SIZE 1<<16
static void parseCmdArgs(char *buffer, char** cmdArgs,
size_t cmdArgsSize, size_t *nargs)
{
char *bufCmdArgs[cmdArgsSize];
char **temp;
char *buf;
size_t n, p;
cmdArgs[0] = buf = bufCmdArgs[0] = buffer;
for(temp=bufCmdArgs; (*temp=strsep(&buf, " \n\t")) != NULL ;){
if ((*temp != '\0') && (++temp >= &bufCmdArgs[cmdArgsSize]))
break;
}
for (p=n=0; bufCmdArgs[n]!=NULL; n++){
if(strlen(bufCmdArgs[n])>0)
cmdArgs[p++]=bufCmdArgs[n];
}
*nargs=p;
cmdArgs[p]=NULL;
}
//int main(int argc, char *argv[], char *envp[]){
int main(void)
{
char buffer[BUFFER_SIZE];
char *args[ARRAY_SIZE];
int retStatus;
size_t nargs;
pid_t pid;
while(1){
printf("$dummyshell ");
fgets(buffer, BUFFER_SIZE, stdin);
parseCmdArgs(buffer, args, ARRAY_SIZE, &nargs);
if (nargs==0)
continue;
if (!strcmp(args[0], "help"))
{
printf("cat cd (absolute path references only\n");
printf("exit\n");
printf("help history\n");
printf("jobs kill\n");
printf("ls more\n");
printf("ps pwd\n");
continue;
}
if (!strcmp(args[0], "exit" ))
exit(0);
pid = fork();
if (pid){
pid = wait(&retStatus);
}
else {
if( execvp(args[0], args)) {
fprintf(stderr, "%s\n", strerror(errno));
exit(127);
}
}
}
return 0;
}
Most shells will consume the & token (which is normally taken to mean "run in background") and thus it is not supplied as an argument to the program.
To pass a "&" argument to the program, the shell-specific interpretation must be disabled, e.g.: ./dummyshell "programtorun" "&" (two arguments) or ./dummyshell "programtorun &" (one argument).
Real shells normally read from STDIN (or perhaps a terminal directly) or can be supplied a string of commands to execute, like: sh -c "nohup program &".
Reading the input directly avoids the previous mentioned shell parsing and using quotes once again protects the & in the commands from being handled by the shell used to execute the (sh) command; note how the entire set of commands to run are passed as a single argument in this case.

Handling CTRL-C in dummy shell

I'm writing a dummy shell that should not terminate when the user types ctrl-C but should just generate a new prompt line. Currently, my shell does not terminate when I type ctrl-C but it still does not print the new prompt line. Do you know why this is the case and how I can fix this?
My code is below:
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#define BUFFER_SIZE 1<<16
#define ARRAY_SIZE 1<<16
void INThandler(int);
static void parseCmdArgs(char *buffer, char** cmdArgs,
size_t cmdArgsSize, size_t *nargs)
{
char *bufCmdArgs[cmdArgsSize];
char **temp;
char *buf;
size_t n, p;
cmdArgs[0] = buf = bufCmdArgs[0] = buffer;
for(temp=bufCmdArgs; (*temp=strsep(&buf, " \n\t")) != NULL ;){
if ((*temp != '\0') && (++temp >= &bufCmdArgs[cmdArgsSize]))
break;
}
for (p=n=0; bufCmdArgs[n]!=NULL; n++){
if(strlen(bufCmdArgs[n])>0)
cmdArgs[p++]=bufCmdArgs[n];
}
*nargs=p;
cmdArgs[p]=NULL;
}
void INThandler(int sig)
{
printf("\n> ");
signal(sig, SIG_IGN);
}
int main(void)
{
char buffer[BUFFER_SIZE];
char *args[ARRAY_SIZE];
int retStatus;
size_t nargs;
pid_t pid;
printf("$dummyshell\n");
signal(SIGINT, INThandler);
while(1){
printf("> ");
fgets(buffer, BUFFER_SIZE, stdin);
parseCmdArgs(buffer, args, ARRAY_SIZE, &nargs);
if (nargs==0)
continue;
if (!strcmp(args[0], "help"))
{
printf("cat cd (absolute path references only\n");
printf("exit\n");
printf("help history\n");
printf("jobs kill\n");
printf("ls more\n");
printf("ps pwd\n");
continue;
}
if (!strcmp(args[0], "exit" ))
exit(0);
pid = fork();
if (pid){
wait(&retStatus);
}
else {
if( execvp(args[0], args)) {
fprintf(stderr, "%s\n", strerror(errno));
exit(127);
}
}
/* pid = fork();
if (pid == 0)
setpgrp();
else if (pid)
pid = wait(&retStatus);
else {
if (execvp(args[0], args)){
fprintf(stderr, "%s\n", strerror(errno));
exit(127);
}
}*/
}
return 0;
}
but what would I pass through fflush()?
It would be
fflush(stdout);
- but that is not needed because of the fgets(buffer, BUFFER_SIZE, stdin).
Output streams that refer to terminal devices are always line buffered
by default; pending output to such streams is written automatically
whenever an input stream that refers to a terminal device is read.
(See man stdio.)
I'm assuming you want the interrupt handler to jump into the while loop in your main function, instead of printing "\>".
You can use sigsetjmp and siglongjmp for this. You might want to take at [1] for an example.
#include <stdio.h>
#include <signal.h>
#include <setjmp.h>
jmp_buf JumpBuffer;
void INThandler(int);
void main(void)
{
signal(SIGINT, INThandler);
while (1) {
if (setjmp(JumpBuffer) == 0) {
printf(">");
/*...*/
}
}
}
void INThandler(int sig)
{
signal(sig, SIG_IGN);
signal(SIGINT, INThandler);
longjmp(JumpBuffer, 1);
}
This was adapted from [2]. If you use sigaction(), sigprocmask(), or sigsuspend() you need to use the siglongjmp and sigsetjmp functions, respectively [3].
Sources:
[1] https://publib.boulder.ibm.com/iseries/v5r2/ic2924/index.htm?info/apis/siglngj.htm
[2] http://www.csl.mtu.edu/cs4411.ck/www/NOTES/non-local-goto/sig-1.html
[3] sigsetjmp - The Open Group Base Specifications Issue 6 IEEE Std 1003.1, 2004 Edition

Resources