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,276 @@ |
+// 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. |
+const char* const kKnownFileSystems[] = { |
+ "ext2", |
+ "ext3", |
+ "ext4", |
+ "fat", |
+ "hfsplus", |
+ "iso9660", |
+ "msdos", |
+ "ntfs", |
+ "udf", |
+ "vfat", |
+}; |
+ |
+} // 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()); |
+ |
+ // Put |kKnownFileSystems| in set to get O(log N) access time. |
+ for (size_t i = 0; i < arraysize(kKnownFileSystems); ++i) |
+ known_file_systems_.insert(kKnownFileSystems[i]); |
+} |
+ |
+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(); |
vandebo (ex-Chrome)
2012/03/06 00:41:40
It'd be great if we could reuse FilePathWatcher
Lei Zhang
2012/03/06 19:00:25
Done.
|
+ 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; |
+ DCHECK_LE(bytes_processed + event_size, static_cast<size_t>(bytes_read)); |
+ if (ProcessInotifyEvent(event)) { |
+ mtab_changed = true; |
+ break; |
+ } |
+ 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) { |
vandebo (ex-Chrome)
2012/03/06 00:41:40
nit: InotifyEventIsInteresting ?
Lei Zhang
2012/03/06 19:00:25
Code got removed.
|
+ 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); |
+ |
+ // Check new mtab entries against existing ones. |
+ for (MountMap::const_iterator newiter = new_mtab.begin(); |
+ newiter != new_mtab.end(); |
+ ++newiter) { |
+ const MountPoint& mount_point(newiter->first); |
+ const MountDevice& mount_device(newiter->second); |
+ MountMap::const_iterator olditer = mtab_.find(mount_point); |
+ if (olditer == mtab_.end()) { |
+ AddNewDevice(mount_point, mount_device); |
+ } else { |
+ const MountDevice& old_mount_device(olditer->second); |
+ if (mount_device != old_mount_device) { |
+ RemoveOldDevice(mount_point, old_mount_device); |
+ AddNewDevice(mount_point, mount_device); |
+ } |
+ } |
+ } |
+ |
+ // 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); |
+ const MountDevice& mount_device(it->second); |
+ if (new_mtab.find(mount_point) == new_mtab.end()) |
+ RemoveOldDevice(mount_point, mount_device); |
+ } |
+ |
+ // Done processing, update |mtab_|. |
+ mtab_ = new_mtab; |
vandebo (ex-Chrome)
2012/03/06 00:41:40
I think if you update mtab_ in place (i.e. add and
Lei Zhang
2012/03/06 19:00:25
Done.
|
+} |
+ |
+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))) { |
+ // We only care about real file systems. |
+ if (known_file_systems_.find(entry.mnt_type) == known_file_systems_.end()) |
+ continue; |
+ |
+ // Check to see if the device has already been mounted elsewhere. |
+ const MountDevice mount_device(entry.mnt_fsname); |
+ const MountPoint mount_point(entry.mnt_dir); |
+ for (MountMap::iterator it = new_mtab.begin(); it != new_mtab.end(); ++it) { |
vandebo (ex-Chrome)
2012/03/06 00:41:40
This is n^2. Should we just do a dev -> point map
Lei Zhang
2012/03/06 19:00:25
Sure, it's not that trivial though. We still need
|
+ if (it->second == mount_device) { |
+ new_mtab.erase(it); |
+ break; |
+ } |
+ } |
+ new_mtab[mount_point] = mount_device; |
+ } |
+ endmntent(fp); |
+} |
+ |
+void MediaDeviceNotificationsLinux::AddNewDevice( |
+ 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 similarly. |
+ // TODO(vandebo) Try to figure out how Mac OS X decides this. |
+ 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::RemoveOldDevice( |
+ 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 |