Skip to content

Commit

Permalink
Add local OTA for filesystem in emergency mode page
Browse files Browse the repository at this point in the history
Allows for upload of a .tar file
  • Loading branch information
alufers committed May 8, 2024
1 parent cfde699 commit e38a278
Show file tree
Hide file tree
Showing 12 changed files with 197 additions and 14 deletions.
2 changes: 1 addition & 1 deletion src/core/external/bell
Submodule bell updated 1 files
+5 −6 main/io/BellTar.cpp
9 changes: 6 additions & 3 deletions src/core/main/app/Core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "CoreEvents.h"
#include "EmergencyMode.h"
#include "EventBus.h"
#include "LocalOTAEndpoints.h"
#include "OTAHandler.h"
#include "URLParser.h"
#include "X509Bundle.h"
Expand Down Expand Up @@ -51,6 +52,8 @@ void Core::initialize() {
// Register HTTP handlers for OTA, update packages if necessary
this->otaHandler->initialize(this->http->getServer());

registerLocalOTAEndpoints(*this->http->getServer(), this->ctx);

// Check if contains X509 SSL bundle

std::ifstream bundleFile(this->ctx->rootPath +
Expand Down Expand Up @@ -134,9 +137,9 @@ void Core::initialize() {
} catch (berry::BerryErrorException& e) {
this->ctx->emergencyMode->trip(EmergencyModeReason::BERRY_INIT_ERROR,
e.what());
} catch(PackageLoaderFileNotFoundException& e) {
this->ctx->emergencyMode->trip(EmergencyModeReason::LOADING_BERRY_HOOK_FAILED,
e.what());
} catch (PackageLoaderFileNotFoundException& e) {
this->ctx->emergencyMode->trip(
EmergencyModeReason::LOADING_BERRY_HOOK_FAILED, e.what());
}

// Initialize HTTP
Expand Down
11 changes: 7 additions & 4 deletions src/core/main/app/EmergencyMode.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "EmergencyMode.h"
#include <memory>
#include <shared_mutex>
#include <string>
#include "CoreEvents.h"
Expand All @@ -11,6 +12,8 @@ extern unsigned char emergencyModeHtml[];
extern unsigned int emergencyModeHtml_len;
} // namespace euph

static const char* TAG = "EmergencyMode";

EmergencyMode::EmergencyMode(std::weak_ptr<euph::EventBus> eventBus)
: eventBus(eventBus) {}

Expand All @@ -20,13 +23,13 @@ bool EmergencyMode::isActive() const {

void EmergencyMode::trip(EmergencyModeReason reason,
const std::string& message) {
EUPH_LOG(error, "EmergencyMode", "===============================");
EUPH_LOG(error, "EmergencyMode", "Tripped emergency mode with reason: %s",
EUPH_LOG(error, TAG, "===============================");
EUPH_LOG(error, TAG, "Tripped emergency mode with reason: %s",
getReasonString(reason).c_str());
EUPH_LOG(error, "EmergencyMode", "===============================");
EUPH_LOG(error, TAG, "===============================");
this->reason = reason;
if (std::shared_ptr<euph::EventBus> bus = this->eventBus.lock()) {
EUPH_LOG(error, "EmergencyMode", "Posting EmergencyModeTrippedEvent");
EUPH_LOG(error, TAG, "Posting EmergencyModeTrippedEvent");
auto evt = std::make_unique<EmergencyModeTrippedEvent>(reason);
bus->postEvent(std::move(evt));
}
Expand Down
124 changes: 124 additions & 0 deletions src/core/main/app/LocalOTAEndpoints.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
#include "LocalOTAEndpoints.h"
#include <filesystem>
#include <fstream>
#include <memory>
#include "UpdateFilesystemServiceJob.h"

using namespace euph;

static const char* TAG = "LocalOTAEndpoints";

/**
* @brief Helper struct passed as userData to mongoose form data handlers.
*/
struct FilesystemUpdateContext {
bool hasValidFile;
bool isCurrentlyReceivingFile;
std::string tarFilePath;
std::ofstream file;
};

static int filesystemUpdateFieldFound(const char* key, const char* filename,
char* path, size_t pathlen,
void* userData) {

std::string_view keyView(key);
FilesystemUpdateContext* context =
static_cast<FilesystemUpdateContext*>(userData);
context->isCurrentlyReceivingFile = false;
if (keyView == "fs") {
context->hasValidFile = true;
context->isCurrentlyReceivingFile = true;
return MG_FORM_FIELD_STORAGE_GET;
}

return MG_FORM_FIELD_STORAGE_SKIP;
}

static int filesystemUpdateFieldGet(const char* key, const char* value,
size_t valuelen, void* userData) {

std::string_view keyView(key);
FilesystemUpdateContext* context =
static_cast<FilesystemUpdateContext*>(userData);

if (context->isCurrentlyReceivingFile) {
if (!context->file.is_open()) {
context->file = std::ofstream(context->tarFilePath,
std::ios::binary | std::ios::trunc);
if (!context->file.is_open()) {
EUPH_LOG(error, TAG,
"Could not open file '/fs/tmp/fs.tar' for writing");
return MG_FORM_FIELD_HANDLE_ABORT;
}
}

context->file.write(value, valuelen);
return MG_FORM_FIELD_HANDLE_GET;
}

return MG_FORM_FIELD_HANDLE_NEXT;
}

void euph::registerLocalOTAEndpoints(bell::BellHTTPServer& server,
std::weak_ptr<euph::Context> ctxPtr) {
server.registerPost(
"/api/emergency-mode/filesystem-update",
[&server, ctxPtr](struct mg_connection* conn) {
auto ctx = ctxPtr.lock();
if (!ctx) {
EUPH_LOG(error, TAG, "Could not lock context.");
return server.makeEmptyResponse();
}

const struct mg_request_info* req_info = mg_get_request_info(conn);
std::string tmpDir = ctx->rootPath + "/tmp";

FilesystemUpdateContext context{.hasValidFile = false,
.isCurrentlyReceivingFile = false,
.tarFilePath = tmpDir + "/fs.tar",
.file = std::ofstream()};

// Check if /tmp exists (might not be the case on boards with completely erased flash)
if (!std::filesystem::exists(tmpDir)) {

EUPH_LOG(info, TAG, "Creating %s directory", tmpDir.c_str());
// Create /tmp
std::filesystem::create_directory(tmpDir);
}

struct mg_form_data_handler fdh = {filesystemUpdateFieldFound,
filesystemUpdateFieldGet, NULL,
&context};
(void)req_info;

int ret = mg_handle_form_request(conn, &fdh);
if (context.file.is_open()) {
context.file.close();
}
if (ret <= 0 || !context.hasValidFile) {
mg_printf(conn,
"HTTP/1.1 400 OK\r\nContent-Type: "
"application/json\r\nConnection: close\r\n\r\n");
mg_printf(conn, "{\"status\": \"error\"}");
return server.makeEmptyResponse();
}

EUPH_LOG(info, TAG,
"Filesystem update archive received successfully, unpacking.");
std::unique_ptr<ServiceJob> updateJob =
std::make_unique<UpdateFilesystemServiceJob>(context.tarFilePath);

if (!ctx->serviceTask->submitJob(std::move(updateJob))) {
EUPH_LOG(error, TAG,
"Could not submit filesystem update job to service task.");
}

mg_printf(conn,
"HTTP/1.1 200 OK\r\nContent-Type: "
"application/json\r\nConnection: close\r\n\r\n");
mg_printf(conn, "{}");

return server.makeEmptyResponse();
});
}
4 changes: 3 additions & 1 deletion src/core/main/app/ServiceTask.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ void ServiceJob::reportProgress(std::shared_ptr<euph::Context> ctx,
}

ServiceTask::ServiceTask(std::weak_ptr<euph::Context> ctx)
: bell::Task("ServiceTask", 1024 * 16, 1, 0), ctx(ctx), jobSemaphore(0) {}
: bell::Task("ServiceTask", 1024 * 16, 1, 0), ctx(ctx), jobSemaphore(0) {
startTask();
}

void ServiceTask::runTask() {
while (true) {
Expand Down
1 change: 1 addition & 0 deletions src/core/main/app/UpdateFilesystemServiceJob.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ void UpdateFilesystemServiceJob::run(std::shared_ptr<euph::Context> ctx) {
size_t fileSize = std::filesystem::file_size(path);
size_t progressReportStep = fileSize / 100; // Report progress every 1%

EUPH_LOG(info, jobTypeName(), "Archive size: %d", fileSize);
// Open the file
std::ifstream fileStream(archivePath, std::ios::in | std::ios::binary);

Expand Down
36 changes: 36 additions & 0 deletions src/core/main/app/emergency.html
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,16 @@
font-size: 0.9rem;
overflow-x: auto;
}
summary {
cursor: pointer;
margin-bottom: 0.5rem;
}
details {
margin-top: 1rem;
border: 1px solid #ccc;
border-radius: 5px;
padding: 1rem;
}
</style>
</head>
<body>
Expand All @@ -50,6 +60,16 @@ <h1>Emergency mode</h1>

</code>
</pre>

<h2>Repair options</h2>
<details>
<summary>Upload new filesystem image</summary>
<div>
Select .tar file:
<input type="file" id="file" accept=".tar" />
<button type="button" id="upload-fs">Upload</button>
</div>
</details>
</div>
<script>
(() => {
Expand All @@ -62,6 +82,22 @@ <h1>Emergency mode</h1>
$("#emergencyModeReason").textContent = emergencyMode.reason;
$("#emergencyModeMessage").textContent = emergencyMode.message;
});

$("#upload-fs").addEventListener("click", () => {
const file = $("#file").files[0];
if (!file) {
alert("Please select a file");
return;
}

const formData = new FormData();
formData.append("fs", file);

fetch("/api/emergency-mode/filesystem-update", {
method: "POST",
body: formData,
}).then((res) => res.json());
});
})();
</script>
</body>
Expand Down
4 changes: 2 additions & 2 deletions src/core/main/app/include/EmergencyMode.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@

#include <atomic>
#include <memory>
#include <mutex>
#include <nlohmann/json.hpp>
#include <shared_mutex>
#include <string>
#include "civetweb.h"

#include "EventBus.h"

namespace euph {
Expand Down Expand Up @@ -64,6 +62,8 @@ class EmergencyMode {

/**
* @brief Serve the emergency mode page, if emergency mode is active.
*
* To be used as a hook in the HTTP server, before trying to serve files from the filesystem.
*
* @param conn The connection to serve the page to.
* @return true If the page was served, caller should return from the request handler.
Expand Down
12 changes: 12 additions & 0 deletions src/core/main/app/include/LocalOTAEndpoints.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#pragma once

#include <memory>
#include "EuphContext.h"
#include "BellHTTPServer.h"

namespace euph {

void registerLocalOTAEndpoints(bell::BellHTTPServer& server,
std::weak_ptr<euph::Context> ctx);
}

2 changes: 1 addition & 1 deletion src/core/main/app/include/OTAHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,4 @@ class OTAHandler {

std::string validatePackage();
};
} // namespace euph
} // namespace euph
3 changes: 2 additions & 1 deletion src/core/main/app/include/UpdateFilesystemServiceJob.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ namespace euph {
* @brief Unpacks a tar archive with a filesystem OTA update.
*
*/
class UpdateFilesystemServiceJob : euph::ServiceJob {
class UpdateFilesystemServiceJob : public euph::ServiceJob {
public:
UpdateFilesystemServiceJob(std::string archivePath);

virtual std::string jobTypeName() override;
virtual void run(std::shared_ptr<euph::Context> ctx) override;

Expand Down
3 changes: 2 additions & 1 deletion src/targets/esp32/main/app/include/StatusLED.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ class StatusLED : public bell::Task, public euph::EventSubscriber {

};

ModeDefinition currentStatus = {.r = 70,
ModeDefinition currentStatus = {.prio = 0,
.r = 70,
.g = 30,
.b = 0,
.behaviour = Behaviour::BREATHING};
Expand Down

0 comments on commit e38a278

Please sign in to comment.