fxsdk/fxlink/main.c

304 lines
8.9 KiB
C

#include "config.h"
#include "fxlink.h"
#include "util.h"
#include "properties.h"
#include "filter.h"
#include "usb.h"
#include <libusb.h>
#include <getopt.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <time.h>
int main_test(libusb_device *device, libusb_context *context);
static const char *help_string =
"usage: %1$s -l [options...]\n"
" %1$s -b [options...]\n"
" %1$s -s [options...] <FILES...>\n"
" %1$s --test\n"
"\n"
"fxlink interacts with CASIO calculators of the fx-9860G and fx-CG 50 series\n"
"over the USB port, through mass storage and custom USB protocols. Depending\n"
"on the mode, fxlink uses libusb (for discovery and USB communication)or\n"
"the UDisks2 library (to mount and use Mass Storage devices).\n"
"\n"
"Operating modes:\n"
" -l, --list List detected calculators on the USB ports (libusb)\n"
" -b, --blocks List detected Mass Storage filesystems (udisks2)\n"
" -s, --send Send a file to a Mass Storage calculator (udisks2)\n"
" -i, --interactive Interactive messaging with a gint add-in (libusb)\n"
"\n"
"General options:\n"
" -w DELAY Wait up to this many seconds for a calculator to\n"
" connect. If DELAY is unspecified, wait indefinitely.\n"
" -f FILTER Filter which calculators can be detected and used\n"
" --libusb-log=LEVEL libusb log level: NONE, ERROR, WARNING, INFO, DEBUG\n"
" --fxlink-log=file Log text data into a logfile called file. If file is\n"
" missing, a generic filename will be created.\n"
" -q, --quiet Activate quiet mode, i.e. minimum verbosity and very\n"
" limited decorations (no extra messages).\n"
" -u, --unmount Force unmount disk at the end of operations, even if\n"
" not mounted here\n"
"\n"
"Device filters:\n"
" A device filter is a comma-separated list of properties that a device has\n"
" to match in order to be listed or used, such as 'p7,serial=00000001'.\n"
" Several filters can be separated with a semicolon, in which case a device\n"
" will be considered as long as it matches one of the filters. For example,\n"
" 'p7 ; mass_storage,serial=IGQcGRe9'.\n"
"\n"
" The following properties are defined; the libraries in which each can be\n"
" detected and used is indicated in brackets.\n"
" p7 Matches Protocol 7 calculators (all the FX models\n"
" except the G-III). [libusb, udisks2]\n"
" mass_storage Matches Mass Storage calculators (the CG series and\n"
" the G-III). [libusb, udisks2]\n"
" series_cg Matches CG-series calculators. [udisks2]\n"
" series_g3 Matches G-III series calculators. [udisks2]\n"
" serial_number=ID Matches this specific serial number. Requires write\n"
" access to the device in libusb. [libusb, udisks2]\n";
bool silentmode = false;
bool loginfile = false;
char *userlogfilename = NULL;
bool forceunmount = false;
int main(int argc, char **argv)
{
int rc=1, mode=0, error=0, option=0, loglevel=LIBUSB_LOG_LEVEL_ERROR;
delay_t delay = delay_seconds(0);
filter_t *filter = NULL;
//---
// Command-line argument parsing
//---
enum { LIBUSB_LOG=1 };
enum { LOG_IN_FILE=2 };
const struct option longs[] = {
{ "help", no_argument, NULL, 'h' },
{ "list", no_argument, NULL, 'l' },
{ "blocks", no_argument, NULL, 'b' },
{ "send", no_argument, NULL, 's' },
{ "interactive", no_argument, NULL, 'i' },
{ "libusb-log", required_argument, NULL, LIBUSB_LOG },
{ "quiet", no_argument, NULL, 'q' },
{ "fxlink-log", optional_argument, NULL, LOG_IN_FILE },
{ "unmount", no_argument, NULL, 'u' },
};
while(option >= 0 && option != '?')
switch((option = getopt_long(argc, argv, "hlbsiquf:w::", longs, NULL)))
{
case 'h':
fprintf(stderr, help_string, argv[0]);
return 0;
case 'l':
case 'b':
case 's':
case 'i':
mode = option;
break;
case LIBUSB_LOG:
if(!strcmp(optarg, "NONE"))
loglevel = LIBUSB_LOG_LEVEL_NONE;
else if(!strcmp(optarg, "ERROR"))
loglevel = LIBUSB_LOG_LEVEL_ERROR;
else if(!strcmp(optarg, "WARNING"))
loglevel = LIBUSB_LOG_LEVEL_WARNING;
else if(!strcmp(optarg, "INFO"))
loglevel = LIBUSB_LOG_LEVEL_INFO;
else if(!strcmp(optarg, "DEBUG"))
loglevel = LIBUSB_LOG_LEVEL_DEBUG;
else fprintf(stderr, "warning: ignoring log level '%s'; should be "
"NONE, ERROR, WARNING, INFO or DEBUG\n", optarg);
break;
case 'q':
printf("Enabling quiet mode (i.e. minimum verbosity)\n");
silentmode = true;
break;
case 'u':
printf("Force unmount the disk at the end.\n");
forceunmount = true;
break;
case LOG_IN_FILE:
printf("Enabling Log in File Mode\n");
loginfile = true;
if (optarg) {
asprintf( &userlogfilename, "%s", optarg );
}
else {
time_t time_raw;
struct tm time_bd;
time(&time_raw);
localtime_r(&time_raw, &time_bd);
asprintf( &userlogfilename, "./fxlink-logfile-%04d.%02d.%02d-%02dh%02d.log",
time_bd.tm_year + 1900, time_bd.tm_mon + 1, time_bd.tm_mday,
time_bd.tm_hour, time_bd.tm_min );
}
printf("Log File will be : %s \n", userlogfilename );
break;
case 'w':
if(!optarg) {
delay = delay_infinite();
break;
}
char *end;
int seconds = strtol(optarg, &end, 10);
if(seconds < 0 || *end != 0) {
error = err("invalid delay '%s'\n", optarg);
break;
}
delay = delay_seconds(seconds);
break;
case 'f':
filter = filter_parse(optarg);
break;
case '?':
error = 1;
}
if(mode == 's' && optind == argc)
error = err("send mode requires additional arguments (file names)");
/* No arguments or bad arguments */
if(error)
return 1;
if(!mode) {
fprintf(stderr, help_string, argv[0]);
return 1;
}
//---
// libusb initialization
//---
libusb_context *context = NULL;
/* Initialize libusb for corresponding modes */
if(mode == 'l' || mode == 'i') {
if((rc = libusb_init(&context)))
return libusb_err(rc, "error initializing libusb");
libusb_set_option(context, LIBUSB_OPTION_LOG_LEVEL, loglevel);
}
//---
// Main functions
//---
if(mode == 'l') {
rc = main_list(filter, &delay, context);
}
else if(mode == 'b') {
#ifndef FXLINK_DISABLE_UDISKS2
rc = main_blocks(filter, &delay);
#else
rc = err("this fxlink was built without UDisks2; -b is disabled");
#endif
}
else if(mode == 's') {
#ifndef FXLINK_DISABLE_UDISKS2
rc = main_send(filter, &delay, argv + optind);
#else
rc = err("this fxlink was built without UDisks2; -s is disabled");
#endif
}
else if(mode == 'i') {
rc = main_interactive(filter, &delay, context);
}
if(context) libusb_exit(context);
return rc;
}
//---
// Device list
//---
int main_list(filter_t *filter, delay_t *delay, libusb_context *context)
{
/* Wait for a device to be connected */
filter_clean_libusb(filter);
usb_unique_wait(filter, delay, context, NULL);
int total_devices = 0;
bool error;
for_libusb_devices(it, context, &error) {
if(!filter_match(&it.props, filter)) continue;
if(total_devices > 0) printf("\n");
if(it.dc.idProduct == 0x6101)
printf("fx-9860G series (Protocol 7) calculator\n");
else if(it.dc.idProduct == 0x6102)
printf("fx-CG or G-III series (USB Mass Storage) calculator\n");
else
printf("Unknown calculator (idProduct: %04x)\n", it.dc.idProduct);
printf(" Device location: Bus %d, Port %d, Device %d\n",
libusb_get_bus_number(it.dev),
libusb_get_port_number(it.dev),
libusb_get_device_address(it.dev));
printf(" Identification: idVendor: %04x, idProduct: %04x\n",
it.dc.idVendor, it.dc.idProduct);
/* FIXME: This assumes a short path (no hub or dual-device) */
printf(" Guessed sysfs path: /sys/bus/usb/devices/%d-%d/\n",
libusb_get_bus_number(it.dev),
libusb_get_port_number(it.dev));
char *serial = it.dh ? usb_serial_number(it.dh) : NULL;
if(serial)
printf(" Serial number: %s\n", serial);
free(serial);
printf(" Properties: ");
properties_print(stdout, &it.props);
printf("\n");
total_devices++;
}
if(!error && !total_devices)
printf("No%s device found.\n", filter ? " matching" : "");
return 0;
}
//---
// libudev tests; work but not useful yet
//---
#if 0
#include <libudev.h>
int main_udev_test(libusb_device *dev)
{
struct udev *udev = NULL;
struct udev_device *udev_device = NULL;
udev = udev_new();
if(!udev) return err("cannot create udev context");
static char sys_path[128];
sprintf(sys_path, "/sys/bus/usb/devices/%d-%d",
libusb_get_bus_number(dev),
libusb_get_port_number(dev));
udev_device = udev_device_new_from_syspath(udev, sys_path);
if(!udev_device) {
udev_unref(udev);
return err("cannot get udev device for %s", sys_path);
}
printf("Device number: %ld\n", udev_device_get_devnum(udev_device));
printf("Device devnode: %s\n", udev_device_get_devnode(udev_device));
if(udev) udev_unref(udev);
return 0;
}
#endif