I\'m slowly writing a specialized web server application in C++ (using the C onion http server library and the JSONCPP library for JSON serialization, if t
A deficiency with the filestream API is that you cannot (at least not easily) access the file descriptor of an fstream (see here and here, for example). This is because there is no requirement that fstream is implemented in terms of FILE* or file descriptors (though in practice it always is). This is also required for using pipes as C++ streams.
Therefore the 'canonical' answer (as implied in the comments to the question) is:
create a stream buffer (derived from std::basic_streambuf) that uses Posix and C stdio I/O functions (i.e open etc) and thus gives access to the file descriptor.
Create your own 'LockableFileStream' (derived from std::basic_iostream) using your stdio based stream buffer instead of std::streambuf.
You may now have a fstream like class from which you may gain access to the file descriptor and thus use fcntl (or lockf) as appropriate.
There are a few libraries which provide this out of the box.
I had thought this was addressed partly now that we've reached C++17 but I can't find the link so I must have dreamed it.
Is the traditional unix-y solution of relying on the atomicity of rename() unacceptable?
I mean, unless your JSON serialization format supports in-place update (with a transaction log or whatever), then updating your password database entails rewriting the entire file, doesn't it? So you might as well write it to a temporary file, then rename it over the real name, thus ensuring that readers read a consistent entry? (Of course, in order for this to work each reader must open() the file each time it wants to access a DB entry, leaving the file open doesn't cut it)
My solution to this problem is derived from this answer: https://stackoverflow.com/a/19749019/5899976
I've only tested it with GCC 4.8.5.
#include <cstring> // for strerror()
#include <iostream> // for std::cerr
#include <fstream>
#include <ext/stdio_filebuf.h>
extern "C" {
#include <errno.h>
#include <sys/file.h> // for flock()
}
// Atomically increments a persistent counter, stored in /tmp/counter.txt
int increment_counter()
{
std::fstream file( "/tmp/counter.txt" );
if (!file) file.open( "/tmp/counter.txt", std::fstream::out );
int fd = static_cast< __gnu_cxx::stdio_filebuf< char > * const >( file.rdbuf() )->fd();
if (flock( fd, LOCK_EX ))
{
std::cerr << "Failed to lock file: " << strerror( errno ) << "\n";
}
int value = 0;
file >> value;
file.clear(); // clear eof bit.
file.seekp( 0 );
file << ++value;
return value;
// When 'file' goes out of scope, it's closed. Moreover, since flock() is
// tied to the file descriptor, it gets released when the file is closed.
}
You might want to use a separate lockfile rather than trying to get the descriptor from the ifstream
. It's much easier to implement, and you could probably wrap the ifstream
in a class that automates this.
If you want to ensure atomic open/lock, You might want to construct a stream using the method suggested in this SO answer, following open
and flock