Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

flock-ing a C++ ifstream on Linux (GCC 4.6)

Tags:

c++

linux

g++

flock

context

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 that matters)., for a Linux system with GCC 4.6 compiler (I don't care about portability to non Linux systems, or to GCC before 4.5 or to Clang before 3.0).

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

 { "_user" : "basile" ;
   "_crypasswd" : "XYZABC123" ; 
   "_email" : "[email protected]" ;
   "firstname" : "Basile" ;
   "lastname" : "Starynkevitch" ;
   "privileges" : "all" ;
 }

with the convention (à la .htpasswd) that the _crypasswd field is the crypt(3) "encryption" of the user password, salted by the _user name;

The reason I want to describe users by Json objects is that my application might add (not replace) some JSON fields (like e.g. privileges above) in such Json objects describing users. I'm using JsonCpp as a Json parsing library for C++. This library wants an ifstream to be parsed.

So I am reading my password file with

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 to flock or lockf the password file, to ensure that only one process is reading or writing it. To call these functions, I need to get the file descriptor (in Unix parlance) of the ifstream jsinpass. But Google gives me mostly Kreckel's fileno (which I find complete, but a bit insane) to get the file descriptor of an std::ifstream and I am not sure that the constructor won't pre-read some of it. Hence my question:

how can I lock a C++ ifstream (Linux, GCC 4.6) ?

(Or do you find some other way to tackle that issue?)

Thanks

like image 941
Basile Starynkevitch Avatar asked Dec 29 '11 11:12

Basile Starynkevitch


2 Answers

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.
}
like image 85
Droid Coder Avatar answered Sep 23 '22 14:09

Droid Coder


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

like image 30
Hasturkun Avatar answered Sep 19 '22 14:09

Hasturkun