Click here to Skip to main content
15,890,557 members
Please Sign up or sign in to vote.
3.00/5 (1 vote)
See more:
I have a data file where I am locking byte 0 of the file to prevent simultaneous access to the file. In general, this works just fine but I have come up with one case that I do not know how to handle.

When the file does not yet exist. Process 1 needs to write data and so must create the file and cannot lock the first byte since it does not exist yet. Meanwhile process 2 becomes ready to write data and finds the file existing and so opens the file and IS able to lock byte 0 since process 1 has started writing it data. Process 2 has the lock and so begins writing and stomps on process 1 data that is already written.

Note: All processes will not be on the same machine.

The question is: How do I handle the special case with the file has just been created?

The best solution that I have come up with is to open the file for exclusive access with it is initially created but this will require additional logic in the open method to handle this case.

Thanks in advance for any insight into this issue.
Posted
Comments
André Kraak 11-Oct-11 15:49pm    
Why would creating the file exclusively need additional logic, you already have logic for creating the file don't you.
Or are you creating it then closing it in order to open it again.

Open the file during creating with exclusive read/write access. At that point, no other app should be able to create the file since it already exists and is "locked". As a matter of fact, you shouldn't have to lock the file by manipulating the 1st byte. Just open it with exlusive write and nobody else can write to it.
 
Share this answer
 
Comments
Sergey Alexandrovich Kryukov 11-Oct-11 18:43pm    
Exactly, my 5.
--SA
There are two methods to do "exclusive use" of a data file.

1) Use the operating systems methods for "exclusive access" to prevent multiple accessors from opening the file at the same time. In Windows, opening with GENERIC_READ | GENERIC_WRITE does the trick. If you receive INVALID_HANDLE_VALUE back, somebody has the file already "locked".

You can tell if the file already existed before the CreateFile() call by checking the last error, even on success! A reading from the MS help on CreateFile()

Return Value
If the function succeeds, the return value is an open handle to a specified file. If a specified file exists before the function call and dwCreationDisposition is CREATE_ALWAYS or OPEN_ALWAYS, a call to GetLastError returns ERROR_ALREADY_EXISTS, even when the function succeeds. If a file does not exist before the call, GetLastError returns zero (0).


So now you know if you have to initialize the file (initial create) or just use the data that's in the file already.

Locking stays in effect until you close the file handle.

2) another is the use method 1 to open / change / close a file where, instead of the operating system holding the exclusive access to an open file, you use the data content to determine if the file is "logically locked". In that case, you use the exclusive access simply to only allow one process at a time to view the content of the file to determine the applications "lock state". This sounds like what you are doing. So, the only thing you really have to worry about is initializing the content of the file on initial creation. Something you can easily detect from the return values of CreateFile()
 
Share this answer
 
Comments
Sergey Alexandrovich Kryukov 11-Oct-11 18:43pm    
Correct, a 5.
--SA
One of the other solutions mentions that much information is available in the windows SDK documentation and I have indeed used this and many other sources that are available on the internet.

This solution is Windows specific.

For my solution, I have come up with a wrapper class that I call locked_text_file_t. This class manages all the details of the file locking and providing a line based access to the unicode text file.

For my purposes, I am using this class to write exception and error logs from a soft real-ime system so the file is opened without write buffering and it is immediately closed after each write. My GUI process also opens the file using the same locked_text_file_t class and provides a 'tail' like functionality always of displaying the end of file to the user. Specifically, it displays only those messages that have been added since the GUI process startup.

C++
--- Begin LockedTextFile.h ----------------
#include "CoreUtilsApi.h"
#include "LargeInteger.h"
#include <string>
#include "..\nUsbDataServer\ni_typedefs.h"
#include <winscard.h>

/******************************************************************************
* class: locked_text_file_t
*
* Wraps a Win32 HANDLE to a file
* Provides line based access to a unicode text file
* Provides random file region locking by offset and byte range
* Allows multiple simultaneous readers and writers that are required to lock
*  file regions that will be manipulated
* Note that Lock() blocks until the lock is gained.
******************************************************************************/
class COREUTILS_API locked_text_file_t
{
public:
    locked_text_file_t ();
    virtual ~locked_text_file_t ();

    operator HANDLE&                            ()                      {return m_hFile; };

    bool                    OpenOrCreate        (LPCWSTR szFileName);
    bool                    OpenExisting        (LPCWSTR szFileName);
    void                    Close               ();

    bool                    Lock                (DWORD nBytes, large_integer_t Offset);
    bool                    Unlock              (DWORD nBytes, large_integer_t Offset);

    bool                    Write               (const wchar_t* pwszFormat, ...);
    bool                    Writeln             (const wchar_t* pwszFormat, ...);

    bool                    Read                (LPBYTE pBuffer, DWORD nBufLen, DWORD* nBytesRead = NULL);
    bool                    Readln              (std::wstring& line, large_integer_t* liNewFilePointer = NULL);         // read next line
    bool                    Readln              (wstr_list_t& lines, large_integer_t* liNewFilePointer = NULL);         // read to end of file

    bool                    GetFilePos          (large_integer_t& Offset);                                              // offset from beginning of file

    bool                    SetFilePos          (large_integer_t Offset, large_integer_t* liNewFilePointer = NULL);     // offset from beginning of file
    bool                    OffsetFilePos       (large_integer_t Offset, large_integer_t* liNewFilePointer = NULL);     // offset from current file pointer
    bool                    EndFilePos          (large_integer_t Offset, large_integer_t* liNewFilePointer = NULL);     // offset from end of file

protected:

    bool                    _Open               (LPCWSTR szFileName, bool bCreate);
    bool                    _Write              (LPCBYTE pBuffer, DWORD nBytes, DWORD* nBytesWritten = NULL);

    HANDLE      m_hFile;

private:
    locked_text_file_t (locked_text_file_t&);
    locked_text_file_t& operator = (const locked_text_file_t&);
};

--- End LockedTextFile.h ----------------
#include "StdAfx.h"
#include "LockedTextFile.h"
#include "StringHelpers.h"
#include <winnt.h>

using namespace std;


/******************************************************************************
*******************************************************************************
*
* Implementation: locked_text_file_t
*
*******************************************************************************
******************************************************************************/

/******************************************************************************
* locked_text_file_t::locked_text_file_t 
******************************************************************************/
locked_text_file_t::locked_text_file_t ()
    : m_hFile   (INVALID_HANDLE_VALUE)
{
}

/******************************************************************************
* locked_text_file_t::~locked_text_file_t 
******************************************************************************/
locked_text_file_t::~locked_text_file_t ()
{
    Close();
}

/******************************************************************************
* locked_text_file_t::_Open 
******************************************************************************/
bool locked_text_file_t::_Open (LPCWSTR szFileName, bool bCreate)
{
    bool    bOK             = false;
    DWORD   dwLastError     = 0;
    bool    bFileCreated    = false;
    DWORD   nCreateAttempts = 0;

    // This outer loop is allowing to retry to open the existing file
    // This is needed to handle the case where the file is initially determined not to exist
    // but then another process creates the file causing our create attempt to fail
    do 
    {
        // First attempt to open the file with shared access and lock byte 0 
        // to establish our exclusive ownership of the file
        // Retry on sharing violation
        do 
        {
            // Open file for both read and write with cache write through
            m_hFile = CreateFile( szFileName,
                                  GENERIC_READ | GENERIC_WRITE,                // access
                                  FILE_SHARE_READ | FILE_SHARE_WRITE,          // share mode
                                  NULL,                                        // security
                                  OPEN_EXISTING,                               // create type
                                  FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH, // file attributes
                                  NULL);                                       // template 
            bOK = (m_hFile != INVALID_HANDLE_VALUE);
            dwLastError = GetLastError();
            if (dwLastError == ERROR_SHARING_VIOLATION)
            {
                Sleep(0);
            }
        } while ((!bOK) && (dwLastError == ERROR_SHARING_VIOLATION));

        if (!bOK)
        {
            if ((dwLastError == ERROR_FILE_NOT_FOUND) && (bCreate))
            {
                // Now, if file could not be opened due to file not found
                // Create the file with exclusive access so that we can write the unicode marker and the
                // first message without holding the lock
                // This create could still fail if another process has created the file after
                // we found that it did not exist
                nCreateAttempts++;
                m_hFile = CreateFile( szFileName,
                                    GENERIC_READ | GENERIC_WRITE,                // access
                                    /*Exclusive Access*/0,          // share mode
                                    NULL,                                        // security
                                    CREATE_NEW,                                  // create type
                                    FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH, // file attributes
                                    NULL);                                       // template 
                bOK = bFileCreated = (m_hFile != INVALID_HANDLE_VALUE);
                if (!bOK)
                {
                    Sleep(0);
                }
            }
        }
    } while ((!bOK) && (nCreateAttempts < 3));

    if (bOK) 
    {
        if (bFileCreated)
        {
            // write the unicode marker at the beginning of the file
            byte    UnicodeMarker []    = { 0xFF, 0xFE };
            DWORD   nBytes              = 2;
            bOK = _Write(UnicodeMarker, nBytes);
        }

        // Lock byte 0 of the file - block here until we hold the lock
        large_integer_t Offset;
        bOK = Lock(/*DWORD nBytes*/1, Offset);
        if (bOK)
        {
            // move file pointer to end of file
            EndFilePos(Offset);
        }

        if (!bOK)
        {
            Close();
        }
    }

    return bOK;
}

/******************************************************************************
* locked_text_file_t::OpenOrCreate
******************************************************************************/
bool locked_text_file_t::OpenOrCreate (LPCWSTR szFileName)
{
    bool bOK = false;

    if (m_hFile == INVALID_HANDLE_VALUE)
    {
        bOK = _Open(szFileName, /*bool bCreate*/true);
    }

    return bOK;
}

/******************************************************************************
* locked_text_file_t::OpenExisting
******************************************************************************/
bool locked_text_file_t::OpenExisting (LPCWSTR szFileName)
{
    bool bOK = false;

    if (m_hFile == INVALID_HANDLE_VALUE)
    {
        bOK = _Open(szFileName, /*bool bCreate*/false);
    }

    return bOK;
}

/******************************************************************************
* locked_text_file_t::Close
******************************************************************************/
void locked_text_file_t::Close ()
{
    if (m_hFile != INVALID_HANDLE_VALUE)
    {
        CloseHandle(m_hFile);
        m_hFile = INVALID_HANDLE_VALUE;
    }
}

/******************************************************************************
* locked_text_file_t::Lock 
******************************************************************************/
bool locked_text_file_t::Lock (DWORD nBytes, large_integer_t Offset)
{
    bool bOK = false;

    if (m_hFile != INVALID_HANDLE_VALUE)
    {
        OVERLAPPED oio;
        memset(&oio, 0, sizeof(oio));
        // set first byte of lock in the oio offset, length is set in LockFileEx parameters
        oio.Offset      = Offset.LowPart();
        oio.OffsetHigh  = Offset.HighPart();

        // note: LockFileEx will block until the lock is obtained
        bOK = LockFileEx(m_hFile, LOCKFILE_EXCLUSIVE_LOCK,
                                  0,        // reserved, must be zero
                                  nBytes,   // number of bytes to lock (low)
                                  0,        // number of bytes to lock (high)
                                  &oio);    // contains the file offset
    }

    return bOK;
}

/******************************************************************************
* locked_text_file_t::Unlock 
******************************************************************************/
bool locked_text_file_t::Unlock (DWORD nBytes, large_integer_t Offset)
{
    bool bOK = false;

    if (m_hFile != INVALID_HANDLE_VALUE)
    {
        OVERLAPPED oio;
        memset(&oio, 0, sizeof(oio));
        // set first byte of lock in the oio offset, length is set in UnlockFileEx parameters
        oio.Offset      = Offset.LowPart();
        oio.OffsetHigh  = Offset.HighPart();

        bOK = UnlockFileEx(m_hFile, 0,        // reserved, must be zero
                                    nBytes,   // number of bytes to lock (low)
                                    0,        // number of bytes to lock (high)
                                    &oio);    // contains the file offset
    }

    return bOK;
}

/******************************************************************************
* locked_text_file_t::_Write 
******************************************************************************/
bool locked_text_file_t::_Write (LPCBYTE pBuffer, DWORD nBytes, DWORD* pBytesWritten)
{
    bool    bOK                 = false;
    DWORD   LocalBytesWritten   = 0;

    if (m_hFile != INVALID_HANDLE_VALUE)
    {
        if (pBytesWritten == NULL)
        {
            pBytesWritten = &LocalBytesWritten;
        }

        bOK = WriteFile(m_hFile, pBuffer, nBytes, pBytesWritten, NULL);
    }

    return bOK;
}

/******************************************************************************
* locked_text_file_t::Write
******************************************************************************/
bool locked_text_file_t::Write (const wchar_t* pwszFormat, ...)
{
    bool bOK = false;

    if (m_hFile != INVALID_HANDLE_VALUE)
    {
        va_list argList;
        va_start(argList, pwszFormat);
        wchar_t* pwszResult = _FormatV(pwszFormat, argList);
        va_end(argList);
        DWORD nBytes = (DWORD)(wcslen(pwszResult) * sizeof(wchar_t));
        bOK = _Write((LPCBYTE)pwszResult, nBytes);
        free(pwszResult);
    }

    return bOK;
}

/******************************************************************************
* locked_text_file_t::Writeln 
******************************************************************************/
bool locked_text_file_t::Writeln (const wchar_t* pwszFormat, ...)
{
    bool bOK = false;

    if (m_hFile != INVALID_HANDLE_VALUE)
    {
        va_list argList;
        va_start(argList, pwszFormat);
        wchar_t* pwszResult = _FormatV(pwszFormat, argList);
        va_end(argList);
        DWORD nBytes = (DWORD)(wcslen(pwszResult) * sizeof(wchar_t));
        bOK = _Write((LPCBYTE)pwszResult, nBytes);
        if (bOK)
        {
            bOK = _Write((LPCBYTE)L"\r\n", 4);
        }
        free(pwszResult);
    }

    return bOK;
}

/******************************************************************************
* locked_text_file_t::Read 
******************************************************************************/
bool locked_text_file_t::Read (LPBYTE pBuffer, DWORD nBuflen, DWORD* pBytesRead)
{
    bool    bOK                 = false;
    DWORD   LocalBytesRead      = 0;

    if (m_hFile != INVALID_HANDLE_VALUE)
    {
        if (pBytesRead == NULL)
        {
            pBytesRead = &LocalBytesRead;
        }

        bOK = ReadFile(m_hFile, pBuffer, nBuflen, pBytesRead, NULL);
    }

    return bOK;
}

/******************************************************************************
* locked_text_file_t::Readln 
******************************************************************************/
bool locked_text_file_t::Readln (wstring& line, large_integer_t* liNewFilePointer)
{
    bool    bOK                 = false;

    line = L"";

    if (m_hFile != INVALID_HANDLE_VALUE)
    {
        // determine required buffer size
        large_integer_t liCurFilePos;
        GetFilePos(liCurFilePos);

        large_integer_t liEndFilePos;
        EndFilePos(liEndFilePos);
        GetFilePos(liEndFilePos);

        SetFilePos(liCurFilePos);

        DWORD   BufRequired     = (liEndFilePos - liCurFilePos).QuadPart();
        if (BufRequired > 0)
        {
            // allocate buffer
            LPBYTE  pBuffer         = (LPBYTE)malloc(BufRequired + 1);  // one extra byte so we don't overrun looking for CR LF
            DWORD   BytesRead       = 0;

            // read to end of file
            if (Read(pBuffer, BufRequired, &BytesRead))
            {
                bOK = (BufRequired == BytesRead);
            }

            if (bOK)
            {
                // find end of first string and set to NULL
                DWORD nChars = BytesRead / sizeof(wchar_t);
                DWORD nCharsUsed = 0;
                wchar_t* pCh = (wchar_t*)pBuffer;
                bool End = false;
                do 
                {
                    End = (((*pCh == L'\r') && (*(pCh+1) == L'\n')) || (nChars == 0));
                    if (!End)
                    {
                        if (*pCh == L'\n')
                        {
                            *pCh = L' ';
                        }
                        pCh++;
                        nChars--;
                        nCharsUsed++;
                    }
                } while (!End);
                // record intended NULL location
                wchar_t* pChNull = pCh;
                // continue counting past the CR LF
                while (((*pCh == L'\r') || (*pCh == L'\n')) && (nChars))
                {
                    pCh++;
                    nChars--;
                    nCharsUsed++;
                }
                // now set the NULL at end of the line
                *pChNull = NULL;

                // line to be returned
                line = (wchar_t*)pBuffer;

                // calculate new file pointer
                large_integer_t liLocalFilePointer;
                if (liNewFilePointer == NULL)
                {
                    liNewFilePointer = &liLocalFilePointer;
                }
                *liNewFilePointer = liCurFilePos + (nCharsUsed * sizeof(wchar_t));
                SetFilePos(*liNewFilePointer);
            }

            // deallocate the buffer
            free(pBuffer);
        }
    }

    return bOK;
}

/******************************************************************************
* locked_text_file_t::Readln 
******************************************************************************/
bool locked_text_file_t::Readln (wstr_list_t& lines, large_integer_t* liNewFilePointer)
{
    return false;
}

/******************************************************************************
* locked_text_file_t::GetFilePos 
******************************************************************************/
bool locked_text_file_t::GetFilePos (large_integer_t& liOffset)
{
    bool bOK = false;

    liOffset = large_integer_t();

    if (m_hFile != INVALID_HANDLE_VALUE)
    {
        // move to offset 0 from current file pointer to force a return of the current file pointer
        large_integer_t liDistanceToMove;
        bOK = SetFilePointerEx(m_hFile, liDistanceToMove, &((LARGE_INTEGER&)liOffset), FILE_CURRENT);
    }

    return bOK;
}

/******************************************************************************
* locked_text_file_t::SetFilePos 
******************************************************************************/
bool locked_text_file_t::SetFilePos (large_integer_t liOffset, large_integer_t* liNewFilePointer)
{
    bool bOK = false;

    if (m_hFile != INVALID_HANDLE_VALUE)
    {
        large_integer_t liLocalFilePointer;
        if (liNewFilePointer == NULL)
        {
            liNewFilePointer = &liLocalFilePointer;
        }
        bOK = SetFilePointerEx(m_hFile, liOffset, &(LARGE_INTEGER&)*liNewFilePointer, FILE_BEGIN);
    }

    return bOK;
}

/******************************************************************************
* locked_text_file_t::OffsetFilePos 
******************************************************************************/
bool locked_text_file_t::OffsetFilePos (large_integer_t liOffset, large_integer_t* liNewFilePointer)
{
    bool bOK = false;

    if (m_hFile != INVALID_HANDLE_VALUE)
    {
        large_integer_t liLocalFilePointer;
        if (liNewFilePointer == NULL)
        {
            liNewFilePointer = &liLocalFilePointer;
        }
        bOK = SetFilePointerEx(m_hFile, liOffset, &(LARGE_INTEGER&)*liNewFilePointer, FILE_CURRENT);
    }

    return bOK;
}

/******************************************************************************
* locked_text_file_t::EndFilePos 
******************************************************************************/
bool locked_text_file_t::EndFilePos (large_integer_t liOffset, large_integer_t* liNewFilePointer)
{
    bool bOK = false;

    if (m_hFile != INVALID_HANDLE_VALUE)
    {
        large_integer_t liLocalFilePointer;
        if (liNewFilePointer == NULL)
        {
            liNewFilePointer = &liLocalFilePointer;
        }
        bOK = SetFilePointerEx(m_hFile, liOffset, &(LARGE_INTEGER&)*liNewFilePointer, FILE_END);
    }

    return bOK;
}

--- End LockedTextFile.cpp ----------------
 
Share this answer
 
v3
There are two ways of doing that, but one, with the use of Windows' APIs (LockFileEx and UnlockFileEx) using the parameter LOCKFILE_EXCLUSIVE_LOCK was not good for my case, so I found that:

Create the file with the OpenFile function and handle it:

hMyLockedFile := OpenFile( 'c:\variables.dat', ofStruct, OF_CREATE Or OF_READWRITE Or OF_SHARE_EXCLUSIVE );

Now, you can work with your file, but users cannot change it!


A last comment:
I found that in Win32 SDK Reference, so if you need to know more (and there's more to know: believe me!) you should use it!
 
Share this answer
 
Since you explicitely mentioned that the processes run on different computers you might also want to look into Distributed Mutual Exclusion.
Here are some nice resources on that:http://www.cs.gsu.edu/~cscyqz/courses/aos/slides08/ch4.5-Fall08.ppt[^] which is a power point presentation that gives an introduction to Distributed Mutual Exclusion and this here is a PDF on some algorithms for the same: http://www.cs.uic.edu/~ajayk/Chapter9.pdf[^].

Best Regards,

—MRB
 
Share this answer
 
In response to Solution 5...

I have read up on your references. I think you would need a really large system to begin to see benefits from these elaborate and expensive procedures. And mostly I say this to give you benefit of the doubt.

File locking appears to be a much simplier solution. No explicit messaging required. All messaging and blocking is taken care of in requesting a lock on a region of the file. It can be used for exclusive access to file, many readers with exclusive writer, or can allow each accessor to lock their region of interest in the file and have exclusive access to that region. And all locks get released if process crashes or otherwise closes the file.
 
Share this answer
 
Comments
André Kraak 18-Oct-11 16:40pm    
If you have a question about or comment on a given solution use the "Have a Question or Comment?" option beneath the solution. When using this option the person who gave the solution gets an e-mail message and knows you placed a comment and can respond if he/she wants.

Please move the content of this solution to the solution you are commenting on and remove the solution.
Thank you.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900