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;
}
Related
I am trying to implement a simple shell program that runs user input commands. I want the user to enter "ls" or "dir" and have the shell run /bin/ls or /bin/dir . For the execve argument what would be correct:
char *args[] ={"/bin/", "ls", NULL};
//Fork, pid, etc...then
execve(args[0], args+1, NULL);
Or would it be something different?? I've see some people using /bin/ls as the pathname and then no arguments or ls as the pathname and \bin for the environment? I tried what I had above and it didn't work so I want to know if it is the arguments I am sending it or whether I should be looking elsewhere in my code for the problem. Note I am not interested in other variations of execve such as execvp. I am on a Linux system. Thanks
PATHNAME
The pathname in execve() must be the full path to the executable, such as /bin/ls. If using execvpe(), you could use ls alone as the pathname, but as you already specified, you don’t want to use that.
ARGUMENTS
The arguments should be an array of strings, one for each space-separated argument specified on the command line. The last one should be NULL. The first argument should be the pathname itself. For example:
char* args[] = {"/bin/ls", "-la", "foo/bar", NULL};
ENVIRONMENT
The environment variables cannot be omitted when using execve(). In some implementations, NULL can be passed as the last argument to execve(), but this is not standard. Instead, you should pass a pointer to a null pointer; essentially an empty array of environment variables.
Putting it together
char *args[] ={"/bin/ls", "-foo", "bar", NULL};
//Fork, pid, etc...then
char* nullstr = NULL;
execve(args[0], args, &nullstr);
From execve [emphasis added]:
int execve(const char *pathname, char *const argv[],
char *const envp[]);
execve() executes the program referred to by pathname. This
causes the program that is currently being run by the calling
process to be replaced with a new program, with newly initialized
stack, heap, and (initialized and uninitialized) data segments.
pathname must be either a binary executable, or a script starting
with a line of the form:
#!interpreter [optional-arg]
For details of the latter case, see "Interpreter scripts" below.
argv is an array of pointers to strings passed to the new program
as its command-line arguments. By convention, the first of these
strings (i.e., argv[0]) should contain the filename associated
with the file being executed. The argv array must be terminated
by a NULL pointer. (Thus, in the new program, argv[argc] will be
NULL.)
In your case, the pathname should be "/bin/ls" and not "/bin/". If you want to pass any command line argument with the command, you can provide first argument them with argv vector index 1, second argument with index 2 and so on and terminate the argument vector with NULL.
A sample program which replace the current executable image with /bin/ls program and runs /bin/ls testfile:
#include <stdio.h>
#include <unistd.h>
int main (void) {
char *args[] = {"/bin/ls", "testfile", NULL};
// here you can call fork and then call execve in child process
execve(args[0], args, NULL);
return 0;
}
Output:
# ./a.out
testfile
I'm trying to execute this simple command ls -1 *.c using the execv() function.
#include<stdio.h>
#include<fcntl.h>
int main(int argc,char* argv[]){
char *arr[3]={"ls","-1","*.c"};
execv("/bin/ls",arr);
}
The output I'm getting is
ls: cannot access *.c: No such file or directory
There's a big problem in your code: execv can't tell how big the array you're passing it is. You absolutely need a terminating NULL element to mark the end:
char *arr[] = { "ls", "-1", "*.c", NULL };
OK, now that we have a valid execv invocation, we can deal with the ls error.
Calling execv like this is equivalent to running
'ls' '-1' '*.c'
on the command line (which would produce the same error).
When you do
ls -1 *.c
on the command line, ls never sees *.c because the shell expands wildcards and passes a list of matching filenames to ls.
If you want to replicate that in your C code, you have to do the same thing manually. See e.g. man glob for a function that does most of the work. Here's an adapted example from the man page that shows the general principle:
glob_t globbuf;
globbuf.gl_offs = 2;
glob("*.c", GLOB_DOOFFS, NULL, &globbuf);
globbuf.gl_pathv[0] = "ls";
globbuf.gl_pathv[1] = "-1";
execv("/bin/ls", globbuf.gl_pathv);
I need to see a concrete example of how to specify the environment for execve() in a c program. In my class, we are writing a program that will utilize both standard LINUX executables and our own executables. Thus, the environment searching PATH will have to contain tokens for both types of executables. I cannot find a good example of how to specify the environment (third argument) for execve() as every article seems to suggest we use execvp() or *clp() or *cl(), etc., instead.
In my project, we must use execve().
Right now, I'm just trying to get execve() to work for a basic "ls" command so that I can get it to work later for any and all executables.
Here is a snippet of my experiment code:
else if(strcmp(tokens[0], "1") == 0) {
char *args[] = {"ls", "-l", "-a", (char *)0};
char *env_args[] = {"/bin", (char*)0};
execve(args[0], args, env_args);
printf("ERROR\n");
}
Each time command "1" is entered in my shell, I see my error message. I suspect this is because of the way I am declaring env_args[].
Can someone show me a good example of how to implement execve() with a specified command searching environment?
here is the documentation on execve() function http://linux.die.net/man/2/execve
it says:
int execve(const char *filename, char *const argv[], char *const envp[]);
envp is an array of strings, conventionally of the form
key=value, which are passed as environment to the new program.
but in your program env_args does not look like key=value
So probably you should define env_args by the following way:
char *env_args[] = {"PATH=/bin", (char*)0};
or just
char *env_args[] = { (char*)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 am trying to use execve to run the ls command. Currently I'm running it with the following arguments:
execve(args[0], args, env_args)
//args looks like {"ls", "-l", "-a", NULL}
//env_args looks like {"PATH=/bin", "USER=me", NULL}
What I expected this to do was run the ls command using my new env_args meaning that it would look up ls in my PATH. However, this code actually doesn't do anything and when I run the code it just returns to my command prompt without output.
Using the same args[] I was using execvp and ls worked and searched my current path.
Can you tell me what I am doing wrong?
What I am trying to do is write my own shell program where I can create and export my own environment and have exec use the environment I have defined in a char**. Essentially I am writing my own functions to operate on env_args to add and remove vars and when I call exec i want to be able to call exec on {"ls", "-l", NULL} and have it look down my new environments path variable for a valid program called ls. I hope this explains what I am doing a little better. I don't think the extern environ var will work for me in this case.
The execve() function does not look at PATH; for that, you need execvp(). Your program was failing to execute ls, and apparently you don't report failures to execute a program after the execve(). Note that members of the exec*() family of functions only return on error.
You'd get the result you expected (more or less) if you ran the program with /bin as your current directory (because ./ls - aka ls - would then exist).
You need to provide the pathname of the executable in the first argument to execve(), after finding it using an appropriate PATH setting.
Or continue to use execvp(), but set the variable environ to your new environment. Note that environ is unique among POSIX global variables in that is it not declared in any header.
extern char **environ;
environ = env_args;
execvp(args[0], &args[0]);
You don't need to save the old value and restore it; you're in the child process and switching its environment won't affect the main program (shell).
This seems to work as I'd expect - and demonstrates that the original code behaves as I'd expect.
#include <stdio.h>
#include <unistd.h>
extern char **environ;
int main(void)
{
char *args[] = { "ls", "-l", "-a", NULL };
char *env_args[] = { "PATH=/bin", "USER=me", NULL };
execve(args[0], args, env_args);
fprintf(stderr, "Oops!\n");
environ = env_args;
execvp(args[0], &args[0]);
fprintf(stderr, "Oops again!\n");
return -1;
}
I get an 'Oops!' followed by the listing of my directory. When I create an executable ls in my current directory:
#!/bin/sh
echo "Haha!"
then I don't get the 'Oops!' and do get the 'Haha!'.