From 5a681968a2629afe557bb0df5067df43835eadc6 Mon Sep 17 00:00:00 2001 From: Oleksij Rempel Date: Sun, 4 Jan 2026 18:42:14 +0100 Subject: [PATCH 1/3] isobusfs: selftest: Handle read requests larger than file size Add expected_size field to test patterns to properly validate cases where the requested read size exceeds the actual file size. This allows testing the server's behavior when clients request more data than is available. When a read request is larger than the file, the server correctly returns data up to EOF. The test framework now distinguishes between this expected scenario and genuine read failures. Signed-off-by: Oleksij Rempel --- isobusfs/isobusfs_cli_selftests.c | 62 ++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 22 deletions(-) diff --git a/isobusfs/isobusfs_cli_selftests.c b/isobusfs/isobusfs_cli_selftests.c index d8e4cbb9..68530a25 100644 --- a/isobusfs/isobusfs_cli_selftests.c +++ b/isobusfs/isobusfs_cli_selftests.c @@ -603,23 +603,24 @@ struct isobusfs_cli_test_rf_path { uint8_t flags; uint32_t offset; uint32_t read_size; + uint32_t expected_size; bool expect_pass; }; static struct isobusfs_cli_test_rf_path test_rf_patterns[] = { /* expected result \\vol1\dir1\dir2\ */ - { "\\\\vol1\\dir1\\dir2\\file1k", 0, 0, 0, true }, - { "\\\\vol1\\dir1\\dir2\\file1k", 0, 0, 1, true }, - { "\\\\vol1\\dir1\\dir2\\file1k", 0, 1, 1, true }, - { "\\\\vol1\\dir1\\dir2\\file1k", 0, 2, 1, true }, - { "\\\\vol1\\dir1\\dir2\\file1k", 0, 3, 1, true }, - { "\\\\vol1\\dir1\\dir2\\file1m", 0, 0, 8, true }, - { "\\\\vol1\\dir1\\dir2\\file1m", 0, 0, 8 * 100, true }, - { "\\\\vol1\\dir1\\dir2\\file1m", 0, 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 & ~3) + 16, true }, - { "\\\\vol1\\dir1\\dir2\\file1m", 0, 0, ISOBUSFS_MAX_DATA_LENGH + 1, true }, - { "\\\\vol1\\dir1\\dir2\\file1m", 0, 0, -1, true }, + { "\\\\vol1\\dir1\\dir2\\file1k", 0, 0, 0, 0, true }, + { "\\\\vol1\\dir1\\dir2\\file1k", 0, 0, 1, 1, true }, + { "\\\\vol1\\dir1\\dir2\\file1k", 0, 1, 1, 1, true }, + { "\\\\vol1\\dir1\\dir2\\file1k", 0, 2, 1, 1, true }, + { "\\\\vol1\\dir1\\dir2\\file1k", 0, 3, 1, 1, true }, + { "\\\\vol1\\dir1\\dir2\\file1m", 0, 0, 8, 8, true }, + { "\\\\vol1\\dir1\\dir2\\file1m", 0, 0, 8 * 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, ISOBUSFS_MAX_DATA_LENGH, 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, ISOBUSFS_MAX_DATA_LENGH + 1, true }, + { "\\\\vol1\\dir1\\dir2\\file1m", 0, 0, UINT32_MAX, 1024 * 1024, true }, }; 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]; uint32_t actual_sum, expected_sum; struct timespec current_time; - ssize_t remaining_size, read_size; + int64_t remaining_size; + ssize_t read_size; int ret; clock_gettime(CLOCK_MONOTONIC, ¤t_time); @@ -801,16 +803,23 @@ static int isobusfs_cli_test_rf_req(struct isobusfs_priv *priv, bool *complete) free(priv->read_data); priv->read_data = NULL; - remaining_size = (tp->offset + tp->read_size) - - (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", - remaining_size, priv->read_offset, priv->read_data_len, + remaining_size = ((int64_t)tp->offset + tp->read_size) - + ((int64_t)priv->read_offset + priv->read_data_len); + pr_debug("remaining_size: %lld, read_offset: %zu, read_data_len: %zu, test read size: %u, test offset %u", + (long long)remaining_size, priv->read_offset, priv->read_data_len, tp->read_size, tp->offset); if (remaining_size < 0) { pr_err("pattern test failed: %s. Read size is too big", tp->path_name); ret = -EINVAL; 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) { 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; test_start_time = current_time; break; - } else if (remaining_size > 0 && priv->read_data_len == 0 && tp->expect_pass) { - pr_err("read test failed: %s. Read size is zero, but expected more data: %zd", - tp->path_name, remaining_size); - ret = -EINVAL; - goto test_fail; + } else if (remaining_size > 0 && priv->read_data_len == 0) { + if (tp->read_size > tp->expected_size && + tp->read_size - remaining_size == tp->expected_size) { + /* this is acceptable case when read size + * 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 */ From a5bd7919996181e4439b80640067bdaedbd87c1f Mon Sep 17 00:00:00 2001 From: Oleksij Rempel Date: Tue, 6 Jan 2026 11:19:50 +0100 Subject: [PATCH 2/3] isobusfs: treat interactive exit as control flow, not error The cmd_exit() function used -EINTR to signal a clean exit from the interactive mode. This is semantically incorrect because EINTR is an errno value meant for system call interruptions, not application-level control flow. Stop using -EINTR for cmd_exit(). Introduce ISOBUSFS_CLI_RET_EXIT (a positive return code) to distinguish control flow from actual errors. Map this to exit code 0 in main(), keeping negative errno values for real errors. This makes the exit path explicit and prevents confusion with actual interrupted system calls. Signed-off-by: Oleksij Rempel --- isobusfs/isobusfs_cli.c | 4 ++++ isobusfs/isobusfs_cli.h | 3 +++ isobusfs/isobusfs_cli_int.c | 4 ++-- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/isobusfs/isobusfs_cli.c b/isobusfs/isobusfs_cli.c index 6d74bbae..b1f1e6c5 100644 --- a/isobusfs/isobusfs_cli.c +++ b/isobusfs/isobusfs_cli.c @@ -671,6 +671,10 @@ int main(int argc, char *argv[]) while (1) { ret = isobusfs_cli_process_events_and_tasks(priv); + if (ret == ISOBUSFS_CLI_RET_EXIT) { + ret = 0; + break; + } if (ret) break; } diff --git a/isobusfs/isobusfs_cli.h b/isobusfs/isobusfs_cli.h index 6689c6c3..63ffde74 100644 --- a/isobusfs/isobusfs_cli.h +++ b/isobusfs/isobusfs_cli.h @@ -13,6 +13,9 @@ #define ISOBUSFS_CLI_MAX_EPOLL_EVENTS 10 #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 { ISOBUSFS_CLI_STATE_CONNECTING, ISOBUSFS_CLI_STATE_IDLE, diff --git a/isobusfs/isobusfs_cli_int.c b/isobusfs/isobusfs_cli_int.c index ae0217a9..3d2d544a 100644 --- a/isobusfs/isobusfs_cli_int.c +++ b/isobusfs/isobusfs_cli_int.c @@ -66,8 +66,8 @@ static int cmd_help(struct isobusfs_priv *priv, const char *options) static int cmd_exit(struct isobusfs_priv *priv, const char *options) { 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) From 61eb881dc92ffc3ba9d05d8b77532eabd6278eee Mon Sep 17 00:00:00 2001 From: Oleksij Rempel Date: Tue, 6 Jan 2026 12:15:04 +0100 Subject: [PATCH 3/3] isobusfs: fix directory read and seek semantics to match ISO 11783-13 Fix directory operations to comply with ISO 11783-13:2021 (sections C.3.4.2 and C.3.5.2), which specifies that directory operations differ significantly from standard file operations: - The Count parameter in a Read File request represents the number of directory entries to read, not the number of bytes. - The Offset parameter in a Seek File request represents the logical entry index, not a byte offset. The previous implementation treated directories strictly as files, using byte-based offsets and counts. This resulted in incorrect seeking behavior and protocol violations when listing directories. Align the implementation with the standard by: 1. Server side: - Introduce isobusfs_srv_dir_entry_visible() to consistently filter out invalid (unreadable, hidden, oversized) entries. This ensures that entry indices remain stable. - Implement isobusfs_srv_dir_skip_entries() to advance the directory stream by logical visible entries rather than bytes. - Update the Read File handler to interpret count as the maximum number of entries and return the number of entries read in the response header. - Update the Seek File handler to seek by entry index. 2. Client side: - Calculate the request count based on the number of minimal-size entries that fit into the maximum data length. - Interpret the response count as the number of entries received rather than the byte length of the payload. Signed-off-by: Oleksij Rempel --- isobusfs/isobusfs_cli_int.c | 101 +++++++++++---- isobusfs/isobusfs_srv_fa.c | 251 +++++++++++++++++++++++++++++------- 2 files changed, 281 insertions(+), 71 deletions(-) diff --git a/isobusfs/isobusfs_cli_int.c b/isobusfs/isobusfs_cli_int.c index 3d2d544a..b2c4cd60 100644 --- a/isobusfs/isobusfs_cli_int.c +++ b/isobusfs/isobusfs_cli_int.c @@ -16,6 +16,12 @@ #define MAX_COMMAND_LENGTH 256 #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 { const char *command; @@ -510,8 +516,12 @@ isobusfs_cli_ls_handle_open_dir_sent(struct isobusfs_priv *priv, ctx->handle = res->handle; + ctx->offset = 0; + ctx->entry_count = 0; + ret = isobusfs_cli_send_and_register_fa_sf_event(priv, ctx->handle, - 0, ctx->entry_count, + ISOBUSFS_FA_SEEK_SET, + ctx->offset, cb, ctx); if (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; struct isobusfs_fa_seekf_res *res = - (struct isobusfs_fa_seekf_res *)msg; + (struct isobusfs_fa_seekf_res *)msg->buf; uint16_t count; int ret; @@ -543,8 +553,10 @@ isobusfs_cli_ls_handle_seek_dir_sent(struct isobusfs_priv *priv, goto error; } - /* set max possible number fitting in to 16bits */ - count = UINT16_MAX; + /* ISO 11783-13:2021 C.3.5.2: count is number of directory entries. */ + count = ISOBUSFS_MAX_DATA_LENGH / ISOBUSFS_MIN_DIR_ENTRY_SIZE; + if (!count) + count = 1; ctx->request_count = count; 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, uint32_t *file_size) { + size_t entry_total_len, copy_len; uint8_t filename_length; - size_t entry_total_len; if (*pos + 2 > buffer_length) { pr_int("Error: Incomplete data in buffer\n"); @@ -633,8 +645,14 @@ static bool isobusfs_cli_extract_directory_entry(const uint8_t *buffer, } (*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; if (filename_length > MAX_DISPLAY_FILENAME_LENGTH) { /* 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 isobusfs_cli_print_directory_entries(struct isobusfs_cli_ls_context *ctx, 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]; uint16_t file_date, file_time; uint32_t file_size; uint8_t attributes; 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, &pos, filename, &attributes, @@ -709,6 +729,7 @@ isobusfs_cli_print_directory_entries(struct isobusfs_cli_ls_context *ctx, file_date, file_time, file_size); 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; size_t buffer_length = msg->len - sizeof(*res); isobusfs_event_callback cb; + size_t entries_before; + size_t entries_in_message; uint16_t count; int ret; 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)) goto error; count = le16toh(res->count); - if (count && count != buffer_length) { - pr_int("Buffer length mismatch: %u != %zu\n", count, - buffer_length); - goto error; + if (count && buffer_length) { + entries_before = ctx->entry_count; + isobusfs_cli_print_directory_entries(ctx, res->data, + buffer_length, count); + entries_in_message = ctx->entry_count - entries_before; + } else { + entries_in_message = 0; } - if (count) - isobusfs_cli_print_directory_entries(ctx, res->data, - buffer_length); + if (count != entries_in_message) { + pr_warn("Directory entry count mismatch: server sent %u, parsed %zu\n", + 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; - if (count) { + + if (res->error_code == ISOBUSFS_ERR_END_OF_FILE) { ret = isobusfs_cli_send_and_register_fa_cf_event(priv, ctx->handle, 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; - } else { - 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); + return; + } - 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; error: - ctx->state = ISOBUSFS_CLI_LS_STATE_ERROR; } diff --git a/isobusfs/isobusfs_srv_fa.c b/isobusfs/isobusfs_srv_fa.c index 0941e04b..039f3248 100644 --- a/isobusfs/isobusfs_srv_fa.c +++ b/isobusfs/isobusfs_srv_fa.c @@ -495,15 +495,119 @@ static int check_access_with_base(const char *base_dir, 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, - uint8_t *buffer, size_t count, - ssize_t *readed_size) + uint8_t *buffer, size_t max_bytes, + uint16_t max_entries, + ssize_t *readed_size, + uint16_t *entries_read) { DIR *dir = handle->dir; struct dirent *entry; 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). * * 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 * on the application's requirements. */ - for (int i = 0; i < handle->dir_pos && - (entry = readdir(dir)) != NULL; i++) { - /* Iterating to the desired position */ + if (handle->dir_pos < 0) { + pr_err("Directory handle in invalid state (pos=%d)\n", + 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. * 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 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; __le16 file_date, file_time; - uint8_t attributes = 0; struct stat file_stat; + uint8_t attributes = 0; __le32 size; - if (check_access_with_base(handle->path, entry->d_name, R_OK) != 0) - continue; /* Skip this entry if it's not readable */ - - 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) + if (!isobusfs_srv_dir_entry_visible(handle, entry, &file_stat, + &entry_name_len, + &attributes)) continue; 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; 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); 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; 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); memcpy(buffer + pos, &size, sizeof(size)); pos += sizeof(size); + (*entries_read)++; } *readed_size = pos; + handle->dir_pos += *entries_read; 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_fa_readf_req *req; ssize_t readed_size = 0; + uint16_t entries_read = 0; uint8_t error_code = 0; ssize_t send_size; + bool res_allocated = false; + size_t max_bytes = 0; + uint16_t count = 0; int ret = 0; - int count; + bool is_dir = false; req = (struct isobusfs_fa_readf_req *)msg->buf; 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", 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, * 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); if (!client) { pr_warn("client not found"); @@ -649,12 +747,33 @@ static int isobusfs_srv_fa_rf_req(struct isobusfs_srv_priv *priv, if (!handle) { pr_warn("failed to find file with handle: %x", req->handle); error_code = ISOBUSFS_ERR_FILE_ORPATH_NOT_FOUND; + goto send_response; } /* Determine whether to read a file or a directory */ if (handle->dir) { - ret = isobusfs_srv_read_directory(handle, res->data, count, - &readed_size); + /* ISO 11783-13:2021 C.3.5.2: count is entry count for directories. */ + 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 { ret = isobusfs_srv_read_file(handle, res->data, count, &readed_size); @@ -663,7 +782,10 @@ static int isobusfs_srv_fa_rf_req(struct isobusfs_srv_priv *priv, if (ret < 0) { error_code = ret; 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; } @@ -673,7 +795,10 @@ static int isobusfs_srv_fa_rf_req(struct isobusfs_srv_priv *priv, ISOBUSFS_FA_F_READ_FILE_RES); res->tan = req->tan; 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; if (send_size < ISOBUSFS_MIN_TRANSFER_LENGH) @@ -690,7 +815,8 @@ static int isobusfs_srv_fa_rf_req(struct isobusfs_srv_priv *priv, error_code, isobusfs_error_to_str(error_code), readed_size); free_res: - free(res); + if (res_allocated) + free(res); return ret; } @@ -754,19 +880,56 @@ static int isobusfs_srv_seek(struct isobusfs_srv_priv *priv, 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, 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; - 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; + } + + /* Validate requested offset */ + if (offset < 0) { + pr_err("Invalid seek offset: %d\n", offset); + return ISOBUSFS_ERR_INVALID_REQUESTED_LENGHT; + } - for (int32_t i = 0; i < offset; i++) { - if (readdir(dir) == NULL) - return ISOBUSFS_ERR_END_OF_FILE; + /* + * 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;