Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to open file in exclusive mode in C++

Tags:

c++

io

I am implementing some file system in C++. Up to now I was using fstream but I realized that it is impossible to open it in exclusive mode. Since there are many threads I want to allow multiple reads, and when opening file in writing mode I want to open the file in exclusive mode?
What is the best way to do it? I think Boost offers some features. And is there any other possibility? I would also like to see simple example. If it is not easy / good to do in C++ I could write in C as well.

I am using Windows.

like image 364
rank1 Avatar asked Apr 22 '13 06:04

rank1


3 Answers

On many operating systems, it's simply impossible, so C++ doesn't support it. You'll have to write your own streambuf. If the only platform you're worried about is Windows, you can possibly use the exclusive mode for opening that it offers. More likely, however, you would want to use some sort of file locking, which is more precise, and is available on most, if not all platforms (but not portably—you'll need LockFileEx under Windows, fcntl under Unix).

Under Posix, you could also use pthread_rwlock. Butenhof gives an implementation of this using classical mutex and condition variables, which are present in C++11, so you could actually implement a portable version (provided all of the readers and writers are in the same process—the Posix requests will work across process boundaries, but this is not true for the C++ threading primitives).

like image 156
James Kanze Avatar answered Nov 07 '22 23:11

James Kanze


if your app only works on Windows, the win32 API function "CreateFile()" is your choice.

For example: HANDLE hFile = ::CreateFileW(lpszFileFullPathName, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, NULL, NULL);

like image 28
skyfree Avatar answered Nov 07 '22 22:11

skyfree


If you are open to using boost, then I would suggest you use the file_lock class. This means you want to keep the filename of the files you open/close because fstream does not do so for you.

They have two modes lock() that you can use for writing (i.e. only one such lock at a time, the sharable lock prevents this lock too) and lock_sharable() that you can use for reading (i.e. any number of threads can obtain such a lock).

Note that you will find it eventually complicated to manage both, read and write, in this way. That is, if there is always someone to read, the sharable lock may never get released. In that case, the exclusive lock will never be given a chance to take....

// add the lock in your class
#include <boost/interprocess/sync/file_lock.hpp>
class my_files
{
...
private:
    ...
    boost::file_lock     m_lock;
};

Now when you want to access a file, you can lock it one way or the other. If the thread is in charge of when they do that, you could add functions for the user to have access to the lock. If your implementation of the read and write functions in my_files are in charge, you want to get a stack based object that locks and unlocks for you (RAII):

class safe_exclusive_lock
{
public:
    safe_exclusive_lock(file_lock & lock)
        : m_lock_ref(lock)
    {
        m_lock_ref.lock();
    }
    ~safe_exclusive_lock()
    {
        m_lock_ref.unlock();
    }
private:
    file_lock & m_lock_ref;
};

Now you can safely lock the file (i.e. you lock, do things that may throw, you always unlock before exiting your current {}-block):

ssize_t my_files::read(char *buf, size_t len)
{
    safe_exclusive_lock guard(m_lock);
    ...your read code here...
    return len;
} // <- here we get the unlock()

ssize_t my_files::write(char const *buf, size_t len)
{
    safe_exclusive_lock guard(m_lock);
    ...your write code here...
    return len;
} // <- here we get the unlock()

The file_lock uses a file, so you will want to have the fstream file already created whenever the file_lock is created. If the fstream file may not be created in your constructor, you probably will want to transform the m_lock variable in a unique pointer:

private:
    std::unique_ptr<file_lock>  m_lock;

And when you reference it, you now need an asterisk:

safe_exclusive_lock guard(*m_lock);

Note that for safety, you should check whether the pointer is indeed allocated, if not defined, it means the file is not open yet so I would suggest you throw:

if(m_lock)
{
    safe_exclusive_lock guard(*m_lock);
    ...do work here...
}
else
{
    throw file_not_open();
}
// here the lock was released so you cannot touch the file anymore

In the open, you create the lock:

bool open(std::string const & filename)
{
    m_stream.open(...);
    ...make sure it worked...
    m_lock.reset(new file_lock(filename));
    // TODO: you may want a try/catch around the m_lock and
    //       close the m_stream if it fails or use a local
    //       variable and swap() on success...
    return true;
}

And do not forget to release the lock object in your close:

void close()
{
    m_lock.reset();
}
like image 2
Alexis Wilke Avatar answered Nov 07 '22 22:11

Alexis Wilke