#ifdef __MWERKS__
#define _CPP_ATOMICFILE
#endif
#include <Security/AtomicFile.h>
#include <Security/DbName.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <memory>
#include <stack>
#if _USE_IO == _USE_IO_POSIX
#include <stdio.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <locale.h>
#include <stdlib.h>
#include <string.h>
#elif _USE_IO == _USE_IO_MACOS
typedef SInt32 ssize_t;
#endif
using namespace std;
AtomicFile::AtomicFile(const DbName &inDbName) :
mReadFile(nil),
mReadFilename(inDbName.dbName()),
mWriteFile(nil),
mWriteFilename(mReadFilename + ",") {
if (inDbName.dbLocation() != nil || inDbName.dbName().length() == 0)
CssmError::throwMe(CSSMERR_DL_INVALID_DB_LOCATION);
}
AtomicFile::~AtomicFile()
{
try
{
if (mWriteFile)
rollback();
}
catch(...) {}
for (OpenFileMap::iterator it = mOpenFileMap.begin(); it != mOpenFileMap.end(); it++)
{
try
{
it->second->close();
}
catch(...) {}
try
{
delete it->second;
}
catch(...) {}
}
}
void
AtomicFile::close()
{
StLock<Mutex> _(mReadLock);
if (mReadFile == nil)
return;
OpenFile *aOpenFile = mReadFile;
mReadFile = nil;
if (aOpenFile->mUseCount == 0)
{
if (mWriteLock.tryLock())
{
mWriteLock.unlock();
mOpenFileMap.erase(aOpenFile->versionId());
try
{
aOpenFile->close();
}
catch(...)
{
delete aOpenFile;
throw;
}
delete aOpenFile;
}
}
}
AtomicFile::VersionId
AtomicFile::enterRead(const uint8 *&outFileAddress, size_t &outLength)
{
StLock<Mutex> _(mReadLock);
if (mReadFile != nil)
{
if (mReadFile->isDirty())
{
OpenFile *aOpenFile = mReadFile;
mReadFile = nil;
if (aOpenFile->mUseCount == 0)
{
if (mWriteLock.tryLock())
{
mWriteLock.unlock();
mOpenFileMap.erase(aOpenFile->versionId());
try
{
aOpenFile->close();
}
catch(...)
{
delete aOpenFile;
throw;
}
delete aOpenFile;
}
}
}
}
if (mReadFile == nil)
{
mReadFile = new OpenFile(mReadFilename, false, false, 0);
mOpenFileMap.insert(OpenFileMap::value_type(mReadFile->versionId(), mReadFile));
}
mReadFile->mUseCount++;
outLength = mReadFile->length();
outFileAddress = mReadFile->address();
return mReadFile->versionId();
}
void
AtomicFile::exitRead(VersionId inVersionId)
{
StLock<Mutex> _(mReadLock);
OpenFileMap::iterator it = mOpenFileMap.find(inVersionId);
if (it == mOpenFileMap.end())
CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR);
OpenFile *aOpenFile = it->second;
aOpenFile->mUseCount--;
if (aOpenFile->mUseCount == 0 && aOpenFile != mReadFile)
{
if (mWriteLock.tryLock())
{
mWriteLock.unlock();
mOpenFileMap.erase(it);
try
{
aOpenFile->close();
}
catch(...)
{
delete aOpenFile;
throw;
}
delete aOpenFile;
}
}
}
bool AtomicFile::isDirty(VersionId inVersionId)
{
StLock<Mutex> _(mReadLock);
OpenFileMap::iterator it = mOpenFileMap.find(inVersionId);
if (it == mOpenFileMap.end())
CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR);
return it->second->isDirty();
}
void
AtomicFile::performDelete()
{
mWriteLock.lock();
OpenFile *aReadFile = nil;
try
{
for (;;)
{
aReadFile = new OpenFile(mReadFilename, true, true, 0);
if (!aReadFile->isDirty())
break;
aReadFile->close();
delete aReadFile;
aReadFile = nil;
}
StLock<Mutex> _(mReadLock);
unlink(mReadFilename);
mReadFile = nil;
aReadFile->setDirty();
endWrite();
}
catch(...)
{
if (aReadFile)
{
try
{
VersionId aVersionId = aReadFile->versionId();
aReadFile->close();
mOpenFileMap.erase(aVersionId);
} catch(...) {}
delete aReadFile;
}
endWrite();
throw;
}
endWrite();
}
AtomicFile::VersionId
AtomicFile::enterCreate(FileRef &outWriteRef)
{
mWriteLock.lock();
OpenFile *aReadFile = nil;
try
{
StLock<Mutex> _(mReadLock);
aReadFile = new OpenFile(mReadFilename, false, true, 1);
mWriteFile = new OpenFile(mWriteFilename, true, false, aReadFile->versionId() + 1);
mOpenFileMap.insert(OpenFileMap::value_type(-1, aReadFile));
outWriteRef = mWriteFile->fileRef();
mCreating = true; return aReadFile->versionId();
}
catch(...)
{
try
{
if (aReadFile)
{
try
{
aReadFile->close();
mOpenFileMap.erase(-1);
} catch(...) {}
delete aReadFile;
}
if (mWriteFile)
{
try
{
mWriteFile->close();
unlink(mWriteFilename);
} catch(...) {}
delete mWriteFile;
mWriteFile = nil;
}
}
catch(...) {}
endWrite();
throw;
}
}
AtomicFile::VersionId
AtomicFile::enterWrite(const uint8 *&outFileAddress, size_t &outLength, FileRef &outWriteRef)
{
mWriteLock.lock();
mCreating = false; OpenFile *aReadFile = nil;
try
{
for (;;)
{
aReadFile = new OpenFile(mReadFilename, true, true, 0);
if (!aReadFile->isDirty())
break;
aReadFile->close();
delete aReadFile;
aReadFile = nil;
}
StLock<Mutex> _(mReadLock);
mWriteFile = new OpenFile(mWriteFilename, true, false, aReadFile->versionId() + 1);
mOpenFileMap.insert(OpenFileMap::value_type(-1, aReadFile));
outWriteRef = mWriteFile->fileRef();
outLength = aReadFile->length();
outFileAddress = aReadFile->address();
return aReadFile->versionId();
}
catch(...)
{
try
{
if (aReadFile)
{
try
{
aReadFile->close();
mOpenFileMap.erase(-1);
} catch(...) {}
delete aReadFile;
}
if (mWriteFile)
{
try
{
mWriteFile->close();
unlink(mWriteFilename);
} catch(...) {}
delete mWriteFile;
mWriteFile = nil;
}
}
catch(...) {}
endWrite();
throw;
}
}
AtomicFile::VersionId
AtomicFile::commit()
{
StLock<Mutex> _(mReadLock);
if (mWriteFile == nil)
CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR);
try
{
VersionId aVersionId = mWriteFile->versionId();
mWriteFile->close();
delete mWriteFile;
mWriteFile = nil;
OpenFileMap::iterator it = mOpenFileMap.find(-1);
if (it == mOpenFileMap.end())
CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR);
rename(mWriteFilename, mReadFilename);
OpenFile *aOpenFile = it->second;
mReadFile = nil;
aOpenFile->setDirty();
endWrite();
return aVersionId;
}
catch (...)
{
try
{
unlink(mWriteFilename);
}catch(...) {}
endWrite();
throw;
}
}
void
AtomicFile::rollback()
{
StLock<Mutex> _(mReadLock);
if (mWriteFile == nil)
CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR);
try
{
mWriteFile->close();
delete mWriteFile;
mWriteFile = nil;
unlink(mWriteFilename);
if (mCreating)
unlink(mReadFilename);
endWrite();
}
catch(...)
{
try
{
unlink(mWriteFilename);
}catch(...) {}
endWrite();
throw;
}
}
void
AtomicFile::endWrite()
{
try
{
stack<VersionId> aDeleteList;
OpenFileMap::iterator it;
for (it = mOpenFileMap.begin();
it != mOpenFileMap.end();
it++)
{
OpenFile *aOpenFile = it->second;
if (aOpenFile != mReadFile && aOpenFile->mUseCount == 0)
aDeleteList.push(it->first);
}
while (!aDeleteList.empty())
{
it = mOpenFileMap.find(aDeleteList.top());
aDeleteList.pop();
try
{
it->second->close();
}
catch(...) {}
delete it->second;
mOpenFileMap.erase(it);
}
if (mWriteFile)
{
mWriteFile->close();
}
}
catch(...)
{
delete mWriteFile;
mWriteFile = nil;
mWriteLock.unlock();
throw;
}
delete mWriteFile;
mWriteFile = nil;
mWriteLock.unlock();
}
void
AtomicFile::rename(const string &inSrcFilename, const string &inDestFilename)
{
if (::rename(inSrcFilename.c_str(), inDestFilename.c_str()))
UnixError::throwMe(errno);
}
void
AtomicFile::unlink(const string &inFilename)
{
if (::unlink(inFilename.c_str()))
UnixError::throwMe(errno);
}
void
AtomicFile::write(OffsetType inOffsetType, uint32 inOffset, const uint32 inData)
{
uint32 aData = htonl(inData);
write(inOffsetType, inOffset, reinterpret_cast<uint8 *>(&aData), sizeof(aData));
}
void
AtomicFile::write(OffsetType inOffsetType, uint32 inOffset,
const uint32 *inData, uint32 inCount)
{
#ifdef HOST_LONG_IS_NETWORK_LONG
const uint32 *aBuffer = inData;
#else
auto_array<uint32> aBuffer(inCount);
for (uint32 i = 0; i < inCount; i++)
aBuffer.get()[i] = htonl(inData[i]);
#endif
write(inOffsetType, inOffset, reinterpret_cast<const uint8 *>(aBuffer.get()),
inCount * sizeof(*inData));
}
void
AtomicFile::write(OffsetType inOffsetType, uint32 inOffset, const uint8 *inData, uint32 inLength)
{
if (mWriteFile == nil)
CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR);
if (inOffsetType != None)
{
if (::lseek(mWriteFile->mFileRef, inOffset, inOffsetType == FromStart ? SEEK_SET : SEEK_CUR) == -1)
UnixError::throwMe(errno);
}
if (::write(mWriteFile->mFileRef, reinterpret_cast<const char *>(inData),
inLength) != static_cast<ssize_t>(inLength))
UnixError::throwMe(errno);
}
AtomicFile::OpenFile::OpenFile(const string &inFilename, bool write, bool lock, VersionId inVersionId) :
mUseCount(0),
mVersionId(inVersionId),
mAddress(NULL),
mLength(0)
{
int flags, mode = 0;
if (write && lock)
{
flags = O_RDWR;
mState = ReadWrite;
}
else if (write && !lock)
{
flags = O_WRONLY|O_CREAT|O_TRUNC;
mode = 0666;
mState = Write;
}
else if (!write && lock)
{
flags = O_WRONLY|O_CREAT|O_TRUNC|O_EXCL;
mode = 0666;
mState = Create;
}
else
{
flags = O_RDONLY;
mState = Read;
}
mFileRef = ::open(inFilename.c_str(), flags, mode);
if (mFileRef == -1)
{
int error = errno;
#if _USE_IO == _USE_IO_POSIX
if (error == ENOENT)
{
if (mState == ReadWrite || mState == Read || mState == Write)
CssmError::throwMe(CSSMERR_DL_DATASTORE_DOESNOT_EXIST);
if (mState == Create)
{
mkpath(inFilename);
mFileRef = ::open(inFilename.c_str(), flags, mode);
error = mFileRef == -1 ? errno : 0;
if (error == ENOENT)
CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED);
}
}
if (error == EACCES)
CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED);
if (error == EEXIST)
CssmError::throwMe(CSSMERR_DL_DATASTORE_ALREADY_EXISTS);
#endif
if (error)
UnixError::throwMe(errno);
}
if (mState == Create)
writeVersionId(mVersionId);
if (mState == Write)
return;
try
{
mLength = ::lseek(mFileRef, 0, SEEK_END);
if (mLength == static_cast<size_t>(-1))
UnixError::throwMe(errno);
if (mLength == 0)
{
mVersionId = 0;
return; }
#if _USE_IO == _USE_IO_POSIX
if (lock)
{
struct flock mLock;
mLock.l_start = 0;
mLock.l_len = 1;
mLock.l_pid = getpid();
mLock.l_type = F_WRLCK;
mLock.l_whence = SEEK_SET;
for (;;)
{
if (::fcntl(mFileRef, F_SETLKW, reinterpret_cast<int>(&mLock)) == -1)
{
int error = errno;
if (error == EINTR)
continue;
if (error != ENOTSUP)
UnixError::throwMe(error);
mFcntlLock = false;
}
else
mFcntlLock = true;
break;
}
}
if (mState != Create)
{
mAddress = reinterpret_cast<const uint8 *>
(::mmap(0, mLength, PROT_READ, MAP_FILE|MAP_SHARED,
mFileRef, 0));
if (mAddress == reinterpret_cast<const uint8 *>(-1))
{
mAddress = NULL;
UnixError::throwMe(errno);
}
mVersionId = readVersionId();
}
#else
if (mState != Create)
{
mAddress = reinterpret_cast<const uint8 *>(-1);
auto_array<char> aBuffer(mLength);
if (::read(mFileRef, aBuffer.get(), mLength) != mLength)
UnixError::throwMe(errno);
mAddress = reinterpret_cast<const uint8 *>(aBuffer.release());
mVersionId = readVersionId();
}
#endif
}
catch(...)
{
if (mState != Closed)
::close(mFileRef);
throw;
}
}
AtomicFile::OpenFile::~OpenFile()
{
close();
}
void
AtomicFile::OpenFile::close()
{
int error = 0;
if (mAddress != NULL)
{
#if _USE_IO == _USE_IO_POSIX
if (::munmap(const_cast<uint8 *>(mAddress), mLength) == -1)
error = errno;
#else
delete[] mAddress;
#endif
mAddress = NULL;
}
if (mState == Write)
writeVersionId(mVersionId);
if (mState != Closed)
{
mState = Closed;
if (::close(mFileRef) == -1)
error = errno;
}
if (error != 0)
UnixError::throwMe(error);
}
bool
AtomicFile::OpenFile::isDirty()
{
if (mAddress == NULL)
CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR);
return (mVersionId != readVersionId()) || mVersionId == 0;
}
void
AtomicFile::OpenFile::setDirty()
{
if (mState != ReadWrite && mState != Create)
CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR);
writeVersionId(0);
}
void
AtomicFile::OpenFile::unlock()
{
#if 0
if (mFcntlLock)
{
struct flock mLock;
mLock.l_start = 0;
mLock.l_len = 1;
mLock.l_pid = getpid();
mLock.l_type = F_UNLCK;
mLock.l_whence = SEEK_SET;
if (::fcntl(mFileRef, F_SETLK, reinterpret_cast<int>(&mLock)) == -1)
UnixError::throwMe(errno);
}
#endif
}
AtomicFile::VersionId
AtomicFile::OpenFile::readVersionId()
{
const uint8 *ptr;
char buf[4];
if (mAddress == NULL)
{
if (mLength < 4)
CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT);
if (::lseek(mFileRef, mLength - 4, SEEK_SET) == -1)
UnixError::throwMe(errno);
ptr = reinterpret_cast<uint8 *>(buf);
if (::read(mFileRef, buf, 4) != 4)
UnixError::throwMe(errno);
}
else
{
ptr = mAddress + mLength - 4;
if (mLength < 4)
CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT);
}
VersionId aVersionId = 0;
for (int i = 0; i < 4; i++)
{
aVersionId = (aVersionId << 8) + ptr[i];
}
return aVersionId;
}
void
AtomicFile::OpenFile::writeVersionId(VersionId inVersionId)
{
if (mState == ReadWrite)
{
if (mLength < 4)
CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT);
if (::lseek(mFileRef, mLength - 4, SEEK_SET) == -1)
UnixError::throwMe(errno);
}
else
{
if (::lseek(mFileRef, 0, SEEK_END) == -1)
UnixError::throwMe(errno);
}
uint8 buf[4];
for (int i = 3; i >= 0; i--)
{
buf[i] = inVersionId & 0xff;
inVersionId = inVersionId >> 8;
}
if (::write(mFileRef, reinterpret_cast<char *>(buf), 4) != 4)
UnixError::throwMe(errno);
}
void
AtomicFile::OpenFile::mkpath(const std::string &inFilename)
{
char *path = const_cast<char *>(inFilename.c_str()); struct stat sb;
char *slash;
mode_t dir_mode = (0777 & ~umask(0)) | S_IWUSR | S_IXUSR;
slash = path;
for (;;)
{
slash += strspn(slash, "/");
slash += strcspn(slash, "/");
if (*slash == '\0')
break;
*slash = '\0';
if (stat(path, &sb))
{
if (errno != ENOENT || mkdir(path, dir_mode))
UnixError::throwMe(errno);
if (chmod(path, dir_mode) == -1)
UnixError::throwMe(errno);
}
else if (!S_ISDIR(sb.st_mode))
CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED);
*slash = '/';
}
}
struct AtomicFileRef::InitArg
{
AtomicFile::VersionId versionId;
const uint8 *address;
size_t length;
};
AtomicFileRef::~AtomicFileRef()
{
}
AtomicFileRef::AtomicFileRef(AtomicFile &inAtomicFile, const InitArg &inInitArg) :
mVersionId(inInitArg.versionId),
mAtomicFile(inAtomicFile),
mAddress(inInitArg.address),
mLength(inInitArg.length)
{
}
AtomicFileReadRef::~AtomicFileReadRef()
{
try {
mAtomicFile.exitRead(mVersionId);
}
catch(...) {
}
}
AtomicFileRef::InitArg
AtomicFileReadRef::enterRead(AtomicFile &inAtomicFile)
{
InitArg anInitArg;
anInitArg.versionId = inAtomicFile.enterRead(anInitArg.address, anInitArg.length);
return anInitArg;
}
AtomicFileReadRef::AtomicFileReadRef(AtomicFile &inAtomicFile) :
AtomicFileRef(inAtomicFile, enterRead(inAtomicFile))
{
}
AtomicFileWriteRef::~AtomicFileWriteRef()
{
if (mOpen) {
try {
mAtomicFile.rollback();
}
catch (...)
{
}
}
}
AtomicFileRef::InitArg
AtomicFileWriteRef::enterWrite(AtomicFile &inAtomicFile, AtomicFile::FileRef &outWriteFileRef)
{
InitArg anInitArg;
anInitArg.versionId = inAtomicFile.enterWrite(anInitArg.address, anInitArg.length, outWriteFileRef);
return anInitArg;
}
AtomicFileWriteRef::AtomicFileWriteRef(AtomicFile &inAtomicFile) :
AtomicFileRef(inAtomicFile, enterWrite(inAtomicFile, mFileRef))
{
}