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.
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.
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).
Related
I'm writing a C program that uses execlp() to run the linux command-line tool, convert. This command takes optional arguments. However, when using it with execlp(), my C program doesn't recognize the flags I pass in and thus doesn't do the command properly.
For example, if I were to run this command in terminal convert -resize 10% src.jpg dst.jpg it will resize the src image by 10%, saving it to dst. However when I run my C program with this code
rc = execlp("convert", "-resize 10%", src, dst, NULL);
my computer doesn't recognize the resize -10% flag and doesn't do anything to my source image. Why is that?
By convention, the first parameter to a process (accessible as argv[0]) is the name of the process. You haven't included that, so "-resize 10%" is read as the process name instead of an option.
Also, "-resize 10%" is actually two parameters separated by a space, so you need to split them up.
rc = execlp("convert", "convert", "-resize", "10%", src, dst, NULL);
Most likely, the -resize should be one option and 10% should be another:
rc = execlp("convert", "convert", "-resize", "10%", src, dst, NULL);
Using execlp() is a bad idea if you have variable numbers of arguments — use execvp() instead, building an array of arguments terminated by a null pointer. Use execlp() only when the argument list is fixed.
char *args[6];
int i = 0;
args[i++] = "convert";
args[i++] = "-resize";
args[i++] = "10%";
args[i++] = src;
args[i++] = dst;
args[i++] = NULL;
rc = execvp(args[0], args);
Note that this formula ensures that the program name is passed correctly — once as a string that is searched for on $PATH, and once as the argv[0] of the executed program.
With execlp(), as dbush notes, you have to repeat the command name — once to specify the executable and once to specify the value for argv[0].
Note too that there is nothing to stop you from telling the program via argv[0] that it has a wholly different name from the name that you execute. This rarely happens (shells don't do it) but when you write the code yourself, it is possible.
I'm new to using linux and the bash command line but I was trying to see whether I could execute basic bash commands (or program) from a c file. My code is really simple and I could've sworn that it was working but suddenly it is not acknowledging that the path even exists.
char* arg[] = {"ls", "-l"};
char* environ[] = {"PATH=./", (char*)0};
execve("/bin/ls", arg, environ);/*execute the command 'ls -l' in curdir*/
I've tried to set as PATH=/bin but it's not even executing the command in that directory. it just returns a similar error.
NOTE: I have tried
char* environ[] = {"PATH=./", NULL};
I've even tried using the envp from main() and that STILL doesn't work.
This error message ...
ls: cannot access 'PATH=./': No such file or directory
... indicates that the ls utility is attempting to access a file named "PATH=./", which it does not find. That is a manifestation of the undefined behavior arising from your code ...
char* arg[] = {"ls", "-l"};
char* environ[] = {"PATH=./", (char*)0};
execve("/bin/ls", arg, environ);/*execute the command 'ls -l' in curdir*/
... on account of execve() expecting and relying upon the argument list to which arg points being terminated by a null pointer.
Although it is somewhat fraught to try to rationalize or interpret undefined behavior, you can imagine that the contents of arrays arg and environ are laid out one immediately after the other in memory, so that the combined representation of these two is the same as the representation of a four-element array of char *. This view is perhaps useful for understanding why the arg and env arrays must be terminated by null pointers in the first place.
The fix is to append a null pointer to the value of arg:
char* arg[] = {"ls", "-l", NULL};
Note also that in this particular case there is no apparent advantage to specifying an environment, so you could simplify by using execv() instead.
Note, too, that path elements other than / itself should not contain a trailing / character. This is not part of a correct name for a directory, and although you can often get away with it, it is poor form.
Put NULL in the end of arg array.
char* arg[] = {"ls", "-l", NULL};
The NULL is used to mark the end of the array.
Please always post a complete program.
As said in the man page, and as said in the comments above, you need a NULL at the end of the arguments list.
You do not need to pass anything in envp[] since you are running just ls
By convention you should pass the full path of the executable in argv[0]
In short, this works
#include <stdio.h>
#include <unistd.h>
int main(void)
{
char* arg[] = { "/bin/ls", "-l", NULL };
execve("/bin/ls", arg, NULL);
return 0;
}
I need to have an input that looks like
./a.out <exe> <arg1> ... <argn> <others_stuff>
where <exe> <arg1> ... <argn> is the input that I must execute as a separate process (the objective is to save the exe's output into a txt).
Saving output into a txt file isn't a problem, I just have to redirect stdout (using dup2, freopen, or something similar).
The problem is to execute just a portion of argv! Because exec's family functions (they are so many!) let to give as input whole argv, or specifying each arg.
I'm writing over here because I can't solve the problem, so I hope you're going to help me (I googled everywhere with no success).
EDIT: I forgot to say that i cannot use system for execute the command!
If you want to use a contiguous portion of argv, you have two options, you can (as you have tried) create a new arg array, properly filling it as so:
char *params[argc-2];
memcpy(params, argv+1, sizeof params);
params[argc-3] = NULL;
execvp(*params, params);
You could just smash argv
argv[argc-3] = NULL;
execvp(argv[1], argv+1);
Or if you don't have too many args, you can use execlp:
execlp(argv[0], argv[0], argv[3], argv[2], argv[4], NULL);
Since exec accepts an argv argument as a char* array terminated by a NULL pointer, you can just use the existing argv and set the member after the last one you want to pass to NULL.
This does destroy argv - if that's a problem, you can copy it first (you'll have to allocate some memory for the new copy...)
I have to write a shell program in c that doesn't use the system() function. One of the features is that we have to be able to use wild cards. I can't seem to find a good example of how to use glob or this fnmatch functions that I have been running into so I have been messing around and so far I have a some what working blog feature (depending on how I have arranged my code).
If I have a glob variable declared as a global then the function partially works. However any command afterwards produces in error. example:
ls *.c
produce correct results
ls -l //no glob required
null passed through
so I tried making it a local variable. This is my code right now:
int runCommand(commandStruct * command1) {
if(!globbing)
execvp(command1->cmd_path, command1->argv);
else{
glob_t globbuf;
printf("globChar: %s\n", globChar);
glob(globChar, GLOB_DOOFFS, NULL, &globbuf);
//printf("globbuf.gl_pathv[0]: %s\n", &globbuf.gl_pathv[0]);
execvp(command1->cmd_path, &globbuf.gl_pathv[0]);
//globfree(&globbuf);
globbing = 0;
}
return 1;
}
When doing this with the globbuf as a local, it produces a null for globbuf.gl_path[0]. Can't seem to figure out why. Anyone with a knowledge of how glob works know what might be the cause? Can post more code if necessary but this is where the problem lies.
this works for me:
...
glob_t glob_buffer;
const char * pattern = "/tmp/*";
int i;
int match_count;
glob( pattern , 0 , NULL , &glob_buffer );
match_count = glob_buffer.gl_pathc;
printf("Number of mathces: %d \n", match_count);
for (i=0; i < match_count; i++)
printf("match[%d] = %s \n",i,glob_buffer.gl_pathv[i]);
globfree( &glob_buffer );
...
Observe that the execvp function expects the argument list to end with a NULL pointer, i.e. I think it will be the easiest to create your own char ** argv copy with all the elements from the glob_buffer.gl_pathv[] and a NULL pointer at the end.
You are asking for GLOB_DOOFFS but you did not specify any number in globbuf.gl_offs saying how many slots to reserve.
Presumably as a global variable it gets initialized to 0.
Also this: &globbuf.gl_pathv[0] can simply be globbuf.gl_pathv.
And don't forget to run globfree(globbuf).
I suggest running your program under valgrind because it probably has a number of memory leaks, and/or access to uninitialized memory.
If you don't have to use * style wildcards I've always found it simpler to use opendir(), readdir() and strcasestr(). opendir() opens a directory (can be ".") like a file, readdir() reads an entry from it, returns NULL at the end. So use it like
struct dirent *de = NULL;
DIR *dirp = opendir(".");
while ((de = readdir(dirp)) != NULL) {
if ((strcasestr(de->d_name,".jpg") != NULL) {
// do something with your JPEG
}
}
Just remember to closedir() what you opendir(). A struct dirent has the d_type field if you want to use it, most files are type DT_REG (not dirs, pipes, symlinks, sockets, etc.).
It doesn't make a list like glob does, the directory is the list, you just use criteria to control what you select from it.
char * S = "hello"; // assume it's dynamically allocated correctly
I want to use S in the below statement when S would be treated as a string with the value "hello".
system("grep S searchtext.txt > result.txt");
How do I do this?
In general it's a very very bad idea to use system like this. system runs the command through the shell, meaning that the string you pass to system is subject to all of the shell's variable expansion, command expansion, special character interpretation, etc.
If you insist on using system, you must first sanitize your string. The easiest way to do that is:
char *tmp = malloc(4*strlen(S)+3);
tmp[0] = '\'';
for (i=0,j=1; tmp[j]=S[i]; i++, j++)
if (S[i]=='\'') tmp[++j]='\\', tmp[++j]='\'', tmp[++j]='\'';
tmp[j++] = '\'';
tmp[j++] = 0;
if (snprintf(cmd, sizeof cmd, "foo %s ...", tmp) >= sizeof cmd) goto error;
system(cmd);
This code single-quotes the whole string S and replaces any embedded single-quotes with '\''. Note that I also checked for command line truncation in case it could lead to execution of dangerous commands.
A better alternative would be to abandon system entirely and perform your own fork and exec to bypass the shell. Then there is no command line to be interpreted; you have full control over the arguments (*argv[] array) that are passed to the external program.
In plain C, you traditionally use snprintf() to format your command line string into a buffer:
char buf[1024];
snprintf(buf, sizeof(buf), "grep '%s' searchtext.txt > result.txt", S);
system(buf);
Of course, for security reasons, you should never do that if S comes from an external source such as a file, a database, or the user himself. That could lead to shell code injection.
Well there is system primitive -- execl, execp.
So you can do this execl("ls", "-la", NULL) in main.