Merge pull request #487 from olerem/isobusfs
Implement ISOBUS File Server (FS) Interface as a Personal Projectpull/495/head
commit
8a2619c68d
|
|
@ -38,6 +38,8 @@ GNUmakefile.in
|
||||||
/cansend
|
/cansend
|
||||||
/cansequence
|
/cansequence
|
||||||
/cansniffer
|
/cansniffer
|
||||||
|
/isobusfs-cli
|
||||||
|
/isobusfs-srv
|
||||||
/isotpdump
|
/isotpdump
|
||||||
/isotpperf
|
/isotpperf
|
||||||
/isotprecv
|
/isotprecv
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,11 @@ set(PROGRAMS_J1939
|
||||||
testj1939
|
testj1939
|
||||||
)
|
)
|
||||||
|
|
||||||
|
set(PROGRAMS_ISOBUSFS
|
||||||
|
isobusfs-srv
|
||||||
|
isobusfs-cli
|
||||||
|
)
|
||||||
|
|
||||||
set(PROGRAMS
|
set(PROGRAMS
|
||||||
${PROGRAMS_CANLIB}
|
${PROGRAMS_CANLIB}
|
||||||
canfdtest
|
canfdtest
|
||||||
|
|
@ -86,6 +91,7 @@ add_executable(mcp251xfd-dump
|
||||||
|
|
||||||
if(NOT ANDROID)
|
if(NOT ANDROID)
|
||||||
list(APPEND PROGRAMS ${PROGRAMS_J1939})
|
list(APPEND PROGRAMS ${PROGRAMS_J1939})
|
||||||
|
list(APPEND PROGRAMS ${PROGRAMS_ISOBUSFS})
|
||||||
|
|
||||||
add_library(j1939 STATIC
|
add_library(j1939 STATIC
|
||||||
libj1939.c
|
libj1939.c
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,14 @@ LDADD = \
|
||||||
|
|
||||||
noinst_HEADERS = \
|
noinst_HEADERS = \
|
||||||
canframelen.h \
|
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 \
|
lib.h \
|
||||||
libj1939.h \
|
libj1939.h \
|
||||||
terminal.h \
|
terminal.h \
|
||||||
|
|
@ -75,6 +83,13 @@ EXTRA_DIST += \
|
||||||
mcp251xfd/devcoredump \
|
mcp251xfd/devcoredump \
|
||||||
mcp251xfd/mcp251xfd-gen-testdata.sh
|
mcp251xfd/mcp251xfd-gen-testdata.sh
|
||||||
|
|
||||||
|
lib_LTLIBRARIES = \
|
||||||
|
libisobusfs.la
|
||||||
|
|
||||||
|
libisobusfs_la_SOURCES = \
|
||||||
|
isobusfs/isobusfs_cmn.c \
|
||||||
|
isobusfs/isobusfs_cmn_dh.c
|
||||||
|
|
||||||
bin_PROGRAMS = \
|
bin_PROGRAMS = \
|
||||||
asc2log \
|
asc2log \
|
||||||
can-calc-bit-timing \
|
can-calc-bit-timing \
|
||||||
|
|
@ -87,6 +102,8 @@ bin_PROGRAMS = \
|
||||||
cansend \
|
cansend \
|
||||||
cansequence \
|
cansequence \
|
||||||
cansniffer \
|
cansniffer \
|
||||||
|
isobusfs-cli \
|
||||||
|
isobusfs-srv \
|
||||||
isotpdump \
|
isotpdump \
|
||||||
isotpperf \
|
isotpperf \
|
||||||
isotprecv \
|
isotprecv \
|
||||||
|
|
@ -112,6 +129,33 @@ bin_PROGRAMS += \
|
||||||
isotpserver
|
isotpserver
|
||||||
endif
|
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
|
j1939acd_LDADD = libj1939.la
|
||||||
j1939cat_LDADD = libj1939.la
|
j1939cat_LDADD = libj1939.la
|
||||||
j1939spy_LDADD = libj1939.la
|
j1939spy_LDADD = libj1939.la
|
||||||
|
|
|
||||||
22
Makefile
22
Makefile
|
|
@ -63,6 +63,10 @@ CPPFLAGS += \
|
||||||
PROGRAMS_CANGW := \
|
PROGRAMS_CANGW := \
|
||||||
cangw
|
cangw
|
||||||
|
|
||||||
|
PROGRAMS_ISOBUSFS := \
|
||||||
|
isobusfs-srv \
|
||||||
|
isobusfs-cli
|
||||||
|
|
||||||
PROGRAMS_ISOTP := \
|
PROGRAMS_ISOTP := \
|
||||||
isotpdump \
|
isotpdump \
|
||||||
isotpperf \
|
isotpperf \
|
||||||
|
|
@ -89,6 +93,7 @@ PROGRAMS_SLCAN := \
|
||||||
|
|
||||||
PROGRAMS := \
|
PROGRAMS := \
|
||||||
$(PROGRAMS_CANGW) \
|
$(PROGRAMS_CANGW) \
|
||||||
|
$(PROGRAMS_ISOBUSFS) \
|
||||||
$(PROGRAMS_ISOTP) \
|
$(PROGRAMS_ISOTP) \
|
||||||
$(PROGRAMS_J1939) \
|
$(PROGRAMS_J1939) \
|
||||||
$(PROGRAMS_SLCAN) \
|
$(PROGRAMS_SLCAN) \
|
||||||
|
|
@ -139,6 +144,8 @@ j1939cat.o: libj1939.h
|
||||||
j1939spy.o: libj1939.h
|
j1939spy.o: libj1939.h
|
||||||
j1939sr.o: libj1939.h
|
j1939sr.o: libj1939.h
|
||||||
testj1939.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
|
canframelen.o: canframelen.h
|
||||||
|
|
||||||
asc2log: asc2log.o lib.o
|
asc2log: asc2log.o lib.o
|
||||||
|
|
@ -156,6 +163,21 @@ j1939cat: j1939cat.o libj1939.o
|
||||||
j1939spy: j1939spy.o libj1939.o
|
j1939spy: j1939spy.o libj1939.o
|
||||||
j1939sr: j1939sr.o libj1939.o
|
j1939sr: j1939sr.o libj1939.o
|
||||||
testj1939: testj1939.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
|
canbusload: canbusload.o canframelen.o
|
||||||
|
|
||||||
can-calc-bit-timing: calc-bit-timing/can-calc-bit-timing.o
|
can-calc-bit-timing: calc-bit-timing/can-calc-bit-timing.o
|
||||||
|
|
|
||||||
|
|
@ -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, ¤t_time);
|
||||||
|
timeout_timespec = ms_to_timespec(ISOBUSFS_CLI_DEFAULT_WAIT_TIMEOUT_MS);
|
||||||
|
event->timeout.tv_sec = current_time.tv_sec + timeout_timespec.tv_sec;
|
||||||
|
event->timeout.tv_nsec = current_time.tv_nsec + timeout_timespec.tv_nsec;
|
||||||
|
|
||||||
|
/* Adjust for nanosecond overflow */
|
||||||
|
if (event->timeout.tv_nsec >= 1000000000) {
|
||||||
|
event->timeout.tv_nsec -= 1000000000;
|
||||||
|
event->timeout.tv_sec++;
|
||||||
|
}
|
||||||
|
|
||||||
|
event->one_shot = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isobusfs_cli_has_event_expired(const struct timespec *timeout)
|
||||||
|
{
|
||||||
|
struct timespec now;
|
||||||
|
|
||||||
|
clock_gettime(CLOCK_REALTIME, &now);
|
||||||
|
return now.tv_sec > timeout->tv_sec ||
|
||||||
|
(now.tv_sec == timeout->tv_sec &&
|
||||||
|
now.tv_nsec > timeout->tv_nsec);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void isobusfs_cli_process_expired_events(struct isobusfs_priv *priv)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < priv->num_events; ++i) {
|
||||||
|
struct isobusfs_event *event = &priv->events[i];
|
||||||
|
|
||||||
|
if (isobusfs_cli_has_event_expired(&event->timeout)) {
|
||||||
|
event->cb(priv, NULL, event->ctx, -ETIME);
|
||||||
|
|
||||||
|
isobusfs_cli_remove_event(priv, event);
|
||||||
|
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int isobusfs_cli_rx_event(struct isobusfs_priv *priv, int sock,
|
||||||
|
struct isobusfs_msg *msg, bool *found)
|
||||||
|
{
|
||||||
|
struct isobusfs_event *event = NULL;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
*found = false;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < priv->num_events; ++i) {
|
||||||
|
event = &priv->events[i];
|
||||||
|
|
||||||
|
if (event->fd == sock && event->fs_function == msg->buf[0]) {
|
||||||
|
if (event->cb)
|
||||||
|
ret = event->cb(priv, msg, event->ctx, 0);
|
||||||
|
|
||||||
|
*found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*found && event->one_shot)
|
||||||
|
isobusfs_cli_remove_event(priv, event);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int isobusfs_cli_rx(struct isobusfs_priv *priv, struct isobusfs_msg *msg)
|
||||||
|
{
|
||||||
|
int cmd = isobusfs_buf_to_cmd(msg->buf);
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
switch (cmd) {
|
||||||
|
case ISOBUSFS_CG_CONNECTION_MANAGMENT:
|
||||||
|
ret = isobusfs_cli_rx_cg_cm(priv, msg);
|
||||||
|
break;
|
||||||
|
case ISOBUSFS_CG_DIRECTORY_HANDLING:
|
||||||
|
ret = isobusfs_cli_rx_cg_dh(priv, msg);
|
||||||
|
break;
|
||||||
|
case ISOBUSFS_CG_FILE_ACCESS: /* fall through */
|
||||||
|
ret = isobusfs_cli_rx_cg_fa(priv, msg);
|
||||||
|
break;
|
||||||
|
case ISOBUSFS_CG_FILE_HANDLING: /* fall through */
|
||||||
|
case ISOBUSFS_CG_VOLUME_HANDLING: /* fall through */
|
||||||
|
default:
|
||||||
|
isobusfs_send_nack(priv->sock_nack, msg);
|
||||||
|
pr_warn("unsupported command group: %i", cmd);
|
||||||
|
/* Not a critical error */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int isobusfs_cli_rx_ack(struct isobusfs_priv *priv, struct isobusfs_msg *msg)
|
||||||
|
{
|
||||||
|
enum isobusfs_ack_ctrl ctrl = msg->buf[0];
|
||||||
|
|
||||||
|
switch (ctrl) {
|
||||||
|
case ISOBUS_ACK_CTRL_ACK:
|
||||||
|
/* received ACK unexpectedly an ACK, no idea what to do */
|
||||||
|
pr_debug("< rx: ACK?????");
|
||||||
|
break;
|
||||||
|
case ISOBUS_ACK_CTRL_NACK:
|
||||||
|
/* we did something wrong */
|
||||||
|
pr_debug("< rx: NACK!!!!!!");
|
||||||
|
/* try to provide some usable information with a trace of
|
||||||
|
* the TX history
|
||||||
|
*/
|
||||||
|
isobusfs_dump_tx_data(&priv->tx_buf_log);
|
||||||
|
priv->state = ISOBUSFS_CLI_STATE_IDLE;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
pr_warn("%s: unsupported ACK control: %i", __func__, ctrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Not a critical error */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int isobusfs_cli_rx_buf(struct isobusfs_priv *priv, struct isobusfs_msg *msg)
|
||||||
|
{
|
||||||
|
pgn_t pgn = msg->peername.can_addr.j1939.pgn;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
switch (pgn) {
|
||||||
|
case ISOBUSFS_PGN_FS_TO_CL:
|
||||||
|
ret = isobusfs_cli_rx(priv, msg);
|
||||||
|
break;
|
||||||
|
case ISOBUS_PGN_ACK:
|
||||||
|
ret = isobusfs_cli_rx_ack(priv, msg);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
pr_warn("%s: unsupported PGN: %x", __func__, pgn);
|
||||||
|
/* Not a critical error */
|
||||||
|
ret = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int isobusfs_cli_rx_one(struct isobusfs_priv *priv, int sock)
|
||||||
|
{
|
||||||
|
struct isobusfs_msg *msg;
|
||||||
|
int flags = 0;
|
||||||
|
bool found;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
msg = malloc(sizeof(*msg));
|
||||||
|
if (!msg) {
|
||||||
|
pr_err("can't allocate rx msg struct\n");
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
msg->buf_size = ISOBUSFS_MAX_TRANSFER_LENGH;
|
||||||
|
msg->peer_addr_len = sizeof(msg->peername);
|
||||||
|
msg->sock = sock;
|
||||||
|
|
||||||
|
ret = recvfrom(sock, &msg->buf[0], msg->buf_size, flags,
|
||||||
|
(struct sockaddr *)&msg->peername, &msg->peer_addr_len);
|
||||||
|
|
||||||
|
if (ret < 0) {
|
||||||
|
ret = -errno;
|
||||||
|
pr_warn("recvfrom() failed: %i %s", ret, strerror(-ret));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret < ISOBUSFS_MIN_TRANSFER_LENGH) {
|
||||||
|
pr_warn("buf is less then min transfer: %i\n", ret);
|
||||||
|
isobusfs_send_nack(priv->sock_nack, msg);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
msg->len = ret;
|
||||||
|
|
||||||
|
ret = isobusfs_cli_rx_event(priv, sock, msg, &found);
|
||||||
|
if (ret < 0) {
|
||||||
|
pr_warn("failed to process rx event: %i (%s)\n", ret, strerror(ret));
|
||||||
|
return ret;
|
||||||
|
} else if (found)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
ret = isobusfs_cli_rx_buf(priv, msg);
|
||||||
|
if (ret < 0) {
|
||||||
|
pr_warn("failed to process rx buf: %i (%s)\n", ret, strerror(ret));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int isobusfs_cli_handle_events(struct isobusfs_priv *priv, 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -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 */
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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
|
|
@ -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, ¤t_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, ¤t_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, ¤t_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, ¤t_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, ¤t_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, ¤t_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, ¤t_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, ¤t_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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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_ */
|
||||||
|
|
@ -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 */
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -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 */
|
||||||
|
|
@ -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 */
|
||||||
|
|
@ -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 */
|
||||||
|
|
@ -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 */
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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 */
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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 2–n: 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;
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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
18
lib.c
|
|
@ -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);
|
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
17
lib.h
|
|
@ -45,6 +45,7 @@
|
||||||
#ifndef CAN_UTILS_LIB_H
|
#ifndef CAN_UTILS_LIB_H
|
||||||
#define CAN_UTILS_LIB_H
|
#define CAN_UTILS_LIB_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
#ifdef DEBUG
|
#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.
|
* 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
|
#endif
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue