pull/629/merge
Oleksij Rempel 2026-05-22 19:57:08 +02:00 committed by GitHub
commit 6556657c31
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 331 additions and 96 deletions

View File

@ -671,6 +671,10 @@ int main(int argc, char *argv[])
while (1) { while (1) {
ret = isobusfs_cli_process_events_and_tasks(priv); ret = isobusfs_cli_process_events_and_tasks(priv);
if (ret == ISOBUSFS_CLI_RET_EXIT) {
ret = 0;
break;
}
if (ret) if (ret)
break; break;
} }

View File

@ -13,6 +13,9 @@
#define ISOBUSFS_CLI_MAX_EPOLL_EVENTS 10 #define ISOBUSFS_CLI_MAX_EPOLL_EVENTS 10
#define ISOBUSFS_CLI_DEFAULT_WAIT_TIMEOUT_MS 1000 /* ms */ #define ISOBUSFS_CLI_DEFAULT_WAIT_TIMEOUT_MS 1000 /* ms */
/* internal return codes, not errno values */
#define ISOBUSFS_CLI_RET_EXIT 1
enum isobusfs_cli_state { enum isobusfs_cli_state {
ISOBUSFS_CLI_STATE_CONNECTING, ISOBUSFS_CLI_STATE_CONNECTING,
ISOBUSFS_CLI_STATE_IDLE, ISOBUSFS_CLI_STATE_IDLE,

View File

@ -16,6 +16,12 @@
#define MAX_COMMAND_LENGTH 256 #define MAX_COMMAND_LENGTH 256
#define MAX_DISPLAY_FILENAME_LENGTH 100 #define MAX_DISPLAY_FILENAME_LENGTH 100
/*
* ISO 11783-13:2021 B.21 minimal directory entry payload size in bytes:
* 1 (name length) + 1 (min name byte) + 1 (attributes) +
* 2 (date) + 2 (time) + 4 (size).
*/
#define ISOBUSFS_MIN_DIR_ENTRY_SIZE (1 + 1 + 1 + 2 + 2 + 4)
struct command_mapping { struct command_mapping {
const char *command; const char *command;
@ -66,8 +72,8 @@ static int cmd_help(struct isobusfs_priv *priv, const char *options)
static int cmd_exit(struct isobusfs_priv *priv, const char *options) static int cmd_exit(struct isobusfs_priv *priv, const char *options)
{ {
pr_int("exit interactive mode\n"); pr_int("exit interactive mode\n");
/* Return -EINTR to indicate the program should exit */
return -EINTR; return ISOBUSFS_CLI_RET_EXIT;
} }
static int cmd_dmesg(struct isobusfs_priv *priv, const char *options) static int cmd_dmesg(struct isobusfs_priv *priv, const char *options)
@ -510,8 +516,12 @@ isobusfs_cli_ls_handle_open_dir_sent(struct isobusfs_priv *priv,
ctx->handle = res->handle; ctx->handle = res->handle;
ctx->offset = 0;
ctx->entry_count = 0;
ret = isobusfs_cli_send_and_register_fa_sf_event(priv, ctx->handle, ret = isobusfs_cli_send_and_register_fa_sf_event(priv, ctx->handle,
0, ctx->entry_count, ISOBUSFS_FA_SEEK_SET,
ctx->offset,
cb, ctx); cb, ctx);
if (ret) if (ret)
pr_int("Failed to send seek file request: %i\n", ret); pr_int("Failed to send seek file request: %i\n", ret);
@ -530,7 +540,7 @@ isobusfs_cli_ls_handle_seek_dir_sent(struct isobusfs_priv *priv,
{ {
isobusfs_event_callback cb = isobusfs_cli_ls_event_callback; isobusfs_event_callback cb = isobusfs_cli_ls_event_callback;
struct isobusfs_fa_seekf_res *res = struct isobusfs_fa_seekf_res *res =
(struct isobusfs_fa_seekf_res *)msg; (struct isobusfs_fa_seekf_res *)msg->buf;
uint16_t count; uint16_t count;
int ret; int ret;
@ -543,8 +553,10 @@ isobusfs_cli_ls_handle_seek_dir_sent(struct isobusfs_priv *priv,
goto error; goto error;
} }
/* set max possible number fitting in to 16bits */ /* ISO 11783-13:2021 C.3.5.2: count is number of directory entries. */
count = UINT16_MAX; count = ISOBUSFS_MAX_DATA_LENGH / ISOBUSFS_MIN_DIR_ENTRY_SIZE;
if (!count)
count = 1;
ctx->request_count = count; ctx->request_count = count;
ret = isobusfs_cli_send_and_register_fa_rf_event(priv, ctx->handle, ret = isobusfs_cli_send_and_register_fa_rf_event(priv, ctx->handle,
@ -616,8 +628,8 @@ static bool isobusfs_cli_extract_directory_entry(const uint8_t *buffer,
uint16_t *file_time, uint16_t *file_time,
uint32_t *file_size) uint32_t *file_size)
{ {
size_t entry_total_len, copy_len;
uint8_t filename_length; uint8_t filename_length;
size_t entry_total_len;
if (*pos + 2 > buffer_length) { if (*pos + 2 > buffer_length) {
pr_int("Error: Incomplete data in buffer\n"); pr_int("Error: Incomplete data in buffer\n");
@ -633,8 +645,14 @@ static bool isobusfs_cli_extract_directory_entry(const uint8_t *buffer,
} }
(*pos)++; (*pos)++;
strncpy(filename, (const char *)buffer + *pos, filename_length);
filename[filename_length] = '\0'; if (filename_length > ISOBUSFS_MAX_DIR_ENTRY_NAME_LENGTH)
copy_len = ISOBUSFS_MAX_DIR_ENTRY_NAME_LENGTH;
else
copy_len = filename_length;
strncpy(filename, (const char *)buffer + *pos, copy_len);
filename[copy_len] = '\0';
*pos += filename_length; *pos += filename_length;
if (filename_length > MAX_DISPLAY_FILENAME_LENGTH) { if (filename_length > MAX_DISPLAY_FILENAME_LENGTH) {
/* Truncate the filename and replace the last character /* Truncate the filename and replace the last character
@ -688,15 +706,17 @@ isobusfs_cli_print_directory_entry(struct isobusfs_cli_ls_context *ctx,
static void static void
isobusfs_cli_print_directory_entries(struct isobusfs_cli_ls_context *ctx, isobusfs_cli_print_directory_entries(struct isobusfs_cli_ls_context *ctx,
const uint8_t *buffer, const uint8_t *buffer,
size_t buffer_length) size_t buffer_length,
uint16_t max_entries)
{ {
char filename[ISOBUSFS_MAX_DIR_ENTRY_NAME_LENGTH + 1]; char filename[ISOBUSFS_MAX_DIR_ENTRY_NAME_LENGTH + 1];
uint16_t file_date, file_time; uint16_t file_date, file_time;
uint32_t file_size; uint32_t file_size;
uint8_t attributes; uint8_t attributes;
size_t pos = 0; size_t pos = 0;
uint16_t entries = 0;
while (pos < buffer_length) { while (pos < buffer_length && entries < max_entries) {
if (!isobusfs_cli_extract_directory_entry(buffer, buffer_length, if (!isobusfs_cli_extract_directory_entry(buffer, buffer_length,
&pos, filename, &pos, filename,
&attributes, &attributes,
@ -709,6 +729,7 @@ isobusfs_cli_print_directory_entries(struct isobusfs_cli_ls_context *ctx,
file_date, file_time, file_date, file_time,
file_size); file_size);
ctx->entry_count++; ctx->entry_count++;
entries++;
} }
} }
@ -721,26 +742,42 @@ isobusfs_cli_ls_handle_read_dir_sent(struct isobusfs_priv *priv,
(struct isobusfs_read_file_response *)msg->buf; (struct isobusfs_read_file_response *)msg->buf;
size_t buffer_length = msg->len - sizeof(*res); size_t buffer_length = msg->len - sizeof(*res);
isobusfs_event_callback cb; isobusfs_event_callback cb;
size_t entries_before;
size_t entries_in_message;
uint16_t count; uint16_t count;
int ret; int ret;
pr_debug("< rx: Read File Response. Error code: %i", res->error_code); 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)) if (isobusfs_cli_int_is_error(priv, 0, res->error_code, res->tan))
goto error; goto error;
count = le16toh(res->count); count = le16toh(res->count);
if (count && count != buffer_length) { if (count && buffer_length) {
pr_int("Buffer length mismatch: %u != %zu\n", count, entries_before = ctx->entry_count;
buffer_length); isobusfs_cli_print_directory_entries(ctx, res->data,
goto error; buffer_length, count);
entries_in_message = ctx->entry_count - entries_before;
} else {
entries_in_message = 0;
} }
if (count) if (count != entries_in_message) {
isobusfs_cli_print_directory_entries(ctx, res->data, pr_warn("Directory entry count mismatch: server sent %u, parsed %zu\n",
buffer_length); count, entries_in_message);
/* Continue processing if we parsed at least some entries.
* Strict validation would reject valid responses if parsing
* fails partway through due to corruption.
*/
if (entries_in_message == 0 && count > 0) {
pr_int("Error: Failed to parse any directory entries\n");
goto error;
}
}
cb = isobusfs_cli_ls_event_callback; cb = isobusfs_cli_ls_event_callback;
if (count) {
if (res->error_code == ISOBUSFS_ERR_END_OF_FILE) {
ret = isobusfs_cli_send_and_register_fa_cf_event(priv, ret = isobusfs_cli_send_and_register_fa_cf_event(priv,
ctx->handle, ctx->handle,
cb, ctx); cb, ctx);
@ -750,21 +787,31 @@ isobusfs_cli_ls_handle_read_dir_sent(struct isobusfs_priv *priv,
} }
ctx->state = ISOBUSFS_CLI_LS_STATE_CLOSE_DIR_SENT; ctx->state = ISOBUSFS_CLI_LS_STATE_CLOSE_DIR_SENT;
} else { return;
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;
} }
if (!count) {
pr_int("Error: zero-length read without EOF\n");
goto error;
}
/*
* Directory seek offset is entry index, not byte offset.
* Server side seek rewinds and skips "offset" entries.
*/
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; return;
error: error:
ctx->state = ISOBUSFS_CLI_LS_STATE_ERROR; ctx->state = ISOBUSFS_CLI_LS_STATE_ERROR;
} }

View File

@ -603,23 +603,24 @@ struct isobusfs_cli_test_rf_path {
uint8_t flags; uint8_t flags;
uint32_t offset; uint32_t offset;
uint32_t read_size; uint32_t read_size;
uint32_t expected_size;
bool expect_pass; bool expect_pass;
}; };
static struct isobusfs_cli_test_rf_path test_rf_patterns[] = { static struct isobusfs_cli_test_rf_path test_rf_patterns[] = {
/* expected result \\vol1\dir1\dir2\ */ /* expected result \\vol1\dir1\dir2\ */
{ "\\\\vol1\\dir1\\dir2\\file1k", 0, 0, 0, true }, { "\\\\vol1\\dir1\\dir2\\file1k", 0, 0, 0, 0, true },
{ "\\\\vol1\\dir1\\dir2\\file1k", 0, 0, 1, true }, { "\\\\vol1\\dir1\\dir2\\file1k", 0, 0, 1, 1, true },
{ "\\\\vol1\\dir1\\dir2\\file1k", 0, 1, 1, true }, { "\\\\vol1\\dir1\\dir2\\file1k", 0, 1, 1, 1, true },
{ "\\\\vol1\\dir1\\dir2\\file1k", 0, 2, 1, true }, { "\\\\vol1\\dir1\\dir2\\file1k", 0, 2, 1, 1, true },
{ "\\\\vol1\\dir1\\dir2\\file1k", 0, 3, 1, true }, { "\\\\vol1\\dir1\\dir2\\file1k", 0, 3, 1, 1, true },
{ "\\\\vol1\\dir1\\dir2\\file1m", 0, 0, 8, true }, { "\\\\vol1\\dir1\\dir2\\file1m", 0, 0, 8, 8, true },
{ "\\\\vol1\\dir1\\dir2\\file1m", 0, 0, 8 * 100, true }, { "\\\\vol1\\dir1\\dir2\\file1m", 0, 0, 8 * 100, 8 * 100, true },
{ "\\\\vol1\\dir1\\dir2\\file1m", 0, 100, 8 * 100, true }, { "\\\\vol1\\dir1\\dir2\\file1m", 0, 100, 8 * 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, 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 & ~3) + 16, (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, ISOBUSFS_MAX_DATA_LENGH + 1, ISOBUSFS_MAX_DATA_LENGH + 1, true },
{ "\\\\vol1\\dir1\\dir2\\file1m", 0, 0, -1, true }, { "\\\\vol1\\dir1\\dir2\\file1m", 0, 0, UINT32_MAX, 1024 * 1024, true },
}; };
size_t current_rf_pattern_test; size_t current_rf_pattern_test;
@ -678,7 +679,8 @@ static int isobusfs_cli_test_rf_req(struct isobusfs_priv *priv, bool *complete)
&test_rf_patterns[current_rf_pattern_test]; &test_rf_patterns[current_rf_pattern_test];
uint32_t actual_sum, expected_sum; uint32_t actual_sum, expected_sum;
struct timespec current_time; struct timespec current_time;
ssize_t remaining_size, read_size; int64_t remaining_size;
ssize_t read_size;
int ret; int ret;
clock_gettime(CLOCK_MONOTONIC, &current_time); clock_gettime(CLOCK_MONOTONIC, &current_time);
@ -801,16 +803,23 @@ static int isobusfs_cli_test_rf_req(struct isobusfs_priv *priv, bool *complete)
free(priv->read_data); free(priv->read_data);
priv->read_data = NULL; priv->read_data = NULL;
remaining_size = (tp->offset + tp->read_size) - remaining_size = ((int64_t)tp->offset + tp->read_size) -
(priv->read_offset + priv->read_data_len); ((int64_t)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", pr_debug("remaining_size: %lld, read_offset: %zu, read_data_len: %zu, test read size: %u, test offset %u",
remaining_size, priv->read_offset, priv->read_data_len, (long long)remaining_size, priv->read_offset, priv->read_data_len,
tp->read_size, tp->offset); tp->read_size, tp->offset);
if (remaining_size < 0) { if (remaining_size < 0) {
pr_err("pattern test failed: %s. Read size is too big", pr_err("pattern test failed: %s. Read size is too big",
tp->path_name); tp->path_name);
ret = -EINVAL; ret = -EINVAL;
goto test_fail; goto test_fail;
} else if (remaining_size == 0) {
if (tp->read_size > tp->expected_size) {
pr_err("read test failed: %s. Server returned more data than expected file size (%u > %u)",
tp->path_name, tp->read_size, tp->expected_size);
ret = -EINVAL;
goto test_fail;
}
} else if (remaining_size > 0 && priv->read_data_len != 0) { } else if (remaining_size > 0 && priv->read_data_len != 0) {
priv->read_offset += priv->read_data_len; priv->read_offset += priv->read_data_len;
@ -825,11 +834,20 @@ static int isobusfs_cli_test_rf_req(struct isobusfs_priv *priv, bool *complete)
goto test_fail; goto test_fail;
test_start_time = current_time; test_start_time = current_time;
break; break;
} else if (remaining_size > 0 && priv->read_data_len == 0 && tp->expect_pass) { } else if (remaining_size > 0 && priv->read_data_len == 0) {
pr_err("read test failed: %s. Read size is zero, but expected more data: %zd", if (tp->read_size > tp->expected_size &&
tp->path_name, remaining_size); tp->read_size - remaining_size == tp->expected_size) {
ret = -EINVAL; /* this is acceptable case when read size
goto test_fail; * is larger than actual file size
*/
pr_info("read test passed: %s. Reached end of file as expected.",
tp->path_name);
} else {
pr_err("read test failed: %s. Server returned zero bytes, but expected more data: %lld",
tp->path_name, (long long)remaining_size);
ret = -EINVAL;
goto test_fail;
}
} }
/* fall troth */ /* fall troth */

View File

@ -495,15 +495,119 @@ static int check_access_with_base(const char *base_dir,
return access(full_path, mode); return access(full_path, mode);
} }
/*
* ISO 11783-13:2021 B.21 and B.15:
* Filters directory entries that can be returned in a Read File response
* for directory handles while collecting the attributes and name length.
*/
/**
* isobusfs_srv_dir_entry_visible() - Filter visible directory entries.
* @handle: Directory handle for access checks.
* @entry: Directory entry to inspect.
* @file_stat: Stat buffer to fill for the entry.
* @entry_name_len: Returns entry name length on success.
* @attributes: Returns computed attributes on success.
*
* ISO 11783-13:2021 B.21 and B.15 define the directory entry layout and
* attributes. This helper skips entries that are not readable or too long
* and returns attributes for the entry that will be serialized.
*
* Return: true when the entry should be emitted, false otherwise.
*/
static bool isobusfs_srv_dir_entry_visible(struct isobusfs_srv_handles *handle,
const struct dirent *entry,
struct stat *file_stat,
size_t *entry_name_len,
uint8_t *attributes)
{
if (check_access_with_base(handle->path, entry->d_name, R_OK) != 0)
return false;
if (fstatat(handle->fd, entry->d_name, file_stat, 0) < 0)
return false;
*entry_name_len = strlen(entry->d_name);
if (*entry_name_len > ISOBUSFS_MAX_DIR_ENTRY_NAME_LENGTH)
return false;
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;
return true;
}
/*
* ISO 11783-13:2021 C.3.4.2 and C.3.5.2:
* Directory offsets/counts are in entries, so advance the directory stream
* by visible entries only and report EOF if the entry offset is past the end.
*/
/**
* isobusfs_srv_dir_skip_entries() - Advance directory stream by entries.
* @handle: Directory handle to reposition.
* @offset: Entry offset to seek to.
*
* ISO 11783-13:2021 C.3.4.2 and C.3.5.2 state directory offsets/counts are in
* entries. This helper advances by visible entries only.
*
* Return: ISOBUSFS_ERR_SUCCESS or a protocol error code.
*/
static int isobusfs_srv_dir_skip_entries(struct isobusfs_srv_handles *handle,
int32_t offset)
{
struct dirent *entry;
int32_t skipped = 0;
rewinddir(handle->dir);
while (skipped < offset && (entry = readdir(handle->dir)) != NULL) {
struct stat file_stat;
size_t entry_name_len = 0;
uint8_t attributes = 0;
if (!isobusfs_srv_dir_entry_visible(handle, entry, &file_stat,
&entry_name_len,
&attributes))
continue;
skipped++;
}
if (offset > 0 && skipped < offset)
return ISOBUSFS_ERR_END_OF_FILE;
return ISOBUSFS_ERR_SUCCESS;
}
/**
* isobusfs_srv_read_directory() - Read directory entries into a response buffer.
* @handle: Directory handle to read.
* @buffer: Output buffer for directory entries.
* @max_bytes: Maximum payload bytes allowed in the response.
* @max_entries: Maximum number of entries to return.
* @readed_size: Returns payload size in bytes.
* @entries_read: Returns number of entries serialized.
*
* ISO 11783-13:2021 C.3.5.2: directory Count is entry based. This function
* encodes up to @max_entries entries while respecting @max_bytes.
*
* Return: ISOBUSFS_ERR_SUCCESS or a protocol error code.
*/
static int isobusfs_srv_read_directory(struct isobusfs_srv_handles *handle, static int isobusfs_srv_read_directory(struct isobusfs_srv_handles *handle,
uint8_t *buffer, size_t count, uint8_t *buffer, size_t max_bytes,
ssize_t *readed_size) uint16_t max_entries,
ssize_t *readed_size,
uint16_t *entries_read)
{ {
DIR *dir = handle->dir; DIR *dir = handle->dir;
struct dirent *entry; struct dirent *entry;
size_t pos = 0; size_t pos = 0;
int ret;
/* /*
* ISO 11783-13:2021 C.3.5.2:
* Directory offsets are entry indices, not byte positions.
* Position the directory stream to the previously stored offset (handle->dir_pos). * Position the directory stream to the previously stored offset (handle->dir_pos).
* *
* Handling Changes in Directory Contents: * Handling Changes in Directory Contents:
@ -519,13 +623,18 @@ static int isobusfs_srv_read_directory(struct isobusfs_srv_handles *handle,
* either returning an error or restarting from the beginning of the directory, depending * either returning an error or restarting from the beginning of the directory, depending
* on the application's requirements. * on the application's requirements.
*/ */
for (int i = 0; i < handle->dir_pos && if (handle->dir_pos < 0) {
(entry = readdir(dir)) != NULL; i++) { pr_err("Directory handle in invalid state (pos=%d)\n",
/* Iterating to the desired position */ handle->dir_pos);
return ISOBUSFS_ERR_INVALID_HANDLE;
} }
ret = isobusfs_srv_dir_skip_entries(handle, handle->dir_pos);
if (ret != ISOBUSFS_ERR_SUCCESS)
return ret;
/* /*
* Directory Entry Layout: * Directory Entry Layout (ISO 11783-13:2021 B.21):
* This loop reads directory entries and encodes them into a buffer. * 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. * Each entry in the buffer follows the format specified in ISO 11783-13:2021.
* *
@ -550,26 +659,23 @@ static int isobusfs_srv_read_directory(struct isobusfs_srv_handles *handle,
* The handle->dir_pos is incremented after processing each entry, marking * The handle->dir_pos is incremented after processing each entry, marking
* the current position in the directory stream for subsequent reads. * the current position in the directory stream for subsequent reads.
*/ */
while ((entry = readdir(dir)) != NULL) { *entries_read = 0;
while ((entry = readdir(dir)) != NULL &&
(*entries_read) < max_entries) {
size_t entry_name_len, entry_total_len; size_t entry_name_len, entry_total_len;
__le16 file_date, file_time; __le16 file_date, file_time;
uint8_t attributes = 0;
struct stat file_stat; struct stat file_stat;
uint8_t attributes = 0;
__le32 size; __le32 size;
if (check_access_with_base(handle->path, entry->d_name, R_OK) != 0) if (!isobusfs_srv_dir_entry_visible(handle, entry, &file_stat,
continue; /* Skip this entry if it's not readable */ &entry_name_len,
&attributes))
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; continue;
entry_total_len = 1 + entry_name_len + 1 + 2 + 2 + 4; entry_total_len = 1 + entry_name_len + 1 + 2 + 2 + 4;
if (pos + entry_total_len > count) if (pos + entry_total_len > max_bytes)
break; break;
buffer[pos++] = (uint8_t)entry_name_len; buffer[pos++] = (uint8_t)entry_name_len;
@ -577,10 +683,6 @@ static int isobusfs_srv_read_directory(struct isobusfs_srv_handles *handle,
memcpy(buffer + pos, entry->d_name, entry_name_len); memcpy(buffer + pos, entry->d_name, entry_name_len);
pos += 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; buffer[pos++] = attributes;
file_date = htole16(convert_to_file_date(file_stat.st_mtime)); file_date = htole16(convert_to_file_date(file_stat.st_mtime));
@ -594,9 +696,11 @@ static int isobusfs_srv_read_directory(struct isobusfs_srv_handles *handle,
size = htole32(file_stat.st_size); size = htole32(file_stat.st_size);
memcpy(buffer + pos, &size, sizeof(size)); memcpy(buffer + pos, &size, sizeof(size));
pos += sizeof(size); pos += sizeof(size);
(*entries_read)++;
} }
*readed_size = pos; *readed_size = pos;
handle->dir_pos += *entries_read;
return 0; return 0;
} }
@ -609,13 +713,18 @@ static int isobusfs_srv_fa_rf_req(struct isobusfs_srv_priv *priv,
struct isobusfs_srv_client *client; struct isobusfs_srv_client *client;
struct isobusfs_fa_readf_req *req; struct isobusfs_fa_readf_req *req;
ssize_t readed_size = 0; ssize_t readed_size = 0;
uint16_t entries_read = 0;
uint8_t error_code = 0; uint8_t error_code = 0;
ssize_t send_size; ssize_t send_size;
bool res_allocated = false;
size_t max_bytes = 0;
uint16_t count = 0;
int ret = 0; int ret = 0;
int count; bool is_dir = false;
req = (struct isobusfs_fa_readf_req *)msg->buf; req = (struct isobusfs_fa_readf_req *)msg->buf;
count = le16toh(req->count); count = le16toh(req->count);
res = (struct isobusfs_read_file_response *)&res_fail[0];
pr_debug("< rx: Read File Request. tan: %d, handle: %d, count: %d", pr_debug("< rx: Read File Request. tan: %d, handle: %d, count: %d",
req->tan, req->handle, count); req->tan, req->handle, count);
@ -627,17 +736,6 @@ static int isobusfs_srv_fa_rf_req(struct isobusfs_srv_priv *priv,
* TODO: currently we are not able to detect support transport mode, * TODO: currently we are not able to detect support transport mode,
* so ETP is assumed. * 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); client = isobusfs_srv_get_client_by_msg(priv, msg);
if (!client) { if (!client) {
pr_warn("client not found"); pr_warn("client not found");
@ -649,12 +747,33 @@ static int isobusfs_srv_fa_rf_req(struct isobusfs_srv_priv *priv,
if (!handle) { if (!handle) {
pr_warn("failed to find file with handle: %x", req->handle); pr_warn("failed to find file with handle: %x", req->handle);
error_code = ISOBUSFS_ERR_FILE_ORPATH_NOT_FOUND; error_code = ISOBUSFS_ERR_FILE_ORPATH_NOT_FOUND;
goto send_response;
} }
/* Determine whether to read a file or a directory */ /* Determine whether to read a file or a directory */
if (handle->dir) { if (handle->dir) {
ret = isobusfs_srv_read_directory(handle, res->data, count, /* ISO 11783-13:2021 C.3.5.2: count is entry count for directories. */
&readed_size); is_dir = true;
max_bytes = ISOBUSFS_MAX_DATA_LENGH;
} else {
if (count > ISOBUSFS_MAX_DATA_LENGH)
count = ISOBUSFS_MAX_DATA_LENGH;
max_bytes = count;
}
res = malloc(sizeof(*res) + max_bytes);
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;
}
res_allocated = true;
if (is_dir) {
ret = isobusfs_srv_read_directory(handle, res->data, max_bytes,
count, &readed_size,
&entries_read);
} else { } else {
ret = isobusfs_srv_read_file(handle, res->data, count, ret = isobusfs_srv_read_file(handle, res->data, count,
&readed_size); &readed_size);
@ -663,7 +782,10 @@ static int isobusfs_srv_fa_rf_req(struct isobusfs_srv_priv *priv,
if (ret < 0) { if (ret < 0) {
error_code = ret; error_code = ret;
readed_size = 0; readed_size = 0;
} else if (count != 0 && readed_size == 0) { entries_read = 0;
} else if (count != 0 &&
((is_dir && entries_read == 0) ||
(!is_dir && readed_size == 0))) {
error_code = ISOBUSFS_ERR_END_OF_FILE; error_code = ISOBUSFS_ERR_END_OF_FILE;
} }
@ -673,7 +795,10 @@ send_response:
ISOBUSFS_FA_F_READ_FILE_RES); ISOBUSFS_FA_F_READ_FILE_RES);
res->tan = req->tan; res->tan = req->tan;
res->error_code = error_code; res->error_code = error_code;
res->count = htole16(readed_size); if (is_dir)
res->count = htole16(entries_read);
else
res->count = htole16(readed_size);
send_size = sizeof(*res) + readed_size; send_size = sizeof(*res) + readed_size;
if (send_size < ISOBUSFS_MIN_TRANSFER_LENGH) if (send_size < ISOBUSFS_MIN_TRANSFER_LENGH)
@ -690,7 +815,8 @@ send_response:
error_code, isobusfs_error_to_str(error_code), readed_size); error_code, isobusfs_error_to_str(error_code), readed_size);
free_res: free_res:
free(res); if (res_allocated)
free(res);
return ret; return ret;
} }
@ -754,19 +880,56 @@ static int isobusfs_srv_seek(struct isobusfs_srv_priv *priv,
return ISOBUSFS_ERR_SUCCESS; return ISOBUSFS_ERR_SUCCESS;
} }
/**
* isobusfs_srv_seek_directory() - Seek a directory by entry index.
* @handle: Directory handle to seek.
* @offset: Entry index to seek to.
*
* ISO 11783-13:2021 C.3.4.2 defines directory offsets as entry indices.
*
* Return: ISOBUSFS_ERR_SUCCESS or a protocol error code.
*/
static int isobusfs_srv_seek_directory(struct isobusfs_srv_handles *handle, static int isobusfs_srv_seek_directory(struct isobusfs_srv_handles *handle,
int32_t offset) int32_t offset)
{ {
DIR *dir = fdopendir(handle->fd); int32_t current_pos = handle->dir_pos;
int ret;
if (!dir) if (!handle->dir)
return ISOBUSFS_ERR_OTHER; return ISOBUSFS_ERR_OTHER;
rewinddir(dir); /* Check if handle is in valid state */
if (current_pos < 0) {
pr_err("Cannot seek directory in invalid state (pos=%d)\n",
current_pos);
return ISOBUSFS_ERR_INVALID_HANDLE;
}
for (int32_t i = 0; i < offset; i++) { /* Validate requested offset */
if (readdir(dir) == NULL) if (offset < 0) {
return ISOBUSFS_ERR_END_OF_FILE; pr_err("Invalid seek offset: %d\n", offset);
return ISOBUSFS_ERR_INVALID_REQUESTED_LENGHT;
}
/*
* ISO 11783-13:2021 C.3.4.2:
* Directory offsets are entry indices. If we fail to seek, restore
* the previous entry position since the position shall not change on error.
*/
ret = isobusfs_srv_dir_skip_entries(handle, offset);
if (ret != ISOBUSFS_ERR_SUCCESS) {
int restore_ret;
restore_ret = isobusfs_srv_dir_skip_entries(handle, current_pos);
if (restore_ret != ISOBUSFS_ERR_SUCCESS) {
pr_warn("Failed to restore directory position after seek failure: %d -> %d",
current_pos, restore_ret);
/* Directory stream is now in undefined state.
* Mark position as invalid.
*/
handle->dir_pos = -1;
}
return ret;
} }
handle->dir_pos = offset; handle->dir_pos = offset;