naren naren - 1 month ago 18
Linux Question

can't acquire a lock on a file

FileLocker_wo.h

#include <string>

namespace Utils
{
namespace FileLocker
{
bool lock_file(std::string aFileName, int& aFileDescriptor);

bool unlock_file(int& aFileDescriptor);

bool is_file_locked(std::string aFileName);
};
}


FileLocker_wo.cpp

namespace Utils
{
namespace FileLocker
{
bool lock_file(std::string aFileName, int& aFileDescriptor)
{
aFileDescriptor = open(aFileName.c_str(), O_RDWR);

if (aFileDescriptor != -1)
{
if (lockf(aFileDescriptor, F_TLOCK, 0) == 0)
{
return true;
}

std::cout << strerror(errno) << std::endl;
}

return false;
}

bool unlock_file(int& aFileDescriptor)
{
if (lockf(aFileDescriptor, F_ULOCK, 0) == 0)
{
std::cout << "unloced file" << std::endl;
close(aFileDescriptor);
return true;
}
close(aFileDescriptor);
return false;
}

bool is_file_locked(std::string aFileName)
{
int file_descriptor = open(aFileName.c_str(), O_RDWR);

if (file_descriptor != -1)
{
int ret = lockf(file_descriptor, F_TEST, 0);

if (ret == -1 && (errno == EACCES || errno == EAGAIN))
{
std::cout << "locked by another process" << std::endl;
close(file_descriptor);
return true;
}

if (ret != 0)
{
std::cout << "return value is " << ret << " " << strerror(errno) << std::endl;
}

}
close(file_descriptor);
return false;
}
}
}


p1.cpp

#include <iostream>
#include <fstream>

#include "FileLocker_wo.h"


int main()
{

int fd = -1;
if (Utils::FileLocker::lock_file("hello.txt", fd))
{
std::ofstream out("hello.txt");
out << "hello ding dong" << std::endl;
out.close();

std::cout << "locked" << std::endl;
sleep(5);
if (Utils::FileLocker::unlock_file(fd))
{
std::cout << "unlocked" << std::endl;
}
}

return 0;
}


p2.cpp

#include "FileLocker_wo.h"
#include <iostream>
#include <fstream>

int main()
{
int max_trys = 2;
int trys = 0;
bool is_locked = false;

do
{
is_locked = Utils::FileLocker::is_file_locked("hello.txt");

if (!is_locked)
{
std::cout << "not locked" << std::endl;
break;
}

std::cout << "locked" << std::endl;

sleep(1);
++trys;
}
while(trys < max_trys);

if (!is_locked)
{
std::string s;
std::ifstream in("hello.txt");
while(getline(in,s))
{
std::cout << "s is " << s << std::endl;
}
}

return 0;
}


I am trying to get a file lock in one process and checking whether there is any lock on that file in other process using lockf (p1.cpp, p2.cpp).

In p1.cpp I am locking the file hello.txt and waiting for 5 seconds. Meanwhile I start p2.cpp and checking whether any lock is there by other process, but always getting there is no lock> I am stuck with this for last 2 hours.

Can anybody tell what is wrong in this?

Answer

You've tripped over one of the nastier design errors in POSIX file locks. You probably didn't know about this because you only read the lockf manpage, not the fcntl manpage, so here's the important bit of the fcntl manpage:

  • If a process closes any file descriptor referring to a file, then all of the process's locks on that file are released, regardless of the file descriptor(s) on which the locks were obtained.

What this means is, in this bit of your code

    if (Utils::FileLocker::lock_file("hello.txt", fd))
    {
            std::ofstream out("hello.txt");
            out << "hello ding dong" << std::endl;
            out.close();

you lose your lock on the file when you call out.close(), even though out is a different OS-level "open file description" than you used in lock_file!

In order to use POSIX locks safely you must ensure that you call open() on the file to be locked once and only once per process, you must never duplicate the file descriptor, and you must only close it again when you are ready to drop the lock. Because there may not be any way (even using unportable extensions) to construct an iostreams object from a file descriptor, or to extract a file descriptor from an iostreams object, the path of least resistance is to use only OS-level I/O primitives (open, close, read, write, fcntl, lseek, ftruncate) with files that you need to apply POSIX locks to.

Comments