mirror of
https://github.com/linux-can/can-utils.git
synced 2026-11-04 07:21:13 +01:00
Several log messages used format specifiers which did not match the types of the provided arguments (e.g. %d or %i for size_t/ssize_t, %ld for int64_t/time_t and size_t for the '.*' precision). On 64 bit hosts (and for int64_t/time_t on 32 bit hosts) this leads to wrong output or undefined behaviour when the log level is enabled. Additionally the seek response position was compared to the requested offset without le32toh() conversion which fails on big endian hosts. Fix the format specifiers and add the missing le32toh() conversion. Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
1136 lines
29 KiB
C
1136 lines
29 KiB
C
// SPDX-License-Identifier: LGPL-2.0-only
|
|
// SPDX-FileCopyrightText: 2023 Oleksij Rempel <linux@rempel-privat.de>
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include "../libj1939.h"
|
|
#include "isobusfs_cli.h"
|
|
#include "isobusfs_cmn_dh.h"
|
|
#include "isobusfs_cmn_fa.h"
|
|
|
|
#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;
|
|
int (*function)(struct isobusfs_priv *priv, const char *options);
|
|
const char *help;
|
|
};
|
|
|
|
struct command_mapping commands[];
|
|
|
|
static bool isobusfs_cli_int_is_error(struct isobusfs_priv *priv, int error,
|
|
uint8_t error_code, uint8_t tan)
|
|
{
|
|
bool is_error = false;
|
|
|
|
if (error) {
|
|
pr_int("failed with error: %i (%s)\n", error,
|
|
strerror(error));
|
|
is_error = true;
|
|
} else if (!isobusfs_cli_tan_is_valid(tan, priv)) {
|
|
pr_int("Invalid TAN\n");
|
|
is_error = true;
|
|
} else if (error_code && error_code != ISOBUSFS_ERR_END_OF_FILE) {
|
|
pr_int("Failed with error code: %i (%s)\n", error_code,
|
|
isobusfs_error_to_str(error_code));
|
|
is_error = true;
|
|
}
|
|
|
|
return is_error;
|
|
}
|
|
|
|
static void isobusfs_cli_promt(struct isobusfs_priv *priv)
|
|
{
|
|
/* we are currently waiting for a response */
|
|
if (priv->int_busy)
|
|
return;
|
|
|
|
pr_int("isobusfs> ");
|
|
}
|
|
|
|
static int cmd_help(struct isobusfs_priv *priv, const char *options)
|
|
{
|
|
for (int i = 0; commands[i].command != NULL; i++)
|
|
pr_int("%s - %s\n", commands[i].command, commands[i].help);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_exit(struct isobusfs_priv *priv, const char *options)
|
|
{
|
|
pr_int("exit interactive mode\n");
|
|
|
|
return ISOBUSFS_CLI_RET_EXIT;
|
|
}
|
|
|
|
static int cmd_dmesg(struct isobusfs_priv *priv, const char *options)
|
|
{
|
|
isobusfs_print_log_buffer();
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_selftest(struct isobusfs_priv *priv, const char *options)
|
|
{
|
|
pr_int("run selftest\n");
|
|
priv->run_selftest = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* ------ get command -------*/
|
|
enum isobusfs_cli_get_state {
|
|
ISOBUSFS_CLI_GET_STATE_START,
|
|
ISOBUSFS_CLI_GET_STATE_OPEN_FILE_SENT,
|
|
ISOBUSFS_CLI_GET_STATE_SEEK_FILE_SENT,
|
|
ISOBUSFS_CLI_GET_STATE_READ_FILE_SENT,
|
|
ISOBUSFS_CLI_GET_STATE_CLOSE_FILE_SENT,
|
|
ISOBUSFS_CLI_GET_STATE_COMPLETED,
|
|
ISOBUSFS_CLI_GET_STATE_ERROR
|
|
};
|
|
|
|
struct isobusfs_cli_get_context {
|
|
enum isobusfs_cli_get_state state;
|
|
int handle;
|
|
size_t offset;
|
|
char *remote_path;
|
|
char *local_path;
|
|
FILE *local_file;
|
|
size_t bytes_received;
|
|
size_t total_size;
|
|
};
|
|
|
|
static int isobusfs_cli_get_event_callback(struct isobusfs_priv *priv,
|
|
struct isobusfs_msg *msg,
|
|
void *context, int error);
|
|
|
|
static void isobusfs_cli_get_handle_send_open_file(struct isobusfs_priv *priv,
|
|
struct isobusfs_cli_get_context *ctx)
|
|
{
|
|
isobusfs_event_callback cb = isobusfs_cli_get_event_callback;
|
|
const char *remote_path = ctx->remote_path;
|
|
uint8_t flags = ISOBUSFS_FA_OPEN_FILE_RO;
|
|
int ret;
|
|
|
|
/* Send the open file request to the server */
|
|
ret = isobusfs_cli_send_and_register_fa_of_event(priv, remote_path,
|
|
strlen(remote_path),
|
|
flags, cb, ctx);
|
|
if (ret) {
|
|
pr_int("Error: Failed to send open file request, error code: %d\n",
|
|
ret);
|
|
ctx->state = ISOBUSFS_CLI_GET_STATE_ERROR;
|
|
return;
|
|
}
|
|
|
|
ctx->state = ISOBUSFS_CLI_GET_STATE_OPEN_FILE_SENT;
|
|
}
|
|
|
|
static void
|
|
isobusfs_cli_get_handle_open_file_sent(struct isobusfs_priv *priv,
|
|
struct isobusfs_cli_get_context *ctx,
|
|
struct isobusfs_msg *msg)
|
|
{
|
|
isobusfs_event_callback cb = isobusfs_cli_get_event_callback;
|
|
struct isobusfs_fa_openf_res *res =
|
|
(struct isobusfs_fa_openf_res *)msg->buf;
|
|
int ret;
|
|
|
|
if (isobusfs_cli_int_is_error(priv, 0, res->error_code, res->tan) ||
|
|
res->handle == ISOBUSFS_FILE_HANDLE_ERROR) {
|
|
pr_int("Error: Failed to open file on server, error code: %d, handle: %d\n",
|
|
res->error_code, res->handle);
|
|
ctx->state = ISOBUSFS_CLI_GET_STATE_ERROR;
|
|
return;
|
|
}
|
|
|
|
ctx->handle = res->handle;
|
|
|
|
/* Seek to the beginning of the file */
|
|
ret = isobusfs_cli_send_and_register_fa_sf_event(priv, ctx->handle,
|
|
ISOBUSFS_FA_SEEK_SET,
|
|
0, cb, ctx);
|
|
if (ret) {
|
|
pr_int("Error: Failed to send seek request, error code: %d\n",
|
|
ret);
|
|
ctx->state = ISOBUSFS_CLI_GET_STATE_ERROR;
|
|
return;
|
|
}
|
|
|
|
ctx->state = ISOBUSFS_CLI_GET_STATE_SEEK_FILE_SENT;
|
|
}
|
|
|
|
static void
|
|
isobusfs_cli_get_handle_seek_file_sent(struct isobusfs_priv *priv,
|
|
struct isobusfs_cli_get_context *ctx,
|
|
struct isobusfs_msg *msg)
|
|
{
|
|
isobusfs_event_callback cb = isobusfs_cli_get_event_callback;
|
|
struct isobusfs_fa_seekf_res *res =
|
|
(struct isobusfs_fa_seekf_res *)msg->buf;
|
|
uint16_t read_size;
|
|
int ret;
|
|
|
|
if (isobusfs_cli_int_is_error(priv, 0, res->error_code, res->tan) ||
|
|
res->position != ctx->offset) {
|
|
pr_int("Error: Failed to seek file on server, error code: %d, position: %d\n",
|
|
res->error_code, res->position);
|
|
ctx->state = ISOBUSFS_CLI_GET_STATE_ERROR;
|
|
return;
|
|
}
|
|
|
|
/* set max possible number fitting in to 16bits */
|
|
read_size = UINT16_MAX;
|
|
|
|
ret = isobusfs_cli_send_and_register_fa_rf_event(priv, ctx->handle,
|
|
read_size, cb, ctx);
|
|
if (ret) {
|
|
pr_int("Error: Failed to send read file request, error code: %d\n",
|
|
ret);
|
|
ctx->state = ISOBUSFS_CLI_GET_STATE_ERROR;
|
|
return;
|
|
}
|
|
|
|
ctx->state = ISOBUSFS_CLI_GET_STATE_READ_FILE_SENT;
|
|
}
|
|
|
|
static void
|
|
isobusfs_cli_get_handle_read_file_sent(struct isobusfs_priv *priv,
|
|
struct isobusfs_cli_get_context *ctx,
|
|
struct isobusfs_msg *msg)
|
|
{
|
|
isobusfs_event_callback cb = isobusfs_cli_get_event_callback;
|
|
struct isobusfs_read_file_response *res =
|
|
(struct isobusfs_read_file_response *)msg->buf;
|
|
size_t bytes_read, bytes_written;
|
|
uint8_t position_mode;
|
|
int ret;
|
|
|
|
if (isobusfs_cli_int_is_error(priv, 0, res->error_code, res->tan)) {
|
|
pr_int("Error: Failed to read file from server, error code: %d\n",
|
|
res->error_code);
|
|
ctx->state = ISOBUSFS_CLI_GET_STATE_ERROR;
|
|
return;
|
|
}
|
|
|
|
/* Write the received data to the local file */
|
|
bytes_read = le16toh(res->count);
|
|
bytes_written = fwrite(res->data, 1, bytes_read, ctx->local_file);
|
|
if (bytes_written != bytes_read) {
|
|
pr_int("Error: Failed to write data to local file.\n");
|
|
ctx->state = ISOBUSFS_CLI_GET_STATE_ERROR;
|
|
return;
|
|
}
|
|
|
|
ctx->bytes_received += bytes_written;
|
|
ctx->offset += bytes_written;
|
|
|
|
/* Check if the end of the file has been reached */
|
|
if (res->error_code == ISOBUSFS_ERR_END_OF_FILE) {
|
|
/* Send a close file request */
|
|
ret = isobusfs_cli_send_and_register_fa_cf_event(priv,
|
|
ctx->handle,
|
|
cb, ctx);
|
|
if (ret) {
|
|
pr_int("Error: Failed to send close file request, error code: %d\n",
|
|
ret);
|
|
ctx->state = ISOBUSFS_CLI_GET_STATE_ERROR;
|
|
return;
|
|
}
|
|
ctx->state = ISOBUSFS_CLI_GET_STATE_CLOSE_FILE_SENT;
|
|
return;
|
|
}
|
|
|
|
position_mode = ISOBUSFS_FA_SEEK_SET;
|
|
/* If more data is available, send a new seek request */
|
|
|
|
ret = isobusfs_cli_send_and_register_fa_sf_event(priv, ctx->handle,
|
|
position_mode,
|
|
ctx->offset, cb, ctx);
|
|
if (ret) {
|
|
pr_int("Error: Failed to send next seek request, error code: %d\n",
|
|
ret);
|
|
ctx->state = ISOBUSFS_CLI_GET_STATE_ERROR;
|
|
return;
|
|
}
|
|
|
|
ctx->state = ISOBUSFS_CLI_GET_STATE_SEEK_FILE_SENT;
|
|
}
|
|
|
|
static void
|
|
isobusfs_cli_get_handle_close_file_sent(struct isobusfs_priv *priv,
|
|
struct isobusfs_cli_get_context *ctx,
|
|
struct isobusfs_msg *msg)
|
|
{
|
|
struct isobusfs_close_file_res *res =
|
|
(struct isobusfs_close_file_res *)msg->buf;
|
|
|
|
if (isobusfs_cli_int_is_error(priv, 0, res->error_code, res->tan)) {
|
|
pr_int("Error: Failed to close file on server, error code: %d\n",
|
|
res->error_code);
|
|
ctx->state = ISOBUSFS_CLI_GET_STATE_ERROR;
|
|
} else {
|
|
pr_int("File closed successfully.\n");
|
|
ctx->state = ISOBUSFS_CLI_GET_STATE_COMPLETED;
|
|
}
|
|
}
|
|
|
|
static void isobusfs_cli_get_free_ctx(struct isobusfs_cli_get_context *ctx)
|
|
{
|
|
if (ctx->local_file)
|
|
fclose(ctx->local_file);
|
|
|
|
free(ctx);
|
|
}
|
|
|
|
static void
|
|
isobusfs_cli_process_get_command(struct isobusfs_priv *priv,
|
|
struct isobusfs_cli_get_context *ctx,
|
|
struct isobusfs_msg *msg)
|
|
{
|
|
switch (ctx->state) {
|
|
case ISOBUSFS_CLI_GET_STATE_START:
|
|
isobusfs_cli_get_handle_send_open_file(priv, ctx);
|
|
break;
|
|
case ISOBUSFS_CLI_GET_STATE_OPEN_FILE_SENT:
|
|
isobusfs_cli_get_handle_open_file_sent(priv, ctx, msg);
|
|
break;
|
|
case ISOBUSFS_CLI_GET_STATE_SEEK_FILE_SENT:
|
|
isobusfs_cli_get_handle_seek_file_sent(priv, ctx, msg);
|
|
break;
|
|
case ISOBUSFS_CLI_GET_STATE_READ_FILE_SENT:
|
|
isobusfs_cli_get_handle_read_file_sent(priv, ctx, msg);
|
|
break;
|
|
case ISOBUSFS_CLI_GET_STATE_CLOSE_FILE_SENT:
|
|
isobusfs_cli_get_handle_close_file_sent(priv, ctx, msg);
|
|
break;
|
|
default:
|
|
pr_int("Error: Unexpected state in get command processing: %d\n",
|
|
ctx->state);
|
|
break;
|
|
}
|
|
|
|
if (ctx->state == ISOBUSFS_CLI_GET_STATE_COMPLETED ||
|
|
ctx->state == ISOBUSFS_CLI_GET_STATE_ERROR) {
|
|
if (ctx->state != ISOBUSFS_CLI_GET_STATE_COMPLETED) {
|
|
/* Try to close handle and do not wait for response. */
|
|
isobusfs_cli_send_and_register_fa_cf_event(priv,
|
|
ctx->handle,
|
|
NULL, NULL);
|
|
}
|
|
|
|
pr_int("File transfer %s.\n",
|
|
ctx->state == ISOBUSFS_CLI_GET_STATE_COMPLETED ?
|
|
"completed" : "failed");
|
|
priv->int_busy = false;
|
|
isobusfs_cli_get_free_ctx(ctx);
|
|
isobusfs_cli_promt(priv);
|
|
}
|
|
}
|
|
|
|
static int isobusfs_cli_get_event_callback(struct isobusfs_priv *priv,
|
|
struct isobusfs_msg *msg,
|
|
void *context, int error)
|
|
{
|
|
struct isobusfs_cli_get_context *ctx =
|
|
(struct isobusfs_cli_get_context *)context;
|
|
|
|
if (error) {
|
|
pr_int("Error in get event callback: %d\n", error);
|
|
ctx->state = ISOBUSFS_CLI_GET_STATE_ERROR;
|
|
isobusfs_cli_process_get_command(priv, ctx, NULL);
|
|
return error;
|
|
}
|
|
|
|
isobusfs_cli_process_get_command(priv, ctx, msg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_get(struct isobusfs_priv *priv, const char *options)
|
|
{
|
|
struct isobusfs_cli_get_context *ctx;
|
|
const char *remote_path = NULL;
|
|
char *local_path = NULL;
|
|
char *options_copy, *opt;
|
|
|
|
if (!options) {
|
|
pr_int("Usage: get <remote_path> [local_path]\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
options_copy = strdup(options);
|
|
if (!options_copy) {
|
|
pr_int("Error: Unable to allocate memory for options processing.\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
opt = strtok(options_copy, " ");
|
|
if (opt) {
|
|
remote_path = opt;
|
|
opt = strtok(NULL, " ");
|
|
if (opt)
|
|
local_path = strdup(opt);
|
|
}
|
|
|
|
if (!remote_path) {
|
|
pr_int("Error: Invalid arguments. Usage: get <remote_path> [local_path]\n");
|
|
free(options_copy);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!local_path) {
|
|
const char *filename = strrchr(remote_path, '/');
|
|
|
|
filename = filename ? filename + 1 : remote_path;
|
|
local_path = strdup(filename);
|
|
if (!local_path) {
|
|
pr_int("Error: Unable to allocate memory for local path.\n");
|
|
free(options_copy);
|
|
return -ENOMEM;
|
|
}
|
|
}
|
|
|
|
ctx = calloc(1, sizeof(*ctx));
|
|
if (!ctx) {
|
|
pr_int("Error: Unable to allocate memory for get context.\n");
|
|
free(local_path);
|
|
free(options_copy);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
ctx->remote_path = strdup(remote_path);
|
|
if (!ctx->remote_path) {
|
|
pr_int("Error: Unable to allocate memory for remote path.\n");
|
|
free(ctx);
|
|
free(local_path);
|
|
free(options_copy);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
ctx->local_path = local_path;
|
|
|
|
ctx->local_file = fopen(ctx->local_path, "wb");
|
|
if (!ctx->local_file) {
|
|
int ret = -errno;
|
|
|
|
pr_int("Error: Unable to open local file for writing. %s\n",
|
|
strerror(errno));
|
|
free(ctx->remote_path);
|
|
free(ctx->local_path);
|
|
free(ctx);
|
|
free(options_copy);
|
|
return ret;
|
|
}
|
|
|
|
priv->int_busy = true;
|
|
ctx->state = ISOBUSFS_CLI_GET_STATE_START;
|
|
isobusfs_cli_process_get_command(priv, ctx, NULL);
|
|
|
|
free(options_copy);
|
|
return 0;
|
|
}
|
|
|
|
/* ------ ls command -------*/
|
|
enum isobusfs_cli_ls_state {
|
|
ISOBUSFS_CLI_LS_STATE_START,
|
|
ISOBUSFS_CLI_LS_STATE_OPEN_DIR_SENT,
|
|
ISOBUSFS_CLI_LS_STATE_SEEK_DIR_SENT,
|
|
ISOBUSFS_CLI_LS_STATE_READ_DIR_SENT,
|
|
ISOBUSFS_CLI_LS_STATE_CLOSE_DIR_SENT,
|
|
ISOBUSFS_CLI_LS_STATE_COMPLETED,
|
|
ISOBUSFS_CLI_LS_STATE_ERROR
|
|
};
|
|
|
|
struct isobusfs_cli_ls_context {
|
|
enum isobusfs_cli_ls_state state;
|
|
int handle;
|
|
size_t offset;
|
|
char *path;
|
|
bool long_format;
|
|
size_t entry_count;
|
|
size_t request_count;
|
|
};
|
|
|
|
static int isobusfs_cli_ls_event_callback(struct isobusfs_priv *priv,
|
|
struct isobusfs_msg *msg,
|
|
void *context, int error);
|
|
|
|
static void isobusfs_cli_ls_free_ctx(struct isobusfs_cli_ls_context *ctx)
|
|
{
|
|
free(ctx->path);
|
|
free(ctx);
|
|
}
|
|
|
|
static void
|
|
isobusfs_cli_ls_handle_send_open_dir(struct isobusfs_priv *priv,
|
|
struct isobusfs_cli_ls_context *ctx)
|
|
|
|
{
|
|
isobusfs_event_callback cb = isobusfs_cli_ls_event_callback;
|
|
uint8_t flags = ISOBUSFS_FA_OPEN_DIR;
|
|
int ret;
|
|
|
|
ret = isobusfs_cli_send_and_register_fa_of_event(priv,
|
|
ctx->path,
|
|
strlen(ctx->path),
|
|
flags, cb, ctx);
|
|
if (ret) {
|
|
pr_int("Error: Unable to send open dir command.\n");
|
|
ctx->state = ISOBUSFS_CLI_LS_STATE_ERROR;
|
|
return;
|
|
}
|
|
|
|
ctx->state = ISOBUSFS_CLI_LS_STATE_OPEN_DIR_SENT;
|
|
}
|
|
|
|
static void
|
|
isobusfs_cli_ls_handle_open_dir_sent(struct isobusfs_priv *priv,
|
|
struct isobusfs_cli_ls_context *ctx,
|
|
struct isobusfs_msg *msg)
|
|
{
|
|
isobusfs_event_callback cb = isobusfs_cli_ls_event_callback;
|
|
struct isobusfs_fa_openf_res *res =
|
|
(struct isobusfs_fa_openf_res *)msg->buf;
|
|
int ret;
|
|
|
|
if (isobusfs_cli_int_is_error(priv, 0, res->error_code, res->tan))
|
|
goto error;
|
|
else if (res->handle == ISOBUSFS_FILE_HANDLE_ERROR)
|
|
goto error;
|
|
|
|
pr_debug("< rx: Open File Response. Error code: %i",
|
|
res->error_code);
|
|
|
|
ctx->handle = res->handle;
|
|
|
|
ctx->offset = 0;
|
|
ctx->entry_count = 0;
|
|
|
|
ret = isobusfs_cli_send_and_register_fa_sf_event(priv, ctx->handle,
|
|
ISOBUSFS_FA_SEEK_SET,
|
|
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;
|
|
}
|
|
|
|
static void
|
|
isobusfs_cli_ls_handle_seek_dir_sent(struct isobusfs_priv *priv,
|
|
struct isobusfs_cli_ls_context *ctx,
|
|
struct isobusfs_msg *msg)
|
|
{
|
|
isobusfs_event_callback cb = isobusfs_cli_ls_event_callback;
|
|
struct isobusfs_fa_seekf_res *res =
|
|
(struct isobusfs_fa_seekf_res *)msg->buf;
|
|
uint16_t count;
|
|
int ret;
|
|
|
|
if (isobusfs_cli_int_is_error(priv, 0, res->error_code, res->tan))
|
|
goto error;
|
|
|
|
if (le32toh(res->position) != ctx->offset) {
|
|
pr_int("Failed to seek to position %zu, got %u\n",
|
|
ctx->offset, le32toh(res->position));
|
|
goto error;
|
|
}
|
|
|
|
/* 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,
|
|
count, cb, ctx);
|
|
if (ret) {
|
|
pr_int("Failed to send read file request: %i\n", ret);
|
|
goto error;
|
|
}
|
|
|
|
ctx->state = ISOBUSFS_CLI_LS_STATE_READ_DIR_SENT;
|
|
|
|
return;
|
|
error:
|
|
ctx->state = ISOBUSFS_CLI_LS_STATE_ERROR;
|
|
}
|
|
|
|
/**
|
|
* Convert a 16-bit encoded date to a formatted date string ("YYYY-MM-DD").
|
|
*
|
|
* @param encoded_date The 16-bit encoded date.
|
|
* @param formatted_date Buffer to store the formatted date string.
|
|
*/
|
|
static void convert_to_formatted_date(uint16_t encoded_date,
|
|
char *formatted_date)
|
|
{
|
|
int year, month, day;
|
|
|
|
if (!formatted_date)
|
|
return;
|
|
|
|
/* Extract year, month, and day from the encoded date */
|
|
year = ((encoded_date >> 9) & 0x7f) + 1980; /* Bits 15 … 9 */
|
|
month = (encoded_date >> 5) & 0x0f; /* Bits 8 … 5 */
|
|
day = encoded_date & 0x1f; /* Bits 4 … 0 */
|
|
|
|
snprintf(formatted_date, 11, "%04d-%02d-%02d", year, month, day);
|
|
}
|
|
|
|
/**
|
|
* Convert a 16-bit encoded time to a formatted time string ("HH:MM:SS").
|
|
*
|
|
* @param encoded_time The 16-bit encoded time.
|
|
* @param formatted_time Buffer to store the formatted time string.
|
|
*/
|
|
static void convert_to_formatted_time(uint16_t encoded_time,
|
|
char *formatted_time)
|
|
{
|
|
int hours, minutes, seconds;
|
|
|
|
if (!formatted_time)
|
|
return;
|
|
|
|
/* Extract hours, minutes, and seconds from the encoded time */
|
|
/* Bits 15 … 11 */
|
|
hours = (encoded_time >> 11) & 0x1f;
|
|
/* Bits 10 … 5 */
|
|
minutes = (encoded_time >> 5) & 0x3f;
|
|
/* Bits 4 … 0, in steps of 2 seconds */
|
|
seconds = (encoded_time & 0x1f) * 2;
|
|
|
|
snprintf(formatted_time, 9, "%02d:%02d:%02d", hours, minutes, seconds);
|
|
}
|
|
|
|
static bool isobusfs_cli_extract_directory_entry(const uint8_t *buffer,
|
|
size_t buffer_length,
|
|
size_t *pos, char *filename,
|
|
uint8_t *attributes,
|
|
uint16_t *file_date,
|
|
uint16_t *file_time,
|
|
uint32_t *file_size)
|
|
{
|
|
size_t entry_total_len, copy_len;
|
|
uint8_t filename_length;
|
|
|
|
if (*pos + 2 > buffer_length) {
|
|
pr_int("Error: Incomplete data in buffer\n");
|
|
return false;
|
|
}
|
|
|
|
filename_length = buffer[*pos];
|
|
entry_total_len = 1 + filename_length + 1 + 2 + 2 + 4;
|
|
|
|
if (*pos + entry_total_len > buffer_length) {
|
|
pr_int("Error: Incomplete data in buffer\n");
|
|
return false;
|
|
}
|
|
|
|
(*pos)++;
|
|
|
|
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
|
|
* with a dots
|
|
*/
|
|
filename[MAX_DISPLAY_FILENAME_LENGTH] = '\0';
|
|
filename[MAX_DISPLAY_FILENAME_LENGTH - 1] = '.';
|
|
filename[MAX_DISPLAY_FILENAME_LENGTH - 2] = '.';
|
|
}
|
|
|
|
*attributes = buffer[*pos];
|
|
(*pos)++;
|
|
|
|
*file_date = le16toh(*(__le16 *)(buffer + *pos));
|
|
*pos += 2;
|
|
*file_time = le16toh(*(__le16 *)(buffer + *pos));
|
|
*pos += 2;
|
|
*file_size = le32toh(*(__le32 *)(buffer + *pos));
|
|
*pos += 4;
|
|
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
isobusfs_cli_print_directory_entry(struct isobusfs_cli_ls_context *ctx,
|
|
const char *filename, uint8_t attributes,
|
|
uint16_t file_date, uint16_t file_time,
|
|
uint32_t file_size)
|
|
{
|
|
char formatted_date[] = "YYYY-MM-DD\0";
|
|
char formatted_time[] = "HH:MM:SS\0";
|
|
char file_type, writeable;
|
|
|
|
|
|
if (!ctx->long_format) {
|
|
pr_int("%s\n", filename);
|
|
return;
|
|
}
|
|
|
|
file_type = (attributes & ISOBUSFS_ATTR_DIRECTORY) ? 'd' : '-';
|
|
writeable = (attributes & ISOBUSFS_ATTR_READ_ONLY) ? '-' : 'w';
|
|
|
|
convert_to_formatted_date(file_date, formatted_date);
|
|
convert_to_formatted_time(file_time, formatted_time);
|
|
|
|
pr_int("%c%c%c %u %s %s %s\n",
|
|
file_type, 'r', writeable,
|
|
file_size, formatted_date, formatted_time, filename);
|
|
}
|
|
|
|
static void
|
|
isobusfs_cli_print_directory_entries(struct isobusfs_cli_ls_context *ctx,
|
|
const uint8_t *buffer,
|
|
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 && entries < max_entries) {
|
|
if (!isobusfs_cli_extract_directory_entry(buffer, buffer_length,
|
|
&pos, filename,
|
|
&attributes,
|
|
&file_date,
|
|
&file_time,
|
|
&file_size))
|
|
return;
|
|
|
|
isobusfs_cli_print_directory_entry(ctx, filename, attributes,
|
|
file_date, file_time,
|
|
file_size);
|
|
ctx->entry_count++;
|
|
entries++;
|
|
}
|
|
}
|
|
|
|
static void
|
|
isobusfs_cli_ls_handle_read_dir_sent(struct isobusfs_priv *priv,
|
|
struct isobusfs_cli_ls_context *ctx,
|
|
struct isobusfs_msg *msg)
|
|
{
|
|
struct isobusfs_read_file_response *res =
|
|
(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 && 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_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 (res->error_code == ISOBUSFS_ERR_END_OF_FILE) {
|
|
ret = isobusfs_cli_send_and_register_fa_cf_event(priv,
|
|
ctx->handle,
|
|
cb, ctx);
|
|
if (ret) {
|
|
pr_int("Failed to send close file request: %i\n", ret);
|
|
goto error;
|
|
}
|
|
|
|
ctx->state = ISOBUSFS_CLI_LS_STATE_CLOSE_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;
|
|
}
|
|
|
|
static void
|
|
isobusfs_cli_ls_handle_close_dir_sent(struct isobusfs_priv *priv,
|
|
struct isobusfs_cli_ls_context *ctx,
|
|
struct isobusfs_msg *msg)
|
|
{
|
|
struct isobusfs_close_file_res *res =
|
|
(struct isobusfs_close_file_res *)msg->buf;
|
|
|
|
if (isobusfs_cli_int_is_error(priv, 0, res->error_code, res->tan))
|
|
goto error;
|
|
|
|
pr_debug("< rx: Close File Response. Error code: %i",
|
|
res->error_code);
|
|
|
|
return;
|
|
error:
|
|
ctx->state = ISOBUSFS_CLI_LS_STATE_ERROR;
|
|
}
|
|
|
|
static void isobusfs_cli_process_ls_command(struct isobusfs_priv *priv,
|
|
struct isobusfs_cli_ls_context *ctx,
|
|
struct isobusfs_msg *msg)
|
|
{
|
|
switch (ctx->state) {
|
|
case ISOBUSFS_CLI_LS_STATE_START:
|
|
isobusfs_cli_ls_handle_send_open_dir(priv, ctx);
|
|
break;
|
|
case ISOBUSFS_CLI_LS_STATE_OPEN_DIR_SENT:
|
|
isobusfs_cli_ls_handle_open_dir_sent(priv, ctx, msg);
|
|
break;
|
|
case ISOBUSFS_CLI_LS_STATE_SEEK_DIR_SENT:
|
|
isobusfs_cli_ls_handle_seek_dir_sent(priv, ctx, msg);
|
|
break;
|
|
case ISOBUSFS_CLI_LS_STATE_READ_DIR_SENT:
|
|
isobusfs_cli_ls_handle_read_dir_sent(priv, ctx, msg);
|
|
break;
|
|
case ISOBUSFS_CLI_LS_STATE_CLOSE_DIR_SENT:
|
|
isobusfs_cli_ls_handle_close_dir_sent(priv, ctx, msg);
|
|
ctx->state = ISOBUSFS_CLI_LS_STATE_COMPLETED;
|
|
break;
|
|
default:
|
|
pr_int("Unexpected state: %i\n", ctx->state);
|
|
break;
|
|
}
|
|
|
|
if (ctx->state == ISOBUSFS_CLI_LS_STATE_COMPLETED ||
|
|
ctx->state == ISOBUSFS_CLI_LS_STATE_ERROR) {
|
|
if (ctx->state != ISOBUSFS_CLI_LS_STATE_COMPLETED) {
|
|
/* Try to close handle and do not wait for response. */
|
|
isobusfs_cli_send_and_register_fa_cf_event(priv,
|
|
ctx->handle, NULL, NULL);
|
|
}
|
|
|
|
pr_int("Entries found: %zu\n", ctx->entry_count);
|
|
priv->int_busy = false;
|
|
isobusfs_cli_ls_free_ctx(ctx);
|
|
isobusfs_cli_promt(priv);
|
|
}
|
|
}
|
|
|
|
static int isobusfs_cli_ls_event_callback(struct isobusfs_priv *priv,
|
|
struct isobusfs_msg *msg,
|
|
void *context, int error)
|
|
{
|
|
struct isobusfs_cli_ls_context *ctx =
|
|
(struct isobusfs_cli_ls_context *)context;
|
|
|
|
if (!error) {
|
|
isobusfs_cli_process_ls_command(priv, ctx, msg);
|
|
} else {
|
|
ctx->state = ISOBUSFS_CLI_LS_STATE_ERROR;
|
|
isobusfs_cli_process_ls_command(priv, ctx, NULL);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_ls(struct isobusfs_priv *priv, const char *options)
|
|
{
|
|
struct isobusfs_cli_ls_context *ctx;
|
|
bool long_format = false;
|
|
char *options_copy, *opt;
|
|
const char *path = ".";
|
|
|
|
if (options) {
|
|
options_copy = strdup(options);
|
|
if (!options_copy) {
|
|
pr_int("Error: Unable to allocate memory for options processing.\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
opt = strtok(options_copy, " ");
|
|
while (opt != NULL) {
|
|
if (strcmp(opt, "-h") == 0) {
|
|
pr_int("Usage: ls [-t] [path]\n");
|
|
pr_int("Options:\n");
|
|
pr_int(" -l\tuse a long listing format\n");
|
|
pr_int(" path\tDirectory to list\n");
|
|
free(options_copy);
|
|
return 0;
|
|
} else if (strcmp(opt, "-l") == 0) {
|
|
long_format = true;
|
|
} else {
|
|
/* Assume any non-option argument is the path */
|
|
path = opt;
|
|
}
|
|
opt = strtok(NULL, " ");
|
|
}
|
|
free(options_copy);
|
|
}
|
|
|
|
ctx = calloc(1, sizeof(*ctx));
|
|
if (!ctx) {
|
|
pr_int("Error: Unable to allocate memory for ls context.\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
ctx->path = strdup(path);
|
|
if (!ctx->path) {
|
|
free(ctx);
|
|
pr_int("Error: Unable to allocate memory for path.\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
priv->int_busy = true;
|
|
ctx->long_format = long_format;
|
|
ctx->state = ISOBUSFS_CLI_LS_STATE_START;
|
|
isobusfs_cli_process_ls_command(priv, ctx, NULL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_ll(struct isobusfs_priv *priv, const char *options)
|
|
{
|
|
char *options_copy = NULL;
|
|
|
|
if (!options)
|
|
return cmd_ls(priv, "-l");
|
|
|
|
int ret;
|
|
|
|
options_copy = strdup(options);
|
|
if (!options_copy) {
|
|
pr_int("Error: Unable to allocate memory for options processing.\n");
|
|
return -1;
|
|
}
|
|
|
|
ret = cmd_ls(priv, strcat(options_copy, " -l"));
|
|
free(options_copy);
|
|
return ret;
|
|
}
|
|
|
|
static int isobusfs_cli_int_cd_state(struct isobusfs_priv *priv,
|
|
struct isobusfs_msg *msg,
|
|
void *ctx, int error)
|
|
{
|
|
struct isobusfs_dh_ccd_res *res =
|
|
(struct isobusfs_dh_ccd_res *)msg->buf;
|
|
|
|
if (!isobusfs_cli_int_is_error(priv, error, res->error_code, res->tan))
|
|
pr_debug("< rx: change current directory response. Error code: %i",
|
|
res->error_code);
|
|
|
|
priv->int_busy = false;
|
|
isobusfs_cli_promt(priv);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_cd(struct isobusfs_priv *priv, const char *options)
|
|
{
|
|
char *options_copy = NULL;
|
|
const char *path = "."; /* Default path is the current directory */
|
|
char *opt;
|
|
int ret;
|
|
|
|
if (!options)
|
|
goto send_ccd_req;
|
|
|
|
options_copy = strdup(options);
|
|
if (!options_copy) {
|
|
pr_int("Error: Unable to allocate memory for options processing.\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
opt = strtok(options_copy, " ");
|
|
while (opt) {
|
|
if (strcmp(options, "-h") == 0) {
|
|
pr_int("Usage: cd [path]\n");
|
|
pr_int("Options:\n");
|
|
pr_int(" path\tPath of new directory\n");
|
|
return 0;
|
|
}
|
|
|
|
/* Assume any non-option argument is the path */
|
|
path = opt;
|
|
opt = strtok(NULL, " ");
|
|
}
|
|
|
|
send_ccd_req:
|
|
ret = isobusfs_cli_send_and_register_ccd_event(priv, path, strlen(path),
|
|
isobusfs_cli_int_cd_state,
|
|
NULL);
|
|
if (ret) {
|
|
pr_int("Error: Unable to send CCD request.\n");
|
|
goto free_options;
|
|
}
|
|
priv->int_busy = true;
|
|
|
|
free_options:
|
|
free(options_copy);
|
|
return 0;
|
|
}
|
|
|
|
static int isobusfs_cli_int_pwd_state(struct isobusfs_priv *priv,
|
|
struct isobusfs_msg *msg,
|
|
void *ctx, int error)
|
|
{
|
|
struct isobusfs_dh_get_cd_res *res =
|
|
(struct isobusfs_dh_get_cd_res *)msg->buf;
|
|
char str[ISOBUSFS_MAX_PATH_NAME_LENGTH];
|
|
uint16_t str_len;
|
|
|
|
if (isobusfs_cli_int_is_error(priv, error, res->error_code, res->tan))
|
|
goto error;
|
|
|
|
str_len = le16toh(res->name_len);
|
|
if (str_len > ISOBUSFS_MAX_PATH_NAME_LENGTH) {
|
|
pr_int("path name too long: %i, max is %i", str_len,
|
|
ISOBUSFS_MAX_PATH_NAME_LENGTH);
|
|
str_len = ISOBUSFS_MAX_PATH_NAME_LENGTH;
|
|
|
|
}
|
|
strncpy(str, (const char *)&res->name[0], str_len);
|
|
|
|
priv->int_busy = false;
|
|
pr_int("%s\n", str);
|
|
error:
|
|
isobusfs_cli_promt(priv);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_pwd(struct isobusfs_priv *priv, const char *options)
|
|
{
|
|
int ret;
|
|
|
|
ret = isobusfs_cli_send_and_register_gcd_event(priv,
|
|
isobusfs_cli_int_pwd_state, NULL);
|
|
if (ret) {
|
|
pr_int("Error: Unable to send Get Current Dir request.\n");
|
|
return ret;
|
|
}
|
|
priv->int_busy = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct command_mapping commands[] = {
|
|
{"exit", cmd_exit, "exit interactive mode"},
|
|
{"quit", cmd_exit, "exit interactive mode"},
|
|
{"help", cmd_help, "show this help"},
|
|
{"dmesg", cmd_dmesg, "show log buffer"},
|
|
{"selftest", cmd_selftest, "run selftest"},
|
|
{"ls", cmd_ls, "list directory"},
|
|
{"ll", cmd_ll, "list directory with long listing format"},
|
|
{"cd", cmd_cd, "change directory"},
|
|
{"pwd", cmd_pwd, "print name of current/working directory"},
|
|
{"get", cmd_get, "get file"},
|
|
{NULL, NULL, NULL}
|
|
};
|
|
|
|
void isobusfs_cli_int_start(struct isobusfs_priv *priv)
|
|
{
|
|
pr_int("Interactive mode\n");
|
|
isobusfs_cli_promt(priv);
|
|
}
|
|
|
|
int isobusfs_cli_interactive(struct isobusfs_priv *priv)
|
|
{
|
|
char command[MAX_COMMAND_LENGTH];
|
|
ssize_t len;
|
|
int ret;
|
|
|
|
len = read(STDIN_FILENO, command, MAX_COMMAND_LENGTH);
|
|
if (len == 1) {
|
|
isobusfs_cli_promt(priv);
|
|
return 0;
|
|
} else if (len > 0) {
|
|
char *cmd, *options;
|
|
|
|
if (command[len - 1] == '\n')
|
|
command[len - 1] = '\0';
|
|
else
|
|
command[len] = '\0';
|
|
|
|
cmd = strtok(command, " ");
|
|
options = strtok(NULL, "\0");
|
|
|
|
for (int i = 0; commands[i].command != NULL; i++) {
|
|
if (strcmp(cmd, commands[i].command) == 0) {
|
|
ret = commands[i].function(priv, options);
|
|
if (ret)
|
|
return ret;
|
|
isobusfs_cli_promt(priv);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
pr_int("unknown command\n");
|
|
isobusfs_cli_promt(priv);
|
|
} else {
|
|
if (errno != EAGAIN && errno != EWOULDBLOCK)
|
|
pr_int("read error\n");
|
|
}
|
|
|
|
return 0;
|
|
}
|