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.

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

mercurial