tags:

views:

494

answers:

6

Hello all, I am looking for a way to create a program in unmanaged c++ that waits until a file is unlocked (as in it is no longer in use) and than does something. I have had no luck locating how to do this, any help would be appreciated!

UPDATE: I may have answered my own question, see below and tell me what you think..

UPDATE: All that really matters is that the file is writable, it doesn't matter if it is still in use.

+1  A: 

Create a loop that calls ::CreateFile() every 5 seconds until it succeeds. Pass 0 in the dwShareMode argument, to make sure there are no other process that have the file opened.

Andomar
Will this stomp on the file that I'm trying to monitor?
Seamus
Whadda ya mean by stomp?
Andomar
Overwrite the original file.
Seamus
That depends on the dwCreationDisposition you specify. If you choose OPEN_EXISTING, it will only open an existing file. The name CreateFile refers to creating a handle to the file, not necessarily creating a file itself.
Andomar
A: 

Here is one possible solution to my problem, does anyone see a problem with this?

#include <iostream>
#include <fstream>
#include <windows.h>
using namespace std;
void main(int argc, char ** argv) {
    if (argc < 2 || argc > 2) {
     return;
    }

    ofstream myfile;
    myfile.open(argv[1], ios_base::app);
    while (!myfile.is_open()) { Sleep(100); myfile.open(argv[1], ios_base::app); }
    myfile.close();

    // file should now be unlocked..
}

Thanks again!

UPDATE: Changed the code to be more complete.

Seamus
Doesn't look like it's waiting?
Andomar
I don't think is_open blocks, if that's what you're hoping for.
Mike Daniels
No, it doesn't, I have a Sleep(100) in the version that I am using.
Seamus
Standard C++ has no file locking functionality.
anon
That's true, but most operating systems do, and this only needs to run on XP.
Seamus
+2  A: 

Something like this will wait without wasting cpu cycles.

HANDLE h = FindFirstChangeNotification("C:\Path to folder holding file", FALSE, FILE_NOTIFY_CHANGE_LAST_WRITE);

while(true)
{
    if (CheckLockFile("C:\Path to file"))
    {
        // Do something
        break;
    }

    WaitForSingleObject(h, APPROPRIATE_TIMEOUT_VALUE);
    FindNextChangeNotification(h);
}

bool CheckLockFile(char* FilePath)
{
    HANDLE fh = CreateFile(FilePath, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0,NULL);
    if (fh == INVALID_HANDLE_VALUE)
    {
        return false; 
    }
    CloseHandle(fh);

    return true;
}

This assumes the application with the lock on the file has it open for writing.

Tony Edgecombe
This is not exactly waterproof: FILE_NOTIFY_CHANGE_LAST_WRITE will not trigger when a file that was opened read-only is closed; it only works when the disk cache is sufficiently flushed; and notifications can get lost when the system is very busy.
Andomar
A: 

It sounds like you want to access the file after another program has released the lock. UNIX (and Cygwin) gives you this behavior by simply locking the file.

Using something like ScopeGuard can make File_locker unnecessary, but if you're not using ScopeGuard, go like this:

UNIX:

#include <stdexcept>
#include <string>
#include "sys/file.h" //flock
#include "sys/fcntl.h" //open

class File_locker {
    int file_descriptor;
  public:
    File_locker(std::string filename)
    {
        // you can use errno to determine why the open/flock failed,
        // but this is a demo, not production code

        file_descriptor = ::open(filename.c_str(), O_RDWR);
        if (file_descriptor < 0)
            throw std::runtime_error((std::string("unable to open file ")
                + filename).c_str());
        if (::flock(file_descriptor, LOCK_EX)) {
            ::close(file_descriptor);
            throw std::runtime_error((std::string("unable to flock file ")
                + filename).c_str());
        }
    }
    ~File_locker()
    {
        ::flock(file_descriptor, LOCK_UN); // don't forget to unlock
        ::close(file_descriptor);
    }
};

In Windows it appears you have to poll the file.

Windows:

#include <string>
#include "windows.h"

class File_locker {
    HANDLE file_handle;
    static const int MAX_TRIES = 10;
    static const int SLEEP_INTERVAL = 500;

  public:
    File_locker(std::string filename)
    {
        // you can use GetLastError() to determine why the open failed,
        // but this is a demo, not production code
        for (int i = 0; i < MAX_TRIES; ++i) {
            file_handle = ::CreateFile(filename.c_str(),
                                       GENERIC_READ | GENERIC_WRITE,
                                       0,
                                       NULL,
                                       OPEN_EXISTING,
                                       FILE_ATTRIBUTE_NORMAL,
                                       NULL);
            if (file_handle != INVALID_HANDLE_VALUE)
                return;
            ::Sleep(SLEEP_INTERVAL);
            }
            throw std::runtime_error((std::string("unable to open file ")
                + filename).c_str());
    }
    ~File_locker()
    {
        ::CloseHandle(file_handle);
    }
};

Use it like so:

#include <fstream>
#include <stdexcept>

// .. define File_locker, as above

int main()
{
    try {
        File_locker fl("filename.txt");
        // once fl is constructed, nobody else has the file locked
        std::fstream file("filename.txt");
        // ...
        return 0;
    }
    catch (std::runtime_error& ex)
    {
        // just bail
        return 1;
    }
}
Max Lybbert
I thought that under windows, CreateFile() won't block on locks?
bdonlan
It looks like your right, so I'll edit the answer.
Max Lybbert
A: 

Simply use System locking File Notifications

Could you elaborate? Perhaps a link or an example?
Seamus
A: 

this works great - thanks I made a multi-platform version with an optional "read-only" argument that will create a shared lock for read only.

Kevin