759 lines
22 KiB
C
759 lines
22 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
||
// SPDX-FileCopyrightText: 2023 Oleksij Rempel <linux@rempel-privat.de>
|
||
|
||
#include <errno.h>
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
#include <unistd.h>
|
||
#include <stdlib.h>
|
||
|
||
#include "isobusfs_srv.h"
|
||
#include "isobusfs_cmn_dh.h"
|
||
|
||
void isobusfs_srv_set_default_current_dir(struct isobusfs_srv_priv *priv,
|
||
struct isobusfs_srv_client *client)
|
||
{
|
||
snprintf(client->current_dir, ISOBUSFS_SRV_MAX_PATH_LEN, "\\\\%s",
|
||
priv->default_volume);
|
||
}
|
||
|
||
static const char *isobusfs_srv_get_volume_end(const char *path, size_t path_size)
|
||
{
|
||
const char *vol_end = NULL;
|
||
size_t i;
|
||
|
||
if (!path || !path_size)
|
||
return NULL;
|
||
|
||
if (!(path[0] == '\\' && path[1] == '\\' && path[2] != '\0'))
|
||
return NULL;
|
||
|
||
for (i = 2; i < path_size; i++) {
|
||
if (path[i] == '\\' || path[i] == '\0') {
|
||
vol_end = &path[i];
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!vol_end)
|
||
vol_end = &path[i];
|
||
|
||
return vol_end;
|
||
}
|
||
|
||
int isobusfs_path_to_linux_path(struct isobusfs_srv_priv *priv,
|
||
const char *isobusfs_path, size_t isobusfs_path_size,
|
||
char *linux_path, size_t linux_path_size)
|
||
{
|
||
struct isobusfs_srv_volume *volume = NULL;
|
||
size_t isobusfs_path_pos = 0;
|
||
const char *vol_end;
|
||
char *ptr;
|
||
int i;
|
||
|
||
if (!priv || !isobusfs_path || !linux_path || !linux_path_size ||
|
||
!isobusfs_path_size) {
|
||
pr_err("%s: invalid argument\n", __func__);
|
||
return -EINVAL;
|
||
}
|
||
|
||
vol_end = isobusfs_srv_get_volume_end(isobusfs_path, isobusfs_path_size);
|
||
if (!vol_end) {
|
||
pr_err("%s: invalid path %s. Can't find end of volume string\n",
|
||
__func__, isobusfs_path);
|
||
return -EINVAL;
|
||
}
|
||
|
||
/* Search for the volume in the priv->volumes array */
|
||
for (i = 0; i < priv->volume_count; i++) {
|
||
size_t volume_name_len = vol_end - (isobusfs_path + 2);
|
||
|
||
if (volume_name_len == strlen(priv->volumes[i].name) &&
|
||
memcmp(priv->volumes[i].name, isobusfs_path + 2,
|
||
volume_name_len) == 0) {
|
||
volume = &priv->volumes[i];
|
||
break;
|
||
}
|
||
}
|
||
if (!volume) {
|
||
pr_err("%s: invalid path %s. Can't find volume\n",
|
||
__func__, isobusfs_path);
|
||
return -ENODEV;
|
||
}
|
||
|
||
/* Copy the volume's Linux path to the output buffer */
|
||
strncpy(linux_path, volume->path, linux_path_size - 1);
|
||
linux_path[linux_path_size - 1] = '\0';
|
||
|
||
isobusfs_path_pos = vol_end - isobusfs_path;
|
||
/* Add a forward slash if path ends after volume name */
|
||
if (*vol_end == '\0' || isobusfs_path_pos == isobusfs_path_size - 1)
|
||
strncat(linux_path, "/",
|
||
linux_path_size - strlen(linux_path) - 1);
|
||
|
||
|
||
if (isobusfs_path_pos + 3 < isobusfs_path_size && strncmp(vol_end, "\\~\\", 3) == 0) {
|
||
strncat(linux_path, "/", linux_path_size - strlen(linux_path) - 1);
|
||
/* convert tilde to manufacturer-specific directory */
|
||
strncat(linux_path, priv->mfs_dir,
|
||
linux_path_size - strlen(linux_path) - 1);
|
||
vol_end += 2;
|
||
}
|
||
|
||
/* Replace backslashes with forward slashes for the rest of the path */
|
||
ptr = linux_path + strlen(linux_path);
|
||
while (vol_end < isobusfs_path + isobusfs_path_size && *vol_end) {
|
||
if (*vol_end == '\\')
|
||
*ptr = '/';
|
||
else
|
||
*ptr = *vol_end;
|
||
|
||
ptr++;
|
||
vol_end++;
|
||
if (ptr - linux_path >= linux_path_size) {
|
||
/* Ensure null termination */
|
||
linux_path[linux_path_size - 1] = '\0';
|
||
break;
|
||
}
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
int isobusfs_check_current_dir_access(struct isobusfs_srv_priv *priv,
|
||
const char *path, size_t path_size)
|
||
{
|
||
char linux_path[ISOBUSFS_SRV_MAX_PATH_LEN];
|
||
int ret;
|
||
|
||
ret = isobusfs_path_to_linux_path(priv, path, path_size,
|
||
linux_path, sizeof(linux_path));
|
||
if (ret < 0)
|
||
return ret;
|
||
|
||
pr_debug("convert ISOBUS FS path to linux path: %.*s -> %s",
|
||
path_size, path, linux_path);
|
||
|
||
ret = isobusfs_cmn_dh_validate_dir_path(linux_path, false);
|
||
if (ret < 0)
|
||
return ret;
|
||
|
||
return 0;
|
||
}
|
||
|
||
/* current directory response function */
|
||
static int isobusfs_srv_dh_current_dir_res(struct isobusfs_srv_priv *priv,
|
||
struct isobusfs_msg *msg)
|
||
{
|
||
struct isobusfs_dh_get_cd_req *req =
|
||
(struct isobusfs_dh_get_cd_req *)msg->buf;
|
||
uint8_t error_code = ISOBUSFS_ERR_SUCCESS;
|
||
struct isobusfs_dh_get_cd_res *res;
|
||
struct isobusfs_srv_client *client;
|
||
size_t str_len, buf_size;
|
||
size_t fixed_res_size;
|
||
size_t padding_size = 0;
|
||
int ret;
|
||
|
||
client = isobusfs_srv_get_client_by_msg(priv, msg);
|
||
if (!client) {
|
||
pr_warn("client not found");
|
||
return -ENOENT;
|
||
}
|
||
|
||
if (client->current_dir[0] == '\0')
|
||
isobusfs_srv_set_default_current_dir(priv, client);
|
||
|
||
ret = isobusfs_check_current_dir_access(priv, client->current_dir,
|
||
sizeof(client->current_dir));
|
||
if (ret < 0) {
|
||
switch (ret) {
|
||
case -ENOENT:
|
||
error_code = ISOBUSFS_ERR_FILE_ORPATH_NOT_FOUND;
|
||
case -ENOMEDIUM:
|
||
error_code = ISOBUSFS_ERR_VOLUME_NOT_INITIALIZED;
|
||
case -ENOMEM:
|
||
error_code = ISOBUSFS_ERR_OUT_OF_MEM;
|
||
default:
|
||
error_code = ISOBUSFS_ERR_OTHER;
|
||
}
|
||
}
|
||
|
||
fixed_res_size = sizeof(*res);
|
||
str_len = strlen(client->current_dir) + 1;
|
||
buf_size = fixed_res_size + str_len;
|
||
|
||
if (buf_size > ISOBUSFS_MAX_TRANSFER_LENGH) {
|
||
pr_warn("current directory response too long");
|
||
|
||
/* Calculate the maximum allowed string length based on the
|
||
* buffer size
|
||
*/
|
||
str_len = ISOBUSFS_MAX_TRANSFER_LENGH - fixed_res_size;
|
||
|
||
/* Update the buffer size accordingly */
|
||
buf_size = fixed_res_size + str_len;
|
||
|
||
error_code = ISOBUSFS_ERR_OUT_OF_MEM;
|
||
|
||
} else if (buf_size < ISOBUSFS_MIN_TRANSFER_LENGH) {
|
||
/* Update the buffer size accordingly */
|
||
padding_size = ISOBUSFS_MIN_TRANSFER_LENGH - buf_size;
|
||
buf_size = ISOBUSFS_MIN_TRANSFER_LENGH;
|
||
}
|
||
|
||
res = malloc(buf_size);
|
||
if (!res) {
|
||
pr_err("failed to allocate memory for current directory response");
|
||
return -ENOMEM;
|
||
}
|
||
|
||
res->fs_function =
|
||
isobusfs_cg_function_to_buf(ISOBUSFS_CG_DIRECTORY_HANDLING,
|
||
ISOBUSFS_DH_F_GET_CURRENT_DIR_RES);
|
||
res->tan = req->tan;
|
||
res->error_code = error_code;
|
||
/* TODO: implement total_space and free_space */
|
||
res->total_space = htole16(0);
|
||
res->free_space = htole16(0);
|
||
res->name_len = htole16(str_len);
|
||
memcpy(res->name, client->current_dir, str_len);
|
||
|
||
if (padding_size) {
|
||
/* Fill the rest of the res structure with 0xff */
|
||
memset(((uint8_t *)res) + buf_size - padding_size, 0xff,
|
||
padding_size);
|
||
}
|
||
|
||
/* send to socket */
|
||
ret = isobusfs_srv_sendto(priv, msg, res, buf_size);
|
||
if (ret < 0) {
|
||
pr_warn("can't send current directory response");
|
||
goto free_res;
|
||
}
|
||
|
||
pr_debug("> tx: current directory response: %s, total space: %i, free space: %i",
|
||
client->current_dir, le16toh(res->total_space),
|
||
le16toh(res->free_space));
|
||
|
||
free_res:
|
||
free(res);
|
||
|
||
return ret;
|
||
}
|
||
|
||
/**
|
||
* isobusfs_is_forbidden_char() - check if the given character is forbidden
|
||
* @ch: character to check
|
||
*
|
||
* Return: true if the character is forbidden, false otherwise
|
||
*
|
||
* The function checks if the given character is forbidden in the ISOBUS FS
|
||
* as defined in ISO 11783-13:2021, section A.2.2.1 Names:
|
||
* To avoid incompatibility between different operating systems, the client
|
||
* shall not create folder/files with names, which only differs in case, and
|
||
* names shall not end with a '.' or include ‘<’, ‘>’, ‘|’ (the latter three
|
||
* may cause issues on FAT32).
|
||
* ....
|
||
* LongNameChar ::= any single character defined by Unicode/ISO/IEC 10646,
|
||
* except 0x00 to 0x1f, 0x7f to 0x9f, ‘\’, ‘*’, ‘?’, ‘/’.
|
||
*/
|
||
static bool isobusfs_is_forbidden_char(wchar_t ch)
|
||
{
|
||
if (ch >= 0x00 && ch <= 0x1f)
|
||
return true;
|
||
if (ch >= 0x7f && ch <= 0x9f)
|
||
return true;
|
||
if (ch == L'*' || ch == L'?' || ch == L'/' ||
|
||
ch == L'<' || ch == L'>' || ch == L'|')
|
||
return true;
|
||
return false;
|
||
}
|
||
|
||
static int isobusfs_validate_path_chars(const char *path, size_t size)
|
||
{
|
||
for (size_t i = 0; i < size; ++i) {
|
||
wchar_t ch = path[i];
|
||
|
||
if (isobusfs_is_forbidden_char(ch))
|
||
return -EINVAL;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static int isobusfs_handle_path_prefix(const char *current_dir,
|
||
size_t current_dir_len,
|
||
const char *rel_path,
|
||
size_t rel_path_size,
|
||
size_t *rel_path_pos, char *abs_path,
|
||
size_t abs_path_size,
|
||
size_t *abs_path_pos)
|
||
{
|
||
if (strncmp(rel_path, "~\\", 2) == 0) {
|
||
size_t vol_len;
|
||
const char *vol_end;
|
||
|
||
vol_end = isobusfs_srv_get_volume_end(current_dir,
|
||
current_dir_len);
|
||
if (!vol_end)
|
||
return -EINVAL;
|
||
|
||
vol_len = vol_end - current_dir;
|
||
strncpy(abs_path, current_dir, vol_len);
|
||
abs_path[vol_len] = '\\';
|
||
*abs_path_pos = vol_len + 1;
|
||
} else if (strncmp(rel_path, "\\\\", 2) == 0) {
|
||
/* Too many back slashes, drop it. */
|
||
if (rel_path[2] == '\\')
|
||
return -EINVAL;
|
||
|
||
strncpy(abs_path, rel_path, 2);
|
||
*abs_path_pos = 2;
|
||
*rel_path_pos = 2;
|
||
} else {
|
||
strncpy(abs_path, current_dir, abs_path_size);
|
||
*abs_path_pos = current_dir_len;
|
||
if (abs_path[*abs_path_pos - 1] != '\\') {
|
||
if (*abs_path_pos < abs_path_size - 1) {
|
||
abs_path[*abs_path_pos] = '\\';
|
||
*abs_path_pos += 1;
|
||
} else {
|
||
return -ENOMEM;
|
||
}
|
||
}
|
||
if (rel_path[*rel_path_pos] == '\\')
|
||
*rel_path_pos += 1;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/**
|
||
* is_valid_path_char - Check if the current character is valid in the path
|
||
* @rel_path: The relative path being processed
|
||
* @rel_path_size: The size of the relative path
|
||
* @rel_path_pos: Pointer to the current position in the relative path
|
||
*
|
||
* Checks if the current character at the position in the relative path
|
||
* is not the end of the string, not a null character, and not a backslash.
|
||
*
|
||
* Return: True if the current character is valid, False otherwise.
|
||
*/
|
||
static bool is_valid_path_char(const char *rel_path, size_t rel_path_size,
|
||
const size_t *rel_path_pos)
|
||
{
|
||
return *rel_path_pos < rel_path_size &&
|
||
rel_path[*rel_path_pos] != '\0' &&
|
||
rel_path[*rel_path_pos] != '\\';
|
||
}
|
||
|
||
/**
|
||
* Checks if the specified number of positions ahead in the relative path
|
||
* are either the end of the buffer or a backslash.
|
||
*
|
||
* @param rel_path The relative path being processed.
|
||
* @param rel_path_size The size of the relative path.
|
||
* @param rel_path_pos The current position in the relative path.
|
||
* @param look_ahead The number of positions ahead to check.
|
||
* @return True if the specified positions ahead are the end or a backslash,
|
||
* False otherwise.
|
||
*/
|
||
static bool is_end_or_backslash(const char *rel_path, size_t rel_path_size,
|
||
const size_t *rel_path_pos, size_t look_ahead)
|
||
{
|
||
if (*rel_path_pos + look_ahead >= rel_path_size)
|
||
return true; /* End of buffer */
|
||
|
||
return rel_path[*rel_path_pos + look_ahead] == '\\';
|
||
}
|
||
|
||
/**
|
||
* is_path_separator - Check if the current character is a path separator
|
||
* @rel_path: The relative path being processed
|
||
* @rel_path_size: The size of the relative path
|
||
* @rel_path_pos: Pointer to the current position in the relative path
|
||
*
|
||
* Checks if the current character at the position in the relative path
|
||
* is a backslash and the position is within the string size.
|
||
*
|
||
* Return: True if the current character is a backslash, False otherwise.
|
||
*/
|
||
static bool is_path_separator(const char *rel_path, size_t rel_path_size,
|
||
const size_t *rel_path_pos)
|
||
{
|
||
return *rel_path_pos < rel_path_size && rel_path[*rel_path_pos] == '\\';
|
||
}
|
||
|
||
static bool isobusfs_is_dot_directive(const char *rel_path,
|
||
size_t rel_path_size,
|
||
const size_t *rel_path_pos)
|
||
{
|
||
if (rel_path[*rel_path_pos] == '.') {
|
||
/* Check for '.' followed by a backslash or at the end of the
|
||
* string
|
||
*/
|
||
if (is_end_or_backslash(rel_path, rel_path_size,
|
||
rel_path_pos, 1) ||
|
||
rel_path[*rel_path_pos + 1] == '\0') {
|
||
return true;
|
||
}
|
||
|
||
/* Check for '..' followed by a backslash or at the end of the
|
||
* string
|
||
*/
|
||
if (rel_path[*rel_path_pos + 1] == '.') {
|
||
if (is_end_or_backslash(rel_path, rel_path_size,
|
||
rel_path_pos, 2) ||
|
||
rel_path[*rel_path_pos + 2] == '\0') {
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* isobusfs_handle_single_dot - Processes a single dot directive in a relative
|
||
* path
|
||
* @rel_path: The relative path being processed
|
||
* @rel_path_size: The size of the relative path
|
||
* @rel_path_pos: Pointer to the current position in the relative path
|
||
*
|
||
* This function checks if the current segment in the relative path is a single
|
||
* dot ('.'). A single dot represents the current directory. If the next
|
||
* character after the dot is either a backslash or the end of the string,
|
||
* the function advances the path position appropriately. The function returns
|
||
* true if it processes a single dot, indicating that the current directory
|
||
* directive was found and handled.
|
||
*
|
||
* Return: True if a single dot directive is detected, False otherwise.
|
||
*/
|
||
static bool isobusfs_handle_single_dot(const char *rel_path,
|
||
size_t rel_path_size,
|
||
size_t *rel_path_pos)
|
||
{
|
||
bool is_dot = false;
|
||
|
||
if (is_end_or_backslash(rel_path, rel_path_size, rel_path_pos, 1)) {
|
||
*rel_path_pos += 2;
|
||
is_dot = true;
|
||
} else if (rel_path[*rel_path_pos + 1] == '\0') {
|
||
*rel_path_pos += 1;
|
||
is_dot = true;
|
||
}
|
||
|
||
return is_dot;
|
||
}
|
||
|
||
/**
|
||
* isobusfs_handle_double_dots - Processes a double dot directive in a relative
|
||
* path
|
||
*
|
||
* @rel_path: The relative path being processed
|
||
* @rel_path_size: The size of the relative path
|
||
* @rel_path_pos: Pointer to the current position in the relative path
|
||
* @abs_path: Buffer to store the absolute path being constructed
|
||
* @abs_path_pos: Pointer to the current position in the absolute path buffer
|
||
*
|
||
* This function processes the double dot directive ('..') in a relative path.
|
||
* The double dot represents the parent directory. If the double dot directive
|
||
* is followed by a backslash or is at the end of the string, the function
|
||
* advances the path position accordingly. Additionally, it adjusts the
|
||
* absolute path position to move up one directory in the path hierarchy. The
|
||
* function ensures that it does not go beyond the root of the absolute path
|
||
* while moving up the directory hierarchy.
|
||
*/
|
||
static void isobusfs_handle_double_dots(const char *rel_path,
|
||
size_t rel_path_size,
|
||
size_t *rel_path_pos, char *abs_path,
|
||
size_t *abs_path_pos)
|
||
{
|
||
/* Move the relative path position forward after handling '..' */
|
||
if (is_end_or_backslash(rel_path, rel_path_size, rel_path_pos, 2))
|
||
*rel_path_pos += 3;
|
||
else if (rel_path[*rel_path_pos + 2] == '\0')
|
||
*rel_path_pos += 2;
|
||
|
||
/* Move the absolute path position backward to simulate moving up a
|
||
* directory
|
||
*/
|
||
if (*abs_path_pos > 2 && abs_path[*abs_path_pos - 1] == '\\')
|
||
*abs_path_pos -= 1;
|
||
|
||
while (*abs_path_pos > 2 && abs_path[*abs_path_pos - 1] != '\\')
|
||
*abs_path_pos -= 1;
|
||
}
|
||
|
||
/**
|
||
* isobusfs_handle_dot_directive - Processes '.' and '..' directives in a path
|
||
* @rel_path: The relative path being processed
|
||
* @rel_path_size: The size of the relative path
|
||
* @rel_path_pos: Pointer to the current position in the relative path
|
||
* @abs_path: Buffer to store the absolute path being constructed
|
||
* @abs_path_pos: Pointer to the current position in the absolute path buffer
|
||
*
|
||
* This function processes the dot directives found in a relative path. It
|
||
* handles both single dot ('.') and double dot ('..') directives. A single dot
|
||
* represents the current directory, while a double dot represents moving up to
|
||
* the parent directory.
|
||
*/
|
||
static void isobusfs_handle_dot_directive(const char *rel_path,
|
||
size_t rel_path_size,
|
||
size_t *rel_path_pos, char *abs_path,
|
||
size_t *abs_path_pos)
|
||
{
|
||
if (rel_path[*rel_path_pos] == '.') {
|
||
bool is_dot = isobusfs_handle_single_dot(rel_path, rel_path_size,
|
||
rel_path_pos);
|
||
|
||
if (!is_dot && rel_path[*rel_path_pos + 1] == '.') {
|
||
isobusfs_handle_double_dots(rel_path, rel_path_size,
|
||
rel_path_pos, abs_path,
|
||
abs_path_pos);
|
||
}
|
||
}
|
||
|
||
/* Skip additional backslashes after '.' or '..' */
|
||
while (is_path_separator(rel_path, rel_path_size, rel_path_pos))
|
||
*rel_path_pos += 1;
|
||
}
|
||
|
||
/**
|
||
* isobusfs_process_path_segment - Processes normal path segments
|
||
* @rel_path: The relative path being processed
|
||
* @rel_path_size: The size of the relative path
|
||
* @rel_path_pos: Pointer to the current position in the relative path
|
||
* @abs_path: The buffer to store the absolute path
|
||
* @abs_path_size: The size of the absolute path buffer
|
||
* @abs_path_pos: Pointer to the position in the absolute path buffer
|
||
*
|
||
* This function processes normal segments of a relative path, copying them
|
||
* into the absolute path buffer. It handles each character until it encounters
|
||
* a path separator or reaches the end of the relative path. If a path separator
|
||
* is found, it adds a single backslash to the absolute path. The function
|
||
* ensures that the buffer limits are respected to prevent buffer overflows.
|
||
*
|
||
* Return: 0 on successful processing of the segment, -ENOMEM if the absolute
|
||
* path buffer runs out of space.
|
||
*/
|
||
static int isobusfs_process_path_segment(const char *rel_path,
|
||
size_t rel_path_size,
|
||
size_t *rel_path_pos, char *abs_path,
|
||
size_t abs_path_size,
|
||
size_t *abs_path_pos)
|
||
{
|
||
/* Process the current character from the relative path */
|
||
abs_path[*abs_path_pos] = rel_path[*rel_path_pos];
|
||
*abs_path_pos += 1;
|
||
*rel_path_pos += 1;
|
||
|
||
/* Continue processing until a path separator or end of the string is
|
||
* reached
|
||
*/
|
||
while (is_valid_path_char(rel_path, rel_path_size, rel_path_pos)) {
|
||
if (*abs_path_pos >= abs_path_size - 1)
|
||
return -ENOMEM;
|
||
|
||
abs_path[*abs_path_pos] = rel_path[*rel_path_pos];
|
||
*rel_path_pos += 1;
|
||
*abs_path_pos += 1;
|
||
}
|
||
/* Add a single backslash if next character is a backslash */
|
||
if (is_path_separator(rel_path, rel_path_size, rel_path_pos)) {
|
||
*rel_path_pos += 1;
|
||
if (*abs_path_pos < abs_path_size - 1) {
|
||
abs_path[*abs_path_pos] = '\\';
|
||
*abs_path_pos += 1;
|
||
}
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int isobusfs_handle_relative_path(const char *rel_path,
|
||
size_t rel_path_size,
|
||
size_t *rel_path_pos, char *abs_path,
|
||
size_t abs_path_size,
|
||
size_t *abs_path_pos)
|
||
{
|
||
int ret;
|
||
|
||
if (*abs_path_pos >= abs_path_size - 1)
|
||
return -ENOMEM;
|
||
|
||
/* Check for '.' or '..' followed by a backslash or at the end of the
|
||
* string
|
||
*/
|
||
if (isobusfs_is_dot_directive(rel_path, rel_path_size, rel_path_pos)) {
|
||
isobusfs_handle_dot_directive(rel_path, rel_path_size,
|
||
rel_path_pos, abs_path,
|
||
abs_path_pos);
|
||
} else {
|
||
/* Process normally for filenames or directories */
|
||
ret = isobusfs_process_path_segment(rel_path, rel_path_size,
|
||
rel_path_pos, abs_path,
|
||
abs_path_size,
|
||
abs_path_pos);
|
||
if (ret)
|
||
return ret;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
int isobusfs_convert_relative_to_absolute(struct isobusfs_srv_priv *priv,
|
||
const char *current_dir,
|
||
const char *rel_path,
|
||
size_t rel_path_size, char *abs_path,
|
||
size_t abs_path_size)
|
||
{
|
||
size_t abs_path_pos = 0;
|
||
size_t rel_path_pos = 0;
|
||
size_t current_dir_len;
|
||
int ret;
|
||
|
||
if (!current_dir || !rel_path || !abs_path || !rel_path_size ||
|
||
!abs_path_size)
|
||
return -EINVAL;
|
||
|
||
ret = isobusfs_validate_path_chars(rel_path, rel_path_size);
|
||
if (ret != 0)
|
||
return ret;
|
||
|
||
current_dir_len = strlen(current_dir);
|
||
if (current_dir_len >= abs_path_size)
|
||
return -ENOMEM;
|
||
if (current_dir_len == 0)
|
||
return -EINVAL;
|
||
|
||
ret = isobusfs_handle_path_prefix(current_dir, current_dir_len,
|
||
rel_path, rel_path_size,
|
||
&rel_path_pos, abs_path,
|
||
abs_path_size, &abs_path_pos);
|
||
if (ret)
|
||
return ret;
|
||
|
||
while (rel_path_pos < rel_path_size && rel_path[rel_path_pos] != '\0') {
|
||
ret = isobusfs_handle_relative_path(rel_path, rel_path_size,
|
||
&rel_path_pos, abs_path,
|
||
abs_path_size,
|
||
&abs_path_pos);
|
||
if (ret)
|
||
return ret;
|
||
}
|
||
|
||
abs_path[abs_path_pos] = '\0';
|
||
|
||
return 0;
|
||
}
|
||
|
||
/* change current directory response function */
|
||
static int isobusfs_srv_dh_ccd_res(struct isobusfs_srv_priv *priv,
|
||
struct isobusfs_msg *msg)
|
||
{
|
||
struct isobusfs_dh_ccd_req *req =
|
||
(struct isobusfs_dh_ccd_req *)msg->buf;
|
||
uint8_t error_code = ISOBUSFS_ERR_SUCCESS;
|
||
struct isobusfs_srv_client *client;
|
||
struct isobusfs_dh_ccd_res res;
|
||
size_t abs_path_len;
|
||
char *abs_path;
|
||
int ret;
|
||
|
||
/*
|
||
* We assime, the relative path stored in res->name is not longer
|
||
* than absolue path
|
||
*/
|
||
if (req->name_len > ISOBUSFS_SRV_MAX_PATH_LEN) {
|
||
pr_warn("path too long");
|
||
return -EINVAL;
|
||
}
|
||
|
||
client = isobusfs_srv_get_client_by_msg(priv, msg);
|
||
if (!client) {
|
||
pr_warn("client not found");
|
||
return -ENOENT;
|
||
}
|
||
|
||
abs_path_len = ISOBUSFS_SRV_MAX_PATH_LEN;
|
||
abs_path = malloc(abs_path_len);
|
||
if (!abs_path) {
|
||
pr_warn("failed to allocate memory");
|
||
return -ENOMEM;
|
||
}
|
||
|
||
pr_debug("< rx change current directory request from client 0x%2x: %.*s. Current directory: %s",
|
||
client->addr, req->name_len, req->name, client->current_dir);
|
||
/* Normalize provided string and convert it to absolute ISOBUS FS path */
|
||
ret = isobusfs_convert_relative_to_absolute(priv, client->current_dir,
|
||
(char *)req->name, req->name_len,
|
||
abs_path, abs_path_len);
|
||
if (ret < 0)
|
||
goto process_error;
|
||
|
||
pr_debug("converted relative to absolute ISOBUS FS internal path: %s", abs_path);
|
||
ret = isobusfs_check_current_dir_access(priv, abs_path, abs_path_len);
|
||
process_error:
|
||
if (ret < 0) {
|
||
/* linux_error_to_isobusfs_error() can't distinguish between
|
||
* -EINVAL vor SRC and DST, so we have to do it manually.
|
||
*/
|
||
if (ret == -EINVAL)
|
||
error_code = ISOBUSFS_ERR_INVALID_DST_NAME;
|
||
else
|
||
error_code = linux_error_to_isobusfs_error(ret);
|
||
} else {
|
||
/* change current directory */
|
||
strncpy(client->current_dir, abs_path, ISOBUSFS_SRV_MAX_PATH_LEN);
|
||
}
|
||
|
||
res.fs_function =
|
||
isobusfs_cg_function_to_buf(ISOBUSFS_CG_DIRECTORY_HANDLING,
|
||
ISOBUSFS_DH_F_CHANGE_CURRENT_DIR_RES);
|
||
res.tan = req->tan;
|
||
res.error_code = error_code;
|
||
memset(&res.reserved[0], 0xff, sizeof(res.reserved));
|
||
|
||
/* send to socket */
|
||
ret = isobusfs_srv_sendto(priv, msg, &res, sizeof(res));
|
||
if (ret < 0) {
|
||
pr_warn("can't send current directory response");
|
||
goto free_abs_path;
|
||
}
|
||
|
||
pr_debug("> tx: ccd response. Error code: %d", error_code);
|
||
free_abs_path:
|
||
free(abs_path);
|
||
|
||
return ret;
|
||
}
|
||
|
||
/* current directory response function */
|
||
/* Command group: directory handling */
|
||
int isobusfs_srv_rx_cg_dh(struct isobusfs_srv_priv *priv,
|
||
struct isobusfs_msg *msg)
|
||
{
|
||
int func = isobusfs_buf_to_function(msg->buf);
|
||
int ret = 0;
|
||
|
||
switch (func) {
|
||
case ISOBUSFS_DH_F_GET_CURRENT_DIR_REQ:
|
||
return isobusfs_srv_dh_current_dir_res(priv, msg);
|
||
case ISOBUSFS_DH_F_CHANGE_CURRENT_DIR_REQ:
|
||
return isobusfs_srv_dh_ccd_res(priv, msg);
|
||
default:
|
||
goto not_supported;
|
||
}
|
||
|
||
return ret;
|
||
|
||
not_supported:
|
||
isobusfs_srv_send_error(priv, msg, ISOBUSFS_ERR_FUNC_NOT_SUPPORTED);
|
||
|
||
pr_warn("%s: unsupported function: %i", __func__, func);
|
||
|
||
/* Not a critical error */
|
||
return 0;
|
||
}
|