2 * Copyright (c) 2000-2012 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(), 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
);
233 static std::string
RemoveDoubleSlashes(const std::string
&path
)
237 for (i
= 0; i
< path
.length(); ++i
)
240 if ((i
< path
.length() - 2) && path
[i
] == '/' && path
[i
+ 1] == '/')
242 i
+= 1; // skip a second '/'
252 // Make sure the directory up to inDir exists inDir *must* end in a slash.
255 AtomicFile::mkpath(const std::string
&inDir
, mode_t mode
)
257 for (std::string::size_type pos
= 0; (pos
= inDir
.find('/', pos
+ 1)) != std::string::npos
;)
259 std::string path
= inDir
.substr(0, pos
);
260 const char *cpath
= path
.c_str();
262 if (::stat(cpath
, &sb
))
264 // if we are creating a path in the user's home directory, override the user's mode
265 std::string homedir
= getenv("HOME");
267 // canonicalize the path (remove double slashes)
268 string canonPath
= RemoveDoubleSlashes(cpath
);
270 if (canonPath
.find(homedir
, 0) == 0)
275 if (errno
!= ENOENT
|| ::mkdir(cpath
, mode
))
276 UnixError::throwMe(errno
);
278 else if (!S_ISDIR(sb
.st_mode
))
279 CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED
); // @@@ Should be is a directory
284 AtomicFile::ropen(const char *const name
, int flags
, mode_t mode
)
286 bool isCreate
= (flags
& O_CREAT
) != 0;
289 The purpose of checkForRead and checkForWrite is to mitigate
290 spamming of the log when a user has installed certain third
291 party software packages which create additional keychains.
292 Certain applications use a custom sandbox profile which do not
293 permit this and so the user gets a ton of spam in the log.
294 This turns into a serious performance problem.
296 We handle this situation by checking two factors:
298 1: If the user is trying to create a file, we send the
299 request directly to open. This is the right thing
300 to do, as we don't want most applications creating
301 keychains unless they have been expressly authorized
304 The layers above this one only set O_CREAT when a file
305 doesn't exist, so the case where O_CREAT can be called
306 on an existing file is irrelevant.
308 2: If the user is trying to open the file for reading or
309 writing, we check with the sandbox mechanism to see if
310 the operation will be permitted (and tell it not to
311 log if it the operation will fail).
313 If the operation is not permitted, we return -1 which
314 emulates the behavior of open. sandbox_check sets
315 errno properly, so the layers which call this function
316 will be able to act as though open had been called.
319 bool checkForRead
= false;
320 bool checkForWrite
= false;
322 int fd
, tries_left
= 4 /* kNoResRetry */;
326 switch (flags
& O_ACCMODE
)
332 checkForWrite
= true;
336 checkForWrite
= true;
342 int result
= sandbox_check(getpid(), "file-read-data", (sandbox_filter_type
) (SANDBOX_FILTER_PATH
| SANDBOX_CHECK_NO_REPORT
), name
);
351 int result
= sandbox_check(getpid(), "file-write-data", (sandbox_filter_type
) (SANDBOX_FILTER_PATH
| SANDBOX_CHECK_NO_REPORT
), name
);
361 fd
= ::open(name
, flags
, mode
);
362 } while (fd
< 0 && (errno
== EINTR
|| (errno
== ENFILE
&& --tries_left
>= 0)));
368 AtomicFile::rclose(int fd
)
373 result
= ::close(fd
);
374 } while(result
&& errno
== EINTR
);
380 // AtomicBufferedFile - This represents an instance of a file opened for reading.
381 // The file is read into memory and closed after this is done.
382 // The memory is released when this object is destroyed.
384 AtomicBufferedFile::AtomicBufferedFile(const std::string
&inPath
, bool isLocal
) :
393 AtomicBufferedFile::~AtomicBufferedFile()
397 AtomicFile::rclose(mFileRef
);
398 secdebug("atomicfile", "%p closed %s", this, mPath
.c_str());
403 secdebug("atomicfile", "%p free %s buffer %p", this, mPath
.c_str(), mBuffer
);
409 // Open the file and return the length in bytes.
412 AtomicBufferedFile::open()
414 const char *path
= mPath
.c_str();
417 secdebug("atomicfile", "open %s: already open, closing and reopening", path
);
421 mFileRef
= AtomicFile::ropen(path
, O_RDONLY
, 0);
425 secdebug("atomicfile", "open %s: %s", path
, strerror(error
));
427 // Do the obvious error code translations here.
428 // @@@ Consider moving these up a level.
430 CssmError::throwMe(CSSMERR_DL_DATASTORE_DOESNOT_EXIST
);
431 else if (error
== EACCES
)
432 CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED
);
434 UnixError::throwMe(error
);
438 int result
= fstat(mFileRef
, &st
);
441 mLength
= st
.st_size
;
446 secdebug("atomicfile", "lseek(%s, END): %s", path
, strerror(error
));
447 AtomicFile::rclose(mFileRef
);
448 UnixError::throwMe(error
);
451 secdebug("atomicfile", "%p opened %s: %qd bytes", this, path
, mLength
);
457 // Unload the contents of the file.
460 AtomicBufferedFile::unloadBuffer()
468 munmap(mBuffer
, mLength
);
473 // Load the contents of the file into memory.
474 // If we are on a local file system, we mmap the file. Otherwise, we
475 // read it all into memory
477 AtomicBufferedFile::loadBuffer()
481 // make a buffer big enough to hold the entire file
482 mBuffer
= new uint8
[mLength
];
483 lseek(mFileRef
, 0, SEEK_SET
);
486 ssize_t bytesToRead
= mLength
;
487 while (bytesToRead
> 0)
489 ssize_t bytesRead
= ::read(mFileRef
, mBuffer
+ pos
, bytesToRead
);
495 secdebug("atomicfile", "lseek(%s, END): %s", mPath
.c_str(), strerror(error
));
496 AtomicFile::rclose(mFileRef
);
497 UnixError::throwMe(error
);
502 bytesToRead
-= bytesRead
;
509 // mmap the buffer into place
510 mBuffer
= (uint8
*) mmap(NULL
, mLength
, PROT_READ
, MAP_PRIVATE
, mFileRef
, 0);
511 if (mBuffer
== (uint8
*) -1)
514 secdebug("atomicfile", "lseek(%s, END): %s", mPath
.c_str(), strerror(error
));
515 AtomicFile::rclose(mFileRef
);
516 UnixError::throwMe(error
);
524 // Read the file starting at inOffset for inLength bytes into the buffer and return
525 // a pointer to it. On return outLength contain the actual number of bytes read, it
526 // will only ever be less than inLength if EOF was reached, and it will never be more
530 AtomicBufferedFile::read(off_t inOffset
, off_t inLength
, off_t
&outLength
)
534 secdebug("atomicfile", "read %s: file yet not opened, opening", mPath
.c_str());
538 off_t bytesLeft
= inLength
;
541 secdebug("atomicfile", "%p free %s buffer %p", this, mPath
.c_str(), mBuffer
);
547 secdebug("atomicfile", "%p allocated %s buffer %p size %qd", this, mPath
.c_str(), mBuffer
, bytesLeft
);
549 ssize_t maxEnd
= inOffset
+ inLength
;
550 if (maxEnd
> mLength
)
555 outLength
= maxEnd
- inOffset
;
557 return mBuffer
+ inOffset
;
561 AtomicBufferedFile::close()
565 secdebug("atomicfile", "close %s: already closed", mPath
.c_str());
569 int result
= AtomicFile::rclose(mFileRef
);
574 secdebug("atomicfile", "close %s: %s", mPath
.c_str(), strerror(errno
));
575 UnixError::throwMe(error
);
578 secdebug("atomicfile", "%p closed %s", this, mPath
.c_str());
584 // AtomicTempFile - A temporary file to write changes to.
586 AtomicTempFile::AtomicTempFile(AtomicFile
&inFile
, const RefPointer
<AtomicLockedFile
> &inLockedFile
, mode_t mode
) :
588 mLockedFile(inLockedFile
),
594 AtomicTempFile::AtomicTempFile(AtomicFile
&inFile
, const RefPointer
<AtomicLockedFile
> &inLockedFile
) :
596 mLockedFile(inLockedFile
),
599 create(mFile
.mode());
602 AtomicTempFile::~AtomicTempFile()
604 // rollback if we didn't commit yet.
610 // Open the file and return the length in bytes.
613 AtomicTempFile::create(mode_t mode
)
615 // we now generate our temporary file name through sandbox API's.
617 // put the dir into a canonical form
618 string dir
= mFile
.dir();
619 int i
= dir
.length() - 1;
621 // walk backwards until we get to a non / character
622 while (i
>= 0 && dir
[i
] == '/')
627 // point one beyond the string
630 const char* temp
= _amkrtemp((dir
.substr(0, i
) + "/" + mFile
.file()).c_str());
633 UnixError::throwMe(errno
);
639 const char *path
= mPath
.c_str();
641 mFileRef
= AtomicFile::ropen(path
, O_WRONLY
|O_CREAT
|O_TRUNC
, mode
);
645 secdebug("atomicfile", "open %s: %s", path
, strerror(error
));
647 // Do the obvious error code translations here.
648 // @@@ Consider moving these up a level.
650 CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED
);
652 UnixError::throwMe(error
);
655 // If we aren't creating the inital file, make sure we preserve
656 // the mode of the old file regardless of the current umask.
657 // If we are creating the inital file we respect the users
661 if (::fchmod(mFileRef
, mode
))
664 secdebug("atomicfile", "fchmod %s: %s", path
, strerror(error
));
665 UnixError::throwMe(error
);
669 secdebug("atomicfile", "%p created %s", this, path
);
673 AtomicTempFile::write(AtomicFile::OffsetType inOffsetType
, off_t inOffset
, const uint32 inData
)
675 uint32 aData
= htonl(inData
);
676 write(inOffsetType
, inOffset
, reinterpret_cast<uint8
*>(&aData
), sizeof(aData
));
680 AtomicTempFile::write(AtomicFile::OffsetType inOffsetType
, off_t inOffset
,
681 const uint32
*inData
, uint32 inCount
)
683 #ifdef HOST_LONG_IS_NETWORK_LONG
684 // Optimize this for the case where hl == nl
685 const uint32
*aBuffer
= inData
;
687 auto_array
<uint32
> aBuffer(inCount
);
688 for (uint32 i
= 0; i
< inCount
; i
++)
689 aBuffer
.get()[i
] = htonl(inData
[i
]);
692 write(inOffsetType
, inOffset
, reinterpret_cast<const uint8
*>(aBuffer
.get()),
693 inCount
* sizeof(*inData
));
697 AtomicTempFile::write(AtomicFile::OffsetType inOffsetType
, off_t inOffset
, const uint8
*inData
, size_t inLength
)
700 if (inOffsetType
== AtomicFile::FromEnd
)
702 pos
= ::lseek(mFileRef
, 0, SEEK_END
);
706 secdebug("atomicfile", "lseek(%s, %qd): %s", mPath
.c_str(), inOffset
, strerror(error
));
707 UnixError::throwMe(error
);
710 else if (inOffsetType
== AtomicFile::FromStart
)
713 CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR
);
715 off_t bytesLeft
= inLength
;
716 const uint8
*ptr
= inData
;
719 size_t toWrite
= bytesLeft
> kAtomicFileMaxBlockSize
? kAtomicFileMaxBlockSize
: size_t(bytesLeft
);
720 ssize_t bytesWritten
= ::pwrite(mFileRef
, ptr
, toWrite
, pos
);
721 if (bytesWritten
== -1)
726 // We got interrupted by a signal, so try again.
727 secdebug("atomicfile", "write %s: interrupted, retrying", mPath
.c_str());
731 secdebug("atomicfile", "write %s: %s", mPath
.c_str(), strerror(error
));
732 UnixError::throwMe(error
);
735 // Write returning 0 is bad mmkay.
736 if (bytesWritten
== 0)
738 secdebug("atomicfile", "write %s: 0 bytes written", mPath
.c_str());
739 CssmError::throwMe(CSSMERR_DL_INTERNAL_ERROR
);
742 secdebug("atomicfile", "%p wrote %s %ld bytes from %p", this, mPath
.c_str(), bytesWritten
, ptr
);
744 bytesLeft
-= bytesWritten
;
751 AtomicTempFile::fsync()
755 secdebug("atomicfile", "fsync %s: already closed", mPath
.c_str());
762 result
= ::fsync(mFileRef
);
763 } while (result
&& errno
== EINTR
);
768 secdebug("atomicfile", "fsync %s: %s", mPath
.c_str(), strerror(errno
));
769 UnixError::throwMe(error
);
772 secdebug("atomicfile", "%p fsynced %s", this, mPath
.c_str());
777 AtomicTempFile::close()
781 secdebug("atomicfile", "close %s: already closed", mPath
.c_str());
785 int result
= AtomicFile::rclose(mFileRef
);
790 secdebug("atomicfile", "close %s: %s", mPath
.c_str(), strerror(errno
));
791 UnixError::throwMe(error
);
794 secdebug("atomicfile", "%p closed %s", this, mPath
.c_str());
798 // Commit the current create or write and close the write file. Note that a throw during the commit does an automatic rollback.
800 AtomicTempFile::commit()
806 const char *oldPath
= mPath
.c_str();
807 const char *newPath
= mFile
.path().c_str();
809 // <rdar://problem/6991037>
810 // Copy the security parameters of one file to another
811 // Adding this to guard against setuid utilities that are re-writing a user's keychain. We don't want to leave them root-owned.
812 // In order to not break backward compatability we'll make a best effort, but continue if these efforts fail.
814 // To clear something up - newPath is the name the keychain will become - which is the name of the file being replaced
815 // oldPath is the "temp filename".
818 s
= copyfile_state_alloc();
820 if(copyfile(newPath
, oldPath
, s
, COPYFILE_SECURITY
| COPYFILE_NOFOLLOW
) == -1) // Not fatal
821 secdebug("atomicfile", "copyfile (%s, %s): %s", oldPath
, newPath
, strerror(errno
));
823 copyfile_state_free(s
);
824 // END <rdar://problem/6991037>
826 ::utimes(oldPath
, NULL
);
828 if (::rename(oldPath
, newPath
) == -1)
831 secdebug("atomicfile", "rename (%s, %s): %s", oldPath
, newPath
, strerror(errno
));
832 UnixError::throwMe(error
);
835 // Unlock the lockfile
838 secdebug("atomicfile", "%p commited %s", this, oldPath
);
847 // Rollback the current create or write (happens automatically if commit() isn't called before the destructor is.
849 AtomicTempFile::rollback() throw()
853 AtomicFile::rclose(mFileRef
);
857 // @@@ Log errors if this fails.
858 const char *path
= mPath
.c_str();
859 if (::unlink(path
) == -1)
861 secdebug("atomicfile", "unlink %s: %s", path
, strerror(errno
));
862 // rollback can't throw
865 // @@@ Think about this. Depending on how we do locking we might not need this.
868 const char *path
= mFile
.path().c_str();
869 if (::unlink(path
) == -1)
871 secdebug("atomicfile", "unlink %s: %s", path
, strerror(errno
));
872 // rollback can't throw
879 // An advisory write lock for inFile.
881 FileLocker::~FileLocker()
887 LocalFileLocker::LocalFileLocker(AtomicFile
&inFile
) :
888 mPath(inFile
.lockFileName())
893 LocalFileLocker::~LocalFileLocker()
900 static double GetTime()
903 gettimeofday(&t
, NULL
);
904 return ((double) t
.tv_sec
) + ((double) t
.tv_usec
) / 1000000.0;
911 LocalFileLocker::lock(mode_t mode
)
917 // if the lock file doesn't exist, create it
918 mLockFile
= open(mPath
.c_str(), O_RDONLY
| O_CREAT
, mode
);
920 // if we can't open or create the file, something is wrong
923 UnixError::throwMe(errno
);
926 // try to get exclusive access to the file
927 IFDEBUG(double startTime
= GetTime());
928 int result
= flock(mLockFile
, LOCK_EX
);
929 IFDEBUG(double endTime
= GetTime());
931 IFDEBUG(secdebug("atomictime", "Waited %.4f milliseconds for file lock", (endTime
- startTime
) * 1000.0));
933 // errors at this point are bad
936 UnixError::throwMe(errno
);
939 // check and see if the file we have access to still exists. If not, another file shared our file lock
940 // due to a hash collision and has thrown our lock away -- that, or a user blew the lock file away himself.
942 result
= fstat(mLockFile
, &st
);
944 // errors at this point are bad
947 UnixError::throwMe(errno
);
950 if (st
.st_nlink
== 0) // we've been unlinked!
954 } while (st
.st_nlink
== 0);
959 LocalFileLocker::unlock()
961 flock(mLockFile
, LOCK_UN
);
967 NetworkFileLocker::NetworkFileLocker(AtomicFile
&inFile
) :
969 mPath(inFile
.dir() + "lck~" + inFile
.file())
973 NetworkFileLocker::~NetworkFileLocker()
978 NetworkFileLocker::unique(mode_t mode
)
980 static const int randomPart
= 16;
981 DevRandomGenerator randomGen
;
982 std::string::size_type dirSize
= mDir
.size();
983 std::string
fullname(dirSize
+ randomPart
+ 2, '\0');
984 fullname
.replace(0, dirSize
, mDir
);
985 fullname
[dirSize
] = '~'; /* UNIQ_PREFIX */
986 char buf
[randomPart
];
990 for (int retries
= 0; retries
< 10; ++retries
)
992 /* Make a random filename. */
993 randomGen
.random(buf
, randomPart
);
994 for (int ix
= 0; ix
< randomPart
; ++ix
)
996 char ch
= buf
[ix
] & 0x3f;
997 fullname
[ix
+ dirSize
+ 1] = ch
+
999 : ch
< 26 + 26 ? 'a' - 26
1000 : ch
< 26 + 26 + 10 ? '0' - 26 - 26
1001 : ch
== 26 + 26 + 10 ? '-' - 26 - 26 - 10
1002 : '_' - 26 - 26 - 11);
1005 result
= lstat(fullname
.c_str(), &filebuf
);
1006 if (result
&& errno
== ENAMETOOLONG
)
1009 fullname
.erase(fullname
.end() - 1);
1010 while((result
= lstat(fullname
.c_str(), &filebuf
)) && errno
== ENAMETOOLONG
&& fullname
.size() > dirSize
+ 8);
1011 } /* either it stopped being a problem or we ran out of filename */
1013 if (result
&& errno
== ENOENT
)
1015 fd
= AtomicFile::ropen(fullname
.c_str(), O_WRONLY
|O_CREAT
|O_EXCL
, mode
);
1016 if (fd
>= 0 || errno
!= EEXIST
)
1024 ::syslog(LOG_ERR
, "Couldn't create temp file %s: %s", fullname
.c_str(), strerror(error
));
1025 secdebug("atomicfile", "Couldn't create temp file %s: %s", fullname
.c_str(), strerror(error
));
1026 UnixError::throwMe(error
);
1029 /* @@@ Check for EINTR. */
1030 write(fd
, "0", 1); /* pid 0, `works' across networks */
1032 AtomicFile::rclose(fd
);
1037 /* 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. */
1039 NetworkFileLocker::rlink(const char *const old
, const char *const newn
, struct stat
&sto
)
1041 int result
= ::link(old
,newn
);
1045 if (::lstat(old
, &sto
) == 0)
1048 if (::lstat(newn
, &stn
) == 0
1049 && sto
.st_dev
== stn
.st_dev
1050 && sto
.st_ino
== stn
.st_ino
1051 && sto
.st_uid
== stn
.st_uid
1052 && sto
.st_gid
== stn
.st_gid
1053 && !S_ISLNK(sto
.st_mode
))
1055 /* Link failed but files are the same so the link really went ok. */
1061 errno
= serrno
; /* Restore errno from link() */
1067 /* NFS-resistant rename()
1068 * rename with fallback for systems that don't support it
1069 * Note that this does not preserve the contents of the file. */
1071 NetworkFileLocker::myrename(const char *const old
, const char *const newn
)
1077 /* Try a real hardlink */
1078 ret
= rlink(old
, newn
, stbuf
);
1081 if (stbuf
.st_nlink
< 2 && (errno
== EXDEV
|| errno
== ENOTSUP
))
1083 /* Hard link failed so just create a new file with O_EXCL instead. */
1084 fd
= AtomicFile::ropen(newn
, O_WRONLY
|O_CREAT
|O_EXCL
, stbuf
.st_mode
);
1090 /* We want the errno from the link or the ropen, not that of the unlink. */
1093 /* Unlink the temp file. */
1096 AtomicFile::rclose(fd
);
1103 NetworkFileLocker::xcreat(const char *const name
, mode_t mode
, time_t &tim
)
1105 std::string uniqueName
= unique(mode
);
1106 const char *uniquePath
= uniqueName
.c_str();
1107 struct stat stbuf
; /* return the filesystem time to the caller */
1108 stat(uniquePath
, &stbuf
);
1109 tim
= stbuf
.st_mtime
;
1110 return myrename(uniquePath
, name
);
1114 NetworkFileLocker::lock(mode_t mode
)
1116 const char *path
= mPath
.c_str();
1117 bool triedforce
= false;
1119 time_t t
, locktimeout
= 1024; /* DEFlocktimeout, 17 minutes. */
1120 bool doSyslog
= false;
1121 bool failed
= false;
1126 /* Don't syslog first time through. */
1128 ::syslog(LOG_NOTICE
, "Locking %s", path
);
1132 secdebug("atomicfile", "Locking %s", path
); /* in order to cater for clock skew: get */
1133 if (!xcreat(path
, mode
, t
)) /* time t from the filesystem */
1135 /* lock acquired, hurray! */
1140 case EEXIST
: /* check if it's time for a lock override */
1141 if (!lstat(path
, &stbuf
) && stbuf
.st_size
<= 16 /* MAX_locksize */ && locktimeout
1142 && !lstat(path
, &stbuf
) && locktimeout
< t
- stbuf
.st_mtime
)
1143 /* stat() till unlink() should be atomic, but can't guarantee that. */
1147 /* Already tried, force lock override, not trying again */
1151 else if (S_ISDIR(stbuf
.st_mode
) || ::unlink(path
))
1154 ::syslog(LOG_ERR
, "Forced unlock denied on %s", path
);
1155 secdebug("atomicfile", "Forced unlock denied on %s", path
);
1159 ::syslog(LOG_ERR
, "Forcing lock on %s", path
);
1160 secdebug("atomicfile", "Forcing lock on %s", path
);
1161 sleep(16 /* DEFsuspend */);
1166 triedforce
= false; /* legitimate iteration, clear flag */
1168 /* Reset retry counter. */
1173 case ENOSPC
: /* no space left, treat it as a transient */
1174 #ifdef EDQUOT /* NFS failure */
1175 case EDQUOT
: /* maybe it was a short term shortage? */
1181 if(++retries
< (256 + 1)) /* nfsTRY number of times+1 to ignore spurious NFS errors */
1188 case ENAMETOOLONG
: /* Filename is too long, shorten and retry */
1189 if (mPath
.size() > mDir
.size() + 8)
1191 secdebug("atomicfile", "Truncating %s and retrying lock", path
);
1192 mPath
.erase(mPath
.end() - 1);
1193 path
= mPath
.c_str();
1194 /* Reset retry counter. */
1209 ::syslog(LOG_ERR
, "Lock failure on %s: %s", path
, strerror(error
));
1210 secdebug("atomicfile", "Lock failure on %s: %s", path
, strerror(error
));
1211 UnixError::throwMe(error
);
1216 NetworkFileLocker::unlock()
1218 const char *path
= mPath
.c_str();
1219 if (::unlink(path
) == -1)
1221 secdebug("atomicfile", "unlink %s: %s", path
, strerror(errno
));
1222 // unlock can't throw
1228 AtomicLockedFile::AtomicLockedFile(AtomicFile
&inFile
)
1230 if (inFile
.isOnLocalFileSystem())
1232 mFileLocker
= new LocalFileLocker(inFile
);
1236 mFileLocker
= new NetworkFileLocker(inFile
);
1244 AtomicLockedFile::~AtomicLockedFile()
1253 AtomicLockedFile::lock(mode_t mode
)
1255 mFileLocker
->lock(mode
);
1260 void AtomicLockedFile::unlock() throw()
1262 mFileLocker
->unlock();
1267 #undef kAtomicFileMaxBlockSize