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..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; @@ -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) { 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) @@ -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_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 */ 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;