832 lines
17 KiB
C
832 lines
17 KiB
C
/**
|
|
* @file mtp.c
|
|
* @brief libmtp-backed storage access.
|
|
*/
|
|
|
|
#include "trainlog/mtp.h"
|
|
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include <libmtp.h>
|
|
|
|
static void mtp_copy_text(
|
|
char *output,
|
|
size_t capacity,
|
|
const char *value
|
|
)
|
|
{
|
|
if (output == NULL ||
|
|
capacity == 0U) {
|
|
return;
|
|
}
|
|
|
|
if (value == NULL) {
|
|
output[0] = '\0';
|
|
return;
|
|
}
|
|
|
|
(void)snprintf(
|
|
output,
|
|
capacity,
|
|
"%s",
|
|
value
|
|
);
|
|
}
|
|
|
|
static bool mtp_raw_device_matches(
|
|
const LIBMTP_raw_device_t *device,
|
|
unsigned int bus_number,
|
|
unsigned int device_number
|
|
)
|
|
{
|
|
if (device == NULL ||
|
|
device_number > UINT8_MAX) {
|
|
return false;
|
|
}
|
|
|
|
return
|
|
device->bus_location ==
|
|
(uint32_t)bus_number &&
|
|
device->devnum ==
|
|
(uint8_t)device_number;
|
|
}
|
|
|
|
static TrainlogStatus mtp_detect_raw_devices(
|
|
LIBMTP_raw_device_t **output_devices,
|
|
int *output_count
|
|
)
|
|
{
|
|
LIBMTP_error_number_t error;
|
|
|
|
if (output_devices == NULL ||
|
|
output_count == NULL) {
|
|
return
|
|
TRAINLOG_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
*output_devices = NULL;
|
|
*output_count = 0;
|
|
|
|
LIBMTP_Init();
|
|
|
|
error =
|
|
LIBMTP_Detect_Raw_Devices(
|
|
output_devices,
|
|
output_count
|
|
);
|
|
|
|
switch (error) {
|
|
case LIBMTP_ERROR_NONE:
|
|
return TRAINLOG_STATUS_OK;
|
|
|
|
case LIBMTP_ERROR_NO_DEVICE_ATTACHED:
|
|
return TRAINLOG_STATUS_NOT_FOUND;
|
|
|
|
case LIBMTP_ERROR_MEMORY_ALLOCATION:
|
|
return TRAINLOG_STATUS_SYSTEM_ERROR;
|
|
|
|
case LIBMTP_ERROR_GENERAL:
|
|
case LIBMTP_ERROR_PTP_LAYER:
|
|
case LIBMTP_ERROR_USB_LAYER:
|
|
case LIBMTP_ERROR_STORAGE_FULL:
|
|
case LIBMTP_ERROR_CONNECTING:
|
|
case LIBMTP_ERROR_CANCELLED:
|
|
default:
|
|
return TRAINLOG_STATUS_SYSTEM_ERROR;
|
|
}
|
|
}
|
|
|
|
TrainlogStatus trainlog_mtp_list_storages(
|
|
unsigned int bus_number,
|
|
unsigned int device_number,
|
|
TrainlogMtpStorage *output,
|
|
size_t capacity,
|
|
size_t *output_count
|
|
)
|
|
{
|
|
LIBMTP_raw_device_t *raw_devices = NULL;
|
|
LIBMTP_mtpdevice_t *device = NULL;
|
|
LIBMTP_devicestorage_t *storage;
|
|
TrainlogStatus status;
|
|
int raw_count = 0;
|
|
int index;
|
|
size_t discovered = 0U;
|
|
|
|
if (output_count == NULL ||
|
|
(output == NULL &&
|
|
capacity != 0U) ||
|
|
device_number > UINT8_MAX) {
|
|
return
|
|
TRAINLOG_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
*output_count = 0U;
|
|
|
|
status =
|
|
mtp_detect_raw_devices(
|
|
&raw_devices,
|
|
&raw_count
|
|
);
|
|
|
|
if (status != TRAINLOG_STATUS_OK) {
|
|
free(raw_devices);
|
|
return status;
|
|
}
|
|
|
|
for (index = 0;
|
|
index < raw_count;
|
|
++index) {
|
|
if (mtp_raw_device_matches(
|
|
&raw_devices[index],
|
|
bus_number,
|
|
device_number
|
|
)) {
|
|
device =
|
|
LIBMTP_Open_Raw_Device(
|
|
&raw_devices[index]
|
|
);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (device == NULL) {
|
|
free(raw_devices);
|
|
return TRAINLOG_STATUS_NOT_FOUND;
|
|
}
|
|
|
|
if (LIBMTP_Get_Storage(
|
|
device,
|
|
LIBMTP_STORAGE_SORTBY_NOTSORTED
|
|
) != 0) {
|
|
LIBMTP_Clear_Errorstack(device);
|
|
LIBMTP_Release_Device(device);
|
|
free(raw_devices);
|
|
|
|
return
|
|
TRAINLOG_STATUS_SYSTEM_ERROR;
|
|
}
|
|
|
|
storage = device->storage;
|
|
|
|
while (storage != NULL) {
|
|
if (output != NULL &&
|
|
discovered < capacity) {
|
|
TrainlogMtpStorage *item =
|
|
&output[discovered];
|
|
|
|
(void)memset(
|
|
item,
|
|
0,
|
|
sizeof(*item)
|
|
);
|
|
|
|
item->storage_id =
|
|
storage->id;
|
|
|
|
item->storage_type =
|
|
storage->StorageType;
|
|
|
|
item->filesystem_type =
|
|
storage->FilesystemType;
|
|
|
|
item->access_capability =
|
|
storage->AccessCapability;
|
|
|
|
item->max_capacity_bytes =
|
|
storage->MaxCapacity;
|
|
|
|
item->free_space_bytes =
|
|
storage->FreeSpaceInBytes;
|
|
|
|
item->free_space_objects =
|
|
storage->FreeSpaceInObjects;
|
|
|
|
mtp_copy_text(
|
|
item->description,
|
|
sizeof(item->description),
|
|
storage->StorageDescription
|
|
);
|
|
|
|
mtp_copy_text(
|
|
item->volume_identifier,
|
|
sizeof(item->volume_identifier),
|
|
storage->VolumeIdentifier
|
|
);
|
|
}
|
|
|
|
++discovered;
|
|
storage = storage->next;
|
|
}
|
|
|
|
*output_count =
|
|
discovered;
|
|
|
|
if (output != NULL &&
|
|
discovered > capacity) {
|
|
status =
|
|
TRAINLOG_STATUS_INVALID_ARGUMENT;
|
|
} else {
|
|
status =
|
|
TRAINLOG_STATUS_OK;
|
|
}
|
|
|
|
LIBMTP_Release_Device(device);
|
|
free(raw_devices);
|
|
|
|
return status;
|
|
}
|
|
|
|
static TrainlogStatus mtp_open_exact_device(
|
|
unsigned int bus_number,
|
|
unsigned int device_number,
|
|
LIBMTP_mtpdevice_t **output_device
|
|
)
|
|
{
|
|
LIBMTP_raw_device_t *raw_devices = NULL;
|
|
LIBMTP_mtpdevice_t *device = NULL;
|
|
TrainlogStatus status;
|
|
int raw_count = 0;
|
|
int index;
|
|
|
|
if (output_device == NULL ||
|
|
device_number > UINT8_MAX) {
|
|
return TRAINLOG_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
*output_device = NULL;
|
|
|
|
status =
|
|
mtp_detect_raw_devices(
|
|
&raw_devices,
|
|
&raw_count
|
|
);
|
|
|
|
if (status != TRAINLOG_STATUS_OK) {
|
|
free(raw_devices);
|
|
return status;
|
|
}
|
|
|
|
for (index = 0;
|
|
index < raw_count;
|
|
++index) {
|
|
if (mtp_raw_device_matches(
|
|
&raw_devices[index],
|
|
bus_number,
|
|
device_number
|
|
)) {
|
|
device =
|
|
LIBMTP_Open_Raw_Device(
|
|
&raw_devices[index]
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
|
|
free(raw_devices);
|
|
|
|
if (device == NULL) {
|
|
return TRAINLOG_STATUS_NOT_FOUND;
|
|
}
|
|
|
|
*output_device = device;
|
|
|
|
return TRAINLOG_STATUS_OK;
|
|
}
|
|
|
|
static TrainlogStatus mtp_open_exact_device_uncached(
|
|
unsigned int bus_number,
|
|
unsigned int device_number,
|
|
LIBMTP_mtpdevice_t **output_device
|
|
)
|
|
{
|
|
LIBMTP_raw_device_t *raw_devices = NULL;
|
|
LIBMTP_mtpdevice_t *device = NULL;
|
|
TrainlogStatus status;
|
|
int raw_count = 0;
|
|
int index;
|
|
|
|
if (output_device == NULL ||
|
|
device_number > UINT8_MAX) {
|
|
return TRAINLOG_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
*output_device = NULL;
|
|
|
|
status =
|
|
mtp_detect_raw_devices(
|
|
&raw_devices,
|
|
&raw_count
|
|
);
|
|
|
|
if (status != TRAINLOG_STATUS_OK) {
|
|
free(raw_devices);
|
|
return status;
|
|
}
|
|
|
|
for (index = 0;
|
|
index < raw_count;
|
|
++index) {
|
|
if (mtp_raw_device_matches(
|
|
&raw_devices[index],
|
|
bus_number,
|
|
device_number
|
|
)) {
|
|
device =
|
|
LIBMTP_Open_Raw_Device_Uncached(
|
|
&raw_devices[index]
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
|
|
free(raw_devices);
|
|
|
|
if (device == NULL) {
|
|
return TRAINLOG_STATUS_NOT_FOUND;
|
|
}
|
|
|
|
*output_device = device;
|
|
|
|
return TRAINLOG_STATUS_OK;
|
|
}
|
|
|
|
static LIBMTP_folder_t *mtp_find_root_folder(
|
|
LIBMTP_folder_t *folders,
|
|
uint32_t storage_id,
|
|
const char *folder_name
|
|
)
|
|
{
|
|
LIBMTP_folder_t *folder;
|
|
|
|
if (folder_name == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
for (folder = folders;
|
|
folder != NULL;
|
|
folder = folder->sibling) {
|
|
if (folder->storage_id == storage_id &&
|
|
folder->name != NULL &&
|
|
strcmp(
|
|
folder->name,
|
|
folder_name
|
|
) == 0) {
|
|
return folder;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static char *mtp_duplicate_text(
|
|
const char *text
|
|
)
|
|
{
|
|
size_t length;
|
|
char *copy;
|
|
|
|
if (text == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
length = strlen(text);
|
|
|
|
copy =
|
|
malloc(
|
|
length + 1U
|
|
);
|
|
|
|
if (copy == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
(void)memcpy(
|
|
copy,
|
|
text,
|
|
length + 1U
|
|
);
|
|
|
|
return copy;
|
|
}
|
|
|
|
TrainlogStatus trainlog_mtp_ensure_root_folder(
|
|
unsigned int bus_number,
|
|
unsigned int device_number,
|
|
uint32_t storage_id,
|
|
const char *folder_name,
|
|
uint32_t *output_folder_id,
|
|
bool *output_created
|
|
)
|
|
{
|
|
LIBMTP_mtpdevice_t *device = NULL;
|
|
LIBMTP_folder_t *folders = NULL;
|
|
LIBMTP_folder_t *existing;
|
|
TrainlogStatus status;
|
|
uint32_t folder_id;
|
|
char mutable_name[256];
|
|
|
|
if (folder_name == NULL ||
|
|
folder_name[0] == '\0' ||
|
|
output_folder_id == NULL ||
|
|
output_created == NULL ||
|
|
strlen(folder_name) >= sizeof(mutable_name)) {
|
|
return TRAINLOG_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
*output_folder_id = 0U;
|
|
*output_created = false;
|
|
|
|
status =
|
|
mtp_open_exact_device(
|
|
bus_number,
|
|
device_number,
|
|
&device
|
|
);
|
|
|
|
if (status != TRAINLOG_STATUS_OK) {
|
|
return status;
|
|
}
|
|
|
|
folders =
|
|
LIBMTP_Get_Folder_List_For_Storage(
|
|
device,
|
|
storage_id
|
|
);
|
|
|
|
existing =
|
|
mtp_find_root_folder(
|
|
folders,
|
|
storage_id,
|
|
folder_name
|
|
);
|
|
|
|
if (existing != NULL) {
|
|
*output_folder_id =
|
|
existing->folder_id;
|
|
|
|
if (folders != NULL) {
|
|
LIBMTP_destroy_folder_t(folders);
|
|
}
|
|
|
|
LIBMTP_Release_Device(device);
|
|
|
|
return TRAINLOG_STATUS_OK;
|
|
}
|
|
|
|
(void)snprintf(
|
|
mutable_name,
|
|
sizeof(mutable_name),
|
|
"%s",
|
|
folder_name
|
|
);
|
|
|
|
folder_id =
|
|
LIBMTP_Create_Folder(
|
|
device,
|
|
mutable_name,
|
|
UINT32_MAX,
|
|
storage_id
|
|
);
|
|
|
|
if (folders != NULL) {
|
|
LIBMTP_destroy_folder_t(folders);
|
|
}
|
|
|
|
if (folder_id == 0U) {
|
|
LIBMTP_Clear_Errorstack(device);
|
|
LIBMTP_Release_Device(device);
|
|
return TRAINLOG_STATUS_SYSTEM_ERROR;
|
|
}
|
|
|
|
*output_folder_id = folder_id;
|
|
*output_created = true;
|
|
|
|
LIBMTP_Release_Device(device);
|
|
|
|
return TRAINLOG_STATUS_OK;
|
|
}
|
|
|
|
TrainlogStatus trainlog_mtp_send_text_file(
|
|
unsigned int bus_number,
|
|
unsigned int device_number,
|
|
uint32_t storage_id,
|
|
uint32_t parent_folder_id,
|
|
const char *local_path,
|
|
const char *remote_filename,
|
|
uint32_t *output_item_id
|
|
)
|
|
{
|
|
struct stat file_stat;
|
|
LIBMTP_mtpdevice_t *device = NULL;
|
|
LIBMTP_file_t *metadata = NULL;
|
|
TrainlogStatus status;
|
|
int rc;
|
|
|
|
if (local_path == NULL ||
|
|
local_path[0] == '\0' ||
|
|
remote_filename == NULL ||
|
|
remote_filename[0] == '\0' ||
|
|
output_item_id == NULL) {
|
|
return TRAINLOG_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
*output_item_id = 0U;
|
|
|
|
if (stat(
|
|
local_path,
|
|
&file_stat
|
|
) != 0 ||
|
|
file_stat.st_size < 0) {
|
|
return TRAINLOG_STATUS_SYSTEM_ERROR;
|
|
}
|
|
|
|
status =
|
|
mtp_open_exact_device(
|
|
bus_number,
|
|
device_number,
|
|
&device
|
|
);
|
|
|
|
if (status != TRAINLOG_STATUS_OK) {
|
|
return status;
|
|
}
|
|
|
|
metadata =
|
|
LIBMTP_new_file_t();
|
|
|
|
if (metadata == NULL) {
|
|
LIBMTP_Release_Device(device);
|
|
return TRAINLOG_STATUS_SYSTEM_ERROR;
|
|
}
|
|
|
|
metadata->filename =
|
|
mtp_duplicate_text(
|
|
remote_filename
|
|
);
|
|
|
|
if (metadata->filename == NULL) {
|
|
LIBMTP_destroy_file_t(metadata);
|
|
LIBMTP_Release_Device(device);
|
|
return TRAINLOG_STATUS_SYSTEM_ERROR;
|
|
}
|
|
|
|
metadata->parent_id =
|
|
parent_folder_id;
|
|
|
|
metadata->storage_id =
|
|
storage_id;
|
|
|
|
metadata->filesize =
|
|
(uint64_t)file_stat.st_size;
|
|
|
|
metadata->filetype =
|
|
LIBMTP_FILETYPE_TEXT;
|
|
|
|
rc =
|
|
LIBMTP_Send_File_From_File(
|
|
device,
|
|
local_path,
|
|
metadata,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
if (rc != 0) {
|
|
LIBMTP_Clear_Errorstack(device);
|
|
LIBMTP_destroy_file_t(metadata);
|
|
LIBMTP_Release_Device(device);
|
|
return TRAINLOG_STATUS_SYSTEM_ERROR;
|
|
}
|
|
|
|
*output_item_id =
|
|
metadata->item_id;
|
|
|
|
LIBMTP_destroy_file_t(metadata);
|
|
LIBMTP_Release_Device(device);
|
|
|
|
return TRAINLOG_STATUS_OK;
|
|
}
|
|
|
|
TrainlogStatus trainlog_mtp_list_folder(
|
|
unsigned int bus_number,
|
|
unsigned int device_number,
|
|
uint32_t storage_id,
|
|
uint32_t parent_folder_id,
|
|
TrainlogMtpEntry *output,
|
|
size_t capacity,
|
|
size_t *output_count
|
|
)
|
|
{
|
|
LIBMTP_mtpdevice_t *device = NULL;
|
|
LIBMTP_file_t *items = NULL;
|
|
LIBMTP_file_t *item;
|
|
TrainlogStatus status;
|
|
size_t discovered = 0U;
|
|
|
|
if (output_count == NULL ||
|
|
(output == NULL &&
|
|
capacity != 0U)) {
|
|
return
|
|
TRAINLOG_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
*output_count = 0U;
|
|
|
|
status =
|
|
mtp_open_exact_device_uncached(
|
|
bus_number,
|
|
device_number,
|
|
&device
|
|
);
|
|
|
|
if (status != TRAINLOG_STATUS_OK) {
|
|
return status;
|
|
}
|
|
|
|
items =
|
|
LIBMTP_Get_Files_And_Folders(
|
|
device,
|
|
storage_id,
|
|
parent_folder_id
|
|
);
|
|
|
|
for (item = items;
|
|
item != NULL;
|
|
item = item->next) {
|
|
if (output != NULL &&
|
|
discovered < capacity) {
|
|
TrainlogMtpEntry *entry =
|
|
&output[discovered];
|
|
|
|
(void)memset(
|
|
entry,
|
|
0,
|
|
sizeof(*entry)
|
|
);
|
|
|
|
entry->item_id =
|
|
item->item_id;
|
|
|
|
entry->parent_id =
|
|
item->parent_id;
|
|
|
|
entry->storage_id =
|
|
item->storage_id;
|
|
|
|
entry->size_bytes =
|
|
item->filesize;
|
|
|
|
/* MTP exposes this value with file metadata. Keep the transport
|
|
* timestamp separate from the filename: callers use it only to
|
|
* choose the newest immutable exchange artifact, never as a
|
|
* domain timestamp. A zero value means the device did not supply
|
|
* a usable modification time. */
|
|
if (item->modificationdate > 0) {
|
|
entry->modification_unix_seconds =
|
|
(uint64_t)item->modificationdate;
|
|
}
|
|
|
|
entry->folder =
|
|
item->filetype ==
|
|
LIBMTP_FILETYPE_FOLDER;
|
|
|
|
mtp_copy_text(
|
|
entry->name,
|
|
sizeof(entry->name),
|
|
item->filename
|
|
);
|
|
}
|
|
|
|
++discovered;
|
|
}
|
|
|
|
if (items != NULL) {
|
|
LIBMTP_destroy_file_t(
|
|
items
|
|
);
|
|
}
|
|
|
|
LIBMTP_Release_Device(
|
|
device
|
|
);
|
|
|
|
*output_count =
|
|
discovered;
|
|
|
|
if (output != NULL &&
|
|
discovered > capacity) {
|
|
return
|
|
TRAINLOG_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
return
|
|
TRAINLOG_STATUS_OK;
|
|
}
|
|
|
|
TrainlogStatus trainlog_mtp_receive_file(
|
|
unsigned int bus_number,
|
|
unsigned int device_number,
|
|
uint32_t item_id,
|
|
const char *local_path
|
|
)
|
|
{
|
|
LIBMTP_mtpdevice_t *device = NULL;
|
|
TrainlogStatus status;
|
|
int rc;
|
|
|
|
if (item_id == 0U ||
|
|
local_path == NULL ||
|
|
local_path[0] == '\0') {
|
|
return
|
|
TRAINLOG_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
status =
|
|
mtp_open_exact_device_uncached(
|
|
bus_number,
|
|
device_number,
|
|
&device
|
|
);
|
|
|
|
if (status != TRAINLOG_STATUS_OK) {
|
|
return status;
|
|
}
|
|
|
|
rc =
|
|
LIBMTP_Get_File_To_File(
|
|
device,
|
|
item_id,
|
|
local_path,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
if (rc != 0) {
|
|
LIBMTP_Clear_Errorstack(
|
|
device
|
|
);
|
|
|
|
LIBMTP_Release_Device(
|
|
device
|
|
);
|
|
|
|
return
|
|
TRAINLOG_STATUS_SYSTEM_ERROR;
|
|
}
|
|
|
|
LIBMTP_Release_Device(
|
|
device
|
|
);
|
|
|
|
return
|
|
TRAINLOG_STATUS_OK;
|
|
}
|
|
|
|
TrainlogStatus trainlog_mtp_delete_object(
|
|
unsigned int bus_number,
|
|
unsigned int device_number,
|
|
uint32_t item_id
|
|
)
|
|
{
|
|
LIBMTP_mtpdevice_t *device = NULL;
|
|
TrainlogStatus status;
|
|
int rc;
|
|
|
|
if (item_id == 0U) {
|
|
return TRAINLOG_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
|
|
status =
|
|
mtp_open_exact_device(
|
|
bus_number,
|
|
device_number,
|
|
&device
|
|
);
|
|
|
|
if (status != TRAINLOG_STATUS_OK) {
|
|
return status;
|
|
}
|
|
|
|
rc =
|
|
LIBMTP_Delete_Object(
|
|
device,
|
|
item_id
|
|
);
|
|
|
|
if (rc != 0) {
|
|
LIBMTP_Clear_Errorstack(device);
|
|
LIBMTP_Release_Device(device);
|
|
|
|
return TRAINLOG_STATUS_SYSTEM_ERROR;
|
|
}
|
|
|
|
LIBMTP_Release_Device(device);
|
|
|
|
return TRAINLOG_STATUS_OK;
|
|
}
|