UNIX simple shell in C, execve and parameters - c

[...] Preprocesser directives
void read_command()
{
int i; //index to the arrays stored in parameter[]
char *cp; //points to the command[]
const char *hash = " "; //figures out the strings seperated by spaces
memset(command, 0, 100); //Clear the memory for array
parameter[0] = "/bn/"; //Initialize the path
//Get the user input and check if an input did occur
if(fgets(command, sizeof(command), stdin) == NULL)
{
printf("Exit!\n");
exit(0);
}
//Split the command and look store each string in parameter[]
cp = strtok(command, " "); //Get the initial string (the command)
strcat(parameter[0], cp); //Append the command after the path
for(i = 1; i < MAX_ARG; i++)
{
cp = strtok(NULL, " "); //Check for each string in the array
parameter[i] = cp; //Store the result string in an indexed off array
if(parameter[i] == NULL)
{
break;
cp = NULL;
}
}
//Exit the shell when the input is "exit"
if(strcmp(parameter[0], "exit") == 0)
{
printf("Exit!\n");
exit(0);
}
}
int main()
{
[...]
read_command();
env = NULL; //There is no environment variable
proc = fork();
if(proc == -1) //Check if forked properly
{
perror("Error");
exit(1);
}
if (proc == 0) //Child process
{
execve(parameter[0], parameter, env); //Execute the process
}
else //Parent process
{
waitpid(-1, &status, 0); //Wait for the child to be done
}
[...]
}
The basic idea of the code is to read the input command by the user (done in the read_command() function) (ex: ls -l). Then I divide the input string in little strings and store them in an array. The point is to store the command in parameter[0] (ex: ls) and the parameters in parameter[1,2,3 etc.] (ex: -l). However, I think I executing the execve() function incorrectly.

There are all types of issues with your code including the following (some of them are correctly pointed out by Jonathan Leffler):
"/bin/" is misspelled as "/bn/"
Since parameter[0] points to a string literal ("/bn/") in strcat(parameter[0], cp); you are trying to append to this string literal which is incorrect. You should allocate a buffer to hold the concatenated string instead.
Your tokenizing code doesn't handle the trailing newline in command properly.
env should point to a NULL-terminated array of strings.
In general, I think you should focus on implementing and testing parts of your code properly before integrating them in a larger program. If you tested the read_command before trying to pass its results to execve, you would notice that it doesn't work.

Related

Execute fork processes simultaneously [duplicate]

I am working on a home made shell (very simple shell). I have decided to take the route of using execvp as my path is not a changeable element for my shell. I am running into an issue with coming up with the logic on how to fork and exec multiple processes at once.
My program should work with a command as such:
ls ; echo hello ; cat shell.c
Where each ";" indicates that we would like to run these processes at once simultaneously. So on our terminal output we should get a mix of these commands working at once.
To elaborate I'd like to explain how my program works:
A. Intake full command line into char array with a grab line function
B. Split the char array received from the last function by delimiters and place into an array of char arrays (pointer to pointer).
C. If one of the elements in our array of char arrays is ";" we can assume that multi commands are necessary. This is where I have trouble.
I have gotten as far as to know exactly how many processes I need to fork and such, but I cannot seem to wrap my head around how to pass all of these functions plus their arguments to the execvp function at once. Should I use a temp array? I know this shouldn't be this complicated but for some reason I cannot figure it out. I'm submitting my launch function below, which intakes an array of char arrays and executes accordingly based on my "multiCommand" variable which is set when multi commands are needed (by my split line function)
int launch(char **args){
pid_t pid;
int status;
int i = 0;
if(strcmp(args[0], "quit") == 0){
exit(EXIT_SUCCESS);
}
if(strcmp(args[0], ";") != 0){
printf("Essential Command Found : %s\n", args[0]);
numFork++;
}
if(multiCommand == 1){
//Handle Multicommands here
printf("Multi Commands Handling Here\n");
for(; i < elements - 1; i++){
if(strcmp(args[i], ";") == 0){
if((i + 1) < elements){
printf("Essential Command Found : %s\n", args[i + 1]);
numFork++;
}
}
}
//This is where I need to figure out what to do
printf("Fork: %d times\n", numFork);
}else if (multiCommand == 0){
pid = fork();
if(pid == 0){
execvp(args[0], args);
}else{
wait(&status);
}
}
multiCommand = 0;
elements = 0;
return 1;
}
The general idea would be to have a for loop over the different commands and fork each of them.
E.g.
for(int i = 0; i < commandCount; i++) {
int pid = fork();
if(pid == 0) { //this is the child (don't forget to check for errors and what-not)
execCommand(all, of, the, info, needed);
}
}
You can easily get the different commands using strtok().
Here's an example:
#include <string.h>
#include <stdio.h>
int main() {
char input[] = "abc;def;ghi";
char *token = strtok(input, ";");
while(token != NULL) {
printf("%s\n", token);
token = strtok(NULL, ";");
}
return 0;
}
Output:
abc
def
ghi
The final function will look something like this:
char *token = strtok(input, ";");
while(token != NULL) {
int pid = fork();
if(pid == 0) {
//token is one command
//parse the different parts here
execCommand(args, to, exec);
}
token = strtok(NULL, ";");
}

Creating Simple Shell in Linux

I am working on an assignment to create an extremely simple Linux shell in C, and it works almost exactly how I want it to.
If the user enters a simple Linux command, the program will run it and loop to allow another command. If the user enters "quit", the program exits.
My problem is that the commands only work the first time. Afterward, they seem to somehow become formatted improperly. Is there a way I can reinitialize my args array so that it will receive the new input properly?
int main() {
char* args[50]; // Argument array.
char userInput[200]; // User input.
char* userQuit = "quit"; // String to be compared to user input to quit program.
int pid; // Process ID for fork().
int i = 0; // Counter.
while(1) {
// Promt and get input from user.
printf("minor5> ");
fgets(userInput, sizeof(userInput), stdin);
// Pass userInput into args array.
args[0] = strtok(userInput, " \n\0");
// Loop to separate args into individual arguments, delimited by either space, newline, or NULL.
while(args[i] != NULL) {
i++;
args[i] = strtok(NULL, " \n\0");
}
// If the first argument is "quit", exit the program.
if(strcmp(args[0], userQuit) == 0) {
printf("Exiting Minor5 Shell...\n");
exit(EXIT_SUCCESS);
}
// Create child process.
pid = fork();
// Parent process will wait for child to execute.
// Child process will execute the command given in userInput.
if(pid > 0) {
// Parent //
wait( (int *) 0 );
} else {
// Child //
int errChk;
errChk = execvp(args[0], args);
if(errChk == -1) {
printf("%s: Command not found\n", userInput);
}
}
}
return 0;
}
You need to ensure that args has a NULL last value. It probably had one on the first command, by chance, but no guarantee
Here's a reworked snippet of your parsing loop [please pardon the gratuitous style cleanup]:
// Pass userInput into args array.
char *uptr = userInput;
i = 0;
while (1) {
char *token = strtok(uptr, " \n");
uptr = NULL;
if (token == NULL)
break;
args[i++] = token;
}
// NOTE: this is the key missing ingredient from your code
args[i] = NULL;

execv() system call creating stacking smashing error

Whenever I used the execv() here in my code, it works and has no errors, but still causes stack smashing to crash the program during runtime. Am I doing anything wrong here?
Here is the function with the execv():
void execute(char *args[], char *cmd[])
{
pid_t pid;
int status;
char bin[10] = "/bin/";
pid = fork();
// child process
if(pid == 0)
{
strcat(bin, cmd[0]);
execv(bin, args);
} else{
perror("error");
while(wait(&status) != pid);
}
}
here is where I am getting args and cmd from. Would it possibly be caused by something I did here?
void parseString(char *command)
{
char **args = malloc(sizeof(char*) * 16);
int i = 0;
int j = 0;
char *cmd[1];
// split each command by semicolons if necessary, then send each sub command to parseString()
if(strchr(command, ';')) {
char *semi_token = strtok(command, ";");
while(semi_token != NULL){
args[i] = semi_token;
semi_token = strtok(NULL, " ");
parseString(args[i]);
i++;
}
} else {
// if no semi colons, split the commandby spaces and call execute() using the args and cmd
char *token = strtok(command, " ");
while(token != NULL)
{
args[i] = token;
args[++i] = NULL;
while(j == 0 && token != NULL) {
cmd[0] = token;
cmd[1] = NULL;
j++;
}
token = strtok(NULL, " ");
}
execute(args, cmd);
}
j = 0;
i = 0;
free(args);
}
function call happens here. command is input from stdin from the user. Only need basic commands all located in /bin/. something like ls -l or cat file.
while(1){
command = getCommand();
parseString(command);
}
You have two serious errors: One that will lead to out-of-bounds writing of an array, and one that will probably lead to that.
The first, the certain out-of-bounds writing, is in the parseString function. First you have the declaration of the cmd variable:
char *cmd[1];
This defines cmd as an array of one element. Then you do
cmd[0] = token;
cmd[1] = NULL;
which writes to two elements of the one-element array. Writing out of bounds leads to undefined behavior.
The second error is in the execute function, and is the one I talked about in my first comment. There you have
char bin[10] = "/bin/";
That defines bin as an array of ten characters, and you fill up six of them (don't forget the string terminator). In the child-process you do
strcat(bin, cmd[0]);
which appends to string in cmd[0] to the string in bin. The problem here is that bin only have space for ten characters, of which six is already used (as explained above). That means there's only space left for four characters. If the command is any longer than that you will also go out of bounds and again have undefined behavior.
The solution to the first error is simply, make cmd an array of two elements. The solution to the second error is to either make bin larger, and not concatenate more than can fit in the array; Or to allocate the array dynamically (not forgetting space for the terminator).
There are also lot of other potential problems with your code, like the limit on 16 pointers for args. And that you don't really parse arguments in the parseString function, every argument is seen as a separate command. And the memory leak in the case there are semicolon-separated "commands". Or that you don't check for or handle errors everywhere needed. And that you use errno even if there's no error.

Using strtok to run multiple commands

I'm trying to create a shell using C that can take multiple commands separated by a semicolon(;). Currently I'm trying to use strtok to separate the commands but I don't think I'm using it correctly. I'll post all the info I can without posting the entire code. Is strtok being used correctly?
char *semi=";";
else
{
char *token=strtok(str,semi);
if(token != NULL)
{
token=strtok(NULL,semi);
if((childpid = fork()) == 0)
{
if ((execvp(args[0], args))<0)//prints error message when unknown command is used
{
printf("Error! Command not recognized.\n");
}
execvp(args[0],args);
free(args);//deallocate args
exit(0);
}
Edit: As per instructed I removed a large chunk of the code originally posted to focus solely on the use of strtok. When compiled the makeshift shell will accept one command at a time. I'm trying to use ";" to separate and run two commands simultaneously. Am I using strtok correctly? If not, is there an alternative?
You should always check, if strtok() returns NULL. I would change the structure as follows:
char* semi = ";"; // Your semikolon
char *token = NULL; // Your token string
// ...
// Split first occour of semicolon
token = strtok(str,semi);
if(token == NULL){
perror("No command given ...");
return NULL;
}
do {
// Execute your code here
// fork() etc.
// You should get each line (each semikolon seperated string)
// and it should be stored into token
} while((token = strtok(NULL, semi) != NULL);
I hope, I did understand your problem right ...
But as I can see, you need to split the token again by spaces to get them into a char-Array for the argv[] (second parameter) of execvp(). Here the problem is, that strtok() internally uses a static (?) variable to store the last position. So using another strtok() inside the loop would "destroy" your text.
You could do something like this:
char *str; // Your string ...
char semi[1] = ";"; // Your semikolon AND space; strtok() will split at both
char *token = NULL; // Your token string
int len = 0;
char *token2;
int argvpos = 0;
// ...
// Split first occour of semicolon
token = strtok(str,semi);
if(token == NULL){
perror("No command given ...");
return EXIT_FAILURE;
}
do {
// save length of token
len = strlen(token);
// Split for blanks to get the arguments
token2 = strtok(token," ");
// Build array of arguments
while(token2 != NULL){
args[argvpos++] = token2;
token2 = strtok(NULL," ");
}
// Do something with token (as command)
// and args (as arguments)
// ...
} while((token = strtok(token+len+1, semi) != NULL);
// In the while condition you add the length to the token; so you get the "old" last position
I think it is not a good solution, but it should work. And I hope, I did understand you problem ;-)
Kind regards.
In order to work correctly, strtok should be used along with a while loop. Also, you don't need to run execvp twice.
I created a small sample program using your code to demonstrate how you can correctly use your code:
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
char str[] = "ls -1; echo 'hello world'"; // Input commands separated by ';'
// Break the commands string into an array
char *commands[10]; // Array to hold a max of 10 commands
char *semi = ";";
char *token = strtok(str, semi);
int i = 0;
while (token != NULL)
{
commands[i] = token;
++i;
token = strtok(NULL, semi);
}
int numCommands = i; // numCommands is the max number of input commands
// Run each input command in a child process
i = 0;
while (i < numCommands)
{
printf("Command: %s\n", commands[i]);
// Tokenize the command so that it can be run using execvp
char *args[10] = {}; // Array to hold command args
args[0] = strtok(commands[i], " ");
int tokenCounter = 0;
while (args[tokenCounter] != NULL)
{
tokenCounter++;
args[tokenCounter] = strtok(NULL, " ");
}
// Create a child process
int childpid = fork();
// If this is child process, run the command
if (childpid == 0)
{
if ((execvp(args[0], args)) < 0)
{
printf("Error! Command not recognized.\n");
}
exit(0);
}
// If this is the parent, wait for the child to finish
else if (childpid > 0)
{
wait(&childpid);
}
// If the child process could not be created, print an error and exit
else
{
printf("Error: Could not create a child process.\n");
exit(1);
}
++i;
}
return 0;
}

Program strangely returning to the top of the loop

I would like to preface this question by saying I'm new to C and therefore terrible at it, so I apologize in advance for any blatant errors or bad style. Also, I'm not sure how to introduce the problem before showing you my code, so here it is:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int MAX_INPUT_SIZE = 200;
volatile int running = 1;
while (running)
{
char input[MAX_INPUT_SIZE];
char *tokens[100];
const char *cmds[] = { "wait", "pwd", "cd", "exit" };
char *cmdargs[100];
printf("shell> ");
fgets(input, MAX_INPUT_SIZE, stdin);
// remove newline character at end
int nl = strlen(input) - 1;
if (input[nl] == '\n')
{
input[nl] = '\0';
}
// tokenize input string, put each token into an array
char *space;
space = strtok(input, " ");
tokens[0] = space;
int i = 1;
while (space != NULL)
{
space = strtok(NULL, " ");
tokens[i] = space;
++i;
}
// copy tokens after first one into string
int noargscheck;
if (tokens[1] != NULL)
{
noargscheck = 0;
strcpy((char *)cmdargs, tokens[1]);
for (i = 2; tokens[i] != NULL; i++)
{
strcat((char *)cmdargs, " ");
strcat((char *)cmdargs, tokens[i]);
}
}
else
{
noargscheck = 1;
}
// compare tokens[0] to list of internal commands
int isInternal = -1;
for (i = 0; i < 4; i++)
{
if (strcmp(tokens[0], cmds[i]) == 0)
{
isInternal = i;
}
}
// internal commands
char wd[200];
if (isInternal != -1)
{
switch (isInternal)
{
case 0:
// wait
break;
case 1:
// pwd
if (getcwd(wd, sizeof(wd)) == NULL)
{
perror("getcwd() error!");
}
else
{
printf("%s\n", wd);
}
break;
case 2:
// cd
if (noargscheck)
{
chdir("/home");
}
else if (chdir((const char *)cmdargs) != 0)
{
perror("cd failed");
}
break;
case 3:
// exit
exit(1);
break;
}
}
else
{
// external commands
pid_t child_pid;
switch (child_pid = fork())
{
case -1:
perror("Fork failed");
return 1;
case 0:
// child
printf("\nHERE\n"); // for debugging
execvp(tokens[0], cmdargs);
break;
}
}
}
}
When I run this code with the input echo hello world, the program successfully enters the case 0 case in the second switch statement that starts with switch (child_pid=fork()) but the output, which is unexpected, is as follows:
OUTPUT: (including one line that shows my input at the prompt)
shell> echo hello world (my input)
shell> (this is the part I don't understand)
HERE
shell> (the program now waits here at the prompt for the next user input)
I cannot figure out why the extra shell> prompt is printing. Can anybody see the problem?
EDIT: fixed the first parameter of execvp. Changed from "echo" (which was there because I'm silly) to tokens[0].
When you fork, you now have two processes at that point. Your child will print the HERE message and then call execvp. You do not check the return value of execvp and so it may be returning an error. cmdargs should be a vector - that is, a null-pointer-terminated array of string pointers. Instead, you are passing execvp a string. In other words, it expects a char* [], which is what cmdargs is, however you have previously treated cmdargs incorrectly.
For example, you say strcpy((char*)cmdargs, tokens[1]);. This places a string at *cmdargs. A string is an array of zero or more non-zero characters followed by an ascii NUL which is 8 bits wide:
char* cmdargs[] is a double pointer
you treat cmdargs as a single pointer and feed it to strcpy
cmdargs points to: 'H' 'e' 'l' 'l' 'o' '\0'
However, this is not what execvp wants. Execvp wants a vector, which looks somewhat more like this:
char* cmdargs[] is a double pointer
cmdargs[0] is a single pointer
cmdargs[0] points to: 'H' 'e' 'l' 'l' 'o' '\0'
cmdargs[1] points to: 'w' 'o' 'r' 'l' 'd' '\0'
cmdargs[2] is a null pointer, indicating that it is the end of the vector
Therefore, execvp is unable to find the end of the vector and fails, returning -1. You do not test for this, so the child process continues back to the top of the loop, as does the parent, and both processes print shell>.
EDIT: By the way, the FIRST string in the argv vector SHOULD be the name of the file being executed - in this case, echo, and then the second and third strings should be the first and second 'arguments' - here hello and world. This is the argv vector fed to the program you call, and by convention the first element in that vector is the name of the called program. If you ignore that convention, echo will get terribly confused.
cmdargs is defined as an array of 100 string-pointers, but you seem to use it as one 100-byte buffer for a single string. I also don't get why you handle token[1] specially. Only token[0] is special, that's the command, all the others are arguments. Processing the arguments should be a loop of
while (cmdargs[i++] = strtok(NULL, " "))
and then the closing NULL pointer in cmdargs for execvp():
cmdargs[i] = NULL;
This being a shell, you also forgot to wait for child processes. You will prompt the user for input before the last child process has finished. The final switch-case should look line this:
pid_t child_pid;
switch (child_pid = fork())
{
case -1:
perror("Fork failed");
return 1;
case 0:
// child
printf("\nHERE\n"); // for debugging
execvp(tokens[0], cmdargs);
perror("Exec failed");
exit(1);
default:
// parent
int status;
wait(&status);
break;
}

Resources