/*
- * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved.
+ * Copyright (c) 2000-2001, 2003 Apple Computer, Inc. All Rights Reserved.
*
* The contents of this file constitute Original Code as defined in and are
* subject to the Apple Public Source License Version 1.2 (the 'License').
*/
-//
-// AtomicFile.cpp - Description t.b.d.
-//
-#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>
+#include <Security/devrandom.h>
-#if _USE_IO == _USE_IO_POSIX
-#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <syslog.h>
+#include <sys/param.h>
#include <sys/types.h>
-#include <sys/mman.h>
+#include <unistd.h>
-#include <sys/stat.h>
-//#include <err.h>
-#include <locale.h>
-#include <stdlib.h>
-#include <cstring>
-#include <sys/param.h>
-#elif _USE_IO == _USE_IO_MACOS
-typedef SInt32 ssize_t;
-#endif
+#define kAtomicFileMaxBlockSize INT_MAX
-using namespace std;
-AtomicFile::AtomicFile(const DbName &inDbName) :
- mReadFile(nil),
- mReadFilename(inDbName.dbName()),
- mWriteFile(nil),
- mWriteFilename(mReadFilename + ",") // XXX Do some more work here like resolving symlinks/aliases etc.
+//
+// AtomicFile.cpp - Description t.b.d.
+//
+AtomicFile::AtomicFile(const std::string &inPath) :
+ mPath(inPath)
{
- debug("atomicfile", "%p construct name=%s", this, mReadFilename.c_str());
- // We only support databases with string names of non-zero length.
- if (inDbName.dbLocation() != nil || inDbName.dbName().length() == 0)
- CssmError::throwMe(CSSMERR_DL_INVALID_DB_LOCATION);
+ pathSplit(inPath, mDir, mFile);
}
AtomicFile::~AtomicFile()
{
- // Assume there are no more running theads in this object.
- debug("atomicfile", "%p destroyed", this);
-
- // Try hard to clean up as much as possible.
- try
- {
- // Rollback any pending write.
- if (mWriteFile)
- rollback();
- }
- catch(...) {}
-
- // Close and delete all files in mOpenFileMap
- for (OpenFileMap::iterator it = mOpenFileMap.begin(); it != mOpenFileMap.end(); it++)
- {
- try
- {
- it->second->close();
- }
- catch(...) {}
- try
- {
- delete it->second;
- }
- catch(...) {}
- }
}
+// Aquire the write lock and remove the file.
void
-AtomicFile::close()
+AtomicFile::performDelete()
{
- debug("atomicfile", "%p close", this);
- StLock<Mutex> _(mReadLock);
-
- // If we have no read file we have nothing to close.
- if (mReadFile == nil)
- return;
+ AtomicLockedFile lock(*this);
+ if (::unlink(mPath.c_str()) != 0)
+ {
+ int error = errno;
+ secdebug("atomicfile", "unlink %s: %s", mPath.c_str(), strerror(error));
+ if (error == ENOENT)
+ CssmError::throwMe(CSSMERR_DL_DATASTORE_DOESNOT_EXIST);
+ else
+ UnixError::throwMe(error);
+ }
+}
- // Remember mReadFile and set it to nil, so that it will be closed after any pending write completes
- OpenFile *aOpenFile = mReadFile;
- mReadFile = nil;
+// Aquire the write lock and rename the file (and bump the version and stuff).
+void
+AtomicFile::rename(const std::string &inNewPath)
+{
+ const char *path = mPath.c_str();
+ const char *newPath = inNewPath.c_str();
- // If aOpenFile has a zero use count no other thread is currently using it,
- // so we can safely remove it from the map.
- if (aOpenFile->mUseCount == 0)
- {
- // Do not close any files (nor remove them from the map) while some thread is writing
- // since doing so might release the lock we are holding.
- if (mWriteLock.tryLock())
- {
- // Release the write lock immediately since tryLock just aquired it and we don't want to write.
- mWriteLock.unlock();
-
- // Remove aOpenFile from the map of open files.
- mOpenFileMap.erase(aOpenFile->versionId());
- try
- {
- aOpenFile->close();
- }
- catch(...)
- {
- delete aOpenFile;
- throw;
- }
- delete aOpenFile;
- }
- }
+ // @@@ lock the destination file too.
+ AtomicLockedFile lock(*this);
+ if (::rename(path, newPath) != 0)
+ {
+ int error = errno;
+ secdebug("atomicfile", "rename(%s, %s): %s", path, newPath, strerror(error));
+ UnixError::throwMe(error);
+ }
}
-AtomicFile::VersionId
-AtomicFile::enterRead(const uint8 *&outFileAddress, size_t &outLength)
+// Lock the file for writing and return a newly created AtomicTempFile.
+RefPointer<AtomicTempFile>
+AtomicFile::create(mode_t mode)
{
- StLock<Mutex> _(mReadLock);
+ const char *path = mPath.c_str();
- // If we already have a read file check if it is still current.
- if (mReadFile != nil)
- {
- if (mReadFile->isDirty())
- {
- // Remember mReadFile and set it to nil in case an exception is thrown
- OpenFile *aOpenFile = mReadFile;
- mReadFile = nil;
-
- // If aOpenFile has a zero use count no other thread is currently using it,
- // so we can safely remove it from the map.
- if (aOpenFile->mUseCount == 0)
- {
- // Do not close any files (nor remove them from the map) while some thread is writing
- // since doing so might release the lock we are holding.
- if (mWriteLock.tryLock())
- {
- // Release the write lock immediately since tryLock just aquired it and we don't want to write.
- mWriteLock.unlock();
-
- // Remove aOpenFile from the map of open files.
- mOpenFileMap.erase(aOpenFile->versionId());
- try
- {
- aOpenFile->close();
- }
- catch(...)
- {
- delete aOpenFile;
- throw;
- }
- delete aOpenFile;
- }
- }
- }
- }
+ // First make sure the directory to this file exists and is writable
+ mkpath(mDir);
- // If we never had or no longer have an open read file. Open it now.
- if (mReadFile == nil)
+ RefPointer<AtomicLockedFile> lock(new AtomicLockedFile(*this));
+ int fileRef = ropen(path, O_WRONLY|O_CREAT|O_EXCL, mode);
+ if (fileRef == -1)
{
- mReadFile = new OpenFile(mReadFilename, false, false, 0, 0);
- mOpenFileMap.insert(OpenFileMap::value_type(mReadFile->versionId(), mReadFile));
+ int error = errno;
+ secdebug("atomicfile", "open %s: %s", path, strerror(error));
+
+ // Do the obvious error code translations here.
+ // @@@ Consider moving these up a level.
+ if (error == EACCES)
+ CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED);
+ else if (error == EEXIST)
+ CssmError::throwMe(CSSMERR_DL_DATASTORE_ALREADY_EXISTS);
+ else
+ UnixError::throwMe(error);
}
- // Note that mReadFile->isDirty() might actually return true here, but all that means is
- // that we are looking at data that was commited after we opened the file which might
- // happen in a few miliseconds anyway.
+ rclose(fileRef);
- // Bump up the use count of our OpenFile.
- mReadFile->mUseCount++;
+ try
+ {
+ // Now that we have created the lock and the new db file create a tempfile
+ // object.
+ RefPointer<AtomicTempFile> temp(new AtomicTempFile(*this, lock, mode));
+ secdebug("atomicfile", "%p created %s", this, path);
+ return temp;
+ }
+ catch (...)
+ {
+ // Creating the temp file failed so remove the db file we just created too.
+ if (::unlink(path) == -1)
+ {
+ secdebug("atomicfile", "unlink %s: %s", path, strerror(errno));
+ }
+ throw;
+ }
+}
- // Return the length of the file and the mapped address.
- outLength = mReadFile->length();
- outFileAddress = mReadFile->address();
- return mReadFile->versionId();
+// Lock the database file for writing and return a newly created AtomicTempFile.
+RefPointer<AtomicTempFile>
+AtomicFile::write()
+{
+ RefPointer<AtomicLockedFile> lock(new AtomicLockedFile(*this));
+ return new AtomicTempFile(*this, lock);
}
-void
-AtomicFile::exitRead(VersionId inVersionId)
+// Return a bufferedFile containing current version of the file for reading.
+RefPointer<AtomicBufferedFile>
+AtomicFile::read()
{
- StLock<Mutex> _(mReadLock);
- OpenFileMap::iterator it = mOpenFileMap.find(inVersionId);
- // If the inVersionId is not in the map anymore something really bad happned.
- if (it == mOpenFileMap.end())
- CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR);
-
- OpenFile *aOpenFile = it->second;
- aOpenFile->mUseCount--;
-
- // Don't close the current active file even if its mUseCount hits 0 since someone
- // else will probably request it soon.
- if (aOpenFile->mUseCount == 0 && aOpenFile != mReadFile)
- {
- // Do not close any files (nor remove them from the map) while some thread is writing
- // since doing so might release the lock we are holding.
- if (mWriteLock.tryLock())
- {
- // Release the write lock immidiatly since tryLock just aquired it and we don't want to write.
- mWriteLock.unlock();
-
- // Remove from the map, close and delete aOpenFile.
- mOpenFileMap.erase(it);
- try
- {
- aOpenFile->close();
- }
- catch(...)
- {
- delete aOpenFile;
- throw;
- }
- delete aOpenFile;
- }
- }
+ return new AtomicBufferedFile(mPath);
}
-bool AtomicFile::isDirty(VersionId inVersionId)
+mode_t
+AtomicFile::mode() const
{
- StLock<Mutex> _(mReadLock);
- OpenFileMap::iterator it = mOpenFileMap.find(inVersionId);
- // If the inVersionId is not in the map anymore something really bad happned.
- if (it == mOpenFileMap.end())
- CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR);
+ const char *path = mPath.c_str();
+ struct stat st;
+ if (::stat(path, &st) == -1)
+ {
+ int error = errno;
+ secdebug("atomicfile", "stat %s: %s", path, strerror(error));
+ UnixError::throwMe(error);
+ }
+ return st.st_mode;
+}
- return it->second->isDirty();
+// Split full into a dir and file component.
+void
+AtomicFile::pathSplit(const std::string &inFull, std::string &outDir, std::string &outFile)
+{
+ std::string::size_type slash, len = inFull.size();
+ slash = inFull.rfind('/');
+ if (slash == std::string::npos)
+ {
+ outDir = "";
+ outFile = inFull;
+ }
+ else if (slash + 1 == len)
+ {
+ outDir = inFull;
+ outFile = "";
+ }
+ else
+ {
+ outDir = inFull.substr(0, slash + 1);
+ outFile = inFull.substr(slash + 1, len);
+ }
}
+//
+// Make sure the directory up to inDir exists inDir *must* end in a slash.
+//
void
-AtomicFile::performDelete()
+AtomicFile::mkpath(const std::string &inDir, mode_t mode)
{
- // Prevent any other threads in this process from writing.
- mWriteLock.lock();
+ for (std::string::size_type pos = 0; (pos = inDir.find('/', pos + 1)) != std::string::npos;)
+ {
+ std::string path = inDir.substr(0, pos);
+ const char *cpath = path.c_str();
+ struct stat sb;
+ if (::stat(cpath, &sb))
+ {
+ if (errno != ENOENT || ::mkdir(cpath, mode))
+ UnixError::throwMe(errno);
+ }
+ else if (!S_ISDIR(sb.st_mode))
+ CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED); // @@@ Should be is a directory
+ }
+}
- OpenFile *aReadFile = nil;
- try
- {
- // Keep reopening mReadFilename until the lock has been aquired on a non-dirty file.
- // XXX This is a potential infinite loop.
- for (;;)
- {
- aReadFile = new OpenFile(mReadFilename, true, true, 0, 0);
- if (!aReadFile->isDirty())
- break;
+int
+AtomicFile::ropen(const char *const name, int flags, mode_t mode)
+{
+ int fd, tries_left = 4 /* kNoResRetry */;
+ do
+ {
+ fd = ::open(name, flags, mode);
+ } while (fd < 0 && (errno == EINTR || errno == ENFILE && --tries_left >= 0));
- aReadFile->close();
- delete aReadFile;
- aReadFile = nil;
- }
+ return fd;
+}
- // Aquire the read lock so no other thread will open the file
- StLock<Mutex> _(mReadLock);
+int
+AtomicFile::rclose(int fd)
+{
+ int result;
+ do
+ {
+ result = ::close(fd);
+ } while(result && errno == EINTR);
- // Delete the file.
- unlink(mReadFilename);
+ return result;
+}
- // Clear our current mReadFile since it refers to the deleted file.
- mReadFile = nil;
+//
+// AtomicBufferedFile - This represents an instance of a file opened for reading.
+// The file is read into memory and closed after this is done.
+// The memory is released when this object is destroyed.
+//
+AtomicBufferedFile::AtomicBufferedFile(const std::string &inPath) :
+ mPath(inPath),
+ mFileRef(-1),
+ mBuffer(NULL),
+ mLength(0)
+{
+}
- // Mark the old file as modified
- aReadFile->setDirty();
+AtomicBufferedFile::~AtomicBufferedFile()
+{
+ if (mFileRef >= 0)
+ {
+ AtomicFile::rclose(mFileRef);
+ secdebug("atomicfile", "%p closed %s", this, mPath.c_str());
+ }
- // Close any open files.
- endWrite();
- }
- catch(...)
- {
- if (aReadFile)
- {
- try
- {
- VersionId aVersionId = aReadFile->versionId();
- aReadFile->close();
- mOpenFileMap.erase(aVersionId);
- } catch(...) {}
- delete aReadFile;
- }
- endWrite();
- throw;
- }
- endWrite();
+ if (mBuffer)
+ {
+ secdebug("atomicfile", "%p free %s buffer %p", this, mPath.c_str(), mBuffer);
+ free(mBuffer);
+ }
}
-AtomicFile::VersionId
-AtomicFile::enterCreate(FileRef &outWriteRef)
+//
+// Open the file and return the length in bytes.
+//
+off_t
+AtomicBufferedFile::open()
{
- // Prevent any other threads in this process from writing.
- mWriteLock.lock();
- OpenFile *aReadFile = nil;
- try
+ const char *path = mPath.c_str();
+ if (mFileRef >= 0)
+ {
+ secdebug("atomicfile", "open %s: already open, closing and reopening", path);
+ close();
+ }
+
+ mFileRef = AtomicFile::ropen(path, O_RDONLY, 0);
+ if (mFileRef == -1)
{
- // No threads can read during creation
- StLock<Mutex> _(mReadLock);
+ int error = errno;
+ secdebug("atomicfile", "open %s: %s", path, strerror(error));
- // Create mReadFilename until the lock has been aquired on a non-dirty file.
- aReadFile = new OpenFile(mReadFilename, false, true, 1, 0666);
+ // Do the obvious error code translations here.
+ // @@@ Consider moving these up a level.
+ if (error == ENOENT)
+ CssmError::throwMe(CSSMERR_DL_DATASTORE_DOESNOT_EXIST);
+ else if (error == EACCES)
+ CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED);
+ else
+ UnixError::throwMe(error);
+ }
- // Open mWriteFile for writing.
- mWriteFile = new OpenFile(mWriteFilename, true, false, aReadFile->versionId() + 1, 0666);
+ mLength = ::lseek(mFileRef, 0, SEEK_END);
+ if (mLength == -1)
+ {
+ int error = errno;
+ secdebug("atomicfile", "lseek(%s, END): %s", path, strerror(error));
+ AtomicFile::rclose(mFileRef);
+ UnixError::throwMe(error);
+ }
- // Insert aReadFile into the map (do this after opening mWriteFile just in case that throws).
- mOpenFileMap.insert(OpenFileMap::value_type(-1, aReadFile));
+ secdebug("atomicfile", "%p opened %s: %qd bytes", this, path, mLength);
- outWriteRef = mWriteFile->fileRef();
- mCreating = true; // So rollback() will delete mReadFileName.
- return aReadFile->versionId();
- }
- catch(...)
- {
- // Make sure we don't thow during cleanup since that would clobber the original
- // error and prevent us from releasing mWriteLock
- try
- {
- if (aReadFile)
- {
- try
- {
- aReadFile->close();
- // XXX We should only unlink if we know that no one else is currently creating the file.
- //unlink(mReadFilename);
- mOpenFileMap.erase(-1);
- } catch(...) {}
- delete aReadFile;
- }
-
- if (mWriteFile)
- {
- try
- {
- mWriteFile->close();
- unlink(mWriteFilename);
- } catch(...) {}
- delete mWriteFile;
- mWriteFile = nil;
- }
- }
- catch(...) {} // Do not throw since we already have an error.
-
- // Release the write lock and remove any unused files from the map
- endWrite();
- throw;
- }
+ return mLength;
}
-AtomicFile::VersionId
-AtomicFile::enterWrite(const uint8 *&outFileAddress, size_t &outLength, FileRef &outWriteRef)
+//
+// Read the file starting at inOffset for inLength bytes into the buffer and return
+// a pointer to it. On return outLength contain the actual number of bytes read, it
+// will only ever be less than inLength if EOF was reached, and it will never be more
+// than inLength.
+//
+const uint8 *
+AtomicBufferedFile::read(off_t inOffset, off_t inLength, off_t &outLength)
{
- // Wait for all other threads in this process to finish writing.
- mWriteLock.lock();
- mCreating = false; // So rollback() will not delete mReadFileName.
- OpenFile *aReadFile = nil;
- try
- {
- // Keep reopening mReadFilename until the lock has been aquired on a non-dirty file.
- // XXX This is a potential infinite loop.
- for (;;)
- {
- aReadFile = new OpenFile(mReadFilename, true, true, 0, 0);
- if (!aReadFile->isDirty())
- break;
-
- aReadFile->close();
- delete aReadFile;
- aReadFile = nil;
- }
-
- // We have the write lock on the file now we start modifying our shared data
- // stuctures so aquire the read lock.
- StLock<Mutex> _(mReadLock);
-
- // Open mWriteFile for writing.
- mWriteFile = new OpenFile(mWriteFilename, true, false, aReadFile->versionId() + 1, aReadFile->mode());
-
- // Insert aReadFile into the map (do this after opening mWriteFile just in case that throws).
- mOpenFileMap.insert(OpenFileMap::value_type(-1, aReadFile));
-
- outWriteRef = mWriteFile->fileRef();
- outLength = aReadFile->length();
- outFileAddress = aReadFile->address();
- return aReadFile->versionId();
- }
- catch(...)
- {
- // Make sure we don't thow during cleanup since that would clobber the original
- // error and prevent us from releasing mWriteLock
- 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(...) {} // Do not throw since we already have an error.
-
- // Release the write lock and remove any unused files from the map
- endWrite();
- throw;
- }
-}
+ if (mFileRef < 0)
+ {
+ secdebug("atomicfile", "read %s: file yet not opened, opening", mPath.c_str());
+ open();
+ }
-AtomicFile::VersionId
-AtomicFile::commit()
-{
- debug("atomicfile", "%p commit", this);
- StLock<Mutex> _(mReadLock);
- if (mWriteFile == nil)
- CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR);
+ off_t bytesLeft = inLength;
+ uint8 *ptr;
+ if (mBuffer)
+ {
+ secdebug("atomicfile", "%p free %s buffer %p", this, mPath.c_str(), mBuffer);
+ free(mBuffer);
+ }
- try
- {
- VersionId aVersionId = mWriteFile->versionId();
- mWriteFile->close();
- delete mWriteFile;
- mWriteFile = nil;
+ mBuffer = ptr = reinterpret_cast<uint8 *>(malloc(bytesLeft));
+ secdebug("atomicfile", "%p allocated %s buffer %p size %qd", this, mPath.c_str(), mBuffer, bytesLeft);
+ off_t pos = inOffset;
+ while (bytesLeft)
+ {
+ size_t toRead = bytesLeft > kAtomicFileMaxBlockSize ? kAtomicFileMaxBlockSize : size_t(bytesLeft);
+ ssize_t bytesRead = ::pread(mFileRef, ptr, toRead, pos);
+ if (bytesRead == -1)
+ {
+ int error = errno;
+ if (error == EINTR)
+ {
+ // We got interrupted by a signal, so try again.
+ secdebug("atomicfile", "pread %s: interrupted, retrying", mPath.c_str());
+ continue;
+ }
- OpenFileMap::iterator it = mOpenFileMap.find(-1);
- if (it == mOpenFileMap.end())
- CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR);
+ secdebug("atomicfile", "pread %s: %s", mPath.c_str(), strerror(error));
+ free(mBuffer);
+ mBuffer = NULL;
+ UnixError::throwMe(error);
+ }
- // First rename the file and them mark the old one as modified
- rename(mWriteFilename, mReadFilename);
- OpenFile *aOpenFile = it->second;
+ // Read returning 0 means EOF was reached so we're done.
+ if (bytesRead == 0)
+ break;
- // Clear our current mReadFile since it refers to the old file.
- mReadFile = nil;
+ secdebug("atomicfile", "%p read %s: %d bytes to %p", this, mPath.c_str(), bytesRead, ptr);
- // Mark the old file as modified
- aOpenFile->setDirty();
+ bytesLeft -= bytesRead;
+ ptr += bytesRead;
+ pos += bytesRead;
+ }
- // Close all unused files (in particular aOpenFile) and remove them from mOpenFileMap
- endWrite();
- debug("atomicfile", "%p commit done", this);
- return aVersionId;
- }
- catch (...)
- {
- // Unlink the new file to rollback the transaction and close any open files.
- try
- {
- unlink(mWriteFilename);
- }catch(...) {}
- endWrite();
- debug("atomicfile", "%p commit failed, rethrowing", this);
- throw;
- }
+ // Compute length
+ outLength = ptr - mBuffer;
+
+ return mBuffer;
}
void
-AtomicFile::rollback()
+AtomicBufferedFile::close()
{
- debug("atomicfile", "%p rollback", this);
- StLock<Mutex> _(mReadLock);
- if (mWriteFile == nil)
- CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR);
+ if (mFileRef < 0)
+ {
+ secdebug("atomicfile", "close %s: already closed", mPath.c_str());
+ }
+ else
+ {
+ int result = AtomicFile::rclose(mFileRef);
+ mFileRef = -1;
+ if (result == -1)
+ {
+ int error = errno;
+ secdebug("atomicfile", "close %s: %s", mPath.c_str(), strerror(errno));
+ UnixError::throwMe(error);
+ }
- try
- {
- mWriteFile->close();
- delete mWriteFile;
- mWriteFile = nil;
-
- // First rename the file and them mark the old one as modified
- unlink(mWriteFilename);
- if (mCreating)
- unlink(mReadFilename);
- endWrite();
- debug("atomicfile", "%p rollback complete", this);
- }
- catch(...)
- {
- // Unlink the new file to rollback the transaction and close any open files.
- try
- {
- unlink(mWriteFilename);
- }catch(...) {}
- endWrite();
- debug("atomicfile", "%p rollback failed, rethrowing", this);
- throw;
- }
+ secdebug("atomicfile", "%p closed %s", this, mPath.c_str());
+ }
}
-// This private function is called by a successfull commit(), rollback() or performDelete() as well
-// as by a failed enterWrite() or enterCreate().
-void
-AtomicFile::endWrite()
+
+//
+// AtomicTempFile - A temporary file to write changes to.
+//
+AtomicTempFile::AtomicTempFile(AtomicFile &inFile, const RefPointer<AtomicLockedFile> &inLockedFile, mode_t mode) :
+ mFile(inFile),
+ mLockedFile(inLockedFile),
+ mCreating(true)
{
- try
- {
- // We need to go in and close and delete all unused files from the queue
- stack<VersionId> aDeleteList;
- OpenFileMap::iterator it;
- for (it = mOpenFileMap.begin();
- it != mOpenFileMap.end();
- it++)
- {
- OpenFile *aOpenFile = it->second;
- // If aOpenFile is unused and it is not the mReadFile schedule it for close and removal.
- // Note that if this is being called after a commit mReadFile will have been set to nil.
- if (aOpenFile != mReadFile && aOpenFile->mUseCount == 0)
- aDeleteList.push(it->first);
- }
-
- // Remove everything that was scheduled for removal
- 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;
- }
+ create(mode);
+}
- delete mWriteFile;
- mWriteFile = nil;
- mWriteLock.unlock();
+AtomicTempFile::AtomicTempFile(AtomicFile &inFile, const RefPointer<AtomicLockedFile> &inLockedFile) :
+ mFile(inFile),
+ mLockedFile(inLockedFile),
+ mCreating(false)
+{
+ create(mFile.mode());
}
-void
-AtomicFile::rename(const string &inSrcFilename, const string &inDestFilename)
+AtomicTempFile::~AtomicTempFile()
{
- if (::rename(inSrcFilename.c_str(), inDestFilename.c_str()))
- UnixError::throwMe(errno);
+ // rollback if we didn't commit yet.
+ if (mFileRef >= 0)
+ rollback();
}
+//
+// Open the file and return the length in bytes.
+//
void
-AtomicFile::unlink(const string &inFilename)
+AtomicTempFile::create(mode_t mode)
{
- if (::unlink(inFilename.c_str()))
- UnixError::throwMe(errno);
+ mPath = mFile.dir() + "," + mFile.file();
+ const char *path = mPath.c_str();
+
+ mFileRef = AtomicFile::ropen(path, O_WRONLY|O_CREAT|O_TRUNC, mode);
+ if (mFileRef == -1)
+ {
+ int error = errno;
+ secdebug("atomicfile", "open %s: %s", path, strerror(error));
+
+ // Do the obvious error code translations here.
+ // @@@ Consider moving these up a level.
+ if (error == EACCES)
+ CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED);
+ else
+ UnixError::throwMe(error);
+ }
+
+ secdebug("atomicfile", "%p created %s", this, path);
}
void
-AtomicFile::write(OffsetType inOffsetType, uint32 inOffset, const uint32 inData)
+AtomicTempFile::write(AtomicFile::OffsetType inOffsetType, off_t inOffset, const uint32 inData)
{
uint32 aData = htonl(inData);
write(inOffsetType, inOffset, reinterpret_cast<uint8 *>(&aData), sizeof(aData));
}
void
-AtomicFile::write(OffsetType inOffsetType, uint32 inOffset,
+AtomicTempFile::write(AtomicFile::OffsetType inOffsetType, off_t inOffset,
const uint32 *inData, uint32 inCount)
{
#ifdef HOST_LONG_IS_NETWORK_LONG
- // XXX Optimize this for the case where hl == nl
+ // Optimize this for the case where hl == nl
const uint32 *aBuffer = inData;
#else
auto_array<uint32> aBuffer(inCount);
}
void
-AtomicFile::write(OffsetType inOffsetType, uint32 inOffset, const uint8 *inData, uint32 inLength)
-{
- // Seriously paranoid check.
- 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 implementation
-
-AtomicFile::OpenFile::OpenFile(const string &inFilename, bool write, bool lock, VersionId inVersionId, mode_t mode) :
- mUseCount(0),
- mVersionId(inVersionId),
- mAddress(NULL),
- mLength(0)
+AtomicTempFile::write(AtomicFile::OffsetType inOffsetType, off_t inOffset, const uint8 *inData, size_t inLength)
{
- int flags;
- if (write && lock)
- {
- flags = O_RDWR;
- mState = ReadWrite;
- }
- else if (write && !lock)
- {
- flags = O_WRONLY|O_CREAT|O_TRUNC;
- mState = Write;
- }
- else if (!write && lock)
- {
- flags = O_WRONLY|O_CREAT|O_TRUNC|O_EXCL;
- mState = Create;
- }
- else
- {
- flags = O_RDONLY;
- mState = Read;
- }
- debug("atomicfile", "%p openfile(%s,%s%s,%d,0x%x) -> flags=0x%x, state=%d",
- this, inFilename.c_str(), write ? "write" : "read", lock ? ",lock" : "",
- inVersionId, mode, flags, mState);
-
- mFileRef = ::open(inFilename.c_str(), flags, mode);
- if (mFileRef == -1)
- {
- int error = errno;
- debug("atomicfile", "%p openfile open failed(errno=%d)", this, error);
-
-#if _USE_IO == _USE_IO_POSIX
- // Do the obvious error code translations here.
- if (error == ENOENT)
+ off_t pos;
+ if (inOffsetType == AtomicFile::FromEnd)
+ {
+ pos = ::lseek(mFileRef, 0, SEEK_END);
+ if (pos == -1)
+ {
+ int error = errno;
+ secdebug("atomicfile", "lseek(%s, %qd): %s", mPath.c_str(), inOffset, strerror(error));
+ UnixError::throwMe(error);
+ }
+ }
+ else if (inOffsetType == AtomicFile::FromStart)
+ pos = inOffset;
+ else
+ CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR);
+
+ off_t bytesLeft = inLength;
+ const uint8 *ptr = inData;
+ while (bytesLeft)
+ {
+ size_t toWrite = bytesLeft > kAtomicFileMaxBlockSize ? kAtomicFileMaxBlockSize : size_t(bytesLeft);
+ ssize_t bytesWritten = ::pwrite(mFileRef, ptr, toWrite, pos);
+ if (bytesWritten == -1)
{
- // Throw CSSMERR_DL_DATASTORE_DOESNOT_EXIST even in Write state since it means someone threw away our parent directory.
- if (mState == ReadWrite || mState == Read || mState == Write)
- CssmError::throwMe(CSSMERR_DL_DATASTORE_DOESNOT_EXIST);
- if (mState == Create)
+ int error = errno;
+ if (error == EINTR)
{
- // Attempt to create the path to inFilename since one or more of the directories
- // in the path do not yet exist.
- mkpath(inFilename);
-
- // Now try the open again.
- mFileRef = ::open(inFilename.c_str(), flags, mode);
- debug("atomicfile", "%p openfile reopen %s (%d)",
- this, (mFileRef == -1) ? "failed" : "ok", errno);
- error = mFileRef == -1 ? errno : 0;
- if (error == ENOENT)
- CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED);
+ // We got interrupted by a signal, so try again.
+ secdebug("atomicfile", "write %s: interrupted, retrying", mPath.c_str());
+ continue;
}
- }
- if (error == EACCES)
- CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED);
-
- if (error == EEXIST)
- CssmError::throwMe(CSSMERR_DL_DATASTORE_ALREADY_EXISTS);
-#endif
-
- // Check if we are still in an error state.
- if (error)
- UnixError::throwMe(errno);
- }
+ secdebug("atomicfile", "write %s: %s", mPath.c_str(), strerror(error));
+ UnixError::throwMe(error);
+ }
- // If this is a new file write out the versionId
- if (mState == Create)
- writeVersionId(mVersionId);
+ // Write returning 0 is bad mmkay.
+ if (bytesWritten == 0)
+ {
+ secdebug("atomicfile", "write %s: 0 bytes written", mPath.c_str());
+ CssmError::throwMe(CSSMERR_DL_INTERNAL_ERROR);
+ }
- // If this is a temp output file we are done.
- if (mState == Write)
- return;
+ secdebug("atomicfile", "%p wrote %s %d bytes from %p", this, mPath.c_str(), bytesWritten, ptr);
- try
- {
- mLength = ::lseek(mFileRef, 0, SEEK_END);
- if (mLength == static_cast<size_t>(-1))
- UnixError::throwMe(errno);
- if (mLength == 0)
- {
- // XXX What to set versionId to?
- mVersionId = 0;
- return; // No point in mapping a zero length file.
- }
-
-#if _USE_IO == _USE_IO_POSIX
- // Lock the file if required.
- 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;
-
- // Keep trying to obtain the lock if we get interupted.
- for (;;)
- {
- if (::fcntl(mFileRef, F_SETLKW, reinterpret_cast<int>(&mLock)) == -1)
- {
- int error = errno;
- if (error == EINTR)
- continue;
-
- if (error != ENOTSUP)
- UnixError::throwMe(error);
-
- // XXX Filesystem does not support locking with fcntl use an alternative.
- 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();
+ bytesLeft -= bytesWritten;
+ ptr += bytesWritten;
+ pos += bytesWritten;
+ }
}
void
-AtomicFile::OpenFile::close()
+AtomicTempFile::fsync()
{
- IFDEBUG(if (mState != Closed) debug("atomicfile", "%p openfile closing(ref=%d)",
- this, mFileRef));
- int error = 0;
- if (mAddress != NULL)
- {
-#if _USE_IO == _USE_IO_POSIX
- debug("atomicfile", "%p openfile is unmapping %p:%ld", this, mAddress, mLength);
- if (::munmap(const_cast<uint8 *>(mAddress), mLength) == -1)
- error = errno;
-#else
- debug("atomicfile", "%p openfile deleting %p", this, mAddress);
- 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;
-}
+ if (mFileRef < 0)
+ {
+ secdebug("atomicfile", "fsync %s: already closed", mPath.c_str());
+ }
+ else
+ {
+ int result;
+ do
+ {
+ result = ::fsync(mFileRef);
+ } while (result && errno == EINTR);
-// Set the files dirty bit (requires the file to be writeable and locked).
-void
-AtomicFile::OpenFile::setDirty()
-{
- if (mState != ReadWrite && mState != Create)
- CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR);
+ if (result == -1)
+ {
+ int error = errno;
+ secdebug("atomicfile", "fsync %s: %s", mPath.c_str(), strerror(errno));
+ UnixError::throwMe(error);
+ }
- writeVersionId(0);
+ secdebug("atomicfile", "%p fsynced %s", this, mPath.c_str());
+ }
}
void
-AtomicFile::OpenFile::unlock()
+AtomicTempFile::close()
{
-// XXX This should be called.
-#if 0
- if (mFcntlLock)
+ if (mFileRef < 0)
{
- 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);
+ secdebug("atomicfile", "close %s: already closed", mPath.c_str());
}
-#endif
-}
+ else
+ {
+ int result = AtomicFile::rclose(mFileRef);
+ mFileRef = -1;
+ if (result == -1)
+ {
+ int error = errno;
+ secdebug("atomicfile", "close %s: %s", mPath.c_str(), strerror(errno));
+ UnixError::throwMe(error);
+ }
-mode_t
-AtomicFile::OpenFile::mode()
-{
- struct stat st;
- if (::fstat(mFileRef, &st) == -1)
- UnixError::throwMe(errno);
- return st.st_mode;
+ secdebug("atomicfile", "%p closed %s", this, mPath.c_str());
+ }
}
-
-AtomicFile::VersionId
-AtomicFile::OpenFile::readVersionId()
+// Commit the current create or write and close the write file. Note that a throw during the commit does an automatic rollback.
+void
+AtomicTempFile::commit()
{
- const uint8 *ptr;
- char buf[4];
-
- // Read the VersionId
- if (mAddress == NULL)
- {
- // Seek to the end of the file minus 4
- 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);
- }
+ try
+ {
+ fsync();
+ close();
+ const char *oldPath = mPath.c_str();
+ const char *newPath = mFile.path().c_str();
+ if (::rename(oldPath, newPath) == -1)
+ {
+ int error = errno;
+ secdebug("atomicfile", "rename (%s, %s): %s", oldPath, newPath, strerror(errno));
+ UnixError::throwMe(error);
+ }
- VersionId aVersionId = 0;
- for (int i = 0; i < 4; i++)
- {
- aVersionId = (aVersionId << 8) + ptr[i];
- }
+ // Unlock the lockfile
+ mLockedFile = NULL;
- return aVersionId;
+ secdebug("atomicfile", "%p commited %s", this, oldPath);
+ }
+ catch (...)
+ {
+ rollback();
+ throw;
+ }
}
+// Rollback the current create or write (happens automatically if commit() isn't called before the destructor is.
void
-AtomicFile::OpenFile::writeVersionId(VersionId inVersionId)
+AtomicTempFile::rollback() throw()
{
- if (mState == ReadWrite)
+ if (mFileRef >= 0)
{
- // Seek to the end of the file minus 4
- if (mLength < 4)
- CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT);
+ AtomicFile::rclose(mFileRef);
+ mFileRef = -1;
+ }
- if (::lseek(mFileRef, mLength - 4, SEEK_SET) == -1)
- UnixError::throwMe(errno);
+ // @@@ Log errors if this fails.
+ const char *path = mPath.c_str();
+ if (::unlink(path) == -1)
+ {
+ secdebug("atomicfile", "unlink %s: %s", path, strerror(errno));
+ // rollback can't throw
}
- else /* if (mState == Create || mState == Write) */
+
+ // @@@ Think about this. Depending on how we do locking we might not need this.
+ if (mCreating)
{
- // Seek to the end of the file.
- if (::lseek(mFileRef, 0, SEEK_END) == -1)
- UnixError::throwMe(errno);
+ const char *path = mFile.path().c_str();
+ if (::unlink(path) == -1)
+ {
+ secdebug("atomicfile", "unlink %s: %s", path, strerror(errno));
+ // rollback can't throw
+ }
}
+}
- uint8 buf[4];
- // Serialize the VersionId
- for (int i = 3; i >= 0; i--)
- {
- buf[i] = inVersionId & 0xff;
- inVersionId = inVersionId >> 8;
- }
- // Write the VersionId
- if (::write(mFileRef, reinterpret_cast<char *>(buf), 4) != 4)
- UnixError::throwMe(errno);
+//
+// An advisory write lock for inFile.
+//
+AtomicLockedFile::AtomicLockedFile(AtomicFile &inFile) :
+ mDir(inFile.dir()),
+ mPath(inFile.dir() + "lck~" + inFile.file())
+{
+ lock();
}
-void
-AtomicFile::OpenFile::mkpath(const std::string &inFilename)
+AtomicLockedFile::~AtomicLockedFile()
{
- const char *path = inFilename.c_str();
- struct stat sb;
- char dirPath[MAXPATHLEN];
- size_t slash = 0;
+ unlock();
+}
- for (;;)
- {
- slash += strspn(path + slash, "/");
- slash += strcspn(path + slash, "/");
+std::string
+AtomicLockedFile::unique(mode_t mode)
+{
+ static const int randomPart = 16;
+ DevRandomGenerator randomGen;
+ std::string::size_type dirSize = mDir.size();
+ std::string fullname(dirSize + randomPart + 2, '\0');
+ fullname.replace(0, dirSize, mDir);
+ fullname[dirSize] = '~'; /* UNIQ_PREFIX */
+ char buf[randomPart];
+ struct stat filebuf;
+ int result, fd = -1;
- if (path[slash] == '\0')
- break;
+ for (int retries = 0; retries < 10; ++retries)
+ {
+ /* Make a random filename. */
+ randomGen.random(buf, randomPart);
+ for (int ix = 0; ix < randomPart; ++ix)
+ {
+ char ch = buf[ix] & 0x3f;
+ fullname[ix + dirSize + 1] = ch +
+ ( ch < 26 ? 'A'
+ : ch < 26 + 26 ? 'a' - 26
+ : ch < 26 + 26 + 10 ? '0' - 26 - 26
+ : ch == 26 + 26 + 10 ? '-' - 26 - 26 - 10
+ : '_' - 26 - 26 - 11);
+ }
- if (slash >= MAXPATHLEN)
- UnixError::throwMe(ENAMETOOLONG);
- strncpy(dirPath, path, slash);
- dirPath[slash] = '\0';
+ result = lstat(fullname.c_str(), &filebuf);
+ if (result && errno == ENAMETOOLONG)
+ {
+ do
+ fullname.erase(fullname.end() - 1);
+ while((result = lstat(fullname.c_str(), &filebuf)) && errno == ENAMETOOLONG && fullname.size() > dirSize + 8);
+ } /* either it stopped being a problem or we ran out of filename */
- if (stat(dirPath, &sb))
+ if (result && errno == ENOENT)
{
- if (errno != ENOENT || mkdir(dirPath, 0777))
- UnixError::throwMe(errno);
+ fd = AtomicFile::ropen(fullname.c_str(), O_WRONLY|O_CREAT|O_EXCL, mode);
+ if (fd >= 0 || errno != EEXIST)
+ break;
}
- else if (!S_ISDIR(sb.st_mode))
- CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED); // @@@ Should be is a directory
}
-}
+ if (fd < 0)
+ {
+ int error = errno;
+ ::syslog(LOG_ERR, "Couldn't create temp file %s: %s", fullname.c_str(), strerror(error));
+ secdebug("atomicfile", "Couldn't create temp file %s: %s", fullname.c_str(), strerror(error));
+ UnixError::throwMe(error);
+ }
+ /* @@@ Check for EINTR. */
+ write(fd, "0", 1); /* pid 0, `works' across networks */
-// Constructor uglyness to work around C++ language limitations.
-struct AtomicFileRef::InitArg
-{
- AtomicFile::VersionId versionId;
- const uint8 *address;
- size_t length;
-};
+ AtomicFile::rclose(fd);
-AtomicFileRef::~AtomicFileRef()
-{
+ return fullname;
}
-AtomicFileRef::AtomicFileRef(AtomicFile &inAtomicFile, const InitArg &inInitArg) :
- mVersionId(inInitArg.versionId),
- mAtomicFile(inAtomicFile),
- mAddress(inInitArg.address),
- mLength(inInitArg.length)
+/* Return 0 on success and 1 on failure if st is set to the result of stat(old) and -1 on failure if the stat(old) failed. */
+int
+AtomicLockedFile::rlink(const char *const old, const char *const newn, struct stat &sto)
{
+ int result = ::link(old,newn);
+ if (result)
+ {
+ int serrno = errno;
+ if (::lstat(old, &sto) == 0)
+ {
+ struct stat stn;
+ if (::lstat(newn, &stn) == 0
+ && sto.st_dev == stn.st_dev
+ && sto.st_ino == stn.st_ino
+ && sto.st_uid == stn.st_uid
+ && sto.st_gid == stn.st_gid
+ && !S_ISLNK(sto.st_mode))
+ {
+ /* Link failed but files are the same so the link really went ok. */
+ return 0;
+ }
+ else
+ result = 1;
+ }
+ errno = serrno; /* Restore errno from link() */
+ }
+
+ return result;
}
-AtomicFileReadRef::~AtomicFileReadRef()
+/* NFS-resistant rename()
+ * rename with fallback for systems that don't support it
+ * Note that this does not preserve the contents of the file. */
+int
+AtomicLockedFile::myrename(const char *const old, const char *const newn)
{
- try {
- mAtomicFile.exitRead(mVersionId);
- }
- catch(...) {
+ struct stat stbuf;
+ int fd = -1;
+ int ret;
+
+ /* Try a real hardlink */
+ ret = rlink(old, newn, stbuf);
+ if (ret > 0)
+ {
+ if (stbuf.st_nlink < 2 && (errno == EXDEV || errno == ENOTSUP))
+ {
+ /* Hard link failed so just create a new file with O_EXCL instead. */
+ fd = AtomicFile::ropen(newn, O_WRONLY|O_CREAT|O_EXCL, stbuf.st_mode);
+ if (fd >= 0)
+ ret = 0;
+ }
}
-}
-AtomicFileRef::InitArg
-AtomicFileReadRef::enterRead(AtomicFile &inAtomicFile)
-{
- InitArg anInitArg;
- anInitArg.versionId = inAtomicFile.enterRead(anInitArg.address, anInitArg.length);
- return anInitArg;
+ /* We want the errno from the link or the ropen, not that of the unlink. */
+ int serrno = errno;
+
+ /* Unlink the temp file. */
+ ::unlink(old);
+ if (fd > 0)
+ AtomicFile::rclose(fd);
+
+ errno = serrno;
+ return ret;
}
-AtomicFileReadRef::AtomicFileReadRef(AtomicFile &inAtomicFile) :
- AtomicFileRef(inAtomicFile, enterRead(inAtomicFile))
+int
+AtomicLockedFile::xcreat(const char *const name, mode_t mode, time_t &tim)
{
+ std::string uniqueName = unique(mode);
+ const char *uniquePath = uniqueName.c_str();
+ struct stat stbuf; /* return the filesystem time to the caller */
+ stat(uniquePath, &stbuf);
+ tim = stbuf.st_mtime;
+ return myrename(uniquePath, name);
}
-AtomicFileWriteRef::~AtomicFileWriteRef()
+void
+AtomicLockedFile::lock(mode_t mode)
{
- if (mOpen) {
- try {
- mAtomicFile.rollback();
+ const char *path = mPath.c_str();
+ bool triedforce = false;
+ struct stat stbuf;
+ time_t t, locktimeout = 1024; /* DEFlocktimeout, 17 minutes. */
+ bool doSyslog = false;
+ bool failed = false;
+ int retries = 0;
+
+ while (!failed)
+ {
+ /* Don't syslog first time through. */
+ if (doSyslog)
+ ::syslog(LOG_NOTICE, "Locking %s", path);
+ else
+ doSyslog = true;
+
+ secdebug("atomicfile", "Locking %s", path); /* in order to cater for clock skew: get */
+ if (!xcreat(path, mode, t)) /* time t from the filesystem */
+ {
+ /* lock acquired, hurray! */
+ break;
}
- catch (...)
+ switch(errno)
{
+ case EEXIST: /* check if it's time for a lock override */
+ if (!lstat(path, &stbuf) && stbuf.st_size <= 16 /* MAX_locksize */ && locktimeout
+ && !lstat(path, &stbuf) && locktimeout < t - stbuf.st_mtime)
+ /* stat() till unlink() should be atomic, but can't guarantee that. */
+ {
+ if (triedforce)
+ {
+ /* Already tried, force lock override, not trying again */
+ failed = true;
+ break;
+ }
+ else if (S_ISDIR(stbuf.st_mode) || ::unlink(path))
+ {
+ triedforce=true;
+ ::syslog(LOG_ERR, "Forced unlock denied on %s", path);
+ secdebug("atomicfile", "Forced unlock denied on %s", path);
+ }
+ else
+ {
+ ::syslog(LOG_ERR, "Forcing lock on %s", path);
+ secdebug("atomicfile", "Forcing lock on %s", path);
+ sleep(16 /* DEFsuspend */);
+ break;
+ }
+ }
+ else
+ triedforce = false; /* legitimate iteration, clear flag */
+
+ /* Reset retry counter. */
+ retries = 0;
+ sleep(8 /* DEFlocksleep */);
+ break;
+
+ case ENOSPC: /* no space left, treat it as a transient */
+#ifdef EDQUOT /* NFS failure */
+ case EDQUOT: /* maybe it was a short term shortage? */
+#endif
+ case ENOENT:
+ case ENOTDIR:
+ case EIO:
+ /*case EACCES:*/
+ if(++retries < (7 + 1)) /* nfsTRY number of times+1 to ignore spurious NFS errors */
+ sleep(8 /* DEFlocksleep */);
+ else
+ failed = true;
+ break;
+
+#ifdef ENAMETOOLONG
+ case ENAMETOOLONG: /* Filename is too long, shorten and retry */
+ if (mPath.size() > mDir.size() + 8)
+ {
+ secdebug("atomicfile", "Truncating %s and retrying lock", path);
+ mPath.erase(mPath.end() - 1);
+ path = mPath.c_str();
+ /* Reset retry counter. */
+ retries = 0;
+ break;
+ }
+ /* DROPTHROUGH */
+#endif
+ default:
+ failed = true;
+ break;
}
}
-}
-AtomicFileRef::InitArg
-AtomicFileWriteRef::enterWrite(AtomicFile &inAtomicFile, AtomicFile::FileRef &outWriteFileRef)
-{
- InitArg anInitArg;
- anInitArg.versionId = inAtomicFile.enterWrite(anInitArg.address, anInitArg.length, outWriteFileRef);
- return anInitArg;
+ if (failed)
+ {
+ int error = errno;
+ ::syslog(LOG_ERR, "Lock failure on %s: %s", path, strerror(error));
+ secdebug("atomicfile", "Lock failure on %s: %s", path, strerror(error));
+ UnixError::throwMe(error);
+ }
}
-AtomicFileWriteRef::AtomicFileWriteRef(AtomicFile &inAtomicFile) :
- AtomicFileRef(inAtomicFile, enterWrite(inAtomicFile, mFileRef))
+void
+AtomicLockedFile::unlock() throw()
{
+ const char *path = mPath.c_str();
+ if (::unlink(path) == -1)
+ {
+ secdebug("atomicfile", "unlink %s: %s", path, strerror(errno));
+ // unlock can't throw
+ }
}
+
+
+#undef kAtomicFileMaxBlockSize