]> git.saurik.com Git - apple/security.git/blobdiff - cdsa/cdsa_utilities/AtomicFile.cpp
Security-177.tar.gz
[apple/security.git] / cdsa / cdsa_utilities / AtomicFile.cpp
index f7be07282a2678bd0fe6c4e5c4fe350252f4a9b4..bbde5634fe2ba565ceec8bfabd48a83fe7f0fc39 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 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);
@@ -605,435 +455,430 @@ AtomicFile::write(OffsetType inOffsetType, uint32 inOffset,
 }
 
 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