MtpServer.cpp revision b14e588bec4d5e39e61b020b5b575f2ce555d316
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#define LOG_TAG "MtpServer"
18
19#include <stdio.h>
20#include <stdlib.h>
21#include <sys/types.h>
22#include <sys/ioctl.h>
23#include <sys/stat.h>
24#include <fcntl.h>
25#include <errno.h>
26
27#include <cutils/properties.h>
28
29#include "MtpDebug.h"
30#include "MtpServer.h"
31#include "MtpStorage.h"
32#include "MtpStringBuffer.h"
33#include "MtpDatabase.h"
34#include "MtpDebug.h"
35
36#include "f_mtp.h"
37
38namespace android {
39
40static const MtpOperationCode kSupportedOperationCodes[] = {
41    MTP_OPERATION_GET_DEVICE_INFO,
42    MTP_OPERATION_OPEN_SESSION,
43    MTP_OPERATION_CLOSE_SESSION,
44    MTP_OPERATION_GET_STORAGE_IDS,
45    MTP_OPERATION_GET_STORAGE_INFO,
46    MTP_OPERATION_GET_NUM_OBJECTS,
47    MTP_OPERATION_GET_OBJECT_HANDLES,
48    MTP_OPERATION_GET_OBJECT_INFO,
49    MTP_OPERATION_GET_OBJECT,
50//    MTP_OPERATION_GET_THUMB,
51    MTP_OPERATION_DELETE_OBJECT,
52    MTP_OPERATION_SEND_OBJECT_INFO,
53    MTP_OPERATION_SEND_OBJECT,
54//    MTP_OPERATION_INITIATE_CAPTURE,
55//    MTP_OPERATION_FORMAT_STORE,
56//    MTP_OPERATION_RESET_DEVICE,
57//    MTP_OPERATION_SELF_TEST,
58//    MTP_OPERATION_SET_OBJECT_PROTECTION,
59//    MTP_OPERATION_POWER_DOWN,
60    MTP_OPERATION_GET_DEVICE_PROP_DESC,
61    MTP_OPERATION_GET_DEVICE_PROP_VALUE,
62    MTP_OPERATION_SET_DEVICE_PROP_VALUE,
63    MTP_OPERATION_RESET_DEVICE_PROP_VALUE,
64//    MTP_OPERATION_TERMINATE_OPEN_CAPTURE,
65//    MTP_OPERATION_MOVE_OBJECT,
66//    MTP_OPERATION_COPY_OBJECT,
67//    MTP_OPERATION_GET_PARTIAL_OBJECT,
68//    MTP_OPERATION_INITIATE_OPEN_CAPTURE,
69    MTP_OPERATION_GET_OBJECT_PROPS_SUPPORTED,
70//    MTP_OPERATION_GET_OBJECT_PROP_DESC,
71    MTP_OPERATION_GET_OBJECT_PROP_VALUE,
72    MTP_OPERATION_SET_OBJECT_PROP_VALUE,
73//    MTP_OPERATION_GET_OBJECT_REFERENCES,
74//    MTP_OPERATION_SET_OBJECT_REFERENCES,
75//    MTP_OPERATION_SKIP,
76};
77
78static const MtpObjectProperty kSupportedObjectProperties[] = {
79    MTP_PROPERTY_STORAGE_ID,
80    MTP_PROPERTY_OBJECT_FORMAT,
81    MTP_PROPERTY_OBJECT_SIZE,
82    MTP_PROPERTY_OBJECT_FILE_NAME,
83    MTP_PROPERTY_PARENT_OBJECT,
84};
85
86static const MtpObjectFormat kSupportedPlaybackFormats[] = {
87    // MTP_FORMAT_UNDEFINED,
88    MTP_FORMAT_ASSOCIATION,
89    // MTP_FORMAT_TEXT,
90    // MTP_FORMAT_HTML,
91    MTP_FORMAT_MP3,
92    //MTP_FORMAT_AVI,
93    MTP_FORMAT_MPEG,
94    // MTP_FORMAT_ASF,
95    MTP_FORMAT_EXIF_JPEG,
96    MTP_FORMAT_TIFF_EP,
97    // MTP_FORMAT_BMP,
98    MTP_FORMAT_GIF,
99    MTP_FORMAT_JFIF,
100    MTP_FORMAT_PNG,
101    MTP_FORMAT_TIFF,
102    MTP_FORMAT_WMA,
103    MTP_FORMAT_OGG,
104    MTP_FORMAT_AAC,
105    // MTP_FORMAT_FLAC,
106    // MTP_FORMAT_WMV,
107    MTP_FORMAT_MP4_CONTAINER,
108    MTP_FORMAT_MP2,
109    MTP_FORMAT_3GP_CONTAINER,
110    // MTP_FORMAT_ABSTRACT_AUDIO_ALBUM,
111    // MTP_FORMAT_ABSTRACT_AV_PLAYLIST,
112    // MTP_FORMAT_WPL_PLAYLIST,
113    // MTP_FORMAT_M3U_PLAYLIST,
114    // MTP_FORMAT_MPL_PLAYLIST,
115    // MTP_FORMAT_PLS_PLAYLIST,
116};
117
118MtpServer::MtpServer(int fd, const char* databasePath)
119    :   mFD(fd),
120        mDatabasePath(databasePath),
121        mDatabase(NULL),
122        mSessionID(0),
123        mSessionOpen(false),
124        mSendObjectHandle(kInvalidObjectHandle),
125        mSendObjectFileSize(0)
126{
127    mDatabase = new MtpDatabase();
128    mDatabase->open(databasePath, true);
129}
130
131MtpServer::~MtpServer() {
132}
133
134void MtpServer::addStorage(const char* filePath) {
135    int index = mStorages.size() + 1;
136    index |= index << 16;   // set high and low part to our index
137    MtpStorage* storage = new MtpStorage(index, filePath, mDatabase);
138    addStorage(storage);
139}
140
141MtpStorage* MtpServer::getStorage(MtpStorageID id) {
142    for (int i = 0; i < mStorages.size(); i++) {
143        MtpStorage* storage =  mStorages[i];
144        if (storage->getStorageID() == id)
145            return storage;
146    }
147    return NULL;
148}
149
150void MtpServer::scanStorage() {
151    for (int i = 0; i < mStorages.size(); i++) {
152        MtpStorage* storage =  mStorages[i];
153        storage->scanFiles();
154    }
155}
156
157void MtpServer::run() {
158    int fd = mFD;
159
160    LOGD("MtpServer::run fd: %d", fd);
161
162    while (1) {
163        int ret = mRequest.read(fd);
164        if (ret < 0) {
165            LOGE("request read returned %d, errno: %d", ret, errno);
166            if (errno == ECANCELED) {
167                // return to top of loop and wait for next command
168                continue;
169            }
170            break;
171        }
172        MtpOperationCode operation = mRequest.getOperationCode();
173        MtpTransactionID transaction = mRequest.getTransactionID();
174
175        LOGV("operation: %s", MtpDebug::getOperationCodeName(operation));
176        mRequest.dump();
177
178        // FIXME need to generalize this
179        bool dataIn = (operation == MTP_OPERATION_SEND_OBJECT_INFO);
180        if (dataIn) {
181            int ret = mData.read(fd);
182            if (ret < 0) {
183                LOGE("data read returned %d, errno: %d", ret, errno);
184                if (errno == ECANCELED) {
185                    // return to top of loop and wait for next command
186                    continue;
187                }
188                break;
189            }
190            LOGV("received data:");
191            mData.dump();
192        } else {
193            mData.reset();
194        }
195
196        if (handleRequest()) {
197            if (!dataIn && mData.hasData()) {
198                mData.setOperationCode(operation);
199                mData.setTransactionID(transaction);
200                LOGV("sending data:");
201                mData.dump();
202                ret = mData.write(fd);
203                if (ret < 0) {
204                    LOGE("request write returned %d, errno: %d", ret, errno);
205                    if (errno == ECANCELED) {
206                        // return to top of loop and wait for next command
207                        continue;
208                    }
209                    break;
210                }
211            }
212
213            mResponse.setTransactionID(transaction);
214            LOGV("sending response %04X", mResponse.getResponseCode());
215            ret = mResponse.write(fd);
216            if (ret < 0) {
217                LOGE("request write returned %d, errno: %d", ret, errno);
218                if (errno == ECANCELED) {
219                    // return to top of loop and wait for next command
220                    continue;
221                }
222                break;
223            }
224        } else {
225            LOGV("skipping response");
226        }
227    }
228}
229
230bool MtpServer::handleRequest() {
231    MtpOperationCode operation = mRequest.getOperationCode();
232    MtpResponseCode response;
233
234    mResponse.reset();
235
236    if (mSendObjectHandle != kInvalidObjectHandle && operation != MTP_OPERATION_SEND_OBJECT) {
237        // FIXME - need to delete mSendObjectHandle from the database
238        LOGE("expected SendObject after SendObjectInfo");
239        mSendObjectHandle = kInvalidObjectHandle;
240    }
241
242    switch (operation) {
243        case MTP_OPERATION_GET_DEVICE_INFO:
244            response = doGetDeviceInfo();
245            break;
246        case MTP_OPERATION_OPEN_SESSION:
247            response = doOpenSession();
248            break;
249        case MTP_OPERATION_CLOSE_SESSION:
250            response = doCloseSession();
251            break;
252        case MTP_OPERATION_GET_STORAGE_IDS:
253            response = doGetStorageIDs();
254            break;
255         case MTP_OPERATION_GET_STORAGE_INFO:
256            response = doGetStorageInfo();
257            break;
258        case MTP_OPERATION_GET_OBJECT_PROPS_SUPPORTED:
259            response = doGetObjectPropsSupported();
260            break;
261        case MTP_OPERATION_GET_OBJECT_HANDLES:
262            response = doGetObjectHandles();
263            break;
264        case MTP_OPERATION_GET_OBJECT_PROP_VALUE:
265            response = doGetObjectPropValue();
266            break;
267        case MTP_OPERATION_GET_OBJECT_INFO:
268            response = doGetObjectInfo();
269            break;
270        case MTP_OPERATION_GET_OBJECT:
271            response = doGetObject();
272            break;
273        case MTP_OPERATION_SEND_OBJECT_INFO:
274            response = doSendObjectInfo();
275            break;
276        case MTP_OPERATION_SEND_OBJECT:
277            response = doSendObject();
278            break;
279        case MTP_OPERATION_DELETE_OBJECT:
280            response = doDeleteObject();
281            break;
282        case MTP_OPERATION_GET_OBJECT_PROP_DESC:
283        default:
284            response = MTP_RESPONSE_OPERATION_NOT_SUPPORTED;
285            break;
286    }
287
288    if (response == MTP_RESPONSE_TRANSACTION_CANCELLED)
289        return false;
290    mResponse.setResponseCode(response);
291    return true;
292}
293
294MtpResponseCode MtpServer::doGetDeviceInfo() {
295    MtpStringBuffer   string;
296    char prop_value[PROPERTY_VALUE_MAX];
297
298    // fill in device info
299    mData.putUInt16(MTP_STANDARD_VERSION);
300    mData.putUInt32(6); // MTP Vendor Extension ID
301    mData.putUInt16(MTP_STANDARD_VERSION);
302    string.set("microsoft.com: 1.0;");
303    mData.putString(string); // MTP Extensions
304    mData.putUInt16(0); //Functional Mode
305    mData.putAUInt16(kSupportedOperationCodes,
306            sizeof(kSupportedOperationCodes) / sizeof(uint16_t)); // Operations Supported
307    mData.putEmptyArray(); // Events Supported
308    mData.putEmptyArray(); // Device Properties Supported
309    mData.putEmptyArray(); // Capture Formats
310    mData.putAUInt16(kSupportedPlaybackFormats,
311            sizeof(kSupportedPlaybackFormats) / sizeof(uint16_t)); // Playback Formats
312    // FIXME
313    string.set("Google, Inc.");
314    mData.putString(string);   // Manufacturer
315
316    property_get("ro.product.model", prop_value, "MTP Device");
317    string.set(prop_value);
318    mData.putString(string);   // Model
319    string.set("1.0");
320    mData.putString(string);   // Device Version
321
322    property_get("ro.serialno", prop_value, "????????");
323    string.set(prop_value);
324    mData.putString(string);   // Serial Number
325
326    return MTP_RESPONSE_OK;
327}
328
329MtpResponseCode MtpServer::doOpenSession() {
330    if (mSessionOpen) {
331        mResponse.setParameter(1, mSessionID);
332        return MTP_RESPONSE_SESSION_ALREADY_OPEN;
333    }
334    mSessionID = mRequest.getParameter(1);
335    mSessionOpen = true;
336    return MTP_RESPONSE_OK;
337}
338
339MtpResponseCode MtpServer::doCloseSession() {
340    if (!mSessionOpen)
341        return MTP_RESPONSE_SESSION_NOT_OPEN;
342    mSessionID = 0;
343    mSessionOpen = false;
344    return MTP_RESPONSE_OK;
345}
346
347MtpResponseCode MtpServer::doGetStorageIDs() {
348    if (!mSessionOpen)
349        return MTP_RESPONSE_SESSION_NOT_OPEN;
350
351    int count = mStorages.size();
352    mData.putUInt32(count);
353    for (int i = 0; i < count; i++)
354        mData.putUInt32(mStorages[i]->getStorageID());
355
356    return MTP_RESPONSE_OK;
357}
358
359MtpResponseCode MtpServer::doGetStorageInfo() {
360    MtpStringBuffer   string;
361
362    if (!mSessionOpen)
363        return MTP_RESPONSE_SESSION_NOT_OPEN;
364    MtpStorageID id = mRequest.getParameter(1);
365    MtpStorage* storage = getStorage(id);
366    if (!storage)
367        return MTP_RESPONSE_INVALID_STORAGE_ID;
368
369    mData.putUInt16(storage->getType());
370    mData.putUInt16(storage->getFileSystemType());
371    mData.putUInt16(storage->getAccessCapability());
372    mData.putUInt64(storage->getMaxCapacity());
373    mData.putUInt64(storage->getFreeSpace());
374    mData.putUInt32(1024*1024*1024); // Free Space in Objects
375    string.set(storage->getDescription());
376    mData.putString(string);
377    mData.putEmptyString();   // Volume Identifier
378
379    return MTP_RESPONSE_OK;
380}
381
382MtpResponseCode MtpServer::doGetObjectPropsSupported() {
383    if (!mSessionOpen)
384        return MTP_RESPONSE_SESSION_NOT_OPEN;
385    MtpObjectFormat format = mRequest.getParameter(1);
386    mData.putAUInt16(kSupportedObjectProperties,
387            sizeof(kSupportedObjectProperties) / sizeof(uint16_t));
388    return MTP_RESPONSE_OK;
389}
390
391MtpResponseCode MtpServer::doGetObjectHandles() {
392    if (!mSessionOpen)
393        return MTP_RESPONSE_SESSION_NOT_OPEN;
394    MtpStorageID storageID = mRequest.getParameter(1);      // 0xFFFFFFFF for all storage
395    MtpObjectFormat format = mRequest.getParameter(2);      // 0 for all formats
396    MtpObjectHandle parent = mRequest.getParameter(3);      // 0xFFFFFFFF for objects with no parent
397                                                            // 0x00000000 for all objects?
398
399    MtpObjectHandleList* handles = mDatabase->getObjectList(storageID, format, parent);
400    mData.putAUInt32(handles);
401    delete handles;
402    return MTP_RESPONSE_OK;
403}
404
405MtpResponseCode MtpServer::doGetObjectPropValue() {
406    MtpObjectHandle handle = mRequest.getParameter(1);
407    MtpObjectProperty property = mRequest.getParameter(2);
408
409    return mDatabase->getObjectProperty(handle, property, mData);
410}
411
412MtpResponseCode MtpServer::doGetObjectInfo() {
413    MtpObjectHandle handle = mRequest.getParameter(1);
414    return mDatabase->getObjectInfo(handle, mData);
415}
416
417MtpResponseCode MtpServer::doGetObject() {
418    MtpObjectHandle handle = mRequest.getParameter(1);
419    MtpString pathBuf;
420    int64_t fileLength;
421    if (!mDatabase->getObjectFilePath(handle, pathBuf, fileLength))
422        return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
423    const char* filePath = (const char *)pathBuf;
424
425    mtp_file_range  mfr;
426    mfr.fd = open(filePath, O_RDONLY);
427    if (mfr.fd < 0) {
428        return MTP_RESPONSE_GENERAL_ERROR;
429    }
430    mfr.offset = 0;
431    mfr.length = fileLength;
432
433    // send data header
434    mData.setOperationCode(mRequest.getOperationCode());
435    mData.setTransactionID(mRequest.getTransactionID());
436    mData.writeDataHeader(mFD, fileLength);
437
438    // then transfer the file
439    int ret = ioctl(mFD, MTP_SEND_FILE, (unsigned long)&mfr);
440    close(mfr.fd);
441    if (ret < 0) {
442        if (errno == ECANCELED)
443            return MTP_RESPONSE_TRANSACTION_CANCELLED;
444        else
445            return MTP_RESPONSE_GENERAL_ERROR;
446    }
447    return MTP_RESPONSE_OK;
448}
449
450MtpResponseCode MtpServer::doSendObjectInfo() {
451    MtpString path;
452    MtpStorageID storageID = mRequest.getParameter(1);
453    MtpStorage* storage = getStorage(storageID);
454    MtpObjectHandle parent = mRequest.getParameter(2);
455    if (!storage)
456        return MTP_RESPONSE_INVALID_STORAGE_ID;
457
458    // special case the root
459    if (parent == MTP_PARENT_ROOT)
460        path = storage->getPath();
461    else {
462        int64_t dummy;
463        if (!mDatabase->getObjectFilePath(parent, path, dummy))
464            return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
465    }
466
467    // read only the fields we need
468    mData.getUInt32();  // storage ID
469    MtpObjectFormat format = mData.getUInt16();
470    mData.getUInt16();  // protection status
471    mSendObjectFileSize = mData.getUInt32();
472    mData.getUInt16();  // thumb format
473    mData.getUInt32();  // thumb compressed size
474    mData.getUInt32();  // thumb pix width
475    mData.getUInt32();  // thumb pix height
476    mData.getUInt32();  // image pix width
477    mData.getUInt32();  // image pix height
478    mData.getUInt32();  // image bit depth
479    mData.getUInt32();  // parent
480    uint16_t associationType = mData.getUInt16();
481    uint32_t associationDesc = mData.getUInt32();   // association desc
482    mData.getUInt32();  // sequence number
483    MtpStringBuffer name, created, modified;
484    mData.getString(name);    // file name
485    mData.getString(created);      // date created
486    mData.getString(modified);     // date modified
487    // keywords follow
488
489    time_t modifiedTime;
490    if (!parseDateTime(modified, modifiedTime))
491        modifiedTime = 0;
492    LOGV("SendObjectInfo format: %04X size: %d name: %s, created: %s, modified: %s",
493            format, mSendObjectFileSize, (const char*)name, (const char*)created,
494            (const char*)modified);
495
496    if (path[path.size() - 1] != '/')
497        path += "/";
498    path += (const char *)name;
499
500    mDatabase->beginTransaction();
501    MtpObjectHandle handle = mDatabase->addFile((const char*)path, format, parent, storageID,
502                                    mSendObjectFileSize, modifiedTime);
503    if (handle == kInvalidObjectHandle) {
504        mDatabase->rollbackTransaction();
505        return MTP_RESPONSE_GENERAL_ERROR;
506    }
507    uint32_t table = MtpDatabase::getTableForFile(format);
508    if (table == kObjectHandleTableAudio)
509        handle = mDatabase->addAudioFile(handle);
510    mDatabase->commitTransaction();
511
512  if (format == MTP_FORMAT_ASSOCIATION) {
513        mode_t mask = umask(0);
514        int ret = mkdir((const char *)path, S_IRWXU | S_IRWXG | S_IRWXO);
515        umask(mask);
516        if (ret && ret != -EEXIST)
517            return MTP_RESPONSE_GENERAL_ERROR;
518    } else {
519        mSendObjectFilePath = path;
520        // save the handle for the SendObject call, which should follow
521        mSendObjectHandle = handle;
522    }
523
524    mResponse.setParameter(1, storageID);
525    mResponse.setParameter(2, parent);
526    mResponse.setParameter(3, handle);
527
528    return MTP_RESPONSE_OK;
529}
530
531MtpResponseCode MtpServer::doSendObject() {
532    if (mSendObjectHandle == kInvalidObjectHandle) {
533        LOGE("Expected SendObjectInfo before SendObject");
534        return MTP_RESPONSE_NO_VALID_OBJECT_INFO;
535    }
536
537    // read the header
538    int ret = mData.readDataHeader(mFD);
539    // FIXME - check for errors here.
540
541    // reset so we don't attempt to send this back
542    mData.reset();
543
544    mtp_file_range  mfr;
545    mfr.fd = open(mSendObjectFilePath, O_RDWR | O_CREAT | O_TRUNC);
546    if (mfr.fd < 0) {
547        return MTP_RESPONSE_GENERAL_ERROR;
548    }
549    mfr.offset = 0;
550    mfr.length = mSendObjectFileSize;
551
552    // transfer the file
553    ret = ioctl(mFD, MTP_RECEIVE_FILE, (unsigned long)&mfr);
554    close(mfr.fd);
555    // FIXME - we need to delete mSendObjectHandle from the database if this fails.
556    LOGV("MTP_RECEIVE_FILE returned %d", ret);
557    mSendObjectHandle = kInvalidObjectHandle;
558
559    if (ret < 0) {
560        unlink(mSendObjectFilePath);
561        if (errno == ECANCELED)
562            return MTP_RESPONSE_TRANSACTION_CANCELLED;
563        else
564            return MTP_RESPONSE_GENERAL_ERROR;
565    }
566    return MTP_RESPONSE_OK;
567}
568
569MtpResponseCode MtpServer::doDeleteObject() {
570    MtpObjectHandle handle = mRequest.getParameter(1);
571    MtpObjectFormat format = mRequest.getParameter(1);
572    // FIXME - support deleting all objects if handle is 0xFFFFFFFF
573    // FIXME - implement deleting objects by format
574    // FIXME - handle non-empty directories
575
576    MtpString filePath;
577    int64_t fileLength;
578    if (!mDatabase->getObjectFilePath(handle, filePath, fileLength))
579        return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
580
581    LOGV("deleting %s", (const char *)filePath);
582    // one of these should work
583    rmdir((const char *)filePath);
584    unlink((const char *)filePath);
585
586    mDatabase->deleteFile(handle);
587
588    return MTP_RESPONSE_OK;
589}
590
591MtpResponseCode MtpServer::doGetObjectPropDesc() {
592    MtpObjectProperty property = mRequest.getParameter(1);
593    MtpObjectFormat format = mRequest.getParameter(2);
594
595    return -1;
596}
597
598}  // namespace android
599