michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "Hal.h" michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include "nsIObserverService.h" michael@0: #include "nsIDiskSpaceWatcher.h" michael@0: #include "mozilla/ModuleUtils.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "base/message_loop.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/Services.h" michael@0: #include "nsXULAppAPI.h" michael@0: #include "fanotify.h" michael@0: #include "DiskSpaceWatcher.h" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: namespace mozilla { namespace hal_impl { class GonkDiskSpaceWatcher; } } michael@0: michael@0: using namespace mozilla::hal_impl; michael@0: michael@0: template<> michael@0: struct RunnableMethodTraits michael@0: { michael@0: static void RetainCallee(GonkDiskSpaceWatcher* obj) { } michael@0: static void ReleaseCallee(GonkDiskSpaceWatcher* obj) { } michael@0: }; michael@0: michael@0: namespace mozilla { michael@0: namespace hal_impl { michael@0: michael@0: // fanotify_init and fanotify_mark functions are syscalls. michael@0: // The user space bits are not part of bionic so we add them here michael@0: // as well as fanotify.h michael@0: int fanotify_init (unsigned int flags, unsigned int event_f_flags) michael@0: { michael@0: return syscall(367, flags, event_f_flags); michael@0: } michael@0: michael@0: // Add, remove, or modify an fanotify mark on a filesystem object. michael@0: int fanotify_mark (int fanotify_fd, unsigned int flags, michael@0: uint64_t mask, int dfd, const char *pathname) michael@0: { michael@0: michael@0: // On 32 bits platforms we have to convert the 64 bits mask into michael@0: // two 32 bits ints. michael@0: if (sizeof(void *) == 4) { michael@0: union { michael@0: uint64_t _64; michael@0: uint32_t _32[2]; michael@0: } _mask; michael@0: _mask._64 = mask; michael@0: return syscall(368, fanotify_fd, flags, _mask._32[0], _mask._32[1], michael@0: dfd, pathname); michael@0: } michael@0: michael@0: return syscall(368, fanotify_fd, flags, mask, dfd, pathname); michael@0: } michael@0: michael@0: class GonkDiskSpaceWatcher MOZ_FINAL : public MessageLoopForIO::Watcher michael@0: { michael@0: public: michael@0: GonkDiskSpaceWatcher(); michael@0: ~GonkDiskSpaceWatcher() {}; michael@0: michael@0: virtual void OnFileCanReadWithoutBlocking(int aFd); michael@0: michael@0: // We should never write to the fanotify fd. michael@0: virtual void OnFileCanWriteWithoutBlocking(int aFd) michael@0: { michael@0: MOZ_CRASH("Must not write to fanotify fd"); michael@0: } michael@0: michael@0: void DoStart(); michael@0: void DoStop(); michael@0: michael@0: private: michael@0: void NotifyUpdate(); michael@0: michael@0: uint64_t mLowThreshold; michael@0: uint64_t mHighThreshold; michael@0: TimeDuration mTimeout; michael@0: TimeStamp mLastTimestamp; michael@0: uint64_t mLastFreeSpace; michael@0: uint32_t mSizeDelta; michael@0: michael@0: bool mIsDiskFull; michael@0: uint64_t mFreeSpace; michael@0: michael@0: int mFd; michael@0: MessageLoopForIO::FileDescriptorWatcher mReadWatcher; michael@0: }; michael@0: michael@0: static GonkDiskSpaceWatcher* gHalDiskSpaceWatcher = nullptr; michael@0: michael@0: #define WATCHER_PREF_LOW "disk_space_watcher.low_threshold" michael@0: #define WATCHER_PREF_HIGH "disk_space_watcher.high_threshold" michael@0: #define WATCHER_PREF_TIMEOUT "disk_space_watcher.timeout" michael@0: #define WATCHER_PREF_SIZE_DELTA "disk_space_watcher.size_delta" michael@0: michael@0: static const char kWatchedPath[] = "/data"; michael@0: michael@0: // Helper class to dispatch calls to xpcom on the main thread. michael@0: class DiskSpaceNotifier : public nsRunnable michael@0: { michael@0: public: michael@0: DiskSpaceNotifier(const bool aIsDiskFull, const uint64_t aFreeSpace) : michael@0: mIsDiskFull(aIsDiskFull), michael@0: mFreeSpace(aFreeSpace) {} michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: DiskSpaceWatcher::UpdateState(mIsDiskFull, mFreeSpace); michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: bool mIsDiskFull; michael@0: uint64_t mFreeSpace; michael@0: }; michael@0: michael@0: // Helper runnable to delete the watcher on the main thread. michael@0: class DiskSpaceCleaner : public nsRunnable michael@0: { michael@0: public: michael@0: NS_IMETHOD Run() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: if (gHalDiskSpaceWatcher) { michael@0: delete gHalDiskSpaceWatcher; michael@0: gHalDiskSpaceWatcher = nullptr; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: }; michael@0: michael@0: GonkDiskSpaceWatcher::GonkDiskSpaceWatcher() : michael@0: mLastFreeSpace(UINT64_MAX), michael@0: mIsDiskFull(false), michael@0: mFreeSpace(UINT64_MAX), michael@0: mFd(-1) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: MOZ_ASSERT(gHalDiskSpaceWatcher == nullptr); michael@0: michael@0: // Default values: 5MB for low threshold, 10MB for high threshold, and michael@0: // a timeout of 5 seconds. michael@0: mLowThreshold = Preferences::GetInt(WATCHER_PREF_LOW, 5) * 1024 * 1024; michael@0: mHighThreshold = Preferences::GetInt(WATCHER_PREF_HIGH, 10) * 1024 * 1024; michael@0: mTimeout = TimeDuration::FromSeconds(Preferences::GetInt(WATCHER_PREF_TIMEOUT, 5)); michael@0: mSizeDelta = Preferences::GetInt(WATCHER_PREF_SIZE_DELTA, 1) * 1024 * 1024; michael@0: } michael@0: michael@0: void michael@0: GonkDiskSpaceWatcher::DoStart() michael@0: { michael@0: NS_ASSERTION(XRE_GetIOMessageLoop() == MessageLoopForIO::current(), michael@0: "Not on the correct message loop"); michael@0: michael@0: mFd = fanotify_init(FAN_CLASS_NOTIF, FAN_CLOEXEC); michael@0: if (mFd == -1) { michael@0: NS_WARNING("Error calling inotify_init()"); michael@0: if (errno == ENOSYS) { michael@0: printf_stderr("Warning: No fanotify support in this device's kernel.\n"); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: if (fanotify_mark(mFd, FAN_MARK_ADD | FAN_MARK_MOUNT, FAN_CLOSE, michael@0: 0, kWatchedPath) < 0) { michael@0: NS_WARNING("Error calling fanotify_mark"); michael@0: close(mFd); michael@0: mFd = -1; michael@0: return; michael@0: } michael@0: michael@0: if (!MessageLoopForIO::current()->WatchFileDescriptor( michael@0: mFd, /* persistent = */ true, michael@0: MessageLoopForIO::WATCH_READ, michael@0: &mReadWatcher, gHalDiskSpaceWatcher)) { michael@0: NS_WARNING("Unable to watch fanotify fd."); michael@0: close(mFd); michael@0: mFd = -1; michael@0: } michael@0: } michael@0: michael@0: void michael@0: GonkDiskSpaceWatcher::DoStop() michael@0: { michael@0: NS_ASSERTION(XRE_GetIOMessageLoop() == MessageLoopForIO::current(), michael@0: "Not on the correct message loop"); michael@0: michael@0: if (mFd != -1) { michael@0: mReadWatcher.StopWatchingFileDescriptor(); michael@0: fanotify_mark(mFd, FAN_MARK_FLUSH, 0, 0, kWatchedPath); michael@0: close(mFd); michael@0: mFd = -1; michael@0: } michael@0: michael@0: // Dispatch the cleanup to the main thread. michael@0: nsCOMPtr runnable = new DiskSpaceCleaner(); michael@0: NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL); michael@0: } michael@0: michael@0: // We are called off the main thread, so we proxy first to the main thread michael@0: // before calling the xpcom object. michael@0: void michael@0: GonkDiskSpaceWatcher::NotifyUpdate() michael@0: { michael@0: mLastTimestamp = TimeStamp::Now(); michael@0: mLastFreeSpace = mFreeSpace; michael@0: michael@0: nsCOMPtr runnable = michael@0: new DiskSpaceNotifier(mIsDiskFull, mFreeSpace); michael@0: NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL); michael@0: } michael@0: michael@0: void michael@0: GonkDiskSpaceWatcher::OnFileCanReadWithoutBlocking(int aFd) michael@0: { michael@0: struct fanotify_event_metadata* fem = nullptr; michael@0: char buf[4096]; michael@0: struct statfs sfs; michael@0: int32_t len, rc; michael@0: michael@0: do { michael@0: len = read(aFd, buf, sizeof(buf)); michael@0: } while(len == -1 && errno == EINTR); michael@0: michael@0: // Bail out if the file is busy. michael@0: if (len < 0 && errno == ETXTBSY) { michael@0: return; michael@0: } michael@0: michael@0: // We should get an exact multiple of fanotify_event_metadata michael@0: if (len <= 0 || (len % FAN_EVENT_METADATA_LEN != 0)) { michael@0: printf_stderr("About to crash: fanotify_event_metadata read error."); michael@0: MOZ_CRASH(); michael@0: } michael@0: michael@0: fem = reinterpret_cast(buf); michael@0: michael@0: while (FAN_EVENT_OK(fem, len)) { michael@0: rc = fstatfs(fem->fd, &sfs); michael@0: if (rc < 0) { michael@0: NS_WARNING("Unable to stat fan_notify fd"); michael@0: } else { michael@0: bool firstRun = mFreeSpace == UINT64_MAX; michael@0: mFreeSpace = sfs.f_bavail * sfs.f_bsize; michael@0: // We change from full <-> free depending on the free space and the michael@0: // low and high thresholds. michael@0: // Once we are in 'full' mode we send updates for all size changes with michael@0: // a minimum of time between messages or when we cross a size change michael@0: // threshold. michael@0: if (firstRun) { michael@0: mIsDiskFull = mFreeSpace <= mLowThreshold; michael@0: // Always notify the current state at first run. michael@0: NotifyUpdate(); michael@0: } else if (!mIsDiskFull && (mFreeSpace <= mLowThreshold)) { michael@0: mIsDiskFull = true; michael@0: NotifyUpdate(); michael@0: } else if (mIsDiskFull && (mFreeSpace > mHighThreshold)) { michael@0: mIsDiskFull = false; michael@0: NotifyUpdate(); michael@0: } else if (mIsDiskFull) { michael@0: if (mTimeout < TimeStamp::Now() - mLastTimestamp || michael@0: mSizeDelta < llabs(mFreeSpace - mLastFreeSpace)) { michael@0: NotifyUpdate(); michael@0: } michael@0: } michael@0: } michael@0: close(fem->fd); michael@0: fem = FAN_EVENT_NEXT(fem, len); michael@0: } michael@0: } michael@0: michael@0: void michael@0: StartDiskSpaceWatcher() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: // Bail out if called several times. michael@0: if (gHalDiskSpaceWatcher != nullptr) { michael@0: return; michael@0: } michael@0: michael@0: gHalDiskSpaceWatcher = new GonkDiskSpaceWatcher(); michael@0: michael@0: XRE_GetIOMessageLoop()->PostTask( michael@0: FROM_HERE, michael@0: NewRunnableMethod(gHalDiskSpaceWatcher, &GonkDiskSpaceWatcher::DoStart)); michael@0: } michael@0: michael@0: void michael@0: StopDiskSpaceWatcher() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: if (!gHalDiskSpaceWatcher) { michael@0: return; michael@0: } michael@0: michael@0: XRE_GetIOMessageLoop()->PostTask( michael@0: FROM_HERE, michael@0: NewRunnableMethod(gHalDiskSpaceWatcher, &GonkDiskSpaceWatcher::DoStop)); michael@0: } michael@0: michael@0: } // namespace hal_impl michael@0: } // namespace mozilla