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>
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 // Aquire the write lock and remove the file.
95 AtomicFile::performDelete()
97 AtomicLockedFile
lock(*this);
98 if (::unlink(mPath
.c_str()) != 0)
101 secdebug("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 // Aquire 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 secdebug("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 secdebug("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 secdebug("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 secdebug("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 secdebug("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
);
347 int result
= sandbox_check(getpid(), "file-write-data", (sandbox_filter_type
) (SANDBOX_FILTER_PATH
| SANDBOX_CHECK_NO_REPORT
), name
);
357 fd
= ::open(name
, flags
, mode
);
358 } while (fd
< 0 && (errno
== EINTR
|| (errno
== ENFILE
&& --tries_left
>= 0)));
364 AtomicFile::rclose(int fd
)
369 result
= ::close(fd
);
370 } while(result
&& errno
== EINTR
);
376 // AtomicBufferedFile - This represents an instance of a file opened for reading.
377 // The file is read into memory and closed after this is done.
378 // The memory is released when this object is destroyed.
380 AtomicBufferedFile::AtomicBufferedFile(const std::string
&inPath
, bool isLocal
) :
389 AtomicBufferedFile::~AtomicBufferedFile()
393 // In release mode, the assert() is compiled out so rv may be unused.
394 __unused
int rv
= AtomicFile::rclose(mFileRef
);
396 secdebug("atomicfile", "%p closed %s", this, mPath
.c_str());
401 secdebug("atomicfile", "%p free %s buffer %p", this, mPath
.c_str(), mBuffer
);
407 // Open the file and return the length in bytes.
410 AtomicBufferedFile::open()
412 const char *path
= mPath
.c_str();
415 secdebug("atomicfile", "open %s: already open, closing and reopening", path
);
419 mFileRef
= AtomicFile::ropen(path
, O_RDONLY
, 0);
423 secdebug("atomicfile", "open %s: %s", path
, strerror(error
));
425 // Do the obvious error code translations here.
426 // @@@ Consider moving these up a level.
428 CssmError::throwMe(CSSMERR_DL_DATASTORE_DOESNOT_EXIST
);
429 else if (error
== EACCES
)
430 CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED
);
432 UnixError::throwMe(error
);
436 int result
= fstat(mFileRef
, &st
);
439 mLength
= st
.st_size
;
444 secdebug("atomicfile", "lseek(%s, END): %s", path
, strerror(error
));
445 AtomicFile::rclose(mFileRef
);
447 UnixError::throwMe(error
);
450 secdebug("atomicfile", "%p opened %s: %qd bytes", this, path
, mLength
);
456 // Unload the contents of the file.
459 AtomicBufferedFile::unloadBuffer()
467 munmap(mBuffer
, (size_t)mLength
);
472 // Load the contents of the file into memory.
473 // If we are on a local file system, we mmap the file. Otherwise, we
474 // read it all into memory
476 AtomicBufferedFile::loadBuffer()
480 // make a buffer big enough to hold the entire file
481 mBuffer
= new uint8
[mLength
];
482 lseek(mFileRef
, 0, SEEK_SET
);
485 ssize_t bytesToRead
= (ssize_t
)mLength
;
486 while (bytesToRead
> 0)
488 ssize_t bytesRead
= ::read(mFileRef
, mBuffer
+ pos
, bytesToRead
);
494 secdebug("atomicfile", "lseek(%s, END): %s", mPath
.c_str(), strerror(error
));
496 AtomicFile::rclose(mFileRef
);
499 UnixError::throwMe(error
);
504 bytesToRead
-= bytesRead
;
511 // mmap the buffer into place
512 mBuffer
= (uint8
*) mmap(NULL
, (size_t)mLength
, PROT_READ
, MAP_PRIVATE
, mFileRef
, 0);
513 if (mBuffer
== (uint8
*) -1)
516 secdebug("atomicfile", "lseek(%s, END): %s", mPath
.c_str(), strerror(error
));
518 AtomicFile::rclose(mFileRef
);
521 UnixError::throwMe(error
);
529 // Read the file starting at inOffset for inLength bytes into the buffer and return
530 // a pointer to it. On return outLength contain the actual number of bytes read, it
531 // will only ever be less than inLength if EOF was reached, and it will never be more
535 AtomicBufferedFile::read(off_t inOffset
, off_t inLength
, off_t
&outLength
)
539 secdebug("atomicfile", "read %s: file yet not opened, opening", mPath
.c_str());
543 off_t bytesLeft
= inLength
;
546 secdebug("atomicfile", "%p free %s buffer %p", this, mPath
.c_str(), mBuffer
);
552 secdebug("atomicfile", "%p allocated %s buffer %p size %qd", this, mPath
.c_str(), mBuffer
, bytesLeft
);
554 off_t maxEnd
= inOffset
+ inLength
;
555 if (maxEnd
> mLength
)
560 outLength
= maxEnd
- inOffset
;
562 return mBuffer
+ inOffset
;
566 AtomicBufferedFile::close()
570 secdebug("atomicfile", "close %s: already closed", mPath
.c_str());
574 int result
= AtomicFile::rclose(mFileRef
);
579 secdebug("atomicfile", "close %s: %s", mPath
.c_str(), strerror(errno
));
580 UnixError::throwMe(error
);
583 secdebug("atomicfile", "%p closed %s", this, mPath
.c_str());
589 // AtomicTempFile - A temporary file to write changes to.
591 AtomicTempFile::AtomicTempFile(AtomicFile
&inFile
, const RefPointer
<AtomicLockedFile
> &inLockedFile
, mode_t mode
) :
593 mLockedFile(inLockedFile
),
599 AtomicTempFile::AtomicTempFile(AtomicFile
&inFile
, const RefPointer
<AtomicLockedFile
> &inLockedFile
) :
601 mLockedFile(inLockedFile
),
604 create(mFile
.mode());
607 AtomicTempFile::~AtomicTempFile()
609 // rollback if we didn't commit yet.
615 // Open the file and return the length in bytes.
618 AtomicTempFile::create(mode_t mode
)
620 // we now generate our temporary file name through sandbox API's.
622 // put the dir into a canonical form
623 string dir
= mFile
.dir();
624 int i
= (int)dir
.length() - 1;
626 // walk backwards until we get to a non / character
627 while (i
>= 0 && dir
[i
] == '/')
632 // point one beyond the string
635 const char* temp
= _amkrtemp((dir
.substr(0, i
) + "/" + mFile
.file()).c_str());
638 UnixError::throwMe(errno
);
644 const char *path
= mPath
.c_str();
646 mFileRef
= AtomicFile::ropen(path
, O_WRONLY
|O_CREAT
|O_TRUNC
, mode
);
650 secdebug("atomicfile", "open %s: %s", path
, strerror(error
));
652 // Do the obvious error code translations here.
653 // @@@ Consider moving these up a level.
655 CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED
);
657 UnixError::throwMe(error
);
660 // If we aren't creating the inital file, make sure we preserve
661 // the mode of the old file regardless of the current umask.
662 // If we are creating the inital file we respect the users
666 if (::fchmod(mFileRef
, mode
))
669 secdebug("atomicfile", "fchmod %s: %s", path
, strerror(error
));
670 UnixError::throwMe(error
);
674 secdebug("atomicfile", "%p created %s", this, path
);
678 AtomicTempFile::write(AtomicFile::OffsetType inOffsetType
, off_t inOffset
, const uint32 inData
)
680 uint32 aData
= htonl(inData
);
681 write(inOffsetType
, inOffset
, reinterpret_cast<uint8
*>(&aData
), sizeof(aData
));
685 AtomicTempFile::write(AtomicFile::OffsetType inOffsetType
, off_t inOffset
,
686 const uint32
*inData
, uint32 inCount
)
688 #ifdef HOST_LONG_IS_NETWORK_LONG
689 // Optimize this for the case where hl == nl
690 const uint32
*aBuffer
= inData
;
692 auto_array
<uint32
> aBuffer(inCount
);
693 for (uint32 i
= 0; i
< inCount
; i
++)
694 aBuffer
.get()[i
] = htonl(inData
[i
]);
697 write(inOffsetType
, inOffset
, reinterpret_cast<const uint8
*>(aBuffer
.get()),
698 inCount
* sizeof(*inData
));
702 AtomicTempFile::write(AtomicFile::OffsetType inOffsetType
, off_t inOffset
, const uint8
*inData
, size_t inLength
)
705 if (inOffsetType
== AtomicFile::FromEnd
)
707 pos
= ::lseek(mFileRef
, 0, SEEK_END
);
711 secdebug("atomicfile", "lseek(%s, %qd): %s", mPath
.c_str(), inOffset
, strerror(error
));
712 UnixError::throwMe(error
);
715 else if (inOffsetType
== AtomicFile::FromStart
)
718 CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR
);
720 off_t bytesLeft
= inLength
;
721 const uint8
*ptr
= inData
;
724 size_t toWrite
= bytesLeft
> kAtomicFileMaxBlockSize
? kAtomicFileMaxBlockSize
: size_t(bytesLeft
);
725 ssize_t bytesWritten
= ::pwrite(mFileRef
, ptr
, toWrite
, pos
);
726 if (bytesWritten
== -1)
731 // We got interrupted by a signal, so try again.
732 secdebug("atomicfile", "write %s: interrupted, retrying", mPath
.c_str());
736 secdebug("atomicfile", "write %s: %s", mPath
.c_str(), strerror(error
));
737 UnixError::throwMe(error
);
740 // Write returning 0 is bad mmkay.
741 if (bytesWritten
== 0)
743 secdebug("atomicfile", "write %s: 0 bytes written", mPath
.c_str());
744 CssmError::throwMe(CSSMERR_DL_INTERNAL_ERROR
);
747 secdebug("atomicfile", "%p wrote %s %ld bytes from %p", this, mPath
.c_str(), bytesWritten
, ptr
);
749 bytesLeft
-= bytesWritten
;
756 AtomicTempFile::fsync()
760 secdebug("atomicfile", "fsync %s: already closed", mPath
.c_str());
767 result
= ::fsync(mFileRef
);
768 } while (result
&& errno
== EINTR
);
773 secdebug("atomicfile", "fsync %s: %s", mPath
.c_str(), strerror(errno
));
774 UnixError::throwMe(error
);
777 secdebug("atomicfile", "%p fsynced %s", this, mPath
.c_str());
782 AtomicTempFile::close()
786 secdebug("atomicfile", "close %s: already closed", mPath
.c_str());
790 int result
= AtomicFile::rclose(mFileRef
);
795 secdebug("atomicfile", "close %s: %s", mPath
.c_str(), strerror(errno
));
796 UnixError::throwMe(error
);
799 secdebug("atomicfile", "%p closed %s", this, mPath
.c_str());
803 // Commit the current create or write and close the write file. Note that a throw during the commit does an automatic rollback.
805 AtomicTempFile::commit()
811 const char *oldPath
= mPath
.c_str();
812 const char *newPath
= mFile
.path().c_str();
814 // <rdar://problem/6991037>
815 // Copy the security parameters of one file to another
816 // Adding this to guard against setuid utilities that are re-writing a user's keychain. We don't want to leave them root-owned.
817 // In order to not break backward compatability we'll make a best effort, but continue if these efforts fail.
819 // To clear something up - newPath is the name the keychain will become - which is the name of the file being replaced
820 // oldPath is the "temp filename".
823 s
= copyfile_state_alloc();
825 if(copyfile(newPath
, oldPath
, s
, COPYFILE_SECURITY
| COPYFILE_NOFOLLOW
) == -1) // Not fatal
826 secdebug("atomicfile", "copyfile (%s, %s): %s", oldPath
, newPath
, strerror(errno
));
828 copyfile_state_free(s
);
829 // END <rdar://problem/6991037>
831 ::utimes(oldPath
, NULL
);
833 if (::rename(oldPath
, newPath
) == -1)
836 secdebug("atomicfile", "rename (%s, %s): %s", oldPath
, newPath
, strerror(errno
));
837 UnixError::throwMe(error
);
840 // Unlock the lockfile
843 secdebug("atomicfile", "%p commited %s", this, oldPath
);
852 // Rollback the current create or write (happens automatically if commit() isn't called before the destructor is.
854 AtomicTempFile::rollback() throw()
858 AtomicFile::rclose(mFileRef
);
862 // @@@ Log errors if this fails.
863 const char *path
= mPath
.c_str();
864 if (::unlink(path
) == -1)
866 secdebug("atomicfile", "unlink %s: %s", path
, strerror(errno
));
867 // rollback can't throw
870 // @@@ Think about this. Depending on how we do locking we might not need this.
873 const char *path
= mFile
.path().c_str();
874 if (::unlink(path
) == -1)
876 secdebug("atomicfile", "unlink %s: %s", path
, strerror(errno
));
877 // rollback can't throw
884 // An advisory write lock for inFile.
886 FileLocker::~FileLocker()
892 LocalFileLocker::LocalFileLocker(AtomicFile
&inFile
) :
893 mPath(inFile
.lockFileName())
898 LocalFileLocker::~LocalFileLocker()
905 static double GetTime()
908 gettimeofday(&t
, NULL
);
909 return ((double) t
.tv_sec
) + ((double) t
.tv_usec
) / 1000000.0;
916 LocalFileLocker::lock(mode_t mode
)
922 // if the lock file doesn't exist, create it
923 mLockFile
= open(mPath
.c_str(), O_RDONLY
| O_CREAT
, mode
);
925 // if we can't open or create the file, something is wrong
928 UnixError::throwMe(errno
);
931 // try to get exclusive access to the file
932 IFDEBUG(double startTime
= GetTime());
933 int result
= flock(mLockFile
, LOCK_EX
);
934 IFDEBUG(double endTime
= GetTime());
936 IFDEBUG(secdebug("atomictime", "Waited %.4f milliseconds for file lock", (endTime
- startTime
) * 1000.0));
938 // errors at this point are bad
941 UnixError::throwMe(errno
);
944 // check and see if the file we have access to still exists. If not, another file shared our file lock
945 // due to a hash collision and has thrown our lock away -- that, or a user blew the lock file away himself.
947 result
= fstat(mLockFile
, &st
);
949 // errors at this point are bad
952 UnixError::throwMe(errno
);
955 if (st
.st_nlink
== 0) // we've been unlinked!
959 } while (st
.st_nlink
== 0);
964 LocalFileLocker::unlock()
966 flock(mLockFile
, LOCK_UN
);
972 NetworkFileLocker::NetworkFileLocker(AtomicFile
&inFile
) :
974 mPath(inFile
.dir() + "lck~" + inFile
.file())
978 NetworkFileLocker::~NetworkFileLocker()
983 NetworkFileLocker::unique(mode_t mode
)
985 static const int randomPart
= 16;
986 DevRandomGenerator randomGen
;
987 std::string::size_type dirSize
= mDir
.size();
988 std::string
fullname(dirSize
+ randomPart
+ 2, '\0');
989 fullname
.replace(0, dirSize
, mDir
);
990 fullname
[dirSize
] = '~'; /* UNIQ_PREFIX */
991 char buf
[randomPart
];
995 for (int retries
= 0; retries
< 10; ++retries
)
997 /* Make a random filename. */
998 randomGen
.random(buf
, randomPart
);
999 for (int ix
= 0; ix
< randomPart
; ++ix
)
1001 char ch
= buf
[ix
] & 0x3f;
1002 fullname
[ix
+ dirSize
+ 1] = ch
+
1004 : ch
< 26 + 26 ? 'a' - 26
1005 : ch
< 26 + 26 + 10 ? '0' - 26 - 26
1006 : ch
== 26 + 26 + 10 ? '-' - 26 - 26 - 10
1007 : '_' - 26 - 26 - 11);
1010 result
= lstat(fullname
.c_str(), &filebuf
);
1011 if (result
&& errno
== ENAMETOOLONG
)
1014 fullname
.erase(fullname
.end() - 1);
1015 while((result
= lstat(fullname
.c_str(), &filebuf
)) && errno
== ENAMETOOLONG
&& fullname
.size() > dirSize
+ 8);
1016 } /* either it stopped being a problem or we ran out of filename */
1018 if (result
&& errno
== ENOENT
)
1020 fd
= AtomicFile::ropen(fullname
.c_str(), O_WRONLY
|O_CREAT
|O_EXCL
, mode
);
1021 if (fd
>= 0 || errno
!= EEXIST
)
1029 ::syslog(LOG_ERR
, "Couldn't create temp file %s: %s", fullname
.c_str(), strerror(error
));
1030 secdebug("atomicfile", "Couldn't create temp file %s: %s", fullname
.c_str(), strerror(error
));
1031 UnixError::throwMe(error
);
1034 /* @@@ Check for EINTR. */
1035 write(fd
, "0", 1); /* pid 0, `works' across networks */
1037 AtomicFile::rclose(fd
);
1042 /* Return 0 on success and 1 on failure if st is set to the result of stat(old) and -1 on failure if the stat(old) failed. */
1044 NetworkFileLocker::rlink(const char *const old
, const char *const newn
, struct stat
&sto
)
1046 int result
= ::link(old
,newn
);
1050 if (::lstat(old
, &sto
) == 0)
1053 if (::lstat(newn
, &stn
) == 0
1054 && sto
.st_dev
== stn
.st_dev
1055 && sto
.st_ino
== stn
.st_ino
1056 && sto
.st_uid
== stn
.st_uid
1057 && sto
.st_gid
== stn
.st_gid
1058 && !S_ISLNK(sto
.st_mode
))
1060 /* Link failed but files are the same so the link really went ok. */
1066 errno
= serrno
; /* Restore errno from link() */
1072 /* NFS-resistant rename()
1073 * rename with fallback for systems that don't support it
1074 * Note that this does not preserve the contents of the file. */
1076 NetworkFileLocker::myrename(const char *const old
, const char *const newn
)
1082 /* Try a real hardlink */
1083 ret
= rlink(old
, newn
, stbuf
);
1086 if (stbuf
.st_nlink
< 2 && (errno
== EXDEV
|| errno
== ENOTSUP
))
1088 /* Hard link failed so just create a new file with O_EXCL instead. */
1089 fd
= AtomicFile::ropen(newn
, O_WRONLY
|O_CREAT
|O_EXCL
, stbuf
.st_mode
);
1095 /* We want the errno from the link or the ropen, not that of the unlink. */
1098 /* Unlink the temp file. */
1101 AtomicFile::rclose(fd
);
1108 NetworkFileLocker::xcreat(const char *const name
, mode_t mode
, time_t &tim
)
1110 std::string uniqueName
= unique(mode
);
1111 const char *uniquePath
= uniqueName
.c_str();
1112 struct stat stbuf
; /* return the filesystem time to the caller */
1113 stat(uniquePath
, &stbuf
);
1114 tim
= stbuf
.st_mtime
;
1115 return myrename(uniquePath
, name
);
1119 NetworkFileLocker::lock(mode_t mode
)
1121 const char *path
= mPath
.c_str();
1122 bool triedforce
= false;
1124 time_t t
, locktimeout
= 1024; /* DEFlocktimeout, 17 minutes. */
1125 bool doSyslog
= false;
1126 bool failed
= false;
1131 /* Don't syslog first time through. */
1133 ::syslog(LOG_NOTICE
, "Locking %s", path
);
1137 secdebug("atomicfile", "Locking %s", path
); /* in order to cater for clock skew: get */
1138 if (!xcreat(path
, mode
, t
)) /* time t from the filesystem */
1140 /* lock acquired, hurray! */
1145 case EEXIST
: /* check if it's time for a lock override */
1146 if (!lstat(path
, &stbuf
) && stbuf
.st_size
<= 16 /* MAX_locksize */ && locktimeout
1147 && !lstat(path
, &stbuf
) && locktimeout
< t
- stbuf
.st_mtime
)
1148 /* stat() till unlink() should be atomic, but can't guarantee that. */
1152 /* Already tried, force lock override, not trying again */
1156 else if (S_ISDIR(stbuf
.st_mode
) || ::unlink(path
))
1159 ::syslog(LOG_ERR
, "Forced unlock denied on %s", path
);
1160 secdebug("atomicfile", "Forced unlock denied on %s", path
);
1164 ::syslog(LOG_ERR
, "Forcing lock on %s", path
);
1165 secdebug("atomicfile", "Forcing lock on %s", path
);
1166 sleep(16 /* DEFsuspend */);
1171 triedforce
= false; /* legitimate iteration, clear flag */
1173 /* Reset retry counter. */
1178 case ENOSPC
: /* no space left, treat it as a transient */
1179 #ifdef EDQUOT /* NFS failure */
1180 case EDQUOT
: /* maybe it was a short term shortage? */
1186 if(++retries
< (256 + 1)) /* nfsTRY number of times+1 to ignore spurious NFS errors */
1193 case ENAMETOOLONG
: /* Filename is too long, shorten and retry */
1194 if (mPath
.size() > mDir
.size() + 8)
1196 secdebug("atomicfile", "Truncating %s and retrying lock", path
);
1197 mPath
.erase(mPath
.end() - 1);
1198 path
= mPath
.c_str();
1199 /* Reset retry counter. */
1214 ::syslog(LOG_ERR
, "Lock failure on %s: %s", path
, strerror(error
));
1215 secdebug("atomicfile", "Lock failure on %s: %s", path
, strerror(error
));
1216 UnixError::throwMe(error
);
1221 NetworkFileLocker::unlock()
1223 const char *path
= mPath
.c_str();
1224 if (::unlink(path
) == -1)
1226 secdebug("atomicfile", "unlink %s: %s", path
, strerror(errno
));
1227 // unlock can't throw
1233 AtomicLockedFile::AtomicLockedFile(AtomicFile
&inFile
)
1235 if (inFile
.isOnLocalFileSystem())
1237 mFileLocker
= new LocalFileLocker(inFile
);
1241 mFileLocker
= new NetworkFileLocker(inFile
);
1249 AtomicLockedFile::~AtomicLockedFile()
1258 AtomicLockedFile::lock(mode_t mode
)
1260 mFileLocker
->lock(mode
);
1265 void AtomicLockedFile::unlock() throw()
1267 mFileLocker
->unlock();
1272 #undef kAtomicFileMaxBlockSize