Double pointer as argument to execvp() - c

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

Related

C execvp usage, what am I missing?

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.

How to convert a char ** data type to something that can be printed in c?

I've been tasked with creating a nested Unix shell that executes commands using the fork/exec/wait model. One of the instructions states that if the command doesn't exist (like "cd", then I need to print the attempted command. E.G. (If input is cd, output is "cd: command not found.")
But I cannot for the life of me figure out how to print the command that was input.
I declared an array, tried using scanf, tried converting from char ** to char *, or to int; none of my attempts to brute force it are working.
Here's the code:
int main() {
char **command;
char *input;
pid_t child_pid;
int stat_loc;
char arg[20];
while (1)
{
input = readline("minor2> ");
command = get_input(input);
if(!command[0])
{
free(input);
free(command);
continue;
}
else if(command[0]=="quit")
{
exit(1);
}
child_pid = fork();
if (child_pid == 0)
{
/*Never returns if call is a success*/
execvp(command[0], command);
printf("%s", arg);
printf(": command not found.\n");
}
else
{
waitpid(child_pid, &stat_loc, WUNTRACED);
}
free(input);
free(command);
}
char **get_input(char *input) {
char **command = malloc(8 * sizeof(char *));
char *separator = " ";
char *parsed;
int index = 0;
parsed = strtok(input, separator);
while (parsed != NULL) {
command[index] = parsed;
index++;
parsed = strtok(NULL, separator);
}
command[index] = NULL;
return command;
}
Some of the errors I've receieved:
"assignment makes integer from pointer without a cast -wint-conversion"
"Cast to pointer of integer with different size"
"1value required as left operand of assignment"
I'm just confused. I'm sure there's an elegant solution, I just am not experienced enough to figure it out.
First of all, you are trying to print arg as the command name, but I really cannot see where you assign anything to arg, so that print may break anytime due to invalid memory access (unless you hit a 0 interpreted as NULL-terminator somewhere close on the stack). You can simply replace
printf("%s", arg);
with
printf("%s", command[0]);
and it should do it.
Otherwise you should take care of filling the arg char array to hold the command name.
I'll edit my answer as I could see another problem as well:
In C, a char * is a pointer to a memory location, so it is technically just an integer. If you try to compare like this:
if (command[0]=="quit")
the check will almost always fail (in your code flow it will always fail). This is because you compare 2 memory addresses which are different whereas you should compare the contents of the memory locations. The proper way to compare 2 strings in C is to compare character by character until you hit the NULL-terminator. There is a function which does that:
strcmp(command[0], "quit");
It will return 0 if the strings are equal, or something different than 0 otherwise.

Bad address error with execvp

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.

Storing as using result of strtok in array in C

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.

using getline and strtok together in a loop

I am new to C, and trying to implement whoami, as an exercise to myself. I have following code:
#define _POSIX_SOURCE
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h> // strtok
int str_to_int(const char *str)
{
int acc = 0;
int i;
for (i = 0; str[i] != '\0'; ++i) {
acc = (10 * acc) + (str[i] - 48); // 48 -> 0 in ascii
}
return acc;
}
int main()
{
FILE *passwd;
char *line = NULL;
size_t line_size;
passwd = fopen("/etc/passwd","r");
uid_t uid = getuid();
while (getline(&line, &line_size,passwd) != -1) {
char *name = strtok(line,":");
strtok(line,":"); // passwd
char *user_id = strtok(line,":");
if (str_to_int(user_id) == uid) {
printf("%s\n",name);
break;
}
}
fclose(passwd);
return 0;
}
Do I need to save line pointer inside of the while loop. Because I think strtok modifies it somehow, but I am not sure if I need to copy the line, or starting address of the line before I use it with strtok.
strtok is a horrid function. I don't know what documentation you read (if any?) but it both modifies the buffer it is passed and retains an internal pointer into the buffer; you should only pass the buffer the first time you use it on a given line, and pass NULL subsequently so it knows to pick up where it left off instead of starting at the beginning again (which won't actually work quite right because it stomped on the buffer...).
Better, find some other way to parse and stay far away from strtok.
It might be safer to use strtok_r. It is safer in a multi-threaded situation. That may not apply in this case, but it is sometimes better just to assume that some point any snippet you write might end up in a multi-threaded app. The following is the OP code modified to use strtok_r.
char *pos;
char *name = strtok_r(line,":",&pos);
strtok_r(NULL,":",&pos); // passwd
char *user_id = strtok_r(NULL,":",&pos);
And, yes, strtok (and strtok_r) do modify the given input buffer (first parameter). But it can be safe if used properly. Since strtok returns a pointer to a buffer inside the given string, you need to be careful how you use it. In your case, when it breaks out of the loop, name and user_id will point to a value inside the line buffer.
And you maybe should read the man pages for getline. The way you are using it, it returns an allocated buffer that your application is responsible for freeing. That might be what you are aiming for, but I mention it because I don't see a free call for it in the posted code.
I totally agree with geekosaur (and Mark). Paraphrasing his comment, you can modify the above code as following:
while (getline(&line, &line_size, passwd) != -1) {
char *name = strtok(line,":");
strtok(NULL,":"); // passwd
char *user_id = strtok(NULL,":");
if (str_to_int(user_id) == uid) {
printf("%s\n",name);
break;
}
}
You should pass NULL for the strtok invocations other than the first one.

Resources