usb-mtp: Advertise SendObjectInfo for write support
This patch implements a dummy ObjectInfo structure so that it's easy to typecast the incoming data. If the metadata is valid, write_pending is set. Also, the incoming filename is utf-16, so, instead of depending on external libraries, just implement a simple function to get the filename Signed-off-by: Bandan Das <bsd@redhat.com> Message-id: 20180223164829.29683-6-bsd@redhat.com Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
This commit is contained in:
parent
88d5f381ec
commit
53735bef10
136
hw/usb/dev-mtp.c
136
hw/usb/dev-mtp.c
@ -47,6 +47,7 @@ enum mtp_code {
|
|||||||
CMD_GET_OBJECT_INFO = 0x1008,
|
CMD_GET_OBJECT_INFO = 0x1008,
|
||||||
CMD_GET_OBJECT = 0x1009,
|
CMD_GET_OBJECT = 0x1009,
|
||||||
CMD_DELETE_OBJECT = 0x100b,
|
CMD_DELETE_OBJECT = 0x100b,
|
||||||
|
CMD_SEND_OBJECT_INFO = 0x100c,
|
||||||
CMD_SEND_OBJECT = 0x100d,
|
CMD_SEND_OBJECT = 0x100d,
|
||||||
CMD_GET_PARTIAL_OBJECT = 0x101b,
|
CMD_GET_PARTIAL_OBJECT = 0x101b,
|
||||||
CMD_GET_OBJECT_PROPS_SUPPORTED = 0x9801,
|
CMD_GET_OBJECT_PROPS_SUPPORTED = 0x9801,
|
||||||
@ -67,8 +68,10 @@ enum mtp_code {
|
|||||||
RES_STORE_FULL = 0x200c,
|
RES_STORE_FULL = 0x200c,
|
||||||
RES_STORE_READ_ONLY = 0x200e,
|
RES_STORE_READ_ONLY = 0x200e,
|
||||||
RES_PARTIAL_DELETE = 0x2012,
|
RES_PARTIAL_DELETE = 0x2012,
|
||||||
|
RES_STORE_NOT_AVAILABLE = 0x2013,
|
||||||
RES_SPEC_BY_FORMAT_UNSUPPORTED = 0x2014,
|
RES_SPEC_BY_FORMAT_UNSUPPORTED = 0x2014,
|
||||||
RES_INVALID_OBJECTINFO = 0x2015,
|
RES_INVALID_OBJECTINFO = 0x2015,
|
||||||
|
RES_DESTINATION_UNSUPPORTED = 0x2020,
|
||||||
RES_INVALID_PARENT_OBJECT = 0x201a,
|
RES_INVALID_PARENT_OBJECT = 0x201a,
|
||||||
RES_INVALID_PARAMETER = 0x201d,
|
RES_INVALID_PARAMETER = 0x201d,
|
||||||
RES_SESSION_ALREADY_OPEN = 0x201e,
|
RES_SESSION_ALREADY_OPEN = 0x201e,
|
||||||
@ -196,6 +199,34 @@ struct MTPState {
|
|||||||
} dataset;
|
} dataset;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ObjectInfo dataset received from initiator
|
||||||
|
* Fields we don't care about are ignored
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
uint32_t storage_id; /*unused*/
|
||||||
|
uint16_t format;
|
||||||
|
uint16_t protection_status; /*unused*/
|
||||||
|
uint32_t size;
|
||||||
|
uint16_t thumb_format; /*unused*/
|
||||||
|
uint32_t thumb_comp_sz; /*unused*/
|
||||||
|
uint32_t thumb_pix_width; /*unused*/
|
||||||
|
uint32_t thumb_pix_height; /*unused*/
|
||||||
|
uint32_t image_pix_width; /*unused*/
|
||||||
|
uint32_t image_pix_height; /*unused*/
|
||||||
|
uint32_t image_bit_depth; /*unused*/
|
||||||
|
uint32_t parent; /*unused*/
|
||||||
|
uint16_t assoc_type;
|
||||||
|
uint32_t assoc_desc;
|
||||||
|
uint32_t seq_no; /*unused*/
|
||||||
|
uint8_t length; /*part of filename field*/
|
||||||
|
uint16_t filename[0];
|
||||||
|
char date_created[0]; /*unused*/
|
||||||
|
char date_modified[0]; /*unused*/
|
||||||
|
char keywords[0]; /*unused*/
|
||||||
|
/* string and other data follows */
|
||||||
|
} QEMU_PACKED ObjectInfo;
|
||||||
|
|
||||||
#define TYPE_USB_MTP "usb-mtp"
|
#define TYPE_USB_MTP "usb-mtp"
|
||||||
#define USB_MTP(obj) OBJECT_CHECK(MTPState, (obj), TYPE_USB_MTP)
|
#define USB_MTP(obj) OBJECT_CHECK(MTPState, (obj), TYPE_USB_MTP)
|
||||||
|
|
||||||
@ -437,7 +468,6 @@ static MTPObject *usb_mtp_add_child(MTPState *s, MTPObject *o,
|
|||||||
return child;
|
return child;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_INOTIFY1
|
|
||||||
static MTPObject *usb_mtp_object_lookup_name(MTPObject *parent,
|
static MTPObject *usb_mtp_object_lookup_name(MTPObject *parent,
|
||||||
char *name, int len)
|
char *name, int len)
|
||||||
{
|
{
|
||||||
@ -452,6 +482,7 @@ static MTPObject *usb_mtp_object_lookup_name(MTPObject *parent,
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_INOTIFY1
|
||||||
static MTPObject *usb_mtp_object_lookup_wd(MTPState *s, int wd)
|
static MTPObject *usb_mtp_object_lookup_wd(MTPState *s, int wd)
|
||||||
{
|
{
|
||||||
MTPObject *iter;
|
MTPObject *iter;
|
||||||
@ -815,6 +846,7 @@ static MTPData *usb_mtp_get_device_info(MTPState *s, MTPControl *c)
|
|||||||
CMD_GET_OBJECT_HANDLES,
|
CMD_GET_OBJECT_HANDLES,
|
||||||
CMD_GET_OBJECT_INFO,
|
CMD_GET_OBJECT_INFO,
|
||||||
CMD_DELETE_OBJECT,
|
CMD_DELETE_OBJECT,
|
||||||
|
CMD_SEND_OBJECT_INFO,
|
||||||
CMD_SEND_OBJECT,
|
CMD_SEND_OBJECT,
|
||||||
CMD_GET_OBJECT,
|
CMD_GET_OBJECT,
|
||||||
CMD_GET_PARTIAL_OBJECT,
|
CMD_GET_PARTIAL_OBJECT,
|
||||||
@ -1243,7 +1275,7 @@ static void usb_mtp_object_delete(MTPState *s, uint32_t handle,
|
|||||||
static void usb_mtp_command(MTPState *s, MTPControl *c)
|
static void usb_mtp_command(MTPState *s, MTPControl *c)
|
||||||
{
|
{
|
||||||
MTPData *data_in = NULL;
|
MTPData *data_in = NULL;
|
||||||
MTPObject *o;
|
MTPObject *o = NULL;
|
||||||
uint32_t nres = 0, res0 = 0;
|
uint32_t nres = 0, res0 = 0;
|
||||||
|
|
||||||
/* sanity checks */
|
/* sanity checks */
|
||||||
@ -1390,6 +1422,41 @@ static void usb_mtp_command(MTPState *s, MTPControl *c)
|
|||||||
nres = 1;
|
nres = 1;
|
||||||
res0 = data_in->length;
|
res0 = data_in->length;
|
||||||
break;
|
break;
|
||||||
|
case CMD_SEND_OBJECT_INFO:
|
||||||
|
/* Return error if store is read-only */
|
||||||
|
if (!FLAG_SET(s, MTP_FLAG_WRITABLE)) {
|
||||||
|
usb_mtp_queue_result(s, RES_STORE_READ_ONLY,
|
||||||
|
c->trans, 0, 0, 0, 0);
|
||||||
|
} else if (c->argv[0] && (c->argv[0] != QEMU_STORAGE_ID)) {
|
||||||
|
/* First parameter points to storage id or is 0 */
|
||||||
|
usb_mtp_queue_result(s, RES_STORE_NOT_AVAILABLE, c->trans,
|
||||||
|
0, 0, 0, 0);
|
||||||
|
} else if (c->argv[1] && !c->argv[0]) {
|
||||||
|
/* If second parameter is specified, first must also be specified */
|
||||||
|
usb_mtp_queue_result(s, RES_DESTINATION_UNSUPPORTED, c->trans,
|
||||||
|
0, 0, 0, 0);
|
||||||
|
} else {
|
||||||
|
uint32_t handle = c->argv[1];
|
||||||
|
if (handle == 0xFFFFFFFF || handle == 0) {
|
||||||
|
/* root object */
|
||||||
|
o = QTAILQ_FIRST(&s->objects);
|
||||||
|
} else {
|
||||||
|
o = usb_mtp_object_lookup(s, handle);
|
||||||
|
}
|
||||||
|
if (o == NULL) {
|
||||||
|
usb_mtp_queue_result(s, RES_INVALID_OBJECT_HANDLE, c->trans,
|
||||||
|
0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
if (o->format != FMT_ASSOCIATION) {
|
||||||
|
usb_mtp_queue_result(s, RES_INVALID_PARENT_OBJECT, c->trans,
|
||||||
|
0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (o) {
|
||||||
|
s->dataset.parent_handle = o->handle;
|
||||||
|
}
|
||||||
|
s->data_out = usb_mtp_data_alloc(c);
|
||||||
|
return;
|
||||||
case CMD_SEND_OBJECT:
|
case CMD_SEND_OBJECT:
|
||||||
if (!FLAG_SET(s, MTP_FLAG_WRITABLE)) {
|
if (!FLAG_SET(s, MTP_FLAG_WRITABLE)) {
|
||||||
usb_mtp_queue_result(s, RES_STORE_READ_ONLY,
|
usb_mtp_queue_result(s, RES_STORE_READ_ONLY,
|
||||||
@ -1497,6 +1564,19 @@ static void usb_mtp_cancel_packet(USBDevice *dev, USBPacket *p)
|
|||||||
fprintf(stderr, "%s\n", __func__);
|
fprintf(stderr, "%s\n", __func__);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void utf16_to_str(uint8_t len, uint16_t *arr, char *name)
|
||||||
|
{
|
||||||
|
int count;
|
||||||
|
wchar_t *wstr = g_new0(wchar_t, len);
|
||||||
|
|
||||||
|
for (count = 0; count < len; count++) {
|
||||||
|
wstr[count] = (wchar_t)arr[count];
|
||||||
|
}
|
||||||
|
|
||||||
|
wcstombs(name, wstr, len);
|
||||||
|
g_free(wstr);
|
||||||
|
}
|
||||||
|
|
||||||
static void usb_mtp_write_data(MTPState *s)
|
static void usb_mtp_write_data(MTPState *s)
|
||||||
{
|
{
|
||||||
MTPData *d = s->data_out;
|
MTPData *d = s->data_out;
|
||||||
@ -1570,6 +1650,45 @@ free:
|
|||||||
s->write_pending = false;
|
s->write_pending = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void usb_mtp_write_metadata(MTPState *s)
|
||||||
|
{
|
||||||
|
MTPData *d = s->data_out;
|
||||||
|
ObjectInfo *dataset = (ObjectInfo *)d->data;
|
||||||
|
char *filename = g_new0(char, dataset->length);
|
||||||
|
MTPObject *o;
|
||||||
|
MTPObject *p = usb_mtp_object_lookup(s, s->dataset.parent_handle);
|
||||||
|
uint32_t next_handle = s->next_handle;
|
||||||
|
|
||||||
|
assert(!s->write_pending);
|
||||||
|
|
||||||
|
utf16_to_str(dataset->length, dataset->filename, filename);
|
||||||
|
|
||||||
|
o = usb_mtp_object_lookup_name(p, filename, dataset->length);
|
||||||
|
if (o != NULL) {
|
||||||
|
next_handle = o->handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
s->dataset.filename = filename;
|
||||||
|
s->dataset.format = dataset->format;
|
||||||
|
s->dataset.size = dataset->size;
|
||||||
|
s->dataset.filename = filename;
|
||||||
|
s->write_pending = true;
|
||||||
|
|
||||||
|
if (s->dataset.format == FMT_ASSOCIATION) {
|
||||||
|
usb_mtp_write_data(s);
|
||||||
|
/* next_handle will be allocated to the newly created dir */
|
||||||
|
if (d->fd == -1) {
|
||||||
|
usb_mtp_queue_result(s, RES_STORE_FULL, d->trans,
|
||||||
|
0, 0, 0, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
d->fd = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
usb_mtp_queue_result(s, RES_OK, d->trans, 3, QEMU_STORAGE_ID,
|
||||||
|
s->dataset.parent_handle, next_handle);
|
||||||
|
}
|
||||||
|
|
||||||
static void usb_mtp_get_data(MTPState *s, mtp_container *container,
|
static void usb_mtp_get_data(MTPState *s, mtp_container *container,
|
||||||
USBPacket *p)
|
USBPacket *p)
|
||||||
{
|
{
|
||||||
@ -1594,6 +1713,19 @@ static void usb_mtp_get_data(MTPState *s, mtp_container *container,
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (d->code) {
|
switch (d->code) {
|
||||||
|
case CMD_SEND_OBJECT_INFO:
|
||||||
|
usb_packet_copy(p, d->data + d->offset, dlen);
|
||||||
|
d->offset += dlen;
|
||||||
|
if (d->offset == d->length) {
|
||||||
|
/* The operation might have already failed */
|
||||||
|
if (!s->result) {
|
||||||
|
usb_mtp_write_metadata(s);
|
||||||
|
}
|
||||||
|
usb_mtp_data_free(s->data_out);
|
||||||
|
s->data_out = NULL;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
case CMD_SEND_OBJECT:
|
case CMD_SEND_OBJECT:
|
||||||
usb_packet_copy(p, d->data + d->offset, dlen);
|
usb_packet_copy(p, d->data + d->offset, dlen);
|
||||||
d->offset += dlen;
|
d->offset += dlen;
|
||||||
|
Loading…
Reference in New Issue
Block a user