2  * Copyright (c) 2000-2013 Apple Inc. All Rights Reserved. 
   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 
  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. 
  19 #include <security_filedb/AtomicFile.h> 
  21 #include <CommonCrypto/CommonDigest.h> 
  22 #include <security_cdsa_utilities/cssmerrors.h> 
  23 #include <Security/cssm.h> 
  24 #include <Security/SecRandom.h> 
  29 #include <sys/param.h> 
  30 #include <sys/types.h> 
  31 #include <sys/mount.h> 
  41 #define kAtomicFileMaxBlockSize INT_MAX 
  47 AtomicFile::AtomicFile(const std::string 
&inPath
) : 
  50         pathSplit(inPath
, mDir
, mFile
); 
  52     if (mDir
.length() == 0) 
  54         const char* buffer 
= getwd(NULL
); 
  61         // determine if the path is on a local or a networked volume 
  63         int result 
= statfs(mDir
.c_str(), &info
); 
  64         if (result 
== -1) // error on opening? 
  66                 mIsLocalFileSystem 
= false; // revert to the old ways if we can't tell what kind of system we have 
  70                 mIsLocalFileSystem 
= (info
.f_flags 
& MNT_LOCAL
) != 0; 
  71                 if (mIsLocalFileSystem
) 
  73                         // compute the name of the lock file for this file 
  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
); 
  80                         u_int32_t hash 
= (digest
[0] << 24) | (digest
[1] << 16) | (digest
[2] << 8) | digest
[3]; 
  83                         sprintf(buffer
, "%08X", hash
); 
  84                         mLockFilePath 
= mDir 
+ ".fl" + buffer
; 
  89 AtomicFile::~AtomicFile() 
  93 // Acquire the write lock and remove the file. 
  95 AtomicFile::performDelete() 
  97         AtomicLockedFile 
lock(*this); 
  98         if (::unlink(mPath
.c_str()) != 0) 
 101                 secinfo("atomicfile", "unlink %s: %s", mPath
.c_str(), strerror(error
)); 
 103                         CssmError::throwMe(CSSMERR_DL_DATASTORE_DOESNOT_EXIST
); 
 105                         UnixError::throwMe(error
); 
 108         // unlink our lock file 
 109         ::unlink(mLockFilePath
.c_str()); 
 112 // Acquire the write lock and rename the file (and bump the version and stuff). 
 114 AtomicFile::rename(const std::string 
&inNewPath
) 
 116         const char *path 
= mPath
.c_str(); 
 117         const char *newPath 
= inNewPath
.c_str(); 
 119         // @@@ lock the destination file too. 
 120         AtomicLockedFile 
lock(*this); 
 121         if (::rename(path
, newPath
) != 0) 
 124                 secinfo("atomicfile", "rename(%s, %s): %s", path
, newPath
, strerror(error
)); 
 125                 UnixError::throwMe(error
); 
 129 // Lock the file for writing and return a newly created AtomicTempFile. 
 130 RefPointer
<AtomicTempFile
> 
 131 AtomicFile::create(mode_t mode
) 
 133         const char *path 
= mPath
.c_str(); 
 135         // First make sure the directory to this file exists and is writable 
 138         RefPointer
<AtomicLockedFile
> lock(new AtomicLockedFile(*this)); 
 139         int fileRef 
= ropen(path
, O_WRONLY
|O_CREAT
|O_EXCL
, mode
); 
 143                 secinfo("atomicfile", "open %s: %s", path
, strerror(error
)); 
 145         // Do the obvious error code translations here. 
 146                 // @@@ Consider moving these up a level. 
 148                         CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED
); 
 149         else if (error 
== EEXIST
) 
 150                         CssmError::throwMe(CSSMERR_DL_DATASTORE_ALREADY_EXISTS
); 
 152                         UnixError::throwMe(error
); 
 158                 // Now that we have created the lock and the new db file create a tempfile 
 160                 RefPointer
<AtomicTempFile
> temp(new AtomicTempFile(*this, lock
, mode
)); 
 161                 secinfo("atomicfile", "%p created %s", this, path
); 
 166                 // Creating the temp file failed so remove the db file we just created too. 
 167                 if (::unlink(path
) == -1) 
 169                         secnotice("atomicfile", "unlink %s: %s", path
, strerror(errno
)); 
 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. 
 182 RefPointer
<AtomicTempFile
> 
 186         RefPointer
<AtomicLockedFile
> lock(new AtomicLockedFile(*this)); 
 187         return new AtomicTempFile(*this, lock
); 
 190 // Return a bufferedFile containing current version of the file for reading. 
 191 RefPointer
<AtomicBufferedFile
> 
 194         return new AtomicBufferedFile(mPath
, mIsLocalFileSystem
); 
 198 AtomicFile::mode() const 
 200         const char *path 
= mPath
.c_str(); 
 202         if (::stat(path
, &st
) == -1) 
 205                 secinfo("atomicfile", "stat %s: %s", path
, strerror(error
)); 
 206                 UnixError::throwMe(error
); 
 211 // Split full into a dir and file component. 
 213 AtomicFile::pathSplit(const std::string 
&inFull
, std::string 
&outDir
, std::string 
&outFile
) 
 215         std::string::size_type slash
, len 
= inFull
.size(); 
 216         slash 
= inFull
.rfind('/'); 
 217         if (slash 
== std::string::npos
) 
 222         else if (slash 
+ 1 == len
) 
 229                 outDir 
= inFull
.substr(0, slash 
+ 1); 
 230                 outFile 
= inFull
.substr(slash 
+ 1, len
); 
 235 // Make sure the directory up to inDir exists inDir *must* end in a slash. 
 238 AtomicFile::mkpath(const std::string 
&inDir
, mode_t mode
) 
 240     // see if the file already exists and is a directory 
 242     int result 
= stat(inDir
.c_str(), &st
); 
 244     if (result 
== 0) // file exists 
 246         if ((st
.st_mode 
& S_IFDIR
) == 0) 
 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
); 
 255         // the file did not exist, try to create it 
 256         result 
= mkpath_np(inDir
.c_str(), 0777); // make the directory with umask 
 259             // mkpath_np does not set errno, you have to look at the result. 
 260             UnixError::throwMe(result
); 
 264     // Double check and see if we got what we hoped for 
 265     result 
= stat(inDir
.c_str(), &st
); 
 268         UnixError::throwMe(errno
); 
 271     if ((st
.st_mode 
& S_IFDIR
) == 0) 
 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
); 
 280 AtomicFile::ropen(const char *const name
, int flags
, mode_t mode
) 
 282     bool isCreate 
= (flags 
& O_CREAT
) != 0; 
 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. 
 292         We handle this situation by checking two factors: 
 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 
 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. 
 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). 
 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. 
 315     bool checkForRead 
= false; 
 316     bool checkForWrite 
= false; 
 318     int fd
, tries_left 
= 4 /* kNoResRetry */; 
 322         switch (flags 
& O_ACCMODE
)  
 328                 checkForWrite 
= true; 
 332                 checkForWrite 
= true; 
 338             int result 
= sandbox_check(getpid(), "file-read-data", (sandbox_filter_type
) (SANDBOX_FILTER_PATH 
| SANDBOX_CHECK_NO_REPORT
), name
); 
 341                 secdebug("atomicfile", "sandboxing rejected read access to %s", name
); 
 348             int result 
= sandbox_check(getpid(), "file-write-data", (sandbox_filter_type
) (SANDBOX_FILTER_PATH 
| SANDBOX_CHECK_NO_REPORT
), name
); 
 351                 secdebug("atomicfile", "sandboxing rejected write access to %s", name
); 
 359                 fd 
= ::open(name
, flags
, mode
); 
 360         } while (fd 
< 0 && (errno 
== EINTR 
|| (errno 
== ENFILE 
&& --tries_left 
>= 0))); 
 366 AtomicFile::rclose(int fd
) 
 371                 result 
= ::close(fd
); 
 372         } while(result 
&& errno 
== EINTR
); 
 378 // AtomicBufferedFile - This represents an instance of a file opened for reading. 
 379 // The file is read into memory and closed after this is done. 
 380 // The memory is released when this object is destroyed. 
 382 AtomicBufferedFile::AtomicBufferedFile(const std::string 
&inPath
, bool isLocal
) : 
 390 AtomicBufferedFile::~AtomicBufferedFile() 
 394                 // In release mode, the assert() is compiled out so rv may be unused. 
 395                 __unused 
int rv 
= AtomicFile::rclose(mFileRef
); 
 397                 secinfo("atomicfile", "%p closed %s", this, mPath
.c_str()); 
 402                 secinfo("atomicfile", "%p free %s buffer %p", this, mPath
.c_str(), mBuffer
); 
 408 // Open the file and return the length in bytes. 
 411 AtomicBufferedFile::open() 
 413         const char *path 
= mPath
.c_str(); 
 416                 secinfo("atomicfile", "open %s: already open, closing and reopening", path
); 
 420         mFileRef 
= AtomicFile::ropen(path
, O_RDONLY
, 0); 
 424                 secinfo("atomicfile", "open %s: %s", path
, strerror(error
)); 
 426         // Do the obvious error code translations here. 
 427                 // @@@ Consider moving these up a level. 
 429                         CssmError::throwMe(CSSMERR_DL_DATASTORE_DOESNOT_EXIST
); 
 430                 else if (error 
== EACCES
) 
 431                         CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED
); 
 433                         UnixError::throwMe(error
); 
 437         int result 
= fstat(mFileRef
, &st
); 
 440                 mLength 
= st
.st_size
; 
 445                 secinfo("atomicfile", "lseek(%s, END): %s", path
, strerror(error
)); 
 446                 AtomicFile::rclose(mFileRef
); 
 448                 UnixError::throwMe(error
); 
 451         secinfo("atomicfile", "%p opened %s: %qd bytes", this, path
, mLength
); 
 457 // Unload the contents of the file. 
 460 AtomicBufferedFile::unloadBuffer() 
 469 // Load the contents of the file into memory. 
 471 AtomicBufferedFile::loadBuffer() 
 473     // make a buffer big enough to hold the entire file 
 474     mBuffer 
= new uint8
[(size_t) mLength
]; 
 475     if(lseek(mFileRef
, 0, SEEK_SET
) < 0) { 
 477         secinfo("atomicfile", "lseek(%s, BEGINNING): %s", mPath
.c_str(), strerror(error
)); 
 478         UnixError::throwMe(error
); 
 482     ssize_t bytesToRead 
= (ssize_t
)mLength
; 
 483     while (bytesToRead 
> 0) 
 485         ssize_t bytesRead 
= ::read(mFileRef
, mBuffer 
+ pos
, bytesToRead
); 
 491                 secinfo("atomicfile", "read(%s, %zd): %s", mPath
.c_str(), bytesToRead
, strerror(error
)); 
 492                 AtomicFile::rclose(mFileRef
); 
 494                 UnixError::throwMe(error
); 
 499             bytesToRead 
-= bytesRead
; 
 508 // Read the file starting at inOffset for inLength bytes into the buffer and return 
 509 // a pointer to it.  On return outLength contain the actual number of bytes read, it 
 510 // will only ever be less than inLength if EOF was reached, and it will never be more 
 514 AtomicBufferedFile::read(off_t inOffset
, off_t inLength
, off_t 
&outLength
) 
 518                 secinfo("atomicfile", "read %s: file yet not opened, opening", mPath
.c_str()); 
 522         off_t bytesLeft 
= inLength
; 
 525                 secinfo("atomicfile", "%p free %s buffer %p", this, mPath
.c_str(), mBuffer
); 
 531         secinfo("atomicfile", "%p allocated %s buffer %p size %qd", this, mPath
.c_str(), mBuffer
, bytesLeft
); 
 533         off_t maxEnd 
= inOffset 
+ inLength
; 
 534         if (maxEnd 
> mLength
) 
 539         outLength 
= maxEnd 
- inOffset
; 
 541         return mBuffer 
+ inOffset
; 
 545 AtomicBufferedFile::close() 
 549                 secinfo("atomicfile", "close %s: already closed", mPath
.c_str()); 
 553                 int result 
= AtomicFile::rclose(mFileRef
); 
 558                         secnotice("atomicfile", "close %s: %s", mPath
.c_str(), strerror(errno
)); 
 559                         UnixError::throwMe(error
); 
 562                 secinfo("atomicfile", "%p closed %s", this, mPath
.c_str()); 
 568 // AtomicTempFile - A temporary file to write changes to. 
 570 AtomicTempFile::AtomicTempFile(AtomicFile 
&inFile
, const RefPointer
<AtomicLockedFile
> &inLockedFile
, mode_t mode
) : 
 572         mLockedFile(inLockedFile
), 
 578 AtomicTempFile::AtomicTempFile(AtomicFile 
&inFile
, const RefPointer
<AtomicLockedFile
> &inLockedFile
) : 
 580         mLockedFile(inLockedFile
), 
 583         create(mFile
.mode()); 
 586 AtomicTempFile::~AtomicTempFile() 
 588         // rollback if we didn't commit yet. 
 594 // Open the file and return the length in bytes. 
 597 AtomicTempFile::create(mode_t mode
) 
 599         // we now generate our temporary file name through sandbox API's. 
 601     // put the dir into a canonical form 
 602     string dir 
= mFile
.dir(); 
 603     int i 
= (int)dir
.length() - 1; 
 605     // walk backwards until we get to a non / character 
 606     while (i 
>= 0 && dir
[i
] == '/') 
 611     // point one beyond the string 
 614     const char* temp 
= _amkrtemp((dir
.substr(0, i
) + "/" + mFile
.file()).c_str()); 
 617         UnixError::throwMe(errno
); 
 623         const char *path 
= mPath
.c_str(); 
 625         mFileRef 
= AtomicFile::ropen(path
, O_WRONLY
|O_CREAT
|O_TRUNC
, mode
); 
 629                 secnotice("atomicfile", "create %s: %s", path
, strerror(error
)); 
 631         // Do the obvious error code translations here. 
 632                 // @@@ Consider moving these up a level. 
 634                         CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED
); 
 636                         UnixError::throwMe(error
); 
 639         // If we aren't creating the inital file, make sure we preserve 
 640         // the mode of the old file regardless of the current umask. 
 641         // If we are creating the inital file we respect the users 
 645                 if (::fchmod(mFileRef
, mode
)) 
 648                         secnotice("atomicfile", "fchmod %s: %s", path
, strerror(error
)); 
 649                         UnixError::throwMe(error
); 
 653         secinfo("atomicfile", "%p created %s", this, path
); 
 657 AtomicTempFile::write(AtomicFile::OffsetType inOffsetType
, off_t inOffset
, const uint32 inData
) 
 659     uint32 aData 
= htonl(inData
); 
 660     write(inOffsetType
, inOffset
, reinterpret_cast<uint8 
*>(&aData
), sizeof(aData
)); 
 664 AtomicTempFile::write(AtomicFile::OffsetType inOffsetType
, off_t inOffset
, 
 665                                   const uint32 
*inData
, uint32 inCount
) 
 667 #ifdef HOST_LONG_IS_NETWORK_LONG 
 668     // Optimize this for the case where hl == nl 
 669     const uint32 
*aBuffer 
= inData
; 
 671     auto_array
<uint32
> aBuffer(inCount
); 
 672     for (uint32 i 
= 0; i 
< inCount
; i
++) 
 673         aBuffer
.get()[i
] = htonl(inData
[i
]); 
 676     write(inOffsetType
, inOffset
, reinterpret_cast<const uint8 
*>(aBuffer
.get()), 
 677           inCount 
* sizeof(*inData
)); 
 681 AtomicTempFile::write(AtomicFile::OffsetType inOffsetType
, off_t inOffset
, const uint8 
*inData
, size_t inLength
) 
 684         if (inOffsetType 
== AtomicFile::FromEnd
) 
 686                 pos 
= ::lseek(mFileRef
, 0, SEEK_END
); 
 690                         secnotice("atomicfile", "lseek(%s, %qd): %s", mPath
.c_str(), inOffset
, strerror(error
)); 
 691                         UnixError::throwMe(error
); 
 694         else if (inOffsetType 
== AtomicFile::FromStart
) 
 697                 CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR
); 
 699         off_t bytesLeft 
= inLength
; 
 700         const uint8 
*ptr 
= inData
; 
 703                 size_t toWrite 
= bytesLeft 
> kAtomicFileMaxBlockSize 
? kAtomicFileMaxBlockSize 
: size_t(bytesLeft
); 
 704                 ssize_t bytesWritten 
= ::pwrite(mFileRef
, ptr
, toWrite
, pos
); 
 705                 if (bytesWritten 
== -1) 
 710                                 // We got interrupted by a signal, so try again. 
 711                                 secnotice("atomicfile", "write %s: interrupted, retrying", mPath
.c_str()); 
 715                         secnotice("atomicfile", "write %s: %s", mPath
.c_str(), strerror(error
)); 
 716                         UnixError::throwMe(error
); 
 719                 // Write returning 0 is bad mmkay. 
 720                 if (bytesWritten 
== 0) 
 722                         secnotice("atomicfile", "write %s: 0 bytes written", mPath
.c_str()); 
 723                         CssmError::throwMe(CSSMERR_DL_INTERNAL_ERROR
); 
 726                 secdebug("atomicfile", "%p wrote %s %ld bytes from %p", this, mPath
.c_str(), bytesWritten
, ptr
); 
 728                 bytesLeft 
-= bytesWritten
; 
 735 AtomicTempFile::fsync() 
 739                 secnotice("atomicfile", "fsync %s: already closed", mPath
.c_str()); 
 746                         result 
= ::fsync(mFileRef
); 
 747                 } while (result 
&& errno 
== EINTR
); 
 752                         secnotice("atomicfile", "fsync %s: %s", mPath
.c_str(), strerror(errno
)); 
 753                         UnixError::throwMe(error
); 
 756                 secinfo("atomicfile", "%p fsynced %s", this, mPath
.c_str()); 
 761 AtomicTempFile::close() 
 765                 secnotice("atomicfile", "close %s: already closed", mPath
.c_str()); 
 769                 int result 
= AtomicFile::rclose(mFileRef
); 
 774                         secnotice("atomicfile", "close %s: %s", mPath
.c_str(), strerror(errno
)); 
 775                         UnixError::throwMe(error
); 
 778                 secinfo("atomicfile", "%p closed %s", this, mPath
.c_str()); 
 782 // Commit the current create or write and close the write file.  Note that a throw during the commit does an automatic rollback. 
 784 AtomicTempFile::commit() 
 790                 const char *oldPath 
= mPath
.c_str(); 
 791                 const char *newPath 
= mFile
.path().c_str(); 
 793                 // <rdar://problem/6991037> 
 794                 // Copy the security parameters of one file to another 
 795                 // Adding this to guard against setuid utilities that are re-writing a user's keychain.  We don't want to leave them root-owned. 
 796                 // In order to not break backward compatability we'll make a best effort, but continue if these efforts fail. 
 798                 // To clear something up - newPath is the name the keychain will become - which is the name of the file being replaced 
 799                 //                         oldPath is the "temp filename". 
 802                 s 
= copyfile_state_alloc(); 
 804                 if(copyfile(newPath
, oldPath
, s
, COPYFILE_SECURITY 
| COPYFILE_NOFOLLOW
) == -1) // Not fatal 
 805                         secnotice("atomicfile", "copyfile (%s, %s): %s", oldPath
, newPath
, strerror(errno
)); 
 807                 copyfile_state_free(s
); 
 808                 // END <rdar://problem/6991037> 
 810                 ::utimes(oldPath
, NULL
); 
 812                 if (::rename(oldPath
, newPath
) == -1) 
 815                         secnotice("atomicfile", "rename (%s, %s): %s", oldPath
, newPath
, strerror(errno
)); 
 816                         UnixError::throwMe(error
); 
 819         secnotice("atomicfile", "%p commited %s to %s", this, oldPath
, newPath
); 
 821                 // Unlock the lockfile 
 831 // Rollback the current create or write (happens automatically if commit() isn't called before the destructor is. 
 833 AtomicTempFile::rollback() throw() 
 837                 AtomicFile::rclose(mFileRef
); 
 841         // @@@ Log errors if this fails. 
 842         const char *path 
= mPath
.c_str(); 
 843         if (::unlink(path
) == -1) 
 845                 secnotice("atomicfile", "unlink %s: %s", path
, strerror(errno
)); 
 846                 // rollback can't throw 
 849         // @@@ Think about this.  Depending on how we do locking we might not need this. 
 852                 const char *path 
= mFile
.path().c_str(); 
 853                 if (::unlink(path
) == -1) 
 855                         secnotice("atomicfile", "unlink %s: %s", path
, strerror(errno
)); 
 856                         // rollback can't throw 
 863 // An advisory write lock for inFile. 
 865 FileLocker::~FileLocker() 
 871 LocalFileLocker::LocalFileLocker(AtomicFile 
&inFile
) : 
 872         mPath(inFile
.lockFileName()) 
 877 LocalFileLocker::~LocalFileLocker() 
 884 static double GetTime() 
 887         gettimeofday(&t
, NULL
); 
 888         return ((double) t
.tv_sec
) + ((double) t
.tv_usec
) / 1000000.0; 
 895 LocalFileLocker::lock(mode_t mode
) 
 901                 // if the lock file doesn't exist, create it 
 902                 mLockFile 
= open(mPath
.c_str(), O_RDONLY 
| O_CREAT
, mode
); 
 904                 // if we can't open or create the file, something is wrong 
 907                         UnixError::throwMe(errno
); 
 910                 // try to get exclusive access to the file 
 911                 IFDEBUG(double startTime 
= GetTime()); 
 912                 int result 
= flock(mLockFile
, LOCK_EX
); 
 913                 IFDEBUG(double endTime 
= GetTime()); 
 915                 IFDEBUG(secnotice("atomictime", "Waited %.4f milliseconds for file lock", (endTime 
- startTime
) * 1000.0)); 
 917                 // errors at this point are bad 
 920                         UnixError::throwMe(errno
); 
 923                 // check and see if the file we have access to still exists.  If not, another file shared our file lock 
 924                 // due to a hash collision and has thrown our lock away -- that, or a user blew the lock file away himself. 
 926                 result 
= fstat(mLockFile
, &st
); 
 928                 // errors at this point are bad 
 931                         UnixError::throwMe(errno
); 
 934                 if (st
.st_nlink 
== 0) // we've been unlinked! 
 938         } while (st
.st_nlink 
== 0); 
 943 LocalFileLocker::unlock() 
 945         flock(mLockFile
, LOCK_UN
); 
 951 NetworkFileLocker::NetworkFileLocker(AtomicFile 
&inFile
) : 
 953         mPath(inFile
.dir() + "lck~" + inFile
.file()) 
 957 NetworkFileLocker::~NetworkFileLocker() 
 962 NetworkFileLocker::unique(mode_t mode
) 
 964         static const int randomPart 
= 16; 
 965         std::string::size_type dirSize 
= mDir
.size(); 
 966         std::string 
fullname(dirSize 
+ randomPart 
+ 2, '\0'); 
 967         fullname
.replace(0, dirSize
, mDir
); 
 968         fullname
[dirSize
] = '~'; /* UNIQ_PREFIX */ 
 969         char buf
[randomPart
]; 
 973         for (int retries 
= 0; retries 
< 10; ++retries
) 
 975                 /* Make a random filename. */ 
 976         MacOSError::check(SecRandomCopyBytes(kSecRandomDefault
, randomPart
, buf
)); 
 977                 for (int ix 
= 0; ix 
< randomPart
; ++ix
) 
 979                         char ch 
= buf
[ix
] & 0x3f; 
 980                         fullname
[ix 
+ dirSize 
+ 1] = ch 
+ 
 982                                 : ch 
< 26 + 26       ? 'a' - 26 
 983                                 : ch 
< 26 + 26 + 10  ? '0' - 26 - 26 
 984                                 : ch 
== 26 + 26 + 10 ? '-' - 26 - 26 - 10 
 985                                 :                      '_' - 26 - 26 - 11); 
 988                 result 
= lstat(fullname
.c_str(), &filebuf
); 
 989                 if (result 
&& errno 
== ENAMETOOLONG
) 
 992                                 fullname
.erase(fullname
.end() - 1); 
 993                         while((result 
= lstat(fullname
.c_str(), &filebuf
)) && errno 
== ENAMETOOLONG 
&& fullname
.size() > dirSize 
+ 8); 
 994                 }       /* either it stopped being a problem or we ran out of filename */ 
 996                 if (result 
&& errno 
== ENOENT
) 
 998                         fd 
= AtomicFile::ropen(fullname
.c_str(), O_WRONLY
|O_CREAT
|O_EXCL
, mode
); 
 999                         if (fd 
>= 0 || errno 
!= EEXIST
) 
1007                 ::syslog(LOG_ERR
, "Couldn't create temp file %s: %s", fullname
.c_str(), strerror(error
)); 
1008                 secnotice("atomicfile", "Couldn't create temp file %s: %s", fullname
.c_str(), strerror(error
)); 
1009                 UnixError::throwMe(error
); 
1012         /* @@@ Check for EINTR. */ 
1013         write(fd
, "0", 1); /* pid 0, `works' across networks */ 
1015         AtomicFile::rclose(fd
); 
1020 /* Return 0 on success and 1 on failure if st is set to the result of stat(old) and -1 on failure if the stat(old) failed. */ 
1022 NetworkFileLocker::rlink(const char *const old
, const char *const newn
, struct stat 
&sto
) 
1024         int result 
= ::link(old
,newn
); 
1028                 if (::lstat(old
, &sto
) == 0) 
1031                         if (::lstat(newn
, &stn
) == 0 
1032                                 && sto
.st_dev 
== stn
.st_dev
 
1033                                 && sto
.st_ino 
== stn
.st_ino
 
1034                                 && sto
.st_uid 
== stn
.st_uid
 
1035                                 && sto
.st_gid 
== stn
.st_gid
 
1036                                 && !S_ISLNK(sto
.st_mode
)) 
1038                                 /* Link failed but files are the same so the link really went ok. */ 
1044                 errno 
= serrno
; /* Restore errno from link() */ 
1050 /* NFS-resistant rename() 
1051  * rename with fallback for systems that don't support it 
1052  * Note that this does not preserve the contents of the file. */ 
1054 NetworkFileLocker::myrename(const char *const old
, const char *const newn
) 
1060         /* Try a real hardlink */ 
1061         ret 
= rlink(old
, newn
, stbuf
); 
1064                 if (stbuf
.st_nlink 
< 2 && (errno 
== EXDEV 
|| errno 
== ENOTSUP
)) 
1066                         /* Hard link failed so just create a new file with O_EXCL instead.  */ 
1067                         fd 
= AtomicFile::ropen(newn
, O_WRONLY
|O_CREAT
|O_EXCL
, stbuf
.st_mode
); 
1073         /* We want the errno from the link or the ropen, not that of the unlink. */ 
1076         /* Unlink the temp file. */ 
1079                 AtomicFile::rclose(fd
); 
1086 NetworkFileLocker::xcreat(const char *const name
, mode_t mode
, time_t &tim
) 
1088         std::string uniqueName 
= unique(mode
); 
1089         const char *uniquePath 
= uniqueName
.c_str(); 
1090         struct stat stbuf
;       /* return the filesystem time to the caller */ 
1091         stat(uniquePath
, &stbuf
); 
1092         tim 
= stbuf
.st_mtime
; 
1093         return myrename(uniquePath
, name
); 
1097 NetworkFileLocker::lock(mode_t mode
) 
1099         const char *path 
= mPath
.c_str(); 
1100         bool triedforce 
= false; 
1102         time_t t
, locktimeout 
= 1024; /* DEFlocktimeout, 17 minutes. */ 
1103         bool doSyslog 
= false; 
1104         bool failed 
= false; 
1109                 /* Don't syslog first time through. */ 
1111                         ::syslog(LOG_NOTICE
, "Locking %s", path
); 
1115                 secinfo("atomicfile", "Locking %s", path
);          /* in order to cater for clock skew: get */ 
1116                 if (!xcreat(path
, mode
, t
))    /* time t from the filesystem */ 
1118                         /* lock acquired, hurray! */ 
1123                 case EEXIST
:               /* check if it's time for a lock override */ 
1124                         if (!lstat(path
, &stbuf
) && stbuf
.st_size 
<= 16 /* MAX_locksize */ && locktimeout
 
1125                                 && !lstat(path
, &stbuf
) && locktimeout 
< t 
- stbuf
.st_mtime
) 
1126                                 /* stat() till unlink() should be atomic, but can't guarantee that. */ 
1130                                         /* Already tried, force lock override, not trying again */ 
1134                                 else if (S_ISDIR(stbuf
.st_mode
) || ::unlink(path
)) 
1137                                         ::syslog(LOG_ERR
, "Forced unlock denied on %s", path
); 
1138                                         secnotice("atomicfile", "Forced unlock denied on %s", path
); 
1142                                         ::syslog(LOG_ERR
, "Forcing lock on %s", path
); 
1143                                         secnotice("atomicfile", "Forcing lock on %s", path
); 
1144                                         sleep(16 /* DEFsuspend */); 
1149                                 triedforce 
= false;              /* legitimate iteration, clear flag */ 
1151                         /* Reset retry counter. */ 
1156                 case ENOSPC
:               /* no space left, treat it as a transient */ 
1157 #ifdef EDQUOT                                                 /* NFS failure */ 
1158                 case EDQUOT
:                  /* maybe it was a short term shortage? */ 
1164                         if(++retries 
< (256 + 1))  /* nfsTRY number of times+1 to ignore spurious NFS errors */ 
1171                 case ENAMETOOLONG
:     /* Filename is too long, shorten and retry */ 
1172                         if (mPath
.size() > mDir
.size() + 8) 
1174                                 secnotice("atomicfile", "Truncating %s and retrying lock", path
); 
1175                                 mPath
.erase(mPath
.end() - 1); 
1176                                 path 
= mPath
.c_str(); 
1177                                 /* Reset retry counter. */ 
1192                 ::syslog(LOG_ERR
, "Lock failure on %s: %s", path
, strerror(error
)); 
1193                 secnotice("atomicfile", "Lock failure on %s: %s", path
, strerror(error
)); 
1194                 UnixError::throwMe(error
); 
1199 NetworkFileLocker::unlock() 
1201         const char *path 
= mPath
.c_str(); 
1202         if (::unlink(path
) == -1) 
1204                 secnotice("atomicfile", "unlink %s: %s", path
, strerror(errno
)); 
1205                 // unlock can't throw 
1211 AtomicLockedFile::AtomicLockedFile(AtomicFile 
&inFile
) 
1213         if (inFile
.isOnLocalFileSystem()) 
1215                 mFileLocker 
= new LocalFileLocker(inFile
); 
1219                 mFileLocker 
= new NetworkFileLocker(inFile
); 
1227 AtomicLockedFile::~AtomicLockedFile() 
1236 AtomicLockedFile::lock(mode_t mode
) 
1238         mFileLocker
->lock(mode
); 
1243 void AtomicLockedFile::unlock() throw() 
1245         mFileLocker
->unlock(); 
1250 #undef kAtomicFileMaxBlockSize