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