Bumping manifests a=b2g-bump
[gecko.git] / hal / gonk / GonkDiskSpaceWatcher.cpp
blob6a16df3efcdc026657d7d56a0b847cf54114585e
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>
31 static void RetainCallee(GonkDiskSpaceWatcher* obj) { }
32 static void ReleaseCallee(GonkDiskSpaceWatcher* obj) { }
35 namespace mozilla {
36 namespace hal_impl {
38 // NOTE: this should be unnecessary once we no longer support ICS.
39 #ifndef __NR_fanotify_init
40 #if defined(__ARM_EABI__)
41 #define __NR_fanotify_init 367
42 #define __NR_fanotify_mark 368
43 #elif defined(__i386__)
44 #define __NR_fanotify_init 338
45 #define __NR_fanotify_mark 339
46 #else
47 #error "Unhandled architecture"
48 #endif
49 #endif
51 // fanotify_init and fanotify_mark functions are syscalls.
52 // The user space bits are not part of bionic so we add them here
53 // as well as fanotify.h
54 int fanotify_init (unsigned int flags, unsigned int event_f_flags)
56 return syscall(__NR_fanotify_init, flags, event_f_flags);
59 // Add, remove, or modify an fanotify mark on a filesystem object.
60 int fanotify_mark (int fanotify_fd, unsigned int flags,
61 uint64_t mask, int dfd, const char *pathname)
64 // On 32 bits platforms we have to convert the 64 bits mask into
65 // two 32 bits ints.
66 if (sizeof(void *) == 4) {
67 union {
68 uint64_t _64;
69 uint32_t _32[2];
70 } _mask;
71 _mask._64 = mask;
72 return syscall(__NR_fanotify_mark, fanotify_fd, flags,
73 _mask._32[0], _mask._32[1], dfd, pathname);
76 return syscall(__NR_fanotify_mark, fanotify_fd, flags, mask, dfd, pathname);
79 class GonkDiskSpaceWatcher MOZ_FINAL : public MessageLoopForIO::Watcher
81 public:
82 GonkDiskSpaceWatcher();
83 ~GonkDiskSpaceWatcher() {};
85 virtual void OnFileCanReadWithoutBlocking(int aFd);
87 // We should never write to the fanotify fd.
88 virtual void OnFileCanWriteWithoutBlocking(int aFd)
90 MOZ_CRASH("Must not write to fanotify fd");
93 void DoStart();
94 void DoStop();
96 private:
97 void NotifyUpdate();
99 uint64_t mLowThreshold;
100 uint64_t mHighThreshold;
101 TimeDuration mTimeout;
102 TimeStamp mLastTimestamp;
103 uint64_t mLastFreeSpace;
104 uint32_t mSizeDelta;
106 bool mIsDiskFull;
107 uint64_t mFreeSpace;
109 int mFd;
110 MessageLoopForIO::FileDescriptorWatcher mReadWatcher;
113 static GonkDiskSpaceWatcher* gHalDiskSpaceWatcher = nullptr;
115 #define WATCHER_PREF_LOW "disk_space_watcher.low_threshold"
116 #define WATCHER_PREF_HIGH "disk_space_watcher.high_threshold"
117 #define WATCHER_PREF_TIMEOUT "disk_space_watcher.timeout"
118 #define WATCHER_PREF_SIZE_DELTA "disk_space_watcher.size_delta"
120 static const char kWatchedPath[] = "/data";
122 // Helper class to dispatch calls to xpcom on the main thread.
123 class DiskSpaceNotifier : public nsRunnable
125 public:
126 DiskSpaceNotifier(const bool aIsDiskFull, const uint64_t aFreeSpace) :
127 mIsDiskFull(aIsDiskFull),
128 mFreeSpace(aFreeSpace) {}
130 NS_IMETHOD Run()
132 MOZ_ASSERT(NS_IsMainThread());
133 DiskSpaceWatcher::UpdateState(mIsDiskFull, mFreeSpace);
134 return NS_OK;
137 private:
138 bool mIsDiskFull;
139 uint64_t mFreeSpace;
142 // Helper runnable to delete the watcher on the main thread.
143 class DiskSpaceCleaner : public nsRunnable
145 public:
146 NS_IMETHOD Run()
148 MOZ_ASSERT(NS_IsMainThread());
149 if (gHalDiskSpaceWatcher) {
150 delete gHalDiskSpaceWatcher;
151 gHalDiskSpaceWatcher = nullptr;
153 return NS_OK;
157 GonkDiskSpaceWatcher::GonkDiskSpaceWatcher() :
158 mLastFreeSpace(UINT64_MAX),
159 mIsDiskFull(false),
160 mFreeSpace(UINT64_MAX),
161 mFd(-1)
163 MOZ_ASSERT(NS_IsMainThread());
164 MOZ_ASSERT(gHalDiskSpaceWatcher == nullptr);
166 // Default values: 5MB for low threshold, 10MB for high threshold, and
167 // a timeout of 5 seconds.
168 mLowThreshold = Preferences::GetInt(WATCHER_PREF_LOW, 5) * 1024 * 1024;
169 mHighThreshold = Preferences::GetInt(WATCHER_PREF_HIGH, 10) * 1024 * 1024;
170 mTimeout = TimeDuration::FromSeconds(Preferences::GetInt(WATCHER_PREF_TIMEOUT, 5));
171 mSizeDelta = Preferences::GetInt(WATCHER_PREF_SIZE_DELTA, 1) * 1024 * 1024;
174 void
175 GonkDiskSpaceWatcher::DoStart()
177 NS_ASSERTION(XRE_GetIOMessageLoop() == MessageLoopForIO::current(),
178 "Not on the correct message loop");
180 mFd = fanotify_init(FAN_CLASS_NOTIF, FAN_CLOEXEC | O_LARGEFILE);
181 if (mFd == -1) {
182 if (errno == ENOSYS) {
183 NS_WARNING("Warning: No fanotify support in this device's kernel.\n");
184 #if ANDROID_VERSION >= 19
185 MOZ_CRASH("Fanotify support must be enabled in the kernel.");
186 #endif
187 } else {
188 NS_WARNING("Error calling fanotify_init()");
190 return;
193 if (fanotify_mark(mFd, FAN_MARK_ADD | FAN_MARK_MOUNT, FAN_CLOSE,
194 0, kWatchedPath) < 0) {
195 NS_WARNING("Error calling fanotify_mark");
196 close(mFd);
197 mFd = -1;
198 return;
201 if (!MessageLoopForIO::current()->WatchFileDescriptor(
202 mFd, /* persistent = */ true,
203 MessageLoopForIO::WATCH_READ,
204 &mReadWatcher, gHalDiskSpaceWatcher)) {
205 NS_WARNING("Unable to watch fanotify fd.");
206 close(mFd);
207 mFd = -1;
211 void
212 GonkDiskSpaceWatcher::DoStop()
214 NS_ASSERTION(XRE_GetIOMessageLoop() == MessageLoopForIO::current(),
215 "Not on the correct message loop");
217 if (mFd != -1) {
218 mReadWatcher.StopWatchingFileDescriptor();
219 fanotify_mark(mFd, FAN_MARK_FLUSH, 0, 0, kWatchedPath);
220 close(mFd);
221 mFd = -1;
224 // Dispatch the cleanup to the main thread.
225 nsCOMPtr<nsIRunnable> runnable = new DiskSpaceCleaner();
226 NS_DispatchToMainThread(runnable);
229 // We are called off the main thread, so we proxy first to the main thread
230 // before calling the xpcom object.
231 void
232 GonkDiskSpaceWatcher::NotifyUpdate()
234 mLastTimestamp = TimeStamp::Now();
235 mLastFreeSpace = mFreeSpace;
237 nsCOMPtr<nsIRunnable> runnable =
238 new DiskSpaceNotifier(mIsDiskFull, mFreeSpace);
239 NS_DispatchToMainThread(runnable);
242 void
243 GonkDiskSpaceWatcher::OnFileCanReadWithoutBlocking(int aFd)
245 struct fanotify_event_metadata* fem = nullptr;
246 char buf[4096];
247 struct statfs sfs;
248 int32_t len, rc;
250 do {
251 len = read(aFd, buf, sizeof(buf));
252 } while(len == -1 && errno == EINTR);
254 // Bail out if the file is busy.
255 if (len < 0 && errno == ETXTBSY) {
256 return;
259 // We should get an exact multiple of fanotify_event_metadata
260 if (len <= 0 || (len % FAN_EVENT_METADATA_LEN != 0)) {
261 MOZ_CRASH("About to crash: fanotify_event_metadata read error.");
264 fem = reinterpret_cast<fanotify_event_metadata *>(buf);
266 while (FAN_EVENT_OK(fem, len)) {
267 rc = fstatfs(fem->fd, &sfs);
268 if (rc < 0) {
269 NS_WARNING("Unable to stat fan_notify fd");
270 } else {
271 bool firstRun = mFreeSpace == UINT64_MAX;
272 mFreeSpace = sfs.f_bavail * sfs.f_bsize;
273 // We change from full <-> free depending on the free space and the
274 // low and high thresholds.
275 // Once we are in 'full' mode we send updates for all size changes with
276 // a minimum of time between messages or when we cross a size change
277 // threshold.
278 if (firstRun) {
279 mIsDiskFull = mFreeSpace <= mLowThreshold;
280 // Always notify the current state at first run.
281 NotifyUpdate();
282 } else if (!mIsDiskFull && (mFreeSpace <= mLowThreshold)) {
283 mIsDiskFull = true;
284 NotifyUpdate();
285 } else if (mIsDiskFull && (mFreeSpace > mHighThreshold)) {
286 mIsDiskFull = false;
287 NotifyUpdate();
288 } else if (mIsDiskFull) {
289 if (mTimeout < TimeStamp::Now() - mLastTimestamp ||
290 mSizeDelta < llabs(mFreeSpace - mLastFreeSpace)) {
291 NotifyUpdate();
295 close(fem->fd);
296 fem = FAN_EVENT_NEXT(fem, len);
300 void
301 StartDiskSpaceWatcher()
303 MOZ_ASSERT(NS_IsMainThread());
305 // Bail out if called several times.
306 if (gHalDiskSpaceWatcher != nullptr) {
307 return;
310 gHalDiskSpaceWatcher = new GonkDiskSpaceWatcher();
312 XRE_GetIOMessageLoop()->PostTask(
313 FROM_HERE,
314 NewRunnableMethod(gHalDiskSpaceWatcher, &GonkDiskSpaceWatcher::DoStart));
317 void
318 StopDiskSpaceWatcher()
320 MOZ_ASSERT(NS_IsMainThread());
321 if (!gHalDiskSpaceWatcher) {
322 return;
325 XRE_GetIOMessageLoop()->PostTask(
326 FROM_HERE,
327 NewRunnableMethod(gHalDiskSpaceWatcher, &GonkDiskSpaceWatcher::DoStop));
330 } // namespace hal_impl
331 } // namespace mozilla