689 lines
16 KiB
C
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, ¤t_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 && ret != -EINTR)
|
|
return 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 = isobusfs_cmn_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 = isobusfs_cmn_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 = isobusfs_cmn_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 = isobusfs_cmn_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 isobusfs_cmn_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 isobusfs_cmn_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 = isobusfs_cmn_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 = isobusfs_cmn_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 = isobusfs_cmn_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 isobusfs_cmn_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 = isobusfs_cmn_open_socket();
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
priv->sock_nack = ret;
|
|
|
|
addr.can_addr.j1939.pgn = ISOBUS_PGN_ACK;
|
|
ret = isobusfs_cmn_bind_socket(priv->sock_nack, &addr);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = isobusfs_cmn_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 isobusfs_cmn_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 = isobusfs_cmn_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 = isobusfs_cmn_bind_socket(priv->sock_bcast_rx, &addr);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = isobusfs_cmn_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 isobusfs_cmn_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 = isobusfs_cmn_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));
|
|
|
|
isobusfs_init_sockaddr_can(&priv->sockname, J1939_NO_PGN);
|
|
isobusfs_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;
|
|
}
|
|
|