hal/gonk/GonkDiskSpaceWatcher.cpp

Wed, 31 Dec 2014 07:16:47 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:16:47 +0100
branch
TOR_BUG_9701
changeset 3
141e0f1194b1
permissions
-rw-r--r--

Revert simplistic fix pending revisit of Mozilla integration attempt.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     3  * You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 #include "Hal.h"
     6 #include <sys/syscall.h>
     7 #include <sys/vfs.h>
     8 #include <fcntl.h>
     9 #include <errno.h>
    10 #include "nsIObserverService.h"
    11 #include "nsIDiskSpaceWatcher.h"
    12 #include "mozilla/ModuleUtils.h"
    13 #include "nsAutoPtr.h"
    14 #include "nsThreadUtils.h"
    15 #include "base/message_loop.h"
    16 #include "mozilla/Preferences.h"
    17 #include "mozilla/Services.h"
    18 #include "nsXULAppAPI.h"
    19 #include "fanotify.h"
    20 #include "DiskSpaceWatcher.h"
    22 using namespace mozilla;
    24 namespace mozilla { namespace hal_impl { class GonkDiskSpaceWatcher; } }
    26 using namespace mozilla::hal_impl;
    28 template<>
    29 struct RunnableMethodTraits<GonkDiskSpaceWatcher>
    30 {
    31   static void RetainCallee(GonkDiskSpaceWatcher* obj) { }
    32   static void ReleaseCallee(GonkDiskSpaceWatcher* obj) { }
    33 };
    35 namespace mozilla {
    36 namespace hal_impl {
    38 // fanotify_init and fanotify_mark functions are syscalls.
    39 // The user space bits are not part of bionic so we add them here
    40 // as well as fanotify.h
    41 int fanotify_init (unsigned int flags, unsigned int event_f_flags)
    42 {
    43   return syscall(367, flags, event_f_flags);
    44 }
    46 // Add, remove, or modify an fanotify mark on a filesystem object.
    47 int fanotify_mark (int fanotify_fd, unsigned int flags,
    48                    uint64_t mask, int dfd, const char *pathname)
    49 {
    51   // On 32 bits platforms we have to convert the 64 bits mask into
    52   // two 32 bits ints.
    53   if (sizeof(void *) == 4) {
    54     union {
    55       uint64_t _64;
    56       uint32_t _32[2];
    57     } _mask;
    58     _mask._64 = mask;
    59     return syscall(368, fanotify_fd, flags, _mask._32[0], _mask._32[1],
    60                    dfd, pathname);
    61   }
    63   return syscall(368, fanotify_fd, flags, mask, dfd, pathname);
    64 }
    66 class GonkDiskSpaceWatcher MOZ_FINAL : public MessageLoopForIO::Watcher
    67 {
    68 public:
    69   GonkDiskSpaceWatcher();
    70   ~GonkDiskSpaceWatcher() {};
    72   virtual void OnFileCanReadWithoutBlocking(int aFd);
    74   // We should never write to the fanotify fd.
    75   virtual void OnFileCanWriteWithoutBlocking(int aFd)
    76   {
    77     MOZ_CRASH("Must not write to fanotify fd");
    78   }
    80   void DoStart();
    81   void DoStop();
    83 private:
    84   void NotifyUpdate();
    86   uint64_t mLowThreshold;
    87   uint64_t mHighThreshold;
    88   TimeDuration mTimeout;
    89   TimeStamp  mLastTimestamp;
    90   uint64_t mLastFreeSpace;
    91   uint32_t mSizeDelta;
    93   bool mIsDiskFull;
    94   uint64_t mFreeSpace;
    96   int mFd;
    97   MessageLoopForIO::FileDescriptorWatcher mReadWatcher;
    98 };
   100 static GonkDiskSpaceWatcher* gHalDiskSpaceWatcher = nullptr;
   102 #define WATCHER_PREF_LOW        "disk_space_watcher.low_threshold"
   103 #define WATCHER_PREF_HIGH       "disk_space_watcher.high_threshold"
   104 #define WATCHER_PREF_TIMEOUT    "disk_space_watcher.timeout"
   105 #define WATCHER_PREF_SIZE_DELTA "disk_space_watcher.size_delta"
   107 static const char kWatchedPath[] = "/data";
   109 // Helper class to dispatch calls to xpcom on the main thread.
   110 class DiskSpaceNotifier : public nsRunnable
   111 {
   112 public:
   113   DiskSpaceNotifier(const bool aIsDiskFull, const uint64_t aFreeSpace) :
   114     mIsDiskFull(aIsDiskFull),
   115     mFreeSpace(aFreeSpace) {}
   117   NS_IMETHOD Run()
   118   {
   119     MOZ_ASSERT(NS_IsMainThread());
   120     DiskSpaceWatcher::UpdateState(mIsDiskFull, mFreeSpace);
   121     return NS_OK;
   122   }
   124 private:
   125   bool mIsDiskFull;
   126   uint64_t mFreeSpace;
   127 };
   129 // Helper runnable to delete the watcher on the main thread.
   130 class DiskSpaceCleaner : public nsRunnable
   131 {
   132 public:
   133   NS_IMETHOD Run()
   134   {
   135     MOZ_ASSERT(NS_IsMainThread());
   136     if (gHalDiskSpaceWatcher) {
   137       delete gHalDiskSpaceWatcher;
   138       gHalDiskSpaceWatcher = nullptr;
   139     }
   140     return NS_OK;
   141   }
   142 };
   144 GonkDiskSpaceWatcher::GonkDiskSpaceWatcher() :
   145   mLastFreeSpace(UINT64_MAX),
   146   mIsDiskFull(false),
   147   mFreeSpace(UINT64_MAX),
   148   mFd(-1)
   149 {
   150   MOZ_ASSERT(NS_IsMainThread());
   151   MOZ_ASSERT(gHalDiskSpaceWatcher == nullptr);
   153   // Default values: 5MB for low threshold, 10MB for high threshold, and
   154   // a timeout of 5 seconds.
   155   mLowThreshold = Preferences::GetInt(WATCHER_PREF_LOW, 5) * 1024 * 1024;
   156   mHighThreshold = Preferences::GetInt(WATCHER_PREF_HIGH, 10) * 1024 * 1024;
   157   mTimeout = TimeDuration::FromSeconds(Preferences::GetInt(WATCHER_PREF_TIMEOUT, 5));
   158   mSizeDelta = Preferences::GetInt(WATCHER_PREF_SIZE_DELTA, 1) * 1024 * 1024;
   159 }
   161 void
   162 GonkDiskSpaceWatcher::DoStart()
   163 {
   164   NS_ASSERTION(XRE_GetIOMessageLoop() == MessageLoopForIO::current(),
   165                "Not on the correct message loop");
   167   mFd = fanotify_init(FAN_CLASS_NOTIF, FAN_CLOEXEC);
   168   if (mFd == -1) {
   169     NS_WARNING("Error calling inotify_init()");
   170     if (errno == ENOSYS) {
   171       printf_stderr("Warning: No fanotify support in this device's kernel.\n");
   172     }
   173     return;
   174   }
   176   if (fanotify_mark(mFd, FAN_MARK_ADD | FAN_MARK_MOUNT, FAN_CLOSE,
   177                     0, kWatchedPath) < 0) {
   178     NS_WARNING("Error calling fanotify_mark");
   179     close(mFd);
   180     mFd = -1;
   181     return;
   182   }
   184   if (!MessageLoopForIO::current()->WatchFileDescriptor(
   185     mFd, /* persistent = */ true,
   186     MessageLoopForIO::WATCH_READ,
   187     &mReadWatcher, gHalDiskSpaceWatcher)) {
   188       NS_WARNING("Unable to watch fanotify fd.");
   189       close(mFd);
   190       mFd = -1;
   191   }
   192 }
   194 void
   195 GonkDiskSpaceWatcher::DoStop()
   196 {
   197   NS_ASSERTION(XRE_GetIOMessageLoop() == MessageLoopForIO::current(),
   198                "Not on the correct message loop");
   200   if (mFd != -1) {
   201     mReadWatcher.StopWatchingFileDescriptor();
   202     fanotify_mark(mFd, FAN_MARK_FLUSH, 0, 0, kWatchedPath);
   203     close(mFd);
   204     mFd = -1;
   205   }
   207   // Dispatch the cleanup to the main thread.
   208   nsCOMPtr<nsIRunnable> runnable = new DiskSpaceCleaner();
   209   NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL);
   210 }
   212 // We are called off the main thread, so we proxy first to the main thread
   213 // before calling the xpcom object.
   214 void
   215 GonkDiskSpaceWatcher::NotifyUpdate()
   216 {
   217   mLastTimestamp = TimeStamp::Now();
   218   mLastFreeSpace = mFreeSpace;
   220   nsCOMPtr<nsIRunnable> runnable =
   221     new DiskSpaceNotifier(mIsDiskFull, mFreeSpace);
   222   NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL);
   223 }
   225 void
   226 GonkDiskSpaceWatcher::OnFileCanReadWithoutBlocking(int aFd)
   227 {
   228   struct fanotify_event_metadata* fem = nullptr;
   229   char buf[4096];
   230   struct statfs sfs;
   231   int32_t len, rc;
   233   do {
   234     len = read(aFd, buf, sizeof(buf));
   235   } while(len == -1 && errno == EINTR);
   237   // Bail out if the file is busy.
   238   if (len < 0 && errno == ETXTBSY) {
   239     return;
   240   }
   242   // We should get an exact multiple of fanotify_event_metadata
   243   if (len <= 0 || (len % FAN_EVENT_METADATA_LEN != 0)) {
   244     printf_stderr("About to crash: fanotify_event_metadata read error.");
   245     MOZ_CRASH();
   246   }
   248   fem = reinterpret_cast<fanotify_event_metadata *>(buf);
   250   while (FAN_EVENT_OK(fem, len)) {
   251     rc = fstatfs(fem->fd, &sfs);
   252     if (rc < 0) {
   253       NS_WARNING("Unable to stat fan_notify fd");
   254     } else {
   255       bool firstRun = mFreeSpace == UINT64_MAX;
   256       mFreeSpace = sfs.f_bavail * sfs.f_bsize;
   257       // We change from full <-> free depending on the free space and the
   258       // low and high thresholds.
   259       // Once we are in 'full' mode we send updates for all size changes with
   260       // a minimum of time between messages or when we cross a size change
   261       // threshold.
   262       if (firstRun) {
   263         mIsDiskFull = mFreeSpace <= mLowThreshold;
   264         // Always notify the current state at first run.
   265         NotifyUpdate();
   266       } else if (!mIsDiskFull && (mFreeSpace <= mLowThreshold)) {
   267         mIsDiskFull = true;
   268         NotifyUpdate();
   269       } else if (mIsDiskFull && (mFreeSpace > mHighThreshold)) {
   270         mIsDiskFull = false;
   271         NotifyUpdate();
   272       } else if (mIsDiskFull) {
   273         if (mTimeout < TimeStamp::Now() - mLastTimestamp ||
   274             mSizeDelta < llabs(mFreeSpace - mLastFreeSpace)) {
   275           NotifyUpdate();
   276         }
   277       }
   278     }
   279     close(fem->fd);
   280     fem = FAN_EVENT_NEXT(fem, len);
   281   }
   282 }
   284 void
   285 StartDiskSpaceWatcher()
   286 {
   287   MOZ_ASSERT(NS_IsMainThread());
   289   // Bail out if called several times.
   290   if (gHalDiskSpaceWatcher != nullptr) {
   291     return;
   292   }
   294   gHalDiskSpaceWatcher = new GonkDiskSpaceWatcher();
   296   XRE_GetIOMessageLoop()->PostTask(
   297     FROM_HERE,
   298     NewRunnableMethod(gHalDiskSpaceWatcher, &GonkDiskSpaceWatcher::DoStart));
   299 }
   301 void
   302 StopDiskSpaceWatcher()
   303 {
   304   MOZ_ASSERT(NS_IsMainThread());
   305   if (!gHalDiskSpaceWatcher) {
   306     return;
   307   }
   309   XRE_GetIOMessageLoop()->PostTask(
   310     FROM_HERE,
   311     NewRunnableMethod(gHalDiskSpaceWatcher, &GonkDiskSpaceWatcher::DoStop));
   312 }
   314 } // namespace hal_impl
   315 } // namespace mozilla

mercurial