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 // 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
53 if (sizeof(void *) == 4) {
59 return syscall(368, fanotify_fd
, flags
, _mask
._32
[0], _mask
._32
[1],
63 return syscall(368, fanotify_fd
, flags
, mask
, dfd
, pathname
);
66 class GonkDiskSpaceWatcher MOZ_FINAL
: public MessageLoopForIO::Watcher
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");
86 uint64_t mLowThreshold
;
87 uint64_t mHighThreshold
;
88 TimeDuration mTimeout
;
89 TimeStamp mLastTimestamp
;
90 uint64_t mLastFreeSpace
;
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
113 DiskSpaceNotifier(const bool aIsDiskFull
, const uint64_t aFreeSpace
) :
114 mIsDiskFull(aIsDiskFull
),
115 mFreeSpace(aFreeSpace
) {}
119 MOZ_ASSERT(NS_IsMainThread());
120 DiskSpaceWatcher::UpdateState(mIsDiskFull
, mFreeSpace
);
129 // Helper runnable to delete the watcher on the main thread.
130 class DiskSpaceCleaner
: public nsRunnable
135 MOZ_ASSERT(NS_IsMainThread());
136 if (gHalDiskSpaceWatcher
) {
137 delete gHalDiskSpaceWatcher
;
138 gHalDiskSpaceWatcher
= nullptr;
144 GonkDiskSpaceWatcher::GonkDiskSpaceWatcher() :
145 mLastFreeSpace(UINT64_MAX
),
147 mFreeSpace(UINT64_MAX
),
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;
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
);
169 NS_WARNING("Error calling inotify_init()");
170 if (errno
== ENOSYS
) {
171 printf_stderr("Warning: No fanotify support in this device's kernel.\n");
176 if (fanotify_mark(mFd
, FAN_MARK_ADD
| FAN_MARK_MOUNT
, FAN_CLOSE
,
177 0, kWatchedPath
) < 0) {
178 NS_WARNING("Error calling fanotify_mark");
184 if (!MessageLoopForIO::current()->WatchFileDescriptor(
185 mFd
, /* persistent = */ true,
186 MessageLoopForIO::WATCH_READ
,
187 &mReadWatcher
, gHalDiskSpaceWatcher
)) {
188 NS_WARNING("Unable to watch fanotify fd.");
195 GonkDiskSpaceWatcher::DoStop()
197 NS_ASSERTION(XRE_GetIOMessageLoop() == MessageLoopForIO::current(),
198 "Not on the correct message loop");
201 mReadWatcher
.StopWatchingFileDescriptor();
202 fanotify_mark(mFd
, FAN_MARK_FLUSH
, 0, 0, kWatchedPath
);
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.
215 GonkDiskSpaceWatcher::NotifyUpdate()
217 mLastTimestamp
= TimeStamp::Now();
218 mLastFreeSpace
= mFreeSpace
;
220 nsCOMPtr
<nsIRunnable
> runnable
=
221 new DiskSpaceNotifier(mIsDiskFull
, mFreeSpace
);
222 NS_DispatchToMainThread(runnable
);
226 GonkDiskSpaceWatcher::OnFileCanReadWithoutBlocking(int aFd
)
228 struct fanotify_event_metadata
* fem
= nullptr;
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
) {
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.");
248 fem
= reinterpret_cast<fanotify_event_metadata
*>(buf
);
250 while (FAN_EVENT_OK(fem
, len
)) {
251 rc
= fstatfs(fem
->fd
, &sfs
);
253 NS_WARNING("Unable to stat fan_notify fd");
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
263 mIsDiskFull
= mFreeSpace
<= mLowThreshold
;
264 // Always notify the current state at first run.
266 } else if (!mIsDiskFull
&& (mFreeSpace
<= mLowThreshold
)) {
269 } else if (mIsDiskFull
&& (mFreeSpace
> mHighThreshold
)) {
272 } else if (mIsDiskFull
) {
273 if (mTimeout
< TimeStamp::Now() - mLastTimestamp
||
274 mSizeDelta
< llabs(mFreeSpace
- mLastFreeSpace
)) {
280 fem
= FAN_EVENT_NEXT(fem
, len
);
285 StartDiskSpaceWatcher()
287 MOZ_ASSERT(NS_IsMainThread());
289 // Bail out if called several times.
290 if (gHalDiskSpaceWatcher
!= nullptr) {
294 gHalDiskSpaceWatcher
= new GonkDiskSpaceWatcher();
296 XRE_GetIOMessageLoop()->PostTask(
298 NewRunnableMethod(gHalDiskSpaceWatcher
, &GonkDiskSpaceWatcher::DoStart
));
302 StopDiskSpaceWatcher()
304 MOZ_ASSERT(NS_IsMainThread());
305 if (!gHalDiskSpaceWatcher
) {
309 XRE_GetIOMessageLoop()->PostTask(
311 NewRunnableMethod(gHalDiskSpaceWatcher
, &GonkDiskSpaceWatcher::DoStop
));
314 } // namespace hal_impl
315 } // namespace mozilla