diff --git a/.gitignore b/.gitignore index 9e80381..97657b8 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,8 @@ GNUmakefile.in /cansend /cansequence /cansniffer +/isobusfs-cli +/isobusfs-srv /isotpdump /isotpperf /isotprecv diff --git a/CMakeLists.txt b/CMakeLists.txt index 28314f3..f0fbd4a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,6 +53,11 @@ set(PROGRAMS_J1939 testj1939 ) +set(PROGRAMS_ISOBUSFS + isobusfs-srv + isobusfs-cli +) + set(PROGRAMS ${PROGRAMS_CANLIB} canfdtest @@ -86,6 +91,7 @@ add_executable(mcp251xfd-dump if(NOT ANDROID) list(APPEND PROGRAMS ${PROGRAMS_J1939}) + list(APPEND PROGRAMS ${PROGRAMS_ISOBUSFS}) add_library(j1939 STATIC libj1939.c diff --git a/GNUmakefile.am b/GNUmakefile.am index 4c595ab..3ea593b 100644 --- a/GNUmakefile.am +++ b/GNUmakefile.am @@ -13,6 +13,14 @@ LDADD = \ noinst_HEADERS = \ canframelen.h \ + isobusfs/isobusfs_cli.h \ + isobusfs/isobusfs_cmn.h \ + isobusfs/isobusfs_cmn_cm.h \ + isobusfs/isobusfs_cmn_dh.h \ + isobusfs/isobusfs_cmn_fa.h \ + isobusfs/isobusfs_cmn_fh.h \ + isobusfs/isobusfs_cmn_va.h \ + isobusfs/isobusfs_srv.h \ lib.h \ libj1939.h \ terminal.h \ @@ -75,6 +83,13 @@ EXTRA_DIST += \ mcp251xfd/devcoredump \ mcp251xfd/mcp251xfd-gen-testdata.sh +lib_LTLIBRARIES = \ + libisobusfs.la + +libisobusfs_la_SOURCES = \ + isobusfs/isobusfs_cmn.c \ + isobusfs/isobusfs_cmn_dh.c + bin_PROGRAMS = \ asc2log \ can-calc-bit-timing \ @@ -87,6 +102,8 @@ bin_PROGRAMS = \ cansend \ cansequence \ cansniffer \ + isobusfs-cli \ + isobusfs-srv \ isotpdump \ isotpperf \ isotprecv \ @@ -112,6 +129,33 @@ bin_PROGRAMS += \ isotpserver endif +isobusfs_cli_SOURCES = \ + isobusfs/isobusfs_cli.c \ + isobusfs/isobusfs_cli_cm.c \ + isobusfs/isobusfs_cli_dh.c \ + isobusfs/isobusfs_cli_fa.c \ + isobusfs/isobusfs_cli_selftests.c \ + isobusfs/isobusfs_cli_int.c + +isobusfs_cli_LDADD = \ + libisobusfs.la \ + libj1939.la \ + libcan.la + +isobusfs_srv_SOURCES = \ + isobusfs/isobusfs_srv.c \ + isobusfs/isobusfs_srv_cm.c \ + isobusfs/isobusfs_srv_cm_fss.c \ + isobusfs/isobusfs_srv_dh.c \ + isobusfs/isobusfs_srv_fa.c \ + isobusfs/isobusfs_srv_fh.c \ + isobusfs/isobusfs_srv_vh.c + +isobusfs_srv_LDADD = \ + libisobusfs.la \ + libj1939.la \ + libcan.la + j1939acd_LDADD = libj1939.la j1939cat_LDADD = libj1939.la j1939spy_LDADD = libj1939.la diff --git a/Makefile b/Makefile index a26ff3d..229bcfc 100644 --- a/Makefile +++ b/Makefile @@ -63,6 +63,10 @@ CPPFLAGS += \ PROGRAMS_CANGW := \ cangw +PROGRAMS_ISOBUSFS := \ + isobusfs-srv \ + isobusfs-cli + PROGRAMS_ISOTP := \ isotpdump \ isotpperf \ @@ -89,6 +93,7 @@ PROGRAMS_SLCAN := \ PROGRAMS := \ $(PROGRAMS_CANGW) \ + $(PROGRAMS_ISOBUSFS) \ $(PROGRAMS_ISOTP) \ $(PROGRAMS_J1939) \ $(PROGRAMS_SLCAN) \ @@ -139,6 +144,8 @@ j1939cat.o: libj1939.h j1939spy.o: libj1939.h j1939sr.o: libj1939.h testj1939.o: libj1939.h +isobusfs_srv.o: libj1939.h lib.h +isobusfs_c.o: libj1939.h lib.h canframelen.o: canframelen.h asc2log: asc2log.o lib.o @@ -156,6 +163,21 @@ j1939cat: j1939cat.o libj1939.o j1939spy: j1939spy.o libj1939.o j1939sr: j1939sr.o libj1939.o testj1939: testj1939.o libj1939.o +isobusfs-srv: isobusfs_srv.o isobusfs_cmn.o libj1939.o lib.o \ + isobusfs_srv_cm.o \ + isobusfs_srv_cm_fss.o \ + isobusfs_srv_dh.o \ + isobusfs_srv_fa.o \ + isobusfs_srv_fh.o \ + isobusfs_srv_vh.o \ + isobusfs_cmn_dh.o + +isobusfs-cli: isobusfs_cli.o isobusfs_cmn.o libj1939.o lib.o \ + isobusfs_cli_cm.o \ + isobusfs_cli_dh.o \ + isobusfs_cli_fa.o \ + isobusfs_cli_selftests.o \ + isobusfs_cli_int.o canbusload: canbusload.o canframelen.o can-calc-bit-timing: calc-bit-timing/can-calc-bit-timing.o diff --git a/isobusfs/isobusfs_cli.c b/isobusfs/isobusfs_cli.c new file mode 100644 index 0000000..5cd4b11 --- /dev/null +++ b/isobusfs/isobusfs_cli.c @@ -0,0 +1,688 @@ +// SPDX-License-Identifier: GPL-2.0-only +// SPDX-FileCopyrightText: 2023 Oleksij Rempel + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 or -i \n"); + printf(" --local-address or -a \n"); + printf(" --local-name or -n \n"); + printf(" --log-level or -l (Default %d)\n", + LOG_LEVEL_INFO); + printf(" --remote-address or -r \n"); + printf(" --remote-name or -m \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; +} + diff --git a/isobusfs/isobusfs_cli.h b/isobusfs/isobusfs_cli.h new file mode 100644 index 0000000..6e9b917 --- /dev/null +++ b/isobusfs/isobusfs_cli.h @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: GPL-2.0-only + + +#ifndef ISOBUSFS_CLI_H +#define ISOBUSFS_CLI_H + +#include +#include + +#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 */ diff --git a/isobusfs/isobusfs_cli_cm.c b/isobusfs/isobusfs_cli_cm.c new file mode 100644 index 0000000..fd7c56c --- /dev/null +++ b/isobusfs/isobusfs_cli_cm.c @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: GPL-2.0-only +// SPDX-FileCopyrightText: 2023 Oleksij Rempel + +#include +#include +#include + +#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; +} diff --git a/isobusfs/isobusfs_cli_dh.c b/isobusfs/isobusfs_cli_dh.c new file mode 100644 index 0000000..6ac0399 --- /dev/null +++ b/isobusfs/isobusfs_cli_dh.c @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: GPL-2.0-only +// SPDX-FileCopyrightText: 2023 Oleksij Rempel + +#include +#include +#include +#include + +#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; +} diff --git a/isobusfs/isobusfs_cli_fa.c b/isobusfs/isobusfs_cli_fa.c new file mode 100644 index 0000000..7af3904 --- /dev/null +++ b/isobusfs/isobusfs_cli_fa.c @@ -0,0 +1,430 @@ +// SPDX-License-Identifier: GPL-2.0-only +// SPDX-FileCopyrightText: 2023 Oleksij Rempel + +#include +#include +#include +#include +#include + +#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; +} diff --git a/isobusfs/isobusfs_cli_int.c b/isobusfs/isobusfs_cli_int.c new file mode 100644 index 0000000..6d58fb6 --- /dev/null +++ b/isobusfs/isobusfs_cli_int.c @@ -0,0 +1,1088 @@ +// SPDX-License-Identifier: GPL-2.0-only +// SPDX-FileCopyrightText: 2023 Oleksij Rempel + +#include +#include +#include +#include +#include +#include + +#include "../libj1939.h" +#include "isobusfs_cli.h" +#include "isobusfs_cmn_dh.h" +#include "isobusfs_cmn_fa.h" + +#define MAX_COMMAND_LENGTH 256 + +#define MAX_DISPLAY_FILENAME_LENGTH 100 + +struct command_mapping { + const char *command; + int (*function)(struct isobusfs_priv *priv, const char *options); + const char *help; +}; + +struct command_mapping commands[]; + +static bool isobusfs_cli_int_is_error(struct isobusfs_priv *priv, int error, + uint8_t error_code, uint8_t tan) +{ + bool is_error = false; + + if (error) { + pr_int("failed with error: %i (%s)\n", error, + strerror(error)); + is_error = true; + } else if (!isobusfs_cli_tan_is_valid(tan, priv)) { + pr_int("Invalid TAN\n"); + is_error = true; + } else if (error_code && error_code != ISOBUSFS_ERR_END_OF_FILE) { + pr_int("Failed with error code: %i (%s)\n", error_code, + isobusfs_error_to_str(error_code)); + is_error = true; + } + + return is_error; +} + +static void isobusfs_cli_promt(struct isobusfs_priv *priv) +{ + /* we are currently waiting for a response */ + if (priv->int_busy) + return; + + pr_int("isobusfs> "); +} + +static int cmd_help(struct isobusfs_priv *priv, const char *options) +{ + for (int i = 0; commands[i].command != NULL; i++) + pr_int("%s - %s\n", commands[i].command, commands[i].help); + + return 0; +} + +static int cmd_exit(struct isobusfs_priv *priv, const char *options) +{ + pr_int("exit interactive mode\n"); + /* Return -EINTR to indicate the program should exit */ + return -EINTR; +} + +static int cmd_dmesg(struct isobusfs_priv *priv, const char *options) +{ + isobusfs_print_log_buffer(); + return 0; +} + +static int cmd_selftest(struct isobusfs_priv *priv, const char *options) +{ + pr_int("run selftest\n"); + priv->run_selftest = true; + + return 0; +} + +/* ------ get command -------*/ +enum isobusfs_cli_get_state { + ISOBUSFS_CLI_GET_STATE_START, + ISOBUSFS_CLI_GET_STATE_OPEN_FILE_SENT, + ISOBUSFS_CLI_GET_STATE_SEEK_FILE_SENT, + ISOBUSFS_CLI_GET_STATE_READ_FILE_SENT, + ISOBUSFS_CLI_GET_STATE_CLOSE_FILE_SENT, + ISOBUSFS_CLI_GET_STATE_COMPLETED, + ISOBUSFS_CLI_GET_STATE_ERROR +}; + +struct isobusfs_cli_get_context { + enum isobusfs_cli_get_state state; + int handle; + size_t offset; + char *remote_path; + char *local_path; + FILE *local_file; + size_t bytes_received; + size_t total_size; +}; + +static int isobusfs_cli_get_event_callback(struct isobusfs_priv *priv, + struct isobusfs_msg *msg, + void *context, int error); + +static void isobusfs_cli_get_handle_send_open_file(struct isobusfs_priv *priv, + struct isobusfs_cli_get_context *ctx) +{ + isobusfs_event_callback cb = isobusfs_cli_get_event_callback; + const char *remote_path = ctx->remote_path; + uint8_t flags = ISOBUSFS_FA_OPEN_FILE_RO; + int ret; + + /* Send the open file request to the server */ + ret = isobusfs_cli_send_and_register_fa_of_event(priv, remote_path, + strlen(remote_path), + flags, cb, ctx); + if (ret) { + pr_int("Error: Failed to send open file request, error code: %d\n", + ret); + ctx->state = ISOBUSFS_CLI_GET_STATE_ERROR; + return; + } + + ctx->state = ISOBUSFS_CLI_GET_STATE_OPEN_FILE_SENT; +} + +static void +isobusfs_cli_get_handle_open_file_sent(struct isobusfs_priv *priv, + struct isobusfs_cli_get_context *ctx, + struct isobusfs_msg *msg) +{ + isobusfs_event_callback cb = isobusfs_cli_get_event_callback; + struct isobusfs_fa_openf_res *res = + (struct isobusfs_fa_openf_res *)msg->buf; + int ret; + + if (isobusfs_cli_int_is_error(priv, 0, res->error_code, res->tan) || + res->handle == ISOBUSFS_FILE_HANDLE_ERROR) { + pr_int("Error: Failed to open file on server, error code: %d, handle: %d\n", + res->error_code, res->handle); + ctx->state = ISOBUSFS_CLI_GET_STATE_ERROR; + return; + } + + ctx->handle = res->handle; + + /* Seek to the beginning of the file */ + ret = isobusfs_cli_send_and_register_fa_sf_event(priv, ctx->handle, + ISOBUSFS_FA_SEEK_SET, + 0, cb, ctx); + if (ret) { + pr_int("Error: Failed to send seek request, error code: %d\n", + ret); + ctx->state = ISOBUSFS_CLI_GET_STATE_ERROR; + return; + } + + ctx->state = ISOBUSFS_CLI_GET_STATE_SEEK_FILE_SENT; +} + +static void +isobusfs_cli_get_handle_seek_file_sent(struct isobusfs_priv *priv, + struct isobusfs_cli_get_context *ctx, + struct isobusfs_msg *msg) +{ + isobusfs_event_callback cb = isobusfs_cli_get_event_callback; + struct isobusfs_fa_seekf_res *res = + (struct isobusfs_fa_seekf_res *)msg->buf; + uint16_t read_size; + int ret; + + if (isobusfs_cli_int_is_error(priv, 0, res->error_code, res->tan) || + res->position != ctx->offset) { + pr_int("Error: Failed to seek file on server, error code: %d, position: %d\n", + res->error_code, res->position); + ctx->state = ISOBUSFS_CLI_GET_STATE_ERROR; + return; + } + + /* set max possible number fitting in to 16bits */ + read_size = UINT16_MAX; + + ret = isobusfs_cli_send_and_register_fa_rf_event(priv, ctx->handle, + read_size, cb, ctx); + if (ret) { + pr_int("Error: Failed to send read file request, error code: %d\n", + ret); + ctx->state = ISOBUSFS_CLI_GET_STATE_ERROR; + return; + } + + ctx->state = ISOBUSFS_CLI_GET_STATE_READ_FILE_SENT; +} + +static void +isobusfs_cli_get_handle_read_file_sent(struct isobusfs_priv *priv, + struct isobusfs_cli_get_context *ctx, + struct isobusfs_msg *msg) +{ + isobusfs_event_callback cb = isobusfs_cli_get_event_callback; + struct isobusfs_read_file_response *res = + (struct isobusfs_read_file_response *)msg->buf; + size_t bytes_read, bytes_written; + uint8_t position_mode; + int ret; + + if (isobusfs_cli_int_is_error(priv, 0, res->error_code, res->tan)) { + pr_int("Error: Failed to read file from server, error code: %d\n", + res->error_code); + ctx->state = ISOBUSFS_CLI_GET_STATE_ERROR; + return; + } + + /* Write the received data to the local file */ + bytes_read = le16toh(res->count); + bytes_written = fwrite(res->data, 1, bytes_read, ctx->local_file); + if (bytes_written != bytes_read) { + pr_int("Error: Failed to write data to local file.\n"); + ctx->state = ISOBUSFS_CLI_GET_STATE_ERROR; + return; + } + + ctx->bytes_received += bytes_written; + ctx->offset += bytes_written; + + /* Check if the end of the file has been reached */ + if (res->error_code == ISOBUSFS_ERR_END_OF_FILE) { + /* Send a close file request */ + ret = isobusfs_cli_send_and_register_fa_cf_event(priv, + ctx->handle, + cb, ctx); + if (ret) { + pr_int("Error: Failed to send close file request, error code: %d\n", + ret); + ctx->state = ISOBUSFS_CLI_GET_STATE_ERROR; + return; + } + ctx->state = ISOBUSFS_CLI_GET_STATE_CLOSE_FILE_SENT; + return; + } + + position_mode = ISOBUSFS_FA_SEEK_SET; + /* If more data is available, send a new seek request */ + + ret = isobusfs_cli_send_and_register_fa_sf_event(priv, ctx->handle, + position_mode, + ctx->offset, cb, ctx); + if (ret) { + pr_int("Error: Failed to send next seek request, error code: %d\n", + ret); + ctx->state = ISOBUSFS_CLI_GET_STATE_ERROR; + return; + } + + ctx->state = ISOBUSFS_CLI_GET_STATE_SEEK_FILE_SENT; +} + +static void +isobusfs_cli_get_handle_close_file_sent(struct isobusfs_priv *priv, + struct isobusfs_cli_get_context *ctx, + struct isobusfs_msg *msg) +{ + struct isobusfs_close_file_res *res = + (struct isobusfs_close_file_res *)msg->buf; + + if (isobusfs_cli_int_is_error(priv, 0, res->error_code, res->tan)) { + pr_int("Error: Failed to close file on server, error code: %d\n", + res->error_code); + ctx->state = ISOBUSFS_CLI_GET_STATE_ERROR; + } else { + pr_int("File closed successfully.\n"); + ctx->state = ISOBUSFS_CLI_GET_STATE_COMPLETED; + } +} + +static void isobusfs_cli_get_free_ctx(struct isobusfs_cli_get_context *ctx) +{ + if (ctx->local_file) + fclose(ctx->local_file); + + free(ctx); +} + +static void +isobusfs_cli_process_get_command(struct isobusfs_priv *priv, + struct isobusfs_cli_get_context *ctx, + struct isobusfs_msg *msg) +{ + switch (ctx->state) { + case ISOBUSFS_CLI_GET_STATE_START: + isobusfs_cli_get_handle_send_open_file(priv, ctx); + break; + case ISOBUSFS_CLI_GET_STATE_OPEN_FILE_SENT: + isobusfs_cli_get_handle_open_file_sent(priv, ctx, msg); + break; + case ISOBUSFS_CLI_GET_STATE_SEEK_FILE_SENT: + isobusfs_cli_get_handle_seek_file_sent(priv, ctx, msg); + break; + case ISOBUSFS_CLI_GET_STATE_READ_FILE_SENT: + isobusfs_cli_get_handle_read_file_sent(priv, ctx, msg); + break; + case ISOBUSFS_CLI_GET_STATE_CLOSE_FILE_SENT: + isobusfs_cli_get_handle_close_file_sent(priv, ctx, msg); + break; + default: + pr_int("Error: Unexpected state in get command processing: %d\n", + ctx->state); + break; + } + + if (ctx->state == ISOBUSFS_CLI_GET_STATE_COMPLETED || + ctx->state == ISOBUSFS_CLI_GET_STATE_ERROR) { + if (ctx->state != ISOBUSFS_CLI_GET_STATE_COMPLETED) { + /* Try to close handle and do not wait for response. */ + isobusfs_cli_send_and_register_fa_cf_event(priv, + ctx->handle, + NULL, NULL); + } + + pr_int("File transfer %s.\n", + ctx->state == ISOBUSFS_CLI_GET_STATE_COMPLETED ? + "completed" : "failed"); + priv->int_busy = false; + isobusfs_cli_get_free_ctx(ctx); + isobusfs_cli_promt(priv); + } +} + +static int isobusfs_cli_get_event_callback(struct isobusfs_priv *priv, + struct isobusfs_msg *msg, + void *context, int error) +{ + struct isobusfs_cli_get_context *ctx = + (struct isobusfs_cli_get_context *)context; + + if (error) { + pr_int("Error in get event callback: %d\n", error); + ctx->state = ISOBUSFS_CLI_GET_STATE_ERROR; + isobusfs_cli_process_get_command(priv, ctx, NULL); + return error; + } + + isobusfs_cli_process_get_command(priv, ctx, msg); + + return 0; +} + +static int cmd_get(struct isobusfs_priv *priv, const char *options) +{ + struct isobusfs_cli_get_context *ctx; + const char *remote_path = NULL; + char *local_path = NULL; + char *options_copy, *opt; + + if (!options) { + pr_int("Usage: get [local_path]\n"); + return -EINVAL; + } + + options_copy = strdup(options); + if (!options_copy) { + pr_int("Error: Unable to allocate memory for options processing.\n"); + return -ENOMEM; + } + + opt = strtok(options_copy, " "); + if (opt) { + remote_path = opt; + opt = strtok(NULL, " "); + if (opt) + local_path = strdup(opt); + } + + if (!remote_path) { + pr_int("Error: Invalid arguments. Usage: get [local_path]\n"); + free(options_copy); + return -EINVAL; + } + + if (!local_path) { + const char *filename = strrchr(remote_path, '/'); + + filename = filename ? filename + 1 : remote_path; + local_path = strdup(filename); + if (!local_path) { + pr_int("Error: Unable to allocate memory for local path.\n"); + free(options_copy); + return -ENOMEM; + } + } + + ctx = calloc(1, sizeof(*ctx)); + if (!ctx) { + pr_int("Error: Unable to allocate memory for get context.\n"); + free(local_path); + free(options_copy); + return -ENOMEM; + } + + ctx->remote_path = strdup(remote_path); + if (!ctx->remote_path) { + pr_int("Error: Unable to allocate memory for remote path.\n"); + free(ctx); + free(local_path); + free(options_copy); + return -ENOMEM; + } + + ctx->local_path = local_path; + + ctx->local_file = fopen(ctx->local_path, "wb"); + if (!ctx->local_file) { + int ret = -errno; + + pr_int("Error: Unable to open local file for writing. %s\n", + strerror(errno)); + free(ctx->remote_path); + free(ctx->local_path); + free(ctx); + free(options_copy); + return ret; + } + + priv->int_busy = true; + ctx->state = ISOBUSFS_CLI_GET_STATE_START; + isobusfs_cli_process_get_command(priv, ctx, NULL); + + free(options_copy); + return 0; +} + +/* ------ ls command -------*/ +enum isobusfs_cli_ls_state { + ISOBUSFS_CLI_LS_STATE_START, + ISOBUSFS_CLI_LS_STATE_OPEN_DIR_SENT, + ISOBUSFS_CLI_LS_STATE_SEEK_DIR_SENT, + ISOBUSFS_CLI_LS_STATE_READ_DIR_SENT, + ISOBUSFS_CLI_LS_STATE_CLOSE_DIR_SENT, + ISOBUSFS_CLI_LS_STATE_COMPLETED, + ISOBUSFS_CLI_LS_STATE_ERROR +}; + +struct isobusfs_cli_ls_context { + enum isobusfs_cli_ls_state state; + int handle; + size_t offset; + char *path; + bool long_format; + size_t entry_count; + size_t request_count; +}; + +static int isobusfs_cli_ls_event_callback(struct isobusfs_priv *priv, + struct isobusfs_msg *msg, + void *context, int error); + +static void isobusfs_cli_ls_free_ctx(struct isobusfs_cli_ls_context *ctx) +{ + free(ctx->path); + free(ctx); +} + +static void +isobusfs_cli_ls_handle_send_open_dir(struct isobusfs_priv *priv, + struct isobusfs_cli_ls_context *ctx) + +{ + isobusfs_event_callback cb = isobusfs_cli_ls_event_callback; + uint8_t flags = ISOBUSFS_FA_OPEN_DIR; + int ret; + + ret = isobusfs_cli_send_and_register_fa_of_event(priv, + ctx->path, + strlen(ctx->path), + flags, cb, ctx); + if (ret) { + pr_int("Error: Unable to send open dir command.\n"); + ctx->state = ISOBUSFS_CLI_LS_STATE_ERROR; + return; + } + + ctx->state = ISOBUSFS_CLI_LS_STATE_OPEN_DIR_SENT; +} + +static void +isobusfs_cli_ls_handle_open_dir_sent(struct isobusfs_priv *priv, + struct isobusfs_cli_ls_context *ctx, + struct isobusfs_msg *msg) +{ + isobusfs_event_callback cb = isobusfs_cli_ls_event_callback; + struct isobusfs_fa_openf_res *res = + (struct isobusfs_fa_openf_res *)msg->buf; + int ret; + + if (isobusfs_cli_int_is_error(priv, 0, res->error_code, res->tan)) + goto error; + else if (res->handle == ISOBUSFS_FILE_HANDLE_ERROR) + goto error; + + pr_debug("< rx: Open File Response. Error code: %i", + res->error_code); + + ctx->handle = res->handle; + + ret = isobusfs_cli_send_and_register_fa_sf_event(priv, ctx->handle, + 0, ctx->entry_count, + cb, ctx); + if (ret) + pr_int("Failed to send seek file request: %i\n", ret); + + ctx->state = ISOBUSFS_CLI_LS_STATE_SEEK_DIR_SENT; + + return; +error: + ctx->state = ISOBUSFS_CLI_LS_STATE_ERROR; +} + +static void +isobusfs_cli_ls_handle_seek_dir_sent(struct isobusfs_priv *priv, + struct isobusfs_cli_ls_context *ctx, + struct isobusfs_msg *msg) +{ + isobusfs_event_callback cb = isobusfs_cli_ls_event_callback; + struct isobusfs_fa_seekf_res *res = + (struct isobusfs_fa_seekf_res *)msg; + uint16_t count; + int ret; + + if (isobusfs_cli_int_is_error(priv, 0, res->error_code, res->tan)) + goto error; + + if (res->position != ctx->offset) { + pr_int("Failed to seek to position %zu, got %zu\n", + ctx->offset, res->position); + goto error; + } + + /* set max possible number fitting in to 16bits */ + count = UINT16_MAX; + ctx->request_count = count; + + ret = isobusfs_cli_send_and_register_fa_rf_event(priv, ctx->handle, + count, cb, ctx); + if (ret) { + pr_int("Failed to send read file request: %i\n", ret); + goto error; + } + + ctx->state = ISOBUSFS_CLI_LS_STATE_READ_DIR_SENT; + + return; +error: + ctx->state = ISOBUSFS_CLI_LS_STATE_ERROR; +} + +/** + * Convert a 16-bit encoded date to a formatted date string ("YYYY-MM-DD"). + * + * @param encoded_date The 16-bit encoded date. + * @param formatted_date Buffer to store the formatted date string. + */ +static void convert_to_formatted_date(uint16_t encoded_date, + char *formatted_date) +{ + int year, month, day; + + if (!formatted_date) + return; + + /* Extract year, month, and day from the encoded date */ + year = ((encoded_date >> 9) & 0x7f) + 1980; /* Bits 15 … 9 */ + month = (encoded_date >> 5) & 0x0f; /* Bits 8 … 5 */ + day = encoded_date & 0x1f; /* Bits 4 … 0 */ + + snprintf(formatted_date, 11, "%04d-%02d-%02d", year, month, day); +} + +/** + * Convert a 16-bit encoded time to a formatted time string ("HH:MM:SS"). + * + * @param encoded_time The 16-bit encoded time. + * @param formatted_time Buffer to store the formatted time string. + */ +static void convert_to_formatted_time(uint16_t encoded_time, + char *formatted_time) +{ + int hours, minutes, seconds; + + if (!formatted_time) + return; + + /* Extract hours, minutes, and seconds from the encoded time */ + /* Bits 15 … 11 */ + hours = (encoded_time >> 11) & 0x1f; + /* Bits 10 … 5 */ + minutes = (encoded_time >> 5) & 0x3f; + /* Bits 4 … 0, in steps of 2 seconds */ + seconds = (encoded_time & 0x1f) * 2; + + snprintf(formatted_time, 9, "%02d:%02d:%02d", hours, minutes, seconds); +} + +static bool isobusfs_cli_extract_directory_entry(const uint8_t *buffer, + size_t buffer_length, + size_t *pos, char *filename, + uint8_t *attributes, + uint16_t *file_date, + uint16_t *file_time, + uint32_t *file_size) +{ + uint8_t filename_length; + size_t entry_total_len; + + if (*pos + 2 > buffer_length) { + pr_int("Error: Incomplete data in buffer\n"); + return false; + } + + filename_length = buffer[*pos]; + entry_total_len = 1 + filename_length + 1 + 2 + 2 + 4; + + if (*pos + entry_total_len > buffer_length) { + pr_int("Error: Incomplete data in buffer\n"); + return false; + } + + (*pos)++; + strncpy(filename, (const char *)buffer + *pos, filename_length); + filename[filename_length] = '\0'; + *pos += filename_length; + if (filename_length > MAX_DISPLAY_FILENAME_LENGTH) { + /* Truncate the filename and replace the last character + * with a dots + */ + filename[MAX_DISPLAY_FILENAME_LENGTH] = '\0'; + filename[MAX_DISPLAY_FILENAME_LENGTH - 1] = '.'; + filename[MAX_DISPLAY_FILENAME_LENGTH - 2] = '.'; + } + + *attributes = buffer[*pos]; + (*pos)++; + + *file_date = le16toh(*(__le16 *)(buffer + *pos)); + *pos += 2; + *file_time = le16toh(*(__le16 *)(buffer + *pos)); + *pos += 2; + *file_size = le32toh(*(__le32 *)(buffer + *pos)); + *pos += 4; + + return true; +} + +static void +isobusfs_cli_print_directory_entry(struct isobusfs_cli_ls_context *ctx, + const char *filename, uint8_t attributes, + uint16_t file_date, uint16_t file_time, + uint32_t file_size) +{ + char formatted_date[] = "YYYY-MM-DD\0"; + char formatted_time[] = "HH:MM:SS\0"; + char file_type, writeable; + + + if (!ctx->long_format) { + pr_int("%s\n", filename); + return; + } + + file_type = (attributes & ISOBUSFS_ATTR_DIRECTORY) ? 'd' : '-'; + writeable = (attributes & ISOBUSFS_ATTR_READ_ONLY) ? '-' : 'w'; + + convert_to_formatted_date(file_date, formatted_date); + convert_to_formatted_time(file_time, formatted_time); + + pr_int("%c%c%c %u %s %s %s\n", + file_type, 'r', writeable, + file_size, formatted_date, formatted_time, filename); +} + +static void +isobusfs_cli_print_directory_entries(struct isobusfs_cli_ls_context *ctx, + const uint8_t *buffer, + size_t buffer_length) +{ + char filename[ISOBUSFS_MAX_DIR_ENTRY_NAME_LENGTH + 1]; + uint16_t file_date, file_time; + uint32_t file_size; + uint8_t attributes; + size_t pos = 0; + + while (pos < buffer_length) { + if (!isobusfs_cli_extract_directory_entry(buffer, buffer_length, + &pos, filename, + &attributes, + &file_date, + &file_time, + &file_size)) + return; + + isobusfs_cli_print_directory_entry(ctx, filename, attributes, + file_date, file_time, + file_size); + ctx->entry_count++; + } +} + +static void +isobusfs_cli_ls_handle_read_dir_sent(struct isobusfs_priv *priv, + struct isobusfs_cli_ls_context *ctx, + struct isobusfs_msg *msg) +{ + struct isobusfs_read_file_response *res = + (struct isobusfs_read_file_response *)msg->buf; + size_t buffer_length = msg->len - sizeof(*res); + isobusfs_event_callback cb; + uint16_t count; + int ret; + + pr_debug("< rx: Read File Response. Error code: %i", res->error_code); + if (isobusfs_cli_int_is_error(priv, 0, res->error_code, res->tan)) + goto error; + + count = le16toh(res->count); + if (count && count != buffer_length) { + pr_int("Buffer length mismatch: %u != %zu\n", count, + buffer_length); + goto error; + } + + if (count) + isobusfs_cli_print_directory_entries(ctx, res->data, + buffer_length); + + cb = isobusfs_cli_ls_event_callback; + if (count) { + ret = isobusfs_cli_send_and_register_fa_cf_event(priv, + ctx->handle, + cb, ctx); + if (ret) { + pr_int("Failed to send close file request: %i\n", ret); + goto error; + } + + ctx->state = ISOBUSFS_CLI_LS_STATE_CLOSE_DIR_SENT; + } else { + ctx->offset = ctx->entry_count; + ret = isobusfs_cli_send_and_register_fa_sf_event(priv, + ctx->handle, 0, + ctx->offset, + cb, ctx); + if (ret) + pr_int("Failed to send seek file request: %i\n", ret); + + ctx->state = ISOBUSFS_CLI_LS_STATE_SEEK_DIR_SENT; + } + + return; +error: + + ctx->state = ISOBUSFS_CLI_LS_STATE_ERROR; +} + +static void +isobusfs_cli_ls_handle_close_dir_sent(struct isobusfs_priv *priv, + struct isobusfs_cli_ls_context *ctx, + struct isobusfs_msg *msg) +{ + struct isobusfs_close_file_res *res = + (struct isobusfs_close_file_res *)msg->buf; + + if (isobusfs_cli_int_is_error(priv, 0, res->error_code, res->tan)) + goto error; + + pr_debug("< rx: Close File Response. Error code: %i", + res->error_code); + + return; +error: + ctx->state = ISOBUSFS_CLI_LS_STATE_ERROR; +} + +static void isobusfs_cli_process_ls_command(struct isobusfs_priv *priv, + struct isobusfs_cli_ls_context *ctx, + struct isobusfs_msg *msg) +{ + switch (ctx->state) { + case ISOBUSFS_CLI_LS_STATE_START: + isobusfs_cli_ls_handle_send_open_dir(priv, ctx); + break; + case ISOBUSFS_CLI_LS_STATE_OPEN_DIR_SENT: + isobusfs_cli_ls_handle_open_dir_sent(priv, ctx, msg); + break; + case ISOBUSFS_CLI_LS_STATE_SEEK_DIR_SENT: + isobusfs_cli_ls_handle_seek_dir_sent(priv, ctx, msg); + break; + case ISOBUSFS_CLI_LS_STATE_READ_DIR_SENT: + isobusfs_cli_ls_handle_read_dir_sent(priv, ctx, msg); + break; + case ISOBUSFS_CLI_LS_STATE_CLOSE_DIR_SENT: + isobusfs_cli_ls_handle_close_dir_sent(priv, ctx, msg); + ctx->state = ISOBUSFS_CLI_LS_STATE_COMPLETED; + break; + default: + pr_int("Unexpected state: %i\n", ctx->state); + break; + } + + if (ctx->state == ISOBUSFS_CLI_LS_STATE_COMPLETED || + ctx->state == ISOBUSFS_CLI_LS_STATE_ERROR) { + if (ctx->state != ISOBUSFS_CLI_LS_STATE_COMPLETED) { + /* Try to close handle and do not wait for response. */ + isobusfs_cli_send_and_register_fa_cf_event(priv, + ctx->handle, NULL, NULL); + } + + pr_int("Entries found: %i\n", ctx->entry_count); + priv->int_busy = false; + isobusfs_cli_ls_free_ctx(ctx); + isobusfs_cli_promt(priv); + } +} + +static int isobusfs_cli_ls_event_callback(struct isobusfs_priv *priv, + struct isobusfs_msg *msg, + void *context, int error) +{ + struct isobusfs_cli_ls_context *ctx = + (struct isobusfs_cli_ls_context *)context; + + if (!error) { + isobusfs_cli_process_ls_command(priv, ctx, msg); + } else { + ctx->state = ISOBUSFS_CLI_LS_STATE_ERROR; + isobusfs_cli_process_ls_command(priv, ctx, NULL); + } + + return 0; +} + +static int cmd_ls(struct isobusfs_priv *priv, const char *options) +{ + struct isobusfs_cli_ls_context *ctx; + bool long_format = false; + char *options_copy, *opt; + const char *path = "."; + + if (options) { + options_copy = strdup(options); + if (!options_copy) { + pr_int("Error: Unable to allocate memory for options processing.\n"); + return -ENOMEM; + } + + opt = strtok(options_copy, " "); + while (opt != NULL) { + if (strcmp(opt, "-h") == 0) { + pr_int("Usage: ls [-t] [path]\n"); + pr_int("Options:\n"); + pr_int(" -l\tuse a long listing format\n"); + pr_int(" path\tDirectory to list\n"); + free(options_copy); + return 0; + } else if (strcmp(opt, "-l") == 0) { + long_format = true; + } else { + /* Assume any non-option argument is the path */ + path = opt; + } + opt = strtok(NULL, " "); + } + free(options_copy); + } + + ctx = calloc(1, sizeof(*ctx)); + if (!ctx) { + pr_int("Error: Unable to allocate memory for ls context.\n"); + return -ENOMEM; + } + + ctx->path = strdup(path); + if (!ctx->path) { + free(ctx); + pr_int("Error: Unable to allocate memory for path.\n"); + return -ENOMEM; + } + + priv->int_busy = true; + ctx->long_format = long_format; + ctx->state = ISOBUSFS_CLI_LS_STATE_START; + isobusfs_cli_process_ls_command(priv, ctx, NULL); + + return 0; +} + +static int cmd_ll(struct isobusfs_priv *priv, const char *options) +{ + char *options_copy = NULL; + + if (!options) + return cmd_ls(priv, "-l"); + + int ret; + + options_copy = strdup(options); + if (!options_copy) { + pr_int("Error: Unable to allocate memory for options processing.\n"); + return -1; + } + + ret = cmd_ls(priv, strcat(options_copy, " -l")); + free(options_copy); + return ret; +} + +static int isobusfs_cli_int_cd_state(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 (!isobusfs_cli_int_is_error(priv, error, res->error_code, res->tan)) + pr_debug("< rx: change current directory response. Error code: %i", + res->error_code); + + priv->int_busy = false; + isobusfs_cli_promt(priv); + + return 0; +} + +static int cmd_cd(struct isobusfs_priv *priv, const char *options) +{ + char *options_copy = NULL; + const char *path = "."; /* Default path is the current directory */ + char *opt; + int ret; + + if (!options) + goto send_ccd_req; + + options_copy = strdup(options); + if (!options_copy) { + pr_int("Error: Unable to allocate memory for options processing.\n"); + return -ENOMEM; + } + + opt = strtok(options_copy, " "); + while (opt) { + if (strcmp(options, "-h") == 0) { + pr_int("Usage: cd [path]\n"); + pr_int("Options:\n"); + pr_int(" path\tPath of new directory\n"); + return 0; + } + + /* Assume any non-option argument is the path */ + path = opt; + opt = strtok(NULL, " "); + } + +send_ccd_req: + ret = isobusfs_cli_send_and_register_ccd_event(priv, path, strlen(path), + isobusfs_cli_int_cd_state, + NULL); + if (ret) { + pr_int("Error: Unable to send CCD request.\n"); + goto free_options; + } + priv->int_busy = true; + +free_options: + free(options_copy); + return 0; +} + +static int isobusfs_cli_int_pwd_state(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 str_len; + + if (isobusfs_cli_int_is_error(priv, error, res->error_code, res->tan)) + goto error; + + str_len = le16toh(res->name_len); + if (str_len > ISOBUSFS_MAX_PATH_NAME_LENGTH) { + pr_int("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->int_busy = false; + pr_int("%s\n", str); +error: + isobusfs_cli_promt(priv); + + return 0; +} + +static int cmd_pwd(struct isobusfs_priv *priv, const char *options) +{ + int ret; + + ret = isobusfs_cli_send_and_register_gcd_event(priv, + isobusfs_cli_int_pwd_state, NULL); + if (ret) { + pr_int("Error: Unable to send Get Current Dir request.\n"); + return ret; + } + priv->int_busy = true; + + return 0; +} + +struct command_mapping commands[] = { + {"exit", cmd_exit, "exit interactive mode"}, + {"quit", cmd_exit, "exit interactive mode"}, + {"help", cmd_help, "show this help"}, + {"dmesg", cmd_dmesg, "show log buffer"}, + {"selftest", cmd_selftest, "run selftest"}, + {"ls", cmd_ls, "list directory"}, + {"ll", cmd_ll, "list directory with long listing format"}, + {"cd", cmd_cd, "change directory"}, + {"pwd", cmd_pwd, "print name of current/working directory"}, + {"get", cmd_get, "get file"}, + {NULL, NULL, NULL} +}; + +void isobusfs_cli_int_start(struct isobusfs_priv *priv) +{ + pr_int("Interactive mode\n"); + isobusfs_cli_promt(priv); +} + +int isobusfs_cli_interactive(struct isobusfs_priv *priv) +{ + char command[MAX_COMMAND_LENGTH]; + ssize_t len; + int ret; + + len = read(STDIN_FILENO, command, MAX_COMMAND_LENGTH); + if (len == 1) { + isobusfs_cli_promt(priv); + return 0; + } else if (len > 0) { + char *cmd, *options; + + if (command[len - 1] == '\n') + command[len - 1] = '\0'; + else + command[len] = '\0'; + + cmd = strtok(command, " "); + options = strtok(NULL, "\0"); + + for (int i = 0; commands[i].command != NULL; i++) { + if (strcmp(cmd, commands[i].command) == 0) { + ret = commands[i].function(priv, options); + if (ret) + return ret; + isobusfs_cli_promt(priv); + return 0; + } + } + + pr_int("unknown comand\n"); + isobusfs_cli_promt(priv); + } else { + if (errno != EAGAIN && errno != EWOULDBLOCK) + pr_int("read error\n"); + } + + return 0; +} diff --git a/isobusfs/isobusfs_cli_selftests.c b/isobusfs/isobusfs_cli_selftests.c new file mode 100644 index 0000000..db9c1f4 --- /dev/null +++ b/isobusfs/isobusfs_cli_selftests.c @@ -0,0 +1,920 @@ +// SPDX-License-Identifier: GPL-2.0-only +// SPDX-FileCopyrightText: 2023 Oleksij Rempel + +#include +#include +#include +#include +#include +#include +#include +#include + +#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; + } + } +} diff --git a/isobusfs/isobusfs_cmn.c b/isobusfs/isobusfs_cmn.c new file mode 100644 index 0000000..429939a --- /dev/null +++ b/isobusfs/isobusfs_cmn.c @@ -0,0 +1,884 @@ +// SPDX-License-Identifier: GPL-2.0-only +// SPDX-FileCopyrightText: 2023 Oleksij Rempel + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#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 ""; + } +} + +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; + } +} diff --git a/isobusfs/isobusfs_cmn.h b/isobusfs/isobusfs_cmn.h new file mode 100644 index 0000000..cfff448 --- /dev/null +++ b/isobusfs/isobusfs_cmn.h @@ -0,0 +1,362 @@ +// SPDX-License-Identifier: GPL-2.0-only +// SPDX-FileCopyrightText: 2023 Oleksij Rempel + +#ifndef _ISOBUSFS_H_ +#define _ISOBUSFS_H_ + +#include +#include +#include + +#include +#include +#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_ */ diff --git a/isobusfs/isobusfs_cmn_cm.h b/isobusfs/isobusfs_cmn_cm.h new file mode 100644 index 0000000..a71ef18 --- /dev/null +++ b/isobusfs/isobusfs_cmn_cm.h @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: GPL-2.0-only +// SPDX-FileCopyrightText: 2023 Oleksij Rempel + +#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 */ diff --git a/isobusfs/isobusfs_cmn_dh.c b/isobusfs/isobusfs_cmn_dh.c new file mode 100644 index 0000000..35704ac --- /dev/null +++ b/isobusfs/isobusfs_cmn_dh.c @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-2.0-only +// SPDX-FileCopyrightText: 2023 Oleksij Rempel + +#include +#include +#include + +#include "isobusfs_cmn.h" + +#include +#include +#include +#include + +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; +} + diff --git a/isobusfs/isobusfs_cmn_dh.h b/isobusfs/isobusfs_cmn_dh.h new file mode 100644 index 0000000..b5ca9cf --- /dev/null +++ b/isobusfs/isobusfs_cmn_dh.h @@ -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 */ diff --git a/isobusfs/isobusfs_cmn_fa.h b/isobusfs/isobusfs_cmn_fa.h new file mode 100644 index 0000000..7b1c807 --- /dev/null +++ b/isobusfs/isobusfs_cmn_fa.h @@ -0,0 +1,381 @@ +#ifndef _ISOBUSFS_CMN_FA_H +#define _ISOBUSFS_CMN_FA_H + +#include + +#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 */ diff --git a/isobusfs/isobusfs_cmn_fh.h b/isobusfs/isobusfs_cmn_fh.h new file mode 100644 index 0000000..ec192ae --- /dev/null +++ b/isobusfs/isobusfs_cmn_fh.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 */ diff --git a/isobusfs/isobusfs_cmn_va.h b/isobusfs/isobusfs_cmn_va.h new file mode 100644 index 0000000..f0ee20d --- /dev/null +++ b/isobusfs/isobusfs_cmn_va.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 */ diff --git a/isobusfs/isobusfs_create_test_dirs.sh b/isobusfs/isobusfs_create_test_dirs.sh new file mode 100755 index 0000000..ca62db2 --- /dev/null +++ b/isobusfs/isobusfs_create_test_dirs.sh @@ -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 + diff --git a/isobusfs/isobusfs_create_test_file.sh b/isobusfs/isobusfs_create_test_file.sh new file mode 100755 index 0000000..07a102d --- /dev/null +++ b/isobusfs/isobusfs_create_test_file.sh @@ -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 + diff --git a/isobusfs/isobusfs_srv.c b/isobusfs/isobusfs_srv.c new file mode 100644 index 0000000..2de81f7 --- /dev/null +++ b/isobusfs/isobusfs_srv.c @@ -0,0 +1,803 @@ +// SPDX-License-Identifier: GPL-2.0-only +// SPDX-FileCopyrightText: 2023 Oleksij Rempel + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 or -a \n"); + printf(" --default-volume or -d \n"); + printf(" --interface or -i \n"); + printf(" --log-level or -l \n"); + printf(" --name or -n \n"); + printf(" --removable-volume or -r \n"); + printf(" --server-version or -s \n"); + printf(" --volume : or -v :\n"); + printf(" --writeable-volume or -w \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; +} diff --git a/isobusfs/isobusfs_srv.h b/isobusfs/isobusfs_srv.h new file mode 100644 index 0000000..6e8fd0a --- /dev/null +++ b/isobusfs/isobusfs_srv.h @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#ifndef ISOBUSFS_SRV_H +#define ISOBUSFS_SRV_H + +#include +#include +#include +#include + +#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 */ diff --git a/isobusfs/isobusfs_srv_cm.c b/isobusfs/isobusfs_srv_cm.c new file mode 100644 index 0000000..b037de1 --- /dev/null +++ b/isobusfs/isobusfs_srv_cm.c @@ -0,0 +1,645 @@ +// SPDX-License-Identifier: GPL-2.0-only +// SPDX-FileCopyrightText: 2023 Oleksij Rempel +/* + * 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 +#include +#include +#include + +#include + +#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; +} diff --git a/isobusfs/isobusfs_srv_cm_fss.c b/isobusfs/isobusfs_srv_cm_fss.c new file mode 100644 index 0000000..2434edd --- /dev/null +++ b/isobusfs/isobusfs_srv_cm_fss.c @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-2.0-only +// SPDX-FileCopyrightText: 2023 Oleksij Rempel +/* + * This file implements Annex C.1.2 File Server Status according to + * ISO 11783-13:2021. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/isobusfs/isobusfs_srv_dh.c b/isobusfs/isobusfs_srv_dh.c new file mode 100644 index 0000000..f0d824c --- /dev/null +++ b/isobusfs/isobusfs_srv_dh.c @@ -0,0 +1,758 @@ +// SPDX-License-Identifier: GPL-2.0-only +// SPDX-FileCopyrightText: 2023 Oleksij Rempel + +#include +#include +#include +#include +#include + +#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; +} diff --git a/isobusfs/isobusfs_srv_fa.c b/isobusfs/isobusfs_srv_fa.c new file mode 100644 index 0000000..b56ddd7 --- /dev/null +++ b/isobusfs/isobusfs_srv_fa.c @@ -0,0 +1,919 @@ +// SPDX-License-Identifier: GPL-2.0-only +// SPDX-FileCopyrightText: 2023 Oleksij Rempel + +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/isobusfs/isobusfs_srv_fh.c b/isobusfs/isobusfs_srv_fh.c new file mode 100644 index 0000000..dfaf74a --- /dev/null +++ b/isobusfs/isobusfs_srv_fh.c @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-2.0-only +// SPDX-FileCopyrightText: 2023 Oleksij Rempel + +#include +#include +#include + +#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; +} diff --git a/isobusfs/isobusfs_srv_vh.c b/isobusfs/isobusfs_srv_vh.c new file mode 100644 index 0000000..7424794 --- /dev/null +++ b/isobusfs/isobusfs_srv_vh.c @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0-only +// SPDX-FileCopyrightText: 2023 Oleksij Rempel + +#include +#include +#include + +#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; +} diff --git a/lib.h b/lib.h index dfd1f77..0efd1cb 100644 --- a/lib.h +++ b/lib.h @@ -45,6 +45,7 @@ #ifndef CAN_UTILS_LIB_H #define CAN_UTILS_LIB_H +#include #include #ifdef DEBUG