can-utils/isobusfs/isobusfs_srv_dh.c

759 lines
22 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// 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;
}