2

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?

Naren
  • 139
  • 1
  • 17
  • 3
    Change your programs to print `strerror(errno)` in *every place* where `lockf` has just returned a nonzero value. Run them again. Tell us what they print. – zwol Oct 20 '16 at 15:29
  • 1
    it is not returning non-zero value. I have the logs in place but they are not getting printed because it is always returning 0. editing those log changes now in to code. – Naren Oct 20 '16 at 15:35
  • 1
    Shouldn't you create some sort of object so that you don't have to keep opening the file? In `is_file_locked()`, you open but don't close the file descriptor; you will run out of file descriptors. You don't close the file descriptor in `lock_file()` or in `unlock_file()`, but that leaves the onus on the locker to close the file (thereby unlocking the file). A destructor and RAII would make life simpler, I believe. – Jonathan Leffler Oct 20 '16 at 15:36
  • Added close changes but still not working... should we close the file in lock_file without unlocking? I am only closing in unlock_file and is_file_locked. Updated the code. – Naren Oct 20 '16 at 15:44
  • As info I have a class version of File locker also, but it is also not working. – Naren Oct 20 '16 at 15:45

1 Answers1

3

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.

zwol
  • 135,547
  • 38
  • 252
  • 361
  • I have another question in this context, will close(fd) will close all the descriptors that is used by ofstream (if it is used to open the same file and I did not call ofstream.close())? – Naren Oct 20 '16 at 16:08
  • No, the ofstream has its own "open file description" and close(fd) will not affect it. However, the lock will be dropped at the *first* of `ofstream.close()` and `close(fd)`, because the lock is associated with the *process*, not the open file. – zwol Oct 20 '16 at 16:10
  • ok, what I wanted to ask is if I call close(fd), do I still need to call ofstream.close()? – Naren Oct 20 '16 at 16:12
  • Yes, that is what I said. Please note that I also said you probably need to stop using iostreams at all. – zwol Oct 20 '16 at 16:16