]> git.saurik.com Git - apple/security.git/blob - OSX/libsecurity_filedb/lib/AtomicFile.cpp
Security-57740.51.3.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 // Acquire 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 // Acquire 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 secinfo("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 mBuffer = NULL;
463 }
464 }
465
466 //
467 // Load the contents of the file into memory.
468 void
469 AtomicBufferedFile::loadBuffer()
470 {
471 // make a buffer big enough to hold the entire file
472 mBuffer = new uint8[(size_t) mLength];
473 if(lseek(mFileRef, 0, SEEK_SET) < 0) {
474 int error = errno;
475 secinfo("atomicfile", "lseek(%s, BEGINNING): %s", mPath.c_str(), strerror(error));
476 UnixError::throwMe(error);
477 }
478 ssize_t pos = 0;
479
480 ssize_t bytesToRead = (ssize_t)mLength;
481 while (bytesToRead > 0)
482 {
483 ssize_t bytesRead = ::read(mFileRef, mBuffer + pos, bytesToRead);
484 if (bytesRead == -1)
485 {
486 if (errno != EINTR)
487 {
488 int error = errno;
489 secinfo("atomicfile", "read(%s, %zd): %s", mPath.c_str(), bytesToRead, strerror(error));
490 AtomicFile::rclose(mFileRef);
491 mFileRef = -1;
492 UnixError::throwMe(error);
493 }
494 }
495 else
496 {
497 bytesToRead -= bytesRead;
498 pos += bytesRead;
499 }
500 }
501 }
502
503
504
505 //
506 // Read the file starting at inOffset for inLength bytes into the buffer and return
507 // a pointer to it. On return outLength contain the actual number of bytes read, it
508 // will only ever be less than inLength if EOF was reached, and it will never be more
509 // than inLength.
510 //
511 const uint8 *
512 AtomicBufferedFile::read(off_t inOffset, off_t inLength, off_t &outLength)
513 {
514 if (mFileRef < 0)
515 {
516 secinfo("atomicfile", "read %s: file yet not opened, opening", mPath.c_str());
517 open();
518 }
519
520 off_t bytesLeft = inLength;
521 if (mBuffer)
522 {
523 secinfo("atomicfile", "%p free %s buffer %p", this, mPath.c_str(), mBuffer);
524 unloadBuffer();
525 }
526
527 loadBuffer();
528
529 secinfo("atomicfile", "%p allocated %s buffer %p size %qd", this, mPath.c_str(), mBuffer, bytesLeft);
530
531 off_t maxEnd = inOffset + inLength;
532 if (maxEnd > mLength)
533 {
534 maxEnd = mLength;
535 }
536
537 outLength = maxEnd - inOffset;
538
539 return mBuffer + inOffset;
540 }
541
542 void
543 AtomicBufferedFile::close()
544 {
545 if (mFileRef < 0)
546 {
547 secnotice("atomicfile", "close %s: already closed", mPath.c_str());
548 }
549 else
550 {
551 int result = AtomicFile::rclose(mFileRef);
552 mFileRef = -1;
553 if (result == -1)
554 {
555 int error = errno;
556 secnotice("atomicfile", "close %s: %s", mPath.c_str(), strerror(errno));
557 UnixError::throwMe(error);
558 }
559
560 secinfo("atomicfile", "%p closed %s", this, mPath.c_str());
561 }
562 }
563
564
565 //
566 // AtomicTempFile - A temporary file to write changes to.
567 //
568 AtomicTempFile::AtomicTempFile(AtomicFile &inFile, const RefPointer<AtomicLockedFile> &inLockedFile, mode_t mode) :
569 mFile(inFile),
570 mLockedFile(inLockedFile),
571 mCreating(true)
572 {
573 create(mode);
574 }
575
576 AtomicTempFile::AtomicTempFile(AtomicFile &inFile, const RefPointer<AtomicLockedFile> &inLockedFile) :
577 mFile(inFile),
578 mLockedFile(inLockedFile),
579 mCreating(false)
580 {
581 create(mFile.mode());
582 }
583
584 AtomicTempFile::~AtomicTempFile()
585 {
586 // rollback if we didn't commit yet.
587 if (mFileRef >= 0)
588 rollback();
589 }
590
591 //
592 // Open the file and return the length in bytes.
593 //
594 void
595 AtomicTempFile::create(mode_t mode)
596 {
597 // we now generate our temporary file name through sandbox API's.
598
599 // put the dir into a canonical form
600 string dir = mFile.dir();
601 int i = (int)dir.length() - 1;
602
603 // walk backwards until we get to a non / character
604 while (i >= 0 && dir[i] == '/')
605 {
606 i -= 1;
607 }
608
609 // point one beyond the string
610 i += 1;
611
612 const char* temp = _amkrtemp((dir.substr(0, i) + "/" + mFile.file()).c_str());
613 if (temp == NULL)
614 {
615 UnixError::throwMe(errno);
616 }
617
618 mPath = temp;
619 free((void*) temp);
620
621 const char *path = mPath.c_str();
622
623 mFileRef = AtomicFile::ropen(path, O_WRONLY|O_CREAT|O_TRUNC, mode);
624 if (mFileRef == -1)
625 {
626 int error = errno;
627 secnotice("atomicfile", "create %s: %s", path, strerror(error));
628
629 // Do the obvious error code translations here.
630 // @@@ Consider moving these up a level.
631 if (error == EACCES)
632 CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED);
633 else
634 UnixError::throwMe(error);
635 }
636
637 // If we aren't creating the inital file, make sure we preserve
638 // the mode of the old file regardless of the current umask.
639 // If we are creating the inital file we respect the users
640 // current umask.
641 if (!mCreating)
642 {
643 if (::fchmod(mFileRef, mode))
644 {
645 int error = errno;
646 secnotice("atomicfile", "fchmod %s: %s", path, strerror(error));
647 UnixError::throwMe(error);
648 }
649 }
650
651 secnotice("atomicfile", "%p created %s", this, path);
652 }
653
654 void
655 AtomicTempFile::write(AtomicFile::OffsetType inOffsetType, off_t inOffset, const uint32 inData)
656 {
657 uint32 aData = htonl(inData);
658 write(inOffsetType, inOffset, reinterpret_cast<uint8 *>(&aData), sizeof(aData));
659 }
660
661 void
662 AtomicTempFile::write(AtomicFile::OffsetType inOffsetType, off_t inOffset,
663 const uint32 *inData, uint32 inCount)
664 {
665 #ifdef HOST_LONG_IS_NETWORK_LONG
666 // Optimize this for the case where hl == nl
667 const uint32 *aBuffer = inData;
668 #else
669 auto_array<uint32> aBuffer(inCount);
670 for (uint32 i = 0; i < inCount; i++)
671 aBuffer.get()[i] = htonl(inData[i]);
672 #endif
673
674 write(inOffsetType, inOffset, reinterpret_cast<const uint8 *>(aBuffer.get()),
675 inCount * sizeof(*inData));
676 }
677
678 void
679 AtomicTempFile::write(AtomicFile::OffsetType inOffsetType, off_t inOffset, const uint8 *inData, size_t inLength)
680 {
681 off_t pos;
682 if (inOffsetType == AtomicFile::FromEnd)
683 {
684 pos = ::lseek(mFileRef, 0, SEEK_END);
685 if (pos == -1)
686 {
687 int error = errno;
688 secnotice("atomicfile", "lseek(%s, %qd): %s", mPath.c_str(), inOffset, strerror(error));
689 UnixError::throwMe(error);
690 }
691 }
692 else if (inOffsetType == AtomicFile::FromStart)
693 pos = inOffset;
694 else
695 CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR);
696
697 off_t bytesLeft = inLength;
698 const uint8 *ptr = inData;
699 while (bytesLeft)
700 {
701 size_t toWrite = bytesLeft > kAtomicFileMaxBlockSize ? kAtomicFileMaxBlockSize : size_t(bytesLeft);
702 ssize_t bytesWritten = ::pwrite(mFileRef, ptr, toWrite, pos);
703 if (bytesWritten == -1)
704 {
705 int error = errno;
706 if (error == EINTR)
707 {
708 // We got interrupted by a signal, so try again.
709 secnotice("atomicfile", "write %s: interrupted, retrying", mPath.c_str());
710 continue;
711 }
712
713 secnotice("atomicfile", "write %s: %s", mPath.c_str(), strerror(error));
714 UnixError::throwMe(error);
715 }
716
717 // Write returning 0 is bad mmkay.
718 if (bytesWritten == 0)
719 {
720 secnotice("atomicfile", "write %s: 0 bytes written", mPath.c_str());
721 CssmError::throwMe(CSSMERR_DL_INTERNAL_ERROR);
722 }
723
724 secdebug("atomicfile", "%p wrote %s %ld bytes from %p", this, mPath.c_str(), bytesWritten, ptr);
725
726 bytesLeft -= bytesWritten;
727 ptr += bytesWritten;
728 pos += bytesWritten;
729 }
730 }
731
732 void
733 AtomicTempFile::fsync()
734 {
735 if (mFileRef < 0)
736 {
737 secnotice("atomicfile", "fsync %s: already closed", mPath.c_str());
738 }
739 else
740 {
741 int result;
742 do
743 {
744 result = ::fsync(mFileRef);
745 } while (result && errno == EINTR);
746
747 if (result == -1)
748 {
749 int error = errno;
750 secnotice("atomicfile", "fsync %s: %s", mPath.c_str(), strerror(errno));
751 UnixError::throwMe(error);
752 }
753
754 secinfo("atomicfile", "%p fsynced %s", this, mPath.c_str());
755 }
756 }
757
758 void
759 AtomicTempFile::close()
760 {
761 if (mFileRef < 0)
762 {
763 secnotice("atomicfile", "close %s: already closed", mPath.c_str());
764 }
765 else
766 {
767 int result = AtomicFile::rclose(mFileRef);
768 mFileRef = -1;
769 if (result == -1)
770 {
771 int error = errno;
772 secnotice("atomicfile", "close %s: %s", mPath.c_str(), strerror(errno));
773 UnixError::throwMe(error);
774 }
775
776 secinfo("atomicfile", "%p closed %s", this, mPath.c_str());
777 }
778 }
779
780 // Commit the current create or write and close the write file. Note that a throw during the commit does an automatic rollback.
781 void
782 AtomicTempFile::commit()
783 {
784 try
785 {
786 fsync();
787 close();
788 const char *oldPath = mPath.c_str();
789 const char *newPath = mFile.path().c_str();
790
791 // <rdar://problem/6991037>
792 // Copy the security parameters of one file to another
793 // Adding this to guard against setuid utilities that are re-writing a user's keychain. We don't want to leave them root-owned.
794 // In order to not break backward compatability we'll make a best effort, but continue if these efforts fail.
795 //
796 // To clear something up - newPath is the name the keychain will become - which is the name of the file being replaced
797 // oldPath is the "temp filename".
798
799 copyfile_state_t s;
800 s = copyfile_state_alloc();
801
802 if(copyfile(newPath, oldPath, s, COPYFILE_SECURITY | COPYFILE_NOFOLLOW) == -1) // Not fatal
803 secnotice("atomicfile", "copyfile (%s, %s): %s", oldPath, newPath, strerror(errno));
804
805 copyfile_state_free(s);
806 // END <rdar://problem/6991037>
807
808 ::utimes(oldPath, NULL);
809
810 if (::rename(oldPath, newPath) == -1)
811 {
812 int error = errno;
813 secnotice("atomicfile", "rename (%s, %s): %s", oldPath, newPath, strerror(errno));
814 UnixError::throwMe(error);
815 }
816
817 secnotice("atomicfile", "%p commited %s to %s", this, oldPath, newPath);
818
819 // Unlock the lockfile
820 mLockedFile = NULL;
821 }
822 catch (...)
823 {
824 rollback();
825 throw;
826 }
827 }
828
829 // Rollback the current create or write (happens automatically if commit() isn't called before the destructor is.
830 void
831 AtomicTempFile::rollback() throw()
832 {
833 if (mFileRef >= 0)
834 {
835 AtomicFile::rclose(mFileRef);
836 mFileRef = -1;
837 }
838
839 // @@@ Log errors if this fails.
840 const char *path = mPath.c_str();
841 if (::unlink(path) == -1)
842 {
843 secnotice("atomicfile", "unlink %s: %s", path, strerror(errno));
844 // rollback can't throw
845 }
846
847 // @@@ Think about this. Depending on how we do locking we might not need this.
848 if (mCreating)
849 {
850 const char *path = mFile.path().c_str();
851 if (::unlink(path) == -1)
852 {
853 secnotice("atomicfile", "unlink %s: %s", path, strerror(errno));
854 // rollback can't throw
855 }
856 }
857 }
858
859
860 //
861 // An advisory write lock for inFile.
862 //
863 FileLocker::~FileLocker()
864 {
865 }
866
867
868
869 LocalFileLocker::LocalFileLocker(AtomicFile &inFile) :
870 mPath(inFile.lockFileName())
871 {
872 }
873
874
875 LocalFileLocker::~LocalFileLocker()
876 {
877 }
878
879
880
881 #ifndef NDEBUG
882 static double GetTime()
883 {
884 struct timeval t;
885 gettimeofday(&t, NULL);
886 return ((double) t.tv_sec) + ((double) t.tv_usec) / 1000000.0;
887 }
888 #endif
889
890
891
892 void
893 LocalFileLocker::lock(mode_t mode)
894 {
895 struct stat st;
896
897 do
898 {
899 // if the lock file doesn't exist, create it
900 mLockFile = open(mPath.c_str(), O_RDONLY | O_CREAT, mode);
901
902 // if we can't open or create the file, something is wrong
903 if (mLockFile == -1)
904 {
905 UnixError::throwMe(errno);
906 }
907
908 // try to get exclusive access to the file
909 IFDEBUG(double startTime = GetTime());
910 int result = flock(mLockFile, LOCK_EX);
911 IFDEBUG(double endTime = GetTime());
912
913 IFDEBUG(secnotice("atomictime", "Waited %.4f milliseconds for file lock", (endTime - startTime) * 1000.0));
914
915 // errors at this point are bad
916 if (result == -1)
917 {
918 UnixError::throwMe(errno);
919 }
920
921 // check and see if the file we have access to still exists. If not, another file shared our file lock
922 // due to a hash collision and has thrown our lock away -- that, or a user blew the lock file away himself.
923
924 result = fstat(mLockFile, &st);
925
926 // errors at this point are bad
927 if (result == -1)
928 {
929 UnixError::throwMe(errno);
930 }
931
932 if (st.st_nlink == 0) // we've been unlinked!
933 {
934 close(mLockFile);
935 }
936 } while (st.st_nlink == 0);
937 }
938
939
940 void
941 LocalFileLocker::unlock()
942 {
943 flock(mLockFile, LOCK_UN);
944 close(mLockFile);
945 }
946
947
948
949 NetworkFileLocker::NetworkFileLocker(AtomicFile &inFile) :
950 mDir(inFile.dir()),
951 mPath(inFile.dir() + "lck~" + inFile.file())
952 {
953 }
954
955 NetworkFileLocker::~NetworkFileLocker()
956 {
957 }
958
959 std::string
960 NetworkFileLocker::unique(mode_t mode)
961 {
962 static const int randomPart = 16;
963 DevRandomGenerator randomGen;
964 std::string::size_type dirSize = mDir.size();
965 std::string fullname(dirSize + randomPart + 2, '\0');
966 fullname.replace(0, dirSize, mDir);
967 fullname[dirSize] = '~'; /* UNIQ_PREFIX */
968 char buf[randomPart];
969 struct stat filebuf;
970 int result, fd = -1;
971
972 for (int retries = 0; retries < 10; ++retries)
973 {
974 /* Make a random filename. */
975 randomGen.random(buf, randomPart);
976 for (int ix = 0; ix < randomPart; ++ix)
977 {
978 char ch = buf[ix] & 0x3f;
979 fullname[ix + dirSize + 1] = ch +
980 ( ch < 26 ? 'A'
981 : ch < 26 + 26 ? 'a' - 26
982 : ch < 26 + 26 + 10 ? '0' - 26 - 26
983 : ch == 26 + 26 + 10 ? '-' - 26 - 26 - 10
984 : '_' - 26 - 26 - 11);
985 }
986
987 result = lstat(fullname.c_str(), &filebuf);
988 if (result && errno == ENAMETOOLONG)
989 {
990 do
991 fullname.erase(fullname.end() - 1);
992 while((result = lstat(fullname.c_str(), &filebuf)) && errno == ENAMETOOLONG && fullname.size() > dirSize + 8);
993 } /* either it stopped being a problem or we ran out of filename */
994
995 if (result && errno == ENOENT)
996 {
997 fd = AtomicFile::ropen(fullname.c_str(), O_WRONLY|O_CREAT|O_EXCL, mode);
998 if (fd >= 0 || errno != EEXIST)
999 break;
1000 }
1001 }
1002
1003 if (fd < 0)
1004 {
1005 int error = errno;
1006 ::syslog(LOG_ERR, "Couldn't create temp file %s: %s", fullname.c_str(), strerror(error));
1007 secnotice("atomicfile", "Couldn't create temp file %s: %s", fullname.c_str(), strerror(error));
1008 UnixError::throwMe(error);
1009 }
1010
1011 /* @@@ Check for EINTR. */
1012 write(fd, "0", 1); /* pid 0, `works' across networks */
1013
1014 AtomicFile::rclose(fd);
1015
1016 return fullname;
1017 }
1018
1019 /* 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. */
1020 int
1021 NetworkFileLocker::rlink(const char *const old, const char *const newn, struct stat &sto)
1022 {
1023 int result = ::link(old,newn);
1024 if (result)
1025 {
1026 int serrno = errno;
1027 if (::lstat(old, &sto) == 0)
1028 {
1029 struct stat stn;
1030 if (::lstat(newn, &stn) == 0
1031 && sto.st_dev == stn.st_dev
1032 && sto.st_ino == stn.st_ino
1033 && sto.st_uid == stn.st_uid
1034 && sto.st_gid == stn.st_gid
1035 && !S_ISLNK(sto.st_mode))
1036 {
1037 /* Link failed but files are the same so the link really went ok. */
1038 return 0;
1039 }
1040 else
1041 result = 1;
1042 }
1043 errno = serrno; /* Restore errno from link() */
1044 }
1045
1046 return result;
1047 }
1048
1049 /* NFS-resistant rename()
1050 * rename with fallback for systems that don't support it
1051 * Note that this does not preserve the contents of the file. */
1052 int
1053 NetworkFileLocker::myrename(const char *const old, const char *const newn)
1054 {
1055 struct stat stbuf;
1056 int fd = -1;
1057 int ret;
1058
1059 /* Try a real hardlink */
1060 ret = rlink(old, newn, stbuf);
1061 if (ret > 0)
1062 {
1063 if (stbuf.st_nlink < 2 && (errno == EXDEV || errno == ENOTSUP))
1064 {
1065 /* Hard link failed so just create a new file with O_EXCL instead. */
1066 fd = AtomicFile::ropen(newn, O_WRONLY|O_CREAT|O_EXCL, stbuf.st_mode);
1067 if (fd >= 0)
1068 ret = 0;
1069 }
1070 }
1071
1072 /* We want the errno from the link or the ropen, not that of the unlink. */
1073 int serrno = errno;
1074
1075 /* Unlink the temp file. */
1076 ::unlink(old);
1077 if (fd > 0)
1078 AtomicFile::rclose(fd);
1079
1080 errno = serrno;
1081 return ret;
1082 }
1083
1084 int
1085 NetworkFileLocker::xcreat(const char *const name, mode_t mode, time_t &tim)
1086 {
1087 std::string uniqueName = unique(mode);
1088 const char *uniquePath = uniqueName.c_str();
1089 struct stat stbuf; /* return the filesystem time to the caller */
1090 stat(uniquePath, &stbuf);
1091 tim = stbuf.st_mtime;
1092 return myrename(uniquePath, name);
1093 }
1094
1095 void
1096 NetworkFileLocker::lock(mode_t mode)
1097 {
1098 const char *path = mPath.c_str();
1099 bool triedforce = false;
1100 struct stat stbuf;
1101 time_t t, locktimeout = 1024; /* DEFlocktimeout, 17 minutes. */
1102 bool doSyslog = false;
1103 bool failed = false;
1104 int retries = 0;
1105
1106 while (!failed)
1107 {
1108 /* Don't syslog first time through. */
1109 if (doSyslog)
1110 ::syslog(LOG_NOTICE, "Locking %s", path);
1111 else
1112 doSyslog = true;
1113
1114 secinfo("atomicfile", "Locking %s", path); /* in order to cater for clock skew: get */
1115 if (!xcreat(path, mode, t)) /* time t from the filesystem */
1116 {
1117 /* lock acquired, hurray! */
1118 break;
1119 }
1120 switch(errno)
1121 {
1122 case EEXIST: /* check if it's time for a lock override */
1123 if (!lstat(path, &stbuf) && stbuf.st_size <= 16 /* MAX_locksize */ && locktimeout
1124 && !lstat(path, &stbuf) && locktimeout < t - stbuf.st_mtime)
1125 /* stat() till unlink() should be atomic, but can't guarantee that. */
1126 {
1127 if (triedforce)
1128 {
1129 /* Already tried, force lock override, not trying again */
1130 failed = true;
1131 break;
1132 }
1133 else if (S_ISDIR(stbuf.st_mode) || ::unlink(path))
1134 {
1135 triedforce=true;
1136 ::syslog(LOG_ERR, "Forced unlock denied on %s", path);
1137 secnotice("atomicfile", "Forced unlock denied on %s", path);
1138 }
1139 else
1140 {
1141 ::syslog(LOG_ERR, "Forcing lock on %s", path);
1142 secnotice("atomicfile", "Forcing lock on %s", path);
1143 sleep(16 /* DEFsuspend */);
1144 break;
1145 }
1146 }
1147 else
1148 triedforce = false; /* legitimate iteration, clear flag */
1149
1150 /* Reset retry counter. */
1151 retries = 0;
1152 usleep(250000);
1153 break;
1154
1155 case ENOSPC: /* no space left, treat it as a transient */
1156 #ifdef EDQUOT /* NFS failure */
1157 case EDQUOT: /* maybe it was a short term shortage? */
1158 #endif
1159 case ENOENT:
1160 case ENOTDIR:
1161 case EIO:
1162 /*case EACCES:*/
1163 if(++retries < (256 + 1)) /* nfsTRY number of times+1 to ignore spurious NFS errors */
1164 usleep(250000);
1165 else
1166 failed = true;
1167 break;
1168
1169 #ifdef ENAMETOOLONG
1170 case ENAMETOOLONG: /* Filename is too long, shorten and retry */
1171 if (mPath.size() > mDir.size() + 8)
1172 {
1173 secnotice("atomicfile", "Truncating %s and retrying lock", path);
1174 mPath.erase(mPath.end() - 1);
1175 path = mPath.c_str();
1176 /* Reset retry counter. */
1177 retries = 0;
1178 break;
1179 }
1180 /* DROPTHROUGH */
1181 #endif
1182 default:
1183 failed = true;
1184 break;
1185 }
1186 }
1187
1188 if (failed)
1189 {
1190 int error = errno;
1191 ::syslog(LOG_ERR, "Lock failure on %s: %s", path, strerror(error));
1192 secnotice("atomicfile", "Lock failure on %s: %s", path, strerror(error));
1193 UnixError::throwMe(error);
1194 }
1195 }
1196
1197 void
1198 NetworkFileLocker::unlock()
1199 {
1200 const char *path = mPath.c_str();
1201 if (::unlink(path) == -1)
1202 {
1203 secnotice("atomicfile", "unlink %s: %s", path, strerror(errno));
1204 // unlock can't throw
1205 }
1206 }
1207
1208
1209
1210 AtomicLockedFile::AtomicLockedFile(AtomicFile &inFile)
1211 {
1212 if (inFile.isOnLocalFileSystem())
1213 {
1214 mFileLocker = new LocalFileLocker(inFile);
1215 }
1216 else
1217 {
1218 mFileLocker = new NetworkFileLocker(inFile);
1219 }
1220
1221 lock();
1222 }
1223
1224
1225
1226 AtomicLockedFile::~AtomicLockedFile()
1227 {
1228 unlock();
1229 delete mFileLocker;
1230 }
1231
1232
1233
1234 void
1235 AtomicLockedFile::lock(mode_t mode)
1236 {
1237 mFileLocker->lock(mode);
1238 }
1239
1240
1241
1242 void AtomicLockedFile::unlock() throw()
1243 {
1244 mFileLocker->unlock();
1245 }
1246
1247
1248
1249 #undef kAtomicFileMaxBlockSize