Reputation: 2527
I am trying to implement basic command execution in a shell program for the unix-like xv6 OS. The part of the shell code that I am editing is the runcmd function where I am using the execvp command to execute the commands used in the terminal. The program compiles without errors when I compile it but nothing happens when I try to type a command on the command line. I've read the man pages for the exec command, but I still don't really understand the proper way in which these arguments need to passed in the exec() command or when to use which version of exec() as I'm still very new to OS programming.
What haven't I implemented here that needs to be added in order for commands to be executed? I have the code for the runcmd function below:
I just added more exec statements with the paths to the binary for each command; however, only the first exec command works (which is cd in this case). When I use any other command, the command line executes it as if it is CD. How do I get it to work for multiple commands?
struct cmd {
int type; // ' ' (exec), | (pipe), '<' or '>' for redirection
};
struct
execcmd {
int type; // ' '
char *argv[MAXARGS]; // arguments to the command to be exec-ed
};
// Execute cmd. Never returns.
void
runcmd(struct cmd *cmd)
{
int p[2], r;
struct execcmd *ecmd;
struct pipecmd *pcmd;
struct redircmd *rcmd;
if(cmd == 0)
exit(0);
switch(cmd->type){
default:
fprintf(stderr, "unknown runcmd\n");
exit(-1);
case ' ':
ecmd = (struct execcmd*)cmd;
if(ecmd->argv[0] == 0)
exit(0);
//fprintf(stderr, "exec not implemented\n");
execvp("/bin/cd" , ecmd->argv );
execvp("/bin/grep" , ecmd->argv );
execvp("/bin/echo" , ecmd->argv );
execvp("/bin/cat" , ecmd->argv );
execvp("/bin/ls" , ecmd->argv );
break;
case '>':
case '<':
rcmd = (struct redircmd*)cmd;
//fprintf(stderr, "redir not implemented\n");
execvp("/bin" , ecmd->argv );
runcmd(rcmd->cmd);
break;
case '|':
pcmd = (struct pipecmd*)cmd;
fprintf(stderr, "pipe not implemented\n");
int execl(const char *path, const char *arg, ...);
break;
}
exit(0);
}
Upvotes: 2
Views: 2755
Reputation: 140748
I think this is what you actually need:
case ' ':
ecmd = (struct execcmd*)cmd;
if (ecmd->argv[0] == 0)
_exit(0);
execvp(ecmd->argv[0], ecmd->argv);
/* if control reaches this point, execvp failed */
perror(ecmd->argv[0]);
_exit(127);
You may be wondering why execvp
takes the executable to load as a separate argument from the argument vector, when you're just going to hand it argv[0]
. This is legacy functionality; if I were designing this API from scratch today, I don't think I would include it. However, the idea is that a program might behave differently depending on what its argv[0]
is. For instance, back in the day ex
and vi
were the same executable (two hard links to the same inode -- symlinks had not been invented yet), which decided what mode to start up in based on its argv[0]
. And, since the dawn of Unix, login(1)
has invoked shells with the first character of argv[0]
set to '-'
; there is no /bin/-sh
, so it actually needs the ability to specify the program-to-invoke separately from the argument vector.
I can't presently think of a situation where a shell would use anything other than argv[0]
as the first argument to execvp
.
Notes on other changes to your code:
exit
on the child side of a fork
, only _exit
.strerror(errno)
, to stderr
, not stdout
. perror
is a convenient shorthand for this operation.perror
call to be safe, you must have called fflush(0)
in the parent, immediately before the fork
, and your C library must implement line-buffering of stderr, correctly. I mention this because XV6 appears to be a teaching OS where you have to implement bits of it yourself, and I don't know whether stdio is your responsibility or not.Upvotes: 2
Reputation: 98509
It looks like you are trying to execute the "/bin" directory.
You should make the first argument to the exec call be the binary the user wants to run.
Using the perror function would also give you useful output when the command fails.
Upvotes: 3