Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(243)

Side by Side Diff: content/browser/media_device_notifications_linux.cc

Issue 9560008: Implement Linux media notifier. (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: Created 8 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
Property Changes:
Added: svn:eol-style
+ LF
OLDNEW
(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 const char* const kKnownFileSystems[] = {
28 "ext2",
29 "ext3",
30 "ext4",
31 "fat",
32 "hfsplus",
33 "iso9660",
34 "msdos",
35 "ntfs",
36 "udf",
37 "vfat",
38 };
39
40 } // namespace
41
42 namespace content {
43
44 MediaDeviceNotificationsLinux::MediaDeviceNotificationsLinux(
45 const FilePath& path)
46 : initialized_(false),
47 watch_dir_(path.DirName()),
48 watch_file_(path.BaseName()),
49 inotify_fd_(-1),
50 inotify_wd_(-1),
51 current_device_id_(0U) {
52 CHECK(!path.empty());
53
54 // Put |kKnownFileSystems| in set to get O(log N) access time.
55 for (size_t i = 0; i < arraysize(kKnownFileSystems); ++i)
56 known_file_systems_.insert(kKnownFileSystems[i]);
57 }
58
59 MediaDeviceNotificationsLinux::~MediaDeviceNotificationsLinux() {
60 if (!initialized_)
61 return;
62
63 if (inotify_fd_ >= 0) {
64 inotify_watcher_.StopWatchingFileDescriptor();
65 CleanupInotifyFD();
66 }
67 }
68
69 void MediaDeviceNotificationsLinux::InitOnFileThread() {
70 DCHECK(!initialized_);
71 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
72 initialized_ = true;
73
74 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.
75 if (inotify_fd_ < 0) {
76 PLOG(ERROR) << "inotify_init failed";
77 return;
78 }
79
80 int flags = fcntl(inotify_fd_, F_GETFL);
81 if (fcntl(inotify_fd_, F_SETFL, flags | O_NONBLOCK) < 0) {
82 PLOG(ERROR) << "fcntl failed";
83 CleanupInotifyFD();
84 return;
85 }
86
87 inotify_wd_ = inotify_add_watch(inotify_fd_,
88 watch_dir_.value().c_str(),
89 IN_CLOSE_WRITE | IN_MOVED_TO);
90 if (inotify_wd_ < 0) {
91 PLOG(ERROR) << "inotify_add_watch failed";
92 CleanupInotifyFD();
93 return;
94 }
95
96 MessageLoopForIO* message_loop = MessageLoopForIO::current();
97 if (!message_loop->WatchFileDescriptor(inotify_fd_,
98 true,
99 MessageLoopForIO::WATCH_READ,
100 &inotify_watcher_,
101 this)) {
102 LOG(ERROR) << "WatchFileDescriptor failed";
103 CleanupInotifyFD();
104 return;
105 }
106 UpdateMtab();
107 }
108
109 void MediaDeviceNotificationsLinux::OnFileCanReadWithoutBlocking(int fd) {
110 DCHECK_EQ(inotify_fd_, fd);
111 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
112
113 int buffer_size;
114 int ioctl_result = HANDLE_EINTR(ioctl(inotify_fd_, FIONREAD, &buffer_size));
115 if (ioctl_result != 0) {
116 PLOG(ERROR) << "ioctl failed";
117 return;
118 }
119
120 std::vector<char> buffer(buffer_size);
121 const ssize_t bytes_read =
122 HANDLE_EINTR(read(inotify_fd_, &buffer[0], buffer_size));
123 if (bytes_read < 0) {
124 PLOG(ERROR) << "read failed";
125 return;
126 } else if (bytes_read != buffer_size) {
127 NOTREACHED() << "Expected to read " << buffer_size
128 << " bytes, got " << bytes_read;
129 return;
130 }
131
132 bool mtab_changed = false;
133 ssize_t bytes_processed = 0;
134 while (bytes_processed < bytes_read) {
135 inotify_event* event =
136 reinterpret_cast<inotify_event*>(&buffer[bytes_processed]);
137 size_t event_size = sizeof(inotify_event) + event->len;
138 DCHECK_LE(bytes_processed + event_size, static_cast<size_t>(bytes_read));
139 if (ProcessInotifyEvent(event)) {
140 mtab_changed = true;
141 break;
142 }
143 bytes_processed += event_size;
144 }
145 if (!mtab_changed)
146 return;
147 UpdateMtab();
148 }
149
150 void MediaDeviceNotificationsLinux::OnFileCanWriteWithoutBlocking(int fd) {
151 NOTREACHED();
152 }
153
154 void MediaDeviceNotificationsLinux::CleanupInotifyFD() {
155 DCHECK_GE(inotify_fd_, 0);
156 close(inotify_fd_);
157 inotify_fd_ = -1;
158 }
159
160 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.
161 if (event->mask & IN_IGNORED)
162 return false;
163 CHECK_EQ(inotify_wd_, event->wd);
164 CHECK_NE(event->mask & (IN_CLOSE_WRITE | IN_MOVED_TO), 0U);
165
166 if (!event->len)
167 return false;
168 return (FilePath(event->name) == watch_file_);
169 }
170
171 void MediaDeviceNotificationsLinux::UpdateMtab() {
172 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
173
174 MountMap new_mtab;
175 ReadMtab(&new_mtab);
176
177 // Check new mtab entries against existing ones.
178 for (MountMap::const_iterator newiter = new_mtab.begin();
179 newiter != new_mtab.end();
180 ++newiter) {
181 const MountPoint& mount_point(newiter->first);
182 const MountDevice& mount_device(newiter->second);
183 MountMap::const_iterator olditer = mtab_.find(mount_point);
184 if (olditer == mtab_.end()) {
185 AddNewDevice(mount_point, mount_device);
186 } else {
187 const MountDevice& old_mount_device(olditer->second);
188 if (mount_device != old_mount_device) {
189 RemoveOldDevice(mount_point, old_mount_device);
190 AddNewDevice(mount_point, mount_device);
191 }
192 }
193 }
194
195 // Check existing mtab entries for unaccounted mount points.
196 // These mount points must have been removed in the new mtab.
197 for (MountMap::const_iterator it = mtab_.begin(); it != mtab_.end(); ++it) {
198 const MountPoint& mount_point(it->first);
199 const MountDevice& mount_device(it->second);
200 if (new_mtab.find(mount_point) == new_mtab.end())
201 RemoveOldDevice(mount_point, mount_device);
202 }
203
204 // Done processing, update |mtab_|.
205 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.
206 }
207
208 void MediaDeviceNotificationsLinux::ReadMtab(MountMap* mtab) {
209 FILE* fp = setmntent(watch_dir_.Append(watch_file_).value().c_str(), "r");
210 if (!fp)
211 return;
212
213 MountMap& new_mtab = *mtab;
214 struct mntent entry;
215 char buf[512];
216 while (getmntent_r(fp, &entry, buf, sizeof(buf))) {
217 // We only care about real file systems.
218 if (known_file_systems_.find(entry.mnt_type) == known_file_systems_.end())
219 continue;
220
221 // Check to see if the device has already been mounted elsewhere.
222 const MountDevice mount_device(entry.mnt_fsname);
223 const MountPoint mount_point(entry.mnt_dir);
224 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
225 if (it->second == mount_device) {
226 new_mtab.erase(it);
227 break;
228 }
229 }
230 new_mtab[mount_point] = mount_device;
231 }
232 endmntent(fp);
233 }
234
235 void MediaDeviceNotificationsLinux::AddNewDevice(
236 const MountPoint& mount_point,
237 const MountDevice& mount_device) {
238 // Check for the existence of a DCIM directory. Otherwise it's not a media
239 // device. Mac OS X behaves similarly.
240 // TODO(vandebo) Try to figure out how Mac OS X decides this.
241 FilePath dcim_path(mount_point);
242 FilePath::StringType dcim_dir(kDCIMDirName);
243 if (!file_util::DirectoryExists(dcim_path.Append(dcim_dir))) {
244 // Check for lowercase 'dcim' as well.
245 FilePath dcim_path_lower(dcim_path.Append(StringToLowerASCII(dcim_dir)));
246 if (!file_util::DirectoryExists(dcim_path_lower)) {
247 return;
248 }
249 }
250
251 const MountEntry entry(mount_point, mount_device);
252 base::SystemMonitor::DeviceIdType device_id = current_device_id_++;
253 device_id_map_[entry] = device_id;
254 base::SystemMonitor* system_monitor = base::SystemMonitor::Get();
255 system_monitor->ProcessMediaDeviceAttached(device_id,
256 mount_device,
257 FilePath(mount_point));
258 }
259
260 void MediaDeviceNotificationsLinux::RemoveOldDevice(
261 const MountPoint& mount_point,
262 const MountDevice& mount_device) {
263 // Look for the entry in |device_id_map_|. It may not be in there because
264 // the removed mount point didn't have a DCIM directory.
265 const MountEntry entry(mount_point, mount_device);
266 DeviceIdMap::iterator it = device_id_map_.find(entry);
267 if (it == device_id_map_.end())
268 return;
269
270 base::SystemMonitor::DeviceIdType device_id = it->second;
271 device_id_map_.erase(it);
272 base::SystemMonitor* system_monitor = base::SystemMonitor::Get();
273 system_monitor->ProcessMediaDeviceDetached(device_id);
274 }
275
276 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698