Get environment variables using C code - c

Here I wrote a C program which executes hi.sh file using system call.
Here I used . ./hi.sh so I want to execute this script in the same shell
and then try to get environment variable using getenv function, but here I am getting different output from what I expected.
The hi.sh file contains
export TEST=10
return
Means when I run this hi.sh file using system call, its export TEST sets the value to 10 in same shell.
After this, I am trying to get this variable value but its given NULL value.
And if I run this script manually from console like . ./hi.sh then it works fine and I get 10 value of TEST using getenv("TEST") function.
Code:
#include <stdio.h>
int main()
{
system(". ./hi.sh");
char *errcode;
char *env = "TEST";
int errCode;
errcode = getenv(env);
printf("Value is = %s\n",errcode);
if (errcode != NULL) {
errCode =atoi(errcode);
printf("Value is = %d\n",errCode);
}
}
output :
Value is = (null)
How can I export TEST variable in program shell? If system() executes commands in different shell then how can I use C program code to get an environment variable which is exported by the shell invoked via a system() call?

The child process cannot directly set the parent process's environment. The approach using system() and getenv() is doomed to fail, therefore.
If you are trying to import selected variables set by the script hi.sh, then you have a couple of choices. Either you can read the script hi.sh and work out what it would set them to (rather hard), or you can run the script and have the code you run report back on the environment variables of interest.
Suppose that hi.sh sets $ENV1 and $ENV2. You can use popen() to get the values back to your program, and setenv() to set your program's environment. In outline:
FILE *fp = popen(". ./hi.sh; echo ENV1=$ENV1; echo ENV2=$ENV2", "r");
while (fgets(buffer, sizeof(buffer), fp) != 0)
{
...split the buffer into env_name, env_value...
setenv(env_name, env_value);
}
pclose(fp);
Note that I included the variable name in the echoed information; this simplifies life. If your list of variables gets unwieldy, maybe you run ". ./hi.sh; env" to get the entire environment, and then read each line and work out from your built-in list whether its a variable setting you want to use or not. Or you can simply set your entire environment again, if that pleases you. You should check that the setenv() function succeeded (it returns zero when it does succeed). You should also check that the popen() was successful (fp != 0). In this context, you probably can use strtok() to look for the = separating the variable name from the value; it tramples a null byte over the =, giving you a null terminated name and a null terminated value:
char *env_name = strtok(buffer, "=");
char *env_value = buffer + strlen(env_name) + 1;
if (setenv(env_name, env_value) != 0)
...report trouble...

As usual, the man page does explain this, but you need to read it very carefully.
DESCRIPTION
system() executes a command specified in command by calling /bin/sh -c
command, and returns after the command has been completed. During exe‐
cution of the command, SIGCHLD will be blocked, and SIGINT and SIGQUIT
will be ignored.
In other words, system() first starts /bin/sh, and then has /bin/sh start whatever command you want to execute.
So what happens here is that the TEST variable is exported to the /bin/sh shell the system() call implicitly started, but not to the program which called system().

Another possible solution is to have your program exec itself through another shell. That shell replace the running program, then read the environment variables and then replace shell with a new copy of the program. You need to tell the new copy that it has already done an exec or it will just loop doing it over and over. You could look for the environment variable, or pass a command-line flag.
An untested example:
execl("/bin/sh", "-c", ". ./hi.sh; exec ./a.out --envset", NULL);
You would need to replace a.out with whatever the real program name is. You would probably want to extract it from argv[0] and also pass the rest of the argv array. But you have to reformat the arguments to work as shell arguments, so they need to be quoted as necessary, etc.

You can either set the environment variable in your own process using setenv() (which system() then silently passes on to child processes, or explicitly pass the variables by using fork() and execve() to run the shell script.

Related

No such file or directory: Running Ruby as an interpreter script for execv()

I'm writing a C program that passes data to a Ruby script. The snippet of code in main.c is below. Whenever I run, I receive an error from execv() that there is no such file or directory. I guessed that I didn't add my file to the PATH but I did and I still ended up with the same outcome.
int temp = execv("#!/usr/bin/ruby xmlReader.rb", list);
if (spec == -1)
printf("%s\n", strerror(errno));
The exec*() family of system calls are very low level, they replace the executing program with the program mentioned as the first argument, so there's no return value unless there's an error. Upon success, your program is entirely gone, overlaid with the new program. It's a one-way program-execution chain.
It doesn't know how to run command lines, and exec() is usually usually is done after a fork() operation. You almost certainly don't want to do this.
What you probably want is int temp = system("ruby xmlreader.rb"); which will run the command line given in a subprocess (ultimately using fork and exec, though this aspect is invisible to you), wait for it to complete, then return the exit value to you.

exec function only running some commands, won't run echo

I'm trying to run command line arguments (specifically echo) through the exec family of functions. I can get the execv function to run if I write my own executable and run it, but if I try to run touch or echo it returns -1
#include <stdio.h>
#include <unistd.h> // exec functions
#include <sys/types.h> // pid_t
#include <sys/wait.h>
#define HIGH 1
#define LOW 0
int digitalWrite(int pin, short type) {
pid_t pid = fork();
if (pid == 0) {
printf("pid == %i\n", pid);
if (type == HIGH) {
char* args[] = {"echo", "1", ">", "/sys/class/gpio/gpio67/value", NULL};
int val = execv(args[0], args);
printf("ran function execl, %i\n", val);
} else {
printf("Unable to do anything but set pin to HIGH\n");
}
} else if (pid < 0) { // pid < 0
printf("fork failed\n");
}
wait(NULL);
}
int main() {
printf("Starting digitalWrite\n");
digitalWrite(0, HIGH);
printf("Completed digitalWrite()\n");
return 0;
}
Just for context here's my build:
$ gcc wiringbeagle.c
$ ./a.out
Starting digitalWrite
pid == 0
ran function execl, -1
Completed digitalWrite()
Completed digitalWrite()
$ ls
a.out wiringbeagle.c
The command echo 1 > /sys/class/gpio/gpio67/value runs fine in the terminal on it's own, and if I create a local file (i.e. touch tmpfile.txt) and try to run echo hi > tmpfile.txt it runs as expected in my command line but doesn't run in the program.
I must be not understanding something with execv, and any assistance would be greatly appreciated!
The first argument to execv is the file to be executed. Unlike your shell, execv does not search through the directories indicated by the PATH environment variable, so you need to give it the complete path to the executable. Unless there is an executable file called echo in your current working directory, execv("echo",...) will fail with a "file not found" error. (Use perror to get better error messages).
If you want to search for the executable as the shell does, use execvp. But note that your shell probably executes echo as a built-in command, so it won't be the same echo as your shell uses. In this case, that's fine.
Once you fix that, you will encounter a different problem. Since you are just invoking a command-line utility with arguments, rather than using a shell, the argument ">" is just an argument. It is the shell which handles redirections (as well as pipes, quoting, and a bunch of other useful stuff). So all you will accomplish is to send the three arguments to stdout.
You could use the system function to execute a command using the shell, or you could set up the redirection yourself by freopening stdout in your child before doing the execvp.
You can get quite a lot of information about system interfaces using the man command. For example, to learn what freopen does, use man freopen. You can also read manpages on the internet, eg. http://man7.org/linux/man-pages/man3/freopen.3.html, but the documentation on your own system is right there, and also applies to the actual version of the software installed on your system (assuming you installed the documentation).
I'm not entirely certain why you're even using the exec family to run external programs in this case. The C standard library provides perfectly adequate file I/O stuff.
For example, you can simply fopen, fprintf, and fclose the file without ever starting another external process to do that work for you:
int bytesWrit = 0;
FILE *gpioHndl = fopen("/sys/class/gpio/gpio67/value");
if (gpioHndl != NULL) {
bytesWrit = fprintf(gpioHndl, "1\n");
fclose(gpioHndl);
}
if (bytesWrit != 2) {
HandleError();
}
This is probably the preferred way to do what you want, which is simply writing a fixed value to a file.
In terms of why your execv call isn't working (though it's totally irrelevant if you take my advice above), there are several things you need to be aware of.
First, while some commands are actually files on the disk that you can exec, others may be internal bash commands(a). On my system, for example:
pax:~$ type ftp
ftp is /usr/bin/ftp
pax:~$ type echo
echo is a shell builtin
One way to solve this is to run the actual bash executable (which, being an on-disk command, can be done via exec), telling it to run its internal echo command. From the command line:
pax:~$ bash -c 'echo xyzzy'
xyzzy
Second, if want to use redirection, this is normally something that's done by the shell, not the exec calls or individual executables.
Trying to do redirection via the exec family will generally only result in the >somefile being passed as a literal parameter to the executable (in the argv array), not being used to attach standard output to a file. In other words, it won't work unless the executable specifically handles redirection, which is rare.
So that means you will have to run the shell with redirection and have it run the executable after performing those redirections, even if the command is not an internal one.
Thirdly, if you want the path searched for your executable, execvp is the call you want, not execv (the latter just uses the file you explicitly provide, either relative from the current working directory or an absolute path like /bin/ls). So, in your case, you should either:
use execvp to search the path; or
fully specify the path with execv.
(a) The echo command, while it is bash-internal may also be provided as a separate executable (I believe Posix requires this), so this may not be an issue here. It may be an issue if you expect them to act exactly the same in terms of more esoteric arguments :-)
execv() does not search the PATH environment variable in order to find an executable file. Per the Linux execv() man page (bolded text added):
...
Special semantics for execlp(), execvp(), and execvpe()
The execlp(), execvp(), and execvpe() functions duplicate the actions
of the shell in searching for an executable file if the specified
filename does not contain a slash (/) character. ...
...
So, those three will search the PATH environment variable if the filename passed does not contain a / character.
You're using execv(), which is not one of those three. Therefore, execv() will not search the PATH environment variable.
Since your current working directory doesn't contain an executable file called echo, execv() fails.
You need to use execvp() per the man page.
You need to use absolute path as first parameter in execv
Then, the correct is:
char* args[] = {"/bin/echo","echo", "1", ">", "/sys/class/gpio/gpio67/value", NULL};
But to run what you want (put value '1' in file '/sys/class/gpio/gpio67/value'), you need to use command sh:
char* args[] = {"/bin/sh", "sh","-c", "/bin/echo 1 > /sys/class/gpio/gpio67/value", NULL};
The parameter to "sh -c" is a string. Then, you need to put all command together as a string

In C, how to create and send shell variable?

I would like to get the following result, let me explain, we have a file "test.sh", in which we call a command written in c (mycmd.c), and we assign it an argument. We want it to create a variable whose name is the argument passed and then return the result in the "test.sh" file.
Example :
In the file test.sh, we call our command, we assign as argument the name of the variable to create, then we display the extracted result with an echo.
#!/bin/bash
./mycmd myvar
echo "OK : $myvar"
In the mycmd.c file, we create a shell variable named myvar (the passed argument), then we assign a value to this variable (myvar = foo), and then we return it to the script "test.sh", who returns so "OK : foo".
It's possible ? Thanks.
What you request cannot be done. Every program you run, including shells and subshells, gets its own environment. By default, the contents of that environment is a copy of that of its parent process, but it is separate and independent. A process can modify only its own environment, not that of any other process.
I was going to suggest you use setenv, but after some thinking and testing, it is in fact not possible to do what you want with an arbitrary compiled program. Process environments are protected, and your program gets a new process and therefore a new environment when it runs.
The best way to do this would be to have your program output some shell commands to run. So if your program were the following
#include <stdlib.h>
#include <stdio.h>
int main() {
printf("export MYENV=helloworld");
return 0;
}
And you invoked it as $(./myprogram), the result of echo $MYENV would be helloworld. Tested on Bash with Arch Linux. I know this is not exactly what you want, and may not work for your purposes, but it is the best I know.

Best way in C to test whether `foo` is executable at the command line?

In C, what is the best way to find out if foo is available at the command line to execute on the host? If I were at the bash command line, I'd run type foo. In C, I could do type foo as a system call and check the exit status. But it's a good idea to avoid system calls when possible, right? Is there a better way?
My program may have to do a system command that eventually would run foo inside a shell script. (It's someone else's world and they supposedly use foo.) But if it can tell at the start that foo would be unavailable, it can avoid doing a lot of unnecessary computation because no foo means failure is certain.
type (and its cousin, which) are commands provided by most Unix-like OS's and also implemented by most shells as an intrinsic.
You could simply invoke those commands by starting a child process from your program, and then read their output. If you want to rely on the behavior of a shell's version of the command, then you must start a child process that launches that shell, then command it to run type or which. If you'd rather not use child processes, then you must re-implement their logic in your program.
Using the FreeBSD implementation of which as a guide, we can see the basic steps for doing this:
Read the value of the $PATH environment variable.
Split apart $PATH into the various directories that it contains.
Test to see if the target file/program exists at one of the various sub directories in path.
And in code:
Read $PATH from the env:
if ((p = getenv("PATH")) == NULL)
exit(EXIT_FAILURE);
Call print_matches with one of the arguments to which, providing the entire value of the $PATH:
while (argc > 0) {
memcpy(path, p, pathlen);
if (strlen(*argv) >= FILENAME_MAX ||
print_matches(path, *argv) == -1)
status = EXIT_FAILURE;
...
In print_matches, break apart the $PATH variable by splitting on the : character:
while ((d = strsep(&path, ":")) != NULL) {
And for each directory, concatenate the target program to the directory (checking to make sure the string doesn't become too big):
if (snprintf(candidate, sizeof(candidate), "%s/%s", d,
filename) >= (int)sizeof(candidate))
continue;
And then test to see if that file exists:
if (is_there(candidate)) {

UNIX run program within another program

I am trying to execute a program from within a C program (inside UNIX).
I have been given an executable ( the program requires a string input during execution and writes that input to another file called sample ) called exec and I want to execute it in program.c, but giving the string input through indirection.
For that I created a file as follows:
% vim input
I wrote the following inside the input file
content
Now in program.c,
#include<unistd.h>
int main()
{
const char* command = "./exec < input";
execvp(command, NULL);
return 0;
}
When I run the program, the content is not entered into the sample file.
But when I run it without indirection, i.e.
const char* command = "./exec";
then it works, and input entered in saved in sample file.
Can someone please tell what am I doing wrong in the indirection syntax.
Thanks.
The syntax you are using is supposed to be interpreted by a shell like bash, csh, ksh, etc.
The system call execvp only expects the path to the executable and a number of arguments, the shell is not invoked there.
To perform redirection in this manner, you'll have to use the dup2(2) system call before calling execvp:
int fd = open("input", O_RDONLY);
/* redirect standard input to the opened file */
dup2(fd, 0);
execvp("/path/to/exec", ...);
Of course, you'll need some additional error checking in a real-world program.
You can't do redirection like that with execvp. Use system() or start getting friendly with dup() and friends. You might google 'implementing redirection'.. you'll likely turn up plenty of examples of how shells (for example) handle this problem.
The exec(3) family of functions does not know anything about input redirection or parsing command lines: it tries to execute exactly the executable you give it. It's trying to search for an executable file with the name "./exec < input", which unsurprisingly does not exist.
One solution would be to use the system(3) function instead of exec. system invokes the user's shell (such as /bin/bash), which is capable of parsing the command line and doing appropriate redirections. But, system() is not as versatile as exec, so it may or may not be suitable for your needs.
The better solution is to do the input redirection yourself. What you need to do us use open(3) to open the file and dup2(3) to duplicate the file descriptor onto file descriptor 0 (standard input), and then exec the executable.

Resources