I have built a IPC prgram with FIFO(named piped).
A very interesting problem is that my written message may be lost.
the following is the code snippet.
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
int main()
{
char buffer[2000] = {0};
strcpy(buffer, "abc");
char *write_path = "test-123";
mkfifo(write_path, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
int dummy = open(write_path, O_RDONLY | O_NONBLOCK);
int fd = open(write_path, O_WRONLY | O_NONBLOCK);
int bytes = write(fd, buffer, 2000);
printf("write_path:%d %s %d %d\n", bytes, write_path, dummy, fd);
close(fd);
close(dummy);
}
how to reproduce?
ubuntu 1804
gcc main.c -o main
./main
cat < test-123
it will be pending. I think, it should output abc.
You open the FIFO in nonblocking mode, which in means I/O functions can fail with (-1 and errno set to) EAGAIN or EWOULDBLOCK, instead of blocking (waiting) for the function to complete. In Linux, open descriptors to a FIFO have the same semantics as pipes.
If you bothered to check for errors (printing strerror(errno) whenever open() or write() returns -1 indicating an error), you'd already know the reason why this fails. As is, the code shown does not even reproduce the problem. The following code,
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
int main(int argc, char *argv[])
{
const char *msg = "The Example Message.\n";
const size_t msg_len = strlen(msg);
int rfd, wfd;
if (argc != 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
const char *mypath = (argc > 0 && argv && argv[0] && argv[0][0]) ? argv[0] : "(this)";
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", mypath);
fprintf(stderr, " %s FIFO\n", mypath);
fprintf(stderr, "\n");
fprintf(stderr, "This program tests opening and writing a short message to the FIFO.\n");
fprintf(stderr, "\n");
return EXIT_SUCCESS;
}
rfd = open(argv[1], O_RDONLY | O_NONBLOCK);
if (rfd == -1) {
fprintf(stderr, "%s: Cannot open FIFO for reading: %s.\n", argv[1], strerror(errno));
return EXIT_FAILURE;
}
wfd = open(argv[1], O_WRONLY | O_NONBLOCK);
if (wfd == -1) {
fprintf(stderr, "%s: Cannot open FIFO for writing: %s.\n", argv[1], strerror(errno));
return EXIT_FAILURE;
}
ssize_t n = write(wfd, msg, msg_len);
if (n == -1) {
fprintf(stderr, "%s: Cannot write to FIFO: %s.\n", argv[1], strerror(errno));
return EXIT_FAILURE;
} else
if (n != (ssize_t)msg_len) {
fprintf(stderr, "%s: Wrote only %zd of %zu bytes to the FIFO.\n", argv[1], n, msg_len);
return EXIT_FAILURE;
} else {
printf("Success!\n");
}
close(wfd);
close(rfd);
return EXIT_SUCCESS;
}
compiles (gcc -Wall -Wextra -O2 source.c -o binary) cleanly, and implements the shown code (but with error checking, and using a command line parameter for the name of the FIFO to be opened). It does not verify the named file is a FIFO, though. If run on a non-existent FIFO, it complains "FIFO: Cannot open FIFO for reading: No such file or directory."
If you create the FIFO (mkfifo test-fifo) and run the test program, there are no errors; the only output is "Success!".
If you create the FIFO, and repeatedly write data to it but never read from it, at some point the kernel buffer for the FIFO becomes full, and running the test program will report "test-fifo: Cannot write to FIFO: Resource temporarily unavailable." (which means that write() returned -1 with errno==EWOULDBLOCK or errno==EAGAIN).
You can simulate this by running e.g. bash -c 'exec 4<>test-fifo ; dd if=/dev/zero of=test-fifo bs=1 oflag=nonblock status=progress', which opens the test-fifo FIFO read-write (which in Linux always succeeds) to descriptor 4, then runs dd to fill it with zeroes, using Bash (sub-)shell. On my system, a FIFO can hold 65536 bytes (64k). After filling the FIFO like this, running the test program (./binary test-fifo) will fail as described.
Hopefully, you'll see the light and the importance and usefulness of error checking. It is NOT something you should consider as "I'll add them in later when/if I have time"; they are also an important development tool.
Related
I am trying to write to and then read from a file descriptor opened using shm_open. It works as I expect on Linux, but not on macOS (specifically macOS Monterey 12.5 21G72).
Here is the code:
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
int main(int argc, const char * argv[]) {
int fd = shm_open("/example", O_CREAT|O_RDWR, S_IRUSR|S_IWUSR);
if (fd < 0) {
printf("shm_open() failed %s (%d)\n", strerror(errno), errno);
return 1;
}
const char *buf = "hello world";
unsigned long len = strlen(buf);
ssize_t ret = write(fd, buf, len);
if (ret < 0) {
printf("write() failed %s (%d)\n", strerror(errno), errno);
return 1;
}
ret = lseek(fd, 0, SEEK_SET);
if (ret < 0) {
printf("lseek() failed %s (%d)\n", strerror(errno), errno);
return 1;
}
char *newbuf = calloc(len + 1, 1);
ret = read(fd, newbuf, len);
if (ret < 0) {
printf("read() failed %s (%d)\n", strerror(errno), errno);
return 1;
}
printf("read: %s\n", newbuf);
return 0;
}
On Linux the output is what I would expect:
$ cc main.c
$ ./a.out
read: hello world
On macOS I get this:
$ cc main.c
$ ./a.out
write() failed Device not configured (6)
Under Linux, the POSIX shared memory is typically backed by a tmpfs file system mounted on /dev/shm:
$ cat /proc/mounts | grep /dev/shm
tmpfs /dev/shm tmpfs rw,nosuid,nodev,inode64 0 0
The name passed to shm_open() is the name of the file entry corresponding to the shared memory area:
$ gcc main.c -lrt
$ ./a.out
read: hello world
$ ls -l /dev/shm
total 4
-rw------- 1 xxx xxx 11 sept. 17 08:53 example
The above file system is typically mounted at startup through /etc/fstab or by systemd.
Under MacOS, this manual says that there is no visible entry in the file system for the shared memory segments:
There is no visible entry in the file system for the created object in this implementation.
So, the low level implementation is different than the one in Linux. You may only be able to access the shared memory by adding a call to ftruncate() to set the size of the memory segment and use mmap() to map the content into the process address space as it is the way we normally use the shared memory. Any process wanting to access the area, will do the same except that only one should specify O_CREAT to shm_open() and call ftruncate() to create/resize the object:
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
int main(int argc, const char * argv[]) {
int flags = O_RDWR;
// Pass some parameter to trigger the creation of the object
if (argc > 1) {
flags |= O_CREAT;
}
int fd = shm_open("/example", flags, S_IRUSR|S_IWUSR);
if (fd < 0) {
printf("shm_open() failed %s (%d)\n", strerror(errno), errno);
return 1;
}
const char *buf = "hello world";
unsigned long len = strlen(buf);
if (argc > 1) {
ssize_t ret = ftruncate(fd, len + 1);
if (ret < 0) {
printf("ftruncate() failed %s (%d)\n", strerror(errno), errno);
return 1;
}
}
char *newbuf = (char *)mmap(NULL, len + 1, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if (newbuf == MAP_FAILED) {
printf("mmap() failed %s (%d)\n", strerror(errno), errno);
return 1;
}
if (argc > 1) {
memcpy(newbuf, buf, len + 1);
}
printf("read: %s\n", newbuf);
return 0;
}
Example of execution under Linux:
$ gcc main.c -lrt
$ ls -l /dev/shm
total 0
$ ./a.out
shm_open() failed No such file or directory (2)
$ ls -l /dev/shm
total 0
$ ./a.out creat
read: hello world
$ ls -l /dev/shm
total 4
-rw------- 1 xxx xxx 12 sept. 17 09:36 example
$ ./a.out
read: hello world
Additional information
MacOS is derived from BSD. The manual of the latter clearly specifies that operations like read() or write() on the resulting file descriptor return in error:
The result of using open(2), read(2), or write(2) on a shared memory object, or on the descriptor returned by shm_open(), is undefined. It is also undefined whether the shared memory object itself, or its contents, persist across reboots.
In FreeBSD, read(2) and write(2) on a shared memory object will fail with EOPNOTSUPP and neither shared memory objects nor their contents persist across reboots.
My intention was to open two files, where the second one would be brand new, with the same permissions as the first file. So to test my code I changed the first file permissions to "777". Then I proceeded to run my program. And to my surprise, the permission of the newborn file2 were wrong! They where set to 755. Even weirder is when I set the first file to "111" and tried again, the result now was "1204".
Can someone explain to me this weird behavior?
Here's my code
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
int main(int argc, char *args[]) {
struct stat stats;
int fd1, fd2;
fd1 = open("testfile.txt", O_RDONLY);
/* Error check*/
if (fd1 == -1) {
/* Error handling */
perror("Opening");
printf("Unable to open file: %s\n", "testfile.txt");
printf("ERROR: %s\n", strerror(errno));
return 1;
}
if(fstat(fd1, &stats) == -1)
{
printf("Error while getting stats: %s\n", strerror(errno));
exit(-1);
}
//Receives the output file as a main argument . . .
if (argc > 1)
{
//(stats.st_mode = Gets the mask of the first file)
fd2 = open(args[1], O_WRONLY|O_CREAT, stats.st_mode);
/* Error check*/
if (fd2 == -1) {
/* Error handling */
perror("Opening");
printf("Unable to open file: %s\n",args[1]);
printf("ERROR: %s\n", strerror(errno));
return 1;
}
}
//. . . if it doesn't it creates a standard one warning you about it
else
{
fd2 = open("Nope.txt", O_WRONLY|O_CREAT, stats.st_mode);
/* Error check*/
if (fd2 == -1) {
/* Error handling */
perror("Opening");
printf("Unable to open file: %s\n",args[1]);
printf("ERROR: %s\n", strerror(errno));
return 1;
}
printf("Standard file created\n");
}
close(fd1);
close(fd2);
return 0;
}
I tried to make it as tidy as I could :)
From open(2) man page on the part about O_CREATE:
The effective mode is modified by the process's umask in the usual way: in the absence of a default ACL, the mode of the created file is (mode & ~umask).
If you type umask in bash you can see what value is used and which bits get cleared from the mode you provide.
I am trying to figure out how to correctly use tee. In my application, tee always returns EINVAL for some reason. I was getting desperate, and tried to run the example application listed in the man page of tee (for example: https://linux.die.net/man/2/tee), only to find out that even that example code always fails with: tee: Invalid argument, for example when using it as follows: cat tee.c | ./tee tee.log. Any idea why this might happen?
The example code from die.net:
#define _GNU_SOURCE
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <limits.h>
int
main(int argc, char *argv[])
{
int fd;
int len, slen;
if (argc != 2) {
fprintf(stderr, "Usage: %s <file>\n", argv[0]);
exit(EXIT_FAILURE);
}
fd = open(argv[1], O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd == -1) {
perror("open");
exit(EXIT_FAILURE);
}
do {
/*
* tee stdin to stdout.
*/
len = tee(STDIN_FILENO, STDOUT_FILENO,
INT_MAX, SPLICE_F_NONBLOCK);
if (len < 0) {
if (errno == EAGAIN)
continue;
perror("tee");
exit(EXIT_FAILURE);
} else
if (len == 0)
break;
/*
* Consume stdin by splicing it to a file.
*/
while (len > 0) {
slen = splice(STDIN_FILENO, NULL, fd, NULL,
len, SPLICE_F_MOVE);
if (slen < 0) {
perror("splice");
break;
}
len -= slen;
}
} while (1);
close(fd);
exit(EXIT_SUCCESS);
}
tee requires a pipe for both file descriptors, fd_in and fd_out.
Your invocation does not supply a pipe for the second file descriptor, but a file descriptor referring to a TTY. Note also that the example in the manpage specifically uses a trailing | cat:
The example below implements a basic tee(1) program using the tee() system call.
Here is an example of its use:
$ date |./a.out out.log | cat
Tue Oct 28 10:06:00 CET 2014
$ cat out.log
Tue Oct 28 10:06:00 CET 2014
Not using a pipe file descriptor for the second (or first, for that matter) argument would qualify for EINVAL:
EINVAL fd_in or fd_out does not refer to a pipe; or fd_in and fd_out refer to
the same pipe.
So I am playing around with the idea of ports and client/server communication.
I have a server.c program that can open a port, open a listening descriptor, and upon receiving a connection, fork a child to handle communication with a connecting client. I have a client.c program that takes in 5 commandline arguments. Basically the first 3 arguments are practice strings to send to server and the 4th is hostname and the 5th is the port number.
So far connecting these two has worked fine, however, when client tries to write the 3 different strings (argv[1],argv[2], and argv[3]) to the server.c, server.c seems to only be able to read the first one then it seems to be stuck and not continue on with the additional reads even though client will finish writing all the strings to the communication file descriptor. I have been stuck for over 4 hours trying to figure out what should have been a simple practice program to better learn servers and clients. I don't wanna get anymore lost then I already am so I hope someone could anyone give me any advice on how to handle this issue or what I am doing wrong.
Client.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include "uici.h"
#include "func.h"
int main(int argc, char *argv[]){
int fd;
u_port_t portnum;
if(argc != 6){
fprintf(stderr, "Usage: %s string1 string2 string3 host port\n",argv[0]);
return -1;
}
portnum = (u_port_t)atoi(argv[5]);
if((fd = u_connect(portnum, argv[4])) == -1){
perror("Failled to establish connection");
return 1;
}
fprintf(stderr, "[%ld]:connection made to %s\n", (long)getpid(), argv[4]);
if((write(fd, argv[3], strlen(argv[3])+1)) == -1){
fprintf(stderr, "Failed to write %s to fd", argv[3]);
r_close(fd);
return 0;
}
if((write(fd, argv[1], strlen(argv[1])+1)) == -1){
fprintf(stderr, "Failed to write %s to fd", argv[1]);
r_close(fd);
return 0;
}
if((write(fd, argv[2], strlen(argv[2])+1)) == -1){
fprintf(stderr, "Failed to write %s to fd", argv[2]);
close(fd);
return 0;
}
fprintf(stderr, "Everything has been written\n");
return 0;
}
Server.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "func.h"
#include "uici.h"
int main(int argc, char *argv[])
{
u_port_t portnumber;
int listenfd;
int fd;
char client[MAX_CANON];
int bytes_copied;
pid_t child;
if (argc != 2) {
fprintf(stderr, "Usage: %s port\n", argv[0]);
return 1;
}
portnumber = (u_port_t) atoi(argv[1]);
if ((listenfd = u_open(portnumber)) < 0) {
perror("Listen endpoint creation failed");
return 1;
}
fprintf(stderr, "[%ld]: Waiting for the first connection on port %d\n",
(long)getpid(), (int)portnumber);
for ( ; ; ) {
if ((fd = u_accept(listenfd, client, MAX_CANON)) != -1) {
fprintf(stderr, "[%ld]: A connection has been received from %s\n",
(long) getpid(), client);
if ((child = fork()) == -1)
perror("Could not fork a child");
if (child == 0) { /* child code */
r_close(listenfd);
int MAXSZ = 1024;
char str3[MAXSZ];
char str1[MAXSZ];
char str2[MAXSZ];
int bytesread = 0;
fprintf(stderr, "Beginning the reads\n");
read(fd,str3, MAXSZ);
fprintf(stderr, "Finished 1st read\n");
read(fd,str1, MAXSZ);
fprintf(stderr, "Finished 2nd read\n");
read(fd,str2, MAXSZ);
fprintf(stderr, "str3: %s\n",str3);
fprintf(stderr, "str1 = %s\n",str1);
fprintf(stderr, "str2 = %s\n",str2);
close(fd);
return 0;
} else { /* parent code */
close(fd);
while (waitpid(-1, NULL, WNOHANG) > 0) ; /* clean up zombies */
}
}
else
perror("Accept failed");
}
}
First child forked closes the listener with r_close(listenfd);
Then when client send argv[1] no listener are available and, I think, u_accept(listenfd, client, MAX_CANON) return an error because of listenfd is not valid.
Note: There is no guarantee that read or write will read or write all the data from/to the file descriptor, and no guarantee that the data blocks the client is writing will be read in the same way on the server.
You have to make sure to check the bytes written and continue to write data to the fd until you have written them all. When reading, you have to have the client send some kind of header data describing the amount of data that is to be expected, or in your case, you could read 1 byte at a time and look for a '\0' character, indicating the end of a string.
Since MAXSZ is likely larger than the strings you are sending, the server could be reading all of the strings at once into the first buffer, then blocking on the subsequent read.
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.