@ -47,6 +47,7 @@ enum mtp_code {
CMD_GET_OBJECT_INFO = 0x1008 ,
CMD_GET_OBJECT = 0x1009 ,
CMD_DELETE_OBJECT = 0x100b ,
CMD_SEND_OBJECT = 0x100d ,
CMD_GET_PARTIAL_OBJECT = 0x101b ,
CMD_GET_OBJECT_PROPS_SUPPORTED = 0x9801 ,
CMD_GET_OBJECT_PROP_DESC = 0x9802 ,
@ -63,9 +64,11 @@ enum mtp_code {
RES_INVALID_STORAGE_ID = 0x2008 ,
RES_INVALID_OBJECT_HANDLE = 0x2009 ,
RES_INVALID_OBJECT_FORMAT_CODE = 0x200b ,
RES_STORE_FULL = 0x200c ,
RES_STORE_READ_ONLY = 0x200e ,
RES_PARTIAL_DELETE = 0x2012 ,
RES_SPEC_BY_FORMAT_UNSUPPORTED = 0x2014 ,
RES_INVALID_OBJECTINFO = 0x2015 ,
RES_INVALID_PARENT_OBJECT = 0x201a ,
RES_INVALID_PARAMETER = 0x201d ,
RES_SESSION_ALREADY_OPEN = 0x201e ,
@ -183,6 +186,14 @@ struct MTPState {
int inotifyfd ;
QTAILQ_HEAD ( events , MTPMonEntry ) events ;
# endif
/* Responder is expecting a write operation */
bool write_pending ;
struct {
uint32_t parent_handle ;
uint16_t format ;
uint32_t size ;
char * filename ;
} dataset ;
} ;
# define TYPE_USB_MTP "usb-mtp"
@ -804,6 +815,7 @@ static MTPData *usb_mtp_get_device_info(MTPState *s, MTPControl *c)
CMD_GET_OBJECT_HANDLES ,
CMD_GET_OBJECT_INFO ,
CMD_DELETE_OBJECT ,
CMD_SEND_OBJECT ,
CMD_GET_OBJECT ,
CMD_GET_PARTIAL_OBJECT ,
CMD_GET_OBJECT_PROPS_SUPPORTED ,
@ -1378,6 +1390,19 @@ static void usb_mtp_command(MTPState *s, MTPControl *c)
nres = 1 ;
res0 = data_in - > length ;
break ;
case CMD_SEND_OBJECT :
if ( ! FLAG_SET ( s , MTP_FLAG_WRITABLE ) ) {
usb_mtp_queue_result ( s , RES_STORE_READ_ONLY ,
c - > trans , 0 , 0 , 0 , 0 ) ;
return ;
}
if ( ! s - > write_pending ) {
usb_mtp_queue_result ( s , RES_INVALID_OBJECTINFO ,
c - > trans , 0 , 0 , 0 , 0 ) ;
return ;
}
s - > data_out = usb_mtp_data_alloc ( c ) ;
return ;
case CMD_GET_OBJECT_PROPS_SUPPORTED :
if ( c - > argv [ 0 ] ! = FMT_UNDEFINED_OBJECT & &
c - > argv [ 0 ] ! = FMT_ASSOCIATION ) {
@ -1472,12 +1497,126 @@ static void usb_mtp_cancel_packet(USBDevice *dev, USBPacket *p)
fprintf ( stderr , " %s \n " , __func__ ) ;
}
static void usb_mtp_write_data ( MTPState * s )
{
MTPData * d = s - > data_out ;
MTPObject * parent =
usb_mtp_object_lookup ( s , s - > dataset . parent_handle ) ;
char * path = NULL ;
int rc = - 1 ;
mode_t mask = 0644 ;
assert ( d ! = NULL ) ;
if ( parent = = NULL | | ! s - > write_pending ) {
usb_mtp_queue_result ( s , RES_INVALID_OBJECTINFO , d - > trans ,
0 , 0 , 0 , 0 ) ;
return ;
}
if ( s - > dataset . filename ) {
path = g_strdup_printf ( " %s/%s " , parent - > path , s - > dataset . filename ) ;
if ( s - > dataset . format = = FMT_ASSOCIATION ) {
d - > fd = mkdir ( path , mask ) ;
goto free ;
}
if ( s - > dataset . size < d - > length ) {
usb_mtp_queue_result ( s , RES_STORE_FULL , d - > trans ,
0 , 0 , 0 , 0 ) ;
goto done ;
}
d - > fd = open ( path , O_CREAT | O_WRONLY , mask ) ;
if ( d - > fd = = - 1 ) {
usb_mtp_queue_result ( s , RES_STORE_FULL , d - > trans ,
0 , 0 , 0 , 0 ) ;
goto done ;
}
/*
* Return success if initiator sent 0 sized data
*/
if ( ! s - > dataset . size ) {
goto success ;
}
rc = write ( d - > fd , d - > data , s - > dataset . size ) ;
if ( rc = = - 1 ) {
usb_mtp_queue_result ( s , RES_STORE_FULL , d - > trans ,
0 , 0 , 0 , 0 ) ;
goto done ;
}
if ( rc ! = s - > dataset . size ) {
usb_mtp_queue_result ( s , RES_INCOMPLETE_TRANSFER , d - > trans ,
0 , 0 , 0 , 0 ) ;
goto done ;
}
}
success :
usb_mtp_queue_result ( s , RES_OK , d - > trans ,
0 , 0 , 0 , 0 ) ;
done :
/*
* The write dataset is kept around and freed only
* on success or if another write request comes in
*/
if ( d - > fd ! = - 1 ) {
close ( d - > fd ) ;
}
free :
g_free ( s - > dataset . filename ) ;
g_free ( path ) ;
s - > write_pending = false ;
}
static void usb_mtp_get_data ( MTPState * s , mtp_container * container ,
USBPacket * p )
{
MTPData * d = s - > data_out ;
uint64_t dlen ;
uint32_t data_len = p - > iov . size ;
if ( d - > first ) {
/* Total length of incoming data */
d - > length = cpu_to_le32 ( container - > length ) - sizeof ( mtp_container ) ;
/* Length of data in this packet */
data_len - = sizeof ( mtp_container ) ;
usb_mtp_realloc ( d , d - > length ) ;
d - > offset = 0 ;
d - > first = false ;
}
if ( d - > length - d - > offset > data_len ) {
dlen = data_len ;
} else {
dlen = d - > length - d - > offset ;
}
switch ( d - > code ) {
case CMD_SEND_OBJECT :
usb_packet_copy ( p , d - > data + d - > offset , dlen ) ;
d - > offset + = dlen ;
if ( d - > offset = = d - > length ) {
usb_mtp_write_data ( s ) ;
usb_mtp_data_free ( s - > data_out ) ;
s - > data_out = NULL ;
return ;
}
break ;
default :
p - > status = USB_RET_STALL ;
return ;
}
}
static void usb_mtp_handle_data ( USBDevice * dev , USBPacket * p )
{
MTPState * s = USB_MTP ( dev ) ;
MTPControl cmd ;
mtp_container container ;
uint32_t params [ 5 ] ;
uint16_t container_type ;
int i , rc ;
switch ( p - > ep - > nr ) {
@ -1567,8 +1706,13 @@ static void usb_mtp_handle_data(USBDevice *dev, USBPacket *p)
p - > status = USB_RET_STALL ;
return ;
}
usb_packet_copy ( p , & container , sizeof ( container ) ) ;
switch ( le16_to_cpu ( container . type ) ) {
if ( s - > data_out & & ! s - > data_out - > first ) {
container_type = TYPE_DATA ;
} else {
usb_packet_copy ( p , & container , sizeof ( container ) ) ;
container_type = le16_to_cpu ( container . type ) ;
}
switch ( container_type ) {
case TYPE_COMMAND :
if ( s - > data_in | | s - > data_out | | s - > result ) {
trace_usb_mtp_stall ( s - > dev . addr , " transaction inflight " ) ;
@ -1599,6 +1743,15 @@ static void usb_mtp_handle_data(USBDevice *dev, USBPacket *p)
( cmd . argc > 4 ) ? cmd . argv [ 4 ] : 0 ) ;
usb_mtp_command ( s , & cmd ) ;
break ;
case TYPE_DATA :
/* One of the previous transfers has already errored but the
* responder is still sending data associated with it
*/
if ( s - > result ! = NULL ) {
return ;
}
usb_mtp_get_data ( s , & container , p ) ;
break ;
default :
/* not needed as long as the mtp device is read-only */
p - > status = USB_RET_STALL ;