Yet another minishell pipeline in C - 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.

Related

using dup2 and pipe to redirect stdin

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.

Streaming execvp output via socket

I know this question has been asked a billion times, but all the solutions are not working for me. I need to stream the stdout of an execvp via a socket to a client. The relevant client code is this
static void execute_cmd(int fd, char *cmd, char **params) {
pid_t cid;
size_t i = 0, c = 0;
cid = fork();
if(cid == 0) {
close(STDOUT_FILENO);
dup2(fd, STDOUT_FILENO);
if(execvp(cmd, params) < 0) {
fprintf(stderr, "Could not execute \"%s\": %d\n", cmd, errno);
exit(1);
}
} else {
close(fd);
waitpid(cid, NULL, 0);
exit(1);
}
}
I have tried to copy the answer on this question. However, I get nothing back when I try this code:
int sockfd = serverConnect();
write(sockfd, "echo math", 11);
n = read(sockfd, buffer, 1023);
printf("Got %d bytes\n", n);
printf("%s\n", buffer);
close(sockfd);
I triple checked that the connection is established correctly. When I replace execute_cmd with a simple write, the client correctly prints the answer. But nothing happens when I execute the code above, I get no bytes back. I have removed the dup2 call and got no output from the execvp call either.
Getting quite desperate here, tried pipe and whatever I could find. Where did I go wrong? The command itself is ok, too, works on the shell, and the execvp call does not throw an error.
Turns out the code above is correct. The problem was an incorrect use of an earlier strtok that resulted in a silent crash of strdup. The fork above was simply never executed, and all my tests were above the strtok line. Only after putting printfs into every line of the code I could find the problem.
Frankly, I feel stupid.

Tee - mimicking program only writes the initial input to file, ignoring all sequential inputs

So I have a mytee program (with much much less functionality). Trying to learn how to work with pipes / children / etc
(1) I do pipe
(2) Create the file(s)
(3) fork
(4) the parent does scanf to get the text
(5) sends the text to the pipe
(6) child receives it and writes it to files
-> #4 should be a loop until the user writes '.'
-> #6 should continue writing new lines, but somewhere there is a breakdown.
Some of the things that I think it might be:
1. Something is wrong with my permissions (but O_APPEND is there, and not sure what else I would need)
2. there may be a problem in parent do while loop, where it should send the msg to the pipe (fd[1])
3. #6 where I strongly think my problem lies. After the initial write it doesn, continue writing. I am not sure if I need to somehow keep track of the size of bytes already written, but if that was the case I would expect the last message to be there not the first.
I'm pretty much at a loss right now
I run it using
./mytee test1
Code:
ret = pipe (fd);
if (ret == -1)
{
perror ("pipe");
return 1;
}
for (i=0;i<argc-1;i++) {
if ((filefd[i] = open(argv[i+1], O_CREAT|O_TRUNC|O_WRONLY|O_APPEND, 0644)) < 0) {
perror(argv[i]); /* open failed */
return 1;
}
}
pid = fork();
if (pid==0) /* child */
{
int read_data;
do {
read_data = read(fd[0], buffer, sizeof(buffer));
for(i=0;i<argc;i++) {
write(filefd[i], buffer, read_data);
}
} while (read_data > 1);
for (i=0; i<argc; i++)
close(filefd[i]);
return 0;
}
else { /* parent */
char msg[20];
do{
scanf("%s",msg);
write(fd[1],msg,sizeof(msg));
}while (strcmp(msg,".")!=0);
while ((pid = wait(&status)) != -1)
fprintf(stderr, "process %d exits with %d\n", pid, WEXITSTATUS(status));
return 0;
}
Adding Output:
$ ./a.out test1
qwe
asd
zxc
.
^C
It doesn't exit properly. I think the child is stuck in the loop
And the contents of test1:
qwe
Working through this with the OP, reportedly the problem was unconditionally writing all 20 bytes of msg instead of just the NUL-terminated string contained within it. Suggested minimal fix: change
scanf("%s",msg);
write(fd[1],msg,sizeof(msg));
to
scanf("%19s",msg);
write(fd[1],msg,strlen(msg));
I see a couple of issues, which could potentially cause that behaviour.
Firstly, your loop condition doesn't look right. Currently it will terminate if a single byte is read. Change it to this:
while (read_data > 0);
The other issue I see is that you're writing to more files than you opened. Make sure you loop to argc-1, not argc:
for (i=0; i<argc-1; i++)

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

Unable to use "execve()" successfully

The aim of the program is to fork a new child process and execute a process which also has command line arguments. If I enter /bin/ls --help, I get the error:
shadyabhi#shadyabhi-desktop:~/lab/200801076_lab3$ ./a.out
Enter the name of the executable(with full path)/bin/ls --help
Starting the executable as a new child process...
Binary file to be executed: /bin/ls
/bin/ls: unrecognized option '--help
'
Try `/bin/ls --help' for more information.
Status returned by Child process: 2
shadyabhi#shadyabhi-desktop:~/lab/200801076_lab3$
What would be the right argument to execve()?
#include<stdio.h>
#include<string.h> //strcpy() used
#include<malloc.h> //malloc() used
#include<unistd.h> //fork() used
#include<stdlib.h> //exit() function used
#include<sys/wait.h> //waitpid() used
int main(int argc, char **argv)
{
char command[256];
char **args=NULL;
char *arg;
int count=0;
char *binary;
pid_t pid;
printf("Enter the name of the executable(with full path)");
fgets(command,256,stdin);
binary=strtok(command," ");
args=malloc(sizeof(char*)*10);
args[0]=malloc(strlen(binary)+1);
strcpy(args[0],binary);
while ((arg=strtok(NULL," "))!=NULL)
{
if ( count%10 == 0) args=realloc(args,sizeof(char*)*10);
count++;
args[count]=malloc(strlen(arg));
strcpy(args[count],arg);
}
args[++count]=NULL;
if ((pid = fork()) == -1)
{
perror("Error forking...\n");
exit(1);
}
if (pid == 0)
{
printf("Starting the executable as a new child process...\n");
printf("Binary file to be executed: %s\n",binary);
execve(args[0],args,NULL);
}
else
{
int status;
waitpid(-1, &status, 0);
printf("Status returned by Child process: %d\n",WEXITSTATUS(status));
}
return 0;
}
The first entry in the args array should be the program name again. Your code calls /bin/ls with --help as the process name.
Please check to make sure args is not getting clobbered by the realloc call. See here on SO regarding realloc
Edit:
Also the loop looks funny....
You called strtok like this:
binary=strtok(command," ");
Change the loop construct to use binary instead as shown...
char *tmpPtr;
while (binary != NULL){
if ( count%10 == 0) tmpPtr=realloc(args,sizeof(char)*10);
if (tmpPtr != NULL) args = tmpPtr;
count++;
args[count-1]=malloc(strlen(binary)+1);
strcpy(args[count-1],binary);
binary = strtok(command, " ");
}
And use the binary for copying the string....
Hope this helps,
Best regards,
Tom.
Your program has some obvious errors. For instance, declaring char **args=NULL; and then args=realloc(args,sizeof(char)*10); (since it's char**, you should be alloc-ing to char*, no?..).
Since sizeof(char*) is usually 4 while sizeof(char) is usually 1, you end up with some serious memory management problems around there (you alloc less than you use, and you end up writing where you shouldn't). From there on, all hell breaks loose and you can't expect your program's behavior to make any sense.
I'd suggest that you run your program through an util such as Valgrind to figure out memory leaks and correct the program appropriately. Probably your execve problems will disappear as soon as the memory problems are corrected.

Resources