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>
45 #include <sys/param.h>
47 #elif _USE_IO == _USE_IO_MACOS
48 typedef SInt32 ssize_t
;
53 AtomicFile::AtomicFile(const DbName
&inDbName
) :
55 mReadFilename(inDbName
.dbName()),
57 mWriteFilename(mReadFilename
+ ",") // XXX Do some more work here like resolving symlinks/aliases etc.
59 // We only support databases with string names of non-zero length.
60 if (inDbName
.dbLocation() != nil
|| inDbName
.dbName().length() == 0)
61 CssmError::throwMe(CSSMERR_DL_INVALID_DB_LOCATION
);
64 AtomicFile::~AtomicFile()
66 // Assume there are no more running theads in this object.
68 // Try hard to clean up as much as possible.
71 // Rollback any pending write.
77 // Close and delete all files in mOpenFileMap
78 for (OpenFileMap::iterator it
= mOpenFileMap
.begin(); it
!= mOpenFileMap
.end(); it
++)
96 StLock
<Mutex
> _(mReadLock
);
98 // If we have no read file we have nothing to close.
102 // Remember mReadFile and set it to nil, so that it will be closed after any pending write completes
103 OpenFile
*aOpenFile
= mReadFile
;
106 // If aOpenFile has a zero use count no other thread is currently using it,
107 // so we can safely remove it from the map.
108 if (aOpenFile
->mUseCount
== 0)
110 // Do not close any files (nor remove them from the map) while some thread is writing
111 // since doing so might release the lock we are holding.
112 if (mWriteLock
.tryLock())
114 // Release the write lock immediately since tryLock just aquired it and we don't want to write.
117 // Remove aOpenFile from the map of open files.
118 mOpenFileMap
.erase(aOpenFile
->versionId());
133 AtomicFile::VersionId
134 AtomicFile::enterRead(const uint8
*&outFileAddress
, size_t &outLength
)
136 StLock
<Mutex
> _(mReadLock
);
138 // If we already have a read file check if it is still current.
139 if (mReadFile
!= nil
)
141 if (mReadFile
->isDirty())
143 // Remember mReadFile and set it to nil in case an exception is thrown
144 OpenFile
*aOpenFile
= mReadFile
;
147 // If aOpenFile has a zero use count no other thread is currently using it,
148 // so we can safely remove it from the map.
149 if (aOpenFile
->mUseCount
== 0)
151 // Do not close any files (nor remove them from the map) while some thread is writing
152 // since doing so might release the lock we are holding.
153 if (mWriteLock
.tryLock())
155 // Release the write lock immediately since tryLock just aquired it and we don't want to write.
158 // Remove aOpenFile from the map of open files.
159 mOpenFileMap
.erase(aOpenFile
->versionId());
175 // If we never had or no longer have an open read file. Open it now.
176 if (mReadFile
== nil
)
178 mReadFile
= new OpenFile(mReadFilename
, false, false, 0, 0);
179 mOpenFileMap
.insert(OpenFileMap::value_type(mReadFile
->versionId(), mReadFile
));
181 // Note that mReadFile->isDirty() might actually return true here, but all that means is
182 // that we are looking at data that was commited after we opened the file which might
183 // happen in a few miliseconds anyway.
185 // Bump up the use count of our OpenFile.
186 mReadFile
->mUseCount
++;
188 // Return the length of the file and the mapped address.
189 outLength
= mReadFile
->length();
190 outFileAddress
= mReadFile
->address();
191 return mReadFile
->versionId();
195 AtomicFile::exitRead(VersionId inVersionId
)
197 StLock
<Mutex
> _(mReadLock
);
198 OpenFileMap::iterator it
= mOpenFileMap
.find(inVersionId
);
199 // If the inVersionId is not in the map anymore something really bad happned.
200 if (it
== mOpenFileMap
.end())
201 CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR
);
203 OpenFile
*aOpenFile
= it
->second
;
204 aOpenFile
->mUseCount
--;
206 // Don't close the current active file even if its mUseCount hits 0 since someone
207 // else will probably request it soon.
208 if (aOpenFile
->mUseCount
== 0 && aOpenFile
!= mReadFile
)
210 // Do not close any files (nor remove them from the map) while some thread is writing
211 // since doing so might release the lock we are holding.
212 if (mWriteLock
.tryLock())
214 // Release the write lock immidiatly since tryLock just aquired it and we don't want to write.
217 // Remove from the map, close and delete aOpenFile.
218 mOpenFileMap
.erase(it
);
233 bool AtomicFile::isDirty(VersionId inVersionId
)
235 StLock
<Mutex
> _(mReadLock
);
236 OpenFileMap::iterator it
= mOpenFileMap
.find(inVersionId
);
237 // If the inVersionId is not in the map anymore something really bad happned.
238 if (it
== mOpenFileMap
.end())
239 CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR
);
241 return it
->second
->isDirty();
245 AtomicFile::performDelete()
247 // Prevent any other threads in this process from writing.
250 OpenFile
*aReadFile
= nil
;
253 // Keep reopening mReadFilename until the lock has been aquired on a non-dirty file.
254 // XXX This is a potential infinite loop.
257 aReadFile
= new OpenFile(mReadFilename
, true, true, 0, 0);
258 if (!aReadFile
->isDirty())
266 // Aquire the read lock so no other thread will open the file
267 StLock
<Mutex
> _(mReadLock
);
270 unlink(mReadFilename
);
272 // Clear our current mReadFile since it refers to the deleted file.
275 // Mark the old file as modified
276 aReadFile
->setDirty();
278 // Close any open files.
287 VersionId aVersionId
= aReadFile
->versionId();
289 mOpenFileMap
.erase(aVersionId
);
299 AtomicFile::VersionId
300 AtomicFile::enterCreate(FileRef
&outWriteRef
)
302 // Prevent any other threads in this process from writing.
304 OpenFile
*aReadFile
= nil
;
307 // No threads can read during creation
308 StLock
<Mutex
> _(mReadLock
);
310 // Create mReadFilename until the lock has been aquired on a non-dirty file.
311 aReadFile
= new OpenFile(mReadFilename
, false, true, 1, 0666);
313 // Open mWriteFile for writing.
314 mWriteFile
= new OpenFile(mWriteFilename
, true, false, aReadFile
->versionId() + 1, 0666);
316 // Insert aReadFile into the map (do this after opening mWriteFile just in case that throws).
317 mOpenFileMap
.insert(OpenFileMap::value_type(-1, aReadFile
));
319 outWriteRef
= mWriteFile
->fileRef();
320 mCreating
= true; // So rollback() will delete mReadFileName.
321 return aReadFile
->versionId();
325 // Make sure we don't thow during cleanup since that would clobber the original
326 // error and prevent us from releasing mWriteLock
334 // XXX We should only unlink if we know that no one else is currently creating the file.
335 //unlink(mReadFilename);
336 mOpenFileMap
.erase(-1);
346 unlink(mWriteFilename
);
352 catch(...) {} // Do not throw since we already have an error.
354 // Release the write lock and remove any unused files from the map
360 AtomicFile::VersionId
361 AtomicFile::enterWrite(const uint8
*&outFileAddress
, size_t &outLength
, FileRef
&outWriteRef
)
363 // Wait for all other threads in this process to finish writing.
365 mCreating
= false; // So rollback() will not delete mReadFileName.
366 OpenFile
*aReadFile
= nil
;
369 // Keep reopening mReadFilename until the lock has been aquired on a non-dirty file.
370 // XXX This is a potential infinite loop.
373 aReadFile
= new OpenFile(mReadFilename
, true, true, 0, 0);
374 if (!aReadFile
->isDirty())
382 // We have the write lock on the file now we start modifying our shared data
383 // stuctures so aquire the read lock.
384 StLock
<Mutex
> _(mReadLock
);
386 // Open mWriteFile for writing.
387 mWriteFile
= new OpenFile(mWriteFilename
, true, false, aReadFile
->versionId() + 1, aReadFile
->mode());
389 // Insert aReadFile into the map (do this after opening mWriteFile just in case that throws).
390 mOpenFileMap
.insert(OpenFileMap::value_type(-1, aReadFile
));
392 outWriteRef
= mWriteFile
->fileRef();
393 outLength
= aReadFile
->length();
394 outFileAddress
= aReadFile
->address();
395 return aReadFile
->versionId();
399 // Make sure we don't thow during cleanup since that would clobber the original
400 // error and prevent us from releasing mWriteLock
408 mOpenFileMap
.erase(-1);
418 unlink(mWriteFilename
);
424 catch(...) {} // Do not throw since we already have an error.
426 // Release the write lock and remove any unused files from the map
432 AtomicFile::VersionId
435 StLock
<Mutex
> _(mReadLock
);
436 if (mWriteFile
== nil
)
437 CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR
);
441 VersionId aVersionId
= mWriteFile
->versionId();
446 OpenFileMap::iterator it
= mOpenFileMap
.find(-1);
447 if (it
== mOpenFileMap
.end())
448 CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR
);
450 // First rename the file and them mark the old one as modified
451 rename(mWriteFilename
, mReadFilename
);
452 OpenFile
*aOpenFile
= it
->second
;
454 // Clear our current mReadFile since it refers to the old file.
457 // Mark the old file as modified
458 aOpenFile
->setDirty();
460 // Close all unused files (in particular aOpenFile) and remove them from mOpenFileMap
466 // Unlink the new file to rollback the transaction and close any open files.
469 unlink(mWriteFilename
);
477 AtomicFile::rollback()
479 StLock
<Mutex
> _(mReadLock
);
480 if (mWriteFile
== nil
)
481 CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR
);
489 // First rename the file and them mark the old one as modified
490 unlink(mWriteFilename
);
492 unlink(mReadFilename
);
497 // Unlink the new file to rollback the transaction and close any open files.
500 unlink(mWriteFilename
);
507 // This private function is called by a successfull commit(), rollback() or performDelete() as well
508 // as by a failed enterWrite() or enterCreate().
510 AtomicFile::endWrite()
514 // We need to go in and close and delete all unused files from the queue
515 stack
<VersionId
> aDeleteList
;
516 OpenFileMap::iterator it
;
517 for (it
= mOpenFileMap
.begin();
518 it
!= mOpenFileMap
.end();
521 OpenFile
*aOpenFile
= it
->second
;
522 // If aOpenFile is unused and it is not the mReadFile schedule it for close and removal.
523 // Note that if this is being called after a commit mReadFile will have been set to nil.
524 if (aOpenFile
!= mReadFile
&& aOpenFile
->mUseCount
== 0)
525 aDeleteList
.push(it
->first
);
528 // Remove everything that was scheduled for removal
529 while (!aDeleteList
.empty())
531 it
= mOpenFileMap
.find(aDeleteList
.top());
539 mOpenFileMap
.erase(it
);
561 AtomicFile::rename(const string
&inSrcFilename
, const string
&inDestFilename
)
563 if (::rename(inSrcFilename
.c_str(), inDestFilename
.c_str()))
564 UnixError::throwMe(errno
);
568 AtomicFile::unlink(const string
&inFilename
)
570 if (::unlink(inFilename
.c_str()))
571 UnixError::throwMe(errno
);
575 AtomicFile::write(OffsetType inOffsetType
, uint32 inOffset
, const uint32 inData
)
577 uint32 aData
= htonl(inData
);
578 write(inOffsetType
, inOffset
, reinterpret_cast<uint8
*>(&aData
), sizeof(aData
));
582 AtomicFile::write(OffsetType inOffsetType
, uint32 inOffset
,
583 const uint32
*inData
, uint32 inCount
)
585 #ifdef HOST_LONG_IS_NETWORK_LONG
586 // XXX Optimize this for the case where hl == nl
587 const uint32
*aBuffer
= inData
;
589 auto_array
<uint32
> aBuffer(inCount
);
590 for (uint32 i
= 0; i
< inCount
; i
++)
591 aBuffer
.get()[i
] = htonl(inData
[i
]);
594 write(inOffsetType
, inOffset
, reinterpret_cast<const uint8
*>(aBuffer
.get()),
595 inCount
* sizeof(*inData
));
599 AtomicFile::write(OffsetType inOffsetType
, uint32 inOffset
, const uint8
*inData
, uint32 inLength
)
601 // Seriously paranoid check.
602 if (mWriteFile
== nil
)
603 CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR
);
605 if (inOffsetType
!= None
)
607 if (::lseek(mWriteFile
->mFileRef
, inOffset
, inOffsetType
== FromStart
? SEEK_SET
: SEEK_CUR
) == -1)
608 UnixError::throwMe(errno
);
611 if (::write(mWriteFile
->mFileRef
, reinterpret_cast<const char *>(inData
),
612 inLength
) != static_cast<ssize_t
>(inLength
))
613 UnixError::throwMe(errno
);
616 // AtomicFile::OpenFile implementation
618 AtomicFile::OpenFile::OpenFile(const string
&inFilename
, bool write
, bool lock
, VersionId inVersionId
, mode_t mode
) :
620 mVersionId(inVersionId
),
630 else if (write
&& !lock
)
632 flags
= O_WRONLY
|O_CREAT
|O_TRUNC
;
635 else if (!write
&& lock
)
637 flags
= O_WRONLY
|O_CREAT
|O_TRUNC
|O_EXCL
;
646 mFileRef
= ::open(inFilename
.c_str(), flags
, mode
);
651 #if _USE_IO == _USE_IO_POSIX
652 // Do the obvious error code translations here.
655 // Throw CSSMERR_DL_DATASTORE_DOESNOT_EXIST even in Write state since it means someone threw away our parent directory.
656 if (mState
== ReadWrite
|| mState
== Read
|| mState
== Write
)
657 CssmError::throwMe(CSSMERR_DL_DATASTORE_DOESNOT_EXIST
);
658 if (mState
== Create
)
660 // Attempt to create the path to inFilename since one or more of the directories
661 // in the path do not yet exist.
664 // Now try the open again.
665 mFileRef
= ::open(inFilename
.c_str(), flags
, mode
);
666 error
= mFileRef
== -1 ? errno
: 0;
668 CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED
);
673 CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED
);
676 CssmError::throwMe(CSSMERR_DL_DATASTORE_ALREADY_EXISTS
);
679 // Check if we are still in an error state.
681 UnixError::throwMe(errno
);
684 // If this is a new file write out the versionId
685 if (mState
== Create
)
686 writeVersionId(mVersionId
);
688 // If this is a temp output file we are done.
694 mLength
= ::lseek(mFileRef
, 0, SEEK_END
);
695 if (mLength
== static_cast<size_t>(-1))
696 UnixError::throwMe(errno
);
699 // XXX What to set versionId to?
701 return; // No point in mapping a zero length file.
704 #if _USE_IO == _USE_IO_POSIX
705 // Lock the file if required.
711 mLock
.l_pid
= getpid();
712 mLock
.l_type
= F_WRLCK
;
713 mLock
.l_whence
= SEEK_SET
;
715 // Keep trying to obtain the lock if we get interupted.
718 if (::fcntl(mFileRef
, F_SETLKW
, reinterpret_cast<int>(&mLock
)) == -1)
724 if (error
!= ENOTSUP
)
725 UnixError::throwMe(error
);
727 // XXX Filesystem does not support locking with fcntl use an alternative.
737 if (mState
!= Create
)
739 mAddress
= reinterpret_cast<const uint8
*>
740 (::mmap(0, mLength
, PROT_READ
, MAP_FILE
|MAP_SHARED
,
742 if (mAddress
== reinterpret_cast<const uint8
*>(-1))
745 UnixError::throwMe(errno
);
748 mVersionId
= readVersionId();
751 if (mState
!= Create
)
753 mAddress
= reinterpret_cast<const uint8
*>(-1);
754 auto_array
<char> aBuffer(mLength
);
755 if (::read(mFileRef
, aBuffer
.get(), mLength
) != mLength
)
756 UnixError::throwMe(errno
);
758 mAddress
= reinterpret_cast<const uint8
*>(aBuffer
.release());
759 mVersionId
= readVersionId();
765 if (mState
!= Closed
)
771 AtomicFile::OpenFile::~OpenFile()
777 AtomicFile::OpenFile::close()
780 if (mAddress
!= NULL
)
782 #if _USE_IO == _USE_IO_POSIX
783 if (::munmap(const_cast<uint8
*>(mAddress
), mLength
) == -1)
793 writeVersionId(mVersionId
);
795 if (mState
!= Closed
)
798 if (::close(mFileRef
) == -1)
803 UnixError::throwMe(error
);
807 AtomicFile::OpenFile::isDirty()
809 if (mAddress
== NULL
)
810 CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR
);
812 return (mVersionId
!= readVersionId()) || mVersionId
== 0;
815 // Set the files dirty bit (requires the file to be writeable and locked).
817 AtomicFile::OpenFile::setDirty()
819 if (mState
!= ReadWrite
&& mState
!= Create
)
820 CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR
);
826 AtomicFile::OpenFile::unlock()
828 // XXX This should be called.
835 mLock
.l_pid
= getpid();
836 mLock
.l_type
= F_UNLCK
;
837 mLock
.l_whence
= SEEK_SET
;
838 if (::fcntl(mFileRef
, F_SETLK
, reinterpret_cast<int>(&mLock
)) == -1)
839 UnixError::throwMe(errno
);
845 AtomicFile::OpenFile::mode()
848 if (::fstat(mFileRef
, &st
) == -1)
849 UnixError::throwMe(errno
);
854 AtomicFile::VersionId
855 AtomicFile::OpenFile::readVersionId()
860 // Read the VersionId
861 if (mAddress
== NULL
)
863 // Seek to the end of the file minus 4
865 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT
);
867 if (::lseek(mFileRef
, mLength
- 4, SEEK_SET
) == -1)
868 UnixError::throwMe(errno
);
870 ptr
= reinterpret_cast<uint8
*>(buf
);
871 if (::read(mFileRef
, buf
, 4) != 4)
872 UnixError::throwMe(errno
);
876 ptr
= mAddress
+ mLength
- 4;
878 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT
);
881 VersionId aVersionId
= 0;
882 for (int i
= 0; i
< 4; i
++)
884 aVersionId
= (aVersionId
<< 8) + ptr
[i
];
891 AtomicFile::OpenFile::writeVersionId(VersionId inVersionId
)
893 if (mState
== ReadWrite
)
895 // Seek to the end of the file minus 4
897 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT
);
899 if (::lseek(mFileRef
, mLength
- 4, SEEK_SET
) == -1)
900 UnixError::throwMe(errno
);
902 else /* if (mState == Create || mState == Write) */
904 // Seek to the end of the file.
905 if (::lseek(mFileRef
, 0, SEEK_END
) == -1)
906 UnixError::throwMe(errno
);
910 // Serialize the VersionId
911 for (int i
= 3; i
>= 0; i
--)
913 buf
[i
] = inVersionId
& 0xff;
914 inVersionId
= inVersionId
>> 8;
917 // Write the VersionId
918 if (::write(mFileRef
, reinterpret_cast<char *>(buf
), 4) != 4)
919 UnixError::throwMe(errno
);
923 AtomicFile::OpenFile::mkpath(const std::string
&inFilename
)
925 const char *path
= inFilename
.c_str();
927 char dirPath
[MAXPATHLEN
];
932 slash
+= strspn(path
+ slash
, "/");
933 slash
+= strcspn(path
+ slash
, "/");
935 if (path
[slash
] == '\0')
938 if (slash
>= MAXPATHLEN
)
939 UnixError::throwMe(ENAMETOOLONG
);
940 strncpy(dirPath
, path
, slash
);
941 dirPath
[slash
] = '\0';
943 if (stat(dirPath
, &sb
))
945 if (errno
!= ENOENT
|| mkdir(dirPath
, 0777))
946 UnixError::throwMe(errno
);
948 else if (!S_ISDIR(sb
.st_mode
))
949 CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED
); // @@@ Should be is a directory
955 // Constructor uglyness to work around C++ language limitations.
956 struct AtomicFileRef::InitArg
958 AtomicFile::VersionId versionId
;
959 const uint8
*address
;
963 AtomicFileRef::~AtomicFileRef()
967 AtomicFileRef::AtomicFileRef(AtomicFile
&inAtomicFile
, const InitArg
&inInitArg
) :
968 mVersionId(inInitArg
.versionId
),
969 mAtomicFile(inAtomicFile
),
970 mAddress(inInitArg
.address
),
971 mLength(inInitArg
.length
)
975 AtomicFileReadRef::~AtomicFileReadRef()
978 mAtomicFile
.exitRead(mVersionId
);
984 AtomicFileRef::InitArg
985 AtomicFileReadRef::enterRead(AtomicFile
&inAtomicFile
)
988 anInitArg
.versionId
= inAtomicFile
.enterRead(anInitArg
.address
, anInitArg
.length
);
992 AtomicFileReadRef::AtomicFileReadRef(AtomicFile
&inAtomicFile
) :
993 AtomicFileRef(inAtomicFile
, enterRead(inAtomicFile
))
997 AtomicFileWriteRef::~AtomicFileWriteRef()
1001 mAtomicFile
.rollback();
1009 AtomicFileRef::InitArg
1010 AtomicFileWriteRef::enterWrite(AtomicFile
&inAtomicFile
, AtomicFile::FileRef
&outWriteFileRef
)
1013 anInitArg
.versionId
= inAtomicFile
.enterWrite(anInitArg
.address
, anInitArg
.length
, outWriteFileRef
);
1017 AtomicFileWriteRef::AtomicFileWriteRef(AtomicFile
&inAtomicFile
) :
1018 AtomicFileRef(inAtomicFile
, enterWrite(inAtomicFile
, mFileRef
))