Named pipe, read fail with bad file descriptor - c

I'm beginning with pipes under Linux and I have a problem with my code.
I wanted to test sending an integer through the fifo so I code a small program to test it.
First I open the read only descriptor and then the write only one as precised in the documentation.
I send the number to the pipe and close the write descriptor.
However, when I'm trying to read from the pipe, it says that I have a bad file descriptor, I don't understand why it's not working because as the descriptor is the good one and is oppened with good option (O_RDONLY | O_NONBLOCK).
Here is my code :
void main(void)
{
int modePipeWrite, modePipeRead;
if(mkfifo("test.fifo", 0777) == -1)
{
perror("mkfifo");
exit(EXIT_FAILURE);
}
if(modePipeRead = open("test.fifo", O_RDONLY | O_NONBLOCK) == -1)
{
perror("openRead");
exit(EXIT_FAILURE);
}
if(modePipeWrite = open("test.fifo", O_WRONLY | O_NONBLOCK) == -1)
{
perror("openWrite");
exit(EXIT_FAILURE);
}
int n = 0;
if(write(modePipeWrite, &n, sizeof(n)) == -1)
{
perror("write");
}
printf("Send value: %d\n", n);
close(modePipeWrite);
int mode;
while(1)
{
if(read(modePipeRead, &mode, sizeof(mode)) == -1)
{
perror("read");
}
printf("Received value: %d\n", getpid(), mode);
sleep(1);
}
}
And the output :
./a.out
Send value: 0
read: Bad file descriptor
Received value: -1877110288
read: Bad file descriptor
Received value: -1877110288
read: Bad file descriptor
Received value: -1877110288
I don't understand what could be wrong here. If someone have some advices I would be glad to hear it.

Your code has a couple of issues, which you would've found if you compiled it with all warnings enabled. If you use gcc, the following options are nice/mandatory:
-Wall -Wextra -Werror -pedantic
One issue is that you don't assign the return value of open() to the file descriptors. You need to add braces around the assignment, or move it out of the if-statements.
Here's a working example, using a thread to read:
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <pthread.h>
static void *reader(void *arg)
{
int fd, val;
if ((fd = open("test.fifo", O_RDONLY)) == -1) {
perror("openRead");
exit(EXIT_FAILURE);
}
if (read(fd, &val, sizeof val) == -1)
perror("read");
else
printf("Received value: %d\n", val);
close(fd);
return NULL;
}
int main(void)
{
int modePipeWrite, modePipeRead;
pthread_t readerid;
if (mkfifo("test.fifo", 0777) == -1) {
perror("mkfifo");
exit(EXIT_FAILURE);
}
pthread_create(&readerid, NULL, reader, NULL);
sleep(1);
if ((modePipeWrite = open("test.fifo", O_WRONLY)) == -1) {
perror("openWrite");
exit(EXIT_FAILURE);
}
int n = 1234;
if (write(modePipeWrite, &n, sizeof n) == -1)
perror("write");
else
printf("Sent value: %d\n", n);
sleep(1);
close(modePipeWrite);
return 0;
}

Related

Difference in kqueue handling of fifos between Mac OS and FreeBSD?

I'm working on an application that uses fifos for IPC and uses an event-notification API (such as epoll or kqueue) to monitor the fifos for data to be read.
The application expects that if the writer for a fifo terminates that the reader will receive an event via the event notification API, allowing the reader to notice that the writer terminated.
I'm currently porting this application to macos and I'm running into some odd behavior with kqueue. I've been able to create a reproducer of this behavior:
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/event.h>
#include <sys/time.h>
#include <sys/errno.h>
static int child() {
char child_fifo_path[64];
char parent_fifo_path[64];
printf("Child %d\n", getpid());
sprintf(child_fifo_path, "/tmp/child-%d", getpid());
sprintf(parent_fifo_path, "/tmp/parent-%d", getpid());
mkfifo(child_fifo_path, 0644);
mkfifo(parent_fifo_path, 0644);
int parent_fd = open(parent_fifo_path, O_RDONLY);
if (parent_fd == -1) {
perror("open");
return EXIT_FAILURE;
}
unsigned char parent_val;
read(parent_fd, &parent_val, 1);
printf("Received %hhx from parent\n", parent_val);
int child_fd = open(child_fifo_path, O_WRONLY);
if (child_fd == -1) {
perror("open");
return EXIT_FAILURE;
}
write(child_fd, &parent_val, 1);
printf("Wrote %hhx to parent\n", parent_val);
close(parent_fd);
close(child_fd);
return EXIT_SUCCESS;
}
static int parent(pid_t child_pid) {
char child_fifo_path[64];
char parent_fifo_path[64];
printf("Parent %d\n", getpid());
sprintf(child_fifo_path, "/tmp/child-%d", child_pid);
sprintf(parent_fifo_path, "/tmp/parent-%d", child_pid);
int result = -1;
while (result == -1) {
struct stat buf;
result = stat(child_fifo_path, &buf);
if (result == -1) {
if (errno != ENOENT) {
perror("open");
return EXIT_FAILURE;
}
}
}
unsigned char val = 20;
int parent_fd = open(parent_fifo_path, O_WRONLY);
if (parent_fd == -1) {
perror("open");
return EXIT_FAILURE;
}
write(parent_fd, &val, 1);
printf("Wrote %hhx to child\n", val);
int child_fd = open(child_fifo_path, O_RDONLY);
if (child_fd == -1) {
perror("open");
close(parent_fd);
return EXIT_FAILURE;
}
int kq = kqueue();
struct kevent event;
EV_SET(&event, child_fd, EVFILT_READ, EV_ADD, 0, 0, 0);
result = kevent(kq, &event, 1, NULL, 0, NULL);
if (result == -1) {
perror("kevent");
close(child_fd);
close(parent_fd);
return EXIT_FAILURE;
}
int done = 0;
while (!done) {
memset(&event, 0, sizeof(event));
printf("Waiting for events\n");
result = kevent(kq, NULL, 0, &event, 1, NULL);
if (result == -1) {
perror("kevent");
close(child_fd);
close(parent_fd);
return EXIT_FAILURE;
}
if (event.ident == child_fd) {
if (event.flags & EV_EOF) {
printf("Child exited\n");
done = 1;
}else if ( event.data > 0 ) {
unsigned char child_val;
result = read(child_fd, &child_val, 1);
if (result == -1) {
perror("read");
return EXIT_FAILURE;
}
printf("Received %hhx from child\n", child_val);
}
}
}
return EXIT_SUCCESS;
}
int main(int argc, char *argv[]) {
pid_t child_pid = fork();
if (child_pid == -1) {
perror("fork");
return EXIT_FAILURE;
}
if (child_pid) {
return parent(child_pid);
} else {
return child();
}
}
This reproducer forks a child process, which creates 2 fifos: /tmp/parent-$CHILD_PID and /tmp/child-$CHILD_PID. The parent waits until /tmp/parent-$CHILD_PID is created and then writes a byte to it. The child opens /tmp/parent-$CHILD_PID and blocks to read the byte written by the parent. Once complete, the child goes to write that same byte to the parent via /tmp/child-$CHILD_PID. The parent uses kqueue to observe the write to /tmp/child-$CHILD_PID.
This sequence of events works fine.
The issue occurs when the child closes its file referring to /tmp/child-$CHILD_PID. I'm seeing that this event is not reported to the parent via kqueue.
The most interesting part: this code works as I would expect on FreeBSD.
Version info:
Mac OS X: 10.11.6
FreeBSD 10.4-RELEASE-p3
Is there a difference between kqueue on macos and FreeBSD in this context? If so, is there some documentation that documents this difference?
This is not the best answer to your question but I hope can help you find other differences that may influence your code behavior when using kqueue between macOS and FreeBSD
In my case I use kqueue EVFILT_VNODE to check for changes, but based on the operating system I need to define different flags openModeDir when using the syscall.Open
For macOS (openmode_darwin.go) I use this:
openModeDir = syscall.O_EVTONLY | syscall.O_DIRECTORY
openModeFile = syscall.O_EVTONLY
And for FreeBSD (openmode.go) I use:
openModeDir = syscall.O_NONBLOCK | syscall.O_RDONLY | syscall.O_DIRECTORY
openModeFile = syscall.O_NONBLOCK | syscall.O_RDONLY
From macOS docs open(2), this is the flag description:
O_EVTONLY descriptor requested for event notifications only
And from FreeBSD open(2), there is no O_EVTONLY.
Putting all together this is how I call kqueue:
...
watchfd, err := syscall.Open(dir, openModeDir, 0700)
if err != nil {
return err
}
kq, err := syscall.Kqueue()
if err != nil {
syscall.Close(watchfd)
return err
}
ev1 := syscall.Kevent_t{
Ident: uint64(watchfd),
Filter: syscall.EVFILT_VNODE,
Flags: syscall.EV_ADD | syscall.EV_ENABLE | syscall.EV_CLEAR,
Fflags: syscall.NOTE_WRITE | syscall.NOTE_ATTRIB,
Data: 0,
}
...
I am using go, but as mentioned before hope can give you an idea while dealing with Kqueue, In my case this simple change of flags made a difference.

Program with mkfifo() and open() cannot exit

I am trying to use FIFO for interprocessing. But when trying to create a FIFO and then open it, my program hangs (cannot exit).
if (mkfifo("./fifo.txt", S_IRUSR | S_IWUSE) < 0) {
fprint("Can not create fifo");
return 1;
}
if ((readfd = open("./fifo.txt", O_RDONLY)) < 0) {
return 1;
}
What am I doing wrong here?
Thank you very much.
Read fifo(7), notably:
Normally, opening the FIFO blocks until the other end is opened also.
So I guess that your call to open(2) is blocked. Perhaps you want to pass the O_NONBLOCK flag.
You should use strace(1) to debug your program (and perhaps also strace the other program on the other end of the fifo). And call perror(3) on error.
Perhaps using unix(7) sockets could be more relevant in your case. You can then poll(2) before accept(2)
You should read Advanced Linux Programming.
Here is an example code:
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
void child(void)
{
int fd = 0;
if ((fd = open("./fifo.txt", O_WRONLY)) < 0) {
return;
}
write(fd, "hello world!", 12);
}
void parent(void)
{
int fd = 0;
if ((fd = open("./fifo.txt", O_RDONLY)) < 0) {
return;
}
char buf[36] = {0};
read(fd, buf, 36);
printf("%s\n", buf);
}
int main(void)
{
pid_t pid = 0;
if (mkfifo("./fifo.txt", S_IRUSR | S_IWUSR) < 0) {
printf("Can not create fifo\n");
return 1;
}
pid = fork();
if (pid == 0) {
printf("child process\n");
child();
} else if (pid < 0) {
printf("fork error\n");
return -1;
}
parent();
}

Simple shell with indirect input

I am writing a simple code to implement the indirect input function for a unix/linux shell.
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
extern void error(char* message);
void
cisshRedirectedInput(char* command[], char* inputFile)
{
//Try to implement the RedirectInput from here
pid_t pid;
int status;
int fd;
//For the child process
if ((pid=fork())==0)
{
//Try to input files, failing on an error
fd=open(inputFile,O_RDONLY);//To read input file
if(fd < 0)
{
error("sampleSh: error opening standard input file");
exit(1);
}
//use dup() to copy file
close(1);
if(dup(fd) < 0)
{
error("sampleSh: error duplicating standard input");
perror("dup()");
exit(1);
}
//Close file and exec()
close(fd);
execvp(command[0], command);
//If failure in any case
error("sampleSh: failure to execute command");
exit(1);
}
else
{
/* This is the parent process.
* Wait for the child to terminate.
*/
if(wait(&status) < 0)
{
error("sampleSh: error waiting for child.");
perror("wait");
}
if(status != 0)
error("sampleSh: command exited with nonzero error status.");
}
}
However, after compilation (no error reported), but when I try (fileList created already)
sort -r <fileList
The shell just stuck there without giving me answer, what is the problem please?
The standard input file descriptor is 0 (or STDIN_FILENO), not 1 (or STDOUT_FILENO).
Either use:
int fd = open(inputFile, O_RDONLY);
if (fd < 0) …
close(0);
if (dup(fd) < 0) …
close(fd);
Or:
int fd = open(inputFile, O_RDONLY);
if (fd < 0) …
if (dup2(fd, 0) < 0) …
close(fd);
It is good that your code does the close(fd) after duplicating to a standard I/O descriptor — that is almost always correct. It's also good that you are checking that the key system calls succeed. (There isn't much you can do if close() fails.)
This simple modification of your code (key change: use close(0); instead of close(1);) works for me. Did you null terminate your argument list?
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
static inline void error(char *message)
{
fprintf(stderr, "%s\n", message);
}
void
cisshRedirectedInput(char *command[], char *inputFile);
void
cisshRedirectedInput(char *command[], char *inputFile)
{
// Try to implement the RedirectInput from here
pid_t pid;
int status;
int fd;
// For the child process
if ((pid = fork()) == 0)
{
// Try to input files, failing on an error
fd = open(inputFile, O_RDONLY); // To read input file
if (fd < 0)
{
error("sampleSh: error opening standard input file");
exit(1);
}
// use dup() to copy file
close(0);
if (dup(fd) < 0)
{
error("sampleSh: error duplicating standard input");
perror("dup()");
exit(1);
}
// Close file and exec()
close(fd);
execvp(command[0], command);
// If failure in any case
error("sampleSh: failure to execute command");
exit(1);
}
else
{
/* This is the parent process.
* Wait for the child to terminate.
*/
if (wait(&status) < 0)
{
error("sampleSh: error waiting for child.");
perror("wait");
}
if (status != 0)
error("sampleSh: command exited with nonzero error status.");
}
}
int main(void)
{
char *args[] = { "sort", "-r", 0 };
cisshRedirectedInput(args, "fileList");
return 0;
}
Input file:
bash-assoc-arrays.sh
cissh.c
fileList
kwargs.py
makefile
posixver.h
rangeinc.c
select.c
spc.py
testcsv.py
uncrustify.bug
yield.py
Output:
yield.py
uncrustify.bug
testcsv.py
spc.py
select.c
rangeinc.c
posixver.h
makefile
kwargs.py
fileList
cissh.c
bash-assoc-arrays.sh

What is wrong with the function shmdt()?

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#define BUFSZ 2048
int main()
{
int shmid,i,fd,nwrite,nread;
char *shmadd;
char buf[5];
buf[5] = '\0';
if((shmid=shmget(IPC_PRIVATE,BUFSZ,0x666))<0)
{
perror("shmget");
exit(1);
}
else
printf("created shared-memory: %d\n",shmid);
if((shmadd=shmat(shmid,0,0))<(char *)0)
{
perror("shmat");
exit(1);
}
else
printf("attached shared-memory\n");
shmadd="Hello";
if((fd = open("share",O_CREAT | O_RDWR,0666))<0)
{
perror("open");
exit(1);
}
else
printf("open success!\n");
if((nwrite=write(fd,shmadd,5))<0)
{
perror("write");
exit(1);
}
else
printf("write success!\n");
lseek( fd, 0, SEEK_SET );
if((nread=read(fd,buf,5))<0)
{
perror("read");
exit(1);
}
else
printf("read %d form file:%s\n",nread,buf);
if(close(fd) == -1)
printf("close fd fails!\n");
else
printf("close fd succeeds!\n");
if((shmdt(shmadd))<0)
{
perror("shmdt");
exit(1);
}
else
printf("deleted shared-memory\n");
exit(0);
}
Above is the code which is to demo the shared memory in Linux. And the running result is below:
$ ./ex2
created shared-memory: 1572887
attached shared-memory
open success!
write success!
read 5 form file:Hello
close fd succeeds!
shmdt: Invalid argument
As you can see, everything goes fine except shmdt(). Why it fails?
Further:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#define BUFSZ 2048
int main()
{
int shmid,i,fd,nwrite,nread;
char *shmadd;
char buf[5];
buf[5] = '\0';
if((shmid=shmget(IPC_PRIVATE,BUFSZ,0x666)) < 0)
{
perror("shmget");
exit(1);
}
else
printf("created shared-memory: %d\n",shmid);
if((shmadd=shmat(shmid,0,0)) < (char *)0)
{
perror("shmat");
exit(1);
}
else
printf("attached shared-memory\n");
strcpy(shmadd, "Hello");
if((fd = open("share",O_CREAT | O_RDWR,0666)) < 0)
{
perror("open");
exit(1);
}
else
printf("open success!\n");
if((nwrite=write(fd,shmadd,5)) < 0)
{
perror("write");
exit(1);
}
else
printf("write success!\n");
lseek( fd, 0, SEEK_SET );
if((nread=read(fd,buf,5)) < 0)
{
perror("read");
exit(1);
}
else
printf("read %d form file:%s\n",nread,buf);
if(close(fd) == -1)
printf("close fd fails!\n");
else
printf("close fd succeeds!\n");
if((shmdt(shmadd))<0)
{
perror("shmdt");
exit(1);
}
else
printf("deleted shared-memory\n");
exit(0);
}
Following your answers, I changed the code as above. But now I got new error!
$ ./ex2
created shared-memory: 2129948
attached shared-memory
Segmentation fault (core dumped)
I seems that strcpy causes the error. But why?
This is the problem:
shmadd="Hello";
This changes the shmadd pointer to point to a string in memory. I think you intended to copy the string into the shared memory. To do that, you would do:
strcpy(shmadd,"Hello");
Also note that your check for errors is wrong, it should be:
if((shmadd=shmat(shmid,0,0)) == (void *)-1) { ... error ... }
And your permissions should be octal, not hex:
if((shmid=shmget(IPC_PRIVATE,BUFSZ,0666)) < 0)
Your check for an error with shmadd in this line is incorrect:
if((shmadd=shmat(shmid,0,0)) < (char *)0)
You need to do an explicit comparison to -1 as per the man page, as a -1 cast to a pointer becomes an unsigned value (0xFFFFFFFF on 32-bit systems). Replace that line with this one:
if((shmadd=shmat(shmid,0,0)) == (char *)-1)
and you'll get a permission denied error because you are not root.

Why read() wont return zero when reading from pipe

I am having a problem with my assignment I have due for class. I have to create a read/write program that will read a text file into it and write the contents to a new text file. The thing is, I have to use parent/child processes and piping. I have to pass the contents into the pipe with one child, and use another child to read the data from the pipe and write it to a new file.
I have three files: parent.c, read.c and write.c. The program works fine for the most part! It even transfers the data from one file to the other perfectly. The problem I am having is that the write.c process will never complete. I think it may have something to do with the reading from pipe(won't return 0 or EOF). Here is my source code:
parent.c
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#define BUFF_SIZE 255
int main(int ac, char* av[])
{
if(ac <3)
{
printf("Please enter all required arguments!\n");
exit(0);
}
int pfd[2];
int pipeCreated;
char readFile[50];
char writePipe[20];
pid_t child_pid_read;
pid_t child_pid_write;
pipeCreated = pipe(pfd);
if(pipeCreated == -1)
{
printf("An error occurred when trying to create a pipe\n");
exit(0);
}
strcpy(readFile, av[1]);
sprintf(writePipe,"%d", pfd[1]);
child_pid_read = fork();
char writeFile[50];
char readPipe[20];
//Handling the read()
switch(child_pid_read)
{
//Error in case forfk() failed
case -1:
perror("fork failed");
return 1;
//Handle child processes
case 0:
if(close(pfd[0]) == -1)
{
printf("An error occurred while closing the pipe\n");
exit(0);
}
if(execle("./read.out", "./read.out", readFile, writePipe, (char*)0, NULL) == -1)
{
printf("Child: Error creating read.\n");
exit(0);
}
default:
wait(&child_pid_read);
strcpy(writeFile, av[2]);
sprintf(readPipe,"%d", pfd[0]);
child_pid_write = fork();
break;
}
//Handling the write
switch(child_pid_write)
{
//Error in case fork() failed
case -1:
perror("fork failed");
return 1;
//Handle child processes
case 0:
if(close(pfd[1]) == -1)
{
printf("An error occurred while closing the pipe\n");
exit(0);
}
if(execle("./write.out", "./write.out", writeFile, readPipe, (char*)0, NULL) == -1)
{
printf("Child: Error creating read.\n");
exit(-1);
}
break;
default:
wait(&child_pid_write);
break;
}
printf("Write completed!");
return 0;
}
read.c:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#define BUFF_SIZE 16
int main(int ac, char* av[])
{
char buffer[BUFF_SIZE];
int fd;
int pid;
if(ac > 1)
{
fd = open(av[1], O_RDONLY);
if(fd == -1)
{
printf("error: Could Not Open File\n");
exit(0);
}
pid = atoi(av[2]);
}
int num_read = 1;
while(1)
{
num_read = read(fd, buffer, BUFF_SIZE);
if(num_read == -1)
{
printf("Error reading file\n");
exit(0);
}
if(num_read == 0)
{
break;
}
if(write(pid, buffer, num_read) != num_read)
{
printf("Error writing to pipe\n");
break;
}
}
close(fd);
return 1;
}
write.c
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#define BUFF_SIZE 1
int main(int ac, char* av[])
{
char buffer[BUFF_SIZE];
int fd = open(av[1], O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
int pid = atoi(av[2]);
int num_read = 1;
while(1)
{
num_read = read(pid, buffer, BUFF_SIZE);
printf("num_read: %d\n", num_read);
if(num_read == -1)
{
printf("Error reading pipe\n");
break;
}
if(write(fd, buffer, num_read) != num_read)
{
printf("Error writing to file\n");
break;
}
if(num_read == EOF)
{
break;
}
}
close(fd);
return 1;
}
Please look over my code and suggest corrections. I am passing in the names of the text files through the terminal (./parent.out, oldFile.txt, newFile.txt).
Two problems:
You're not forking the write process until after wait() for the read process returns. If the read process tries to write more data than will fit in the pipe buffer, it will block and never exit. You need to allow both processes to run concurrently to avoid this deadlock. It will work with a small file, but if the file is bigger than 4KB it will hang.
After forking the write process, the parent process has to close pfd[0]. The reader of a pipe doesn't get EOF until all processes that have the write end open close it. It should be:
default:
if(close(pfd[0]) == -1)
{
printf("An error occurred while closing the pipe\n");
exit(0);
}
wait(&child_pid_write);
break;
Your child want to read data,why you close the fd[0], return from pipe indicating that fd[0] for reading and fd[1] for writing.As i can't add a comment, i have to post the comment here....

Resources