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;
}
Related
So, I'm playing with pipes in c, and I have an exercise where I call a program through command line as this: "./self 1" which then calls itself with execlp but with an argument 2: "./self 2" which further on calls itself with argument 3: "./self 3". The point of these processes is this: process1 takes a line from keyboard and puts it in pipe1, then process2 gets the line from pipe1 and puts it in pipe2, then process3 gets it from pipe2 and counts the number of space characters. This code never works if I dont print a newline character on the screen before taking inputs with fprintf(stdout,"\n"); . Why is that?
Here is the code:
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char* argv[]) {
if (strcmp(argv[1], "1") == 0) {
int fdpipe1[2];
if (pipe(fdpipe1)) {
printf("Error pipe1\n");
return 0;
}
pid_t p;
p = fork();
if (p == 0) {
close(fdpipe1[1]);
dup2(fdpipe1[0], 0);
execlp("./self", "./self", "2", NULL);
} else {
close(fdpipe1[0]);
fprintf(stdout, "\n");
dup2(fdpipe1[1], 1);
char input[100];
gets(input);
puts(input);
wait(NULL);
}
}
else if (strcmp(argv[1], "2") == 0) {
int fdpipe2[2];
if (pipe(fdpipe2)) {
printf("Error pipe2\n");
return 0;
}
pid_t p;
p = fork();
if (p == 0) {
close(fdpipe2[1]);
dup2(fdpipe2[0], 0);
execlp("./self", "./self", "3", NULL);
} else {
close(fdpipe2[0]);
fprintf(stdout, "\n");
dup2(fdpipe2[1], 1);
char input[100];
gets(input);
puts(input);
wait(NULL);
}
}
else if (strcmp(argv[1], "3") == 0) {
char input[100];
gets(input);
int i = 0, counter = 0;
while (input[i] != '\0') {
if (input[i++] == ' ') counter++;
}
printf("%d\n", counter);
}
return;
}
In this kind of construct, when you connect stdout from a process to stdin of another process via unnamed pipe, a newline character is added usually to ensure the stream is sent, i.e. the stdout buffer is flushed, as a parallel example, when you use scanf, only when you hit enter (a newline is added to stdin) is the stream read, a similar principle applies here.
I would suggest you use STDIN_FILENO and STDOUT_FILENO
built in macros instead of the hard coded file descriptors, if not for anything else, it makes the code more readable for someone who is unfamiliar with the matter.
Please avoid using gets, this is a dangerous function, it does not check the bounds of the destination buffer, it can cause all kinds of trouble, so much so it was deprecated and later removed from the standard, though it still can be used with some compilers, for legacy reasons I would imagine, check this fantastic answer on a post about this topic:
Why is the gets function so dangerous that it should not be used?
The advice is to use fgets instead.
I was working on an assignment using Windows Subsystem for Linux. Below is the C Code that was used to write a mini-shell for this assignment.
I ran into an interesting issue using WSL. On line 35, you can see that I call the read function to read in the buffer and it does a check for null. When pressing Ctrl+D, while using WSL, it will go into the if statement and prints the print message on line 36 infinitely and does not stop until I used Ctrl+C to exit. When running this program on a Linux machine, it behaves appropriately and prints once, and brings us to the top of the loop.
Any ideas as to what this bug could be?
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <errno.h>
#include <string.h>
#include <sys/types
#include <unistd.h>
#include <error.h>
char prompt[] = "$ ";
static int
Fork()
{
pid_t pid;
if ((pid = fork()) < 0)
error(EXIT_FAILURE, errno, "fork error");
return(pid);
}
int
main(void)
{
long MAX = sysconf(_SC_LINE_MAX);
char buf[MAX];
pid_t pid;
int status, n;
do {
write(STDOUT_FILENO, prompt, strlen(prompt));
fflush(NULL);
memset(buf, 0, MAX);
if((n = read(STDIN_FILENO, buf, MAX)) == 0) {
printf("use exit to exit shell\n");
continue;
}
buf[strlen(buf) - 1] = '\0'; // chomp '\n'
if (strncmp(buf, "exit", MAX) == 0) { // match
break;
}
pid = Fork();
if (pid == 0) { // child
execlp(buf, buf, (char *)NULL);
error(EXIT_FAILURE, errno, "exec failure");
}
// parent
if ((pid = waitpid(pid, &status, 0)) < 0)
error(EXIT_FAILURE, errno, "waitpid error");
} while(1);
exit(EXIT_SUCCESS);
}
The program is in C but there are no options available to insert C Code snippets.
Documentation on read() (Linux manpages v 3.54) does not specify that end of file (ctrl/D) causes read to return anything besides of 0. On the contrary, it says that return value zero indicates end of file. So you're relying upon undefined behavior.
Somehow on your Linux ctrl/D causes error, thus read() returns -1. Your program in this case exits the loop. Or, ctrl/D is read literally, then read() returns 1.
Different OSs use different keystrokes for EOF.
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.
I'm supposed to create two programs (main and aux), where main forks a child to execute aux. The parent takes input from the user, until blank line '\n', and the child executes aux, which is supposed to print the input back out. I'm able to get it to work in main with the commented code instead of execlp(), but cannot get execlp(aux) to work correctly. Any help is appreciated.
"main.c"
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
int main() {
int fd[2], i;
char line[100], buffer[100];
pipe(fd);
pid_t pid = fork();
if (pid < 0) {
printf("Fork Failed\n");
exit(-1);
}
else if (pid > 0) {
close(fd[0]);
while(fgets(line, sizeof(line), stdin) && line[0] != '\n') {
write(fd[1], line, sizeof(line));
}
close(fd[1]);
}
else {
close(fd[1]);
dup2(fd[0], STDIN_FILENO);
//while(read(fd[0], buffer, sizeof(buffer)))
// printf("> %s", buffer);
execlp("./aux", "aux", (char *)0);
}
return 0;
}
"aux.c"
#include <stdio.h>
#include <stdlib.h>
int main() {
char data[100];
while(fgets(data, sizeof(data), stdin))
printf(">%s\n", data);
return 0;
}
sample input/output
this
>this
is a test
>
> test
only prints larger text with random \n
>
>ts larger text with random \n
Your call to write(2) is wrong (you always write 100 bytes even for shorter line-s):
write(fd[1], line, sizeof(line)); // WRONG
should probably be using strlen(3)
size_t ll = strlen(line);
ssize_t wc = write(fd[1], line, ll);
if (wc != ll)
fprintf(stderr, "write was wrong (only %d, wanted %d) - %s\n",
(int) wc, (int) ll, strerror(errno));
Since you want to write only the filled bytes of the line buffer, not always 100 bytes each time (some of them not being initialized).
In your case sizeof(data) is 100 since you declared char data[100];
Please read carefully the documentation of every used function (and also ALP or some other book on Unix/POSIX/Linux programming). The documentation of strerror(3) and of errno(3) tells that you need to add:
#include <string.h>
#include <errno.h>
Actually, if you want to use read(2) and write(2) directly (without stdio(3)) you should prefer using larger buffers (e.g. 4Kbytes each at least for efficiency) and you need to manage partial read-s and write-s and do your buffering by yourself.
BTW, compile with all warnings and debug info: gcc -Wall -Wextra -g and learn to use the gdb debugger and strace(1) (and valgrind). In general, be scared of undefined behavior (however, at a first glance, your program don't seem to have UB).
Notice that execlp(3) could fail. Consider adding some call to perror(3) after it.
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.