Files
can-utils/isobusfs/isobusfs_cli.c
T
Oleksij Rempel af95ee0c6d move part of isobusfs code to the libj1939
Move part of isobusfs which can be reused by other applications to the
libj1939. By the way, reuse some of new libj1939 code in the j1939cat.

Signed-off-by: Oleksij Rempel <linux@rempel-privat.de>
2024-06-06 13:53:19 +02:00

689 lines
16 KiB
C

// SPDX-License-Identifier: LGPL-2.0-only
// SPDX-FileCopyrightText: 2023 Oleksij Rempel <linux@rempel-privat.de>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <net/if.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include "isobusfs_cli.h"
/* Maximal number of events that can be registered. The number is
* based on feeling not on any real data.
*/
#define ISOBUSFS_CLI_MAX_EVENTS 10
int isobusfs_cli_register_event(struct isobusfs_priv *priv,
const struct isobusfs_event *new_event)
{
if (!priv->events) {
priv->events = malloc(sizeof(*new_event) *
ISOBUSFS_CLI_MAX_EVENTS);
if (!priv->events)
return -ENOMEM;
priv->max_events = ISOBUSFS_CLI_MAX_EVENTS;
} else if (priv->num_events >= priv->max_events) {
return -1;
}
memcpy(&priv->events[priv->num_events], new_event,
sizeof(*new_event));
priv->num_events++;
return 0;
}
int isobusfs_cli_remove_event(struct isobusfs_priv *priv,
struct isobusfs_event *event_to_remove)
{
size_t i;
for (i = 0; i < priv->num_events; ++i) {
if (&priv->events[i] == event_to_remove) {
memmove(&priv->events[i], &priv->events[i + 1],
(priv->num_events - i - 1) *
sizeof(*priv->events));
priv->num_events--;
return 0;
}
}
return -ENOENT;
}
struct timespec ms_to_timespec(unsigned int timeout_ms)
{
struct timespec ts;
ts.tv_sec = timeout_ms / 1000;
ts.tv_nsec = (timeout_ms % 1000) * 1000000;
return ts;
}
void isobusfs_cli_prepare_response_event(struct isobusfs_event *event, int sock,
uint8_t fs_function)
{
struct timespec current_time, timeout_timespec;
event->fd = sock;
event->fs_function = fs_function;
/* Calculate the timeout */
clock_gettime(CLOCK_REALTIME, &current_time);
timeout_timespec = ms_to_timespec(ISOBUSFS_CLI_DEFAULT_WAIT_TIMEOUT_MS);
event->timeout.tv_sec = current_time.tv_sec + timeout_timespec.tv_sec;
event->timeout.tv_nsec = current_time.tv_nsec + timeout_timespec.tv_nsec;
/* Adjust for nanosecond overflow */
if (event->timeout.tv_nsec >= 1000000000) {
event->timeout.tv_nsec -= 1000000000;
event->timeout.tv_sec++;
}
event->one_shot = true;
}
static bool isobusfs_cli_has_event_expired(const struct timespec *timeout)
{
struct timespec now;
clock_gettime(CLOCK_REALTIME, &now);
return now.tv_sec > timeout->tv_sec ||
(now.tv_sec == timeout->tv_sec &&
now.tv_nsec > timeout->tv_nsec);
}
static void isobusfs_cli_process_expired_events(struct isobusfs_priv *priv)
{
for (size_t i = 0; i < priv->num_events; ++i) {
struct isobusfs_event *event = &priv->events[i];
if (isobusfs_cli_has_event_expired(&event->timeout)) {
event->cb(priv, NULL, event->ctx, -ETIME);
isobusfs_cli_remove_event(priv, event);
i--;
}
}
}
static int isobusfs_cli_rx_event(struct isobusfs_priv *priv, int sock,
struct isobusfs_msg *msg, bool *found)
{
struct isobusfs_event *event = NULL;
int ret = 0;
*found = false;
for (size_t i = 0; i < priv->num_events; ++i) {
event = &priv->events[i];
if (event->fd == sock && event->fs_function == msg->buf[0]) {
if (event->cb)
ret = event->cb(priv, msg, event->ctx, 0);
*found = true;
break;
}
}
if (*found && event->one_shot)
isobusfs_cli_remove_event(priv, event);
return ret;
}
static int isobusfs_cli_rx(struct isobusfs_priv *priv, struct isobusfs_msg *msg)
{
int cmd = isobusfs_buf_to_cmd(msg->buf);
int ret = 0;
switch (cmd) {
case ISOBUSFS_CG_CONNECTION_MANAGMENT:
ret = isobusfs_cli_rx_cg_cm(priv, msg);
break;
case ISOBUSFS_CG_DIRECTORY_HANDLING:
ret = isobusfs_cli_rx_cg_dh(priv, msg);
break;
case ISOBUSFS_CG_FILE_ACCESS: /* fall through */
ret = isobusfs_cli_rx_cg_fa(priv, msg);
break;
case ISOBUSFS_CG_FILE_HANDLING: /* fall through */
case ISOBUSFS_CG_VOLUME_HANDLING: /* fall through */
default:
isobusfs_send_nack(priv->sock_nack, msg);
pr_warn("unsupported command group: %i", cmd);
/* Not a critical error */
return 0;
}
return ret;
}
static int isobusfs_cli_rx_ack(struct isobusfs_priv *priv, struct isobusfs_msg *msg)
{
enum isobusfs_ack_ctrl ctrl = msg->buf[0];
switch (ctrl) {
case ISOBUS_ACK_CTRL_ACK:
/* received ACK unexpectedly an ACK, no idea what to do */
pr_debug("< rx: ACK?????");
break;
case ISOBUS_ACK_CTRL_NACK:
/* we did something wrong */
pr_debug("< rx: NACK!!!!!!");
/* try to provide some usable information with a trace of
* the TX history
*/
isobusfs_dump_tx_data(&priv->tx_buf_log);
priv->state = ISOBUSFS_CLI_STATE_IDLE;
break;
default:
pr_warn("%s: unsupported ACK control: %i", __func__, ctrl);
}
/* Not a critical error */
return 0;
}
static int isobusfs_cli_rx_buf(struct isobusfs_priv *priv, struct isobusfs_msg *msg)
{
pgn_t pgn = msg->peername.can_addr.j1939.pgn;
int ret;
switch (pgn) {
case ISOBUSFS_PGN_FS_TO_CL:
ret = isobusfs_cli_rx(priv, msg);
break;
case ISOBUS_PGN_ACK:
ret = isobusfs_cli_rx_ack(priv, msg);
break;
default:
pr_warn("%s: unsupported PGN: %x", __func__, pgn);
/* Not a critical error */
ret = 0;
break;
}
return ret;
}
static int isobusfs_cli_rx_one(struct isobusfs_priv *priv, int sock)
{
struct isobusfs_msg *msg;
int flags = 0;
bool found;
int ret;
msg = malloc(sizeof(*msg));
if (!msg) {
pr_err("can't allocate rx msg struct\n");
return -ENOMEM;
}
msg->buf_size = ISOBUSFS_MAX_TRANSFER_LENGH;
msg->peer_addr_len = sizeof(msg->peername);
msg->sock = sock;
ret = recvfrom(sock, &msg->buf[0], msg->buf_size, flags,
(struct sockaddr *)&msg->peername, &msg->peer_addr_len);
if (ret < 0) {
ret = -errno;
pr_warn("recvfrom() failed: %i %s", ret, strerror(-ret));
return ret;
}
if (ret < ISOBUSFS_MIN_TRANSFER_LENGH) {
pr_warn("buf is less then min transfer: %i\n", ret);
isobusfs_send_nack(priv->sock_nack, msg);
return -EINVAL;
}
msg->len = ret;
ret = isobusfs_cli_rx_event(priv, sock, msg, &found);
if (ret < 0) {
pr_warn("failed to process rx event: %i (%s)\n", ret, strerror(ret));
return ret;
} else if (found)
return 0;
ret = isobusfs_cli_rx_buf(priv, msg);
if (ret < 0) {
pr_warn("failed to process rx buf: %i (%s)\n", ret, strerror(ret));
return ret;
}
return 0;
}
static int isobusfs_cli_handle_events(struct isobusfs_priv *priv, unsigned int nfds)
{
int ret;
unsigned int n;
for (n = 0; n < nfds && n < priv->cmn.epoll_events_size; ++n) {
struct epoll_event *ev = &priv->cmn.epoll_events[n];
if (!ev->events) {
warn("no events");
continue;
}
if (ev->data.fd == priv->sock_ccm) {
if (ev->events & POLLERR) {
struct isobusfs_err_msg emsg = {
.stats = &priv->stats,
};
ret = isobusfs_recv_err(priv->sock_ccm, &emsg);
if (ret)
pr_warn("error queue reported error: %i", ret);
}
} else if (ev->data.fd == STDIN_FILENO) {
if (!priv->interactive) {
warn("got POLLIN on stdin, but interactive mode is disabled");
continue;
}
if (ev->events & POLLIN) {
ret = isobusfs_cli_interactive(priv);
if (ret)
return ret;
} else
warn("got not POLLIN on stdin");
} else if (ev->events & POLLIN) {
ret = isobusfs_cli_rx_one(priv, ev->data.fd);
if (ret) {
warn("recv one");
return ret;
}
}
}
return 0;
}
static int isobusfs_cli_handle_periodic_tasks(struct isobusfs_priv *priv)
{
/* detect FS timeout */
isobusfs_cli_fs_detect_timeout(priv);
isobusfs_cli_run_self_tests(priv);
isobusfs_cli_process_expired_events(priv);
/* this function will send status only if it is proper time to do so */
return isobusfs_cli_ccm_send(priv);
}
int isobusfs_cli_process_events_and_tasks(struct isobusfs_priv *priv)
{
bool dont_wait = false;
int nfds = 0;
int ret;
if (priv->state == ISOBUSFS_CLI_STATE_SELFTEST)
dont_wait = true;
ret = libj1939_prepare_for_events(&priv->cmn, &nfds, dont_wait);
if (ret)
return ret;
if (nfds > 0) {
ret = isobusfs_cli_handle_events(priv, (unsigned int)nfds);
if (ret)
return ret;
}
return isobusfs_cli_handle_periodic_tasks(priv);
}
static int isobusfs_cli_sock_main_prepare(struct isobusfs_priv *priv)
{
struct sockaddr_can addr = priv->sockname;
int ret;
ret = libj1939_open_socket();
if (ret < 0)
return ret;
priv->sock_main = ret;
/* TODO: this is TX only socket */
addr.can_addr.j1939.pgn = ISOBUSFS_PGN_FS_TO_CL;
ret = libj1939_bind_socket(priv->sock_main, &addr);
if (ret < 0)
return ret;
ret = isobusfs_cmn_set_linger(priv->sock_main);
if (ret < 0)
return ret;
ret = libj1939_socket_prio(priv->sock_main, ISOBUSFS_PRIO_DEFAULT);
if (ret < 0)
return ret;
ret = isobusfs_cmn_connect_socket(priv->sock_main, &priv->peername);
if (ret < 0)
return ret;
return libj1939_add_socket_to_epoll(priv->cmn.epoll_fd,
priv->sock_main, EPOLLIN);
}
/* isobusfs_cli_sock_int_prepare() is used to prepare stdin for interactive
* mode.
*/
static int isobusfs_cli_sock_int_prepare(struct isobusfs_priv *priv)
{
int ret;
if (!priv->interactive)
return 0;
isobusfs_set_interactive(true);
ret = fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK);
if (ret < 0)
return ret;
return libj1939_add_socket_to_epoll(priv->cmn.epoll_fd,
STDIN_FILENO, EPOLLIN);
}
static int isobusfs_cli_sock_ccm_prepare(struct isobusfs_priv *priv)
{
struct sockaddr_can addr = priv->sockname;
int ret;
ret = libj1939_open_socket();
if (ret < 0)
return ret;
priv->sock_ccm = ret;
ret = isobusfs_cmn_configure_error_queue(priv->sock_ccm);
if (ret < 0)
return ret;
/* TODO: this is TX only socket */
addr.can_addr.j1939.pgn = J1939_NO_PGN;
ret = libj1939_bind_socket(priv->sock_ccm, &addr);
if (ret < 0)
return ret;
ret = isobusfs_cmn_set_linger(priv->sock_ccm);
if (ret < 0)
return ret;
ret = libj1939_socket_prio(priv->sock_ccm, ISOBUSFS_PRIO_DEFAULT);
if (ret < 0)
return ret;
ret = isobusfs_cmn_connect_socket(priv->sock_ccm, &priv->peername);
if (ret < 0)
return ret;
/* poll for errors to get confirmation if our packets are send */
return libj1939_add_socket_to_epoll(priv->cmn.epoll_fd, priv->sock_ccm,
EPOLLERR);
}
static int isobusfs_cli_sock_nack_prepare(struct isobusfs_priv *priv)
{
struct sockaddr_can addr = priv->sockname;
int ret;
ret = libj1939_open_socket();
if (ret < 0)
return ret;
priv->sock_nack = ret;
addr.can_addr.j1939.pgn = ISOBUS_PGN_ACK;
ret = libj1939_bind_socket(priv->sock_nack, &addr);
if (ret < 0)
return ret;
ret = libj1939_socket_prio(priv->sock_nack, ISOBUSFS_PRIO_ACK);
if (ret < 0)
return ret;
/* poll for errors to get confirmation if our packets are send */
return libj1939_add_socket_to_epoll(priv->cmn.epoll_fd,
priv->sock_nack, EPOLLIN);
}
/* rx socket for fss and volume status announcements */
static int isobusfs_cli_sock_bcast_prepare(struct isobusfs_priv *priv)
{
struct sockaddr_can addr = priv->sockname;
int ret;
ret = libj1939_open_socket();
if (ret < 0)
return ret;
priv->sock_bcast_rx = ret;
/* keep address and name and overwrite PGN */
addr.can_addr.j1939.name = J1939_NO_NAME;
addr.can_addr.j1939.addr = J1939_NO_ADDR;
addr.can_addr.j1939.pgn = ISOBUSFS_PGN_FS_TO_CL;
ret = libj1939_bind_socket(priv->sock_bcast_rx, &addr);
if (ret < 0)
return ret;
ret = libj1939_set_broadcast(priv->sock_bcast_rx);
if (ret < 0)
return ret;
ret = isobusfs_cmn_connect_socket(priv->sock_bcast_rx, &priv->peername);
if (ret < 0)
return ret;
return libj1939_add_socket_to_epoll(priv->cmn.epoll_fd, priv->sock_bcast_rx,
EPOLLIN);
}
static int isobusfs_cli_sock_prepare(struct isobusfs_priv *priv)
{
int ret;
ret = libj1939_create_epoll();
if (ret < 0)
return ret;
priv->cmn.epoll_fd = ret;
priv->cmn.epoll_events = calloc(ISOBUSFS_CLI_MAX_EPOLL_EVENTS,
sizeof(struct epoll_event));
if (!priv->cmn.epoll_events)
return -ENOMEM;
priv->cmn.epoll_events_size = ISOBUSFS_CLI_MAX_EPOLL_EVENTS;
ret = isobusfs_cli_sock_int_prepare(priv);
if (ret < 0)
return ret;
ret = isobusfs_cli_sock_ccm_prepare(priv);
if (ret < 0)
return ret;
ret = isobusfs_cli_sock_bcast_prepare(priv);
if (ret < 0)
return ret;
ret = isobusfs_cli_sock_main_prepare(priv);
if (ret < 0)
return ret;
return isobusfs_cli_sock_nack_prepare(priv);
}
static void isobusfs_cli_print_help(void)
{
printf("Usage: isobusfs-cli [options]\n");
printf("Options:\n");
printf(" --interactive or -I (Default)\n");
printf(" --interface <interface_name> or -i <interface_name>\n");
printf(" --local-address <local_address_hex> or -a <local_address_hex>\n");
printf(" --local-name <local_name_hex> or -n <local_name_hex>\n");
printf(" --log-level <logging_level> or -l <logging_level> (Default %d)\n",
LOG_LEVEL_INFO);
printf(" --remote-address <remote_address_hex> or -r <remote_address_hex>\n");
printf(" --remote-name <remote_name_hex> or -m <remote_name_hex>\n");
printf("Note: Local address and local name are mutually exclusive\n");
printf("Note: Remote address and remote name are mutually exclusive\n");
}
static int isobusfs_cli_parse_args(struct isobusfs_priv *priv, int argc, char *argv[])
{
struct sockaddr_can *remote = &priv->peername;
struct sockaddr_can *local = &priv->sockname;
bool local_address_set = false;
bool local_name_set = false;
bool remote_address_set = false;
bool remote_name_set = false;
bool interface_set = false;
int long_index = 0;
int level;
int opt;
static struct option long_options[] = {
{"interface", required_argument, 0, 'i'},
{"interactive", no_argument, 0, 'I'},
{"local-address", required_argument, 0, 'a'},
{"local-name", required_argument, 0, 'n'},
{"log-level", required_argument, 0, 'l'},
{"remote-address", required_argument, 0, 'r'},
{"remote-name", required_argument, 0, 'm'},
{0, 0, 0, 0}
};
/* active by default */
priv->interactive = true;
while ((opt = getopt_long(argc, argv, "a:n:r:m:Ii:l:", long_options, &long_index)) != -1) {
switch (opt) {
case 'a':
local->can_addr.j1939.addr = strtoul(optarg, NULL, 16);
local_address_set = true;
break;
case 'n':
local->can_addr.j1939.name = strtoull(optarg, NULL, 16);
local_name_set = true;
break;
case 'r':
remote->can_addr.j1939.addr = strtoul(optarg, NULL, 16);
remote_address_set = true;
break;
case 'm':
remote->can_addr.j1939.name = strtoull(optarg, NULL, 16);
remote_name_set = true;
break;
case 'i':
local->can_ifindex = if_nametoindex(optarg);
if (!local->can_ifindex) {
pr_err("Interface %s not found. Error: %d (%s)\n",
optarg, errno, strerror(errno));
return -EINVAL;
}
remote->can_ifindex = local->can_ifindex;
interface_set = true;
break;
case 'I':
priv->interactive = true;
break;
case 'l':
level = strtoul(optarg, NULL, 0);
if (level < LOG_LEVEL_ERROR || level > LOG_LEVEL_DEBUG)
pr_err("invalid debug level %d", level);
isobusfs_log_level_set(level);
break;
default:
isobusfs_cli_print_help();
return -EINVAL;
}
}
if (!interface_set) {
pr_err("interface not specified");
isobusfs_cli_print_help();
return -EINVAL;
}
if ((local_address_set && local_name_set) ||
(remote_address_set && remote_name_set)) {
pr_err("local address and local name or remote address and remote name are mutually exclusive");
isobusfs_cli_print_help();
return -EINVAL;
}
return 0;
}
int main(int argc, char *argv[])
{
struct isobusfs_priv *priv;
struct timespec ts;
int ret;
priv = malloc(sizeof(*priv));
if (!priv)
err(EXIT_FAILURE, "can't allocate priv");
bzero(priv, sizeof(*priv));
libj1939_init_sockaddr_can(&priv->sockname, J1939_NO_PGN);
libj1939_init_sockaddr_can(&priv->peername, ISOBUSFS_PGN_CL_TO_FS);
ret = isobusfs_cli_parse_args(priv, argc, argv);
if (ret)
return ret;
ret = isobusfs_cli_sock_prepare(priv);
if (ret)
return ret;
isobusfs_cli_ccm_init(priv);
/* Init next st_next_send_time value to avoid warnings */
clock_gettime(CLOCK_MONOTONIC, &ts);
priv->cmn.next_send_time = ts;
if (priv->interactive)
isobusfs_cli_int_start(priv);
else
pr_debug("starting client\n");
while (1) {
ret = isobusfs_cli_process_events_and_tasks(priv);
if (ret)
break;
}
close(priv->cmn.epoll_fd);
free(priv->cmn.epoll_events);
close(priv->sock_main);
close(priv->sock_nack);
close(priv->sock_ccm);
close(priv->sock_bcast_rx);
return ret;
}