/* 
 * RIO Karma helper utility.
 */
#include <stdio.h>
#include <usb.h>
#include <string.h>
#include <dirent.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <pwd.h>

#define RIO_PROD      0x5210 
#define RIO_SPROD      "5210"
#define RIO_VENDOR    0x045a
#define RIO_SVEND      "045a"

#define RIO_DEV_DIR "/sys/bus/usb/devices/"
#define RIO_BIND "/sys/bus/usb/drivers/usb-storage/bind"

typedef struct usb_device usb_dev_t;

#ifndef LIBUSB_HAS_DETACH_KERNEL_DRIVER_NP
#define usb_detach_kernel_driver_np(a, b) (0)
#endif

static char *sysfs_read_dev(char *dev)
{
    DIR *dir, *dir2;
    struct dirent *ent, *ent2;
    char c;
    char dirname[64];
    char id[4];
    int dirlen, devlen, prod, vend, fd;

    dir = opendir(RIO_DEV_DIR);
    if (!dir)
        return NULL;

    while ( (ent = readdir(dir)) != NULL) {
        c = ent->d_name[0];
        if (c < '0' || c > '9')
            continue;

        dirlen = strlen(RIO_DEV_DIR);
        devlen = strlen(ent->d_name);
        memcpy(dirname, RIO_DEV_DIR, dirlen);
        memcpy(dirname+dirlen, ent->d_name, devlen);
        dirlen += devlen;
        dirname[dirlen++] = '/';
        dirname[dirlen] = '\0';
        dir2 = opendir(dirname);
        if (!dir2)
            continue;
        prod = vend = 0;
        while ( (ent2 = readdir(dir2)) != NULL) {
            if (memcmp(ent2->d_name, "idVendor",  8) == 0) {
                memcpy(dirname+dirlen, ent2->d_name, strlen(ent2->d_name)+1);
                fd = open(dirname, O_RDONLY);
                if (fd == -1)
                    goto next;
                read(fd, id, 4);
                close(fd);
                if (memcmp(id, RIO_SVEND, 4) != 0)
                    goto next;
                if (prod)
                    goto found;
                vend = 1;
            } else if (memcmp(ent2->d_name, "idProduct",  9) == 0) {
                memcpy(dirname+dirlen, ent2->d_name, strlen(ent2->d_name)+1);
                fd = open(dirname, O_RDONLY);
                if (fd == -1)
                    goto next;
                read(fd, id, 4);
                close(fd);
                if (memcmp(id, RIO_SPROD, 4) != 0)
                    goto next;
                if (vend)
                    goto found;
                prod = 1;
            }
        }
next:
        closedir(dir2);
        dir2 = NULL;
    }

    if (dir2)
        closedir(dir2);
    if (dir)
        closedir(dir);
    return NULL;

found:
    if (dir2)
        closedir(dir2);
    if (dir)
        closedir(dir);
    memcpy(dev, ent->d_name, devlen);
    memcpy(dev+devlen, ":1.0", devlen+5);
    return dev;
}

static void usage(int ret)
{
    printf("usage: karma_helper [-b] [-u] [-e] [-l] [-r] [-t] [-i] [-h]\n\n"
           "options:\n"
           "  -b  bind driver to device\n"
           "  -u  unbind driver from device\n"
           "  -e  enter storage mode\n"
           "  -l  leave storage mode\n"
           "  -r  reset device (leave \"Finished\" screen)\n"
           "  -t  update time\n"
           "  -i  retrieve device information string\n"
           "  -h  display usage info\n");
    exit(ret);
}

static usb_dev_t *get_device(int vendor, int product)
{
    struct usb_device *device;
    struct usb_bus *bus;

    for (bus = usb_busses; bus; bus=bus->next)
    {
        for (device = bus->devices; device; device=device->next)
        {
            if (device->descriptor.idVendor == vendor &&
                device->descriptor.idProduct == product)
            {
                return device;
            }
        }
    }
    return NULL;
}

int main(int argc, char **argv)
{
    int i, ret, seq, cmd, fd = 0, sent = 0;
    time_t sec = 0;
    char riop[] = "RIOP";
    char request[40];
    char response[512];
    usb_dev_t *device;
    usb_dev_handle *handle;
    int bind = 0;
    int unbind = 0;
    int info = 0;

    if (argc != 2 || *argv[1] != '-')
        usage(1);

    switch (*(argv[1]+1)) {
        case 'h':
            usage(0);
            break;
        case 'b':
            bind = 1;
            break;
        case 'u':
            unbind = 1;
            break;
        case 'e':
            cmd = 0x1;
            break;
        case 'l':
            cmd = 0x2;
            break;
        case 'r':
            cmd = 0xc;
            break;
        case 't':
#ifndef __APPLE__
            tzset();
            sec = time(NULL) - timezone + daylight * 3600;
            cmd = 0x7;
#endif
            break;
        case 'i':
            info = 1;
            cmd = 0x5;
            break;
        default:
            usage(1);
    }

    /* check for console permissions given by pam_console */

    if (getuid() != 0)
        fd = open("/var/run/console/console.lock", O_RDONLY);

    if (fd > 0) {
        char *name = response;
        struct passwd *pass;

        ret = read(fd, name, 64);
        close(fd);
        if (!ret)
            goto err;
        name[ret] = '\0';
        pass = getpwnam(name);
        if (!pass)
            goto err;
        if (pass->pw_uid == getuid())
            goto ok;
err:
        fprintf(stderr, "error: user does not have console ownership\n");
        return 1;
    }
ok:

    if (bind) {
        struct stat st;
        char *dev = request;
        char *bound = response;
        int len;

        fd = open(RIO_BIND, O_WRONLY);
        if (fd == -1) {
            fprintf(stderr, "error: unable to open " RIO_BIND "\n");
            return 1;
        }
        dev = sysfs_read_dev(dev);
        if (!dev) {
            fprintf(stderr, "error: unable to find karma\n");
            return 1;
        }
        len = strlen(RIO_BIND)-4;
        memcpy(bound, RIO_BIND, len);
        memcpy(bound+len, dev, strlen(dev)+1);
        ret = -1;
        for (i = 0; i < 100 && ret == -1; i++) {
            len = write(fd, dev, strlen(dev));
            ret = stat(bound, &st);
            usleep(10);
        }
        close(fd);

        return ret;
    }

    seq = 0x10;

    usb_init();

    usb_find_busses();
    usb_find_devices();

    if (!(device = get_device(RIO_VENDOR, RIO_PROD)))
    {
        fprintf(stderr, "Could not open device\n");
        exit(1);
    }

    handle = usb_open(device);

#ifdef __linux__
    if (unbind)
        return usb_detach_kernel_driver_np(handle, 0);
#endif

    if (usb_claim_interface(handle, 0) < 0)
    {
        perror("user.c");
        exit(1);
    }

    usb_resetep(handle, 0x02);
    usb_resetep(handle, 0x88);

    memset(request, 0, sizeof(request));
    memset(response, 0, sizeof(response));
    strcpy (request, riop);
    request[5] = cmd;
    request[6] = seq;
    if (sec)
        memcpy(request+8, &sec, sizeof(sec));

    usb_bulk_write(handle, 0x02, request, sizeof(request), 1000);
    usb_bulk_read(handle, 0x88, response, sizeof(response), 1000);

    for (i = 0; i < 100 && !sent; i++)
    {
        memset(request, 0, sizeof(request));
        memset(response, 0, sizeof(response));
        strcpy (request, riop);
        request[4] = (char)0x80;
        request[5] = 0;
        request[6] = seq;

        usb_bulk_write(handle, 0x02, request, sizeof(request), 1000);
        usb_bulk_read(handle, 0x88, response, sizeof(response), 1000);

        sent = (response[5] == request[6]);
        if (!sent)
            usleep(10);
        else if (info)
            printf("\n%s\n", &response[7]);
    }
    usb_release_interface(handle, 0);
    usb_close(handle);

    return 0;
}
