I'm trying to make a shell "bosh>" which takes in Unix commands and keep getting a bad address error. I know my code reads in the commands and parses them but for some reason, I cannot get them to execute, instead, I get a "bad address" error.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <string.h>
#include <sys/wait.h>
#define MAX_LINE 128
#define MAX_ARGS 10
int main(){
pid_t pid;
char command[MAX_LINE]; /*command line buffer*/
char *commandArgs[MAX_ARGS]; /*command line arg*/
int i;
char *sPtr=strtok(command," ");
int n=0;
printf("bosh>");
fgets(command, MAX_LINE-1,stdin);
command[strlen(command)-1]='\0';
while(strcmp(command,"quit")!=0)
{
n=0;
sPtr=strtok(command," ");
while(sPtr&&n<MAX_ARGS)
{
sPtr=strtok(NULL," ");
n++;
}
commandArgs[0]=malloc(strlen(command)+1);
strcpy(commandArgs[0],command);
if(fork()==0)
{
execvp(commandArgs[0],commandArgs);
perror("execvp failed");
exit(2);
}
pid=wait(NULL);
printf("%s",">" );
fgets(command, MAX_LINE-1,stdin);
command[strlen(command)-1]='\0';
}
printf("Command (%d) done\n", pid);
return 0;
}
These two lines are the culprit:
commandArgs[0]=malloc(strlen(command)+1);
strcpy(commandArgs[0],command);
First of all, malloc(strlen(...)) followed by strcpy is what the POSIX function strdup already does. But then, you don't need to even copy the string - it is enough to just store the pointer to the original string into commandArgs[0]:
commandArgs[0] = command;
But then, how does execvp how many arguments the command is going to take? If you read the manuals carefully, they'd say something like:
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.
Your argument array is not NULL-terminated. To fix it, use
commandArgs[0] = command;
commandArgs[1] = NULL; // !!!!
(Then you'd notice that you'd actually want to assign the arguments within the strtok parsing loop, so that you can actually assign all of the arguments into the commandArgs array; and compile with all warnings enabled and address those, and so forth).
You initialize sPtr in its declaration, which you do not need to do because you never use the initial value. But the initialization produces undefined behavior because it depends on the contents of the command array, which at that point are indeterminate.
The array passed as the second argument to execvp() is expected to contain a NULL pointer after the last argument. You do not ensure that yours does.
You furthermore appear to drop all arguments to the input command by failing to assign tokens to commandArgs[]. After tokenizing you do copy the first token (only) and assign the copy to the first element of commandArgs, but any other tokens are ignored.
Related
I am trying to make a program that uses shell commands for a school project. I am able to compile and run the code without errors, but when I input a command such as ls, noting happens. I think I am missing something with the execvp.
I have been trying to use various configurations of inputs.
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <wait.h>
#include <stdlib.h>
#define MAX_LINE 80 /* Maximum length of a command */
int main(void) {
char args[MAX_LINE / 2 + 1]; /* command line arguments */
int should_run = 1; /* Flag to determine when to exit the program */
pid_t pid;
char *myCmd;
char *tokens[40];
pid = fork();
while (should_run) {
printf("osh>");
fflush(stdout);
scanf("%s", myCmd);
int i = 0;
char *token = strtok(myCmd, " ");
while (token != NULL) {
tokens[i] = token;
i++;
token = strtok(NULL, " ");
}
if (pid < 0) {
printf("Fork Failed\n");
//exit(1);
} else
if (pid == 0) {
execvp(tokens[0], tokens);
//exit(1);
} else {
if (strcmp(tokens[i - 1], "&")) {
waitpid(pid, NULL, 0);
}
}
}
return 0;
}
In addition to the serious issues with command input and formatting of execvp() arguments called out in another answer, you are fork()ing in the wrong place. You need to fork() a new child for each command the shell runs, so the fork call should go inside the loop. Moreover, it should go after printing the prompt and reading a command, because you want only the parent to do those things, not the children. And you do not need both parent and child to parse the command. Although you could rely on the child to do that, it would be more conventional to do it in the parent. But certainly not in both.
It is conceivable that your program would still seem to work despite the other errors (though that would not be any justification for failing to fix them), but the fork() placement issue is absolutely breaking. It will cause unexpected extra prompting, and the shell will never attempt to execute more than one command (though you might not notice, in part because you do not check the return values of several key function calls).
Writing to unallocated memory:
char* myCmd;
only allocates memory for the pointer. The pointer has an indeterminate value i.e. it may be pointing to anything and
any attempt to dereference a pointer with a bad value would result in undefined behaviour.
Automatically and dynamically allocated objects are initialized only
if an initial value is explicitly specified; otherwise they initially
have indeterminate values (typically, whatever bit pattern happens to
be present in the storage, which might not even represent a valid
value for that type).
The subsequent call to scanf() then invokes undefined behaviour, because no memory was ever allocated for the string.
scanf("%s", myCmd);
Possible fix:
If no command is ever going to exceed 80 characters, as stated in this comment:
define MAX_LINE 80 /*Maximum length of a command*/
You can allocate an array[80] of char and then pass it to scanf().
And to limit input, you could specify a width specifier like so:
scanf("%79s", myCmd);
Note:
Don't use scanf(). Use fgets(). With scanf(), it will only grab the first whitespace separated token (e.g. for input of Hello World, scanf() will only return Hello). — #Craig Estey
The array of pointers to execvp shall be null-terminated:
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.
Code is missing:
tokens[i] = NULL;
after the while loop.
Aside: A shell in C - Tutorial and Beej's guide to UNIX Interprocess Communication might help to elaborate on what #John pointed out in his answer.
I am trying to execute execvp() using a custom **tokens double pointer as input, instead of argv[] on a "create a custom shell" assignment, like this:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
int main(){
char *token;
char **tokens = malloc(sizeof(char*)*512); //512 is for the maximum input-command length
char *command=malloc(sizeof(char)*512);
int i = 0;
pid_t child_pid;
int status;
//***********take input from user*************************************
fgets(command,512,stdin);
//***********parse input*********************************************
token = strtok(command," \t");
while( token != NULL ) {
tokens[i]=token;
i ++;
token = strtok(NULL, " \t");
}
child_pid = fork();
if(child_pid == 0) {
/* This is done by the child process. */
execvp(tokens[0], tokens);
} else {
waitpid(child_pid, &status, WUNTRACED);
}
}
The problem is definately on this line:
execvp(tokens[0], tokens);
and I just can't understand why it can't be executed and print to my stdout.
I have tried this:
execvp("ls", tokens);
and it works just fine.
And this:
printf("%s\n", tokens[0]);
with the output being (according to the test input: ls ):
ls
You have several problems in your code, including:
The array of argument pointers passed to execvp() must be terminated by a null pointer. You do not ensure that.
The string obtained via fgets will include all characters up to and including the line's newline, if the buffer is large enough to accommodate it. You do not include the newline among your token delimiter characters, so for a one-word command ls, the command passed to execvp() is equivalent to "ls\n", not "ls". It is unlikely (but not impossible) that ls\n is an available command on your machine.
You do not check the return value of execvp(), or of any of your other functions, nor do you handle any errors. execvp() is special in that it returns only if there is an error, but you would have saved yourself some confusion if you had handled that case by emitting an error message.
After I correct the first two of those, your program successfully runs an "ls" command for me.
You need to allocate the memory with sizeof(char *).
char **tokens = malloc(sizeof(char *)*512);
^^----------->Size of char pointer
As of now you are allocating sizeof(char) thus invoking undefined behavior.
Also consider the first comment pointed by #n.m
In my program, I am trying to copy each argv[i] to keyword[i], but my program fails with a segmentation fault. What am I doing wrong?
#include <stdio.h>
#include <cs50.h>
#include <ctype.h>
#include <string.h>
int main(int argc, string argv[])
{
//prototype
string keyword = "";
//int j;
for (int i = 0, n = strlen(argv[1]); i < n; i++)
{
keyword[i] = toupper(argv[1][i]);
printf("%i-- printing letters\n", keyword[i]);
}
}
As others have observed, you initialize variable keyword either as an empty string or as a pointer to an empty string literal, depending on the definition of type string. Either way, it is then valid to evaluate keyword[i] only for i equal to zero; any other value -- for read or write -- is out of bounds. Furthermore, in the latter (pointer to string literal) case, you must not attempt to modify the array keyword points to.
Note in particular that C does not automatically expand strings if you try to access an out of bounds element. Instead, an attempt to do so produces "undefined behavior", and a common way for that to manifest in such cases is in the form of a segmentation fault. You can view a segmentation fault as the system slapping down your program for attempting to access memory that does not belong to it.
Since you don't know a priori how long the argument string will be before you copy it, the most viable type for keyword is char *. I will use that type instead of string in what follows, for clarity.
If you indeed do want to make a copy of the argument, then by far the easiest way to do so is via the for-purpose function strdup():
char *keyword = strdup(argv[1]);
That allocates enough memory for a copy of its argument, including the terminator, copies it, and returns a pointer to the result. You are then obligated to free the resulting memory via the free() function when you're done with it. Having made a copy in that way, you can then upcase each element in place:
for (int i = 0, n = strlen(keyword); i < n; i++)
{
keyword[i] = toupper(keyword[i]);
printf("%c-- printing letters\n", keyword[i]);
}
Note, by the way, that the printf() format descriptor for a single character is %c, not %i. You must use that to print the characters as characters, rather than their integer values.
That's one of the simplest ways to write C code for what you're trying to do, though there are many variations. The only other one I'll offer for your consideration is to not copy the argument at all:
char *keyword = argv[1];
If you initialize keyword that way then you do not allocate any memory or make a copy; instead, you set keyword to point to the same string that argv[1] points to. You can modify that string in-place (though you cannot lengthen it), provided that you do not need to retain its original contents.
Before I wrap this up, I should also observe that your program does not check whether there actually is an argument. In the event that there is not (i.e. argc < 2), argv[1] either contains a null pointer (argc == 1) or is undefined (argc == 0; you're unlikely ever to run into this). Either way, your program produces undefined behavior in that case if it attempts to use argv[1] as if it were a pointer to a valid string. You should test for this case first off, and terminate with a diagnostic message if no program argument is available.
Your main problem: you're not allocating memory for your new string, (string keyword = "").
In C, every size that is not known at compilation time has to be dynamically allocated during run-time.
Also, you never check for missing parameters which may crash your program.
See code below for both fixes
#include <cs50.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, string argv[])
{
if (argc != 2)
{
printf("Usage: %s <word>\n", argv[0]);
return 1;
}
int length = strlen(argv[1]);
string keyword = malloc(length+1);
for(int i = 0, n = strlen(argv[1]); i < n; i++)
{
keyword[i] = toupper(argv[1][i]);
printf("%i-- printing letters\n", keyword[i]);
}
keyword[length]=0;
free(keyword);
}
I'm trying to split the input from fgets using strtok, and store the results in an array, i.e. newArgs, so I can then call execvp and essentially execute the input passed by fgets.
E.g. ls -la will map to /bin/ls -la and execute correctly.
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char * argv[])
{
char buff[1024];
fgets(buff, 1024, stdin);
buff[strcspn(buff, "\n")] = 0;
printf("%s\n", buff);
printf("%d\n", strlen(buff));
char *newArgs[30];
char *token;
char delim[2] = " ";
token = strtok(buff, delim);
int i = 0;
while(token != NULL)
{
if(newArgs[i])
{
sprintf(newArgs[i], "%s", token);
printf("%s\n", newArgs[i]);
}
token = strtok(NULL, delim);
i++;
}
execvp(newArgs[0], newArgs);
return 0;
}
I keep getting a Segmentation fault, even though I'm checking the existence of newArgs[i], which is a little odd. Any ideas as to what's going wrong?
You're not allocating any memory for each element of newArgs. Try using a multi-dimensional array, like newArgs[30][100]. Don't forget to ensure they're null terminated.
Problems I see:
You are using uninitialized values of newArgs[i]. You have:
char *newArgs[30];
This is an array of uninitialized pointers. Then, you go on to use them as:
if(newArgs[i])
That is cause for undefined behavior. You can fix that by initializing the pointers to NULL.
char *newArgs[30] = {};
You haven't allocated memory for newArgs[i] before calling
sprintf(newArgs[i], "%s", token);
That is also cause for undefined behavior. You can fix that by using:
newArgs[i] = strdup(token);
The list of arguments being passed to execvp must contains a NULL pointer.
From http://linux.die.net/man/3/execvp (emphasis mine):
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 are missing the last requirement. You need o make sure that one of the elements of newArgs is a NULL pointer. This problem will go away if you initialize the pointers to NULL.
You are not allocating memory for newArgs before storing it in the string.
Add
newArgs[i] = malloc(strlen(token));
before the if statement inside the for loop.
There is absolutely no reason to copy the tokens you are finding in buff.
That won't always be the case, but it certainly is here: buff is not modified before the execvp and execvp doesn't return. Knowing when not to copy a C string is not as useful as knowing how to copy a C string, but both are important.
Not copying the strings will simplify the code considerably. All you need to do is fill in the array of strings which you will pass to execvp:
char* args[30]; /* Think about dynamic allocation instead */
char** arg = &args[0];
*arg = strtok(buff, " ");
while (*arg++) {
/* Should check for overflow of the args array */
*arg = strtok(NULL, " ");
}
execvp(args[0], args);
Note that the above code will store the NULL returned by strtok at the end of the args array. This is required by execvp, which needs to know where the last arg is.
Here's more code I whipped up since i was having trouble with my major program that I now fixed.
I have a function which modifies a series of bytes. In this example, the function is supposed to fill up the first 9 bytes of the char array with the numbers 1 through 9 consecutively.
Two tests are run. The first one is calling the function where the parameter is (char*)&myvar. The second test only uses myvar as a parameter. I thought I always had to use an & in front of a char array pointer when I want the string returned in the parameter portion of the function.
Why does this program only work when I don't prepend (char*)& to my char array variable?
When I do apply it, I receive a segmentation fault.
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
int func(char* abc){
char *p=abc;
int n;
for (n=1;n<10;n++){
*p=(unsigned char)(n+48);p++;
}
return 0;
}
int main(){
char vars[1000]="234";
char *myvar=vars;
printf("Function test\n");
int result=func((char*)&myvar); //causes segfault
printf("Function result %s\n",myvar); //segfault here
printf("Function test again\n");
int result2=func(myvar); //works
printf("Function result %s\n",myvar);
printf("DONE\n");
return 0;
}
Why does this program only work when I don't prepend (char*)& to my char array variable?
Because doing that is completely wrong and not a thing that makes sense.
I thought I always had to use an & in front of a char array pointer when I want the string returned in the parameter portion of the function.
You don't. (Also, you do not have a "char array pointer", and "when I want the string returned in the parameter portion of the function" doesn't make sense.)
When you need to pass a char * to a function that takes a char *, you do not need to put any special prefix in front of the pointer. You just pass it directly, the way you did with
int result2=func(myvar);
You could also have passed in vars, due to the automatic conversion from an array to a pointer to its first element, just like you were able to do char *myvar=vars; without any special casting.