Ken Van Hoeylandt 79d3dd4fdb
Improvements for apps, filesystems, terminal and shell (#659)
app-module

- Apps now have support for:
  - environment variables (inherit from parent app)
  - current workdir
  - window size (when running in terminal)
- Add type aliases like `AppId` and `PackageId`
- Remove data duplication between ledger and app instance records: create `AppManifestSet`
- `AppManifest` usage safety improved: pass by value where needed, or use `AppId` instead of a manifest.

Terminal and shell

- Moved shell commands into coreutils-module as proper apps.
- Refine stack size for terminal and shell because apps now run with their own task and stack (including in-memory apps)

Platform

- Fix for listing root dir on ESP32: wrap posix directory functions and wire in file systems.
- Moved all platform-specific file system init code to platform subprojects

Other

- Fix for screenshot capture (issue with paths)
- Fixes for coreutils app correctness
- Memory optimizations (use more PSRAM where possible) in various locations such as app-module, shell and terminal.
- `ls` and `help` commands now use terminal width and show columns of data by default
- Created fs helpers at `TactilityKernel/include/tactility/filesystem/fs.h` (partially ported from `ShellFs`)
2026-09-25 08:29:27 +02:00

135 lines
4.1 KiB
C++

// SPDX-License-Identifier: Apache-2.0
#include <app/env.h>
#include <app/private/env_internal.h>
#include <app/private/ledger.h>
#include <app/scheduler.h>
#include <tactility/concurrent/mutex.h>
#include <algorithm>
#include <cstring>
#include <string>
#include <vector>
namespace {
bool is_valid_name(const char* name) {
return name != nullptr && name[0] != '\0' && strchr(name, '=') == nullptr;
}
// "NAME=..." prefix match, not just a prefix: requires the '=' right after it, so "FOO" doesn't match "FOOBAR=x".
bool entry_matches_name(const std::string& entry, const char* name, size_t name_len) {
return entry.size() > name_len && entry[name_len] == '=' && entry.compare(0, name_len, name) == 0;
}
// Finds the calling app instance's environment under the ledger lock.
// On a non-NULL return, the lock is left held for the caller to keep mutating/reading it,
// and must be released via mutex_unlock(&app_ledger().mutex) before returning to app_env_*()'s own caller.
AppEnv* lock_current_env() {
auto& ledger = app_ledger();
mutex_lock(&ledger.mutex);
auto iterator = ledger.instances.find(app_scheduler_current_app_id());
if (iterator == ledger.instances.end()) {
mutex_unlock(&ledger.mutex);
return nullptr;
}
return &iterator->second.env;
}
AppEnv::iterator find_entry(AppEnv& env, const char* name, size_t name_len) {
return std::find_if(env.begin(), env.end(), [&](const std::string& entry) {
return entry_matches_name(entry, name, name_len);
});
}
} // namespace
void app_env_apply(AppEnv& env, const char* const* overlay) {
for (int i = 0; overlay != nullptr && overlay[i] != nullptr; i++) {
const char* separator = strchr(overlay[i], '=');
if (separator == nullptr || separator == overlay[i]) {
continue; // matches app_env_put()'s own rejection of a nameless/equals-less entry
}
const size_t name_len = static_cast<size_t>(separator - overlay[i]);
const std::string name(overlay[i], name_len);
auto iterator = find_entry(env, name.c_str(), name_len);
if (iterator == env.end()) {
env.emplace_back(overlay[i]);
} else {
*iterator = overlay[i];
}
}
}
extern "C" {
error_t app_env_set(const char* name, const char* value, bool overwrite) {
if (!is_valid_name(name) || value == nullptr) {
return ERROR_INVALID_ARGUMENT;
}
AppEnv* env = lock_current_env();
if (env == nullptr) {
return ERROR_NOT_FOUND;
}
const size_t name_len = strlen(name);
auto iterator = find_entry(*env, name, name_len);
if (iterator == env->end()) {
env->emplace_back(std::string(name) + "=" + value);
} else if (overwrite) {
*iterator = std::string(name) + "=" + value;
}
mutex_unlock(&app_ledger().mutex);
return ERROR_NONE;
}
error_t app_env_unset(const char* name) {
if (!is_valid_name(name)) {
return ERROR_INVALID_ARGUMENT;
}
AppEnv* env = lock_current_env();
if (env == nullptr) {
return ERROR_NOT_FOUND;
}
auto iterator = find_entry(*env, name, strlen(name));
if (iterator != env->end()) {
env->erase(iterator);
}
mutex_unlock(&app_ledger().mutex);
return ERROR_NONE;
}
const char* app_env_get(const char* name) {
if (!is_valid_name(name)) {
return nullptr;
}
AppEnv* env = lock_current_env();
if (env == nullptr) {
return nullptr;
}
const size_t name_len = strlen(name);
auto iterator = find_entry(*env, name, name_len);
const char* result = iterator != env->end() ? iterator->c_str() + name_len + 1 : nullptr;
mutex_unlock(&app_ledger().mutex);
return result;
}
error_t app_env_put(const char* string) {
if (string == nullptr) {
return ERROR_INVALID_ARGUMENT;
}
const char* separator = strchr(string, '=');
if (separator == nullptr || separator == string) {
return ERROR_INVALID_ARGUMENT;
}
const std::string name(string, separator - string);
return app_env_set(name.c_str(), separator + 1, true);
}
} // extern "C"