diff options
author | Valentina Manea <valentina.manea.m@gmail.com> | 2014-08-20 07:30:59 +0300 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2014-08-25 10:38:56 -0700 |
commit | 588b48caf65c4a92af567948ec0025065e749ddf (patch) | |
tree | 6cf9771e522864db1685ffd7cabd31439fe93305 /tools/usb/usbip/src | |
parent | 1ddb55275d8489d0ecd4722a997531cf26a035cb (diff) | |
download | linux-588b48caf65c4a92af567948ec0025065e749ddf.tar.gz linux-588b48caf65c4a92af567948ec0025065e749ddf.tar.bz2 linux-588b48caf65c4a92af567948ec0025065e749ddf.zip |
usbip: move usbip userspace code out of staging
At this point, USB/IP userspace code is fully functional
and can be moved out of staging.
Signed-off-by: Valentina Manea <valentina.manea.m@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'tools/usb/usbip/src')
-rw-r--r-- | tools/usb/usbip/src/Makefile.am | 11 | ||||
-rw-r--r-- | tools/usb/usbip/src/usbip.c | 201 | ||||
-rw-r--r-- | tools/usb/usbip/src/usbip.h | 40 | ||||
-rw-r--r-- | tools/usb/usbip/src/usbip_attach.c | 241 | ||||
-rw-r--r-- | tools/usb/usbip/src/usbip_bind.c | 214 | ||||
-rw-r--r-- | tools/usb/usbip/src/usbip_detach.c | 110 | ||||
-rw-r--r-- | tools/usb/usbip/src/usbip_list.c | 283 | ||||
-rw-r--r-- | tools/usb/usbip/src/usbip_network.c | 303 | ||||
-rw-r--r-- | tools/usb/usbip/src/usbip_network.h | 185 | ||||
-rw-r--r-- | tools/usb/usbip/src/usbip_port.c | 57 | ||||
-rw-r--r-- | tools/usb/usbip/src/usbip_unbind.c | 141 | ||||
-rw-r--r-- | tools/usb/usbip/src/usbipd.c | 679 | ||||
-rw-r--r-- | tools/usb/usbip/src/utils.c | 52 | ||||
-rw-r--r-- | tools/usb/usbip/src/utils.h | 25 |
14 files changed, 2542 insertions, 0 deletions
diff --git a/tools/usb/usbip/src/Makefile.am b/tools/usb/usbip/src/Makefile.am new file mode 100644 index 000000000000..e81a4ebadeff --- /dev/null +++ b/tools/usb/usbip/src/Makefile.am @@ -0,0 +1,11 @@ +AM_CPPFLAGS = -I$(top_srcdir)/libsrc -DUSBIDS_FILE='"@USBIDS_DIR@/usb.ids"' +AM_CFLAGS = @EXTRA_CFLAGS@ +LDADD = $(top_builddir)/libsrc/libusbip.la + +sbin_PROGRAMS := usbip usbipd + +usbip_SOURCES := usbip.h utils.h usbip.c utils.c usbip_network.c \ + usbip_attach.c usbip_detach.c usbip_list.c \ + usbip_bind.c usbip_unbind.c usbip_port.c + +usbipd_SOURCES := usbip_network.h usbipd.c usbip_network.c diff --git a/tools/usb/usbip/src/usbip.c b/tools/usb/usbip/src/usbip.c new file mode 100644 index 000000000000..d7599d943529 --- /dev/null +++ b/tools/usb/usbip/src/usbip.c @@ -0,0 +1,201 @@ +/* + * command structure borrowed from udev + * (git://git.kernel.org/pub/scm/linux/hotplug/udev.git) + * + * Copyright (C) 2011 matt mooney <mfm@muteddisk.com> + * 2005-2007 Takahiro Hirofuchi + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdio.h> +#include <stdlib.h> + +#include <getopt.h> +#include <syslog.h> + +#include "usbip_common.h" +#include "usbip_network.h" +#include "usbip.h" + +static int usbip_help(int argc, char *argv[]); +static int usbip_version(int argc, char *argv[]); + +static const char usbip_version_string[] = PACKAGE_STRING; + +static const char usbip_usage_string[] = + "usbip [--debug] [--log] [--tcp-port PORT] [version]\n" + " [help] <command> <args>\n"; + +static void usbip_usage(void) +{ + printf("usage: %s", usbip_usage_string); +} + +struct command { + const char *name; + int (*fn)(int argc, char *argv[]); + const char *help; + void (*usage)(void); +}; + +static const struct command cmds[] = { + { + .name = "help", + .fn = usbip_help, + .help = NULL, + .usage = NULL + }, + { + .name = "version", + .fn = usbip_version, + .help = NULL, + .usage = NULL + }, + { + .name = "attach", + .fn = usbip_attach, + .help = "Attach a remote USB device", + .usage = usbip_attach_usage + }, + { + .name = "detach", + .fn = usbip_detach, + .help = "Detach a remote USB device", + .usage = usbip_detach_usage + }, + { + .name = "list", + .fn = usbip_list, + .help = "List exportable or local USB devices", + .usage = usbip_list_usage + }, + { + .name = "bind", + .fn = usbip_bind, + .help = "Bind device to " USBIP_HOST_DRV_NAME ".ko", + .usage = usbip_bind_usage + }, + { + .name = "unbind", + .fn = usbip_unbind, + .help = "Unbind device from " USBIP_HOST_DRV_NAME ".ko", + .usage = usbip_unbind_usage + }, + { + .name = "port", + .fn = usbip_port_show, + .help = "Show imported USB devices", + .usage = NULL + }, + { NULL, NULL, NULL, NULL } +}; + +static int usbip_help(int argc, char *argv[]) +{ + const struct command *cmd; + int i; + int ret = 0; + + if (argc > 1 && argv++) { + for (i = 0; cmds[i].name != NULL; i++) + if (!strcmp(cmds[i].name, argv[0]) && cmds[i].usage) { + cmds[i].usage(); + goto done; + } + ret = -1; + } + + usbip_usage(); + printf("\n"); + for (cmd = cmds; cmd->name != NULL; cmd++) + if (cmd->help != NULL) + printf(" %-10s %s\n", cmd->name, cmd->help); + printf("\n"); +done: + return ret; +} + +static int usbip_version(int argc, char *argv[]) +{ + (void) argc; + (void) argv; + + printf(PROGNAME " (%s)\n", usbip_version_string); + return 0; +} + +static int run_command(const struct command *cmd, int argc, char *argv[]) +{ + dbg("running command: `%s'", cmd->name); + return cmd->fn(argc, argv); +} + +int main(int argc, char *argv[]) +{ + static const struct option opts[] = { + { "debug", no_argument, NULL, 'd' }, + { "log", no_argument, NULL, 'l' }, + { "tcp-port", required_argument, NULL, 't' }, + { NULL, 0, NULL, 0 } + }; + + char *cmd; + int opt; + int i, rc = -1; + + usbip_use_stderr = 1; + opterr = 0; + for (;;) { + opt = getopt_long(argc, argv, "+dlt:", opts, NULL); + + if (opt == -1) + break; + + switch (opt) { + case 'd': + usbip_use_debug = 1; + break; + case 'l': + usbip_use_syslog = 1; + openlog("", LOG_PID, LOG_USER); + break; + case 't': + usbip_setup_port_number(optarg); + break; + case '?': + printf("usbip: invalid option\n"); + default: + usbip_usage(); + goto out; + } + } + + cmd = argv[optind]; + if (cmd) { + for (i = 0; cmds[i].name != NULL; i++) + if (!strcmp(cmds[i].name, cmd)) { + argc -= optind; + argv += optind; + optind = 0; + rc = run_command(&cmds[i], argc, argv); + goto out; + } + } + + /* invalid command */ + usbip_help(0, NULL); +out: + return (rc > -1 ? EXIT_SUCCESS : EXIT_FAILURE); +} diff --git a/tools/usb/usbip/src/usbip.h b/tools/usb/usbip/src/usbip.h new file mode 100644 index 000000000000..84fe66a9d8ad --- /dev/null +++ b/tools/usb/usbip/src/usbip.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2011 matt mooney <mfm@muteddisk.com> + * 2005-2007 Takahiro Hirofuchi + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __USBIP_H +#define __USBIP_H + +#ifdef HAVE_CONFIG_H +#include "../config.h" +#endif + +/* usbip commands */ +int usbip_attach(int argc, char *argv[]); +int usbip_detach(int argc, char *argv[]); +int usbip_list(int argc, char *argv[]); +int usbip_bind(int argc, char *argv[]); +int usbip_unbind(int argc, char *argv[]); +int usbip_port_show(int argc, char *argv[]); + +void usbip_attach_usage(void); +void usbip_detach_usage(void); +void usbip_list_usage(void); +void usbip_bind_usage(void); +void usbip_unbind_usage(void); + +#endif /* __USBIP_H */ diff --git a/tools/usb/usbip/src/usbip_attach.c b/tools/usb/usbip/src/usbip_attach.c new file mode 100644 index 000000000000..d58a14dfc094 --- /dev/null +++ b/tools/usb/usbip/src/usbip_attach.c @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2011 matt mooney <mfm@muteddisk.com> + * 2005-2007 Takahiro Hirofuchi + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <sys/stat.h> + +#include <limits.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> + +#include <fcntl.h> +#include <getopt.h> +#include <unistd.h> +#include <errno.h> + +#include "vhci_driver.h" +#include "usbip_common.h" +#include "usbip_network.h" +#include "usbip.h" + +static const char usbip_attach_usage_string[] = + "usbip attach <args>\n" + " -r, --remote=<host> The machine with exported USB devices\n" + " -b, --busid=<busid> Busid of the device on <host>\n"; + +void usbip_attach_usage(void) +{ + printf("usage: %s", usbip_attach_usage_string); +} + +#define MAX_BUFF 100 +static int record_connection(char *host, char *port, char *busid, int rhport) +{ + int fd; + char path[PATH_MAX+1]; + char buff[MAX_BUFF+1]; + int ret; + + ret = mkdir(VHCI_STATE_PATH, 0700); + if (ret < 0) { + /* if VHCI_STATE_PATH exists, then it better be a directory */ + if (errno == EEXIST) { + struct stat s; + + ret = stat(VHCI_STATE_PATH, &s); + if (ret < 0) + return -1; + if (!(s.st_mode & S_IFDIR)) + return -1; + } else + return -1; + } + + snprintf(path, PATH_MAX, VHCI_STATE_PATH"/port%d", rhport); + + fd = open(path, O_WRONLY|O_CREAT|O_TRUNC, S_IRWXU); + if (fd < 0) + return -1; + + snprintf(buff, MAX_BUFF, "%s %s %s\n", + host, port, busid); + + ret = write(fd, buff, strlen(buff)); + if (ret != (ssize_t) strlen(buff)) { + close(fd); + return -1; + } + + close(fd); + + return 0; +} + +static int import_device(int sockfd, struct usbip_usb_device *udev) +{ + int rc; + int port; + + rc = usbip_vhci_driver_open(); + if (rc < 0) { + err("open vhci_driver"); + return -1; + } + + port = usbip_vhci_get_free_port(); + if (port < 0) { + err("no free port"); + usbip_vhci_driver_close(); + return -1; + } + + rc = usbip_vhci_attach_device(port, sockfd, udev->busnum, + udev->devnum, udev->speed); + if (rc < 0) { + err("import device"); + usbip_vhci_driver_close(); + return -1; + } + + usbip_vhci_driver_close(); + + return port; +} + +static int query_import_device(int sockfd, char *busid) +{ + int rc; + struct op_import_request request; + struct op_import_reply reply; + uint16_t code = OP_REP_IMPORT; + + memset(&request, 0, sizeof(request)); + memset(&reply, 0, sizeof(reply)); + + /* send a request */ + rc = usbip_net_send_op_common(sockfd, OP_REQ_IMPORT, 0); + if (rc < 0) { + err("send op_common"); + return -1; + } + + strncpy(request.busid, busid, SYSFS_BUS_ID_SIZE-1); + + PACK_OP_IMPORT_REQUEST(0, &request); + + rc = usbip_net_send(sockfd, (void *) &request, sizeof(request)); + if (rc < 0) { + err("send op_import_request"); + return -1; + } + + /* receive a reply */ + rc = usbip_net_recv_op_common(sockfd, &code); + if (rc < 0) { + err("recv op_common"); + return -1; + } + + rc = usbip_net_recv(sockfd, (void *) &reply, sizeof(reply)); + if (rc < 0) { + err("recv op_import_reply"); + return -1; + } + + PACK_OP_IMPORT_REPLY(0, &reply); + + /* check the reply */ + if (strncmp(reply.udev.busid, busid, SYSFS_BUS_ID_SIZE)) { + err("recv different busid %s", reply.udev.busid); + return -1; + } + + /* import a device */ + return import_device(sockfd, &reply.udev); +} + +static int attach_device(char *host, char *busid) +{ + int sockfd; + int rc; + int rhport; + + sockfd = usbip_net_tcp_connect(host, usbip_port_string); + if (sockfd < 0) { + err("tcp connect"); + return -1; + } + + rhport = query_import_device(sockfd, busid); + if (rhport < 0) { + err("query"); + return -1; + } + + close(sockfd); + + rc = record_connection(host, usbip_port_string, busid, rhport); + if (rc < 0) { + err("record connection"); + return -1; + } + + return 0; +} + +int usbip_attach(int argc, char *argv[]) +{ + static const struct option opts[] = { + { "remote", required_argument, NULL, 'r' }, + { "busid", required_argument, NULL, 'b' }, + { NULL, 0, NULL, 0 } + }; + char *host = NULL; + char *busid = NULL; + int opt; + int ret = -1; + + for (;;) { + opt = getopt_long(argc, argv, "r:b:", opts, NULL); + + if (opt == -1) + break; + + switch (opt) { + case 'r': + host = optarg; + break; + case 'b': + busid = optarg; + break; + default: + goto err_out; + } + } + + if (!host || !busid) + goto err_out; + + ret = attach_device(host, busid); + goto out; + +err_out: + usbip_attach_usage(); +out: + return ret; +} diff --git a/tools/usb/usbip/src/usbip_bind.c b/tools/usb/usbip/src/usbip_bind.c new file mode 100644 index 000000000000..fa46141ae68b --- /dev/null +++ b/tools/usb/usbip/src/usbip_bind.c @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2011 matt mooney <mfm@muteddisk.com> + * 2005-2007 Takahiro Hirofuchi + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <libudev.h> + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <getopt.h> + +#include "usbip_common.h" +#include "utils.h" +#include "usbip.h" +#include "sysfs_utils.h" + +enum unbind_status { + UNBIND_ST_OK, + UNBIND_ST_USBIP_HOST, + UNBIND_ST_FAILED +}; + +static const char usbip_bind_usage_string[] = + "usbip bind <args>\n" + " -b, --busid=<busid> Bind " USBIP_HOST_DRV_NAME ".ko to device " + "on <busid>\n"; + +void usbip_bind_usage(void) +{ + printf("usage: %s", usbip_bind_usage_string); +} + +/* call at unbound state */ +static int bind_usbip(char *busid) +{ + char attr_name[] = "bind"; + char bind_attr_path[SYSFS_PATH_MAX]; + int rc = -1; + + snprintf(bind_attr_path, sizeof(bind_attr_path), "%s/%s/%s/%s/%s/%s", + SYSFS_MNT_PATH, SYSFS_BUS_NAME, SYSFS_BUS_TYPE, + SYSFS_DRIVERS_NAME, USBIP_HOST_DRV_NAME, attr_name); + + rc = write_sysfs_attribute(bind_attr_path, busid, strlen(busid)); + if (rc < 0) { + err("error binding device %s to driver: %s", busid, + strerror(errno)); + return -1; + } + + return 0; +} + +/* buggy driver may cause dead lock */ +static int unbind_other(char *busid) +{ + enum unbind_status status = UNBIND_ST_OK; + + char attr_name[] = "unbind"; + char unbind_attr_path[SYSFS_PATH_MAX]; + int rc = -1; + + struct udev *udev; + struct udev_device *dev; + const char *driver; + const char *bDevClass; + + /* Create libudev context. */ + udev = udev_new(); + + /* Get the device. */ + dev = udev_device_new_from_subsystem_sysname(udev, "usb", busid); + if (!dev) { + dbg("unable to find device with bus ID %s", busid); + goto err_close_busid_dev; + } + + /* Check what kind of device it is. */ + bDevClass = udev_device_get_sysattr_value(dev, "bDeviceClass"); + if (!bDevClass) { + dbg("unable to get bDevClass device attribute"); + goto err_close_busid_dev; + } + + if (!strncmp(bDevClass, "09", strlen(bDevClass))) { + dbg("skip unbinding of hub"); + goto err_close_busid_dev; + } + + /* Get the device driver. */ + driver = udev_device_get_driver(dev); + if (!driver) { + /* No driver bound to this device. */ + goto out; + } + + if (!strncmp(USBIP_HOST_DRV_NAME, driver, + strlen(USBIP_HOST_DRV_NAME))) { + /* Already bound to usbip-host. */ + status = UNBIND_ST_USBIP_HOST; + goto out; + } + + /* Unbind device from driver. */ + snprintf(unbind_attr_path, sizeof(unbind_attr_path), "%s/%s/%s/%s/%s/%s", + SYSFS_MNT_PATH, SYSFS_BUS_NAME, SYSFS_BUS_TYPE, + SYSFS_DRIVERS_NAME, driver, attr_name); + + rc = write_sysfs_attribute(unbind_attr_path, busid, strlen(busid)); + if (rc < 0) { + err("error unbinding device %s from driver", busid); + goto err_close_busid_dev; + } + + goto out; + +err_close_busid_dev: + status = UNBIND_ST_FAILED; +out: + udev_device_unref(dev); + udev_unref(udev); + + return status; +} + +static int bind_device(char *busid) +{ + int rc; + struct udev *udev; + struct udev_device *dev; + + /* Check whether the device with this bus ID exists. */ + udev = udev_new(); + dev = udev_device_new_from_subsystem_sysname(udev, "usb", busid); + if (!dev) { + err("device with the specified bus ID does not exist"); + return -1; + } + udev_unref(udev); + + rc = unbind_other(busid); + if (rc == UNBIND_ST_FAILED) { + err("could not unbind driver from device on busid %s", busid); + return -1; + } else if (rc == UNBIND_ST_USBIP_HOST) { + err("device on busid %s is already bound to %s", busid, + USBIP_HOST_DRV_NAME); + return -1; + } + + rc = modify_match_busid(busid, 1); + if (rc < 0) { + err("unable to bind device on %s", busid); + return -1; + } + + rc = bind_usbip(busid); + if (rc < 0) { + err("could not bind device to %s", USBIP_HOST_DRV_NAME); + modify_match_busid(busid, 0); + return -1; + } + + info("bind device on busid %s: complete", busid); + + return 0; +} + +int usbip_bind(int argc, char *argv[]) +{ + static const struct option opts[] = { + { "busid", required_argument, NULL, 'b' }, + { NULL, 0, NULL, 0 } + }; + + int opt; + int ret = -1; + + for (;;) { + opt = getopt_long(argc, argv, "b:", opts, NULL); + + if (opt == -1) + break; + + switch (opt) { + case 'b': + ret = bind_device(optarg); + goto out; + default: + goto err_out; + } + } + +err_out: + usbip_bind_usage(); +out: + return ret; +} diff --git a/tools/usb/usbip/src/usbip_detach.c b/tools/usb/usbip/src/usbip_detach.c new file mode 100644 index 000000000000..05c6d15856eb --- /dev/null +++ b/tools/usb/usbip/src/usbip_detach.c @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2011 matt mooney <mfm@muteddisk.com> + * 2005-2007 Takahiro Hirofuchi + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <ctype.h> +#include <limits.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <getopt.h> +#include <unistd.h> + +#include "vhci_driver.h" +#include "usbip_common.h" +#include "usbip_network.h" +#include "usbip.h" + +static const char usbip_detach_usage_string[] = + "usbip detach <args>\n" + " -p, --port=<port> " USBIP_VHCI_DRV_NAME + " port the device is on\n"; + +void usbip_detach_usage(void) +{ + printf("usage: %s", usbip_detach_usage_string); +} + +static int detach_port(char *port) +{ + int ret; + uint8_t portnum; + char path[PATH_MAX+1]; + + for (unsigned int i = 0; i < strlen(port); i++) + if (!isdigit(port[i])) { + err("invalid port %s", port); + return -1; + } + + /* check max port */ + + portnum = atoi(port); + + /* remove the port state file */ + + snprintf(path, PATH_MAX, VHCI_STATE_PATH"/port%d", portnum); + + remove(path); + rmdir(VHCI_STATE_PATH); + + ret = usbip_vhci_driver_open(); + if (ret < 0) { + err("open vhci_driver"); + return -1; + } + + ret = usbip_vhci_detach_device(portnum); + if (ret < 0) + return -1; + + usbip_vhci_driver_close(); + + return ret; +} + +int usbip_detach(int argc, char *argv[]) +{ + static const struct option opts[] = { + { "port", required_argument, NULL, 'p' }, + { NULL, 0, NULL, 0 } + }; + int opt; + int ret = -1; + + for (;;) { + opt = getopt_long(argc, argv, "p:", opts, NULL); + + if (opt == -1) + break; + + switch (opt) { + case 'p': + ret = detach_port(optarg); + goto out; + default: + goto err_out; + } + } + +err_out: + usbip_detach_usage(); +out: + return ret; +} diff --git a/tools/usb/usbip/src/usbip_list.c b/tools/usb/usbip/src/usbip_list.c new file mode 100644 index 000000000000..d5ce34a410e7 --- /dev/null +++ b/tools/usb/usbip/src/usbip_list.c @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2011 matt mooney <mfm@muteddisk.com> + * 2005-2007 Takahiro Hirofuchi + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <sys/types.h> +#include <libudev.h> + +#include <errno.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <getopt.h> +#include <netdb.h> +#include <unistd.h> + +#include "usbip_common.h" +#include "usbip_network.h" +#include "usbip.h" + +static const char usbip_list_usage_string[] = + "usbip list [-p|--parsable] <args>\n" + " -p, --parsable Parsable list format\n" + " -r, --remote=<host> List the exportable USB devices on <host>\n" + " -l, --local List the local USB devices\n"; + +void usbip_list_usage(void) +{ + printf("usage: %s", usbip_list_usage_string); +} + +static int get_exported_devices(char *host, int sockfd) +{ + char product_name[100]; + char class_name[100]; + struct op_devlist_reply reply; + uint16_t code = OP_REP_DEVLIST; + struct usbip_usb_device udev; + struct usbip_usb_interface uintf; + unsigned int i; + int rc, j; + + rc = usbip_net_send_op_common(sockfd, OP_REQ_DEVLIST, 0); + if (rc < 0) { + dbg("usbip_net_send_op_common failed"); + return -1; + } + + rc = usbip_net_recv_op_common(sockfd, &code); + if (rc < 0) { + dbg("usbip_net_recv_op_common failed"); + return -1; + } + + memset(&reply, 0, sizeof(reply)); + rc = usbip_net_recv(sockfd, &reply, sizeof(reply)); + if (rc < 0) { + dbg("usbip_net_recv_op_devlist failed"); + return -1; + } + PACK_OP_DEVLIST_REPLY(0, &reply); + dbg("exportable devices: %d\n", reply.ndev); + + if (reply.ndev == 0) { + info("no exportable devices found on %s", host); + return 0; + } + + printf("Exportable USB devices\n"); + printf("======================\n"); + printf(" - %s\n", host); + + for (i = 0; i < reply.ndev; i++) { + memset(&udev, 0, sizeof(udev)); + rc = usbip_net_recv(sockfd, &udev, sizeof(udev)); + if (rc < 0) { + dbg("usbip_net_recv failed: usbip_usb_device[%d]", i); + return -1; + } + usbip_net_pack_usb_device(0, &udev); + + usbip_names_get_product(product_name, sizeof(product_name), + udev.idVendor, udev.idProduct); + usbip_names_get_class(class_name, sizeof(class_name), + udev.bDeviceClass, udev.bDeviceSubClass, + udev.bDeviceProtocol); + printf("%11s: %s\n", udev.busid, product_name); + printf("%11s: %s\n", "", udev.path); + printf("%11s: %s\n", "", class_name); + + for (j = 0; j < udev.bNumInterfaces; j++) { + rc = usbip_net_recv(sockfd, &uintf, sizeof(uintf)); + if (rc < 0) { + err("usbip_net_recv failed: usbip_usb_intf[%d]", + j); + + return -1; + } + usbip_net_pack_usb_interface(0, &uintf); + + usbip_names_get_class(class_name, sizeof(class_name), + uintf.bInterfaceClass, + uintf.bInterfaceSubClass, + uintf.bInterfaceProtocol); + printf("%11s: %2d - %s\n", "", j, class_name); + } + + printf("\n"); + } + + return 0; +} + +static int list_exported_devices(char *host) +{ + int rc; + int sockfd; + + sockfd = usbip_net_tcp_connect(host, usbip_port_string); + if (sockfd < 0) { + err("could not connect to %s:%s: %s", host, + usbip_port_string, gai_strerror(sockfd)); + return -1; + } + dbg("connected to %s:%s", host, usbip_port_string); + + rc = get_exported_devices(host, sockfd); + if (rc < 0) { + err("failed to get device list from %s", host); + return -1; + } + + close(sockfd); + + return 0; +} + +static void print_device(const char *busid, const char *vendor, + const char *product, bool parsable) +{ + if (parsable) + printf("busid=%s#usbid=%.4s:%.4s#", busid, vendor, product); + else + printf(" - busid %s (%.4s:%.4s)\n", busid, vendor, product); +} + +static void print_product_name(char *product_name, bool parsable) +{ + if (!parsable) + printf(" %s\n", product_name); +} + +static int list_devices(bool parsable) +{ + struct udev *udev; + struct udev_enumerate *enumerate; + struct udev_list_entry *devices, *dev_list_entry; + struct udev_device *dev; + const char *path; + const char *idVendor; + const char *idProduct; + const char *bConfValue; + const char *bNumIntfs; + const char *busid; + char product_name[128]; + int ret = -1; + + /* Create libudev context. */ + udev = udev_new(); + + /* Create libudev device enumeration. */ + enumerate = udev_enumerate_new(udev); + + /* Take only USB devices that are not hubs and do not have + * the bInterfaceNumber attribute, i.e. are not interfaces. + */ + udev_enumerate_add_match_subsystem(enumerate, "usb"); + udev_enumerate_add_nomatch_sysattr(enumerate, "bDeviceClass", "09"); + udev_enumerate_add_nomatch_sysattr(enumerate, "bInterfaceNumber", NULL); + udev_enumerate_scan_devices(enumerate); + + devices = udev_enumerate_get_list_entry(enumerate); + + /* Show information about each device. */ + udev_list_entry_foreach(dev_list_entry, devices) { + path = udev_list_entry_get_name(dev_list_entry); + dev = udev_device_new_from_syspath(udev, path); + + /* Get device information. */ + idVendor = udev_device_get_sysattr_value(dev, "idVendor"); + idProduct = udev_device_get_sysattr_value(dev, "idProduct"); + bConfValue = udev_device_get_sysattr_value(dev, "bConfigurationValue"); + bNumIntfs = udev_device_get_sysattr_value(dev, "bNumInterfaces"); + busid = udev_device_get_sysname(dev); + if (!idVendor || !idProduct || !bConfValue || !bNumIntfs) { + err("problem getting device attributes: %s", + strerror(errno)); + goto err_out; + } + + /* Get product name. */ + usbip_names_get_product(product_name, sizeof(product_name), + strtol(idVendor, NULL, 16), + strtol(idProduct, NULL, 16)); + + /* Print information. */ + print_device(busid, idVendor, idProduct, parsable); + print_product_name(product_name, parsable); + + printf("\n"); + + udev_device_unref(dev); + } + + ret = 0; + +err_out: + udev_enumerate_unref(enumerate); + udev_unref(udev); + + return ret; +} + +int usbip_list(int argc, char *argv[]) +{ + static const struct option opts[] = { + { "parsable", no_argument, NULL, 'p' }, + { "remote", required_argument, NULL, 'r' }, + { "local", no_argument, NULL, 'l' }, + { NULL, 0, NULL, 0 } + }; + + bool parsable = false; + int opt; + int ret = -1; + + if (usbip_names_init(USBIDS_FILE)) + err("failed to open %s", USBIDS_FILE); + + for (;;) { + opt = getopt_long(argc, argv, "pr:l", opts, NULL); + + if (opt == -1) + break; + + switch (opt) { + case 'p': + parsable = true; + break; + case 'r': + ret = list_exported_devices(optarg); + goto out; + case 'l': + ret = list_devices(parsable); + goto out; + default: + goto err_out; + } + } + +err_out: + usbip_list_usage(); +out: + usbip_names_free(); + + return ret; +} diff --git a/tools/usb/usbip/src/usbip_network.c b/tools/usb/usbip/src/usbip_network.c new file mode 100644 index 000000000000..b4c37e76a6e0 --- /dev/null +++ b/tools/usb/usbip/src/usbip_network.c @@ -0,0 +1,303 @@ +/* + * Copyright (C) 2011 matt mooney <mfm@muteddisk.com> + * 2005-2007 Takahiro Hirofuchi + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <sys/socket.h> + +#include <string.h> + +#include <arpa/inet.h> +#include <netdb.h> +#include <netinet/tcp.h> +#include <unistd.h> + +#ifdef HAVE_LIBWRAP +#include <tcpd.h> +#endif + +#include "usbip_common.h" +#include "usbip_network.h" + +int usbip_port = 3240; +char *usbip_port_string = "3240"; + +void usbip_setup_port_number(char *arg) +{ + dbg("parsing port arg '%s'", arg); + char *end; + unsigned long int port = strtoul(arg, &end, 10); + + if (end == arg) { + err("port: could not parse '%s' as a decimal integer", arg); + return; + } + + if (*end != '\0') { + err("port: garbage at end of '%s'", arg); + return; + } + + if (port > UINT16_MAX) { + err("port: %s too high (max=%d)", + arg, UINT16_MAX); + return; + } + + usbip_port = port; + usbip_port_string = arg; + info("using port %d (\"%s\")", usbip_port, usbip_port_string); +} + +void usbip_net_pack_uint32_t(int pack, uint32_t *num) +{ + uint32_t i; + + if (pack) + i = htonl(*num); + else + i = ntohl(*num); + + *num = i; +} + +void usbip_net_pack_uint16_t(int pack, uint16_t *num) +{ + uint16_t i; + + if (pack) + i = htons(*num); + else + i = ntohs(*num); + + *num = i; +} + +void usbip_net_pack_usb_device(int pack, struct usbip_usb_device *udev) +{ + usbip_net_pack_uint32_t(pack, &udev->busnum); + usbip_net_pack_uint32_t(pack, &udev->devnum); + usbip_net_pack_uint32_t(pack, &udev->speed); + + usbip_net_pack_uint16_t(pack, &udev->idVendor); + usbip_net_pack_uint16_t(pack, &udev->idProduct); + usbip_net_pack_uint16_t(pack, &udev->bcdDevice); +} + +void usbip_net_pack_usb_interface(int pack __attribute__((unused)), + struct usbip_usb_interface *udev + __attribute__((unused))) +{ + /* uint8_t members need nothing */ +} + +static ssize_t usbip_net_xmit(int sockfd, void *buff, size_t bufflen, + int sending) +{ + ssize_t nbytes; + ssize_t total = 0; + + if (!bufflen) + return 0; + + do { + if (sending) + nbytes = send(sockfd, buff, bufflen, 0); + else + nbytes = recv(sockfd, buff, bufflen, MSG_WAITALL); + + if (nbytes <= 0) + return -1; + + buff = (void *)((intptr_t) buff + nbytes); + bufflen -= nbytes; + total += nbytes; + + } while (bufflen > 0); + + return total; +} + +ssize_t usbip_net_recv(int sockfd, void *buff, size_t bufflen) +{ + return usbip_net_xmit(sockfd, buff, bufflen, 0); +} + +ssize_t usbip_net_send(int sockfd, void *buff, size_t bufflen) +{ + return usbip_net_xmit(sockfd, buff, bufflen, 1); +} + +int usbip_net_send_op_common(int sockfd, uint32_t code, uint32_t status) +{ + struct op_common op_common; + int rc; + + memset(&op_common, 0, sizeof(op_common)); + + op_common.version = USBIP_VERSION; + op_common.code = code; + op_common.status = status; + + PACK_OP_COMMON(1, &op_common); + + rc = usbip_net_send(sockfd, &op_common, sizeof(op_common)); + if (rc < 0) { + dbg("usbip_net_send failed: %d", rc); + return -1; + } + + return 0; +} + +int usbip_net_recv_op_common(int sockfd, uint16_t *code) +{ + struct op_common op_common; + int rc; + + memset(&op_common, 0, sizeof(op_common)); + + rc = usbip_net_recv(sockfd, &op_common, sizeof(op_common)); + if (rc < 0) { + dbg("usbip_net_recv failed: %d", rc); + goto err; + } + + PACK_OP_COMMON(0, &op_common); + + if (op_common.version != USBIP_VERSION) { + dbg("version mismatch: %d %d", op_common.version, + USBIP_VERSION); + goto err; + } + + switch (*code) { + case OP_UNSPEC: + break; + default: + if (op_common.code != *code) { + dbg("unexpected pdu %#0x for %#0x", op_common.code, + *code); + goto err; + } + } + + if (op_common.status != ST_OK) { + dbg("request failed at peer: %d", op_common.status); + goto err; + } + + *code = op_common.code; + + return 0; +err: + return -1; +} + +int usbip_net_set_reuseaddr(int sockfd) +{ + const int val = 1; + int ret; + + ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)); + if (ret < 0) + dbg("setsockopt: SO_REUSEADDR"); + + return ret; +} + +int usbip_net_set_nodelay(int sockfd) +{ + const int val = 1; + int ret; + + ret = setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val)); + if (ret < 0) + dbg("setsockopt: TCP_NODELAY"); + + return ret; +} + +int usbip_net_set_keepalive(int sockfd) +{ + const int val = 1; + int ret; + + ret = setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)); + if (ret < 0) + dbg("setsockopt: SO_KEEPALIVE"); + + return ret; +} + +int usbip_net_set_v6only(int sockfd) +{ + const int val = 1; + int ret; + + ret = setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val)); + if (ret < 0) + dbg("setsockopt: IPV6_V6ONLY"); + + return ret; +} + +/* + * IPv6 Ready + */ +int usbip_net_tcp_connect(char *hostname, char *service) +{ + struct addrinfo hints, *res, *rp; + int sockfd; + int ret; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + /* get all possible addresses */ + ret = getaddrinfo(hostname, service, &hints, &res); + if (ret < 0) { + dbg("getaddrinfo: %s service %s: %s", hostname, service, + gai_strerror(ret)); + return ret; + } + + /* try the addresses */ + for (rp = res; rp; rp = rp->ai_next) { + sockfd = socket(rp->ai_family, rp->ai_socktype, + rp->ai_protocol); + if (sockfd < 0) + continue; + + /* should set TCP_NODELAY for usbip */ + usbip_net_set_nodelay(sockfd); + /* TODO: write code for heartbeat */ + usbip_net_set_keepalive(sockfd); + + if (connect(sockfd, rp->ai_addr, rp->ai_addrlen) == 0) + break; + + close(sockfd); + } + + freeaddrinfo(res); + + if (!rp) + return EAI_SYSTEM; + + return sockfd; +} diff --git a/tools/usb/usbip/src/usbip_network.h b/tools/usb/usbip/src/usbip_network.h new file mode 100644 index 000000000000..c1e875cf1078 --- /dev/null +++ b/tools/usb/usbip/src/usbip_network.h @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2005-2007 Takahiro Hirofuchi + */ + +#ifndef __USBIP_NETWORK_H +#define __USBIP_NETWORK_H + +#ifdef HAVE_CONFIG_H +#include "../config.h" +#endif + +#include <sys/types.h> + +#include <stdint.h> + +extern int usbip_port; +extern char *usbip_port_string; +void usbip_setup_port_number(char *arg); + +/* ---------------------------------------------------------------------- */ +/* Common header for all the kinds of PDUs. */ +struct op_common { + uint16_t version; + +#define OP_REQUEST (0x80 << 8) +#define OP_REPLY (0x00 << 8) + uint16_t code; + + /* add more error code */ +#define ST_OK 0x00 +#define ST_NA 0x01 + uint32_t status; /* op_code status (for reply) */ + +} __attribute__((packed)); + +#define PACK_OP_COMMON(pack, op_common) do {\ + usbip_net_pack_uint16_t(pack, &(op_common)->version);\ + usbip_net_pack_uint16_t(pack, &(op_common)->code);\ + usbip_net_pack_uint32_t(pack, &(op_common)->status);\ +} while (0) + +/* ---------------------------------------------------------------------- */ +/* Dummy Code */ +#define OP_UNSPEC 0x00 +#define OP_REQ_UNSPEC OP_UNSPEC +#define OP_REP_UNSPEC OP_UNSPEC + +/* ---------------------------------------------------------------------- */ +/* Retrieve USB device information. (still not used) */ +#define OP_DEVINFO 0x02 +#define OP_REQ_DEVINFO (OP_REQUEST | OP_DEVINFO) +#define OP_REP_DEVINFO (OP_REPLY | OP_DEVINFO) + +struct op_devinfo_request { + char busid[SYSFS_BUS_ID_SIZE]; +} __attribute__((packed)); + +struct op_devinfo_reply { + struct usbip_usb_device udev; + struct usbip_usb_interface uinf[]; +} __attribute__((packed)); + +/* ---------------------------------------------------------------------- */ +/* Import a remote USB device. */ +#define OP_IMPORT 0x03 +#define OP_REQ_IMPORT (OP_REQUEST | OP_IMPORT) +#define OP_REP_IMPORT (OP_REPLY | OP_IMPORT) + +struct op_import_request { + char busid[SYSFS_BUS_ID_SIZE]; +} __attribute__((packed)); + +struct op_import_reply { + struct usbip_usb_device udev; +// struct usbip_usb_interface uinf[]; +} __attribute__((packed)); + +#define PACK_OP_IMPORT_REQUEST(pack, request) do {\ +} while (0) + +#define PACK_OP_IMPORT_REPLY(pack, reply) do {\ + usbip_net_pack_usb_device(pack, &(reply)->udev);\ +} while (0) + +/* ---------------------------------------------------------------------- */ +/* Export a USB device to a remote host. */ +#define OP_EXPORT 0x06 +#define OP_REQ_EXPORT (OP_REQUEST | OP_EXPORT) +#define OP_REP_EXPORT (OP_REPLY | OP_EXPORT) + +struct op_export_request { + struct usbip_usb_device udev; +} __attribute__((packed)); + +struct op_export_reply { + int returncode; +} __attribute__((packed)); + + +#define PACK_OP_EXPORT_REQUEST(pack, request) do {\ + usbip_net_pack_usb_device(pack, &(request)->udev);\ +} while (0) + +#define PACK_OP_EXPORT_REPLY(pack, reply) do {\ +} while (0) + +/* ---------------------------------------------------------------------- */ +/* un-Export a USB device from a remote host. */ +#define OP_UNEXPORT 0x07 +#define OP_REQ_UNEXPORT (OP_REQUEST | OP_UNEXPORT) +#define OP_REP_UNEXPORT (OP_REPLY | OP_UNEXPORT) + +struct op_unexport_request { + struct usbip_usb_device udev; +} __attribute__((packed)); + +struct op_unexport_reply { + int returncode; +} __attribute__((packed)); + +#define PACK_OP_UNEXPORT_REQUEST(pack, request) do {\ + usbip_net_pack_usb_device(pack, &(request)->udev);\ +} while (0) + +#define PACK_OP_UNEXPORT_REPLY(pack, reply) do {\ +} while (0) + +/* ---------------------------------------------------------------------- */ +/* Negotiate IPSec encryption key. (still not used) */ +#define OP_CRYPKEY 0x04 +#define OP_REQ_CRYPKEY (OP_REQUEST | OP_CRYPKEY) +#define OP_REP_CRYPKEY (OP_REPLY | OP_CRYPKEY) + +struct op_crypkey_request { + /* 128bit key */ + uint32_t key[4]; +} __attribute__((packed)); + +struct op_crypkey_reply { + uint32_t __reserved; +} __attribute__((packed)); + + +/* ---------------------------------------------------------------------- */ +/* Retrieve the list of exported USB devices. */ +#define OP_DEVLIST 0x05 +#define OP_REQ_DEVLIST (OP_REQUEST | OP_DEVLIST) +#define OP_REP_DEVLIST (OP_REPLY | OP_DEVLIST) + +struct op_devlist_request { +} __attribute__((packed)); + +struct op_devlist_reply { + uint32_t ndev; + /* followed by reply_extra[] */ +} __attribute__((packed)); + +struct op_devlist_reply_extra { + struct usbip_usb_device udev; + struct usbip_usb_interface uinf[]; +} __attribute__((packed)); + +#define PACK_OP_DEVLIST_REQUEST(pack, request) do {\ +} while (0) + +#define PACK_OP_DEVLIST_REPLY(pack, reply) do {\ + usbip_net_pack_uint32_t(pack, &(reply)->ndev);\ +} while (0) + +void usbip_net_pack_uint32_t(int pack, uint32_t *num); +void usbip_net_pack_uint16_t(int pack, uint16_t *num); +void usbip_net_pack_usb_device(int pack, struct usbip_usb_device *udev); +void usbip_net_pack_usb_interface(int pack, struct usbip_usb_interface *uinf); + +ssize_t usbip_net_recv(int sockfd, void *buff, size_t bufflen); +ssize_t usbip_net_send(int sockfd, void *buff, size_t bufflen); +int usbip_net_send_op_common(int sockfd, uint32_t code, uint32_t status); +int usbip_net_recv_op_common(int sockfd, uint16_t *code); +int usbip_net_set_reuseaddr(int sockfd); +int usbip_net_set_nodelay(int sockfd); +int usbip_net_set_keepalive(int sockfd); +int usbip_net_set_v6only(int sockfd); +int usbip_net_tcp_connect(char *hostname, char *port); + +#endif /* __USBIP_NETWORK_H */ diff --git a/tools/usb/usbip/src/usbip_port.c b/tools/usb/usbip/src/usbip_port.c new file mode 100644 index 000000000000..a2e884fd9226 --- /dev/null +++ b/tools/usb/usbip/src/usbip_port.c @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2011 matt mooney <mfm@muteddisk.com> + * 2005-2007 Takahiro Hirofuchi + * + * 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 "vhci_driver.h" +#include "usbip_common.h" + +static int list_imported_devices(void) +{ + int i; + struct usbip_imported_device *idev; + int ret; + + ret = usbip_vhci_driver_open(); + if (ret < 0) { + err("open vhci_driver"); + return -1; + } + + printf("Imported USB devices\n"); + printf("====================\n"); + + for (i = 0; i < vhci_driver->nports; i++) { + idev = &vhci_driver->idev[i]; + + if (usbip_vhci_imported_device_dump(idev) < 0) + ret = -1; + } + + usbip_vhci_driver_close(); + + return ret; + +} + +int usbip_port_show(__attribute__((unused)) int argc, + __attribute__((unused)) char *argv[]) +{ + int ret; + + ret = list_imported_devices(); + if (ret < 0) + err("list imported devices"); + + return ret; +} diff --git a/tools/usb/usbip/src/usbip_unbind.c b/tools/usb/usbip/src/usbip_unbind.c new file mode 100644 index 000000000000..a4a496c9cbaf --- /dev/null +++ b/tools/usb/usbip/src/usbip_unbind.c @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2011 matt mooney <mfm@muteddisk.com> + * 2005-2007 Takahiro Hirofuchi + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <libudev.h> + +#include <errno.h> +#include <stdio.h> +#include <string.h> + +#include <getopt.h> + +#include "usbip_common.h" +#include "utils.h" +#include "usbip.h" +#include "sysfs_utils.h" + +static const char usbip_unbind_usage_string[] = + "usbip unbind <args>\n" + " -b, --busid=<busid> Unbind " USBIP_HOST_DRV_NAME ".ko from " + "device on <busid>\n"; + +void usbip_unbind_usage(void) +{ + printf("usage: %s", usbip_unbind_usage_string); +} + +static int unbind_device(char *busid) +{ + char bus_type[] = "usb"; + int rc, ret = -1; + + char unbind_attr_name[] = "unbind"; + char unbind_attr_path[SYSFS_PATH_MAX]; + char rebind_attr_name[] = "rebind"; + char rebind_attr_path[SYSFS_PATH_MAX]; + + struct udev *udev; + struct udev_device *dev; + const char *driver; + + /* Create libudev context. */ + udev = udev_new(); + + /* Check whether the device with this bus ID exists. */ + dev = udev_device_new_from_subsystem_sysname(udev, "usb", busid); + if (!dev) { + err("device with the specified bus ID does not exist"); + goto err_close_udev; + } + + /* Check whether the device is using usbip-host driver. */ + driver = udev_device_get_driver(dev); + if (!driver || strcmp(driver, "usbip-host")) { + err("device is not bound to usbip-host driver"); + goto err_close_udev; + } + + /* Unbind device from driver. */ + snprintf(unbind_attr_path, sizeof(unbind_attr_path), "%s/%s/%s/%s/%s/%s", + SYSFS_MNT_PATH, SYSFS_BUS_NAME, bus_type, SYSFS_DRIVERS_NAME, + USBIP_HOST_DRV_NAME, unbind_attr_name); + + rc = write_sysfs_attribute(unbind_attr_path, busid, strlen(busid)); + if (rc < 0) { + err("error unbinding device %s from driver", busid); + goto err_close_udev; + } + + /* Notify driver of unbind. */ + rc = modify_match_busid(busid, 0); + if (rc < 0) { + err("unable to unbind device on %s", busid); + goto err_close_udev; + } + + /* Trigger new probing. */ + snprintf(rebind_attr_path, sizeof(unbind_attr_path), "%s/%s/%s/%s/%s/%s", + SYSFS_MNT_PATH, SYSFS_BUS_NAME, bus_type, SYSFS_DRIVERS_NAME, + USBIP_HOST_DRV_NAME, rebind_attr_name); + + rc = write_sysfs_attribute(rebind_attr_path, busid, strlen(busid)); + if (rc < 0) { + err("error rebinding"); + goto err_close_udev; + } + + ret = 0; + info("unbind device on busid %s: complete", busid); + +err_close_udev: + udev_device_unref(dev); + udev_unref(udev); + + return ret; +} + +int usbip_unbind(int argc, char *argv[]) +{ + static const struct option opts[] = { + { "busid", required_argument, NULL, 'b' }, + { NULL, 0, NULL, 0 } + }; + + int opt; + int ret = -1; + + for (;;) { + opt = getopt_long(argc, argv, "b:", opts, NULL); + + if (opt == -1) + break; + + switch (opt) { + case 'b': + ret = unbind_device(optarg); + goto out; + default: + goto err_out; + } + } + +err_out: + usbip_unbind_usage(); +out: + return ret; +} diff --git a/tools/usb/usbip/src/usbipd.c b/tools/usb/usbip/src/usbipd.c new file mode 100644 index 000000000000..2f87f2d348ba --- /dev/null +++ b/tools/usb/usbip/src/usbipd.c @@ -0,0 +1,679 @@ +/* + * Copyright (C) 2011 matt mooney <mfm@muteddisk.com> + * 2005-2007 Takahiro Hirofuchi + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifdef HAVE_CONFIG_H +#include "../config.h" +#endif + +#define _GNU_SOURCE +#include <errno.h> +#include <unistd.h> +#include <netdb.h> +#include <string.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <arpa/inet.h> +#include <sys/socket.h> +#include <netinet/in.h> + +#ifdef HAVE_LIBWRAP +#include <tcpd.h> +#endif + +#include <getopt.h> +#include <signal.h> +#include <poll.h> + +#include "usbip_host_driver.h" +#include "usbip_common.h" +#include "usbip_network.h" +#include "list.h" + +#undef PROGNAME +#define PROGNAME "usbipd" +#define MAXSOCKFD 20 + +#define MAIN_LOOP_TIMEOUT 10 + +#define DEFAULT_PID_FILE "/var/run/" PROGNAME ".pid" + +static const char usbip_version_string[] = PACKAGE_STRING; + +static const char usbipd_help_string[] = + "usage: usbipd [options]\n" + "\n" + " -4, --ipv4\n" + " Bind to IPv4. Default is both.\n" + "\n" + " -6, --ipv6\n" + " Bind to IPv6. Default is both.\n" + "\n" + " -D, --daemon\n" + " Run as a daemon process.\n" + "\n" + " -d, --debug\n" + " Print debugging information.\n" + "\n" + " -PFILE, --pid FILE\n" + " Write process id to FILE.\n" + " If no FILE specified, use " DEFAULT_PID_FILE "\n" + "\n" + " -tPORT, --tcp-port PORT\n" + " Listen on TCP/IP port PORT.\n" + "\n" + " -h, --help\n" + " Print this help.\n" + "\n" + " -v, --version\n" + " Show version.\n"; + +static void usbipd_help(void) +{ + printf("%s\n", usbipd_help_string); +} + +static int recv_request_import(int sockfd) +{ + struct op_import_request req; + struct op_common reply; + struct usbip_exported_device *edev; + struct usbip_usb_device pdu_udev; + struct list_head *i; + int found = 0; + int error = 0; + int rc; + + memset(&req, 0, sizeof(req)); + memset(&reply, 0, sizeof(reply)); + + rc = usbip_net_recv(sockfd, &req, sizeof(req)); + if (rc < 0) { + dbg("usbip_net_recv failed: import request"); + return -1; + } + PACK_OP_IMPORT_REQUEST(0, &req); + + list_for_each(i, &host_driver->edev_list) { + edev = list_entry(i, struct usbip_exported_device, node); + if (!strncmp(req.busid, edev->udev.busid, SYSFS_BUS_ID_SIZE)) { + info("found requested device: %s", req.busid); + found = 1; + break; + } + } + + if (found) { + /* should set TCP_NODELAY for usbip */ + usbip_net_set_nodelay(sockfd); + + /* export device needs a TCP/IP socket descriptor */ + rc = usbip_host_export_device(edev, sockfd); + if (rc < 0) + error = 1; + } else { + info("requested device not found: %s", req.busid); + error = 1; + } + + rc = usbip_net_send_op_common(sockfd, OP_REP_IMPORT, + (!error ? ST_OK : ST_NA)); + if (rc < 0) { + dbg("usbip_net_send_op_common failed: %#0x", OP_REP_IMPORT); + return -1; + } + + if (error) { + dbg("import request busid %s: failed", req.busid); + return -1; + } + + memcpy(&pdu_udev, &edev->udev, sizeof(pdu_udev)); + usbip_net_pack_usb_device(1, &pdu_udev); + + rc = usbip_net_send(sockfd, &pdu_udev, sizeof(pdu_udev)); + if (rc < 0) { + dbg("usbip_net_send failed: devinfo"); + return -1; + } + + dbg("import request busid %s: complete", req.busid); + + return 0; +} + +static int send_reply_devlist(int connfd) +{ + struct usbip_exported_device *edev; + struct usbip_usb_device pdu_udev; + struct usbip_usb_interface pdu_uinf; + struct op_devlist_reply reply; + struct list_head *j; + int rc, i; + + reply.ndev = 0; + /* number of exported devices */ + list_for_each(j, &host_driver->edev_list) { + reply.ndev += 1; + } + info("exportable devices: %d", reply.ndev); + + rc = usbip_net_send_op_common(connfd, OP_REP_DEVLIST, ST_OK); + if (rc < 0) { + dbg("usbip_net_send_op_common failed: %#0x", OP_REP_DEVLIST); + return -1; + } + PACK_OP_DEVLIST_REPLY(1, &reply); + + rc = usbip_net_send(connfd, &reply, sizeof(reply)); + if (rc < 0) { + dbg("usbip_net_send failed: %#0x", OP_REP_DEVLIST); + return -1; + } + + list_for_each(j, &host_driver->edev_list) { + edev = list_entry(j, struct usbip_exported_device, node); + dump_usb_device(&edev->udev); + memcpy(&pdu_udev, &edev->udev, sizeof(pdu_udev)); + usbip_net_pack_usb_device(1, &pdu_udev); + + rc = usbip_net_send(connfd, &pdu_udev, sizeof(pdu_udev)); + if (rc < 0) { + dbg("usbip_net_send failed: pdu_udev"); + return -1; + } + + for (i = 0; i < edev->udev.bNumInterfaces; i++) { + dump_usb_interface(&edev->uinf[i]); + memcpy(&pdu_uinf, &edev->uinf[i], sizeof(pdu_uinf)); + usbip_net_pack_usb_interface(1, &pdu_uinf); + + rc = usbip_net_send(connfd, &pdu_uinf, + sizeof(pdu_uinf)); + if (rc < 0) { + err("usbip_net_send failed: pdu_uinf"); + return -1; + } + } + } + + return 0; +} + +static int recv_request_devlist(int connfd) +{ + struct op_devlist_request req; + int rc; + + memset(&req, 0, sizeof(req)); + + rc = usbip_net_recv(connfd, &req, sizeof(req)); + if (rc < 0) { + dbg("usbip_net_recv failed: devlist request"); + return -1; + } + + rc = send_reply_devlist(connfd); + if (rc < 0) { + dbg("send_reply_devlist failed"); + return -1; + } + + return 0; +} + +static int recv_pdu(int connfd) +{ + uint16_t code = OP_UNSPEC; + int ret; + + ret = usbip_net_recv_op_common(connfd, &code); + if (ret < 0) { + dbg("could not receive opcode: %#0x", code); + return -1; + } + + ret = usbip_host_refresh_device_list(); + if (ret < 0) { + dbg("could not refresh device list: %d", ret); + return -1; + } + + info("received request: %#0x(%d)", code, connfd); + switch (code) { + case OP_REQ_DEVLIST: + ret = recv_request_devlist(connfd); + break; + case OP_REQ_IMPORT: + ret = recv_request_import(connfd); + break; + case OP_REQ_DEVINFO: + case OP_REQ_CRYPKEY: + default: + err("received an unknown opcode: %#0x", code); + ret = -1; + } + + if (ret == 0) + info("request %#0x(%d): complete", code, connfd); + else + info("request %#0x(%d): failed", code, connfd); + + return ret; +} + +#ifdef HAVE_LIBWRAP +static int tcpd_auth(int connfd) +{ + struct request_info request; + int rc; + + request_init(&request, RQ_DAEMON, PROGNAME, RQ_FILE, connfd, 0); + fromhost(&request); + rc = hosts_access(&request); + if (rc == 0) + return -1; + + return 0; +} +#endif + +static int do_accept(int listenfd) +{ + int connfd; + struct sockaddr_storage ss; + socklen_t len = sizeof(ss); + char host[NI_MAXHOST], port[NI_MAXSERV]; + int rc; + + memset(&ss, 0, sizeof(ss)); + + connfd = accept(listenfd, (struct sockaddr *)&ss, &len); + if (connfd < 0) { + err("failed to accept connection"); + return -1; + } + + rc = getnameinfo((struct sockaddr *)&ss, len, host, sizeof(host), + port, sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV); + if (rc) + err("getnameinfo: %s", gai_strerror(rc)); + +#ifdef HAVE_LIBWRAP + rc = tcpd_auth(connfd); + if (rc < 0) { + info("denied access from %s", host); + close(connfd); + return -1; + } +#endif + info("connection from %s:%s", host, port); + + return connfd; +} + +int process_request(int listenfd) +{ + pid_t childpid; + int connfd; + + connfd = do_accept(listenfd); + if (connfd < 0) + return -1; + childpid = fork(); + if (childpid == 0) { + close(listenfd); + recv_pdu(connfd); + exit(0); + } + close(connfd); + return 0; +} + +static void addrinfo_to_text(struct addrinfo *ai, char buf[], + const size_t buf_size) +{ + char hbuf[NI_MAXHOST]; + char sbuf[NI_MAXSERV]; + int rc; + + buf[0] = '\0'; + + rc = getnameinfo(ai->ai_addr, ai->ai_addrlen, hbuf, sizeof(hbuf), + sbuf, sizeof(sbuf), NI_NUMERICHOST | NI_NUMERICSERV); + if (rc) + err("getnameinfo: %s", gai_strerror(rc)); + + snprintf(buf, buf_size, "%s:%s", hbuf, sbuf); +} + +static int listen_all_addrinfo(struct addrinfo *ai_head, int sockfdlist[], + int maxsockfd) +{ + struct addrinfo *ai; + int ret, nsockfd = 0; + const size_t ai_buf_size = NI_MAXHOST + NI_MAXSERV + 2; + char ai_buf[ai_buf_size]; + + for (ai = ai_head; ai && nsockfd < maxsockfd; ai = ai->ai_next) { + int sock; + + addrinfo_to_text(ai, ai_buf, ai_buf_size); + dbg("opening %s", ai_buf); + sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if (sock < 0) { + err("socket: %s: %d (%s)", + ai_buf, errno, strerror(errno)); + continue; + } + + usbip_net_set_reuseaddr(sock); + usbip_net_set_nodelay(sock); + /* We use seperate sockets for IPv4 and IPv6 + * (see do_standalone_mode()) */ + usbip_net_set_v6only(sock); + + if (sock >= FD_SETSIZE) { + err("FD_SETSIZE: %s: sock=%d, max=%d", + ai_buf, sock, FD_SETSIZE); + close(sock); + continue; + } + + ret = bind(sock, ai->ai_addr, ai->ai_addrlen); + if (ret < 0) { + err("bind: %s: %d (%s)", + ai_buf, errno, strerror(errno)); + close(sock); + continue; + } + + ret = listen(sock, SOMAXCONN); + if (ret < 0) { + err("listen: %s: %d (%s)", + ai_buf, errno, strerror(errno)); + close(sock); + continue; + } + + info("listening on %s", ai_buf); + sockfdlist[nsockfd++] = sock; + } + + return nsockfd; +} + +static struct addrinfo *do_getaddrinfo(char *host, int ai_family) +{ + struct addrinfo hints, *ai_head; + int rc; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = ai_family; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; + + rc = getaddrinfo(host, usbip_port_string, &hints, &ai_head); + if (rc) { + err("failed to get a network address %s: %s", usbip_port_string, + gai_strerror(rc)); + return NULL; + } + + return ai_head; +} + +static void signal_handler(int i) +{ + dbg("received '%s' signal", strsignal(i)); +} + +static void set_signal(void) +{ + struct sigaction act; + + memset(&act, 0, sizeof(act)); + act.sa_handler = signal_handler; + sigemptyset(&act.sa_mask); + sigaction(SIGTERM, &act, NULL); + sigaction(SIGINT, &act, NULL); + act.sa_handler = SIG_IGN; + sigaction(SIGCLD, &act, NULL); +} + +static const char *pid_file; + +static void write_pid_file(void) +{ + if (pid_file) { + dbg("creating pid file %s", pid_file); + FILE *fp; + + fp = fopen(pid_file, "w"); + if (!fp) { + err("pid_file: %s: %d (%s)", + pid_file, errno, strerror(errno)); + return; + } + fprintf(fp, "%d\n", getpid()); + fclose(fp); + } +} + +static void remove_pid_file(void) +{ + if (pid_file) { + dbg("removing pid file %s", pid_file); + unlink(pid_file); + } +} + +static int do_standalone_mode(int daemonize, int ipv4, int ipv6) +{ + struct addrinfo *ai_head; + int sockfdlist[MAXSOCKFD]; + int nsockfd, family; + int i, terminate; + struct pollfd *fds; + struct timespec timeout; + sigset_t sigmask; + + if (usbip_host_driver_open()) { + err("please load " USBIP_CORE_MOD_NAME ".ko and " + USBIP_HOST_DRV_NAME ".ko!"); + return -1; + } + + if (daemonize) { + if (daemon(0, 0) < 0) { + err("daemonizing failed: %s", strerror(errno)); + usbip_host_driver_close(); + return -1; + } + umask(0); + usbip_use_syslog = 1; + } + set_signal(); + write_pid_file(); + + info("starting " PROGNAME " (%s)", usbip_version_string); + + /* + * To suppress warnings on systems with bindv6only disabled + * (default), we use seperate sockets for IPv6 and IPv4 and set + * IPV6_V6ONLY on the IPv6 sockets. + */ + if (ipv4 && ipv6) + family = AF_UNSPEC; + else if (ipv4) + family = AF_INET; + else + family = AF_INET6; + + ai_head = do_getaddrinfo(NULL, family); + if (!ai_head) { + usbip_host_driver_close(); + return -1; + } + nsockfd = listen_all_addrinfo(ai_head, sockfdlist, + sizeof(sockfdlist) / sizeof(*sockfdlist)); + freeaddrinfo(ai_head); + if (nsockfd <= 0) { + err("failed to open a listening socket"); + usbip_host_driver_close(); + return -1; + } + + dbg("listening on %d address%s", nsockfd, (nsockfd == 1) ? "" : "es"); + + fds = calloc(nsockfd, sizeof(struct pollfd)); + for (i = 0; i < nsockfd; i++) { + fds[i].fd = sockfdlist[i]; + fds[i].events = POLLIN; + } + timeout.tv_sec = MAIN_LOOP_TIMEOUT; + timeout.tv_nsec = 0; + + sigfillset(&sigmask); + sigdelset(&sigmask, SIGTERM); + sigdelset(&sigmask, SIGINT); + + terminate = 0; + while (!terminate) { + int r; + + r = ppoll(fds, nsockfd, &timeout, &sigmask); + if (r < 0) { + dbg("%s", strerror(errno)); + terminate = 1; + } else if (r) { + for (i = 0; i < nsockfd; i++) { + if (fds[i].revents & POLLIN) { + dbg("read event on fd[%d]=%d", + i, sockfdlist[i]); + process_request(sockfdlist[i]); + } + } + } else { + dbg("heartbeat timeout on ppoll()"); + } + } + + info("shutting down " PROGNAME); + free(fds); + usbip_host_driver_close(); + + return 0; +} + +int main(int argc, char *argv[]) +{ + static const struct option longopts[] = { + { "ipv4", no_argument, NULL, '4' }, + { "ipv6", no_argument, NULL, '6' }, + { "daemon", no_argument, NULL, 'D' }, + { "daemon", no_argument, NULL, 'D' }, + { "debug", no_argument, NULL, 'd' }, + { "pid", optional_argument, NULL, 'P' }, + { "tcp-port", required_argument, NULL, 't' }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'v' }, + { NULL, 0, NULL, 0 } + }; + + enum { + cmd_standalone_mode = 1, + cmd_help, + cmd_version + } cmd; + + int daemonize = 0; + int ipv4 = 0, ipv6 = 0; + int opt, rc = -1; + + pid_file = NULL; + + usbip_use_stderr = 1; + usbip_use_syslog = 0; + + if (geteuid() != 0) + err("not running as root?"); + + cmd = cmd_standalone_mode; + for (;;) { + opt = getopt_long(argc, argv, "46DdP::t:hv", longopts, NULL); + + if (opt == -1) + break; + + switch (opt) { + case '4': + ipv4 = 1; + break; + case '6': + ipv6 = 1; + break; + case 'D': + daemonize = 1; + break; + case 'd': + usbip_use_debug = 1; + break; + case 'h': + cmd = cmd_help; + break; + case 'P': + pid_file = optarg ? optarg : DEFAULT_PID_FILE; + break; + case 't': + usbip_setup_port_number(optarg); + break; + case 'v': + cmd = cmd_version; + break; + case '?': + usbipd_help(); + default: + goto err_out; + } + } + + if (!ipv4 && !ipv6) + ipv4 = ipv6 = 1; + + switch (cmd) { + case cmd_standalone_mode: + rc = do_standalone_mode(daemonize, ipv4, ipv6); + remove_pid_file(); + break; + case cmd_version: + printf(PROGNAME " (%s)\n", usbip_version_string); + rc = 0; + break; + case cmd_help: + usbipd_help(); + rc = 0; + break; + default: + usbipd_help(); + goto err_out; + } + +err_out: + return (rc > -1 ? EXIT_SUCCESS : EXIT_FAILURE); +} diff --git a/tools/usb/usbip/src/utils.c b/tools/usb/usbip/src/utils.c new file mode 100644 index 000000000000..2b3d6d235015 --- /dev/null +++ b/tools/usb/usbip/src/utils.c @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2011 matt mooney <mfm@muteddisk.com> + * 2005-2007 Takahiro Hirofuchi + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <errno.h> +#include <stdio.h> +#include <string.h> + +#include "usbip_common.h" +#include "utils.h" +#include "sysfs_utils.h" + +int modify_match_busid(char *busid, int add) +{ + char attr_name[] = "match_busid"; + char command[SYSFS_BUS_ID_SIZE + 4]; + char match_busid_attr_path[SYSFS_PATH_MAX]; + int rc; + + snprintf(match_busid_attr_path, sizeof(match_busid_attr_path), + "%s/%s/%s/%s/%s/%s", SYSFS_MNT_PATH, SYSFS_BUS_NAME, + SYSFS_BUS_TYPE, SYSFS_DRIVERS_NAME, USBIP_HOST_DRV_NAME, + attr_name); + + if (add) + snprintf(command, SYSFS_BUS_ID_SIZE + 4, "add %s", busid); + else + snprintf(command, SYSFS_BUS_ID_SIZE + 4, "del %s", busid); + + rc = write_sysfs_attribute(match_busid_attr_path, command, + sizeof(command)); + if (rc < 0) { + dbg("failed to write match_busid: %s", strerror(errno)); + return -1; + } + + return 0; +} diff --git a/tools/usb/usbip/src/utils.h b/tools/usb/usbip/src/utils.h new file mode 100644 index 000000000000..5916fd3e02a6 --- /dev/null +++ b/tools/usb/usbip/src/utils.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2011 matt mooney <mfm@muteddisk.com> + * 2005-2007 Takahiro Hirofuchi + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __UTILS_H +#define __UTILS_H + +int modify_match_busid(char *busid, int add); + +#endif /* __UTILS_H */ + |