refactor(core): sync projects/unix/main.c with micropython
What changed, and why it matters
This commit refactors the Trezor firmware's Unix emulator main file to more closely match the upstream MicroPython project. It enables a virtual file system (VFS) for the emulator, but explicitly blocks live filesystem access when running the frozen (production-like) emulator build. The changes are described as maintenance-only and carry a '[no changelog]' tag, meaning the vendor does not treat them as a security fix.
No immediate security action is required. Reviewers should verify that TREZOR_EMULATOR_FROZEN is always defined for release emulator builds to ensure the host filesystem remains unmounted, and confirm that the new VFS surface does not expose unintended import paths in frozen configurations.
Security signals we found
Enables VFS subsystem in the Unix emulator (MICROPY_VFS=1, MICROPY_VFS_POSIX=1, MICROPY_READER_VFS=1)
Adds host filesystem mount for non-frozen emulator builds only; frozen builds explicitly skip the mount
Removes custom file/stat/open fallback code in favor of upstream MicroPython VFS implementation
Adds MICROPY_PYEXEC_ENABLE_EXIT_CODE_HANDLING for consistent SystemExit behavior
No changelog entry; commit is labeled as refactor/maintenance
Evidence from the diff
The patch synchronizes core/embed/projects/unix/main.c with upstream MicroPython. Key changes include: enabling MICROPY_VFS and MICROPY_READER_VFS, adding vfs.c, vfs_posix.c, vfs_reader.c, and pyexec.c sources, replacing custom lexer execution with pyexec_file/pyexec_vstr, switching stack control to mp_cstack_init_with_sp_here, and adding a TREZOR_EMULATOR_FROZEN guard that prevents mounting the host filesystem. Non-frozen emulator builds still mount VfsPosix at ‘/’. The firmware mpconfigport.h and build.rs are updated to include the new VFS/reader sources. A typo ‘sys_set_excecutable’ is introduced but is functionally harmless. No vulnerability or exploit is evident from the diff.
Changed components
core/embed/projects/unix/main.ccore/embed/projects/unix/mpconfigport.hcore/embed/projects/firmware/mpconfigport.hcore/SConscript.unixcore/embed/upymod/build.rscore/src/prof/__main__.pyInspect captured patch +320 / −213
diff --git a/core/SConscript.unix b/core/SConscript.unix
index 2cc254c0..98e3188f 100644
--- a/core/SConscript.unix
+++ b/core/SConscript.unix
@@ -96,7 +96,10 @@ CPPDEFINES_HAL = []
PATH_HAL = []
CPPDEFINES_MOD = []
SOURCE_MOD = [
+ 'vendor/micropython/extmod/vfs.c',
+ 'vendor/micropython/extmod/vfs_posix.c',
'vendor/micropython/extmod/vfs_posix_file.c',
+ 'vendor/micropython/extmod/vfs_reader.c',
]
SOURCE_MOD_CRYPTO = []
RUST_UI_FEATURES = []
@@ -438,6 +441,7 @@ SOURCE_UNIX = [
'vendor/micropython/ports/unix/input.c',
'vendor/micropython/ports/unix/unix_mphal.c',
'vendor/micropython/shared/runtime/gchelper_generic.c',
+ 'vendor/micropython/shared/runtime/pyexec.c',
]
if "app_loading" in FEATURES_WANTED:
diff --git a/core/embed/projects/firmware/mpconfigport.h b/core/embed/projects/firmware/mpconfigport.h
index da362a1b..cdd773c9 100644
--- a/core/embed/projects/firmware/mpconfigport.h
+++ b/core/embed/projects/firmware/mpconfigport.h
@@ -180,7 +180,8 @@
// by default contains nearest git tag, which may not be present in shallow
// repo, breaking reproducibility
-#define MICROPY_BANNER_NAME_AND_VERSION ""
+#define MICROPY_BANNER_NAME_AND_VERSION "MicroPython"
+#define MICROPY_BANNER_MACHINE "Trezor"
// ============= this ends common config section ===================
diff --git a/core/embed/projects/unix/main.c b/core/embed/projects/unix/main.c
index e31ba5b2..c7ae55ca 100644
--- a/core/embed/projects/unix/main.c
+++ b/core/embed/projects/unix/main.c
@@ -4,6 +4,7 @@
* The MIT License (MIT)
*
* Copyright (c) 2013, 2014 Damien P. George
+ * Copyright (c) 2014-2017 Paul Sokolovsky
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -32,32 +33,42 @@
#include <sys/dbg_console.h>
#endif
+#include "version.h"
+
#include <ctype.h>
#include <errno.h>
#include <signal.h>
#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
#include <stdlib.h>
+#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "extmod/misc.h"
+#include "extmod/modplatform.h"
+#include "extmod/vfs.h"
#include "extmod/vfs_posix.h"
#include "genhdr/mpversion.h"
#include "input.h"
-
#include "py/builtin.h"
#include "py/compile.h"
+#include "py/cstack.h"
#include "py/gc.h"
#include "py/mperrno.h"
#include "py/mphal.h"
#include "py/mpthread.h"
+#include "py/objstr.h"
#include "py/repl.h"
#include "py/runtime.h"
-#include "py/stackctrl.h"
+#include "shared/runtime/pyexec.h"
+#include "stack_size.h"
// Command line options, with their defaults
-static bool compile_only = false;
+bool mp_compile_only = false;
static uint emit_opt = MP_EMIT_OPT_NONE;
#if MICROPY_ENABLE_GC
@@ -66,6 +77,19 @@ static uint emit_opt = MP_EMIT_OPT_NONE;
long heap_size = 1024 * 1024 * (sizeof(mp_uint_t) / 4);
#endif
+// Number of heaps to assign by default if MICROPY_GC_SPLIT_HEAP=1
+#ifndef MICROPY_GC_SPLIT_HEAP_N_HEAPS
+#define MICROPY_GC_SPLIT_HEAP_N_HEAPS (1)
+#endif
+
+#if !MICROPY_PY_SYS_PATH
+#error "The unix port requires MICROPY_PY_SYS_PATH=1"
+#endif
+
+#if !MICROPY_PY_SYS_ARGV
+#error "The unix port requires MICROPY_PY_SYS_ARGV=1"
+#endif
+
static void stderr_print_strn(void *env, const char *str, size_t len) {
(void)env;
#ifdef USE_DBG_CONSOLE
@@ -99,8 +123,6 @@ static int handle_uncaught_exception(mp_obj_base_t *exc) {
}
#define LEX_SRC_STR (1)
-#define LEX_SRC_VSTR (2)
-#define LEX_SRC_FILENAME (3)
#define LEX_SRC_STDIN (4)
// Returns standard error codes: 0 for success, 1 for all other errors,
@@ -118,19 +140,13 @@ static int execute_from_lexer(int source_kind, const void *source,
const char *line = source;
lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, line, strlen(line),
false);
- } else if (source_kind == LEX_SRC_VSTR) {
- const vstr_t *vstr = source;
- lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, vstr->buf,
- vstr->len, false);
- } else if (source_kind == LEX_SRC_FILENAME) {
- lex = mp_lexer_new_from_file((const char *)source);
} else { // LEX_SRC_STDIN
lex = mp_lexer_new_from_fd(MP_QSTR__lt_stdin_gt_, 0, false);
}
qstr source_name = lex->source_name;
-#if MICROPY_PY___FILE__
+#if MICROPY_MODULE___FILE__
if (input_kind == MP_PARSE_FILE_INPUT) {
mp_store_global(MP_QSTR___file__, MP_OBJ_NEW_QSTR(source_name));
}
@@ -138,26 +154,31 @@ static int execute_from_lexer(int source_kind, const void *source,
mp_parse_tree_t parse_tree = mp_parse(lex, input_kind);
+#if defined(MICROPY_UNIX_COVERAGE)
+ // allow to print the parse tree in the coverage build
+ if (mp_verbose_flag >= 3) {
+ printf("----------------\n");
+ mp_parse_node_print(&mp_plat_print, parse_tree.root, 0);
+ printf("----------------\n");
+ }
+#endif
+
mp_obj_t module_fun = mp_compile(&parse_tree, source_name, is_repl);
- if (!compile_only) {
+ if (!mp_compile_only) {
// execute it
mp_call_function_0(module_fun);
- // check for pending exception
- if (MP_STATE_MAIN_THREAD(mp_pending_exception) != MP_OBJ_NULL) {
- mp_obj_t obj = MP_STATE_MAIN_THREAD(mp_pending_exception);
- MP_STATE_MAIN_THREAD(mp_pending_exception) = MP_OBJ_NULL;
- nlr_raise(obj);
- }
}
mp_hal_set_interrupt_char(-1);
+ mp_handle_pending(MP_HANDLE_PENDING_CALLBACKS_AND_EXCEPTIONS);
nlr_pop();
return 0;
} else {
// uncaught exception
mp_hal_set_interrupt_char(-1);
+ mp_handle_pending(MP_HANDLE_PENDING_CALLBACKS_AND_CLEAR_EXCEPTIONS);
return handle_uncaught_exception(nlr.ret_val);
}
}
@@ -181,101 +202,40 @@ static char *strjoin(const char *s1, int sep_char, const char *s2) {
#endif
static int do_repl(void) {
- mp_hal_stdout_tx_str("MicroPython " MICROPY_GIT_TAG " on " MICROPY_BUILD_DATE
- "; " MICROPY_PY_SYS_PLATFORM
- " version\nUse Ctrl-D to exit, Ctrl-E for paste mode\n");
-
#if MICROPY_USE_READLINE == 1
- // use MicroPython supplied readline
+ // use MicroPython supplied readline-based REPL
- vstr_t line;
- vstr_init(&line, 16);
+ int ret = 0;
for (;;) {
- mp_hal_stdio_mode_raw();
-
- input_restart:
- vstr_reset(&line);
- int ret = readline(&line, ">>> ");
- mp_parse_input_kind_t parse_input_kind = MP_PARSE_SINGLE_INPUT;
-
- if (ret == CHAR_CTRL_C) {
- // cancel input
- mp_hal_stdout_tx_str("\r\n");
- goto input_restart;
- } else if (ret == CHAR_CTRL_D) {
- // EOF
- printf("\n");
- mp_hal_stdio_mode_orig();
- vstr_clear(&line);
- return 0;
- } else if (ret == CHAR_CTRL_E) {
- // paste mode
- mp_hal_stdout_tx_str(
- "\npaste mode; Ctrl-C to cancel, Ctrl-D to finish\n=== ");
- vstr_reset(&line);
- for (;;) {
- char c = mp_hal_stdin_rx_chr();
- if (c == CHAR_CTRL_C) {
- // cancel everything
- mp_hal_stdout_tx_str("\n");
- goto input_restart;
- } else if (c == CHAR_CTRL_D) {
- // end of input
- mp_hal_stdout_tx_str("\n");
- break;
- } else {
- // add char to buffer and echo
- vstr_add_byte(&line, c);
- if (c == '\r') {
- mp_hal_stdout_tx_str("\n=== ");
- } else {
- mp_hal_stdout_tx_strn(&c, 1);
- }
- }
- }
- parse_input_kind = MP_PARSE_FILE_INPUT;
- } else if (line.len == 0) {
- if (ret != 0) {
- printf("\n");
+ if (pyexec_mode_kind == PYEXEC_MODE_RAW_REPL) {
+ if ((ret = pyexec_raw_repl()) != 0) {
+ break;
}
- goto input_restart;
} else {
- // got a line with non-zero length, see if it needs continuing
- while (mp_repl_continue_with_input(vstr_null_terminated_str(&line))) {
- vstr_add_byte(&line, '\n');
- ret = readline(&line, "... ");
- if (ret == CHAR_CTRL_C) {
- // cancel everything
- printf("\n");
- goto input_restart;
- } else if (ret == CHAR_CTRL_D) {
- // stop entering compound statement
- break;
- }
+ if ((ret = pyexec_friendly_repl()) != 0) {
+ break;
}
}
-
- mp_hal_stdio_mode_orig();
-
- ret = execute_from_lexer(LEX_SRC_VSTR, &line, parse_input_kind, true);
- if (ret & FORCED_EXIT) {
- return ret;
- }
}
+ return ret;
#else
// use simple readline
+ mp_hal_stdout_tx_str(MICROPY_BANNER_NAME_AND_VERSION);
+ mp_hal_stdout_tx_str("; " MICROPY_BANNER_MACHINE);
+ mp_hal_stdout_tx_str("\nUse Ctrl-D to exit, Ctrl-E for paste mode\n");
+
for (;;) {
- char *line = prompt(">>> ");
+ char *line = prompt((char *)mp_repl_get_ps1());
if (line == NULL) {
// EOF
return 0;
}
while (mp_repl_continue_with_input(line)) {
- char *line2 = prompt("... ");
+ char *line2 = prompt((char *)mp_repl_get_ps2());
if (line2 == NULL) {
break;
}
@@ -287,29 +247,65 @@ static int do_repl(void) {
int ret =
execute_from_lexer(LEX_SRC_STR, line, MP_PARSE_SINGLE_INPUT, true);
+ free(line);
if (ret & FORCED_EXIT) {
return ret;
}
- free(line);
}
#endif
}
+static inline int convert_pyexec_result(int ret) {
+#if MICROPY_PYEXEC_ENABLE_EXIT_CODE_HANDLING
+ // With exit code handling enabled:
+ // pyexec returns exit code with PYEXEC_FORCED_EXIT flag set for SystemExit
+ // Unix port expects: 0 for success, non-zero for error/exit
+ if (ret & PYEXEC_FORCED_EXIT) {
+ // SystemExit: extract exit code from lower bits
+ return ret & 0xFF;
+ }
+ // Normal execution or exception: return as-is (0 for success, 1 for
+ // exception)
+ return ret;
+#else
+ // pyexec returns 1 for success, 0 for exception, PYEXEC_FORCED_EXIT for
+ // SystemExit Convert to unix port's expected codes: 0 for success, 1 for
+ // exception, FORCED_EXIT|val for SystemExit
+ if (ret == 1) {
+ return 0; // success
+ } else if (ret & PYEXEC_FORCED_EXIT) {
+ return ret; // SystemExit with exit value in lower 8 bits
+ } else {
+ return 1; // exception
+ }
+#endif
+}
+
static int do_file(const char *file) {
- return execute_from_lexer(LEX_SRC_FILENAME, file, MP_PARSE_FILE_INPUT, false);
+ return convert_pyexec_result(pyexec_file(file));
}
static int do_str(const char *str) {
- return execute_from_lexer(LEX_SRC_STR, str, MP_PARSE_FILE_INPUT, false);
+ vstr_t vstr;
+ vstr.buf = (char *)str;
+ vstr.len = strlen(str);
+ int ret = pyexec_vstr(&vstr, true);
+ return convert_pyexec_result(ret);
}
-static int usage(char **argv) {
+static void print_help(char **argv) {
printf(
- "usage: %s [<opts>] [-X <implopt>] [-c <command>] [<filename>]\n"
+ "usage: %s [<opts>] [-X <implopt>] [-i | -c <command> | -m <module> | "
+ "<filename>]\n"
"Options:\n"
"--emulator-properties : print basic emulator info and exit\n"
+ "--version : show version information\n"
+ "-h : print this help message\n"
+ "-i : enable REPL\n"
+#if MICROPY_DEBUG_PRINTERS
"-v : verbose (trace various operations); can be multiple\n"
+#endif
"-O[N] : apply bytecode optimizations of level N\n"
"\n"
"Implementation specific options (-X):\n",
@@ -334,7 +330,10 @@ static int usage(char **argv) {
if (impl_opts_cnt == 0) {
printf(" (none)\n");
}
+}
+static int invalid_args(void) {
+ fprintf(stderr, "Invalid command line arguments. Use -h option for help.\n");
return 1;
}
@@ -342,13 +341,23 @@ static int usage(char **argv) {
static void pre_process_options(int argc, char **argv) {
for (int a = 1; a < argc; a++) {
if (argv[a][0] == '-') {
+ if (strcmp(argv[a], "-h") == 0) {
+ print_help(argv);
+ exit(0);
+ }
+ if (strcmp(argv[a], "--version") == 0) {
+ printf("%s (trezor-core %d.%d.%d.%d); %s\n",
+ MICROPY_BANNER_NAME_AND_VERSION, VERSION_MAJOR, VERSION_MINOR,
+ VERSION_PATCH, VERSION_BUILD, MICROPY_BANNER_MACHINE);
+ exit(0);
+ }
if (strcmp(argv[a], "-X") == 0) {
if (a + 1 >= argc) {
- exit(usage(argv));
+ exit(invalid_args());
}
if (0) {
} else if (strcmp(argv[a + 1], "compile-only") == 0) {
- compile_only = true;
+ mp_compile_only = true;
} else if (strcmp(argv[a + 1], "emit=bytecode") == 0) {
emit_opt = MP_EMIT_OPT_BYTECODE;
#if MICROPY_EMIT_NATIVE
@@ -394,8 +403,7 @@ static void pre_process_options(int argc, char **argv) {
#endif
} else {
invalid_arg:
- printf("Invalid option\n");
- exit(usage(argv));
+ exit(invalid_args());
}
a++;
}
@@ -409,6 +417,21 @@ static void set_sys_argv(char *argv[], int argc, int start_arg) {
}
}
+#if MICROPY_PY_SYS_EXECUTABLE
+extern mp_obj_str_t mp_sys_executable_obj;
+static char *executable_path = NULL;
+
+static void sys_set_excecutable(char *argv0) {
+ if (executable_path == NULL) {
+ executable_path = realpath(argv0, NULL);
+ }
+ if (executable_path != NULL) {
+ mp_obj_str_set_data(&mp_sys_executable_obj, (byte *)executable_path,
+ strlen(executable_path));
+ }
+}
+#endif
+
#ifdef _WIN32
#define PATHLIST_SEP_CHAR ';'
#else
@@ -417,7 +440,7 @@ static void set_sys_argv(char *argv[], int argc, int start_arg) {
static int do_import_module(const char *modname) {
mp_obj_t import_args[4];
- import_args[0] = mp_obj_new_str(modname, strlen(modname));
+ import_args[0] = mp_obj_new_str_from_cstr(modname);
import_args[1] = import_args[2] = mp_const_none;
// Ask __import__ to handle imported module specially - set its __name__
// to __main__, and also return this leaf module, not top-level package
@@ -433,16 +456,22 @@ static int do_import_module(const char *modname) {
bool subpkg_tried = false;
reimport:
+ mp_hal_set_interrupt_char(CHAR_CTRL_C);
if (nlr_push(&nlr) == 0) {
mod = mp_builtin___import__(MP_ARRAY_SIZE(import_args), import_args);
+ mp_hal_set_interrupt_char(-1);
+ mp_handle_pending(MP_HANDLE_PENDING_CALLBACKS_AND_EXCEPTIONS);
nlr_pop();
} else {
// uncaught exception
+ mp_hal_set_interrupt_char(-1);
+ mp_handle_pending(MP_HANDLE_PENDING_CALLBACKS_AND_CLEAR_EXCEPTIONS);
exit(handle_uncaught_exception(nlr.ret_val) & 0xff);
}
+ // If this module is a package, see if it has a `__main__.py`.
mp_obj_t dest[2];
- mp_load_method_maybe(mod, MP_QSTR___path__, dest);
+ mp_load_method_protected(mod, MP_QSTR___path__, dest, true);
if (dest[0] != MP_OBJ_NULL && !subpkg_tried) {
subpkg_tried = true;
vstr_t vstr;
@@ -482,13 +511,25 @@ MP_NOINLINE int main_(int argc, char **argv) {
signal(SIGPIPE, SIG_IGN);
#endif
- mp_stack_set_limit(600000 * (sizeof(void *) / 4));
-
pre_process_options(argc, argv);
#if MICROPY_ENABLE_GC
+#if !MICROPY_GC_SPLIT_HEAP
char *heap = malloc(heap_size);
gc_init(heap, heap + heap_size);
+#else
+ assert(MICROPY_GC_SPLIT_HEAP_N_HEAPS > 0);
+ char *heaps[MICROPY_GC_SPLIT_HEAP_N_HEAPS];
+ long multi_heap_size = heap_size / MICROPY_GC_SPLIT_HEAP_N_HEAPS;
+ for (size_t i = 0; i < MICROPY_GC_SPLIT_HEAP_N_HEAPS; i++) {
+ heaps[i] = malloc(multi_heap_size);
+ if (i == 0) {
+ gc_init(heaps[i], heaps[i] + multi_heap_size);
+ } else {
+ gc_add(heaps[i], heaps[i] + multi_heap_size);
+ }
+ }
+#endif
#endif
#if MICROPY_ENABLE_PYSTACK
@@ -498,72 +539,101 @@ MP_NOINLINE int main_(int argc, char **argv) {
mp_init();
+#if MICROPY_EMIT_NATIVE
+ // Set default emitter options
+ MP_STATE_VM(default_emit_opt) = emit_opt;
+#else
+ (void)emit_opt;
+#endif
+
#if MICROPY_ENABLE_COMPILER && MICROPY_ENABLE_SOURCE_LINE
// include source lines on non-frozen builds
MP_STATE_VM(include_source_lines) = true;
#endif
- char *home = getenv("HOME");
- char *path = getenv("MICROPYPATH");
- if (path == NULL) {
+// do not let frozen emulator import live files
+#ifndef TREZOR_EMULATOR_FROZEN
+#if MICROPY_VFS_POSIX
+ {
+ // Mount the host FS at the root of our internal VFS
+ mp_obj_t args[2] = {
+ MP_OBJ_TYPE_GET_SLOT(&mp_type_vfs_posix, make_new)(&mp_type_vfs_posix,
+ 0, 0, NULL),
+ MP_OBJ_NEW_QSTR(MP_QSTR__slash_),
+ };
+ mp_vfs_mount(2, args, (mp_map_t *)&mp_const_empty_map);
+
+ // Make sure the root that was just mounted is the current VFS (it's always
+ // at the end of the linked list). Can't use chdir('/') because that will
+ // change the current path within the VfsPosix object.
+ MP_STATE_VM(vfs_cur) = MP_STATE_VM(vfs_mount_table);
+ while (MP_STATE_VM(vfs_cur)->next != NULL) {
+ MP_STATE_VM(vfs_cur) = MP_STATE_VM(vfs_cur)->next;
+ }
+ }
+#endif
+#endif
+
+ {
+ // sys.path starts as [""]
+ mp_sys_path = mp_obj_new_list(0, NULL);
+ mp_obj_list_append(mp_sys_path, MP_OBJ_NEW_QSTR(MP_QSTR_));
+
+ // Add colon-separated entries from MICROPYPATH.
+ char *home = getenv("HOME");
+ char *path = getenv("MICROPYPATH");
+ if (path == NULL) {
#ifdef MICROPY_PY_SYS_PATH_DEFAULT
- path = MICROPY_PY_SYS_PATH_DEFAULT;
+ path = MICROPY_PY_SYS_PATH_DEFAULT;
#else
- path = ".frozen";
+ path = ".frozen";
#endif
- }
- size_t path_num = 1; // [0] is for current dir (or base dir of the script)
- if (*path == ':') {
- path_num++;
- }
- for (char *p = path; p != NULL; p = strchr(p, PATHLIST_SEP_CHAR)) {
- path_num++;
- if (p != NULL) {
- p++;
}
- }
- mp_obj_list_init(MP_OBJ_TO_PTR(mp_sys_path), path_num);
- mp_obj_t *path_items;
- mp_obj_list_get(mp_sys_path, &path_num, &path_items);
- path_items[0] = MP_OBJ_NEW_QSTR(MP_QSTR_);
- {
- char *p = path;
- for (mp_uint_t i = 1; i < path_num; i++) {
- char *p1 = strchr(p, PATHLIST_SEP_CHAR);
- if (p1 == NULL) {
- p1 = p + strlen(p);
+ if (*path == PATHLIST_SEP_CHAR) {
+ // First entry is empty. We've already added an empty entry to sys.path,
+ // so skip it.
+ ++path;
+ }
+ // GCC targeting RISC-V 64 reports a warning about `path_remaining` being
+ // clobbered by either setjmp or vfork if that variable it is allocated on
+ // the stack. This may probably be a compiler error as it occurs on a few
+ // recent GCC releases (up to 14.1.0) but LLVM doesn't report any warnings.
+ static bool path_remaining;
+ path_remaining = *path;
+ while (path_remaining) {
+ char *path_entry_end = strchr(path, PATHLIST_SEP_CHAR);
+ if (path_entry_end == NULL) {
+ path_entry_end = path + strlen(path);
+ path_remaining = false;
}
- if (p[0] == '~' && p[1] == '/' && home != NULL) {
+ if (path[0] == '~' && path[1] == '/' && home != NULL) {
// Expand standalone ~ to $HOME
int home_l = strlen(home);
vstr_t vstr;
- vstr_init(&vstr, home_l + (p1 - p - 1) + 1);
+ vstr_init(&vstr, home_l + (path_entry_end - path - 1) + 1);
vstr_add_strn(&vstr, home, home_l);
- vstr_add_strn(&vstr, p + 1, p1 - p - 1);
- path_items[i] = mp_obj_new_str_from_vstr(&vstr);
+ vstr_add_strn(&vstr, path + 1, path_entry_end - path - 1);
+ mp_obj_list_append(mp_sys_path, mp_obj_new_str_from_vstr(&vstr));
} else {
- path_items[i] = mp_obj_new_str_via_qstr(p, p1 - p);
+ mp_obj_list_append(
+ mp_sys_path, mp_obj_new_str_via_qstr(path, path_entry_end - path));
}
- p = p1 + 1;
+ path = path_entry_end + 1;
}
}
mp_obj_list_init(MP_OBJ_TO_PTR(mp_sys_argv), 0);
- // Here is some example code to create a class and instance of that class.
- // First is the Python, then the C code.
- //
- // class TestClass:
- // pass
- // test_obj = TestClass()
- // test_obj.attr = 42
- //
- // mp_obj_t test_class_type, test_class_instance;
- // test_class_type = mp_obj_new_type(QSTR_FROM_STR_STATIC("TestClass"),
- // mp_const_empty_tuple, mp_obj_new_dict(0));
- // mp_store_name(QSTR_FROM_STR_STATIC("test_obj"), test_class_instance =
- // mp_call_function_0(test_class_type)); mp_store_attr(test_class_instance,
- // QSTR_FROM_STR_STATIC("attr"), mp_obj_new_int(42));
+#if defined(MICROPY_UNIX_COVERAGE)
+ {
+ MP_DECLARE_CONST_FUN_OBJ_0(extra_coverage_obj);
+ MP_DECLARE_CONST_FUN_OBJ_0(extra_cpp_coverage_obj);
+ mp_store_global(MP_QSTR_extra_coverage,
+ MP_OBJ_FROM_PTR(&extra_coverage_obj));
+ mp_store_global(MP_QSTR_extra_cpp_coverage,
+ MP_OBJ_FROM_PTR(&extra_cpp_coverage_obj));
+ }
+#endif
/*
printf("bytes:\n");
@@ -572,6 +642,10 @@ MP_NOINLINE int main_(int argc, char **argv) {
printf(" peak %d\n", m_get_peak_bytes_allocated());
*/
+#if MICROPY_PY_SYS_EXECUTABLE
+ sys_set_excecutable(argv[0]);
+#endif
+
const int NOTHING_EXECUTED = -2;
int ret = NOTHING_EXECUTED;
bool inspect = false;
@@ -583,7 +657,7 @@ MP_NOINLINE int main_(int argc, char **argv) {
inspect = true;
} else if (strcmp(argv[a], "-c") == 0) {
if (a + 1 >= argc) {
- return usage(argv);
+ return invalid_args();
}
ret = do_str(argv[a + 1]);
if (ret & FORCED_EXIT) {
@@ -592,7 +666,7 @@ MP_NOINLINE int main_(int argc, char **argv) {
a += 1;
} else if (strcmp(argv[a], "-m") == 0) {
if (a + 1 >= argc) {
- return usage(argv);
+ return invalid_args();
}
default_import = false;
set_sys_argv(argv, argc, a + 1);
@@ -613,24 +687,23 @@ MP_NOINLINE int main_(int argc, char **argv) {
p++, MP_STATE_VM(mp_optimise_value)++);
}
} else {
- return usage(argv);
+ return invalid_args();
}
} else {
- char *pathbuf = malloc(PATH_MAX);
- char *basedir = realpath(argv[a], pathbuf);
+ char *basedir = realpath(argv[a], NULL);
if (basedir == NULL) {
mp_printf(&mp_stderr_print, "%s: can't open file '%s': [Errno %d] %s\n",
argv[0], argv[a], errno, strerror(errno));
// CPython exits with 2 in such case
ret = 2;
- free(pathbuf);
break;
}
- // Set base dir of the script as first entry in sys.path
+ // Set base dir of the script as first entry in sys.path.
char *p = strrchr(basedir, '/');
- path_items[0] = mp_obj_new_str_via_qstr(basedir, p - basedir);
- free(pathbuf);
+ mp_obj_list_store(mp_sys_path, MP_OBJ_NEW_SMALL_INT(0),
+ mp_obj_new_str_via_qstr(basedir, p - basedir));
+ free(basedir);
set_sys_argv(argv, argc, a);
ret = do_file(argv[a]);
@@ -642,8 +715,12 @@ MP_NOINLINE int main_(int argc, char **argv) {
ret = do_import_module("main");
}
+ const char *inspect_env = getenv("MICROPYINSPECT");
+ if (inspect_env && inspect_env[0] != '\0') {
+ inspect = true;
+ }
if (ret == NOTHING_EXECUTED || inspect) {
- if (isatty(0)) {
+ if (isatty(0) || inspect) {
prompt_read_history();
ret = do_repl();
prompt_write_history();
@@ -674,12 +751,36 @@ MP_NOINLINE int main_(int argc, char **argv) {
}
#endif
+#if MICROPY_PY_BLUETOOTH
+ int mp_bluetooth_deinit(void);
+ mp_bluetooth_deinit();
+#endif
+
+#if MICROPY_PY_THREAD
+ mp_thread_deinit();
+#endif
+
+#if defined(MICROPY_UNIX_COVERAGE)
+ gc_sweep_all();
+#endif
+
mp_deinit();
#if MICROPY_ENABLE_GC && !defined(NDEBUG)
- // We don't really need to free memory since we are about to exit the
- // process, but doing so helps to find memory leaks.
+// We don't really need to free memory since we are about to exit the
+// process, but doing so helps to find memory leaks.
+#if !MICROPY_GC_SPLIT_HEAP
free(heap);
+#else
+ for (size_t i = 0; i < MICROPY_GC_SPLIT_HEAP_N_HEAPS; i++) {
+ free(heaps[i]);
+ }
+#endif
+#endif
+
+#if MICROPY_PY_SYS_EXECUTABLE && !defined(NDEBUG)
+ // Again, make memory leak detector happy
+ free(executable_path);
#endif
// printf("total bytes = %d\n", m_get_total_bytes_allocated());
@@ -690,59 +791,43 @@ int coreapp_emu(int argc, char **argv) {
#if MICROPY_PY_THREAD
mp_thread_init();
#endif
+
+ // Define a reasonable stack limit to detect stack overflow.
+ mp_uint_t stack_size = 600000 * UNIX_STACK_MULTIPLIER;
+
// We should capture stack top ASAP after start, and it should be
// captured guaranteedly before any other stack variables are allocated.
// For this, actual main (renamed main_) should not be inlined into
// this function. main_() itself may have other functions inlined (with
// their own stack variables), that's why we need this main/main_ split.
- mp_stack_ctrl_init();
+ mp_cstack_init_with_sp_here(stack_size);
return main_(argc, argv);
}
-#if !MICROPY_VFS
-
-#ifdef TREZOR_EMULATOR_FROZEN
-mp_import_stat_t mp_import_stat(const char *path) {
- return MP_IMPORT_STAT_NO_EXIST;
+void nlr_jump_fail(void *val) {
+#if MICROPY_USE_READLINE == 1
+ mp_hal_stdio_mode_orig();
+#endif
+ fprintf(stderr, "FATAL: uncaught NLR %p\n", val);
+ exit(1);
}
-#else
-mp_import_stat_t mp_import_stat(const char *path) {
- struct stat st;
- if (stat(path, &st) == 0) {
- if (S_ISDIR(st.st_mode)) {
- return MP_IMPORT_STAT_DIR;
- } else if (S_ISREG(st.st_mode)) {
- return MP_IMPORT_STAT_FILE;
- }
+
+#if MICROPY_VFS_ROM_IOCTL
+
+static uint8_t romfs_buf[4] = {0xd2, 0xcd, 0x31, 0x00}; // empty ROMFS
+static const MP_DEFINE_MEMORYVIEW_OBJ(romfs_obj, 'B', 0, sizeof(romfs_buf),
+ romfs_buf);
+
+mp_obj_t mp_vfs_rom_ioctl(size_t n_args, const mp_obj_t *args) {
+ switch (mp_obj_get_int(args[0])) {
+ case MP_VFS_ROM_IOCTL_GET_NUMBER_OF_SEGMENTS:
+ return MP_OBJ_NEW_SMALL_INT(1);
+
+ case MP_VFS_ROM_IOCTL_GET_SEGMENT:
+ return MP_OBJ_FROM_PTR(&romfs_obj);
}
- return MP_IMPORT_STAT_NO_EXIST;
-}
-#endif
-#if MICROPY_PY_IO
-// Factory function for I/O stream classes, only needed if generic VFS subsystem
-// isn't used. Note: buffering and encoding are currently ignored.
-mp_obj_t mp_builtin_open(size_t n_args, const mp_obj_t *pos_args,
- mp_map_t *kwargs) {
- enum { ARG_file, ARG_mode };
- static const mp_arg_t allowed_args[] = {
- {MP_QSTR_file, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_rom_obj = MP_ROM_NONE}},
- {MP_QSTR_mode, MP_ARG_OBJ, {.u_obj = MP_OBJ_NEW_QSTR(MP_QSTR_r)}},
- {MP_QSTR_buffering, MP_ARG_INT, {.u_int = -1}},
- {MP_QSTR_encoding, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE}},
- };
- mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
- mp_arg_parse_all(n_args, pos_args, kwargs, MP_ARRAY_SIZE(allowed_args),
- allowed_args, args);
- return mp_vfs_posix_file_open(&mp_type_textio, args[ARG_file].u_obj,
- args[ARG_mode].u_obj);
+ return MP_OBJ_NEW_SMALL_INT(-MP_EINVAL);
}
-MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_open_obj, 1, mp_builtin_open);
-#endif
#endif
-
-void nlr_jump_fail(void *val) {
- printf("FATAL: uncaught NLR %p\n", val);
- exit(1);
-}
diff --git a/core/embed/projects/unix/mpconfigport.h b/core/embed/projects/unix/mpconfigport.h
index 29744c9b..046c0df7 100644
--- a/core/embed/projects/unix/mpconfigport.h
+++ b/core/embed/projects/unix/mpconfigport.h
@@ -71,7 +71,7 @@
#define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_CORE_FEATURES)
// Python internal features
-#define MICROPY_READER_VFS (0)
+#define MICROPY_READER_VFS (1)
#define MICROPY_ENABLE_GC (1)
#define MICROPY_ENABLE_FINALISER (1)
#define MICROPY_STACK_CHECK (1)
@@ -92,11 +92,12 @@
#define MICROPY_STREAMS_NON_BLOCK (1)
#define MICROPY_MODULE_WEAK_LINKS (0)
#define MICROPY_CAN_OVERRIDE_BUILTINS (0)
-#define MICROPY_VFS_POSIX_FILE (1)
+#define MICROPY_VFS_POSIX (1)
#define MICROPY_USE_INTERNAL_ERRNO (0)
+#define MICROPY_PYEXEC_ENABLE_EXIT_CODE_HANDLING (1)
#define MICROPY_ENABLE_SCHEDULER (0)
#define MICROPY_SCHEDULER_DEPTH (0)
-#define MICROPY_VFS (0)
+#define MICROPY_VFS (1)
// control over Python builtins
#define MICROPY_PY_FUNCTION_ATTRS (1)
@@ -230,7 +231,8 @@ extern const struct _mp_print_t mp_stderr_print;
// by default contains nearest git tag, which may not be present in shallow
// repo, breaking reproducibility
-#define MICROPY_BANNER_NAME_AND_VERSION ""
+#define MICROPY_BANNER_NAME_AND_VERSION "MicroPython"
+#define MICROPY_BANNER_MACHINE "unix"
// ============= this ends common config section ===================
diff --git a/core/embed/upymod/build.rs b/core/embed/upymod/build.rs
index 0e1c6be6..1e305950 100644
--- a/core/embed/upymod/build.rs
+++ b/core/embed/upymod/build.rs
@@ -152,7 +152,6 @@ fn main() -> Result<()> {
"py/emitinlinethumb.c",
"py/formatfloat.c",
"py/frozenmod.c",
- "py/lexer.c",
"py/malloc.c",
"py/map.c",
"py/modarray.c",
@@ -216,7 +215,6 @@ fn main() -> Result<()> {
"py/parsenumbase.c",
"py/persistentcode.c",
"py/qstr.c",
- "py/reader.c",
"py/repl.c",
"py/runtime.c",
"py/runtime_utils.c",
@@ -243,13 +241,18 @@ fn main() -> Result<()> {
lib.add_sources_in_dir(
mpy_dir,
[
+ "extmod/vfs.c",
+ "extmod/vfs_posix.c",
"extmod/vfs_posix_file.c",
+ "extmod/vfs_reader.c",
"extmod/modos.c",
+ "extmod/modvfs.c",
"py/emitnarm.c",
"py/emitnative.c",
"py/emitnthumb.c",
"py/emitnx64.c",
"py/emitnx86.c",
+ "py/lexer.c",
"py/nlr.c",
"py/nlraarch64.c",
"py/nlrsetjmp.c",
@@ -257,11 +260,13 @@ fn main() -> Result<()> {
"py/nlrx64.c",
"py/nlrx86.c",
"py/profile.c",
+ "py/reader.c",
"ports/unix/alloc.c",
"ports/unix/gccollect.c",
"ports/unix/input.c",
"ports/unix/unix_mphal.c",
"shared/runtime/gchelper_generic.c",
+ "shared/runtime/pyexec.c",
"shared/readline/readline.c",
],
);
diff --git a/core/mocks/generated/vfs.pyi b/core/mocks/generated/vfs.pyi
new file mode 120000
index 00000000..fa1093fe
--- /dev/null
+++ b/core/mocks/generated/vfs.pyi
@@ -0,0 +1 @@
+../vfs.pyi
\ No newline at end of file
diff --git a/core/mocks/vfs.pyi b/core/mocks/vfs.pyi
new file mode 100644
index 00000000..f4ea6058
--- /dev/null
+++ b/core/mocks/vfs.pyi
@@ -0,0 +1,4 @@
+def mount(fsobj: Any = None, mount_point: str | None = None, *args: Any) -> list: ...
+
+class VfsPosix:
+ def __init__(root: Any = None) -> None: ...
diff --git a/core/src/prof/__main__.py b/core/src/prof/__main__.py
index 4f64c570..70dd8a3d 100644
--- a/core/src/prof/__main__.py
+++ b/core/src/prof/__main__.py
@@ -1,5 +1,6 @@
import micropython
import sys
+import vfs
from io import open
from os import getenv
from typing import TYPE_CHECKING, Any, Callable, TypeAlias
@@ -119,4 +120,7 @@ try:
import main # noqa: F401
finally:
print("\n------------------ script exited ------------------")
+ # enable filesystem access for frozen emulator
+ if len(vfs.mount()) == 0:
+ vfs.mount(vfs.VfsPosix(), "/")
__prof__.write_data()
diff --git a/pyproject.toml b/pyproject.toml
index 8886c9da..de76d314 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -99,6 +99,7 @@ extra_standard_library = [
"trezorutils",
"trezorconfig",
"trezorcrypto",
+ "vfs",
]
known_first_party = ["trezorlib", "apps", "coin_info", "marketcap", "ui_tests"]
known_third_party = ["trezor", "storage"]
Why this scored 27/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.