Bumping manifests a=b2g-bump
[gecko.git] / hal / gonk / GonkDiskSpaceWatcher.cpp
blob0b88e8abb459fdc092cff717b9a7ae916a8b04bd
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 // 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)
43 return syscall(367, flags, event_f_flags);
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)
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);
63 return syscall(368, fanotify_fd, flags, mask, dfd, pathname);
66 class GonkDiskSpaceWatcher MOZ_FINAL : public MessageLoopForIO::Watcher
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)
77 MOZ_CRASH("Must not write to fanotify fd");
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;
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
112 public:
113 DiskSpaceNotifier(const bool aIsDiskFull, const uint64_t aFreeSpace) :
114 mIsDiskFull(aIsDiskFull),
115 mFreeSpace(aFreeSpace) {}
117 NS_IMETHOD Run()
119 MOZ_ASSERT(NS_IsMainThread());
120 DiskSpaceWatcher::UpdateState(mIsDiskFull, mFreeSpace);
121 return NS_OK;
124 private:
125 bool mIsDiskFull;
126 uint64_t mFreeSpace;
129 // Helper runnable to delete the watcher on the main thread.
130 class DiskSpaceCleaner : public nsRunnable
132 public:
133 NS_IMETHOD Run()
135 MOZ_ASSERT(NS_IsMainThread());
136 if (gHalDiskSpaceWatcher) {
137 delete gHalDiskSpaceWatcher;
138 gHalDiskSpaceWatcher = nullptr;
140 return NS_OK;
144 GonkDiskSpaceWatcher::GonkDiskSpaceWatcher() :
145 mLastFreeSpace(UINT64_MAX),
146 mIsDiskFull(false),
147 mFreeSpace(UINT64_MAX),
148 mFd(-1)
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;
161 void
162 GonkDiskSpaceWatcher::DoStart()
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");
173 return;
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;
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;
194 void
195 GonkDiskSpaceWatcher::DoStop()
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;
207 // Dispatch the cleanup to the main thread.
208 nsCOMPtr<nsIRunnable> runnable = new DiskSpaceCleaner();
209 NS_DispatchToMainThread(runnable);
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()
217 mLastTimestamp = TimeStamp::Now();
218 mLastFreeSpace = mFreeSpace;
220 nsCOMPtr<nsIRunnable> runnable =
221 new DiskSpaceNotifier(mIsDiskFull, mFreeSpace);
222 NS_DispatchToMainThread(runnable);
225 void
226 GonkDiskSpaceWatcher::OnFileCanReadWithoutBlocking(int aFd)
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;
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();
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();
279 close(fem->fd);
280 fem = FAN_EVENT_NEXT(fem, len);
284 void
285 StartDiskSpaceWatcher()
287 MOZ_ASSERT(NS_IsMainThread());
289 // Bail out if called several times.
290 if (gHalDiskSpaceWatcher != nullptr) {
291 return;
294 gHalDiskSpaceWatcher = new GonkDiskSpaceWatcher();
296 XRE_GetIOMessageLoop()->PostTask(
297 FROM_HERE,
298 NewRunnableMethod(gHalDiskSpaceWatcher, &GonkDiskSpaceWatcher::DoStart));
301 void
302 StopDiskSpaceWatcher()
304 MOZ_ASSERT(NS_IsMainThread());
305 if (!gHalDiskSpaceWatcher) {
306 return;
309 XRE_GetIOMessageLoop()->PostTask(
310 FROM_HERE,
311 NewRunnableMethod(gHalDiskSpaceWatcher, &GonkDiskSpaceWatcher::DoStop));
314 } // namespace hal_impl
315 } // namespace mozilla