/* * Copyright 2012, The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "bcc/Support/FileBase.h" #include "bcc/Support/Log.h" #include <sys/file.h> #include <sys/stat.h> #include <unistd.h> #include <cerrno> #include <cstring> #include <new> #include <utils/FileMap.h> using namespace bcc; #ifdef _WIN32 // TODO: Fix flock usage under windows #define LOCK_SH 0 #define LOCK_EX 0 #define LOCK_NB 0 #define LOCK_UN 0 int flock(int fd, int operation) { return 0; } #endif // _WIN32 FileBase::FileBase(const std::string &pFilename, unsigned pOpenFlags, unsigned pFlags) : mFD(-1), mError(), mName(pFilename), mOpenFlags(pOpenFlags), mShouldUnlock(false), mShouldDelete(false) { // Process pFlags #ifdef O_BINARY if (pFlags & kBinary) { mOpenFlags |= O_BINARY; } #endif if (pFlags & kTruncate) { mOpenFlags |= O_TRUNC; } if (pFlags & kAppend) { mOpenFlags |= O_APPEND; } if (pFlags & kDeleteOnClose) { mShouldDelete = true; } // Open the file. open(); return; } FileBase::~FileBase() { close(); } bool FileBase::open() { do { // FIXME: Hard-coded permissions (0644) for newly created file should be // removed and provide a way to let the user configure the value. mFD = ::open(mName.c_str(), mOpenFlags, 0644); if (mFD > 0) { return true; } // Some errors occurred ... if (errno != EINTR) { detectError(); return false; } } while (true); // unreachable } bool FileBase::checkFileIntegrity() { // Check the file integrity by examining whether the inode referring to the mFD // and to the file mName are the same. struct stat fd_stat, file_stat; // Get the file status of file descriptor mFD. do { if (::fstat(mFD, &fd_stat) == 0) { break; } else if (errno != EINTR) { detectError(); return false; } } while (true); // Get the file status of file mName. do { if (::stat(mName.c_str(), &file_stat) == 0) { break; } else if (errno != EINTR) { detectError(); return false; } } while (true); return ((fd_stat.st_dev == file_stat.st_dev) && (fd_stat.st_ino == file_stat.st_ino)); } void FileBase::detectError() { // Read error from errno. mError.assign(errno, std::generic_category()); } bool FileBase::lock(enum LockModeEnum pMode, bool pNonblocking, unsigned pMaxRetry, useconds_t pRetryInterval) { int lock_operation; unsigned retry = 0; // Check the state. if ((mFD < 0) || hasError()) { return false; } // Return immediately if it's already locked. if (mShouldUnlock) { return true; } // Determine the lock operation (2nd argument) to the flock(). if (pMode == kReadLock) { lock_operation = LOCK_SH; } else if (pMode == kWriteLock) { lock_operation = LOCK_EX; } else { mError = std::make_error_code(std::errc::invalid_argument); return false; } if (pNonblocking) { lock_operation |= LOCK_NB; } do { if (::flock(mFD, lock_operation) == 0) { mShouldUnlock = true; // Here we got a lock but we need to check whether the mFD still // "represents" the filename (mName) we opened in the contructor. This // check may failed when another process deleted the original file mFD // mapped when we were trying to obtain the lock on the file. if (!checkFileIntegrity()) { if (hasError() || !reopen()) { // Error occurred when check the file integrity or re-open the file. return false; } else { // Wait a while before the next try. ::usleep(pRetryInterval); retry++; continue; } } return true; } // flock() was not performed successfully. Check the errno to see whether // it's retry-able. if (errno == EINTR) { // flock() was interrupted by delivery of a signal. Restart without // decrement the retry counter. continue; } else if (errno == EWOULDBLOCK) { // The file descriptor was locked by others, wait for a while before next // retry. retry++; ::usleep(pRetryInterval); } else { // There's a fatal error occurs when perform flock(). Return immediately // without further retry. detectError(); return false; } } while (retry <= pMaxRetry); return false; } void FileBase::unlock() { if (mFD < 0) { return; } do { if (::flock(mFD, LOCK_UN) == 0) { mShouldUnlock = false; return; } } while (errno == EINTR); detectError(); return; } android::FileMap *FileBase::createMap(off_t pOffset, size_t pLength, bool pIsReadOnly) { if (mFD < 0 || hasError()) { return nullptr; } android::FileMap *map = new (std::nothrow) android::FileMap(); if (map == nullptr) { mError = make_error_code(std::errc::not_enough_memory); return nullptr; } if (!map->create(nullptr, mFD, pOffset, pLength, pIsReadOnly)) { detectError(); delete map; return nullptr; } return map; } size_t FileBase::getSize() { if (mFD < 0 || hasError()) { return static_cast<size_t>(-1); } struct stat file_stat; do { if (::fstat(mFD, &file_stat) == 0) { break; } else if (errno != EINTR) { detectError(); return static_cast<size_t>(-1); } } while (true); return file_stat.st_size; } off_t FileBase::seek(off_t pOffset) { if ((mFD < 0) || hasError()) { return static_cast<off_t>(-1); } do { off_t result = ::lseek(mFD, pOffset, SEEK_SET); if (result == pOffset) { return result; } } while (errno == EINTR); detectError(); return static_cast<off_t>(-1); } off_t FileBase::tell() { if ((mFD < 0) || hasError()) { return static_cast<off_t>(-1); } do { off_t result = ::lseek(mFD, 0, SEEK_CUR); if (result != static_cast<off_t>(-1)) { return result; } } while (errno == EINTR); detectError(); return static_cast<off_t>(-1); } void FileBase::close() { if (mShouldUnlock) { unlock(); mShouldUnlock = false; } if (mFD > 0) { ::close(mFD); mFD = -1; } if (mShouldDelete) { int res = ::remove(mName.c_str()); if (res != 0) { ALOGE("Failed to remove file: %s - %s", mName.c_str(), ::strerror(res)); } } return; }