2 * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved.
4 * The contents of this file constitute Original Code as defined in and are
5 * subject to the Apple Public Source License Version 1.2 (the 'License').
6 * You may not use this file except in compliance with the License. Please obtain
7 * a copy of the License at http://www.apple.com/publicsource and read it before
10 * This Original Code and all software distributed under the License are
11 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS
12 * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT
13 * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14 * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the
15 * specific language governing rights and limitations under the License.
20 // AtomicFile.cpp - Description t.b.d.
23 #define _CPP_ATOMICFILE
26 #include <Security/AtomicFile.h>
27 #include <Security/DbName.h>
35 #if _USE_IO == _USE_IO_POSIX
37 #include <sys/types.h>
46 #elif _USE_IO == _USE_IO_MACOS
47 typedef SInt32 ssize_t
;
52 AtomicFile::AtomicFile(const DbName
&inDbName
) :
54 mReadFilename(inDbName
.dbName()),
56 mWriteFilename(mReadFilename
+ ",") // XXX Do some more work here like resolving symlinks/aliases etc.
58 // We only support databases with string names of non-zero length.
59 if (inDbName
.dbLocation() != nil
|| inDbName
.dbName().length() == 0)
60 CssmError::throwMe(CSSMERR_DL_INVALID_DB_LOCATION
);
63 AtomicFile::~AtomicFile()
65 // Assume there are no more running theads in this object.
67 // Try hard to clean up as much as possible.
70 // Rollback any pending write.
76 // Close and delete all files in mOpenFileMap
77 for (OpenFileMap::iterator it
= mOpenFileMap
.begin(); it
!= mOpenFileMap
.end(); it
++)
95 StLock
<Mutex
> _(mReadLock
);
97 // If we have no read file we have nothing to close.
101 // Remember mReadFile and set it to nil, so that it will be closed after any pending write completes
102 OpenFile
*aOpenFile
= mReadFile
;
105 // If aOpenFile has a zero use count no other thread is currently using it,
106 // so we can safely remove it from the map.
107 if (aOpenFile
->mUseCount
== 0)
109 // Do not close any files (nor remove them from the map) while some thread is writing
110 // since doing so might release the lock we are holding.
111 if (mWriteLock
.tryLock())
113 // Release the write lock immediately since tryLock just aquired it and we don't want to write.
116 // Remove aOpenFile from the map of open files.
117 mOpenFileMap
.erase(aOpenFile
->versionId());
132 AtomicFile::VersionId
133 AtomicFile::enterRead(const uint8
*&outFileAddress
, size_t &outLength
)
135 StLock
<Mutex
> _(mReadLock
);
137 // If we already have a read file check if it is still current.
138 if (mReadFile
!= nil
)
140 if (mReadFile
->isDirty())
142 // Remember mReadFile and set it to nil in case an exception is thrown
143 OpenFile
*aOpenFile
= mReadFile
;
146 // If aOpenFile has a zero use count no other thread is currently using it,
147 // so we can safely remove it from the map.
148 if (aOpenFile
->mUseCount
== 0)
150 // Do not close any files (nor remove them from the map) while some thread is writing
151 // since doing so might release the lock we are holding.
152 if (mWriteLock
.tryLock())
154 // Release the write lock immediately since tryLock just aquired it and we don't want to write.
157 // Remove aOpenFile from the map of open files.
158 mOpenFileMap
.erase(aOpenFile
->versionId());
174 // If we never had or no longer have an open read file. Open it now.
175 if (mReadFile
== nil
)
177 mReadFile
= new OpenFile(mReadFilename
, false, false, 0);
178 mOpenFileMap
.insert(OpenFileMap::value_type(mReadFile
->versionId(), mReadFile
));
180 // Note that mReadFile->isDirty() might actually return true here, but all that mean is
181 // that we are looking at data that was commited after we opened the file which might
182 // happen in a few miliseconds anyway.
184 // Bump up the use count of our OpenFile.
185 mReadFile
->mUseCount
++;
187 // Return the length of the file and the mapped address.
188 outLength
= mReadFile
->length();
189 outFileAddress
= mReadFile
->address();
190 return mReadFile
->versionId();
194 AtomicFile::exitRead(VersionId inVersionId
)
196 StLock
<Mutex
> _(mReadLock
);
197 OpenFileMap::iterator it
= mOpenFileMap
.find(inVersionId
);
198 // If the inVersionId is not in the map anymore something really bad happned.
199 if (it
== mOpenFileMap
.end())
200 CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR
);
202 OpenFile
*aOpenFile
= it
->second
;
203 aOpenFile
->mUseCount
--;
205 // Don't close the current active file even if its mUseCount hits 0 since someone
206 // else will probably request it soon.
207 if (aOpenFile
->mUseCount
== 0 && aOpenFile
!= mReadFile
)
209 // Do not close any files (nor remove them from the map) while some thread is writing
210 // since doing so might release the lock we are holding.
211 if (mWriteLock
.tryLock())
213 // Release the write lock immidiatly since tryLock just aquired it and we don't want to write.
216 // Remove from the map, close and delete aOpenFile.
217 mOpenFileMap
.erase(it
);
232 bool AtomicFile::isDirty(VersionId inVersionId
)
234 StLock
<Mutex
> _(mReadLock
);
235 OpenFileMap::iterator it
= mOpenFileMap
.find(inVersionId
);
236 // If the inVersionId is not in the map anymore something really bad happned.
237 if (it
== mOpenFileMap
.end())
238 CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR
);
240 return it
->second
->isDirty();
244 AtomicFile::performDelete()
246 // Prevent any other threads in this process from writing.
249 OpenFile
*aReadFile
= nil
;
252 // Keep reopening mReadFilename until the lock has been aquired on a non-dirty file.
253 // XXX This is a potential infinite loop.
256 aReadFile
= new OpenFile(mReadFilename
, true, true, 0);
257 if (!aReadFile
->isDirty())
265 // Aquire the read lock so no other thread will open the file
266 StLock
<Mutex
> _(mReadLock
);
269 unlink(mReadFilename
);
271 // Clear our current mReadFile since it refers to the deleted file.
274 // Mark the old file as modified
275 aReadFile
->setDirty();
277 // Close any open files.
286 VersionId aVersionId
= aReadFile
->versionId();
288 mOpenFileMap
.erase(aVersionId
);
298 AtomicFile::VersionId
299 AtomicFile::enterCreate(FileRef
&outWriteRef
)
301 // Prevent any other threads in this process from writing.
303 OpenFile
*aReadFile
= nil
;
306 // No threads can read during creation
307 StLock
<Mutex
> _(mReadLock
);
309 // Create mReadFilename until the lock has been aquired on a non-dirty file.
310 aReadFile
= new OpenFile(mReadFilename
, false, true, 1);
312 // Open mWriteFile for writing.
313 mWriteFile
= new OpenFile(mWriteFilename
, true, false, aReadFile
->versionId() + 1);
315 // Insert aReadFile into the map (do this after opening mWriteFile just in case that throws).
316 mOpenFileMap
.insert(OpenFileMap::value_type(-1, aReadFile
));
318 outWriteRef
= mWriteFile
->fileRef();
319 mCreating
= true; // So rollback() will delete mReadFileName.
320 return aReadFile
->versionId();
324 // Make sure we don't thow during cleanup since that would clobber the original
325 // error and prevent us from releasing mWriteLock
333 // XXX We should only unlink if we know that no one else is currently creating the file.
334 //unlink(mReadFilename);
335 mOpenFileMap
.erase(-1);
345 unlink(mWriteFilename
);
351 catch(...) {} // Do not throw since we already have an error.
353 // Release the write lock and remove any unused files from the map
359 AtomicFile::VersionId
360 AtomicFile::enterWrite(const uint8
*&outFileAddress
, size_t &outLength
, FileRef
&outWriteRef
)
362 // Wait for all other threads in this process to finish writing.
364 mCreating
= false; // So rollback() will not delete mReadFileName.
365 OpenFile
*aReadFile
= nil
;
368 // Keep reopening mReadFilename until the lock has been aquired on a non-dirty file.
369 // XXX This is a potential infinite loop.
372 aReadFile
= new OpenFile(mReadFilename
, true, true, 0);
373 if (!aReadFile
->isDirty())
381 // We have the write lock on the file now we start modifying our shared data
382 // stuctures so aquire the read lock.
383 StLock
<Mutex
> _(mReadLock
);
385 // Open mWriteFile for writing.
386 mWriteFile
= new OpenFile(mWriteFilename
, true, false, aReadFile
->versionId() + 1);
388 // Insert aReadFile into the map (do this after opening mWriteFile just in case that throws).
389 mOpenFileMap
.insert(OpenFileMap::value_type(-1, aReadFile
));
391 outWriteRef
= mWriteFile
->fileRef();
392 outLength
= aReadFile
->length();
393 outFileAddress
= aReadFile
->address();
394 return aReadFile
->versionId();
398 // Make sure we don't thow during cleanup since that would clobber the original
399 // error and prevent us from releasing mWriteLock
407 mOpenFileMap
.erase(-1);
417 unlink(mWriteFilename
);
423 catch(...) {} // Do not throw since we already have an error.
425 // Release the write lock and remove any unused files from the map
431 AtomicFile::VersionId
434 StLock
<Mutex
> _(mReadLock
);
435 if (mWriteFile
== nil
)
436 CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR
);
440 VersionId aVersionId
= mWriteFile
->versionId();
445 OpenFileMap::iterator it
= mOpenFileMap
.find(-1);
446 if (it
== mOpenFileMap
.end())
447 CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR
);
449 // First rename the file and them mark the old one as modified
450 rename(mWriteFilename
, mReadFilename
);
451 OpenFile
*aOpenFile
= it
->second
;
453 // Clear our current mReadFile since it refers to the old file.
456 // Mark the old file as modified
457 aOpenFile
->setDirty();
459 // Close all unused files (in particular aOpenFile) and remove them from mOpenFileMap
465 // Unlink the new file to rollback the transaction and close any open files.
468 unlink(mWriteFilename
);
476 AtomicFile::rollback()
478 StLock
<Mutex
> _(mReadLock
);
479 if (mWriteFile
== nil
)
480 CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR
);
488 // First rename the file and them mark the old one as modified
489 unlink(mWriteFilename
);
491 unlink(mReadFilename
);
496 // Unlink the new file to rollback the transaction and close any open files.
499 unlink(mWriteFilename
);
506 // This private function is called by a successfull commit(), rollback() or performDelete() as well
507 // as by a failed enterWrite() or enterCreate().
509 AtomicFile::endWrite()
513 // We need to go in and close and delete all unused files from the queue
514 stack
<VersionId
> aDeleteList
;
515 OpenFileMap::iterator it
;
516 for (it
= mOpenFileMap
.begin();
517 it
!= mOpenFileMap
.end();
520 OpenFile
*aOpenFile
= it
->second
;
521 // If aOpenFile is unused and it is not the mReadFile schedule it for close and removal.
522 // Note that if this is being called after a commit mReadFile will have been set to nil.
523 if (aOpenFile
!= mReadFile
&& aOpenFile
->mUseCount
== 0)
524 aDeleteList
.push(it
->first
);
527 // Remove everything that was scheduled for removal
528 while (!aDeleteList
.empty())
530 it
= mOpenFileMap
.find(aDeleteList
.top());
538 mOpenFileMap
.erase(it
);
560 AtomicFile::rename(const string
&inSrcFilename
, const string
&inDestFilename
)
562 if (::rename(inSrcFilename
.c_str(), inDestFilename
.c_str()))
563 UnixError::throwMe(errno
);
567 AtomicFile::unlink(const string
&inFilename
)
569 if (::unlink(inFilename
.c_str()))
570 UnixError::throwMe(errno
);
574 AtomicFile::write(OffsetType inOffsetType
, uint32 inOffset
, const uint32 inData
)
576 uint32 aData
= htonl(inData
);
577 write(inOffsetType
, inOffset
, reinterpret_cast<uint8
*>(&aData
), sizeof(aData
));
581 AtomicFile::write(OffsetType inOffsetType
, uint32 inOffset
,
582 const uint32
*inData
, uint32 inCount
)
584 #ifdef HOST_LONG_IS_NETWORK_LONG
585 // XXX Optimize this for the case where hl == nl
586 const uint32
*aBuffer
= inData
;
588 auto_array
<uint32
> aBuffer(inCount
);
589 for (uint32 i
= 0; i
< inCount
; i
++)
590 aBuffer
.get()[i
] = htonl(inData
[i
]);
593 write(inOffsetType
, inOffset
, reinterpret_cast<const uint8
*>(aBuffer
.get()),
594 inCount
* sizeof(*inData
));
598 AtomicFile::write(OffsetType inOffsetType
, uint32 inOffset
, const uint8
*inData
, uint32 inLength
)
600 // Seriously paranoid check.
601 if (mWriteFile
== nil
)
602 CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR
);
604 if (inOffsetType
!= None
)
606 if (::lseek(mWriteFile
->mFileRef
, inOffset
, inOffsetType
== FromStart
? SEEK_SET
: SEEK_CUR
) == -1)
607 UnixError::throwMe(errno
);
610 if (::write(mWriteFile
->mFileRef
, reinterpret_cast<const char *>(inData
),
611 inLength
) != static_cast<ssize_t
>(inLength
))
612 UnixError::throwMe(errno
);
615 // AtomicFile::OpenFile implementation
617 AtomicFile::OpenFile::OpenFile(const string
&inFilename
, bool write
, bool lock
, VersionId inVersionId
) :
619 mVersionId(inVersionId
),
629 else if (write
&& !lock
)
631 flags
= O_WRONLY
|O_CREAT
|O_TRUNC
;
635 else if (!write
&& lock
)
637 flags
= O_WRONLY
|O_CREAT
|O_TRUNC
|O_EXCL
;
647 mFileRef
= ::open(inFilename
.c_str(), flags
, mode
);
652 #if _USE_IO == _USE_IO_POSIX
653 // Do the obvious error code translations here.
656 // Throw CSSMERR_DL_DATASTORE_DOESNOT_EXIST even in Write state since it means someone threw away our parent directory.
657 if (mState
== ReadWrite
|| mState
== Read
|| mState
== Write
)
658 CssmError::throwMe(CSSMERR_DL_DATASTORE_DOESNOT_EXIST
);
659 if (mState
== Create
)
661 // Attempt to create the path to inFilename since one or more of the directories
662 // in the path do not yet exist.
665 // Now try the open again.
666 mFileRef
= ::open(inFilename
.c_str(), flags
, mode
);
667 error
= mFileRef
== -1 ? errno
: 0;
669 CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED
);
674 CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED
);
677 CssmError::throwMe(CSSMERR_DL_DATASTORE_ALREADY_EXISTS
);
680 // Check if we are still in an error state.
682 UnixError::throwMe(errno
);
685 // If this is a new file write out the versionId
686 if (mState
== Create
)
687 writeVersionId(mVersionId
);
689 // If this is a temp output file we are done.
695 mLength
= ::lseek(mFileRef
, 0, SEEK_END
);
696 if (mLength
== static_cast<size_t>(-1))
697 UnixError::throwMe(errno
);
700 // XXX What to set versionId to?
702 return; // No point in mapping a zero length file.
705 #if _USE_IO == _USE_IO_POSIX
706 // Lock the file if required.
712 mLock
.l_pid
= getpid();
713 mLock
.l_type
= F_WRLCK
;
714 mLock
.l_whence
= SEEK_SET
;
716 // Keep trying to obtain the lock if we get interupted.
719 if (::fcntl(mFileRef
, F_SETLKW
, reinterpret_cast<int>(&mLock
)) == -1)
725 if (error
!= ENOTSUP
)
726 UnixError::throwMe(error
);
728 // XXX Filesystem does not support locking with fcntl use an alternative.
738 if (mState
!= Create
)
740 mAddress
= reinterpret_cast<const uint8
*>
741 (::mmap(0, mLength
, PROT_READ
, MAP_FILE
|MAP_SHARED
,
743 if (mAddress
== reinterpret_cast<const uint8
*>(-1))
746 UnixError::throwMe(errno
);
749 mVersionId
= readVersionId();
752 if (mState
!= Create
)
754 mAddress
= reinterpret_cast<const uint8
*>(-1);
755 auto_array
<char> aBuffer(mLength
);
756 if (::read(mFileRef
, aBuffer
.get(), mLength
) != mLength
)
757 UnixError::throwMe(errno
);
759 mAddress
= reinterpret_cast<const uint8
*>(aBuffer
.release());
760 mVersionId
= readVersionId();
766 if (mState
!= Closed
)
772 AtomicFile::OpenFile::~OpenFile()
778 AtomicFile::OpenFile::close()
781 if (mAddress
!= NULL
)
783 #if _USE_IO == _USE_IO_POSIX
784 if (::munmap(const_cast<uint8
*>(mAddress
), mLength
) == -1)
794 writeVersionId(mVersionId
);
796 if (mState
!= Closed
)
799 if (::close(mFileRef
) == -1)
804 UnixError::throwMe(error
);
808 AtomicFile::OpenFile::isDirty()
810 if (mAddress
== NULL
)
811 CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR
);
813 return (mVersionId
!= readVersionId()) || mVersionId
== 0;
816 // Set the files dirty bit (requires the file to be writeable and locked).
818 AtomicFile::OpenFile::setDirty()
820 if (mState
!= ReadWrite
&& mState
!= Create
)
821 CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR
);
827 AtomicFile::OpenFile::unlock()
829 // XXX This should be called.
836 mLock
.l_pid
= getpid();
837 mLock
.l_type
= F_UNLCK
;
838 mLock
.l_whence
= SEEK_SET
;
839 if (::fcntl(mFileRef
, F_SETLK
, reinterpret_cast<int>(&mLock
)) == -1)
840 UnixError::throwMe(errno
);
845 AtomicFile::VersionId
846 AtomicFile::OpenFile::readVersionId()
851 // Read the VersionId
852 if (mAddress
== NULL
)
854 // Seek to the end of the file minus 4
856 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT
);
858 if (::lseek(mFileRef
, mLength
- 4, SEEK_SET
) == -1)
859 UnixError::throwMe(errno
);
861 ptr
= reinterpret_cast<uint8
*>(buf
);
862 if (::read(mFileRef
, buf
, 4) != 4)
863 UnixError::throwMe(errno
);
867 ptr
= mAddress
+ mLength
- 4;
869 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT
);
872 VersionId aVersionId
= 0;
873 for (int i
= 0; i
< 4; i
++)
875 aVersionId
= (aVersionId
<< 8) + ptr
[i
];
882 AtomicFile::OpenFile::writeVersionId(VersionId inVersionId
)
884 if (mState
== ReadWrite
)
886 // Seek to the end of the file minus 4
888 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT
);
890 if (::lseek(mFileRef
, mLength
- 4, SEEK_SET
) == -1)
891 UnixError::throwMe(errno
);
893 else /* if (mState == Create || mState == Write) */
895 // Seek to the end of the file.
896 if (::lseek(mFileRef
, 0, SEEK_END
) == -1)
897 UnixError::throwMe(errno
);
901 // Serialize the VersionId
902 for (int i
= 3; i
>= 0; i
--)
904 buf
[i
] = inVersionId
& 0xff;
905 inVersionId
= inVersionId
>> 8;
908 // Write the VersionId
909 if (::write(mFileRef
, reinterpret_cast<char *>(buf
), 4) != 4)
910 UnixError::throwMe(errno
);
914 AtomicFile::OpenFile::mkpath(const std::string
&inFilename
)
916 char *path
= const_cast<char *>(inFilename
.c_str()); // @@@ Const_cast is a lie!!!
919 mode_t dir_mode
= (0777 & ~umask(0)) | S_IWUSR
| S_IXUSR
;
925 slash
+= strspn(slash
, "/");
926 slash
+= strcspn(slash
, "/");
935 if (errno
!= ENOENT
|| mkdir(path
, dir_mode
))
936 UnixError::throwMe(errno
);
937 /* The mkdir() and umask() calls both honor only the low
938 nine bits, so if you try to set a mode including the
939 sticky, setuid, setgid bits you lose them. So chmod(). */
940 if (chmod(path
, dir_mode
) == -1)
941 UnixError::throwMe(errno
);
943 else if (!S_ISDIR(sb
.st_mode
))
944 CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED
); // @@@ Should be is a directory
952 // Constructor uglyness to work around C++ language limitations.
953 struct AtomicFileRef::InitArg
955 AtomicFile::VersionId versionId
;
956 const uint8
*address
;
960 AtomicFileRef::~AtomicFileRef()
964 AtomicFileRef::AtomicFileRef(AtomicFile
&inAtomicFile
, const InitArg
&inInitArg
) :
965 mVersionId(inInitArg
.versionId
),
966 mAtomicFile(inAtomicFile
),
967 mAddress(inInitArg
.address
),
968 mLength(inInitArg
.length
)
972 AtomicFileReadRef::~AtomicFileReadRef()
975 mAtomicFile
.exitRead(mVersionId
);
981 AtomicFileRef::InitArg
982 AtomicFileReadRef::enterRead(AtomicFile
&inAtomicFile
)
985 anInitArg
.versionId
= inAtomicFile
.enterRead(anInitArg
.address
, anInitArg
.length
);
989 AtomicFileReadRef::AtomicFileReadRef(AtomicFile
&inAtomicFile
) :
990 AtomicFileRef(inAtomicFile
, enterRead(inAtomicFile
))
994 AtomicFileWriteRef::~AtomicFileWriteRef()
998 mAtomicFile
.rollback();
1006 AtomicFileRef::InitArg
1007 AtomicFileWriteRef::enterWrite(AtomicFile
&inAtomicFile
, AtomicFile::FileRef
&outWriteFileRef
)
1010 anInitArg
.versionId
= inAtomicFile
.enterWrite(anInitArg
.address
, anInitArg
.length
, outWriteFileRef
);
1014 AtomicFileWriteRef::AtomicFileWriteRef(AtomicFile
&inAtomicFile
) :
1015 AtomicFileRef(inAtomicFile
, enterWrite(inAtomicFile
, mFileRef
))