]> git.saurik.com Git - apple/security.git/blob - OSX/libsecurity_filedb/lib/AtomicFile.cpp
Security-57740.31.2.tar.gz
[apple/security.git] / OSX / libsecurity_filedb / lib / AtomicFile.cpp
1 /*
2 * Copyright (c) 2000-2013 Apple 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 #include <security_filedb/AtomicFile.h>
20
21 #include <security_utilities/devrandom.h>
22 #include <CommonCrypto/CommonDigest.h>
23 #include <security_cdsa_utilities/cssmerrors.h>
24 #include <Security/cssm.h>
25 #include <errno.h>
26 #include <fcntl.h>
27 #include <limits.h>
28 #include <syslog.h>
29 #include <sys/param.h>
30 #include <sys/types.h>
31 #include <sys/mount.h>
32 #include <sys/file.h>
33 #include <sys/stat.h>
34 #include <sys/time.h>
35 #include <sys/mman.h>
36 #include <copyfile.h>
37 #include <sandbox.h>
38 #include <set>
39 #include <assert.h>
40
41 #define kAtomicFileMaxBlockSize INT_MAX
42
43
44 //
45 // AtomicFile.cpp
46 //
47 AtomicFile::AtomicFile(const std::string &inPath) :
48 mPath(inPath)
49 {
50 pathSplit(inPath, mDir, mFile);
51
52 if (mDir.length() == 0)
53 {
54 const char* buffer = getwd(NULL);
55 mDir = buffer;
56 free((void*) buffer);
57 }
58
59 mDir += '/';
60
61 // determine if the path is on a local or a networked volume
62 struct statfs info;
63 int result = statfs(mDir.c_str(), &info);
64 if (result == -1) // error on opening?
65 {
66 mIsLocalFileSystem = false; // revert to the old ways if we can't tell what kind of system we have
67 }
68 else
69 {
70 mIsLocalFileSystem = (info.f_flags & MNT_LOCAL) != 0;
71 if (mIsLocalFileSystem)
72 {
73 // compute the name of the lock file for this file
74 CC_SHA1_CTX ctx;
75 CC_SHA1_Init(&ctx);
76 CC_SHA1_Update(&ctx, (const void*) mFile.c_str(), (CC_LONG)mFile.length());
77 u_int8_t digest[CC_SHA1_DIGEST_LENGTH];
78 CC_SHA1_Final(digest, &ctx);
79
80 u_int32_t hash = (digest[0] << 24) | (digest[1] << 16) | (digest[2] << 8) | digest[3];
81
82 char buffer[256];
83 sprintf(buffer, "%08X", hash);
84 mLockFilePath = mDir + ".fl" + buffer;
85 }
86 }
87 }
88
89 AtomicFile::~AtomicFile()
90 {
91 }
92
93 // Aquire the write lock and remove the file.
94 void
95 AtomicFile::performDelete()
96 {
97 AtomicLockedFile lock(*this);
98 if (::unlink(mPath.c_str()) != 0)
99 {
100 int error = errno;
101 secnotice("atomicfile", "unlink %s: %s", mPath.c_str(), strerror(error));
102 if (error == ENOENT)
103 CssmError::throwMe(CSSMERR_DL_DATASTORE_DOESNOT_EXIST);
104 else
105 UnixError::throwMe(error);
106 }
107
108 // unlink our lock file
109 ::unlink(mLockFilePath.c_str());
110 }
111
112 // Aquire the write lock and rename the file (and bump the version and stuff).
113 void
114 AtomicFile::rename(const std::string &inNewPath)
115 {
116 const char *path = mPath.c_str();
117 const char *newPath = inNewPath.c_str();
118
119 // @@@ lock the destination file too.
120 AtomicLockedFile lock(*this);
121 if (::rename(path, newPath) != 0)
122 {
123 int error = errno;
124 secnotice("atomicfile", "rename(%s, %s): %s", path, newPath, strerror(error));
125 UnixError::throwMe(error);
126 }
127 }
128
129 // Lock the file for writing and return a newly created AtomicTempFile.
130 RefPointer<AtomicTempFile>
131 AtomicFile::create(mode_t mode)
132 {
133 const char *path = mPath.c_str();
134
135 // First make sure the directory to this file exists and is writable
136 mkpath(mDir);
137
138 RefPointer<AtomicLockedFile> lock(new AtomicLockedFile(*this));
139 int fileRef = ropen(path, O_WRONLY|O_CREAT|O_EXCL, mode);
140 if (fileRef == -1)
141 {
142 int error = errno;
143 secnotice("atomicfile", "open %s: %s", path, strerror(error));
144
145 // Do the obvious error code translations here.
146 // @@@ Consider moving these up a level.
147 if (error == EACCES)
148 CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED);
149 else if (error == EEXIST)
150 CssmError::throwMe(CSSMERR_DL_DATASTORE_ALREADY_EXISTS);
151 else
152 UnixError::throwMe(error);
153 }
154 rclose(fileRef);
155
156 try
157 {
158 // Now that we have created the lock and the new db file create a tempfile
159 // object.
160 RefPointer<AtomicTempFile> temp(new AtomicTempFile(*this, lock, mode));
161 secinfo("atomicfile", "%p created %s", this, path);
162 return temp;
163 }
164 catch (...)
165 {
166 // Creating the temp file failed so remove the db file we just created too.
167 if (::unlink(path) == -1)
168 {
169 secnotice("atomicfile", "unlink %s: %s", path, strerror(errno));
170 }
171 throw;
172 }
173 }
174
175 // Lock the database file for writing and return a newly created AtomicTempFile.
176 // If the parent directory allows the write we're going to allow this. Previous
177 // versions checked for writability of the db file and that caused problems when
178 // setuid programs had made entries. As long as the db (keychain) file is readable
179 // this function can make the newer keychain file with the correct owner just by virtue
180 // of the copy that takes place.
181
182 RefPointer<AtomicTempFile>
183 AtomicFile::write()
184 {
185
186 RefPointer<AtomicLockedFile> lock(new AtomicLockedFile(*this));
187 return new AtomicTempFile(*this, lock);
188 }
189
190 // Return a bufferedFile containing current version of the file for reading.
191 RefPointer<AtomicBufferedFile>
192 AtomicFile::read()
193 {
194 return new AtomicBufferedFile(mPath, mIsLocalFileSystem);
195 }
196
197 mode_t
198 AtomicFile::mode() const
199 {
200 const char *path = mPath.c_str();
201 struct stat st;
202 if (::stat(path, &st) == -1)
203 {
204 int error = errno;
205 secinfo("atomicfile", "stat %s: %s", path, strerror(error));
206 UnixError::throwMe(error);
207 }
208 return st.st_mode;
209 }
210
211 // Split full into a dir and file component.
212 void
213 AtomicFile::pathSplit(const std::string &inFull, std::string &outDir, std::string &outFile)
214 {
215 std::string::size_type slash, len = inFull.size();
216 slash = inFull.rfind('/');
217 if (slash == std::string::npos)
218 {
219 outDir = "";
220 outFile = inFull;
221 }
222 else if (slash + 1 == len)
223 {
224 outDir = inFull;
225 outFile = "";
226 }
227 else
228 {
229 outDir = inFull.substr(0, slash + 1);
230 outFile = inFull.substr(slash + 1, len);
231 }
232 }
233
234 //
235 // Make sure the directory up to inDir exists inDir *must* end in a slash.
236 //
237 void
238 AtomicFile::mkpath(const std::string &inDir, mode_t mode)
239 {
240 // see if the file already exists and is a directory
241 struct stat st;
242 int result = stat(inDir.c_str(), &st);
243
244 if (result == 0) // file exists
245 {
246 if ((st.st_mode & S_IFDIR) == 0)
247 {
248 // whatever was there, it wasn't a directory. That's really bad, so complain
249 syslog(LOG_ALERT, "Needed a directory at %s, but the file that was there was not one.\n", inDir.c_str());
250 UnixError::throwMe(ENOTDIR);
251 }
252 }
253 else
254 {
255 // the file did not exist, try to create it
256 result = mkpath_np(inDir.c_str(), 0777); // make the directory with umask
257 if (result != 0)
258 {
259 // mkpath_np does not set errno, you have to look at the result.
260 UnixError::throwMe(result);
261 }
262 }
263
264 // Double check and see if we got what we hoped for
265 result = stat(inDir.c_str(), &st);
266 if (result != 0)
267 {
268 UnixError::throwMe(errno);
269 }
270
271 if ((st.st_mode & S_IFDIR) == 0)
272 {
273 // we didn't create a dictionary? That's curious...
274 syslog(LOG_ALERT, "Failed to create a directory when we asked for one to be created at %s\n", inDir.c_str());
275 UnixError::throwMe(ENOTDIR);
276 }
277 }
278
279 int
280 AtomicFile::ropen(const char *const name, int flags, mode_t mode)
281 {
282 bool isCreate = (flags & O_CREAT) != 0;
283
284 /*
285 The purpose of checkForRead and checkForWrite is to mitigate
286 spamming of the log when a user has installed certain third
287 party software packages which create additional keychains.
288 Certain applications use a custom sandbox profile which do not
289 permit this and so the user gets a ton of spam in the log.
290 This turns into a serious performance problem.
291
292 We handle this situation by checking two factors:
293
294 1: If the user is trying to create a file, we send the
295 request directly to open. This is the right thing
296 to do, as we don't want most applications creating
297 keychains unless they have been expressly authorized
298 to do so.
299
300 The layers above this one only set O_CREAT when a file
301 doesn't exist, so the case where O_CREAT can be called
302 on an existing file is irrelevant.
303
304 2: If the user is trying to open the file for reading or
305 writing, we check with the sandbox mechanism to see if
306 the operation will be permitted (and tell it not to
307 log if it the operation will fail).
308
309 If the operation is not permitted, we return -1 which
310 emulates the behavior of open. sandbox_check sets
311 errno properly, so the layers which call this function
312 will be able to act as though open had been called.
313 */
314
315 bool checkForRead = false;
316 bool checkForWrite = false;
317
318 int fd, tries_left = 4 /* kNoResRetry */;
319
320 if (!isCreate)
321 {
322 switch (flags & O_ACCMODE)
323 {
324 case O_RDONLY:
325 checkForRead = true;
326 break;
327 case O_WRONLY:
328 checkForWrite = true;
329 break;
330 case O_RDWR:
331 checkForRead = true;
332 checkForWrite = true;
333 break;
334 }
335
336 if (checkForRead)
337 {
338 int result = sandbox_check(getpid(), "file-read-data", (sandbox_filter_type) (SANDBOX_FILTER_PATH | SANDBOX_CHECK_NO_REPORT), name);
339 if (result != 0)
340 {
341 return -1;
342 }
343 }
344
345 if (checkForWrite)
346 {
347 int result = sandbox_check(getpid(), "file-write-data", (sandbox_filter_type) (SANDBOX_FILTER_PATH | SANDBOX_CHECK_NO_REPORT), name);
348 if (result != 0)
349 {
350 return -1;
351 }
352 }
353 }
354
355 do
356 {
357 fd = ::open(name, flags, mode);
358 } while (fd < 0 && (errno == EINTR || (errno == ENFILE && --tries_left >= 0)));
359
360 return fd;
361 }
362
363 int
364 AtomicFile::rclose(int fd)
365 {
366 int result;
367 do
368 {
369 result = ::close(fd);
370 } while(result && errno == EINTR);
371
372 return result;
373 }
374
375 //
376 // AtomicBufferedFile - This represents an instance of a file opened for reading.
377 // The file is read into memory and closed after this is done.
378 // The memory is released when this object is destroyed.
379 //
380 AtomicBufferedFile::AtomicBufferedFile(const std::string &inPath, bool isLocal) :
381 mPath(inPath),
382 mFileRef(-1),
383 mBuffer(NULL),
384 mLength(0)
385 {
386 }
387
388 AtomicBufferedFile::~AtomicBufferedFile()
389 {
390 if (mFileRef >= 0)
391 {
392 // In release mode, the assert() is compiled out so rv may be unused.
393 __unused int rv = AtomicFile::rclose(mFileRef);
394 assert(rv == 0);
395 secnotice("atomicfile", "%p closed %s", this, mPath.c_str());
396 }
397
398 if (mBuffer)
399 {
400 secinfo("atomicfile", "%p free %s buffer %p", this, mPath.c_str(), mBuffer);
401 unloadBuffer();
402 }
403 }
404
405 //
406 // Open the file and return the length in bytes.
407 //
408 off_t
409 AtomicBufferedFile::open()
410 {
411 const char *path = mPath.c_str();
412 if (mFileRef >= 0)
413 {
414 secnotice("atomicfile", "open %s: already open, closing and reopening", path);
415 close();
416 }
417
418 mFileRef = AtomicFile::ropen(path, O_RDONLY, 0);
419 if (mFileRef == -1)
420 {
421 int error = errno;
422 secinfo("atomicfile", "open %s: %s", path, strerror(error));
423
424 // Do the obvious error code translations here.
425 // @@@ Consider moving these up a level.
426 if (error == ENOENT)
427 CssmError::throwMe(CSSMERR_DL_DATASTORE_DOESNOT_EXIST);
428 else if (error == EACCES)
429 CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED);
430 else
431 UnixError::throwMe(error);
432 }
433
434 struct stat st;
435 int result = fstat(mFileRef, &st);
436 if (result == 0)
437 {
438 mLength = st.st_size;
439 }
440 else
441 {
442 int error = errno;
443 secinfo("atomicfile", "lseek(%s, END): %s", path, strerror(error));
444 AtomicFile::rclose(mFileRef);
445 mFileRef = -1;
446 UnixError::throwMe(error);
447 }
448
449 secnotice("atomicfile", "%p opened %s: %qd bytes", this, path, mLength);
450
451 return mLength;
452 }
453
454 //
455 // Unload the contents of the file.
456 //
457 void
458 AtomicBufferedFile::unloadBuffer()
459 {
460 if(mBuffer) {
461 delete [] mBuffer;
462 }
463 }
464
465 //
466 // Load the contents of the file into memory.
467 void
468 AtomicBufferedFile::loadBuffer()
469 {
470 // make a buffer big enough to hold the entire file
471 mBuffer = new uint8[mLength];
472 if(lseek(mFileRef, 0, SEEK_SET) < 0) {
473 int error = errno;
474 secinfo("atomicfile", "lseek(%s, BEGINNING): %s", mPath.c_str(), strerror(error));
475 UnixError::throwMe(error);
476 }
477 ssize_t pos = 0;
478
479 ssize_t bytesToRead = (ssize_t)mLength;
480 while (bytesToRead > 0)
481 {
482 ssize_t bytesRead = ::read(mFileRef, mBuffer + pos, bytesToRead);
483 if (bytesRead == -1)
484 {
485 if (errno != EINTR)
486 {
487 int error = errno;
488 secinfo("atomicfile", "read(%s, %zd): %s", mPath.c_str(), bytesToRead, strerror(error));
489 if (mFileRef >= 0) {
490 AtomicFile::rclose(mFileRef);
491 mFileRef = -1;
492 }
493 UnixError::throwMe(error);
494 }
495 }
496 else
497 {
498 bytesToRead -= bytesRead;
499 pos += bytesRead;
500 }
501 }
502 }
503
504
505
506 //
507 // Read the file starting at inOffset for inLength bytes into the buffer and return
508 // a pointer to it. On return outLength contain the actual number of bytes read, it
509 // will only ever be less than inLength if EOF was reached, and it will never be more
510 // than inLength.
511 //
512 const uint8 *
513 AtomicBufferedFile::read(off_t inOffset, off_t inLength, off_t &outLength)
514 {
515 if (mFileRef < 0)
516 {
517 secinfo("atomicfile", "read %s: file yet not opened, opening", mPath.c_str());
518 open();
519 }
520
521 off_t bytesLeft = inLength;
522 if (mBuffer)
523 {
524 secinfo("atomicfile", "%p free %s buffer %p", this, mPath.c_str(), mBuffer);
525 unloadBuffer();
526 }
527
528 loadBuffer();
529
530 secinfo("atomicfile", "%p allocated %s buffer %p size %qd", this, mPath.c_str(), mBuffer, bytesLeft);
531
532 off_t maxEnd = inOffset + inLength;
533 if (maxEnd > mLength)
534 {
535 maxEnd = mLength;
536 }
537
538 outLength = maxEnd - inOffset;
539
540 return mBuffer + inOffset;
541 }
542
543 void
544 AtomicBufferedFile::close()
545 {
546 if (mFileRef < 0)
547 {
548 secnotice("atomicfile", "close %s: already closed", mPath.c_str());
549 }
550 else
551 {
552 int result = AtomicFile::rclose(mFileRef);
553 mFileRef = -1;
554 if (result == -1)
555 {
556 int error = errno;
557 secnotice("atomicfile", "close %s: %s", mPath.c_str(), strerror(errno));
558 UnixError::throwMe(error);
559 }
560
561 secnotice("atomicfile", "%p closed %s", this, mPath.c_str());
562 }
563 }
564
565
566 //
567 // AtomicTempFile - A temporary file to write changes to.
568 //
569 AtomicTempFile::AtomicTempFile(AtomicFile &inFile, const RefPointer<AtomicLockedFile> &inLockedFile, mode_t mode) :
570 mFile(inFile),
571 mLockedFile(inLockedFile),
572 mCreating(true)
573 {
574 create(mode);
575 }
576
577 AtomicTempFile::AtomicTempFile(AtomicFile &inFile, const RefPointer<AtomicLockedFile> &inLockedFile) :
578 mFile(inFile),
579 mLockedFile(inLockedFile),
580 mCreating(false)
581 {
582 create(mFile.mode());
583 }
584
585 AtomicTempFile::~AtomicTempFile()
586 {
587 // rollback if we didn't commit yet.
588 if (mFileRef >= 0)
589 rollback();
590 }
591
592 //
593 // Open the file and return the length in bytes.
594 //
595 void
596 AtomicTempFile::create(mode_t mode)
597 {
598 // we now generate our temporary file name through sandbox API's.
599
600 // put the dir into a canonical form
601 string dir = mFile.dir();
602 int i = (int)dir.length() - 1;
603
604 // walk backwards until we get to a non / character
605 while (i >= 0 && dir[i] == '/')
606 {
607 i -= 1;
608 }
609
610 // point one beyond the string
611 i += 1;
612
613 const char* temp = _amkrtemp((dir.substr(0, i) + "/" + mFile.file()).c_str());
614 if (temp == NULL)
615 {
616 UnixError::throwMe(errno);
617 }
618
619 mPath = temp;
620 free((void*) temp);
621
622 const char *path = mPath.c_str();
623
624 mFileRef = AtomicFile::ropen(path, O_WRONLY|O_CREAT|O_TRUNC, mode);
625 if (mFileRef == -1)
626 {
627 int error = errno;
628 secnotice("atomicfile", "create %s: %s", path, strerror(error));
629
630 // Do the obvious error code translations here.
631 // @@@ Consider moving these up a level.
632 if (error == EACCES)
633 CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED);
634 else
635 UnixError::throwMe(error);
636 }
637
638 // If we aren't creating the inital file, make sure we preserve
639 // the mode of the old file regardless of the current umask.
640 // If we are creating the inital file we respect the users
641 // current umask.
642 if (!mCreating)
643 {
644 if (::fchmod(mFileRef, mode))
645 {
646 int error = errno;
647 secnotice("atomicfile", "fchmod %s: %s", path, strerror(error));
648 UnixError::throwMe(error);
649 }
650 }
651
652 secnotice("atomicfile", "%p created %s", this, path);
653 }
654
655 void
656 AtomicTempFile::write(AtomicFile::OffsetType inOffsetType, off_t inOffset, const uint32 inData)
657 {
658 uint32 aData = htonl(inData);
659 write(inOffsetType, inOffset, reinterpret_cast<uint8 *>(&aData), sizeof(aData));
660 }
661
662 void
663 AtomicTempFile::write(AtomicFile::OffsetType inOffsetType, off_t inOffset,
664 const uint32 *inData, uint32 inCount)
665 {
666 #ifdef HOST_LONG_IS_NETWORK_LONG
667 // Optimize this for the case where hl == nl
668 const uint32 *aBuffer = inData;
669 #else
670 auto_array<uint32> aBuffer(inCount);
671 for (uint32 i = 0; i < inCount; i++)
672 aBuffer.get()[i] = htonl(inData[i]);
673 #endif
674
675 write(inOffsetType, inOffset, reinterpret_cast<const uint8 *>(aBuffer.get()),
676 inCount * sizeof(*inData));
677 }
678
679 void
680 AtomicTempFile::write(AtomicFile::OffsetType inOffsetType, off_t inOffset, const uint8 *inData, size_t inLength)
681 {
682 off_t pos;
683 if (inOffsetType == AtomicFile::FromEnd)
684 {
685 pos = ::lseek(mFileRef, 0, SEEK_END);
686 if (pos == -1)
687 {
688 int error = errno;
689 secnotice("atomicfile", "lseek(%s, %qd): %s", mPath.c_str(), inOffset, strerror(error));
690 UnixError::throwMe(error);
691 }
692 }
693 else if (inOffsetType == AtomicFile::FromStart)
694 pos = inOffset;
695 else
696 CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR);
697
698 off_t bytesLeft = inLength;
699 const uint8 *ptr = inData;
700 while (bytesLeft)
701 {
702 size_t toWrite = bytesLeft > kAtomicFileMaxBlockSize ? kAtomicFileMaxBlockSize : size_t(bytesLeft);
703 ssize_t bytesWritten = ::pwrite(mFileRef, ptr, toWrite, pos);
704 if (bytesWritten == -1)
705 {
706 int error = errno;
707 if (error == EINTR)
708 {
709 // We got interrupted by a signal, so try again.
710 secnotice("atomicfile", "write %s: interrupted, retrying", mPath.c_str());
711 continue;
712 }
713
714 secnotice("atomicfile", "write %s: %s", mPath.c_str(), strerror(error));
715 UnixError::throwMe(error);
716 }
717
718 // Write returning 0 is bad mmkay.
719 if (bytesWritten == 0)
720 {
721 secnotice("atomicfile", "write %s: 0 bytes written", mPath.c_str());
722 CssmError::throwMe(CSSMERR_DL_INTERNAL_ERROR);
723 }
724
725 secdebug("atomicfile", "%p wrote %s %ld bytes from %p", this, mPath.c_str(), bytesWritten, ptr);
726
727 bytesLeft -= bytesWritten;
728 ptr += bytesWritten;
729 pos += bytesWritten;
730 }
731 }
732
733 void
734 AtomicTempFile::fsync()
735 {
736 if (mFileRef < 0)
737 {
738 secnotice("atomicfile", "fsync %s: already closed", mPath.c_str());
739 }
740 else
741 {
742 int result;
743 do
744 {
745 result = ::fsync(mFileRef);
746 } while (result && errno == EINTR);
747
748 if (result == -1)
749 {
750 int error = errno;
751 secnotice("atomicfile", "fsync %s: %s", mPath.c_str(), strerror(errno));
752 UnixError::throwMe(error);
753 }
754
755 secinfo("atomicfile", "%p fsynced %s", this, mPath.c_str());
756 }
757 }
758
759 void
760 AtomicTempFile::close()
761 {
762 if (mFileRef < 0)
763 {
764 secnotice("atomicfile", "close %s: already closed", mPath.c_str());
765 }
766 else
767 {
768 int result = AtomicFile::rclose(mFileRef);
769 mFileRef = -1;
770 if (result == -1)
771 {
772 int error = errno;
773 secnotice("atomicfile", "close %s: %s", mPath.c_str(), strerror(errno));
774 UnixError::throwMe(error);
775 }
776
777 secnotice("atomicfile", "%p closed %s", this, mPath.c_str());
778 }
779 }
780
781 // Commit the current create or write and close the write file. Note that a throw during the commit does an automatic rollback.
782 void
783 AtomicTempFile::commit()
784 {
785 try
786 {
787 fsync();
788 close();
789 const char *oldPath = mPath.c_str();
790 const char *newPath = mFile.path().c_str();
791
792 // <rdar://problem/6991037>
793 // Copy the security parameters of one file to another
794 // Adding this to guard against setuid utilities that are re-writing a user's keychain. We don't want to leave them root-owned.
795 // In order to not break backward compatability we'll make a best effort, but continue if these efforts fail.
796 //
797 // To clear something up - newPath is the name the keychain will become - which is the name of the file being replaced
798 // oldPath is the "temp filename".
799
800 copyfile_state_t s;
801 s = copyfile_state_alloc();
802
803 if(copyfile(newPath, oldPath, s, COPYFILE_SECURITY | COPYFILE_NOFOLLOW) == -1) // Not fatal
804 secnotice("atomicfile", "copyfile (%s, %s): %s", oldPath, newPath, strerror(errno));
805
806 copyfile_state_free(s);
807 // END <rdar://problem/6991037>
808
809 ::utimes(oldPath, NULL);
810
811 if (::rename(oldPath, newPath) == -1)
812 {
813 int error = errno;
814 secnotice("atomicfile", "rename (%s, %s): %s", oldPath, newPath, strerror(errno));
815 UnixError::throwMe(error);
816 }
817
818 secnotice("atomicfile", "%p commited %s to %s", this, oldPath, newPath);
819
820 // Unlock the lockfile
821 mLockedFile = NULL;
822 }
823 catch (...)
824 {
825 rollback();
826 throw;
827 }
828 }
829
830 // Rollback the current create or write (happens automatically if commit() isn't called before the destructor is.
831 void
832 AtomicTempFile::rollback() throw()
833 {
834 if (mFileRef >= 0)
835 {
836 AtomicFile::rclose(mFileRef);
837 mFileRef = -1;
838 }
839
840 // @@@ Log errors if this fails.
841 const char *path = mPath.c_str();
842 if (::unlink(path) == -1)
843 {
844 secnotice("atomicfile", "unlink %s: %s", path, strerror(errno));
845 // rollback can't throw
846 }
847
848 // @@@ Think about this. Depending on how we do locking we might not need this.
849 if (mCreating)
850 {
851 const char *path = mFile.path().c_str();
852 if (::unlink(path) == -1)
853 {
854 secnotice("atomicfile", "unlink %s: %s", path, strerror(errno));
855 // rollback can't throw
856 }
857 }
858 }
859
860
861 //
862 // An advisory write lock for inFile.
863 //
864 FileLocker::~FileLocker()
865 {
866 }
867
868
869
870 LocalFileLocker::LocalFileLocker(AtomicFile &inFile) :
871 mPath(inFile.lockFileName())
872 {
873 }
874
875
876 LocalFileLocker::~LocalFileLocker()
877 {
878 }
879
880
881
882 #ifndef NDEBUG
883 static double GetTime()
884 {
885 struct timeval t;
886 gettimeofday(&t, NULL);
887 return ((double) t.tv_sec) + ((double) t.tv_usec) / 1000000.0;
888 }
889 #endif
890
891
892
893 void
894 LocalFileLocker::lock(mode_t mode)
895 {
896 struct stat st;
897
898 do
899 {
900 // if the lock file doesn't exist, create it
901 mLockFile = open(mPath.c_str(), O_RDONLY | O_CREAT, mode);
902
903 // if we can't open or create the file, something is wrong
904 if (mLockFile == -1)
905 {
906 UnixError::throwMe(errno);
907 }
908
909 // try to get exclusive access to the file
910 IFDEBUG(double startTime = GetTime());
911 int result = flock(mLockFile, LOCK_EX);
912 IFDEBUG(double endTime = GetTime());
913
914 IFDEBUG(secnotice("atomictime", "Waited %.4f milliseconds for file lock", (endTime - startTime) * 1000.0));
915
916 // errors at this point are bad
917 if (result == -1)
918 {
919 UnixError::throwMe(errno);
920 }
921
922 // check and see if the file we have access to still exists. If not, another file shared our file lock
923 // due to a hash collision and has thrown our lock away -- that, or a user blew the lock file away himself.
924
925 result = fstat(mLockFile, &st);
926
927 // errors at this point are bad
928 if (result == -1)
929 {
930 UnixError::throwMe(errno);
931 }
932
933 if (st.st_nlink == 0) // we've been unlinked!
934 {
935 close(mLockFile);
936 }
937 } while (st.st_nlink == 0);
938 }
939
940
941 void
942 LocalFileLocker::unlock()
943 {
944 flock(mLockFile, LOCK_UN);
945 close(mLockFile);
946 }
947
948
949
950 NetworkFileLocker::NetworkFileLocker(AtomicFile &inFile) :
951 mDir(inFile.dir()),
952 mPath(inFile.dir() + "lck~" + inFile.file())
953 {
954 }
955
956 NetworkFileLocker::~NetworkFileLocker()
957 {
958 }
959
960 std::string
961 NetworkFileLocker::unique(mode_t mode)
962 {
963 static const int randomPart = 16;
964 DevRandomGenerator randomGen;
965 std::string::size_type dirSize = mDir.size();
966 std::string fullname(dirSize + randomPart + 2, '\0');
967 fullname.replace(0, dirSize, mDir);
968 fullname[dirSize] = '~'; /* UNIQ_PREFIX */
969 char buf[randomPart];
970 struct stat filebuf;
971 int result, fd = -1;
972
973 for (int retries = 0; retries < 10; ++retries)
974 {
975 /* Make a random filename. */
976 randomGen.random(buf, randomPart);
977 for (int ix = 0; ix < randomPart; ++ix)
978 {
979 char ch = buf[ix] & 0x3f;
980 fullname[ix + dirSize + 1] = ch +
981 ( ch < 26 ? 'A'
982 : ch < 26 + 26 ? 'a' - 26
983 : ch < 26 + 26 + 10 ? '0' - 26 - 26
984 : ch == 26 + 26 + 10 ? '-' - 26 - 26 - 10
985 : '_' - 26 - 26 - 11);
986 }
987
988 result = lstat(fullname.c_str(), &filebuf);
989 if (result && errno == ENAMETOOLONG)
990 {
991 do
992 fullname.erase(fullname.end() - 1);
993 while((result = lstat(fullname.c_str(), &filebuf)) && errno == ENAMETOOLONG && fullname.size() > dirSize + 8);
994 } /* either it stopped being a problem or we ran out of filename */
995
996 if (result && errno == ENOENT)
997 {
998 fd = AtomicFile::ropen(fullname.c_str(), O_WRONLY|O_CREAT|O_EXCL, mode);
999 if (fd >= 0 || errno != EEXIST)
1000 break;
1001 }
1002 }
1003
1004 if (fd < 0)
1005 {
1006 int error = errno;
1007 ::syslog(LOG_ERR, "Couldn't create temp file %s: %s", fullname.c_str(), strerror(error));
1008 secnotice("atomicfile", "Couldn't create temp file %s: %s", fullname.c_str(), strerror(error));
1009 UnixError::throwMe(error);
1010 }
1011
1012 /* @@@ Check for EINTR. */
1013 write(fd, "0", 1); /* pid 0, `works' across networks */
1014
1015 AtomicFile::rclose(fd);
1016
1017 return fullname;
1018 }
1019
1020 /* Return 0 on success and 1 on failure if st is set to the result of stat(old) and -1 on failure if the stat(old) failed. */
1021 int
1022 NetworkFileLocker::rlink(const char *const old, const char *const newn, struct stat &sto)
1023 {
1024 int result = ::link(old,newn);
1025 if (result)
1026 {
1027 int serrno = errno;
1028 if (::lstat(old, &sto) == 0)
1029 {
1030 struct stat stn;
1031 if (::lstat(newn, &stn) == 0
1032 && sto.st_dev == stn.st_dev
1033 && sto.st_ino == stn.st_ino
1034 && sto.st_uid == stn.st_uid
1035 && sto.st_gid == stn.st_gid
1036 && !S_ISLNK(sto.st_mode))
1037 {
1038 /* Link failed but files are the same so the link really went ok. */
1039 return 0;
1040 }
1041 else
1042 result = 1;
1043 }
1044 errno = serrno; /* Restore errno from link() */
1045 }
1046
1047 return result;
1048 }
1049
1050 /* NFS-resistant rename()
1051 * rename with fallback for systems that don't support it
1052 * Note that this does not preserve the contents of the file. */
1053 int
1054 NetworkFileLocker::myrename(const char *const old, const char *const newn)
1055 {
1056 struct stat stbuf;
1057 int fd = -1;
1058 int ret;
1059
1060 /* Try a real hardlink */
1061 ret = rlink(old, newn, stbuf);
1062 if (ret > 0)
1063 {
1064 if (stbuf.st_nlink < 2 && (errno == EXDEV || errno == ENOTSUP))
1065 {
1066 /* Hard link failed so just create a new file with O_EXCL instead. */
1067 fd = AtomicFile::ropen(newn, O_WRONLY|O_CREAT|O_EXCL, stbuf.st_mode);
1068 if (fd >= 0)
1069 ret = 0;
1070 }
1071 }
1072
1073 /* We want the errno from the link or the ropen, not that of the unlink. */
1074 int serrno = errno;
1075
1076 /* Unlink the temp file. */
1077 ::unlink(old);
1078 if (fd > 0)
1079 AtomicFile::rclose(fd);
1080
1081 errno = serrno;
1082 return ret;
1083 }
1084
1085 int
1086 NetworkFileLocker::xcreat(const char *const name, mode_t mode, time_t &tim)
1087 {
1088 std::string uniqueName = unique(mode);
1089 const char *uniquePath = uniqueName.c_str();
1090 struct stat stbuf; /* return the filesystem time to the caller */
1091 stat(uniquePath, &stbuf);
1092 tim = stbuf.st_mtime;
1093 return myrename(uniquePath, name);
1094 }
1095
1096 void
1097 NetworkFileLocker::lock(mode_t mode)
1098 {
1099 const char *path = mPath.c_str();
1100 bool triedforce = false;
1101 struct stat stbuf;
1102 time_t t, locktimeout = 1024; /* DEFlocktimeout, 17 minutes. */
1103 bool doSyslog = false;
1104 bool failed = false;
1105 int retries = 0;
1106
1107 while (!failed)
1108 {
1109 /* Don't syslog first time through. */
1110 if (doSyslog)
1111 ::syslog(LOG_NOTICE, "Locking %s", path);
1112 else
1113 doSyslog = true;
1114
1115 secinfo("atomicfile", "Locking %s", path); /* in order to cater for clock skew: get */
1116 if (!xcreat(path, mode, t)) /* time t from the filesystem */
1117 {
1118 /* lock acquired, hurray! */
1119 break;
1120 }
1121 switch(errno)
1122 {
1123 case EEXIST: /* check if it's time for a lock override */
1124 if (!lstat(path, &stbuf) && stbuf.st_size <= 16 /* MAX_locksize */ && locktimeout
1125 && !lstat(path, &stbuf) && locktimeout < t - stbuf.st_mtime)
1126 /* stat() till unlink() should be atomic, but can't guarantee that. */
1127 {
1128 if (triedforce)
1129 {
1130 /* Already tried, force lock override, not trying again */
1131 failed = true;
1132 break;
1133 }
1134 else if (S_ISDIR(stbuf.st_mode) || ::unlink(path))
1135 {
1136 triedforce=true;
1137 ::syslog(LOG_ERR, "Forced unlock denied on %s", path);
1138 secnotice("atomicfile", "Forced unlock denied on %s", path);
1139 }
1140 else
1141 {
1142 ::syslog(LOG_ERR, "Forcing lock on %s", path);
1143 secnotice("atomicfile", "Forcing lock on %s", path);
1144 sleep(16 /* DEFsuspend */);
1145 break;
1146 }
1147 }
1148 else
1149 triedforce = false; /* legitimate iteration, clear flag */
1150
1151 /* Reset retry counter. */
1152 retries = 0;
1153 usleep(250000);
1154 break;
1155
1156 case ENOSPC: /* no space left, treat it as a transient */
1157 #ifdef EDQUOT /* NFS failure */
1158 case EDQUOT: /* maybe it was a short term shortage? */
1159 #endif
1160 case ENOENT:
1161 case ENOTDIR:
1162 case EIO:
1163 /*case EACCES:*/
1164 if(++retries < (256 + 1)) /* nfsTRY number of times+1 to ignore spurious NFS errors */
1165 usleep(250000);
1166 else
1167 failed = true;
1168 break;
1169
1170 #ifdef ENAMETOOLONG
1171 case ENAMETOOLONG: /* Filename is too long, shorten and retry */
1172 if (mPath.size() > mDir.size() + 8)
1173 {
1174 secnotice("atomicfile", "Truncating %s and retrying lock", path);
1175 mPath.erase(mPath.end() - 1);
1176 path = mPath.c_str();
1177 /* Reset retry counter. */
1178 retries = 0;
1179 break;
1180 }
1181 /* DROPTHROUGH */
1182 #endif
1183 default:
1184 failed = true;
1185 break;
1186 }
1187 }
1188
1189 if (failed)
1190 {
1191 int error = errno;
1192 ::syslog(LOG_ERR, "Lock failure on %s: %s", path, strerror(error));
1193 secnotice("atomicfile", "Lock failure on %s: %s", path, strerror(error));
1194 UnixError::throwMe(error);
1195 }
1196 }
1197
1198 void
1199 NetworkFileLocker::unlock()
1200 {
1201 const char *path = mPath.c_str();
1202 if (::unlink(path) == -1)
1203 {
1204 secnotice("atomicfile", "unlink %s: %s", path, strerror(errno));
1205 // unlock can't throw
1206 }
1207 }
1208
1209
1210
1211 AtomicLockedFile::AtomicLockedFile(AtomicFile &inFile)
1212 {
1213 if (inFile.isOnLocalFileSystem())
1214 {
1215 mFileLocker = new LocalFileLocker(inFile);
1216 }
1217 else
1218 {
1219 mFileLocker = new NetworkFileLocker(inFile);
1220 }
1221
1222 lock();
1223 }
1224
1225
1226
1227 AtomicLockedFile::~AtomicLockedFile()
1228 {
1229 unlock();
1230 delete mFileLocker;
1231 }
1232
1233
1234
1235 void
1236 AtomicLockedFile::lock(mode_t mode)
1237 {
1238 mFileLocker->lock(mode);
1239 }
1240
1241
1242
1243 void AtomicLockedFile::unlock() throw()
1244 {
1245 mFileLocker->unlock();
1246 }
1247
1248
1249
1250 #undef kAtomicFileMaxBlockSize