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).