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 <security_utilities/devrandom.h>
22 #include <CommonCrypto/CommonDigest.h>
23 #include <security_cdsa_utilities/cssmerrors.h>
24 #include <Security/cssm.h>
29 #include <sys/param.h>
30 #include <sys/types.h>
31 #include <sys/mount.h>
40 #define kAtomicFileMaxBlockSize INT_MAX
46 AtomicFile::AtomicFile(const std::string
&inPath
) :
49 pathSplit(inPath
, mDir
, mFile
);
51 if (mDir
.length() == 0)
53 const char* buffer
= getwd(NULL
);
60 // determine if the path is on a local or a networked volume
62 int result
= statfs(mDir
.c_str(), &info
);
63 if (result
== -1) // error on opening?
65 mIsLocalFileSystem
= false; // revert to the old ways if we can't tell what kind of system we have
69 mIsLocalFileSystem
= (info
.f_flags
& MNT_LOCAL
) != 0;
70 if (mIsLocalFileSystem
)
72 // compute the name of the lock file for this file
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
);
79 u_int32_t hash
= (digest
[0] << 24) | (digest
[1] << 16) | (digest
[2] << 8) | digest
[3];
82 sprintf(buffer
, "%08X", hash
);
83 mLockFilePath
= mDir
+ ".fl" + buffer
;
88 AtomicFile::~AtomicFile()
92 // Aquire the write lock and remove the file.
94 AtomicFile::performDelete()
96 AtomicLockedFile
lock(*this);
97 if (::unlink(mPath
.c_str()) != 0)
100 secdebug("atomicfile", "unlink %s: %s", mPath
.c_str(), strerror(error
));
102 CssmError::throwMe(CSSMERR_DL_DATASTORE_DOESNOT_EXIST
);
104 UnixError::throwMe(error
);
107 // unlink our lock file
108 ::unlink(mLockFilePath
.c_str());
111 // Aquire the write lock and rename the file (and bump the version and stuff).
113 AtomicFile::rename(const std::string
&inNewPath
)
115 const char *path
= mPath
.c_str();
116 const char *newPath
= inNewPath
.c_str();
118 // @@@ lock the destination file too.
119 AtomicLockedFile
lock(*this);
120 if (::rename(path
, newPath
) != 0)
123 secdebug("atomicfile", "rename(%s, %s): %s", path
, newPath
, strerror(error
));
124 UnixError::throwMe(error
);
128 // Lock the file for writing and return a newly created AtomicTempFile.
129 RefPointer
<AtomicTempFile
>
130 AtomicFile::create(mode_t mode
)
132 const char *path
= mPath
.c_str();
134 // First make sure the directory to this file exists and is writable
137 RefPointer
<AtomicLockedFile
> lock(new AtomicLockedFile(*this));
138 int fileRef
= ropen(path
, O_WRONLY
|O_CREAT
|O_EXCL
, mode
);
142 secdebug("atomicfile", "open %s: %s", path
, strerror(error
));
144 // Do the obvious error code translations here.
145 // @@@ Consider moving these up a level.
147 CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED
);
148 else if (error
== EEXIST
)
149 CssmError::throwMe(CSSMERR_DL_DATASTORE_ALREADY_EXISTS
);
151 UnixError::throwMe(error
);
157 // Now that we have created the lock and the new db file create a tempfile
159 RefPointer
<AtomicTempFile
> temp(new AtomicTempFile(*this, lock
, mode
));
160 secdebug("atomicfile", "%p created %s", this, path
);
165 // Creating the temp file failed so remove the db file we just created too.
166 if (::unlink(path
) == -1)
168 secdebug("atomicfile", "unlink %s: %s", path
, strerror(errno
));
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.
181 RefPointer
<AtomicTempFile
>
185 RefPointer
<AtomicLockedFile
> lock(new AtomicLockedFile(*this));
186 return new AtomicTempFile(*this, lock
);
189 // Return a bufferedFile containing current version of the file for reading.
190 RefPointer
<AtomicBufferedFile
>
193 return new AtomicBufferedFile(mPath
, mIsLocalFileSystem
);
197 AtomicFile::mode() const
199 const char *path
= mPath
.c_str();
201 if (::stat(path
, &st
) == -1)
204 secdebug("atomicfile", "stat %s: %s", path
, strerror(error
));
205 UnixError::throwMe(error
);
210 // Split full into a dir and file component.
212 AtomicFile::pathSplit(const std::string
&inFull
, std::string
&outDir
, std::string
&outFile
)
214 std::string::size_type slash
, len
= inFull
.size();
215 slash
= inFull
.rfind('/');
216 if (slash
== std::string::npos
)
221 else if (slash
+ 1 == len
)
228 outDir
= inFull
.substr(0, slash
+ 1);
229 outFile
= inFull
.substr(slash
+ 1, len
);
234 // Make sure the directory up to inDir exists inDir *must* end in a slash.
237 AtomicFile::mkpath(const std::string
&inDir
, mode_t mode
)
239 // see if the file already exists and is a directory
241 int result
= stat(inDir
.c_str(), &st
);
243 if (result
== 0) // file exists
245 if ((st
.st_mode
& S_IFDIR
) == 0)
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
);
254 // the file did not exist, try to create it
255 result
= mkpath_np(inDir
.c_str(), 0777); // make the directory with umask
258 // mkpath_np does not set errno, you have to look at the result.
259 UnixError::throwMe(result
);
263 // Double check and see if we got what we hoped for
264 result
= stat(inDir
.c_str(), &st
);
267 UnixError::throwMe(errno
);
270 if ((st
.st_mode
& S_IFDIR
) == 0)
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
);
279 AtomicFile::ropen(const char *const name
, int flags
, mode_t mode
)
281 bool isCreate
= (flags
& O_CREAT
) != 0;
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.
291 We handle this situation by checking two factors:
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
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.
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).
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.
314 bool checkForRead
= false;
315 bool checkForWrite
= false;
317 int fd
, tries_left
= 4 /* kNoResRetry */;
321 switch (flags
& O_ACCMODE
)
327 checkForWrite
= true;
331 checkForWrite
= true;
337 int result
= sandbox_check(getpid(), "file-read-data", (sandbox_filter_type
) (SANDBOX_FILTER_PATH
| SANDBOX_CHECK_NO_REPORT
), name
);
346 int result
= sandbox_check(getpid(), "file-write-data", (sandbox_filter_type
) (SANDBOX_FILTER_PATH
| SANDBOX_CHECK_NO_REPORT
), name
);
356 fd
= ::open(name
, flags
, mode
);
357 } while (fd
< 0 && (errno
== EINTR
|| (errno
== ENFILE
&& --tries_left
>= 0)));
363 AtomicFile::rclose(int fd
)
368 result
= ::close(fd
);
369 } while(result
&& errno
== EINTR
);
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.
379 AtomicBufferedFile::AtomicBufferedFile(const std::string
&inPath
, bool isLocal
) :
388 AtomicBufferedFile::~AtomicBufferedFile()
392 AtomicFile::rclose(mFileRef
);
393 secdebug("atomicfile", "%p closed %s", this, mPath
.c_str());
398 secdebug("atomicfile", "%p free %s buffer %p", this, mPath
.c_str(), mBuffer
);
404 // Open the file and return the length in bytes.
407 AtomicBufferedFile::open()
409 const char *path
= mPath
.c_str();
412 secdebug("atomicfile", "open %s: already open, closing and reopening", path
);
416 mFileRef
= AtomicFile::ropen(path
, O_RDONLY
, 0);
420 secdebug("atomicfile", "open %s: %s", path
, strerror(error
));
422 // Do the obvious error code translations here.
423 // @@@ Consider moving these up a level.
425 CssmError::throwMe(CSSMERR_DL_DATASTORE_DOESNOT_EXIST
);
426 else if (error
== EACCES
)
427 CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED
);
429 UnixError::throwMe(error
);
433 int result
= fstat(mFileRef
, &st
);
436 mLength
= st
.st_size
;
441 secdebug("atomicfile", "lseek(%s, END): %s", path
, strerror(error
));
442 AtomicFile::rclose(mFileRef
);
443 UnixError::throwMe(error
);
446 secdebug("atomicfile", "%p opened %s: %qd bytes", this, path
, mLength
);
452 // Unload the contents of the file.
455 AtomicBufferedFile::unloadBuffer()
463 munmap(mBuffer
, (size_t)mLength
);
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
472 AtomicBufferedFile::loadBuffer()
476 // make a buffer big enough to hold the entire file
477 mBuffer
= new uint8
[mLength
];
478 lseek(mFileRef
, 0, SEEK_SET
);
481 ssize_t bytesToRead
= (ssize_t
)mLength
;
482 while (bytesToRead
> 0)
484 ssize_t bytesRead
= ::read(mFileRef
, mBuffer
+ pos
, bytesToRead
);
490 secdebug("atomicfile", "lseek(%s, END): %s", mPath
.c_str(), strerror(error
));
491 AtomicFile::rclose(mFileRef
);
492 UnixError::throwMe(error
);
497 bytesToRead
-= bytesRead
;
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)
509 secdebug("atomicfile", "lseek(%s, END): %s", mPath
.c_str(), strerror(error
));
510 AtomicFile::rclose(mFileRef
);
511 UnixError::throwMe(error
);
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
525 AtomicBufferedFile::read(off_t inOffset
, off_t inLength
, off_t
&outLength
)
529 secdebug("atomicfile", "read %s: file yet not opened, opening", mPath
.c_str());
533 off_t bytesLeft
= inLength
;
536 secdebug("atomicfile", "%p free %s buffer %p", this, mPath
.c_str(), mBuffer
);
542 secdebug("atomicfile", "%p allocated %s buffer %p size %qd", this, mPath
.c_str(), mBuffer
, bytesLeft
);
544 off_t maxEnd
= inOffset
+ inLength
;
545 if (maxEnd
> mLength
)
550 outLength
= maxEnd
- inOffset
;
552 return mBuffer
+ inOffset
;
556 AtomicBufferedFile::close()
560 secdebug("atomicfile", "close %s: already closed", mPath
.c_str());
564 int result
= AtomicFile::rclose(mFileRef
);
569 secdebug("atomicfile", "close %s: %s", mPath
.c_str(), strerror(errno
));
570 UnixError::throwMe(error
);
573 secdebug("atomicfile", "%p closed %s", this, mPath
.c_str());
579 // AtomicTempFile - A temporary file to write changes to.
581 AtomicTempFile::AtomicTempFile(AtomicFile
&inFile
, const RefPointer
<AtomicLockedFile
> &inLockedFile
, mode_t mode
) :
583 mLockedFile(inLockedFile
),
589 AtomicTempFile::AtomicTempFile(AtomicFile
&inFile
, const RefPointer
<AtomicLockedFile
> &inLockedFile
) :
591 mLockedFile(inLockedFile
),
594 create(mFile
.mode());
597 AtomicTempFile::~AtomicTempFile()
599 // rollback if we didn't commit yet.
605 // Open the file and return the length in bytes.
608 AtomicTempFile::create(mode_t mode
)
610 // we now generate our temporary file name through sandbox API's.
612 // put the dir into a canonical form
613 string dir
= mFile
.dir();
614 int i
= (int)dir
.length() - 1;
616 // walk backwards until we get to a non / character
617 while (i
>= 0 && dir
[i
] == '/')
622 // point one beyond the string
625 const char* temp
= _amkrtemp((dir
.substr(0, i
) + "/" + mFile
.file()).c_str());
628 UnixError::throwMe(errno
);
634 const char *path
= mPath
.c_str();
636 mFileRef
= AtomicFile::ropen(path
, O_WRONLY
|O_CREAT
|O_TRUNC
, mode
);
640 secdebug("atomicfile", "open %s: %s", path
, strerror(error
));
642 // Do the obvious error code translations here.
643 // @@@ Consider moving these up a level.
645 CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED
);
647 UnixError::throwMe(error
);
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
656 if (::fchmod(mFileRef
, mode
))
659 secdebug("atomicfile", "fchmod %s: %s", path
, strerror(error
));
660 UnixError::throwMe(error
);
664 secdebug("atomicfile", "%p created %s", this, path
);
668 AtomicTempFile::write(AtomicFile::OffsetType inOffsetType
, off_t inOffset
, const uint32 inData
)
670 uint32 aData
= htonl(inData
);
671 write(inOffsetType
, inOffset
, reinterpret_cast<uint8
*>(&aData
), sizeof(aData
));
675 AtomicTempFile::write(AtomicFile::OffsetType inOffsetType
, off_t inOffset
,
676 const uint32
*inData
, uint32 inCount
)
678 #ifdef HOST_LONG_IS_NETWORK_LONG
679 // Optimize this for the case where hl == nl
680 const uint32
*aBuffer
= inData
;
682 auto_array
<uint32
> aBuffer(inCount
);
683 for (uint32 i
= 0; i
< inCount
; i
++)
684 aBuffer
.get()[i
] = htonl(inData
[i
]);
687 write(inOffsetType
, inOffset
, reinterpret_cast<const uint8
*>(aBuffer
.get()),
688 inCount
* sizeof(*inData
));
692 AtomicTempFile::write(AtomicFile::OffsetType inOffsetType
, off_t inOffset
, const uint8
*inData
, size_t inLength
)
695 if (inOffsetType
== AtomicFile::FromEnd
)
697 pos
= ::lseek(mFileRef
, 0, SEEK_END
);
701 secdebug("atomicfile", "lseek(%s, %qd): %s", mPath
.c_str(), inOffset
, strerror(error
));
702 UnixError::throwMe(error
);
705 else if (inOffsetType
== AtomicFile::FromStart
)
708 CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR
);
710 off_t bytesLeft
= inLength
;
711 const uint8
*ptr
= inData
;
714 size_t toWrite
= bytesLeft
> kAtomicFileMaxBlockSize
? kAtomicFileMaxBlockSize
: size_t(bytesLeft
);
715 ssize_t bytesWritten
= ::pwrite(mFileRef
, ptr
, toWrite
, pos
);
716 if (bytesWritten
== -1)
721 // We got interrupted by a signal, so try again.
722 secdebug("atomicfile", "write %s: interrupted, retrying", mPath
.c_str());
726 secdebug("atomicfile", "write %s: %s", mPath
.c_str(), strerror(error
));
727 UnixError::throwMe(error
);
730 // Write returning 0 is bad mmkay.
731 if (bytesWritten
== 0)
733 secdebug("atomicfile", "write %s: 0 bytes written", mPath
.c_str());
734 CssmError::throwMe(CSSMERR_DL_INTERNAL_ERROR
);
737 secdebug("atomicfile", "%p wrote %s %ld bytes from %p", this, mPath
.c_str(), bytesWritten
, ptr
);
739 bytesLeft
-= bytesWritten
;
746 AtomicTempFile::fsync()
750 secdebug("atomicfile", "fsync %s: already closed", mPath
.c_str());
757 result
= ::fsync(mFileRef
);
758 } while (result
&& errno
== EINTR
);
763 secdebug("atomicfile", "fsync %s: %s", mPath
.c_str(), strerror(errno
));
764 UnixError::throwMe(error
);
767 secdebug("atomicfile", "%p fsynced %s", this, mPath
.c_str());
772 AtomicTempFile::close()
776 secdebug("atomicfile", "close %s: already closed", mPath
.c_str());
780 int result
= AtomicFile::rclose(mFileRef
);
785 secdebug("atomicfile", "close %s: %s", mPath
.c_str(), strerror(errno
));
786 UnixError::throwMe(error
);
789 secdebug("atomicfile", "%p closed %s", this, mPath
.c_str());
793 // Commit the current create or write and close the write file. Note that a throw during the commit does an automatic rollback.
795 AtomicTempFile::commit()
801 const char *oldPath
= mPath
.c_str();
802 const char *newPath
= mFile
.path().c_str();
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.
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".
813 s
= copyfile_state_alloc();
815 if(copyfile(newPath
, oldPath
, s
, COPYFILE_SECURITY
| COPYFILE_NOFOLLOW
) == -1) // Not fatal
816 secdebug("atomicfile", "copyfile (%s, %s): %s", oldPath
, newPath
, strerror(errno
));
818 copyfile_state_free(s
);
819 // END <rdar://problem/6991037>
821 ::utimes(oldPath
, NULL
);
823 if (::rename(oldPath
, newPath
) == -1)
826 secdebug("atomicfile", "rename (%s, %s): %s", oldPath
, newPath
, strerror(errno
));
827 UnixError::throwMe(error
);
830 // Unlock the lockfile
833 secdebug("atomicfile", "%p commited %s", this, oldPath
);
842 // Rollback the current create or write (happens automatically if commit() isn't called before the destructor is.
844 AtomicTempFile::rollback() throw()
848 AtomicFile::rclose(mFileRef
);
852 // @@@ Log errors if this fails.
853 const char *path
= mPath
.c_str();
854 if (::unlink(path
) == -1)
856 secdebug("atomicfile", "unlink %s: %s", path
, strerror(errno
));
857 // rollback can't throw
860 // @@@ Think about this. Depending on how we do locking we might not need this.
863 const char *path
= mFile
.path().c_str();
864 if (::unlink(path
) == -1)
866 secdebug("atomicfile", "unlink %s: %s", path
, strerror(errno
));
867 // rollback can't throw
874 // An advisory write lock for inFile.
876 FileLocker::~FileLocker()
882 LocalFileLocker::LocalFileLocker(AtomicFile
&inFile
) :
883 mPath(inFile
.lockFileName())
888 LocalFileLocker::~LocalFileLocker()
895 static double GetTime()
898 gettimeofday(&t
, NULL
);
899 return ((double) t
.tv_sec
) + ((double) t
.tv_usec
) / 1000000.0;
906 LocalFileLocker::lock(mode_t mode
)
912 // if the lock file doesn't exist, create it
913 mLockFile
= open(mPath
.c_str(), O_RDONLY
| O_CREAT
, mode
);
915 // if we can't open or create the file, something is wrong
918 UnixError::throwMe(errno
);
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());
926 IFDEBUG(secdebug("atomictime", "Waited %.4f milliseconds for file lock", (endTime
- startTime
) * 1000.0));
928 // errors at this point are bad
931 UnixError::throwMe(errno
);
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.
937 result
= fstat(mLockFile
, &st
);
939 // errors at this point are bad
942 UnixError::throwMe(errno
);
945 if (st
.st_nlink
== 0) // we've been unlinked!
949 } while (st
.st_nlink
== 0);
954 LocalFileLocker::unlock()
956 flock(mLockFile
, LOCK_UN
);
962 NetworkFileLocker::NetworkFileLocker(AtomicFile
&inFile
) :
964 mPath(inFile
.dir() + "lck~" + inFile
.file())
968 NetworkFileLocker::~NetworkFileLocker()
973 NetworkFileLocker::unique(mode_t mode
)
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
];
985 for (int retries
= 0; retries
< 10; ++retries
)
987 /* Make a random filename. */
988 randomGen
.random(buf
, randomPart
);
989 for (int ix
= 0; ix
< randomPart
; ++ix
)
991 char ch
= buf
[ix
] & 0x3f;
992 fullname
[ix
+ dirSize
+ 1] = ch
+
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);
1000 result
= lstat(fullname
.c_str(), &filebuf
);
1001 if (result
&& errno
== ENAMETOOLONG
)
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 */
1008 if (result
&& errno
== ENOENT
)
1010 fd
= AtomicFile::ropen(fullname
.c_str(), O_WRONLY
|O_CREAT
|O_EXCL
, mode
);
1011 if (fd
>= 0 || errno
!= EEXIST
)
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
);
1024 /* @@@ Check for EINTR. */
1025 write(fd
, "0", 1); /* pid 0, `works' across networks */
1027 AtomicFile::rclose(fd
);
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. */
1034 NetworkFileLocker::rlink(const char *const old
, const char *const newn
, struct stat
&sto
)
1036 int result
= ::link(old
,newn
);
1040 if (::lstat(old
, &sto
) == 0)
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
))
1050 /* Link failed but files are the same so the link really went ok. */
1056 errno
= serrno
; /* Restore errno from link() */
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. */
1066 NetworkFileLocker::myrename(const char *const old
, const char *const newn
)
1072 /* Try a real hardlink */
1073 ret
= rlink(old
, newn
, stbuf
);
1076 if (stbuf
.st_nlink
< 2 && (errno
== EXDEV
|| errno
== ENOTSUP
))
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
);
1085 /* We want the errno from the link or the ropen, not that of the unlink. */
1088 /* Unlink the temp file. */
1091 AtomicFile::rclose(fd
);
1098 NetworkFileLocker::xcreat(const char *const name
, mode_t mode
, time_t &tim
)
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
);
1109 NetworkFileLocker::lock(mode_t mode
)
1111 const char *path
= mPath
.c_str();
1112 bool triedforce
= false;
1114 time_t t
, locktimeout
= 1024; /* DEFlocktimeout, 17 minutes. */
1115 bool doSyslog
= false;
1116 bool failed
= false;
1121 /* Don't syslog first time through. */
1123 ::syslog(LOG_NOTICE
, "Locking %s", path
);
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 */
1130 /* lock acquired, hurray! */
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. */
1142 /* Already tried, force lock override, not trying again */
1146 else if (S_ISDIR(stbuf
.st_mode
) || ::unlink(path
))
1149 ::syslog(LOG_ERR
, "Forced unlock denied on %s", path
);
1150 secdebug("atomicfile", "Forced unlock denied on %s", path
);
1154 ::syslog(LOG_ERR
, "Forcing lock on %s", path
);
1155 secdebug("atomicfile", "Forcing lock on %s", path
);
1156 sleep(16 /* DEFsuspend */);
1161 triedforce
= false; /* legitimate iteration, clear flag */
1163 /* Reset retry counter. */
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? */
1176 if(++retries
< (256 + 1)) /* nfsTRY number of times+1 to ignore spurious NFS errors */
1183 case ENAMETOOLONG
: /* Filename is too long, shorten and retry */
1184 if (mPath
.size() > mDir
.size() + 8)
1186 secdebug("atomicfile", "Truncating %s and retrying lock", path
);
1187 mPath
.erase(mPath
.end() - 1);
1188 path
= mPath
.c_str();
1189 /* Reset retry counter. */
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
);
1211 NetworkFileLocker::unlock()
1213 const char *path
= mPath
.c_str();
1214 if (::unlink(path
) == -1)
1216 secdebug("atomicfile", "unlink %s: %s", path
, strerror(errno
));
1217 // unlock can't throw
1223 AtomicLockedFile::AtomicLockedFile(AtomicFile
&inFile
)
1225 if (inFile
.isOnLocalFileSystem())
1227 mFileLocker
= new LocalFileLocker(inFile
);
1231 mFileLocker
= new NetworkFileLocker(inFile
);
1239 AtomicLockedFile::~AtomicLockedFile()
1248 AtomicLockedFile::lock(mode_t mode
)
1250 mFileLocker
->lock(mode
);
1255 void AtomicLockedFile::unlock() throw()
1257 mFileLocker
->unlock();
1262 #undef kAtomicFileMaxBlockSize