Flock-ing C ++ ifstream on Linux (GCC 4.6)

Context

I am slowly writing a specialized web server application in C ++ (using the C moon http server library and the JSONCPP library to serialize JSON, if that matters). for a Linux system with GCC 4.6 compiler (I'm not interested in portability for non-Linux systems or for GCC up to 4.5 or Clang up to 3.0).

I decided to keep the user "database" (there will be very few users, perhaps one or two, so performance is not a concern, and O (n) access time is acceptable) in JSON format, possibly like a small array of JSON objects such as

{ "_user" : "basile" ; "_crypasswd" : "XYZABC123" ; "_email" : " basile@starynkevitch.net " ; "firstname" : "Basile" ; "lastname" : "Starynkevitch" ; "privileges" : "all" ; } 

with the agreement (Γ  la .htpasswd ) that the _crypasswd field is crypt (3) "encrypt" the user password salted under the name _user ;

The reason I want to describe users with Json objects is because my application can add (not replace) some JSON fields (e.g. privileges above) in such Json objects describing users. I am using JsonCpp as a Json parsing library for C ++. This library wants to analyze ifstream .

So, I am reading the password file

 extern char* iaca_passwd_path; // the path of the password file std::ifstream jsinpass(iaca_passwd_path); Json::Value jpassarr; Json::Reader reader; reader.parse(jsinpass,jpassarr,true); jsinpass.close(); assert (jpassarr.isArray()); for (int ix=0; ix<nbu; ix++) { const Json::Value&jcuruser= jpassarr[ix]; assert(jcuruser.isObject()); if (jcuruser["_user"].compare(user) == 0) { std::string crypasswd = jcuruser["_crypasswd"].asString(); if (crypasswd.compare(crypted_password(user,password)) == 0) { // good user } } } 

Question

Obviously, I want a flock or lockf password file to make sure that only one process reads or writes it. To call these functions, I need to get a file descriptor (on Unix parlance) ifstream jsinpass . But Google gives me basically Kreckel fileno (which I find complete, but a little crazy) to get the std::ifstream file descriptor, and I'm not sure if the constructor will not read some of them beforehand. Hence my question :

how can i block c ++ ifstream (linux, gcc 4.6)?

(Or will you find another way to solve this problem?)

thanks

+5
source share
4 answers

You might want to use a separate lock file rather than trying to get a handle from ifstream . This is much easier to implement, and you could possibly wrap the ifstream in a class that automates this.

If you want to provide atomic open / lock, you might want to create a stream using the method suggested in this SO answer after open and flock

+1
source

The disadvantage of the filestream API is that you cannot (at least easily ) access the file descriptor of the fstream file (see here and here ). This is due to the fact that there is no requirement that fstream is implemented from the point of view of FILE * or file descriptors (although this is always the case in practice). It is also necessary for use as C ++ streams.

Therefore, the "canonical" answer (as implied in the comments to the question):

create a stream buffer (obtained from std :: basic_streambuf) that uses the 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 stream buffer instead of std :: streambuf.

Now you can have a class of type fstream from which you can access the file descriptor and, accordingly, use fcntl (or lockf).

There are several libraries that provide this out of the box.

I thought this was partially considered in part when we reached C ++ 17, but I cannot find the link, so I must have dreamed about it.

+1
source

My solution to this problem stems from this answer: fooobar.com/questions/113124 / ...

I only tested this 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 closed. Moreover, since flock() is // tied to the file descriptor, it gets released when the file is closed. } 
+1
source

Is the traditional unix-y solution to rely on atomicity of rename () unacceptable?

I mean, if your JSON serialization format does not support in-place updating (with a transaction log or something else), then updating the password database entails overwriting the entire file, right? So you could write it to a temporary file and then rename it over the real name, which would allow readers to read the sequential write? (Of course, in order for this to work, each reader must open () the file every time he wants to access the database record, leaving the file open, did not cut it)

0
source

Source: https://habr.com/ru/post/1388447/


All Articles