/* * SPDX license identifier: MPL-2.0 * * Copyright (C) 2015 Advanced Driver Information Technology. * This code is developed by Advanced Driver Information Technology. * Copyright of Advanced Driver Information Technology, Bosch and DENSO. * * This file is part of GENIVI Project DLT - Diagnostic Log and Trace. * * This Source Code Form is subject to the terms of the * Mozilla Public License (MPL), v. 2.0. * If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. * * For further information see http://www.genivi.org/. */ /*! * \author * Frederic Berat * * \copyright Copyright © 2015 Advanced Driver Information Technology. \n * License MPL-2.0: Mozilla Public License version 2.0 http://mozilla.org/MPL/2.0/. * * \file dlt_daemon_event_handler.c */ #include #include #include #include #include #include #include "dlt_common.h" #include "dlt-daemon.h" #include "dlt-daemon_cfg.h" #include "dlt_daemon_common.h" #include "dlt_daemon_connection.h" #include "dlt_daemon_connection_types.h" #include "dlt_daemon_event_handler.h" #include "dlt_daemon_event_handler_types.h" /** * \def DLT_EV_TIMEOUT_MSEC * The maximum amount of time to wait for a poll event. * Set to 1 second to avoid unnecessary wake ups. */ #define DLT_EV_TIMEOUT_MSEC 1000 #define DLT_EV_BASE_FD 16 #define DLT_EV_MASK_REJECTED (POLLERR | POLLNVAL) /** @brief Initialize a pollfd structure * * That ensures that no event will be mis-watched. * * @param pfd The element to initialize */ static void init_poll_fd(struct pollfd *pfd) { pfd->fd = -1; pfd->events = 0; pfd->revents = 0; } /** @brief Prepare the event handler * * This will create the base poll file descriptor list. * * @param ev The event handler to prepare. * * @return 0 on success, -1 otherwise. */ int dlt_daemon_prepare_event_handling(DltEventHandler *ev) { int i = 0; if (ev == NULL) return DLT_RETURN_ERROR; ev->pfd = calloc(DLT_EV_BASE_FD, sizeof(struct pollfd)); if (ev->pfd == NULL) { dlt_log(LOG_CRIT, "Creation of poll instance failed!\n"); return -1; } for (i = 0; i < DLT_EV_BASE_FD; i++) init_poll_fd(&ev->pfd[i]); ev->nfds = 0; ev->max_nfds = DLT_EV_BASE_FD; return 0; } /** @brief Enable a file descriptor to be watched * * Adds a file descriptor to the descriptor list. If the list is to small, * increase its size. * * @param ev The event handler structure, containing the list * @param fd The file descriptor to add * @param mask The mask of event to be watched */ static void dlt_event_handler_enable_fd(DltEventHandler *ev, int fd, int mask) { if (ev->max_nfds <= ev->nfds) { int i = ev->nfds; int max = 2 * ev->max_nfds; struct pollfd *tmp = realloc(ev->pfd, max * sizeof(*ev->pfd)); if (!tmp) { dlt_log(LOG_CRIT, "Unable to register new fd for the event handler.\n"); return; } ev->pfd = tmp; ev->max_nfds = max; for (; i < max; i++) init_poll_fd(&ev->pfd[i]); } ev->pfd[ev->nfds].fd = fd; ev->pfd[ev->nfds].events = mask; ev->nfds++; } /** @brief Disable a file descriptor for watching * * The file descriptor is removed from the descriptor list, the list is * compressed during the process. * * @param ev The event handler structure containing the list * @param fd The file descriptor to be removed */ static void dlt_event_handler_disable_fd(DltEventHandler *ev, int fd) { unsigned int i = 0; unsigned int j = 0; unsigned int nfds = ev->nfds; for (; i < nfds; i++, j++) { if (ev->pfd[i].fd == fd) { init_poll_fd(&ev->pfd[i]); j++; ev->nfds--; } if (i == j) continue; /* Compressing the table */ if (i < ev->nfds) { ev->pfd[i].fd = ev->pfd[j].fd; ev->pfd[i].events = ev->pfd[j].events; ev->pfd[i].revents = ev->pfd[j].revents; } else { init_poll_fd(&ev->pfd[i]); } } } /** @brief Catch and process incoming events. * * This function waits for events on all connections. Once an event raise, * the callback for the specific connection is called, or the connection is * destroyed if a hangup occurs. * * @param daemon Structure to be passed to the callback. * @param daemon_local Structure containing needed information. * @param pEvent Event handler structure. * * @return 0 on success, -1 otherwise. May be interrupted. */ int dlt_daemon_handle_event(DltEventHandler *pEvent, DltDaemon *daemon, DltDaemonLocal *daemon_local) { int ret = 0; unsigned int i = 0; int (*callback)(DltDaemon *, DltDaemonLocal *, DltReceiver *, int) = NULL; if ((pEvent == NULL) || (daemon == NULL) || (daemon_local == NULL)) return DLT_RETURN_ERROR; ret = poll(pEvent->pfd, pEvent->nfds, DLT_EV_TIMEOUT_MSEC); if (ret <= 0) { /* We are not interested in EINTR has it comes * either from timeout or signal. */ if (errno == EINTR) ret = 0; if (ret < 0) dlt_vlog(LOG_CRIT, "poll() failed: %s\n", strerror(errno)); return ret; } for (i = 0; i < pEvent->nfds; i++) { int fd = 0; DltConnection *con = NULL; DltConnectionType type = DLT_CONNECTION_TYPE_MAX; if (pEvent->pfd[i].revents == 0) continue; con = dlt_event_handler_find_connection(pEvent, pEvent->pfd[i].fd); if (con && con->receiver) { type = con->type; fd = con->receiver->fd; } else { /* connection might have been destroyed in the meanwhile */ dlt_event_handler_disable_fd(pEvent, pEvent->pfd[i].fd); continue; } /* First of all handle error events */ if (pEvent->pfd[i].revents & DLT_EV_MASK_REJECTED) { /* An error occurred, we need to clean-up the concerned event */ if (type == DLT_CONNECTION_CLIENT_MSG_TCP) /* To transition to BUFFER state if this is final TCP client connection, * call dedicated function. this function also calls * dlt_event_handler_unregister_connection() inside the function. */ dlt_daemon_close_socket(fd, daemon, daemon_local, 0); else dlt_event_handler_unregister_connection(pEvent, daemon_local, fd); continue; } /* Get the function to be used to handle the event */ callback = dlt_connection_get_callback(con); if (!callback) { dlt_vlog(LOG_CRIT, "Unable to find function for %d handle type.\n", type); return -1; } /* From now on, callback is correct */ if (callback(daemon, daemon_local, con->receiver, daemon_local->flags.vflag) == -1) { dlt_vlog(LOG_CRIT, "Processing from %d handle type failed!\n", type); return -1; } } return 0; } /** @brief Find connection with a specific \a fd in the connection list. * * There can be only one event per \a fd. We can then find a specific connection * based on this \a fd. That allows to check if a specific \a fd has already been * registered. * * @param ev The event handler structure where the list of connection is. * @param fd The file descriptor of the connection to be found. * * @return The found connection pointer, NULL otherwise. */ DltConnection *dlt_event_handler_find_connection(DltEventHandler *ev, int fd) { DltConnection *temp = ev->connections; while (temp != NULL) { if ((temp->receiver != NULL) && (temp->receiver->fd == fd)) return temp; temp = temp->next; } return temp; } /** @brief Remove a connection from the list and destroy it. * * This function will first look for the connection in the event handler list, * remove it from the list and then destroy it. * * @param ev The event handler structure where the list of connection is. * @param to_remove The connection to remove from the list. * * @return 0 on success, -1 if the connection is not found. */ DLT_STATIC int dlt_daemon_remove_connection(DltEventHandler *ev, DltConnection *to_remove) { if ((ev == NULL) || (to_remove == NULL)) return DLT_RETURN_ERROR; DltConnection *curr = ev->connections; DltConnection *prev = curr; /* Find the address where to_remove value is registered */ while (curr && (curr != to_remove)) { prev = curr; curr = curr->next; } if (!curr) { /* Must not be possible as we check for existence before */ dlt_log(LOG_CRIT, "Connection not found for removal.\n"); return -1; } else if (curr == ev->connections) { ev->connections = curr->next; } else { prev->next = curr->next; } /* Now we can destroy our pointer */ dlt_connection_destroy(to_remove); return 0; } /** @brief Destroy the connection list. * * This function runs through the connection list and destroy them one by one. * * @param ev Pointer to the event handler structure. */ void dlt_event_handler_cleanup_connections(DltEventHandler *ev) { unsigned int i = 0; if (ev == NULL) /* Nothing to do. */ return; while (ev->connections != NULL) /* We don really care on failure */ (void)dlt_daemon_remove_connection(ev, ev->connections); for (i = 0; i < ev->nfds; i++) init_poll_fd(&ev->pfd[i]); free(ev->pfd); } /** @brief Add a new connection to the list. * * The connection is added at the tail of the list. * * @param ev The event handler structure where the connection list is. * @param connection The connection to be added. */ DLT_STATIC void dlt_daemon_add_connection(DltEventHandler *ev, DltConnection *connection) { DltConnection **temp = &ev->connections; while (*temp != NULL) temp = &(*temp)->next; *temp = connection; } /** @brief Check for connection activation * * If the connection is active and it's not allowed anymore or it the user * ask for deactivation, the connection will be deactivated. * If the connection is inactive, the user asks for activation and it's * allowed for it to be activated, the connection will be activated. * * @param evhdl The event handler structure. * @param con The connection to act on * @param activation_type The type of activation requested ((DE)ACTIVATE) * * @return 0 on success, -1 otherwise */ int dlt_connection_check_activate(DltEventHandler *evhdl, DltConnection *con, int activation_type) { char local_str[DLT_DAEMON_TEXTBUFSIZE] = { '\0' }; if (!evhdl || !con || !con->receiver) { snprintf(local_str, DLT_DAEMON_TEXTBUFSIZE, "%s: wrong parameters (%p %p).\n", __func__, evhdl, con); dlt_log(LOG_ERR, local_str); return -1; } switch (con->status) { case ACTIVE: if (activation_type == DEACTIVATE) { snprintf(local_str, DLT_DAEMON_TEXTBUFSIZE, "Deactivate connection type: %d\n", con->type); dlt_log(LOG_INFO, local_str); dlt_event_handler_disable_fd(evhdl, con->receiver->fd); if (con->type == DLT_CONNECTION_CLIENT_CONNECT) con->receiver->fd = -1; con->status = INACTIVE; } break; case INACTIVE: if (activation_type == ACTIVATE) { snprintf(local_str, DLT_DAEMON_TEXTBUFSIZE, "Activate connection type: %d\n", con->type); dlt_log(LOG_INFO, local_str); dlt_event_handler_enable_fd(evhdl, con->receiver->fd, con->ev_mask); con->status = ACTIVE; } break; default: snprintf(local_str, DLT_DAEMON_TEXTBUFSIZE, "Unknown connection status: %d\n", con->status); dlt_log(LOG_ERR, local_str); return -1; } return 0; } /** @brief Registers a connection for event handling and takes its ownership. * * As we add the connection to the list of connection, we take its ownership. * That's the only place where the connection pointer is stored. * The connection is then used to create a new event trigger. * If the connection is of type DLT_CONNECTION_CLIENT_MSG_TCP, we increase * the daemon_local->client_connections counter. TODO: Move this counter inside * the event handler structure. * * @param evhdl The event handler structure where the connection list is. * @param daemon_local Structure containing needed information. * @param connection The connection to be registered. * @param mask The bit mask of event to be registered. * * @return 0 on success, -1 otherwise. */ int dlt_event_handler_register_connection(DltEventHandler *evhdl, DltDaemonLocal *daemon_local, DltConnection *connection, int mask) { if (!evhdl || !connection || !connection->receiver) { dlt_log(LOG_ERR, "Wrong parameters when registering connection.\n"); return -1; } dlt_daemon_add_connection(evhdl, connection); if ((connection->type == DLT_CONNECTION_CLIENT_MSG_TCP) || (connection->type == DLT_CONNECTION_CLIENT_MSG_SERIAL)) daemon_local->client_connections++; /* On creation the connection is not active by default */ connection->status = INACTIVE; connection->next = NULL; connection->ev_mask = mask; return dlt_connection_check_activate(evhdl, connection, ACTIVATE); } /** @brief Unregisters a connection from the event handler and destroys it. * * We first look for the connection to be unregistered, delete the event * corresponding and then destroy the connection. * If the connection is of type DLT_CONNECTION_CLIENT_MSG_TCP, we decrease * the daemon_local->client_connections counter. TODO: Move this counter inside * the event handler structure. * * @param evhdl The event handler structure where the connection list is. * @param daemon_local Structure containing needed information. * @param fd The file descriptor of the connection to be unregistered. * * @return 0 on success, -1 otherwise. */ int dlt_event_handler_unregister_connection(DltEventHandler *evhdl, DltDaemonLocal *daemon_local, int fd) { if ((evhdl == NULL) || (daemon_local == NULL)) return DLT_RETURN_ERROR; /* Look for the pointer in the client list. * There shall be only one event handler with the same fd. */ DltConnection *temp = dlt_event_handler_find_connection(evhdl, fd); if (!temp) { dlt_log(LOG_ERR, "Connection not found for unregistration.\n"); return -1; } if ((temp->type == DLT_CONNECTION_CLIENT_MSG_TCP) || (temp->type == DLT_CONNECTION_CLIENT_MSG_SERIAL)) { daemon_local->client_connections--; if (daemon_local->client_connections < 0) { daemon_local->client_connections = 0; dlt_log(LOG_CRIT, "Unregistering more client than registered!\n"); } } if (dlt_connection_check_activate(evhdl, temp, DEACTIVATE) < 0) dlt_log(LOG_ERR, "Unable to unregister event.\n"); /* Cannot fail as far as dlt_daemon_find_connection succeed */ return dlt_daemon_remove_connection(evhdl, temp); }