/* * libiio - Library for interfacing industrial I/O (IIO) devices * * Copyright (C) 2014 Analog Devices, Inc. * Author: Paul Cercueil * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * */ #include "../debug.h" #include "../iio.h" #include "../iio-config.h" #include "ops.h" #include "thread-pool.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_AVAHI #include #include #include #endif #define MY_NAME "iiod" #define IIOD_PORT 30431 struct client_data { int fd; bool debug; struct iio_context *ctx; }; bool server_demux; struct thread_pool *main_thread_pool; static struct sockaddr_in sockaddr = { .sin_family = AF_INET, #if __BYTE_ORDER == __LITTLE_ENDIAN .sin_addr.s_addr = __bswap_constant_32(INADDR_ANY), .sin_port = __bswap_constant_16(IIOD_PORT), #else .sin_addr.s_addr = INADDR_ANY, .sin_port = IIOD_PORT, #endif }; #ifdef HAVE_IPV6 static struct sockaddr_in6 sockaddr6 = { .sin6_family = AF_INET6, .sin6_addr = IN6ADDR_ANY_INIT, #if __BYTE_ORDER == __LITTLE_ENDIAN .sin6_port = __bswap_constant_16(IIOD_PORT), #else .sin6_port = IIOD_PORT, #endif }; #endif /* HAVE_IPV6 */ static const struct option options[] = { {"help", no_argument, 0, 'h'}, {"version", no_argument, 0, 'V'}, {"debug", no_argument, 0, 'd'}, {"demux", no_argument, 0, 'D'}, {"interactive", no_argument, 0, 'i'}, {"aio", no_argument, 0, 'a'}, {"ffs", required_argument, 0, 'F'}, {"nb-pipes", required_argument, 0, 'n'}, {0, 0, 0, 0}, }; static const char *options_descriptions[] = { "Show this help and quit.", "Display the version of this program.", "Use alternative (incompatible) debug interface.", "Demux channels directly on the server.", "Run " MY_NAME " in the controlling terminal.", "Use asynchronous I/O.", "Use the given FunctionFS mountpoint to serve over USB", "Specify the number of USB pipes (ep couples) to use", }; #ifdef HAVE_AVAHI static AvahiSimplePoll *avahi_poll; static AvahiClient *avahi_client; static void __avahi_group_cb(AvahiEntryGroup *group, AvahiEntryGroupState state, void *d) { } static void __avahi_client_cb(AvahiClient *client, AvahiClientState state, void *d) { AvahiEntryGroup *group; if (state != AVAHI_CLIENT_S_RUNNING) return; group = avahi_entry_group_new(client, __avahi_group_cb, NULL); if (group && !avahi_entry_group_add_service(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, "iio", "_iio._tcp", NULL, NULL, IIOD_PORT, NULL)) { avahi_entry_group_commit(group); INFO("Registered to ZeroConf server %s\n", avahi_client_get_version_string(client)); } /* NOTE: group is freed by avahi_client_free */ } static int start_avahi(void) { int ret = ENOMEM; avahi_poll = avahi_simple_poll_new(); if (!avahi_poll) return -ENOMEM; avahi_client = avahi_client_new(avahi_simple_poll_get(avahi_poll), 0, __avahi_client_cb, NULL, &ret); if (!avahi_client) { avahi_simple_poll_free(avahi_poll); return -ret; } return 0; } static void stop_avahi(void) { avahi_client_free(avahi_client); avahi_simple_poll_free(avahi_poll); } #endif /* HAVE_AVAHI */ static void usage(void) { unsigned int i; printf("Usage:\n\t" MY_NAME " [OPTIONS ...]\n\nOptions:\n"); for (i = 0; options[i].name; i++) printf("\t-%c, --%s\n\t\t\t%s\n", options[i].val, options[i].name, options_descriptions[i]); } static void client_thd(struct thread_pool *pool, void *d) { struct client_data *cdata = d; interpreter(cdata->ctx, cdata->fd, cdata->fd, cdata->debug, true, false, pool); INFO("Client exited\n"); close(cdata->fd); free(cdata); } static void set_handler(int signal, void (*handler)(int)) { struct sigaction sig; sigaction(signal, NULL, &sig); sig.sa_handler = handler; sigaction(signal, &sig, NULL); } static void sig_handler(int sig) { thread_pool_stop(main_thread_pool); } static int main_interactive(struct iio_context *ctx, bool verbose, bool use_aio) { int flags; if (!use_aio) { flags = fcntl(STDIN_FILENO, F_GETFL); fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK); flags = fcntl(STDOUT_FILENO, F_GETFL); fcntl(STDOUT_FILENO, F_SETFL, flags | O_NONBLOCK); } interpreter(ctx, STDIN_FILENO, STDOUT_FILENO, verbose, false, use_aio, main_thread_pool); return EXIT_SUCCESS; } static int main_server(struct iio_context *ctx, bool debug) { int ret, fd = -1, yes = 1, keepalive_time = 10, keepalive_intvl = 10, keepalive_probes = 6; struct pollfd pfd[2]; char err_str[1024]; bool ipv6; #ifdef HAVE_AVAHI bool avahi_started; #endif INFO("Starting IIO Daemon version %u.%u\n", LIBIIO_VERSION_MAJOR, LIBIIO_VERSION_MINOR); #ifdef HAVE_IPV6 fd = socket(AF_INET6, SOCK_STREAM | SOCK_NONBLOCK, 0); #endif ipv6 = (fd >= 0); if (!ipv6) fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); if (fd < 0) { iio_strerror(errno, err_str, sizeof(err_str)); ERROR("Unable to create socket: %s\n", err_str); return EXIT_FAILURE; } setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); #ifdef HAVE_IPV6 if (ipv6) ret = bind(fd, (struct sockaddr *) &sockaddr6, sizeof(sockaddr6)); #endif if (!ipv6) ret = bind(fd, (struct sockaddr *) &sockaddr, sizeof(sockaddr)); if (ret < 0) { iio_strerror(errno, err_str, sizeof(err_str)); ERROR("Bind failed: %s\n", err_str); goto err_close_socket; } if (ipv6) INFO("IPv6 support enabled\n"); if (listen(fd, 16) < 0) { iio_strerror(errno, err_str, sizeof(err_str)); ERROR("Unable to mark as passive socket: %s\n", err_str); goto err_close_socket; } #ifdef HAVE_AVAHI avahi_started = !start_avahi(); #endif pfd[0].fd = fd; pfd[0].events = POLLIN; pfd[0].revents = 0; pfd[1].fd = thread_pool_get_poll_fd(main_thread_pool); pfd[1].events = POLLIN; pfd[1].revents = 0; while (true) { struct client_data *cdata; struct sockaddr_in caddr; socklen_t addr_len = sizeof(caddr); int new; poll_nointr(pfd, 2); if (pfd[1].revents & POLLIN) /* STOP event */ break; new = accept4(fd, (struct sockaddr *) &caddr, &addr_len, SOCK_NONBLOCK); if (new == -1) { if (errno == EAGAIN || errno == EINTR) continue; iio_strerror(errno, err_str, sizeof(err_str)); ERROR("Failed to create connection socket: %s\n", err_str); continue; } cdata = malloc(sizeof(*cdata)); if (!cdata) { WARNING("Unable to allocate memory for client\n"); close(new); continue; } /* Configure the socket to send keep-alive packets every 10s, * and disconnect the client if no reply was received for one * minute. */ setsockopt(new, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(yes)); setsockopt(new, IPPROTO_TCP, TCP_KEEPCNT, &keepalive_probes, sizeof(keepalive_probes)); setsockopt(new, IPPROTO_TCP, TCP_KEEPIDLE, &keepalive_time, sizeof(keepalive_time)); setsockopt(new, IPPROTO_TCP, TCP_KEEPINTVL, &keepalive_intvl, sizeof(keepalive_intvl)); setsockopt(new, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)); cdata->fd = new; cdata->ctx = ctx; cdata->debug = debug; INFO("New client connected from %s\n", inet_ntoa(caddr.sin_addr)); ret = thread_pool_add_thread(main_thread_pool, client_thd, cdata, "net_client_thd"); if (ret) { iio_strerror(ret, err_str, sizeof(err_str)); ERROR("Failed to create new client thread: %s\n", err_str); close(new); free(cdata); } } DEBUG("Cleaning up\n"); #ifdef HAVE_AVAHI if (avahi_started) stop_avahi(); #endif close(fd); return EXIT_SUCCESS; err_close_socket: close(fd); return EXIT_FAILURE; } int main(int argc, char **argv) { bool debug = false, interactive = false, use_aio = false; #ifdef WITH_IIOD_USBD long nb_pipes = 3; char *end; #endif struct iio_context *ctx; int c, option_index = 0; char *ffs_mountpoint = NULL; char err_str[1024]; int ret; while ((c = getopt_long(argc, argv, "+hVdDiaF:n:", options, &option_index)) != -1) { switch (c) { case 'd': debug = true; break; case 'D': server_demux = true; break; case 'i': interactive = true; break; case 'a': #ifdef WITH_AIO use_aio = true; break; #else ERROR("IIOD was not compiled with AIO support.\n"); return EXIT_FAILURE; #endif case 'F': #ifdef WITH_IIOD_USBD ffs_mountpoint = optarg; break; #else ERROR("IIOD was not compiled with USB support.\n"); return EXIT_FAILURE; #endif case 'n': #ifdef WITH_IIOD_USBD nb_pipes = strtol(optarg, &end, 10); if (optarg == end || nb_pipes < 1) { ERROR("--nb-pipes: Invalid parameter\n"); return EXIT_FAILURE; } break; #else ERROR("IIOD was not compiled with USB support.\n"); return EXIT_FAILURE; #endif case 'h': usage(); return EXIT_SUCCESS; case 'V': printf("%u.%u\n", LIBIIO_VERSION_MAJOR, LIBIIO_VERSION_MINOR); return EXIT_SUCCESS; case '?': return EXIT_FAILURE; } } ctx = iio_create_local_context(); if (!ctx) { iio_strerror(errno, err_str, sizeof(err_str)); ERROR("Unable to create local context: %s\n", err_str); return EXIT_FAILURE; } main_thread_pool = thread_pool_new(); if (!main_thread_pool) { iio_strerror(errno, err_str, sizeof(err_str)); ERROR("Unable to create thread pool: %s\n", err_str); ret = EXIT_FAILURE; goto out_destroy_context; } set_handler(SIGHUP, sig_handler); set_handler(SIGPIPE, sig_handler); set_handler(SIGINT, sig_handler); set_handler(SIGTERM, sig_handler); if (ffs_mountpoint) { #ifdef WITH_IIOD_USBD /* We pass use_aio == true directly, this is ensured to be true * by the CMake script. */ ret = start_usb_daemon(ctx, ffs_mountpoint, debug, true, (unsigned int) nb_pipes, main_thread_pool); if (ret) { iio_strerror(-ret, err_str, sizeof(err_str)); ERROR("Unable to start USB daemon: %s\n", err_str); ret = EXIT_FAILURE; goto out_destroy_thread_pool; } #endif } if (interactive) ret = main_interactive(ctx, debug, use_aio); else ret = main_server(ctx, debug); /* * In case we got here through an error in the main thread make sure all * the worker threads are signaled to shutdown. */ #ifdef WITH_IIOD_USBD out_destroy_thread_pool: #endif thread_pool_stop_and_wait(main_thread_pool); thread_pool_destroy(main_thread_pool); out_destroy_context: iio_context_destroy(ctx); return ret; }