1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/hal/gonk/GonkDiskSpaceWatcher.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,315 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +#include "Hal.h" 1.9 +#include <sys/syscall.h> 1.10 +#include <sys/vfs.h> 1.11 +#include <fcntl.h> 1.12 +#include <errno.h> 1.13 +#include "nsIObserverService.h" 1.14 +#include "nsIDiskSpaceWatcher.h" 1.15 +#include "mozilla/ModuleUtils.h" 1.16 +#include "nsAutoPtr.h" 1.17 +#include "nsThreadUtils.h" 1.18 +#include "base/message_loop.h" 1.19 +#include "mozilla/Preferences.h" 1.20 +#include "mozilla/Services.h" 1.21 +#include "nsXULAppAPI.h" 1.22 +#include "fanotify.h" 1.23 +#include "DiskSpaceWatcher.h" 1.24 + 1.25 +using namespace mozilla; 1.26 + 1.27 +namespace mozilla { namespace hal_impl { class GonkDiskSpaceWatcher; } } 1.28 + 1.29 +using namespace mozilla::hal_impl; 1.30 + 1.31 +template<> 1.32 +struct RunnableMethodTraits<GonkDiskSpaceWatcher> 1.33 +{ 1.34 + static void RetainCallee(GonkDiskSpaceWatcher* obj) { } 1.35 + static void ReleaseCallee(GonkDiskSpaceWatcher* obj) { } 1.36 +}; 1.37 + 1.38 +namespace mozilla { 1.39 +namespace hal_impl { 1.40 + 1.41 +// fanotify_init and fanotify_mark functions are syscalls. 1.42 +// The user space bits are not part of bionic so we add them here 1.43 +// as well as fanotify.h 1.44 +int fanotify_init (unsigned int flags, unsigned int event_f_flags) 1.45 +{ 1.46 + return syscall(367, flags, event_f_flags); 1.47 +} 1.48 + 1.49 +// Add, remove, or modify an fanotify mark on a filesystem object. 1.50 +int fanotify_mark (int fanotify_fd, unsigned int flags, 1.51 + uint64_t mask, int dfd, const char *pathname) 1.52 +{ 1.53 + 1.54 + // On 32 bits platforms we have to convert the 64 bits mask into 1.55 + // two 32 bits ints. 1.56 + if (sizeof(void *) == 4) { 1.57 + union { 1.58 + uint64_t _64; 1.59 + uint32_t _32[2]; 1.60 + } _mask; 1.61 + _mask._64 = mask; 1.62 + return syscall(368, fanotify_fd, flags, _mask._32[0], _mask._32[1], 1.63 + dfd, pathname); 1.64 + } 1.65 + 1.66 + return syscall(368, fanotify_fd, flags, mask, dfd, pathname); 1.67 +} 1.68 + 1.69 +class GonkDiskSpaceWatcher MOZ_FINAL : public MessageLoopForIO::Watcher 1.70 +{ 1.71 +public: 1.72 + GonkDiskSpaceWatcher(); 1.73 + ~GonkDiskSpaceWatcher() {}; 1.74 + 1.75 + virtual void OnFileCanReadWithoutBlocking(int aFd); 1.76 + 1.77 + // We should never write to the fanotify fd. 1.78 + virtual void OnFileCanWriteWithoutBlocking(int aFd) 1.79 + { 1.80 + MOZ_CRASH("Must not write to fanotify fd"); 1.81 + } 1.82 + 1.83 + void DoStart(); 1.84 + void DoStop(); 1.85 + 1.86 +private: 1.87 + void NotifyUpdate(); 1.88 + 1.89 + uint64_t mLowThreshold; 1.90 + uint64_t mHighThreshold; 1.91 + TimeDuration mTimeout; 1.92 + TimeStamp mLastTimestamp; 1.93 + uint64_t mLastFreeSpace; 1.94 + uint32_t mSizeDelta; 1.95 + 1.96 + bool mIsDiskFull; 1.97 + uint64_t mFreeSpace; 1.98 + 1.99 + int mFd; 1.100 + MessageLoopForIO::FileDescriptorWatcher mReadWatcher; 1.101 +}; 1.102 + 1.103 +static GonkDiskSpaceWatcher* gHalDiskSpaceWatcher = nullptr; 1.104 + 1.105 +#define WATCHER_PREF_LOW "disk_space_watcher.low_threshold" 1.106 +#define WATCHER_PREF_HIGH "disk_space_watcher.high_threshold" 1.107 +#define WATCHER_PREF_TIMEOUT "disk_space_watcher.timeout" 1.108 +#define WATCHER_PREF_SIZE_DELTA "disk_space_watcher.size_delta" 1.109 + 1.110 +static const char kWatchedPath[] = "/data"; 1.111 + 1.112 +// Helper class to dispatch calls to xpcom on the main thread. 1.113 +class DiskSpaceNotifier : public nsRunnable 1.114 +{ 1.115 +public: 1.116 + DiskSpaceNotifier(const bool aIsDiskFull, const uint64_t aFreeSpace) : 1.117 + mIsDiskFull(aIsDiskFull), 1.118 + mFreeSpace(aFreeSpace) {} 1.119 + 1.120 + NS_IMETHOD Run() 1.121 + { 1.122 + MOZ_ASSERT(NS_IsMainThread()); 1.123 + DiskSpaceWatcher::UpdateState(mIsDiskFull, mFreeSpace); 1.124 + return NS_OK; 1.125 + } 1.126 + 1.127 +private: 1.128 + bool mIsDiskFull; 1.129 + uint64_t mFreeSpace; 1.130 +}; 1.131 + 1.132 +// Helper runnable to delete the watcher on the main thread. 1.133 +class DiskSpaceCleaner : public nsRunnable 1.134 +{ 1.135 +public: 1.136 + NS_IMETHOD Run() 1.137 + { 1.138 + MOZ_ASSERT(NS_IsMainThread()); 1.139 + if (gHalDiskSpaceWatcher) { 1.140 + delete gHalDiskSpaceWatcher; 1.141 + gHalDiskSpaceWatcher = nullptr; 1.142 + } 1.143 + return NS_OK; 1.144 + } 1.145 +}; 1.146 + 1.147 +GonkDiskSpaceWatcher::GonkDiskSpaceWatcher() : 1.148 + mLastFreeSpace(UINT64_MAX), 1.149 + mIsDiskFull(false), 1.150 + mFreeSpace(UINT64_MAX), 1.151 + mFd(-1) 1.152 +{ 1.153 + MOZ_ASSERT(NS_IsMainThread()); 1.154 + MOZ_ASSERT(gHalDiskSpaceWatcher == nullptr); 1.155 + 1.156 + // Default values: 5MB for low threshold, 10MB for high threshold, and 1.157 + // a timeout of 5 seconds. 1.158 + mLowThreshold = Preferences::GetInt(WATCHER_PREF_LOW, 5) * 1024 * 1024; 1.159 + mHighThreshold = Preferences::GetInt(WATCHER_PREF_HIGH, 10) * 1024 * 1024; 1.160 + mTimeout = TimeDuration::FromSeconds(Preferences::GetInt(WATCHER_PREF_TIMEOUT, 5)); 1.161 + mSizeDelta = Preferences::GetInt(WATCHER_PREF_SIZE_DELTA, 1) * 1024 * 1024; 1.162 +} 1.163 + 1.164 +void 1.165 +GonkDiskSpaceWatcher::DoStart() 1.166 +{ 1.167 + NS_ASSERTION(XRE_GetIOMessageLoop() == MessageLoopForIO::current(), 1.168 + "Not on the correct message loop"); 1.169 + 1.170 + mFd = fanotify_init(FAN_CLASS_NOTIF, FAN_CLOEXEC); 1.171 + if (mFd == -1) { 1.172 + NS_WARNING("Error calling inotify_init()"); 1.173 + if (errno == ENOSYS) { 1.174 + printf_stderr("Warning: No fanotify support in this device's kernel.\n"); 1.175 + } 1.176 + return; 1.177 + } 1.178 + 1.179 + if (fanotify_mark(mFd, FAN_MARK_ADD | FAN_MARK_MOUNT, FAN_CLOSE, 1.180 + 0, kWatchedPath) < 0) { 1.181 + NS_WARNING("Error calling fanotify_mark"); 1.182 + close(mFd); 1.183 + mFd = -1; 1.184 + return; 1.185 + } 1.186 + 1.187 + if (!MessageLoopForIO::current()->WatchFileDescriptor( 1.188 + mFd, /* persistent = */ true, 1.189 + MessageLoopForIO::WATCH_READ, 1.190 + &mReadWatcher, gHalDiskSpaceWatcher)) { 1.191 + NS_WARNING("Unable to watch fanotify fd."); 1.192 + close(mFd); 1.193 + mFd = -1; 1.194 + } 1.195 +} 1.196 + 1.197 +void 1.198 +GonkDiskSpaceWatcher::DoStop() 1.199 +{ 1.200 + NS_ASSERTION(XRE_GetIOMessageLoop() == MessageLoopForIO::current(), 1.201 + "Not on the correct message loop"); 1.202 + 1.203 + if (mFd != -1) { 1.204 + mReadWatcher.StopWatchingFileDescriptor(); 1.205 + fanotify_mark(mFd, FAN_MARK_FLUSH, 0, 0, kWatchedPath); 1.206 + close(mFd); 1.207 + mFd = -1; 1.208 + } 1.209 + 1.210 + // Dispatch the cleanup to the main thread. 1.211 + nsCOMPtr<nsIRunnable> runnable = new DiskSpaceCleaner(); 1.212 + NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL); 1.213 +} 1.214 + 1.215 +// We are called off the main thread, so we proxy first to the main thread 1.216 +// before calling the xpcom object. 1.217 +void 1.218 +GonkDiskSpaceWatcher::NotifyUpdate() 1.219 +{ 1.220 + mLastTimestamp = TimeStamp::Now(); 1.221 + mLastFreeSpace = mFreeSpace; 1.222 + 1.223 + nsCOMPtr<nsIRunnable> runnable = 1.224 + new DiskSpaceNotifier(mIsDiskFull, mFreeSpace); 1.225 + NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL); 1.226 +} 1.227 + 1.228 +void 1.229 +GonkDiskSpaceWatcher::OnFileCanReadWithoutBlocking(int aFd) 1.230 +{ 1.231 + struct fanotify_event_metadata* fem = nullptr; 1.232 + char buf[4096]; 1.233 + struct statfs sfs; 1.234 + int32_t len, rc; 1.235 + 1.236 + do { 1.237 + len = read(aFd, buf, sizeof(buf)); 1.238 + } while(len == -1 && errno == EINTR); 1.239 + 1.240 + // Bail out if the file is busy. 1.241 + if (len < 0 && errno == ETXTBSY) { 1.242 + return; 1.243 + } 1.244 + 1.245 + // We should get an exact multiple of fanotify_event_metadata 1.246 + if (len <= 0 || (len % FAN_EVENT_METADATA_LEN != 0)) { 1.247 + printf_stderr("About to crash: fanotify_event_metadata read error."); 1.248 + MOZ_CRASH(); 1.249 + } 1.250 + 1.251 + fem = reinterpret_cast<fanotify_event_metadata *>(buf); 1.252 + 1.253 + while (FAN_EVENT_OK(fem, len)) { 1.254 + rc = fstatfs(fem->fd, &sfs); 1.255 + if (rc < 0) { 1.256 + NS_WARNING("Unable to stat fan_notify fd"); 1.257 + } else { 1.258 + bool firstRun = mFreeSpace == UINT64_MAX; 1.259 + mFreeSpace = sfs.f_bavail * sfs.f_bsize; 1.260 + // We change from full <-> free depending on the free space and the 1.261 + // low and high thresholds. 1.262 + // Once we are in 'full' mode we send updates for all size changes with 1.263 + // a minimum of time between messages or when we cross a size change 1.264 + // threshold. 1.265 + if (firstRun) { 1.266 + mIsDiskFull = mFreeSpace <= mLowThreshold; 1.267 + // Always notify the current state at first run. 1.268 + NotifyUpdate(); 1.269 + } else if (!mIsDiskFull && (mFreeSpace <= mLowThreshold)) { 1.270 + mIsDiskFull = true; 1.271 + NotifyUpdate(); 1.272 + } else if (mIsDiskFull && (mFreeSpace > mHighThreshold)) { 1.273 + mIsDiskFull = false; 1.274 + NotifyUpdate(); 1.275 + } else if (mIsDiskFull) { 1.276 + if (mTimeout < TimeStamp::Now() - mLastTimestamp || 1.277 + mSizeDelta < llabs(mFreeSpace - mLastFreeSpace)) { 1.278 + NotifyUpdate(); 1.279 + } 1.280 + } 1.281 + } 1.282 + close(fem->fd); 1.283 + fem = FAN_EVENT_NEXT(fem, len); 1.284 + } 1.285 +} 1.286 + 1.287 +void 1.288 +StartDiskSpaceWatcher() 1.289 +{ 1.290 + MOZ_ASSERT(NS_IsMainThread()); 1.291 + 1.292 + // Bail out if called several times. 1.293 + if (gHalDiskSpaceWatcher != nullptr) { 1.294 + return; 1.295 + } 1.296 + 1.297 + gHalDiskSpaceWatcher = new GonkDiskSpaceWatcher(); 1.298 + 1.299 + XRE_GetIOMessageLoop()->PostTask( 1.300 + FROM_HERE, 1.301 + NewRunnableMethod(gHalDiskSpaceWatcher, &GonkDiskSpaceWatcher::DoStart)); 1.302 +} 1.303 + 1.304 +void 1.305 +StopDiskSpaceWatcher() 1.306 +{ 1.307 + MOZ_ASSERT(NS_IsMainThread()); 1.308 + if (!gHalDiskSpaceWatcher) { 1.309 + return; 1.310 + } 1.311 + 1.312 + XRE_GetIOMessageLoop()->PostTask( 1.313 + FROM_HERE, 1.314 + NewRunnableMethod(gHalDiskSpaceWatcher, &GonkDiskSpaceWatcher::DoStop)); 1.315 +} 1.316 + 1.317 +} // namespace hal_impl 1.318 +} // namespace mozilla