We are having a lot of trouble interpreting our teacher. We asked for clarification and got the following back from him
For execve, send it a environment you setup with your exported variables and create a builtin command to spawn a subshell of /bin/bash, that way you can see your exported variables using env.
(He is talking about creating our own environment vars here.)
Yes create your own. You can start by copying environ when your shell starts and add only exported variables
This is related to the following post on Stack Overflow by me (reading this other post will help you understand what I am trying to do):
using a new path with execve to run ls command
We are just very confused about this. One more time I will explain what we are trying to do now. Similar to how your Linux shell does this, we need to write our own program that can set environment variables like PATH and USER and whatever other vars the user wants to define.
An example of how you would call this would be (inside your program at its prompt):
mysetenv dog spike
which would create an environment variable looking like "dog=spike"
More importantly, we need to be able to set our own PATH variable and send it to an exec command. This is the confusing part because, based on all of our questions, we don't understand what we are supposed to do.
It is actually very simple. You already know that your arguments are a list of char *, terminated by a NULL pointer. Similarly, the environment is simply a list of char *, terminated by a NULL pointer. Conventionally, the values in the list take the form VARNAME=var-value, though you can pass other formats if you wish.
So, to take a simple case:
#include <unistd.h>
#include <stdio.h>
int main(void)
{
char *argv[] = { "/bin/sh", "-c", "env", 0 };
char *envp[] =
{
"HOME=/",
"PATH=/bin:/usr/bin",
"TZ=UTC0",
"USER=beelzebub",
"LOGNAME=tarzan",
0
};
execve(argv[0], &argv[0], envp);
fprintf(stderr, "Oops!\n");
return -1;
}
In this example, the program will run /bin/sh with arguments -c and env, which means that the shell will run the env program found on its current PATH. The environment here is set to contain 5 values in the orthodox format. If you change env to date (or env; date), you will see the effect of the TZ setting, for example. When I run that on my MacOS X machine, the output is:
USER=beelzebub
PATH=/bin:/usr/bin
PWD=/Users/jleffler/tmp/soq
TZ=UTC0
SHLVL=1
HOME=/
LOGNAME=tarzan
_=/usr/bin/env
The shell has added environment variables SHLVL, _ and PWD to the ones I set explicitly in the execve() call.
You can also do fancier things, such as copy in some of the other environment variables from your genuine environment where they do not conflict with the ones you want to set explicitly. You can also play games like having two values for a single variable in the environment - which one takes effect? And you can play games with variable names that contain spaces (the shell doesn't like that much), or entries that do not match the 'varname=value' notation at all (no equals sign).
I'm a little late to the party here, but if you want to preserve the old environment variables as well as creating your own, use setenv, and then pass environ to execve().
setenv("dog", "spike", 1);
extern char** environ;
execve(argv[0], argv, environ);
environ is a variable declared in unistd.h, and it keeps track of the environment variables during this running process.
setenv() and putenv() modify environ, so when you pass it over execve(), the environment variables will be just as you'd expect.
The code from Jonathan Leffler works great, except if you want to change the PWD (working directory) variable.
What I did, in order to change the working directory, was to put a chdir(..) before execve(..) and call:
chdir("/foo/bar");
execve(argv[0], &argv[0], envp);
Related
My very simple C program just hangs and I don’t know why.
I am trying to make a simple executable to handle multiple monotonous actions for me every time I start a new programming session.
So I decided with something simple (below) yet every time I run it, the app just hangs, never returns. So I have to Ctrl-C out of it. I have added printf commands to see if it goes anywhere, but those never appear.
My build command returns no error messages:
gcc -o tail tail.c
Just curious what I am missing.
#include <stdio.h>
#include <unistd.h>
int main() {
chdir("\\var\\www");
return 0;
}
There are at least two problems with the source code:
It is unlikely that you have a sub-directory called \var\www in your current directory — Ubuntu uses / and not \ for path separators.
Even if there was a sub-directory with the right name, your program would change directory to it but that wouldn't affect the calling program.
You should check the return value from chdir() — at minimum:
if (chdir("/var/www") != 0)
{
perror("chdir");
exit(EXIT_FAILURE);
}
And, as Max pointed out, calling your program by the name of a well-known utility such as tail is likely to lead to confusion. Use a different name.
Incidentally, don't use test as a program name either. That, too, will lead to confusion as it is a shell built-in as well as an executable in either /bin or /usr/bin. There is also a program /bin/cd or /usr/bin/cd on your machine — it will check that it can change directory, but won't affect the current directory of your shell. You have to invoke it explicitly by the full pathname to get it to run at all because cd is another shell built-in.
Two things:
First, that's not what Linux paths look like
Second, check the return value from chdir()
ie
if (chdir("/var/www") != 0)
printf("failed to change directory");
Finally, the effect of chdir() lasts for the duration of the program. It will not change the current directory of your shell once this program finishes.
The other answers adequately cover the issues in your C code. However, the reason you are seeing it hang is because you chose the name tail for your program.
In Linux, tail is a command in /usr/bin in most setups, and if you just type tail at the command line, the shell searches the $PATH first, and runs this. Without any parameters, it waits for input on its stdin. You can end it by pressing control-d to mark the end of file.
You can bypass the $PATH lookup by typing ./tail instead.
$ tail
[system tail]
$ ./tail
[tail in your current directory]
It is a good idea to use ./ as a habit, but you can also avoid confusion by not naming your program the same as common commands. Another name to avoid is test which is a shell built-in for testing various aspects of files, but appears to do nothing as it reports results in its system return code.
This is similar to how to set environment variable when execle executes bash?
I am trying to use execle() in C to perform /bin/login and pass the environment variable to target shell.
And can not make it work.
I have tried by passing environment as described in the man page, as a NULL terminated array of VAR=VAL strings.
Also I tried with putenv() before the call.
For example:
const char *env[] = { "MYVAR=myval", (char *)0 };
putenv("MYVAR=myval");
execle("/bin/login", "login", "-p", "-f", user, (char *)0, env);
After successful login I was expecting to see the MYVAR as an environment variable, but only have variables, like USER, HOME, SHELL, PATH etc.
I have tried with BusyBox login and with tinylogin ... same result.
Any help is highly appreciated.
Works for me. Are you sure your execle() is succeeding? For example, on BSD systems (including macOS) the correct path is /usr/bin/login. You should add a fprintf(stderr, "execle() failed with errno %d\n", errno); after the execle() call.
Are you sure the BusyBox login command on your system supports those options?
Also, please use NULL rather than (char *)0.
Jonathan and Kurtis, thank you very much for your help and comments.
I have found out that there is no problem with env when using "-p" and when
user shell is /bin/sh.
But when I tried using "-p" and user shell was klish (CLI utility) there were
some errors in sourcing some .sh files.
So, I looked into BusyBox sources and in su.c it executes clearenv() if "-p"
is not used.
I have fixed that by passing it around and manually adding it into the env.
Since this is not the best way to do it, I will have to do some more research
about my klish sourcing.
Cheers.
Thanks again.
I am currently writing my own terminal in C.
I found out, that there is multiple variants of the exec() methode, that i can use.
Its simple occurance lead me to use execv():
int main(int argc , char* argv[]){
char* dir = getcwd(NULL, 0);
char* command[] = {"echo", "Hello", "World", "!!!", NULL};
execv(dir, command);
}
From my understanding this should work. It is compiling, but nothing happens.
The path argument to execv is supposed to be the path specification to the executable file you want to run, not just a directory as returned by getcwd. From the manpage:
The initial argument for these functions is the pathname of a file which is to be executed.
In other words, you're looking for something like:
execv ("/bin/echo", command);
The code you currently have is trying to run your current directory, something that's unlikely to end well, and something you may have noticed if you checked the return value from execv along with errno: nudge, nudge, wink, wink :-)
In terms of what to do for other programs, you simply substitute their full path name for /bin/echo.
You should also be aware that exec is a family of functions, each with slight variations.
Some allow environments to be passed, some automatically search the path for your executable (depending on the name given), and some use variable argument lists rather than arrays. If you want to use the automatic path searching, you would look into execvp rather than execv, then you don't have to worry about where the executable file is located.
I've been following tutorials online on C coding, and the code is using the Apache APR library.
It uses the apr_proc_t structure to execute an external app.
I'm confused about this function, could someone explain what this function means:
apr_status_t apr_procattr_cmdtype_set ( apr_procattr_t * attr,
apr_cmdtype_e cmd
)
Set what type of command the child process will call.
Parameters:
attr The procattr we care about.
cmd The type of command. One of:
APR_SHELLCMD -- Anything that the shell can handle
APR_PROGRAM -- Executable program (default)
APR_PROGRAM_ENV -- Executable program, copy environment
APR_PROGRAM_PATH -- Executable program on PATH, copy env
The apr_procattr_cmdtype_set function is used to tell APR how you want to execute the external command, it probably just sets an internal flag and does a bit of bookkeeping.
Let us look at the enum apr_cmdtype_e:
APR_SHELLCMD
use the shell to invoke the program
APR_PROGRAM
invoke the program directly, no copied env
APR_PROGRAM_ENV
invoke the program, replicating our environment
APR_PROGRAM_PATH
find program on PATH, use our environment
APR_SHELLCMD_ENV
use the shell to invoke the program, replicating our environment
The first and last options (APR_SHELLCMD and APR_SHELLCMD_ENV) pretty much say "use a portable version of system" (with or without copying the current environment variables to the new process). The others are just variations on the Unix fork/exec pair with the flag choosing which of the exec family of functions to use.
hello every one I want to ask that I am making a program in which i have to run shell script using c program. up till now i have separated the arguments. and i have searched that exec should be use to run shell scripts
but i am totally confused as there are many variants of exec and by reading the man pages i am unable to find which is best suitable
Also in some exec function first arg is
path
and some have
pointer to file
what is the difference and what should i write in place of it.kindly guide me
thanks
Running a shell script from a C program is usually done using
#include <stdlib.h>
int system (char *s);
where s is a pointer to the pathname of the script, e.g.
int rc = system ("/home/username/bin/somescript.sh");
If you need the stdout of the script, look at the popen man page.
#include <stdio.h>
#include <stdlib.h>
#define SHELLSCRIPT "\
for ((i=0 ; i < 10 ; i++))\n\
do\n\
echo \"Count: $i\"\n\
done\n\
"
int main(void)
{
puts("Will execute sh with the following script:");
puts(SHELLSCRIPT);
puts("Starting now:");
system(SHELLSCRIPT);
return 0;
}
Reference:
http://www.unix.com/programming/216190-putting-bash-script-c-program.html
All exec* library functions are ultimately convenience wrappers over the execve() system call. Just use the one that you find more convenient.
The ones that end in p (execlp(), execvp()) use the $PATH environment variable to find the program to run. For the others you need to use the full path as the first argument.
The ones ending in e (execle(), execve()) allow you to define the environment (using the last argument). This way you avoid potential problems with $PATH, $IFS and other dangerous environment variables.
The ones wih an v in its name take an array to specify arguments to the program to run, while the ones with an l take the arguments to the program to run as variable arguments, ending in (char *)NULL. As an example, execle() is very convenient to construct a fixed invocation, while execv* allow for a number of arguments that varies programatically.