Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
751 changes: 674 additions & 77 deletions components/kaos/appmgr/src/appmgr_ao.c

Large diffs are not rendered by default.

22 changes: 18 additions & 4 deletions components/kaos/core/include/kaos.h
Original file line number Diff line number Diff line change
Expand Up @@ -3042,6 +3042,16 @@ typedef enum {
* 32 KB chunk per step paced by a QTimeEvt, so
* lower AOs keep running. Answered once at the
* end; a failed copy leaves no destination. */
STORAGE_PAIR_REPLACE, /**< Put @p src on @p dst, which may be taken: the
* file there goes to `<dst>.bak` first, is put
* back if a step fails and is unlinked when the
* copy is in place, all in one step with one
* answer. Same volume only. @p src stays where it
* was when the replacement fails. A `<dst>.bak`
* already there is removed. A reset between the
* steps leaves the file that was there as
* `<dst>.bak` and @p src where it was, never a
* cut file on @p dst. */
} storage_pair_op_t;

/**
Expand All @@ -3060,18 +3070,22 @@ typedef enum {
void storage_path_request(QActive *who, uint8_t op, const char *path);

/**
* @brief Ask AO_Storage to move or copy @p src to @p dst.
* @brief Ask AO_Storage to move or copy @p src to @p dst, or to replace @p dst by it.
*
* Answered once with StoragePathRspEvt; `rc` says how it went. On KODE_ERROR,
* `exists` true means the source was checked afterwards and is missing.
* MOVE waits for a transfer in flight like storage_path_request()'s deletes.
* MOVE: KODE_DENIED malformed, no volume, two volumes, or a transfer that outlasted
* the wait; KODE_ERROR no mount or rename failed. COPY: KODE_DENIED malformed, no volume, same file, or a transfer in
* the wait; KODE_ERROR no mount or rename failed. REPLACE waits for a transfer in
* flight as MOVE does and is refused as it is, and with KODE_DENIED when a folder
* is on @p dst; KODE_ERROR when a step failed, the file on @p dst then as it was
* unless it would not go back, which the log says with both names. COPY: KODE_DENIED malformed, no volume, same file, or a transfer in
* flight; KODE_ERROR otherwise. COPY answers at its end: size deadlines for it.
*
* @param who Where the response is posted. NULL is served and not answered for
* MOVE; refused for COPY, whose only sign of life is @p who.
* @param op storage_pair_op_t: STORAGE_PAIR_MOVE or STORAGE_PAIR_COPY.
* MOVE and REPLACE; refused for COPY, whose only sign of life is @p who.
* @param op storage_pair_op_t: STORAGE_PAIR_MOVE, _COPY or _REPLACE. The answer's
* `op` is the same.
* @param src Absolute, normalised, virtual. Copied into the event.
* @param dst Absolute, normalised, virtual. Copied into the event.
*/
Expand Down
107 changes: 103 additions & 4 deletions components/kaos/storage/src/storage_ao.c
Original file line number Diff line number Diff line change
Expand Up @@ -3532,6 +3532,92 @@ static void answer_pair_move(QActive *who, uint8_t op, const char *src,
post_path_rsp(who, op, rc, src_missing, false, 0U);
}

/**
* @brief Answer a STORAGE_PAIR_REPLACE: put @p src on @p dst, which may be taken.
*
* FatFs f_rename() will not overwrite, so a taken name is moved aside to
* `<dst>.bak`, @p src is put on it and the backup unlinked, as
* answer_text_line_write() does for a rewrite; a step that fails puts the
* original back. One dispatch step, one answer. A reset between the two
* renames leaves the name free, the original as `<dst>.bak` and @p src where it
* was: the next replacement of the name puts its copy there and removes that
* backup. A `<dst>.bak` that is there is removed, and said.
*/
static void answer_pair_replace(QActive *who, uint8_t op, const char *src,
const char *dst)
{
char bak[KODE_PATH_MAX];
kdrv_stat_t st = { .exists = false, .is_dir = false, .size = 0U };
kode_status_t rc;

warn_if_card("replace", dst);
int const wrote = snprintf(bak, sizeof(bak), "%s.bak", dst);
if ( (0 > wrote) || ((size_t)wrote >= sizeof(bak)) )
{
KLOG_W(TAG, "replace '%s': no room for a backup name", dst);
post_path_rsp(who, op, KODE_DENIED, false, false, 0U);
return;
}
if ( (kdrv_storage_stat(dst, &st) != KODE_OK) || !st.exists )
{
/* The name is free (or the volume is not there, which the rename
* says): the one rename is the whole replacement. */
rc = kdrv_storage_rename(src, dst);
if ( KODE_OK != rc )
{
KLOG_W(TAG, "replace '%s' -> '%s' failed", src, dst);
}
else if ( kdrv_storage_unlink(bak) == KODE_OK )
{
KLOG_N(TAG, "replace '%s': '%s' was there and is removed", dst, bak);
}
else
{
/* no backup in the way: the usual case */
}
post_path_rsp(who, op, rc, false, false, 0U);
return;
}
if ( st.is_dir )
{
KLOG_W(TAG, "replace '%s': a folder is on the name", dst);
post_path_rsp(who, op, KODE_DENIED, false, false, 0U);
return;
}
if ( kdrv_storage_unlink(bak) == KODE_OK )
{
KLOG_N(TAG, "replace '%s': '%s' was there and is removed", dst, bak);
}
rc = kdrv_storage_rename(dst, bak);
if ( KODE_OK != rc )
{
KLOG_W(TAG, "replace '%s': could not move the file there aside", dst);
post_path_rsp(who, op, KODE_ERROR, false, false, 0U);
return;
}
rc = kdrv_storage_rename(src, dst);
if ( KODE_OK != rc )
{
if ( kdrv_storage_rename(bak, dst) == KODE_OK )
{
KLOG_W(TAG, "replace '%s' -> '%s' failed; the file there is put back",
src, dst);
}
else
{
KLOG_E(TAG, "replace '%s' failed and the file there would not go "
"back: it stays as '%s', the copy as '%s'", dst, bak, src);
}
post_path_rsp(who, op, KODE_ERROR, false, false, 0U);
return;
}
if ( kdrv_storage_unlink(bak) != KODE_OK )
{
KLOG_W(TAG, "replace '%s' done, backup '%s' left behind", dst, bak);
}
post_path_rsp(who, op, KODE_OK, false, false, 0U);
}

/* ---- cp -------------------------------------------------------------------
* The only thing in kodeOS that holds a reader and a writer at once. One
* 32 KB chunk per dispatch step with a QTimeEvt between, so AO_Gui, AO_Radio
Expand Down Expand Up @@ -4368,8 +4454,8 @@ static void handle_path_req(Storage * const me, const StoragePathEvt *q)
/**
* @brief Dispatch one StoragePairEvt by storage_pair_op_t.
*
* MOVE is one rename() answered in this step; COPY holds two handles and
* answers many steps later. The default refuses, as in handle_path_req().
* MOVE and REPLACE are renames answered in this step; COPY holds two handles
* and answers many steps later. The default refuses, as in handle_path_req().
*/
static void handle_pair_req(Storage * const me, const StoragePairEvt *q)
{
Expand All @@ -4388,6 +4474,17 @@ static void handle_pair_req(Storage * const me, const StoragePairEvt *q)
answer_pair_move(q->who, q->op, q->src, q->dst);
break;
}
case STORAGE_PAIR_REPLACE: {
if ( busy(me) )
{
KLOG_W(TAG, "a transfer is already in flight; replace of '%s' "
"refused", q->dst);
post_path_rsp(q->who, q->op, KODE_DENIED, false, false, 0U);
break;
}
answer_pair_replace(q->who, q->op, q->src, q->dst);
break;
}
case STORAGE_PAIR_COPY: {
answer_pair_copy(me, q->who, q->op, q->src, q->dst);
break;
Expand Down Expand Up @@ -7624,8 +7721,10 @@ static bool turn_wanted(QEvt const * const e)
break;
}
case STORAGE_PAIR_REQ_SIG: {
wanted = ((storage_pair_op_t)Q_EVT_CAST(StoragePairEvt)->op
== STORAGE_PAIR_MOVE);
storage_pair_op_t const op =
(storage_pair_op_t)Q_EVT_CAST(StoragePairEvt)->op;

wanted = (STORAGE_PAIR_MOVE == op) || (STORAGE_PAIR_REPLACE == op);
break;
}
default: {
Expand Down
1 change: 1 addition & 0 deletions components/kdrv/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ set(srcs
"apps/src/kdrv_slot_crash.c"
"apps/src/kdrv_apps.c"
"apps/src/kdrv_kfw.c"
"apps/src/kdrv_image_check.c"
"apps/src/kapp_desc.c"
"apps/src/kapp_cat.c"
"apps/src/kapp_data.c"
Expand Down
63 changes: 63 additions & 0 deletions components/kdrv/apps/include/kdrv_image_check.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
/**
* @file kdrv_image_check.h
* @brief An ESP image judged as its bytes arrive: the header chain against the file's length, then the SHA-256 it carries.
*/
#ifndef KDRV_IMAGE_CHECK_H
#define KDRV_IMAGE_CHECK_H

#include <stddef.h>
#include <stdint.h>

#include "kode_defs.h"

#ifdef __cplusplus
extern "C" {
#endif

/**
* @brief Open a check for an image in a file of @p file_len bytes.
*
* One check at a time. The bytes are fed in order, from the image's first;
* nothing is read from flash and nothing is written. Whoever is about to give
* up an installed app for an image that arrives from a file feeds it through
* here first.
*
* @param file_len The file's length: no segment may declare bytes past it.
* @return KODE_OK; KODE_DENIED when a check is already open.
*/
kode_status_t kdrv_image_check_begin(uint32_t file_len);

/**
* @brief Feed the next bytes of the file.
*
* Bytes past the image's end (the SHA-256 included) are taken and ignored.
*
* @param data The bytes.
* @param len How many.
* @return KODE_OK while the file may still be a whole image; KODE_ERROR once
* it cannot be: a bad magic or segment count, a segment that leaves
* the file, or a SHA-256 that does not match. The check is then spent
* and kdrv_image_check_end() answers KODE_ERROR; KODE_DENIED with no
* check open.
*/
kode_status_t kdrv_image_check_feed(const void *data, size_t len);

/**
* @brief Close the check and give its verdict.
*
* @return KODE_OK when the chain closed inside the bytes fed and the SHA-256
* it carries, if it carries one, matched; KODE_ERROR otherwise,
* including a file that ends inside its image; KODE_DENIED with no
* check open.
*/
kode_status_t kdrv_image_check_end(void);

/** @brief Abandon the check. Always safe. */
void kdrv_image_check_abort(void);

#ifdef __cplusplus
}
#endif

#endif /* KDRV_IMAGE_CHECK_H */
Loading
Loading