git commit -m "first commit for v2"

This commit is contained in:
2025-12-29 16:21:22 +07:00
commit aa3d832d5c
1807 changed files with 307078 additions and 0 deletions

View File

@@ -0,0 +1,758 @@
// SPDX-License-Identifier: LGPL-2.0-only
// SPDX-FileCopyrightText: 2023 Oleksij Rempel <linux@rempel-privat.de>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include "isobusfs_srv.h"
#include "isobusfs_cmn_dh.h"
void isobusfs_srv_set_default_current_dir(struct isobusfs_srv_priv *priv,
struct isobusfs_srv_client *client)
{
snprintf(client->current_dir, ISOBUSFS_SRV_MAX_PATH_LEN, "\\\\%s",
priv->default_volume);
}
static const char *isobusfs_srv_get_volume_end(const char *path, size_t path_size)
{
const char *vol_end = NULL;
size_t i;
if (!path || !path_size)
return NULL;
if (!(path[0] == '\\' && path[1] == '\\' && path[2] != '\0'))
return NULL;
for (i = 2; i < path_size; i++) {
if (path[i] == '\\' || path[i] == '\0') {
vol_end = &path[i];
break;
}
}
if (!vol_end)
vol_end = &path[i];
return vol_end;
}
int isobusfs_path_to_linux_path(struct isobusfs_srv_priv *priv,
const char *isobusfs_path, size_t isobusfs_path_size,
char *linux_path, size_t linux_path_size)
{
struct isobusfs_srv_volume *volume = NULL;
size_t isobusfs_path_pos = 0;
const char *vol_end;
char *ptr;
int i;
if (!priv || !isobusfs_path || !linux_path || !linux_path_size ||
!isobusfs_path_size) {
pr_err("%s: invalid argument\n", __func__);
return -EINVAL;
}
vol_end = isobusfs_srv_get_volume_end(isobusfs_path, isobusfs_path_size);
if (!vol_end) {
pr_err("%s: invalid path %s. Can't find end of volume string\n",
__func__, isobusfs_path);
return -EINVAL;
}
/* Search for the volume in the priv->volumes array */
for (i = 0; i < priv->volume_count; i++) {
size_t volume_name_len = vol_end - (isobusfs_path + 2);
if (volume_name_len == strlen(priv->volumes[i].name) &&
memcmp(priv->volumes[i].name, isobusfs_path + 2,
volume_name_len) == 0) {
volume = &priv->volumes[i];
break;
}
}
if (!volume) {
pr_err("%s: invalid path %s. Can't find volume\n",
__func__, isobusfs_path);
return -ENODEV;
}
/* Copy the volume's Linux path to the output buffer */
strncpy(linux_path, volume->path, linux_path_size - 1);
linux_path[linux_path_size - 1] = '\0';
isobusfs_path_pos = vol_end - isobusfs_path;
/* Add a forward slash if path ends after volume name */
if (*vol_end == '\0' || isobusfs_path_pos == isobusfs_path_size - 1)
strncat(linux_path, "/",
linux_path_size - strlen(linux_path) - 1);
if (isobusfs_path_pos + 3 < isobusfs_path_size && strncmp(vol_end, "\\~\\", 3) == 0) {
strncat(linux_path, "/", linux_path_size - strlen(linux_path) - 1);
/* convert tilde to manufacturer-specific directory */
strncat(linux_path, priv->mfs_dir,
linux_path_size - strlen(linux_path) - 1);
vol_end += 2;
}
/* Replace backslashes with forward slashes for the rest of the path */
ptr = linux_path + strlen(linux_path);
while (vol_end < isobusfs_path + isobusfs_path_size && *vol_end) {
if (*vol_end == '\\')
*ptr = '/';
else
*ptr = *vol_end;
ptr++;
vol_end++;
if (ptr - linux_path >= (long int)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;
}