Named pipes in C program (unix) - c

I have to use 3 processes in order to solve the problem.
First processes gets input from (entered via keyboard) and sends it to the second procces
The second process replaces all the vocals from the text with 12345 (a with 1, e with 2, ...). I got a well working sh script (tested it) that uses sed to do this task. I will put it here.
Thrid process outputs on the screen only the alphanumeric lines. I also got a script that uses grep to do this task and also works fine (tested it).
This processes should communicate trough a named pipe (a FIFO file) and i'm running into some difficulties sending and receiving the data trough the FIFO. When i use the write function to write the data to the FIFO it outputs the data on the screen and when i'm in the second process and i try to read the data from the FIFO it just waits for a new input entered by me.
First process:
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
int main() {
char text[500]; // buffer for the inputed text
char aux[100]; // Also a buffer to save multi-line input into text buffer
char* myfifo = "myfifo";
int fd_text;
printf("\nInput: (to stop giving input just type 0 on a new line):\n");
// Forming the text
while(scanf("%[^\n]%*c", aux) == 1) {
if(strcmp(aux, "0") == 0)
break;
strcat(aux, "\n");
strcat(text, aux);
}
strcat(text, "\0");
// Everything works well reading the input
int returnValue = mkfifo(myfifo, 0666);
if(returnValue < 0) {
printf("mkfifo() failed\nerrno = %d\n", errno);
if(errno == EEXIST)
printf("That file already exists.\n");
}
if(fd_text = open(myfifo, O_WRONLY) < 0) {
printf("error while opening FIFO");
exit(0);
}
int indicator = write(fd_text, text, strlen(text) + 1);
if(indicator == 0) {
printf("error while writing to FIFO");
}
close(fd_text);
return 0;
}
Second process:
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
int main() {
char text[500];
char* myfifo = "myfifo";
int fd_text2;
if (fd_text2 = open(myfifo, O_RDONLY) < 0) {
printf("error while opening FIFO");
exit(1);
}
int result = read(fd_text2, text, 500);
if (result < 0)
printf("ERROR WHILE READING FROM THE FILE\n");
printf("\n%d bytes were read from the file\n", result);
printf("\nText read from FIFO:\n%s\n", text);
// Saving the text into a txt to perfom the sed command on it
FILE *fp_replace_text;
fp_replace_text = fopen("replace_text.txt", "w");
fprintf(fp_replace_text, "%s", text);
fclose(fp_replace_text);
system("chmod 777 replace.sh");
system("./replace.sh replace_text.txt");
close(fd_text2);
unlink(myfifo);
return 0;
}
replace.sh
#!/bin/sh
sed -i 's/a/1/gi' $1
sed -i 's/e/2/gi' $1
sed -i 's/i/3/gi' $1
sed -i 's/o/4/gi' $1
sed -i 's/u/5/gi' $1
If i can figure out why communication between process 1 and 2 is not working, process 3 will be the same, so i'm not posting it anymore.
Edit:
Second Process
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main() {
char text[500];
char* myfifo = "myfifo";
int fd_text2;
if ((fd_text2 = open(myfifo, O_RDWR)) < 0) {
printf("error while opening pipe");
exit(1);
}
int result = read(fd_text2, text, 500);
if (result < 0)
printf("ERROR WHILE READING FROM THE FILE\n");
FILE *fp_replace_text;
fp_replace_text = fopen("replace_text.txt", "w");
fprintf(fp_replace_text, "%s", text);
fclose(fp_replace_text);
system("chmod 777 replace.sh");
system("./replace.sh replace_text.txt");
fp_replace_text = fopen("replace_text.txt", "r");
fread(text, 500, 1, fp_replace_text);
fclose(fp_replace_text);
write(fd_text2, text, strlen(text) + 1);
close(fd_text2);
return 0;
}
Third process
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main() {
char text[500];
char* myfifo = "myfifo";
int fd_text3;
if ((fd_text3 = open(myfifo, O_RDONLY)) < 0) {
printf("error while opening pipe");
exit(1);
}
int result = read(fd_text3, text, 500);
if (result < 0)
printf("ERROR WHILE READING FROM THE FILE\n");
FILE *fp_replace_text;
fp_replace_text = fopen("replace_text.txt", "r");
fread(text, 500, 1, fp_replace_text);
fclose(fp_replace_text);
printf("Textul final este:\n");
system("chmod 777 output.sh");
system("./output.sh replace_text.txt");
unlink(myfifo);
return 0;
}
I'm also trying to send the content of the txt filo to the third process trough FIFO (i know i could just use the txt file, but i want to use the FIFO file and read from it again). So i'm use the shell script on the txt file, i read from and and then i want to send what i read from the txt file to the thrid process to FIFO, but after i run the second process the execution stops and doesn't blocks. When i'm writing to the FIFO in the first process, it blocks until the second process reads from the FIFO. Second process keeps executing and third process can't get anything. I guess there has to be something wrong with FIFO principles, that if u want to perform input/output operations on a FIFO both ends should be opened.

The problem (which ought to have cause a warning from your compiler) is in statements like this:
if(fd_text = open(myfifo, O_WRONLY) < 0)
You seem to assume that this will be evaluated from left to right, first assigning a value to fd_text, then testing that value against 0 to give a boolean value which will determine whether to enter the if block. But in C, relational operators (like <) take precedence over assignment operators (like =). So first we compare the result of the open command to 0, then assign the boolean result of that comparison to fd_text. So the file descriptor is lost, and further attempts to reach the fifo will fail. We can correct this with a pair of parentheses:
if((fd_text = open(myfifo, O_WRONLY)) < 0)
More generally, it's a good idea to develop new functionality in isolation as much as possible. If you want to send text to a fifo and receive it, just hard-code some text in the first module, don't have all that code to read user input, and don't attempt the further step of sending the text from the second module to some other process for modification, not until the fifo is working perfectly. Small steps.

Related

Redirecting stdin with FIFO (names pipe)

I'm creating a C program with a server-client bases.
I've been trying to redirect the stdin to a named pipe I created and I've managed to put a client writing to the pipe. On the server side I opened the same pipe, closed stdin and redirected the stdin, using dup (tried with dup2 as well), to the pipe.
I have to read the input with the function getline. The problem is it reads the first input correctly, but recieves only nulls after it. I'll add a sample to the question.
server:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
main () {
char* str;
size_t size=0;
int pshell_in;
unlink("/tmp/par-shell-in");
if(mkfifo("/tmp/par-shell-in", 0777) < 0){
fprintf(stderr, "Error: Could not create pipe\n");
exit(-1);
}
if((pshell_in = open("/tmp/par-shell-in", O_CREAT | O_RDONLY, S_IRUSR)) < 0){
fprintf(stderr, "Error: Failed to open file\n");
exit(-1);
}
dup2(pshell_in, 0);
close(pshell_in);
while(1) {
if (getline(&str, &size, stdin)<0) {
printf("Oh dear, something went wrong with getline()! %s\n", strerror(errno));
return -1;
}
printf("%s", str);
}
}
* I know its null cause I've printed it with read (instead of redirecting) and it prints (null).
client:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
#define VECTORSIZE 7
int main() {
char* buf;
int pshell_in;
size_t size=0;
if((pshell_in = open("/tmp/par-shell-in", O_WRONLY, S_IWUSR)) < 0){
fprintf(stderr, "Error: Failed to open file\n");
exit(-1);
}
printf("%d\n", pshell_in);
while(1) {
if (getline(&buf, &size, stdin) < 0) {
return -1;
}
write(pshell_in, buf, 256);
}
}
I suspect its right because if I use read on the client side (replacing O_WRONLY with O_RDWR) it prints the string as I typed it.
Can anyone help me with this one?
FIFOs are funny things. If a process tries to open one for reading, it will block until there's a process that opens it for writing. Conversely, if a process tries to open one for writingt, it will block until there's a process that opens it for reading. However, multiple processes can open it for reading or writing. When there are no more processes with it open for reading, writes will fail; when there are no more processes with it open for writing, reads will fail. And when the operations fail, you have to close and reopen the FIFO to continue processing data afresh.
I strongly suspect you're running into problems because of these behaviours.
Additionally, your client write code is dubious; you aren't paying any attention to how much data was read. You have:
while(1) {
if (getline(&buf, &size, stdin) < 0) {
return -1;
}
write(pshell_in, buf, 256);
}
If, as is probable, you read less than 256 characters of input in the line, then it's quite possible that you go writing beyond the bounds of the array that was allocated by getline(). It's also distinctly possible that some or even most of that data is null bytes. However, the (null) you're seeing in the server typically indicates that you're trying to print a string but passed printf() a null pointer. Whatever's going on, most of it is undefined behaviour which is a Bad Thing™ and should be avoided at all costs.
You should have something more like:
ssize_t nbytes;
while ((nbytes = getline(&buf, &size, stdin)) > 0)
{
if (write(pshell_in, buf, nbytes) != nbytes)
{
fprintf(stderr, "Short write to FIFO\n");
break;
}
}
free(buf);
Note how this only writes as much data as was read and doesn't assume that 256 bytes were available to be written.

Can't open() second file C

For some reason if I do a second open, it compiles but when I try to run it, it does nothing like it's locked. It's missing a lot of other functions, because it's a work in progress for a school project. If I remove one of the open(), the program runs just fine.
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define BUFFER_SIZE 100
#define INPUT "/tmp/father"
int main(int argc, char **argv)
{
int fds;
int fd;
char mode[BUFFER_SIZE];
char buffer[BUFFER_SIZE];
unlink(INPUT);
mkfifo(INPUT, S_IRUSR | S_IWUSR);
if(argc != 2)
{
fputs("Argumentos invalidos\n", stderr);
exit(EXIT_FAILURE);
}
fd = open(argv[1], O_WRONLY);
if(fd == -1)
{
fprintf(stderr, "\nCan't open pipe\n");
exit(EXIT_FAILURE);
}
fds = open(INPUT, O_RDONLY);
if(fds == -1)
{
fprintf(stderr, "\nCan't open pipe\n");
exit(EXIT_FAILURE);
}
while(1)
{
fgets(buffer,BUFFER_SIZE,stdin);
sscanf(buffer,"%s", mode);
write(fd,buffer,strlen(buffer));
}
}
Are you sure there's a problem? You are reading from stdin (the fgets at the bottom), and writing to the pipe. What you're missing is something reading from the pipe. So if in another terminal you type:
$ cat /tmp/father
then anything you type into your prog will appear there.
So, in one terminal I do:
$ ./test /tmp/father
line one
line two
And in the second terminal:
$ cat /tmp/father
and I see:
line one
line two
No?
P.S. You are doing sscanf to read from buffer and write to mode, then writing out the buffer string. Not that it matters, but you're not using mode.

result of child process's exection of some system command can't send to the father process with pipe

Maybe this is not a compact title, I am very sorry about that:). I try redirecting stdin/stdout of a child process to its parent process with pipes. The child process execute a system command from the father process input and return the exec result to the father process with a pipe. Here I implemented "cat -n" and "tr /a-z/ /A-Z/", the former works fine, but later haven't return any results. What has caused this? Thank you.
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <assert.h>
#include <sys/sem.h>
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while( 0)
int main(int argc, char *argv[])
{
int chi_pipe[2], par_pipe[2];
if (pipe(chi_pipe) == -1 || pipe(par_pipe) == -1)
ERR_EXIT("pipe error");
/* Set O_NONBLOCK flag for the read end (pfd[0]) of the pipe. */
if (fcntl(chi_pipe[0], F_SETFL, O_NONBLOCK) == -1) {
fprintf(stderr, "Call to fcntl failed.\n"); exit(1);
}
/* Set O_NONBLOCK flag for the read end (pfd[0]) of the pipe. */
if (fcntl(chi_pipe[1], F_SETFL, O_NONBLOCK) == -1) {
fprintf(stderr, "Call to fcntl failed.\n"); exit(1);
}
pid_t pid;
pid = fork();
if (pid == -1)
ERR_EXIT("fork error");
if (pid == 0)
{
close(chi_pipe[0]); // I don't read in channel 1
close(par_pipe[1]); // I don't write in channel 2
dup2(chi_pipe[1], STDOUT_FILENO);
close(STDIN_FILENO);
dup2(par_pipe[0], STDIN_FILENO);
execlp("cat", "cat" , "-n", NULL);
//execlp("tr", "tr" , "/a-z/", "/A-Z/", NULL);
sleep(10);
close(chi_pipe[1]);
close(par_pipe[0]);
_exit(0);
}
close(par_pipe[0]);
close(chi_pipe[1]);
while(1) {
char input[1024];
memset(input, 0 , 1024);
fgets(input, 1024 ,stdin);
write(par_pipe[1], input, strlen(input));
char buf[3*1024];
int count = 0;
while (count <= 0)
count=read(chi_pipe[0], buf, 1024*3);
if (count >= 1)
{
printf("buf=%s", buf);
printf("\n");
}
}
close(par_pipe[1]);
close(chi_pipe[0]);
return 0;
}
A couple of points:
You are suffering from the need to perform non-blocking I/O. You are reading a line from a file, then writing it to a pipe. But there is no guarantee tr will conveniently write that line back translated. It might wait for the next line to come in. There is no line discipline in place. What you need to do is read from your file, write to tr (if the pipe is not full) and read from tr (if bytes are ready) at the same time. Or, more accurately, according to availability of data on the fd (to read) or the availability of space in the pipe (to write). Otherwise you will run into deadlock problems. tr isn't writing because it would rather read more first, and it hasn't got EOF. You aren't reading from tr because it hasn't written yet, so you aren't reading any more from the file either. To do this, you want to use select() (or poll()).
The only way execlp will return is if the exec fails; in that case you don't want to exit(0) as it's necessarily an error.

How to use redirection in C for file input

I need to get the file from the terminal, I know the command will look like:
./a.out < fileName.txt
I'm not sure how to use fgets() in my program to use the file requested from the terminal.
Using redirection sends the contents of the input file to stdin, so you need to read from stdin inside your code, so something like (error checking omitted for clarity)
#include <stdio.h>
#define BUFFERSIZE 100
int main (int argc, char *argv[])
{
char buffer[BUFFERSIZE];
fgets(buffer, BUFFERSIZE , stdin);
printf("Read: %s", buffer);
return 0;
}
1.) you close stdin then assign a different file handler to it
2.) replace stdin with any other file handler
using dup2 function you can achieve it
Short Answer
open() your file, then dup2() your file descriptor towards Standard Input.
A dummy example
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
int fd;
char *command[] = {"/usr/bin/sort", NULL};
if (close(STDIN_FILENO) < 0)
{
perror("Error close()");
exit(EXIT_FAILURE);
}
if (fd = open(argv[2], O_RDONLY, S_IWUSR | S_IRUSR) < 0)
{
perror("Error open()");
exit(EXIT_FAILURE);
}
if (dup2(fd, STDIN_FILENO) < 0)
{
perror("Error dup2()");
exit(EXIT_FAILURE);
}
if (execv(command[0], command) < 0)
{
perror("Error execv()");
exit(EXIT_FAILURE);
}
return EXIT_SUCCESS;
}
Output:
$ ./a.out < JohnLennon_Imagine_Lyrics.txt
Above us, only sky
A brotherhood of man
Ah
And no religion, too
And the world will be as one
And the world will live as one
But I'm not the only one
But I'm not the only one
I hope someday you'll join us
I hope someday you'll join us
Imagine all the people
Imagine all the people
Imagine all the people
Imagine no possessions
Imagine there's no countries
Imagine there's no heaven
It isn't hard to do
It's easy if you try
I wonder if you can
Livin' for today
Livin' life in peace
No hell below us
No need for greed or hunger
Nothing to kill or die for
Sharing all the world
You
You
You may say I'm a dreamer
You may say I'm a dreamer
You can use fread function
#include <stdio.h>
#include <malloc.h>
int main () {
fseek(stdin, 0, SEEK_END);
long size = ftell(stdin);
rewind(stdin);
char *buffer = (char*) malloc(size);
fread(buffer, size, 1, stdin);
printf("Buffer content:\n%s\n", buffer);
return 0;
}

Named pipes without child process

I used a FIFO for a simple read/write programme where the input from user is written to standard output by the writer function. The question is however, am I able to run this program without creating a child process (with the fork() operation). From what I see from examples about FIFOs, most read/write programmes with a named pipe/FIFO are done with 2 files - one for reading and one for writing. Could I do these all in a file?
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
/* read from user */
void reader(char *namedpipe) {
char c;
int fd;
while (1) {
/* Read from keyboard */
c = getchar();
fd = open(namedpipe, O_WRONLY);
write(fd, &c, 1);
fflush(stdout);
}
}
/* writes to screen */
void writer(char *namedpipe) {
char c;
int fd;
while (1) {
fd = open(namedpipe, O_RDONLY);
read(fd, &c, 1);
putchar(c);
}
}
int main(int argc, char *argv[]) {
int child,res;
if (access("my_fifo", F_OK) == -1) {
res = mkfifo("my_fifo", 0777);
if (res < 0) {
return errno;
}
}
child = fork();
if (child == -1)
return errno;
if (child == 0) {
reader("my_fifo");
}
else {
writer("my_fifo");
}
return 0;
}
You'll need to put a lock on the file, or else you could attempt to be reading when someone else is writing. You'll also want to flush the write buffer, or your changes to the fifo might actually not be recorded until the kernel write buffer fills and then writes to the file (in linux, write doesn't guarantee a write happens at that exact moment. i see you're flushing stdout, but you should also fsync on the file descriptor. This will cause the file to lock during any write operation so that no one else can write. In order to lock the file for reading, you might have to use a semaphore.

Resources