Merge pull request #487 from olerem/isobusfs

Implement ISOBUS File Server (FS) Interface as a Personal Project
pull/495/head
Marc Kleine-Budde 2024-02-02 16:04:03 +01:00 committed by GitHub
commit 8a2619c68d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 9784 additions and 0 deletions

2
.gitignore vendored
View File

@ -38,6 +38,8 @@ GNUmakefile.in
/cansend
/cansequence
/cansniffer
/isobusfs-cli
/isobusfs-srv
/isotpdump
/isotpperf
/isotprecv

View File

@ -53,6 +53,11 @@ set(PROGRAMS_J1939
testj1939
)
set(PROGRAMS_ISOBUSFS
isobusfs-srv
isobusfs-cli
)
set(PROGRAMS
${PROGRAMS_CANLIB}
canfdtest
@ -86,6 +91,7 @@ add_executable(mcp251xfd-dump
if(NOT ANDROID)
list(APPEND PROGRAMS ${PROGRAMS_J1939})
list(APPEND PROGRAMS ${PROGRAMS_ISOBUSFS})
add_library(j1939 STATIC
libj1939.c

View File

@ -13,6 +13,14 @@ LDADD = \
noinst_HEADERS = \
canframelen.h \
isobusfs/isobusfs_cli.h \
isobusfs/isobusfs_cmn.h \
isobusfs/isobusfs_cmn_cm.h \
isobusfs/isobusfs_cmn_dh.h \
isobusfs/isobusfs_cmn_fa.h \
isobusfs/isobusfs_cmn_fh.h \
isobusfs/isobusfs_cmn_va.h \
isobusfs/isobusfs_srv.h \
lib.h \
libj1939.h \
terminal.h \
@ -75,6 +83,13 @@ EXTRA_DIST += \
mcp251xfd/devcoredump \
mcp251xfd/mcp251xfd-gen-testdata.sh
lib_LTLIBRARIES = \
libisobusfs.la
libisobusfs_la_SOURCES = \
isobusfs/isobusfs_cmn.c \
isobusfs/isobusfs_cmn_dh.c
bin_PROGRAMS = \
asc2log \
can-calc-bit-timing \
@ -87,6 +102,8 @@ bin_PROGRAMS = \
cansend \
cansequence \
cansniffer \
isobusfs-cli \
isobusfs-srv \
isotpdump \
isotpperf \
isotprecv \
@ -112,6 +129,33 @@ bin_PROGRAMS += \
isotpserver
endif
isobusfs_cli_SOURCES = \
isobusfs/isobusfs_cli.c \
isobusfs/isobusfs_cli_cm.c \
isobusfs/isobusfs_cli_dh.c \
isobusfs/isobusfs_cli_fa.c \
isobusfs/isobusfs_cli_selftests.c \
isobusfs/isobusfs_cli_int.c
isobusfs_cli_LDADD = \
libisobusfs.la \
libj1939.la \
libcan.la
isobusfs_srv_SOURCES = \
isobusfs/isobusfs_srv.c \
isobusfs/isobusfs_srv_cm.c \
isobusfs/isobusfs_srv_cm_fss.c \
isobusfs/isobusfs_srv_dh.c \
isobusfs/isobusfs_srv_fa.c \
isobusfs/isobusfs_srv_fh.c \
isobusfs/isobusfs_srv_vh.c
isobusfs_srv_LDADD = \
libisobusfs.la \
libj1939.la \
libcan.la
j1939acd_LDADD = libj1939.la
j1939cat_LDADD = libj1939.la
j1939spy_LDADD = libj1939.la

View File

@ -63,6 +63,10 @@ CPPFLAGS += \
PROGRAMS_CANGW := \
cangw
PROGRAMS_ISOBUSFS := \
isobusfs-srv \
isobusfs-cli
PROGRAMS_ISOTP := \
isotpdump \
isotpperf \
@ -89,6 +93,7 @@ PROGRAMS_SLCAN := \
PROGRAMS := \
$(PROGRAMS_CANGW) \
$(PROGRAMS_ISOBUSFS) \
$(PROGRAMS_ISOTP) \
$(PROGRAMS_J1939) \
$(PROGRAMS_SLCAN) \
@ -139,6 +144,8 @@ j1939cat.o: libj1939.h
j1939spy.o: libj1939.h
j1939sr.o: libj1939.h
testj1939.o: libj1939.h
isobusfs_srv.o: libj1939.h lib.h
isobusfs_c.o: libj1939.h lib.h
canframelen.o: canframelen.h
asc2log: asc2log.o lib.o
@ -156,6 +163,21 @@ j1939cat: j1939cat.o libj1939.o
j1939spy: j1939spy.o libj1939.o
j1939sr: j1939sr.o libj1939.o
testj1939: testj1939.o libj1939.o
isobusfs-srv: isobusfs_srv.o isobusfs_cmn.o libj1939.o lib.o \
isobusfs_srv_cm.o \
isobusfs_srv_cm_fss.o \
isobusfs_srv_dh.o \
isobusfs_srv_fa.o \
isobusfs_srv_fh.o \
isobusfs_srv_vh.o \
isobusfs_cmn_dh.o
isobusfs-cli: isobusfs_cli.o isobusfs_cmn.o libj1939.o lib.o \
isobusfs_cli_cm.o \
isobusfs_cli_dh.o \
isobusfs_cli_fa.o \
isobusfs_cli_selftests.o \
isobusfs_cli_int.o
canbusload: canbusload.o canframelen.o
can-calc-bit-timing: calc-bit-timing/can-calc-bit-timing.o

View File

@ -0,0 +1,688 @@
// SPDX-License-Identifier: GPL-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, int nfds)
{
int ret;
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, 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;
}

View File

@ -0,0 +1,215 @@
// SPDX-License-Identifier: GPL-2.0-only
#ifndef ISOBUSFS_CLI_H
#define ISOBUSFS_CLI_H
#include <sys/epoll.h>
#include <stdbool.h>
#include "isobusfs_cmn.h"
#include "isobusfs_cmn_cm.h"
#define ISOBUSFS_CLI_MAX_EPOLL_EVENTS 10
#define ISOBUSFS_CLI_DEFAULT_WAIT_TIMEOUT_MS 1000 /* ms */
enum isobusfs_cli_state {
ISOBUSFS_CLI_STATE_CONNECTING,
ISOBUSFS_CLI_STATE_IDLE,
ISOBUSFS_CLI_STATE_NACKED, /* here is NACKed and not what you think */
ISOBUSFS_CLI_STATE_SELFTEST,
ISOBUSFS_CLI_STATE_WAIT_FS_PROPERTIES,
ISOBUSFS_CLI_STATE_WAIT_CURRENT_DIR,
ISOBUSFS_CLI_STATE_WAIT_CCD_RESP,
ISOBUSFS_CLI_STATE_WAIT_OF_RESP,
ISOBUSFS_CLI_STATE_WAIT_FILE_SIZE,
ISOBUSFS_CLI_STATE_WAIT_FILE,
ISOBUSFS_CLI_STATE_WAIT_VOLUME_STATUS,
ISOBUSFS_CLI_STATE_WAIT_CF_RESP,
ISOBUSFS_CLI_STATE_WAIT_SF_RESP, /* wait for seek file response */
ISOBUSFS_CLI_STATE_WAIT_RF_RESP, /* wait for read file response */
ISOBUSFS_CLI_STATE_MAX_WAITING,
ISOBUSFS_CLI_STATE_CONNECTING_DONE,
ISOBUSFS_CLI_STATE_GET_FS_PROPERTIES_DONE,
ISOBUSFS_CLI_STATE_GET_CURRENT_DIR_DONE,
ISOBUSFS_CLI_STATE_GET_CURRENT_DIR_FAIL,
ISOBUSFS_CLI_STATE_GET_FILE_SIZE_DONE,
ISOBUSFS_CLI_STATE_GET_FILE_DONE,
ISOBUSFS_CLI_STATE_VOLUME_STATUS_DONE,
ISOBUSFS_CLI_STATE_CCD_DONE,
ISOBUSFS_CLI_STATE_CCD_FAIL,
ISOBUSFS_CLI_STATE_OF_DONE,
ISOBUSFS_CLI_STATE_OF_FAIL,
ISOBUSFS_CLI_STATE_CF_DONE,
ISOBUSFS_CLI_STATE_CF_FAIL,
ISOBUSFS_CLI_STATE_SF_DONE,
ISOBUSFS_CLI_STATE_SF_FAIL,
ISOBUSFS_CLI_STATE_RF_CONT,
ISOBUSFS_CLI_STATE_RF_DONE,
ISOBUSFS_CLI_STATE_RF_FAIL,
ISOBUSFS_CLI_STATE_MAX_DONE,
ISOBUSFS_CLI_STATE_GET_FS_PROPERTIES,
ISOBUSFS_CLI_STATE_GET_CURRENT_DIR,
ISOBUSFS_CLI_STATE_GET_FILE_SIZE,
ISOBUSFS_CLI_STATE_GET_FILE,
ISOBUSFS_CLI_STATE_VOLUME_STATUS,
ISOBUSFS_CLI_STATE_TEST_CLEANUP,
ISOBUSFS_CLI_STATE_TEST_DONE,
ISOBUSFS_CLI_STATE_MAX_ACTIVE,
};
struct isobusfs_priv;
typedef int (*isobusfs_event_callback)(struct isobusfs_priv *priv,
struct isobusfs_msg *msg, void *ctx,
int error);
struct isobusfs_event {
isobusfs_event_callback cb;
struct timespec timeout;
/* fs_function is needed to identify package type for event
* subscription
*/
uint8_t fs_function;
int fd;
bool one_shot;
void *ctx;
};
struct isobusfs_priv {
int sock_ccm;
int sock_nack;
int sock_main;
int sock_bcast_rx;
struct isobusfs_cm_ccm ccm; /* file server status message */
bool run_selftest;
struct sockaddr_can sockname;
struct sockaddr_can peername;
struct isobusfs_stats stats;
uint8_t next_tan;
uint8_t cl_buf[1];
bool fs_is_active;
struct timespec fs_last_seen;
uint8_t fs_version;
uint8_t fs_max_open_files;
uint8_t fs_caps;
struct isobusfs_buf_log tx_buf_log;
enum isobusfs_cli_state state;
struct isobusfs_cmn cmn;
uint8_t handle;
uint32_t read_offset;
uint8_t *read_data;
size_t read_data_len;
bool interactive;
bool int_busy;
struct isobusfs_event *events;
uint num_events;
uint max_events;
enum isobusfs_error error_code;
};
/* isobusfs_cli_cm.c */
void isobusfs_cli_ccm_init(struct isobusfs_priv *priv);
int isobusfs_cli_ccm_send(struct isobusfs_priv *priv);
void isobusfs_cli_fs_detect_timeout(struct isobusfs_priv *priv);
int isobusfs_cli_rx_cg_cm(struct isobusfs_priv *priv, struct isobusfs_msg *msg);
int isobusfs_cli_property_req(struct isobusfs_priv *priv);
int isobusfs_cli_volume_status_req(struct isobusfs_priv *priv,
uint8_t volume_mode,
uint16_t path_name_length,
const char *volume_name);
/* isobusfs_cli_dh.c */
int isobusfs_cli_ccd_req(struct isobusfs_priv *priv, const char *name,
size_t name_len);
int isobusfs_cli_get_current_dir_req(struct isobusfs_priv *priv);
int isobusfs_cli_rx_cg_dh(struct isobusfs_priv *priv,
struct isobusfs_msg *msg);
int isobusfs_cli_send_and_register_ccd_event(struct isobusfs_priv *priv,
const char *name,
size_t name_len,
isobusfs_event_callback cb,
void *ctx);
int isobusfs_cli_send_and_register_gcd_event(struct isobusfs_priv *priv,
isobusfs_event_callback cb,
void *ctx);
/* isobusfs_cli_fa.c */
int isobusfs_cli_rx_cg_fa(struct isobusfs_priv *priv,
struct isobusfs_msg *msg);
int isobusfs_cli_fa_of_req(struct isobusfs_priv *priv, const char *name,
size_t name_len, uint8_t flags);
int isobusfs_cli_fa_cf_req(struct isobusfs_priv *priv, uint8_t handle);
int isobusfs_cli_fa_rf_req(struct isobusfs_priv *priv, uint8_t handle,
uint16_t count);
int isobusfs_cli_fa_sf_req(struct isobusfs_priv *priv, uint8_t handle,
uint8_t position_mode, int32_t offset);
int isobusfs_cli_send_and_register_fa_of_event(struct isobusfs_priv *priv,
const char *name,
size_t name_len,
uint8_t flags,
isobusfs_event_callback cb,
void *ctx);
int isobusfs_cli_send_and_register_fa_sf_event(struct isobusfs_priv *priv,
uint8_t handle,
uint8_t position_mode,
int32_t offset,
isobusfs_event_callback cb,
void *ctx);
int isobusfs_cli_send_and_register_fa_rf_event(struct isobusfs_priv *priv,
uint8_t handle,
uint16_t count,
isobusfs_event_callback cb,
void *ctx);
int isobusfs_cli_send_and_register_fa_cf_event(struct isobusfs_priv *priv,
uint8_t handle,
isobusfs_event_callback cb,
void *ctx);
/* isobusfs_cli_selftests.c */
void isobusfs_cli_run_self_tests(struct isobusfs_priv *priv);
/* isobusfs_cli_int.c */
void isobusfs_cli_int_start(struct isobusfs_priv *priv);
int isobusfs_cli_interactive(struct isobusfs_priv *priv);
/* isobusfs_cli.c */
int isobusfs_cli_process_events_and_tasks(struct isobusfs_priv *priv);
void isobusfs_cli_prepare_response_event(struct isobusfs_event *event, int sock,
uint8_t fs_function);
int isobusfs_cli_register_event(struct isobusfs_priv *priv,
const struct isobusfs_event *new_event);
static inline uint8_t isobusfs_cli_get_next_tan(struct isobusfs_priv *priv)
{
return priv->next_tan++;
}
static inline bool isobusfs_cli_tan_is_valid(uint8_t tan,
struct isobusfs_priv *priv)
{
uint8_t expected_tan = priv->next_tan == 0 ? 255 : priv->next_tan - 1;
if (tan != expected_tan) {
pr_err("%s: tan %d is not valid, expected tan %d\n", __func__,
tan, expected_tan);
return false;
}
return true;
}
#endif /* ISOBUSFS_CLI_H */

View File

@ -0,0 +1,241 @@
// SPDX-License-Identifier: GPL-2.0-only
// SPDX-FileCopyrightText: 2023 Oleksij Rempel <linux@rempel-privat.de>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include "isobusfs_cli.h"
#include "isobusfs_cmn_cm.h"
int isobusfs_cli_volume_status_req(struct isobusfs_priv *priv,
uint8_t volume_mode,
uint16_t path_name_length,
const char *volume_name)
{
struct isobusfs_cm_vol_stat_req req;
size_t req_size;
int ret;
req.fs_function =
isobusfs_cg_function_to_buf(ISOBUSFS_CG_CONNECTION_MANAGMENT,
ISOBUSFS_CM_VOLUME_STATUS_REQ);
req.volume_mode = volume_mode;
req.name_len = htole16(path_name_length);
req_size = sizeof(req) - sizeof(req.name) + path_name_length;
memcpy(req.name, volume_name, path_name_length);
ret = isobusfs_send(priv->sock_main, &req, req_size, &priv->tx_buf_log);
if (ret < 0) {
ret = -errno;
pr_warn("failed to send volume status request: %d (%s)", ret, strerror(ret));
return ret;
}
priv->state = ISOBUSFS_CLI_STATE_WAIT_VOLUME_STATUS;
pr_debug("> tx: volume status request");
return 0;
}
int isobusfs_cli_property_req(struct isobusfs_priv *priv)
{
uint8_t buf[ISOBUSFS_MIN_TRANSFER_LENGH];
int ret;
/* not used space should be filled with 0xff */
memset(buf, 0xff, ARRAY_SIZE(buf));
buf[0] = isobusfs_cg_function_to_buf(ISOBUSFS_CG_CONNECTION_MANAGMENT,
ISOBUSFS_CM_GET_FS_PROPERTIES);
/* send property request */
ret = isobusfs_send(priv->sock_main, buf, sizeof(buf), &priv->tx_buf_log);
if (ret < 0)
return ret;
priv->state = ISOBUSFS_CLI_STATE_WAIT_FS_PROPERTIES;
pr_debug("> tx: FS property request");
return 0;
}
/* ccm section */
void isobusfs_cli_ccm_init(struct isobusfs_priv *priv)
{
struct isobusfs_cm_ccm *ccm = &priv->ccm;
ccm->fs_function =
isobusfs_cg_function_to_buf(ISOBUSFS_CG_CONNECTION_MANAGMENT,
ISOBUSFS_CM_F_FS_STATUS);
ccm->version = 2;
memset(ccm->reserved, 0xFF, sizeof(ccm->reserved));
}
/**
* isobusfs_cli_ccm_send - send periodic file server status messages
* @priv: pointer to the isobusfs_priv structure
*
* Returns 0 on success, -1 on errors.
*/
int isobusfs_cli_ccm_send(struct isobusfs_priv *priv)
{
int64_t time_diff;
int ret;
/* Test if it is proper time to send next status message. */
time_diff = timespec_diff_ms(&priv->cmn.next_send_time,
&priv->cmn.last_time);
if (time_diff > ISOBUSFS_CM_F_FS_STATUS_RATE_JITTER) {
/* too early to send next message */
return 0;
}
if (time_diff < -ISOBUSFS_CM_F_FS_STATUS_RATE_JITTER) {
pr_warn("too late to send next fs status message: %ld ms",
time_diff);
}
/* Make sure we send the message with the latest stats */
if (priv->stats.tskey_sch != priv->stats.tskey_ack)
pr_warn("previous message was not acked");
/* send periodic file servers status messages. */
ret = isobusfs_send(priv->sock_ccm, &priv->ccm, sizeof(priv->ccm),
&priv->tx_buf_log);
if (ret < 0) {
pr_err("sendto() failed: %d (%s)", ret, strerror(ret));
return ret;
}
pr_debug("> tx: ccm version: %d", priv->ccm.version);
priv->cmn.next_send_time = priv->cmn.last_time;
timespec_add_ms(&priv->cmn.next_send_time, 2000);
return 0;
}
/* detect if FS is timeout */
void isobusfs_cli_fs_detect_timeout(struct isobusfs_priv *priv)
{
int64_t time_diff;
if (!priv->fs_is_active)
return;
time_diff = timespec_diff_ms(&priv->cmn.last_time,
&priv->fs_last_seen);
if (time_diff > ISOBUSFS_FS_TIMEOUT) {
pr_debug("file server timeout");
priv->fs_is_active = false;
}
}
/* activate FS status if was not active till now */
static void isobusfs_cli_fs_activate(struct isobusfs_priv *priv)
{
if (priv->fs_is_active)
return;
pr_debug("file server detectet");
priv->fs_is_active = true;
}
static int isobusfs_cli_rx_fs_status(struct isobusfs_priv *priv,
struct isobusfs_msg *msg)
{
struct isobusfs_cm_fss *fs_status = (void *)msg->buf;
int ret = 0;
if (msg->len != sizeof(*fs_status)) {
pr_warn("wrong message length: %d", msg->len);
return -EINVAL;
}
isobusfs_cli_fs_activate(priv);
priv->fs_last_seen = priv->cmn.last_time;
pr_debug("< rx: fs status: %x, opened files: %d",
fs_status->status, fs_status->num_open_files);
return ret;
}
/* process FS properties response */
static int isobusfs_cli_rx_fs_property_res(struct isobusfs_priv *priv,
struct isobusfs_msg *msg)
{
struct isobusfs_cm_get_fs_props_resp *fs_prop = (void *)msg->buf;
int ret = 0;
if (priv->state != ISOBUSFS_CLI_STATE_WAIT_FS_PROPERTIES) {
pr_warn("unexpected fs properties response");
return -EINVAL;
}
if (msg->len != sizeof(*fs_prop)) {
pr_warn("wrong message length: %d", msg->len);
return -EINVAL;
}
priv->fs_version = fs_prop->version_number;
priv->fs_max_open_files = fs_prop->max_open_files;
priv->fs_caps = fs_prop->fs_capabilities;
pr_debug("< rx: fs properties: version: %d, max open files: %d, caps: %x",
priv->fs_version, priv->fs_max_open_files, priv->fs_caps);
priv->state = ISOBUSFS_CLI_STATE_GET_FS_PROPERTIES_DONE;
return ret;
}
/* function to handle ISOBUSFS_CM_VOLUME_STATUS_RES */
static int isobusfs_cli_rx_volume_status_res(struct isobusfs_priv *priv,
struct isobusfs_msg *msg)
{
struct isobusfs_cm_vol_stat_res *vol_status = (void *)msg->buf;
int ret = 0;
if (priv->state != ISOBUSFS_CLI_STATE_WAIT_VOLUME_STATUS) {
pr_warn("unexpected volume status response");
return -EINVAL;
}
pr_debug("< rx: volume status: %x, max time before remove %d, error code %d, path name length %d, name %s",
vol_status->volume_status,
vol_status->max_time_before_removal,
vol_status->error_code,
vol_status->name_len,
vol_status->name);
priv->state = ISOBUSFS_CLI_STATE_VOLUME_STATUS_DONE;
return ret;
}
/* Command group: connection management */
int isobusfs_cli_rx_cg_cm(struct isobusfs_priv *priv, struct isobusfs_msg *msg)
{
int func = isobusfs_buf_to_function(msg->buf);
int ret = 0;
switch (func) {
case ISOBUSFS_CM_F_FS_STATUS:
return isobusfs_cli_rx_fs_status(priv, msg);
case ISOBUSFS_CM_GET_FS_PROPERTIES_RES:
return isobusfs_cli_rx_fs_property_res(priv, msg);
case ISOBUSFS_CM_VOLUME_STATUS_RES:
return isobusfs_cli_rx_volume_status_res(priv, msg);
default:
pr_warn("unsupported function: %i", func);
return -EINVAL;
}
return ret;
}

View File

@ -0,0 +1,232 @@
// SPDX-License-Identifier: GPL-2.0-only
// SPDX-FileCopyrightText: 2023 Oleksij Rempel <linux@rempel-privat.de>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include "isobusfs_cli.h"
#include "isobusfs_cmn_dh.h"
/*
* C.2.3.2 Change Current Directory Request
*/
int isobusfs_cli_ccd_req(struct isobusfs_priv *priv, const char *name,
size_t name_len)
{
struct isobusfs_dh_ccd_req *req;
size_t req_len = sizeof(*req) + name_len;
size_t padding_size = 0;
int ret;
if (name_len > ISOBUSFS_MAX_PATH_NAME_LENGTH) {
pr_warn("path name too long: %i, max is %i", name_len,
ISOBUSFS_MAX_PATH_NAME_LENGTH);
return -EINVAL;
}
if (req_len < ISOBUSFS_MIN_TRANSFER_LENGH) {
/* Update the buffer size accordingly */
padding_size = ISOBUSFS_MIN_TRANSFER_LENGH - req_len;
req_len = ISOBUSFS_MIN_TRANSFER_LENGH;
}
req = malloc(req_len);
if (!req) {
pr_err("failed to allocate memory for ccd request");
return -ENOMEM;
}
req->fs_function = isobusfs_cg_function_to_buf(ISOBUSFS_CG_DIRECTORY_HANDLING,
ISOBUSFS_DH_F_CHANGE_CURRENT_DIR_REQ);
req->tan = isobusfs_cli_get_next_tan(priv);
memcpy(&req->name[0], name, name_len);
req->name_len = name_len;
if (padding_size) {
/* Fill the rest of the res structure with 0xff */
memset(((uint8_t *)req) + req_len - padding_size, 0xff,
padding_size);
}
priv->state = ISOBUSFS_CLI_STATE_WAIT_CCD_RESP;
ret = isobusfs_send(priv->sock_main, req, req_len, &priv->tx_buf_log);
if (ret < 0) {
ret = -errno;
pr_warn("failed to send ccd request: %d (%s)",
ret, strerror(ret));
goto free_req;
}
pr_debug("> tx: ccd request for %s", name);
free_req:
free(req);
return ret;
}
static int isobusfs_cli_dh_ccd_res_log(struct isobusfs_priv *priv,
struct isobusfs_msg *msg, void *ctx,
int error)
{
struct isobusfs_dh_ccd_res *res =
(struct isobusfs_dh_ccd_res *)msg->buf;
if (priv->state != ISOBUSFS_CLI_STATE_WAIT_CCD_RESP) {
pr_warn("invalid state: %i (expected %i)", priv->state,
ISOBUSFS_CLI_STATE_WAIT_CCD_RESP);
return -EINVAL;
}
if (!isobusfs_cli_tan_is_valid(res->tan, priv)) {
priv->state = ISOBUSFS_CLI_STATE_CCD_FAIL;
} else if (res->error_code != 0) {
pr_warn("ccd failed with error code: %i", res->error_code);
priv->state = ISOBUSFS_CLI_STATE_CCD_FAIL;
} else {
priv->state = ISOBUSFS_CLI_STATE_CCD_DONE;
}
priv->error_code = res->error_code;
if (!error)
pr_debug("< rx: change current directory response. Error code: %i",
res->error_code);
return 0;
}
int isobusfs_cli_send_and_register_ccd_event(struct isobusfs_priv *priv,
const char *name,
size_t name_len,
isobusfs_event_callback cb,
void *ctx)
{
struct isobusfs_event event;
uint8_t fs_function;
int ret;
ret = isobusfs_cli_ccd_req(priv, name, name_len);
if (ret < 0)
return ret;
fs_function =
isobusfs_cg_function_to_buf(ISOBUSFS_CG_DIRECTORY_HANDLING,
ISOBUSFS_DH_F_CHANGE_CURRENT_DIR_RES);
if (cb)
event.cb = cb;
else
event.cb = isobusfs_cli_dh_ccd_res_log;
event.ctx = ctx;
isobusfs_cli_prepare_response_event(&event, priv->sock_main,
fs_function);
return isobusfs_cli_register_event(priv, &event);
}
/* function to send current directory request */
int isobusfs_cli_get_current_dir_req(struct isobusfs_priv *priv)
{
struct isobusfs_dh_get_cd_req req;
int ret;
req.fs_function = isobusfs_cg_function_to_buf(ISOBUSFS_CG_DIRECTORY_HANDLING,
ISOBUSFS_DH_F_GET_CURRENT_DIR_REQ);
req.tan = isobusfs_cli_get_next_tan(priv);
ret = isobusfs_send(priv->sock_main, &req, sizeof(req), &priv->tx_buf_log);
if (ret < 0) {
ret = -errno;
pr_warn("failed to send current directory request: %d (%s)",
ret, strerror(ret));
return ret;
}
priv->state = ISOBUSFS_CLI_STATE_WAIT_CURRENT_DIR;
pr_debug("> tx: current directory request");
return 0;
}
static int isobusfs_cli_dh_current_dir_res_log(struct isobusfs_priv *priv,
struct isobusfs_msg *msg,
void *ctx, int error)
{
struct isobusfs_dh_get_cd_res *res =
(struct isobusfs_dh_get_cd_res *)msg->buf;
char str[ISOBUSFS_MAX_PATH_NAME_LENGTH];
uint16_t total_space, free_space, str_len;
if (!isobusfs_cli_tan_is_valid(res->tan, priv))
pr_warn("invalid tan: %i", res->tan);
total_space = le16toh(res->total_space);
free_space = le16toh(res->free_space);
str_len = le16toh(res->name_len);
if (str_len > ISOBUSFS_MAX_PATH_NAME_LENGTH) {
pr_warn("path name too long: %i, max is %i", str_len,
ISOBUSFS_MAX_PATH_NAME_LENGTH);
str_len = ISOBUSFS_MAX_PATH_NAME_LENGTH;
}
strncpy(str, (const char *)&res->name[0], str_len);
priv->state = ISOBUSFS_CLI_STATE_GET_CURRENT_DIR_DONE;
pr_debug("< rx: current directory response: %s, total space: %i, free space: %i",
str, total_space, free_space);
return 0;
}
int isobusfs_cli_send_and_register_gcd_event(struct isobusfs_priv *priv,
isobusfs_event_callback cb,
void *ctx)
{
struct isobusfs_event event;
uint8_t fs_function;
int ret;
ret = isobusfs_cli_get_current_dir_req(priv);
if (ret < 0)
return ret;
fs_function =
isobusfs_cg_function_to_buf(ISOBUSFS_CG_DIRECTORY_HANDLING,
ISOBUSFS_DH_F_GET_CURRENT_DIR_RES);
if (cb)
event.cb = cb;
else
event.cb = isobusfs_cli_dh_ccd_res_log;
event.ctx = ctx;
isobusfs_cli_prepare_response_event(&event, priv->sock_main,
fs_function);
return isobusfs_cli_register_event(priv, &event);
}
/* Command group: directory handling */
int isobusfs_cli_rx_cg_dh(struct isobusfs_priv *priv,
struct isobusfs_msg *msg)
{
int func = isobusfs_buf_to_function(msg->buf);
int ret = 0;
switch (func) {
case ISOBUSFS_DH_F_GET_CURRENT_DIR_RES:
return isobusfs_cli_dh_current_dir_res_log(priv, msg, NULL, 0);
case ISOBUSFS_DH_F_CHANGE_CURRENT_DIR_RES:
return isobusfs_cli_dh_ccd_res_log(priv, msg, NULL, 0);
default:
pr_warn("%s: unsupported function: %i", __func__, func);
/* Not a critical error */
}
return ret;
}

View File

@ -0,0 +1,430 @@
// SPDX-License-Identifier: GPL-2.0-only
// SPDX-FileCopyrightText: 2023 Oleksij Rempel <linux@rempel-privat.de>
#include <errno.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include "isobusfs_cli.h"
#include "isobusfs_cmn_fa.h"
int isobusfs_cli_fa_sf_req(struct isobusfs_priv *priv, uint8_t handle,
uint8_t position_mode, int32_t offset)
{
struct isobusfs_fa_seekf_req req;
int ret;
req.fs_function = isobusfs_cg_function_to_buf(ISOBUSFS_CG_FILE_ACCESS,
ISOBUSFS_FA_F_SEEK_FILE_REQ);
req.tan = isobusfs_cli_get_next_tan(priv);
req.handle = handle;
req.position_mode = position_mode;
req.offset = htole32(offset);
priv->state = ISOBUSFS_CLI_STATE_WAIT_SF_RESP;
ret = isobusfs_send(priv->sock_main, &req, sizeof(req),
&priv->tx_buf_log);
if (ret < 0) {
pr_warn("failed to send Seek File Request: %d (%s)", ret,
strerror(ret));
return ret;
}
pr_debug("> tx: Seek File Request for handle: %x, position mode: %d, offset: %d",
req.handle, req.position_mode, req.offset);
return ret;
}
static int isobusfs_cli_fa_sf_res_log(struct isobusfs_priv *priv,
struct isobusfs_msg *msg,
void *ctx, int error)
{
struct isobusfs_fa_seekf_res *res =
(struct isobusfs_fa_seekf_res *)msg;
if (!isobusfs_cli_tan_is_valid(res->tan, priv)) {
priv->state = ISOBUSFS_CLI_STATE_SF_FAIL;
return -EINVAL;
}
if (res->error_code) {
priv->state = ISOBUSFS_CLI_STATE_SF_FAIL;
pr_warn("< rx: Seek File Error - Error code: %d",
res->error_code);
return -EIO;
}
priv->read_offset = le32toh(res->position);
priv->state = ISOBUSFS_CLI_STATE_SF_DONE;
pr_debug("< rx: Seek File Success, position: %d", res->position);
return 0;
}
int isobusfs_cli_send_and_register_fa_sf_event(struct isobusfs_priv *priv,
uint8_t handle,
uint8_t position_mode,
int32_t offset,
isobusfs_event_callback cb,
void *ctx)
{
struct isobusfs_event event;
uint8_t fs_function;
int ret;
ret = isobusfs_cli_fa_sf_req(priv, handle, position_mode, offset);
if (ret < 0)
return ret;
fs_function =
isobusfs_cg_function_to_buf(ISOBUSFS_CG_FILE_ACCESS,
ISOBUSFS_FA_F_SEEK_FILE_RES);
if (cb)
event.cb = cb;
else
event.cb = isobusfs_cli_fa_sf_res_log;
event.ctx = ctx;
isobusfs_cli_prepare_response_event(&event, priv->sock_main,
fs_function);
return isobusfs_cli_register_event(priv, &event);
}
int isobusfs_cli_fa_rf_req(struct isobusfs_priv *priv, uint8_t handle,
uint16_t count)
{
struct isobusfs_fa_readf_req req;
int ret;
req.fs_function = isobusfs_cg_function_to_buf(ISOBUSFS_CG_FILE_ACCESS,
ISOBUSFS_FA_F_READ_FILE_REQ);
req.tan = isobusfs_cli_get_next_tan(priv);
req.handle = handle;
req.count = htole16(count);
memset(req.reserved, 0xff, sizeof(req.reserved));
priv->state = ISOBUSFS_CLI_STATE_WAIT_RF_RESP;
ret = isobusfs_send(priv->sock_main, &req, sizeof(req), &priv->tx_buf_log);
if (ret < 0) {
ret = -errno;
pr_warn("failed to send Read File Request: %d (%s)", ret, strerror(ret));
return ret;
}
pr_debug("> tx: Read File Request for handle: %x, size: %d", req.handle, count);
return ret;
}
static int isobusfs_cli_fa_rf_res_log(struct isobusfs_priv *priv,
struct isobusfs_msg *msg,
void *ctx, int error)
{
struct isobusfs_read_file_response *res =
(struct isobusfs_read_file_response *)msg->buf;
if (priv->state != ISOBUSFS_CLI_STATE_WAIT_RF_RESP) {
pr_warn("invalid state: %i (expected %i)", priv->state,
ISOBUSFS_CLI_STATE_WAIT_RF_RESP);
return -EINVAL;
}
if (!isobusfs_cli_tan_is_valid(res->tan, priv)) {
priv->state = ISOBUSFS_CLI_STATE_RF_FAIL;
} else if (res->error_code && res->error_code != ISOBUSFS_ERR_END_OF_FILE) {
pr_warn("read file failed with error code: %i", res->error_code);
priv->state = ISOBUSFS_CLI_STATE_RF_FAIL;
} else {
if (priv->read_data) {
pr_err("read data buffer not empty");
free(priv->read_data);
}
priv->read_data_len = le16toh(res->count);
priv->read_data = malloc(priv->read_data_len);
if (!priv->read_data) {
pr_err("failed to allocate memory for data");
priv->state = ISOBUSFS_CLI_STATE_RF_FAIL;
} else {
memcpy(priv->read_data, res->data, priv->read_data_len);
priv->state = ISOBUSFS_CLI_STATE_RF_DONE;
}
}
pr_debug("< rx: Read File Response. Error code: %i", res->error_code);
return 0;
}
int isobusfs_cli_send_and_register_fa_rf_event(struct isobusfs_priv *priv,
uint8_t handle,
uint16_t count,
isobusfs_event_callback cb,
void *ctx)
{
struct isobusfs_event event;
uint8_t fs_function;
int ret;
ret = isobusfs_cli_fa_rf_req(priv, handle, count);
if (ret < 0)
return ret;
fs_function =
isobusfs_cg_function_to_buf(ISOBUSFS_CG_FILE_ACCESS,
ISOBUSFS_FA_F_READ_FILE_RES);
if (cb)
event.cb = cb;
else
event.cb = isobusfs_cli_fa_rf_res_log;
event.ctx = ctx;
isobusfs_cli_prepare_response_event(&event, priv->sock_main,
fs_function);
return isobusfs_cli_register_event(priv, &event);
}
int isobusfs_cli_fa_cf_req(struct isobusfs_priv *priv, uint8_t handle)
{
struct isobusfs_close_file_request req;
int ret;
req.fs_function = isobusfs_cg_function_to_buf(ISOBUSFS_CG_FILE_ACCESS,
ISOBUSFS_FA_F_CLOSE_FILE_REQ);
req.tan = isobusfs_cli_get_next_tan(priv);
req.handle = handle;
memset(req.reserved, 0xff, sizeof(req.reserved));
priv->state = ISOBUSFS_CLI_STATE_WAIT_CF_RESP;
ret = isobusfs_send(priv->sock_main, &req, sizeof(req), &priv->tx_buf_log);
if (ret < 0) {
ret = -errno;
pr_warn("failed to send Close File request: %d (%s)", ret, strerror(ret));
return ret;
}
pr_debug("> tx: Close File Request for handle: %x", req.handle);
return ret;
}
static int isobusfs_cli_fa_cf_res_log(struct isobusfs_priv *priv,
struct isobusfs_msg *msg,
void *ctx, int error)
{
struct isobusfs_close_file_res *res =
(struct isobusfs_close_file_res *)msg->buf;
if (priv->state != ISOBUSFS_CLI_STATE_WAIT_CF_RESP) {
pr_warn("invalid state: %i (expected %i)", priv->state,
ISOBUSFS_CLI_STATE_WAIT_CF_RESP);
return -EINVAL;
}
if (!isobusfs_cli_tan_is_valid(res->tan, priv)) {
priv->state = ISOBUSFS_CLI_STATE_CF_FAIL;
} else if (res->error_code != 0) {
pr_warn("ccd failed with error code: %i", res->error_code);
priv->state = ISOBUSFS_CLI_STATE_CF_FAIL;
} else {
priv->state = ISOBUSFS_CLI_STATE_CF_DONE;
}
pr_debug("< rx: Close File Response. Error code: %i",
res->error_code);
return 0;
}
int isobusfs_cli_send_and_register_fa_cf_event(struct isobusfs_priv *priv,
uint8_t handle,
isobusfs_event_callback cb,
void *ctx)
{
struct isobusfs_event event;
uint8_t fs_function;
int ret;
ret = isobusfs_cli_fa_cf_req(priv, handle);
if (ret < 0)
return ret;
fs_function =
isobusfs_cg_function_to_buf(ISOBUSFS_CG_FILE_ACCESS,
ISOBUSFS_FA_F_CLOSE_FILE_RES);
if (cb)
event.cb = cb;
else
event.cb = isobusfs_cli_fa_cf_res_log;
event.ctx = ctx;
isobusfs_cli_prepare_response_event(&event, priv->sock_main,
fs_function);
return isobusfs_cli_register_event(priv, &event);
}
int isobusfs_cli_fa_of_req(struct isobusfs_priv *priv, const char *name,
size_t name_len, uint8_t flags)
{
struct isobusfs_fa_openf_req *req;
size_t req_len = sizeof(*req) + name_len;
size_t padding_size = 0;
int ret;
if (name_len > ISOBUSFS_MAX_PATH_NAME_LENGTH) {
pr_warn("path name too long: %i, max is %i", name_len,
ISOBUSFS_MAX_PATH_NAME_LENGTH);
return -EINVAL;
}
if (req_len < ISOBUSFS_MIN_TRANSFER_LENGH) {
/* Update the buffer size accordingly */
padding_size = ISOBUSFS_MIN_TRANSFER_LENGH - req_len;
req_len = ISOBUSFS_MIN_TRANSFER_LENGH;
}
req = malloc(req_len);
if (!req) {
pr_err("failed to allocate memory for ccd request");
return -ENOMEM;
}
req->fs_function = isobusfs_cg_function_to_buf(ISOBUSFS_CG_FILE_ACCESS,
ISOBUSFS_FA_F_OPEN_FILE_REQ);
req->tan = isobusfs_cli_get_next_tan(priv);
req->flags = flags;
memcpy(&req->name[0], name, name_len);
req->name_len = name_len;
if (padding_size) {
/* Fill the rest of the res structure with 0xff */
memset(((uint8_t *)req) + req_len - padding_size, 0xff,
padding_size);
}
priv->handle = ISOBUSFS_FILE_HANDLE_ERROR;
priv->state = ISOBUSFS_CLI_STATE_WAIT_OF_RESP;
ret = isobusfs_send(priv->sock_main, req, req_len, &priv->tx_buf_log);
if (ret < 0) {
ret = -errno;
pr_warn("failed to send ccd request: %d (%s)",
ret, strerror(ret));
goto free_req;
}
pr_debug("> tx: Open File Request for %s, with flags: %x", name,
req->flags);
free_req:
free(req);
return ret;
}
static int isobusfs_cli_fa_open_file_res_log(struct isobusfs_priv *priv,
struct isobusfs_msg *msg,
void *ctx, int error)
{
struct isobusfs_fa_openf_res *res =
(struct isobusfs_fa_openf_res *)msg->buf;
if (priv->state != ISOBUSFS_CLI_STATE_WAIT_OF_RESP) {
pr_warn("invalid state: %i (expected %i)", priv->state,
ISOBUSFS_CLI_STATE_WAIT_OF_RESP);
return -EINVAL;
}
if (!isobusfs_cli_tan_is_valid(res->tan, priv)) {
priv->state = ISOBUSFS_CLI_STATE_OF_FAIL;
} else if (res->error_code != 0) {
pr_warn("open file request failed with error code: %i", res->error_code);
priv->state = ISOBUSFS_CLI_STATE_OF_FAIL;
} else if (res->handle == ISOBUSFS_FILE_HANDLE_ERROR) {
pr_warn("open file request didn't failed with error code, but with handle");
priv->state = ISOBUSFS_CLI_STATE_OF_FAIL;
} else {
priv->state = ISOBUSFS_CLI_STATE_OF_DONE;
priv->handle = res->handle;
}
pr_debug("< rx: Open File Response. Error code: %i",
res->error_code);
return 0;
}
int isobusfs_cli_send_and_register_fa_of_event(struct isobusfs_priv *priv,
const char *name,
size_t name_len,
uint8_t flags,
isobusfs_event_callback cb,
void *ctx)
{
struct isobusfs_event event;
uint8_t fs_function;
int ret;
ret = isobusfs_cli_fa_of_req(priv, name, name_len, flags);
if (ret < 0)
return ret;
fs_function =
isobusfs_cg_function_to_buf(ISOBUSFS_CG_FILE_ACCESS,
ISOBUSFS_FA_F_OPEN_FILE_RES);
if (cb)
event.cb = cb;
else
event.cb = isobusfs_cli_fa_open_file_res_log;
event.ctx = ctx;
isobusfs_cli_prepare_response_event(&event, priv->sock_main,
fs_function);
return isobusfs_cli_register_event(priv, &event);
}
/* Command group: directory handling */
int isobusfs_cli_rx_cg_fa(struct isobusfs_priv *priv,
struct isobusfs_msg *msg)
{
int func = isobusfs_buf_to_function(msg->buf);
int ret = 0;
switch (func) {
case ISOBUSFS_FA_F_OPEN_FILE_RES:
ret = isobusfs_cli_fa_open_file_res_log(priv, msg, NULL, 0);
break;
case ISOBUSFS_FA_F_CLOSE_FILE_RES:
ret = isobusfs_cli_fa_cf_res_log(priv, msg, NULL, 0);
break;
case ISOBUSFS_FA_F_READ_FILE_RES:
ret = isobusfs_cli_fa_rf_res_log(priv, msg, NULL, 0);
break;
case ISOBUSFS_FA_F_SEEK_FILE_RES:
ret = isobusfs_cli_fa_sf_res_log(priv, msg, NULL, 0);
break;
case ISOBUSFS_FA_F_WRITE_FILE_RES:
default:
pr_warn("%s: unsupported function: %i", __func__, func);
/* Not a critical error */
}
return ret;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,920 @@
// SPDX-License-Identifier: GPL-2.0-only
// SPDX-FileCopyrightText: 2023 Oleksij Rempel <linux@rempel-privat.de>
#include <errno.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <time.h>
#include "isobusfs_cli.h"
#include "isobusfs_cmn.h"
#include "isobusfs_cmn_fa.h"
size_t current_test;
bool test_running;
struct timespec test_start_time;
struct isobusfs_cli_test_case {
int (*test_func)(struct isobusfs_priv *priv, bool *complete);
const char *test_description;
};
static int isobusfs_cli_test_connect(struct isobusfs_priv *priv, bool *complete)
{
struct timespec current_time;
int ret;
clock_gettime(CLOCK_MONOTONIC, &current_time);
switch (priv->state) {
case ISOBUSFS_CLI_STATE_SELFTEST:
test_start_time = current_time;
priv->state = ISOBUSFS_CLI_STATE_CONNECTING;
/* fall through */
case ISOBUSFS_CLI_STATE_CONNECTING:
if (priv->fs_is_active) {
*complete = true;
break;
}
if (current_time.tv_sec - test_start_time.tv_sec >= 5) {
ret = -ETIMEDOUT;
goto test_fail;
}
break;
default:
pr_err("%s:%i: unknown state: %d", __func__, __LINE__, priv->state);
ret = -EINVAL;
goto test_fail;
}
return 0;
test_fail:
/* without server all other tests make no sense */
priv->run_selftest = false;
*complete = true;
return ret;
}
static int isobusfs_cli_test_property_req(struct isobusfs_priv *priv, bool *complete)
{
struct timespec current_time;
int ret;
clock_gettime(CLOCK_MONOTONIC, &current_time);
switch (priv->state) {
case ISOBUSFS_CLI_STATE_SELFTEST:
test_start_time = current_time;
ret = isobusfs_cli_property_req(priv);
if (ret)
goto test_fail;
break;
case ISOBUSFS_CLI_STATE_WAIT_FS_PROPERTIES:
if (current_time.tv_sec - test_start_time.tv_sec >= 5) {
ret = -ETIMEDOUT;
goto test_fail;
}
break;
case ISOBUSFS_CLI_STATE_GET_FS_PROPERTIES_DONE:
*complete = true;
break;
default:
pr_err("%s:%i: unknown state: %d", __func__, __LINE__, priv->state);
ret = -EINVAL;
goto test_fail;
}
return 0;
test_fail:
*complete = true;
return ret;
}
static int isobusfs_cli_test_volume_status_req(struct isobusfs_priv *priv, bool *complete)
{
static const char volume_name[] = "\\\\vol1";
struct timespec current_time;
int ret;
clock_gettime(CLOCK_MONOTONIC, &current_time);
switch (priv->state) {
case ISOBUSFS_CLI_STATE_SELFTEST:
test_start_time = current_time;
ret = isobusfs_cli_volume_status_req(priv, 0,
sizeof(volume_name) - 1, volume_name);
if (ret)
goto test_fail;
break;
case ISOBUSFS_CLI_STATE_WAIT_VOLUME_STATUS:
if (current_time.tv_sec - test_start_time.tv_sec >= 5) {
ret = -ETIMEDOUT;
goto test_fail;
}
break;
case ISOBUSFS_CLI_STATE_VOLUME_STATUS_DONE:
*complete = true;
break;
default:
pr_err("%s:%i: unknown state: %d", __func__, __LINE__, priv->state);
ret = -EINVAL;
goto test_fail;
}
return 0;
test_fail:
*complete = true;
return ret;
}
static int isobusfs_cli_test_current_dir_req(struct isobusfs_priv *priv,
bool *complete)
{
struct timespec current_time;
int ret;
clock_gettime(CLOCK_MONOTONIC, &current_time);
switch (priv->state) {
case ISOBUSFS_CLI_STATE_SELFTEST:
test_start_time = current_time;
ret = isobusfs_cli_get_current_dir_req(priv);
if (ret)
goto test_fail;
break;
case ISOBUSFS_CLI_STATE_WAIT_CURRENT_DIR:
if (current_time.tv_sec - test_start_time.tv_sec >= 5) {
ret = -ETIMEDOUT;
goto test_fail;
}
break;
case ISOBUSFS_CLI_STATE_GET_CURRENT_DIR_DONE:
*complete = true;
break;
default:
pr_err("%s:%i: unknown state: %d", __func__, __LINE__, priv->state);
ret = -EINVAL;
goto test_fail;
}
return 0;
test_fail:
*complete = true;
return ret;
}
struct isobusfs_cli_test_dir_path {
const char *dir_name;
bool expect_pass;
};
static struct isobusfs_cli_test_dir_path test_dir_patterns[] = {
/* expected result \\vol1\dir1\ */
{ "\\\\vol1\\dir1", true },
/* expected result \\vol1\dir1\dir2\ */
{ "\\\\vol1\\dir1\\dir2", true },
/* expected result \\vol1\dir1\dir2\dir3\dir4\ */
{ ".\\dir3\\dir4", true },
/* expected result \\vol1\dir1\dir2\dir3\dir5\ */
{ "..\\dir5", true },
/* expected result \\vol1\ */
{ "..\\..\\..\\..\\..\\..\\vol1", true },
/* expected result \\vol1\~\ */
{ "~\\", true },
/* expected result \\vol1\~\msd_dir1\msd_dir2\ */
{ "~\\msd_dir1\\msd_dir2", true },
/* expected result \\vol1\~\ */
{ "\\\\vol1\\~\\", true },
/* expected result \\vol1\~\msd_dir1\msd_dir2\ */
{ "\\\\vol1\\~\\msd_dir1\\msd_dir2", true },
/* expected result \\vol1\~\msd_dir1\msd_dir2\~\ */
{ ".\\~\\", true },
/* expected result \\vol1\~\msd_dir1\msd_dir2\~\~tilde_dir */
{ "~tilde_dir", true },
/* expected result \\vol1\dir1\~\ */
{ "\\\\vol1\\dir1\\~", true },
/* expected result \\vol1\~\ not clear if it is manufacture speficic dir */
{ "\\~\\", true },
/* expected result \\~\ */
{ "\\\\~\\", false },
/* expected result: should fail */
{ "\\\\\\\\\\\\\\\\", false },
/* Set back to dir1 for other test. Expected result \\vol1\dir1\ */
{ "\\\\vol1\\dir1", true },
/* Initialize server path to root: Expected initial state: root */
{ "\\\\vol1\\dir1", true }, /* Set server path to \\vol1\dir1\ */
/* Test absolute paths: Expected state: \\vol1\dir1\ */
{ "\\\\vol1\\dir1\\dir2", true }, /* Changes to \\vol1\dir1\dir2\ */
{ "\\\\vol1\\dir1", true }, /* Changes back to \\vol1\dir1\ */
/* Test relative path .\ : Expected state: \\vol1\dir1\ */
{ ".\\dir2\\dir3\\dir4", true }, /* Changes to \\vol1\dir1\dir2\dir3\dir4\ */
{ "..\\dir5", true }, /* Changes to \\vol1\dir1\dir2\dir3\dir5\ */
{ "..\\..\\..\\..\\..\\..\\vol1", true }, /* Changes to \\vol1\ */
{ ".\\dir1\\dir2", true }, /* Changes back to \\vol1\dir1\dir2 */
/* Test relative path ..\ with multiple backslashes:
* Expected state: \\vol1\dir1\dir2\
*/
{ "..\\\\\\", true }, /* Changes to \\vol1\dir1\ */
{ ".\\dir2", true }, /* Changes back to \\vol1\dir1\dir2\ */
{ "..\\\\\\\\\\\\\\", true }, /* Changes to \\vol1\dir1\ */
{ ".\\dir2", true }, /* Changes back to \\vol1\dir1\dir2 */
{ "\\\\vol1\\dir1", true }, /* Changes back to \\vol1\dir1\ */
/* Test relative path .\ with multiple backslashes: Expected state:
* \\vol1\dir1\
*/
{ ".\\\\\\", true }, /* Remains at \\vol1\dir1\ */
{ ".\\dir2", true }, /* Changes back to \\vol1\dir1\dir2 */
{ "..\\", true }, /* Changes to \\vol1\dir1\ */
{ ".\\\\\\\\\\\\\\", true }, /* Remains at \\vol1\dir1\ */
{ ".\\dir2", true }, /* Changes back to \\vol1\dir1\dir2 */
{ "..\\", true }, /* Changes to \\vol1\dir1\ */
/* Test navigating up and down: Expected state: \\vol1\dir1\ */
{ "..\\..\\..\\..\\..\\..\\vol1", true }, /* Changes to \\vol1\ */
/* prepare for tilde tests */
{ "\\\\vol1\\", true }, /* Set server path to \\vol1\ */
/* Tilde used correctly at the beginning of a path */
{ "~\\", true }, /* Replace with the manufacturer-specific directory on
* the current volume
*/
/* Tilde used correctly after a volume name */
{ "\\\\vol1\\~\\", true }, /* Replace ~ with the manufacturer-specific
* directory on vol1
*/
/* Tilde used in non-root locations, treated as a regular directory */
{ "\\\\vol1\\dir1\\~", true }, /* Treated as a regular directory named
* '~' under \\vol1\dir1\
*/
{ ".\\~\\", true }, /* Treated as a regular directory named '~' in the
* current directory: \\vol1\dir1\~\
*/
/* Tilde used with a specific manufacturer directory at the root */
{ "~\\msd_dir1\\msd_dir2", true }, /* Replace ~ and append rest of the
* path on the current volume
*/
{ "\\\\vol1\\~\\msd_dir1\\msd_dir2", true },
/* Replace ~ and append rest of the
* path on vol1
*/
{ ".\\~\\", true }, /* Treated as a regular directory named '~' in the
* current directory: \\vol1\~\msd_dir1\msd_dir2\~\
*/
{ "~tilde_dir", true }, /* Treated as a regular directory named
* '~tilde_dir' in the current directory:
* \\vol1\~\msd_dir1\msd_dir2\~\~tilde_dir\
*/
/* Invalid usage of tilde at non-root locations (as a
* manufacturer-specific directory)
*/
{ "\\~\\", false }, /* Incorrect usage of tilde at non-root, expected
* to fail
*/
{ "\\\\~\\", false }, /* Incorrect usage of tilde at non-root, expected
* to fail
*/
/* Test invalid or ambiguous paths: Expected state: \\vol1\dir1\ */
{ "\\\\\\\\\\\\\\\\", false }, /* Invalid path, should fail */
/* Set back to dir1 for other tests: Expected state: \\vol1\dir1\ */
{ "\\\\vol1\\dir1", true }, /* Ensure server path is set to \\vol1\dir1\ */
};
size_t current_dir_pattern_test;
static int isobusfs_cli_test_ccd_req(struct isobusfs_priv *priv, bool *complete)
{
size_t num_patterns = ARRAY_SIZE(test_dir_patterns);
struct isobusfs_cli_test_dir_path *tp =
&test_dir_patterns[current_dir_pattern_test];
struct timespec current_time;
bool fail = false;
int ret;
clock_gettime(CLOCK_MONOTONIC, &current_time);
switch (priv->state) {
case ISOBUSFS_CLI_STATE_SELFTEST:
test_start_time = current_time;
pr_info("Start pattern test: %s", tp->dir_name);
ret = isobusfs_cli_ccd_req(priv, tp->dir_name,
strlen(tp->dir_name));
if (ret)
goto test_fail;
break;
case ISOBUSFS_CLI_STATE_WAIT_CCD_RESP:
if (current_time.tv_sec - test_start_time.tv_sec >= 5) {
ret = -ETIMEDOUT;
goto test_fail;
}
break;
case ISOBUSFS_CLI_STATE_CCD_FAIL:
fail = true;
/* fallthrough */
case ISOBUSFS_CLI_STATE_CCD_DONE:
if (tp->expect_pass && fail) {
pr_err("pattern test failed: %s", tp->dir_name);
ret = -EINVAL;
goto test_fail;
} else if (!tp->expect_pass && !fail) {
pr_err("pattern test failed: %s", tp->dir_name);
ret = -EINVAL;
goto test_fail;
}
current_dir_pattern_test++;
if (current_dir_pattern_test >= num_patterns) {
*complete = true;
break;
}
priv->state = ISOBUSFS_CLI_STATE_SELFTEST;
break;
default:
pr_err("%s:%i: unknown state: %d", __func__, __LINE__, priv->state);
ret = -EINVAL;
goto test_fail;
}
return 0;
test_fail:
*complete = true;
return ret;
}
struct isobusfs_cli_test_of_path {
const char *path_name;
uint8_t flags;
bool expect_pass;
};
static struct isobusfs_cli_test_of_path test_of_patterns[] = {
/* expected result \\vol1\dir1\dir2\ */
{ "\\\\vol1\\dir1\\dir2", 0, false },
{ "\\\\vol1\\dir1\\dir2\\file0", 0, true },
};
size_t current_of_pattern_test;
static int isobusfs_cli_test_of_req(struct isobusfs_priv *priv, bool *complete)
{
size_t num_patterns = ARRAY_SIZE(test_of_patterns);
struct isobusfs_cli_test_of_path *tp =
&test_of_patterns[current_of_pattern_test];
struct timespec current_time;
int ret;
clock_gettime(CLOCK_MONOTONIC, &current_time);
switch (priv->state) {
case ISOBUSFS_CLI_STATE_SELFTEST:
test_start_time = current_time;
pr_info("Start pattern test: %s", tp->path_name);
ret = isobusfs_cli_fa_of_req(priv, tp->path_name,
strlen(tp->path_name), tp->flags);
if (ret)
goto test_fail;
break;
case ISOBUSFS_CLI_STATE_WAIT_OF_RESP:
if (current_time.tv_sec - test_start_time.tv_sec >= 5) {
ret = -ETIMEDOUT;
goto test_fail;
}
break;
case ISOBUSFS_CLI_STATE_OF_FAIL:
if (tp->expect_pass) {
pr_err("pattern test failed: %s", tp->path_name);
ret = -EINVAL;
goto test_fail;
}
priv->state = ISOBUSFS_CLI_STATE_TEST_DONE;
break;
case ISOBUSFS_CLI_STATE_OF_DONE:
if (!tp->expect_pass) {
pr_err("pattern test failed: %s", tp->path_name);
ret = -EINVAL;
goto test_fail;
}
if (priv->handle != ISOBUSFS_FILE_HANDLE_ERROR)
isobusfs_cli_fa_cf_req(priv, priv->handle);
else
priv->state = ISOBUSFS_CLI_STATE_TEST_DONE;
break;
case ISOBUSFS_CLI_STATE_CF_FAIL:
pr_err("failed to close file: %s", tp->path_name);
ret = -EINVAL;
goto test_fail;
case ISOBUSFS_CLI_STATE_CF_DONE:
case ISOBUSFS_CLI_STATE_TEST_DONE:
current_of_pattern_test++;
if (current_of_pattern_test >= num_patterns)
*complete = true;
else
priv->state = ISOBUSFS_CLI_STATE_SELFTEST;
break;
default:
pr_err("%s:%i: unknown state: %d", __func__, __LINE__, priv->state);
ret = -EINVAL;
goto test_fail;
}
return 0;
test_fail:
*complete = true;
return ret;
}
struct isobusfs_cli_test_sf_path {
const char *path_name;
uint8_t flags;
uint32_t offset;
uint32_t read_size;
bool expect_pass;
};
static struct isobusfs_cli_test_sf_path test_sf_patterns[] = {
/* expected result \\vol1\dir1\dir2\ */
{ "\\\\vol1\\dir1\\dir2\\file1k", 0, 0, 0, true },
{ "\\\\vol1\\dir1\\dir2\\file1k", 0, 10, 0, true },
};
size_t current_sf_pattern_test;
static int isobusfs_cli_test_sf_req(struct isobusfs_priv *priv, bool *complete)
{
size_t num_patterns = ARRAY_SIZE(test_sf_patterns);
struct isobusfs_cli_test_sf_path *tp =
&test_sf_patterns[current_sf_pattern_test];
struct timespec current_time;
int ret;
clock_gettime(CLOCK_MONOTONIC, &current_time);
switch (priv->state) {
case ISOBUSFS_CLI_STATE_SELFTEST:
test_start_time = current_time;
pr_info("Start pattern test: %s", tp->path_name);
ret = isobusfs_cli_fa_of_req(priv, tp->path_name,
strlen(tp->path_name), tp->flags);
if (ret)
goto test_fail;
break;
case ISOBUSFS_CLI_STATE_WAIT_OF_RESP:
if (current_time.tv_sec - test_start_time.tv_sec >= 5) {
ret = -ETIMEDOUT;
goto test_fail;
}
break;
case ISOBUSFS_CLI_STATE_OF_FAIL:
if (tp->expect_pass) {
pr_err("pattern test failed: %s", tp->path_name);
ret = -EINVAL;
goto test_fail;
}
priv->state = ISOBUSFS_CLI_STATE_TEST_DONE;
break;
case ISOBUSFS_CLI_STATE_OF_DONE:
if (!tp->expect_pass) {
pr_err("pattern test failed: %s", tp->path_name);
ret = -EINVAL;
goto test_fail;
}
ret = isobusfs_cli_fa_sf_req(priv, priv->handle,
ISOBUSFS_FA_SEEK_SET, tp->offset);
if (ret)
goto test_fail;
break;
case ISOBUSFS_CLI_STATE_SF_FAIL:
if (tp->expect_pass) {
pr_err("pattern test failed: %s", tp->path_name);
ret = -EINVAL;
goto test_fail;
}
priv->state = ISOBUSFS_CLI_STATE_TEST_DONE;
break;
case ISOBUSFS_CLI_STATE_SF_DONE:
if (!tp->expect_pass) {
pr_err("pattern test failed: %s", tp->path_name);
ret = -EINVAL;
goto test_fail;
}
if (priv->read_offset != tp->offset) {
pr_err("Not expected read offset: %s", tp->path_name);
ret = -EINVAL;
goto test_fail;
}
ret = isobusfs_cli_fa_rf_req(priv, priv->handle,
tp->read_size);
if (ret)
goto test_fail;
break;
case ISOBUSFS_CLI_STATE_RF_FAIL:
if (tp->expect_pass) {
pr_err("pattern test failed: %s", tp->path_name);
ret = -EINVAL;
goto test_fail;
}
priv->state = ISOBUSFS_CLI_STATE_TEST_CLEANUP;
break;
case ISOBUSFS_CLI_STATE_RF_DONE:
if (!tp->expect_pass) {
pr_err("pattern test failed: %s", tp->path_name);
ret = -EINVAL;
goto test_fail;
}
/* fall troth */
case ISOBUSFS_CLI_STATE_TEST_CLEANUP:
if (priv->handle != ISOBUSFS_FILE_HANDLE_ERROR)
isobusfs_cli_fa_cf_req(priv, priv->handle);
else
priv->state = ISOBUSFS_CLI_STATE_TEST_DONE;
break;
case ISOBUSFS_CLI_STATE_CF_FAIL:
pr_err("failed to close file: %s", tp->path_name);
ret = -EINVAL;
goto test_fail;
case ISOBUSFS_CLI_STATE_CF_DONE:
case ISOBUSFS_CLI_STATE_TEST_DONE:
current_of_pattern_test++;
if (current_of_pattern_test >= num_patterns)
*complete = true;
else
priv->state = ISOBUSFS_CLI_STATE_SELFTEST;
break;
default:
pr_err("%s:%i: unknown state: %d", __func__, __LINE__, priv->state);
ret = -EINVAL;
goto test_fail;
}
return 0;
test_fail:
*complete = true;
return ret;
}
struct isobusfs_cli_test_rf_path {
const char *path_name;
uint8_t flags;
uint32_t offset;
uint32_t read_size;
bool expect_pass;
};
static struct isobusfs_cli_test_rf_path test_rf_patterns[] = {
/* expected result \\vol1\dir1\dir2\ */
{ "\\\\vol1\\dir1\\dir2\\file1k", 0, 0, 0, true },
{ "\\\\vol1\\dir1\\dir2\\file1k", 0, 0, 1, true },
{ "\\\\vol1\\dir1\\dir2\\file1k", 0, 1, 1, true },
{ "\\\\vol1\\dir1\\dir2\\file1k", 0, 2, 1, true },
{ "\\\\vol1\\dir1\\dir2\\file1k", 0, 3, 1, true },
{ "\\\\vol1\\dir1\\dir2\\file1m", 0, 0, 8, true },
{ "\\\\vol1\\dir1\\dir2\\file1m", 0, 0, 8 * 100, true },
{ "\\\\vol1\\dir1\\dir2\\file1m", 0, 100, 8 * 100, true },
{ "\\\\vol1\\dir1\\dir2\\file1m", 0, 0, ISOBUSFS_MAX_DATA_LENGH, true },
{ "\\\\vol1\\dir1\\dir2\\file1m", 0, 0, (ISOBUSFS_MAX_DATA_LENGH & ~3) + 16, true },
{ "\\\\vol1\\dir1\\dir2\\file1m", 0, 0, ISOBUSFS_MAX_DATA_LENGH + 1, true },
{ "\\\\vol1\\dir1\\dir2\\file1m", 0, 0, -1, true },
};
size_t current_rf_pattern_test;
static uint32_t isobusfs_cli_calculate_sum(uint8_t *data, size_t size,
uint32_t offset)
{
const uint8_t xor_pattern[] = {0xde, 0xad, 0xbe, 0xef};
uint32_t actual_sum = 0;
uint32_t current_value = 0;
uint8_t byte_offset = 0;
size_t idx;
byte_offset = offset % 4;
for (idx = 0; idx < size; idx++) {
uint8_t byte;
if (data) {
byte = data[idx] ^ xor_pattern[byte_offset];
current_value |= (byte << ((3 - byte_offset) * 8));
} else {
uint32_t value_at_offset;
/* if no data is provided, generate the data based on
* offset
*/
value_at_offset = (offset + idx) / 4;
byte_offset = (offset + idx) % 4;
byte = (value_at_offset >> ((3 - byte_offset) * 8)) & 0xff;
current_value |= (byte << ((3 - byte_offset) * 8));
}
if (byte_offset == 3) {
actual_sum += current_value;
current_value = 0;
}
byte_offset = (byte_offset + 1) % 4;
/* if this is the last byte in the buffer but it's not aligned,
* add the partial uint32_t to the sum
*/
if (idx == size - 1 && byte_offset != 0)
actual_sum += current_value;
}
return actual_sum;
}
static int isobusfs_cli_test_rf_req(struct isobusfs_priv *priv, bool *complete)
{
size_t num_patterns = ARRAY_SIZE(test_rf_patterns);
struct isobusfs_cli_test_rf_path *tp =
&test_rf_patterns[current_rf_pattern_test];
uint32_t actual_sum, expected_sum;
struct timespec current_time;
ssize_t remaining_size, read_size;
int ret;
clock_gettime(CLOCK_MONOTONIC, &current_time);
switch (priv->state) {
case ISOBUSFS_CLI_STATE_SELFTEST:
test_start_time = current_time;
pr_info("Start read test. Path: %s, size: %d, offset: %d, flags: %x",
tp->path_name, tp->read_size, tp->offset, tp->flags);
ret = isobusfs_cli_fa_of_req(priv, tp->path_name,
strlen(tp->path_name), tp->flags);
if (ret)
goto test_fail;
break;
case ISOBUSFS_CLI_STATE_WAIT_OF_RESP:
if (current_time.tv_sec - test_start_time.tv_sec >= 5) {
ret = -ETIMEDOUT;
goto test_fail;
}
break;
case ISOBUSFS_CLI_STATE_OF_FAIL:
if (tp->expect_pass) {
pr_err("pattern test failed: %s", tp->path_name);
ret = -EINVAL;
goto test_fail;
}
priv->state = ISOBUSFS_CLI_STATE_TEST_DONE;
break;
case ISOBUSFS_CLI_STATE_OF_DONE:
if (!tp->expect_pass) {
pr_err("pattern test failed: %s", tp->path_name);
ret = -EINVAL;
goto test_fail;
}
ret = isobusfs_cli_fa_sf_req(priv, priv->handle,
ISOBUSFS_FA_SEEK_SET, tp->offset);
if (ret)
goto test_fail;
break;
case ISOBUSFS_CLI_STATE_SF_FAIL:
if (tp->expect_pass) {
pr_err("pattern test failed: %s", tp->path_name);
ret = -EINVAL;
goto test_fail;
}
priv->state = ISOBUSFS_CLI_STATE_TEST_DONE;
break;
case ISOBUSFS_CLI_STATE_SF_DONE:
if (!tp->expect_pass) {
pr_err("pattern test failed: %s", tp->path_name);
ret = -EINVAL;
goto test_fail;
}
if (priv->read_offset != tp->offset) {
pr_err("Not expected read offset: %s", tp->path_name);
ret = -EINVAL;
goto test_fail;
}
if (tp->read_size > 0xffff)
read_size = 0xffff;
else
read_size = tp->read_size;
ret = isobusfs_cli_fa_rf_req(priv, priv->handle,
read_size);
if (ret)
goto test_fail;
test_start_time = current_time;
break;
case ISOBUSFS_CLI_STATE_WAIT_RF_RESP:
if (current_time.tv_sec - test_start_time.tv_sec >= 5) {
ret = -ETIMEDOUT;
goto test_fail;
}
break;
case ISOBUSFS_CLI_STATE_RF_FAIL:
if (tp->expect_pass) {
pr_err("pattern test failed: %s", tp->path_name);
ret = -EINVAL;
goto test_fail;
}
priv->state = ISOBUSFS_CLI_STATE_TEST_CLEANUP;
break;
case ISOBUSFS_CLI_STATE_RF_DONE:
if (!tp->expect_pass) {
pr_err("pattern test failed: %s", tp->path_name);
ret = -EINVAL;
goto test_fail;
}
pr_info("read file size: %zu", priv->read_data_len);
actual_sum = isobusfs_cli_calculate_sum(priv->read_data,
priv->read_data_len,
priv->read_offset);
expected_sum = isobusfs_cli_calculate_sum(NULL,
priv->read_data_len,
priv->read_offset);
if (actual_sum != expected_sum) {
pr_err("pattern test failed: incorrect sum in %s. Sum got: %d, expected: %d",
tp->path_name, actual_sum, expected_sum);
isobusfs_cmn_dump_last_x_bytes(priv->read_data,
priv->read_data_len, 16);
ret = -EINVAL;
goto test_fail;
} else {
pr_info("pattern test passed: %s. Sum got: %d, expected: %d",
tp->path_name, actual_sum, expected_sum);
isobusfs_cmn_dump_last_x_bytes(priv->read_data,
priv->read_data_len, 16);
}
free(priv->read_data);
priv->read_data = NULL;
remaining_size = (tp->offset + tp->read_size) -
(priv->read_offset + priv->read_data_len);
pr_debug("remaining_size: %zd, read_offset: %zu, read_data_len: %zu, test read size: %zu, test offset %zu",
remaining_size, priv->read_offset, priv->read_data_len,
tp->read_size, tp->offset);
if (remaining_size < 0) {
pr_err("pattern test failed: %s. Read size is too big",
tp->path_name);
ret = -EINVAL;
goto test_fail;
} else if (remaining_size > 0 && priv->read_data_len != 0) {
priv->read_offset += priv->read_data_len;
if (remaining_size > 0xffff)
read_size = 0xffff;
else
read_size = remaining_size;
ret = isobusfs_cli_fa_rf_req(priv, priv->handle,
read_size);
if (ret)
goto test_fail;
test_start_time = current_time;
break;
} else if (remaining_size > 0 && priv->read_data_len == 0 && tp->expect_pass) {
pr_err("read test failed: %s. Read size is zero, but expected more data: %zd",
tp->path_name, remaining_size);
ret = -EINVAL;
goto test_fail;
}
/* fall troth */
case ISOBUSFS_CLI_STATE_TEST_CLEANUP:
if (priv->handle != ISOBUSFS_FILE_HANDLE_ERROR) {
isobusfs_cli_fa_cf_req(priv, priv->handle);
test_start_time = current_time;
} else {
priv->state = ISOBUSFS_CLI_STATE_TEST_DONE;
}
break;
case ISOBUSFS_CLI_STATE_WAIT_CF_RESP:
if (current_time.tv_sec - test_start_time.tv_sec >= 5) {
ret = -ETIMEDOUT;
goto test_fail;
}
break;
case ISOBUSFS_CLI_STATE_CF_FAIL:
pr_err("failed to close file: %s", tp->path_name);
ret = -EINVAL;
goto test_fail;
case ISOBUSFS_CLI_STATE_CF_DONE:
case ISOBUSFS_CLI_STATE_TEST_DONE:
current_rf_pattern_test++;
if (current_rf_pattern_test >= num_patterns)
*complete = true;
else
priv->state = ISOBUSFS_CLI_STATE_SELFTEST;
break;
default:
pr_err("%s:%i: unknown state: %d", __func__, __LINE__,
priv->state);
ret = -EINVAL;
goto test_fail;
}
return 0;
test_fail:
*complete = true;
return ret;
}
struct isobusfs_cli_test_case test_cases[] = {
{ isobusfs_cli_test_connect, "Server connection" },
{ isobusfs_cli_test_property_req, "Server property request" },
{ isobusfs_cli_test_volume_status_req, "Volume status request" },
{ isobusfs_cli_test_current_dir_req, "Get current dir request" },
{ isobusfs_cli_test_ccd_req, "Change current dir request" },
{ isobusfs_cli_test_of_req, "Open File request" },
{ isobusfs_cli_test_sf_req, "Seek File request" },
{ isobusfs_cli_test_rf_req, "Read File request" },
};
void isobusfs_cli_run_self_tests(struct isobusfs_priv *priv)
{
if (priv->run_selftest) {
size_t num_tests = ARRAY_SIZE(test_cases);
if (current_test < num_tests) {
bool test_complete = false;
int ret;
if (!test_running) {
pr_int("Executing test %zu: %s\n", current_test + 1, test_cases[current_test].test_description);
test_running = true;
priv->state = ISOBUSFS_CLI_STATE_SELFTEST;
}
ret = test_cases[current_test].test_func(priv, &test_complete);
if (test_complete) {
test_running = false;
pr_int("Test %zu: %s.\n", current_test + 1, ret ? "FAILED" : "PASSED");
current_test++;
priv->state = ISOBUSFS_CLI_STATE_SELFTEST;
}
} else {
pr_int("All tests completed.\n");
priv->run_selftest = false;
current_test = 0;
priv->state = ISOBUSFS_CLI_STATE_IDLE;
}
}
}

View File

@ -0,0 +1,884 @@
// SPDX-License-Identifier: GPL-2.0-only
// SPDX-FileCopyrightText: 2023 Oleksij Rempel <linux@rempel-privat.de>
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <limits.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <time.h>
#include <linux/errqueue.h>
#include <linux/net_tstamp.h>
#include <linux/netlink.h>
#include "isobusfs_cmn.h"
#include "isobusfs_srv.h"
static log_level_t log_level = LOG_LEVEL_INFO;
static bool interactive_mode;
#define LOG_BUFFER_SIZE 1024
#define LOG_ENTRY_MAX_SIZE 256
struct isobusfs_log_buffer {
char buffer[LOG_BUFFER_SIZE][LOG_ENTRY_MAX_SIZE];
int write_index;
};
struct isobusfs_log_buffer log_buffer = { .write_index = 0 };
void add_log_to_buffer(const char *log_entry)
{
int idx = log_buffer.write_index;
char *buffer = log_buffer.buffer[idx];
strncpy(buffer, log_entry, LOG_ENTRY_MAX_SIZE);
buffer[LOG_ENTRY_MAX_SIZE - 1] = '\0'; /* Ensure null termination */
log_buffer.write_index = (idx + 1) % LOG_BUFFER_SIZE;
}
void isobusfs_print_log_buffer(void)
{
printf("\n---- Log Buffer Start ----\n");
for (int i = 0; i < LOG_BUFFER_SIZE; i++) {
int idx = (log_buffer.write_index + i) % LOG_BUFFER_SIZE;
if (log_buffer.buffer[idx][0] != '\0')
printf("%s\n", log_buffer.buffer[idx]);
}
printf("\n---- Log Buffer End ----\n");
}
void isobusfs_log(log_level_t level, const char *fmt, ...)
{
char complete_log_entry[LOG_ENTRY_MAX_SIZE];
char log_entry[LOG_ENTRY_MAX_SIZE - 64];
const char *level_str;
struct timeval tv;
struct tm *time_info;
char time_buffer[64];
int milliseconds;
va_list args;
if (level > log_level)
return;
switch (level) {
case LOG_LEVEL_DEBUG:
level_str = "DEBUG";
break;
case LOG_LEVEL_INT:
case LOG_LEVEL_INFO:
level_str = "INFO";
break;
case LOG_LEVEL_WARN:
level_str = "WARNING";
break;
case LOG_LEVEL_ERROR:
level_str = "ERROR";
break;
default:
level_str = "UNKNOWN";
break;
}
gettimeofday(&tv, NULL);
time_info = localtime(&tv.tv_sec);
milliseconds = tv.tv_usec / 1000;
strftime(time_buffer, sizeof(time_buffer), "%Y-%m-%d %H:%M:%S",
time_info);
snprintf(time_buffer + strlen(time_buffer),
sizeof(time_buffer) - strlen(time_buffer),
".%03d", milliseconds);
va_start(args, fmt);
vsnprintf(log_entry, sizeof(log_entry), fmt, args);
va_end(args);
snprintf(complete_log_entry, sizeof(complete_log_entry),
"[%s] [%s]: %s", time_buffer, level_str, log_entry);
if (interactive_mode) {
add_log_to_buffer(complete_log_entry);
if (level == LOG_LEVEL_INT) {
fprintf(stdout, "%s", log_entry);
fflush(stdout);
}
} else {
fprintf(stdout, "%s\n", complete_log_entry);
}
}
void isobusfs_set_interactive(bool interactive)
{
interactive_mode = interactive;
}
/* set log level */
void isobusfs_log_level_set(log_level_t level)
{
log_level = level;
}
int isobusfs_get_timeout_ms(struct timespec *ts)
{
struct timespec curr_time;
int64_t time_diff;
int timeout_ms;
clock_gettime(CLOCK_MONOTONIC, &curr_time);
time_diff = timespec_diff_ms(ts, &curr_time);
if (time_diff < 0) {
/* Too late to send next message. Send it now */
timeout_ms = 0;
} else {
if (time_diff > INT_MAX) {
warn("timeout too long: %ld ms", time_diff);
time_diff = INT_MAX;
}
timeout_ms = time_diff;
}
return timeout_ms;
}
const char *isobusfs_error_to_str(enum isobusfs_error err)
{
switch (err) {
case ISOBUSFS_ERR_ACCESS_DENIED:
return "Access Denied";
case ISOBUSFS_ERR_INVALID_ACCESS:
return "Invalid Access";
case ISOBUSFS_ERR_TOO_MANY_FILES_OPEN:
return "Too many files open";
case ISOBUSFS_ERR_FILE_ORPATH_NOT_FOUND:
return "File or path not found";
case ISOBUSFS_ERR_INVALID_HANDLE:
return "Invalid handle";
case ISOBUSFS_ERR_INVALID_SRC_NAME:
return "Invalid given source name";
case ISOBUSFS_ERR_INVALID_DST_NAME:
return "Invalid given destination name";
case ISOBUSFS_ERR_NO_SPACE:
return "Volume out of free space";
case ISOBUSFS_ERR_ON_WRITE:
return "Failure during a write operation";
case ISOBUSFS_ERR_MEDIA_IS_NOT_PRESENT:
return "Media is not present";
case ISOBUSFS_ERR_VOLUME_NOT_INITIALIZED:
return "Volume is possibly not initialized";
case ISOBUSFS_ERR_ON_READ:
return "Failure during a read operation";
case ISOBUSFS_ERR_FUNC_NOT_SUPPORTED:
return "Function not supported";
case ISOBUSFS_ERR_INVALID_REQUESTED_LENGHT:
return "Invalid request length";
case ISOBUSFS_ERR_OUT_OF_MEM:
return "Out of memory";
case ISOBUSFS_ERR_OTHER:
return "Any other error";
case ISOBUSFS_ERR_END_OF_FILE:
return "End of file reached, will only be reported when file pointer is at end of file";
default:
return "<unknown>";
}
}
enum isobusfs_error linux_error_to_isobusfs_error(int linux_err)
{
switch (linux_err) {
case 0:
return ISOBUSFS_ERR_SUCCESS;
case -EINVAL:
return ISOBUSFS_ERR_INVALID_DST_NAME;
case -EACCES:
return ISOBUSFS_ERR_ACCESS_DENIED;
case -ENOTDIR:
return ISOBUSFS_ERR_INVALID_ACCESS;
case -EMFILE:
return ISOBUSFS_ERR_TOO_MANY_FILES_OPEN;
case -ENOENT:
return ISOBUSFS_ERR_FILE_ORPATH_NOT_FOUND;
case -EBADF:
return ISOBUSFS_ERR_INVALID_HANDLE;
case -ENAMETOOLONG:
return ISOBUSFS_ERR_INVALID_SRC_NAME;
case -ENOSPC:
return ISOBUSFS_ERR_NO_SPACE;
case -EIO:
return ISOBUSFS_ERR_ON_WRITE;
case -ENODEV:
return ISOBUSFS_ERR_MEDIA_IS_NOT_PRESENT;
case -EROFS:
return ISOBUSFS_ERR_VOLUME_NOT_INITIALIZED;
case -EFAULT:
return ISOBUSFS_ERR_ON_READ;
case -ENOSYS:
return ISOBUSFS_ERR_FUNC_NOT_SUPPORTED;
case -EMSGSIZE:
return ISOBUSFS_ERR_INVALID_REQUESTED_LENGHT;
case -ENOMEM:
return ISOBUSFS_ERR_OUT_OF_MEM;
case -EPERM:
return ISOBUSFS_ERR_OTHER;
case -ESPIPE:
return ISOBUSFS_ERR_END_OF_FILE;
case -EPROTO:
return ISOBUSFS_ERR_TAN_ERR;
case -EILSEQ:
return ISOBUSFS_ERR_MALFORMED_REQUEST;
default:
return ISOBUSFS_ERR_OTHER;
}
}
void isobusfs_init_sockaddr_can(struct sockaddr_can *sac, uint32_t pgn)
{
sac->can_family = AF_CAN;
sac->can_addr.j1939.addr = J1939_NO_ADDR;
sac->can_addr.j1939.name = J1939_NO_NAME;
sac->can_addr.j1939.pgn = pgn;
}
static void isobusfs_print_timestamp(struct isobusfs_err_msg *emsg,
const char *name, struct timespec *cur)
{
struct isobusfs_stats *stats = emsg->stats;
/* TODO: make it configurable */
return;
if (!(cur->tv_sec | cur->tv_nsec))
return;
fprintf(stderr, " %s: %lu s %lu us (seq=%u/%u, send=%u)",
name, cur->tv_sec, cur->tv_nsec / 1000,
stats->tskey_sch, stats->tskey_ack, stats->send);
fprintf(stderr, "\n");
}
static const char *isobusfs_tstype_to_str(int tstype)
{
switch (tstype) {
case SCM_TSTAMP_SCHED:
return " ENQ";
case SCM_TSTAMP_SND:
return " SND";
case SCM_TSTAMP_ACK:
return " ACK";
default:
return " unk";
}
}
/* Check the stats of SCM_TIMESTAMPING_OPT_STATS */
static void isobusfs_scm_opt_stats(struct isobusfs_err_msg *emsg, void *buf, int len)
{
struct isobusfs_stats *stats = emsg->stats;
int offset = 0;
while (offset < len) {
struct nlattr *nla = (struct nlattr *) ((char *)buf + offset);
switch (nla->nla_type) {
case J1939_NLA_BYTES_ACKED:
stats->send = *(uint32_t *)((char *)nla + NLA_HDRLEN);
break;
default:
warnx("not supported J1939_NLA field\n");
}
offset += NLA_ALIGN(nla->nla_len);
}
}
static int isobusfs_extract_serr(struct isobusfs_err_msg *emsg)
{
struct isobusfs_stats *stats = emsg->stats;
struct sock_extended_err *serr = emsg->serr;
struct scm_timestamping *tss = emsg->tss;
switch (serr->ee_origin) {
case SO_EE_ORIGIN_TIMESTAMPING:
/*
* We expect here following patterns:
* serr->ee_info == SCM_TSTAMP_ACK
* Activated with SOF_TIMESTAMPING_TX_ACK
* or
* serr->ee_info == SCM_TSTAMP_SCHED
* Activated with SOF_TIMESTAMPING_SCHED
* and
* serr->ee_data == tskey
* session message counter which is activate
* with SOF_TIMESTAMPING_OPT_ID
* the serr->ee_errno should be ENOMSG
*/
if (serr->ee_errno != ENOMSG)
warnx("serr: expected ENOMSG, got: %i",
serr->ee_errno);
if (serr->ee_info == SCM_TSTAMP_SCHED)
stats->tskey_sch = serr->ee_data;
else
stats->tskey_ack = serr->ee_data;
isobusfs_print_timestamp(emsg, isobusfs_tstype_to_str(serr->ee_info),
&tss->ts[0]);
if (serr->ee_info == SCM_TSTAMP_SCHED)
return -EINTR;
else
return 0;
case SO_EE_ORIGIN_LOCAL:
/*
* The serr->ee_origin == SO_EE_ORIGIN_LOCAL is
* currently used to notify about locally
* detected protocol/stack errors.
* Following patterns are expected:
* serr->ee_info == J1939_EE_INFO_TX_ABORT
* is used to notify about session TX
* abort.
* serr->ee_data == tskey
* session message counter which is activate
* with SOF_TIMESTAMPING_OPT_ID
* serr->ee_errno == actual error reason
* error reason is converted from J1939
* abort to linux error name space.
*/
if (serr->ee_info != J1939_EE_INFO_TX_ABORT)
warnx("serr: unknown ee_info: %i",
serr->ee_info);
isobusfs_print_timestamp(emsg, " ABT", &tss->ts[0]);
warnx("serr: tx error: %i, %s", serr->ee_errno, strerror(serr->ee_errno));
return serr->ee_errno;
default:
warnx("serr: wrong origin: %u", serr->ee_origin);
}
return 0;
}
static int isobusfs_parse_cm(struct isobusfs_err_msg *emsg,
struct cmsghdr *cm)
{
const size_t hdr_len = CMSG_ALIGN(sizeof(struct cmsghdr));
if (cm->cmsg_level == SOL_SOCKET && cm->cmsg_type == SCM_TIMESTAMPING) {
emsg->tss = (void *)CMSG_DATA(cm);
} else if (cm->cmsg_level == SOL_SOCKET && cm->cmsg_type == SCM_TIMESTAMPING_OPT_STATS) {
void *jstats = (void *)CMSG_DATA(cm);
/* Activated with SOF_TIMESTAMPING_OPT_STATS */
isobusfs_scm_opt_stats(emsg, jstats, cm->cmsg_len - hdr_len);
} else if (cm->cmsg_level == SOL_CAN_J1939 &&
cm->cmsg_type == SCM_J1939_ERRQUEUE) {
emsg->serr = (void *)CMSG_DATA(cm);
} else
warnx("serr: not supported type: %d.%d",
cm->cmsg_level, cm->cmsg_type);
return 0;
}
int isobusfs_recv_err(int sock, struct isobusfs_err_msg *emsg)
{
char control[200];
struct cmsghdr *cm;
int ret;
struct msghdr msg = {
.msg_control = control,
.msg_controllen = sizeof(control),
};
ret = recvmsg(sock, &msg, MSG_ERRQUEUE | MSG_DONTWAIT);
if (ret == -1) {
ret = -errno;
pr_err("recvmsg error notification: %i (%s)", ret, strerror(ret));
return ret;
}
if (msg.msg_flags & MSG_CTRUNC) {
pr_err("recvmsg error notification: truncated");
return -EINVAL;
}
emsg->serr = NULL;
emsg->tss = NULL;
for (cm = CMSG_FIRSTHDR(&msg); cm && cm->cmsg_len;
cm = CMSG_NXTHDR(&msg, cm)) {
isobusfs_parse_cm(emsg, cm);
if (emsg->serr && emsg->tss)
return isobusfs_extract_serr(emsg);
}
return 0;
}
/* send NACK message. function should use src, dst and socket as parameters */
void isobusfs_send_nack(int sock, struct isobusfs_msg *msg)
{
struct sockaddr_can addr = msg->peername;
struct isobusfs_nack nack;
int ret;
nack.ctrl = ISOBUS_ACK_CTRL_NACK;
nack.group_function = msg->buf[0];
memset(&nack.reserved[0], 0xff, sizeof(nack.reserved));
nack.address_nack = addr.can_addr.j1939.addr;
memcpy(&nack.pgn_nack[0], &addr.can_addr.j1939.pgn,
sizeof(nack.pgn_nack));
addr.can_addr.j1939.pgn = ISOBUS_PGN_ACK;
ret = sendto(sock, &nack, sizeof(nack), MSG_DONTWAIT,
(struct sockaddr *)&addr,
sizeof(addr));
if (ret < 0) {
ret = -errno;
pr_warn("failed to send NACK: %i (%s)", ret, strerror(ret));
}
pr_debug("send NACK");
}
/* store data to a recursive buffer */
void isobufs_store_tx_data(struct isobusfs_buf_log *buffer, uint8_t *data)
{
struct isobusfs_buf *entry = &buffer->entries[buffer->index];
/* we assume :) that data is at least 8 bytes long */
memcpy(entry->data, data, sizeof(entry->data));
clock_gettime(CLOCK_REALTIME, &entry->ts);
buffer->index = (buffer->index + 1) % ISOBUSFS_MAX_BUF_ENTRIES;
}
void isobusfs_dump_tx_data(const struct isobusfs_buf_log *buffer)
{
uint i;
for (i = 0; i < ISOBUSFS_MAX_BUF_ENTRIES; ++i) {
const struct isobusfs_buf *entry = &buffer->entries[i];
char data_str[ISOBUSFS_MIN_TRANSFER_LENGH * 3] = {0};
uint j;
for (j = 0; j < ISOBUSFS_MIN_TRANSFER_LENGH; ++j)
snprintf(data_str + j * 3, 4, "%02X ", entry->data[j]);
pr_debug("Entry %u: %s Timestamp: %ld.%09ld\n", i, data_str,
entry->ts.tv_sec, entry->ts.tv_nsec);
}
}
/* wrapper for sendto() */
int isobusfs_sendto(int sock, const void *data, size_t len,
const struct sockaddr_can *addr,
struct isobusfs_buf_log *isobusfs_tx_buffer)
{
int ret;
/* store to tx buffer */
isobufs_store_tx_data(isobusfs_tx_buffer, (uint8_t *)data);
ret = sendto(sock, data, len, MSG_DONTWAIT,
(struct sockaddr *)addr, sizeof(*addr));
if (ret == -1) {
ret = -errno;
pr_warn("failed to send data: %i (%s)", ret, strerror(ret));
return ret;
}
return 0;
}
/* wrapper for send() */
int isobusfs_send(int sock, const void *data, size_t len,
struct isobusfs_buf_log *isobusfs_tx_buffer)
{
int ret;
/* store to tx buffer */
isobufs_store_tx_data(isobusfs_tx_buffer, (uint8_t *)data);
ret = send(sock, data, len, MSG_DONTWAIT);
if (ret == -1) {
ret = -errno;
pr_warn("failed to send data: %i (%s)", ret, strerror(ret));
return ret;
}
return 0;
}
/**
* isobusfs_cmn_open_socket - Open a CAN J1939 socket
*
* This function opens a CAN J1939 socket and returns the file descriptor
* on success. In case of an error, the function returns the negative
* error code.
*/
int isobusfs_cmn_open_socket(void)
{
int ret;
/* Create a new CAN J1939 socket */
ret = socket(PF_CAN, SOCK_DGRAM, CAN_J1939);
if (ret < 0) {
/* Get the error code and print an error message */
ret = -errno;
pr_err("socket(j1939): %d (%s)", ret, strerror(ret));
return ret;
}
return ret;
}
/**
* isobusfs_cmn_configure_socket_filter - Configure a J1939 socket filter
* @sock: Socket file descriptor
* @pgn: Parameter Group Number to filter
*
* This function configures a J1939 socket filter for the provided PGN.
* It allows ISOBUS FS role-specific PGN and ACK messages for troubleshooting.
* Returns 0 on success or a negative error code on failure.
*/
int isobusfs_cmn_configure_socket_filter(int sock, pgn_t pgn)
{
struct j1939_filter sock_filter[2] = {0};
int ret;
if (pgn != ISOBUSFS_PGN_CL_TO_FS && pgn != ISOBUSFS_PGN_FS_TO_CL) {
pr_err("invalid pgn: %d", pgn);
return -EINVAL;
}
/* Allow ISOBUS FS role specific PGN */
sock_filter[0].pgn = pgn;
sock_filter[0].pgn_mask = J1939_PGN_PDU1_MAX;
/*
* ISO 11783-3:2018 - 5.4.5 Acknowledgment.
* Allow ACK messages for troubleshooting
*/
sock_filter[1].pgn = ISOBUS_PGN_ACK;
sock_filter[1].pgn_mask = J1939_PGN_PDU1_MAX;
ret = setsockopt(sock, SOL_CAN_J1939, SO_J1939_FILTER, &sock_filter,
sizeof(sock_filter));
if (ret < 0) {
ret = -errno;
pr_err("failed to set j1939 filter: %d (%s)", ret, strerror(ret));
return ret;
}
return 0;
}
/**
* isobusfs_cmn_configure_timestamping - Configure timestamping options for a
* socket
* @sock: Socket file descriptor
*
* This function configures various timestamping options for the given socket,
* such as software timestamping, CMSG timestamping, transmission
* acknowledgment, transmission scheduling, statistics, and timestamp-only
* options. These options are needed to get a response from different kernel
* j1939 stack layers about egress status, allowing the caller to know if the
* ETP session has finished or if status messages have actually been sent.
* These options make sense only in combination with SO_J1939_ERRQUEUE. Returns
* 0 on success or a negative error code on failure.
*/
static int isobusfs_cmn_configure_timestamping(int sock)
{
unsigned int sock_opt;
int ret;
sock_opt = SOF_TIMESTAMPING_SOFTWARE | SOF_TIMESTAMPING_OPT_CMSG |
SOF_TIMESTAMPING_TX_ACK | SOF_TIMESTAMPING_TX_SCHED |
SOF_TIMESTAMPING_OPT_STATS | SOF_TIMESTAMPING_OPT_TSONLY |
SOF_TIMESTAMPING_OPT_ID;
ret = setsockopt(sock, SOL_SOCKET, SO_TIMESTAMPING,
(char *)&sock_opt, sizeof(sock_opt));
if (ret < 0) {
ret = -errno;
pr_err("setsockopt timestamping: %d (%s)", ret, strerror(ret));
return ret;
}
return 0;
}
/**
* isobusfs_cmn_configure_error_queue - Configure error queue for a J1939 socket
* @sock: socket file descriptor
*
* This function configures the error queue for a given J1939 socket, enabling
* timestamping options and the error queue itself. SO_J1939_ERRQUEUE enables
* the actual feedback channel from the kernel J1939 stack. Timestamping options
* are configured to subscribe the socket to different notifications over this
* channel, providing information on egress status. This helps in determining if
* an ETP session has finished or if status messages were actually sent.
*
* Return: 0 on success, or a negative error code on failure.
*/
int isobusfs_cmn_configure_error_queue(int sock)
{
int err_queue = true;
int ret;
ret = setsockopt(sock, SOL_CAN_J1939, SO_J1939_ERRQUEUE,
&err_queue, sizeof(err_queue));
if (ret < 0) {
ret = -errno;
pr_err("set recverr: %d (%s)", ret, strerror(ret));
return ret;
}
ret = isobusfs_cmn_configure_timestamping(sock);
if (ret < 0)
return ret;
return 0;
}
/**
* isobusfs_cmn_bind_socket - Bind a J1939 socket to a given address
* @sock: socket file descriptor
* @addr: pointer to a sockaddr_can structure containing the address
* information to bind the socket to
*
* This function binds a J1939 socket to the specified address. It returns
* 0 on successful binding or a negative error code on failure.
*
* Return: 0 on success, or a negative error code on failure.
*/
int isobusfs_cmn_bind_socket(int sock, struct sockaddr_can *addr)
{
int ret;
ret = bind(sock, (void *)addr, sizeof(*addr));
if (ret < 0) {
ret = -errno;
pr_err("failed to bind: %d (%s)", ret, strerror(ret));
return ret;
}
return 0;
}
int isobusfs_cmn_socket_prio(int sock, int prio)
{
int ret;
ret = setsockopt(sock, SOL_CAN_J1939, SO_J1939_SEND_PRIO,
&prio, sizeof(prio));
if (ret < 0) {
ret = -errno;
pr_warn("Failed to set priority %i. Error %i (%s)", prio, ret,
strerror(ret));
return ret;
}
return 0;
}
int isobusfs_cmn_connect_socket(int sock, struct sockaddr_can *addr)
{
int ret;
ret = connect(sock, (void *)addr, sizeof(*addr));
if (ret < 0) {
ret = -errno;
pr_err("failed to connect socket: %d (%s)", ret, strerror(ret));
return ret;
}
return 0;
}
/**
* isobusfs_cmn_set_broadcast - Enable broadcast option for a socket
* @sock: socket file descriptor
*
* This function enables the SO_BROADCAST option for the given socket,
* allowing it to send and receive broadcast messages. It returns 0 on success
* or a negative error code on failure.
*
* Return: 0 on success, or a negative error code on failure.
*/
int isobusfs_cmn_set_broadcast(int sock)
{
int broadcast = true;
int ret;
ret = setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &broadcast,
sizeof(broadcast));
if (ret < 0) {
ret = -errno;
pr_err("setsockopt(SO_BROADCAST): %d (%s)", ret, strerror(ret));
return ret;
}
return 0;
}
/* FIXME: linger is currently not supported by the kernel J1939 stack
* but it would be nice to have it. Especially if we wont to stop sending
* messages on a socket when the connection is lost.
*/
int isobusfs_cmn_set_linger(int sock)
{
struct linger linger_opt;
int ret;
linger_opt.l_onoff = 1;
linger_opt.l_linger = 0;
ret = setsockopt(sock, SOL_SOCKET, SO_LINGER, &linger_opt,
sizeof(linger_opt));
if (ret < 0) {
ret = -errno;
pr_err("setsockopt(SO_LINGER): %d (%s)", ret, strerror(ret));
return ret;
}
return 0;
}
int isobusfs_cmn_add_socket_to_epoll(int epoll_fd, int sock, uint32_t events)
{
struct epoll_event ev = {0};
int ret;
ev.events = events;
ev.data.fd = sock;
ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sock, &ev);
if (ret < 0) {
ret = errno;
pr_err("epoll_ctl(EPOLL_CTL_ADD): %d (%s)", ret, strerror(ret));
return ret;
}
return 0;
}
int isobusfs_cmn_create_epoll(void)
{
int ret, epoll_fd;
epoll_fd = epoll_create1(0);
if (epoll_fd < 0) {
ret = -errno;
pr_err("epoll_create1: %d (%s)", ret, strerror(ret));
return ret;
}
return epoll_fd;
}
int isobusfs_cmn_prepare_for_events(struct isobusfs_cmn *cmn, int *nfds,
bool dont_wait)
{
int ret, timeout_ms;
if (dont_wait)
timeout_ms = 0;
else
timeout_ms = isobusfs_get_timeout_ms(&cmn->next_send_time);
ret = epoll_wait(cmn->epoll_fd, cmn->epoll_events,
cmn->epoll_events_size, timeout_ms);
if (ret < 0) {
ret = -errno;
if (ret != -EINTR) {
*nfds = 0;
return ret;
}
}
*nfds = ret;
ret = clock_gettime(CLOCK_MONOTONIC, &cmn->last_time);
if (ret < 0) {
ret = -errno;
pr_err("failed to get time: %i (%s)", ret, strerror(ret));
return ret;
}
return 0;
}
void isobusfs_cmn_dump_last_x_bytes(const uint8_t *buffer, size_t buffer_size,
size_t x)
{
size_t start_offset = 0;
char *output_ptr;
unsigned char c;
size_t remaining;
char output[80];
int n, j;
if (x > 0 && x < buffer_size)
start_offset = (buffer_size - x) & ~0x7;
for (size_t i = start_offset; i < buffer_size; i += 8) {
output_ptr = output;
remaining = sizeof(output);
n = snprintf(output_ptr, remaining, "%08lx: ",
(unsigned long)(start_offset + i));
if (n < 0 || n >= remaining)
break;
output_ptr += n;
remaining -= n;
for (j = 0; j < 8 && i + j < buffer_size; ++j) {
n = snprintf(output_ptr, remaining, "%02x ", buffer[i+j]);
if (n < 0 || n >= remaining)
break;
output_ptr += n;
remaining -= n;
}
for (j = buffer_size - i; j < 8; ++j) {
n = snprintf(output_ptr, remaining, " ");
if (n < 0 || n >= remaining)
break;
output_ptr += n;
remaining -= n;
}
n = snprintf(output_ptr, remaining, " ");
if (n < 0 || n >= remaining)
break;
output_ptr += n;
remaining -= n;
for (j = 0; j < 8 && i + j < buffer_size; ++j) {
c = buffer[i+j];
n = snprintf(output_ptr, remaining, "%c",
isprint(c) ? c : '.');
if (n < 0 || n >= remaining)
break;
output_ptr += n;
remaining -= n;
}
pr_debug("%s", output);
if (n < 0 || n >= remaining)
break;
}
}

View File

@ -0,0 +1,362 @@
// SPDX-License-Identifier: GPL-2.0-only
// SPDX-FileCopyrightText: 2023 Oleksij Rempel <linux@rempel-privat.de>
#ifndef _ISOBUSFS_H_
#define _ISOBUSFS_H_
#include <stdint.h>
#include <endian.h>
#include <stdbool.h>
#include <linux/can.h>
#include <linux/kernel.h>
#include "../libj1939.h"
#include "../lib.h"
/* ISO 11783-13:2021 - C.1.1.a File Server to Client PGN */
#define ISOBUSFS_PGN_FS_TO_CL 0x0ab00 /* 43766 */
/* ISO 11783-13:2021 - C.1.1.b Client to File Server PGN */
#define ISOBUSFS_PGN_CL_TO_FS 0x0aa00 /* 43520 */
#define ISOBUSFS_PRIO_DEFAULT 7
#define ISOBUSFS_PRIO_FSS 5
#define ISOBUSFS_PRIO_ACK 6
#define ISOBUSFS_MAX_OPENED_FILES 255
#define ISOBUSFS_MAX_SHORT_FILENAME_LENGH 12 /* 12 chars */
#define ISOBUSFS_MAX_LONG_FILENAME_LENGH 31 /* 31 chars */
/* ISO 11783-13:2021 - C.3.5.1 Maximal transfer size for TP (Transport Protocol) */
#define ISOBUSFS_TP_MAX_TRANSFER_SIZE 1780
/* ISO 11783-13:2021 - C.3.5.1 Maximal transfer size for ETP (Extended Transport Protocol) */
#define ISOBUSFS_ETP_MAX_TRANSFER_SIZE 65530
#define ISOBUSFS_MAX_DATA_LENGH 65530 /* Bytes */
#define ISOBUSFS_MAX_TRANSFER_LENGH (6 + ISOBUSFS_MAX_DATA_LENGH)
#define ISOBUSFS_MIN_TRANSFER_LENGH 8
#define ISOBUSFS_CLIENT_TIMEOUT 6000 /* ms */
#define ISOBUSFS_FS_TIMEOUT 6000 /* ms */
#define ISOBUSFS_MAX_BUF_ENTRIES 10
#define ISOBUSFS_MAX_PATH_NAME_LENGTH ISOBUSFS_MAX_DATA_LENGH
/* not documented, take some max value */
#define ISOBUSFS_SRV_MAX_VOLUMES 10
/* ISO 11783-13:2021 A.2.2.3 Volumes */
#define ISOBUSFS_SRV_MAX_VOLUME_NAME_LEN 254
#define ISOBUSFS_MAX_VOLUME_NAME_LENGTH 254
#define ISOBUSFS_MAX_DIR_ENTRY_NAME_LENGTH 255
/* not documented, take some max value */
#define ISOBUSFS_SRV_MAX_PATH_LEN 4096
#define ISOBUSFS_FILE_HANDLE_ERROR 255
/* ISO 11783-3:2018 - 5.4.5 Acknowledgment */
#define ISOBUS_PGN_ACK 0x0e800 /* 59392 */
enum isobusfs_ack_ctrl {
ISOBUS_ACK_CTRL_ACK = 0,
ISOBUS_ACK_CTRL_NACK = 1,
};
struct isobusfs_nack {
uint8_t ctrl;
uint8_t group_function;
uint8_t reserved[2];
uint8_t address_nack;
uint8_t pgn_nack[3];
};
/* ISO 11783-13:2021 - Annex B.1 Command Groups (CG) */
enum isobusfs_cg {
ISOBUSFS_CG_CONNECTION_MANAGMENT = 0,
ISOBUSFS_CG_DIRECTORY_HANDLING = 1,
ISOBUSFS_CG_FILE_ACCESS = 2,
ISOBUSFS_CG_FILE_HANDLING = 3,
ISOBUSFS_CG_VOLUME_HANDLING = 4,
};
#define ISOBUSFS_CM_F_CCM_RATE 2000 /* ms */
/* Connection Management functions: */
/* ISO 11783-13:2021 - C.1.* Connection Management - Client to File Server
* functions:
*/
enum isobusfs_cm_cl_to_fs_function {
/* ISO 11783-13:2021 - C.1.3 Client Connection Maintenance */
ISOBUSFS_CM_F_CC_MAINTENANCE = 0,
/* ISO 11783-13:2021 - C.1.4 Get File Server Properties */
ISOBUSFS_CM_GET_FS_PROPERTIES = 1,
/* ISO 11783-13:2021 - C.1.6 Volume Status Request */
ISOBUSFS_CM_VOLUME_STATUS_REQ = 2,
};
/* ISO 11783-13:2021 - C.1.* Connection Management - File Server to client
* functions:
*/
enum isobusfs_cm_fs_to_cl_function {
/* ISO 11783-13:2021 - C.1.2 File Server Status */
ISOBUSFS_CM_F_FS_STATUS = 0,
/* ISO 11783-13:2021 - C.1.5 Get File Server Properties Response */
ISOBUSFS_CM_GET_FS_PROPERTIES_RES = 1,
/* ISO 11783-13:2021 - C.1.7 Volume Status Response */
ISOBUSFS_CM_VOLUME_STATUS_RES = 2,
};
/* Directory Handling functions: */
/* send by server: */
enum isobusfs_dh_fs_to_cl_function {
ISOBUSFS_DH_F_GET_CURRENT_DIR_RES = 0,
ISOBUSFS_DH_F_CHANGE_CURRENT_DIR_RES = 1,
};
/* send by client: */
enum isobusfs_dh_cl_to_fs_function {
ISOBUSFS_DH_F_GET_CURRENT_DIR_REQ = 0,
ISOBUSFS_DH_F_CHANGE_CURRENT_DIR_REQ = 1,
};
/* File Access functions: */
/* send by server: */
enum isobusfs_fa_fs_to_cl_function {
ISOBUSFS_FA_F_OPEN_FILE_RES = 0,
ISOBUSFS_FA_F_SEEK_FILE_RES = 1,
ISOBUSFS_FA_F_READ_FILE_RES = 2,
ISOBUSFS_FA_F_WRITE_FILE_RES = 3,
ISOBUSFS_FA_F_CLOSE_FILE_RES = 4,
};
/* send by client: */
enum isobusfs_fa_cl_to_fs_function {
ISOBUSFS_FA_F_OPEN_FILE_REQ = 0,
ISOBUSFS_FA_F_SEEK_FILE_REQ = 1,
ISOBUSFS_FA_F_READ_FILE_REQ = 2,
ISOBUSFS_FA_F_WRITE_FILE_REQ = 3,
ISOBUSFS_FA_F_CLOSE_FILE_REQ = 4,
};
/* File Handling functions: */
/* send by server: */
enum isobusfs_fh_fs_to_cl_function {
ISOBUSFS_FH_F_MOVE_FILE_RES = 0,
ISOBUSFS_FH_F_DELETE_FILE_RES = 1,
ISOBUSFS_FH_F_GET_FILE_ATTR_RES = 2,
ISOBUSFS_FH_F_SET_FILE_ATTR_RES = 3,
ISOBUSFS_FH_F_GET_FILE_DATETIME_RES = 4,
};
/* send by client: */
enum isobusfs_fh_cl_to_fs_function {
ISOBUSFS_FH_F_MOVE_FILE_REQ = 0,
ISOBUSFS_FH_F_DELETE_FILE_REQ = 1,
ISOBUSFS_FH_F_GET_FILE_ATTR_REQ = 2,
ISOBUSFS_FH_F_SET_FILE_ATTR_REQ = 3,
ISOBUSFS_FH_F_GET_FILE_DATETIME_REQ = 4,
};
/* Volume Access functions: */
/* Preparing or repairing the volume for files and directory structures.
* These commands should be limited to initial setup, intended to be used by
* service tool clients only.
*/
/* send by server: */
/* Initialize Volume: Prepare the volume to accept files and directories. All
* data will be lost upon completion of this command.
*/
enum isobusfs_va_fs_to_cl_function {
ISOBUSFS_VA_F_INITIALIZE_VOLUME_RES = 0,
};
/* send by client: */
enum isobusfs_va_cl_to_fs_function {
/* Initialize Volume: Prepare the volume to accept files and directories.
* All data will be lost upon completion of this command.
*/
ISOBUSFS_VA_F_INITIALIZE_VOLUME_REQ = 0,
};
/* ISO 11783-13:2021 - Annex B.9 Error Code */
enum isobusfs_error {
/* Success */
ISOBUSFS_ERR_SUCCESS = 0,
/* Access Denied */
ISOBUSFS_ERR_ACCESS_DENIED = 1,
/* Invalid Access */
ISOBUSFS_ERR_INVALID_ACCESS = 2,
/* Too many files open */
ISOBUSFS_ERR_TOO_MANY_FILES_OPEN = 3,
/* File or path not found */
ISOBUSFS_ERR_FILE_ORPATH_NOT_FOUND = 4,
/* Invalid handle */
ISOBUSFS_ERR_INVALID_HANDLE = 5,
/* Invalid given source name */
ISOBUSFS_ERR_INVALID_SRC_NAME = 6,
/* Invalid given destination name */
ISOBUSFS_ERR_INVALID_DST_NAME = 7,
/* Volume out of free space */
ISOBUSFS_ERR_NO_SPACE = 8,
/* Failure during a write operation */
ISOBUSFS_ERR_ON_WRITE = 9,
/* Media is not present */
ISOBUSFS_ERR_MEDIA_IS_NOT_PRESENT = 10,
/* Failure during a read operation */
ISOBUSFS_ERR_ON_READ = 11,
/* Function not supported */
ISOBUSFS_ERR_FUNC_NOT_SUPPORTED = 12,
/* Volume is possibly not initialized */
ISOBUSFS_ERR_VOLUME_NOT_INITIALIZED = 13,
/* Invalid request length */
ISOBUSFS_ERR_INVALID_REQUESTED_LENGHT = 42,
/* Out of memory */
ISOBUSFS_ERR_OUT_OF_MEM = 43,
/* Any other error */
ISOBUSFS_ERR_OTHER = 44,
/* End of file reached, will only be reported when file pointer is at
* end of file
*/
ISOBUSFS_ERR_END_OF_FILE = 45,
/* TAN error:
* Same TAN, but different request compared to the previous one (change
* in content or size).
*/
ISOBUSFS_ERR_TAN_ERR = 46,
/* Malformed request:
* Message is shorter than expected. If the message is too short to
* provide a TAN (less than 2 bytes), the TAN shall be set to 0xff in
* the response.
*/
ISOBUSFS_ERR_MALFORMED_REQUEST = 47,
};
/* recursive buffer entry */
struct isobusfs_buf {
uint8_t data[ISOBUSFS_MIN_TRANSFER_LENGH];
struct timespec ts;
};
struct isobusfs_buf_log {
struct isobusfs_buf entries[ISOBUSFS_MAX_BUF_ENTRIES];
unsigned int index;
};
struct isobusfs_stats {
int err;
uint32_t tskey_sch;
uint32_t tskey_ack;
uint32_t send;
};
struct isobusfs_msg {
uint8_t buf[ISOBUSFS_MAX_TRANSFER_LENGH];
size_t buf_size;
ssize_t len; /* length of received message */
struct sockaddr_can peername;
socklen_t peer_addr_len;
int sock;
};
struct isobusfs_err_msg {
struct sock_extended_err *serr;
struct scm_timestamping *tss;
struct isobusfs_stats *stats;
};
struct isobusfs_cmn {
int epoll_fd;
struct epoll_event *epoll_events;
size_t epoll_events_size;
struct timespec next_send_time;
struct timespec last_time;
};
void isobusfs_init_sockaddr_can(struct sockaddr_can *sac, uint32_t pgn);
int isobusfs_recv_err(int sock, struct isobusfs_err_msg *emsg);
/*
* min()/max()/clamp() macros that also do
* strict type-checking.. See the
* "unnecessary" pointer comparison.
*/
#define min(x, y) ({ \
typeof(x) _min1 = (x); \
typeof(y) _min2 = (y); \
(void) (&_min1 == &_min2); \
_min1 < _min2 ? _min1 : _min2; })
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
static inline int isobusfs_buf_to_cmd(uint8_t *buf)
{
return (buf[0] & 0xf0) >> 4;
}
static inline int isobusfs_buf_to_function(uint8_t *buf)
{
return (buf[0] & 0xf);
}
static inline uint8_t isobusfs_cg_function_to_buf(enum isobusfs_cg cg,
uint8_t func)
{
return (func & 0xf) | ((cg & 0xf) << 4);
}
const char *isobusfs_error_to_str(enum isobusfs_error err);
enum isobusfs_error linux_error_to_isobusfs_error(int linux_err);
int isobusfs_get_timeout_ms(struct timespec *ts);
void isobusfs_send_nack(int sock, struct isobusfs_msg *msg);
void isobufs_store_tx_data(struct isobusfs_buf_log *buffer, uint8_t *data);
void isobusfs_dump_tx_data(const struct isobusfs_buf_log *buffer);
int isobusfs_sendto(int sock, const void *data, size_t len,
const struct sockaddr_can *addr,
struct isobusfs_buf_log *isobusfs_tx_buffer);
int isobusfs_send(int sock, const void *data, size_t len,
struct isobusfs_buf_log *isobusfs_tx_buffer);
void isobusfs_cmn_dump_last_x_bytes(const uint8_t *buffer, size_t buffer_size,
size_t x);
int isobusfs_cmn_open_socket(void);
int isobusfs_cmn_configure_socket_filter(int sock, pgn_t pgn);
int isobusfs_cmn_configure_error_queue(int sock);
int isobusfs_cmn_bind_socket(int sock, struct sockaddr_can *addr);
int isobusfs_cmn_connect_socket(int sock, struct sockaddr_can *addr);
int isobusfs_cmn_set_broadcast(int sock);
int isobusfs_cmn_add_socket_to_epoll(int epoll_fd, int sock, uint32_t events);
int isobusfs_cmn_create_epoll(void);
int isobusfs_cmn_socket_prio(int sock, int prio);
int isobusfs_cmn_set_linger(int sock);
int isobusfs_cmn_prepare_for_events(struct isobusfs_cmn *cmn, int *nfds,
bool dont_wait);
/* ============ directory handling ============ */
int isobusfs_cmn_dh_validate_dir_path(const char *path, bool writable);
/* ============ logging ============ */
typedef enum {
LOG_LEVEL_INT,
LOG_LEVEL_ERROR,
LOG_LEVEL_WARN,
LOG_LEVEL_INFO,
LOG_LEVEL_DEBUG,
} log_level_t;
void isobusfs_log_level_set(log_level_t level);
void isobusfs_log(log_level_t level, const char *fmt, ...);
void isobusfs_set_interactive(bool interactive);
void isobusfs_print_log_buffer(void);
/* undefine kernel logging macros */
#undef pr_int
#undef pr_err
#undef pr_warn
#undef pr_info
#undef pr_debug
/* pr_int - print for interactive session */
#define pr_int(fmt, ...) isobusfs_log(LOG_LEVEL_INT, fmt, ##__VA_ARGS__)
#define pr_err(fmt, ...) isobusfs_log(LOG_LEVEL_ERROR, fmt, ##__VA_ARGS__)
#define pr_warn(fmt, ...) isobusfs_log(LOG_LEVEL_WARN, fmt, ##__VA_ARGS__)
#define pr_info(fmt, ...) isobusfs_log(LOG_LEVEL_INFO, fmt, ##__VA_ARGS__)
#define pr_debug(fmt, ...) isobusfs_log(LOG_LEVEL_DEBUG, fmt, ##__VA_ARGS__)
#endif /* !_ISOBUSFS_H_ */

View File

@ -0,0 +1,175 @@
// SPDX-License-Identifier: GPL-2.0-only
// SPDX-FileCopyrightText: 2023 Oleksij Rempel <linux@rempel-privat.de>
#ifndef ISOBUSFS_CMN_CM_H
#define ISOBUSFS_CMN_CM_H
#include "isobusfs_cmn.h"
/* ISOBUSFS_CM_F_FS_STATUS */
#define ISOBUSFS_CM_F_FS_STATUS_IDLE_RATE 2000 /* ms */
#define ISOBUSFS_CM_F_FS_STATUS_BUSY_RATE 200 /* ms */
#define ISOBUSFS_CM_F_FS_STATUS_RATE_JITTER 5 /* ms */
/* File Server Status */
#define ISOBUSFS_FS_SATUS_BUSY_WRITING BIT(1)
#define ISOBUSFS_FS_SATUS_BUSY_READING BIT(0)
/**
* C.1.2 File Server Status
* struct isobusfs_cm_fss - File Server Status structure
* @fs_function: Function and command (1 byte)
* Bits 7-4: 0b0000 (Command - Connection Management, see B.1)
* Bits 3-0: 0b0001 (Function - File Server Status, see B.2)
* @file_server_status: File Server Status (1 byte) (see B.3)
* Bits 7-2: 000000 (Reserved, send as 000000)
* Bit 1: 1 (Busy writing)
* Bit 0: 1 (Busy reading)
* @num_open_files: Number of open files (1 byte)
* @reserved: Reserved for future use (5 bytes)
*
* Transmission repetition rate: 2 000 ms when the status is not busy, 200 ms
* when the status is busy reading or writing and,
* on change of byte 2, up to five messages per
* second.
* Data length: 8 bytes
* Parameter group number: FS to client, destination-specific or use global address: 0xFF
*/
struct isobusfs_cm_fss {
uint8_t fs_function;
uint8_t status;
uint8_t num_open_files;
uint8_t reserved[5];
};
/**
* C.1.3 Client Connection Maintenance
* struct isobusfs_cm_ccm - Client Connection Maintenance structure
* @fs_function: Function and command (1 byte)
* Bits 7-4: 0b0000 (Command - Connection Management, see B.1)
* Bits 3-0: 0b0000 (Function - Client Connection Maintenance, see B.2)
* @version: Version number (1 byte) (see B.5)
* @reserved: Reserved for future use (6 bytes)
*
* Transmission repetition rate: 2000 ms
* Data length: 8 bytes
* Parameter group number: Client to FS, destination-specific
*/
struct isobusfs_cm_ccm {
uint8_t fs_function;
uint8_t version;
uint8_t reserved[6];
};
/**
* C.1.4 Get File Server Properties
* struct isobusfs_cm_get_fs_props_req - Get File Server Properties structure
* @fs_function: Function and command (1 byte)
* Bits 7-4: 0b0000 (Command - Connection Management, see B.1)
* Bits 3-0: 0b0001 (Function - Get File Server Properties, see B.2)
* @reserved: Reserved, transmit as 0xFF (7 bytes)
*
* Transmission repetition rate: On request
* Data length: 8 bytes
* Parameter group number: Client to FS, destination-specific
*/
struct isobusfs_cm_get_fs_props_req {
uint8_t fs_function;
uint8_t reserved[7];
};
/* File Server Capabilities */
/* server support removable volumes */
#define ISOBUSFS_SRV_CAP_REMOVABLE_VOL BIT(1)
/* server support multiple volumes */
#define ISOBUSFS_SRV_CAP_MULTI_VOL BIT(0)
/**
* C.1.5 Get File Server Properties Response
* struct isobusfs_get_fs_props_resp - Get File Server Properties Response
* @fs_function: Function and command (1 byte)
* Bits 7-4: 0b0000 (Command - Connection Management, see B.1)
* Bits 3-0: 0b0001 (Function - Get File Server Properties, see B.2)
* @version_number: Version Number (1 byte, see B.5)
* @max_open_files: Maximum Number of Simultaneously Open Files (1 byte, see B.6)
* @fs_capabilities: File Server Capabilities (1 byte, see B.7)
* @reserved: Reserved, transmit as 0xFF (4 bytes)
*
* Transmission repetition rate: In response to Get File Server Properties message
* Data length: 8 bytes
* Parameter group number: FS to client, destination-specific
*/
struct isobusfs_cm_get_fs_props_resp {
uint8_t fs_function;
uint8_t version_number;
uint8_t max_open_files;
uint8_t fs_capabilities;
uint8_t reserved[4];
};
#define ISOBUSFS_VOL_MODE_PREP_TO_REMOVE BIT(1)
#define ISOBUSFS_VOL_MODE_USED_BY_CLIENT BIT(0)
#define ISOBUSFS_VOL_MODE_NOT_USED 0
/**
* C.1.6 Volume Status Request
* struct isobusfs_cm_vol_stat_req - Volume Status Request structure
* @fs_function: Function and command (1 byte)
* Bits 7-4: 0b0000 (Command - Connection Management, see B.1)
* Bits 3-0: 0b0010 (Function - Removable Media Status, see B.2)
* @volume_mode: Volume Mode (1 byte) (see B.30)
* @name_len: Path Name Length (2 bytes) (__le16, see B.12)
* @name: Volume Name (variable length) (see B.34)
*
* Transmission repetition rate: Upon request
* Data length: Variable
* Parameter group number: Client to FS, specific to the destination
*/
struct isobusfs_cm_vol_stat_req {
uint8_t fs_function;
uint8_t volume_mode;
__le16 name_len;
char name[ISOBUSFS_MAX_VOLUME_NAME_LENGTH];
};
enum isobusfs_vol_status {
ISOBUSFS_VOL_STATUS_PRESENT = 0,
ISOBUSFS_VOL_STATUS_IN_USE = 1,
ISOBUSFS_VOL_STATUS_PREP_TO_REMOVE = 2,
ISOBUSFS_VOL_STATUS_REMOVED = 3,
};
/**
* C.1.7 Volume Status Response
* struct isobusfs_cm_vol_stat_res - Volume Status Response structure
* @fs_function: Function and command (1 byte)
* Bits 7-4: 0b0000 (Command - Connection Management, see B.1)
* Bits 3-0: 0b0010 (Function - Volume Status, see B.2)
* @volume_status: Volume Status (1 byte) (see B.31)
* @max_time_before_removal: Maximum Time Before Volume Removal (1 byte) (see B.32)
* @error_code: Error code (1 byte) (see B.9)
* 0: Success
* 1: Access denied
* 2: Invalid Access
* 4: File, path or volume not found
* 6: Invalid given source name
* 43: Out of memory
* 44: Any other error
* @name_len: Path Name Length (2 bytes) (__le16, see B.12)
* @name: Volume Name (variable length) (see B.34)
*
* Transmission repetition rate: On request and on change of Volume Status
* Data length: Variable
* Parameter group number: FS to client, destination-specific or use global address: FF 16
*/
struct isobusfs_cm_vol_stat_res {
uint8_t fs_function;
uint8_t volume_status;
uint8_t max_time_before_removal;
uint8_t error_code;
__le16 name_len;
char name[ISOBUSFS_MAX_VOLUME_NAME_LENGTH];
};
#endif /* ISOBUSFS_CMN_CM_H */

View File

@ -0,0 +1,45 @@
// SPDX-License-Identifier: GPL-2.0-only
// SPDX-FileCopyrightText: 2023 Oleksij Rempel <linux@rempel-privat.de>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include "isobusfs_cmn.h"
#include <stdbool.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
int isobusfs_cmn_dh_validate_dir_path(const char *path, bool writable)
{
struct stat path_stat;
int mode = R_OK;
int ret;
mode |= writable ? W_OK : 0;
ret = access(path, mode);
if (ret == -1) {
ret = -errno;
pr_err("failed to acces path %s, for read %s. %s", path,
writable ? "and write" : "", strerror(ret));
return ret;
}
ret = stat(path, &path_stat);
if (ret == -1) {
ret = -errno;
pr_err("failed to get stat information on path %s. %s", path,
strerror(ret));
return ret;
}
if (!S_ISDIR(path_stat.st_mode)) {
pr_err("path %s is not a directory", path);
return -ENOTDIR;
}
return 0;
}

View File

@ -0,0 +1,104 @@
#ifndef _ISOBUSFS_CMN_DH_H
#define _ISOBUSFS_CMN_DH_H
#include "isobusfs_cmn.h"
/**
* C.2.2.2 Get Current Directory Request
* struct isobusfs_dh_get_cd_req - Get Current Directory Request structure
* @fs_function: Function and command (1 byte)
* Bits 7-4: 0b0001 (Command - Directory Access, see B.1)
* Bits 3-0: 0b0000 (Function - Get Current Directory, see B.2)
* @tan: Transaction number (1 byte) (see B.8)
* @reserved: Reserved, transmit as 0xFF (6 bytes)
*
* Transmission repetition rate: On request
* Data length: 8 bytes
* Parameter group number: Client to FS, destination-specific
*/
struct isobusfs_dh_get_cd_req {
uint8_t fs_function;
uint8_t tan;
uint8_t reserved[6];
};
/**
* C.2.2.3 Get Current Directory Response
* struct isobusfs_dh_get_cd_res - Get Current Directory Response structure
* @fs_function: Function and command (1 byte)
* Bits 7-4: 0b0001 (Command - Directory Access, see B.1)
* Bits 3-0: 0b0000 (Function - Get Current Directory, see B.2)
* @tan: Transaction number (1 byte) (see B.8)
* @error_code: Error code (1 byte) (see B.9)
* @total_space: Total space (4 bytes) (in units of 512 bytes, see B.11)
* @free_space: Free space (4 bytes) (in units of 512 bytes, see B.11)
* @name_len: Path Name Length (2 bytes) (__le16, see B.12)
* @name: Path Name (variable length) (see B.13)
*
* Transmission repetition rate: In response to Get Current Directory Request message
* Data length: Variable
* Parameter group number: FS to client, destination-specific
*/
struct isobusfs_dh_get_cd_res {
uint8_t fs_function;
uint8_t tan;
uint8_t error_code;
__le32 total_space;
__le32 free_space;
__le16 name_len;
uint8_t name[];
};
/**
* C.2.3.2 Change Current Directory Request
* struct isobusfs_dh_ccd_req - Change Current Directory Request structure
* @fs_function: Function and command (1 byte)
* Bits 7-4: 0b0001 (Command - Directory Access, see B.1)
* Bits 3-0: 0b0001 (Function - Change Current Directory, see B.2)
* @tan: Transaction number (1 byte) (see B.8)
* @name_len: Path Name length (2 bytes) (__le16, see B.12)
* @name: Path Name (variable length) (see B.13)
*
* Transmission repetition rate: On request
* Data length: Variable
* Parameter group number: Client to FS, destination-specific
*/
struct isobusfs_dh_ccd_req {
uint8_t fs_function;
uint8_t tan;
__le16 name_len;
uint8_t name[];
};
/**
* C.2.3.3 Change Current Directory Response
* struct isobusfs_dh_ccd_res - Change Current Directory Response structure
* @fs_function: Function and command (1 byte)
* Bits 7-4: 0b0001 (Command - Directory Access, see B.1)
* Bits 3-0: 0b0001 (Function - Change Current Directory, see B.2)
* @tan: Transaction number (1 byte) (see B.8)
* @error_code: Error code (1 byte) (see B.9)
* 0: Success
* 1: Access denied
* 2: Invalid access
* 4: File, path or volume not found
* 7: Invalid destination name given
* 10: Media is not present
* 13: Volume is possibly not initialized
* 43: Out of memory
* 44: Any other error
* @reserved: Reserved, transmit as 0xFF (5 bytes)
*
* Transmission repetition rate: In response to Change Current Directory Request message
* Data length: 8 bytes
* Parameter group number: FS to client, destination-specific
*/
struct isobusfs_dh_ccd_res {
uint8_t fs_function;
uint8_t tan;
uint8_t error_code;
uint8_t reserved[5];
};
#endif /* _ISOBUSFS_CMN_DH_H */

View File

@ -0,0 +1,381 @@
#ifndef _ISOBUSFS_CMN_FA_H
#define _ISOBUSFS_CMN_FA_H
#include <linux/kernel.h>
#include "isobusfs_cmn.h"
/* B.14 Flags */
/**
* ISOBUSFS_FA_REPORT_HIDDEN: (version 4 and later)
* 0 - Do not report hidden files and folders in directory listing.
* 1 - Report hidden files and folders in directory listing.
*/
#define ISOBUSFS_FA_REPORT_HIDDEN BIT(5)
/**
* ISOBUSFS_FA_OPEN_EXCLUSIVE:
* 0 - Open file for shared read access
* 1 - Open file with exclusive access (fails if already open)
*/
#define ISOBUSFS_FA_OPEN_EXCLUSIVE BIT(4)
/**
* ISOBUSFS_FA_OPEN_APPEND
* 0 - Open file for random access (file pointer set to the start of
* the file)
* 1 - Open file for appending data to the end of the file (file
* pointer set to the end of the file).
*/
#define ISOBUSFS_FA_OPEN_APPEND BIT(3)
/**
* ISOBUSFS_FA_CREATE_FILE_DIR:
* 0 - Open an existing file (fails if non-existent file)
* 1 - Create a new file and/or directories if not yet existing
*/
#define ISOBUSFS_FA_CREATE_FILE_DIR BIT(2)
#define ISOBUSFS_FA_OPEN_MASK GENMASK(1, 0)
#define ISOBUSFS_FA_OPEN_FILE_RO 0
#define ISOBUSFS_FA_OPEN_FILE_WO 1
#define ISOBUSFS_FA_OPEN_FILE_WR 2
#define ISOBUSFS_FA_OPEN_DIR 3
/*
* ISO 11783-13:2021 B.15 - File Attributes
* Bit 7: Case Sensitivity
* 0 - Volume is case-insensitive
* 1 - Volume is case-sensitive (Version 3 and later FS support this attribute)
* Bit 6: Removability
* 0 - Volume is removable
* 1 - Volume is not removable
* Bit 5: Long Filename Support
* 0 - Volume does not support long filenames
* 1 - Volume supports long filenames
* Bit 4: Directory Specification
* 0 - Does not specify a directory
* 1 - Specifies a directory
* Bit 3: Volume Specification
* 0 - Does not specify a volume
* 1 - Specifies a volume
* Bit 2: Hidden Attribute Support
* 0 - Volume does not support hidden attribute
* 1 - Volume supports hidden attribute and implementation supports it for the given volume
* Bit 1: Hidden Attribute Setting
* 0 - "Hidden" attribute is not set
* 1 - "Hidden" attribute is set (not applicable unless volume supports hidden attribute)
* Bit 0: Read-Only Attribute
* 0 - "Read-only" attribute is not set
* 1 - "Read-only" attribute is set
*/
#define ISOBUSFS_ATTR_CASE_SENSITIVE BIT(7)
#define ISOBUSFS_ATTR_REMOVABLE BIT(6)
#define ISOBUSFS_ATTR_LONG_FILENAME BIT(5)
#define ISOBUSFS_ATTR_DIRECTORY BIT(4)
#define ISOBUSFS_ATTR_VOLUME BIT(3)
#define ISOBUSFS_ATTR_HIDDEN_SUPPORT BIT(2)
#define ISOBUSFS_ATTR_HIDDEN BIT(1)
#define ISOBUSFS_ATTR_READ_ONLY BIT(0)
/**
* C.3.3.2 Open File Request
* struct isobusfs_fa_openf_req - Open File Request structure
* @fs_function: Function and command (1 byte)
* Bits 7-4: 0b0010 (Command - File Access)
* Bits 3-0: 0b0000 (Function - Read File) (see B.2)
* @tan: Transaction number (1 byte) (see B.8)
* @flags: Flags (1 byte) (see B.14)
* name_len: Path Name Length (2 bytes) (see B.12)
* @name: Name (Filename, Path or Wildcard name, depending on Flags)
* (Variable length)
*
* Transmission repetition rate: On request
* Data length: Variable
* Parameter group number: Client to FS, destination-specific
*
* Open File Request message is used to request opening a file, directory or
* using a wildcard name. The message contains the TAN, Flags, Path Name
* Length, and Name.
*/
struct isobusfs_fa_openf_req {
uint8_t fs_function;
uint8_t tan;
uint8_t flags;
__le16 name_len;
uint8_t name[];
};
/**
* C.3.3.3 Open File Response
* struct isobusfs_fa_openf_res - Open File Response structure
* @fs_function: Function and command (1 byte)
* Bits 7-4: 0b0010 (Command - File Access) (see B.1)
* Bits 3-0: 0b0000 (Function - Open File) (see B.2)
* @tan: Transaction number (1 byte)
* @error_code: Error code (1 byte), possible values:
* 0: Success
* 1: Access denied
* 2: Invalid access
* 3: Too many files open
* 4: File, path or volume not found
* 6: Invalid given source name
* 8: Volume out of free space
* 10: Media is not present
* 13: Volume is possibly not initialized
* 43: Out of memory
* 44: Any other error
* @handle: File handle (1 byte)
* @attributes: File attributes (1 byte)
* @reserved: Reserved, transmit as 0xFF (3 bytes)
*
* Transmission repetition rate: In response to Open File Request message
* Data length: 8 bytes
* Parameter group number: FS to client, destination-specific
*/
struct isobusfs_fa_openf_res {
uint8_t fs_function;
uint8_t tan;
uint8_t error_code;
uint8_t handle;
uint8_t attributes;
uint8_t reserved[3];
};
/* B.17 Position mode */
/* From the beginning of the file */
#define ISOBUSFS_FA_SEEK_SET 0
/* From the current position in the file */
#define ISOBUSFS_FA_SEEK_CUR 1
/* From the end of the file (can only be negative or 0 value) */
#define ISOBUSFS_FA_SEEK_END 2
/**
* C.3.4.2 Seek File Request
* struct isobusfs_fa_seekf_req - Seek File Request structure
* @fs_function: Function and command (1 byte)
* Bits 7-4: 0b0010 (Command - File Access) (see B.1)
* Bits 3-0: 0b0001 (Function - Seek File) (see B.2)
* @tan: Transaction number (1 byte) (see B.8)
* @handle: Handle (1 byte) (see B.10)
* @position_mode: Position mode (1 byte) (see B.17)
* @offset: Offset (4 bytes) (see B.18)
*/
struct isobusfs_fa_seekf_req {
uint8_t fs_function;
uint8_t tan;
uint8_t handle;
uint8_t position_mode;
__le32 offset;
};
/**
* C.3.4.2 Seek File Response
* struct isobusfs_fa_seekf_res - Seek File Response structure
* @fs_function: Function and command (1 byte)
* Bits 7-4: 0b0010 (Command - File Access) (see B.1)
* Bits 3-0: 0b0001 (Function - Seek File) (see B.2)
* @tan: Transaction number (1 byte) (see B.8)
* @error_code: Error code (1 byte) (see B.9)
* 0: Success
* 1: Access denied
* 5: Invalid Handle
* 11: Failure during a read operation
* 42: Invalid request length
* 43: Out of memory
* 44: Any other error
* 45: File pointer at end of file
* @reserved: Reserved, transmit as 0xFF (1 byte)
* @position: Position (4 bytes) (see B.19)
*/
struct isobusfs_fa_seekf_res {
uint8_t fs_function;
uint8_t tan;
uint8_t error_code;
uint8_t reserved;
__le32 position;
};
/**
* C.3.5.2 Read File Request
* struct isobusfs_fa_readf_req - Read File Request structure
* @fs_function: Function and command (1 byte)
* Bits 7-4: 0b0010 (Command: File Access, see B.1)
* Bits 3-0: 0b0010 (Function: Read File, see B.2)
* @tan: Transaction number (1 byte) (see B.8)
* @handle: File handle (1 byte) (see B.10)
* @count: Count (2 bytes) (see B.20)
* @reserved: Reserved, transmit as 0xFF (3 bytes)
* Byte 6: Version 4 and later: Reserved
* Version 3 and prior: Report Hidden Files (see B.28)
*
* Transmission repetition rate: On request
* Data length: 8 bytes
* Parameter group number: Client to FS, destination-specific
*/
struct isobusfs_fa_readf_req {
uint8_t fs_function;
uint8_t tan;
uint8_t handle;
__le16 count;
uint8_t reserved[3];
};
/**
* C.3.5.3 Read File Response (Handle-referenced file)
* struct isobusfs_read_file_response - Read File Response structure
* @fs_function: Function and command (1 byte)
* Bits 7-4: 0b0010 (Command: File Access, see B.1)
* Bits 3-0: 0b0010 (Function: Read File, see B.2)
* @tan: Transaction number (1 byte) (see B.8)
* @error_code: Error code (1 byte) (see B.9)
* 0: Success
* 1: Access denied
* 5: Invalid Handle
* 11: Failure during a read operation
* 43: Out of memory
* 44: Any other error
* 45: File pointer at end of file
* @count: Count (2 bytes) (see B.20)
* @data: Data (variable length)
*
* Transmission repetition rate: In response to Read File Request message
* Data length: Variable
* Parameter group number: FS to client, destination-specific
*/
struct isobusfs_read_file_response {
uint8_t fs_function;
uint8_t tan;
uint8_t error_code;
__le16 count;
uint8_t data[];
};
/**
* C.3.5.4 Read Directory Response (Handle-referenced directory)
* struct isobusfs_read_dir_response - Read Directory Response structure
* @fs_function: Function and command (1 byte)
* Bits 7-4: 0b0010 (Command: File Access, see B.1)
* Bits 3-0: 0b0010 (Function: Read File, see B.2)
* @tan: Transaction number (1 byte) (see B.8)
* @error_code: Error code (1 byte) (see B.9)
* 0: Success
* 1: Access denied
* 5: Invalid Handle
* 11: Failure during a read operation
* 43: Out of memory
* 44: Any other error
* 45: File pointer at end of file
* @count: Count (2 bytes) (see B.20)
* @data: Data (variable length) (see B.21)
*
* Transmission repetition rate: In response to Read File Request message
* Data length: Variable
* Parameter group number: FS to client, destination-specific
*/
struct isobusfs_read_dir_response {
uint8_t fs_function;
uint8_t tan;
uint8_t error_code;
__le16 count;
uint8_t data[];
};
/**
* C.3.6.2 Write File Request
* struct isobusfs_write_file_request - Write File Request structure
* @fs_function: Function and command (1 byte)
* Bits 7-4: 0b0010 (Command: File Access, see B.1)
* Bits 3-0: 0b0011 (Function: Write File, see B.2)
* @tan: Transaction number (1 byte) (see B.8)
* @handle: Handle (1 byte) (see B.10)
* @count: Count (2 bytes) (see B.20)
* @data: Data (variable length)
*
* Transmission repetition rate: On request
* Data length: Variable
* Parameter group number: Client to FS, destination-specific
*/
struct isobusfs_write_file_request {
uint8_t fs_function;
uint8_t tan;
uint8_t handle;
__le16 count;
uint8_t data[];
};
/**
* C.3.6.3 Write File Response
* struct isobusfs_write_file_response - Write File Response structure
* @fs_function: Function and command (1 byte)
* Bits 7-4: 0b0010 (Command: File Access, see B.1)
* Bits 3-0: 0b0011 (Function: Write File, see B.2)
* @tan: Transaction number (1 byte) (see B.8)
* @error_code: Error code (1 byte) (see B.9)
* 0: Success
* 1: Access denied
* 5: Invalid Handle
* 8: Volume out of space
* 9: Failure during a write operation
* 43: Out of memory
* 44: Any other error
* @count: Count (2 bytes) (see B.20)
* @reserved: Reserved, transmit as 0xFF (3 bytes)
*
* Transmission repetition rate: In response to Write File Request message
* Data length: 8 bytes
* Parameter group number: FS to client, destination-specific
*/
struct isobusfs_write_file_response {
uint8_t fs_function;
uint8_t tan;
uint8_t error_code;
__le16 count;
uint8_t reserved[3];
};
/**
* C.3.7.1 Close File Request
* struct isobusfs_close_file_request - Close File Request structure
* @fs_function: Function and command (1 byte)
* Bits 7-4: 0b0010 (Command: File Access, see B.1)
* Bits 3-0: 0b0100 (Function: Close File, see B.2)
* @tan: Transaction number (1 byte) (see B.8)
* @handle: Handle (1 byte) (see B.10)
* @reserved: Reserved, transmit as 0xFF (5 bytes)
*
* Transmission repetition rate: On request
* Data length: 8 bytes
* Parameter group number: Client to FS, destination-specific
*/
struct isobusfs_close_file_request {
uint8_t fs_function;
uint8_t tan;
uint8_t handle;
uint8_t reserved[5];
};
/**
* C.3.7.2 Close File Response
* struct isobusfs_close_file_response - Close File Response structure
* @fs_function: Function and command (1 byte)
* Bits 7-4: 0b0010 (Command: File Access, see B.1)
* Bits 3-0: 0b0100 (Function: Close File, see B.2)
* @tan: Transaction number (1 byte) (see B.8)
* @error_code: Error code (1 byte) (see B.9)
* 0: Success
* 1: Access denied
* 5: Invalid Handle
* 8: Volume out of space
* 9: Failure during a write operation
* 43: Out of memory
* 44: Any other error
* @reserved: Reserved, transmit as 0xFF (6 bytes)
*/
struct isobusfs_close_file_res {
uint8_t fs_function;
uint8_t tan;
uint8_t error_code;
uint8_t reserved[6];
};
#endif /* _LINUX_ISOBUS_FS_H */

View File

@ -0,0 +1,280 @@
#ifndef _ISOBUSFS_CMN_FH_H
#define _ISOBUSFS_CMN_FH_H
#include "isobusfs_cmn.h"
/**
* C.4.2.2 Move File Request
* struct isobusfs_move_file_request - Move File Request structure
* @fs_function: Function and command (1 byte)
* Bits 7-4: 0b0011 (Command - File Handling, see B.1)
* Bits 3-0: 0b0000 (Function - Move File, see B.2)
* @tan: Transaction number (1 byte) (see B.8)
* @file_handling_mode: File Handling Mode (1 byte) (see B.27)
* @src_path_name_length: Source Path Name Length (2 bytes) (see B.12)
* @dst_path_name_length: Destination Path Name Length (2 bytes) (see B.12)
* @src_path: Source Volume, Path, File and Wildcard Name (variable) (see B.34)
* @dst_path: Destination Volume, Path, File and Wildcard Name (variable) (see B.34)
*
* Transmission repetition rate: On request
* Data length: Variable
* Parameter group number: Client to FS, destination-specific
*/
struct isobusfs_move_file_request {
uint8_t fs_function;
uint8_t tan;
uint8_t file_handling_mode;
__le16 src_path_name_length;
__le16 dst_path_name_length;
/* Variable length data follows */
/* uint8_t src_path[]; */
/* uint8_t dst_path[]; */
};
/**
* C.4.2.3 Move File Response
* struct isobusfs_move_file_response - Move File Response structure
* @fs_function: Function and command (1 byte)
* Bits 7-4: 0b0011 (Command - File Handling, see B.1)
* Bits 3-0: 0b0000 (Function - Move File, see B.2)
* @tan: Transaction number (1 byte) (see B.8)
* @error_code: Error code (1 byte) (see B.9)
* 0: Success
* 1: Access denied
* 2: Invalid access
* 4: File, path or volume not found
* 6: Invalid given source name
* 7: Invalid given destination name
* 8: Volume out of free space
* 9: Failure during read operation
* 13: Volume is possibly not initialized
* 43: Out of memory
* 44: Any other error
* @reserved: Reserved, transmit as 0xFF (5 bytes)
*
* Transmission repetition rate: In response to Move File Request message
* Data length: 8 bytes
* Parameter group number: FS to client, destination-specific
*/
struct isobusfs_move_file_response {
uint8_t fs_function;
uint8_t tan;
uint8_t error_code;
uint8_t reserved[5];
};
/**
* C.4.3.1 Delete File Request
* struct isobusfs_delete_file_request - Delete File Request structure
* @fs_function: Function and command (1 byte)
* Bits 7-4: 0b0011 (Command - File Handling, see B.1)
* Bits 3-0: 0b0001 (Function - Delete File, see B.2)
* @tan: Transaction number (1 byte) (see B.8)
* @mode: File Handling Mode (1 byte) (see B.27)
* @path_len: Path Name Length (2 bytes) (see B.12)
* @path: Volume, Path, File, and Wildcard Name (variable length) (see B.34)
*
* Transmission repetition rate: On request
* Data length: Variable
* Parameter group number: Client to FS, destination-specific
*/
struct isobusfs_delete_file_request {
uint8_t fs_function;
uint8_t tan;
uint8_t mode;
__le16 path_len;
uint8_t path[];
};
/**
* C.4.3.2 Delete File Response
* struct isobusfs_delete_file_response - Delete File Response structure
* @fs_function: Function and command (1 byte)
* Bits 7-4: 0b0011 (Command - File Handling, see B.1)
* Bits 3-0: 0b0001 (Function - Delete File, see B.2)
* @tan: Transaction number (1 byte) (see B.8)
* @error_code: Error code (1 byte) (see B.9)
* 0: Success
* 1: Access denied
* 2: Invalid access
* 4: File, path, or volume not found
* 6: Invalid given file name
* 9: Failure during a write operation
* 13: Volume is possibly not initialized
* 43: Out of memory
* 44: Any other error
* @reserved: Reserved, transmit as 0xFF (5 bytes)
*
* Transmission repetition rate: In response to Delete File Request message
* Data length: 8 bytes
* Parameter group number: FS to client, destination-specific
*/
struct isobusfs_delete_file_response {
uint8_t fs_function;
uint8_t tan;
uint8_t error_code;
uint8_t reserved[5];
};
/**
* C.4.4.2 Get File Attributes Request
* struct isobusfs_get_file_attributes_request - Get File Attributes Request structure
* @fs_function: Function and command (1 byte)
* Bits 7-4: 0b0011 (Command - File Handling, see B.1)
* Bits 3-0: 0b0010 (Function - Get File Attributes, see B.2)
* @tan: Transaction number (1 byte) (see B.8)
* @pathname_len: Pathname Length (2 bytes) (__le16, see B.12)
* @pathname: Volume, Path and Filename (variable length) (see B.35)
*
* Transmission repetition rate: On request
* Data length: Variable
* Parameter group number: Client to FS, destination-specific
*/
struct isobusfs_get_file_attributes_request {
uint8_t fs_function;
uint8_t tan;
__le16 pathname_len;
uint8_t pathname[];
};
/**
* C.4.4.3 Get File Attributes Response
* struct isobusfs_get_file_attributes_response - Get File Attributes Response structure
* @fs_function: Function and command (1 byte)
* Bits 7-4: 0b0011 (Command - File Handling, see B.1)
* Bits 3-0: 0b0010 (Function - Get File Attributes, see B.2)
* @tan: Transaction number (1 byte) (see B.8)
* @error_code: Error code (1 byte) (see B.9)
* 0: Success
* 1: Access denied
* 2: Invalid access
* 4: File, path or volume not found
* 6: Invalid given name
* 11: Failure during a read operation
* 13: Volume is possibly not initialized
* 43: Out of memory
* 44: Any other error
* @attributes: File attributes (1 byte) (see B.15)
* @file_size: File size (4 bytes) (see B.26)
*
* Transmission repetition rate: In response to Get File Attributes Request message
* Data length: Variable
* Parameter group number: FS to client, destination-specific
*/
struct isobusfs_get_file_attributes_response {
uint8_t fs_function;
uint8_t tan;
uint8_t error_code;
uint8_t attributes;
__le32 file_size;
};
/**
* C.4.5.2 Set File Attributes Request
* struct isobusfs_set_file_attributes_request - Set File Attributes Request structure
* @fs_function: Function and command (1 byte)
* Bits 7-4: 0b0011 (Command - File Handling, see B.1)
* Bits 3-0: 0b0011 (Function - Set File Attributes, see B.2)
* @tan: Transaction number (1 byte) (see B.8)
* @attributes: Set Attributes Command (1 byte) (see B.16)
* @name_length: Name length (__le16) (see B.12)
* @name: Path, File and Wildcard Name (n bytes) (see B.34)
*
* Transmission repetition rate: On request
* Data length: Variable
* Parameter group number: Client to FS, destination-specific
*/
struct isobusfs_set_file_attributes_request {
uint8_t fs_function;
uint8_t tan;
uint8_t attributes;
__le16 name_length;
uint8_t name[]; /* Variable length */
};
/**
* C.4.5.3 Set File Attributes Response
* struct isobusfs_set_file_attributes_response - Set File Attributes Response structure
* @fs_function: Function and command (1 byte)
* Bits 7-4: 0b0011 (Command - File Handling, see B.1)
* Bits 3-0: 0b0011 (Function - Set File Attributes, see B.2)
* @tan: Transaction number (1 byte) (see B.8)
* @error_code: Error code (1 byte) (see B.9)
* 0: Success
* 1: Access denied
* 4: File, path or volume not found
* 6: Invalid given name
* 8: Volume out of free space
* 9: Failure during a write operation
* 10: Media is not present
* 13: Volume is possibly not initialized
* 43: Out of memory
* 44: Any other error
* @reserved: Reserved, transmit as 0xFF (5 bytes)
*
* Transmission repetition rate: In response to Get File Attributes Request message
* Data length: Variable
* Parameter group number: FS to client, destination-specific
*/
struct isobusfs_set_file_attributes_response {
uint8_t fs_function;
uint8_t tan;
uint8_t error_code;
uint8_t reserved[5];
};
/**
* C.4.6.2 Get File Date & Time Request
* struct isobusfs_get_file_date_time_request - Get File Date & Time Request structure
* @fs_function: Function and command (1 byte)
* Bits 7-4: 0b0011 (Command - File Handling, see B.1)
* Bits 3-0: 0b0100 (Function - Get File Date & Time, see B.2)
* @tan: Transaction number (1 byte) (see B.8)
* @name_length: Path Name length (2 bytes) (see B.12)
* @name: Path, File and Name (n bytes) (see B.35)
*
* Transmission repetition rate: On request
* Data length: Variable
* Parameter group number: Client to FS, destination-specific
*/
struct isobusfs_get_file_date_time_request {
uint8_t fs_function;
uint8_t tan;
__le16 name_length;
uint8_t name[]; /* Variable length */
};
/**
* C.4.6.2 Get File Date & Time Response
* struct isobusfs_get_file_date_time_response - Get File Date & Time Response structure
* @fs_function: Function and command (1 byte)
* Bits 7-4: 0b0011 (Command - File Handling, see B.1)
* Bits 3-0: 0b0100 (Function - Get File Date & Time, see B.2)
* @tan: Transaction number (1 byte) (see B.8)
* @error_code: Error code (1 byte) (see B.9)
* 0: Success
* 1: Access denied
* 4: File, path, or volume not found
* 6: Invalid given name
* 10: Media is not present
* 11: Failure during read operation
* 13: Volume is possibly not initialized
* 43: Out of memory
* 44: Any other error
* @date: File date (2 bytes) (see B.24)
* @time: File time (2 bytes) (see B.25)
*
* Transmission repetition rate: In response to Get File Date & Time Request message
* Data length: 7 bytes
* Parameter group number: FS to client, destination-specific
*/
struct isobusfs_get_file_date_time_response {
uint8_t fs_function;
uint8_t tan;
uint8_t error_code;
__le16 date;
__le16 time;
};
#endif /* _LINUX_ISOBUS_FS_H */

View File

@ -0,0 +1,64 @@
#ifndef _ISOBUSFS_CMN_VA_H
#define _ISOBUSFS_CMN_VA_H
#include "isobusfs_cmn.h"
/**
* C.5.2.2 Initialize Volume Request
* struct isobusfs_va_init_vol_req - Initialize Volume Request structure
* @fs_function: Function and command (1 byte)
* Bits 7-4: 0b0100 (Command - Volume Access, see B.1)
* Bits 3-0: 0b0000 (Function - Initialize Volume, see B.2)
* @tan: Transaction number (1 byte) (see B.8)
* @space: Space (2 bytes) (see B.11)
* @volume_flags: Volume Flags (1 byte) (see B.29)
* @name_len: Pathname Length (2 bytes) (__le16, see B.12)
* @name: Volume, Path and Filename (variable length) (see B.35)
*
* Transmission repetition rate: On request
* Data length: Variable
* Parameter group number: Client to FS, destination-specific
*/
struct isobusfs_va_init_vol_req {
uint8_t fs_function;
uint8_t tan;
__le16 space;
uint8_t volume_flags;
__le16 name_len;
uint8_t name[];
};
/**
* C.5.2.3 Initialize Volume Response
* struct isobusfs_va_init_vol_res - Initialize Volume Response structure
* @fs_function: Function and command (1 byte)
* Bits 7-4: 0b0100 (Command - Volume Access, see B.1)
* Bits 3-0: 0b0000 (Function - Initialize Volume, see B.2)
* @tan: Transaction number (1 byte) (see B.8)
* @error_code: Error code (1 byte) (see B.9)
* 0: Success
* 1: Access denied
* 4: Volume, path or file not found
* 6: Invalid given source name
* 8: Volume out of free space
* 9: Failure during write operation
* 10: Media is not present
* 11 Failure during read operation
* 12: Function not supported
* 13: Volume is possibly not initialized
* 43: Out of memory
* 44: Any other error
* @reserved: Reserved, transmit as 0xFF (4 bytes)
*
* Transmission repetition rate: In response to Initialize Volume Request message
* Data length: 8 bytes
* Parameter group number: FS to client, destination-specific
*/
struct isobusfs_va_init_vol_res {
uint8_t fs_function;
uint8_t tan;
uint8_t error_code;
uint8_t reserved[4];
};
#endif /* _ISOBUSFS_CMN_VA_H */

View File

@ -0,0 +1,64 @@
#!/bin/bash
mkdir dir1
mkdir dir1/dir2
mkdir dir1/dir2/dir3
mkdir dir1/dir2/dir3/dir4
mkdir dir1/dir2/dir3/dir5
mkdir MCMC0683
mkdir MCMC0683/msd_dir1/msd_dir2
mkdir MCMC0683/msd_dir1/msd_dir2/~
mkdir MCMC0683/msd_dir1/msd_dir2/~/~tilde_dir
mkdir dir1/~
mkdir dir1/~/~
echo "hello" > dir1/dir2/file0
./isobusfs_create_test_file.sh dir1/dir2/file1k 1024
./isobusfs_create_test_file.sh dir1/dir2/file1m 1048576
File and directory names testing
mkdir 'dir1/dir2/special_chars_*?/'
mkdir 'dir1/dir2/unicode_名字'
# Define the long suffix for the filenames
long_suffix="abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnop"
# Loop to create 300 files with long names
for count in {1..300}; do
touch "long_name_${count}_${long_suffix}"
done
touch dir1/dir2/hidden_file
touch dir1/dir2/readonly_file
touch dir1/dir2/executable_file
touch dir1/dir2/no_read_permission_file
# Setting permissions
chmod 444 dir1/dir2/readonly_file # Read-only file
chmod +x dir1/dir2/executable_file # Executable file
chmod 000 dir1/dir2/no_read_permission_file # No read permission for anyone
# Create directories for date problems
mkdir "dir1/dir2/y2000_problem"
mkdir "dir1/dir2/y2038_problem"
mkdir "dir1/dir2/y1979_problem"
mkdir "dir1/dir2/y1980_problem"
mkdir "dir1/dir2/y2107_problem"
mkdir "dir1/dir2/y2108_problem"
# Change timestamps to reflect specific years
# Year 2000
touch -d '2000-01-01 00:00:00' dir1/dir2/y2000_problem
# Year 2038 - beyond 19 January 2038, Unix timestamp issue
touch -d '2038-01-20 00:00:00' dir1/dir2/y2038_problem
# Year 1980 - minimal year supported
touch -d '1979-12-31 23:59:59' dir1/dir2/y1979_problem
touch -d '1980-01-01 00:00:00' dir1/dir2/y1980_problem
# Year 2107 - max year 1980+127
touch -d '2107-12-31 23:59:59' dir1/dir2/y2107_problem
touch -d '2108-01-01 00:00:00' dir1/dir2/y2108_problem

View File

@ -0,0 +1,22 @@
#!/bin/bash
# Arguments: path to the file and the file size
FILEPATH=$1
FILESIZE=$2
# Variable to store the increasing number
counter=0
# XOR pattern (change this to whatever you like)
pattern=0xdeadbeef
# Calculate the number of iterations needed
let iterations=$FILESIZE/4
# Use 'dd' command to generate the file with increasing numbers
for ((i=0; i<$iterations; i++)); do
# Print the 32-bit number in binary format to the file
printf "%08x" $((counter ^ pattern)) | xxd -r -p >> $FILEPATH
let counter=counter+1
done

View File

@ -0,0 +1,803 @@
// SPDX-License-Identifier: GPL-2.0-only
// SPDX-FileCopyrightText: 2023 Oleksij Rempel <linux@rempel-privat.de>
#include <err.h>
#include <errno.h>
#include <inttypes.h>
#include <poll.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <getopt.h>
#include <net/if.h>
#include <linux/net_tstamp.h>
#include "isobusfs_srv.h"
int isobusfs_srv_sendto(struct isobusfs_srv_priv *priv,
struct isobusfs_msg *msg, const void *buf,
size_t buf_size)
{
struct sockaddr_can addr = msg->peername;
addr.can_addr.j1939.pgn = ISOBUSFS_PGN_FS_TO_CL;
return isobusfs_sendto(msg->sock, buf, buf_size, &addr,
&priv->tx_buf_log);
}
int isobusfs_srv_send_error(struct isobusfs_srv_priv *priv,
struct isobusfs_msg *msg,
enum isobusfs_error err)
{
uint8_t buf[ISOBUSFS_MIN_TRANSFER_LENGH];
/* copy 2 bytes with command group, function and TAN from the source
* package
*/
memcpy(buf, &msg->buf[0], 2);
buf[2] = err;
/* not used space should be filled with 0xff */
memset(&buf[3], 0xff, ARRAY_SIZE(buf) - 3);
pr_debug("> tx error: 0x%02x (%s)", err, isobusfs_error_to_str(err));
return isobusfs_srv_sendto(priv, msg, &buf[0], ARRAY_SIZE(buf));
}
/* server side rx */
static int isobusfs_srv_rx_fs(struct isobusfs_srv_priv *priv,
struct isobusfs_msg *msg)
{
enum isobusfs_cg cg = isobusfs_buf_to_cmd(msg->buf);
uint8_t addr = msg->peername.can_addr.j1939.addr;
struct isobusfs_srv_client *client;
int ret = 0;
switch (cg) {
case ISOBUSFS_CG_CONNECTION_MANAGMENT:
case ISOBUSFS_CG_DIRECTORY_HANDLING:
case ISOBUSFS_CG_FILE_ACCESS:
case ISOBUSFS_CG_FILE_HANDLING:
case ISOBUSFS_CG_VOLUME_HANDLING:
break;
default:
pr_warn("%s: unsupported command group (%i)", __func__,
cg);
/* ISO 11783-13:2021 - Annex C.1.1 Overview:
* If a client sends a command, which is not defined withing this
* documentation, the file server shall respond with a
* NACK (ISO 11783-3:2018 Chapter 5.4.5)
*/
isobusfs_send_nack(priv->sock_nack, msg);
/* Not a critical error */
return 0;
}
client = isobusfs_srv_get_client(priv, addr);
if (!client) {
pr_warn("%s: client not found", __func__);
return -EINVAL;
}
msg->sock = client->sock;
switch (cg) {
case ISOBUSFS_CG_CONNECTION_MANAGMENT:
ret = isobusfs_srv_rx_cg_cm(priv, msg);
break;
case ISOBUSFS_CG_DIRECTORY_HANDLING:
ret = isobusfs_srv_rx_cg_dh(priv, msg);
break;
case ISOBUSFS_CG_FILE_ACCESS:
ret = isobusfs_srv_rx_cg_fa(priv, msg);
break;
case ISOBUSFS_CG_FILE_HANDLING:
ret = isobusfs_srv_rx_cg_fh(priv, msg);
break;
case ISOBUSFS_CG_VOLUME_HANDLING:
ret = isobusfs_srv_rx_cg_vh(priv, msg);
break;
}
return ret;
}
static int isobusfs_srv_rx_ack(struct isobusfs_srv_priv *priv,
struct isobusfs_msg *msg)
{
enum isobusfs_ack_ctrl ctrl = msg->buf[0];
switch (ctrl) {
case ISOBUS_ACK_CTRL_ACK:
pr_debug("< rx: ACK?????");
break;
case ISOBUS_ACK_CTRL_NACK:
/* we did something wrong */
pr_debug("< rx: NACK!!!!!");
isobusfs_dump_tx_data(&priv->tx_buf_log);
break;
default:
pr_warn("%s: unsupported ACK control: %i", __func__, ctrl);
return -EINVAL;
}
/* Not a critical error */
return 0;
}
static int isobusfs_srv_rx_buf(struct isobusfs_srv_priv *priv, struct isobusfs_msg *msg)
{
pgn_t pgn = msg->peername.can_addr.j1939.pgn;
int ret;
switch (pgn) {
case ISOBUSFS_PGN_CL_TO_FS:
ret = isobusfs_srv_rx_fs(priv, msg);
break;
case ISOBUS_PGN_ACK:
ret = isobusfs_srv_rx_ack(priv, msg);
break;
default:
pr_warn("%s: unsupported PGN: %i", __func__, pgn);
ret = -EINVAL;
break;
}
return ret;
}
static int isobusfs_srv_recv_one(struct isobusfs_srv_priv *priv, int sock)
{
struct isobusfs_msg *msg;
int flags = 0;
int ret;
msg = malloc(sizeof(*msg));
if (!msg) {
pr_err("can't allocate rx msg struct");
goto done;
}
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) {
pr_err("recvfrom(): %i (%s)", errno, strerror(errno));
goto free_msg;
}
if (ret < ISOBUSFS_MIN_TRANSFER_LENGH) {
pr_warn("buf is less then min transfer: %i < %i. Dropping.",
ret, ISOBUSFS_MIN_TRANSFER_LENGH);
/* TODO: The file server shall respond with Error Code 47
* Malformed Request, if the message is shorter than expected.
*/
isobusfs_send_nack(priv->sock_nack, msg);
goto free_msg;
}
msg->len = ret;
ret = isobusfs_srv_rx_buf(priv, msg);
if (ret < 0) {
pr_err("unhandled error by rx buf: %i", ret);
goto free_msg;
}
free_msg:
free(msg);
done:
return EXIT_SUCCESS;
}
static int isobusfs_srv_handle_events(struct isobusfs_srv_priv *priv, int nfds)
{
int ret;
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_fss) {
if (ev->events & POLLERR) {
struct isobusfs_err_msg emsg = {
.stats = &priv->st_msg_stats,
};
ret = isobusfs_recv_err(priv->sock_fss, &emsg);
if (ret && ret != -EINTR)
return ret;
}
}
if (ev->events & POLLIN) {
ret = isobusfs_srv_recv_one(priv, ev->data.fd);
if (ret) {
warn("recv one");
return ret;
}
}
}
return 0;
}
static int isobusfs_srv_handle_periodic_tasks(struct isobusfs_srv_priv *priv)
{
/* remove timeouted clients */
isobusfs_srv_remove_timeouted_clients(priv);
/* this function will send status only if it is proper time to do so */
return isobusfs_srv_fss_send(priv);
}
static int isobusfs_srv_process_events_and_tasks(struct isobusfs_srv_priv *priv)
{
int ret, nfds;
ret = isobusfs_cmn_prepare_for_events(&priv->cmn, &nfds, false);
if (ret)
return ret;
if (nfds > 0) {
ret = isobusfs_srv_handle_events(priv, nfds);
if (ret)
return ret;
}
return isobusfs_srv_handle_periodic_tasks(priv);
}
static int isobusfs_srv_sock_fss_prepare(struct isobusfs_srv_priv *priv)
{
struct sockaddr_can addr = priv->addr;
int ret;
ret = isobusfs_cmn_open_socket();
if (ret < 0)
return ret;
priv->sock_fss = ret;
ret = isobusfs_cmn_configure_error_queue(priv->sock_fss);
if (ret < 0)
return ret;
/* keep address and name and overwrite PGN */
/* TOOD: actually, this is PGN input filter. Should we use different
* PGN?
*/
addr.can_addr.j1939.pgn = ISOBUSFS_PGN_CL_TO_FS;
ret = isobusfs_cmn_bind_socket(priv->sock_fss, &addr);
if (ret < 0)
return ret;
ret = isobusfs_cmn_set_broadcast(priv->sock_fss);
if (ret < 0)
return ret;
ret = isobusfs_cmn_set_linger(priv->sock_fss);
if (ret < 0)
return ret;
ret = isobusfs_cmn_socket_prio(priv->sock_fss, ISOBUSFS_PRIO_FSS);
if (ret < 0)
return ret;
/* connect to broadcast address */
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_connect_socket(priv->sock_fss, &addr);
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_fss,
EPOLLERR);
}
static int isobusfs_srv_sock_in_prepare(struct isobusfs_srv_priv *priv)
{
struct sockaddr_can addr = priv->addr;
int ret;
ret = isobusfs_cmn_open_socket();
if (ret < 0)
return ret;
priv->sock_in = ret;
/* keep address and name and overwrite PGN */
addr.can_addr.j1939.pgn = ISOBUSFS_PGN_CL_TO_FS;
ret = isobusfs_cmn_bind_socket(priv->sock_in, &addr);
if (ret < 0)
return ret;
return isobusfs_cmn_add_socket_to_epoll(priv->cmn.epoll_fd, priv->sock_in,
EPOLLIN);
}
static int isobusfs_srv_sock_nack_prepare(struct isobusfs_srv_priv *priv)
{
struct sockaddr_can addr = priv->addr;
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);
}
/**
* isobusfs_srv_sock_prepare - Prepares the control socket and epoll instance
* @priv: pointer to the isobusfs_srv_priv structure
*
* This function calls multiple helper functions to prepare the control socket
* and epoll instance for the ISOBUS server.
* Returns: 0 on success, negative error code on failure
*/
static int isobusfs_srv_sock_prepare(struct isobusfs_srv_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_SRV_MAX_EPOLL_EVENTS,
sizeof(struct epoll_event));
if (!priv->cmn.epoll_events)
return -ENOMEM;
priv->cmn.epoll_events_size = ISOBUSFS_SRV_MAX_EPOLL_EVENTS;
ret = isobusfs_srv_sock_fss_prepare(priv);
if (ret < 0)
return ret;
ret = isobusfs_srv_sock_in_prepare(priv);
if (ret < 0)
return ret;
return isobusfs_srv_sock_nack_prepare(priv);
}
static int isobusfs_srv_parse_volume_ext(struct isobusfs_srv_priv *priv,
const char *optarg,
char ***volumes, int *volumes_count)
{
char *volume_info;
char *token;
if (*volumes_count >= ISOBUSFS_SRV_MAX_VOLUMES) {
pr_err("Maximum number of volumes (%d) exceeded\n",
ISOBUSFS_SRV_MAX_VOLUMES);
return -EINVAL;
}
volume_info = strdup(optarg);
token = strtok(volume_info, ",");
while (token) {
*volumes_count += 1;
*volumes = realloc(*volumes,
*volumes_count * sizeof(char *));
*volumes[*volumes_count - 1] = strdup(token);
token = strtok(NULL, ",");
}
return 0;
}
static int isobusfs_srv_parse_volumes(struct isobusfs_srv_priv *priv,
const char *optarg)
{
struct isobusfs_srv_volume *volumes = priv->volumes;
char *volume_info, *name, *path;
size_t name_len, path_len;
int ret;
if (priv->volume_count >= ISOBUSFS_SRV_MAX_VOLUMES) {
pr_err("Maximum number of volumes (%d) exceeded\n",
ISOBUSFS_SRV_MAX_VOLUMES);
return -EINVAL;
}
volume_info = strdup(optarg);
name = strtok(volume_info, ":");
path = strtok(NULL, ":");
if (!name || !path) {
pr_err("Error: volume or path name is missing\n");
ret = -EINVAL;
goto free_volume_info;
}
name_len = strnlen(name, ISOBUSFS_SRV_MAX_VOLUME_NAME_LEN + 2);
if (name_len > ISOBUSFS_SRV_MAX_VOLUME_NAME_LEN) {
pr_err("Error: Volume name exceeds maximum length (%d)\n",
ISOBUSFS_SRV_MAX_VOLUME_NAME_LEN);
ret = -EINVAL;
goto free_volume_info;
}
path_len = strnlen(path, ISOBUSFS_SRV_MAX_PATH_LEN + 2);
if (path_len > ISOBUSFS_SRV_MAX_PATH_LEN) {
pr_err("Error: Path name exceeds maximum length (%d)\n",
ISOBUSFS_SRV_MAX_PATH_LEN);
ret = -EINVAL;
goto free_volume_info;
}
volumes[priv->volume_count].name = strdup(name);
volumes[priv->volume_count].path = strdup(path);
priv->volume_count++;
ret = 0;
free_volume_info:
free(volume_info);
return ret;
}
static void isobusfs_srv_generate_mfs_dir_name(struct isobusfs_srv_priv *priv)
{
uint16_t manufacturer_code = (priv->local_name >> 21) & 0x07FF;
snprintf(&priv->mfs_dir[0], sizeof(priv->mfs_dir), "MCMC%04u",
manufacturer_code);
}
static void isobusfs_srv_print_help(void)
{
printf("Usage: isobusfs-srv [options]\n");
printf("Options:\n");
printf(" --address <local_address_hex> or -a <local_address_hex>\n");
printf(" --default-volume <volume_name> or -d <volume_name>\n");
printf(" --interface <interface_name> or -i <interface_name>\n");
printf(" --log-level <logging_level> or -l <loging_level>\n");
printf(" --name <local_name_hex> or -n <local_name_hex>\n");
printf(" --removable-volume <volume_name_1,volume_name_2,...> or -r <volume_name_1,volume_name_2,...>\n");
printf(" --server-version <version_number> or -s <version_number>\n");
printf(" --volume <volume_name>:<path> or -v <volume_name>:<path>\n");
printf(" --writeable-volume <volume_name_1,volume_name_2,...> or -w <volume_name_1,volume_name_2,...>\n");
printf("Note: Local address and local name are mutually exclusive\n");
}
static int isobusfs_srv_parse_args(struct isobusfs_srv_priv *priv, int argc,
char *argv[])
{
struct sockaddr_can *addr = &priv->addr;
char **removable_volumes = NULL;
char **writeable_volumes = NULL;
uint32_t local_address = 0;
uint64_t local_name = 0;
bool local_address_set = false;
bool local_name_set = false;
bool voluem_set = false;
bool interface_set = false;
int ret, level, version;
int opt, i, j;
int writeable_volumes_count = 0;
int long_index = 0;
struct option long_options[] = {
{"address", required_argument, NULL, 'a'},
{"default-volume", required_argument, NULL, 'd'},
{"interface", required_argument, NULL, 'i'},
{"log-level", required_argument, NULL, 'l'},
{"name", required_argument, NULL, 'n'},
{"removable-volume", required_argument, 0, 'r'},
{"server-version", required_argument, 0, 's'},
{"volume", required_argument, NULL, 'v'},
{"writeable-volume", required_argument, 0, 'w'},
{"help", no_argument, NULL, 'h'},
{NULL, 0, NULL, 0}
};
while ((opt = getopt_long(argc, argv, "a:d:i:l:n:r:s:v:w:h",
long_options, &long_index)) != -1) {
switch (opt) {
case 'a': {
if (local_name_set) {
pr_err("Both local address and local name provided, they are mutually exclusive\n");
return -EINVAL;
}
sscanf(optarg, "%x", &local_address);
addr->can_addr.j1939.addr = local_address;
local_address_set = true;
break;
}
case 'd': {
priv->default_volume = strdup(optarg);
break;
}
case 'i': {
addr->can_ifindex = if_nametoindex(optarg);
if (!addr->can_ifindex) {
pr_err("Interface %s not found. Error: %d (%s)\n",
optarg, errno, strerror(errno));
return -EINVAL;
}
interface_set = 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;
}
case 'n': {
if (local_address_set) {
pr_err("Both local address and local name provided, they are mutually exclusive\n");
return -EINVAL;
}
sscanf(optarg, "%" SCNx64, &local_name);
priv->local_name = local_name;
addr->can_addr.j1939.name = local_name;
local_name_set = true;
break;
}
case 'r': {
ret = isobusfs_srv_parse_volume_ext(priv, optarg,
&removable_volumes,
&priv->removable_volumes_count);
if (ret < 0)
return ret;
break;
}
case 's': {
version = atoi(optarg);
if (version < 0 || version > 255)
pr_err("Invalid server version %d. Using default version: %d",
version, ISOBUSFS_SRV_VERSION);
break;
}
case 'v': {
ret = isobusfs_srv_parse_volumes(priv, optarg);
if (ret < 0)
return ret;
voluem_set = true;
break;
}
case 'w': {
ret = isobusfs_srv_parse_volume_ext(priv, optarg,
&writeable_volumes,
&writeable_volumes_count);
if (ret < 0)
return ret;
break;
}
case 'h':
default:
isobusfs_srv_print_help();
return -EINVAL;
}
}
if (!local_address_set && !local_name_set) {
pr_err("Error: local address or local name is missing");
isobusfs_srv_print_help();
return -EINVAL;
}
if (!voluem_set) {
pr_err("Error: volume is missing");
isobusfs_srv_print_help();
return -EINVAL;
}
if (!interface_set) {
pr_err("Error: interface is missing");
isobusfs_srv_print_help();
return -EINVAL;
}
if (!priv->volume_count) {
pr_err("Error: volume is missing");
isobusfs_srv_print_help();
return -EINVAL;
}
if (priv->volume_count == 1) {
if (priv->default_volume) {
pr_err("Error: default volume is not needed for single volume");
isobusfs_srv_print_help();
return -EINVAL;
}
priv->default_volume = strdup(priv->volumes[0].name);
} else if (priv->volume_count > 1) {
if (!priv->default_volume) {
pr_err("Error: default volume is missing");
isobusfs_srv_print_help();
return -EINVAL;
}
/* Check if default volume is valid */
for (i = 0; i < priv->volume_count; i++) {
if (!strcmp(priv->default_volume, priv->volumes[i].name))
break;
if (i == priv->volume_count - 1) {
pr_err("Error: default volume should be one of defined volumes");
isobusfs_srv_print_help();
return -EINVAL;
}
}
}
for (i = 0; i < priv->removable_volumes_count; i++) {
bool found = false;
for (j = 0; j < priv->volume_count; j++) {
if (!strcmp(removable_volumes[i],
priv->volumes[j].name)) {
priv->volumes[j].removable = true;
found = true;
break;
}
}
if (!found) {
pr_err("Error: removable volume %s is not defined",
removable_volumes[i]);
isobusfs_srv_print_help();
return -EINVAL;
}
}
for (i = 0; i < writeable_volumes_count; i++) {
bool found = false;
for (j = 0; j < priv->volume_count; j++) {
if (!strcmp(writeable_volumes[i],
priv->volumes[j].name)) {
priv->volumes[j].writeable = true;
found = true;
break;
}
}
if (!found) {
pr_err("Error: writeable volume %s is not defined",
writeable_volumes[i]);
isobusfs_srv_print_help();
return -EINVAL;
}
}
for (i = 0; i < priv->volume_count; i++) {
ret = isobusfs_cmn_dh_validate_dir_path(priv->volumes[i].path,
priv->volumes[i].writeable);
if (ret < 0) {
if (ret == -ENOTDIR)
pr_err("Error: path %s is not a directory",
priv->volumes[i].path);
else if (ret == -EACCES)
pr_err("Error: can't access path %s, error %i (%s)",
priv->volumes[i].path, ret, strerror(ret));
/* If volume is not removable, return error. Probably
* we will be able to detect it later.
*/
if (!priv->volumes[i].removable)
return ret;
}
}
if (!local_name_set)
pr_warn("local name is not set. Wont be able to generate proper manufacturer-specific directory name. Falling mack to MCMC0000");
isobusfs_srv_generate_mfs_dir_name(priv);
pr_debug("Server configuration:");
pr_debug(" local NAME: 0x%" SCNx64, priv->local_name);
pr_debug(" manufacturer-specific directory: %s", priv->mfs_dir);
pr_debug("Configured volumes:");
for (i = 0; i < priv->volume_count; i++) {
pr_debug(" %s: %s", priv->volumes[i].name,
priv->volumes[i].path);
pr_debug(" %s", priv->volumes[i].writeable ? "writeable" : "read-only");
pr_debug(" %s", priv->volumes[i].removable ? "removable" : "non-removable");
}
for (i = 0; i < priv->removable_volumes_count; i++)
free(removable_volumes[i]);
free(removable_volumes);
for (i = 0; i < writeable_volumes_count; i++)
free(writeable_volumes[i]);
free(writeable_volumes);
return 0;
}
int main(int argc, char *argv[])
{
struct isobusfs_srv_priv *priv;
struct timespec ts;
int ret;
/* Allocate memory for the private structure */
priv = malloc(sizeof(*priv));
if (!priv)
err(EXIT_FAILURE, "can't allocate priv");
/* Clear memory for the private structure */
memset(priv, 0, sizeof(*priv));
/* Initialize sockaddr_can with a non-configurable PGN */
isobusfs_init_sockaddr_can(&priv->addr, J1939_NO_PGN);
priv->server_version = ISOBUSFS_SRV_VERSION;
/* Parse command line arguments */
ret = isobusfs_srv_parse_args(priv, argc, argv);
if (ret)
return ret;
/* Prepare sockets for the server */
ret = isobusfs_srv_sock_prepare(priv);
if (ret)
return ret;
/* Initialize File Server Status structure */
isobusfs_srv_fss_init(priv);
/* Initialize client structures */
isobusfs_srv_init_clients(priv);
/* Init next st_next_send_time value to avoid warnings */
clock_gettime(CLOCK_MONOTONIC, &ts);
priv->cmn.next_send_time = ts;
/* Start the isobusfsd server */
pr_info("Starting isobusfs-srv");
while (1) {
ret = isobusfs_srv_process_events_and_tasks(priv);
if (ret)
break;
}
/* Close epoll and control sockets */
close(priv->cmn.epoll_fd);
free(priv->cmn.epoll_events);
close(priv->sock_fss);
close(priv->sock_in);
close(priv->sock_nack);
return ret;
}

View File

@ -0,0 +1,172 @@
// SPDX-License-Identifier: GPL-2.0-only
#ifndef ISOBUSFS_SRV_H
#define ISOBUSFS_SRV_H
#include <stdbool.h>
#include <stdint.h>
#include <sys/epoll.h>
#include <dirent.h>
#include "isobusfs_cmn.h"
#include "isobusfs_cmn_cm.h"
#define ISOBUSFS_SRV_VERSION 4
#define ISOBUSFS_SRV_MAX_CTRL_SOCKETS 1
#define ISOBUSFS_SRV_MAX_CLIENT_SOCKETS 255
#define ISOBUSFS_SRV_MAX_EPOLL_EVENTS (ISOBUSFS_SRV_MAX_CTRL_SOCKETS + \
ISOBUSFS_SRV_MAX_CLIENT_SOCKETS)
#define ISOBUSFS_SRV_MAX_OPENED_HANDLES 255
/*
* ISO 11783-13:2021 standard does not explicitly specify a maximum number of
* clients that can be supported on the network. However, the ISO 11783 standard
* is built on top of the SAE J1939 protocol, which has a maximum of 238
* available addresses for nodes. This number is calculated from the available
* address range for assignment to nodes on the network, which includes 127
* addresses in the range 1-127 and 111 addresses in the range 248-254,
* inclusive. Some addresses in the total range (0-255) are reserved for
* specific purposes, such as broadcast messages and null addresses.
*
* The maximum number of 238 nodes includes both clients and servers, so the
* actual number of clients that can be supported will be less than 238.
*
* It is important to note that the practical limit of clients in an ISO
* 11783-13 network could be lower due to factors such as network bandwidth,
* performance constraints of the individual devices, and the complexity of the
* network.
*/
#define ISOBUSFS_SRV_MAX_CLIENTS 237
enum isobusfs_srv_fss_state {
ISOBUSFS_SRV_STATE_IDLE = 0, /* send status with 2000ms interval */
ISOBUSFS_SRV_STATE_STAT_CHANGE_1, /* send status with 200ms interval */
ISOBUSFS_SRV_STATE_STAT_CHANGE_2, /* send status with 200ms interval */
ISOBUSFS_SRV_STATE_STAT_CHANGE_3, /* send status with 200ms interval */
ISOBUSFS_SRV_STATE_STAT_CHANGE_4, /* send status with 200ms interval */
ISOBUSFS_SRV_STATE_STAT_CHANGE_5, /* send status with 200ms interval */
ISOBUSFS_SRV_STATE_BUSY, /* send status with 200ms interval */
};
struct isobusfs_srv_client {
int sock;
struct timespec last_received;
uint8_t addr;
uint8_t tan;
uint8_t version;
char current_dir[ISOBUSFS_SRV_MAX_PATH_LEN];
};
struct isobusfs_srv_volume {
char *name;
char *path;
bool removable;
bool writeable;
int refcount;
struct isobusfs_srv_client *clients[ISOBUSFS_SRV_MAX_CLIENTS];
};
struct isobusfs_srv_handles {
char *path;
int refcount;
int fd;
off_t offset;
int32_t dir_pos;
DIR *dir;
struct isobusfs_srv_client *clients[ISOBUSFS_SRV_MAX_CLIENTS];
};
struct isobusfs_srv_priv {
/* incoming traffic from peers */
int sock_in;
/*
* egress only File Server Status broadcast packets with different
* prio
*/
int sock_fss;
/*
* bidirectional socket for NACK packets.
* ISO 11783-3:2018 5.4.5 Acknowledgement
*/
int sock_nack;
struct sockaddr_can addr;
int server_version;
/* fs status related variables */
struct isobusfs_cm_fss st; /* file server status message */
enum isobusfs_srv_fss_state st_state;
struct isobusfs_stats st_msg_stats;
/* client related variables */
struct isobusfs_srv_client clients[ISOBUSFS_SRV_MAX_CLIENTS];
int clients_count;
struct isobusfs_buf_log tx_buf_log;
struct isobusfs_cmn cmn;
struct isobusfs_srv_volume volumes[ISOBUSFS_SRV_MAX_VOLUMES];
int volume_count;
int removable_volumes_count;
const char *default_volume;
/* manufacturer-specific directory */
char mfs_dir[9];
uint64_t local_name;
struct isobusfs_srv_handles handles[ISOBUSFS_SRV_MAX_OPENED_HANDLES];
int handles_count;
};
/* isobusfs_srv.c */
int isobusfs_srv_send_error(struct isobusfs_srv_priv *priv,
struct isobusfs_msg *msg, enum isobusfs_error err);
int isobusfs_srv_sendto(struct isobusfs_srv_priv *priv,
struct isobusfs_msg *msg, const void *buf,
size_t buf_size);
/* isobusfs_srv_cm_fss.c */
void isobusfs_srv_fss_init(struct isobusfs_srv_priv *priv);
int isobusfs_srv_fss_send(struct isobusfs_srv_priv *priv);
/* isobusfs_srv_cm.c */
int isobusfs_srv_rx_cg_cm(struct isobusfs_srv_priv *priv,
struct isobusfs_msg *msg);
void isobusfs_srv_remove_timeouted_clients(struct isobusfs_srv_priv *priv);
void isobusfs_srv_init_clients(struct isobusfs_srv_priv *priv);
struct isobusfs_srv_client *isobusfs_srv_get_client(
struct isobusfs_srv_priv *priv, uint8_t addr);
struct isobusfs_srv_client *isobusfs_srv_get_client_by_msg(
struct isobusfs_srv_priv *priv, struct isobusfs_msg *msg);
/* isobusfs_srv_dh.c */
int isobusfs_srv_rx_cg_dh(struct isobusfs_srv_priv *priv,
struct isobusfs_msg *msg);
int isobusfs_path_to_linux_path(struct isobusfs_srv_priv *priv,
const char *isobusfs_path, size_t isobusfs_path_size,
char *linux_path, size_t linux_path_size);
int isobusfs_check_current_dir_access(struct isobusfs_srv_priv *priv,
const char *path, size_t path_size);
int isobusfs_convert_relative_to_absolute(struct isobusfs_srv_priv *priv,
const char *current_dir,
const char *rel_path,
size_t rel_path_size, char *abs_path,
size_t abs_path_size);
void isobusfs_srv_set_default_current_dir(struct isobusfs_srv_priv *priv,
struct isobusfs_srv_client *client);
/* isobusfs_srv_vh.c */
int isobusfs_srv_rx_cg_vh(struct isobusfs_srv_priv *priv,
struct isobusfs_msg *msg);
/* isobusfs_srv_fh.c */
int isobusfs_srv_rx_cg_fh(struct isobusfs_srv_priv *priv,
struct isobusfs_msg *msg);
/* isobusfs_srv_fa.c */
int isobusfs_srv_rx_cg_fa(struct isobusfs_srv_priv *priv,
struct isobusfs_msg *msg);
void isobusfs_srv_remove_client_from_handles(struct isobusfs_srv_priv *priv,
struct isobusfs_srv_client *client);
#endif /* ISOBUSFS_SRV_H */

View File

@ -0,0 +1,645 @@
// SPDX-License-Identifier: GPL-2.0-only
// SPDX-FileCopyrightText: 2023 Oleksij Rempel <linux@rempel-privat.de>
/*
* ISOBUS File System Server Connection Management (isobusfs_srv_cm.c)
*
* This code implements the Connection Management functionality for the
* ISOBUS File System Server, according to the ISO 11783-13:2021 standard,
* specifically Section 5.10: Connection/Disconnection of a client.
*
* The ISOBUS File System Server provides a way for clients to interact with
* files and directories on an ISOBUS network. Connection Management is
* responsible for handling client connections and disconnections, ensuring
* proper communication and resource allocation between the server and its
* clients.
*
* This code includes functions for initializing clients, adding new clients,
* managing client connections, and handling client disconnections. It also
* provides utility functions for managing sockets and filters for the
* communication between the server and its clients.
*/
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <linux/kernel.h>
#include "isobusfs_cmn.h"
#include "isobusfs_srv.h"
#include "isobusfs_cmn_cm.h"
/* Request volume by client */
int isobusfs_srv_request_volume(struct isobusfs_srv_priv *priv,
struct isobusfs_srv_client *client,
struct isobusfs_srv_volume *volume)
{
int j;
/* Check if the client already requested this volume */
for (j = 0; j < ARRAY_SIZE(volume->clients); j++) {
if (volume->clients[j] == client) {
/* Client already requested this volume, do not
* increase refcount
*/
return 0;
}
}
/* Add client to the volume's client list and increase refcount */
for (j = 0; j < ARRAY_SIZE(volume->clients); j++) {
if (volume->clients[j] == NULL) {
volume->clients[j] = client;
volume->refcount++;
return 0;
}
}
return -ENOENT;
}
/* Release volume by client */
int isobusfs_srv_release_volume(struct isobusfs_srv_priv *priv,
struct isobusfs_srv_client *client,
const char *volume_name)
{
struct isobusfs_srv_volume *volume;
int i;
/* Find the requested volume */
for (i = 0; i < priv->volume_count; i++) {
if (strcmp(priv->volumes[i].name, volume_name) == 0) {
volume = &priv->volumes[i];
/* Find the client in the volume's client list and
* decrease refcount
*/
for (int j = 0; j < ISOBUSFS_SRV_MAX_CLIENTS; j++) {
if (volume->clients[j] == client) {
volume->clients[j] = NULL;
volume->refcount--;
return 0;
}
}
}
}
return -ENOENT;
}
static void
isobusfs_srv_remove_client_from_volumes(struct isobusfs_srv_priv *priv,
struct isobusfs_srv_client *client)
{
for (int i = 0; i < priv->volume_count; i++) {
struct isobusfs_srv_volume *volume = &priv->volumes[i];
for (int j = 0; j < ISOBUSFS_SRV_MAX_CLIENTS; j++) {
if (volume->clients[j] == client) {
volume->clients[j] = NULL;
volume->refcount--;
}
}
}
}
/**
* isobusfs_srv_init_clients - Initialize the list of clients for the server
* @priv: Pointer to the isobusfs_srv_priv structure containing clients array
*
* This function initializes the clients array in the isobusfs_srv_priv
* structure by setting the socket value to -1 for each client in the list.
* This indicates that the clients are not currently connected.
*/
void isobusfs_srv_init_clients(struct isobusfs_srv_priv *priv)
{
int i;
for (i = 0; i < ISOBUSFS_SRV_MAX_CLIENTS; i++)
priv->clients[i].sock = -1;
}
/**
* isobusfs_srv_get_client - Find a client in the list of clients by address
* @priv: Pointer to the isobusfs_srv_priv structure containing clients array
* @addr: Address of the client to find
*
* This function searches for a client in the list of clients using the
* provided address. If a client with a matching address is found, the
* function returns a pointer to the corresponding isobusfs_srv_client
* structure. If no matching client is found, the function returns NULL.
*
* Note: The function skips clients with negative socket values.
*/
static struct isobusfs_srv_client *isobusfs_srv_find_client(
struct isobusfs_srv_priv *priv, uint8_t addr)
{
int i;
for (i = 0; i < priv->clients_count; i++) {
struct isobusfs_srv_client *client = &priv->clients[i];
if (client->sock < 0)
continue;
if (client->addr == addr)
return &priv->clients[i];
}
return NULL;
}
/**
* isobusfs_srv_remove_client - Remove a client from the list of clients
* @priv: Pointer to the isobusfs_srv_priv structure containing clients array
* @client: Pointer to the isobusfs_srv_client structure to be removed
*
* This function removes a client from the list of clients, adjusts the
* clients array, and decrements the clients_count. The function also closes
* the client's socket.
*/
static void isobusfs_srv_remove_client(struct isobusfs_srv_priv *priv,
struct isobusfs_srv_client *client)
{
int index = client - priv->clients;
int i;
if (client->sock < 0)
return;
close(client->sock);
client->sock = -1;
isobusfs_srv_remove_client_from_handles(priv, client);
isobusfs_srv_remove_client_from_volumes(priv, client);
/* Shift all elements after the removed client to the left by one
* position
*/
for (i = index; i < priv->clients_count - 1; i++)
priv->clients[i] = priv->clients[i + 1];
priv->clients_count--;
pr_debug("client 0x%02x removed", client->addr);
}
/**
* isobusfs_srv_init_client - Initialize a client's socket and connection
* @priv: pointer to the server's private data structure
* @client: pointer to the client's data structure
*
* This function initializes a client's socket, binds it to the server's address,
* sets the socket options, and connects the socket to the destination address.
* If any step fails, it will log a warning message, close the socket if needed,
* and return an error code.
*
* Return: 0 on success, negative error code on failure
*/
static int isobusfs_srv_init_client(struct isobusfs_srv_priv *priv,
struct isobusfs_srv_client *client)
{
struct sockaddr_can addr = priv->addr;
struct j1939_filter filt = {
.addr = J1939_NO_ADDR,
.addr_mask = J1939_NO_ADDR, /* mask is 0xff */
};
int ret;
if (client->sock >= 0) {
pr_warn("client %d already initialized", client->addr);
return -EINVAL;
}
ret = isobusfs_cmn_open_socket();
if (ret < 0)
return ret;
client->sock = ret;
addr.can_addr.j1939.pgn = ISOBUSFS_PGN_CL_TO_FS;
ret = isobusfs_cmn_bind_socket(client->sock, &addr);
if (ret < 0)
return ret;
ret = isobusfs_cmn_set_linger(client->sock);
if (ret < 0)
return ret;
/* use positive filter to not no allow any unicast messages. At same
* time do not allow any broadcast messages. So, this will be transmit
* only socket. This is needed to not let the J1939 kernel stack to
* ACK ETP/TP transfers on the bus and provide false information to
* the client about received data.
*/
ret = setsockopt(client->sock, SOL_CAN_J1939, SO_J1939_FILTER, &filt,
sizeof(filt));
if (ret < 0) {
ret = -errno;
pr_warn("can't set socket filter for client 0x%02x. Error: %i (%s)",
client->addr, ret, strerror(ret));
goto close_socket;
}
ret = isobusfs_cmn_socket_prio(client->sock, ISOBUSFS_PRIO_DEFAULT);
if (ret < 0)
return ret;
addr.can_addr.j1939.name = J1939_NO_NAME;
addr.can_addr.j1939.addr = client->addr;
addr.can_addr.j1939.pgn = ISOBUSFS_PGN_FS_TO_CL;
ret = isobusfs_cmn_connect_socket(client->sock, &addr);
if (ret < 0)
return ret;
return 0;
close_socket:
close(client->sock);
client->sock = -1;
return ret;
}
/**
* isobusfs_srv_add_client - Add a new client to the server's client list
* @priv: pointer to the server's private data structure
* @addr: address of the new client
*
* This function adds a new client to the server's client list if the list
* hasn't reached its maximum capacity (ISOBUSFS_SRV_MAX_CLIENTS).
* If the maximum number of clients is reached, a warning message will be
* logged, and the function returns NULL.
*
* Return: pointer to the new client on success, NULL on failure
*/
static struct isobusfs_srv_client *isobusfs_srv_add_client(
struct isobusfs_srv_priv *priv, uint8_t addr)
{
struct isobusfs_srv_client *client;
int ret;
if (priv->clients_count >= ISOBUSFS_SRV_MAX_CLIENTS) {
pr_warn("too many clients");
return NULL;
}
client = &priv->clients[priv->clients_count];
client->addr = addr;
ret = isobusfs_srv_init_client(priv, client);
if (ret < 0)
return NULL;
priv->clients_count++;
pr_debug("client 0x%02x added", client->addr);
return client;
}
struct isobusfs_srv_client *isobusfs_srv_get_client(
struct isobusfs_srv_priv *priv, uint8_t addr)
{
struct isobusfs_srv_client *client;
/* Get the client with the specified address */
client = isobusfs_srv_find_client(priv, addr);
/* If client is not found, create a new one */
if (!client) {
client = isobusfs_srv_add_client(priv, addr);
if (!client) {
pr_warn("can't add client");
/* Keep running. Nothing we can do here */
return NULL;
}
}
/* Update the client's last_received timestamp */
client->last_received = priv->cmn.last_time;
return client;
}
struct isobusfs_srv_client *isobusfs_srv_get_client_by_msg(
struct isobusfs_srv_priv *priv, struct isobusfs_msg *msg)
{
uint8_t addr = msg->peername.can_addr.j1939.addr;
struct isobusfs_srv_client *client;
client = isobusfs_srv_get_client(priv, addr);
if (!client) {
pr_warn("%s: client not found", __func__);
return NULL;
}
return client;
}
/**
* isobusfs_srv_remove_timeouted_clients - Remove clients that have timed out
* @priv: pointer to the server's private data structure
*
* This function checks each client in the server's client list to determine
* if the client has timed out. If a client has timed out, it is removed
* from the list.
*/
void isobusfs_srv_remove_timeouted_clients(struct isobusfs_srv_priv *priv)
{
int i;
for (i = 0; i < priv->clients_count; i++) {
struct isobusfs_srv_client *client = &priv->clients[i];
int64_t time_diff;
if (client->sock < 0)
continue;
time_diff = timespec_diff_ms(&priv->cmn.last_time,
&client->last_received);
if (time_diff > ISOBUSFS_CLIENT_TIMEOUT) {
isobusfs_srv_remove_client(priv, client);
i--;
}
}
}
/**
* isobusfs_srv_property_res - Send a Get File Server Properties Response
* @priv: pointer to the server's private data structure
* @msg: pointer to the received message that requires a response
*
* This function sends a response to a client's Get File Server Properties
* request according to ISO 11783-13:2021, Annex C.1.5. The response contains
* information about the server's capabilities, version, and maximum number
* of simultaneously open files.
*
* Return: 0 on success, negative error code on failure
*/
static int isobusfs_srv_property_res(struct isobusfs_srv_priv *priv,
struct isobusfs_msg *msg)
{
struct isobusfs_cm_get_fs_props_resp resp;
int ret;
resp.fs_function =
isobusfs_cg_function_to_buf(ISOBUSFS_CG_CONNECTION_MANAGMENT,
ISOBUSFS_CM_GET_FS_PROPERTIES_RES);
/* Version number:
* 0 - Draft
* 1 - Final draft
* 2 - First published version
*/
resp.version_number = priv->server_version;
/* Maximum Number of Simultaneously Open Files */
resp.max_open_files = ISOBUSFS_MAX_OPENED_FILES;
/* File Server Capabilities */
resp.fs_capabilities = 0;
/* not used space should be filled with 0xff */
memset(&resp.reserved[0], 0xff, sizeof(resp.reserved));
ret = isobusfs_srv_sendto(priv, msg, &resp, sizeof(resp));
if (ret < 0) {
pr_warn("can't send property response");
return ret;
}
pr_debug("> tx property response");
return 0;
}
/**
* isobusfs_srv_handle_ccm - Handle a Connection Control Maintenance message
* @priv: pointer to the server's private data structure
* @msg: pointer to the received message that requires a response
*
* This function handles a Connection Control Maintenance (CCM) message
* according to ISO 11783-13:2021, Annex C.1.3. The CCM is used to establish a
* connection between a client and a server. If the client is not found in the
* server's client list, it is added to the list. If the client is found, the
* client's socket is reinitialized.
*
* Return: 0 on success, negative error code on failure
*/
static int isobusfs_srv_handle_ccm(struct isobusfs_srv_priv *priv,
struct isobusfs_msg *msg)
{
struct isobusfs_cm_ccm *ccm = (struct isobusfs_cm_ccm *)msg->buf;
pr_debug("< rx ccm version: %d", ccm->version);
return 0;
}
static int isobusfs_extract_volume_name(const char *src, size_t src_size,
char *dst, size_t dst_size)
{
size_t i = 0, j = 0;
size_t n;
if (src == NULL || dst == NULL || dst_size == 0 || src_size == 0)
return -EINVAL;
if (!(src[0] == '\\' && src[1] == '\\' && src[2] != '\0'))
return -EINVAL;
n = min((size_t)3, min(src_size, dst_size - 1));
memcpy(dst, src, n);
while (i < src_size && src[i] != '\0' && src[i] != '\\' &&
j < dst_size - 1) {
dst[j++] = src[i++];
}
dst[j] = '\0';
return 0;
}
static int isobusfs_srv_process_volume_status_request(struct isobusfs_srv_priv *priv,
struct isobusfs_msg *msg, struct isobusfs_cm_vol_stat_res *resp)
{
struct isobusfs_cm_vol_stat_req *req =
(struct isobusfs_cm_vol_stat_req *)msg->buf;
char isobusfs_volume_path[ISOBUSFS_MAX_VOLUME_NAME_LENGTH];
char linux_path[ISOBUSFS_SRV_MAX_PATH_LEN];
struct isobusfs_srv_volume *volume = NULL;
struct isobusfs_srv_client *client;
const char *path;
size_t path_len;
int ret, i;
pr_debug("< rx volume status request. mode: %x, length: %d, name: %s",
req->volume_mode, req->name_len, req->name);
client = isobusfs_srv_get_client_by_msg(priv, msg);
if (!client) {
pr_warn("can't find client");
return ISOBUSFS_ERR_OTHER;
}
if (req->name_len == 0) {
path = client->current_dir;
path_len = sizeof(client->current_dir);
} else {
path = req->name;
path_len = req->name_len;
}
ret = isobusfs_extract_volume_name(path, path_len, isobusfs_volume_path,
sizeof(isobusfs_volume_path));
if (ret < 0) {
pr_warn("can't extract volume name");
return ISOBUSFS_ERR_OTHER;
}
resp->name_len = strlen(isobusfs_volume_path);
/* the isobusfs_volume_path is already null terminated
* by isobusfs_extract_volume_name()
*/
memcpy(resp->name, isobusfs_volume_path, resp->name_len + 1);
ret = isobusfs_path_to_linux_path(priv, isobusfs_volume_path,
sizeof(isobusfs_volume_path),
linux_path, sizeof(linux_path));
if (ret < 0) {
pr_warn("can't convert %s path to linux path", isobusfs_volume_path);
return ISOBUSFS_ERR_FILE_ORPATH_NOT_FOUND;
}
ret = isobusfs_cmn_dh_validate_dir_path(linux_path, false);
if (ret < 0)
return ISOBUSFS_ERR_INVALID_ACCESS;
/* TODO: we already searched for volume in isobusfs_path_to_linux_path()
* function. We should use the result of that search instead of searching
* again.
*/
for (i = 0; i < priv->volume_count; i++) {
if (strncmp(priv->volumes[i].name, isobusfs_volume_path,
sizeof(isobusfs_volume_path)) == 0) {
volume = &priv->volumes[i];
break;
}
}
if (!volume)
return ISOBUSFS_ERR_FILE_ORPATH_NOT_FOUND;
if (req->volume_mode & ISOBUSFS_VOL_MODE_PREP_TO_REMOVE) {
if (!volume->removable ||
(req->name_len == 0 &&
0 /* Current directory is not set condition */)) {
/* Volume is not removable, or the Path Name Length of
* request is zero and the current directory is not set
*/
return ISOBUSFS_ERR_INVALID_ACCESS;
}
/* TODO: Check if the volume is in use
* TODO: Check if the volume is the current directory
* TODO: add hot removal support.
*/
resp->volume_status = ISOBUSFS_VOL_STATUS_PREP_TO_REMOVE;
} else if (req->volume_mode & ISOBUSFS_VOL_MODE_USED_BY_CLIENT) {
ret = isobusfs_srv_request_volume(priv, client,
volume);
if (ret < 0)
return ISOBUSFS_ERR_INVALID_ACCESS;
}
if (volume->refcount > 0)
resp->volume_status = ISOBUSFS_VOL_STATUS_IN_USE;
else
resp->volume_status = ISOBUSFS_VOL_STATUS_PRESENT;
return 0;
}
static int isobusfs_srv_volume_status_resp(struct isobusfs_srv_priv *priv,
struct isobusfs_msg *msg)
{
struct isobusfs_cm_vol_stat_res resp = {0};
size_t buf_size;
int ret;
resp.fs_function =
isobusfs_cg_function_to_buf(ISOBUSFS_CG_CONNECTION_MANAGMENT,
ISOBUSFS_CM_VOLUME_STATUS_RES);
ret = isobusfs_srv_process_volume_status_request(priv, msg, &resp);
resp.error_code = ret;
buf_size = sizeof(resp);
if (buf_size < ISOBUSFS_MIN_TRANSFER_LENGH) {
buf_size = ISOBUSFS_MIN_TRANSFER_LENGH;
memset(((uint8_t *) &resp) + sizeof(resp), 0xFF, buf_size - sizeof(resp));
} else if (buf_size > ISOBUSFS_MAX_TRANSFER_LENGH) {
pr_warn("volume status response too long");
resp.error_code = ISOBUSFS_ERR_OUT_OF_MEM;
buf_size = ISOBUSFS_MAX_TRANSFER_LENGH;
}
ret = isobusfs_srv_sendto(priv, msg, &resp, buf_size);
if (ret < 0) {
pr_warn("can't send volume status response");
return ret;
}
pr_debug("> tx volume status response. status: %d, error code: %d, name len: %d, name: %s",
resp.volume_status, resp.error_code, resp.name_len, resp.name);
return 0;
}
/**
* isobusfs_srv_rx_cg_cm - Handle received connection management commands
* @priv: pointer to the server's private data structure
* @msg: pointer to the received message that requires processing
*
* This function handles the received connection management commands according
* to the specific function code provided in the message. It then delegates
* the processing to the corresponding handler for each supported function.
* Unsupported functions will result in a warning message and an error response.
*
* Return: 0 on success or when an unsupported function is encountered,
* negative error code on failure
*/
int isobusfs_srv_rx_cg_cm(struct isobusfs_srv_priv *priv,
struct isobusfs_msg *msg)
{
enum isobusfs_cm_cl_to_fs_function func =
isobusfs_buf_to_function(msg->buf);
int ret = 0;
/* Process the received function code and delegate to appropriate
* handlers
*/
switch (func) {
case ISOBUSFS_CM_F_CC_MAINTENANCE:
ret = isobusfs_srv_handle_ccm(priv, msg);
break;
case ISOBUSFS_CM_GET_FS_PROPERTIES:
ret = isobusfs_srv_property_res(priv, msg);
break;
case ISOBUSFS_CM_VOLUME_STATUS_REQ:
if (priv->server_version < 3)
goto not_supported;
ret = isobusfs_srv_volume_status_resp(priv, msg);
break;
default:
goto not_supported;
}
return ret;
not_supported:
/* Handle unsupported functions */
isobusfs_srv_send_error(priv, msg, ISOBUSFS_ERR_FUNC_NOT_SUPPORTED);
pr_warn("%s: unsupported function: %i", __func__, func);
/* Not a critical error */
return 0;
}

View File

@ -0,0 +1,126 @@
// SPDX-License-Identifier: GPL-2.0-only
// SPDX-FileCopyrightText: 2023 Oleksij Rempel <linux@rempel-privat.de>
/*
* This file implements Annex C.1.2 File Server Status according to
* ISO 11783-13:2021.
*/
#include <err.h>
#include <errno.h>
#include <inttypes.h>
#include <poll.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include "isobusfs_srv.h"
/*
* isobusfs_srv_fss_init - Initialize the file server status structure
* @priv: Pointer to the private data structure of the ISOBUS file server
*
* This function initializes the file server status structure, which
* represents the status of the file server according to Annex C.1.2
* of ISO 11783-13:2021.
*/
void isobusfs_srv_fss_init(struct isobusfs_srv_priv *priv)
{
struct isobusfs_cm_fss *st = &priv->st;
st->fs_function =
isobusfs_cg_function_to_buf(ISOBUSFS_CG_CONNECTION_MANAGMENT,
ISOBUSFS_CM_F_FS_STATUS);
st->status = 0;
st->num_open_files = 0;
memset(st->reserved, 0xFF, sizeof(st->reserved));
}
/*
* isobusfs_srv_fss_get_rate - Get the rate of File Server Status transmission
* @priv: Pointer to the private data structure of the ISOBUS file server
*
* Returns: the transmission rate of the File Server Status messages depending
* on the current state of the file server.
*/
static unsigned int isobusfs_srv_fss_get_rate(struct isobusfs_srv_priv *priv)
{
switch (priv->st_state) {
case ISOBUSFS_SRV_STATE_IDLE:
return ISOBUSFS_CM_F_FS_STATUS_IDLE_RATE;
/*
* On every change of Byte 2 "File Server Status" send max 5 status
* messages per second.
*/
case ISOBUSFS_SRV_STATE_STAT_CHANGE_1: /* fall through */
case ISOBUSFS_SRV_STATE_STAT_CHANGE_2: /* fall through */
case ISOBUSFS_SRV_STATE_STAT_CHANGE_3: /* fall through */
case ISOBUSFS_SRV_STATE_STAT_CHANGE_4: /* fall through */
case ISOBUSFS_SRV_STATE_STAT_CHANGE_5:
priv->st_state--;
return ISOBUSFS_CM_F_FS_STATUS_BUSY_RATE;
case ISOBUSFS_SRV_STATE_BUSY:
return ISOBUSFS_CM_F_FS_STATUS_BUSY_RATE;
default:
pr_warn("%s:%i: unknown state %d", __func__, __LINE__,
priv->st_state);
}
/*
* In case something is wrong, fall back to idle rate to not spam the
* bus.
*/
return ISOBUSFS_CM_F_FS_STATUS_IDLE_RATE;
}
/**
* isobusfs_srv_fss_send - Send periodic File Server Status messages
* @priv: Pointer to the private data structure of the ISOBUS file server
*
* Returns: 0 if the message was sent successfully, a negative error code
* otherwise.
*/
int isobusfs_srv_fss_send(struct isobusfs_srv_priv *priv)
{
unsigned int next_msg_rate;
int64_t time_diff;
int ret;
/* Test if it is proper time to send next status message. */
time_diff = timespec_diff_ms(&priv->cmn.next_send_time,
&priv->cmn.last_time);
if (time_diff > ISOBUSFS_CM_F_FS_STATUS_RATE_JITTER) {
/* too early to send next message */
return 0;
}
if (time_diff < -ISOBUSFS_CM_F_FS_STATUS_RATE_JITTER) {
pr_warn("too late to send next fs status message: %ld ms",
time_diff);
}
/* Make sure we send the message with the latest stats */
if (priv->st_msg_stats.tskey_sch != priv->st_msg_stats.tskey_ack)
pr_warn("previous message was not acked");
/* send periodic file servers status messages. */
ret = send(priv->sock_fss, &priv->st, sizeof(priv->st), MSG_DONTWAIT);
if (ret < 0) {
ret = -errno;
pr_warn("Failed to send FS status message, error code: %d (%s)",
ret, strerror(ret));
return ret;
}
pr_debug("> tx FS status: 0x%02x, opened files: %d",
priv->st.status, priv->st.num_open_files);
/* Calculate time for the next status message */
next_msg_rate = isobusfs_srv_fss_get_rate(priv);
priv->cmn.next_send_time = priv->cmn.last_time;
timespec_add_ms(&priv->cmn.next_send_time, next_msg_rate);
return 0;
}

View File

@ -0,0 +1,758 @@
// SPDX-License-Identifier: GPL-2.0-only
// SPDX-FileCopyrightText: 2023 Oleksij Rempel <linux@rempel-privat.de>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include "isobusfs_srv.h"
#include "isobusfs_cmn_dh.h"
void isobusfs_srv_set_default_current_dir(struct isobusfs_srv_priv *priv,
struct isobusfs_srv_client *client)
{
snprintf(client->current_dir, ISOBUSFS_SRV_MAX_PATH_LEN, "\\\\%s",
priv->default_volume);
}
static const char *isobusfs_srv_get_volume_end(const char *path, size_t path_size)
{
const char *vol_end = NULL;
size_t i;
if (!path || !path_size)
return NULL;
if (!(path[0] == '\\' && path[1] == '\\' && path[2] != '\0'))
return NULL;
for (i = 2; i < path_size; i++) {
if (path[i] == '\\' || path[i] == '\0') {
vol_end = &path[i];
break;
}
}
if (!vol_end)
vol_end = &path[i];
return vol_end;
}
int isobusfs_path_to_linux_path(struct isobusfs_srv_priv *priv,
const char *isobusfs_path, size_t isobusfs_path_size,
char *linux_path, size_t linux_path_size)
{
struct isobusfs_srv_volume *volume = NULL;
size_t isobusfs_path_pos = 0;
const char *vol_end;
char *ptr;
int i;
if (!priv || !isobusfs_path || !linux_path || !linux_path_size ||
!isobusfs_path_size) {
pr_err("%s: invalid argument\n", __func__);
return -EINVAL;
}
vol_end = isobusfs_srv_get_volume_end(isobusfs_path, isobusfs_path_size);
if (!vol_end) {
pr_err("%s: invalid path %s. Can't find end of volume string\n",
__func__, isobusfs_path);
return -EINVAL;
}
/* Search for the volume in the priv->volumes array */
for (i = 0; i < priv->volume_count; i++) {
size_t volume_name_len = vol_end - (isobusfs_path + 2);
if (volume_name_len == strlen(priv->volumes[i].name) &&
memcmp(priv->volumes[i].name, isobusfs_path + 2,
volume_name_len) == 0) {
volume = &priv->volumes[i];
break;
}
}
if (!volume) {
pr_err("%s: invalid path %s. Can't find volume\n",
__func__, isobusfs_path);
return -ENODEV;
}
/* Copy the volume's Linux path to the output buffer */
strncpy(linux_path, volume->path, linux_path_size - 1);
linux_path[linux_path_size - 1] = '\0';
isobusfs_path_pos = vol_end - isobusfs_path;
/* Add a forward slash if path ends after volume name */
if (*vol_end == '\0' || isobusfs_path_pos == isobusfs_path_size - 1)
strncat(linux_path, "/",
linux_path_size - strlen(linux_path) - 1);
if (isobusfs_path_pos + 3 < isobusfs_path_size && strncmp(vol_end, "\\~\\", 3) == 0) {
strncat(linux_path, "/", linux_path_size - strlen(linux_path) - 1);
/* convert tilde to manufacturer-specific directory */
strncat(linux_path, priv->mfs_dir,
linux_path_size - strlen(linux_path) - 1);
vol_end += 2;
}
/* Replace backslashes with forward slashes for the rest of the path */
ptr = linux_path + strlen(linux_path);
while (vol_end < isobusfs_path + isobusfs_path_size && *vol_end) {
if (*vol_end == '\\')
*ptr = '/';
else
*ptr = *vol_end;
ptr++;
vol_end++;
if (ptr - linux_path >= linux_path_size) {
/* Ensure null termination */
linux_path[linux_path_size - 1] = '\0';
break;
}
}
return 0;
}
int isobusfs_check_current_dir_access(struct isobusfs_srv_priv *priv,
const char *path, size_t path_size)
{
char linux_path[ISOBUSFS_SRV_MAX_PATH_LEN];
int ret;
ret = isobusfs_path_to_linux_path(priv, path, path_size,
linux_path, sizeof(linux_path));
if (ret < 0)
return ret;
pr_debug("convert ISOBUS FS path to linux path: %.*s -> %s",
path_size, path, linux_path);
ret = isobusfs_cmn_dh_validate_dir_path(linux_path, false);
if (ret < 0)
return ret;
return 0;
}
/* current directory response function */
static int isobusfs_srv_dh_current_dir_res(struct isobusfs_srv_priv *priv,
struct isobusfs_msg *msg)
{
struct isobusfs_dh_get_cd_req *req =
(struct isobusfs_dh_get_cd_req *)msg->buf;
uint8_t error_code = ISOBUSFS_ERR_SUCCESS;
struct isobusfs_dh_get_cd_res *res;
struct isobusfs_srv_client *client;
size_t str_len, buf_size;
size_t fixed_res_size;
size_t padding_size = 0;
int ret;
client = isobusfs_srv_get_client_by_msg(priv, msg);
if (!client) {
pr_warn("client not found");
return -ENOENT;
}
if (client->current_dir[0] == '\0')
isobusfs_srv_set_default_current_dir(priv, client);
ret = isobusfs_check_current_dir_access(priv, client->current_dir,
sizeof(client->current_dir));
if (ret < 0) {
switch (ret) {
case -ENOENT:
error_code = ISOBUSFS_ERR_FILE_ORPATH_NOT_FOUND;
case -ENOMEDIUM:
error_code = ISOBUSFS_ERR_VOLUME_NOT_INITIALIZED;
case -ENOMEM:
error_code = ISOBUSFS_ERR_OUT_OF_MEM;
default:
error_code = ISOBUSFS_ERR_OTHER;
}
}
fixed_res_size = sizeof(*res);
str_len = strlen(client->current_dir) + 1;
buf_size = fixed_res_size + str_len;
if (buf_size > ISOBUSFS_MAX_TRANSFER_LENGH) {
pr_warn("current directory response too long");
/* Calculate the maximum allowed string length based on the
* buffer size
*/
str_len = ISOBUSFS_MAX_TRANSFER_LENGH - fixed_res_size;
/* Update the buffer size accordingly */
buf_size = fixed_res_size + str_len;
error_code = ISOBUSFS_ERR_OUT_OF_MEM;
} else if (buf_size < ISOBUSFS_MIN_TRANSFER_LENGH) {
/* Update the buffer size accordingly */
padding_size = ISOBUSFS_MIN_TRANSFER_LENGH - buf_size;
buf_size = ISOBUSFS_MIN_TRANSFER_LENGH;
}
res = malloc(buf_size);
if (!res) {
pr_err("failed to allocate memory for current directory response");
return -ENOMEM;
}
res->fs_function =
isobusfs_cg_function_to_buf(ISOBUSFS_CG_DIRECTORY_HANDLING,
ISOBUSFS_DH_F_GET_CURRENT_DIR_RES);
res->tan = req->tan;
res->error_code = error_code;
/* TODO: implement total_space and free_space */
res->total_space = htole16(0);
res->free_space = htole16(0);
res->name_len = htole16(str_len);
memcpy(res->name, client->current_dir, str_len);
if (padding_size) {
/* Fill the rest of the res structure with 0xff */
memset(((uint8_t *)res) + buf_size - padding_size, 0xff,
padding_size);
}
/* send to socket */
ret = isobusfs_srv_sendto(priv, msg, res, buf_size);
if (ret < 0) {
pr_warn("can't send current directory response");
goto free_res;
}
pr_debug("> tx: current directory response: %s, total space: %i, free space: %i",
client->current_dir, le16toh(res->total_space),
le16toh(res->free_space));
free_res:
free(res);
return ret;
}
/**
* isobusfs_is_forbidden_char() - check if the given character is forbidden
* @ch: character to check
*
* Return: true if the character is forbidden, false otherwise
*
* The function checks if the given character is forbidden in the ISOBUS FS
* as defined in ISO 11783-13:2021, section A.2.2.1 Names:
* To avoid incompatibility between different operating systems, the client
* shall not create folder/files with names, which only differs in case, and
* names shall not end with a '.' or include <, >, | (the latter three
* may cause issues on FAT32).
* ....
* LongNameChar ::= any single character defined by Unicode/ISO/IEC 10646,
* except 0x00 to 0x1f, 0x7f to 0x9f, \, *, ?, /.
*/
static bool isobusfs_is_forbidden_char(wchar_t ch)
{
if (ch >= 0x00 && ch <= 0x1f)
return true;
if (ch >= 0x7f && ch <= 0x9f)
return true;
if (ch == L'*' || ch == L'?' || ch == L'/' ||
ch == L'<' || ch == L'>' || ch == L'|')
return true;
return false;
}
static int isobusfs_validate_path_chars(const char *path, size_t size)
{
for (size_t i = 0; i < size; ++i) {
wchar_t ch = path[i];
if (isobusfs_is_forbidden_char(ch))
return -EINVAL;
}
return 0;
}
static int isobusfs_handle_path_prefix(const char *current_dir,
size_t current_dir_len,
const char *rel_path,
size_t rel_path_size,
size_t *rel_path_pos, char *abs_path,
size_t abs_path_size,
size_t *abs_path_pos)
{
if (strncmp(rel_path, "~\\", 2) == 0) {
size_t vol_len;
const char *vol_end;
vol_end = isobusfs_srv_get_volume_end(current_dir,
current_dir_len);
if (!vol_end)
return -EINVAL;
vol_len = vol_end - current_dir;
strncpy(abs_path, current_dir, vol_len);
abs_path[vol_len] = '\\';
*abs_path_pos = vol_len + 1;
} else if (strncmp(rel_path, "\\\\", 2) == 0) {
/* Too many back slashes, drop it. */
if (rel_path[2] == '\\')
return -EINVAL;
strncpy(abs_path, rel_path, 2);
*abs_path_pos = 2;
*rel_path_pos = 2;
} else {
strncpy(abs_path, current_dir, abs_path_size);
*abs_path_pos = current_dir_len;
if (abs_path[*abs_path_pos - 1] != '\\') {
if (*abs_path_pos < abs_path_size - 1) {
abs_path[*abs_path_pos] = '\\';
*abs_path_pos += 1;
} else {
return -ENOMEM;
}
}
if (rel_path[*rel_path_pos] == '\\')
*rel_path_pos += 1;
}
return 0;
}
/**
* is_valid_path_char - Check if the current character is valid in the path
* @rel_path: The relative path being processed
* @rel_path_size: The size of the relative path
* @rel_path_pos: Pointer to the current position in the relative path
*
* Checks if the current character at the position in the relative path
* is not the end of the string, not a null character, and not a backslash.
*
* Return: True if the current character is valid, False otherwise.
*/
static bool is_valid_path_char(const char *rel_path, size_t rel_path_size,
const size_t *rel_path_pos)
{
return *rel_path_pos < rel_path_size &&
rel_path[*rel_path_pos] != '\0' &&
rel_path[*rel_path_pos] != '\\';
}
/**
* Checks if the specified number of positions ahead in the relative path
* are either the end of the buffer or a backslash.
*
* @param rel_path The relative path being processed.
* @param rel_path_size The size of the relative path.
* @param rel_path_pos The current position in the relative path.
* @param look_ahead The number of positions ahead to check.
* @return True if the specified positions ahead are the end or a backslash,
* False otherwise.
*/
static bool is_end_or_backslash(const char *rel_path, size_t rel_path_size,
const size_t *rel_path_pos, size_t look_ahead)
{
if (*rel_path_pos + look_ahead >= rel_path_size)
return true; /* End of buffer */
return rel_path[*rel_path_pos + look_ahead] == '\\';
}
/**
* is_path_separator - Check if the current character is a path separator
* @rel_path: The relative path being processed
* @rel_path_size: The size of the relative path
* @rel_path_pos: Pointer to the current position in the relative path
*
* Checks if the current character at the position in the relative path
* is a backslash and the position is within the string size.
*
* Return: True if the current character is a backslash, False otherwise.
*/
static bool is_path_separator(const char *rel_path, size_t rel_path_size,
const size_t *rel_path_pos)
{
return *rel_path_pos < rel_path_size && rel_path[*rel_path_pos] == '\\';
}
static bool isobusfs_is_dot_directive(const char *rel_path,
size_t rel_path_size,
const size_t *rel_path_pos)
{
if (rel_path[*rel_path_pos] == '.') {
/* Check for '.' followed by a backslash or at the end of the
* string
*/
if (is_end_or_backslash(rel_path, rel_path_size,
rel_path_pos, 1) ||
rel_path[*rel_path_pos + 1] == '\0') {
return true;
}
/* Check for '..' followed by a backslash or at the end of the
* string
*/
if (rel_path[*rel_path_pos + 1] == '.') {
if (is_end_or_backslash(rel_path, rel_path_size,
rel_path_pos, 2) ||
rel_path[*rel_path_pos + 2] == '\0') {
return true;
}
}
}
return false;
}
/**
* isobusfs_handle_single_dot - Processes a single dot directive in a relative
* path
* @rel_path: The relative path being processed
* @rel_path_size: The size of the relative path
* @rel_path_pos: Pointer to the current position in the relative path
*
* This function checks if the current segment in the relative path is a single
* dot ('.'). A single dot represents the current directory. If the next
* character after the dot is either a backslash or the end of the string,
* the function advances the path position appropriately. The function returns
* true if it processes a single dot, indicating that the current directory
* directive was found and handled.
*
* Return: True if a single dot directive is detected, False otherwise.
*/
static bool isobusfs_handle_single_dot(const char *rel_path,
size_t rel_path_size,
size_t *rel_path_pos)
{
bool is_dot = false;
if (is_end_or_backslash(rel_path, rel_path_size, rel_path_pos, 1)) {
*rel_path_pos += 2;
is_dot = true;
} else if (rel_path[*rel_path_pos + 1] == '\0') {
*rel_path_pos += 1;
is_dot = true;
}
return is_dot;
}
/**
* isobusfs_handle_double_dots - Processes a double dot directive in a relative
* path
*
* @rel_path: The relative path being processed
* @rel_path_size: The size of the relative path
* @rel_path_pos: Pointer to the current position in the relative path
* @abs_path: Buffer to store the absolute path being constructed
* @abs_path_pos: Pointer to the current position in the absolute path buffer
*
* This function processes the double dot directive ('..') in a relative path.
* The double dot represents the parent directory. If the double dot directive
* is followed by a backslash or is at the end of the string, the function
* advances the path position accordingly. Additionally, it adjusts the
* absolute path position to move up one directory in the path hierarchy. The
* function ensures that it does not go beyond the root of the absolute path
* while moving up the directory hierarchy.
*/
static void isobusfs_handle_double_dots(const char *rel_path,
size_t rel_path_size,
size_t *rel_path_pos, char *abs_path,
size_t *abs_path_pos)
{
/* Move the relative path position forward after handling '..' */
if (is_end_or_backslash(rel_path, rel_path_size, rel_path_pos, 2))
*rel_path_pos += 3;
else if (rel_path[*rel_path_pos + 2] == '\0')
*rel_path_pos += 2;
/* Move the absolute path position backward to simulate moving up a
* directory
*/
if (*abs_path_pos > 2 && abs_path[*abs_path_pos - 1] == '\\')
*abs_path_pos -= 1;
while (*abs_path_pos > 2 && abs_path[*abs_path_pos - 1] != '\\')
*abs_path_pos -= 1;
}
/**
* isobusfs_handle_dot_directive - Processes '.' and '..' directives in a path
* @rel_path: The relative path being processed
* @rel_path_size: The size of the relative path
* @rel_path_pos: Pointer to the current position in the relative path
* @abs_path: Buffer to store the absolute path being constructed
* @abs_path_pos: Pointer to the current position in the absolute path buffer
*
* This function processes the dot directives found in a relative path. It
* handles both single dot ('.') and double dot ('..') directives. A single dot
* represents the current directory, while a double dot represents moving up to
* the parent directory.
*/
static void isobusfs_handle_dot_directive(const char *rel_path,
size_t rel_path_size,
size_t *rel_path_pos, char *abs_path,
size_t *abs_path_pos)
{
if (rel_path[*rel_path_pos] == '.') {
bool is_dot = isobusfs_handle_single_dot(rel_path, rel_path_size,
rel_path_pos);
if (!is_dot && rel_path[*rel_path_pos + 1] == '.') {
isobusfs_handle_double_dots(rel_path, rel_path_size,
rel_path_pos, abs_path,
abs_path_pos);
}
}
/* Skip additional backslashes after '.' or '..' */
while (is_path_separator(rel_path, rel_path_size, rel_path_pos))
*rel_path_pos += 1;
}
/**
* isobusfs_process_path_segment - Processes normal path segments
* @rel_path: The relative path being processed
* @rel_path_size: The size of the relative path
* @rel_path_pos: Pointer to the current position in the relative path
* @abs_path: The buffer to store the absolute path
* @abs_path_size: The size of the absolute path buffer
* @abs_path_pos: Pointer to the position in the absolute path buffer
*
* This function processes normal segments of a relative path, copying them
* into the absolute path buffer. It handles each character until it encounters
* a path separator or reaches the end of the relative path. If a path separator
* is found, it adds a single backslash to the absolute path. The function
* ensures that the buffer limits are respected to prevent buffer overflows.
*
* Return: 0 on successful processing of the segment, -ENOMEM if the absolute
* path buffer runs out of space.
*/
static int isobusfs_process_path_segment(const char *rel_path,
size_t rel_path_size,
size_t *rel_path_pos, char *abs_path,
size_t abs_path_size,
size_t *abs_path_pos)
{
/* Process the current character from the relative path */
abs_path[*abs_path_pos] = rel_path[*rel_path_pos];
*abs_path_pos += 1;
*rel_path_pos += 1;
/* Continue processing until a path separator or end of the string is
* reached
*/
while (is_valid_path_char(rel_path, rel_path_size, rel_path_pos)) {
if (*abs_path_pos >= abs_path_size - 1)
return -ENOMEM;
abs_path[*abs_path_pos] = rel_path[*rel_path_pos];
*rel_path_pos += 1;
*abs_path_pos += 1;
}
/* Add a single backslash if next character is a backslash */
if (is_path_separator(rel_path, rel_path_size, rel_path_pos)) {
*rel_path_pos += 1;
if (*abs_path_pos < abs_path_size - 1) {
abs_path[*abs_path_pos] = '\\';
*abs_path_pos += 1;
}
}
return 0;
}
static int isobusfs_handle_relative_path(const char *rel_path,
size_t rel_path_size,
size_t *rel_path_pos, char *abs_path,
size_t abs_path_size,
size_t *abs_path_pos)
{
int ret;
if (*abs_path_pos >= abs_path_size - 1)
return -ENOMEM;
/* Check for '.' or '..' followed by a backslash or at the end of the
* string
*/
if (isobusfs_is_dot_directive(rel_path, rel_path_size, rel_path_pos)) {
isobusfs_handle_dot_directive(rel_path, rel_path_size,
rel_path_pos, abs_path,
abs_path_pos);
} else {
/* Process normally for filenames or directories */
ret = isobusfs_process_path_segment(rel_path, rel_path_size,
rel_path_pos, abs_path,
abs_path_size,
abs_path_pos);
if (ret)
return ret;
}
return 0;
}
int isobusfs_convert_relative_to_absolute(struct isobusfs_srv_priv *priv,
const char *current_dir,
const char *rel_path,
size_t rel_path_size, char *abs_path,
size_t abs_path_size)
{
size_t abs_path_pos = 0;
size_t rel_path_pos = 0;
size_t current_dir_len;
int ret;
if (!current_dir || !rel_path || !abs_path || !rel_path_size ||
!abs_path_size)
return -EINVAL;
ret = isobusfs_validate_path_chars(rel_path, rel_path_size);
if (ret != 0)
return ret;
current_dir_len = strlen(current_dir);
if (current_dir_len >= abs_path_size)
return -ENOMEM;
if (current_dir_len == 0)
return -EINVAL;
ret = isobusfs_handle_path_prefix(current_dir, current_dir_len,
rel_path, rel_path_size,
&rel_path_pos, abs_path,
abs_path_size, &abs_path_pos);
if (ret)
return ret;
while (rel_path_pos < rel_path_size && rel_path[rel_path_pos] != '\0') {
ret = isobusfs_handle_relative_path(rel_path, rel_path_size,
&rel_path_pos, abs_path,
abs_path_size,
&abs_path_pos);
if (ret)
return ret;
}
abs_path[abs_path_pos] = '\0';
return 0;
}
/* change current directory response function */
static int isobusfs_srv_dh_ccd_res(struct isobusfs_srv_priv *priv,
struct isobusfs_msg *msg)
{
struct isobusfs_dh_ccd_req *req =
(struct isobusfs_dh_ccd_req *)msg->buf;
uint8_t error_code = ISOBUSFS_ERR_SUCCESS;
struct isobusfs_srv_client *client;
struct isobusfs_dh_ccd_res res;
size_t abs_path_len;
char *abs_path;
int ret;
/*
* We assime, the relative path stored in res->name is not longer
* than absolue path
*/
if (req->name_len > ISOBUSFS_SRV_MAX_PATH_LEN) {
pr_warn("path too long");
return -EINVAL;
}
client = isobusfs_srv_get_client_by_msg(priv, msg);
if (!client) {
pr_warn("client not found");
return -ENOENT;
}
abs_path_len = ISOBUSFS_SRV_MAX_PATH_LEN;
abs_path = malloc(abs_path_len);
if (!abs_path) {
pr_warn("failed to allocate memory");
return -ENOMEM;
}
pr_debug("< rx change current directory request from client 0x%2x: %.*s. Current directory: %s",
client->addr, req->name_len, req->name, client->current_dir);
/* Normalize provided string and convert it to absolute ISOBUS FS path */
ret = isobusfs_convert_relative_to_absolute(priv, client->current_dir,
(char *)req->name, req->name_len,
abs_path, abs_path_len);
if (ret < 0)
goto process_error;
pr_debug("converted relative to absolute ISOBUS FS internal path: %s", abs_path);
ret = isobusfs_check_current_dir_access(priv, abs_path, abs_path_len);
process_error:
if (ret < 0) {
/* linux_error_to_isobusfs_error() can't distinguish between
* -EINVAL vor SRC and DST, so we have to do it manually.
*/
if (ret == -EINVAL)
error_code = ISOBUSFS_ERR_INVALID_DST_NAME;
else
error_code = linux_error_to_isobusfs_error(ret);
} else {
/* change current directory */
strncpy(client->current_dir, abs_path, ISOBUSFS_SRV_MAX_PATH_LEN);
}
res.fs_function =
isobusfs_cg_function_to_buf(ISOBUSFS_CG_DIRECTORY_HANDLING,
ISOBUSFS_DH_F_CHANGE_CURRENT_DIR_RES);
res.tan = req->tan;
res.error_code = error_code;
memset(&res.reserved[0], 0xff, sizeof(res.reserved));
/* send to socket */
ret = isobusfs_srv_sendto(priv, msg, &res, sizeof(res));
if (ret < 0) {
pr_warn("can't send current directory response");
goto free_abs_path;
}
pr_debug("> tx: ccd response. Error code: %d", error_code);
free_abs_path:
free(abs_path);
return ret;
}
/* current directory response function */
/* Command group: directory handling */
int isobusfs_srv_rx_cg_dh(struct isobusfs_srv_priv *priv,
struct isobusfs_msg *msg)
{
int func = isobusfs_buf_to_function(msg->buf);
int ret = 0;
switch (func) {
case ISOBUSFS_DH_F_GET_CURRENT_DIR_REQ:
return isobusfs_srv_dh_current_dir_res(priv, msg);
case ISOBUSFS_DH_F_CHANGE_CURRENT_DIR_REQ:
return isobusfs_srv_dh_ccd_res(priv, msg);
default:
goto not_supported;
}
return ret;
not_supported:
isobusfs_srv_send_error(priv, msg, ISOBUSFS_ERR_FUNC_NOT_SUPPORTED);
pr_warn("%s: unsupported function: %i", __func__, func);
/* Not a critical error */
return 0;
}

View File

@ -0,0 +1,919 @@
// SPDX-License-Identifier: GPL-2.0-only
// SPDX-FileCopyrightText: 2023 Oleksij Rempel <linux@rempel-privat.de>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#include "isobusfs_srv.h"
#include "isobusfs_cmn_fa.h"
static struct isobusfs_srv_handles *
isobusfs_srv_walk_handles(struct isobusfs_srv_priv *priv, const char *path)
{
int i;
for (i = 0; i < ARRAY_SIZE(priv->handles); i++) {
if (priv->handles[i].path == NULL)
continue;
if (!strcmp(priv->handles[i].path, path))
return &priv->handles[i];
}
return NULL;
}
static int isobusfs_srv_add_file(struct isobusfs_srv_priv *priv,
const char *path, int fd, DIR *dir)
{
int j;
if (priv->handles_count >= ARRAY_SIZE(priv->handles)) {
pr_err("too many handles");
return -ENOSPC;
}
for (j = 0; j < ARRAY_SIZE(priv->handles); j++) {
if (priv->handles[j].path == NULL)
break;
}
priv->handles[j].path = strdup(path);
priv->handles[j].fd = fd;
priv->handles[j].dir = dir;
priv->handles_count++;
return j;
}
static int isobusfs_srv_add_client_to_file(struct isobusfs_srv_handles *file,
struct isobusfs_srv_client *client)
{
int j;
for (j = 0; j < ARRAY_SIZE(file->clients); j++) {
if (file->clients[j] == client)
return 0;
}
for (j = 0; j < ARRAY_SIZE(file->clients); j++) {
if (file->clients[j] == NULL) {
file->clients[j] = client;
file->refcount++;
return 0;
}
}
pr_err("%s: can't add client to file", __func__);
return -ENOENT;
}
static int isobusfs_srv_request_file(struct isobusfs_srv_priv *priv,
struct isobusfs_srv_client *client,
const char *path, int fd, DIR *dir)
{
struct isobusfs_srv_handles *file;
int handle, ret;
file = isobusfs_srv_walk_handles(priv, path);
if (!file) {
handle = isobusfs_srv_add_file(priv, path, fd, dir);
if (handle < 0)
return handle;
file = &priv->handles[handle];
} else {
handle = file - priv->handles;
}
ret = isobusfs_srv_add_client_to_file(file, client);
if (ret < 0)
return ret;
return handle;
}
static struct isobusfs_srv_handles *
isobusfs_srv_get_handle(struct isobusfs_srv_priv *priv, int handle)
{
if (handle < 0 || handle >= ARRAY_SIZE(priv->handles))
return NULL;
return &priv->handles[handle];
}
static int isobusfs_srv_release_handle(struct isobusfs_srv_priv *priv,
struct isobusfs_srv_client *client,
int handle)
{
struct isobusfs_srv_handles *hdl = isobusfs_srv_get_handle(priv, handle);
int client_index;
if (!hdl) {
pr_warn("%s: invalid handle %d", __func__, handle);
return -ENOENT;
}
/* Find the client in the hdl's client list and remove it */
for (client_index = 0; client_index < ARRAY_SIZE(hdl->clients); client_index++) {
if (hdl->clients[client_index] == client) {
hdl->clients[client_index] = NULL;
hdl->refcount--;
pr_debug("%s: client %p removed from handle %d", __func__, client, handle);
/* If refcount is 0, close the hdl and remove it from the list */
if (hdl->refcount == 0) {
pr_debug("%s: closing handle %d", __func__, handle);
/* fd will be automatically closed when
* closedir(3) is called.
*/
if (hdl->dir)
closedir(hdl->dir);
else
close(hdl->fd);
memset(hdl, 0, sizeof(*hdl));
priv->handles_count--;
}
return 0;
}
}
pr_err("%s: client %p not found in handle %d", __func__, client, handle);
return -ENOENT;
}
void isobusfs_srv_remove_client_from_handles(struct isobusfs_srv_priv *priv,
struct isobusfs_srv_client *client)
{
int handle;
int client_index;
for (handle = 0; handle < ARRAY_SIZE(priv->handles); handle++) {
struct isobusfs_srv_handles *hdl = &priv->handles[handle];
if (hdl->path == NULL)
continue;
for (client_index = 0; client_index < ARRAY_SIZE(hdl->clients); client_index++) {
if (hdl->clients[client_index] == client) {
hdl->clients[client_index] = NULL;
hdl->refcount--;
if (hdl->refcount == 0) {
close(hdl->fd);
memset(hdl, 0, sizeof(*hdl));
priv->handles_count--;
}
break;
}
}
}
}
static int isobusfs_srv_fa_open_directory(struct isobusfs_srv_priv *priv,
struct isobusfs_srv_client *client,
const char *path, size_t path_len,
uint8_t *handle)
{
char linux_path[ISOBUSFS_SRV_MAX_PATH_LEN];
struct isobusfs_srv_handles *hdl;
struct stat file_stat;
int file_index, fd;
DIR *dir;
int ret;
ret = isobusfs_path_to_linux_path(priv, path, path_len, linux_path, sizeof(linux_path));
if (ret < 0)
return ret;
hdl = isobusfs_srv_walk_handles(priv, linux_path);
if (hdl) {
pr_err("%s: Path %s is already opened\n", __func__, linux_path);
return ISOBUSFS_ERR_OTHER;
}
dir = opendir(linux_path);
if (!dir) {
pr_err("%s: Error opening directory %s. Error %d (%s)\n",
__func__, linux_path, errno, strerror(errno));
switch (errno) {
case EACCES:
return ISOBUSFS_ERR_ACCESS_DENIED;
case ENOENT:
return ISOBUSFS_ERR_FILE_ORPATH_NOT_FOUND;
case ENOMEM:
return ISOBUSFS_ERR_OUT_OF_MEM;
default:
return ISOBUSFS_ERR_OTHER;
}
}
fd = dirfd(dir);
if (fd < 0) {
pr_err("%s: Error getting file descriptor for directory %s. Error %d (%s)\n",
__func__, linux_path, errno, strerror(errno));
closedir(dir);
return ISOBUSFS_ERR_OTHER;
}
if (fstat(fd, &file_stat) < 0 || !S_ISDIR(file_stat.st_mode)) {
pr_err("%s: Path %s is not a directory\n", __func__,
linux_path);
closedir(dir);
return ISOBUSFS_ERR_INVALID_ACCESS;
}
file_index = isobusfs_srv_request_file(priv, client, linux_path, fd,
dir);
if (file_index < 0) {
closedir(dir);
return file_index;
}
*handle = (uint8_t)file_index;
return 0;
}
static int isobusfs_srv_fa_open_file(struct isobusfs_srv_priv *priv,
struct isobusfs_srv_client *client,
const char *path, size_t path_len,
uint8_t flags, uint8_t *handle)
{
char linux_path[ISOBUSFS_SRV_MAX_PATH_LEN];
struct isobusfs_srv_handles *hdl;
struct stat file_stat;
int open_flags = 0;
int file_index;
int ret, fd;
ret = isobusfs_path_to_linux_path(priv, path, path_len,
linux_path, sizeof(linux_path));
if (ret < 0)
return ret;
pr_debug("convert ISOBUS FS path to linux path: %.*s -> %s",
path_len, path, linux_path);
/* Determine open flags based on the requested access type */
switch (flags & ISOBUSFS_FA_OPEN_MASK) {
case ISOBUSFS_FA_OPEN_FILE_RO:
open_flags |= O_RDONLY;
break;
case ISOBUSFS_FA_OPEN_FILE_WO:
open_flags |= O_WRONLY;
break;
case ISOBUSFS_FA_OPEN_FILE_WR:
open_flags |= O_RDWR;
if (!(flags & ISOBUSFS_FA_OPEN_APPEND))
open_flags |= O_TRUNC;
break;
default:
return ISOBUSFS_ERR_INVALID_ACCESS;
}
if (flags & ISOBUSFS_FA_OPEN_APPEND)
open_flags |= O_APPEND;
/* Check if the file is already opened */
hdl = isobusfs_srv_walk_handles(priv, linux_path);
if (hdl) {
pr_warn("Handle: %s is already opened by client: %x\n",
linux_path, client->addr);
fd = hdl->fd;
} else {
/* Open the file if not already opened */
fd = open(linux_path, open_flags);
if (fd < 0) {
switch (errno) {
case EACCES:
return ISOBUSFS_ERR_ACCESS_DENIED;
case EINVAL:
return ISOBUSFS_ERR_INVALID_ACCESS;
case EMFILE:
case ENFILE:
return ISOBUSFS_ERR_TOO_MANY_FILES_OPEN;
case ENOENT:
return ISOBUSFS_ERR_FILE_ORPATH_NOT_FOUND;
case ENOMEM:
return ISOBUSFS_ERR_OUT_OF_MEM;
default:
return ISOBUSFS_ERR_OTHER;
}
}
/* Check if the opened path is a regular file */
if (fstat(fd, &file_stat) < 0) {
close(fd);
return ISOBUSFS_ERR_OTHER;
}
if (!S_ISREG(file_stat.st_mode)) {
close(fd);
/* Invalid access (not a regular file) */
return ISOBUSFS_ERR_INVALID_ACCESS;
}
}
/* Request the file, which also handles refcount and client list
* updates
*/
file_index = isobusfs_srv_request_file(priv, client, linux_path, fd,
NULL);
if (file_index < 0) {
close(fd);
return file_index;
}
*handle = (uint8_t)file_index;
return 0;
}
static int isobusfs_srv_fa_open_file_req(struct isobusfs_srv_priv *priv,
struct isobusfs_msg *msg)
{
struct isobusfs_fa_openf_req *req =
(struct isobusfs_fa_openf_req *)msg->buf;
uint16_t name_len = le16toh(req->name_len);
struct isobusfs_srv_client *client;
struct isobusfs_fa_openf_res res;
uint8_t error_code = 0;
uint8_t access_type;
size_t abs_path_len;
char *abs_path;
uint8_t handle;
int ret = 0;
client = isobusfs_srv_get_client_by_msg(priv, msg);
if (!client) {
pr_warn("client not found");
error_code = ISOBUSFS_ERR_OTHER;
goto send_response;
}
if (name_len > msg->len - sizeof(*req)) {
error_code = ISOBUSFS_ERR_INVALID_ACCESS;
goto send_response;
}
/* Perform checks on the received request, e.g., validate path length */
if (name_len > ISOBUSFS_MAX_PATH_NAME_LENGTH) {
error_code = ISOBUSFS_ERR_INVALID_ACCESS;
goto send_response;
}
abs_path_len = ISOBUSFS_SRV_MAX_PATH_LEN;
abs_path = malloc(abs_path_len);
if (!abs_path) {
pr_warn("failed to allocate memory");
return -ENOMEM;
}
if (client->current_dir[0] == '\0')
isobusfs_srv_set_default_current_dir(priv, client);
/* Normalize provided string and convert it to absolute ISOBUS FS path */
ret = isobusfs_convert_relative_to_absolute(priv, client->current_dir,
(char *)req->name, req->name_len,
abs_path, abs_path_len);
if (ret < 0) {
error_code = ISOBUSFS_ERR_FILE_ORPATH_NOT_FOUND;
goto send_response;
}
pr_debug("< rx: Open File Request. from client 0x%2x: %.*s. Current directory: %s",
client->addr, req->name_len, req->name, client->current_dir);
access_type = FIELD_GET(ISOBUSFS_FA_OPEN_MASK, req->flags);
if (access_type == ISOBUSFS_FA_OPEN_DIR) {
error_code = isobusfs_srv_fa_open_directory(priv, client, abs_path,
abs_path_len, &handle);
} else {
error_code = isobusfs_srv_fa_open_file(priv, client, abs_path,
abs_path_len, req->flags,
&handle);
}
send_response:
res.fs_function =
isobusfs_cg_function_to_buf(ISOBUSFS_CG_FILE_ACCESS,
ISOBUSFS_FA_F_OPEN_FILE_RES);
res.tan = req->tan;
res.error_code = error_code;
res.handle = handle;
memset(&res.reserved[0], 0xff, sizeof(res.reserved));
/* send to socket */
ret = isobusfs_srv_sendto(priv, msg, &res, sizeof(res));
if (ret < 0) {
pr_warn("can't send current directory response");
goto err;
}
pr_debug("> tx: Open File Response. Error code: %d (%s).", error_code,
isobusfs_error_to_str(error_code));
err:
return ret;
}
static int isobusfs_srv_read_file(struct isobusfs_srv_handles *handle,
uint8_t *buffer, size_t count,
ssize_t *readed_size)
{
*readed_size = read(handle->fd, buffer, count);
if (*readed_size < 0) {
switch (errno) {
case EBADF:
return ISOBUSFS_ERR_INVALID_HANDLE;
case EFAULT:
return ISOBUSFS_ERR_OUT_OF_MEM;
case EIO:
return ISOBUSFS_ERR_ON_READ;
default:
return ISOBUSFS_ERR_OTHER;
}
}
return 0;
}
static uint16_t convert_to_file_date(time_t time_val)
{
struct tm *timeinfo = localtime(&time_val);
int year, month, day;
if (!timeinfo)
return 0;
year = timeinfo->tm_year + 1900 - 1980;
month = timeinfo->tm_mon + 1;
day = timeinfo->tm_mday;
if (year < 0 || year > 127)
return 0;
return (year << 9) | (month << 5) | day;
}
static uint16_t convert_to_file_time(time_t time_val)
{
struct tm *timeinfo = localtime(&time_val);
int hours, minutes, seconds;
uint16_t time;
if (!timeinfo)
return 0;
hours = timeinfo->tm_hour;
minutes = timeinfo->tm_min;
seconds = timeinfo->tm_sec / 2;
time = (hours << 11) | (minutes << 5) | seconds;
return time;
}
static int check_access_with_base(const char *base_dir,
const char *relative_path, int mode)
{
char full_path[ISOBUSFS_SRV_MAX_PATH_LEN];
if (snprintf(full_path, sizeof(full_path), "%s/%s", base_dir,
relative_path) >= sizeof(full_path)) {
return -ENAMETOOLONG;
}
return access(full_path, mode);
}
static int isobusfs_srv_read_directory(struct isobusfs_srv_handles *handle,
uint8_t *buffer, size_t count,
ssize_t *readed_size)
{
DIR *dir = handle->dir;
struct dirent *entry;
size_t pos = 0;
size_t entry_count = 0;
/*
* Position the directory stream to the previously stored offset (handle->dir_pos).
*
* Handling Changes in Directory Contents:
* - If the directory contents change between reads (e.g., files/directories added or deleted),
* handle->dir_pos may not point to the expected entry.
* - To ensure consistency, implement checks (e.g., compare inode numbers) to verify the correct
* entry position.
*
* Detecting End of Directory:
* - If readdir() returns NULL before reaching handle->dir_pos, it indicates the end of the
* directory with no more entries to read.
* - This situation should be handled appropriately, such as by resetting handle->dir_pos and
* either returning an error or restarting from the beginning of the directory, depending
* on the application's requirements.
*/
for (size_t i = 0; i < handle->dir_pos &&
(entry = readdir(dir)) != NULL; i++) {
/* Iterating to the desired position */
}
/*
* Directory Entry Layout:
* This loop reads directory entries and encodes them into a buffer.
* Each entry in the buffer follows the format specified in ISO 11783-13:2021.
*
* The layout of each directory entry in the buffer is as follows:
* - Byte 1: Filename Length (as per ISO 11783-13:2021 B.22).
* Represents the length of the filename that follows.
*
* - Byte 2n: Filename (as per ISO 11783-13:2021 B.23).
* The actual name of the file or directory.
*
* - Byte n + 1: Attributes (as per ISO 11783-13:2021 B.15).
*
* - Bytes n + 2, n + 3: File Date (as per ISO 11783-13:2021 B.24).
* Encoded file date, using a 16-bit format derived from file_stat.st_mtime.
*
* - Bytes n + 4, n + 5: File Time (as per ISO 11783-13:2021 B.25).
* Encoded file time, using a 16-bit format derived from file_stat.st_mtime.
*
* - Bytes n + 6 n + 9: Size (as per ISO 11783-13:2021 B.26).
* The size of the file in bytes, encoded in a 32-bit little-endian format.
*
* The handle->dir_pos is incremented after processing each entry, marking
* the current position in the directory stream for subsequent reads.
*/
while ((entry = readdir(dir)) != NULL) {
size_t entry_name_len, entry_total_len;
__le16 file_date, file_time;
uint8_t attributes = 0;
struct stat file_stat;
__le32 size;
if (check_access_with_base(handle->path, entry->d_name, R_OK) != 0)
continue; /* Skip this entry if it's not readable */
if (fstatat(handle->fd, entry->d_name, &file_stat, 0) < 0)
continue; /* Skip this entry on error */
entry_name_len = strlen(entry->d_name);
if (entry_name_len > ISOBUSFS_MAX_DIR_ENTRY_NAME_LENGTH)
continue;
entry_total_len = 1 + entry_name_len + 1 + 2 + 2 + 4;
if (pos + entry_total_len > count)
break;
buffer[pos++] = (uint8_t)entry_name_len;
memcpy(buffer + pos, entry->d_name, entry_name_len);
pos += entry_name_len;
if (S_ISDIR(file_stat.st_mode))
attributes |= ISOBUSFS_ATTR_DIRECTORY;
if (check_access_with_base(handle->path, entry->d_name, W_OK) != 0)
attributes |= ISOBUSFS_ATTR_READ_ONLY;
buffer[pos++] = attributes;
file_date = htole16(convert_to_file_date(file_stat.st_mtime));
memcpy(buffer + pos, &file_date, sizeof(file_date));
pos += sizeof(file_date);
file_time = htole16(convert_to_file_time(file_stat.st_mtime));
memcpy(buffer + pos, &file_time, sizeof(file_time));
pos += sizeof(file_time);
size = htole32(file_stat.st_size);
memcpy(buffer + pos, &size, sizeof(size));
pos += sizeof(size);
entry_count++;
}
*readed_size = pos;
return 0;
}
static int isobusfs_srv_fa_rf_req(struct isobusfs_srv_priv *priv,
struct isobusfs_msg *msg)
{
uint8_t res_fail[8] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
struct isobusfs_read_file_response *res;
struct isobusfs_srv_handles *handle;
struct isobusfs_srv_client *client;
struct isobusfs_fa_readf_req *req;
ssize_t readed_size = 0;
uint8_t error_code = 0;
ssize_t send_size;
int ret = 0;
int count;
req = (struct isobusfs_fa_readf_req *)msg->buf;
count = le16toh(req->count);
pr_debug("< rx: Read File Request. tan: %d, handle: %d, count: %d",
req->tan, req->handle, count);
/* C.3.5.1 Read File, General:
* The requested data (excluding the other parameters) is sent in
* the response (up to 1 780 bytes when TP is used, up to 65 530 bytes
* when ETP is used). The number of data bytes read can be less than
* requested if the end of the file is reached.
* TODO: currently we are not able to detect support transport mode,
* so ETP is assumed.
*/
if (count > ISOBUSFS_MAX_DATA_LENGH)
count = ISOBUSFS_MAX_DATA_LENGH;
res = malloc(sizeof(*res) + count);
if (!res) {
pr_warn("failed to allocate memory");
res = (struct isobusfs_read_file_response *)&res_fail[0];
error_code = ISOBUSFS_ERR_OUT_OF_MEM;
goto send_response;
}
client = isobusfs_srv_get_client_by_msg(priv, msg);
if (!client) {
pr_warn("client not found");
error_code = ISOBUSFS_ERR_OTHER;
goto send_response;
}
handle = isobusfs_srv_get_handle(priv, req->handle);
if (!handle) {
pr_warn("failed to find file with handle: %x", req->handle);
error_code = ISOBUSFS_ERR_FILE_ORPATH_NOT_FOUND;
}
/* Determine whether to read a file or a directory */
if (handle->dir) {
ret = isobusfs_srv_read_directory(handle, res->data, count,
&readed_size);
} else {
ret = isobusfs_srv_read_file(handle, res->data, count,
&readed_size);
}
if (ret < 0) {
error_code = ret;
readed_size = 0;
} else if (count != 0 && readed_size == 0) {
error_code = ISOBUSFS_ERR_END_OF_FILE;
}
send_response:
res->fs_function =
isobusfs_cg_function_to_buf(ISOBUSFS_CG_FILE_ACCESS,
ISOBUSFS_FA_F_READ_FILE_RES);
res->tan = req->tan;
res->error_code = error_code;
res->count = htole16(readed_size);
send_size = sizeof(*res) + readed_size;
if (send_size < ISOBUSFS_MIN_TRANSFER_LENGH)
send_size = ISOBUSFS_MIN_TRANSFER_LENGH;
/* send to socket */
ret = isobusfs_srv_sendto(priv, msg, res, send_size);
if (ret < 0) {
pr_warn("can't send Read File Response");
goto free_res;
}
pr_debug("> tx: Read File Response. Error code: %d (%s), readed size: %d",
error_code, isobusfs_error_to_str(error_code), readed_size);
free_res:
free(res);
return ret;
}
static int isobusfs_srv_seek(struct isobusfs_srv_priv *priv,
struct isobusfs_srv_handles *handle, int32_t offset,
uint8_t position_mode)
{
int whence;
off_t offs;
switch (position_mode) {
case ISOBUSFS_FA_SEEK_SET:
whence = SEEK_SET;
if (offset < 0) {
pr_warn("Invalid offset. Offset must be positive.");
return ISOBUSFS_ERR_INVALID_REQUESTED_LENGHT;
}
break;
case ISOBUSFS_FA_SEEK_CUR:
whence = SEEK_CUR;
if (offset < 0 && handle->offset < -offset) {
pr_warn("Invalid offset. Negative offset is too big.");
return ISOBUSFS_ERR_INVALID_REQUESTED_LENGHT;
}
break;
case ISOBUSFS_FA_SEEK_END:
whence = SEEK_END;
if (offset > 0) {
pr_warn("Invalid offset. Offset must be negative");
return ISOBUSFS_ERR_INVALID_REQUESTED_LENGHT;
}
break;
default:
pr_warn("invalid position mode");
return ISOBUSFS_ERR_OTHER;
}
/* seek file */
offs = lseek(handle->fd, offset, whence);
if (offs < 0) {
pr_warn("Failed to seek file");
switch (offs) {
case EBADF:
return ISOBUSFS_ERR_INVALID_HANDLE;
case EINVAL:
return ISOBUSFS_ERR_INVALID_REQUESTED_LENGHT;
case ENXIO:
return ISOBUSFS_ERR_END_OF_FILE;
case EOVERFLOW:
return ISOBUSFS_ERR_OUT_OF_MEM;
case ESPIPE:
return ISOBUSFS_ERR_ACCESS_DENIED;
default:
return ISOBUSFS_ERR_OTHER;
}
}
handle->offset = offs;
return ISOBUSFS_ERR_SUCCESS;
}
static int isobusfs_srv_seek_directory(struct isobusfs_srv_handles *handle,
int32_t offset)
{
DIR *dir = fdopendir(handle->fd);
if (!dir)
return ISOBUSFS_ERR_OTHER;
rewinddir(dir);
for (int32_t i = 0; i < offset; i++) {
if (readdir(dir) == NULL)
return ISOBUSFS_ERR_END_OF_FILE;
}
handle->dir_pos = offset;
return ISOBUSFS_ERR_SUCCESS;
}
static int isobusfs_srv_fa_sf_req(struct isobusfs_srv_priv *priv,
struct isobusfs_msg *msg)
{
struct isobusfs_fa_seekf_res res = {0};
struct isobusfs_srv_client *client;
struct isobusfs_fa_seekf_req *req;
struct isobusfs_srv_handles *handle;
int32_t offset_out = 0;
uint8_t error_code = 0;
int ret;
req = (struct isobusfs_fa_seekf_req *)msg->buf;
pr_debug("< rx: Seek File Request. Handle: %x, offset: %d, position mode: %d",
req->handle, le32toh(req->offset), req->position_mode);
client = isobusfs_srv_get_client_by_msg(priv, msg);
if (!client) {
pr_warn("client not found");
error_code = ISOBUSFS_ERR_OTHER;
goto send_response;
}
handle = isobusfs_srv_get_handle(priv, req->handle);
if (!handle) {
pr_warn("failed to find handle: %x", req->handle);
error_code = ISOBUSFS_ERR_INVALID_HANDLE;
goto send_response;
}
if (handle->dir) {
error_code = isobusfs_srv_seek_directory(handle,
le32toh(req->offset));
res.position = htole32(handle->dir_pos);
} else {
error_code = isobusfs_srv_seek(priv, handle, le32toh(req->offset),
req->position_mode);
res.position = htole32(handle->offset);
}
send_response:
res.fs_function =
isobusfs_cg_function_to_buf(ISOBUSFS_CG_FILE_ACCESS,
ISOBUSFS_FA_F_SEEK_FILE_RES);
res.tan = req->tan;
res.error_code = error_code;
/* send to socket */
ret = isobusfs_srv_sendto(priv, msg, &res, sizeof(res));
if (ret < 0) {
pr_warn("can't send seek file response");
return ret;
}
pr_debug("> tx: Seek File Response. Error code: %d, offset: %d",
error_code, offset_out);
return 0;
}
static int isobusfs_srv_fa_cf_req(struct isobusfs_srv_priv *priv,
struct isobusfs_msg *msg)
{
struct isobusfs_close_file_request *req;
struct isobusfs_close_file_res res;
struct isobusfs_srv_client *client;
uint8_t error_code = 0;
int ret;
req = (struct isobusfs_close_file_request *)msg->buf;
client = isobusfs_srv_get_client_by_msg(priv, msg);
if (!client) {
pr_warn("client not found");
error_code = ISOBUSFS_ERR_OTHER;
goto send_response;
}
ret = isobusfs_srv_release_handle(priv, client, req->handle);
if (ret < 0) {
pr_warn("failed to release handle: %x", req->handle);
switch (ret) {
case -ENOENT:
error_code = ISOBUSFS_ERR_FILE_ORPATH_NOT_FOUND;
break;
default:
error_code = ISOBUSFS_ERR_OTHER;
}
}
send_response:
res.fs_function =
isobusfs_cg_function_to_buf(ISOBUSFS_CG_FILE_ACCESS,
ISOBUSFS_FA_F_CLOSE_FILE_RES);
res.tan = req->tan;
res.error_code = error_code;
memset(&res.reserved[0], 0xff, sizeof(res.reserved));
/* send to socket */
ret = isobusfs_srv_sendto(priv, msg, &res, sizeof(res));
if (ret < 0) {
pr_warn("can't send current directory response");
goto err;
}
pr_debug("> tx: Close File Response. Error code: %d", error_code);
err:
return ret;
}
/* Command group: file access */
int isobusfs_srv_rx_cg_fa(struct isobusfs_srv_priv *priv,
struct isobusfs_msg *msg)
{
int func = isobusfs_buf_to_function(msg->buf);
int ret = 0;
switch (func) {
case ISOBUSFS_FA_F_OPEN_FILE_REQ:
ret = isobusfs_srv_fa_open_file_req(priv, msg);
break;
case ISOBUSFS_FA_F_CLOSE_FILE_REQ:
ret = isobusfs_srv_fa_cf_req(priv, msg);
break;
case ISOBUSFS_FA_F_READ_FILE_REQ:
ret = isobusfs_srv_fa_rf_req(priv, msg);
break;
case ISOBUSFS_FA_F_SEEK_FILE_REQ:
ret = isobusfs_srv_fa_sf_req(priv, msg);
break;
case ISOBUSFS_FA_F_WRITE_FILE_REQ: /* fall through */
default:
pr_warn("%s: unsupported function: %i", __func__, func);
isobusfs_srv_send_error(priv, msg,
ISOBUSFS_ERR_FUNC_NOT_SUPPORTED);
}
return ret;
}

View File

@ -0,0 +1,30 @@
// SPDX-License-Identifier: GPL-2.0-only
// SPDX-FileCopyrightText: 2023 Oleksij Rempel <linux@rempel-privat.de>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include "isobusfs_srv.h"
/* Command group: file handling */
int isobusfs_srv_rx_cg_fh(struct isobusfs_srv_priv *priv,
struct isobusfs_msg *msg)
{
int func = isobusfs_buf_to_function(msg->buf);
int ret = 0;
switch (func) {
case ISOBUSFS_FH_F_MOVE_FILE_REQ:
case ISOBUSFS_FH_F_DELETE_FILE_REQ:
case ISOBUSFS_FH_F_GET_FILE_ATTR_REQ:
case ISOBUSFS_FH_F_SET_FILE_ATTR_REQ:
case ISOBUSFS_FH_F_GET_FILE_DATETIME_REQ:
default:
isobusfs_srv_send_error(priv, msg,
ISOBUSFS_ERR_FUNC_NOT_SUPPORTED);
pr_warn("%s: unsupported function: %i", __func__, func);
}
return ret;
}

View File

@ -0,0 +1,27 @@
// SPDX-License-Identifier: GPL-2.0-only
// SPDX-FileCopyrightText: 2023 Oleksij Rempel <linux@rempel-privat.de>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include "isobusfs_srv.h"
/* Command group: volume hnadling */
int isobusfs_srv_rx_cg_vh(struct isobusfs_srv_priv *priv,
struct isobusfs_msg *msg)
{
int func = isobusfs_buf_to_function(msg->buf);
int ret = 0;
switch (func) {
/* TODO: currently not implemented */
case ISOBUSFS_VA_F_INITIALIZE_VOLUME_REQ: /* fall through */
default:
isobusfs_srv_send_error(priv, msg,
ISOBUSFS_ERR_FUNC_NOT_SUPPORTED);
pr_warn("%s: unsupported function: %i", __func__, func);
}
return ret;
}

18
lib.c
View File

@ -704,3 +704,21 @@ void snprintf_can_error_frame(char *buf, size_t len, const struct canfd_frame *c
n += snprintf_error_cnt(buf + n, len - n, cf);
}
}
int64_t timespec_diff_ms(struct timespec *ts1,
struct timespec *ts2)
{
int64_t diff = (ts1->tv_sec - ts2->tv_sec) * 1000;
diff += (ts1->tv_nsec - ts2->tv_nsec) / 1000000;
return diff;
}
void timespec_add_ms(struct timespec *ts, uint64_t milliseconds)
{
uint64_t total_ns = ts->tv_nsec + (milliseconds * 1000000);
ts->tv_sec += total_ns / 1000000000;
ts->tv_nsec = total_ns % 1000000000;
}

17
lib.h
View File

@ -45,6 +45,7 @@
#ifndef CAN_UTILS_LIB_H
#define CAN_UTILS_LIB_H
#include <stdint.h>
#include <stdio.h>
#ifdef DEBUG
@ -229,4 +230,20 @@ void snprintf_can_error_frame(char *buf, size_t len, const struct canfd_frame *c
* Creates a CAN error frame output in user readable format.
*/
/**
* timespec_diff_ms - calculate timespec difference in milliseconds
* @ts1: first timespec
* @ts2: second timespec
*
* Return negative difference if in the past.
*/
int64_t timespec_diff_ms(struct timespec *ts1, struct timespec *ts2);
/**
* timespec_add_ms - add milliseconds to timespec
* @ts: timespec
* @milliseconds: milliseconds to add
*/
void timespec_add_ms(struct timespec *ts, uint64_t milliseconds);
#endif