/*
 *   Razer daemon
 *   Daemon to keep track of Razer device state.
 *
 *   Copyright (C) 2008-2016 Michael Buesch <m@bues.ch>
 *
 *   This program is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU General Public License
 *   as published by the Free Software Foundation; either version 2
 *   of the License, or (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 */

#include "librazer.h"

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <stdint.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <getopt.h>
#include <syslog.h>
#include <stdarg.h>
#include <stdbool.h>

#ifdef __DragonFly__
#include <sys/endian.h>
#else
#include <byteswap.h>
#endif


#undef min
#undef max
#undef offsetof
#define offsetof(type, member)	((size_t)&((type *)0)->member)
#define min(x, y)		({ __typeof__(x) __x = (x); \
				   __typeof__(y) __y = (y); \
				   __x < __y ? __x : __y; })
#define max(x, y)		({ __typeof__(x) __x = (x); \
				   __typeof__(y) __y = (y); \
				   __x > __y ? __x : __y; })
#define ARRAY_SIZE(x)		(sizeof(x) / sizeof((x)[0]))

#define _packed			__attribute__((__packed__))

enum {
	LOGLEVEL_ERROR = 0,
	LOGLEVEL_WARNING,
	LOGLEVEL_INFO,
	LOGLEVEL_DEBUG,
};

struct commandline_args {
	bool background;
	const char *configfile;
	const char *pidfile;
	int loglevel;
	bool force;
	bool no_profile_emu;
} cmdargs = {
#ifdef DEBUG
	.loglevel	= LOGLEVEL_DEBUG,
#else
	.loglevel	= LOGLEVEL_INFO,
#endif
};


#define RUNDIR			"/run"
#define RUNDIR_RAZERD		RUNDIR "/razerd"
#define SOCKPATH		RUNDIR_RAZERD "/socket"
#define PRIV_SOCKPATH		RUNDIR_RAZERD "/socket.privileged"

#define INTERFACE_REVISION	6

#define COMMAND_MAX_SIZE	512
#define COMMAND_HDR_SIZE	sizeof(struct command_hdr)
#define BULK_CHUNK_SIZE		128

#define MAX_FIRMWARE_SIZE	0x400000

enum {
	COMMAND_ID_GETREV = 0,		/* Get the revision number of the socket interface. */
	COMMAND_ID_RESCANMICE,		/* Rescan mice. */
	COMMAND_ID_GETMICE,		/* Get a list of detected mice. */
	COMMAND_ID_GETFWVER,		/* Get the firmware rev of a mouse. */
	COMMAND_ID_SUPPFREQS,		/* Get a list of supported frequencies. */
	COMMAND_ID_SUPPRESOL,		/* Get a list of supported resolutions. */
	COMMAND_ID_SUPPDPIMAPPINGS,	/* Get a list of supported DPI mappings. */
	COMMAND_ID_CHANGEDPIMAPPING,	/* Modify a DPI mapping. */
	COMMAND_ID_GETDPIMAPPING,	/* Get the active DPI mapping for a profile. */
	COMMAND_ID_SETDPIMAPPING,	/* Set the active DPI mapping for a profile. */
	COMMAND_ID_GETLEDS,		/* Get a list of LEDs on the device. */
	COMMAND_ID_SETLED,		/* Set the state of a LED. */
	COMMAND_ID_GETFREQ,		/* Get the current frequency. */
	COMMAND_ID_SETFREQ,		/* Set the frequency. */
	COMMAND_ID_GETPROFILES,		/* Get a list of supported profiles. */
	COMMAND_ID_GETACTIVEPROF,	/* Get the active profile. */
	COMMAND_ID_SETACTIVEPROF,	/* Set the active profile. */
	COMMAND_ID_SUPPBUTTONS,		/* Get a list of physical buttons. */
	COMMAND_ID_SUPPBUTFUNCS,	/* Get a list of supported button functions. */
	COMMAND_ID_GETBUTFUNC,		/* Get the current function of a button. */
	COMMAND_ID_SETBUTFUNC,		/* Set the current function of a button. */
	COMMAND_ID_SUPPAXES,		/* Get a list of supported axes. */
	COMMAND_ID_RECONFIGMICE,	/* Reconfigure all mice. */
	COMMAND_ID_GETMOUSEINFO,	/* Get detailed information about a mouse */
	COMMAND_ID_GETPROFNAME,		/* Get a profile name. */
	COMMAND_ID_SETPROFNAME,		/* Set a profile name. */

	/* Privileged commands */
	COMMAND_PRIV_FLASHFW = 128,	/* Upload and flash a firmware image */
	COMMAND_PRIV_CLAIM,		/* Claim the device. */
	COMMAND_PRIV_RELEASE,		/* Release the device. */
};

enum {
	ERR_NONE = 0,		/* No error */
	ERR_CMDSIZE,
	ERR_NOMEM,
	ERR_NOMOUSE,
	ERR_NOLED,
	ERR_CLAIM,
	ERR_FAIL,
	ERR_PAYLOAD,
	ERR_NOTSUPP,
};

enum mouseinfo_flags {
	MOUSEINFOFLG_RESULTOK		= (1 << 0), /* Other flags are ok, if this is set. */
	MOUSEINFOFLG_GLOBAL_LEDS	= (1 << 1), /* The device has global LEDs. */
	MOUSEINFOFLG_PROFILE_LEDS	= (1 << 2), /* The device has per-profile LEDs. */
	MOUSEINFOFLG_GLOBAL_FREQ	= (1 << 3), /* The device has global frequency settings. */
	MOUSEINFOFLG_PROFILE_FREQ	= (1 << 4), /* The device has per-profile frequency settings. */
	MOUSEINFOFLG_PROFNAMEMUTABLE	= (1 << 5), /* Profile names can be changed. */
	MOUSEINFOFLG_SUGGESTFWUP	= (1 << 6), /* A firmware update is suggested. */
};

enum led_flags {
	LED_FLAG_HAVECOLOR		= (1 << 0),
	LED_FLAG_CHANGECOLOR		= (1 << 1),
};

enum profile_special_values {
	PROFILE_INVALID			= 0xFFFFFFFF,
};

struct command_hdr {
	uint8_t id;
} _packed;

struct command {
	struct command_hdr hdr;
	char idstr[RAZER_IDSTR_MAX_SIZE];
	union {
		struct {
		} _packed getfwver;

		struct {
		} _packed suppaxes;

		struct {
		} _packed suppfreqs;

		struct {
		} _packed suppresol;

		struct {
		} _packed suppdpimappings;

		struct {
			uint32_t id;
			uint32_t dimension;
			uint32_t new_resolution;
		} _packed changedpimapping;

		struct {
			uint32_t profile_id;
			uint32_t axis_id;
		} _packed getdpimapping;

		struct {
			uint32_t profile_id;
			uint32_t axis_id;
			uint32_t mapping_id;
		} _packed setdpimapping;

		struct {
			uint32_t profile_id;
		} _packed getleds;

		struct {
			uint32_t profile_id;
			char led_name[RAZER_LEDNAME_MAX_SIZE];
			uint8_t new_state;
			uint8_t new_mode;
			uint32_t color;
		} _packed setled;

		struct {
			uint32_t profile_id;
			uint32_t new_frequency;
		} _packed setfreq;

		struct {
		} _packed getprofiles;

		struct {
		} _packed getactiveprof;

		struct {
			uint32_t id;
		} _packed setactiveprof;

		struct {
			uint32_t profile_id;
		} _packed getfreq;

		struct {
		} _packed suppbuttons;

		struct {
		} _packed suppbutfuncs;

		struct {
			uint32_t profile_id;
			uint32_t button_id;
		} _packed getbutfunc;

		struct {
			uint32_t profile_id;
			uint32_t button_id;
			uint32_t function_id;
		} _packed setbutfunc;

		struct {
		} _packed getmouseinfo;

		struct {
			uint32_t profile_id;
		} _packed getprofname;

		struct {
			uint32_t profile_id;
			uint8_t utf16be_name[64 * 2];
		} _packed setprofname;

		struct {
			uint32_t imagesize;
		} _packed flashfw;

		struct {
		} _packed claim;

		struct {
		} _packed release;

	} _packed;
} _packed;

#define CMD_SIZE(name)	(offsetof(struct command, name) + \
			 sizeof(((struct command *)0)->name))

enum {
	REPLY_ID_U32 = 0,		/* An unsigned 32bit integer. */
	REPLY_ID_STR,			/* A string */

	/* Asynchonous notifications. */
	NOTIFY_ID_NEWMOUSE = 128,	/* New mouse was connected. */
	NOTIFY_ID_DELMOUSE,		/* A mouse was removed. */
};

enum string_encoding {
	STRING_ENC_ASCII,
	STRING_ENC_UTF8,
	STRING_ENC_UTF16BE,
};

struct reply_hdr {
	uint8_t id;
} _packed;

struct reply {
	struct reply_hdr hdr;
	union {
		struct {
			uint32_t val;
		} _packed u32;
		struct {
			uint8_t encoding;
			uint16_t len; /* in characters */
			uint8_t str[0]; /* Payload buffer */
		} _packed string;

		struct {
		} _packed notify_newmouse;
		struct {
		} _packed notify_delmouse;
	} _packed;
} _packed;

#define REPLY_SIZE(name)	(offsetof(struct reply, name) + \
				 sizeof(((struct reply *)0)->name))

struct client {
	struct client *next;
	struct sockaddr_un sockaddr;
	socklen_t socklen;
	int fd;
};

/* Control socket FDs. */
static int ctlsock = -1;
static int privsock = -1;
/* Linked list of connected clients. */
static struct client *clients;
static struct client *privileged_clients;
/* Linked list of detected mice. */
static struct razer_mouse *mice;


static inline uint32_t cpu_to_be32(uint32_t v)
{
#ifdef BIG_ENDIAN_HOST
	return v;
#else
	return bswap_32(v);
#endif
}

static inline uint16_t cpu_to_be16(uint16_t v)
{
#ifdef BIG_ENDIAN_HOST
	return v;
#else
	return bswap_16(v);
#endif
}

static inline uint32_t be32_to_cpu(uint32_t v)
{
	return cpu_to_be32(v);
}

static inline uint16_t be16_to_cpu(uint16_t v)
{
	return cpu_to_be16(v);
}

static void loginfo(const char *fmt, ...)
{
	va_list args;

	if (cmdargs.loglevel < LOGLEVEL_INFO)
		return;
	va_start(args, fmt);
	if (cmdargs.background)
		vsyslog(LOG_MAKEPRI(LOG_DAEMON, LOG_INFO), fmt, args);
	else
		vfprintf(stdout, fmt, args);
	va_end(args);
}

static void logerr(const char *fmt, ...)
{
	va_list args;

	if (cmdargs.loglevel < LOGLEVEL_ERROR)
		return;
	va_start(args, fmt);
	if (cmdargs.background)
		vsyslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), fmt, args);
	else
		vfprintf(stderr, fmt, args);
	va_end(args);
}

static void logdebug(const char *fmt, ...)
{
	va_list args;

	if (cmdargs.loglevel < LOGLEVEL_DEBUG)
		return;
	va_start(args, fmt);
	if (cmdargs.background) {
		vsyslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), fmt, args);
	} else {
		fprintf(stdout, "[razerd debug]: ");
		vfprintf(stdout, fmt, args);
	}
	va_end(args);
}

static void remove_pidfile(void)
{
	if (!cmdargs.pidfile)
		return;
	if (unlink(cmdargs.pidfile)) {
		logerr("Failed to remove PID-file %s: %s\n",
		       cmdargs.pidfile, strerror(errno));
	}
}

static int create_pidfile(void)
{
	char buf[32] = { 0, };
	pid_t pid = getpid();
	int fd;
	ssize_t res;

	if (!cmdargs.pidfile)
		return 0;

	if (cmdargs.force)
		unlink(cmdargs.pidfile);
	fd = open(cmdargs.pidfile, O_RDWR | O_CREAT | O_TRUNC, 0444);
	if (fd < 0) {
		logerr("Failed to create PID-file %s: %s\n",
		       cmdargs.pidfile, strerror(errno));
		return -1;
	}
	snprintf(buf, sizeof(buf), "%lu",
		 (unsigned long)pid);
	res = write(fd, buf, strlen(buf));
	close(fd);
	if (res < 0 || (size_t)res != strlen(buf)) {
		logerr("Failed to write PID-file %s: %s\n",
		       cmdargs.pidfile, strerror(errno));
		return -1;
	}

	return 0;
}

static void cleanup_var_run(void)
{
	unlink(SOCKPATH);
	close(ctlsock);
	ctlsock = -1;

	unlink(PRIV_SOCKPATH);
	close(privsock);
	privsock = -1;

	remove_pidfile();
	rmdir(RUNDIR_RAZERD);
}

static int create_socket(const char *path, unsigned int perm,
			 unsigned int nrlisten)
{
	struct sockaddr_un sockaddr;
	int err, fd;

	fd = socket(AF_UNIX, SOCK_STREAM, 0);
	if (fd == -1) {
		logerr("Failed to create socket %s: %s\n",
		       path, strerror(errno));
		goto error;
	}
	err = fcntl(fd, F_SETFL, O_NONBLOCK);
	if (err) {
		logerr("Failed to set O_NONBLOCK on socket %s: %s\n",
		       path, strerror(errno));
		goto error_close_sock;
	}
	sockaddr.sun_family = AF_UNIX;
	razer_strlcpy(sockaddr.sun_path, path, sizeof(sockaddr.sun_path));
	err = bind(fd, (struct sockaddr *)&sockaddr, SUN_LEN(&sockaddr));
	if (err) {
		logerr("Failed to bind socket to %s: %s\n",
		       path, strerror(errno));
		goto error_close_sock;
	}
	err = chmod(path, perm);
	if (err) {
		logerr("Failed to set %s socket permissions: %s\n",
		       path, strerror(errno));
		goto error_unlink_sock;
	}
	err = listen(fd, nrlisten);
	if (err) {
		logerr("Failed to listen on socket %s: %s\n",
		       path, strerror(errno));
		goto error_unlink_sock;
	}

	return fd;

error_unlink_sock:
	unlink(path);
error_close_sock:
	close(fd);
error:
	return -1;
}

static int setup_var_run(void)
{
	int err;

	/* Create /run subdirectory. */
	err = mkdir(RUNDIR_RAZERD, 0755);
	if (err && errno != EEXIST) {
		logerr("Failed to create directory %s: %s\n",
		       RUNDIR_RAZERD, strerror(errno));
		return err;
	}

	create_pidfile();

	/* Create the control socket. */
	if (cmdargs.force)
		unlink(SOCKPATH);
	ctlsock = create_socket(SOCKPATH, 0666, 25);
	if (ctlsock == -1)
		goto err_remove_pidfile;

	/* Create the socket for privileged operations. */
	if (cmdargs.force)
		unlink(PRIV_SOCKPATH);
	privsock = create_socket(PRIV_SOCKPATH, 0660, 15);
	if (privsock == -1)
		goto err_remove_ctlsock;

	return 0;

err_remove_ctlsock:
	unlink(SOCKPATH);
	close(ctlsock);
	ctlsock = -1;

err_remove_pidfile:
	remove_pidfile();
	rmdir(RUNDIR_RAZERD);

	return -1;
}

static int setup_environment(void)
{
	int err;

	err = razer_init(!cmdargs.no_profile_emu);
	if (err) {
		logerr("librazer initialization failed. (%d)\n", err);
		return err;
	}
	razer_set_logging(cmdargs.loglevel >= LOGLEVEL_INFO ? loginfo : NULL,
			  cmdargs.loglevel >= LOGLEVEL_ERROR ? logerr : NULL,
			  cmdargs.loglevel >= LOGLEVEL_DEBUG ? logdebug : NULL);
	err = razer_load_config(cmdargs.configfile);
	if (cmdargs.configfile && err) {
		logerr("Failed to load config file %s\n",
			cmdargs.configfile);
		goto err_exit;
	}
	err = setup_var_run();
	if (err)
		goto err_exit;

	return 0;

err_exit:
	razer_exit();
	return err;
}

static void cleanup_environment(void)
{
	cleanup_var_run();
	razer_exit();
}

static void signal_handler(int signum)
{
	switch (signum) {
	case SIGINT:
	case SIGTERM:
		loginfo("Terminating razerd.\n");
		cleanup_environment();
		exit(0);
		break;
	case SIGPIPE:
		/* Ignore */
		break;
	default:
		logerr("Received unknown signal %d\n", signum);
	}
}

static void setup_sighandler(void)
{
	struct sigaction act;

	memset(&act, 0, sizeof(act));
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;
	act.sa_handler = signal_handler;

	sigaction(SIGINT, &act, NULL);
	sigaction(SIGTERM, &act, NULL);
	sigaction(SIGPIPE, &act, NULL);
}

static void free_client(struct client *client)
{
	free(client);
}

static struct client * new_client(const struct sockaddr_un *sockaddr,
				  socklen_t socklen, int fd)
{
	struct client *client;

	client = malloc(sizeof(*client));
	if (!client)
		return NULL;
	memset(client, 0, sizeof(*client));

	memcpy(&client->sockaddr, sockaddr, sizeof(client->sockaddr));
	client->socklen = socklen;
	client->fd = fd;

	return client;
}

static void client_list_add(struct client **base, struct client *new_entry)
{
	struct client *i;

	new_entry->next = NULL;
	if (!(*base)) {
		*base = new_entry;
		return;
	}
	for (i = *base; i->next; i = i->next)
		;
	i->next = new_entry;
}

static void client_list_del(struct client **base, struct client *del_entry)
{
	struct client *i;

	if (del_entry == *base) {
		*base = (*base)->next;
		return;
	}
	for (i = *base; i && (i->next != del_entry); i = i->next)
		;
	if (i)
		i->next = del_entry->next;
}

static int check_control_socket(int socket_fd, struct client **client_list)
{
	socklen_t socklen;
	struct client *client;
	struct sockaddr_un remoteaddr;
	int err, fd;

	socklen = sizeof(remoteaddr);
	fd = accept(socket_fd, (struct sockaddr *)&remoteaddr, &socklen);
	if (fd == -1)
		return -1;
	/* Connected */
	err = fcntl(fd, F_SETFL, O_NONBLOCK);
	if (err) {
		logerr("Failed to set O_NONBLOCK on client: %s\n",
		       strerror(errno));
		close(fd);
		return -1;
	}
	client = new_client(&remoteaddr, socklen, fd);
	if (!client) {
		close(fd);
		return -1;
	}
	client_list_add(client_list, client);
	if (client_list == &privileged_clients)
		logdebug("Privileged client connected (fd=%d)\n", fd);
	else
		logdebug("Client connected (fd=%d)\n", fd);

	return 0;
}

static void disconnect_client(struct client **client_list, struct client *client)
{
	client_list_del(client_list, client);
	if (client_list == &privileged_clients)
		logdebug("Privileged client disconnected (fd=%d)\n", client->fd);
	else
		logdebug("Client disconnected (fd=%d)\n", client->fd);
	close(client->fd);
	free_client(client);
}

static int send_reply(struct client *client, struct reply *r, size_t len)
{
	const uint8_t *buf = (const uint8_t *)r;
	int ret;

	while (len) {
		ret = send(client->fd, buf, len, 0);
		if (ret < 0) {
			if (errno == EAGAIN ||
			    errno == EINTR) {
				razer_msleep(1);
				continue;
			}
			logerr("send() failed: %s", strerror(errno));
			return -errno;
		}
		len -= ret;
		buf += ret;
	}

	return 0;
}

static int send_u32(struct client *client, uint32_t v)
{
	struct reply r;

	r.hdr.id = REPLY_ID_U32;
	r.u32.val = cpu_to_be32(v);

	return send_reply(client, &r, REPLY_SIZE(u32));
}

static int send_string(struct client *client, const char *str)
{
	struct reply *r;
	size_t i, len = strlen(str);
	int err;

	r = malloc(len + REPLY_SIZE(string));
	if (!r) {
		logerr("Out of memory\n");
		return -ENOMEM;
	}

	r->hdr.id = REPLY_ID_STR;
	r->string.encoding = STRING_ENC_ASCII;
	r->string.len = cpu_to_be16(len);
	for (i = 0; i < len; i++) {
		if ((unsigned char)str[i] <= 0x7Fu)
			r->string.str[i] = str[i];
		else
			r->string.str[i] = '?'; /* Non-ASCII char. */
	}
	err = send_reply(client, r, REPLY_SIZE(string) + len);

	free(r);

	return err;
}

static int send_utf16_string(struct client *client, const razer_utf16_t *str)
{
	struct reply *r;
	size_t i, len = razer_utf16_strlen(str);
	int err;
	uint16_t value;

	r = malloc(len * 2 + REPLY_SIZE(string));
	if (!r) {
		logerr("Out of memory\n");
		return -ENOMEM;
	}

	r->hdr.id = REPLY_ID_STR;
	r->string.encoding = STRING_ENC_UTF16BE;
	r->string.len = cpu_to_be16(len);
	for (i = 0; i < len; i++) {
		value = cpu_to_be16(str[i]);
		memcpy(&((uint16_t *)r->string.str)[i], &value, sizeof(value));
	}
	err = send_reply(client, r, REPLY_SIZE(string) + len * 2);

	free(r);

	return err;
}

static int recv_bulk(struct client *client, char *buf, unsigned int len)
{
	unsigned int next_len, i, got;
	int nr;

	for (i = 0; i < len; i += BULK_CHUNK_SIZE) {
		next_len = BULK_CHUNK_SIZE;
		if (i + next_len > len)
			next_len = len - i;

		got = 0;
		while (got < next_len) {
			nr = recv(client->fd, buf + i + got, next_len - got, 0);
			if (nr < 0) {
				if (errno == EAGAIN || errno == EINTR) {
					razer_msleep(1);
					continue;
				}
				send_u32(client, ERR_PAYLOAD);
				return -1;
			}
			if (nr == 0) {
				/* Peer closed connection unexpectedly. */
				send_u32(client, ERR_PAYLOAD);
				return -1;
			}
			got += (unsigned int)nr;
		}

		if (got != next_len) {
			send_u32(client, ERR_PAYLOAD);
			return -1;
		}

		send_u32(client, ERR_NONE);
	}

	return 0;
}

struct razer_mouse * find_mouse(const char *idstr)
{
	struct razer_mouse *m, *next;

	razer_for_each_mouse(m, next, mice) {
		if (strncmp(m->idstr, idstr, RAZER_IDSTR_MAX_SIZE) == 0)
			return m;
	}

	return NULL;
}

static struct razer_mouse_profile * find_mouse_profile(struct razer_mouse *mouse,
						       unsigned int profile_id)
{
	struct razer_mouse_profile *list;
	unsigned int i;

	if (!mouse->get_profiles)
		return NULL;
	list = mouse->get_profiles(mouse);
	if (!list)
		return NULL;
	for (i = 0; i < mouse->nr_profiles; i++) {
		if (list[i].nr == profile_id)
			return &list[i];
	}

	return NULL;
}

static struct razer_axis * find_mouse_axis(struct razer_mouse *mouse,
					   unsigned int axis_id)
{
	struct razer_axis *list;
	int i, count;

	if (!mouse->supported_axes)
		return NULL;
	count = mouse->supported_axes(mouse, &list);
	if (count <= 0)
		return NULL;
	for (i = 0; i < count; i++) {
		if (list[i].id == axis_id)
			return &list[i];
	}

	return NULL;
}

static struct razer_mouse_dpimapping * find_mouse_dpimapping(struct razer_mouse *mouse,
							     unsigned int mapping_id)
{
	struct razer_mouse_dpimapping *list;
	int i, count;

	if (!mouse->supported_dpimappings)
		return NULL;
	count = mouse->supported_dpimappings(mouse, &list);
	if (count <= 0)
		return NULL;
	for (i = 0; i < count; i++) {
		if (list[i].nr == mapping_id)
			return &list[i];
	}

	return NULL;
}

static struct razer_button * find_mouse_button(struct razer_mouse *mouse,
					       unsigned int button_id)
{
	struct razer_button *list;
	int i, count;

	if (!mouse->supported_buttons)
		return NULL;
	count = mouse->supported_buttons(mouse, &list);
	if (count <= 0)
		return NULL;
	for (i = 0; i < count; i++) {
		if (list[i].id == button_id)
			return &list[i];
	}

	return NULL;
}

static struct razer_button_function * find_mouse_button_function(struct razer_mouse *mouse,
								 unsigned int button_id)
{
	struct razer_button_function *list;
	int i, count;

	if (!mouse->supported_button_functions)
		return NULL;
	count = mouse->supported_button_functions(mouse, &list);
	if (count <= 0)
		return NULL;
	for (i = 0; i < count; i++) {
		if (list[i].id == button_id)
			return &list[i];
	}

	return NULL;
}

static void command_getmice(struct client *client, const struct command *cmd, unsigned int len)
{
	unsigned int count;
	char str[RAZER_IDSTR_MAX_SIZE + 1];
	struct razer_mouse *mouse, *next;

	count = 0;
	razer_for_each_mouse(mouse, next, mice)
		count++;
	send_u32(client, count);
	razer_for_each_mouse(mouse, next, mice) {
		snprintf(str, sizeof(str), "%s", mouse->idstr);
		send_string(client, str);
	}
}

static void command_getfwver(struct client *client, const struct command *cmd, unsigned int len)
{
	struct razer_mouse *mouse;
	uint32_t fwver = 0xFFFFFFFF;
	int err;

	if (len < CMD_SIZE(getfwver))
		goto out;
	mouse = find_mouse(cmd->idstr);
	if (!mouse || !mouse->get_fw_version)
		goto out;
	err = mouse->claim(mouse);
	if (err)
		goto out;
	fwver = mouse->get_fw_version(mouse);
	mouse->release(mouse);
out:
	send_u32(client, fwver);
}

static void command_getfreq(struct client *client, const struct command *cmd, unsigned int len)
{
	struct razer_mouse *mouse;
	struct razer_mouse_profile *profile;
	enum razer_mouse_freq freq;
	unsigned int profile_id;

	if (len < CMD_SIZE(getfreq))
		goto error;
	mouse = find_mouse(cmd->idstr);
	if (!mouse)
		goto error;
	profile_id = be32_to_cpu(cmd->getfreq.profile_id);
	if (profile_id == PROFILE_INVALID) {
		if (!mouse->global_get_freq)
			goto error;
		freq = mouse->global_get_freq(mouse);
	} else {
		profile = find_mouse_profile(mouse, profile_id);
		if (!profile || !profile->get_freq)
			goto error;
		freq = profile->get_freq(profile);
	}

	send_u32(client, freq);

	return;
error:
	send_u32(client, RAZER_MOUSE_FREQ_UNKNOWN);
}

static void command_suppfreqs(struct client *client, const struct command *cmd, unsigned int len)
{
	struct razer_mouse *mouse;
	enum razer_mouse_freq *freq_list;
	int i, count;

	if (len < CMD_SIZE(suppfreqs))
		goto error;
	mouse = find_mouse(cmd->idstr);
	if (!mouse || !mouse->supported_freqs)
		goto error;
	count = mouse->supported_freqs(mouse, &freq_list);
	if (count <= 0)
		goto error;

	send_u32(client, count);
	for (i = 0; i < count; i++)
		send_u32(client, freq_list[i]);
	razer_free_freq_list(freq_list, count);

	return;
error:
	count = 0;
	send_u32(client, count);
}

static void command_suppresol(struct client *client, const struct command *cmd, unsigned int len)
{
	struct razer_mouse *mouse;
	enum razer_mouse_res *res_list;
	int i, count;

	if (len < CMD_SIZE(suppresol))
		goto error;
	mouse = find_mouse(cmd->idstr);
	if (!mouse || !mouse->supported_resolutions)
		goto error;
	count = mouse->supported_resolutions(mouse, &res_list);
	if (count <= 0)
		goto error;

	send_u32(client, count);
	for (i = 0; i < count; i++)
		send_u32(client, res_list[i]);
	razer_free_resolution_list(res_list, count);

	return;
error:
	count = 0;
	send_u32(client, count);
}

static void command_suppdpimappings(struct client *client, const struct command *cmd, unsigned int len)
{
	struct razer_mouse *mouse;
	struct razer_mouse_dpimapping *list;
	int i, j, count;

	if (len < CMD_SIZE(suppdpimappings))
		goto error;
	mouse = find_mouse(cmd->idstr);
	if (!mouse || !mouse->supported_dpimappings)
		goto error;
	count = mouse->supported_dpimappings(mouse, &list);
	if (count <= 0)
		goto error;

	send_u32(client, count);
	for (i = 0; i < count; i++) {
		send_u32(client, list[i].nr);
		send_u32(client, list[i].dimension_mask);
		for (j = 0; j < RAZER_NR_DIMS; j++)
			send_u32(client, list[i].res[j]);
		send_u32(client, (list[i].profile_mask >> 32) & 0xFFFFFFFF);
		send_u32(client, (list[i].profile_mask >> 0) & 0xFFFFFFFF);
		send_u32(client, list[i].change ? 1 : 0);
	}
	/* No need to free list. It's statically allocated. */

	return;
error:
	count = 0;
	send_u32(client, count);
}

static void command_changedpimapping(struct client *client, const struct command *cmd, unsigned int len)
{
	struct razer_mouse *mouse;
	struct razer_mouse_dpimapping *mapping;
	int err;
	uint32_t errorcode = ERR_NONE;

	if (len < CMD_SIZE(changedpimapping)) {
		errorcode = ERR_CMDSIZE;
		goto error;
	}
	mouse = find_mouse(cmd->idstr);
	if (!mouse) {
		errorcode = ERR_NOMOUSE;
		goto error;
	}
	mapping = find_mouse_dpimapping(mouse, be32_to_cpu(cmd->changedpimapping.id));
	if (!mapping || !mapping->change) {
		errorcode = ERR_FAIL;
		goto error;
	}

	err = mouse->claim(mouse);
	if (err) {
		errorcode = ERR_CLAIM;
		goto error;
	}
	err = mapping->change(mapping,
			      be32_to_cpu(cmd->changedpimapping.dimension),
			      be32_to_cpu(cmd->changedpimapping.new_resolution));
	if (err)
		errorcode = ERR_FAIL;
	mouse->release(mouse);

error:
	send_u32(client, errorcode);
}

static void command_getdpimapping(struct client *client, const struct command *cmd, unsigned int len)
{
	struct razer_mouse *mouse;
	struct razer_mouse_profile *profile;
	struct razer_axis *axis;
	struct razer_mouse_dpimapping *mapping;

	if (len < CMD_SIZE(getdpimapping))
		goto error;
	mouse = find_mouse(cmd->idstr);
	if (!mouse)
		goto error;
	profile = find_mouse_profile(mouse, be32_to_cpu(cmd->getdpimapping.profile_id));
	if (!profile)
		goto error;
	axis = find_mouse_axis(mouse, be32_to_cpu(cmd->getdpimapping.axis_id));
	mapping = profile->get_dpimapping(profile, axis);
	if (!mapping)
		goto error;

	send_u32(client, mapping->nr);

	return;
error:
	send_u32(client, 0xFFFFFFFF);
}

static void command_setdpimapping(struct client *client, const struct command *cmd, unsigned int len)
{
	struct razer_mouse *mouse;
	struct razer_mouse_profile *profile;
	struct razer_axis *axis;
	struct razer_mouse_dpimapping *mapping;
	uint32_t errorcode = ERR_NONE;
	int err;

	if (len < CMD_SIZE(setdpimapping)) {
		errorcode = ERR_CMDSIZE;
		goto error;
	}
	mouse = find_mouse(cmd->idstr);
	if (!mouse) {
		errorcode = ERR_NOMOUSE;
		goto error;
	}
	profile = find_mouse_profile(mouse, be32_to_cpu(cmd->setdpimapping.profile_id));
	if (!profile || !profile->set_dpimapping) {
		errorcode = ERR_FAIL;
		goto error;
	}
	axis = find_mouse_axis(mouse, be32_to_cpu(cmd->setdpimapping.axis_id));
	mapping = find_mouse_dpimapping(mouse, be32_to_cpu(cmd->setdpimapping.mapping_id));
	if (!mapping) {
		errorcode = ERR_FAIL;
		goto error;
	}

	err = mouse->claim(mouse);
	if (err) {
		errorcode = ERR_CLAIM;
		goto error;
	}
	err = profile->set_dpimapping(profile, axis, mapping);
	mouse->release(mouse);
	if (err) {
		errorcode = ERR_FAIL;
		goto error;
	}

error:
	send_u32(client, errorcode);
}

static void command_rescanmice(struct client *client, const struct command *cmd, unsigned int len)
{
	mice = razer_rescan_mice();
}

static void command_reconfigmice(struct client *client, const struct command *cmd, unsigned int len)
{
	razer_reconfig_mice();
}

static void command_getmouseinfo(struct client *client, const struct command *cmd, unsigned int len)
{
	struct razer_mouse *mouse;
	struct razer_mouse_profile *profiles;
	unsigned int flags;

	if (len < CMD_SIZE(getmouseinfo))
		goto error;
	mouse = find_mouse(cmd->idstr);
	if (!mouse)
		goto error;
	flags = MOUSEINFOFLG_RESULTOK;
	if (mouse->global_get_leds)
		flags |= MOUSEINFOFLG_GLOBAL_LEDS;
	if (mouse->global_get_freq)
		flags |= MOUSEINFOFLG_GLOBAL_FREQ;
	if (mouse->nr_profiles && mouse->get_profiles) {
		profiles = mouse->get_profiles(mouse);
		if (profiles) {
			if (profiles[0].get_leds)
				flags |= MOUSEINFOFLG_PROFILE_LEDS;
			if (profiles[0].get_freq)
				flags |= MOUSEINFOFLG_PROFILE_FREQ;
			if (profiles[0].set_name)
				flags |= MOUSEINFOFLG_PROFNAMEMUTABLE;
		}
	}
	if (mouse->flags & RAZER_MOUSEFLG_SUGGESTFWUP)
		flags |= MOUSEINFOFLG_SUGGESTFWUP;
	send_u32(client, flags);

	return;
error:
	send_u32(client, 0);
}

static void command_getleds(struct client *client, const struct command *cmd, unsigned int len)
{
	struct razer_mouse *mouse;
	struct razer_mouse_profile *profile;
	struct razer_led *leds_list, *led;
	int count;
	unsigned int flags, profile_id;

	if (len < CMD_SIZE(getleds))
		goto error;
	mouse = find_mouse(cmd->idstr);
	if (!mouse)
		goto error;
	profile_id = be32_to_cpu(cmd->getleds.profile_id);
	if (profile_id == PROFILE_INVALID) {
		if (!mouse->global_get_leds)
			goto error;
		count = mouse->global_get_leds(mouse, &leds_list);
	} else {
		profile = find_mouse_profile(mouse, profile_id);
		if (!profile)
			goto error;
		if (!profile->get_leds)
			goto error;
		count = profile->get_leds(profile, &leds_list);
	}
	if (count <= 0)
		goto error;

	send_u32(client, count);
	for (led = leds_list; led; led = led->next) {
		flags = 0;
		if (led->color.valid)
			flags |= LED_FLAG_HAVECOLOR;
		if (led->change_color)
			flags |= LED_FLAG_CHANGECOLOR;
		send_u32(client, flags);
		send_string(client, led->name);
		send_u32(client, led->state);
		send_u32(client, led->mode);
		send_u32(client, led->supported_modes_mask);
		send_u32(client, ((uint32_t)led->color.r << 16) |
				 ((uint32_t)led->color.g << 8) |
				 ((uint32_t)led->color.b << 0));
	}
	razer_free_leds(leds_list);

	return;
error:
	count = 0;
	send_u32(client, count);
}

static struct razer_led * razer_mouse_find_led(struct razer_led *leds_list,
					       const char *led_name)
{
	struct razer_led *led;

	for (led = leds_list; led; led = led->next) {
		if (strncasecmp(led->name, led_name, RAZER_LEDNAME_MAX_SIZE) == 0)
			return led;
	}

	return NULL;
}

static void command_setled(struct client *client, const struct command *cmd, unsigned int len)
{
	struct razer_mouse *mouse;
	struct razer_mouse_profile *profile;
	struct razer_led *leds_list = NULL, *led;
	enum razer_led_state new_state;
	enum razer_led_mode new_mode;
	struct razer_rgb_color new_color;
	int err, count;
	uint32_t errorcode = ERR_NONE;
	unsigned int profile_id, value;

	if (len < CMD_SIZE(setled)) {
		errorcode = ERR_CMDSIZE;
		goto error;
	}
	mouse = find_mouse(cmd->idstr);
	if (!mouse) {
		errorcode = ERR_NOMOUSE;
		goto error;
	}
	profile_id = be32_to_cpu(cmd->setled.profile_id);
	if (profile_id == PROFILE_INVALID) {
		if (!mouse->global_get_leds) {
			errorcode = ERR_NOLED;
			goto error;
		}
		count = mouse->global_get_leds(mouse, &leds_list);
	} else {
		profile = find_mouse_profile(mouse, profile_id);
		if (!profile) {
			errorcode = ERR_NOLED;
			goto error;
		}
		if (!profile->get_leds) {
			errorcode = ERR_NOLED;
			goto error;
		}
		count = profile->get_leds(profile, &leds_list);
	}
	if (count <= 0) {
		errorcode = ERR_NOMEM;
		goto error;
	}
	led = razer_mouse_find_led(leds_list, cmd->setled.led_name);
	if (!led) {
		errorcode = ERR_NOLED;
		goto error;
	}
	err = mouse->claim(mouse);
	if (err) {
		errorcode = ERR_CLAIM;
		goto error;
	}
	new_state = cmd->setled.new_state ? RAZER_LED_ON : RAZER_LED_OFF;
	if (new_state != led->state) {
		err = led->toggle_state(led, new_state);
		if (err) {
			mouse->release(mouse);
			errorcode = ERR_FAIL;
			goto error;
		}
	}
	new_mode = cmd->setled.new_mode;
	if (new_mode != led->mode) {
		if (led->set_mode) {
			err = led->set_mode(led, new_mode);
			if (err) {
				mouse->release(mouse);
				errorcode = ERR_FAIL;
				goto error;
			}
		}
	}
	if (led->change_color) {
		memset(&new_color, 0, sizeof(new_color));
		value = be32_to_cpu(cmd->setled.color);
		new_color.valid = 1;
		new_color.r = (value >> 16) & 0xFF;
		new_color.g = (value >> 8) & 0xFF;
		new_color.b = (value >> 0) & 0xFF;
		if (!!new_color.valid != !!led->color.valid ||
		    new_color.r != led->color.r ||
		    new_color.g != led->color.g ||
		    new_color.b != led->color.b) {
			err = led->change_color(led, &new_color);
			if (err) {
				mouse->release(mouse);
				errorcode = ERR_FAIL;
				goto error;
			}
		}
	}
	mouse->release(mouse);

error:
	razer_free_leds(leds_list);
	send_u32(client, errorcode);
}

static void command_setfreq(struct client *client, const struct command *cmd, unsigned int len)
{
	struct razer_mouse *mouse;
	struct razer_mouse_profile *profile = NULL;
	int err;
	uint32_t errorcode = ERR_NONE;
	unsigned int profile_id;

	if (len < CMD_SIZE(setfreq)) {
		errorcode = ERR_CMDSIZE;
		goto error;
	}
	mouse = find_mouse(cmd->idstr);
	if (!mouse) {
		errorcode = ERR_NOMOUSE;
		goto error;
	}
	profile_id = be32_to_cpu(cmd->setfreq.profile_id);
	if (profile_id == PROFILE_INVALID) {
		if (!mouse->global_set_freq) {
			errorcode = ERR_NOTSUPP;
			goto error;
		}
	} else {
		profile = find_mouse_profile(mouse, profile_id);
		if (!profile) {
			errorcode = ERR_FAIL;
			goto error;
		}
		if (!profile->set_freq) {
			errorcode = ERR_NOTSUPP;
			goto error;
		}
	}

	err = mouse->claim(mouse);
	if (err) {
		errorcode = ERR_CLAIM;
		goto error;
	}
	if (profile_id == PROFILE_INVALID) {
		err = mouse->global_set_freq(mouse,
			be32_to_cpu(cmd->setfreq.new_frequency));
	} else {
		err = profile->set_freq(profile,
			be32_to_cpu(cmd->setfreq.new_frequency));
	}
	mouse->release(mouse);
	if (err) {
		errorcode = ERR_FAIL;
		goto error;
	}

error:
	send_u32(client, errorcode);
}

static void command_getprofiles(struct client *client, const struct command *cmd, unsigned int len)
{
	struct razer_mouse *mouse;
	struct razer_mouse_profile *list;
	unsigned int i;

	if (len < CMD_SIZE(getprofiles))
		goto error;
	mouse = find_mouse(cmd->idstr);
	if (!mouse)
		goto error;
	list = mouse->get_profiles(mouse);
	if (!list)
		goto error;

	send_u32(client, mouse->nr_profiles);
	for (i = 0; i < mouse->nr_profiles; i++)
		send_u32(client, list[i].nr);

	return;
error:
	send_u32(client, 0);
}

static void command_getprofname(struct client *client, const struct command *cmd, unsigned int len)
{
	struct razer_mouse *mouse;
	struct razer_mouse_profile *profile;
	const razer_utf16_t *name;
	razer_utf16_t namebuf[64] = { };
	char asciibuf[64] = { };

	if (len < CMD_SIZE(getprofname))
		goto error;
	mouse = find_mouse(cmd->idstr);
	if (!mouse)
		goto error;
	profile = find_mouse_profile(mouse, be32_to_cpu(cmd->getprofname.profile_id));
	if (!profile)
		goto error;
	if (profile->get_name) {
		name = profile->get_name(profile);
	} else {
		snprintf(asciibuf, sizeof(asciibuf),
			 "Profile %u", profile->nr + 1);
		razer_ascii_to_utf16(namebuf, ARRAY_SIZE(namebuf), asciibuf);
		name = namebuf;
	}
	if (!name)
		goto error;

	send_utf16_string(client, name);
	return;
error:
	send_string(client, "");
}

static void command_setprofname(struct client *client, const struct command *cmd, unsigned int len)
{
	struct razer_mouse *mouse;
	struct razer_mouse_profile *profile;
	razer_utf16_t namebuf[sizeof(cmd->setprofname.utf16be_name) / 2 + 1] = { };
	unsigned int i;
	uint32_t errorcode = ERR_NONE;
	uint16_t value;

	if (len < CMD_SIZE(setprofname)) {
		errorcode = ERR_CMDSIZE;
		goto error;
	}
	mouse = find_mouse(cmd->idstr);
	if (!mouse) {
		errorcode = ERR_NOMOUSE;
		goto error;
	}
	profile = find_mouse_profile(mouse, be32_to_cpu(cmd->setprofname.profile_id));
	if (!profile) {
		errorcode = ERR_FAIL;
		goto error;
	}
	if (!profile->set_name) {
		errorcode = ERR_NOTSUPP;
		goto error;
	}
	for (i = 0; i < ARRAY_SIZE(namebuf) - 1; i++) {
		memcpy(&value, &((const uint16_t *)cmd->setprofname.utf16be_name)[i], sizeof(value));
		namebuf[i] = be16_to_cpu(value);
	}
	if (profile->set_name(profile, namebuf)) {
		errorcode = ERR_FAIL;
		goto error;
	}

error:
	send_u32(client, errorcode);
}

static void command_getactiveprof(struct client *client, const struct command *cmd, unsigned int len)
{
	struct razer_mouse *mouse;
	struct razer_mouse_profile *activeprof;

	if (len < CMD_SIZE(getactiveprof))
		goto error;
	mouse = find_mouse(cmd->idstr);
	if (!mouse)
		goto error;
	activeprof = mouse->get_active_profile(mouse);
	if (!activeprof)
		goto error;

	send_u32(client, activeprof->nr);

	return;
error:
	send_u32(client, 0xFFFFFFFF);
}

static void command_setactiveprof(struct client *client, const struct command *cmd, unsigned int len)
{
	struct razer_mouse *mouse;
	struct razer_mouse_profile *profile;
	int err;
	uint32_t errorcode = ERR_NONE;

	if (len < CMD_SIZE(setactiveprof)) {
		errorcode = ERR_CMDSIZE;
		goto error;
	}
	mouse = find_mouse(cmd->idstr);
	if (!mouse) {
		errorcode = ERR_NOMOUSE;
		goto error;
	}
	profile = find_mouse_profile(mouse, be32_to_cpu(cmd->setactiveprof.id));
	if (!profile) {
		errorcode = ERR_FAIL;
		goto error;
	}

	err = mouse->claim(mouse);
	if (err) {
		errorcode = ERR_CLAIM;
		goto error;
	}
	err = mouse->set_active_profile(mouse, profile);
	if (err)
		errorcode = ERR_FAIL;
	mouse->release(mouse);

error:
	send_u32(client, errorcode);
}

static void command_suppbuttons(struct client *client, const struct command *cmd, unsigned int len)
{
	struct razer_mouse *mouse;
	struct razer_button *list;
	int count, i;

	if (len < CMD_SIZE(suppbuttons))
		goto error;
	mouse = find_mouse(cmd->idstr);
	if (!mouse || !mouse->supported_buttons)
		goto error;
	count = mouse->supported_buttons(mouse, &list);
	if (count <= 0)
		goto error;

	send_u32(client, count);
	for (i = 0; i < count; i++) {
		send_u32(client, list[i].id);
		send_string(client, list[i].name);
	}

	return;
error:
	send_u32(client, 0);
}

static void command_suppbutfuncs(struct client *client, const struct command *cmd, unsigned int len)
{
	struct razer_mouse *mouse;
	struct razer_button_function *list;
	int count, i;

	if (len < CMD_SIZE(suppbutfuncs))
		goto error;
	mouse = find_mouse(cmd->idstr);
	if (!mouse || !mouse->supported_button_functions)
		goto error;
	count = mouse->supported_button_functions(mouse, &list);
	if (count <= 0)
		goto error;

	send_u32(client, count);
	for (i = 0; i < count; i++) {
		send_u32(client, list[i].id);
		send_string(client, list[i].name);
	}

	return;
error:
	send_u32(client, 0);
}

static void command_getbutfunc(struct client *client, const struct command *cmd, unsigned int len)
{
	struct razer_mouse *mouse;
	struct razer_mouse_profile *profile;
	struct razer_button_function *func;
	struct razer_button *button;

	if (len < CMD_SIZE(getbutfunc))
		goto error;
	mouse = find_mouse(cmd->idstr);
	if (!mouse)
		goto error;
	button = find_mouse_button(mouse, be32_to_cpu(cmd->getbutfunc.button_id));
	if (!button)
		goto error;
	profile = find_mouse_profile(mouse, be32_to_cpu(cmd->getbutfunc.profile_id));
	if (!profile || !profile->get_button_function)
		goto error;
	func = profile->get_button_function(profile, button);
	if (!func)
		goto error;

	send_u32(client, func->id);
	send_string(client, func->name);

	return;
error:
	send_u32(client, 0);
	send_string(client, "");
}

static void command_setbutfunc(struct client *client, const struct command *cmd, unsigned int len)
{
	struct razer_mouse *mouse;
	struct razer_mouse_profile *profile;
	struct razer_button_function *func;
	struct razer_button *button;
	uint32_t errorcode = ERR_NONE;
	int err;

	if (len < CMD_SIZE(setbutfunc)) {
		errorcode = ERR_CMDSIZE;
		goto error;
	}
	mouse = find_mouse(cmd->idstr);
	if (!mouse) {
		errorcode = ERR_NOMOUSE;
		goto error;
	}
	button = find_mouse_button(mouse, be32_to_cpu(cmd->setbutfunc.button_id));
	if (!button) {
		errorcode = ERR_FAIL;
		goto error;
	}
	func = find_mouse_button_function(mouse, be32_to_cpu(cmd->setbutfunc.function_id));
	if (!func) {
		errorcode = ERR_FAIL;
		goto error;
	}
	profile = find_mouse_profile(mouse, be32_to_cpu(cmd->setbutfunc.profile_id));
	if (!profile || !profile->set_button_function) {
		errorcode = ERR_FAIL;
		goto error;
	}

	err = mouse->claim(mouse);
	if (err) {
		errorcode = ERR_CLAIM;
		goto error;
	}
	err = profile->set_button_function(profile, button, func);
	mouse->release(mouse);
	if (err) {
		errorcode = ERR_FAIL;
		goto error;
	}
error:
	send_u32(client, errorcode);
}

static void command_suppaxes(struct client *client, const struct command *cmd, unsigned int len)
{
	struct razer_mouse *mouse;
	struct razer_axis *list;
	int count, i;

	if (len < CMD_SIZE(suppaxes))
		goto error;
	mouse = find_mouse(cmd->idstr);
	if (!mouse || !mouse->supported_axes)
		goto error;
	count = mouse->supported_axes(mouse, &list);
	if (count <= 0)
		goto error;

	send_u32(client, count);
	for (i = 0; i < count; i++) {
		send_u32(client, list[i].id);
		send_string(client, list[i].name);
		send_u32(client, list[i].flags);
	}

	return;
error:
	send_u32(client, 0);
}

static void command_flashfw(struct client *client, const struct command *cmd, unsigned int len)
{
	struct razer_mouse *mouse;
	uint32_t image_size;
	int err;
	uint32_t errorcode = ERR_NONE;
	char *image = NULL;

	if (len < CMD_SIZE(flashfw)) {
		errorcode = ERR_CMDSIZE;
		goto error;
	}
	image_size = be32_to_cpu(cmd->flashfw.imagesize);
	if (image_size > MAX_FIRMWARE_SIZE) {
		errorcode = ERR_CMDSIZE;
		goto error;
	}

	image = malloc(image_size);
	if (!image) {
		errorcode = ERR_NOMEM;
		goto error;
	}

	err = recv_bulk(client, image, image_size);
	if (err) {
		errorcode = ERR_PAYLOAD;
		goto error;
	}

	mouse = find_mouse(cmd->idstr);
	if (!mouse) {
		errorcode = ERR_NOMOUSE;
		goto error;
	}
	if (!mouse->flash_firmware) {
		errorcode = ERR_NOTSUPP;
		goto error;
	}
	err = mouse->claim(mouse);
	if (err) {
		errorcode = ERR_CLAIM;
		goto error;
	}
	err = mouse->flash_firmware(mouse, image, image_size,
				    RAZER_FW_FLASH_MAGIC);
	mouse->release(mouse);
	if (err) {
		errorcode = ERR_FAIL;
		goto error;
	}

error:
	send_u32(client, errorcode);
	free(image);
}

static void command_claim(struct client *client, const struct command *cmd, unsigned int len)
{
	struct razer_mouse *mouse;
	uint32_t errorcode = ERR_NONE;
	int err;

	if (len < CMD_SIZE(claim)) {
		errorcode = ERR_CMDSIZE;
		goto error;
	}
	mouse = find_mouse(cmd->idstr);
	if (!mouse) {
		errorcode = ERR_NOMOUSE;
		goto error;
	}
	err = mouse->claim(mouse);
	if (err) {
		errorcode = ERR_FAIL;
		goto error;
	}

error:
	send_u32(client, errorcode);
}

static void command_release(struct client *client, const struct command *cmd, unsigned int len)
{
	struct razer_mouse *mouse;
	uint32_t errorcode = ERR_NONE;

	if (len < CMD_SIZE(release)) {
		errorcode = ERR_CMDSIZE;
		goto error;
	}
	mouse = find_mouse(cmd->idstr);
	if (!mouse) {
		errorcode = ERR_NOMOUSE;
		goto error;
	}
	mouse->release(mouse);

error:
	send_u32(client, errorcode);
}

static void handle_received_command(struct client *client, const char *_cmd, unsigned int len)
{
	const struct command *cmd = (const struct command *)_cmd;

	if (len < COMMAND_HDR_SIZE)
		return;
	switch (cmd->hdr.id) {
	case COMMAND_ID_GETREV:
		send_u32(client, INTERFACE_REVISION);
		break;
	case COMMAND_ID_RESCANMICE:
		command_rescanmice(client, cmd, len);
		break;
	case COMMAND_ID_GETMICE:
		command_getmice(client, cmd, len);
		break;
	case COMMAND_ID_GETFWVER:
		command_getfwver(client, cmd, len);
		break;
	case COMMAND_ID_SUPPFREQS:
		command_suppfreqs(client, cmd, len);
		break;
	case COMMAND_ID_SUPPRESOL:
		command_suppresol(client, cmd, len);
		break;
	case COMMAND_ID_SUPPDPIMAPPINGS:
		command_suppdpimappings(client, cmd, len);
		break;
	case COMMAND_ID_CHANGEDPIMAPPING:
		command_changedpimapping(client, cmd, len);
		break;
	case COMMAND_ID_GETDPIMAPPING:
		command_getdpimapping(client, cmd, len);
		break;
	case COMMAND_ID_SETDPIMAPPING:
		command_setdpimapping(client, cmd, len);
		break;
	case COMMAND_ID_GETLEDS:
		command_getleds(client, cmd, len);
		break;
	case COMMAND_ID_SETLED:
		command_setled(client, cmd, len);
		break;
	case COMMAND_ID_SETFREQ:
		command_setfreq(client, cmd, len);
		break;
	case COMMAND_ID_GETPROFILES:
		command_getprofiles(client, cmd, len);
		break;
	case COMMAND_ID_GETACTIVEPROF:
		command_getactiveprof(client, cmd, len);
		break;
	case COMMAND_ID_SETACTIVEPROF:
		command_setactiveprof(client, cmd, len);
		break;
	case COMMAND_ID_GETFREQ:
		command_getfreq(client, cmd, len);
		break;
	case COMMAND_ID_SUPPBUTTONS:
		command_suppbuttons(client, cmd, len);
		break;
	case COMMAND_ID_SUPPBUTFUNCS:
		command_suppbutfuncs(client, cmd, len);
		break;
	case COMMAND_ID_GETBUTFUNC:
		command_getbutfunc(client, cmd, len);
		break;
	case COMMAND_ID_SETBUTFUNC:
		command_setbutfunc(client, cmd, len);
		break;
	case COMMAND_ID_SUPPAXES:
		command_suppaxes(client, cmd, len);
		break;
	case COMMAND_ID_RECONFIGMICE:
		command_reconfigmice(client, cmd, len);
		break;
	case COMMAND_ID_GETMOUSEINFO:
		command_getmouseinfo(client, cmd, len);
		break;
	case COMMAND_ID_GETPROFNAME:
		command_getprofname(client, cmd, len);
		break;
	case COMMAND_ID_SETPROFNAME:
		command_setprofname(client, cmd, len);
		break;
	default:
		/* Unknown command. */
		break;
	}
}

static void handle_received_privileged_command(struct client *client,
					       const char *_cmd, unsigned int len)
{
	const struct command *cmd = (const struct command *)_cmd;

	if (len < COMMAND_HDR_SIZE)
		return;
	switch (cmd->hdr.id) {
	case COMMAND_PRIV_FLASHFW:
		command_flashfw(client, cmd, len);
		break;
	case COMMAND_PRIV_CLAIM:
		command_claim(client, cmd, len);
		break;
	case COMMAND_PRIV_RELEASE:
		command_release(client, cmd, len);
		break;
	default:
		/* Unknown command. */
		break;
	}
}

static int check_client_connections(void)
{
	char command[COMMAND_MAX_SIZE + 1] = { 0, };
	int nr;
	struct client *client, *next;
	int ret = 0;

	for (client = clients; client; ) {
		next = client->next;
		nr = recv(client->fd, command, COMMAND_MAX_SIZE, 0);
		if (nr < 0) {
			ret = -1;
			goto next_client;
		}
		if (nr == 0) {
			disconnect_client(&clients, client);
			goto next_client;
		}
		handle_received_command(client, command, nr);
  next_client:
		client = next;
	}

	return ret;
}

static int check_privileged_connections(void)
{
	char command[COMMAND_MAX_SIZE + 1] = { 0, };
	int nr;
	struct client *client, *next;
	int ret = 0;

	for (client = privileged_clients; client; ) {
		next = client->next;
		nr = recv(client->fd, command, COMMAND_MAX_SIZE, 0);
		if (nr < 0) {
			ret = -1;
			goto next_client;
		}
		if (nr == 0) {
			disconnect_client(&privileged_clients, client);
			goto next_client;
		}
		handle_received_privileged_command(client, command, nr);
  next_client:
		client = next;
	}

	return ret;
}

static void broadcast_notification(unsigned int notifyId, size_t size)
{
	struct reply r;
	struct client *client;

	for (client = clients; client; client = client->next) {
		r.hdr.id = notifyId;
		send_reply(client, &r, size);
	}
}

static void event_handler(enum razer_event event,
			  const struct razer_event_data *data)
{
	switch (event) {
	case RAZER_EV_MOUSE_ADD:
		logdebug("Broadcasting mouse-add event\n");
		broadcast_notification(NOTIFY_ID_NEWMOUSE,
				       REPLY_SIZE(notify_newmouse));
		break;
	case RAZER_EV_MOUSE_REMOVE:
		logdebug("Broadcasting mouse-remove event\n");
		broadcast_notification(NOTIFY_ID_DELMOUSE,
				       REPLY_SIZE(notify_delmouse));
		break;
	}
}

static int mainloop(void)
{
	struct client *client;
	int err;
	int errcount = 0;
	fd_set wait_fdset;
	int maxfd;

	loginfo("Razer device service daemon\n");

	setup_sighandler();
	err = setup_environment();
	if (err)
		return 1;
	err = razer_register_event_handler(event_handler);
	if (err) {
		logerr("Failed to register event handler\n");
		cleanup_environment();
		return 1;
	}

	mice = razer_rescan_mice();

	while (1) {
		FD_ZERO(&wait_fdset);

		/* Build fdset while tracking maximum fd. Skip and log fds >= FD_SETSIZE. */
		maxfd = -1;
		if (privsock >= 0) {
			if (privsock < FD_SETSIZE) {
				FD_SET(privsock, &wait_fdset);
				maxfd = max(maxfd, privsock);
			} else {
				logerr("privsock fd %d >= FD_SETSIZE (%d), skipping\n", privsock, FD_SETSIZE);
			}
		}
		if (ctlsock >= 0) {
			if (ctlsock < FD_SETSIZE) {
				FD_SET(ctlsock, &wait_fdset);
				maxfd = max(maxfd, ctlsock);
			} else {
				logerr("ctlsock fd %d >= FD_SETSIZE (%d), skipping\n", ctlsock, FD_SETSIZE);
			}
		}

		for (client = clients; client; client = client->next) {
			if (client->fd >= 0) {
				if (client->fd < FD_SETSIZE) {
					FD_SET(client->fd, &wait_fdset);
					maxfd = max(maxfd, client->fd);
				} else {
					logerr("Client fd %d >= FD_SETSIZE (%d), skipping\n", client->fd, FD_SETSIZE);
				}
			}
		}
		for (client = privileged_clients; client; client = client->next) {
			if (client->fd >= 0) {
				if (client->fd < FD_SETSIZE) {
					FD_SET(client->fd, &wait_fdset);
					maxfd = max(maxfd, client->fd);
				} else {
					logerr("Privileged client fd %d >= FD_SETSIZE (%d), skipping\n", client->fd, FD_SETSIZE);
				}
			}
		}

		if (maxfd < 0) {
			/* No valid fds to wait on. Avoid busy-looping. */
			razer_msleep(100);
			continue;
		}

		err = select(maxfd + 1, &wait_fdset, NULL, NULL, NULL);
		if (err == 0) /* no fd ready */
			err = -1;
		if (err > 0) {
			err = 0;

			err |= check_control_socket(privsock, &privileged_clients);
			err |= check_privileged_connections();

			err |= check_control_socket(ctlsock, &clients);
			err |= check_client_connections();
		}
		if (err) {
			if (errcount >= 3)
				razer_msleep(10);
			else
				errcount++;
		} else
			errcount = 0;
	}

	return 1;
}

static void usage(FILE *fd, int argc, char **argv)
{
	fprintf(fd, "Razer device service daemon\n\n");
	fprintf(fd, "Usage: %s [OPTIONS]\n", argv[0]);
	fprintf(fd, "\n");
	fprintf(fd, "  -B|--background           Fork into the background (daemon mode)\n");
	fprintf(fd, "  -c|--config PATH          Use specified config file. Defaults to %s\n",
		RAZER_DEFAULT_CONFIG);
	fprintf(fd, "  -C|--no-config            Do not load the config file\n");
	fprintf(fd, "  -p|--no-profemu           Disable profile emulation\n");
	fprintf(fd, "  -P|--pidfile PATH         Create a PID-file\n");
	fprintf(fd, "  -l|--loglevel LEVEL       Set the loglevel\n");
	fprintf(fd, "                            0=error, 1=warning, 2=info(default), 3=debug\n");
	fprintf(fd, "  -f|--force                Force remove sockets before starting up\n");
	fprintf(fd, "\n");
	fprintf(fd, "  -h|--help                 Print this help text\n");
}

static int parse_args(int argc, char **argv)
{
	static struct option long_options[] = {
		{ "help", no_argument, 0, 'h', },
		{ "version", no_argument, 0, 'v', },
		{ "background", no_argument, 0, 'B', },
		{ "config", required_argument, 0, 'c', },
		{ "no-config", no_argument, 0, 'C', },
		{ "no-profemu", no_argument, 0, 'p', },
		{ "pidfile", required_argument, 0, 'P', },
		{ "loglevel", required_argument, 0, 'l', },
		{ "force", no_argument, 0, 'f', },
		{ 0, },
	};

	int c, idx;

	while (1) {
		c = getopt_long(argc, argv, "hvBc:CpP:l:f",
				long_options, &idx);
		if (c == -1)
			break;
		switch (c) {
		case 'h':
		case 'v':
			usage(stdout, argc, argv);
			return 1;
		case 'B':
			cmdargs.background = 1;
			break;
		case 'c':
			cmdargs.configfile = optarg;
			break;
		case 'C':
			cmdargs.configfile = "";
			break;
		case 'p':
			cmdargs.no_profile_emu = 1;
			break;
		case 'P':
			cmdargs.pidfile = optarg;
			break;
		case 'l':
			if (sscanf(optarg, "%d", &cmdargs.loglevel) != 1) {
				fprintf(stderr, "Failed to parse --loglevel argument\n");
				return -1;
			}
			break;
		case 'f':
			cmdargs.force = 1;
			break;
		default:
			return -1;
		}
	}

	return 0;
}

int main(int argc, char **argv)
{
	int err;

	err = parse_args(argc, argv);
	if (err > 0)
		return 0;
	if (err)
		return err;

	if (cmdargs.background) {
		err = daemon(0, 0);
		if (err) {
			logerr("Failed to fork into the background: %s\n",
			       strerror(errno));
			return 1;
		}
		/* Child process */
		return mainloop();
	}

	return mainloop();
}
