Using execvp with Input,Output and redirection - c

In a C program, let's say i wann use Exec functions for executing a given program, for example if i wanna just try ls -l i'll do something like
args[0]="ls";
args[1]="-l";
args[2]=NULL;
...
execvp("ls", args);
and it's all fine. Now what if i wanna also add the redirection to a file (or to stderr)?
I'm stuck, it's obvious that adding >log.txt as a 3rd entry in the array won't work, but I don't know how to proceed.
And also, what if I wanna pass some Input parameters? What if i wanna execute a GCC command like "gcc -o out in redirection>log.txt" ?
[update from comment:]
It's a C program that simulate a shell which can "run strings", string that contains a command, a list o parameters, input and a redirection.

Just set up your file descriptors as the exec-d process shall find them and then do the exec.
For that you need open, dup2 and close.
All functions in the exec-family just replace the current process with whatever one you say.

Run the command in a shell:
char * args[] = {
"sh",
"-c",
"ls -l >out.ls 2>err.ls <in.ls",
NULL
};
...
execvp(args[0], args);
perror("execvp() failed");

Related

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

execve not taking environment parameters

I want to write a program which executes the Linux ls command.
I really only want to type in ls and not /bin/ls, so I want to do this using execve (execvp is not an option).
I tried:
char *env[] = { "SHELL=/bin/bash",
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games",
"_=/usr/bin/env",
(char *)0 };
execve(parmList[0], parmList, env);
But it does not seem to work, ls is not recognized but /bin/ls is. Nothing is wrong with parmList because I tried this with execvp and it worked.
Any ideas?
If parmList[0] contains just ls, execve() won't find the ls command unless it is in the current directory.
With execve(), the value of PATH in the environment is immaterial — execve() does not use it. You'd use execvp() (or execvpe() if it is available) to execute the command on the current process's PATH.

execlp command doesn't take into account the asterisk wildcard

this little command :
execlp("/bin/echo", "echo", "*", ">", "toto", 0)
prints * > toto in the terminal, but I want it to print the result of echo * in the file toto.
The command : system("echo * > toto")  works well, but I want to use the execlp command, what I am doing wrong?
Thank you in advance.
The angle bracket ('>') redirection is shell specific.
You could do, for example:
execlp("/bin/sh", "/bin/sh", "-c", "/bin/echo * > toto", NULL);
Note that this invokes 2 specific behaviors that are shell related:
* wildcard: the asterisk wildcard will be expanded (by the shell, very important) to all files in current directory; and
> redirection: the stdout of the echo command will be redirected to file (or pipe) toto.
If you want to do the same kind of redirection in C (i.e. without resorting to executing the shell) you must:
// open the file
int fd = open("toto", "w");
// reassign your file descriptor to stdout (file descriptor 1):
dup2(fd, 1); // this will first close file descriptor, if already open
// optionally close the original file descriptor (as it were duplicated in fd 1 and is not needed anymore):
close(fd);
// finally substitute the running image for another one:
execlp("/bin/echo", "echo", "*" 0);
Note that you'll still get '*' written to the file.
Edit: the first argument to execlp is really the executable to run, file image that will substitute the currently running process. After this first argument comes the full argv array, which must include argv[0]. I've edited the code above to reflect this. Some programs use this argv[0] to change its personality (for example, busybox is a single executable that implements ls, echo, cat and many other unix command line utilities); that surely is the case with bash and whatever is linked from /bin/sh.

Can't get control back after execvp and wait()

I'm coding a small shell that must execute my commands that I parse.
f is a char** like this: [ls][-la]
p is the same, used like this: [wc]
So I tried to pipe ls -la in wc.
My probleme is that when I execute "ls -la | wc && date", which works well for the pipe, my minishell get closed and it doesn't execute "date". I used the wait function to wait for it to finish but doesn't do anything. Looks like it's stuck and exit just after the 2nd execvp.
My arrays ends well by NULL.
ls -la | wc is well executed but I get back to bash after this.
I've tried with execlp and execl but I think this is not the probleme considering that I need options of my first argument (ls + -la).
Could you help me please ?
Thanks in advance :)
All forms of exec never return; they replace the currently running image with the indicated executable. The key word here is "replace".
The only circumstance in which the statement following a call to exec* gets executed is if the exec fails (for example, if it cannot find the executable).

What does execvp actually do? [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Writing a shell - how to execute commands
I've been tasked with writing a shell in C. So far I understand that execvp will try to run the program in arg1 with arg2 as parameters. Now it seems that doing this
execvp ("ls", args); //assume args is {"ls", ">", "awd.txt"}
is not equivalent to typing this in the console
ls > awd.txt
I realize that I need to use freopen to achieve the same results but I'm curious what execvp is doing.
The exec family of functions are ultimately a system call. System calls go straight into the kernel and typically perform a very specific service that only the kernel can do.
Redirection, on the other hand, is a shell feature.
So, when one types ls > awd.txt at a shell, the shell first does a fork(2), then it closes standard output in the child, then it opens awd.txt on file descriptor one so that it's the new standard output.
Then, and only then, the shell will make an exec-family system call.
In your case you simply passed the strings > and awd.txt to the exec system call, and from there to ls. BTW, be sure you terminate your execvp arg array with a null pointer.
Note: As you can see, the redirection operators are never seen by the executed program. Before Unix, directing output to a file had to be done by every program based on an option. More trivia: most programs never know they were redirected, but ironically, ls does check to see if its output is a tty, and if so, it does the multi-column formatted output thing.
It's executing ls with 2 arguments: > and awd.txt. It is equivalent to running:
'ls' '>' 'awd.txt'
You can pass your command directly to the shell:
char * const args[] = { "sh", "-c", "ls > awd.txt", NULL};
execvp("/bin/sh", args);
But it doesn't seems like a good idea.

Resources