/*
* Copyright © 2019 Advanced Driver Information Technology GmbH
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/*******************************************************************************
** **
** TARGET : linux **
** **
** PROJECT : waltham-receiver **
** **
** PURPOSE : This file handles connection with remote-client **
** **
*******************************************************************************/
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <unistd.h>
#include "wth-receiver-comm.h"
#define MAX_EPOLL_WATCHES 2
#define DEFAULT_TCP_PORT 34400
uint16_t tcp_port = 0;
const char *my_app_id = NULL;
static bool *signal_int_handler_run_flag;
/** Print out the application help
*/
static void
usage(void)
{
printf("Usage: waltham receiver [options]\n");
printf("Options:\n");
printf(" -p --port number TCP port number\n");
printf(" -i --app_id Specify an app_id\n");
printf(" -h --help Usage\n");
}
static struct option long_options[] = {
{"port", required_argument, 0, 'p'},
{"app_id", required_argument, NULL, 'i'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0}
};
/**
* parse_args
*
* Parses the application arguments
* The arguments are parsed and saved in static structure for future use.
*
* @param argc The amount of arguments
* @param argv The table of arguments
*
* @return 0 on success, -1 otherwise
*/
static int
parse_args(int argc, char *argv[])
{
int c = -1;
int long_index = 0;
while ((c = getopt_long(argc, argv, "i:p:vh",
long_options,
&long_index)) != -1) {
switch (c) {
case 'i':
my_app_id = optarg;
break;
case 'p':
tcp_port = (uint16_t) atoi(optarg);
break;
case 'v':
printf("No verbose logs for release mode");
break;
case 'h':
usage();
return -1;
default:
wth_error("Try %s -h for more information.\n", argv[0]);
return -1;
}
}
if (tcp_port == 0) {
tcp_port = DEFAULT_TCP_PORT;
}
return 0;
}
static int
watch_ctl(struct watch *w, int op, uint32_t events)
{
struct epoll_event ee;
ee.events = events;
ee.data.ptr = w;
return epoll_ctl(w->receiver->epoll_fd, op, w->fd, &ee);
}
/**
* listen_socket_handle_data
*
* Handles all incoming events on socket
*
* @param names struct watch *w ,uint32_t events
* @param value pointer to watch connection it holds receiver information, Incoming events information
* @return none
*/
static void
listen_socket_handle_data(struct watch *w, uint32_t events)
{
struct receiver *srv = container_of(w, struct receiver, listen_watch);
if (events & EPOLLERR) {
wth_error("Listening socket errored out.\n");
srv->running = false;
return;
}
if (events & EPOLLHUP) {
wth_error("Listening socket hung up.\n");
srv->running = false;
return;
}
if (events & EPOLLIN) {
receiver_accept_client(srv);
}
}
static void
receiver_clean_up_child(pid_t pid, struct receiver *srv)
{
struct client *client;
/* client represents connection with the transmitter, so removing it
* from the client list isn't what need to happen. Mark the
* pid_destroying as to give it a chance for next time */
wl_list_for_each(client, &srv->client_list, link) {
if (client->pid == pid) {
client->pid_destroying = false;
}
}
}
static void
wait_for_children(struct receiver *srv, pid_t pid)
{
pid_t w;
int status;
int options = WUNTRACED | WCONTINUED;
do {
w = waitpid(pid, &status, options);
if (w == -1) {
fprintf(stderr, "err no children to wait for!\n");
}
if (WIFEXITED(status)) {
if (w > 0) {
receiver_clean_up_child(w, srv);
}
} else if (WIFSIGNALED(status)) {
// FIXME should remove the client here, and identify with the pid
} else if (WIFSTOPPED(status)) {
} else if (WIFCONTINUED(status)) {
}
} while (!WIFEXITED(status) && !WIFSIGNALED(status));
}
/**
* receiver_mainloop
*
* This is the main loop, which will flush all pending clients requests and
* listen to input events from socket
*
* @param names void *data
* @param value pointer to receiver struct -
* struct holds the client connection information
* @return none
*/
static void
receiver_mainloop(struct receiver *srv)
{
struct epoll_event ee[MAX_EPOLL_WATCHES];
struct watch *w;
int count;
int i;
srv->running = true;
while (srv->running) {
/* Run any idle tasks at this point. */
bool should_wait_for_children = false;
struct client *client;
receiver_flush_clients(srv);
wl_list_for_each(client, &srv->client_list, link) {
if (client->pid > 0 && client->pid_destroying) {
should_wait_for_children = true;
break;
}
}
/*
* wait_for_children() will block and wait for children that
* died out/terminated correctly as the remote that end sent
* the destroy surface request. pid_destroying is the one that
* allows to make that distinction and will be set by the
* destroy surface handler.
*
* 'children' is just one. A new child will be started when the
* remote side sents a request to create a surface and will be
* killed in the handler that destroys the surface.
*/
if (should_wait_for_children)
wait_for_children(srv, client->pid);
/* Wait for events or signals */
count = epoll_wait(srv->epoll_fd,
ee, ARRAY_LENGTH(ee), -1);
if (count < 0 && errno != EINTR) {
perror("Error with epoll_wait");
break;
}
/* Handle all fds, both the listening socket
* (see listen_socket_handle_data()) and clients
* (see connection_handle_data()).
*/
for (i = 0; i < count; i++) {
w = ee[i].data.ptr;
w->cb(w, ee[i].events);
}
}
}
static int
receiver_listen(uint16_t tcp_port)
{
int fd;
int reuse = 1;
struct sockaddr_in addr;
fd = socket(AF_INET, SOCK_STREAM, 0);
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(tcp_port);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof reuse);
if (bind(fd, (struct sockaddr *)&addr, sizeof addr) < 0) {
wth_error("Failed to bind to port %d", tcp_port);
close(fd);
return -1;
}
if (listen(fd, 1024) < 0) {
wth_error("Failed to listen to port %d", tcp_port);
close (fd);
return -1;
}
return fd;
}
static void
signal_int_handler(int signum)
{
if (!*signal_int_handler_run_flag)
abort();
*signal_int_handler_run_flag = false;
}
static void
set_sigint_handler(bool *running)
{
struct sigaction sigint;
signal_int_handler_run_flag = running;
sigint.sa_handler = signal_int_handler;
sigemptyset(&sigint.sa_mask);
sigint.sa_flags = SA_RESETHAND;
sigaction(SIGINT, &sigint, NULL);
}
/**
* main
*
* waltham receiver main function, it accepts tcp port number as argument.
* Establishes connection on the port and listen to port for incoming connection
* request from waltham clients
*
* @param names argv - argument list and argc -argument count
* @param value tcp port number as argument
* @return 0 on success, -1 on error
*/
int main(int argc, char *argv[])
{
struct receiver srv = { 0 };
struct client *c;
/* Get command line arguments */
if (parse_args(argc, argv) != 0) {
return -1;
}
set_sigint_handler(&srv.running);
wl_list_init(&srv.client_list);
srv.epoll_fd = epoll_create1(EPOLL_CLOEXEC);
if (srv.epoll_fd == -1) {
perror("Error on epoll_create1");
exit(1);
}
srv.listen_fd = receiver_listen(tcp_port);
if (srv.listen_fd < 0) {
perror("Error setting up listening socket");
exit(1);
}
srv.listen_watch.receiver = &srv;
srv.listen_watch.cb = listen_socket_handle_data;
srv.listen_watch.fd = srv.listen_fd;
if (watch_ctl(&srv.listen_watch, EPOLL_CTL_ADD, EPOLLIN) < 0) {
perror("Error setting up listen polling");
exit(1);
}
receiver_mainloop(&srv);
/* destroy all things */
wl_list_last_until_empty(c, &srv.client_list, link)
client_destroy(c);
close(srv.listen_fd);
close(srv.epoll_fd);
return 0;
}