Merge lp:~cyphermox/mtp/windows+sdcard+security into lp:mtp
- windows+sdcard+security
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Sergio Schvezov |
Approved revision: | 54 |
Merged at revision: | 45 |
Proposed branch: | lp:~cyphermox/mtp/windows+sdcard+security |
Merge into: | lp:mtp |
Diff against target: |
1496 lines (+915/-157) 8 files modified
CMakeLists.txt (+4/-1) debian/changelog (+26/-0) debian/control (+3/-0) debian/mtp-server.conf (+4/-0) include/MtpDatabase.h (+8/-0) server/UbuntuMtpDatabase.h (+513/-114) server/server.cpp (+349/-42) tests/MockMtpDatabase.h (+8/-0) |
To merge this branch: | bzr merge lp:~cyphermox/mtp/windows+sdcard+security |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Sergio Schvezov | Approve | ||
PS Jenkins bot | continuous-integration | Approve | |
Manuel de la Peña (community) | Approve | ||
Michael Frey (community) | Approve | ||
Review via email: mp+230834@code.launchpad.net |
Commit message
Add support for Windows, SD card, and screen locking.
Description of the change
Add support for Windows, SD card, and screen locking.
- 49. By Mathieu Trudel-Lapierre
-
Add missing 0.0.3+14.
04.20140409- 0ubuntu2 changelog - 50. By Mathieu Trudel-Lapierre
-
Add missing changes for 0.0.3+14.
04.20140409- 0ubuntu3.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:48
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:50
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Sergio Schvezov (sergiusens) wrote : | # |
I'm getting a bunch of these on boot:
terminate called after throwing an instance of 'std::runtime_
what(): org.freedesktop
I0814 16:34:34.118505 2627 server.cpp:359] MTP server starting...
I'm also not seeing my sdcard; only the internal one (my sdcard has files dropped in, no directories in it)
Mathieu Trudel-Lapierre (cyphermox) wrote : | # |
Are you using the latest image? The security bits depend on having UnityGreeter available on the session bus. It's the only thing that requires DBus.
How do you proceed to do the SD card tests?
Sergio Schvezov (sergiusens) wrote : | # |
On viernes 15 de agosto de 2014 11h'33:39 ART, Mathieu Trudel-Lapierre
wrote:
> Are you using the latest image? The security bits depend on
> having UnityGreeter available on the session bus. It's the only
> thing that requires DBus.
- Tried on image 190.
> How do you proceed to do the SD card tests?
- Tried to test with the sdcard plugged on boot
- Tried popping in the sdcard while in the session.
I have music in the sdcard root. The patched mediascanner picks it up just
fine fwiw.
- 51. By Mathieu Trudel-Lapierre
-
Fix sdcard hotswapping.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:51
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Manuel de la Peña (mandel) wrote : | # |
Some small comments about the code just to make it clear for new commers.
- 52. By Mathieu Trudel-Lapierre
-
Apply suggested changes from review by mandel
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:52
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Manuel de la Peña (mandel) : | # |
- 53. By Mathieu Trudel-Lapierre
-
Revert use of scoped_ptr; seems to crash at least object listing in some cases.
- 54. By Mathieu Trudel-Lapierre
-
Simplify retry logic for when the mountpoint is available but not yet populated.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:54
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Sergio Schvezov (sergiusens) : | # |
Preview Diff
1 | === modified file 'CMakeLists.txt' |
2 | --- CMakeLists.txt 2014-03-27 15:45:30 +0000 |
3 | +++ CMakeLists.txt 2014-08-21 19:42:07 +0000 |
4 | @@ -13,7 +13,8 @@ |
5 | set(MTP_VERSION_PATCH 0) |
6 | |
7 | find_package(Boost REQUIRED COMPONENTS thread system filesystem unit_test_framework) |
8 | -pkg_check_modules(GLOG libglog) |
9 | +pkg_check_modules(DBUSCPP REQUIRED dbus-cpp) |
10 | +pkg_check_modules(GLOG REQUIRED libglog) |
11 | |
12 | set( |
13 | MTP_HEADERS |
14 | @@ -59,6 +60,7 @@ |
15 | include/ |
16 | libusbhost/include |
17 | ${Boost_INCLUDE_DIRS} |
18 | + ${DBUSCPP_INCLUDE_DIRS} |
19 | ) |
20 | |
21 | add_library( |
22 | @@ -70,6 +72,7 @@ |
23 | mtpserver |
24 | android-properties |
25 | ${GLOG_LIBRARIES} |
26 | + ${DBUSCPP_LIBRARIES} |
27 | ) |
28 | |
29 | set_target_properties( |
30 | |
31 | === modified file 'debian/changelog' |
32 | --- debian/changelog 2014-04-09 03:00:23 +0000 |
33 | +++ debian/changelog 2014-08-21 19:42:07 +0000 |
34 | @@ -1,3 +1,29 @@ |
35 | +mtp (0.0.4+14.04.20140814-0ubuntu1) UNRELEASED; urgency=medium |
36 | + |
37 | + * New release: |
38 | + - Bugfixes. |
39 | + - Enhancements on error handling. |
40 | + - Add support for working with Windows systems. |
41 | + - Add support for locking: don't allow access to storage while the screen |
42 | + is locked. |
43 | + - Support removable storage by watching for mounted storage in the /media |
44 | + directory tree. |
45 | + |
46 | + -- Mathieu Trudel-Lapierre <mathieu-tl@ubuntu.com> Thu, 14 Aug 2014 11:09:02 -0400 |
47 | + |
48 | +mtp (0.0.3+14.04.20140409-0ubuntu3) utopic; urgency=medium |
49 | + |
50 | + * make sure mtp-server only gets started in user sessions and not in lightdm |
51 | + greeter sessions |
52 | + |
53 | + -- Oliver Grawert <ogra@ubuntu.com> Tue, 03 Jun 2014 13:24:56 +0200 |
54 | + |
55 | +mtp (0.0.3+14.04.20140409-0ubuntu2) utopic; urgency=high |
56 | + |
57 | + * No change rebuild against boost1.55. |
58 | + |
59 | + -- Dimitri John Ledkov <xnox@ubuntu.com> Tue, 29 Apr 2014 04:38:54 +0100 |
60 | + |
61 | mtp (0.0.3+14.04.20140409-0ubuntu1) trusty; urgency=medium |
62 | |
63 | [ Mathieu Trudel-Lapierre ] |
64 | |
65 | === modified file 'debian/control' |
66 | --- debian/control 2014-03-27 15:45:30 +0000 |
67 | +++ debian/control 2014-08-21 19:42:07 +0000 |
68 | @@ -12,6 +12,9 @@ |
69 | libboost-test-dev, |
70 | libandroid-properties-dev, |
71 | libgoogle-glog-dev, |
72 | + libdbus-1-dev, |
73 | + libproperties-cpp-dev, |
74 | + libdbus-cpp-dev, |
75 | Standards-Version: 3.9.4 |
76 | Homepage: http://launchpad.net/mtp |
77 | # If you aren't ~phablet-team but need to upload packaging changes, |
78 | |
79 | === modified file 'debian/mtp-server.conf' |
80 | --- debian/mtp-server.conf 2014-03-27 15:45:30 +0000 |
81 | +++ debian/mtp-server.conf 2014-08-21 19:42:07 +0000 |
82 | @@ -3,6 +3,10 @@ |
83 | start on :sys:android-mtp-on or :sys:android-usb-connected |
84 | stop on desktop-end or :sys:android-mtp-off |
85 | |
86 | +pre-start script |
87 | + [ "$USER" != "lightdm" ] || { stop; exit 0; } |
88 | +end script |
89 | + |
90 | env GOOGLE_LOGTOSTDERR=1 |
91 | |
92 | respawn |
93 | |
94 | === modified file 'include/MtpDatabase.h' |
95 | --- include/MtpDatabase.h 2014-03-27 15:50:32 +0000 |
96 | +++ include/MtpDatabase.h 2014-08-21 19:42:07 +0000 |
97 | @@ -30,6 +30,14 @@ |
98 | public: |
99 | virtual ~MtpDatabase() {} |
100 | |
101 | + // called to add a path to include in the database. |
102 | + virtual void addStoragePath(const MtpString& path, |
103 | + MtpStorageID storage, |
104 | + bool hidden) = 0; |
105 | + |
106 | + // Called to remove database entries for a storage. |
107 | + virtual void removeStorage(MtpStorageID storage) = 0; |
108 | + |
109 | // called from SendObjectInfo to reserve a database entry for the incoming file |
110 | virtual MtpObjectHandle beginSendObject(const MtpString& path, |
111 | MtpObjectFormat format, |
112 | |
113 | === modified file 'server/UbuntuMtpDatabase.h' |
114 | --- server/UbuntuMtpDatabase.h 2014-04-01 15:50:59 +0000 |
115 | +++ server/UbuntuMtpDatabase.h 2014-08-21 19:42:07 +0000 |
116 | @@ -45,9 +45,12 @@ |
117 | #include <boost/filesystem.hpp> |
118 | #include <boost/range/adaptors.hpp> |
119 | #include <boost/range/algorithm.hpp> |
120 | +#include <boost/date_time/posix_time/posix_time.hpp> |
121 | |
122 | #include <glog/logging.h> |
123 | |
124 | +#define ALL_PROPERTIES 0xffffffff |
125 | + |
126 | namespace asio = boost::asio; |
127 | using namespace boost::filesystem; |
128 | |
129 | @@ -64,6 +67,7 @@ |
130 | std::string display_name; |
131 | std::string path; |
132 | int watch_fd; |
133 | + std::time_t last_modified; |
134 | }; |
135 | |
136 | MtpServer* local_server; |
137 | @@ -85,6 +89,7 @@ |
138 | boost::thread io_service_thread; |
139 | |
140 | asio::io_service io_svc; |
141 | + asio::io_service::work work; |
142 | asio::posix::stream_descriptor stream_desc; |
143 | asio::streambuf buf; |
144 | int inotify_fd; |
145 | @@ -113,7 +118,7 @@ |
146 | } |
147 | |
148 | |
149 | - void add_file_entry(path p, MtpObjectHandle parent) |
150 | + void add_file_entry(path p, MtpObjectHandle parent, MtpStorageID storage) |
151 | { |
152 | MtpObjectHandle handle = counter; |
153 | DbEntry entry; |
154 | @@ -121,28 +126,30 @@ |
155 | counter++; |
156 | |
157 | if (is_directory(p)) { |
158 | - entry.storage_id = MTP_STORAGE_FIXED_RAM; |
159 | + entry.storage_id = storage; |
160 | entry.parent = parent; |
161 | entry.display_name = std::string(p.filename().string()); |
162 | entry.path = p.string(); |
163 | entry.object_format = MTP_FORMAT_ASSOCIATION; |
164 | entry.object_size = 0; |
165 | entry.watch_fd = setup_dir_inotify(p); |
166 | + entry.last_modified = last_write_time(p); |
167 | |
168 | db.insert( std::pair<MtpObjectHandle, DbEntry>(handle, entry) ); |
169 | |
170 | if (local_server) |
171 | local_server->sendObjectAdded(handle); |
172 | |
173 | - parse_directory (p, handle); |
174 | + parse_directory (p, handle, storage); |
175 | } else { |
176 | try { |
177 | - entry.storage_id = MTP_STORAGE_FIXED_RAM; |
178 | + entry.storage_id = storage; |
179 | entry.parent = parent; |
180 | entry.display_name = std::string(p.filename().string()); |
181 | entry.path = p.string(); |
182 | entry.object_format = guess_object_format(p.extension().string()); |
183 | entry.object_size = file_size(p); |
184 | + entry.last_modified = last_write_time(p); |
185 | |
186 | VLOG(1) << "Adding \"" << p.string() << "\""; |
187 | |
188 | @@ -157,20 +164,28 @@ |
189 | } |
190 | } |
191 | |
192 | - void parse_directory(path p, MtpObjectHandle parent) |
193 | + void parse_directory(path p, MtpObjectHandle parent, MtpStorageID storage) |
194 | { |
195 | DbEntry entry; |
196 | std::vector<path> v; |
197 | - |
198 | - copy(directory_iterator(p), directory_iterator(), std::back_inserter(v)); |
199 | + boost::system::error_code ec; |
200 | + directory_iterator i (p, ec); |
201 | + |
202 | + if (ec == boost::system::errc::permission_denied) { |
203 | + VLOG(2) << "Could not immediately read dir; retrying."; |
204 | + boost::this_thread::sleep(boost::posix_time::millisec(500)); |
205 | + i = directory_iterator(p); |
206 | + } |
207 | + |
208 | + copy(i, directory_iterator(), std::back_inserter(v)); |
209 | |
210 | for (std::vector<path>::const_iterator it(v.begin()), it_end(v.end()); it != it_end; ++it) |
211 | { |
212 | - add_file_entry(*it, parent); |
213 | + add_file_entry(*it, parent, storage); |
214 | } |
215 | } |
216 | |
217 | - void readFiles(const std::string& sourcedir) |
218 | + void readFiles(const std::string& sourcedir, MtpStorageID storage, bool hidden) |
219 | { |
220 | path p (sourcedir); |
221 | DbEntry entry; |
222 | @@ -179,20 +194,34 @@ |
223 | try { |
224 | if (exists(p)) { |
225 | if (is_directory(p)) { |
226 | - entry.storage_id = MTP_STORAGE_FIXED_RAM; |
227 | - entry.parent = MTP_PARENT_ROOT; |
228 | + entry.storage_id = storage; |
229 | + entry.parent = hidden ? MTP_PARENT_ROOT : 0; |
230 | entry.display_name = std::string(p.filename().string()); |
231 | entry.path = p.string(); |
232 | entry.object_format = MTP_FORMAT_ASSOCIATION; |
233 | entry.object_size = 0; |
234 | entry.watch_fd = setup_dir_inotify(p); |
235 | + entry.last_modified = last_write_time(p); |
236 | |
237 | db.insert( std::pair<MtpObjectHandle, DbEntry>(handle, entry) ); |
238 | |
239 | - parse_directory (p, handle); |
240 | + parse_directory (p, hidden ? 0 : handle, storage); |
241 | + } else |
242 | + LOG(WARNING) << p << " is not a directory."; |
243 | + } else { |
244 | + if (storage == MTP_STORAGE_FIXED_RAM) |
245 | + LOG(WARNING) << p << " does not exist."; |
246 | + else { |
247 | + entry.storage_id = storage; |
248 | + entry.parent = -1; |
249 | + entry.display_name = std::string(p.parent_path().filename().string()); |
250 | + entry.path = p.parent_path().string(); |
251 | + entry.object_format = MTP_FORMAT_ASSOCIATION; |
252 | + entry.object_size = 0; |
253 | + entry.watch_fd = setup_dir_inotify(p.parent_path()); |
254 | + entry.last_modified = 0; |
255 | } |
256 | - } else |
257 | - LOG(WARNING) << p << " does not exist\n"; |
258 | + } |
259 | } |
260 | catch (const filesystem_error& ex) { |
261 | LOG(ERROR) << ex.what(); |
262 | @@ -247,10 +276,22 @@ |
263 | } |
264 | else if(ievent->len > 0 && ievent->mask & IN_CREATE) |
265 | { |
266 | + int parent_handle = parent; |
267 | + |
268 | VLOG(2) << __PRETTY_FUNCTION__ << ": file created: " << p.string(); |
269 | |
270 | + /* Deal with the special case where the SD card might initially |
271 | + * require an inotify watch, because it's not yet mounted. |
272 | + * In this case, the SD card inotify watch is entered as a |
273 | + * normal object, but the parent for the "real" directory |
274 | + * for the mounted removable media should be the MTP root |
275 | + * for the storage ID. |
276 | + */ |
277 | + if (db.at(parent).parent == MTP_PARENT_ROOT) |
278 | + parent_handle = 0; |
279 | + |
280 | /* try to deal with it as if it was a file. */ |
281 | - add_file_entry(p, parent); |
282 | + add_file_entry(p, parent_handle, db.at(parent).storage_id); |
283 | } |
284 | else if(ievent->len > 0 && ievent->mask & IN_DELETE) |
285 | { |
286 | @@ -271,18 +312,18 @@ |
287 | } |
288 | |
289 | public: |
290 | - UbuntuMtpDatabase(const char *dir): |
291 | + UbuntuMtpDatabase(): |
292 | counter(1), |
293 | stream_desc(io_svc), |
294 | + work(io_svc), |
295 | buf(1024) |
296 | { |
297 | - std::string basedir (dir); |
298 | - |
299 | local_server = nullptr; |
300 | |
301 | inotify_fd = inotify_init(); |
302 | if (inotify_fd <= 0) |
303 | - PLOG(FATAL) << "Unable to initialize inotify"; |
304 | + PLOG(FATAL) << "Invalid file descriptor to inotify"; |
305 | + VLOG(1) << "using inotify fd " << inotify_fd << " for database"; |
306 | |
307 | stream_desc.assign(inotify_fd); |
308 | |
309 | @@ -291,14 +332,6 @@ |
310 | notifier_thread = boost::thread(&UbuntuMtpDatabase::read_more_notify, |
311 | this); |
312 | |
313 | - readFiles(basedir + "/Documents"); |
314 | - readFiles(basedir + "/Music"); |
315 | - readFiles(basedir + "/Videos"); |
316 | - readFiles(basedir + "/Pictures"); |
317 | - readFiles(basedir + "/Downloads"); |
318 | - |
319 | - LOG(INFO) << "Added " << counter << " entries to the database."; |
320 | - |
321 | io_service_thread = boost::thread(boost::bind(&asio::io_service::run, &io_svc)); |
322 | } |
323 | |
324 | @@ -309,6 +342,22 @@ |
325 | close(inotify_fd); |
326 | } |
327 | |
328 | + virtual void addStoragePath(const MtpString& path, |
329 | + MtpStorageID storage, |
330 | + bool hidden) |
331 | + { |
332 | + readFiles(path, storage, hidden); |
333 | + } |
334 | + |
335 | + virtual void removeStorage(MtpStorageID storage) |
336 | + { |
337 | + // remove all database entries corresponding to said storage. |
338 | + BOOST_FOREACH(MtpObjectHandle i, db | boost::adaptors::map_keys) { |
339 | + if (db.at(i).storage_id == storage) |
340 | + db.erase(i); |
341 | + } |
342 | + } |
343 | + |
344 | // called from SendObjectInfo to reserve a database entry for the incoming file |
345 | virtual MtpObjectHandle beginSendObject( |
346 | const MtpString& path, |
347 | @@ -324,7 +373,8 @@ |
348 | if (parent == 0) |
349 | return kInvalidObjectHandle; |
350 | |
351 | - VLOG(1) << __PRETTY_FUNCTION__ << ": " << path << " - " << parent; |
352 | + VLOG(1) << __PRETTY_FUNCTION__ << ": " << path << " - " << parent |
353 | + << " format: " << std::hex << format << std::dec; |
354 | |
355 | entry.storage_id = storage; |
356 | entry.parent = parent; |
357 | @@ -332,6 +382,7 @@ |
358 | entry.path = path; |
359 | entry.object_format = format; |
360 | entry.object_size = size; |
361 | + entry.last_modified = modified; |
362 | |
363 | db.insert( std::pair<MtpObjectHandle, DbEntry>(handle, entry) ); |
364 | |
365 | @@ -351,15 +402,22 @@ |
366 | { |
367 | VLOG(1) << __PRETTY_FUNCTION__ << ": " << path; |
368 | |
369 | - if (!succeeded) { |
370 | - db.erase(handle); |
371 | - } else { |
372 | - boost::filesystem::path p (path); |
373 | + try |
374 | + { |
375 | + if (!succeeded) { |
376 | + db.erase(handle); |
377 | + } else { |
378 | + boost::filesystem::path p (path); |
379 | |
380 | - if (format != MTP_FORMAT_ASSOCIATION) { |
381 | - /* Resync file size, just in case this is actually an Edit. */ |
382 | - db.at(handle).object_size = file_size(p); |
383 | + if (format != MTP_FORMAT_ASSOCIATION) { |
384 | + /* Resync file size, just in case this is actually an Edit. */ |
385 | + db.at(handle).object_size = file_size(p); |
386 | + } |
387 | } |
388 | + } catch(...) |
389 | + { |
390 | + LOG(ERROR) << __PRETTY_FUNCTION__ |
391 | + << ": failed to complete object creation:" << path; |
392 | } |
393 | } |
394 | |
395 | @@ -370,13 +428,18 @@ |
396 | { |
397 | VLOG(1) << __PRETTY_FUNCTION__ << ": " << storageID << ", " << format << ", " << parent; |
398 | MtpObjectHandleList* list = nullptr; |
399 | + |
400 | + if (parent == MTP_PARENT_ROOT) |
401 | + parent = 0; |
402 | + |
403 | try |
404 | { |
405 | std::vector<MtpObjectHandle> keys; |
406 | |
407 | BOOST_FOREACH(MtpObjectHandle i, db | boost::adaptors::map_keys) { |
408 | - if (db.at(i).parent == parent) |
409 | - keys.push_back(i); |
410 | + if (db.at(i).storage_id == storageID && db.at(i).parent == parent) |
411 | + if (format == 0 || db.at(i).object_format == format) |
412 | + keys.push_back(i); |
413 | } |
414 | |
415 | list = new MtpObjectHandleList(keys); |
416 | @@ -394,14 +457,19 @@ |
417 | MtpObjectHandle parent) |
418 | { |
419 | VLOG(1) << __PRETTY_FUNCTION__ << ": " << storageID << ", " << format << ", " << parent; |
420 | + |
421 | + int result = 0; |
422 | + |
423 | try |
424 | { |
425 | - return db.size(); |
426 | + MtpObjectHandleList *list = getObjectList(storageID, format, parent); |
427 | + result = list->size(); |
428 | + delete list; |
429 | } catch(...) |
430 | { |
431 | } |
432 | |
433 | - return 0; |
434 | + return result; |
435 | } |
436 | |
437 | // callee should delete[] the results from these |
438 | @@ -412,6 +480,22 @@ |
439 | static const MtpObjectFormatList list = { |
440 | /* Generic files */ |
441 | MTP_FORMAT_UNDEFINED, |
442 | + MTP_FORMAT_ASSOCIATION, // folders |
443 | + MTP_FORMAT_TEXT, |
444 | + MTP_FORMAT_HTML, |
445 | + |
446 | + /* Supported image formats */ |
447 | + MTP_FORMAT_DEFINED, // generic image |
448 | + MTP_FORMAT_EXIF_JPEG, |
449 | + MTP_FORMAT_TIFF_EP, |
450 | + MTP_FORMAT_BMP, |
451 | + MTP_FORMAT_GIF, |
452 | + MTP_FORMAT_JFIF, |
453 | + MTP_FORMAT_PNG, |
454 | + MTP_FORMAT_TIFF, |
455 | + MTP_FORMAT_TIFF_IT, |
456 | + MTP_FORMAT_JP2, |
457 | + MTP_FORMAT_JPX, |
458 | |
459 | /* Supported audio formats */ |
460 | MTP_FORMAT_OGG, |
461 | @@ -455,11 +539,17 @@ |
462 | MTP_PROPERTY_PARENT_OBJECT, |
463 | MTP_PROPERTY_OBJECT_FORMAT, |
464 | MTP_PROPERTY_OBJECT_SIZE, |
465 | - MTP_PROPERTY_WIDTH, |
466 | - MTP_PROPERTY_HEIGHT, |
467 | - MTP_PROPERTY_IMAGE_BIT_DEPTH, |
468 | MTP_PROPERTY_OBJECT_FILE_NAME, |
469 | - MTP_PROPERTY_DISPLAY_NAME |
470 | + MTP_PROPERTY_DISPLAY_NAME, |
471 | + MTP_PROPERTY_PERSISTENT_UID, |
472 | + MTP_PROPERTY_ASSOCIATION_TYPE, |
473 | + MTP_PROPERTY_ASSOCIATION_DESC, |
474 | + MTP_PROPERTY_PROTECTION_STATUS, |
475 | + MTP_PROPERTY_DATE_CREATED, |
476 | + MTP_PROPERTY_DATE_MODIFIED, |
477 | + MTP_PROPERTY_HIDDEN, |
478 | + MTP_PROPERTY_NON_CONSUMABLE, |
479 | + |
480 | }; |
481 | |
482 | return new MtpObjectPropertyList{list}; |
483 | @@ -468,7 +558,10 @@ |
484 | virtual MtpDevicePropertyList* getSupportedDeviceProperties() |
485 | { |
486 | VLOG(1) << __PRETTY_FUNCTION__; |
487 | - static const MtpDevicePropertyList list = { MTP_DEVICE_PROPERTY_UNDEFINED }; |
488 | + static const MtpDevicePropertyList list = { |
489 | + MTP_DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME, |
490 | + MTP_DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER, |
491 | + }; |
492 | return new MtpDevicePropertyList{list}; |
493 | } |
494 | |
495 | @@ -477,22 +570,62 @@ |
496 | MtpObjectProperty property, |
497 | MtpDataPacket& packet) |
498 | { |
499 | + char date[20]; |
500 | + |
501 | VLOG(1) << __PRETTY_FUNCTION__ |
502 | << " handle: " << handle |
503 | << " property: " << MtpDebug::getObjectPropCodeName(property); |
504 | |
505 | - switch(property) |
506 | - { |
507 | - case MTP_PROPERTY_STORAGE_ID: packet.putUInt32(db.at(handle).storage_id); break; |
508 | - case MTP_PROPERTY_PARENT_OBJECT: packet.putUInt32(db.at(handle).parent); break; |
509 | - case MTP_PROPERTY_OBJECT_FORMAT: packet.putUInt32(db.at(handle).object_format); break; |
510 | - case MTP_PROPERTY_OBJECT_SIZE: packet.putUInt32(db.at(handle).object_size); break; |
511 | - case MTP_PROPERTY_DISPLAY_NAME: packet.putString(db.at(handle).display_name.c_str()); break; |
512 | - case MTP_PROPERTY_OBJECT_FILE_NAME: packet.putString(db.at(handle).display_name.c_str()); break; |
513 | - default: return MTP_RESPONSE_GENERAL_ERROR; break; |
514 | - } |
515 | + if (handle == MTP_PARENT_ROOT || handle == 0) |
516 | + return MTP_RESPONSE_INVALID_OBJECT_HANDLE; |
517 | + |
518 | + try { |
519 | + switch(property) |
520 | + { |
521 | + case MTP_PROPERTY_STORAGE_ID: packet.putUInt32(db.at(handle).storage_id); break; |
522 | + case MTP_PROPERTY_PARENT_OBJECT: packet.putUInt32(db.at(handle).parent); break; |
523 | + case MTP_PROPERTY_OBJECT_FORMAT: packet.putUInt16(db.at(handle).object_format); break; |
524 | + case MTP_PROPERTY_OBJECT_SIZE: packet.putUInt32(db.at(handle).object_size); break; |
525 | + case MTP_PROPERTY_DISPLAY_NAME: packet.putString(db.at(handle).display_name.c_str()); break; |
526 | + case MTP_PROPERTY_OBJECT_FILE_NAME: packet.putString(db.at(handle).display_name.c_str()); break; |
527 | + case MTP_PROPERTY_PERSISTENT_UID: packet.putUInt128(handle); break; |
528 | + case MTP_PROPERTY_ASSOCIATION_TYPE: |
529 | + if (db.at(handle).object_format == MTP_FORMAT_ASSOCIATION) |
530 | + packet.putUInt16(MTP_ASSOCIATION_TYPE_GENERIC_FOLDER); |
531 | + else |
532 | + packet.putUInt16(0); |
533 | + break; |
534 | + case MTP_PROPERTY_ASSOCIATION_DESC: packet.putUInt32(0); break; |
535 | + case MTP_PROPERTY_PROTECTION_STATUS: |
536 | + packet.putUInt16(0x0000); // no files are read-only for now. |
537 | + break; |
538 | + case MTP_PROPERTY_DATE_CREATED: |
539 | + formatDateTime(0, date, sizeof(date)); |
540 | + packet.putString(date); |
541 | + break; |
542 | + case MTP_PROPERTY_DATE_MODIFIED: |
543 | + formatDateTime(db.at(handle).last_modified, date, sizeof(date)); |
544 | + packet.putString(date); |
545 | + break; |
546 | + case MTP_PROPERTY_HIDDEN: packet.putUInt16(0); break; |
547 | + case MTP_PROPERTY_NON_CONSUMABLE: break; |
548 | + if (db.at(handle).object_format == MTP_FORMAT_ASSOCIATION) |
549 | + packet.putUInt16(0); // folders are non-consumable |
550 | + else |
551 | + packet.putUInt16(1); // files can usually be played. |
552 | + break; |
553 | + default: return MTP_RESPONSE_GENERAL_ERROR; break; |
554 | + } |
555 | |
556 | - return MTP_RESPONSE_OK; |
557 | + return MTP_RESPONSE_OK; |
558 | + } |
559 | + catch (...) { |
560 | + LOG(ERROR) << __PRETTY_FUNCTION__ |
561 | + << "Could not retrieve property: " |
562 | + << MtpDebug::getObjectPropCodeName(property) |
563 | + << " for handle: " << handle; |
564 | + return MTP_RESPONSE_GENERAL_ERROR; |
565 | + } |
566 | } |
567 | |
568 | virtual MtpResponseCode setObjectPropertyValue( |
569 | @@ -511,6 +644,9 @@ |
570 | << " handle: " << handle |
571 | << " property: " << MtpDebug::getObjectPropCodeName(property); |
572 | |
573 | + if (handle == MTP_PARENT_ROOT || handle == 0) |
574 | + return MTP_RESPONSE_INVALID_OBJECT_HANDLE; |
575 | + |
576 | switch(property) |
577 | { |
578 | case MTP_PROPERTY_OBJECT_FILE_NAME: |
579 | @@ -539,6 +675,16 @@ |
580 | } |
581 | |
582 | break; |
583 | + case MTP_PROPERTY_PARENT_OBJECT: |
584 | + try { |
585 | + entry = db.at(handle); |
586 | + entry.parent = packet.getUInt32(); |
587 | + } |
588 | + catch (...) { |
589 | + LOG(ERROR) << "Could not change parent object for handle " |
590 | + << handle; |
591 | + return MTP_RESPONSE_GENERAL_ERROR; |
592 | + } |
593 | default: return MTP_RESPONSE_OPERATION_NOT_SUPPORTED; break; |
594 | } |
595 | |
596 | @@ -550,7 +696,16 @@ |
597 | MtpDataPacket& packet) |
598 | { |
599 | VLOG(1) << __PRETTY_FUNCTION__; |
600 | - return MTP_RESPONSE_GENERAL_ERROR; |
601 | + switch(property) |
602 | + { |
603 | + case MTP_DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER: |
604 | + case MTP_DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME: |
605 | + packet.putString(""); |
606 | + break; |
607 | + default: return MTP_RESPONSE_OPERATION_NOT_SUPPORTED; break; |
608 | + } |
609 | + |
610 | + return MTP_RESPONSE_OK; |
611 | } |
612 | |
613 | virtual MtpResponseCode setDevicePropertyValue( |
614 | @@ -558,14 +713,14 @@ |
615 | MtpDataPacket& packet) |
616 | { |
617 | VLOG(1) << __PRETTY_FUNCTION__; |
618 | - return MTP_RESPONSE_OPERATION_NOT_SUPPORTED; |
619 | + return MTP_RESPONSE_DEVICE_PROP_NOT_SUPPORTED; |
620 | } |
621 | |
622 | virtual MtpResponseCode resetDeviceProperty( |
623 | MtpDeviceProperty property) |
624 | { |
625 | VLOG(1) << __PRETTY_FUNCTION__; |
626 | - return MTP_RESPONSE_OPERATION_NOT_SUPPORTED; |
627 | + return MTP_RESPONSE_DEVICE_PROP_NOT_SUPPORTED; |
628 | } |
629 | |
630 | virtual MtpResponseCode getObjectPropertyList( |
631 | @@ -576,8 +731,184 @@ |
632 | int depth, |
633 | MtpDataPacket& packet) |
634 | { |
635 | + std::vector<MtpObjectHandle> handles; |
636 | + |
637 | VLOG(2) << __PRETTY_FUNCTION__; |
638 | - return MTP_RESPONSE_OPERATION_NOT_SUPPORTED; |
639 | + |
640 | + if (handle == kInvalidObjectHandle) |
641 | + return MTP_RESPONSE_PARAMETER_NOT_SUPPORTED; |
642 | + |
643 | + if (property == 0 && groupCode == 0) |
644 | + return MTP_RESPONSE_PARAMETER_NOT_SUPPORTED; |
645 | + |
646 | + if (groupCode != 0) |
647 | + return MTP_RESPONSE_SPECIFICATION_BY_GROUP_UNSUPPORTED; |
648 | + |
649 | + if (depth > 1) |
650 | + return MTP_RESPONSE_SPECIFICATION_BY_DEPTH_UNSUPPORTED; |
651 | + |
652 | + if (depth == 0) { |
653 | + /* For a depth search, a handle of 0 is valid (objects at the root) |
654 | + * but it isn't when querying for the properties of a single object. |
655 | + */ |
656 | + if (db.find(handle) == db.end()) |
657 | + return MTP_RESPONSE_INVALID_OBJECT_HANDLE; |
658 | + |
659 | + handles.push_back(handle); |
660 | + } else { |
661 | + BOOST_FOREACH(MtpObjectHandle i, db | boost::adaptors::map_keys) { |
662 | + if (db.at(i).parent == handle) |
663 | + handles.push_back(i); |
664 | + } |
665 | + } |
666 | + |
667 | + /* |
668 | + * getObjectPropList returns an ObjectPropList dataset table; |
669 | + * built as such: |
670 | + * |
671 | + * 1- Number of elements (quadruples) |
672 | + * a1- Element 1 Object Handle |
673 | + * a2- Element 1 Property Code |
674 | + * a3- Element 1 Data type |
675 | + * a4- Element 1 Value |
676 | + * b... rinse, repeat. |
677 | + */ |
678 | + |
679 | + if (property == ALL_PROPERTIES) |
680 | + packet.putUInt32(6 * handles.size()); |
681 | + else |
682 | + packet.putUInt32(1 * handles.size()); |
683 | + |
684 | + BOOST_FOREACH(MtpObjectHandle i, handles) { |
685 | + DbEntry entry = db.at(i); |
686 | + |
687 | + // Persistent Unique Identifier. |
688 | + if (property == ALL_PROPERTIES || property == MTP_PROPERTY_PERSISTENT_UID) { |
689 | + packet.putUInt32(i); |
690 | + packet.putUInt16(MTP_PROPERTY_PERSISTENT_UID); |
691 | + packet.putUInt16(MTP_TYPE_UINT128); |
692 | + packet.putUInt128(i); |
693 | + } |
694 | + |
695 | + // Storage ID |
696 | + if (property == ALL_PROPERTIES || property == MTP_PROPERTY_STORAGE_ID) { |
697 | + packet.putUInt32(i); |
698 | + packet.putUInt16(MTP_PROPERTY_STORAGE_ID); |
699 | + packet.putUInt16(MTP_TYPE_UINT32); |
700 | + packet.putUInt32(entry.storage_id); |
701 | + } |
702 | + |
703 | + // Parent |
704 | + if (property == ALL_PROPERTIES || property == MTP_PROPERTY_PARENT_OBJECT) { |
705 | + packet.putUInt32(i); |
706 | + packet.putUInt16(MTP_PROPERTY_PARENT_OBJECT); |
707 | + packet.putUInt16(MTP_TYPE_UINT32); |
708 | + packet.putUInt32(entry.parent); |
709 | + } |
710 | + |
711 | + // Object Format |
712 | + if (property == ALL_PROPERTIES || property == MTP_PROPERTY_OBJECT_FORMAT) { |
713 | + packet.putUInt32(i); |
714 | + packet.putUInt16(MTP_PROPERTY_OBJECT_FORMAT); |
715 | + packet.putUInt16(MTP_TYPE_UINT16); |
716 | + packet.putUInt16(entry.object_format); |
717 | + } |
718 | + |
719 | + // Object Size |
720 | + if (property == ALL_PROPERTIES || property == MTP_PROPERTY_OBJECT_SIZE) { |
721 | + packet.putUInt32(i); |
722 | + packet.putUInt16(MTP_PROPERTY_OBJECT_SIZE); |
723 | + packet.putUInt16(MTP_TYPE_UINT32); |
724 | + packet.putUInt32(entry.object_size); |
725 | + } |
726 | + |
727 | + // Object File Name |
728 | + if (property == ALL_PROPERTIES || property == MTP_PROPERTY_OBJECT_FILE_NAME) { |
729 | + packet.putUInt32(i); |
730 | + packet.putUInt16(MTP_PROPERTY_OBJECT_FILE_NAME); |
731 | + packet.putUInt16(MTP_TYPE_STR); |
732 | + packet.putString(entry.display_name.c_str()); |
733 | + } |
734 | + |
735 | + // Display Name |
736 | + if (property == ALL_PROPERTIES || property == MTP_PROPERTY_DISPLAY_NAME) { |
737 | + packet.putUInt32(i); |
738 | + packet.putUInt16(MTP_PROPERTY_DISPLAY_NAME); |
739 | + packet.putUInt16(MTP_TYPE_STR); |
740 | + packet.putString(entry.display_name.c_str()); |
741 | + } |
742 | + |
743 | + // Association Type |
744 | + if (property == ALL_PROPERTIES || property == MTP_PROPERTY_ASSOCIATION_TYPE) { |
745 | + packet.putUInt32(i); |
746 | + packet.putUInt16(MTP_PROPERTY_ASSOCIATION_TYPE); |
747 | + packet.putUInt16(MTP_TYPE_UINT16); |
748 | + if (entry.object_format == MTP_FORMAT_ASSOCIATION) |
749 | + packet.putUInt16(MTP_ASSOCIATION_TYPE_GENERIC_FOLDER); |
750 | + else |
751 | + packet.putUInt16(0); |
752 | + } |
753 | + |
754 | + // Association Description |
755 | + if (property == ALL_PROPERTIES || property == MTP_PROPERTY_ASSOCIATION_DESC) { |
756 | + packet.putUInt32(i); |
757 | + packet.putUInt16(MTP_PROPERTY_ASSOCIATION_DESC); |
758 | + packet.putUInt16(MTP_TYPE_UINT32); |
759 | + packet.putUInt32(0); |
760 | + } |
761 | + |
762 | + // Protection Status |
763 | + if (property == ALL_PROPERTIES || property == MTP_PROPERTY_PROTECTION_STATUS) { |
764 | + packet.putUInt32(i); |
765 | + packet.putUInt16(MTP_PROPERTY_PROTECTION_STATUS); |
766 | + packet.putUInt16(MTP_TYPE_UINT16); |
767 | + packet.putUInt16(0x0000); //FIXME: all files are read-write for now |
768 | + // packet.putUInt16(0x8001); |
769 | + } |
770 | + |
771 | + // Date Created |
772 | + if (property == ALL_PROPERTIES || property == MTP_PROPERTY_DATE_CREATED) { |
773 | + char date[20]; |
774 | + formatDateTime(0, date, sizeof(date)); |
775 | + packet.putUInt32(i); |
776 | + packet.putUInt16(MTP_PROPERTY_DATE_CREATED); |
777 | + packet.putUInt16(MTP_TYPE_STR); |
778 | + packet.putString(date); |
779 | + } |
780 | + |
781 | + // Date Modified |
782 | + if (property == ALL_PROPERTIES || property == MTP_PROPERTY_DATE_MODIFIED) { |
783 | + char date[20]; |
784 | + formatDateTime(entry.last_modified, date, sizeof(date)); |
785 | + packet.putUInt32(i); |
786 | + packet.putUInt16(MTP_PROPERTY_DATE_CREATED); |
787 | + packet.putUInt16(MTP_TYPE_STR); |
788 | + packet.putString(date); |
789 | + } |
790 | + |
791 | + // Hidden |
792 | + if (property == ALL_PROPERTIES || property == MTP_PROPERTY_HIDDEN) { |
793 | + packet.putUInt32(i); |
794 | + packet.putUInt16(MTP_PROPERTY_HIDDEN); |
795 | + packet.putUInt16(MTP_TYPE_UINT16); |
796 | + packet.putUInt16(0); |
797 | + } |
798 | + |
799 | + // Non Consumable |
800 | + if (property == ALL_PROPERTIES || property == MTP_PROPERTY_NON_CONSUMABLE) { |
801 | + packet.putUInt32(i); |
802 | + packet.putUInt16(MTP_PROPERTY_NON_CONSUMABLE); |
803 | + packet.putUInt16(MTP_TYPE_UINT16); |
804 | + if (entry.object_format == MTP_FORMAT_ASSOCIATION) |
805 | + packet.putUInt16(0); // folders are non-consumable |
806 | + else |
807 | + packet.putUInt16(1); // files can usually be played. |
808 | + break; |
809 | + } |
810 | + |
811 | + } |
812 | + |
813 | + return MTP_RESPONSE_OK; |
814 | } |
815 | |
816 | virtual MtpResponseCode getObjectInfo( |
817 | @@ -586,27 +917,37 @@ |
818 | { |
819 | VLOG(2) << __PRETTY_FUNCTION__; |
820 | |
821 | - info.mHandle = handle; |
822 | - info.mStorageID = db.at(handle).storage_id; |
823 | - info.mFormat = db.at(handle).object_format; |
824 | - info.mProtectionStatus = 0x0; |
825 | - info.mCompressedSize = db.at(handle).object_size; |
826 | - info.mImagePixWidth = 0; |
827 | - info.mImagePixHeight = 0; |
828 | - info.mImagePixDepth = 0; |
829 | - info.mParent = db.at(handle).parent; |
830 | - info.mAssociationType = 0; |
831 | - info.mAssociationDesc = 0; |
832 | - info.mSequenceNumber = 0; |
833 | - info.mName = ::strdup(db.at(handle).display_name.c_str()); |
834 | - info.mDateCreated = 0; |
835 | - info.mDateModified = 0; |
836 | - info.mKeywords = ::strdup("ubuntu,touch"); |
837 | - |
838 | - if (VLOG_IS_ON(2)) |
839 | - info.print(); |
840 | - |
841 | - return MTP_RESPONSE_OK; |
842 | + if (handle == 0 || handle == MTP_PARENT_ROOT) |
843 | + return MTP_RESPONSE_INVALID_OBJECT_HANDLE; |
844 | + |
845 | + try { |
846 | + info.mHandle = handle; |
847 | + info.mStorageID = db.at(handle).storage_id; |
848 | + info.mFormat = db.at(handle).object_format; |
849 | + info.mProtectionStatus = 0x0; |
850 | + info.mCompressedSize = db.at(handle).object_size; |
851 | + info.mImagePixWidth = 0; |
852 | + info.mImagePixHeight = 0; |
853 | + info.mImagePixDepth = 0; |
854 | + info.mParent = db.at(handle).parent; |
855 | + info.mAssociationType |
856 | + = info.mFormat == MTP_FORMAT_ASSOCIATION |
857 | + ? MTP_ASSOCIATION_TYPE_GENERIC_FOLDER : 0; |
858 | + info.mAssociationDesc = 0; |
859 | + info.mSequenceNumber = 0; |
860 | + info.mName = ::strdup(db.at(handle).display_name.c_str()); |
861 | + info.mDateCreated = 0; |
862 | + info.mDateModified = db.at(handle).last_modified; |
863 | + info.mKeywords = ::strdup("ubuntu,touch"); |
864 | + |
865 | + if (VLOG_IS_ON(2)) |
866 | + info.print(); |
867 | + |
868 | + return MTP_RESPONSE_OK; |
869 | + } |
870 | + catch (...) { |
871 | + return MTP_RESPONSE_GENERAL_ERROR; |
872 | + } |
873 | } |
874 | |
875 | virtual void* getThumbnail(MtpObjectHandle handle, size_t& outThumbSize) |
876 | @@ -625,15 +966,29 @@ |
877 | int64_t& outFileLength, |
878 | MtpObjectFormat& outFormat) |
879 | { |
880 | - DbEntry entry = db.at(handle); |
881 | - |
882 | VLOG(1) << __PRETTY_FUNCTION__ << " handle: " << handle; |
883 | |
884 | - outFilePath = std::string(entry.path); |
885 | - outFileLength = entry.object_size; |
886 | - outFormat = entry.object_format; |
887 | - |
888 | - return MTP_RESPONSE_OK; |
889 | + if (handle == 0 || handle == MTP_PARENT_ROOT) |
890 | + return MTP_RESPONSE_INVALID_OBJECT_HANDLE; |
891 | + |
892 | + try { |
893 | + DbEntry entry = db.at(handle); |
894 | + |
895 | + VLOG(2) << __PRETTY_FUNCTION__ |
896 | + << "handle: " << handle |
897 | + << "path: " << entry.path |
898 | + << "length: " << entry.object_size |
899 | + << "format: " << entry.object_format; |
900 | + |
901 | + outFilePath = std::string(entry.path); |
902 | + outFileLength = entry.object_size; |
903 | + outFormat = entry.object_format; |
904 | + |
905 | + return MTP_RESPONSE_OK; |
906 | + } |
907 | + catch (...) { |
908 | + return MTP_RESPONSE_GENERAL_ERROR; |
909 | + } |
910 | } |
911 | |
912 | virtual MtpResponseCode deleteFile(MtpObjectHandle handle) |
913 | @@ -643,25 +998,33 @@ |
914 | |
915 | VLOG(2) << __PRETTY_FUNCTION__ << " handle: " << handle; |
916 | |
917 | - if (db.at(handle).object_format == MTP_FORMAT_ASSOCIATION) |
918 | - inotify_rm_watch(inotify_fd, db.at(handle).watch_fd); |
919 | - |
920 | - new_size = db.erase(handle); |
921 | - |
922 | - if (orig_size > new_size) { |
923 | - /* Recursively remove children object from the DB as well. |
924 | - * we can safely ignore failures here, since the objects |
925 | - * would not be reachable anyway. |
926 | - */ |
927 | - BOOST_FOREACH(MtpObjectHandle i, db | boost::adaptors::map_keys) { |
928 | - if (db.at(i).parent == handle) |
929 | - db.erase(i); |
930 | + if (handle == 0 || handle == MTP_PARENT_ROOT) |
931 | + return MTP_RESPONSE_INVALID_OBJECT_HANDLE; |
932 | + |
933 | + try { |
934 | + if (db.at(handle).object_format == MTP_FORMAT_ASSOCIATION) |
935 | + inotify_rm_watch(inotify_fd, db.at(handle).watch_fd); |
936 | + |
937 | + new_size = db.erase(handle); |
938 | + |
939 | + if (orig_size > new_size) { |
940 | + /* Recursively remove children object from the DB as well. |
941 | + * we can safely ignore failures here, since the objects |
942 | + * would not be reachable anyway. |
943 | + */ |
944 | + BOOST_FOREACH(MtpObjectHandle i, db | boost::adaptors::map_keys) { |
945 | + if (db.at(i).parent == handle) |
946 | + db.erase(i); |
947 | + } |
948 | + |
949 | + return MTP_RESPONSE_OK; |
950 | } |
951 | - |
952 | - return MTP_RESPONSE_OK; |
953 | + else |
954 | + return MTP_RESPONSE_GENERAL_ERROR; |
955 | } |
956 | - else |
957 | + catch (...) { |
958 | return MTP_RESPONSE_GENERAL_ERROR; |
959 | + } |
960 | } |
961 | |
962 | virtual MtpResponseCode moveFile(MtpObjectHandle handle, MtpObjectHandle new_parent) |
963 | @@ -669,8 +1032,16 @@ |
964 | VLOG(1) << __PRETTY_FUNCTION__ << " handle: " << handle |
965 | << " new parent: " << new_parent; |
966 | |
967 | - // change parent |
968 | - db.at(handle).parent = new_parent; |
969 | + if (handle == 0 || handle == MTP_PARENT_ROOT) |
970 | + return MTP_RESPONSE_INVALID_OBJECT_HANDLE; |
971 | + |
972 | + try { |
973 | + // change parent |
974 | + db.at(handle).parent = new_parent; |
975 | + } |
976 | + catch (...) { |
977 | + return MTP_RESPONSE_INVALID_OBJECT_HANDLE; |
978 | + } |
979 | |
980 | return MTP_RESPONSE_OK; |
981 | } |
982 | @@ -690,7 +1061,13 @@ |
983 | virtual MtpObjectHandleList* getObjectReferences(MtpObjectHandle handle) |
984 | { |
985 | VLOG(1) << __PRETTY_FUNCTION__; |
986 | - return nullptr; |
987 | + |
988 | + if (handle == 0 || handle == MTP_PARENT_ROOT) |
989 | + return nullptr; |
990 | + |
991 | + return getObjectList(db.at(handle).storage_id, |
992 | + handle, |
993 | + db.at(handle).object_format); |
994 | } |
995 | |
996 | virtual MtpResponseCode setObjectReferences( |
997 | @@ -698,7 +1075,10 @@ |
998 | MtpObjectHandleList* references) |
999 | { |
1000 | VLOG(1) << __PRETTY_FUNCTION__; |
1001 | - return MTP_RESPONSE_OPERATION_NOT_SUPPORTED; |
1002 | + |
1003 | + // ignore, we don't keep the references in a list. |
1004 | + |
1005 | + return MTP_RESPONSE_OK; |
1006 | } |
1007 | |
1008 | virtual MtpProperty* getObjectPropertyDesc( |
1009 | @@ -710,14 +1090,23 @@ |
1010 | MtpProperty* result = nullptr; |
1011 | switch(property) |
1012 | { |
1013 | - case MTP_PROPERTY_STORAGE_ID: result = new MtpProperty(property, MTP_TYPE_UINT32); break; |
1014 | - case MTP_PROPERTY_OBJECT_FORMAT: result = new MtpProperty(property, MTP_TYPE_UINT32); break; |
1015 | - case MTP_PROPERTY_OBJECT_SIZE: result = new MtpProperty(property, MTP_TYPE_UINT32); break; |
1016 | - case MTP_PROPERTY_WIDTH: result = new MtpProperty(property, MTP_TYPE_UINT32); break; |
1017 | - case MTP_PROPERTY_HEIGHT: result = new MtpProperty(property, MTP_TYPE_UINT32); break; |
1018 | - case MTP_PROPERTY_IMAGE_BIT_DEPTH: result = new MtpProperty(property, MTP_TYPE_UINT32); break; |
1019 | + case MTP_PROPERTY_STORAGE_ID: result = new MtpProperty(property, MTP_TYPE_UINT32, false); break; |
1020 | + case MTP_PROPERTY_PARENT_OBJECT: result = new MtpProperty(property, MTP_TYPE_UINT32, true); break; |
1021 | + case MTP_PROPERTY_OBJECT_FORMAT: result = new MtpProperty(property, MTP_TYPE_UINT16, false); break; |
1022 | + case MTP_PROPERTY_OBJECT_SIZE: result = new MtpProperty(property, MTP_TYPE_UINT32, false); break; |
1023 | + case MTP_PROPERTY_WIDTH: result = new MtpProperty(property, MTP_TYPE_UINT32, false); break; |
1024 | + case MTP_PROPERTY_HEIGHT: result = new MtpProperty(property, MTP_TYPE_UINT32, false); break; |
1025 | + case MTP_PROPERTY_IMAGE_BIT_DEPTH: result = new MtpProperty(property, MTP_TYPE_UINT32, false); break; |
1026 | case MTP_PROPERTY_DISPLAY_NAME: result = new MtpProperty(property, MTP_TYPE_STR, true); break; |
1027 | case MTP_PROPERTY_OBJECT_FILE_NAME: result = new MtpProperty(property, MTP_TYPE_STR, true); break; |
1028 | + case MTP_PROPERTY_PERSISTENT_UID: result = new MtpProperty(property, MTP_TYPE_UINT128, false); break; |
1029 | + case MTP_PROPERTY_ASSOCIATION_TYPE: result = new MtpProperty(property, MTP_TYPE_UINT16, false); break; |
1030 | + case MTP_PROPERTY_ASSOCIATION_DESC: result = new MtpProperty(property, MTP_TYPE_UINT32, false); break; |
1031 | + case MTP_PROPERTY_PROTECTION_STATUS: result = new MtpProperty(property, MTP_TYPE_UINT16, false); break; |
1032 | + case MTP_PROPERTY_DATE_CREATED: result = new MtpProperty(property, MTP_TYPE_STR, false); break; |
1033 | + case MTP_PROPERTY_DATE_MODIFIED: result = new MtpProperty(property, MTP_TYPE_STR, false); break; |
1034 | + case MTP_PROPERTY_HIDDEN: result = new MtpProperty(property, MTP_TYPE_UINT16, false); break; |
1035 | + case MTP_PROPERTY_NON_CONSUMABLE: result = new MtpProperty(property, MTP_TYPE_UINT16, false); break; |
1036 | default: break; |
1037 | } |
1038 | |
1039 | @@ -727,7 +1116,17 @@ |
1040 | virtual MtpProperty* getDevicePropertyDesc(MtpDeviceProperty property) |
1041 | { |
1042 | VLOG(1) << __PRETTY_FUNCTION__ << MtpDebug::getDevicePropCodeName(property); |
1043 | - return new MtpProperty(MTP_DEVICE_PROPERTY_UNDEFINED, MTP_TYPE_UNDEFINED); |
1044 | + |
1045 | + MtpProperty* result = nullptr; |
1046 | + switch(property) |
1047 | + { |
1048 | + case MTP_DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER: |
1049 | + case MTP_DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME: |
1050 | + result = new MtpProperty(property, MTP_TYPE_STR, false); break; |
1051 | + default: break; |
1052 | + } |
1053 | + |
1054 | + return result; |
1055 | } |
1056 | |
1057 | virtual void sessionStarted(MtpServer* server) |
1058 | |
1059 | === modified file 'server/server.cpp' |
1060 | --- server/server.cpp 2014-03-27 15:45:30 +0000 |
1061 | +++ server/server.cpp 2014-08-21 19:42:07 +0000 |
1062 | @@ -32,6 +32,69 @@ |
1063 | #include <hybris/properties/properties.h> |
1064 | #include <glog/logging.h> |
1065 | |
1066 | +#include <core/dbus/bus.h> |
1067 | +#include <core/dbus/object.h> |
1068 | +#include <core/dbus/property.h> |
1069 | +#include <core/dbus/service.h> |
1070 | +#include <core/dbus/signal.h> |
1071 | + |
1072 | +#include <core/dbus/asio/executor.h> |
1073 | +#include <core/dbus/types/stl/tuple.h> |
1074 | +#include <core/dbus/types/stl/vector.h> |
1075 | +#include <core/dbus/types/struct.h> |
1076 | + |
1077 | +namespace dbus = core::dbus; |
1078 | +using namespace android; |
1079 | + |
1080 | +namespace core |
1081 | +{ |
1082 | +dbus::Bus::Ptr the_session_bus() |
1083 | +{ |
1084 | + static dbus::Bus::Ptr session_bus = std::make_shared<dbus::Bus>(dbus::WellKnownBus::session); |
1085 | + return session_bus; |
1086 | +} |
1087 | + |
1088 | +struct UnityGreeter |
1089 | +{ |
1090 | + struct Properties |
1091 | + { |
1092 | + struct IsActive |
1093 | + { |
1094 | + inline static std::string name() |
1095 | + { |
1096 | + return "IsActive"; |
1097 | + }; |
1098 | + typedef UnityGreeter Interface; |
1099 | + typedef bool ValueType; |
1100 | + static const bool readable = true; |
1101 | + static const bool writable = false; |
1102 | + }; |
1103 | + }; |
1104 | +}; |
1105 | +} |
1106 | + |
1107 | +namespace core |
1108 | +{ |
1109 | +namespace dbus |
1110 | +{ |
1111 | +namespace traits |
1112 | +{ |
1113 | +template<> |
1114 | +struct Service<core::UnityGreeter> |
1115 | +{ |
1116 | + inline static const std::string& interface_name() |
1117 | + { |
1118 | + static const std::string s |
1119 | + { |
1120 | + "com.canonical.UnityGreeter" |
1121 | + }; |
1122 | + return s; |
1123 | + } |
1124 | +}; |
1125 | +} |
1126 | +} |
1127 | +} |
1128 | + |
1129 | namespace |
1130 | { |
1131 | struct FileSystemConfig |
1132 | @@ -40,59 +103,303 @@ |
1133 | static const int directory_perm = 0755; |
1134 | }; |
1135 | |
1136 | -android::MtpStorage* home_storage; |
1137 | - |
1138 | } |
1139 | |
1140 | +class MtpDaemon |
1141 | +{ |
1142 | + |
1143 | +private: |
1144 | + struct passwd *userdata; |
1145 | + |
1146 | + // Mtp stuff |
1147 | + MtpServer* server; |
1148 | + MtpStorage* home_storage; |
1149 | + MtpStorage* sd_card; |
1150 | + MtpDatabase* mtp_database; |
1151 | + |
1152 | + // Security |
1153 | + std::shared_ptr<core::dbus::Property<core::UnityGreeter::Properties::IsActive> > is_active; |
1154 | + |
1155 | + // inotify stuff |
1156 | + boost::thread notifier_thread; |
1157 | + boost::thread io_service_thread; |
1158 | + |
1159 | + asio::io_service io_svc; |
1160 | + asio::io_service::work work; |
1161 | + asio::posix::stream_descriptor stream_desc; |
1162 | + asio::streambuf buf; |
1163 | + |
1164 | + int inotify_fd; |
1165 | + |
1166 | + int watch_fd; |
1167 | + int media_fd; |
1168 | + |
1169 | + // storage |
1170 | + std::map<std::string, std::tuple<MtpStorage*, bool> > removables; |
1171 | + bool home_storage_added; |
1172 | + |
1173 | + void add_removable_storage(const char *path, const char *name) |
1174 | + { |
1175 | + static int storageID = MTP_STORAGE_REMOVABLE_RAM; |
1176 | + |
1177 | + MtpStorage *removable = new MtpStorage( |
1178 | + storageID, |
1179 | + path, |
1180 | + name, |
1181 | + 1024 * 1024 * 100, /* 100 MB reserved space, to avoid filling the disk */ |
1182 | + true, |
1183 | + 1024 * 1024 * 1024 * 2 /* 2GB arbitrary max file size */); |
1184 | + |
1185 | + storageID++; |
1186 | + |
1187 | + bool screen_locked = is_active->get(); |
1188 | + |
1189 | + if (!screen_locked) { |
1190 | + mtp_database->addStoragePath(path, |
1191 | + removable->getStorageID(), |
1192 | + true); |
1193 | + server->addStorage(removable); |
1194 | + } |
1195 | + |
1196 | + removables.insert(std::pair<std::string, std::tuple<MtpStorage*, bool> > |
1197 | + (name, |
1198 | + std::make_tuple(removable, |
1199 | + screen_locked ? false : true))); |
1200 | + } |
1201 | + |
1202 | + void add_mountpoint_watch(const std::string& path) |
1203 | + { |
1204 | + VLOG(1) << "Adding notify watch for " << path; |
1205 | + watch_fd = inotify_add_watch(inotify_fd, |
1206 | + path.c_str(), |
1207 | + IN_CREATE | IN_DELETE); |
1208 | + } |
1209 | + |
1210 | + void read_more_notify() |
1211 | + { |
1212 | + VLOG(1) << __PRETTY_FUNCTION__; |
1213 | + |
1214 | + stream_desc.async_read_some(buf.prepare(buf.max_size()), |
1215 | + boost::bind(&MtpDaemon::inotify_handler, |
1216 | + this, |
1217 | + asio::placeholders::error, |
1218 | + asio::placeholders::bytes_transferred)); |
1219 | + } |
1220 | + |
1221 | + void inotify_handler(const boost::system::error_code&, |
1222 | + std::size_t transferred) |
1223 | + { |
1224 | + size_t processed = 0; |
1225 | + |
1226 | + while(transferred - processed >= sizeof(inotify_event)) |
1227 | + { |
1228 | + const char* cdata = processed + asio::buffer_cast<const char*>(buf.data()); |
1229 | + const inotify_event* ievent = reinterpret_cast<const inotify_event*>(cdata); |
1230 | + path storage_path ("/media"); |
1231 | + |
1232 | + processed += sizeof(inotify_event) + ievent->len; |
1233 | + |
1234 | + storage_path /= userdata->pw_name; |
1235 | + |
1236 | + if (ievent->len > 0 && ievent->mask & IN_CREATE) |
1237 | + { |
1238 | + if (ievent->wd == media_fd) { |
1239 | + VLOG(1) << "media root was created for user " << ievent->name; |
1240 | + add_mountpoint_watch(storage_path.string()); |
1241 | + } else { |
1242 | + VLOG(1) << "Storage was added: " << ievent->name; |
1243 | + storage_path /= ievent->name; |
1244 | + add_removable_storage(storage_path.string().c_str(), ievent->name); |
1245 | + } |
1246 | + } |
1247 | + else if (ievent->len > 0 && ievent->mask & IN_DELETE) |
1248 | + { |
1249 | + VLOG(1) << "Storage was removed: " << ievent->name; |
1250 | + |
1251 | + // Try to match to which storage was removed. |
1252 | + BOOST_FOREACH(std::string name, removables | boost::adaptors::map_keys) { |
1253 | + if (name == ievent->name) { |
1254 | + auto t = removables.at(name); |
1255 | + MtpStorage *storage = std::get<0>(t); |
1256 | + |
1257 | + VLOG(2) << "removing storage id " |
1258 | + << storage->getStorageID(); |
1259 | + |
1260 | + server->removeStorage(storage); |
1261 | + mtp_database->removeStorage(storage->getStorageID()); |
1262 | + } |
1263 | + } |
1264 | + } |
1265 | + } |
1266 | + |
1267 | + read_more_notify(); |
1268 | + } |
1269 | + |
1270 | +public: |
1271 | + |
1272 | + MtpDaemon(int fd): |
1273 | + stream_desc(io_svc), |
1274 | + work(io_svc), |
1275 | + buf(1024) |
1276 | + { |
1277 | + userdata = getpwuid (getuid()); |
1278 | + |
1279 | + // Removable storage hacks |
1280 | + inotify_fd = inotify_init(); |
1281 | + if (inotify_fd <= 0) |
1282 | + PLOG(FATAL) << "Unable to initialize inotify"; |
1283 | + VLOG(1) << "using inotify fd " << inotify_fd << " for daemon"; |
1284 | + |
1285 | + stream_desc.assign(inotify_fd); |
1286 | + notifier_thread = boost::thread(&MtpDaemon::read_more_notify, this); |
1287 | + io_service_thread = boost::thread(boost::bind(&asio::io_service::run, &io_svc)); |
1288 | + |
1289 | + |
1290 | + // MTP database. |
1291 | + mtp_database = new UbuntuMtpDatabase(); |
1292 | + |
1293 | + |
1294 | + // MTP server |
1295 | + server = new MtpServer( |
1296 | + fd, |
1297 | + mtp_database, |
1298 | + false, |
1299 | + userdata->pw_gid, |
1300 | + FileSystemConfig::file_perm, |
1301 | + FileSystemConfig::directory_perm); |
1302 | + |
1303 | + // security / screen locking |
1304 | + auto bus = core::the_session_bus(); |
1305 | + bus->install_executor(core::dbus::asio::make_executor(bus)); |
1306 | + std::thread t {std::bind(&dbus::Bus::run, bus)}; |
1307 | + auto greeter_service = dbus::Service::use_service(bus, "com.canonical.UnityGreeter"); |
1308 | + dbus::Object::Ptr greeter = greeter_service->object_for_path(dbus::types::ObjectPath("/")); |
1309 | + |
1310 | + t.detach(); |
1311 | + |
1312 | + is_active = greeter->get_property<core::UnityGreeter::Properties::IsActive>(); |
1313 | + } |
1314 | + |
1315 | + void initStorage() |
1316 | + { |
1317 | + char product_name[PROP_VALUE_MAX]; |
1318 | + |
1319 | + // Local storage |
1320 | + property_get ("ro.product.model", product_name, "Ubuntu Touch device"); |
1321 | + |
1322 | + home_storage = new MtpStorage( |
1323 | + MTP_STORAGE_FIXED_RAM, |
1324 | + userdata->pw_dir, |
1325 | + product_name, |
1326 | + 1024 * 1024 * 100, /* 100 MB reserved space, to avoid filling the disk */ |
1327 | + false, |
1328 | + 1024 * 1024 * 1024 * 2 /* 2GB arbitrary max file size */); |
1329 | + mtp_database->addStoragePath(std::string(userdata->pw_dir) + "/Documents", |
1330 | + MTP_STORAGE_FIXED_RAM, false); |
1331 | + mtp_database->addStoragePath(std::string(userdata->pw_dir) + "/Music", |
1332 | + MTP_STORAGE_FIXED_RAM, false); |
1333 | + mtp_database->addStoragePath(std::string(userdata->pw_dir) + "/Videos", |
1334 | + MTP_STORAGE_FIXED_RAM, false); |
1335 | + mtp_database->addStoragePath(std::string(userdata->pw_dir) + "/Pictures", |
1336 | + MTP_STORAGE_FIXED_RAM, false); |
1337 | + mtp_database->addStoragePath(std::string(userdata->pw_dir) + "/Downloads", |
1338 | + MTP_STORAGE_FIXED_RAM, false); |
1339 | + home_storage_added = false; |
1340 | + |
1341 | + // Get any already-mounted removable storage. |
1342 | + path p(std::string("/media/") + userdata->pw_name); |
1343 | + if (exists(p)) { |
1344 | + std::vector<path> v; |
1345 | + copy(directory_iterator(p), directory_iterator(), std::back_inserter(v)); |
1346 | + for (std::vector<path>::const_iterator it(v.begin()), it_end(v.end()); it != it_end; ++it) |
1347 | + { |
1348 | + add_removable_storage(it->string().c_str(), it->leaf().c_str()); |
1349 | + } |
1350 | + |
1351 | + // make sure we can catch any new removable storage that gets added. |
1352 | + add_mountpoint_watch(p.string()); |
1353 | + } else { |
1354 | + media_fd = inotify_add_watch(inotify_fd, |
1355 | + "/media", |
1356 | + IN_CREATE | IN_DELETE); |
1357 | + } |
1358 | + |
1359 | + } |
1360 | + |
1361 | + ~MtpDaemon() |
1362 | + { |
1363 | + // Cleanup |
1364 | + inotify_rm_watch(inotify_fd, watch_fd); |
1365 | + io_svc.stop(); |
1366 | + notifier_thread.detach(); |
1367 | + io_service_thread.join(); |
1368 | + close(inotify_fd); |
1369 | + } |
1370 | + |
1371 | + void run() |
1372 | + { |
1373 | + if (is_active->get()) { |
1374 | + is_active->changed().connect([this](bool active) |
1375 | + { |
1376 | + if (!active) { |
1377 | + VLOG(2) << "device was unlocked, adding storage"; |
1378 | + if (home_storage && !home_storage_added) { |
1379 | + server->addStorage(home_storage); |
1380 | + home_storage_added = true; |
1381 | + } |
1382 | + BOOST_FOREACH(std::string name, removables | boost::adaptors::map_keys) { |
1383 | + auto t = removables.at(name); |
1384 | + MtpStorage *storage = std::get<0>(t); |
1385 | + bool added = std::get<1>(t); |
1386 | + if (!added) { |
1387 | + mtp_database->addStoragePath(storage->getPath(), |
1388 | + storage->getStorageID(), |
1389 | + true); |
1390 | + server->addStorage(storage); |
1391 | + } |
1392 | + } |
1393 | + } |
1394 | + }); |
1395 | + } else { |
1396 | + VLOG(2) << "device is not locked, adding storage"; |
1397 | + if (home_storage) { |
1398 | + server->addStorage(home_storage); |
1399 | + home_storage_added = true; |
1400 | + } |
1401 | + BOOST_FOREACH(std::string name, removables | boost::adaptors::map_keys) { |
1402 | + auto t = removables.at(name); |
1403 | + MtpStorage *storage = std::get<0>(t); |
1404 | + bool added = std::get<1>(t); |
1405 | + if (!added) { |
1406 | + mtp_database->addStoragePath(storage->getPath(), |
1407 | + storage->getStorageID(), |
1408 | + true); |
1409 | + server->addStorage(storage); |
1410 | + } |
1411 | + } |
1412 | + } |
1413 | + |
1414 | + // start the MtpServer main loop |
1415 | + server->run(); |
1416 | + } |
1417 | +}; |
1418 | + |
1419 | int main(int argc, char** argv) |
1420 | { |
1421 | - struct passwd *userdata = getpwuid (getuid()); |
1422 | - char product_name[PROP_VALUE_MAX]; |
1423 | - int fd = open("/dev/mtp_usb", O_RDWR); |
1424 | - |
1425 | google::InitGoogleLogging(argv[0]); |
1426 | |
1427 | LOG(INFO) << "MTP server starting..."; |
1428 | |
1429 | + int fd = open("/dev/mtp_usb", O_RDWR); |
1430 | if (fd < 0) |
1431 | { |
1432 | LOG(FATAL) << "Error opening /dev/mtp_usb, aborting now..."; |
1433 | } |
1434 | |
1435 | - std::shared_ptr<android::MtpServer> server |
1436 | - { |
1437 | - new android::MtpServer( |
1438 | - fd, |
1439 | - new android::UbuntuMtpDatabase(userdata->pw_dir), |
1440 | - false, |
1441 | - userdata->pw_gid, |
1442 | - FileSystemConfig::file_perm, |
1443 | - FileSystemConfig::directory_perm) |
1444 | - }; |
1445 | - |
1446 | - property_get ("ro.product.model", product_name, "Ubuntu Touch device"); |
1447 | - |
1448 | - home_storage = new android::MtpStorage( |
1449 | - MTP_STORAGE_FIXED_RAM, |
1450 | - userdata->pw_dir, |
1451 | - product_name, |
1452 | - 1024 * 1024 * 100, /* 100 MB reserved space, to avoid filling the disk */ |
1453 | - false, |
1454 | - 1024 * 1024 * 1024 * 2 /* 2GB arbitrary max file size */); |
1455 | - |
1456 | - server->addStorage(home_storage); |
1457 | - server->run(); |
1458 | - |
1459 | - /* |
1460 | - std::thread t{[&server](){ server->run(); }}; |
1461 | - |
1462 | - sigset_t signal_set; |
1463 | - sigemptyset(&signal_set); |
1464 | - sigaddset(&signal_set, SIGINT); |
1465 | - int signal; |
1466 | - sigwait(&signal_set, &signal); |
1467 | - |
1468 | - if (t.joinable()) |
1469 | - t.join(); |
1470 | - */ |
1471 | + MtpDaemon *d = new MtpDaemon(fd); |
1472 | + |
1473 | + d->initStorage(); |
1474 | + d->run(); |
1475 | + |
1476 | + delete d; |
1477 | } |
1478 | |
1479 | === modified file 'tests/MockMtpDatabase.h' |
1480 | --- tests/MockMtpDatabase.h 2014-03-27 15:50:32 +0000 |
1481 | +++ tests/MockMtpDatabase.h 2014-08-21 19:42:07 +0000 |
1482 | @@ -74,6 +74,14 @@ |
1483 | |
1484 | virtual ~MockMtpDatabase() {} |
1485 | |
1486 | + virtual void addStoragePath(const MtpString& path, MtpStorageID storage, bool hidden) |
1487 | + { |
1488 | + } |
1489 | + |
1490 | + virtual void removeStorage(MtpStorageID storage) |
1491 | + { |
1492 | + } |
1493 | + |
1494 | virtual MtpObjectHandle beginSendObject( |
1495 | const MtpString& path, |
1496 | MtpObjectFormat format, |
Tested on both Mac OS and Ubuntu.
Can confirm security, sdcard support works as intended.