I implemented forked multiple clients in c and they are all supposed to write to a common file. This has failed so far since the information from these sockets is all messed up in the file destination. This is my code
FILE *Ufptr;
Ufptr = fopen("Unsorted_busy_list.txt","a+");
fprintf(Ufptr, "%s:%d\n",inet_ntoa(newAddr.sin_addr),ntohs(newAddr.sin_port));
fclose(Ufptr);
I've been told that using fcntl and mutex lock onto the file could do, but am new to this and don't know how to implement this around the file writing process. Any help
As I mentioned in a comment, if the parent consumes the output from the children, it is usually easier to use an Unix domain datagram socket pair (or pair per child process). Unix domain datagram sockets preserve message boundaries, so that each datagram successfully sent using send()
is received in a single recv()
. You can even send data as binary structures. No locks are needed. If you use a socket pair per child, you can easily set the parent side to nonblocking, and use select()
or poll()
to read datagrams from all in a single loop.
Back to the question proper.
Here is an example implementation of append_file(filename, format, ...)
that uses POSIX.1-2008 vdprintf()
to write to the file, using fcntl()
-based advisory record locks:
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
int append_file(const char *filename, const char *format, ...)
{
struct flock lock;
va_list args;
int fd, cause;
/* Sanity checks. */
if (!filename || !*filename)
return errno = EINVAL;
/* Open the file for appending. Create if necessary. */
fd = open(filename, O_WRONLY | O_APPEND | O_CREAT, 0666);
if (fd == -1)
return errno;
/* Lock the entire file exclusively.
Because we use record locks, append_file() to the same
file is NOT thread safe: whenever the first descriptor
is closed, all record locks to the same file in the process
are dropped. */
lock.l_type = F_WRLCK;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0;
if (fcntl(fd, F_SETLKW, &lock) == -1) {
cause = errno;
close(fd);
return errno = cause;
}
if (format && *format) {
cause = 0;
va_start(args, format);
if (vdprintf(fd, format, args) < 0)
cause = errno;
va_end(args);
if (cause) {
close(fd);
return errno = cause;
}
}
/* Note: This releases ALL record locks to this file
in this process! */
if (close(fd) == -1)
return errno;
/* Success! */
return 0;
}
int main(int argc, char *argv[])
{
int arg = 1;
if (argc < 3) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
fprintf(stderr, " %s FILENAME STRING [ STRING ... ]\n", argv[0]);
fprintf(stderr, "\n");
}
for (arg = 2; arg < argc; arg++)
if (append_file(argv[1], "%s\n", argv[arg])) {
fprintf(stderr, "%s: %s.\n", argv[1], strerror(errno));
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
If all writers use the above append_file()
to append to the file, it is safe to rename the file at any point. (Just note that it is possible for one or more processes to do a final appending to the file after the rename, if they were waiting for the record lock to be released during the rename.)
To truncate the file, first take an exclusive lock on it, and then call ftruncate(fd, 0)
.
To read the file, take an fcntl()
-based shared lock F_RDLCK
(allowing other readers at the same time; or F_WRLCK
, if you intend to "atomically" truncate the file after you've read the current contents), or you may see a partial final record at the end.