For a project, I'm supposed to pipe the output of a command to my C program (called execute), which will then execute that command.
For example, running this:
echo ls -lR /usr | ./execute, will take the output (ls -lR /usr) and pass it into my C program which will then execute ls -lR /usr.
According to the directions, I'm supposed to use execvpe() to do the actual execution of the program, however I can't find any documentation that makes sense, nor can I get it to work without getting these errors:
execute.c: In function ‘main’:
execute.c:98: warning: implicit declaration of function ‘getenv’
execute.c:98: warning: assignment makes pointer from integer without a cast
execute.c:106: warning: implicit declaration of function ‘execvpe’
My professor said that I have to #include <unistd.h>, and <stdio.h> which I did, parse the input to my program (which I did), and then do this:
int main(void) {
char *path;
path = getenv("PATH");
char *envp[] = {path, NULL};
// the initialized array below could change normally.
// below is just an example
char *tests = {"ls", "-lR", NULL};
int ret = execvpe("ls", tests, envp);
if(ret == -1) { printf("error\n"); }
return 0;
}
He then stated that execvpe should find the path correctly and execute everything. But no matter what I keep getting these warnings. Running the program and ignoring the warnings immediately seg faults. Does anyone know how execvpe works or how I can fix this?
This code should work, assuming your system has execvpe() at all:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(void) {
char *path = getenv("PATH");
char pathenv[strlen(path) + sizeof("PATH=")];
sprintf(pathenv, "PATH=%s", path);
char *envp[] = {pathenv, NULL};
char *tests[] = {"ls", "-lR", NULL};
execvpe(tests[0], tests, envp);
fprintf(stderr, "failed to execute \"%s\"\n", tests[0]);
return 1;
}
Updated to format PATH=$PATH in the environment.
It fixes the compilation error on tests, uses tests[0] as the command name to execvpe(); it reports the error on standard error; it includes the name of the command that was not executed; it returns a failure status (non-zero) when exiting; it notes that execvpe() only returns if it fails so it isn't necessary to test what its return status is. It does not include the system error message in the error message, but you could modify the code to include <errno.h> and <string.h> and use errno and strerror() to report that information too.
Related
Im confused of the use of the systemcall execve.
The second parameter should be a pointer to the arguments but it doesnt work, it does however execute correctly when I send the whole command(/bin/bash) + arg as a second parameter.
./test /bin/bash "echo 'this is a test' | wc -c"
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
int main(int argc, char *argv[]) {
execve(argv[1], &argv[1], NULL); ////works, but I dont understand how since the 2nd param is wrong.
return 0;
}
int main(int argc, char *argv[]) {
execve(argv[1], &argv[2], NULL); ////doenst work, it should since Im sending the correct aguments.
printf("hi");
return 0;
}
argv[0] contains the name of the file being executed. Therefore, execve also needs the file being executed as first element (index 0).
From the execve(2) man page:
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 other words, the arguments list that you pass in the second parameter includes the name of the executable itself (even though that is already specified in the first parameter). This is how executables are able to detect how they're being invoked (e.g. through a symlink) and show the correct name in their "help" output.
In execve man we can see that its prototype is:
int execve(const char *pathname, char *const argv[], char *const envp[]);
According to execve man (https://man7.org/linux/man-pages/man2/execve.2.html)
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 respect to these rules you're code should be:
#include <stdio.h>
#include <unistd.h>
int main ()
{
char *filepath = "/bin/echo";
char *argv[] = { filepath, "Hello World", NULL };
execve (filepath, argv, NULL);
return 0;
}
execve call executes a program from the caller. Just as any program in C, it requires argv[0] to be set to the name of the executable being executed. This is generally done automatically when executing from command line, but since you're using execve you have to do it yourself.
If argv_1[1] = <executable path for program_2>, when you call program_2 from program_1 with execve(argv_1[1], &argv_1[1], NULL), program_2 recieves an argv array such that argv_2[0] = argv_1[1], argv_2[1] = argv_1[2], and so on.
Notice how your second main does not follow this rule.
I was doing a research into the contents of another StackOverflow question and I thought it was a good time to brush up my knowledge of unix system calls.
While experimenting with execvp (WITHOUT fork on purpose) I ran into something that confuses me
I wrote 4 test programs
Program 1
#include <stdio.h>
int main() {
//printf("Doge\n");
execvp("ls");
printf("Foo\n");
return 0;
}
The program works as expected, the contents of the directory are printed and the Foo print statement is not
Program 2
However when I uncomment the first print statement and have the program be this
#include <stdio.h>
int main() {
printf("Doge\n");
execvp("ls");
printf("Foo\n");
return 0;
}
execvp returns a -1 and both print statements are issued. why?
Program 3
I vaguely remember having to use unistd.h when experimenting with unix system calls from college.
So I included it, but not execvp has a different signature and it needed some more args than just the name of the program. So I did this
#include <stdio.h>
#include <unistd.h>
int main() {
printf("Doge\n");
char *const parmList[] = {"ls", NULL};
execvp("ls", parmList);
printf("Foo\n");
return 0;
}
And this works. This has confused me. Why did exec work in the first program?
I also used This as a reference to the system calls.
Finally I wrote
Program 4
#include <stdio.h>
//#include <unistd.h>
int main() {
printf("Doge\n");
char *const parmList[] = {"ls", NULL};
execvp("ls", parmList);
printf("Foo\n");
return 0;
}
Which also works as expected.
Can someone explain what's going on?
With this snippet
#include <stdio.h>
int main() {
execvp("ls");
printf("Foo\n");
return 0;
}
you're invoking undefined behaviour. You're not providing the prototype for execvp which requires an argument list (null terminated) as a second parameter.
Using gcc without any warning option silently uses execvp as implicitly declared, and doesn't check parameters. It just calls the function. The function then looks for a second parameter and encounters... whatever is left of the call stack (or registers, depending on call conventions), that's why a previous printf call can change the behaviour.
Using gcc -Wall gives the following warning:
test.c:5:9: warning: implicit declaration of function 'execvp' [-Wimplicit-function-declaration]
execvp("ls");
Including the proper include (#include <unistd.h>) leads to:
test.c:6:9: error: too few arguments to function 'execvp'
execvp("ls");
^~~~~~
That's why you've got strange behaviour. Don't look further. Use execvp with 2 arguments, period. In your case "Program 3" is the way to go, and always set warning level to the maximum, if possible (gcc and clang: -Wall -Wextra -pedantic -Werror)
I have two files in the same directory.
directory/
| a.c
| b.c
a.c
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
pid_t pid;
int status;
int wret;
if ((pid = fork()) < 0)
printf("error");
else if(pid == 0)
{
printf("%s", argv[1]);
execv(argv[1], &argv[1]);
}
else
{
/* respawn */
if ((wret = wait(&status)) != -1)
execv(argv[1], &argv[1]);
}
return 0;
}
b.c is just a simple program that print "hello".
I want to run ./a b from the command line to make the a program call exexXX to execute the b program.
I don't understand why if I use execv I can write just ./a b in the command line, instead if I use execvp I have to write ./a ./b.
The man exec page is not clear because it reports
"The initial argument for these functions is the name of a file that
is to be executed."
Thanks
If the program name argument contains no slashes, the execvp() function looks for the program to execute in the directories listed on your PATH environment variable. If you don't have . (the current directory) on your PATH and you aren't in one of the directories listed on your path, a plain name like b will not be executed, even if b is in the current directory. If the name contains a slash, it can be relative (./b) or absolute (/home/someone/src/programs/b) and it will be interpreted as a file name to be executed without consulting the PATH environment variable.
By contrast, execv() treats a plain b in the program name argument as ./b — the name of the file in the current directory and executes it if it is present, and fails if it is located somewhere else.
At one time, there was a comment that asked:
Are you saying if you have an executable b in . and you do execv("b", b_args), it will get executed?
On a normal Unix box, yes.
Code b.c:
#include <stdio.h>
int main(void)
{
puts("Hello");
return 0;
}
Code a.c:
#include <stdio.h>
#include <unistd.h>
int main(void)
{
char *argv[] = { "b", 0 };
execv(argv[0], argv);
fprintf(stderr, "failed to execute '%s'\n", argv[0]);
return 1;
}
Running these:
$ (PATH=$(clnpath "$PATH" ".:$PWD"); echopath PATH; ./a)
/Users/jleffler/bin
/opt/informix/12.10.FC6/bin
/Users/jleffler/oss/bin
/Users/jleffler/oss/rcs/bin
/usr/local/mysql/bin
/opt/gcc/v7.3.0/bin
/Users/jleffler/perl/v5.24.0/bin
/usr/local/bin
/usr/bin
/bin
/opt/gnu/bin
/usr/sbin
/sbin
Hello
$
The clnpath script modifies the string provided as its first argument ("$PATH") by removing any occurrences of any of the directory names listed in its second path-like argument (".:$PWD") — it's how I edit my PATH on the fly when I need to. The echopath script echoes the directories on PATH (or any other path-like variable, or it will process the result of expanding a pathlike variable, such as "$PATH"), one per line — the output shows that neither . nor /Users/jleffler/soq (which is where I run the program) is on $PATH in the sub-shell. The ./a runs the code from a.c (it would not be executed without that ./ in front), which in turn runs the code from b.c, which produces the Hello. (If there is some system where this does not work, please identify it.)
I could also arrange for b.c to be:
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
puts("Hello");
const char *env = "PATH";
char *val = getenv(env);
if (val == 0)
val = "<nothing>";
printf("%s=%s\n", env, val);
return 0;
}
which would print the value of $PATH directly from the executable (to verify that neither . nor the value of the current working directory is listed).
I wrote the following code but I always get the output: "ERROR!" (the execv function not scheduled to return)
What am I doing wrong???
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <math.h>
#include <string.h>
#include <malloc.h>
#include "LineParser.h"
#define LOCATION_LEN 200
char* getL(void);
int main(int argc,char *argv[])
{
char *loc = getL();
char *args[] = {loc,"ls",NULL};
int i;
execv(args[0],args);
printf("ERROR!");
free(loc);
}
char* getL(void)
{
char *buff = (char**)malloc(sizeof(char)*LOCATION_LEN);
getcwd(buff,LOCATION_LEN);
return buff;
}
Read documentation of execv(3) and of execve(2) and of perror(3). At the very least, you should code
int main(int argc, char *argv[]) {
char *loc = getL();
char *args[] = { loc, "ls", NULL };
int i;
execv(args[0], args);
perror("execv");
free(loc);
}
You should compile with gcc -Wall -g then use the gdb debugger.
Your usage of execv is obviously wrong (you need a full path, e.g. "/bin/ls", and the order of arguments is wrong). You probably want exevcp(3) and you should in fact code at least:
char *args = { "ls", loc, NULL };
execvp("ls", args);
perror("execvp")
If you insist on using specifically execv(3) you could try
char *args = { "ls", loc, NULL };
execv("/bin/ls", args);
perror("execv")
I don't understand what your code is supposed to do. You might be interested by glob(7) & glob(3).
You probably should read Advanced Linux Programming. It seems that there are several concepts that you don't understand well enough. I guess that strace(1) could be useful to you (at least by running strace ls *.c to understand what is happening).
Maybe your getL is exactly what the GNU function get_current_dir_name(3) is doing, but then the (char**) cast inside it is grossly wrong. And you should better clear the buffer buff using memset(3) before calling getcwd(2) (and you should test against failure of ̀ mallocand ofgetcwd`)
Perhaps you want opendir(3), readdir(3), asprintf(3), stat(2); with all these, you could even avoid running ls
If you are coding some shell, you should strace some existing shell, and after having read all the references I am giving here, study the source code of free software shells like sash and GNU bash
You are not passing the correct arguments to execv. The first argument must be a path to the executable you wish to run but you are passing the path to the current working directory.
Update getL to return the full path to ls.
I have such a funny problem I thought I'd share with you.
I cornered it down to the most little program I could :
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
int cmd_left(char *name)
{
pid_t pid;
int f_d;
if ((pid = fork()) == -1)
{
perror("");
exit(1);
}
f_d = open(name);
printf("%d\n", f_d);
close(f_d);
}
int main(int ac, char **av, char **env)
{
char **dummy_env;
if (ac < 2)
return (0);
dummy_env = malloc(10);
cmd_left(av[1]);
}
Basically, if I remove the malloc, opening works just fine.
You just have to compile and give the program a (valid) file to see the magic.
open(2) takes at least two parameters. Since you are passing it only one argument, you are invoking Undefined Behavior. In this case, open() is just using some garbage as second argument.
You need #include <fcntl.h> to get a declaration for open() in scope, which would then tell you that you are not calling it with enough arguments:
int open(const char *filename, int flags, ...);
(The optional argument - singular - is the permissions for the file (mode_t perms) if you have O_CREAT amongst the options in the flags argument.)
The call to malloc() scribbles over enough stack to remove the zeroes on it initially, which leaves the 'extra arguments' to open() in a state where they are not zero and you run into problems.
Undefined behaviour - which you're invoking - can lead to any weird result.
Make sure you compile with at least 'gcc -Wall' and I recommend 'gcc -Wmissing-prototypes -Wstrict-prototypes -Wall -Wextra'.
The header file for open is missing and open expects at least a second parameter.
If you fix that it should be OK.