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.
Related
I am writing a simple command line interpreter. My code reads a string using scanf and parses it using the function getArgs() shown below, and then uses that array as an argument to execvp to perform a command such as ls. It works if I call only 'ls' but when I call 'ls -la', it gives the same result as 'ls'.
void getArgs(char* command, char* args[]){
int i = 0;
char* p = strtok(command, " ");
args[i] = p;
while(p != NULL){
i++;
p = strtok(NULL, " ");
args[i] = p;
}
}
Here is my main function which includes the initialization of the arguments given:
int main(){
char *args[1024];
char example[30];
char exit[5] = {'q', 'u', 'i', 't', '\0'};
int f1;
int status;
size_t n = sizeof(args)/sizeof(args[0]);
while(strncmp(example, exit, 30) !=0){
printf(">>>");
scanf("%s", example);
getArgs(example, args);
int x = strncmp(args[0], exit, 30);
if (x != 0){
f1 = fork();
if (f1 != 0){
/* wait for child process to terminate */
waitpid(f1, &status, 0);
}
else{myExec(args);}}
else{
return 0;}}
return 0;
}
My guess as to the problem is that my argument array, args, is not null terminated and so when I attempt to use it in myExec():
void myExec(char* args[]){
execvp(args[0], args);
}
this does not work. So my question is, can I set the item after the last non-empty part of my array to null to try to get this to work? If so, how can I do that? Is there a better way to solve this?
The -la is being ignored because scanf("%s", example); will stop at the first space. I suggest
scanf(" %29[^\n]", example);
Which will
Ignore whitespace left in the buffer from the previous command.
Restrict the string input from overflowing.
Allow space seperators in the command.
Note too that in the first execution of while(strncmp(example, exit, 30) !=0) the example is an uninitialised variable, so that needs to be
char example[30] = "";
The %s directive stops scanning at the first whitespace character, so it won't properly capture any commands with spaces (such as ls -la). You should use fgets to get user input if you want to preserve any whitespace:
if ( fgets( example, sizeof example, stdin ) )
{
getArgs( example, args);
...
}
fgets will read up to sizeof example - 1 characters into example (including the newline!) and 0-terminate the string. You may want to take that newline into account with your strtok call.
I've been getting segmentation faults (with gdb printing "??" on backtraces) on a program I'm trying to compile for a while now and after trying many things (such re-programming a data structure I used which should work now) I still kept getting segfaults although now it gave me a line (which I added a comment onto here).
getMains() is ran multiple times to tokenize different lines from the same file.
I wanted mains to be an array of size 4 but when passing it as "char * mains[4]" I I got a compile error for trying to pass it an array (*)[4] which I've never dealt with beforehand (Just started using C). I'm assuming maybe that could be a problem if I try to access any part that wasn't used, but the problem happens while initializing the indices of the array.
The code I'm trying to get to work, where the "char *** mains" argument is taking in a &(char **) from a separate function "runner" which I want to be edited so I can look at its contents in "runner":
bool getMains(FILE * file, char *** mains)
{
char line[256];
int start = 0;
char * token;
const char * mainDelim = "\t \n\0", * commDelim = "\n\t\0";
if(fgets(line, sizeof(line), file) == NULL)
return false;
while(line[0] == '.')
if(fgets(line, sizeof(line), file) == NULL);
return false;
if(line[0] == '\t' || line[0] == ' ')
{
(*mains)[0] = " ";
start = 1;
}
token = strtok(line, mainDelim);
int i;
for(i = start; token != NULL; ++i)
{
(*mains)[i] = strdup(token); // <- gdb: Segmentation Fault occurs here
if(i % 3 == 2)
token = strtok(NULL, commDelim);
else
token = strtok(NULL, mainDelim);
}
free(token); // Unsure if this was necessary but added in case.
return true;
}
/* Snippet of code running it... */
void runner(FILE * file) {
char ** mains;
if(!getMains(*file, &mains))
return;
while(strcmp(mains[1], "END") != 0){
/* do stuff lookinig through indices 0, 1, 2, & 3 */
if(!getMains(*file, &mains))
break;
}
}
Any tips on this or just generally safely modifying arrays through other functions?
Should I change getMains() into "getMains(FILE * file, char ** mains[4]);" and pass it a &"char * mains[4]") for it to be a set size as wanted? Or would that also produce errors?
You need to allocate memory for mains, it should look like this:
char ** mains;
mains = malloc(some number N * sizeof(char*));
You need something like this if you don't use strdup, which allocates the memory for you:
for (int i = 0; i < N; ++i) {
mains[i] = malloc(some number K);
}
In all cases, do not forget to call free on every pointer you received from malloc or strdup. You can skip this part if the program ends right after you would call free.
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;
}
For this Shell program i'm using the functions strtok (see fragmenta.h code) to parsing a string which is introduced by user.
I need to remove the blanks with strotk function and introduce those on a struct of an array of pointers. This are made in fragmenta.h
In the main program (shell.c), is necessary to introduce the string, this one is passed to fragmenta and stored on char **arg. After that, i use the execvp function to execute the command.
The problem is that the program store the whole command, but only execute the first individual command. For example, if we introduce "ls -al", only execute the ls command so i understand that is a problem on the pointer.
Main program shell.c
#include<string.h>
#include<stdlib.h>
#include<stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include "fragmenta.h"
//
char cadena[50];
int pid;
int i, status;
char **arg;
pid_t pid;
//
main()
{
printf("minishell -> ");
printf("Introduce the command \n");
scanf("%[^\n]", cadena);
if (strcmp(cadena, "exit") == 0)
{
exit(0);
}
else
{
pid = fork();
if (pid == -1)
{
printf("Error in fork()\n");
exit(1);
}
else if (pid == 0) //child proccess
{
arg = fragmenta(cadena);
if (execvp(*arg, arg) < 0) /* execute the command */
{
printf("*** ERROR: exec failed\n");
exit(1);
}
}
else /* for the parent: */
{
while (wait(&status) != pid);
}
}
}
int len;
char *dest;
char *ptr;
char *aux;
char **fragmenta(const char *cadena)
{
//
char *token;
int i = 0;
//
len = strlen(cadena);
char *cadstr[len + 1];
dest = (char *)malloc((len + 1) * sizeof(char));
strcpy(dest, cadena);
//printf("Has introducido:%s\n",dest);
token = strtok(dest, " ");
while ( token != NULL)
{
cadstr[i] = malloc(strlen(token) + 1);
strcpy(cadstr[i], token);
token = strtok(NULL, " ");
i++;
}
*cadstr[i] = '\0';
ptr = *cadstr;
i = 0;
while (cadstr[i] != NULL)
{
//printf("almacenado: %s\n",cadstr[i]);
i++;
}
return &ptr;
}
You've got at least two problems.
The first one is this:
ptr=*cadstr;
You've gone through all that trouble to create an array of arguments, and then you just copy the first argument and return a pointer to that copy.
You could just get rid of ptr and return cadstr, except that it's a local variable, so as soon as the function returns, it can be overwritten or deallocated.
Since you're storing everything else in the universe as globals for some reason, the obvious fix to that is to make cadstr global too. (Of course you can't use a C99 runtime-length array that way, but since you've written your code to guarantee a buffer overrun if the input is more than 50 characters, you can safely just allocate it to 50 strings.)
A better solution would be to initialize a new array on the heap and copy all of cadstr into it. Or just initialize cadstr on the heap in the first place.
Second, you never append a NULL to the end of cadstr. Instead, you do this:
*cadstr[i] = '\0';
That leaves the last element in cadstr pointing to whatever uninitialized pointer it was pointing to, but modifies the first byte of whatever that is to be a 0. That could corrupt important memory, or cause a segfault, or be totally harmless, but the one thing it can't do is set cadstr[i] to point to NULL.
When you check this:
i = 0;
while (cadstr[i] != NULL)
i++;
… you only get out of that loop because of luck; you read right past the end of the allocated array and keep going until some other structure or some uninitialized memory happens to be sizeof(void*) 0s in a row.
And when you pass the same thing to execvp, who knows what it's going to do.
You're also declaring main without a prototype, which is deprecated, so you'll probably get a warning for it from any compiler that accepted the rest of your code. To fix that, just do int main().
[...] 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.