|
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 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 | |
OLD | NEW |