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

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

655 lines
19 KiB
C

// SPDX-License-Identifier: LGPL-2.0-only
// SPDX-FileCopyrightText: 2023 Oleksij Rempel <linux@rempel-privat.de>
/*
* ISOBUS File System Server Connection Management (isobusfs_srv_cm.c)
*
* This code implements the Connection Management functionality for the
* ISOBUS File System Server, according to the ISO 11783-13:2021 standard,
* specifically Section 5.10: Connection/Disconnection of a client.
*
* The ISOBUS File System Server provides a way for clients to interact with
* files and directories on an ISOBUS network. Connection Management is
* responsible for handling client connections and disconnections, ensuring
* proper communication and resource allocation between the server and its
* clients.
*
* This code includes functions for initializing clients, adding new clients,
* managing client connections, and handling client disconnections. It also
* provides utility functions for managing sockets and filters for the
* communication between the server and its clients.
*/
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <linux/kernel.h>
#include "isobusfs_cmn.h"
#include "isobusfs_srv.h"
#include "isobusfs_cmn_cm.h"
/* Request volume by client */
int isobusfs_srv_request_volume(struct isobusfs_srv_priv *priv,
struct isobusfs_srv_client *client,
struct isobusfs_srv_volume *volume)
{
unsigned 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 = libj1939_open_socket();
if (ret < 0)
return ret;
client->sock = ret;
addr.can_addr.j1939.pgn = ISOBUSFS_PGN_CL_TO_FS;
ret = libj1939_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 = libj1939_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];
size_t path_len, req_name_len, resp_name_len;
char linux_path[ISOBUSFS_SRV_MAX_PATH_LEN];
struct isobusfs_srv_volume *volume = NULL;
struct isobusfs_srv_client *client;
const char *path;
int ret, i;
req_name_len = le16toh(req->name_len);
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);
resp->name_len = htole16(resp_name_len);
/* 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) - sizeof(resp.name) + le16toh(resp.name_len);
if (buf_size < ISOBUSFS_MIN_TRANSFER_LENGH) {
/* Fill the rest of the buffer with 0xFF. We need to fill
* only buffers under 8 bytes. Padding for ETP/TP is done
* by the kernel.
*/
memset(((uint8_t *) &resp) + buf_size, 0xFF,
ISOBUSFS_MIN_TRANSFER_LENGH - buf_size);
buf_size = ISOBUSFS_MIN_TRANSFER_LENGH;
} 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;
}