1089 lines
27 KiB
C
1089 lines
27 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
|
|
|
|
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 -EINTR to indicate the program should exit */
|
|
return -EINTR;
|
|
}
|
|
|
|
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;
|
|
|
|
ret = isobusfs_cli_send_and_register_fa_sf_event(priv, ctx->handle,
|
|
0, ctx->entry_count,
|
|
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;
|
|
uint16_t count;
|
|
int ret;
|
|
|
|
if (isobusfs_cli_int_is_error(priv, 0, res->error_code, res->tan))
|
|
goto error;
|
|
|
|
if (res->position != ctx->offset) {
|
|
pr_int("Failed to seek to position %zu, got %zu\n",
|
|
ctx->offset, res->position);
|
|
goto error;
|
|
}
|
|
|
|
/* set max possible number fitting in to 16bits */
|
|
count = UINT16_MAX;
|
|
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)
|
|
{
|
|
uint8_t filename_length;
|
|
size_t entry_total_len;
|
|
|
|
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)++;
|
|
strncpy(filename, (const char *)buffer + *pos, filename_length);
|
|
filename[filename_length] = '\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)
|
|
{
|
|
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;
|
|
|
|
while (pos < buffer_length) {
|
|
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++;
|
|
}
|
|
}
|
|
|
|
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;
|
|
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)
|
|
isobusfs_cli_print_directory_entries(ctx, res->data,
|
|
buffer_length);
|
|
|
|
cb = isobusfs_cli_ls_event_callback;
|
|
if (count) {
|
|
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;
|
|
} 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;
|
|
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: %i\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;
|
|
}
|