file: Add LOCK subcommand to do file and directory locking

Provide options to fail without blocking or to block up to a timeout.
Provide options to specify the scope containing the lock so it can be
released automatically at the end of a function, file, or process.

Extend the RunCMake.file test with cases covering the file(LOCK) command
usage and error cases.
This commit is contained in:
Ruslan Baratov 2014-11-26 01:49:25 +03:00 committed by Brad King
parent 05d6531c7a
commit e6db4c5a4e
60 changed files with 1336 additions and 0 deletions

View File

@ -305,3 +305,33 @@ status messages (subject to the :variable:`CMAKE_INSTALL_MESSAGE` variable),
and ``NO_SOURCE_PERMISSIONS`` is default.
Installation scripts generated by the :command:`install` command
use this signature (with some undocumented options for internal use).
------------------------------------------------------------------------------
::
file(LOCK <path> [DIRECTORY] [RELEASE]
[GUARD <FUNCTION|FILE|PROCESS>]
[RESULT_VARIABLE <variable>]
[TIMEOUT <seconds>])
Lock a file specified by ``<path>`` if no ``DIRECTORY`` option present and file
``<path>/cmake.lock`` otherwise. File will be locked for scope defined by
``GUARD`` option (default value is ``PROCESS``). ``RELEASE`` option can be used
to unlock file explicitly. If option ``TIMEOUT`` is not specified CMake will
wait until lock succeed or until fatal error occurs. If ``TIMEOUT`` is set to
``0`` lock will be tried once and result will be reported immediately. If
``TIMEOUT`` is not ``0`` CMake will try to lock file for the period specified
by ``<seconds>`` value. Any errors will be interpreted as fatal if there is no
``RESULT_VARIABLE`` option. Otherwise result will be stored in ``<variable>``
and will be ``0`` on success or error message on failure.
Note that lock is advisory - there is no guarantee that other processes will
respect this lock, i.e. lock synchronize two or more CMake instances sharing
some modifiable resources. Similar logic applied to ``DIRECTORY`` option -
locking parent directory doesn't prevent other ``LOCK`` commands to lock any
child directory or file.
Trying to lock file twice is not allowed. Any intermediate directories and
file itself will be created if they not exist. ``GUARD`` and ``TIMEOUT``
options ignored on ``RELEASE`` operation.

View File

@ -219,6 +219,12 @@ set(SRCS
cmExtraKateGenerator.h
cmExtraSublimeTextGenerator.cxx
cmExtraSublimeTextGenerator.h
cmFileLock.cxx
cmFileLock.h
cmFileLockPool.cxx
cmFileLockPool.h
cmFileLockResult.cxx
cmFileLockResult.h
cmFileTimeComparison.cxx
cmFileTimeComparison.h
cmGeneratedFileStream.cxx

View File

@ -21,6 +21,7 @@
#if defined(CMAKE_BUILD_WITH_CMAKE)
#include "cm_curl.h"
#include "cmFileLockResult.h"
#endif
#undef GetCurrentDirectory
@ -202,6 +203,10 @@ bool cmFileCommand
{
return this->HandleGenerateCommand(args);
}
else if ( subCommand == "LOCK" )
{
return this->HandleLockCommand(args);
}
std::string e = "does not recognize sub-command "+subCommand;
this->SetError(e);
@ -3501,6 +3506,204 @@ bool cmFileCommand::HandleGenerateCommand(
return true;
}
//----------------------------------------------------------------------------
bool cmFileCommand::HandleLockCommand(
std::vector<std::string> const& args)
{
#if defined(CMAKE_BUILD_WITH_CMAKE)
// Default values
bool directory = false;
bool release = false;
enum Guard {
GUARD_FUNCTION,
GUARD_FILE,
GUARD_PROCESS
};
Guard guard = GUARD_PROCESS;
std::string resultVariable;
unsigned timeout = static_cast<unsigned>(-1);
// Parse arguments
if(args.size() < 2)
{
this->Makefile->IssueMessage(
cmake::FATAL_ERROR,
"sub-command LOCK requires at least two arguments.");
return false;
}
std::string path = args[1];
for (unsigned i = 2; i < args.size(); ++i)
{
if (args[i] == "DIRECTORY")
{
directory = true;
}
else if (args[i] == "RELEASE")
{
release = true;
}
else if (args[i] == "GUARD")
{
++i;
const char* merr = "expected FUNCTION, FILE or PROCESS after GUARD";
if (i >= args.size())
{
this->Makefile->IssueMessage(cmake::FATAL_ERROR, merr);
return false;
}
else
{
if (args[i] == "FUNCTION")
{
guard = GUARD_FUNCTION;
}
else if (args[i] == "FILE")
{
guard = GUARD_FILE;
}
else if (args[i] == "PROCESS")
{
guard = GUARD_PROCESS;
}
else
{
cmOStringStream e;
e << merr << ", but got:\n \"" << args[i] << "\".";
this->Makefile->IssueMessage(cmake::FATAL_ERROR, e.str());
return false;
}
}
}
else if (args[i] == "RESULT_VARIABLE")
{
++i;
if (i >= args.size())
{
this->Makefile->IssueMessage(
cmake::FATAL_ERROR,
"expected variable name after RESULT_VARIABLE");
return false;
}
resultVariable = args[i];
}
else if (args[i] == "TIMEOUT")
{
++i;
if (i >= args.size())
{
this->Makefile->IssueMessage(
cmake::FATAL_ERROR,
"expected timeout value after TIMEOUT");
return false;
}
int scanned;
if(!cmSystemTools::StringToInt(args[i].c_str(), &scanned) || scanned < 0)
{
cmOStringStream e;
e << "TIMEOUT value \"" << args[i] << "\" is not an unsigned integer.";
this->Makefile->IssueMessage(cmake::FATAL_ERROR, e.str());
return false;
}
timeout = static_cast<unsigned>(scanned);
}
else
{
cmOStringStream e;
e << "expected DIRECTORY, RELEASE, GUARD, RESULT_VARIABLE or TIMEOUT\n";
e << "but got: \"" << args[i] << "\".";
this->Makefile->IssueMessage(cmake::FATAL_ERROR, e.str());
return false;
}
}
if (directory)
{
path += "/cmake.lock";
}
if (!cmsys::SystemTools::FileIsFullPath(path))
{
path = this->Makefile->GetCurrentDirectory() + ("/" + path);
}
// Unify path (remove '//', '/../', ...)
path = cmSystemTools::CollapseFullPath(path);
// Create file and directories if needed
std::string parentDir = cmSystemTools::GetParentDirectory(path);
if (!cmSystemTools::MakeDirectory(parentDir))
{
cmOStringStream e;
e << "directory\n \"" << parentDir << "\"\ncreation failed ";
e << "(check permissions).";
this->Makefile->IssueMessage(cmake::FATAL_ERROR, e.str());
cmSystemTools::SetFatalErrorOccured();
return false;
}
FILE *file = cmsys::SystemTools::Fopen(path, "w");
if (!file)
{
cmOStringStream e;
e << "file\n \"" << path << "\"\ncreation failed (check permissions).";
this->Makefile->IssueMessage(cmake::FATAL_ERROR, e.str());
cmSystemTools::SetFatalErrorOccured();
return false;
}
fclose(file);
// Actual lock/unlock
cmFileLockPool& lockPool = this->Makefile->GetLocalGenerator()->
GetGlobalGenerator()->GetFileLockPool();
cmFileLockResult fileLockResult(cmFileLockResult::MakeOk());
if (release)
{
fileLockResult = lockPool.Release(path);
}
else
{
switch (guard)
{
case GUARD_FUNCTION:
fileLockResult = lockPool.LockFunctionScope(path, timeout);
break;
case GUARD_FILE:
fileLockResult = lockPool.LockFileScope(path, timeout);
break;
case GUARD_PROCESS:
fileLockResult = lockPool.LockProcessScope(path, timeout);
break;
default:
cmSystemTools::SetFatalErrorOccured();
return false;
}
}
const std::string result = fileLockResult.GetOutputMessage();
if (resultVariable.empty() && !fileLockResult.IsOk())
{
cmOStringStream e;
e << "error locking file\n \"" << path << "\"\n" << result << ".";
this->Makefile->IssueMessage(cmake::FATAL_ERROR, e.str());
cmSystemTools::SetFatalErrorOccured();
return false;
}
if (!resultVariable.empty())
{
this->Makefile->AddDefinition(resultVariable, result.c_str());
}
return true;
#else
static_cast<void>(args);
this->SetError("sub-command LOCK not implemented in bootstrap cmake");
return false;
#endif
}
//----------------------------------------------------------------------------
bool cmFileCommand::HandleTimestampCommand(
std::vector<std::string> const& args)

View File

@ -75,6 +75,7 @@ protected:
bool HandleTimestampCommand(std::vector<std::string> const& args);
bool HandleGenerateCommand(std::vector<std::string> const& args);
bool HandleLockCommand(std::vector<std::string> const& args);
private:
void AddEvaluationFile(const std::string &inputName,

78
Source/cmFileLock.cxx Normal file
View File

@ -0,0 +1,78 @@
/*============================================================================
CMake - Cross Platform Makefile Generator
Copyright 2014 Ruslan Baratov
Distributed under the OSI-approved BSD License (the "License");
see accompanying file Copyright.txt for details.
This software is distributed WITHOUT ANY WARRANTY; without even the
implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the License for more information.
============================================================================*/
#include "cmFileLock.h"
#include <assert.h>
#include "cmFileLockResult.h"
// Common implementation
cmFileLock::~cmFileLock()
{
if (!this->Filename.empty())
{
const cmFileLockResult result = this->Release();
static_cast<void>(result);
assert(result.IsOk());
}
}
cmFileLockResult cmFileLock::Lock(
const std::string& filename, unsigned timeout)
{
if (filename.empty())
{
// Error is internal since all the directories and file must be created
// before actual lock called.
return cmFileLockResult::MakeInternal();
}
if (!this->Filename.empty())
{
// Error is internal since double-lock must be checked in class
// cmFileLockPool by the cmFileLock::IsLocked method.
return cmFileLockResult::MakeInternal();
}
this->Filename = filename;
cmFileLockResult result = this->OpenFile();
if (result.IsOk())
{
if (timeout == static_cast<unsigned>(-1))
{
result = this->LockWithoutTimeout();
}
else
{
result = this->LockWithTimeout(timeout);
}
}
if (!result.IsOk())
{
this->Filename = "";
}
return result;
}
bool cmFileLock::IsLocked(const std::string& filename) const
{
return filename == this->Filename;
}
#if defined(_WIN32)
# include "cmFileLockWin32.cxx"
#else
# include "cmFileLockUnix.cxx"
#endif

74
Source/cmFileLock.h Normal file
View File

@ -0,0 +1,74 @@
/*============================================================================
CMake - Cross Platform Makefile Generator
Copyright 2014 Ruslan Baratov
Distributed under the OSI-approved BSD License (the "License");
see accompanying file Copyright.txt for details.
This software is distributed WITHOUT ANY WARRANTY; without even the
implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the License for more information.
============================================================================*/
#ifndef cmFileLock_h
#define cmFileLock_h
#include "cmStandardIncludes.h"
#if defined(_WIN32)
# include <windows.h> // HANDLE
#endif
class cmFileLockResult;
/**
* @brief Cross-platform file locking.
* @details Under the hood this class use 'fcntl' for Unix-like platforms and
* 'LockFileEx'/'UnlockFileEx' for Win32 platform. Locks are exclusive and
* advisory.
*/
class cmFileLock
{
public:
cmFileLock();
~cmFileLock();
/**
* @brief Lock the file.
* @param timeoutSec Lock timeout. If -1 try until success or fatal error.
*/
cmFileLockResult Lock(const std::string& filename, unsigned timeoutSec);
/**
* @brief Unlock the file.
*/
cmFileLockResult Release();
/**
* @brief Check file is locked by this class.
* @details This function helps to find double locks (deadlocks) and to do
* explicit unlocks.
*/
bool IsLocked(const std::string& filename) const;
private:
cmFileLock(const cmFileLock&);
cmFileLock& operator=(const cmFileLock&);
cmFileLockResult OpenFile();
cmFileLockResult LockWithoutTimeout();
cmFileLockResult LockWithTimeout(unsigned timeoutSec);
#if defined(_WIN32)
typedef HANDLE FileId;
BOOL LockFile(DWORD flags);
#else
typedef int FileId;
int LockFile(int cmd, int type);
#endif
FileId File;
std::string Filename;
};
#endif // cmFileLock_h

198
Source/cmFileLockPool.cxx Normal file
View File

@ -0,0 +1,198 @@
/*============================================================================
CMake - Cross Platform Makefile Generator
Copyright 2014 Ruslan Baratov
Distributed under the OSI-approved BSD License (the "License");
see accompanying file Copyright.txt for details.
This software is distributed WITHOUT ANY WARRANTY; without even the
implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the License for more information.
============================================================================*/
#include "cmFileLockPool.h"
#include <assert.h>
#include "cmFileLock.h"
#include "cmFileLockResult.h"
cmFileLockPool::cmFileLockPool()
{
}
cmFileLockPool::~cmFileLockPool()
{
for (It i = this->FunctionScopes.begin();
i != this->FunctionScopes.end(); ++i)
{
delete *i;
}
for (It i = this->FileScopes.begin(); i != this->FileScopes.end(); ++i)
{
delete *i;
}
}
void cmFileLockPool::PushFunctionScope()
{
this->FunctionScopes.push_back(new ScopePool());
}
void cmFileLockPool::PopFunctionScope()
{
assert(!this->FunctionScopes.empty());
delete this->FunctionScopes.back();
this->FunctionScopes.pop_back();
}
void cmFileLockPool::PushFileScope()
{
this->FileScopes.push_back(new ScopePool());
}
void cmFileLockPool::PopFileScope()
{
assert(!this->FileScopes.empty());
delete this->FileScopes.back();
this->FileScopes.pop_back();
}
cmFileLockResult cmFileLockPool::LockFunctionScope(
const std::string& filename, unsigned timeoutSec)
{
if (this->IsAlreadyLocked(filename))
{
return cmFileLockResult::MakeAlreadyLocked();
}
if (this->FunctionScopes.empty())
{
return cmFileLockResult::MakeNoFunction();
}
return this->FunctionScopes.back()->Lock(filename, timeoutSec);
}
cmFileLockResult cmFileLockPool::LockFileScope(
const std::string& filename, unsigned timeoutSec)
{
if (this->IsAlreadyLocked(filename))
{
return cmFileLockResult::MakeAlreadyLocked();
}
assert(!this->FileScopes.empty());
return this->FileScopes.back()->Lock(filename, timeoutSec);
}
cmFileLockResult cmFileLockPool::LockProcessScope(
const std::string& filename, unsigned timeoutSec)
{
if (this->IsAlreadyLocked(filename))
{
return cmFileLockResult::MakeAlreadyLocked();
}
return this->ProcessScope.Lock(filename, timeoutSec);
}
cmFileLockResult cmFileLockPool::Release(const std::string& filename)
{
for (It i = this->FunctionScopes.begin();
i != this->FunctionScopes.end(); ++i)
{
const cmFileLockResult result = (*i)->Release(filename);
if (!result.IsOk())
{
return result;
}
}
for (It i = this->FileScopes.begin(); i != this->FileScopes.end(); ++i)
{
const cmFileLockResult result = (*i)->Release(filename);
if (!result.IsOk())
{
return result;
}
}
return this->ProcessScope.Release(filename);
}
bool cmFileLockPool::IsAlreadyLocked(const std::string& filename) const
{
for (CIt i = this->FunctionScopes.begin();
i != this->FunctionScopes.end(); ++i)
{
const bool result = (*i)->IsAlreadyLocked(filename);
if (result)
{
return true;
}
}
for (CIt i = this->FileScopes.begin(); i != this->FileScopes.end(); ++i)
{
const bool result = (*i)->IsAlreadyLocked(filename);
if (result)
{
return true;
}
}
return this->ProcessScope.IsAlreadyLocked(filename);
}
cmFileLockPool::ScopePool::ScopePool()
{
}
cmFileLockPool::ScopePool::~ScopePool()
{
for (It i = this->Locks.begin(); i != this->Locks.end(); ++i)
{
delete *i;
}
}
cmFileLockResult cmFileLockPool::ScopePool::Lock(
const std::string& filename, unsigned timeoutSec)
{
cmFileLock *lock = new cmFileLock();
const cmFileLockResult result = lock->Lock(filename, timeoutSec);
if (result.IsOk())
{
this->Locks.push_back(lock);
return cmFileLockResult::MakeOk();
}
else
{
delete lock;
return result;
}
}
cmFileLockResult cmFileLockPool::ScopePool::Release(
const std::string& filename)
{
for (It i = this->Locks.begin(); i != this->Locks.end(); ++i)
{
if ((*i)->IsLocked(filename))
{
return (*i)->Release();
}
}
return cmFileLockResult::MakeOk();
}
bool cmFileLockPool::ScopePool::IsAlreadyLocked(
const std::string& filename) const
{
for (CIt i = this->Locks.begin(); i != this->Locks.end(); ++i)
{
if ((*i)->IsLocked(filename))
{
return true;
}
}
return false;
}

100
Source/cmFileLockPool.h Normal file
View File

@ -0,0 +1,100 @@
/*============================================================================
CMake - Cross Platform Makefile Generator
Copyright 2014 Ruslan Baratov
Distributed under the OSI-approved BSD License (the "License");
see accompanying file Copyright.txt for details.
This software is distributed WITHOUT ANY WARRANTY; without even the
implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the License for more information.
============================================================================*/
#ifndef cmFileLockPool_h
#define cmFileLockPool_h
#include "cmStandardIncludes.h"
class cmFileLockResult;
class cmFileLock;
class cmFileLockPool
{
public:
cmFileLockPool();
~cmFileLockPool();
//@{
/**
* @brief Function scope control.
*/
void PushFunctionScope();
void PopFunctionScope();
//@}
//@{
/**
* @brief File scope control.
*/
void PushFileScope();
void PopFileScope();
//@}
//@{
/**
* @brief Lock the file in given scope.
* @param timeoutSec Lock timeout. If -1 try until success or fatal error.
*/
cmFileLockResult LockFunctionScope(
const std::string& filename, unsigned timeoutSec
);
cmFileLockResult LockFileScope(
const std::string& filename, unsigned timeoutSec
);
cmFileLockResult LockProcessScope(
const std::string& filename, unsigned timeoutSec
);
//@}
/**
* @brief Unlock the file explicitly.
*/
cmFileLockResult Release(const std::string& filename);
private:
cmFileLockPool(const cmFileLockPool&);
cmFileLockPool& operator=(const cmFileLockPool&);
bool IsAlreadyLocked(const std::string& filename) const;
class ScopePool
{
public:
ScopePool();
~ScopePool();
cmFileLockResult Lock(const std::string& filename, unsigned timeoutSec);
cmFileLockResult Release(const std::string& filename);
bool IsAlreadyLocked(const std::string& filename) const;
private:
ScopePool(const ScopePool&);
ScopePool& operator=(const ScopePool&);
typedef std::list<cmFileLock*> List;
typedef List::iterator It;
typedef List::const_iterator CIt;
List Locks;
};
typedef std::list<ScopePool*> List;
typedef List::iterator It;
typedef List::const_iterator CIt;
List FunctionScopes;
List FileScopes;
ScopePool ProcessScope;
};
#endif // cmFileLockPool_h

111
Source/cmFileLockResult.cxx Normal file
View File

@ -0,0 +1,111 @@
/*============================================================================
CMake - Cross Platform Makefile Generator
Copyright 2014 Ruslan Baratov
Distributed under the OSI-approved BSD License (the "License");
see accompanying file Copyright.txt for details.
This software is distributed WITHOUT ANY WARRANTY; without even the
implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the License for more information.
============================================================================*/
#include "cmFileLockResult.h"
#include <errno.h>
cmFileLockResult cmFileLockResult::MakeOk()
{
return cmFileLockResult(OK, 0);
}
cmFileLockResult cmFileLockResult::MakeSystem()
{
#if defined(_WIN32)
const Error lastError = GetLastError();
#else
const Error lastError = errno;
#endif
return cmFileLockResult(SYSTEM, lastError);
}
cmFileLockResult cmFileLockResult::MakeTimeout()
{
return cmFileLockResult(TIMEOUT, 0);
}
cmFileLockResult cmFileLockResult::MakeAlreadyLocked()
{
return cmFileLockResult(ALREADY_LOCKED, 0);
}
cmFileLockResult cmFileLockResult::MakeInternal()
{
return cmFileLockResult(INTERNAL, 0);
}
cmFileLockResult cmFileLockResult::MakeNoFunction()
{
return cmFileLockResult(NO_FUNCTION, 0);
}
bool cmFileLockResult::IsOk() const
{
return this->Type == OK;
}
std::string cmFileLockResult::GetOutputMessage() const
{
switch (this->Type)
{
case OK:
return "0";
case SYSTEM:
#if defined(_WIN32)
{
char* errorText = NULL;
// http://stackoverflow.com/a/455533/2288008
DWORD flags = FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_IGNORE_INSERTS;
::FormatMessageA(
flags,
NULL,
this->ErrorValue,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPSTR)&errorText,
0,
NULL
);
if (errorText != NULL)
{
const std::string message = errorText;
::LocalFree(errorText);
return message;
}
else
{
return "Internal error (FormatMessageA failed)";
}
}
#else
return strerror(this->ErrorValue);
#endif
case TIMEOUT:
return "Timeout reached";
case ALREADY_LOCKED:
return "File already locked";
case NO_FUNCTION:
return "'GUARD FUNCTION' not used in function definition";
case INTERNAL:
default:
return "Internal error";
}
}
cmFileLockResult::cmFileLockResult(ErrorType typeValue, Error errorValue):
Type(typeValue), ErrorValue(errorValue)
{
}

85
Source/cmFileLockResult.h Normal file
View File

@ -0,0 +1,85 @@
/*============================================================================
CMake - Cross Platform Makefile Generator
Copyright 2014 Ruslan Baratov
Distributed under the OSI-approved BSD License (the "License");
see accompanying file Copyright.txt for details.
This software is distributed WITHOUT ANY WARRANTY; without even the
implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the License for more information.
============================================================================*/
#ifndef cmFileLockResult_h
#define cmFileLockResult_h
#include "cmStandardIncludes.h"
#if defined(_WIN32)
# include <windows.h> // DWORD
#endif
/**
* @brief Result of the locking/unlocking file.
* @note See @c cmFileLock
*/
class cmFileLockResult
{
public:
#if defined(_WIN32)
typedef DWORD Error;
#else
typedef int Error;
#endif
/**
* @brief Successful lock/unlock.
*/
static cmFileLockResult MakeOk();
/**
* @brief Lock/Unlock failed. Read error/GetLastError.
*/
static cmFileLockResult MakeSystem();
/**
* @brief Lock/Unlock failed. Timeout reached.
*/
static cmFileLockResult MakeTimeout();
/**
* @brief File already locked.
*/
static cmFileLockResult MakeAlreadyLocked();
/**
* @brief Internal error.
*/
static cmFileLockResult MakeInternal();
/**
* @brief Try to lock with function guard outside of the function
*/
static cmFileLockResult MakeNoFunction();
bool IsOk() const;
std::string GetOutputMessage() const;
private:
enum ErrorType
{
OK,
SYSTEM,
TIMEOUT,
ALREADY_LOCKED,
INTERNAL,
NO_FUNCTION
};
cmFileLockResult(ErrorType type, Error errorValue);
ErrorType Type;
Error ErrorValue;
};
#endif // cmFileLockResult_h

102
Source/cmFileLockUnix.cxx Normal file
View File

@ -0,0 +1,102 @@
/*============================================================================
CMake - Cross Platform Makefile Generator
Copyright 2014 Ruslan Baratov
Distributed under the OSI-approved BSD License (the "License");
see accompanying file Copyright.txt for details.
This software is distributed WITHOUT ANY WARRANTY; without even the
implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the License for more information.
============================================================================*/
#include "cmFileLock.h"
#include <errno.h> // errno
#include <stdio.h> // SEEK_SET
#include <fcntl.h>
#include "cmSystemTools.h"
cmFileLock::cmFileLock(): File(-1)
{
}
cmFileLockResult cmFileLock::Release()
{
if (this->Filename.empty())
{
return cmFileLockResult::MakeOk();
}
const int lockResult = this->LockFile(F_SETLK, F_UNLCK);
this->Filename = "";
if (lockResult == 0)
{
return cmFileLockResult::MakeOk();
}
else
{
return cmFileLockResult::MakeSystem();
}
}
cmFileLockResult cmFileLock::OpenFile()
{
this->File = ::open(this->Filename.c_str(), O_RDWR);
if (this->File == -1)
{
return cmFileLockResult::MakeSystem();
}
else
{
return cmFileLockResult::MakeOk();
}
}
cmFileLockResult cmFileLock::LockWithoutTimeout()
{
if (this->LockFile(F_SETLKW, F_WRLCK) == -1)
{
return cmFileLockResult::MakeSystem();
}
else
{
return cmFileLockResult::MakeOk();
}
}
cmFileLockResult cmFileLock::LockWithTimeout(unsigned seconds)
{
while (true)
{
if (this->LockFile(F_SETLK, F_WRLCK) == -1)
{
if (errno != EACCES && errno != EAGAIN)
{
return cmFileLockResult::MakeSystem();
}
}
else
{
return cmFileLockResult::MakeOk();
}
if (seconds == 0)
{
return cmFileLockResult::MakeTimeout();
}
--seconds;
cmSystemTools::Delay(1000);
}
}
int cmFileLock::LockFile(int cmd, int type)
{
struct ::flock lock;
lock.l_start = 0;
lock.l_len = 0; // lock all bytes
lock.l_pid = 0; // unused (for F_GETLK only)
lock.l_type = static_cast<short>(type); // exclusive lock
lock.l_whence = SEEK_SET;
return ::fcntl(this->File, cmd, &lock);
}

126
Source/cmFileLockWin32.cxx Normal file
View File

@ -0,0 +1,126 @@
/*============================================================================
CMake - Cross Platform Makefile Generator
Copyright 2014 Ruslan Baratov
Distributed under the OSI-approved BSD License (the "License");
see accompanying file Copyright.txt for details.
This software is distributed WITHOUT ANY WARRANTY; without even the
implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the License for more information.
============================================================================*/
#include "cmFileLock.h"
#include <windows.h> // CreateFileW
#include "cmSystemTools.h"
cmFileLock::cmFileLock(): File(INVALID_HANDLE_VALUE)
{
}
cmFileLockResult cmFileLock::Release()
{
if (this->Filename.empty())
{
return cmFileLockResult::MakeOk();
}
const unsigned long len = static_cast<unsigned long>(-1);
static OVERLAPPED overlapped;
const DWORD reserved = 0;
const BOOL unlockResult = UnlockFileEx(
File,
reserved,
len,
len,
&overlapped
);
this->Filename = "";
if (unlockResult)
{
return cmFileLockResult::MakeOk();
}
else
{
return cmFileLockResult::MakeSystem();
}
}
cmFileLockResult cmFileLock::OpenFile()
{
const DWORD access = GENERIC_READ | GENERIC_WRITE;
const DWORD shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
const PSECURITY_ATTRIBUTES security = NULL;
const DWORD attr = 0;
const HANDLE templ = NULL;
this->File = CreateFileW(
cmSystemTools::ConvertToWindowsExtendedPath(this->Filename).c_str(),
access,
shareMode,
security,
OPEN_EXISTING,
attr,
templ
);
if (this->File == INVALID_HANDLE_VALUE)
{
return cmFileLockResult::MakeSystem();
}
else
{
return cmFileLockResult::MakeOk();
}
}
cmFileLockResult cmFileLock::LockWithoutTimeout()
{
if (!this->LockFile(LOCKFILE_EXCLUSIVE_LOCK))
{
return cmFileLockResult::MakeSystem();
}
else
{
return cmFileLockResult::MakeOk();
}
}
cmFileLockResult cmFileLock::LockWithTimeout(unsigned seconds)
{
const DWORD flags = LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY;
while (true)
{
const BOOL result = this->LockFile(flags);
if (result)
{
return cmFileLockResult::MakeOk();
}
const DWORD error = GetLastError();
if (error != ERROR_LOCK_VIOLATION)
{
return cmFileLockResult::MakeSystem();
}
if (seconds == 0)
{
return cmFileLockResult::MakeTimeout();
}
--seconds;
cmSystemTools::Delay(1000);
}
}
BOOL cmFileLock::LockFile(DWORD flags)
{
const DWORD reserved = 0;
const unsigned long len = static_cast<unsigned long>(-1);
static OVERLAPPED overlapped;
return LockFileEx(
this->File,
flags,
reserved,
len,
len,
&overlapped
);
}

View File

@ -23,6 +23,7 @@
#include "cmGeneratorExpression.h"
#if defined(CMAKE_BUILD_WITH_CMAKE)
# include "cmFileLockPool.h"
# include <cmsys/hash_map.hxx>
#endif
@ -348,6 +349,10 @@ public:
std::set<cmTarget const*> const&
GetFilenameTargetDepends(cmSourceFile* sf) const;
#if defined(CMAKE_BUILD_WITH_CMAKE)
cmFileLockPool& GetFileLockPool() { return FileLockPool; }
#endif
protected:
virtual void Generate();
@ -499,6 +504,11 @@ private:
mutable std::map<cmSourceFile*, std::set<cmTarget const*> >
FilenameTargetDepends;
#if defined(CMAKE_BUILD_WITH_CMAKE)
// Pool of file locks
cmFileLockPool FileLockPool;
#endif
};
#endif

View File

@ -79,9 +79,15 @@ public:
this->GG = lg->GetGlobalGenerator();
this->LG = this->GG->GetCurrentLocalGenerator();
this->GG->SetCurrentLocalGenerator(lg);
#if defined(CMAKE_BUILD_WITH_CMAKE)
this->GG->GetFileLockPool().PushFileScope();
#endif
}
~cmLocalGeneratorCurrent()
{
#if defined(CMAKE_BUILD_WITH_CMAKE)
this->GG->GetFileLockPool().PopFileScope();
#endif
this->GG->SetCurrentLocalGenerator(this->LG);
}
};

View File

@ -4522,10 +4522,20 @@ void cmMakefile::PushScope()
this->Internal->VarUsageStack.push(usage);
this->PushLoopBlockBarrier();
#if defined(CMAKE_BUILD_WITH_CMAKE)
this->GetLocalGenerator()->GetGlobalGenerator()->
GetFileLockPool().PushFunctionScope();
#endif
}
void cmMakefile::PopScope()
{
#if defined(CMAKE_BUILD_WITH_CMAKE)
this->GetLocalGenerator()->GetGlobalGenerator()->
GetFileLockPool().PopFunctionScope();
#endif
this->PopLoopBlockBarrier();
cmDefinitions* current = &this->Internal->VarStack.top();

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,8 @@
CMake Error at LOCK-error-file-create-fail\.cmake:[0-9]+ \(file\):
file
".*"
creation failed \(check permissions\)\.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)

View File

@ -0,0 +1,3 @@
set(tmp "${CMAKE_CURRENT_BINARY_DIR}/temp-directory")
file(MAKE_DIRECTORY "${tmp}")
file(LOCK "${tmp}")

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,6 @@
CMake Error at LOCK-error-guard-incorrect\.cmake:[0-9]+ \(file\):
expected FUNCTION, FILE or PROCESS after GUARD, but got:
"FUNCTIO"\.
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)

View File

@ -0,0 +1 @@
file(LOCK "${CMAKE_CURRENT_BINARY_DIR}/file-to-lock" GUARD FUNCTIO)

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,4 @@
CMake Error at LOCK-error-incorrect-timeout\.cmake:[0-9]+ \(file\):
TIMEOUT value "qwerty" is not an unsigned integer\.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)

View File

@ -0,0 +1,4 @@
CMake Error at LOCK-error-incorrect-timeout-trail\.cmake:[0-9]+ \(file\):
TIMEOUT value "123xyz" is not an unsigned integer\.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)

View File

@ -0,0 +1 @@
file(LOCK "${CMAKE_CURRENT_BINARY_DIR}/file-to-lock" TIMEOUT 123xyz)

View File

@ -0,0 +1 @@
file(LOCK "${CMAKE_CURRENT_BINARY_DIR}/file-to-lock" TIMEOUT qwerty)

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,6 @@
CMake Error at LOCK-error-lock-fail.cmake:[0-9]+ \(file\):
directory
".*"
creation failed \(check permissions\)\.

View File

@ -0,0 +1,6 @@
set(lfile "${CMAKE_CURRENT_BINARY_DIR}/file-to-lock")
FILE(WRITE "${lfile}" "")
# Try to lock file '${lfile}/cmake.lock'. Since `lfile` is not a directory
# expected that operation will fail.
file(LOCK "${lfile}" DIRECTORY)

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,4 @@
CMake Error at LOCK-error-negative-timeout\.cmake:[0-9]+ \(file\):
TIMEOUT value "-2" is not an unsigned integer\.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)

View File

@ -0,0 +1 @@
file(LOCK "${CMAKE_CURRENT_BINARY_DIR}/file-to-lock" TIMEOUT -2)

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,8 @@
CMake Error at LOCK-error-no-function\.cmake:[0-9]+ \(file\):
error locking file
".*"
'GUARD FUNCTION' not used in function definition\.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)

View File

@ -0,0 +1 @@
file(LOCK "${CMAKE_CURRENT_BINARY_DIR}/file-to-lock" GUARD FUNCTION)

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,4 @@
CMake Error at LOCK-error-no-guard\.cmake:[0-9]+ \(file\):
expected FUNCTION, FILE or PROCESS after GUARD
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)

View File

@ -0,0 +1 @@
file(LOCK "${CMAKE_CURRENT_BINARY_DIR}/file-to-lock" GUARD)

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,4 @@
CMake Error at LOCK-error-no-path.cmake:[0-9]+ \(file\):
file must be called with at least two arguments.
Call Stack \(most recent call first\):
CMakeLists.txt:[0-9]+ \(include\)

View File

@ -0,0 +1 @@
file(LOCK)

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,4 @@
CMake Error at LOCK-error-no-result-variable\.cmake:[0-9]+ \(file\):
expected variable name after RESULT_VARIABLE
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)

View File

@ -0,0 +1 @@
file(LOCK "${CMAKE_CURRENT_BINARY_DIR}/file-to-lock" RESULT_VARIABLE)

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,4 @@
CMake Error at LOCK-error-no-timeout\.cmake:[0-9]+ \(file\):
expected timeout value after TIMEOUT
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)

View File

@ -0,0 +1 @@
file(LOCK "${CMAKE_CURRENT_BINARY_DIR}/file-to-lock" TIMEOUT)

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,12 @@
Output:[ ]*
Error: CMake Error at .*.timeout-script\.cmake:[0-9]+ \(file\):
error locking file
".*"
Timeout reached\.
+
CMake Error at LOCK-error-timeout\.cmake:[0-9]+ \(message\):
Result: 1
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)

View File

@ -0,0 +1 @@
.*

View File

@ -0,0 +1,17 @@
set(script "${CMAKE_CURRENT_LIST_DIR}/timeout-script.cmake")
set(file_to_lock "${CMAKE_CURRENT_BINARY_DIR}/file-to-lock")
file(LOCK "${file_to_lock}")
execute_process(
COMMAND "${CMAKE_COMMAND}" "-Dfile_to_lock=${file_to_lock}" -P "${script}"
RESULT_VARIABLE result
OUTPUT_VARIABLE output
ERROR_VARIABLE error
)
message("Output: ${output}")
message("Error: ${error}")
if(NOT result EQUAL 0)
message(FATAL_ERROR "Result: ${result}")
endif()

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,6 @@
CMake Error at LOCK-error-unknown-option\.cmake:[0-9]+ \(file\):
expected DIRECTORY, RELEASE, GUARD, RESULT_VARIABLE or TIMEOUT
but got: "UNKNOWN"\.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]* \(include\)

View File

@ -0,0 +1 @@
file(LOCK "${CMAKE_CURRENT_BINARY_DIR}/temp-file" UNKNOWN)

View File

@ -0,0 +1,11 @@
-- Simple lock
-- Directory lock
-- Release
-- Lock function scope
-- Lock file scope
-- Lock in subdirectory
-- Lock process scope
-- Error double lock
-- Ok
-- Timeout 0
-- Timeout not 0

View File

@ -0,0 +1,40 @@
set(lfile "${CMAKE_CURRENT_BINARY_DIR}/file-to-lock")
set(ldir "${CMAKE_CURRENT_BINARY_DIR}/dir-to-lock")
message(STATUS "Simple lock")
file(LOCK ${lfile})
message(STATUS "Directory lock")
file(LOCK ${ldir} DIRECTORY)
message(STATUS "Release")
file(LOCK ${lfile} RELEASE)
function(foo)
file(LOCK "${lfile}" GUARD FUNCTION)
endfunction()
message(STATUS "Lock function scope")
foo()
message(STATUS "Lock file scope")
add_subdirectory(subdir_test_unlock)
message(STATUS "Lock process scope")
file(LOCK "${lfile}" GUARD PROCESS)
message(STATUS "Error double lock")
file(LOCK "${lfile}" RESULT_VARIABLE lock_result)
if(lock_result STREQUAL "File already locked")
message(STATUS "Ok")
else()
message(STATUS FATAL_ERROR "Expected error message")
endif()
message(STATUS "Timeout 0")
file(LOCK "${lfile}" RELEASE)
file(LOCK "${lfile}" TIMEOUT 0)
message(STATUS "Timeout not 0")
file(LOCK "${lfile}" RELEASE)
file(LOCK "${lfile}" TIMEOUT 3)

View File

@ -3,3 +3,17 @@ include(RunCMake)
run_cmake(INSTALL-DIRECTORY)
run_cmake(INSTALL-MESSAGE-bad)
run_cmake(FileOpenFailRead)
run_cmake(LOCK)
run_cmake(LOCK-error-file-create-fail)
run_cmake(LOCK-error-guard-incorrect)
run_cmake(LOCK-error-incorrect-timeout)
run_cmake(LOCK-error-incorrect-timeout-trail)
run_cmake(LOCK-error-lock-fail)
run_cmake(LOCK-error-negative-timeout)
run_cmake(LOCK-error-no-function)
run_cmake(LOCK-error-no-guard)
run_cmake(LOCK-error-no-path)
run_cmake(LOCK-error-no-result-variable)
run_cmake(LOCK-error-no-timeout)
run_cmake(LOCK-error-timeout)
run_cmake(LOCK-error-unknown-option)

View File

@ -0,0 +1,2 @@
message(STATUS "Lock in subdirectory")
file(LOCK "${lfile}" GUARD FILE)

View File

@ -0,0 +1,5 @@
if(NOT file_to_lock)
message(FATAL_ERROR "file_to_lock is empty")
endif()
file(LOCK "${file_to_lock}" TIMEOUT 1)