write Error: Broken Pipe in C using execlp - c

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).

Related

Using 'execlp' System Call to run current program recursively

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).

Why do we pass the command name twice to execve, as a path and in the argument list?

I have a program written by my professor that prints the working directory (pwd) by using execve(), but I don't understand the parameters.
pid_t pid = fork();
if(pid <0)
perror(NULL);
else if(pid == 0)
{
char*argv[] = {"pwd",NULL};
execve("/bin/pwd",argv,NULL);
perror(NULL);
}
else
printf("Im the parent!");
return 0;
}
"/bin/pwd" gives the path to the executable that will be executed.
This means that it will call the pwd function, doesn't it?
Then why do I need to have the parameter pwd?
Couldn't the program run without that parameter?
By convention, the first argument passed to a program is the file name of the executable. However, it doesn't necessarily have to be.
As an example, take the following program:
#include <stdio.h>
int main(int argc, char *argv[])
{
int i;
printf("number of arguments: %d\n", argc);
printf("program name: %s\n", argv[0]);
for (i=1; i<argc; i++) {
printf("arg %d: %s\n", argv[i]);
}
return 0;
}
If you run this program from another like this:
char*argv[] = {"myprog", "A", "B", NULL};
execve("/home/dbush/myprog",argv,NULL);
The above will output:
number of arguments: 3
program name: myprog
arg 1: A
arg 2: B
But you could also run it like this
char*argv[] = {"myotherprog", "A", "B", NULL};
execve("/home/dbush/myprog",argv,NULL);
And it will output:
number of arguments: 3
program name: myotherprog
arg 1: A
arg 2: B
You can use the value of argv[0] as a way to know how your program was called and perhaps expose different functionality based on that.
The popular busybox tool does just this. A single executable is linked with different file names. Depending on which link a user used to run the executable, it can read argv[0] to know whether it was called as ls, ps, pwd, etc.
The execve man page has some mention of this. The emphasis is mine.
By convention, the first of these strings should contain the filename associated with the file being executed.
That is, it is not a actually mandatory for the first argv to be the filename. In fact one can test that by changing the argv[0] to any string in the example code and the result will still be correct.
So it really is just a convention. Many programs will use argv[0] and expect it to be the filename. But many programs also do not care about argv[0] (like pwd). So whether argv[0] actually needs to be set to the filename depends on what program is being executed. Having said that, it would be wise to always follow the convention to play nicely with almost everyone's long held expectations.
From execve man page: http://man7.org/linux/man-pages/man2/execve.2.html
argv is an array of argument strings passed to the new program. By
convention, the first of these strings (i.e., argv[0]) should contain
the filename associated with the file being executed. envp is an
array of strings, conventionally of the form key=value, which are
passed as environment to the new program. The argv and envp arrays
must each include a null pointer at the end of the array.
So, argv is treated as command line args for new program to execute with.
Since by default, for a linux binary invoked with arguments, these args are accessed through argc/argv, where argv[0] holds the program name.
I think this is to keep the behavior parity to match with default case (prog invoked with arguments).
From the source:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/exec.c#l1376
The argv passed to execve is used to construct argv for the about to be launched binary.

How to use execv system call in linux?

I am writing a program using execl to execute my exe file which is testing and it's work very well and display the output in the Linux CLI. But I have not idea how to change the execl to execv, although I know both of the system call will give the same value. I am confused with the array argument for execv system call
This is my execl sample program
int main(void)
{
int childpid;
if((childpid = fork()) == -1 )
{
perror("can't fork");
exit(1);
}
else if(childpid == 0)
{
execl("./testing","","",(char *)0);
exit(0);
}
else
{
printf("finish");
exit(0);
}
}
can I know how to change the execl to execv. What I read from online, we must set the file path for my exe file and the argument of array . What type of argument need to set for the array in order to ask the program to execute the testing exe file ?
https://support.sas.com/documentation/onlinedoc/sasc/doc/lr2/execv.htmIs it the link consist of the thing I want ? But what I read from it ,the command is request the list the file,not execute the file. Correct me I make any mistake
In order to see the difference, here is a line of code executing a ls -l -R -a
with execl(3):
execl("/bin/ls", "ls", "-l", "-R", "-a", NULL);
with execv(3):
char* arr[] = {"ls", "-l", "-R", "-a", NULL};
execv("/bin/ls", arr);
The char(*)[] sent to execv will be passed to /bin/ls as argv (in int main(int argc, char **argv))
According to the man page the use of execv is quite simple. The first argument is the path as a string to the program you want to execute. The second is an array of string that will be used as the arguments of the program you want to execute. It is the kind of array you get if you get the argv array in your main function.
So the array you will pass as a parameter will be the array received in the main function of the program you execute with execv.
By convention, the first argument should be the program name (the one you try to execute) but it is not mandatory (but strongly recommended since it is the behaviour a lot of programs are expecting). Each other string in the array should be an individual argument.
And of course, the array should be terminated with a NULL pointer to mark the end.
Array example: ["prog_name", "arg1", "arg2", "arg3", NULL]
[] is your array, each string separated with a coma is a frame of your array and at the end you have the null frame.
I hope I am clear enough!

running mysql import from execv

pid_t childPid = fork ();
if (childPid == (pid_t) 0)//zero success
{
const char *path = "/usr/local/mysql/bin/mysql";
//doesn't work
//char * const parmList[] = {"--user=root", "test_db", NULL};
//does work
char * const parmList[] = {"", "--user=root", "test_db", NULL};
execv(path, parmList);
printf("ERROR:\tFork failed.\n");
}
else if (childPid < (pid_t) 0)// -1 failure
{
/* The fork failed. */
printf("ERROR:\tFork failed.\n");
return EXIT_FAILURE;
}
else
{
while (true) {
//stay alive
sleep(1);
}
}
printf("done");
exit(0);
I am having trouble importing a sql dump by using execv. You can see I wasn't able to login using the first paramList but the second one worked just fine. Anyways, if I add to the param list:
char * const parmList[] = {"", "--user=root", "test_db", "<", "/Users/joelsaltzman/Desktop/dump.sql", NULL};
The output shows the mysql help for the command line args like I typed something wrong.
Does anybody know how to get this to work?
The first paramList is incorrect, because the first element should be the filename of the program you are going to execute:
The argument argv is an array of character pointers to null-terminated strings. The application shall ensure that the last member of this array is a null pointer. These strings shall constitute the
argument list available to the new process image. The value in argv[0] should point to a filename that is associated with the process being started by one of the exec functions.
The input redirection with < does not work because this is not a feature of the kernel (which you invoke using execv), but of usual Unix shells. The system library call is what you are looking for. (It also just uses a call from the exec-family, but calls a shell with your command, which will then support <.)
Be sure to read the manpage system(3) and think about input validation if you are going to pass it a string that could be influenced by a malicious user.
The second one works better, because the first parameter should be the command name. Therefore, MySQL starts reading from the second parameter. You should use the command name (the path), not an empty string, but it normally doesn't matter.
You can't use redirection with execv, because this is a shell feature, and execv doesn't run the shell. You can execute /bin/sh, with parameters that tell it to run mysql, or you can use dup2 to change stdin to whatever you want.
Use popen() instead to start mysql, and then write the contents of the sql file into the process yourself.

Unix programming... fork() & execv() help... C Programming

I'm writing my own unix terminal and I'm running into a problem executing commands:
First I take the user input and store it into a buffer, then I separate the words and store them into my argv[] array.
i.e
command is "firefox" to launch firefox which is stored in argv[0]
How do I launch the command? This is what I'm trying to do, but I'm getting errors:
void launchProcess(char *command[], char *file){
pid_t pid;
pid = fork();
if (pid == -1) {
perror("Error when forking");
exit(EXIT_FAILURE);
}
if (pid == 0){
fprintf(stderr, "Child pid = %d\n", getpid());
execv(file, command);
}
When I call:
launchProcess(commandArgv, "STANDARD");
I get the following errors:
error: conflicting types for launchProcess
If you have a conflicting type error, you should make sure that the function you listed has the same type as its declaration.
Also, you probably already know, but execv requires a fully qualified path to the executable, so a value like "STANDARD" isn't going to work; use execvp if you want it to use the PATH variable to determine the location of the binary. You should also make sure the last value in the argv array is NULL. Finally, make sure to check the return value of execv; there is a definite possibility it can fail, e.g., if the user tries to execute a program that doesn't exist.
You need to prototype the function: add "void launchProcess(char *command[], char *file);" above your main function.
Your data types look correct, but based on the parameter names: "file" and "command", It looks like you might be using the function in the wrong way.
Here's an example of executing the ls function with no arguments.
char *args[] = { NULL };
execv("/bin/ls", args);
These notes on exec and wait might help some.

Resources