Chromium Code Reviews| Index: content/browser/media_device_notifications_linux.cc |
| =================================================================== |
| --- content/browser/media_device_notifications_linux.cc (revision 0) |
| +++ content/browser/media_device_notifications_linux.cc (revision 0) |
| @@ -0,0 +1,299 @@ |
| +// Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "content/browser/media_device_notifications_linux.h" |
| + |
| +#include <fcntl.h> |
| +#include <mntent.h> |
| +#include <stdio.h> |
| +#include <sys/inotify.h> |
| +#include <sys/ioctl.h> |
| +#include <unistd.h> |
| + |
| +#include <vector> |
| + |
| +#include "base/eintr_wrapper.h" |
| +#include "base/file_util.h" |
| +#include "base/string_util.h" |
| +#include "base/system_monitor/system_monitor.h" |
| +#include "content/public/browser/browser_thread.h" |
| + |
| +namespace { |
| + |
| +const char* const kDCIMDirName = "DCIM"; |
| + |
| +// List of file systems we care about. |
| +// Roughly listed in likely order of occurrence. |
| +const char* const kKnownFileSystems[] = { |
| + "vfat", |
| + "ntfs", |
| + "iso9660", |
| + "udf", |
| + "ext4", |
| + "ext3", |
| + "ext2", |
| + "msdos", |
| + "fat", |
| + "hfsplus", |
| +}; |
| + |
| +} // namespace |
| + |
| +namespace content { |
| + |
| +MediaDeviceNotificationsLinux::MediaDeviceNotificationsLinux( |
| + const FilePath& path) |
| + : initialized_(false), |
| + watch_dir_(path.DirName()), |
| + watch_file_(path.BaseName()), |
| + inotify_fd_(-1), |
| + inotify_wd_(-1), |
| + current_device_id_(0U) { |
| + CHECK(!path.empty()); |
| +} |
| + |
| +MediaDeviceNotificationsLinux::~MediaDeviceNotificationsLinux() { |
| + if (!initialized_) |
| + return; |
| + |
| + if (inotify_fd_ >= 0) { |
| + inotify_watcher_.StopWatchingFileDescriptor(); |
| + CleanupInotifyFD(); |
| + } |
| +} |
| + |
| +void MediaDeviceNotificationsLinux::InitOnFileThread() { |
| + DCHECK(!initialized_); |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| + initialized_ = true; |
| + |
| + inotify_fd_ = inotify_init(); |
| + if (inotify_fd_ < 0) { |
| + PLOG(ERROR) << "inotify_init failed"; |
| + return; |
| + } |
| + |
| + int flags = fcntl(inotify_fd_, F_GETFL); |
| + if (fcntl(inotify_fd_, F_SETFL, flags | O_NONBLOCK) < 0) { |
| + PLOG(ERROR) << "fcntl failed"; |
| + CleanupInotifyFD(); |
| + return; |
| + } |
| + |
| + inotify_wd_ = inotify_add_watch(inotify_fd_, |
| + watch_dir_.value().c_str(), |
| + IN_CLOSE_WRITE | IN_MOVED_TO); |
| + if (inotify_wd_ < 0) { |
| + PLOG(ERROR) << "inotify_add_watch failed"; |
| + CleanupInotifyFD(); |
| + return; |
| + } |
| + |
| + MessageLoopForIO* message_loop = MessageLoopForIO::current(); |
| + if (!message_loop->WatchFileDescriptor(inotify_fd_, |
| + true, |
| + MessageLoopForIO::WATCH_READ, |
| + &inotify_watcher_, |
| + this)) { |
| + LOG(ERROR) << "WatchFileDescriptor failed"; |
| + CleanupInotifyFD(); |
| + return; |
| + } |
| + UpdateMtab(); |
| +} |
| + |
| +void MediaDeviceNotificationsLinux::OnFileCanReadWithoutBlocking(int fd) { |
| + DCHECK_EQ(inotify_fd_, fd); |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| + |
| + int buffer_size; |
| + int ioctl_result = HANDLE_EINTR(ioctl(inotify_fd_, FIONREAD, &buffer_size)); |
| + if (ioctl_result != 0) { |
| + PLOG(ERROR) << "ioctl failed"; |
| + return; |
| + } |
| + |
| + std::vector<char> buffer(buffer_size); |
| + const ssize_t bytes_read = |
| + HANDLE_EINTR(read(inotify_fd_, &buffer[0], buffer_size)); |
| + if (bytes_read < 0) { |
| + PLOG(ERROR) << "read failed"; |
| + return; |
| + } else if (bytes_read != buffer_size) { |
| + NOTREACHED() << "Expected to read " << buffer_size |
| + << " bytes, got " << bytes_read; |
| + return; |
| + } |
| + |
| + bool mtab_changed = false; |
| + ssize_t bytes_processed = 0; |
| + while (bytes_processed < bytes_read) { |
| + inotify_event* event = |
| + reinterpret_cast<inotify_event*>(&buffer[bytes_processed]); |
| + size_t event_size = sizeof(inotify_event) + event->len; |
| + 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.
|
| + mtab_changed = true; |
| + bytes_processed += event_size; |
| + } |
| + if (!mtab_changed) |
| + return; |
| + UpdateMtab(); |
| +} |
| + |
| +void MediaDeviceNotificationsLinux::OnFileCanWriteWithoutBlocking(int fd) { |
| + NOTREACHED(); |
| +} |
| + |
| +void MediaDeviceNotificationsLinux::CleanupInotifyFD() { |
| + DCHECK_GE(inotify_fd_, 0); |
| + close(inotify_fd_); |
| + inotify_fd_ = -1; |
| +} |
| + |
| +bool MediaDeviceNotificationsLinux::ProcessInotifyEvent(inotify_event* event) { |
| + if (event->mask & IN_IGNORED) |
| + return false; |
| + CHECK_EQ(inotify_wd_, event->wd); |
| + CHECK_NE(event->mask & (IN_CLOSE_WRITE | IN_MOVED_TO), 0U); |
| + |
| + if (!event->len) |
| + return false; |
| + return (FilePath(event->name) == watch_file_); |
| +} |
| + |
| +void MediaDeviceNotificationsLinux::UpdateMtab() { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| + |
| + MountMap new_mtab; |
| + ReadMtab(&new_mtab); |
| + |
| + // No changes, nothing to do. |
| + if (new_mtab == mtab_) |
| + return; |
| + |
| + // Check new mtab entries against existing ones. |
| + 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.
|
| + for (MountMap::const_iterator newiter = new_mtab.begin(); |
| + newiter != new_mtab.end(); |
| + ++newiter) { |
| + const MountPoint& mount_point = newiter->first; |
| + seen_mount_points.insert(mount_point); |
| + |
| + MountMap::const_iterator olditer = mtab_.find(mount_point); |
| + if (olditer == mtab_.end()) { |
| + const MountDevices& devices = newiter->second; |
| + 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
|
| + deviceiter != devices.end(); |
| + ++deviceiter) { |
| + AddNewMediaDevice(mount_point, *deviceiter); |
| + } |
| + } else { |
| + CompareMountPointDevices(mount_point, newiter->second, olditer->second); |
| + } |
| + } |
| + |
| + // Check existing mtab entries for unaccounted mount points. |
| + // These mount points must have been removed in the new mtab. |
| + for (MountMap::const_iterator it = mtab_.begin(); it != mtab_.end(); ++it) { |
| + const MountPoint& mount_point = it->first; |
| + if (seen_mount_points.find(mount_point) != seen_mount_points.end()) |
| + continue; |
| + const MountDevices& devices = it->second; |
| + for (MountDevices::const_iterator deviceiter = devices.begin(); |
| + deviceiter != devices.end(); |
| + ++deviceiter) { |
| + RemoveOldMediaDevice(mount_point, *deviceiter); |
| + } |
| + } |
| + |
| + // Done processing, update |mtab_|. |
| + mtab_ = new_mtab; |
| +} |
| + |
| +void MediaDeviceNotificationsLinux::ReadMtab(MountMap* mtab) { |
| + FILE* fp = setmntent(watch_dir_.Append(watch_file_).value().c_str(), "r"); |
| + if (!fp) |
| + return; |
| + |
| + MountMap& new_mtab = *mtab; |
| + struct mntent entry; |
| + char buf[512]; |
| + while (getmntent_r(fp, &entry, buf, sizeof(buf))) { |
| + std::string type(entry.mnt_type); |
| + // We only care about real file systems. |
| + 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.
|
| + if (type != kKnownFileSystems[i]) |
| + continue; |
| + if (new_mtab.find(entry.mnt_dir) == new_mtab.end()) |
| + 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
|
| + new_mtab[entry.mnt_dir].insert(entry.mnt_fsname); |
| + break; |
| + } |
| + } |
| + endmntent(fp); |
| +} |
| + |
| +void MediaDeviceNotificationsLinux::CompareMountPointDevices( |
| + const MountPoint& mount_point, |
| + const MountDevices& new_devices, |
| + const MountDevices& old_devices) { |
| + for (MountDevices::const_iterator it = new_devices.begin(); |
| + it != new_devices.end(); |
| + ++it) { |
| + const MountDevice& device = *it; |
| + if (old_devices.find(device) != old_devices.end()) |
| + continue; |
| + AddNewMediaDevice(mount_point, device); |
| + } |
| + for (MountDevices::const_iterator it = old_devices.begin(); |
| + it != old_devices.end(); |
| + ++it) { |
| + const MountDevice& device = *it; |
| + if (new_devices.find(device) != new_devices.end()) |
| + continue; |
| + RemoveOldMediaDevice(mount_point, device); |
| + } |
| +} |
| + |
| +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.
|
| + const MountPoint& mount_point, |
| + const MountDevice& mount_device) { |
| + // Check for the existence of a DCIM directory. Otherwise it's not a media |
| + // 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.
|
| + FilePath dcim_path(mount_point); |
| + FilePath::StringType dcim_dir(kDCIMDirName); |
| + if (!file_util::DirectoryExists(dcim_path.Append(dcim_dir))) { |
| + // Check for lowercase 'dcim' as well. |
| + FilePath dcim_path_lower = dcim_path.Append(StringToLowerASCII(dcim_dir)); |
| + if (!file_util::DirectoryExists(dcim_path_lower)) { |
| + return; |
| + } |
| + } |
| + |
| + const MountEntry entry(mount_point, mount_device); |
| + base::SystemMonitor::DeviceIdType device_id = current_device_id_++; |
| + device_id_map_[entry] = device_id; |
| + base::SystemMonitor* system_monitor = base::SystemMonitor::Get(); |
| + system_monitor->ProcessMediaDeviceAttached(device_id, |
| + mount_device, |
| + FilePath(mount_point)); |
| +} |
| + |
| +void MediaDeviceNotificationsLinux::RemoveOldMediaDevice( |
| + const MountPoint& mount_point, |
| + const MountDevice& mount_device) { |
| + // Look for the entry in |device_id_map_|. It may not be in there because |
| + // the removed mount point didn't have a DCIM directory. |
| + const MountEntry entry(mount_point, mount_device); |
| + DeviceIdMap::iterator it = device_id_map_.find(entry); |
| + if (it == device_id_map_.end()) |
| + return; |
| + |
| + base::SystemMonitor::DeviceIdType device_id = it->second; |
| + device_id_map_.erase(it); |
| + base::SystemMonitor* system_monitor = base::SystemMonitor::Get(); |
| + system_monitor->ProcessMediaDeviceDetached(device_id); |
| +} |
| + |
| +} // namespace content |
| Property changes on: content/browser/media_device_notifications_linux.cc |
| ___________________________________________________________________ |
| Added: svn:eol-style |
| + LF |