Daniel Rivas
Daniel Rivas

Reputation: 256

malloc on (char**)

Well, I'm trying to write a shell for linux using C. Using the functions fork() and execl(), I can execute each command, but now I'm stuck trying to read the arguments:

char * command;
char ** c_args = NULL;

bytes_read = getline (&command, &nbytes, stdin);

command = strtok(command, "\n ");
int arg = 0;
c_arg = strtok(NULL, "\n ");
while( c_arg != NULL ) {
    if( c_args == NULL ) {
        c_args = (char**) malloc(sizeof(char*));
    }
    else {
        c_args = (char**) realloc( c_args, sizeof(char*) * (arg + 1) );
    }
    c_args[arg] = (char*) malloc( sizeof(char)*1024 );
    strcpy( c_args[arg], c_arg );
    c_arg = strtok(NULL, "\n ");
    arg++;
}
...
pid_t pid = fork()
...
...
execl( <path>, command, c_args, NULL)
...
...

That way I get errors from the command when I try to pass arguments, for example:

ls -l

Gives me:

ls: cannot access p��: No such file or directory

I know that the problem is the c_args allocation. What's wrong with it?

Cheers.

Upvotes: 0

Views: 1456

Answers (2)

user1902824
user1902824

Reputation:

Jonathan has a great answer, I just wanted to add a few more things.

You could use popen or system to just execute by the shell directly. They are usually frowned upon since they can be injected so easily, but if you are writing an open shell, I don't see the harm in using them.

If you are going for a limited shell (which accepts a sh-like syntax), take a look into wordexp. It does a lot, but in my experience it does too much, especially if you are trying to write a moderately secure interpreter (it does silly things like tilde expansion and variable substitution).

Upvotes: 1

Jonathan Leffler
Jonathan Leffler

Reputation: 754710

You can't use execl() for a variable list of arguments; you need to use execv() or one of its variants (execve(), execvp(), etc). You can only use execl() when you know all the arguments that will be present at compile time. In most cases, a general shell won't know that. An exception is when you do something like:

execl("/bin/sh", "/bin/sh", "-c", command_line, (char *)0);

Here, you're invoking the shell to run a single string as the command line (with no other arguments). However, when you're dealing with what people type at the keyboard in a full shell, you won't have the luxury of knowing how many arguments they typed at compile time.

At its simplest, you should be using:

execvp(c_args[0], c_args);

The zeroth argument, the command name, should be what you pass to execvp(). If that's a simple file name (no /), then it will look for the command in directories on your $PATH environment variable. If the command name contains a slash, then it will look for the (relative or absolute) file name specified and execute that if it exists, and fail if it does not. The other arguments should all be in the null-terminated list c_args.

Now, there may also be other memory allocation issues; I've not scrutinized the code. You could check them, though, by diagnostic printing of the argument list:

char **pargs = c_args;
while (*pargs != 0)
    puts(*pargs++);

That prints each argument on a separate line. Note that it doesn't stop until it encounters a null pointer; it is crucial that you null terminate your list of pointers to the argument strings.

This bit of your code:

c_args[arg] = (char*) malloc( sizeof(char)*1024 );
strcpy( c_args[arg], c_arg );

looks like overkill in the usual case, and an inadequate memory allocation in the extreme cases. When you're copying strings around, allocate enough length. I see that you're using strtok() to bust apart a string — it'll do for the early incarnations of a shell, but when you get to process command lines like ls -l>$tmp, you will find strtok()'s penchant for trampling over your delimiter before you get to read it becomes a major liability. However, while you're using it, you probably don't have to copy the arguments like that; you can just set c_args[arg++] = result_from_strtok;. When you do need to copy, you should probably use strdup(); it doesn't forget to allocate enough space for the trailing '\0', for example, and neither over-allocates nor under-allocates.

Upvotes: 2

Related Questions