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