using dup2 and pipe to redirect stdin - c

I have a program A that takes two arguments from stdin and exits with a unique code depending on the arguments. I am writing a program B that calls program A using fork and exec and let program B print out the code program A exits with. For some reason, program A doesn't seem to be getting the data I piped through to it in the child process of fork. I'm not sure if I'm piping the correct data to the child process.
Could someone help me please? Thanks!
Here is my code:
int program_B(void) {
char var_a[256];
char var_b[256];
int fd[2];
// Read from stdin
char *sendarray[2];
sendarray[0] = var_a;
sendarray[1] = var_b;
if(fgets(var_a, MAXLINE, stdin) == NULL) {
perror("fgets");
exit(1);
}
if(fgets(var_b, MAXLINE, stdin) == NULL) {
perror("fgets");
exit(1);
}
if (pipe(fd) == -1) {
perror("pipe");
exit(1);
}
int pid = fork();
// Child process -- error seems to be here.
if (pid == 0) {
close(fd[1]);
dup2(fd[0], fileno(stdin));
close(fd[0]);
execl("program_A", NULL);
perror("exec");
exit(1);
} else {
close(fd[0]);
write(fd[1], sendarray, 2*sizeof(char*));
close (fd[1]);
int status;
if (wait(&status) != -1) {
if (WIFEXITED(status)) {
printf("%d\n", WEXITSTATUS(status));
} else {
perror("wait");
exit(1);
}
}
}
return 0;
}

You are piping the wrong data to the child process.
I am assuming var_a and var_b are the strings you want to send to program A. They are both of type array of chars, which in C is the same thing as pointer to char (Actually there is a small difference between pointers and arrays but this is irrelevant for this problem). So they are actually just pointers to the first byte of each argument. sendarray, however is an array of char-pointers which is the same thing as a pointer to char-pointer. Keep this in mind for a second.
When calling write() the 2nd parameter tells it where the data is in memory. By passing sendarray, write thinks this sendarray points the data you want to write although it actually points to yet another pointer. So what happens is that the pointer values of var_a and var_b (which is what sendarray points to), are written to the pipe.
So you have to pass var_a and var_b to write(), since those are pointers to the actual data you want to send. Also you have to know how long (how many bytes) this data is. If var_a and var_b point to null-terminated strings, you can use strlen() to determine their length.
One last thing: I don't know how exactly your program A obtains 2 arguments from a continuous byte stream like stdin, but assuming it reads it line by line, you obviously have to send a new-line character from program B, as well.
So putting it all together your write statements should look something like this:
write(fd[1], var_a, strlen(var_a));
write(fd[1], "\n", 1);
write(fd[1], var_b, strlen(var_b));
Of course, if any of the assumptions I made is wrong, you have to adopt this code appropriately.

Related

Different Seek Pointer for read\write

I am trying to understand how the seek pointer mechanism works when a parent process opens a new file and then creating a new child process using fork().
Assume I have the following code :
int main(){
int fd = open("myFile.txt", O_RDWR | O_CREAT)
if(fork() == 0){
write(fd,"stack overflow", 16);
close(fd);
}
else{
wait(NULL);
char buff[17];
read(fd, &buff, 16);
printf("%s", buff);
}
}
I get nothing printing to stdout, but I don't really understand why would it behave that way. Wouldn't it be smarter if there were a different seek pointer for read and write so running this code will result in printing "stack overflow" to stdout?
Apparently not, Would like to get an explanation
Both processes are using the same file description (i.e. the same open file).
They are accessing the same file pointer because it's part of the same file description. When one of them writes some characters, the file pointer advances for all of them, because it's the same file pointer.
You can get the file pointer by calling lseek(fd, 0, SEEK_CUR). If you print the file pointer before starting the child program, and after waiting for it, you should see that it has changed.

I am working on creating my own UNIX shell and some times when I run 'ls' command it gives an error bad address

I am wondering if there is an error with execvp calling ls that can cause it to fail occassionally and then work properly other times.
void lookInsideCurrentDirectory(char **parsed){
char* line = NULL;
pid_t pid = fork();
if(pid == -1){
return;
}
else if(pid == 0){
if(execvp(parsed[0], parsed) == -1){
perror("Error: ");
}
exit(0);
}
else{
wait(NULL);
return;
}
}
According to https://www.gnu.org/software/libc/manual/html_node/Error-Codes.html:
Macro: int EFAULT
“Bad address.” An invalid pointer was detected.
If you receive this error from execvp, that means that some of the pointer in parsed was invalid. You should look into the rest of the program and make sure any strings in the parsed array are not free()-ed until the execvp calls are completed.
Another very common mistake is that since you're not passing the length of the argument array to execvp, the argv argument must be a NULL-terminated array, so that execvp knows when to stop reading arguments. That means that if you're receiving command that looks like so: ls -lah /bin then your argv array should be one larger and end it with a NULL pointer:
char** argv = {"ls", "-lah", "/bin", NULL};
If you don't end the argv with a NULL pointer, execvp will try to dereference whatever comes next in the memory as an pointer address, and unless it happens to contain NULL bytes, then the derefence likely will fail or may dereference to unexpected things.

Yet another minishell pipeline in C

As many before me, I'm trying to implement a basic shell in C. Overall things are working nicely, and I'm now trying to add pipes and redirections.
I've read a lot about the pipe() function and have successfully written a side program to pipe a function's output into a second function's input.
Where I have trouble is when it comes to looping over an undetermined amount of functions.
Here's the last version of my function as well as the main I use to test it :
char **g_env;
int ft_pipeline(char **cmd, unsigned int pos)
{
int in;
int pfd[2];
pid_t pid;
char **cur_cmd;
in = 0;
while (cmd[pos])
{
if (pipe(pfd) != 0)
return (1);
close(pfd[0]);
dup2(pfd[1], in);
close(pfd[1]);
pid = fork();
if (pid == -1)
return (2);
if (pid == 0) //child
{
close(pfd[1]);
dup2(pfd[0], 0);
close(pfd[0]);
cur_cmd = ft_strsplit_blank(cmd[pos]);
execve(cur_cmd[0], cur_cmd, g_env);
}
wait(NULL);
in = pfd[0];
pos++;
}
return (0);
}
int main(void)
{
char **cmd = ft_strsplit("/bin/ls -l /dev | /bin/grep std", '|');
g_env = NULL;
ft_pipeline(cmd, 0);
return (0);
}
In this current form, ls is properly executed but written to stdout, and grep returns :
/bin/grep: (standard input): Bad file descriptor
This is the fifth time I rewrite my code, and I've tried to tweak it for a few days now. I've also read several other post here to try and grasp the logic behind this small program, to no avail.
I'd really like it if you could tell me where I'm making the mistake and how I could fix it.
Note : You will very likely find many ways to improve this code in its form. I know about it but that's something that I cannot do, in most cases. Although this is not homework, it is still something that I do for school (see it as a voluntary practice) and I have to respect standards in the way I write my code or the functions I use.

using sort with dup2

I'm experimenting with this dup2 command in linux. I've written a code as follows:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main()
{
int pipe1_ends[2];
int pipe2_ends[2];
char string[] = "this \n is \n not \n sorted";
char buffer[100];
pid_t pid;
pipe(pipe1_ends);
pipe(pipe2_ends);
pid = fork();
if(pid > 0) { /* parent */
close(pipe1_ends[0]);
close(pipe2_ends[1]);
write(pipe1_ends[1],string,strlen(string));
read(pipe2_ends[0], buffer, 100);
printf("%s",buffer);
return 0;
}
if(pid == 0) { /* child */
close(pipe1_ends[1]);
close(pipe2_ends[0]);
dup2(pipe1_ends[0], 0);
dup2(pipe2_ends[1],1);
char *args[2];
args[0] = "/usr/bin/sort";
args[1] = NULL;
execv("/usr/bin/sort",args);
}
return 0;
}
I expect this program to behave as follows:
It should fork a child and replace its image with sort process. And since the stdin and stdout are replaced with dup2 command, I expect sort to read input from the pipe and write the output into the other pipe which is printed by the parent. But the sort program doesn't seem to be reading any input. If no commandline argument is given, sort reads it from the stdin right? Can someone help me with this problem, please.
Many thanks!
Hm. What's happening is that you aren't finishing your write: after sending data to the child process, you have to tell it you're done writing, either by closing pipe1_ends[1] or calling shutdown(2) on it. You should also call write/read in a loop, since it's quite likely in the general case that read at least won't give you all the results in one go. Obviously the full code checks all return values, doesn't it?
One final thing: Your printf is badly broken. It can only accept null-terminated strings, and the result returned by read will not be null-terminated (it's a buffer-with-length, the other common way of knowing where the end is). You want:
int n = read(pipe2_ends[0], buffer, 99);
if (n < 0) { perror("read"); exit(1); }
buffer[n] = 0;
printf("%s",buffer);

Why am I having difficulty making execvp in C work?

I need to implement a basic shell in C.
One of things I need is to implement a function that has a command and to execute it.
my code:
pID=fork();
if (pID == 0)
execvp(tmp[0], tmp);
else if (pID > 0)
{
printf("%d", pID);
wait(NULL);
}
else
printf("Failed to create proccess \n");
The problem is that no matter what is the command I put in tmp, the program shows me the prompt again, and do nothing except that.
For example if I write gedit (in order to open the gedit — a ntpad of Ubuntu), it doesn't open it, or if write ls -a it doesn't show me any output as the terminal of Ubuntu does.
execvp should work. As the others mentioned, you really need to show how you populate tmp. That said, I would guess that that's where the error is. tmp needs to be a null terminated array.
#include <stdio.h>
main( int argc, char * argv[] )
{
int pid = fork;
char * tmp[2];
memset( tmp, 0, sizeof(tmp) );
tmp[0] = argv[0];
if( 0 == pid )
{
if( -1 == execvp( tmp[0], tmp ) )
{
char errmsg[64];
snprintf( errmsg, sizeof(errmsg), "exec '%s' failed", tmp[0] );
perror( errmsg );
}
else if( 0 < pid )
{
printf("[%d] %s\n", pid, tmp[0]);
wait(NULL);
}
else
{
perror("fork failed");
}
}
Although you've failed to tell us what you're passing through the tmp variable to execvp, my psychic sense tells me that you forgot to null-terminate your argument list. A NULL argument tells execvp where the last argument is, and if you fail to put in a NULL, it will start reading random garbage off the stack.
If that random garbage points to large strings of non-zero data, it will run out of space to store the supposed arguments to the new process, which is typically a few hundred KB (see this page for some system-specific numbers, as well as various ways of getting your system's maximum arguments size).
When there's too much argument data, the system call execve(2) (called internally by execvp) fails with the error E2BIG.
So to see if this is what's happening to you, check the return value from execvp. If it even returns at all, it failed (if it succeeded, it wouldn't have returned since a new process would be executing!), so check the global value of errno to see why it failed:
if (pID == 0)
{
execvp(tmp[0], tmp);
printf("exec failed: %s\n", strerror(errno));
exit(1);
}
execvp() requires full path . If in tmp[0] isnt the full path of your executable file use execv()
execv(tmp[0], tmp);

Resources