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