Look in execvp(); - c

I'm new trying to learn the function of fork() and the system calls, now I'm using execvp() trying to make a bash, but I have the problem that when I write a correct command, the program ends and I want to make a loop for using my bash until the person write "exit" in my command line.
The code that I used is:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
int main()
{
char cadena[100];
char *array[100];
char *ptr;
int i=1;
pid_t pid;
pid = fork();
if (pid < 0) {
perror("Error en la llamada a fork().");
return -1;
}
else if (pid == 0) {
do {
printf("prompt$ ");
fgets(cadena, 100, stdin);
ptr = strtok(cadena, " \n");
array[0] = ptr;
while( (ptr = strtok( NULL, " \n" )) != NULL ){
array[i] = ptr;
i++;
}
array[i]=NULL;
execvp(array[0],array);
perror("Execvp failed");
} while(array[0] != "exit");
}
else {
wait(NULL);
}
return 0;
}
I'm using the iterative structure do-while for trying to loop, but it did not work, because when I write a correct commando the program ends, and I need to keep writting commandos because I need to do a list with all the commands that I wrote after the program ends.

You have a general design problem: except when there is a call error, none of the exec functions return to caller. The common design for a shell is in pseudo code:
loop
prompt for a command
read a command line
parse the command line
if exit
then exit loop
else
fork (a child to execute the command, detailed below)
if pid is 0 (child)
then exec command
else
wait for the child to end

This particular part of your function is not doing what you hope:
}while(array[0] != "exit");
You need to use strcmp(), as follows:
} while (strcmp(array[0], "exit"));
... noting that the return value from strcmp() is 0 if both args are equal, so when a != b the condition is true and the loop continues.

Related

How can I get my C Shell to recognize that this is a command?

I am very new at C but am currently working on creating a C program to serve as a shell interface. It is supposed to accept commands and then execute each command in a separate process. I am currently stuck trying to get C to recognize that it is a command. I am unsure how to do this, and can't seem to find any useful examples.
Here is my code, it is saying that everything is not a valid command ("no cmd"). Does anyone have any idea why this would be occurring? Is C not able to recognize it is a command in the execvp() function or do I need to implement something for that specific purpose?
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#define MAX_LINE 80
/* 80 chars per line per command */
int main(void) {
//char *args[MAX_LINE/2 + 1];
char *args = calloc(MAX_LINE, (MAX_LINE/2 +1));
const size_t sz = MAX_LINE;
pid_t pid;
/* command line (of 80) has max of 40 arguments*/
int should_run = 1;
while (should_run) {
printf("osh>"); //beginning of command line
fgets(args, sz, stdin); //gets the stdin
char *token = strtok(args, " \n"); //supposed to break str if it has a space or line and recognize there are 2 commands
printf("%s\n", token);
token = strtok(NULL," \n");
printf("%s\n", token);
pid_t parent = getpid();
pid = fork(); //forking child
if(pid == 0){ //if forking occurred
int status = execvp(&args[0], &args); //status of input, is it a command?
printf("%d", status);
printf("forked!");
if(status == -1) { //if cmd err, print
printf("no cmd");
return 1;
} else {
printf("line will be printed");
}
return 0;
}
fflush(stdout); //flush
/*
* After reading user input, the steps are :
* 1: fork a child process
* 2: the child process will invoke execvp()
* 3: parent process waits for the child to exit before
continuing
*/
}
exit(0);
/**
return to the operating system:
-exit this process
-flush all
*/
}
If you look at the documentation for the exec family of functions, you'll note that the functions only return if the exec failed. That's because exec, when successful, completely replaces the calling process with the invoked program.
What you need to do is, from the parent process (i.e., the one that got a positive value returned from fork), wait on the child process via waitpid.
pid_t pid;
pid = fork();
if ( pid < 0 ) {
// Handle the error.
}
else if ( pid == 0 ) {
execvp(&args[0], &args);
// The fact that we've reached this line means that execvp failed.
exit(1);
}
else {
int status;
while ( waitpid(pid, &status, 0) != pid ) {} // We need this loop in case waitpid gets interrupted by a signal.
// status doesn't equal the return value of the child process. We need to extract that with macros.
if ( WIFEXITED(status) ) {
printf("Child process exited with code %i\n", WEXITSTATUS(status));
}
else {
printf("Child process was terminated by signal number %i\n", WTERMSIG(status));
}
}

execvp() function returns -1 all the time

In my code, I'm trying to use the function execvp() to execute a command that I get in my shell but the function always returns -1 that indicates unsuccess, when I replace the function first argument by (for example) "ps" it works fine but when it is (command) it doesn't work, I've checked that command is fine by printing it after getting it from the input line and it is a fine string with no problems, but the function keeps returning me an error!!
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#define BUFFER_SIZE 100
int main(void)
{
close(2);
dup(1);
char command[BUFFER_SIZE];
while (1)
{
char *arg[3];
fprintf(stdout, "my-shell> ");
memset(command, 0, BUFFER_SIZE);
fgets(command, BUFFER_SIZE, stdin);
if(strncmp(command, "exit", 4) == 0)
{
break;
}
arg[0] = command;
arg[1] = "\0";
arg[2] = "\0";
i = execvp(command,arg);
printf("%d",i);
}
return 0;
}
I expect that the problem is in the way that command is passed in the function but after trying so much edites to the code, I still can't figure out what the problem really is!
There are 3 major problems and 1 minor one that can be picked out of the code shown (plus what I take to be an artefact of reducing your full code to the code in the question, plus some oddities):
The fgets() function includes the newline in the returned string unless the line is too long (a separate problem). You need to zap that newline:
command[strcspn(command, "\n")] = '\0';
The code does not parse the line that's entered, so only single word commands can sensibly be entered. To fix that, you'd have to be prepared to split the line into words using an appropriate algorithm, removing quotes where appropriate, expanding variables and so on. That will be part of the later stages of developing your shell.
The second argument to execvp() needs to be a NULL-terminated list of strings. You only provide the command name and two empty strings without the null terminator, which gives undefined behaviour.
The minor problem is that using "\0" instead of just "" is pointless.
The artefact is that there is no fork() in the code, so if the command is executed successfully, the 'shell' is replaced by the command and exits when the replacement exits.
The close(2); dup(1); sequence is weird — it means standard error refers to the same file descriptor as standard output. Those lines really aren't needed (or desirable). Leave the errors separate from standard output.
The memset() is superfluous too. Using fprintf(stdout, "my-shell> "); is a funny way of writing printf("my-shell> ");. Using strncmp(command, "exit", 4) means that if the user types exit-or-continue, you'll treat it the same as exit, which is far from ideal.
Putting most of those numerous changes into effect (omitting parsing the command line into separate arguments) leaves:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#define BUFFER_SIZE 100
int main(void)
{
char command[BUFFER_SIZE];
while (1)
{
printf("my-shell> ");
fflush(stdout);
if (fgets(command, BUFFER_SIZE, stdin) != command)
break;
command[strcspn(command, "\n")] = '\0';
if(strcmp(command, "exit") == 0)
{
break;
}
int pid = fork();
if (pid < 0)
{
fprintf(stderr, "failed to fork()\n");
exit(EXIT_FAILURE);
}
if (pid == 0)
{
/* Child - execute command */
/* Should break line into command plus arguments */
char *arg[2];
arg[0] = command;
arg[1] = NULL;
execvp(command, arg);
fprintf(stderr, "failed to execute command '%s'\n", command);
exit(EXIT_FAILURE);
}
/* Parent - wait for child to finish */
int corpse;
int status;
while ((corpse = wait(&status)) > 0)
{
if (corpse == pid)
break;
printf("PID %d exited with status 0x%.4X\n", corpse, status);
}
}
return 0;
}

How to set status termination of a process C?

My program is a rudimental little shell.
It allow you to run programs in PATH as ls, cd..also with arguments.
To run the program type from terminal "./myshell2" then it starts and you can insert how many commands you want.
It starts a child process, runs execvp,it returns and restarts so you can type a new command.
When typed "Q" or "q" all the entire program should terminates.
The problem is that I don't know how to stop it,the code is below.
My idea is, when typed "Q" or "q", to kill the child process created and send a signal to comunicate its bad termination(of child process).
So the final status(from parent) 'll be not 1 and the function returns.
I commented some parts of the code hoping that it's easier to understand.
It works the problem is that to stop it I need of ctrl C.
I would like to say to child process that he must ends with a non-zero value.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include <signal.h>
int main(int argc, char * argv[]) {
while(1)
{
pid_t pid = fork();
if (pid == -1) {
perror("fork error");
exit(EXIT_FAILURE);
}
if (pid == 0) { // child process
printf("type the command to start (and arguments if required) \n"
"Q to quit\n");
char *dest[10]; // allow you to insert
char line[4096];//commands from terminal
if (fgets(line,sizeof(line),stdin)==0) return 1;
int i;
line[strcspn(line, "\n")] = '\0';
char *st = line;
for (i=0; i< 10 && (dest[i]=strsep(&st," "))!=NULL;i++)
continue;//now you typed the command
if ( ( memcmp(dest[0],"Q",1)==0 ) // if Q or q the program
|| (memcmp(dest[0],"q",1)==0) ) //must end
{
printf("got it!\n");
if (kill(getpid(),SIGSEGV)==-1) printf("kill error\n");
//in theory the process should terminates with bad status
// and the value of the variable "status" 'll be not 0
// I think that the problem is in this part of the code
}
if( strcmp(dest[0]," ")!=0 )
{
int res = execvp(dest[0], dest);
}
else
{ int res= execvp(dest[1],dest+1);}
perror("execvp error");
exit(EXIT_FAILURE);
}
int status;
pid_t child = wait(&status);
if (child == -1) {
perror("wait error");
exit(EXIT_FAILURE);
}
if (status==1)
break; //so it can exit from the loop that creates new process
setenv("WAIT","TRUE",0); //dont' worry about
//perror("setenv error\n");
if (memcmp("TRUE",getenv("WAIT"),4) == 0 ) //these 6 lines
printf("WAIT=TRUE\n");
else if(memcmp("FALSE",getenv("WAIT"),4) == 0 )
printf("WAIT=FALSE\n");
printf("end current process (status=%d, child=%d)\n", WEXITSTATUS(status), son);
}
return EXIT_SUCCESS;
}
You're printing out WEXITSTATUS() for all cases, but that isn't right. You need to check if the status returned by wait is an exit status or not using WIFEXITED(). If it's non-zero then the child exited normally. Otherwise, you can use WIFSIGNALED() to see if the child was terminated and you'll get the signal from WTERMSIG()
if(WIFEXITED(status))
{
printf("end current process (status=%d, child=%d)\n", WEXITSTATUS(status), son);
}
else if(WIFSIGNALED(status))
{
printf("end current process (signal=%d, child=%d)\n", WTERMSIG(status), son);
}
You really should have the parent process handle the inputting of the command and leave the child process to run it though.

process loop in C

I'm trying to write a code about a process that executes programs from $PATH using the execlp() command.(it doesn't need to be the execlp command but I've found it useful for this one) I've achieved my expected output, but I need to run more than one commands. More specifically I want the child process to run the exec command, then the parent process to print a text indicating that it's ready to accept another command. Then the child process will run the new exec command. My code is this:
int main ( int argc, char *argp[]) {
pid_t progpid = fork(); // the fork command for the creation of the child process
int status = 0;
char com[256];
if (progpid < 0) // the check in case of failure
{
printf("PROGRAM ABORTED!");
return 0;
}
do
{
if (progpid == 0) // the child process
{
scanf( "%s", com);
if (com == "exit")
{
exit(0);
}
else
{
execlp(com, com, NULL);
}
}
else //the parent process
{
wait(&status);
printf("$");
}
}while (com != "exit");
return 0;
}
The expected output is :
<program which I input from keyboard> ( for example : ls )
<output of the program>
$<next program>
<output of the next program>
.
.
.
$exit
In short I want to keep running programs till I enter exit where it ends without doing anything else. However the output I get is this:
<program>
<output of program>
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
It keeps printing $ until I shut it down. I'm new to processes so please don't be too harsh about my code so far.
Thank you in advance!
This
if (com == "exit")
should be
if (strcmp(com, "exit") == 0)
Similarly change the while condition as well.
In C, string comparisons are done using strcmp(). == in your case, simply compares the address of com and the address of the string literal "exit". (In expressions, an array gets converted into a pointer to its first element. Hence, "address" comparison. Also see: What is array decaying?).
Note that your execlp() call has an issue. NULL may be defined as 0, in which case execlp(), being a variadic function, may be able to recognize it as the last argument.
I'd suggest to change it to:
execlp(com, com, (char*)0);
You'd also want to check if wait() failed or not by checking its return code.
Here's a simple example based on yours with improved error checking.
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main ( int argc, char *argp[]) {
for(;;) {
char com[1024];
printf("$ ");
fgets(com, sizeof com, stdin);
com[strcspn(com, "\n")] = 0; /* Remove if there's a newline at the end */
if (strcmp(com, "exit") == 0) {
exit(0);
}
pid_t pid = fork();
if (pid < 0) {
perror("fork");
exit(1);
}
if (pid == 0) { /* child process */
execlp(com, com, (char*)0);
}
int status;
int rc = wait(&status);
/* You can inspect 'status' for further info. */
if (rc == -1) {
perror("wait");
exit(1);
}
}
return 0;
}
Note that if you want your to execute commands with arguments then you need to do argument processing.

exit command does not work properly in my own shell

I wrote a shell for an assignment and it works correctly, but there is a small run time error which i can not figure out. When the user enter the command 'exit' through the shell it should come out of newly created shell. But the problem is I have to type the command 'exit' several times to quit the shell. If someone can help me it will be a great pleasure for me! Thanks all!
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
char* cmndtkn[256];
char buffer[256];
char* path=NULL;
char pwd[128];
int main(){
//setting path variable
char *env;
env=getenv("PATH");
putenv(env);
system("clear");
printf("\t MY OWN SHELL !!!!!!!!!!\n ");
printf("_______________________________________\n\n");
while(1){
fflush(stdin);
getcwd(pwd,128);
printf("[MOSH~%s]$",pwd);
fgets(buffer,sizeof(buffer),stdin);
buffer[sizeof(buffer)-1] = '\0';
//tokenize the input command line
char* tkn = strtok(buffer," \t\n");
int i=0;
int indictr=0;
// loop for every part of the command
while(tkn!=NULL)
{
if(strcoll(tkn,"exit")==0 ){
exit(0);
}
else if(strcoll(buffer,"cd")==0){
path = buffer;
chdir(path+=3);}
else if(strcoll(tkn,"|")==0){
indictr=i;}
cmndtkn[i++] = tkn;
tkn = strtok(NULL," \t\n");
}cmndtkn[i]='\0';
// execute when command has pipe. when | command is found indictr is greater than 0.
if(indictr>0){
char* leftcmnd[indictr+1];
char* rightcmnd[i-indictr];
int a,b;
for(b=0;b<indictr;b++)
leftcmnd[b]=cmndtkn[b];
leftcmnd[indictr]=NULL;
for(a=0;a<i-indictr-1;a++)
rightcmnd[a]=cmndtkn[a+indictr+1];
rightcmnd[i-indictr]=NULL;
if(!fork())
{
fflush(stdout);
int pfds[2];
pipe(pfds);
if(!fork()){
close(1);
dup(pfds[1]);
close(pfds[0]);
execvp(leftcmnd[0],leftcmnd);
}
else{
close(0);
dup(pfds[0]);
close(pfds[1]);
execvp(rightcmnd[0],rightcmnd);
}
}else wait(NULL);
//command not include pipe
}else{
if(!fork()){
fflush(stdout);
execvp(cmndtkn[0],cmndtkn);
}else wait(NULL);
}
}
}
Like the cd command, the exit command has to be interpreted by the shell as a built-in; it must exit the loop or call the exit() function directly. However, it also appears that should be happening. Note that using strcoll() is a little unusual; normally, strcmp() is sufficient.
You should report problems if execvp() returns — and you must make sure the sub-shell exits so that you don't have multiple shell processes reading the input simultaneously. I'm left wondering if this problem is occurring, and that's why you have to type exit multiple times.
You also need to check that fgets() did not report an error. It always null terminates its input; your code does not zap the newline (you'd need strlen(buffer)-1 instead of sizeof(buffer)-1).
The code that reads and sets PATH is wrong. getenv("PATH") returns a pointer to the first character after the PATH= part; you then use that to 'set' the environment. Fortunately for you, the average value for PATH does not contain anything that looks like VAR=value, so it is functionally a no-op (though the information is probably copied into the environment, where it makes a mess without causing any major harm).
Your code indentation scheme is rococo at best — mostly, it is just woefully inconsistent. Please be consistent! The spacing of the lines in the code was also extremely erratic. When you're adding code in SO, do not use tabs, do use 4 spaces per indent level, do highlight a block of code that is left justified and use the {} button above the edit box to indent it as code. This also means you don't need to add blank lines to the code.
You aren't closing enough file descriptors. When you use dup() (or dup2()) to duplicate a pipe to standard input or standard output, you have to close both of the file descriptors returned by pipe().
On Linux, using fflush(stdin) is undefined behaviour, AFAIK. It is defined on Windows, but not on POSIX systems.
You don't check that your chdir() system call works.
Trying your code, I did get one runaway prompt. Unfortunately, I couldn't remember or see what triggered the runaway. The code below is mostly sanitized and seems to behave. I've annotated some critical changes — and not others. One of the things you should be doing for your own benefit is including trace like the dump_cmd() function so you can see what your program is doing.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
char *cmndtkn[256];
char buffer[256];
char *path = NULL;
char pwd[128];
static void dump_cmd(char **argv);
int main(void)
{
/*
//setting path variable
char *env;
env=getenv("PATH");
putenv(env);
system("clear");
*/
printf("\t MY OWN SHELL !!!!!!!!!!\n ");
printf("_______________________________________\n\n");
while (1)
{
//fflush(stdin);
getcwd(pwd, 128);
printf("[MOSH~%s]$", pwd);
if (fgets(buffer, sizeof(buffer), stdin) == 0)
{
putchar('\n');
break;
}
//buffer[sizeof(buffer)-1] = '\0';
buffer[strlen(buffer)-1] = '\0';
//tokenize the input command line
char *tkn = strtok(buffer, " \t\n");
int i = 0;
int indictr = 0;
// loop for every part of the command
while (tkn != NULL)
{
if (strcoll(tkn, "exit") == 0)
{
printf("Got: exit\n");
fflush(stdout);
exit(0);
}
else if (strcoll(tkn, "cd") == 0) // Was buffer, not tkn
{
printf("Got: cd (%s)\n", buffer + 3);
fflush(stdout);
path = buffer;
chdir(path += 3);
}
else if (strcoll(tkn, "|") == 0)
{
indictr = i;
}
cmndtkn[i++] = tkn;
tkn = strtok(NULL, " \t\n");
}
cmndtkn[i] = 0;
// execute when command has pipe. when | command is found indictr is greater than 0.
if (indictr > 0)
{
char *leftcmnd[indictr+1];
char *rightcmnd[i-indictr];
int a, b;
for (b = 0; b < indictr; b++)
leftcmnd[b] = cmndtkn[b];
leftcmnd[indictr] = NULL;
for (a = 0; a < i-indictr-1; a++)
rightcmnd[a] = cmndtkn[a+indictr+1];
rightcmnd[i-indictr-1] = NULL; // Did not include -1
if (!fork())
{
fflush(stdout);
int pfds[2];
pipe(pfds);
if (!fork())
{
dump_cmd(leftcmnd);
close(1);
dup(pfds[1]);
close(pfds[0]);
close(pfds[1]);
execvp(leftcmnd[0], leftcmnd);
fprintf(stderr, "failed to execvp() %s\n", leftcmnd[0]);
exit(1);
}
else
{
dump_cmd(rightcmnd);
close(0);
dup(pfds[0]);
close(pfds[0]);
close(pfds[1]);
execvp(rightcmnd[0], rightcmnd);
fprintf(stderr, "failed to execvp() %s\n", rightcmnd[0]);
exit(1);
}
}
else
wait(NULL);
}
else
{
//command does not include pipe
if (!fork())
{
dump_cmd(cmndtkn);
fflush(stdout);
execvp(cmndtkn[0], cmndtkn);
fprintf(stderr, "failed to execvp() %s\n", cmndtkn[0]);
exit(1);
}
else
wait(NULL);
}
}
return 0;
}
static void dump_cmd(char **argv)
{
int i = 0;
fprintf(stderr, "%d: Command:\n", (int)getpid());
while (*argv != 0)
fprintf(stderr, "%d: %d: [[%s]]\n", (int)getpid(), i++, *argv++);
}
I'm not keen on the code, but it does seem mostly sane.

Resources