|
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/. */ |
|
4 |
|
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" |
|
21 |
|
22 using namespace mozilla; |
|
23 |
|
24 namespace mozilla { namespace hal_impl { class GonkDiskSpaceWatcher; } } |
|
25 |
|
26 using namespace mozilla::hal_impl; |
|
27 |
|
28 template<> |
|
29 struct RunnableMethodTraits<GonkDiskSpaceWatcher> |
|
30 { |
|
31 static void RetainCallee(GonkDiskSpaceWatcher* obj) { } |
|
32 static void ReleaseCallee(GonkDiskSpaceWatcher* obj) { } |
|
33 }; |
|
34 |
|
35 namespace mozilla { |
|
36 namespace hal_impl { |
|
37 |
|
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) |
|
42 { |
|
43 return syscall(367, flags, event_f_flags); |
|
44 } |
|
45 |
|
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) |
|
49 { |
|
50 |
|
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); |
|
61 } |
|
62 |
|
63 return syscall(368, fanotify_fd, flags, mask, dfd, pathname); |
|
64 } |
|
65 |
|
66 class GonkDiskSpaceWatcher MOZ_FINAL : public MessageLoopForIO::Watcher |
|
67 { |
|
68 public: |
|
69 GonkDiskSpaceWatcher(); |
|
70 ~GonkDiskSpaceWatcher() {}; |
|
71 |
|
72 virtual void OnFileCanReadWithoutBlocking(int aFd); |
|
73 |
|
74 // We should never write to the fanotify fd. |
|
75 virtual void OnFileCanWriteWithoutBlocking(int aFd) |
|
76 { |
|
77 MOZ_CRASH("Must not write to fanotify fd"); |
|
78 } |
|
79 |
|
80 void DoStart(); |
|
81 void DoStop(); |
|
82 |
|
83 private: |
|
84 void NotifyUpdate(); |
|
85 |
|
86 uint64_t mLowThreshold; |
|
87 uint64_t mHighThreshold; |
|
88 TimeDuration mTimeout; |
|
89 TimeStamp mLastTimestamp; |
|
90 uint64_t mLastFreeSpace; |
|
91 uint32_t mSizeDelta; |
|
92 |
|
93 bool mIsDiskFull; |
|
94 uint64_t mFreeSpace; |
|
95 |
|
96 int mFd; |
|
97 MessageLoopForIO::FileDescriptorWatcher mReadWatcher; |
|
98 }; |
|
99 |
|
100 static GonkDiskSpaceWatcher* gHalDiskSpaceWatcher = nullptr; |
|
101 |
|
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" |
|
106 |
|
107 static const char kWatchedPath[] = "/data"; |
|
108 |
|
109 // Helper class to dispatch calls to xpcom on the main thread. |
|
110 class DiskSpaceNotifier : public nsRunnable |
|
111 { |
|
112 public: |
|
113 DiskSpaceNotifier(const bool aIsDiskFull, const uint64_t aFreeSpace) : |
|
114 mIsDiskFull(aIsDiskFull), |
|
115 mFreeSpace(aFreeSpace) {} |
|
116 |
|
117 NS_IMETHOD Run() |
|
118 { |
|
119 MOZ_ASSERT(NS_IsMainThread()); |
|
120 DiskSpaceWatcher::UpdateState(mIsDiskFull, mFreeSpace); |
|
121 return NS_OK; |
|
122 } |
|
123 |
|
124 private: |
|
125 bool mIsDiskFull; |
|
126 uint64_t mFreeSpace; |
|
127 }; |
|
128 |
|
129 // Helper runnable to delete the watcher on the main thread. |
|
130 class DiskSpaceCleaner : public nsRunnable |
|
131 { |
|
132 public: |
|
133 NS_IMETHOD Run() |
|
134 { |
|
135 MOZ_ASSERT(NS_IsMainThread()); |
|
136 if (gHalDiskSpaceWatcher) { |
|
137 delete gHalDiskSpaceWatcher; |
|
138 gHalDiskSpaceWatcher = nullptr; |
|
139 } |
|
140 return NS_OK; |
|
141 } |
|
142 }; |
|
143 |
|
144 GonkDiskSpaceWatcher::GonkDiskSpaceWatcher() : |
|
145 mLastFreeSpace(UINT64_MAX), |
|
146 mIsDiskFull(false), |
|
147 mFreeSpace(UINT64_MAX), |
|
148 mFd(-1) |
|
149 { |
|
150 MOZ_ASSERT(NS_IsMainThread()); |
|
151 MOZ_ASSERT(gHalDiskSpaceWatcher == nullptr); |
|
152 |
|
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; |
|
159 } |
|
160 |
|
161 void |
|
162 GonkDiskSpaceWatcher::DoStart() |
|
163 { |
|
164 NS_ASSERTION(XRE_GetIOMessageLoop() == MessageLoopForIO::current(), |
|
165 "Not on the correct message loop"); |
|
166 |
|
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"); |
|
172 } |
|
173 return; |
|
174 } |
|
175 |
|
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; |
|
182 } |
|
183 |
|
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; |
|
191 } |
|
192 } |
|
193 |
|
194 void |
|
195 GonkDiskSpaceWatcher::DoStop() |
|
196 { |
|
197 NS_ASSERTION(XRE_GetIOMessageLoop() == MessageLoopForIO::current(), |
|
198 "Not on the correct message loop"); |
|
199 |
|
200 if (mFd != -1) { |
|
201 mReadWatcher.StopWatchingFileDescriptor(); |
|
202 fanotify_mark(mFd, FAN_MARK_FLUSH, 0, 0, kWatchedPath); |
|
203 close(mFd); |
|
204 mFd = -1; |
|
205 } |
|
206 |
|
207 // Dispatch the cleanup to the main thread. |
|
208 nsCOMPtr<nsIRunnable> runnable = new DiskSpaceCleaner(); |
|
209 NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL); |
|
210 } |
|
211 |
|
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() |
|
216 { |
|
217 mLastTimestamp = TimeStamp::Now(); |
|
218 mLastFreeSpace = mFreeSpace; |
|
219 |
|
220 nsCOMPtr<nsIRunnable> runnable = |
|
221 new DiskSpaceNotifier(mIsDiskFull, mFreeSpace); |
|
222 NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL); |
|
223 } |
|
224 |
|
225 void |
|
226 GonkDiskSpaceWatcher::OnFileCanReadWithoutBlocking(int aFd) |
|
227 { |
|
228 struct fanotify_event_metadata* fem = nullptr; |
|
229 char buf[4096]; |
|
230 struct statfs sfs; |
|
231 int32_t len, rc; |
|
232 |
|
233 do { |
|
234 len = read(aFd, buf, sizeof(buf)); |
|
235 } while(len == -1 && errno == EINTR); |
|
236 |
|
237 // Bail out if the file is busy. |
|
238 if (len < 0 && errno == ETXTBSY) { |
|
239 return; |
|
240 } |
|
241 |
|
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(); |
|
246 } |
|
247 |
|
248 fem = reinterpret_cast<fanotify_event_metadata *>(buf); |
|
249 |
|
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(); |
|
276 } |
|
277 } |
|
278 } |
|
279 close(fem->fd); |
|
280 fem = FAN_EVENT_NEXT(fem, len); |
|
281 } |
|
282 } |
|
283 |
|
284 void |
|
285 StartDiskSpaceWatcher() |
|
286 { |
|
287 MOZ_ASSERT(NS_IsMainThread()); |
|
288 |
|
289 // Bail out if called several times. |
|
290 if (gHalDiskSpaceWatcher != nullptr) { |
|
291 return; |
|
292 } |
|
293 |
|
294 gHalDiskSpaceWatcher = new GonkDiskSpaceWatcher(); |
|
295 |
|
296 XRE_GetIOMessageLoop()->PostTask( |
|
297 FROM_HERE, |
|
298 NewRunnableMethod(gHalDiskSpaceWatcher, &GonkDiskSpaceWatcher::DoStart)); |
|
299 } |
|
300 |
|
301 void |
|
302 StopDiskSpaceWatcher() |
|
303 { |
|
304 MOZ_ASSERT(NS_IsMainThread()); |
|
305 if (!gHalDiskSpaceWatcher) { |
|
306 return; |
|
307 } |
|
308 |
|
309 XRE_GetIOMessageLoop()->PostTask( |
|
310 FROM_HERE, |
|
311 NewRunnableMethod(gHalDiskSpaceWatcher, &GonkDiskSpaceWatcher::DoStop)); |
|
312 } |
|
313 |
|
314 } // namespace hal_impl |
|
315 } // namespace mozilla |