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