I am trying to use execlp to call my program inside of my program (for a project) and am having trouble. I have created a sample project which should count down from n to 0 (essentially run n times). Each time I run it, I get the first decrement and then I get a seg fault. Please let me know what I am doing wrong here.
P.S Fairly new to System calls in C, so explain thoroughly if possible. Thanks in advance!
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <errno.h>
#include <time.h>
int main(int argc, char **argv)
{
int n = atoi(argv[1]);
char newParameters[2];
sprintf(newParameters, "%d", n - 1);
if (n != 0)
{
execlp("./tmp", newParameters, (char *)NULL);
}
printf("The program has finished.\n");
return 0;
}
The C program is called tmp.
From the execlp manual:
The initial argument for these functions is the name of a file that is to be executed.
The const char *arg and subsequent ellipses in the execl(), execlp(), and execle() functions can be thought of as arg0, arg1, ..., argn. Together they describe a list of one or more pointers to null-terminated strings that represent the argument list available to the executed program. The first argument, by convention, should point to the filename associated with the file being executed.
That is, the first argument to execlp is the executable file to run. The second parameter corresponds to argv[0] passed to main. The third parameter is argv[1] and so on. So the execlp in this case needs to be:
execlp("./tmp", "./tmp", newParameters, NULL);
Or better still use argv[0] rather than a hard coded executable name so that it can work no matter how the program is linked:
execlp(argv[0], argv[0], newParameters, NULL);
Other things to note:
newParameters can only hold a single character string (including NUL terminator). So any command line with a number greater than 9 will result in undefined behaviour.
Other good practices: Always check argc before using argv values and always check the return value of all function calls, specifically execlp in this case.
execlp(2) cannot be used to run a program recursively, because no new process is created. The current context (process address space, data, etc.) is loaded (overwritten) with the new mapping to execute the same program again, and the data segment, the stack and memory segments are detached and trashed to create the new ones. When this instance finally exit(2)s, there's no process backed up to return to. To create a new process you need to use fork(2), not execlp(2).
Related
I have issues creating simple C program which takes arguments from command line, the last argument is path to the file. Program runs cat command on given file, and then runs tr on the result of cat. Tr gets arguments from command line(other than the last argument). I am getting errors:
Missing operand.
write error: Broken Pipe.
I am not sure where the mistake is...
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define WRITE_END 1
#define READ_END 0
int main(int argc, char* argv[]){
if(argc < 2){
printf("\nPROVIDE AN ARGUMENT\n");
return 1;
}
const char * file = argv[argc - 1];
char ** args = calloc(argc - 2, sizeof(char*));
for( int i = 1; i<argc-2; i++){
args[i - 1 ] = argv[i];
}
int fd[2];
pipe(fd);
pid_t child;
if((child = fork()) == -1)return 2;
if(child == 0){
dup2(fd[WRITE_END], STDOUT_FILENO);
close(fd[READ_END]);
close(fd[WRITE_END]);
execlp("cat", "cat", file, (char*)NULL);
exit(1);
}
else{
dup2(fd[READ_END], STDIN_FILENO);
close(fd[WRITE_END]);
close(fd[READ_END]);
execlp("tr", "tr", *args, (char*)NULL);
exit(1);
}
close(fd[0]);
close(fd[1]);
wait(0);
wait(0);
return 0;
}
There are a few problems here that are keeping you from getting this to work. First, as mentioned by Nate Eldredge in a comment, there are problems with the allocation and copying of the all-but-last arguments to variable args. Second, your use of execlp has a slight problem in that the arguments should include an extra argument corresponding to the name of the program run (not the same as the file opened as the executable, lots of people get confused about this point). Third, as also mentioned by Nate, you need to call execvp in the branch of the if-else corresponding to the parent process (the "else" branch). Its second argument will need to be an array of pointers to character, the last of which is NULL.
So taking these one at a time. First, you need to allocate argc slots for args to use it in something like the way you intend:
char ** args = calloc(argc, sizeof(char*));
memcpy(args, argv, sizeof(char*)*(argc -1));
The first line allocates an array of character pointers the same size as the arg. list. The second line copies all but the last pointer in argv to the corresponding location in args and leaves the last one as NULL (calloc initialized the storage for it to be zero, and you need the last pointer in args to be a null pointer if you're going to pass it to execvp, which you will). Note that you're not duplicating all of the storage under argv, just the pointers in the first dimension (remember: argv[0] is a pointer and argv[0][0] is the first character in the program name).
Note that your use of close and dup was fine. I don't know why anyone objected to that unless they forgot that allocating a file descriptor always takes the lowest-numbered descriptor that is unused. That's about the most important thing about descriptor tables as originally used in UNIX.
Next, the call to execlp that overlays the child process created by fork with "cat" is missing an argument. It should be:
execlp("cat", "cat", file, (char*)NULL);
That extra "cat" in there is the value cat will receive when it enters main() as argv[0]. You're probably noticing that this looks like you could lie about the name of the program you're running with the exec__ functions, and you can (but you can't completely hide having done it).
Finally, that second execlp call. You can't pass arguments through as if they were typed on the command line, in one big string: exec in any form doesn't use a shell to invoke the other program and it's not going to parse the command line for you. In addition, the way you were (apparently, if I've read your intent correctly) trying to concatenate the argument strings was also not right (see above comments about args allocation and the memcpy call). You have to break out individual arguments and pass them to it. So if you have an array of pointer to character and the last one is NULL, like you'll have in args after the changes I indicated for allocating and copying data, then you can just pass args to execvp:
execvp("tr", args);
These aren't huge errors and a lot of people make these kinds of mistakes when starting out with manipulating the argument list and using the fork and exec functions. A lot of people make mistakes trying to use a pipe between parent and child processes but you seem to have gotten that part right.
One last thing: the lines downstream in execution from the exec__ calls only get executed if there's an error performing the actual replacement of the running program with the new one. Errors on the command line of "cat" or "tr", for example, won't cause exec__ to fail. Errors like lack of permission to execute the file given as the first argument or absence of the file will cause the exec__ functions to fail. Unless exec returns an error, nothing downstream of the exec call is executed in the process in which it is executed (a successful exec never returns).
Problem statement:-
How to pass arguments to a program for execution in a new Xterm/Gnome window which will be calling through execlp.
A little elaborate explanation:-(oxymoron eh?)
Consider the following program which will take a string as argument and display it
//output.c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
if(argc<2)
{
printf("insufficient parameters\n");
exit(1);
}
printf("%s",argv[1]);
sleep(10);
return 0;
}
And another program,client.cwhich, during the course of its execution is required to call output.c and make it display in a new xterm/gnome-terminal window.
//client.c
int main()
{
char buf[25]="Test String";//as argument for program to be called
int pid_child=fork();
if(pid_child==-1)
{
printf("Fork Failed. Exiting");
exit(1);
}
if(pid_child==0)
{
execlp("/usr/bin/xterm","-e","./output",buf,NULL);
}
int status=0;
while(wait(&status)!=-1);
}
The line of contention here is
execlp("/usr/bin/xterm","-e","./output",buf,NULL); //With string `buf` as argument for `output`.
Result:-Does not run
Error
-e: Explicit shell already was /~/cs60/directory/./output
-e: bad command line option "Test String"
execlp("/usr/bin/xterm","-e","./output",NULL);//Without passing variable `buf`
Result:- a) New Xterm window opens. b) output terminates with Insufficient parameters (as expected).
The manpage clearly states for Xterm:
-e program [ arguments ... ]
This option specifies the program (and its command line arguments) to be run in the xterm window.
It works perfectly fine when I run it from a terminal (as a script). But how can I achieve this through C.
Any help will be highly appreciated
You need to understand how execlp() works.
You need to add a second argument to execlp with the command name ("xterm").
execlp("/usr/bin/xterm", "xterm", "-e", "./output", buf, NULL);
Also, your output program may want to do a fflush (so you see the output), and you should exit or otherwise take proper evasive action if execl() fails. Note that when the command name ("/usr/bin/xterm") contains any slashes, execlp() behaves the same as execl().
I'm trying to do basic IPC using pipes. I spent hours searching the internet, doing this and that, reading the API documentations, and ended up with the code below. But it does not work, as I quite expected. Just any help making my code 'work' would be many thanks.
<edit>
I've just found that using system instead of execl makes my program run perfectly as expected. So what is going wrong here when I use execl, while it doesn't happen with the system function?
</edit>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void){
int hInPipe[2];
int hOutPipe[2];
FILE *hInFile;
FILE *hOutFile;
char *s;
pipe(hInPipe);
pipe(hOutPipe);
if(fork()){
close(hInPipe[0]);
close(hOutPipe[1]);
hInFile=fdopen(hInPipe[1],"w");
fprintf(hInFile,"2^100\n");
fclose(hInFile);
hOutFile=fdopen(hOutPipe[0],"r");
fscanf(hOutFile,"%ms",&s);
fclose(hOutFile);
printf("%s\n",s);
free(s);
}else{
dup2(hInPipe[0],STDIN_FILENO);
dup2(hOutPipe[1],STDOUT_FILENO);
close(hInPipe[0]);
close(hInPipe[1]);
close(hOutPipe[0]);
close(hOutPipe[1]);
system("bc -q");/*this works*/
/*execl("bc","-q",NULL);*/ /*but this doesn't*/
}
}
Read the fine man page. :)
execl(const char *path, const char *arg0, ... /*, (char *)0 */);
arg0 (aka argv[0], the name the program is told it was invoked under) is not the same argument as the path (the location of the executable for said program). Moreover, execl takes, as its first argument, a fully-qualified pathname.
Thus, you want:
execl("/usr/bin/bc", "bc", "-q", NULL);
...or, to search the PATH for bc rather than hardcoding a location:
execlp("bc", "bc", "-q", NULL);
I want to write a program Shellcode.c that accepts in input a text file, which contains bash commands separeted by newline, and executes every commands in the text file: for example, the text file will contain:
echo Hello World
mkdir goofy
ls
I tried this one (just to begin practicing with one of the exec functions):
#include <stdio.h>
#include <unistd.h>
void main() {
char *name[3];
name[0] = "echo";
name[1] = "Hello World";
name[2] = NULL;
execvp("/bin/sh", name);
}
I get, in return,
echo: Can't open Hello World
I'm stuck with the execvp function, where did I go wrong?
You're doing it wrong.
The first array index is the name of the program, as explained in the docs:
The execv(), execvp(), and execvpe() functions provide an array of pointers to null-terminated strings that represent the argument list available to the new program. The first argument, by convention, should point to the filename associated with the file being executed. The array of pointers must be terminated by a NULL pointer.
Also, bash doesn't expect free-form argument like that, you need to tell it you're going to pass commands using the -c option:
So, you need:
name[0] = "sh";
name[1] = "-c";
name[2] = "echo hello world";
name[3] = NULL;
To pass a script to bash on the command line you must add the option '-c' and pass the whole script as a single string, i.e.
#include <stdio.h>
#include <unistd.h>
void main() {
char *name[] = {
"/bin/bash",
"-c",
"echo 'Hello World'",
NULL
};
execvp(name[0], name);
}
Many problems here: The exec() family of functions do not execute multiple programs - these functions execute a single program, and replace the currently running process in memory with the new program. The null-pointer-terminated array of strings you pass to execvp is supposed to contain the command-line arguments to the program executed by execvp.
If you want to execute multiple programs, you'll need to loop over each line and execute the programs one by one. But you can't use execvp because that immediately replaces the currently executing process (your C program) with the process executed via the shell, meaning that the rest of your C program will never be executed. You need to learn how to use fork() combined with execvp so you can execute child processes. You first call fork() to create a child process, and then from the child process you call execvp. Fork + Exec is a common strategy in UNIX environments to launch other processes from a parent process.
I have the following c code. I want to display my file with less by calling execv()
however the following seems never work. The program terminates and noting pop out.
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
int main(void){
int pid;
if(pid=fork()>0){
//read in from stdin and pass to pipe
}else if(pid==0){
//read from pipe
//write to out.txt
//everything up to here works fine
char* para[]={"less","/Desktop/out.txt"};
execv("/bin/less",para);
}
return 0;
}
(The original code contained execv("bin/less", para);.) Unless the current directory is the root directory, /, or unless there is a program less in the subdirectory ./bin/less, then one of your problems is that you have a probable typo in the name of the executable. That assumes the program is /bin/less and not /usr/bin/less. You might even use execvp() to do a PATH-based search for the program.
There's an additional problem: you need to include a null pointer to mark the end of the argument list.
Finally, you can print an error message after the execv() returns. The mere fact that it returns tells you it failed.
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
int main(void)
{
int pid;
if ((pid = fork()) != 0)
{
// read in from stdin and pass to pipe
// Need to test for fork() error here too
}
else
{
// read from pipe
// write to out.txt
// everything up to here works fine
char *para[] = { "/bin/less", "Desktop/out.txt", 0 };
execv(para[0], para);
fprintf(stderr, "Failed to execute %s\n", para[0]);
exit(1);
}
return 0;
}
Or:
char *para[] = { "less", "Desktop/out.txt", 0 };
execvp(para[0], para);
fprintf(stderr, "Failed to execute %s\n", para[0]);
The remarks in the code about pipes are puzzling since there is no sign of pipes other than in the comments. As it stands, less will read the file it is told to read. Note that less will not paginate its output if the output is not going to a terminal. Since we can see no I/O redirection, we have to assume, then, that less will ignore anything the program tries to write to it, and will not send any data back to the program.
char* para[]={"less","/Desktop/out.txt"};
execv("/bin/less",para);
How does execv know when to stop reading parameters?
I think if you'd put code in there to handle execv() returning an error you'd have found this. You're also not testing for errors from fork().