diff --git a/isobusfs/isobusfs_cli_int.c b/isobusfs/isobusfs_cli_int.c index 3d2d544..b15da51 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,7 +645,13 @@ static bool isobusfs_cli_extract_directory_entry(const uint8_t *buffer, } (*pos)++; - strncpy(filename, (const char *)buffer + *pos, filename_length); + + 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[filename_length] = '\0'; *pos += filename_length; if (filename_length > MAX_DISPLAY_FILENAME_LENGTH) { @@ -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,35 @@ 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); + 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 != entries_in_message) { + pr_int("Directory entry count mismatch: %u != %zu\n", count, + entries_in_message); goto error; } - if (count) - isobusfs_cli_print_directory_entries(ctx, res->data, - buffer_length); - 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 +780,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); - - ctx->state = ISOBUSFS_CLI_LS_STATE_SEEK_DIR_SENT; + return; } + 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 0941e04..42aa05a 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,12 @@ 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 */ - } + 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 +653,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 +677,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 +690,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 +707,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 +730,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 +741,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 +776,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 +789,10 @@ send_response: 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 +809,8 @@ send_response: error_code, isobusfs_error_to_str(error_code), readed_size); free_res: - free(res); + if (res_allocated) + free(res); return ret; } @@ -754,19 +874,33 @@ 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); - - 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) { + isobusfs_srv_dir_skip_entries(handle, current_pos); + return ret; } handle->dir_pos = offset;