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