]> git.saurik.com Git - apple/security.git/blob - cdsa/cdsa_utilities/AtomicFile.cpp
Security-30.1.tar.gz
[apple/security.git] / cdsa / cdsa_utilities / AtomicFile.cpp
1 /*
2 * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved.
3 *
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
8 * using this file.
9 *
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.
16 */
17
18
19 //
20 // AtomicFile.cpp - Description t.b.d.
21 //
22 #ifdef __MWERKS__
23 #define _CPP_ATOMICFILE
24 #endif
25
26 #include <Security/AtomicFile.h>
27 #include <Security/DbName.h>
28
29 #include <unistd.h>
30 #include <fcntl.h>
31 #include <errno.h>
32 #include <memory>
33 #include <stack>
34
35 #if _USE_IO == _USE_IO_POSIX
36 #include <stdio.h>
37 #include <sys/types.h>
38 #include <sys/mman.h>
39
40 #include <sys/stat.h>
41 //#include <err.h>
42 #include <locale.h>
43 #include <stdlib.h>
44 #include <string.h>
45
46 #elif _USE_IO == _USE_IO_MACOS
47 typedef SInt32 ssize_t;
48 #endif
49
50 using namespace std;
51
52 AtomicFile::AtomicFile(const DbName &inDbName) :
53 mReadFile(nil),
54 mReadFilename(inDbName.dbName()),
55 mWriteFile(nil),
56 mWriteFilename(mReadFilename + ",") // XXX Do some more work here like resolving symlinks/aliases etc.
57 {
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);
61 }
62
63 AtomicFile::~AtomicFile()
64 {
65 // Assume there are no more running theads in this object.
66
67 // Try hard to clean up as much as possible.
68 try
69 {
70 // Rollback any pending write.
71 if (mWriteFile)
72 rollback();
73 }
74 catch(...) {}
75
76 // Close and delete all files in mOpenFileMap
77 for (OpenFileMap::iterator it = mOpenFileMap.begin(); it != mOpenFileMap.end(); it++)
78 {
79 try
80 {
81 it->second->close();
82 }
83 catch(...) {}
84 try
85 {
86 delete it->second;
87 }
88 catch(...) {}
89 }
90 }
91
92 void
93 AtomicFile::close()
94 {
95 StLock<Mutex> _(mReadLock);
96
97 // If we have no read file we have nothing to close.
98 if (mReadFile == nil)
99 return;
100
101 // Remember mReadFile and set it to nil, so that it will be closed after any pending write completes
102 OpenFile *aOpenFile = mReadFile;
103 mReadFile = nil;
104
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)
108 {
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())
112 {
113 // Release the write lock immediately since tryLock just aquired it and we don't want to write.
114 mWriteLock.unlock();
115
116 // Remove aOpenFile from the map of open files.
117 mOpenFileMap.erase(aOpenFile->versionId());
118 try
119 {
120 aOpenFile->close();
121 }
122 catch(...)
123 {
124 delete aOpenFile;
125 throw;
126 }
127 delete aOpenFile;
128 }
129 }
130 }
131
132 AtomicFile::VersionId
133 AtomicFile::enterRead(const uint8 *&outFileAddress, size_t &outLength)
134 {
135 StLock<Mutex> _(mReadLock);
136
137 // If we already have a read file check if it is still current.
138 if (mReadFile != nil)
139 {
140 if (mReadFile->isDirty())
141 {
142 // Remember mReadFile and set it to nil in case an exception is thrown
143 OpenFile *aOpenFile = mReadFile;
144 mReadFile = nil;
145
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)
149 {
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())
153 {
154 // Release the write lock immediately since tryLock just aquired it and we don't want to write.
155 mWriteLock.unlock();
156
157 // Remove aOpenFile from the map of open files.
158 mOpenFileMap.erase(aOpenFile->versionId());
159 try
160 {
161 aOpenFile->close();
162 }
163 catch(...)
164 {
165 delete aOpenFile;
166 throw;
167 }
168 delete aOpenFile;
169 }
170 }
171 }
172 }
173
174 // If we never had or no longer have an open read file. Open it now.
175 if (mReadFile == nil)
176 {
177 mReadFile = new OpenFile(mReadFilename, false, false, 0);
178 mOpenFileMap.insert(OpenFileMap::value_type(mReadFile->versionId(), mReadFile));
179 }
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.
183
184 // Bump up the use count of our OpenFile.
185 mReadFile->mUseCount++;
186
187 // Return the length of the file and the mapped address.
188 outLength = mReadFile->length();
189 outFileAddress = mReadFile->address();
190 return mReadFile->versionId();
191 }
192
193 void
194 AtomicFile::exitRead(VersionId inVersionId)
195 {
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);
201
202 OpenFile *aOpenFile = it->second;
203 aOpenFile->mUseCount--;
204
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)
208 {
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())
212 {
213 // Release the write lock immidiatly since tryLock just aquired it and we don't want to write.
214 mWriteLock.unlock();
215
216 // Remove from the map, close and delete aOpenFile.
217 mOpenFileMap.erase(it);
218 try
219 {
220 aOpenFile->close();
221 }
222 catch(...)
223 {
224 delete aOpenFile;
225 throw;
226 }
227 delete aOpenFile;
228 }
229 }
230 }
231
232 bool AtomicFile::isDirty(VersionId inVersionId)
233 {
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);
239
240 return it->second->isDirty();
241 }
242
243 void
244 AtomicFile::performDelete()
245 {
246 // Prevent any other threads in this process from writing.
247 mWriteLock.lock();
248
249 OpenFile *aReadFile = nil;
250 try
251 {
252 // Keep reopening mReadFilename until the lock has been aquired on a non-dirty file.
253 // XXX This is a potential infinite loop.
254 for (;;)
255 {
256 aReadFile = new OpenFile(mReadFilename, true, true, 0);
257 if (!aReadFile->isDirty())
258 break;
259
260 aReadFile->close();
261 delete aReadFile;
262 aReadFile = nil;
263 }
264
265 // Aquire the read lock so no other thread will open the file
266 StLock<Mutex> _(mReadLock);
267
268 // Delete the file.
269 unlink(mReadFilename);
270
271 // Clear our current mReadFile since it refers to the deleted file.
272 mReadFile = nil;
273
274 // Mark the old file as modified
275 aReadFile->setDirty();
276
277 // Close any open files.
278 endWrite();
279 }
280 catch(...)
281 {
282 if (aReadFile)
283 {
284 try
285 {
286 VersionId aVersionId = aReadFile->versionId();
287 aReadFile->close();
288 mOpenFileMap.erase(aVersionId);
289 } catch(...) {}
290 delete aReadFile;
291 }
292 endWrite();
293 throw;
294 }
295 endWrite();
296 }
297
298 AtomicFile::VersionId
299 AtomicFile::enterCreate(FileRef &outWriteRef)
300 {
301 // Prevent any other threads in this process from writing.
302 mWriteLock.lock();
303 OpenFile *aReadFile = nil;
304 try
305 {
306 // No threads can read during creation
307 StLock<Mutex> _(mReadLock);
308
309 // Create mReadFilename until the lock has been aquired on a non-dirty file.
310 aReadFile = new OpenFile(mReadFilename, false, true, 1);
311
312 // Open mWriteFile for writing.
313 mWriteFile = new OpenFile(mWriteFilename, true, false, aReadFile->versionId() + 1);
314
315 // Insert aReadFile into the map (do this after opening mWriteFile just in case that throws).
316 mOpenFileMap.insert(OpenFileMap::value_type(-1, aReadFile));
317
318 outWriteRef = mWriteFile->fileRef();
319 mCreating = true; // So rollback() will delete mReadFileName.
320 return aReadFile->versionId();
321 }
322 catch(...)
323 {
324 // Make sure we don't thow during cleanup since that would clobber the original
325 // error and prevent us from releasing mWriteLock
326 try
327 {
328 if (aReadFile)
329 {
330 try
331 {
332 aReadFile->close();
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);
336 } catch(...) {}
337 delete aReadFile;
338 }
339
340 if (mWriteFile)
341 {
342 try
343 {
344 mWriteFile->close();
345 unlink(mWriteFilename);
346 } catch(...) {}
347 delete mWriteFile;
348 mWriteFile = nil;
349 }
350 }
351 catch(...) {} // Do not throw since we already have an error.
352
353 // Release the write lock and remove any unused files from the map
354 endWrite();
355 throw;
356 }
357 }
358
359 AtomicFile::VersionId
360 AtomicFile::enterWrite(const uint8 *&outFileAddress, size_t &outLength, FileRef &outWriteRef)
361 {
362 // Wait for all other threads in this process to finish writing.
363 mWriteLock.lock();
364 mCreating = false; // So rollback() will not delete mReadFileName.
365 OpenFile *aReadFile = nil;
366 try
367 {
368 // Keep reopening mReadFilename until the lock has been aquired on a non-dirty file.
369 // XXX This is a potential infinite loop.
370 for (;;)
371 {
372 aReadFile = new OpenFile(mReadFilename, true, true, 0);
373 if (!aReadFile->isDirty())
374 break;
375
376 aReadFile->close();
377 delete aReadFile;
378 aReadFile = nil;
379 }
380
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);
384
385 // Open mWriteFile for writing.
386 mWriteFile = new OpenFile(mWriteFilename, true, false, aReadFile->versionId() + 1);
387
388 // Insert aReadFile into the map (do this after opening mWriteFile just in case that throws).
389 mOpenFileMap.insert(OpenFileMap::value_type(-1, aReadFile));
390
391 outWriteRef = mWriteFile->fileRef();
392 outLength = aReadFile->length();
393 outFileAddress = aReadFile->address();
394 return aReadFile->versionId();
395 }
396 catch(...)
397 {
398 // Make sure we don't thow during cleanup since that would clobber the original
399 // error and prevent us from releasing mWriteLock
400 try
401 {
402 if (aReadFile)
403 {
404 try
405 {
406 aReadFile->close();
407 mOpenFileMap.erase(-1);
408 } catch(...) {}
409 delete aReadFile;
410 }
411
412 if (mWriteFile)
413 {
414 try
415 {
416 mWriteFile->close();
417 unlink(mWriteFilename);
418 } catch(...) {}
419 delete mWriteFile;
420 mWriteFile = nil;
421 }
422 }
423 catch(...) {} // Do not throw since we already have an error.
424
425 // Release the write lock and remove any unused files from the map
426 endWrite();
427 throw;
428 }
429 }
430
431 AtomicFile::VersionId
432 AtomicFile::commit()
433 {
434 StLock<Mutex> _(mReadLock);
435 if (mWriteFile == nil)
436 CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR);
437
438 try
439 {
440 VersionId aVersionId = mWriteFile->versionId();
441 mWriteFile->close();
442 delete mWriteFile;
443 mWriteFile = nil;
444
445 OpenFileMap::iterator it = mOpenFileMap.find(-1);
446 if (it == mOpenFileMap.end())
447 CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR);
448
449 // First rename the file and them mark the old one as modified
450 rename(mWriteFilename, mReadFilename);
451 OpenFile *aOpenFile = it->second;
452
453 // Clear our current mReadFile since it refers to the old file.
454 mReadFile = nil;
455
456 // Mark the old file as modified
457 aOpenFile->setDirty();
458
459 // Close all unused files (in particular aOpenFile) and remove them from mOpenFileMap
460 endWrite();
461 return aVersionId;
462 }
463 catch (...)
464 {
465 // Unlink the new file to rollback the transaction and close any open files.
466 try
467 {
468 unlink(mWriteFilename);
469 }catch(...) {}
470 endWrite();
471 throw;
472 }
473 }
474
475 void
476 AtomicFile::rollback()
477 {
478 StLock<Mutex> _(mReadLock);
479 if (mWriteFile == nil)
480 CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR);
481
482 try
483 {
484 mWriteFile->close();
485 delete mWriteFile;
486 mWriteFile = nil;
487
488 // First rename the file and them mark the old one as modified
489 unlink(mWriteFilename);
490 if (mCreating)
491 unlink(mReadFilename);
492 endWrite();
493 }
494 catch(...)
495 {
496 // Unlink the new file to rollback the transaction and close any open files.
497 try
498 {
499 unlink(mWriteFilename);
500 }catch(...) {}
501 endWrite();
502 throw;
503 }
504 }
505
506 // This private function is called by a successfull commit(), rollback() or performDelete() as well
507 // as by a failed enterWrite() or enterCreate().
508 void
509 AtomicFile::endWrite()
510 {
511 try
512 {
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();
518 it++)
519 {
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);
525 }
526
527 // Remove everything that was scheduled for removal
528 while (!aDeleteList.empty())
529 {
530 it = mOpenFileMap.find(aDeleteList.top());
531 aDeleteList.pop();
532 try
533 {
534 it->second->close();
535 }
536 catch(...) {}
537 delete it->second;
538 mOpenFileMap.erase(it);
539 }
540
541 if (mWriteFile)
542 {
543 mWriteFile->close();
544 }
545 }
546 catch(...)
547 {
548 delete mWriteFile;
549 mWriteFile = nil;
550 mWriteLock.unlock();
551 throw;
552 }
553
554 delete mWriteFile;
555 mWriteFile = nil;
556 mWriteLock.unlock();
557 }
558
559 void
560 AtomicFile::rename(const string &inSrcFilename, const string &inDestFilename)
561 {
562 if (::rename(inSrcFilename.c_str(), inDestFilename.c_str()))
563 UnixError::throwMe(errno);
564 }
565
566 void
567 AtomicFile::unlink(const string &inFilename)
568 {
569 if (::unlink(inFilename.c_str()))
570 UnixError::throwMe(errno);
571 }
572
573 void
574 AtomicFile::write(OffsetType inOffsetType, uint32 inOffset, const uint32 inData)
575 {
576 uint32 aData = htonl(inData);
577 write(inOffsetType, inOffset, reinterpret_cast<uint8 *>(&aData), sizeof(aData));
578 }
579
580 void
581 AtomicFile::write(OffsetType inOffsetType, uint32 inOffset,
582 const uint32 *inData, uint32 inCount)
583 {
584 #ifdef HOST_LONG_IS_NETWORK_LONG
585 // XXX Optimize this for the case where hl == nl
586 const uint32 *aBuffer = inData;
587 #else
588 auto_array<uint32> aBuffer(inCount);
589 for (uint32 i = 0; i < inCount; i++)
590 aBuffer.get()[i] = htonl(inData[i]);
591 #endif
592
593 write(inOffsetType, inOffset, reinterpret_cast<const uint8 *>(aBuffer.get()),
594 inCount * sizeof(*inData));
595 }
596
597 void
598 AtomicFile::write(OffsetType inOffsetType, uint32 inOffset, const uint8 *inData, uint32 inLength)
599 {
600 // Seriously paranoid check.
601 if (mWriteFile == nil)
602 CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR);
603
604 if (inOffsetType != None)
605 {
606 if (::lseek(mWriteFile->mFileRef, inOffset, inOffsetType == FromStart ? SEEK_SET : SEEK_CUR) == -1)
607 UnixError::throwMe(errno);
608 }
609
610 if (::write(mWriteFile->mFileRef, reinterpret_cast<const char *>(inData),
611 inLength) != static_cast<ssize_t>(inLength))
612 UnixError::throwMe(errno);
613 }
614
615 // AtomicFile::OpenFile implementation
616
617 AtomicFile::OpenFile::OpenFile(const string &inFilename, bool write, bool lock, VersionId inVersionId) :
618 mUseCount(0),
619 mVersionId(inVersionId),
620 mAddress(NULL),
621 mLength(0)
622 {
623 int flags, mode = 0;
624 if (write && lock)
625 {
626 flags = O_RDWR;
627 mState = ReadWrite;
628 }
629 else if (write && !lock)
630 {
631 flags = O_WRONLY|O_CREAT|O_TRUNC;
632 mode = 0666;
633 mState = Write;
634 }
635 else if (!write && lock)
636 {
637 flags = O_WRONLY|O_CREAT|O_TRUNC|O_EXCL;
638 mode = 0666;
639 mState = Create;
640 }
641 else
642 {
643 flags = O_RDONLY;
644 mState = Read;
645 }
646
647 mFileRef = ::open(inFilename.c_str(), flags, mode);
648 if (mFileRef == -1)
649 {
650 int error = errno;
651
652 #if _USE_IO == _USE_IO_POSIX
653 // Do the obvious error code translations here.
654 if (error == ENOENT)
655 {
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)
660 {
661 // Attempt to create the path to inFilename since one or more of the directories
662 // in the path do not yet exist.
663 mkpath(inFilename);
664
665 // Now try the open again.
666 mFileRef = ::open(inFilename.c_str(), flags, mode);
667 error = mFileRef == -1 ? errno : 0;
668 if (error == ENOENT)
669 CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED);
670 }
671 }
672
673 if (error == EACCES)
674 CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED);
675
676 if (error == EEXIST)
677 CssmError::throwMe(CSSMERR_DL_DATASTORE_ALREADY_EXISTS);
678 #endif
679
680 // Check if we are still in an error state.
681 if (error)
682 UnixError::throwMe(errno);
683 }
684
685 // If this is a new file write out the versionId
686 if (mState == Create)
687 writeVersionId(mVersionId);
688
689 // If this is a temp output file we are done.
690 if (mState == Write)
691 return;
692
693 try
694 {
695 mLength = ::lseek(mFileRef, 0, SEEK_END);
696 if (mLength == static_cast<size_t>(-1))
697 UnixError::throwMe(errno);
698 if (mLength == 0)
699 {
700 // XXX What to set versionId to?
701 mVersionId = 0;
702 return; // No point in mapping a zero length file.
703 }
704
705 #if _USE_IO == _USE_IO_POSIX
706 // Lock the file if required.
707 if (lock)
708 {
709 struct flock mLock;
710 mLock.l_start = 0;
711 mLock.l_len = 1;
712 mLock.l_pid = getpid();
713 mLock.l_type = F_WRLCK;
714 mLock.l_whence = SEEK_SET;
715
716 // Keep trying to obtain the lock if we get interupted.
717 for (;;)
718 {
719 if (::fcntl(mFileRef, F_SETLKW, reinterpret_cast<int>(&mLock)) == -1)
720 {
721 int error = errno;
722 if (error == EINTR)
723 continue;
724
725 if (error != ENOTSUP)
726 UnixError::throwMe(error);
727
728 // XXX Filesystem does not support locking with fcntl use an alternative.
729 mFcntlLock = false;
730 }
731 else
732 mFcntlLock = true;
733
734 break;
735 }
736 }
737
738 if (mState != Create)
739 {
740 mAddress = reinterpret_cast<const uint8 *>
741 (::mmap(0, mLength, PROT_READ, MAP_FILE|MAP_SHARED,
742 mFileRef, 0));
743 if (mAddress == reinterpret_cast<const uint8 *>(-1))
744 {
745 mAddress = NULL;
746 UnixError::throwMe(errno);
747 }
748
749 mVersionId = readVersionId();
750 }
751 #else
752 if (mState != Create)
753 {
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);
758
759 mAddress = reinterpret_cast<const uint8 *>(aBuffer.release());
760 mVersionId = readVersionId();
761 }
762 #endif
763 }
764 catch(...)
765 {
766 if (mState != Closed)
767 ::close(mFileRef);
768 throw;
769 }
770 }
771
772 AtomicFile::OpenFile::~OpenFile()
773 {
774 close();
775 }
776
777 void
778 AtomicFile::OpenFile::close()
779 {
780 int error = 0;
781 if (mAddress != NULL)
782 {
783 #if _USE_IO == _USE_IO_POSIX
784 if (::munmap(const_cast<uint8 *>(mAddress), mLength) == -1)
785 error = errno;
786 #else
787 delete[] mAddress;
788 #endif
789
790 mAddress = NULL;
791 }
792
793 if (mState == Write)
794 writeVersionId(mVersionId);
795
796 if (mState != Closed)
797 {
798 mState = Closed;
799 if (::close(mFileRef) == -1)
800 error = errno;
801 }
802
803 if (error != 0)
804 UnixError::throwMe(error);
805 }
806
807 bool
808 AtomicFile::OpenFile::isDirty()
809 {
810 if (mAddress == NULL)
811 CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR);
812
813 return (mVersionId != readVersionId()) || mVersionId == 0;
814 }
815
816 // Set the files dirty bit (requires the file to be writeable and locked).
817 void
818 AtomicFile::OpenFile::setDirty()
819 {
820 if (mState != ReadWrite && mState != Create)
821 CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR);
822
823 writeVersionId(0);
824 }
825
826 void
827 AtomicFile::OpenFile::unlock()
828 {
829 // XXX This should be called.
830 #if 0
831 if (mFcntlLock)
832 {
833 struct flock mLock;
834 mLock.l_start = 0;
835 mLock.l_len = 1;
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);
841 }
842 #endif
843 }
844
845 AtomicFile::VersionId
846 AtomicFile::OpenFile::readVersionId()
847 {
848 const uint8 *ptr;
849 char buf[4];
850
851 // Read the VersionId
852 if (mAddress == NULL)
853 {
854 // Seek to the end of the file minus 4
855 if (mLength < 4)
856 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT);
857
858 if (::lseek(mFileRef, mLength - 4, SEEK_SET) == -1)
859 UnixError::throwMe(errno);
860
861 ptr = reinterpret_cast<uint8 *>(buf);
862 if (::read(mFileRef, buf, 4) != 4)
863 UnixError::throwMe(errno);
864 }
865 else
866 {
867 ptr = mAddress + mLength - 4;
868 if (mLength < 4)
869 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT);
870 }
871
872 VersionId aVersionId = 0;
873 for (int i = 0; i < 4; i++)
874 {
875 aVersionId = (aVersionId << 8) + ptr[i];
876 }
877
878 return aVersionId;
879 }
880
881 void
882 AtomicFile::OpenFile::writeVersionId(VersionId inVersionId)
883 {
884 if (mState == ReadWrite)
885 {
886 // Seek to the end of the file minus 4
887 if (mLength < 4)
888 CssmError::throwMe(CSSMERR_DL_DATABASE_CORRUPT);
889
890 if (::lseek(mFileRef, mLength - 4, SEEK_SET) == -1)
891 UnixError::throwMe(errno);
892 }
893 else /* if (mState == Create || mState == Write) */
894 {
895 // Seek to the end of the file.
896 if (::lseek(mFileRef, 0, SEEK_END) == -1)
897 UnixError::throwMe(errno);
898 }
899
900 uint8 buf[4];
901 // Serialize the VersionId
902 for (int i = 3; i >= 0; i--)
903 {
904 buf[i] = inVersionId & 0xff;
905 inVersionId = inVersionId >> 8;
906 }
907
908 // Write the VersionId
909 if (::write(mFileRef, reinterpret_cast<char *>(buf), 4) != 4)
910 UnixError::throwMe(errno);
911 }
912
913 void
914 AtomicFile::OpenFile::mkpath(const std::string &inFilename)
915 {
916 char *path = const_cast<char *>(inFilename.c_str()); // @@@ Const_cast is a lie!!!
917 struct stat sb;
918 char *slash;
919 mode_t dir_mode = (0777 & ~umask(0)) | S_IWUSR | S_IXUSR;
920
921 slash = path;
922
923 for (;;)
924 {
925 slash += strspn(slash, "/");
926 slash += strcspn(slash, "/");
927
928 if (*slash == '\0')
929 break;
930
931 *slash = '\0';
932
933 if (stat(path, &sb))
934 {
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);
942 }
943 else if (!S_ISDIR(sb.st_mode))
944 CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED); // @@@ Should be is a directory
945
946 *slash = '/';
947 }
948 }
949
950
951
952 // Constructor uglyness to work around C++ language limitations.
953 struct AtomicFileRef::InitArg
954 {
955 AtomicFile::VersionId versionId;
956 const uint8 *address;
957 size_t length;
958 };
959
960 AtomicFileRef::~AtomicFileRef()
961 {
962 }
963
964 AtomicFileRef::AtomicFileRef(AtomicFile &inAtomicFile, const InitArg &inInitArg) :
965 mVersionId(inInitArg.versionId),
966 mAtomicFile(inAtomicFile),
967 mAddress(inInitArg.address),
968 mLength(inInitArg.length)
969 {
970 }
971
972 AtomicFileReadRef::~AtomicFileReadRef()
973 {
974 try {
975 mAtomicFile.exitRead(mVersionId);
976 }
977 catch(...) {
978 }
979 }
980
981 AtomicFileRef::InitArg
982 AtomicFileReadRef::enterRead(AtomicFile &inAtomicFile)
983 {
984 InitArg anInitArg;
985 anInitArg.versionId = inAtomicFile.enterRead(anInitArg.address, anInitArg.length);
986 return anInitArg;
987 }
988
989 AtomicFileReadRef::AtomicFileReadRef(AtomicFile &inAtomicFile) :
990 AtomicFileRef(inAtomicFile, enterRead(inAtomicFile))
991 {
992 }
993
994 AtomicFileWriteRef::~AtomicFileWriteRef()
995 {
996 if (mOpen) {
997 try {
998 mAtomicFile.rollback();
999 }
1000 catch (...)
1001 {
1002 }
1003 }
1004 }
1005
1006 AtomicFileRef::InitArg
1007 AtomicFileWriteRef::enterWrite(AtomicFile &inAtomicFile, AtomicFile::FileRef &outWriteFileRef)
1008 {
1009 InitArg anInitArg;
1010 anInitArg.versionId = inAtomicFile.enterWrite(anInitArg.address, anInitArg.length, outWriteFileRef);
1011 return anInitArg;
1012 }
1013
1014 AtomicFileWriteRef::AtomicFileWriteRef(AtomicFile &inAtomicFile) :
1015 AtomicFileRef(inAtomicFile, enterWrite(inAtomicFile, mFileRef))
1016 {
1017 }