Chromium Code Reviews
|
| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "content/browser/media_device_notifications_linux.h" | |
| 6 | |
| 7 #include <fcntl.h> | |
| 8 #include <mntent.h> | |
| 9 #include <stdio.h> | |
| 10 #include <sys/inotify.h> | |
| 11 #include <sys/ioctl.h> | |
| 12 #include <unistd.h> | |
| 13 | |
| 14 #include <vector> | |
| 15 | |
| 16 #include "base/eintr_wrapper.h" | |
| 17 #include "base/file_util.h" | |
| 18 #include "base/string_util.h" | |
| 19 #include "base/system_monitor/system_monitor.h" | |
| 20 #include "content/public/browser/browser_thread.h" | |
| 21 | |
| 22 namespace { | |
| 23 | |
| 24 const char* const kDCIMDirName = "DCIM"; | |
| 25 | |
| 26 // List of file systems we care about. | |
| 27 // Roughly listed in likely order of occurrence. | |
| 28 const char* const kKnownFileSystems[] = { | |
| 29 "vfat", | |
| 30 "ntfs", | |
| 31 "iso9660", | |
| 32 "udf", | |
| 33 "ext4", | |
| 34 "ext3", | |
| 35 "ext2", | |
| 36 "msdos", | |
| 37 "fat", | |
| 38 "hfsplus", | |
| 39 }; | |
| 40 | |
| 41 } // namespace | |
| 42 | |
| 43 namespace content { | |
| 44 | |
| 45 MediaDeviceNotificationsLinux::MediaDeviceNotificationsLinux( | |
| 46 const FilePath& path) | |
| 47 : initialized_(false), | |
| 48 watch_dir_(path.DirName()), | |
| 49 watch_file_(path.BaseName()), | |
| 50 inotify_fd_(-1), | |
| 51 inotify_wd_(-1), | |
| 52 current_device_id_(0U) { | |
| 53 CHECK(!path.empty()); | |
| 54 } | |
| 55 | |
| 56 MediaDeviceNotificationsLinux::~MediaDeviceNotificationsLinux() { | |
| 57 if (!initialized_) | |
| 58 return; | |
| 59 | |
| 60 if (inotify_fd_ >= 0) { | |
| 61 inotify_watcher_.StopWatchingFileDescriptor(); | |
| 62 CleanupInotifyFD(); | |
| 63 } | |
| 64 } | |
| 65 | |
| 66 void MediaDeviceNotificationsLinux::InitOnFileThread() { | |
| 67 DCHECK(!initialized_); | |
| 68 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | |
| 69 initialized_ = true; | |
| 70 | |
| 71 inotify_fd_ = inotify_init(); | |
| 72 if (inotify_fd_ < 0) { | |
| 73 PLOG(ERROR) << "inotify_init failed"; | |
| 74 return; | |
| 75 } | |
| 76 | |
| 77 int flags = fcntl(inotify_fd_, F_GETFL); | |
| 78 if (fcntl(inotify_fd_, F_SETFL, flags | O_NONBLOCK) < 0) { | |
| 79 PLOG(ERROR) << "fcntl failed"; | |
| 80 CleanupInotifyFD(); | |
| 81 return; | |
| 82 } | |
| 83 | |
| 84 inotify_wd_ = inotify_add_watch(inotify_fd_, | |
| 85 watch_dir_.value().c_str(), | |
| 86 IN_CLOSE_WRITE | IN_MOVED_TO); | |
| 87 if (inotify_wd_ < 0) { | |
| 88 PLOG(ERROR) << "inotify_add_watch failed"; | |
| 89 CleanupInotifyFD(); | |
| 90 return; | |
| 91 } | |
| 92 | |
| 93 MessageLoopForIO* message_loop = MessageLoopForIO::current(); | |
| 94 if (!message_loop->WatchFileDescriptor(inotify_fd_, | |
| 95 true, | |
| 96 MessageLoopForIO::WATCH_READ, | |
| 97 &inotify_watcher_, | |
| 98 this)) { | |
| 99 LOG(ERROR) << "WatchFileDescriptor failed"; | |
| 100 CleanupInotifyFD(); | |
| 101 return; | |
| 102 } | |
| 103 UpdateMtab(); | |
| 104 } | |
| 105 | |
| 106 void MediaDeviceNotificationsLinux::OnFileCanReadWithoutBlocking(int fd) { | |
| 107 DCHECK_EQ(inotify_fd_, fd); | |
| 108 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | |
| 109 | |
| 110 int buffer_size; | |
| 111 int ioctl_result = HANDLE_EINTR(ioctl(inotify_fd_, FIONREAD, &buffer_size)); | |
| 112 if (ioctl_result != 0) { | |
| 113 PLOG(ERROR) << "ioctl failed"; | |
| 114 return; | |
| 115 } | |
| 116 | |
| 117 std::vector<char> buffer(buffer_size); | |
| 118 const ssize_t bytes_read = | |
| 119 HANDLE_EINTR(read(inotify_fd_, &buffer[0], buffer_size)); | |
| 120 if (bytes_read < 0) { | |
| 121 PLOG(ERROR) << "read failed"; | |
| 122 return; | |
| 123 } else if (bytes_read != buffer_size) { | |
| 124 NOTREACHED() << "Expected to read " << buffer_size | |
| 125 << " bytes, got " << bytes_read; | |
| 126 return; | |
| 127 } | |
| 128 | |
| 129 bool mtab_changed = false; | |
| 130 ssize_t bytes_processed = 0; | |
| 131 while (bytes_processed < bytes_read) { | |
| 132 inotify_event* event = | |
| 133 reinterpret_cast<inotify_event*>(&buffer[bytes_processed]); | |
| 134 size_t event_size = sizeof(inotify_event) + event->len; | |
| 135 if (ProcessInotifyEvent(event)) | |
|
vandebo (ex-Chrome)
2012/03/02 23:47:35
maybe add a DCHECK(bytes_processed + event_size <=
Lei Zhang
2012/03/03 02:28:07
Done.
| |
| 136 mtab_changed = true; | |
| 137 bytes_processed += event_size; | |
| 138 } | |
| 139 if (!mtab_changed) | |
| 140 return; | |
| 141 UpdateMtab(); | |
| 142 } | |
| 143 | |
| 144 void MediaDeviceNotificationsLinux::OnFileCanWriteWithoutBlocking(int fd) { | |
| 145 NOTREACHED(); | |
| 146 } | |
| 147 | |
| 148 void MediaDeviceNotificationsLinux::CleanupInotifyFD() { | |
| 149 DCHECK_GE(inotify_fd_, 0); | |
| 150 close(inotify_fd_); | |
| 151 inotify_fd_ = -1; | |
| 152 } | |
| 153 | |
| 154 bool MediaDeviceNotificationsLinux::ProcessInotifyEvent(inotify_event* event) { | |
| 155 if (event->mask & IN_IGNORED) | |
| 156 return false; | |
| 157 CHECK_EQ(inotify_wd_, event->wd); | |
| 158 CHECK_NE(event->mask & (IN_CLOSE_WRITE | IN_MOVED_TO), 0U); | |
| 159 | |
| 160 if (!event->len) | |
| 161 return false; | |
| 162 return (FilePath(event->name) == watch_file_); | |
| 163 } | |
| 164 | |
| 165 void MediaDeviceNotificationsLinux::UpdateMtab() { | |
| 166 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | |
| 167 | |
| 168 MountMap new_mtab; | |
| 169 ReadMtab(&new_mtab); | |
| 170 | |
| 171 // No changes, nothing to do. | |
| 172 if (new_mtab == mtab_) | |
| 173 return; | |
| 174 | |
| 175 // Check new mtab entries against existing ones. | |
| 176 std::set<MountPoint> seen_mount_points; | |
|
vandebo (ex-Chrome)
2012/03/02 23:47:35
This copies the mount points (strings), MountPoint
Lei Zhang
2012/03/05 23:14:33
This code has been removed.
| |
| 177 for (MountMap::const_iterator newiter = new_mtab.begin(); | |
| 178 newiter != new_mtab.end(); | |
| 179 ++newiter) { | |
| 180 const MountPoint& mount_point = newiter->first; | |
| 181 seen_mount_points.insert(mount_point); | |
| 182 | |
| 183 MountMap::const_iterator olditer = mtab_.find(mount_point); | |
| 184 if (olditer == mtab_.end()) { | |
| 185 const MountDevices& devices = newiter->second; | |
| 186 for (MountDevices::const_iterator deviceiter = devices.begin(); | |
|
vandebo (ex-Chrome)
2012/03/02 23:47:35
Only one device at a mount point is accessible. E
Lei Zhang
2012/03/05 23:14:33
I've changed the code so the last device mounted a
| |
| 187 deviceiter != devices.end(); | |
| 188 ++deviceiter) { | |
| 189 AddNewMediaDevice(mount_point, *deviceiter); | |
| 190 } | |
| 191 } else { | |
| 192 CompareMountPointDevices(mount_point, newiter->second, olditer->second); | |
| 193 } | |
| 194 } | |
| 195 | |
| 196 // Check existing mtab entries for unaccounted mount points. | |
| 197 // These mount points must have been removed in the new mtab. | |
| 198 for (MountMap::const_iterator it = mtab_.begin(); it != mtab_.end(); ++it) { | |
| 199 const MountPoint& mount_point = it->first; | |
| 200 if (seen_mount_points.find(mount_point) != seen_mount_points.end()) | |
| 201 continue; | |
| 202 const MountDevices& devices = it->second; | |
| 203 for (MountDevices::const_iterator deviceiter = devices.begin(); | |
| 204 deviceiter != devices.end(); | |
| 205 ++deviceiter) { | |
| 206 RemoveOldMediaDevice(mount_point, *deviceiter); | |
| 207 } | |
| 208 } | |
| 209 | |
| 210 // Done processing, update |mtab_|. | |
| 211 mtab_ = new_mtab; | |
| 212 } | |
| 213 | |
| 214 void MediaDeviceNotificationsLinux::ReadMtab(MountMap* mtab) { | |
| 215 FILE* fp = setmntent(watch_dir_.Append(watch_file_).value().c_str(), "r"); | |
| 216 if (!fp) | |
| 217 return; | |
| 218 | |
| 219 MountMap& new_mtab = *mtab; | |
| 220 struct mntent entry; | |
| 221 char buf[512]; | |
| 222 while (getmntent_r(fp, &entry, buf, sizeof(buf))) { | |
| 223 std::string type(entry.mnt_type); | |
| 224 // We only care about real file systems. | |
| 225 for (size_t i = 0; i < arraysize(kKnownFileSystems); ++i) { | |
|
vandebo (ex-Chrome)
2012/03/02 23:47:35
Maybe put known file systems in a set, and use set
Lei Zhang
2012/03/03 02:28:07
Done.
| |
| 226 if (type != kKnownFileSystems[i]) | |
| 227 continue; | |
| 228 if (new_mtab.find(entry.mnt_dir) == new_mtab.end()) | |
| 229 new_mtab[entry.mnt_dir] = MountDevices(); | |
|
vandebo (ex-Chrome)
2012/03/02 23:47:35
Only one device is accessible at a time, do we nee
| |
| 230 new_mtab[entry.mnt_dir].insert(entry.mnt_fsname); | |
| 231 break; | |
| 232 } | |
| 233 } | |
| 234 endmntent(fp); | |
| 235 } | |
| 236 | |
| 237 void MediaDeviceNotificationsLinux::CompareMountPointDevices( | |
| 238 const MountPoint& mount_point, | |
| 239 const MountDevices& new_devices, | |
| 240 const MountDevices& old_devices) { | |
| 241 for (MountDevices::const_iterator it = new_devices.begin(); | |
| 242 it != new_devices.end(); | |
| 243 ++it) { | |
| 244 const MountDevice& device = *it; | |
| 245 if (old_devices.find(device) != old_devices.end()) | |
| 246 continue; | |
| 247 AddNewMediaDevice(mount_point, device); | |
| 248 } | |
| 249 for (MountDevices::const_iterator it = old_devices.begin(); | |
| 250 it != old_devices.end(); | |
| 251 ++it) { | |
| 252 const MountDevice& device = *it; | |
| 253 if (new_devices.find(device) != new_devices.end()) | |
| 254 continue; | |
| 255 RemoveOldMediaDevice(mount_point, device); | |
| 256 } | |
| 257 } | |
| 258 | |
| 259 void MediaDeviceNotificationsLinux::AddNewMediaDevice( | |
|
vandebo (ex-Chrome)
2012/03/02 23:47:35
nit: AddNewDevice, we don't know if it's a media d
Lei Zhang
2012/03/03 02:28:07
Done.
| |
| 260 const MountPoint& mount_point, | |
| 261 const MountDevice& mount_device) { | |
| 262 // Check for the existence of a DCIM directory. Otherwise it's not a media | |
| 263 // device. Mac OS X behaves this way. | |
|
vandebo (ex-Chrome)
2012/03/02 23:47:35
Actually Mac does something more magic (I haven't
Lei Zhang
2012/03/03 02:28:07
I added a TODO for you.
| |
| 264 FilePath dcim_path(mount_point); | |
| 265 FilePath::StringType dcim_dir(kDCIMDirName); | |
| 266 if (!file_util::DirectoryExists(dcim_path.Append(dcim_dir))) { | |
| 267 // Check for lowercase 'dcim' as well. | |
| 268 FilePath dcim_path_lower = dcim_path.Append(StringToLowerASCII(dcim_dir)); | |
| 269 if (!file_util::DirectoryExists(dcim_path_lower)) { | |
| 270 return; | |
| 271 } | |
| 272 } | |
| 273 | |
| 274 const MountEntry entry(mount_point, mount_device); | |
| 275 base::SystemMonitor::DeviceIdType device_id = current_device_id_++; | |
| 276 device_id_map_[entry] = device_id; | |
| 277 base::SystemMonitor* system_monitor = base::SystemMonitor::Get(); | |
| 278 system_monitor->ProcessMediaDeviceAttached(device_id, | |
| 279 mount_device, | |
| 280 FilePath(mount_point)); | |
| 281 } | |
| 282 | |
| 283 void MediaDeviceNotificationsLinux::RemoveOldMediaDevice( | |
| 284 const MountPoint& mount_point, | |
| 285 const MountDevice& mount_device) { | |
| 286 // Look for the entry in |device_id_map_|. It may not be in there because | |
| 287 // the removed mount point didn't have a DCIM directory. | |
| 288 const MountEntry entry(mount_point, mount_device); | |
| 289 DeviceIdMap::iterator it = device_id_map_.find(entry); | |
| 290 if (it == device_id_map_.end()) | |
| 291 return; | |
| 292 | |
| 293 base::SystemMonitor::DeviceIdType device_id = it->second; | |
| 294 device_id_map_.erase(it); | |
| 295 base::SystemMonitor* system_monitor = base::SystemMonitor::Get(); | |
| 296 system_monitor->ProcessMediaDeviceDetached(device_id); | |
| 297 } | |
| 298 | |
| 299 } // namespace content | |
| OLD | NEW |