/* * Copyright (c) 2000-2001, 2003 Apple Computer, Inc. All Rights Reserved. * * The contents of this file constitute Original Code as defined in and are * subject to the Apple Public Source License Version 1.2 (the 'License'). * You may not use this file except in compliance with the License. Please obtain * a copy of the License at http://www.apple.com/publicsource and read it before * using this file. * * This Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the * specific language governing rights and limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define kAtomicFileMaxBlockSize INT_MAX // // AtomicFile.cpp - Description t.b.d. // AtomicFile::AtomicFile(const std::string &inPath) : mPath(inPath) { pathSplit(inPath, mDir, mFile); // determine if the path is on a local or a networked volume struct statfs info; int result = statfs(mDir.c_str(), &info); if (result == -1) // error on opening? { mIsLocalFileSystem = false; // revert to the old ways if we can't tell what kind of system we have } else { mIsLocalFileSystem = (info.f_flags & MNT_LOCAL) != 0; if (mIsLocalFileSystem) { // compute the name of the lock file for this file CC_SHA1_CTX ctx; CC_SHA1_Init(&ctx); CC_SHA1_Update(&ctx, (const void*) mFile.c_str(), mFile.length()); u_int8_t digest[CC_SHA1_DIGEST_LENGTH]; CC_SHA1_Final(digest, &ctx); u_int32_t hash = (digest[0] << 24) | (digest[1] << 16) | (digest[2] << 8) | digest[3]; char buffer[256]; sprintf(buffer, "%08X", hash); mLockFilePath = mDir + ".fl" + buffer; } } } AtomicFile::~AtomicFile() { } // Aquire the write lock and remove the file. void AtomicFile::performDelete() { AtomicLockedFile lock(*this); if (::unlink(mPath.c_str()) != 0) { int error = errno; secdebug("atomicfile", "unlink %s: %s", mPath.c_str(), strerror(error)); if (error == ENOENT) CssmError::throwMe(CSSMERR_DL_DATASTORE_DOESNOT_EXIST); else UnixError::throwMe(error); } // unlink our lock file ::unlink(mLockFilePath.c_str()); } // Aquire the write lock and rename the file (and bump the version and stuff). void AtomicFile::rename(const std::string &inNewPath) { const char *path = mPath.c_str(); const char *newPath = inNewPath.c_str(); // @@@ lock the destination file too. AtomicLockedFile lock(*this); if (::rename(path, newPath) != 0) { int error = errno; secdebug("atomicfile", "rename(%s, %s): %s", path, newPath, strerror(error)); UnixError::throwMe(error); } } // Lock the file for writing and return a newly created AtomicTempFile. RefPointer AtomicFile::create(mode_t mode) { const char *path = mPath.c_str(); // First make sure the directory to this file exists and is writable mkpath(mDir); RefPointer lock(new AtomicLockedFile(*this)); int fileRef = ropen(path, O_WRONLY|O_CREAT|O_EXCL, mode); if (fileRef == -1) { int error = errno; secdebug("atomicfile", "open %s: %s", path, strerror(error)); // Do the obvious error code translations here. // @@@ Consider moving these up a level. if (error == EACCES) CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED); else if (error == EEXIST) CssmError::throwMe(CSSMERR_DL_DATASTORE_ALREADY_EXISTS); else UnixError::throwMe(error); } rclose(fileRef); try { // Now that we have created the lock and the new db file create a tempfile // object. RefPointer temp(new AtomicTempFile(*this, lock, mode)); secdebug("atomicfile", "%p created %s", this, path); return temp; } catch (...) { // Creating the temp file failed so remove the db file we just created too. if (::unlink(path) == -1) { secdebug("atomicfile", "unlink %s: %s", path, strerror(errno)); } throw; } } // Lock the database file for writing and return a newly created AtomicTempFile. // If the parent directory allows the write we're going to allow this. Previous // versions checked for writability of the db file and that caused problems when // setuid programs had made entries. As long as the db (keychain) file is readable // this function can make the newer keychain file with the correct owner just by virtue // of the copy that takes place. RefPointer AtomicFile::write() { RefPointer lock(new AtomicLockedFile(*this)); return new AtomicTempFile(*this, lock); } // Return a bufferedFile containing current version of the file for reading. RefPointer AtomicFile::read() { return new AtomicBufferedFile(mPath, mIsLocalFileSystem); } mode_t AtomicFile::mode() const { const char *path = mPath.c_str(); struct stat st; if (::stat(path, &st) == -1) { int error = errno; secdebug("atomicfile", "stat %s: %s", path, strerror(error)); UnixError::throwMe(error); } return st.st_mode; } // Split full into a dir and file component. void AtomicFile::pathSplit(const std::string &inFull, std::string &outDir, std::string &outFile) { std::string::size_type slash, len = inFull.size(); slash = inFull.rfind('/'); if (slash == std::string::npos) { outDir = ""; outFile = inFull; } else if (slash + 1 == len) { outDir = inFull; outFile = ""; } else { outDir = inFull.substr(0, slash + 1); outFile = inFull.substr(slash + 1, len); } } static std::string RemoveDoubleSlashes(const std::string &path) { std::string result; unsigned i; for (i = 0; i < path.length(); ++i) { result += path[i]; if ((i < path.length() - 2) && path[i] == '/' && path[i + 1] == '/') { i += 1; // skip a second '/' } } return result; } // // Make sure the directory up to inDir exists inDir *must* end in a slash. // void AtomicFile::mkpath(const std::string &inDir, mode_t mode) { for (std::string::size_type pos = 0; (pos = inDir.find('/', pos + 1)) != std::string::npos;) { std::string path = inDir.substr(0, pos); const char *cpath = path.c_str(); struct stat sb; if (::stat(cpath, &sb)) { // if we are creating a path in the user's home directory, override the user's mode std::string homedir = getenv("HOME"); // canonicalize the path (remove double slashes) string canonPath = RemoveDoubleSlashes(cpath); if (canonPath.find(homedir, 0) == 0) { mode = 0700; } if (errno != ENOENT || ::mkdir(cpath, mode)) UnixError::throwMe(errno); } else if (!S_ISDIR(sb.st_mode)) CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED); // @@@ Should be is a directory } } int AtomicFile::ropen(const char *const name, int flags, mode_t mode) { int fd, tries_left = 4 /* kNoResRetry */; do { fd = ::open(name, flags, mode); } while (fd < 0 && (errno == EINTR || errno == ENFILE && --tries_left >= 0)); return fd; } int AtomicFile::rclose(int fd) { int result; do { result = ::close(fd); } while(result && errno == EINTR); return result; } // // AtomicBufferedFile - This represents an instance of a file opened for reading. // The file is read into memory and closed after this is done. // The memory is released when this object is destroyed. // AtomicBufferedFile::AtomicBufferedFile(const std::string &inPath, bool isLocal) : mPath(inPath), mFileRef(-1), mBuffer(NULL), mLength(0), mIsMapped(isLocal) { } AtomicBufferedFile::~AtomicBufferedFile() { if (mFileRef >= 0) { AtomicFile::rclose(mFileRef); secdebug("atomicfile", "%p closed %s", this, mPath.c_str()); } if (mBuffer) { secdebug("atomicfile", "%p free %s buffer %p", this, mPath.c_str(), mBuffer); unloadBuffer(); } } // // Open the file and return the length in bytes. // off_t AtomicBufferedFile::open() { const char *path = mPath.c_str(); if (mFileRef >= 0) { secdebug("atomicfile", "open %s: already open, closing and reopening", path); close(); } mFileRef = AtomicFile::ropen(path, O_RDONLY, 0); if (mFileRef == -1) { int error = errno; secdebug("atomicfile", "open %s: %s", path, strerror(error)); // Do the obvious error code translations here. // @@@ Consider moving these up a level. if (error == ENOENT) CssmError::throwMe(CSSMERR_DL_DATASTORE_DOESNOT_EXIST); else if (error == EACCES) CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED); else UnixError::throwMe(error); } struct stat st; int result = fstat(mFileRef, &st); if (result == 0) { mLength = st.st_size; } else { int error = errno; secdebug("atomicfile", "lseek(%s, END): %s", path, strerror(error)); AtomicFile::rclose(mFileRef); UnixError::throwMe(error); } secdebug("atomicfile", "%p opened %s: %qd bytes", this, path, mLength); return mLength; } // // Unload the contents of the file. // void AtomicBufferedFile::unloadBuffer() { if (!mIsMapped) { delete [] mBuffer; } else { munmap(mBuffer, mLength); } } // // Load the contents of the file into memory. // If we are on a local file system, we mmap the file. Otherwise, we // read it all into memory void AtomicBufferedFile::loadBuffer() { if (!mIsMapped) { // make a buffer big enough to hold the entire file mBuffer = new uint8[mLength]; lseek(mFileRef, 0, SEEK_SET); ssize_t pos = 0; ssize_t bytesToRead = mLength; while (bytesToRead > 0) { ssize_t bytesRead = ::read(mFileRef, mBuffer + pos, bytesToRead); if (bytesRead == -1) { if (errno != EINTR) { int error = errno; secdebug("atomicfile", "lseek(%s, END): %s", mPath.c_str(), strerror(error)); AtomicFile::rclose(mFileRef); UnixError::throwMe(error); } } else { bytesToRead -= bytesRead; pos += bytesRead; } } } else { // mmap the buffer into place mBuffer = (uint8*) mmap(NULL, mLength, PROT_READ, MAP_PRIVATE, mFileRef, 0); if (mBuffer == (uint8*) -1) { int error = errno; secdebug("atomicfile", "lseek(%s, END): %s", mPath.c_str(), strerror(error)); AtomicFile::rclose(mFileRef); UnixError::throwMe(error); } } } // // Read the file starting at inOffset for inLength bytes into the buffer and return // a pointer to it. On return outLength contain the actual number of bytes read, it // will only ever be less than inLength if EOF was reached, and it will never be more // than inLength. // const uint8 * AtomicBufferedFile::read(off_t inOffset, off_t inLength, off_t &outLength) { if (mFileRef < 0) { secdebug("atomicfile", "read %s: file yet not opened, opening", mPath.c_str()); open(); } off_t bytesLeft = inLength; if (mBuffer) { secdebug("atomicfile", "%p free %s buffer %p", this, mPath.c_str(), mBuffer); unloadBuffer(); } loadBuffer(); secdebug("atomicfile", "%p allocated %s buffer %p size %qd", this, mPath.c_str(), mBuffer, bytesLeft); ssize_t maxEnd = inOffset + inLength; if (maxEnd > mLength) { maxEnd = mLength; } outLength = maxEnd - inOffset; return mBuffer + inOffset; } void AtomicBufferedFile::close() { if (mFileRef < 0) { secdebug("atomicfile", "close %s: already closed", mPath.c_str()); } else { int result = AtomicFile::rclose(mFileRef); mFileRef = -1; if (result == -1) { int error = errno; secdebug("atomicfile", "close %s: %s", mPath.c_str(), strerror(errno)); UnixError::throwMe(error); } secdebug("atomicfile", "%p closed %s", this, mPath.c_str()); } } // // AtomicTempFile - A temporary file to write changes to. // AtomicTempFile::AtomicTempFile(AtomicFile &inFile, const RefPointer &inLockedFile, mode_t mode) : mFile(inFile), mLockedFile(inLockedFile), mCreating(true) { create(mode); } AtomicTempFile::AtomicTempFile(AtomicFile &inFile, const RefPointer &inLockedFile) : mFile(inFile), mLockedFile(inLockedFile), mCreating(false) { create(mFile.mode()); } AtomicTempFile::~AtomicTempFile() { // rollback if we didn't commit yet. if (mFileRef >= 0) rollback(); } // // Open the file and return the length in bytes. // void AtomicTempFile::create(mode_t mode) { mPath = mFile.dir() + "," + mFile.file(); const char *path = mPath.c_str(); mFileRef = AtomicFile::ropen(path, O_WRONLY|O_CREAT|O_TRUNC, mode); if (mFileRef == -1) { int error = errno; secdebug("atomicfile", "open %s: %s", path, strerror(error)); // Do the obvious error code translations here. // @@@ Consider moving these up a level. if (error == EACCES) CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED); else UnixError::throwMe(error); } // If we aren't creating the inital file, make sure we preserve // the mode of the old file regardless of the current umask. // If we are creating the inital file we respect the users // current umask. if (!mCreating) { if (::fchmod(mFileRef, mode)) { int error = errno; secdebug("atomicfile", "fchmod %s: %s", path, strerror(error)); UnixError::throwMe(error); } } secdebug("atomicfile", "%p created %s", this, path); } void AtomicTempFile::write(AtomicFile::OffsetType inOffsetType, off_t inOffset, const uint32 inData) { uint32 aData = htonl(inData); write(inOffsetType, inOffset, reinterpret_cast(&aData), sizeof(aData)); } void AtomicTempFile::write(AtomicFile::OffsetType inOffsetType, off_t inOffset, const uint32 *inData, uint32 inCount) { #ifdef HOST_LONG_IS_NETWORK_LONG // Optimize this for the case where hl == nl const uint32 *aBuffer = inData; #else auto_array aBuffer(inCount); for (uint32 i = 0; i < inCount; i++) aBuffer.get()[i] = htonl(inData[i]); #endif write(inOffsetType, inOffset, reinterpret_cast(aBuffer.get()), inCount * sizeof(*inData)); } void AtomicTempFile::write(AtomicFile::OffsetType inOffsetType, off_t inOffset, const uint8 *inData, size_t inLength) { off_t pos; if (inOffsetType == AtomicFile::FromEnd) { pos = ::lseek(mFileRef, 0, SEEK_END); if (pos == -1) { int error = errno; secdebug("atomicfile", "lseek(%s, %qd): %s", mPath.c_str(), inOffset, strerror(error)); UnixError::throwMe(error); } } else if (inOffsetType == AtomicFile::FromStart) pos = inOffset; else CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR); off_t bytesLeft = inLength; const uint8 *ptr = inData; while (bytesLeft) { size_t toWrite = bytesLeft > kAtomicFileMaxBlockSize ? kAtomicFileMaxBlockSize : size_t(bytesLeft); ssize_t bytesWritten = ::pwrite(mFileRef, ptr, toWrite, pos); if (bytesWritten == -1) { int error = errno; if (error == EINTR) { // We got interrupted by a signal, so try again. secdebug("atomicfile", "write %s: interrupted, retrying", mPath.c_str()); continue; } secdebug("atomicfile", "write %s: %s", mPath.c_str(), strerror(error)); UnixError::throwMe(error); } // Write returning 0 is bad mmkay. if (bytesWritten == 0) { secdebug("atomicfile", "write %s: 0 bytes written", mPath.c_str()); CssmError::throwMe(CSSMERR_DL_INTERNAL_ERROR); } secdebug("atomicfile", "%p wrote %s %ld bytes from %p", this, mPath.c_str(), bytesWritten, ptr); bytesLeft -= bytesWritten; ptr += bytesWritten; pos += bytesWritten; } } void AtomicTempFile::fsync() { if (mFileRef < 0) { secdebug("atomicfile", "fsync %s: already closed", mPath.c_str()); } else { int result; do { result = ::fsync(mFileRef); } while (result && errno == EINTR); if (result == -1) { int error = errno; secdebug("atomicfile", "fsync %s: %s", mPath.c_str(), strerror(errno)); UnixError::throwMe(error); } secdebug("atomicfile", "%p fsynced %s", this, mPath.c_str()); } } void AtomicTempFile::close() { if (mFileRef < 0) { secdebug("atomicfile", "close %s: already closed", mPath.c_str()); } else { int result = AtomicFile::rclose(mFileRef); mFileRef = -1; if (result == -1) { int error = errno; secdebug("atomicfile", "close %s: %s", mPath.c_str(), strerror(errno)); UnixError::throwMe(error); } secdebug("atomicfile", "%p closed %s", this, mPath.c_str()); } } // Commit the current create or write and close the write file. Note that a throw during the commit does an automatic rollback. void AtomicTempFile::commit() { try { fsync(); close(); const char *oldPath = mPath.c_str(); const char *newPath = mFile.path().c_str(); if (::rename(oldPath, newPath) == -1) { int error = errno; secdebug("atomicfile", "rename (%s, %s): %s", oldPath, newPath, strerror(errno)); UnixError::throwMe(error); } // Unlock the lockfile mLockedFile = NULL; secdebug("atomicfile", "%p commited %s", this, oldPath); } catch (...) { rollback(); throw; } } // Rollback the current create or write (happens automatically if commit() isn't called before the destructor is. void AtomicTempFile::rollback() throw() { if (mFileRef >= 0) { AtomicFile::rclose(mFileRef); mFileRef = -1; } // @@@ Log errors if this fails. const char *path = mPath.c_str(); if (::unlink(path) == -1) { secdebug("atomicfile", "unlink %s: %s", path, strerror(errno)); // rollback can't throw } // @@@ Think about this. Depending on how we do locking we might not need this. if (mCreating) { const char *path = mFile.path().c_str(); if (::unlink(path) == -1) { secdebug("atomicfile", "unlink %s: %s", path, strerror(errno)); // rollback can't throw } } } // // An advisory write lock for inFile. // FileLocker::~FileLocker() { } LocalFileLocker::LocalFileLocker(AtomicFile &inFile) : mPath(inFile.lockFileName()) { } LocalFileLocker::~LocalFileLocker() { } #ifndef NDEBUG static double GetTime() { struct timeval t; gettimeofday(&t, NULL); return ((double) t.tv_sec) + ((double) t.tv_usec) / 1000000.0; } #endif void LocalFileLocker::lock(mode_t mode) { struct stat st; do { // if the lock file doesn't exist, create it mLockFile = open(mPath.c_str(), O_RDONLY | O_CREAT, mode); // if we can't open or create the file, something is wrong if (mLockFile == -1) { UnixError::throwMe(errno); } // try to get exclusive access to the file IFDEBUG(double startTime = GetTime()); int result = flock(mLockFile, LOCK_EX); IFDEBUG(double endTime = GetTime()); IFDEBUG(secdebug("atomictime", "Waited %.4f milliseconds for file lock", (endTime - startTime) * 1000.0)); // errors at this point are bad if (result == -1) { UnixError::throwMe(errno); } // check and see if the file we have access to still exists. If not, another file shared our file lock // due to a hash collision and has thrown our lock away -- that, or a user blew the lock file away himself. result = fstat(mLockFile, &st); // errors at this point are bad if (result == -1) { UnixError::throwMe(errno); } if (st.st_nlink == 0) // we've been unlinked! { close(mLockFile); } } while (st.st_nlink == 0); } void LocalFileLocker::unlock() { flock(mLockFile, LOCK_UN); close(mLockFile); } NetworkFileLocker::NetworkFileLocker(AtomicFile &inFile) : mDir(inFile.dir()), mPath(inFile.dir() + "lck~" + inFile.file()) { } NetworkFileLocker::~NetworkFileLocker() { } std::string NetworkFileLocker::unique(mode_t mode) { static const int randomPart = 16; DevRandomGenerator randomGen; std::string::size_type dirSize = mDir.size(); std::string fullname(dirSize + randomPart + 2, '\0'); fullname.replace(0, dirSize, mDir); fullname[dirSize] = '~'; /* UNIQ_PREFIX */ char buf[randomPart]; struct stat filebuf; int result, fd = -1; for (int retries = 0; retries < 10; ++retries) { /* Make a random filename. */ randomGen.random(buf, randomPart); for (int ix = 0; ix < randomPart; ++ix) { char ch = buf[ix] & 0x3f; fullname[ix + dirSize + 1] = ch + ( ch < 26 ? 'A' : ch < 26 + 26 ? 'a' - 26 : ch < 26 + 26 + 10 ? '0' - 26 - 26 : ch == 26 + 26 + 10 ? '-' - 26 - 26 - 10 : '_' - 26 - 26 - 11); } result = lstat(fullname.c_str(), &filebuf); if (result && errno == ENAMETOOLONG) { do fullname.erase(fullname.end() - 1); while((result = lstat(fullname.c_str(), &filebuf)) && errno == ENAMETOOLONG && fullname.size() > dirSize + 8); } /* either it stopped being a problem or we ran out of filename */ if (result && errno == ENOENT) { fd = AtomicFile::ropen(fullname.c_str(), O_WRONLY|O_CREAT|O_EXCL, mode); if (fd >= 0 || errno != EEXIST) break; } } if (fd < 0) { int error = errno; ::syslog(LOG_ERR, "Couldn't create temp file %s: %s", fullname.c_str(), strerror(error)); secdebug("atomicfile", "Couldn't create temp file %s: %s", fullname.c_str(), strerror(error)); UnixError::throwMe(error); } /* @@@ Check for EINTR. */ write(fd, "0", 1); /* pid 0, `works' across networks */ AtomicFile::rclose(fd); return fullname; } /* 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. */ int NetworkFileLocker::rlink(const char *const old, const char *const newn, struct stat &sto) { int result = ::link(old,newn); if (result) { int serrno = errno; if (::lstat(old, &sto) == 0) { struct stat stn; if (::lstat(newn, &stn) == 0 && sto.st_dev == stn.st_dev && sto.st_ino == stn.st_ino && sto.st_uid == stn.st_uid && sto.st_gid == stn.st_gid && !S_ISLNK(sto.st_mode)) { /* Link failed but files are the same so the link really went ok. */ return 0; } else result = 1; } errno = serrno; /* Restore errno from link() */ } return result; } /* NFS-resistant rename() * rename with fallback for systems that don't support it * Note that this does not preserve the contents of the file. */ int NetworkFileLocker::myrename(const char *const old, const char *const newn) { struct stat stbuf; int fd = -1; int ret; /* Try a real hardlink */ ret = rlink(old, newn, stbuf); if (ret > 0) { if (stbuf.st_nlink < 2 && (errno == EXDEV || errno == ENOTSUP)) { /* Hard link failed so just create a new file with O_EXCL instead. */ fd = AtomicFile::ropen(newn, O_WRONLY|O_CREAT|O_EXCL, stbuf.st_mode); if (fd >= 0) ret = 0; } } /* We want the errno from the link or the ropen, not that of the unlink. */ int serrno = errno; /* Unlink the temp file. */ ::unlink(old); if (fd > 0) AtomicFile::rclose(fd); errno = serrno; return ret; } int NetworkFileLocker::xcreat(const char *const name, mode_t mode, time_t &tim) { std::string uniqueName = unique(mode); const char *uniquePath = uniqueName.c_str(); struct stat stbuf; /* return the filesystem time to the caller */ stat(uniquePath, &stbuf); tim = stbuf.st_mtime; return myrename(uniquePath, name); } void NetworkFileLocker::lock(mode_t mode) { const char *path = mPath.c_str(); bool triedforce = false; struct stat stbuf; time_t t, locktimeout = 1024; /* DEFlocktimeout, 17 minutes. */ bool doSyslog = false; bool failed = false; int retries = 0; while (!failed) { /* Don't syslog first time through. */ if (doSyslog) ::syslog(LOG_NOTICE, "Locking %s", path); else doSyslog = true; secdebug("atomicfile", "Locking %s", path); /* in order to cater for clock skew: get */ if (!xcreat(path, mode, t)) /* time t from the filesystem */ { /* lock acquired, hurray! */ break; } switch(errno) { case EEXIST: /* check if it's time for a lock override */ if (!lstat(path, &stbuf) && stbuf.st_size <= 16 /* MAX_locksize */ && locktimeout && !lstat(path, &stbuf) && locktimeout < t - stbuf.st_mtime) /* stat() till unlink() should be atomic, but can't guarantee that. */ { if (triedforce) { /* Already tried, force lock override, not trying again */ failed = true; break; } else if (S_ISDIR(stbuf.st_mode) || ::unlink(path)) { triedforce=true; ::syslog(LOG_ERR, "Forced unlock denied on %s", path); secdebug("atomicfile", "Forced unlock denied on %s", path); } else { ::syslog(LOG_ERR, "Forcing lock on %s", path); secdebug("atomicfile", "Forcing lock on %s", path); sleep(16 /* DEFsuspend */); break; } } else triedforce = false; /* legitimate iteration, clear flag */ /* Reset retry counter. */ retries = 0; usleep(250000); break; case ENOSPC: /* no space left, treat it as a transient */ #ifdef EDQUOT /* NFS failure */ case EDQUOT: /* maybe it was a short term shortage? */ #endif case ENOENT: case ENOTDIR: case EIO: /*case EACCES:*/ if(++retries < (256 + 1)) /* nfsTRY number of times+1 to ignore spurious NFS errors */ usleep(250000); else failed = true; break; #ifdef ENAMETOOLONG case ENAMETOOLONG: /* Filename is too long, shorten and retry */ if (mPath.size() > mDir.size() + 8) { secdebug("atomicfile", "Truncating %s and retrying lock", path); mPath.erase(mPath.end() - 1); path = mPath.c_str(); /* Reset retry counter. */ retries = 0; break; } /* DROPTHROUGH */ #endif default: failed = true; break; } } if (failed) { int error = errno; ::syslog(LOG_ERR, "Lock failure on %s: %s", path, strerror(error)); secdebug("atomicfile", "Lock failure on %s: %s", path, strerror(error)); UnixError::throwMe(error); } } void NetworkFileLocker::unlock() { const char *path = mPath.c_str(); if (::unlink(path) == -1) { secdebug("atomicfile", "unlink %s: %s", path, strerror(errno)); // unlock can't throw } } AtomicLockedFile::AtomicLockedFile(AtomicFile &inFile) { if (inFile.isOnLocalFileSystem()) { mFileLocker = new LocalFileLocker(inFile); } else { mFileLocker = new NetworkFileLocker(inFile); } lock(); } AtomicLockedFile::~AtomicLockedFile() { unlock(); delete mFileLocker; } void AtomicLockedFile::lock(mode_t mode) { mFileLocker->lock(mode); } void AtomicLockedFile::unlock() throw() { mFileLocker->unlock(); } #undef kAtomicFileMaxBlockSize