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// 46AtomicFile::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 88AtomicFile::~AtomicFile() 89{ 90} 91 92// Aquire the write lock and remove the file. 93void 94AtomicFile::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). 112void 113AtomicFile::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. 129RefPointer<AtomicTempFile> 130AtomicFile::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 181RefPointer<AtomicTempFile> 182AtomicFile::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. 190RefPointer<AtomicBufferedFile> 191AtomicFile::read() 192{ 193 return new AtomicBufferedFile(mPath, mIsLocalFileSystem); 194} 195 196mode_t 197AtomicFile::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. 211void 212AtomicFile::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// 236void 237AtomicFile::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 278int 279AtomicFile::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 362int 363AtomicFile::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// 379AtomicBufferedFile::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 388AtomicBufferedFile::~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// 406off_t 407AtomicBufferedFile::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// 454void 455AtomicBufferedFile::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 471void 472AtomicBufferedFile::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// 524const uint8 * 525AtomicBufferedFile::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 555void 556AtomicBufferedFile::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// 581AtomicTempFile::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 589AtomicTempFile::AtomicTempFile(AtomicFile &inFile, const RefPointer<AtomicLockedFile> &inLockedFile) : 590 mFile(inFile), 591 mLockedFile(inLockedFile), 592 mCreating(false) 593{ 594 create(mFile.mode()); 595} 596 597AtomicTempFile::~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// 607void 608AtomicTempFile::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 667void 668AtomicTempFile::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 674void 675AtomicTempFile::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 691void 692AtomicTempFile::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 745void 746AtomicTempFile::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 771void 772AtomicTempFile::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. 794void 795AtomicTempFile::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. 843void 844AtomicTempFile::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// 876FileLocker::~FileLocker() 877{ 878} 879 880 881 882LocalFileLocker::LocalFileLocker(AtomicFile &inFile) : 883 mPath(inFile.lockFileName()) 884{ 885} 886 887 888LocalFileLocker::~LocalFileLocker() 889{ 890} 891 892 893 894#ifndef NDEBUG 895static 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 905void 906LocalFileLocker::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 953void 954LocalFileLocker::unlock() 955{ 956 flock(mLockFile, LOCK_UN); 957 close(mLockFile); 958} 959 960 961 962NetworkFileLocker::NetworkFileLocker(AtomicFile &inFile) : 963 mDir(inFile.dir()), 964 mPath(inFile.dir() + "lck~" + inFile.file()) 965{ 966} 967 968NetworkFileLocker::~NetworkFileLocker() 969{ 970} 971 972std::string 973NetworkFileLocker::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. */ 1033int 1034NetworkFileLocker::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. */ 1065int 1066NetworkFileLocker::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 1097int 1098NetworkFileLocker::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 1108void 1109NetworkFileLocker::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 1210void 1211NetworkFileLocker::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 1223AtomicLockedFile::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 1239AtomicLockedFile::~AtomicLockedFile() 1240{ 1241 unlock(); 1242 delete mFileLocker; 1243} 1244 1245 1246 1247void 1248AtomicLockedFile::lock(mode_t mode) 1249{ 1250 mFileLocker->lock(mode); 1251} 1252 1253 1254 1255void AtomicLockedFile::unlock() throw() 1256{ 1257 mFileLocker->unlock(); 1258} 1259 1260 1261 1262#undef kAtomicFileMaxBlockSize 1263