]>
Commit | Line | Data |
---|---|---|
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 | |
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 | { | |
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 | ||
65 | AtomicFile::~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 | ||
95 | void | |
96 | AtomicFile::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 | ||
136 | AtomicFile::VersionId | |
137 | AtomicFile::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 | ||
197 | void | |
198 | AtomicFile::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 | ||
236 | bool 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 | ||
247 | void | |
248 | AtomicFile::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 | ||
302 | AtomicFile::VersionId | |
303 | AtomicFile::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 | ||
363 | AtomicFile::VersionId | |
364 | AtomicFile::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 | ||
435 | AtomicFile::VersionId | |
436 | AtomicFile::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 | ||
482 | void | |
483 | AtomicFile::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(). | |
518 | void | |
519 | AtomicFile::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 | ||
569 | void | |
570 | AtomicFile::rename(const string &inSrcFilename, const string &inDestFilename) | |
571 | { | |
572 | if (::rename(inSrcFilename.c_str(), inDestFilename.c_str())) | |
573 | UnixError::throwMe(errno); | |
574 | } | |
575 | ||
576 | void | |
577 | AtomicFile::unlink(const string &inFilename) | |
578 | { | |
579 | if (::unlink(inFilename.c_str())) | |
580 | UnixError::throwMe(errno); | |
581 | } | |
582 | ||
583 | void | |
584 | AtomicFile::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 | ||
590 | void | |
591 | AtomicFile::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 | ||
607 | void | |
608 | AtomicFile::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 | 627 | AtomicFile::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 | ||
786 | AtomicFile::OpenFile::~OpenFile() | |
787 | { | |
788 | close(); | |
789 | } | |
790 | ||
791 | void | |
792 | AtomicFile::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 | ||
825 | bool | |
826 | AtomicFile::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). | |
835 | void | |
836 | AtomicFile::OpenFile::setDirty() | |
837 | { | |
838 | if (mState != ReadWrite && mState != Create) | |
839 | CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR); | |
840 | ||
841 | writeVersionId(0); | |
842 | } | |
843 | ||
844 | void | |
845 | AtomicFile::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 |
863 | mode_t |
864 | AtomicFile::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 |
873 | AtomicFile::VersionId |
874 | AtomicFile::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 | ||
909 | void | |
910 | AtomicFile::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 | ||
941 | void | |
942 | AtomicFile::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. | |
975 | struct AtomicFileRef::InitArg | |
976 | { | |
977 | AtomicFile::VersionId versionId; | |
978 | const uint8 *address; | |
979 | size_t length; | |
980 | }; | |
981 | ||
982 | AtomicFileRef::~AtomicFileRef() | |
983 | { | |
984 | } | |
985 | ||
986 | AtomicFileRef::AtomicFileRef(AtomicFile &inAtomicFile, const InitArg &inInitArg) : | |
987 | mVersionId(inInitArg.versionId), | |
988 | mAtomicFile(inAtomicFile), | |
989 | mAddress(inInitArg.address), | |
990 | mLength(inInitArg.length) | |
991 | { | |
992 | } | |
993 | ||
994 | AtomicFileReadRef::~AtomicFileReadRef() | |
995 | { | |
996 | try { | |
997 | mAtomicFile.exitRead(mVersionId); | |
998 | } | |
999 | catch(...) { | |
1000 | } | |
1001 | } | |
1002 | ||
1003 | AtomicFileRef::InitArg | |
1004 | AtomicFileReadRef::enterRead(AtomicFile &inAtomicFile) | |
1005 | { | |
1006 | InitArg anInitArg; | |
1007 | anInitArg.versionId = inAtomicFile.enterRead(anInitArg.address, anInitArg.length); | |
1008 | return anInitArg; | |
1009 | } | |
1010 | ||
1011 | AtomicFileReadRef::AtomicFileReadRef(AtomicFile &inAtomicFile) : | |
1012 | AtomicFileRef(inAtomicFile, enterRead(inAtomicFile)) | |
1013 | { | |
1014 | } | |
1015 | ||
1016 | AtomicFileWriteRef::~AtomicFileWriteRef() | |
1017 | { | |
1018 | if (mOpen) { | |
1019 | try { | |
1020 | mAtomicFile.rollback(); | |
1021 | } | |
1022 | catch (...) | |
1023 | { | |
1024 | } | |
1025 | } | |
1026 | } | |
1027 | ||
1028 | AtomicFileRef::InitArg | |
1029 | AtomicFileWriteRef::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 | ||
1036 | AtomicFileWriteRef::AtomicFileWriteRef(AtomicFile &inAtomicFile) : | |
1037 | AtomicFileRef(inAtomicFile, enterWrite(inAtomicFile, mFileRef)) | |
1038 | { | |
1039 | } |