I have to program a little shell for school but I am stuck at even executing a command.
execvp worked when I executed it in the wait for input function, but in the execute command function it doesnt e.g I don't get any output to stdout for commands like ls.
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <errno.h>
int executecommand(char commandarray[]){
char * command = &commandarray[0];
pid_t pid = fork();
if (pid < 0){
perror("failed");
return errno;
} else if (0 == pid) {
execvp(command, &commandarray);
return 0;
} else {
int waitstatus;
waitpid(pid, &waitstatus, 0);
return 0;
}
}
int waitforinput(){
printf("$ ");
char cmd[256];
fgets(cmd, 256, stdin);
executecommand(cmd);
return 0;
}
int main(int argc, char **argv) {
waitforinput();
return 0;
}
The argument to execvp() must be an array of strings ending with NULL, not a pointer to a single string. Normally you would parse the input line into an array containing the command followed by arguments, but if you don't care about arguments you can just create an array with 2 elements, the command followed by NULL.
You also need to remove the newline from the end of the input line returned by fgets(). See Removing trailing newline character from fgets() input for many ways to do this.
If you want to return errno, you need to save it before calling perror(), because that may change errno.
int executecommand(char *commandarray[]){
char * command = commandarray[0];
pid_t pid = fork();
if (pid < 0){
int saved_errno = errno;
perror("fork");
return saved_errno;
} else if (0 == pid) {
execvp(command, commandarray);
int saved_errno = errno;
perror("execvp");
return saved_errno;
} else {
int waitstatus;
waitpid(pid, &waitstatus, 0);
return 0;
}
}
int waitforinput(){
printf("$ ");
char cmd[256];
char *cmd_array[2];
fgets(cmd, 256, stdin);
cmd[strcspn(cmd, "\n")] = 0; // remove trailing newline
cmd_array[0] = cmd;
cmd_array[1] = NULL;
return executecommand(cmd_array);
}
Invalid arguments to execvp :
From the man page:
int execvp(const char *file, char *const argv[])
The execv(), execvp(), and execvpe() functions provide an array of
pointers to null-terminated strings that represent the argument list
available to the new program. The first argument, by convention,
should point to the filename associated with the file being executed.
> The array of pointers must be terminated by a NULL pointer.
The execvp function expects an array of strings terminated by a NULL pointer, not a pointer to one string.
Ignoring the return value of exec* and others:
The exec() functions only return if an error has occurred. The return
value is -1, and errno is set to indicate the error.
Add a check for that.
Likewise, waitpid() may also fail, check its return value.
You also declared waitforinput() and executecommand() to return an int, but ignore the values returned. Either make use of them, or change the return type to void.
Trailing newline:
fgets(cmd, 256, stdin)
fgets will retain the newline in the buffer. Here's a one-liner that I use to remove it¹:
cmd [strcspn (cmd, "\n\r")] = `\0`;
Aside: Change
int main (int argc, char **argv)
to
int main (void)
as you never make use of the arguments.
[1] — Here are some others: https://codereview.stackexchange.com/q/67608/265278
Related
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;
}
printf(*arg);
execvp(*arg, arg);
Here printf() statement prints value= ls.But when running program execvp gives there is no such file or directory.
else if (pid == 0) {
printf(*arg);
execvp(*arg, arg);
char* error = strerror(errno);
printf("shell: %s: %s\n", arg[0], error);
return 0;
if(execvp(arg[0], arg)<0)
{
printf("***ERROR: execution failedn\n");
}
return 0;
}
In the following code are two examples of how to use execvp.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char **argv) {
char *args[] = {"ls", "-l", NULL};
/* an example with a decleared array containing the commande */
if (fork() == 0) {
execvp(args[0], args);
}
/* another example where the commande was passed to main */
printf("argv[0] is the programme/executable name!\nargv[0] = %s\n", argv[0]);
if (fork() == 0) {
execvp(argv[1], argv + 1);
}
return EXIT_SUCCESS;
}
The execv(), execvp(), and execvpe() functions provide an array of
pointers to null-terminated strings that represent the argument list
available to the new program.
The first argument, by convention,
should point to the filename associated with the file being executed.
The array of pointers must be terminated by a null pointer.
While I think I have the grasp on how fork(), exec(), wait() and pid work in C, I have yet to find a way how to run a personal program from within a program.
Here's my code:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h> /* for fork() */
#include<sys/types.h> /* for pid_t */
#include<sys/wait.h> /* fpr wait() */
int main(int argc, char* argv[])
{
char fileName[255];
pid_t pid;
switch (pid = fork()) {
case -1: //Did not fork properly
perror("fork");
break;
case 0: //child
execv(fileName[0],fileName);
puts("Oh my. If this prints, execv() must have failed");
exit(EXIT_FAILURE);
break;
default: //parent
//Infinite Loop
while (1) {
printf(" %s > ", argv[0]);
scanf("%s", fileName); // gets filename
if (fileName[0] == '\0') continue;
printf("\n Entered file: %s",fileName); // prints the fileName
waitpid(pid,0,0); /* wait for child to exit() */
break;
}
}
return 0;
}
My questions are the following:
I want to take a string as an input and I want to limit its scope to 255 characters. Is char fileName[255] and then scanf("%s", fileName); the way to go? Should I use getLine() or some other function instead?
Let's say that the input is taken correctly. How do I execute say an existing hello world program. Will the input be stored in *argv[] ? I found out that in a different program I could use
static char *argv[] = { "echo", "Foo is my name." , NULL };
execv("/bin/echo", argv);
in order to echo "Foo is my name.". Can I do something similar with a helloWorld program?
You're passing a single character as the command name, and the name string as the start of a list of arguments — as if the prototype for execv() were int execv(char cmd, char *args).
The actual prototype is: int execv(char *cmd, char **args), so you need:
char *args[2] = { fileName, 0 };
execv(args[0], args);
I assume you set fileName to a meaningful value somewhere — that isn't shown. For example, it might be "./local_program". It will be treated as the pathname of the executable.
If you want to read the name, then you can use fgets() or getline(), but you'll need to remove the newline:
if (fgets(fileName, sizeof(fileName), stdin) != 0)
{
fileName[strcspn(fileName, "\n")] = '\0';
…as before…
}
or:
char *fileName = 0;
size_t length = 0;
if (getline(&fileName, &length, stdin) != -1) /* Not EOF! */
{
fileName[strcspn(fileName, "\n")] = '\0';
…as before…
}
free(fileName);
The use of strcspn() avoids having to special case overly long command lines such that there isn't a newline in the fileName. Note that this does not attempt to split the input line into a command name and arguments at spaces or anything fancy like that. That's the next level of shell implementation:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(void)
{
char fileName[256];
if (fgets(fileName, sizeof(fileName), stdin) != 0)
{
fileName[strcspn(fileName, "\n")] = '\0';
char *args[129];
char **argv = args;
char *cmd = fileName;
const char *whisp = " \t\f\r\b\n";
/* Ugh — strtok()? OK while only handling white space separators */
char *token;
while ((token = strtok(cmd, whisp)) != 0)
{
*argv++ = token;
cmd = 0;
}
*argv = 0;
execv(args[0], args);
fprintf(stderr, "Oops!\n");
}
return 1;
}
I don't need to check for overflow of the args array because 256 characters of input, minus terminating null, cannot be split to produce more than 128 single-character arguments, each separated from the adjacent ones by a single white space character. Using strtok() is a temporary band-aid. As soon as you need to deal with real shell syntax (pipes, I/O redirections, quoted strings with spaces, etc), strtok() is woefully the wrong tool. (It — strtok() — is also the wrong function to use in any library function whatsoever. Use POSIX strtok_r() on Unix or Microsoft's strtok_s() on Windows if you must use a strtok()-like function in library code.)
As it stands, you'll need to fix the compilation errors from your code:
g++ -std=c++11 -g -Wall -Wextra -Wwrite-strings 36501711.cpp -o 36501711
36501711.cpp: In function ‘int main(int, char**)’:
36501711.cpp:18:25: error: invalid conversion from ‘char’ to ‘const char*’ [-fpermissive]
execv(fileName[0],fileName);
^
36501711.cpp:18:35: error: cannot convert ‘char*’ to ‘char* const*’ for argument ‘2’ to ‘int execv(const char*, char* const*)’
execv(fileName[0],fileName);
^
36501711.cpp: At global scope:
36501711.cpp:7:5: warning: unused parameter ‘argc’ [-Wunused-parameter]
int main(int argc, char* argv[])
^
The warning (about unused argc) is harmless, but the errors are real, and need to be fixed.
You need to initialise fileName before you fork, otherwise the child won't have any valid data there:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h> /* for fork() */
#include<sys/types.h> /* for pid_t */
#include<sys/wait.h> /* fpr wait() */
int main(int argc, char* argv[])
{
char fileName[255];
pid_t pid;
//Infinite Loop
while (1) {
printf(" %s > ", argv[0]);
scanf("%s", fileName);
if (fileName[0] == '\0')
break;
printf("\n Entered file: %s\n", fileName);
pid = fork();
switch (pid) {
case -1: //Did not fork properly
perror("fork");
break;
case 0: //child
execl(fileName, fileName, 0);
perror("exec");
exit(EXIT_FAILURE);
break;
default: //parent
waitpid(pid,0,0); /* wait for child to exit() */
break;
}
}
return EXIT_SUCCESS;
}
I am writing a C program to emulate a simple shell. This shell will basically evaluate commands like any other shell (ls, cat, etc.), as well as handle pipelining and redirection.
Currently, I am trying to start out by getting user input, tokenizing it, and executing the command provided (e.g. executing only "ls" and not "ls -l"). However, I am having a lot of difficulty with the forking. It seems that every time I fork, something goes wrong and hundreds of identical processes are created, leading to my computer freezing and me having to restart. The code appears to be correct, but I have no idea what is causing this behaviour. Below is the relevant portion of my code (main method and input tokenizer method).
int main() {
char inputLine[512]; //user input
char *args[10]; //arguments
char* pathVar = "/bin/";//path for argument
char programPath[512]; //pathVar + args[0]
int n; //count variable
//loop
while (1) {
//print prompt, get input
printf("input> ");
fgets(inputLine, 512, stdin);
n = tokenizer(inputLine, args);
//fork process
pid_t pid = fork();
if (pid != 0) { //if parent
wait(NULL);
} else { //if child
//format input for execution
strcpy(programPath, pathVar);
strcat(programPath, args[0]);
//execute user command
int returnVal = execv(programPath, args);
}
}
return 0;
}
int tokenizer(char *input, char *args[]) {
char *line; //current line
int i = 0; //count variable
line = input;
args[i] = strtok(line, " ");
do {
i++;
line = NULL;
args[i] = strtok(line, " ");
} while (args[i] != NULL);
return i;
}
Putting it all together:
You need to check fork and execv for failure.
You should exit after an execv failure (and perhaps after a fork failure).
And you need to add \n to the strtok delimiters (or remove the newline from the input line in some other way).
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define MAXARGS 10
#define PATH "/bin/"
int main() {
char inputLine[BUFSIZ];
char *args[MAXARGS];
char programPath[BUFSIZ + sizeof(PATH) + 10];
while (1) {
printf(":-> ");
if (fgets(inputLine, BUFSIZ, stdin) == NULL) /* ctrl-D entered */
break;
tokenize(inputLine, args);
pid_t pid = fork();
if (pid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (pid != 0) { /* parent */
wait(NULL);
} else { /* child */
strcpy(programPath, PATH);
strcat(programPath, args[0]);
execv(programPath, args); /* will not return unless it fails */
perror("execv");
exit(EXIT_FAILURE);
}
}
return 0;
}
int tokenize(char *input, char *args[]) {
int i = 0;
args[0] = strtok(input, " \n");
for (i = 0; args[i] && i < MAXARGS-1; ++i)
args[++i] = strtok(NULL, " \n");
return i;
}
You should check that execv doesn't fail and also be sure to exit() at the end of the child block.
//execute user command
int returnVal = execv(programPath, args);
// check return from execv
if (returnVal < 0) {
perror("execv");
exit(1);
}
Also, beware using functions like strcpy in this context since they may lead to buffer overflows. If an untrusted attacker type is talking to your shell this type of security issue could let them break out of the "sandbox".
I want to write a program which will create a new process and in that child process, it should execute the command: ls. In the meanwhile, the parent should wait for the child to die. However, my code does not work.
Please help me thank you very much!
int main()
{
char * read;
size_t size;
getline(&read , &size , stdin);
read[strlen(read)-1] = '\0';
printf("%s\n" , read);
int status;
pid_t f;
if((f = fork()) == 0)
{
execvp(read , &read);
exit(0);
}
else
{
wait(&status);
}
}
From man execvp:
The execv(), execvp(), and execvpe() functions provide an array of pointers to null-terminated strings that represent the argument list available to the new program. The first argument, by convention, should point to the filename associated with the file being executed. The array of pointers must be terminated by a NULL pointer.
You need to use an array of char* and set the last element to NULL.
I am unsure what the getline() is reading but I guess it is the directory to be lsd. The first argument to execvp() should be ls and the second argument the array of char*.
Consider the following:
#define _GNU_SOURCE
#define _POSIX_C_SOURCE 200809L
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main(int argc, char *argv[])
{
char *input_arg[2];
char *input_str = NULL;
size_t input_len = 0;
char **args;
ssize_t len;
size_t n;
pid_t child, p;
int status;
if (argc < 2) {
/* No command line parameters. Read command from stdin. */
len = getline(&input_str, &input_len, stdin);
/* Find length excluding the newline at end. */
if (len > (ssize_t)0)
n = strcspn(input_str, "\r\n");
else
n = 0;
if (n > (size_t)0) {
/* Terminate input command before the newline. */
input_str[n] = '\0';
} else {
fprintf(stderr, "No input, no command.\n");
return 1;
}
input_arg[0] = input_str;
input_arg[1] = NULL;
args = input_arg;
} else {
/* Use command line parameters */
argv[argc] = NULL;
args = argv + 1;
}
child = fork();
if (child == (pid_t)-1) {
fprintf(stderr, "Cannot fork: %s.\n", strerror(errno));
return 1;
}
if (!child) {
/* This is the child process. */
errno = ENOENT;
execvp(args[0], args);
fprintf(stderr, "%s: %s.\n", args[0], strerror(errno));
exit(127);
}
do {
p = waitpid(child, &status, 0);
} while (p == (pid_t)-1 && errno == EINTR);
if (p == (pid_t)-1) {
fprintf(stderr, "Lost child process: %s.\n", strerror(errno));
return 127;
}
if (p != child) {
fprintf(stderr, "waitpid() library bug occurred.\n");
return 127;
}
if (WIFEXITED(status)) {
if (!WEXITSTATUS(status))
fprintf(stderr, "Command successful.\n");
else
fprintf(stderr, "Command failed with exit status %d.\n", WEXITSTATUS(status));
return WEXITSTATUS(status);
}
if (WIFSIGNALED(status)) {
fprintf(stderr, "Command died by signal %s.\n", strsignal(WTERMSIG(status)));
return 126;
}
fprintf(stderr, "Command died from unknown causes.\n");
return 125;
}
The above uses the command line parameters if specified, otherwise it reads one from the standard input. Because the standard input is not tokenized, you can only supply the command name, no parameters. If you enlarge the input_arg[] array into
char *input_arg[4];
and modify the assignment into
input_arg[0] = "/bin/sh";
input_arg[1] = "-c";
input_arg[2] = input_str;
input_arg[3] = NULL;
args = input_arg;
then the input string will be processed using the /bin/sh shell, just like popen() does.
You can also use len = getdelim(&input_str, &input_len, '\0', stdin); and remove the input_str[n] = '\0'; assignment to allow multiline input; the shell should handle those fine, as long as it is short enough to fit in the command line argument buffer (maximum length depends on your OS).
The rules how shells split input into separate commands and parameters are rather complex, and you should not try to emulate them. Instead, find a simple way for the user to specify the parameters separately (like the command-line parameter case), or use the shell to do it for you. If you don't do any splitting, you will probably need to remove the newline at the end of the input line.
The point to note is that for execvp(file, args), args[0] is the name the application sees (as $0 or argv[0]), and args[1] is the first parameter. Each parameter is terminated by NUL (\0) just like strings are normally in C, and the args pointer array must end with a NULL pointer. If there are no parameters, then args[1] == NULL.
why dont you just use system command...
#include <stdio.h>
#include <stdlib.h>
int main ()
{
int i;
printf ("Executing command ls...\n");
i=system ("ls");
printf ("The value returned was: %d.\n",i);
return 0;
}
Update:
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
void main(void)
{
pid_t pid;
pid = fork();
if (pid == 0) // this is child process
{
int i;
printf ("Executing command ls...\n");
i=system ("ls");
printf ("The value returned was: %d.\n",i);
}
else // this is paraent process
{
int status=0
wait(&status);
printf ("Child process is returned with: %d.\n",status);
}
}