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/. */
6 #include <sys/syscall.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"
20 #include "DiskSpaceWatcher.h"
22 using namespace mozilla
;
24 namespace mozilla
{ namespace hal_impl
{ class GonkDiskSpaceWatcher
; } }
26 using namespace mozilla::hal_impl
;
29 struct RunnableMethodTraits
<GonkDiskSpaceWatcher
>
31 static void RetainCallee(GonkDiskSpaceWatcher
* obj
) { }
32 static void ReleaseCallee(GonkDiskSpaceWatcher
* obj
) { }
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
47 #error "Unhandled architecture"
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
66 if (sizeof(void *) == 4) {
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
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");
99 uint64_t mLowThreshold
;
100 uint64_t mHighThreshold
;
101 TimeDuration mTimeout
;
102 TimeStamp mLastTimestamp
;
103 uint64_t mLastFreeSpace
;
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
126 DiskSpaceNotifier(const bool aIsDiskFull
, const uint64_t aFreeSpace
) :
127 mIsDiskFull(aIsDiskFull
),
128 mFreeSpace(aFreeSpace
) {}
132 MOZ_ASSERT(NS_IsMainThread());
133 DiskSpaceWatcher::UpdateState(mIsDiskFull
, mFreeSpace
);
142 // Helper runnable to delete the watcher on the main thread.
143 class DiskSpaceCleaner
: public nsRunnable
148 MOZ_ASSERT(NS_IsMainThread());
149 if (gHalDiskSpaceWatcher
) {
150 delete gHalDiskSpaceWatcher
;
151 gHalDiskSpaceWatcher
= nullptr;
157 GonkDiskSpaceWatcher::GonkDiskSpaceWatcher() :
158 mLastFreeSpace(UINT64_MAX
),
160 mFreeSpace(UINT64_MAX
),
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;
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
);
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.");
188 NS_WARNING("Error calling fanotify_init()");
193 if (fanotify_mark(mFd
, FAN_MARK_ADD
| FAN_MARK_MOUNT
, FAN_CLOSE
,
194 0, kWatchedPath
) < 0) {
195 NS_WARNING("Error calling fanotify_mark");
201 if (!MessageLoopForIO::current()->WatchFileDescriptor(
202 mFd
, /* persistent = */ true,
203 MessageLoopForIO::WATCH_READ
,
204 &mReadWatcher
, gHalDiskSpaceWatcher
)) {
205 NS_WARNING("Unable to watch fanotify fd.");
212 GonkDiskSpaceWatcher::DoStop()
214 NS_ASSERTION(XRE_GetIOMessageLoop() == MessageLoopForIO::current(),
215 "Not on the correct message loop");
218 mReadWatcher
.StopWatchingFileDescriptor();
219 fanotify_mark(mFd
, FAN_MARK_FLUSH
, 0, 0, kWatchedPath
);
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.
232 GonkDiskSpaceWatcher::NotifyUpdate()
234 mLastTimestamp
= TimeStamp::Now();
235 mLastFreeSpace
= mFreeSpace
;
237 nsCOMPtr
<nsIRunnable
> runnable
=
238 new DiskSpaceNotifier(mIsDiskFull
, mFreeSpace
);
239 NS_DispatchToMainThread(runnable
);
243 GonkDiskSpaceWatcher::OnFileCanReadWithoutBlocking(int aFd
)
245 struct fanotify_event_metadata
* fem
= nullptr;
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
) {
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
);
269 NS_WARNING("Unable to stat fan_notify fd");
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
279 mIsDiskFull
= mFreeSpace
<= mLowThreshold
;
280 // Always notify the current state at first run.
282 } else if (!mIsDiskFull
&& (mFreeSpace
<= mLowThreshold
)) {
285 } else if (mIsDiskFull
&& (mFreeSpace
> mHighThreshold
)) {
288 } else if (mIsDiskFull
) {
289 if (mTimeout
< TimeStamp::Now() - mLastTimestamp
||
290 mSizeDelta
< llabs(mFreeSpace
- mLastFreeSpace
)) {
296 fem
= FAN_EVENT_NEXT(fem
, len
);
301 StartDiskSpaceWatcher()
303 MOZ_ASSERT(NS_IsMainThread());
305 // Bail out if called several times.
306 if (gHalDiskSpaceWatcher
!= nullptr) {
310 gHalDiskSpaceWatcher
= new GonkDiskSpaceWatcher();
312 XRE_GetIOMessageLoop()->PostTask(
314 NewRunnableMethod(gHalDiskSpaceWatcher
, &GonkDiskSpaceWatcher::DoStart
));
318 StopDiskSpaceWatcher()
320 MOZ_ASSERT(NS_IsMainThread());
321 if (!gHalDiskSpaceWatcher
) {
325 XRE_GetIOMessageLoop()->PostTask(
327 NewRunnableMethod(gHalDiskSpaceWatcher
, &GonkDiskSpaceWatcher::DoStop
));
330 } // namespace hal_impl
331 } // namespace mozilla