summaryrefslogtreecommitdiffstats
path: root/hal/gonk/GonkDiskSpaceWatcher.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'hal/gonk/GonkDiskSpaceWatcher.cpp')
-rw-r--r--hal/gonk/GonkDiskSpaceWatcher.cpp324
1 files changed, 324 insertions, 0 deletions
diff --git a/hal/gonk/GonkDiskSpaceWatcher.cpp b/hal/gonk/GonkDiskSpaceWatcher.cpp
new file mode 100644
index 000000000..cdc48ef89
--- /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