Bug 853350 - Implement a notification system to monitor disk space r=bent
authorFabrice Desré <fabrice@mozilla.com>
Thu, 09 May 2013 15:57:31 -0700
changeset 142433 1683f7d9f105bb0a5e922294b3951b4df9c3017b
parent 142432 1fca89c36c8b91fbb815b3768d6b2a18a026dfd4
child 142434 ff15e9b25bdd7a7a0599221f487b72520dd11551
push id2579
push userakeybl@mozilla.com
push dateMon, 24 Jun 2013 18:52:47 +0000
treeherdermozilla-beta@b69b7de8a05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbent
bugs853350
milestone23.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 853350 - Implement a notification system to monitor disk space r=bent
b2g/app/b2g.js
b2g/installer/package-manifest.in
hal/Hal.cpp
hal/Hal.h
hal/Makefile.in
hal/fallback/FallbackDiskSpaceWatcher.cpp
hal/gonk/GonkDiskSpaceWatcher.cpp
hal/gonk/fanotify.h
hal/sandbox/SandboxHal.cpp
toolkit/components/diskspacewatcher/DiskSpaceWatcher.cpp
toolkit/components/diskspacewatcher/DiskSpaceWatcher.h
toolkit/components/diskspacewatcher/Makefile.in
toolkit/components/diskspacewatcher/moz.build
toolkit/components/diskspacewatcher/nsIDiskSpaceWatcher.idl
toolkit/components/moz.build
toolkit/library/Makefile.in
toolkit/library/nsStaticXULComponents.cpp
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -712,8 +712,11 @@ pref("toolkit.storage.pageSize", 2048);
 #endif
 
 // Enable captive portal detection.
 pref("captivedetect.canonicalURL", "http://detectportal.firefox.com/success.txt");
 pref("captivedetect.canonicalContent", "success\n");
 
 // The url of the manifest we use for ADU pings.
 pref("ping.manifestURL", "https://marketplace.firefox.com/packaged.webapp");
+
+// Enable the disk space watcher
+pref("disk_space_watcher.enabled", true);
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -150,16 +150,17 @@
 @BINPATH@/components/content_base.xpt
 @BINPATH@/components/content_events.xpt
 @BINPATH@/components/content_canvas.xpt
 @BINPATH@/components/content_htmldoc.xpt
 @BINPATH@/components/content_html.xpt
 @BINPATH@/components/content_xslt.xpt
 @BINPATH@/components/cookie.xpt
 @BINPATH@/components/directory.xpt
+@BINPATH@/components/diskspacewatcher.xpt
 @BINPATH@/components/docshell.xpt
 @BINPATH@/components/dom.xpt
 @BINPATH@/components/dom_activities.xpt
 @BINPATH@/components/dom_apps.xpt
 @BINPATH@/components/dom_audiochannel.xpt
 @BINPATH@/components/dom_base.xpt
 @BINPATH@/components/dom_system.xpt
 #ifdef MOZ_B2G_RIL
--- a/hal/Hal.cpp
+++ b/hal/Hal.cpp
@@ -1153,10 +1153,26 @@ GetFMBandSettings(FMRadioCountry aCountr
 }
 
 void FactoryReset()
 {
   AssertMainThread();
   PROXY_IF_SANDBOXED(FactoryReset());
 }
 
+void
+StartDiskSpaceWatcher()
+{
+  AssertMainProcess();
+  AssertMainThread();
+  PROXY_IF_SANDBOXED(StartDiskSpaceWatcher());
+}
+
+void
+StopDiskSpaceWatcher()
+{
+  AssertMainProcess();
+  AssertMainThread();
+  PROXY_IF_SANDBOXED(StopDiskSpaceWatcher());
+}
+
 } // namespace hal
 } // namespace mozilla
--- a/hal/Hal.h
+++ b/hal/Hal.h
@@ -244,17 +244,17 @@ void NotifyNetworkChange(const hal::Netw
 /**
  * Adjusting system clock.
  * @param aDeltaMilliseconds The difference compared with current system clock.
  */
 void AdjustSystemClock(int64_t aDeltaMilliseconds);
 
 /**
  * Set timezone
- * @param aTimezoneSpec The definition can be found in 
+ * @param aTimezoneSpec The definition can be found in
  * http://en.wikipedia.org/wiki/List_of_tz_database_time_zones
  */
 void SetTimezone(const nsCString& aTimezoneSpec);
 
 /**
  * Get timezone
  * http://en.wikipedia.org/wiki/List_of_tz_database_time_zones
  */
@@ -298,24 +298,24 @@ void UnregisterSystemTimezoneChangeObser
  * Notify of a change in the system timezone.
  * @param aSystemTimezoneChangeInfo
  */
 void NotifySystemTimezoneChange(
   const hal::SystemTimezoneChangeInformation& aSystemTimezoneChangeInfo);
 
 /**
  * Reboot the device.
- * 
+ *
  * This API is currently only allowed to be used from the main process.
  */
 void Reboot();
 
 /**
  * Power off the device.
- * 
+ *
  * This API is currently only allowed to be used from the main process.
  */
 void PowerOff();
 
 /**
  * Enable wake lock notifications from the backend.
  *
  * This method is only used by WakeLockObserversManager.
@@ -418,17 +418,17 @@ void UnlockScreenOrientation();
 void RegisterSwitchObserver(hal::SwitchDevice aDevice, hal::SwitchObserver *aSwitchObserver);
 
 /**
  * Unregister an observer for the switch of given SwitchDevice.
  */
 void UnregisterSwitchObserver(hal::SwitchDevice aDevice, hal::SwitchObserver *aSwitchObserver);
 
 /**
- * Notify the state of the switch. 
+ * Notify the state of the switch.
  *
  * This API is internal to hal; clients shouldn't call it directly.
  */
 void NotifySwitchChange(const hal::SwitchEvent& aEvent);
 
 /**
  * Get current switch information.
  */
@@ -552,17 +552,17 @@ void CancelFMRadioSeek();
  * Get FM radio band settings by country.
  */
 hal::FMRadioSettings GetFMBandSettings(hal::FMRadioCountry aCountry);
 
 /**
  * Start a watchdog to compulsively shutdown the system if it hangs.
  * @param aMode Specify how to shutdown the system.
  * @param aTimeoutSecs Specify the delayed seconds to shutdown the system.
- * 
+ *
  * This API is currently only allowed to be used from the main process.
  */
 void StartForceQuitWatchdog(hal::ShutdownMode aMode, int32_t aTimeoutSecs);
 
 /**
  * Perform Factory Reset to wipe out all user data.
  */
 void FactoryReset();
@@ -572,16 +572,30 @@ void FactoryReset();
  */
 void StartMonitoringGamepadStatus();
 
 /**
  * Stop monitoring the status of gamepads attached to the system.
  */
 void StopMonitoringGamepadStatus();
 
+/**
+ * Start monitoring disk space for low space situations.
+ *
+ * This API is currently only allowed to be used from the main process.
+ */
+void StartDiskSpaceWatcher();
+
+/**
+ * Stop monitoring disk space for low space situations.
+ *
+ * This API is currently only allowed to be used from the main process.
+ */
+void StopDiskSpaceWatcher();
+
 } // namespace MOZ_HAL_NAMESPACE
 } // namespace mozilla
 
 #ifdef MOZ_DEFINED_HAL_NAMESPACE
 # undef MOZ_DEFINED_HAL_NAMESPACE
 # undef MOZ_HAL_NAMESPACE
 #endif
 
--- a/hal/Makefile.in
+++ b/hal/Makefile.in
@@ -55,16 +55,17 @@ CPPSRCS += \
 else ifeq (gonk,$(MOZ_WIDGET_TOOLKIT))
 CPPSRCS += \
   GonkHal.cpp \
   LinuxPower.cpp \
   GonkSensor.cpp \
   UeventPoller.cpp \
   GonkSwitch.cpp \
   GonkFMRadio.cpp \
+  GonkDiskSpaceWatcher.cpp \
   $(NULL)
 else ifeq (Linux,$(OS_TARGET))
 CPPSRCS += \
   LinuxPower.cpp \
   FallbackScreenConfiguration.cpp \
   FallbackSensor.cpp \
   FallbackVibration.cpp \
   FallbackAlarm.cpp \
@@ -129,16 +130,17 @@ CPPSRCS += \
   FallbackLights.cpp  \
   FallbackTime.cpp \
   FallbackWakeLocks.cpp \
   FallbackSwitch.cpp \
   FallbackScreenPower.cpp \
   FallbackProcessPriority.cpp \
   FallbackFMRadio.cpp \
   FallbackFactoryReset.cpp \
+  FallbackDiskSpaceWatcher.cpp \
   $(NULL)
 endif #}
 
 # Fallbacks for backends implemented on Android only.
 ifneq (android,$(MOZ_WIDGET_TOOLKIT))
 CPPSRCS += FallbackNetwork.cpp
 endif
 
new file mode 100644
--- /dev/null
+++ b/hal/fallback/FallbackDiskSpaceWatcher.cpp
@@ -0,0 +1,19 @@
+/* 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/. */
+
+namespace mozilla {
+namespace hal_impl {
+
+void
+StartDiskSpaceWatcher()
+{
+}
+
+void
+StopDiskSpaceWatcher()
+{
+}
+
+} // namespace hal_impl
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/hal/gonk/GonkDiskSpaceWatcher.cpp
@@ -0,0 +1,309 @@
+/* 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 "nsIObserverService.h"
+#include "nsIDiskSpaceWatcher.h"
+#include "mozilla/ModuleUtils.h"
+#include "nsAutoPtr.h"
+#include "nsThreadUtils.h"
+#include "base/message_loop.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "nsXULAppAPI.h"
+#include "fanotify.h"
+#include "DiskSpaceWatcher.h"
+
+using namespace mozilla;
+
+namespace mozilla { namespace hal_impl { class GonkDiskSpaceWatcher; } }
+
+using namespace mozilla::hal_impl;
+
+template<>
+struct RunnableMethodTraits<GonkDiskSpaceWatcher>
+{
+  static void RetainCallee(GonkDiskSpaceWatcher* obj) { }
+  static void ReleaseCallee(GonkDiskSpaceWatcher* obj) { }
+};
+
+namespace mozilla {
+namespace hal_impl {
+
+// 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(367, 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(368, fanotify_fd, flags, _mask._32[0], _mask._32[1],
+                   dfd, pathname);
+  }
+
+  return syscall(368, fanotify_fd, flags, mask, dfd, pathname);
+}
+
+class GonkDiskSpaceWatcher MOZ_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_NOT_REACHED("Must not write to fanotify fd");
+    MOZ_CRASH();
+  }
+
+  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 nsRunnable
+{
+public:
+  DiskSpaceNotifier(const bool aIsDiskFull, const uint64_t aFreeSpace) :
+    mIsDiskFull(aIsDiskFull),
+    mFreeSpace(aFreeSpace) {}
+
+  NS_IMETHOD Run()
+  {
+    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 nsRunnable
+{
+public:
+  NS_IMETHOD Run()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    delete gHalDiskSpaceWatcher;
+    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);
+  if (mFd == -1) {
+    NS_WARNING("Error calling inotify_init()");
+    if (errno == ENOSYS) {
+      printf_stderr("Warning: No fanotify support in this device's kernel.\n");
+    }
+    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, NS_DISPATCH_NORMAL);
+}
+
+// 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, NS_DISPATCH_NORMAL);
+}
+
+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);
+
+  // We should get an exact multiple of fanotify_event_metadata
+  if (len <= 0 || (len % sizeof(*fem) != 0)) {
+    printf_stderr("About to crash: fanotify_event_metadata read error.");
+    MOZ_CRASH();
+  }
+
+  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());
+  MOZ_ASSERT(aService);
+
+  // Bail out if called several times.
+  if (gHalDiskSpaceWatcher != nullptr) {
+    return;
+  }
+
+  gHalDiskSpaceWatcher = new GonkDiskSpaceWatcher();
+
+  XRE_GetIOMessageLoop()->PostTask(
+    FROM_HERE,
+    NewRunnableMethod(gHalDiskSpaceWatcher, &GonkDiskSpaceWatcher::DoStart));
+}
+
+void
+StopDiskSpaceWatcher()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  if (!gHalDiskSpaceWatcher) {
+    return;
+  }
+
+  XRE_GetIOMessageLoop()->PostTask(
+    FROM_HERE,
+    NewRunnableMethod(gHalDiskSpaceWatcher, &GonkDiskSpaceWatcher::DoStop));
+}
+
+} // namespace hal_impl
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/hal/gonk/fanotify.h
@@ -0,0 +1,118 @@
+#ifndef _LINUX_FANOTIFY_H
+#define _LINUX_FANOTIFY_H
+
+/* This is a Linux header generated by "make headers_install" */
+
+#include <linux/types.h>
+
+/* the following events that user-space can register for */
+#define FAN_ACCESS		0x00000001	/* File was accessed */
+#define FAN_MODIFY		0x00000002	/* File was modified */
+#define FAN_CLOSE_WRITE		0x00000008	/* Writtable file closed */
+#define FAN_CLOSE_NOWRITE	0x00000010	/* Unwrittable file closed */
+#define FAN_OPEN		0x00000020	/* File was opened */
+
+#define FAN_Q_OVERFLOW		0x00004000	/* Event queued overflowed */
+
+#define FAN_OPEN_PERM		0x00010000	/* File open in perm check */
+#define FAN_ACCESS_PERM		0x00020000	/* File accessed in perm check */
+
+#define FAN_ONDIR		0x40000000	/* event occurred against dir */
+
+#define FAN_EVENT_ON_CHILD	0x08000000	/* interested in child events */
+
+/* helper events */
+#define FAN_CLOSE		(FAN_CLOSE_WRITE | FAN_CLOSE_NOWRITE) /* close */
+
+/* flags used for fanotify_init() */
+#define FAN_CLOEXEC		0x00000001
+#define FAN_NONBLOCK		0x00000002
+
+/* These are NOT bitwise flags.  Both bits are used togther.  */
+#define FAN_CLASS_NOTIF		0x00000000
+#define FAN_CLASS_CONTENT	0x00000004
+#define FAN_CLASS_PRE_CONTENT	0x00000008
+#define FAN_ALL_CLASS_BITS	(FAN_CLASS_NOTIF | FAN_CLASS_CONTENT | \
+				 FAN_CLASS_PRE_CONTENT)
+
+#define FAN_UNLIMITED_QUEUE	0x00000010
+#define FAN_UNLIMITED_MARKS	0x00000020
+
+#define FAN_ALL_INIT_FLAGS	(FAN_CLOEXEC | FAN_NONBLOCK | \
+				 FAN_ALL_CLASS_BITS | FAN_UNLIMITED_QUEUE |\
+				 FAN_UNLIMITED_MARKS)
+
+/* flags used for fanotify_modify_mark() */
+#define FAN_MARK_ADD		0x00000001
+#define FAN_MARK_REMOVE		0x00000002
+#define FAN_MARK_DONT_FOLLOW	0x00000004
+#define FAN_MARK_ONLYDIR	0x00000008
+#define FAN_MARK_MOUNT		0x00000010
+#define FAN_MARK_IGNORED_MASK	0x00000020
+#define FAN_MARK_IGNORED_SURV_MODIFY	0x00000040
+#define FAN_MARK_FLUSH		0x00000080
+
+#define FAN_ALL_MARK_FLAGS	(FAN_MARK_ADD |\
+				 FAN_MARK_REMOVE |\
+				 FAN_MARK_DONT_FOLLOW |\
+				 FAN_MARK_ONLYDIR |\
+				 FAN_MARK_MOUNT |\
+				 FAN_MARK_IGNORED_MASK |\
+				 FAN_MARK_IGNORED_SURV_MODIFY |\
+				 FAN_MARK_FLUSH)
+
+/*
+ * All of the events - we build the list by hand so that we can add flags in
+ * the future and not break backward compatibility.  Apps will get only the
+ * events that they originally wanted.  Be sure to add new events here!
+ */
+#define FAN_ALL_EVENTS (FAN_ACCESS |\
+			FAN_MODIFY |\
+			FAN_CLOSE |\
+			FAN_OPEN)
+
+/*
+ * All events which require a permission response from userspace
+ */
+#define FAN_ALL_PERM_EVENTS (FAN_OPEN_PERM |\
+			     FAN_ACCESS_PERM)
+
+#define FAN_ALL_OUTGOING_EVENTS	(FAN_ALL_EVENTS |\
+				 FAN_ALL_PERM_EVENTS |\
+				 FAN_Q_OVERFLOW)
+
+#define FANOTIFY_METADATA_VERSION	3
+
+struct fanotify_event_metadata {
+	__u32 event_len;
+	__u8 vers;
+	__u8 reserved;
+	__u16 metadata_len;
+	__u64 mask;
+	__s32 fd;
+	__s32 pid;
+};
+
+struct fanotify_response {
+	__s32 fd;
+	__u32 response;
+};
+
+/* Legit userspace responses to a _PERM event */
+#define FAN_ALLOW	0x01
+#define FAN_DENY	0x02
+/* No fd set in event */
+#define FAN_NOFD	-1
+
+/* Helper functions to deal with fanotify_event_metadata buffers */
+#define FAN_EVENT_METADATA_LEN (sizeof(struct fanotify_event_metadata))
+
+#define FAN_EVENT_NEXT(meta, len) ((len) -= (meta)->event_len, \
+				   (struct fanotify_event_metadata*)(((char *)(meta)) + \
+				   (meta)->event_len))
+
+#define FAN_EVENT_OK(meta, len)	((long)(len) >= (long)FAN_EVENT_METADATA_LEN && \
+				(long)(meta)->event_len >= (long)FAN_EVENT_METADATA_LEN && \
+				(long)(meta)->event_len <= (long)(len))
+
+#endif /* _LINUX_FANOTIFY_H */
--- a/hal/sandbox/SandboxHal.cpp
+++ b/hal/sandbox/SandboxHal.cpp
@@ -404,16 +404,28 @@ CancelFMRadioSeek()
 }
 
 void
 FactoryReset()
 {
   Hal()->SendFactoryReset();
 }
 
+void
+StartDiskSpaceWatcher()
+{
+  NS_RUNTIMEABORT("StartDiskSpaceWatcher() can't be called from sandboxed contexts.");
+}
+
+void
+StopDiskSpaceWatcher()
+{
+  NS_RUNTIMEABORT("StopDiskSpaceWatcher() can't be called from sandboxed contexts.");
+}
+
 class HalParent : public PHalParent
                 , public BatteryObserver
                 , public NetworkObserver
                 , public ISensorObserver
                 , public WakeLockObserver
                 , public ScreenConfigurationObserver
                 , public SwitchObserver
                 , public SystemClockChangeObserver
new file mode 100644
--- /dev/null
+++ b/toolkit/components/diskspacewatcher/DiskSpaceWatcher.cpp
@@ -0,0 +1,153 @@
+/* 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 "DiskSpaceWatcher.h"
+#include "mozilla/Hal.h"
+#include "mozilla/ModuleUtils.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ClearOnShutdown.h"
+
+#define NS_DISKSPACEWATCHER_CID \
+  { 0xab218518, 0xf197, 0x4fb4, { 0x8b, 0x0f, 0x8b, 0xb3, 0x4d, 0xf2, 0x4b, 0xf4 } }
+
+using namespace mozilla;
+
+StaticRefPtr<DiskSpaceWatcher> gDiskSpaceWatcher;
+
+NS_IMPL_ISUPPORTS2(DiskSpaceWatcher, nsIDiskSpaceWatcher, nsIObserver)
+
+uint64_t DiskSpaceWatcher::sFreeSpace = 0;
+bool DiskSpaceWatcher::sIsDiskFull = false;
+
+DiskSpaceWatcher::DiskSpaceWatcher()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!gDiskSpaceWatcher);
+}
+
+DiskSpaceWatcher::~DiskSpaceWatcher()
+{
+  MOZ_ASSERT(!gDiskSpaceWatcher);
+}
+
+already_AddRefed<DiskSpaceWatcher>
+DiskSpaceWatcher::FactoryCreate()
+{
+  if (XRE_GetProcessType() != GeckoProcessType_Default) {
+    return nullptr;
+  }
+
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!Preferences::GetBool("disk_space_watcher.enabled", false)) {
+    return nullptr;
+  }
+
+  if (!gDiskSpaceWatcher) {
+    gDiskSpaceWatcher = new DiskSpaceWatcher();
+    ClearOnShutdown(&gDiskSpaceWatcher);
+  }
+
+  nsRefPtr<DiskSpaceWatcher> service = gDiskSpaceWatcher.get();
+  return service.forget();
+}
+
+NS_IMETHODIMP
+DiskSpaceWatcher::Observe(nsISupports* aSubject, const char* aTopic,
+                          const PRUnichar* aData)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!strcmp(aTopic, "profile-after-change")) {
+    nsCOMPtr<nsIObserverService> observerService =
+      mozilla::services::GetObserverService();
+    observerService->AddObserver(this, "profile-before-change", false);
+    mozilla::hal::StartDiskSpaceWatcher();
+    return NS_OK;
+  }
+
+  if (!strcmp(aTopic, "profile-before-change")) {
+    nsCOMPtr<nsIObserverService> observerService =
+      mozilla::services::GetObserverService();
+    observerService->RemoveObserver(this, "profile-before-change");
+    mozilla::hal::StopDiskSpaceWatcher();
+    return NS_OK;
+  }
+
+  MOZ_ASSERT(false, "DiskSpaceWatcher got unexpected topic!");
+  return NS_ERROR_UNEXPECTED;
+}
+
+/* readonly attribute bool isDiskFull; */
+NS_IMETHODIMP DiskSpaceWatcher::GetIsDiskFull(bool* aIsDiskFull)
+{
+  *aIsDiskFull = sIsDiskFull;
+  return NS_OK;
+}
+
+/* readonly attribute long freeSpace; */
+NS_IMETHODIMP DiskSpaceWatcher::GetFreeSpace(uint64_t* aFreeSpace)
+{
+  *aFreeSpace = sFreeSpace;
+  return NS_OK;
+}
+
+// static
+void DiskSpaceWatcher::UpdateState(bool aIsDiskFull, uint64_t aFreeSpace)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  if (!gDiskSpaceWatcher) {
+    return;
+  }
+
+  nsCOMPtr<nsIObserverService> observerService =
+    mozilla::services::GetObserverService();
+
+  sIsDiskFull = aIsDiskFull;
+  sFreeSpace = aFreeSpace;
+
+  if (!observerService) {
+    return;
+  }
+
+  const PRUnichar stateFull[] = { 'f', 'u', 'l', 'l', 0 };
+  const PRUnichar stateFree[] = { 'f', 'r', 'e', 'e', 0 };
+
+  nsCOMPtr<nsISupports> subject;
+  CallQueryInterface(gDiskSpaceWatcher.get(), getter_AddRefs(subject));
+  MOZ_ASSERT(subject);
+  observerService->NotifyObservers(subject,
+                                   DISKSPACEWATCHER_OBSERVER_TOPIC,
+                                   sIsDiskFull ? stateFull : stateFree);
+  return;
+}
+
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(DiskSpaceWatcher,
+                                         DiskSpaceWatcher::FactoryCreate)
+
+NS_DEFINE_NAMED_CID(NS_DISKSPACEWATCHER_CID);
+
+static const mozilla::Module::CIDEntry kDiskSpaceWatcherCIDs[] = {
+  { &kNS_DISKSPACEWATCHER_CID, false, nullptr, DiskSpaceWatcherConstructor },
+  { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kDiskSpaceWatcherContracts[] = {
+  { "@mozilla.org/toolkit/disk-space-watcher;1", &kNS_DISKSPACEWATCHER_CID },
+  { nullptr }
+};
+
+static const mozilla::Module::CategoryEntry kDiskSpaceWatcherCategories[] = {
+  { "profile-after-change", "Disk Space Watcher Service", DISKSPACEWATCHER_CONTRACTID },
+  { nullptr }
+};
+
+static const mozilla::Module kDiskSpaceWatcherModule = {
+  mozilla::Module::kVersion,
+  kDiskSpaceWatcherCIDs,
+  kDiskSpaceWatcherContracts,
+  kDiskSpaceWatcherCategories
+};
+
+NSMODULE_DEFN(DiskSpaceWatcherModule) = &kDiskSpaceWatcherModule;
new file mode 100644
--- /dev/null
+++ b/toolkit/components/diskspacewatcher/DiskSpaceWatcher.h
@@ -0,0 +1,32 @@
+/* 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/. */
+
+#ifndef __DISKSPACEWATCHER_H__
+
+#include "nsIDiskSpaceWatcher.h"
+#include "nsIObserver.h"
+#include "nsCOMPtr.h"
+
+class DiskSpaceWatcher MOZ_FINAL : public nsIDiskSpaceWatcher,
+                                   public nsIObserver
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIDISKSPACEWATCHER
+  NS_DECL_NSIOBSERVER
+
+  static already_AddRefed<DiskSpaceWatcher>
+  FactoryCreate();
+
+  static void UpdateState(bool aIsDiskFull, uint64_t aFreeSpace);
+
+private:
+  DiskSpaceWatcher();
+  ~DiskSpaceWatcher();
+
+  static uint64_t sFreeSpace;
+  static bool     sIsDiskFull;
+};
+
+#endif // __DISKSPACEWATCHER_H__
new file mode 100644
--- /dev/null
+++ b/toolkit/components/diskspacewatcher/Makefile.in
@@ -0,0 +1,23 @@
+# 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/.
+
+DEPTH     = @DEPTH@
+topsrcdir = @top_srcdir@
+srcdir    = @srcdir@
+VPATH     = @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+LIBRARY_NAME   = diskspacewatcher
+EXPORT_LIBRARY = 1
+IS_COMPONENT   = 1
+MODULE_NAME    = DiskSpaceWatcherModule
+LIBXUL_LIBRARY = 1
+
+CPPSRCS = \
+  DiskSpaceWatcher.cpp \
+  $(NULL)
+
+include $(topsrcdir)/config/rules.mk
+include $(topsrcdir)/ipc/chromium/chromium-config.mk
new file mode 100644
--- /dev/null
+++ b/toolkit/components/diskspacewatcher/moz.build
@@ -0,0 +1,16 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+XPIDL_SOURCES += [
+    'nsIDiskSpaceWatcher.idl',
+]
+
+EXPORTS += [
+    'DiskSpaceWatcher.h'
+]
+
+XPIDL_MODULE = 'diskspacewatcher'
+MODULE = 'toolkitcomps'
new file mode 100644
--- /dev/null
+++ b/toolkit/components/diskspacewatcher/nsIDiskSpaceWatcher.idl
@@ -0,0 +1,19 @@
+/* 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 "nsISupports.idl"
+
+[scriptable, uuid(3aceba74-2ed5-4e99-8fe4-06e90e2b8ef0)]
+interface nsIDiskSpaceWatcher : nsISupports
+{
+  readonly attribute bool isDiskFull; // True if we are low on disk space.
+  readonly attribute unsigned long long freeSpace; // The free space currently available.
+};
+
+%{ C++
+#define DISKSPACEWATCHER_CONTRACTID "@mozilla.org/toolkit/disk-space-watcher;1"
+
+// The data for this notification will be either 'free' or 'full'.
+#define DISKSPACEWATCHER_OBSERVER_TOPIC "disk-space-watcher"
+%}
--- a/toolkit/components/moz.build
+++ b/toolkit/components/moz.build
@@ -11,16 +11,17 @@ if CONFIG['MOZ_ENABLE_XREMOTE']:
 PARALLEL_DIRS += [
     'aboutmemory',
     'alerts',
     'apppicker',
     'commandlines',
     'console',
     'contentprefs',
     'cookie',
+    'diskspacewatcher',
     'downloads',
     'exthelper',
     'filepicker',
     'find',
     'intl',
     'jsdownloads',
     'mediasniffer',
     'microformats',
--- a/toolkit/library/Makefile.in
+++ b/toolkit/library/Makefile.in
@@ -174,16 +174,17 @@ COMPONENT_LIBS += \
   jsreflect \
   composer \
   telemetry \
   jsinspector \
   jsdebugger \
   storagecomps \
   rdf \
   windowds \
+  diskspacewatcher \
   $(NULL)
 
 ifdef BUILD_CTYPES
 COMPONENT_LIBS += \
   jsctypes \
   $(NULL)
 endif
 
--- a/toolkit/library/nsStaticXULComponents.cpp
+++ b/toolkit/library/nsStaticXULComponents.cpp
@@ -237,16 +237,17 @@
     MODULE(identity)                         \
     MODULE(nsServicesCryptoModule)           \
     MOZ_APP_COMPONENT_MODULES                \
     MODULE(nsTelemetryModule)                \
     MODULE(jsinspector)                      \
     MODULE(jsdebugger)                       \
     PEERCONNECTION_MODULE                    \
     GIO_MODULE                               \
+    MODULE(DiskSpaceWatcherModule)           \
     /* end of list */
 
 #define MODULE(_name) \
   NSMODULE_DECL(_name);
 
 XUL_MODULES
 
 #undef MODULE