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