trainlog/tui/src/usb.c

386 lines
6.6 KiB
C

#include "trainlog/usb.h"
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libudev.h>
static const char *usb_property_prefer(
struct udev_device *device,
const char *preferred,
const char *fallback
)
{
const char *value =
udev_device_get_property_value(
device,
preferred
);
if (value != NULL &&
value[0] != '\0') {
return value;
}
return
udev_device_get_property_value(
device,
fallback
);
}
static void usb_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 usb_parse_unsigned(
const char *text,
int base,
unsigned int *output
)
{
char *end = NULL;
unsigned long value;
if (text == NULL ||
output == NULL ||
text[0] == '\0') {
return false;
}
errno = 0;
value =
strtoul(
text,
&end,
base
);
if (errno != 0 ||
end == text ||
*end != '\0' ||
value > (unsigned long)UINT_MAX) {
return false;
}
*output =
(unsigned int)value;
return true;
}
static bool usb_device_is_physical_mtp(
struct udev_device *device
)
{
const char *devtype;
const char *mtp;
if (device == NULL) {
return false;
}
devtype =
udev_device_get_devtype(
device
);
if (devtype == NULL ||
strcmp(
devtype,
"usb_device"
) != 0) {
return false;
}
mtp =
udev_device_get_property_value(
device,
"ID_MTP_DEVICE"
);
return
mtp != NULL &&
strcmp(mtp, "1") == 0;
}
static void usb_fill_device(
struct udev_device *device,
TrainlogUsbDevice *output
)
{
const char *vendor;
const char *model;
const char *serial;
const char *vendor_id;
const char *product_id;
const char *bus;
const char *devnum;
const char *syspath;
(void)memset(
output,
0,
sizeof(*output)
);
output->mtp = true;
vendor =
usb_property_prefer(
device,
"ID_VENDOR_FROM_DATABASE",
"ID_VENDOR"
);
model =
usb_property_prefer(
device,
"ID_MODEL_FROM_DATABASE",
"ID_MODEL"
);
serial =
udev_device_get_property_value(
device,
"ID_SERIAL_SHORT"
);
vendor_id =
udev_device_get_property_value(
device,
"ID_VENDOR_ID"
);
product_id =
udev_device_get_property_value(
device,
"ID_MODEL_ID"
);
bus =
udev_device_get_property_value(
device,
"BUSNUM"
);
devnum =
udev_device_get_property_value(
device,
"DEVNUM"
);
if (bus == NULL) {
bus =
udev_device_get_sysattr_value(
device,
"busnum"
);
}
if (devnum == NULL) {
devnum =
udev_device_get_sysattr_value(
device,
"devnum"
);
}
syspath =
udev_device_get_syspath(
device
);
usb_copy_text(
output->vendor,
sizeof(output->vendor),
vendor
);
usb_copy_text(
output->model,
sizeof(output->model),
model
);
usb_copy_text(
output->serial,
sizeof(output->serial),
serial
);
usb_copy_text(
output->syspath,
sizeof(output->syspath),
syspath
);
(void)usb_parse_unsigned(
vendor_id,
16,
&output->vendor_id
);
(void)usb_parse_unsigned(
product_id,
16,
&output->product_id
);
(void)usb_parse_unsigned(
bus,
10,
&output->bus_number
);
(void)usb_parse_unsigned(
devnum,
10,
&output->device_number
);
}
TrainlogStatus trainlog_usb_list_mtp_devices(
TrainlogUsbDevice *output,
size_t capacity,
size_t *output_count
)
{
struct udev *udev = NULL;
struct udev_enumerate *enumerate = NULL;
struct udev_list_entry *devices;
struct udev_list_entry *entry;
size_t discovered = 0U;
TrainlogStatus status =
TRAINLOG_STATUS_OK;
if (output_count == NULL ||
(output == NULL &&
capacity != 0U)) {
return
TRAINLOG_STATUS_INVALID_ARGUMENT;
}
*output_count = 0U;
udev = udev_new();
if (udev == NULL) {
return
TRAINLOG_STATUS_SYSTEM_ERROR;
}
enumerate =
udev_enumerate_new(udev);
if (enumerate == NULL) {
udev_unref(udev);
return
TRAINLOG_STATUS_SYSTEM_ERROR;
}
if (udev_enumerate_add_match_subsystem(
enumerate,
"usb"
) < 0 ||
udev_enumerate_scan_devices(
enumerate
) < 0) {
status =
TRAINLOG_STATUS_SYSTEM_ERROR;
goto cleanup;
}
devices =
udev_enumerate_get_list_entry(
enumerate
);
udev_list_entry_foreach(
entry,
devices
) {
const char *syspath =
udev_list_entry_get_name(
entry
);
struct udev_device *device;
if (syspath == NULL) {
continue;
}
device =
udev_device_new_from_syspath(
udev,
syspath
);
if (device == NULL) {
continue;
}
if (usb_device_is_physical_mtp(
device
)) {
if (output != NULL &&
discovered < capacity) {
usb_fill_device(
device,
&output[discovered]
);
}
++discovered;
}
udev_device_unref(
device
);
}
*output_count =
discovered;
if (output != NULL &&
discovered > capacity) {
status =
TRAINLOG_STATUS_INVALID_ARGUMENT;
}
cleanup:
udev_enumerate_unref(
enumerate
);
udev_unref(
udev
);
return status;
}