diff --git a/cpu/cortexm_common/include/thread_arch.h b/cpu/cortexm_common/include/thread_arch.h index 987189387597..200734c1888f 100644 --- a/cpu/cortexm_common/include/thread_arch.h +++ b/cpu/cortexm_common/include/thread_arch.h @@ -38,6 +38,19 @@ static inline __attribute__((always_inline)) void thread_yield_higher(void) #endif /* DOXYGEN */ +/** + * @brief Default SVC dispatch handler (weak function). + * + * @param[in] svc_number The svc number to handle. + * @param[in] svc_args Supervisor call arguments. + * + * @retval >= 0 when svc_number has been handled. + * @retval < 0 otherwise. + * + * @see _svc_dispatch + */ +int svc_dispatch_handler(unsigned int svc_number, unsigned int *svc_args); + #ifdef __cplusplus } #endif diff --git a/cpu/cortexm_common/include/vectors_cortexm.h b/cpu/cortexm_common/include/vectors_cortexm.h index f6a2fb2704c7..f84ce0030505 100644 --- a/cpu/cortexm_common/include/vectors_cortexm.h +++ b/cpu/cortexm_common/include/vectors_cortexm.h @@ -90,6 +90,15 @@ void hard_fault_default(void); /* The following four exceptions are only present for Cortex-M3 and -M4 CPUs */ #if defined(CPU_CORE_CORTEX_M3) || defined(CPU_CORE_CORTEX_M4) || \ defined(CPU_CORE_CORTEX_M4F) || defined(CPU_CORE_CORTEX_M7) + +/** + * @brief Default memory manager behavior callback (weak function) + * + * @retval >=0 when the memory fault has been handled, + * @retval < 0 otherwise. + */ +int mem_manage_handler(void); + /** * @brief Memory management exception handler * diff --git a/cpu/cortexm_common/thread_arch.c b/cpu/cortexm_common/thread_arch.c index 1e7a31095d41..faff4deab03a 100644 --- a/cpu/cortexm_common/thread_arch.c +++ b/cpu/cortexm_common/thread_arch.c @@ -88,6 +88,7 @@ */ #include +#include #include "sched.h" #include "thread.h" @@ -274,6 +275,11 @@ void *thread_isr_stack_start(void) return (void *)&_sstack; } +void *thread_isr_stack_end(void) +{ + return (void *)&_estack; +} + void NORETURN cpu_switch_context_exit(void) { #ifdef MODULE_CORTEXM_FPU @@ -488,6 +494,14 @@ void __attribute__((naked)) __attribute__((used)) isr_svc(void) #endif } +__attribute__((weak)) int +svc_dispatch_handler(unsigned int svc_number, unsigned int *svc_args) +{ + (void)svc_number; + (void)svc_args; + return -ENOTSUP; +} + static void __attribute__((used)) _svc_dispatch(unsigned int *svc_args) { /* stack frame: @@ -514,6 +528,9 @@ static void __attribute__((used)) _svc_dispatch(unsigned int *svc_args) SCB->ICSR = SCB_ICSR_PENDSVSET_Msk; break; default: + if (svc_dispatch_handler(svc_number, svc_args) >= 0) { + return; + } DEBUG("svc: unhandled SVC #%u\n", svc_number); break; } diff --git a/cpu/cortexm_common/vectors_cortexm.c b/cpu/cortexm_common/vectors_cortexm.c index 2627815b5da5..c07ef8c41720 100644 --- a/cpu/cortexm_common/vectors_cortexm.c +++ b/cpu/cortexm_common/vectors_cortexm.c @@ -22,6 +22,7 @@ #include #include #include +#include #include "cpu.h" #include "periph_cpu.h" @@ -472,8 +473,17 @@ void hard_fault_default(void) #if defined(CPU_CORE_CORTEX_M3) || defined(CPU_CORE_CORTEX_M33) || \ defined(CPU_CORE_CORTEX_M4) || defined(CPU_CORE_CORTEX_M4F) || \ defined(CPU_CORE_CORTEX_M7) + +__attribute__((weak)) int mem_manage_handler(void) +{ + return -ENOTSUP; +} + void mem_manage_default(void) { + if (mem_manage_handler() >= 0) { + return; + } core_panic(PANIC_MEM_MANAGE, "MEM MANAGE HANDLER"); } diff --git a/examples/advanced/xipfs/Makefile b/examples/advanced/xipfs/Makefile index c380a3621523..23fb42695cab 100644 --- a/examples/advanced/xipfs/Makefile +++ b/examples/advanced/xipfs/Makefile @@ -19,14 +19,23 @@ QUIET ?= 1 TOOLCHAINS_BLACKLIST += llvm BLOBS += hello-world.fae +BLOBS += dumper.fae # Modules to include: USEMODULE += shell USEMODULE += shell_cmds_default USEMODULE += ps USEMODULE += saul_default +USEMODULE += cortexm_svc + +FEATURES_REQUIRED += cortexm_mpu # Use xipfs file system USEMODULE += xipfs +# XIPFS MPU support +CFLAGS += -DXIPFS_ENABLE_SAFE_EXEC_SUPPORT +# XIPFS MPU ISR stack requirement +CFLAGS += -DISR_STACKSIZE=1024 + include $(RIOTBASE)/Makefile.include diff --git a/examples/advanced/xipfs/Makefile.ci b/examples/advanced/xipfs/Makefile.ci index 586cb3252a4c..612731aebd6e 100644 --- a/examples/advanced/xipfs/Makefile.ci +++ b/examples/advanced/xipfs/Makefile.ci @@ -1,24 +1,5 @@ BOARD_INSUFFICIENT_MEMORY := \ - blackpill-stm32f103c8 \ - bluepill-stm32f030c8 \ - bluepill-stm32f103c8 \ - i-nucleo-lrwan1 \ - nucleo-c031c6 \ - nucleo-f030r8 \ - nucleo-f031k6 \ - nucleo-f042k6 \ - nucleo-f302r8 \ - nucleo-f303k8 \ - nucleo-f334r8 \ - nucleo-l011k4 \ - nucleo-l031k6 \ - nucleo-l053r8 \ - samd10-xmini \ - slstk3400a \ - stk3200 \ - stm32f030f4-demo \ - stm32f0discovery \ - stm32g0316-disco \ - stm32l0538-disco \ - weact-g030f6 \ + e104-bt5010a-tb \ + e104-bt5011a-tb \ + im880b \ # diff --git a/examples/advanced/xipfs/dumper.fae b/examples/advanced/xipfs/dumper.fae new file mode 100644 index 000000000000..5a35128e2b18 Binary files /dev/null and b/examples/advanced/xipfs/dumper.fae differ diff --git a/examples/advanced/xipfs/hello-world.fae b/examples/advanced/xipfs/hello-world.fae index 5a92a6aa13d2..730bca5a459d 100644 Binary files a/examples/advanced/xipfs/hello-world.fae and b/examples/advanced/xipfs/hello-world.fae differ diff --git a/examples/advanced/xipfs/main.c b/examples/advanced/xipfs/main.c index a5049eadc9ec..5aea240c786f 100644 --- a/examples/advanced/xipfs/main.c +++ b/examples/advanced/xipfs/main.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 Université de Lille + * Copyright (C) 2024-2025 Université de Lille * * This file is subject to the terms and conditions of the GNU Lesser * General Public License v2.1. See the file LICENSE in the top level @@ -51,12 +51,12 @@ /* * Allocate a new contiguous space for the nvme0p0 file system */ -XIPFS_NEW_PARTITION(nvme0p0, "/dev/nvme0p0", NVME0P0_PAGE_NUM); +XIPFS_NEW_PARTITION(nvme0p0, "/nvme0p0", NVME0P0_PAGE_NUM); /* * Allocate a new contiguous space for the nvme0p1 file system */ -XIPFS_NEW_PARTITION(nvme0p1, "/dev/nvme0p1", NVME0P1_PAGE_NUM); +XIPFS_NEW_PARTITION(nvme0p1, "/nvme0p1", NVME0P1_PAGE_NUM); #ifdef BOARD_DWM1001 @@ -71,113 +71,90 @@ XIPFS_NEW_PARTITION(nvme0p1, "/dev/nvme0p1", NVME0P1_PAGE_NUM); */ #include "blob/hello-world.fae.h" -#define FILENAME_OF_HELLO_WORLD_FAE "/dev/nvme0p0/hello-world.fae" +#define FILENAME_OF_HELLO_WORLD_FAE "/nvme0p0/hello-world.fae" #define SIZEOF_HELLO_WORLD_FAE (sizeof(hello_world_fae) / sizeof(hello_world_fae[0])) -/** - * @brief Execution in-place demonstrator. - * - * This shell command handler will create a file hello-world.fae on /dev/nvme0p0, - * if none exists yet from the data blob above. - * Then, it will execute this file. - * - * Once the file has been created, execute command can be used to rerun - * the executable file as many times as wanted. - * - * ```bash - * 2025-01-14 09:48:36,303 # main(): This is RIOT! (Version: 2024.10) - * 2025-01-14 09:48:36,307 # vfs_mount: "/dev/nvme0p0": OK - * 2025-01-14 09:48:36,313 # vfs_mount: "/dev/nvme0p1": OK - * > help - * 2025-01-14 09:48:42,300 # help - * 2025-01-14 09:48:42,302 # Command Description - * 2025-01-14 09:48:42,305 # --------------------------------------- - * 2025-01-14 09:48:42,309 # exec Execute Hello World - * 2025-01-14 09:48:42,314 # create_executable Create an XIPFS executable file - * 2025-01-14 09:48:42,317 # execute Execute an XIPFS file - * 2025-01-14 09:48:42,320 # ls list files - * 2025-01-14 09:48:42,325 # pm interact with layered PM subsystem - * 2025-01-14 09:48:42,331 # ps Prints information about running threads. - * 2025-01-14 09:48:42,334 # reboot Reboot the node - * 2025-01-14 09:48:42,338 # version Prints current RIOT_VERSION - * 2025-01-14 09:48:42,343 # vfs virtual file system operations - * > exec - * 2025-01-14 09:48:49,572 # exec - * 2025-01-14 09:48:49,573 # Hello World! - * > ls /dev/nvme0p0 - * 2025-01-14 09:48:59,997 # ls /dev/nvme0p0 - * 2025-01-14 09:48:59,999 # hello-world.fae 896 B - * 2025-01-14 09:49:00,000 # total 1 files - * > vfs df - * 2025-01-14 09:49:04,957 # vfs df - * 2025-01-14 09:49:04,962 # Mountpoint Total Used Available Use% - * 2025-01-14 09:49:04,968 # /dev/nvme0p0 40 KiB 4 KiB 36 KiB 10% - * 2025-01-14 09:49:04,974 # /dev/nvme0p1 60 KiB 0 B 60 KiB 0% - * execute /dev/nvme0p0/hello-world.fae ipsum dolores it - * 2025-01-14 09:49:14,223 # execute /dev/nvme0p0/hello-world.fae Lorem ipsum dolor sit amet - * 2025-01-14 09:49:14,225 # Hello World! - * 2025-01-14 09:49:14,225 # Lorem - * 2025-01-14 09:49:14,226 # ipsum - * 2025-01-14 09:49:14,226 # dolor - * 2025-01-14 09:49:14,227 # sit - * 2025-01-14 09:49:14,227 # amet - * ``` - */ -int execution_handler(int argc, char **argv) { - (void)argc; - (void)argv; +#include "blob/dumper.fae.h" + +#define FILENAME_OF_DUMPER_FAE "/nvme0p0/dumper.fae" +#define SIZEOF_DUMPER_FAE (sizeof(dumper_fae) / sizeof(dumper_fae[0])) - int file_handle = vfs_open(FILENAME_OF_HELLO_WORLD_FAE, O_RDONLY, 0); +typedef struct { + const char *filename; + const int bytesize; + const unsigned char *data; + const bool is_executable; +} file_to_drop_t; + +static const file_to_drop_t files_to_drop[2] = { + {FILENAME_OF_HELLO_WORLD_FAE, SIZEOF_HELLO_WORLD_FAE, hello_world_fae, true }, + {FILENAME_OF_DUMPER_FAE, SIZEOF_DUMPER_FAE, dumper_fae, true }, +}; + +static int drop_file(const file_to_drop_t *file_to_drop) { + if (file_to_drop == NULL) { + return EXIT_FAILURE; + } + + int file_handle = vfs_open(file_to_drop->filename, O_RDONLY, 0); if (file_handle < 0) { - /** There's no executable file yet, let's drop one */ + /* There's no executable file yet, let's drop one */ int ret = xipfs_extended_driver_new_file( - FILENAME_OF_HELLO_WORLD_FAE, SIZEOF_HELLO_WORLD_FAE, 1 + file_to_drop->filename, file_to_drop->bytesize, file_to_drop->is_executable ); if (ret < 0) { printf("xipfs_extended_driver_new_file : failed to create '%s' : error=%d\n", - FILENAME_OF_HELLO_WORLD_FAE, ret); + file_to_drop->filename, ret); return EXIT_FAILURE; } - /** - * Fill it with blob data + /* + * Fill it with data * Take care : vfs does not support O_APPEND with vfs_write, only O_WRONLY or O_RDWR */ - file_handle = vfs_open(FILENAME_OF_HELLO_WORLD_FAE, O_WRONLY, 0); + file_handle = vfs_open(file_to_drop->filename, O_WRONLY, 0); if (file_handle < 0) { printf("vfs_open : failed to open '%s' : error =%d\n", - FILENAME_OF_HELLO_WORLD_FAE, file_handle); + file_to_drop->filename, file_handle); return EXIT_FAILURE; } - ssize_t write_ret = vfs_write(file_handle, hello_world_fae, SIZEOF_HELLO_WORLD_FAE); + ssize_t write_ret = vfs_write(file_handle, file_to_drop->data, file_to_drop->bytesize); if (write_ret < 0) { printf("vfs_write : failed to fill '%s' : error=%d\n", - FILENAME_OF_HELLO_WORLD_FAE, write_ret); + file_to_drop->filename, write_ret); vfs_close(file_handle); return EXIT_FAILURE; } } vfs_close(file_handle); + return EXIT_SUCCESS; +} - char *exec_argv[] = { - FILENAME_OF_HELLO_WORLD_FAE, - NULL - }; +/** + * @brief Execution in-place demonstrator. + * + * This shell command handler will create a file hello-world.fae on /dev/nvme0p0, + * if none exists yet from the files_to_drop array. + * + */ +int drop_files_handler(int argc, char **argv) { + (void)argc; + (void)argv; - int ret = xipfs_extended_driver_execv(FILENAME_OF_HELLO_WORLD_FAE, exec_argv); - if (ret < 0) { - printf("Failed to execute '%s' : error=%d\n", FILENAME_OF_HELLO_WORLD_FAE, ret); - return EXIT_FAILURE; + for (unsigned int i = 0; i < ARRAY_SIZE(files_to_drop); ++i) { + if (drop_file(&files_to_drop[i]) != EXIT_SUCCESS) { + return EXIT_FAILURE; + } } return EXIT_SUCCESS; } static shell_command_t shell_commands[] = { - {"exec", "Execute Hello World", execution_handler}, + {"drop_files", "Drop example fae files into /nvme0p0", drop_files_handler}, {NULL, NULL, NULL}, }; diff --git a/pkg/xipfs/Makefile b/pkg/xipfs/Makefile index 2908e1733d2c..3a015f6c43b1 100644 --- a/pkg/xipfs/Makefile +++ b/pkg/xipfs/Makefile @@ -1,10 +1,12 @@ PKG_NAME=xipfs PKG_URL=https://github.com/2xs/xipfs.git -PKG_VERSION=962c5edc55abb6363ff2988dc3b2c3c293362e96 +PKG_VERSION=22ff164f1fb9097f272540836c3f111ecf8cde10 PKG_LICENSE=CeCILL-2.1 include $(RIOTBASE)/pkg/pkg.mk +CFLAGS += -DXIPFS_ENABLE_SAFE_EXEC_SUPPORT + export RIOT_INCLUDES=$(INCLUDES) export RIOT_CFLAGS=$(CFLAGS) diff --git a/pkg/xipfs/Makefile.dep b/pkg/xipfs/Makefile.dep index 556ac3748d2f..04ce2dc6e86f 100644 --- a/pkg/xipfs/Makefile.dep +++ b/pkg/xipfs/Makefile.dep @@ -5,9 +5,12 @@ ifeq (,$(filter xipfs_fs,$(DISABLE_MODULE))) # xipfs only makes sense if the flash memory is addressable FEATURES_REQUIRED += periph_flashpage_in_address_space FEATURES_REQUIRED += arch_32bit arch_arm + # xipfs safe execution feature relies on MPU. + FEATURES_REQUIRED += cortexm_mpu FEATURES_OPTIONAL += periph_flashpage_aux USEMODULE += periph_flashpage USEMODULE += mtd_flashpage USEMODULE += vfs USEMODULE += shell_cmd_xipfs + USEMODULE += cortexm_svc endif diff --git a/pkg/xipfs/doc.txt b/pkg/xipfs/doc.txt index 102b44801f42..8830c9d250c5 100644 --- a/pkg/xipfs/doc.txt +++ b/pkg/xipfs/doc.txt @@ -17,9 +17,14 @@ * * To prevent fragmentation, when a file is deleted, subsequent files are shifted to fill the vacant space. * - *`xipfs` is compatible with all microcontrollers featuring addressable - * flash memory and most operating systems, provided they implement the - * necessary functions to interact with the flash controller. + *`xipfs` is compatible with all ARM microcontrollers featuring addressable flash memory and + * most operating systems, provided they implement the necessary functions to interact with + * the flash controller. + * + * The filesystem is also capable to run files with memory isolation, thanks to the use of + * ARMv7-M Memory Protection Unit. + * Then, the TEXT, DATA and STACK segments of the executable are mapped to MPU regions, isolating + * the program from the rest of flash and RAM. It can only access to its legitimate contents. * * **To have a viable executable file within XiPFS**, please follow these steps : * - create a file with executable flag. diff --git a/pkg/xipfs/fs/xipfs_fs.c b/pkg/xipfs/fs/xipfs_fs.c index 2b99770f0888..275ff16e8a14 100644 --- a/pkg/xipfs/fs/xipfs_fs.c +++ b/pkg/xipfs/fs/xipfs_fs.c @@ -47,6 +47,8 @@ #include "debug.h" #include "fs/xipfs_fs.h" #include "periph/flashpage.h" +#include "vectors_cortexm.h" +#include "thread_arch.h" #include "saul_reg.h" @@ -652,7 +654,7 @@ static int get_file_size(const char *full_path, size_t *size) { return 0; } -static ssize_t copy_file(const char *full_path, void *buf, size_t nbyte) { +static int copy_file(const char *full_path, void *buf, size_t nbyte) { xipfs_mount_t mp; const char *path; xipfs_file_desc_t desc; @@ -663,9 +665,15 @@ static ssize_t copy_file(const char *full_path, void *buf, size_t nbyte) { if (ret < 0) { return ret; } + + if ((file_size == 0) || (nbyte < file_size)) { + return -EINVAL; + } + if (nbyte > file_size) { nbyte = file_size; } + if (full_path == NULL) { return -EFAULT; } @@ -675,7 +683,6 @@ static ssize_t copy_file(const char *full_path, void *buf, size_t nbyte) { if ((path = get_rel_path(&mp, full_path)) == NULL) { return -EIO; } - ret = xipfs_open(&mp, &desc, path, O_RDONLY, 0); if (ret < 0) { return ret; @@ -724,6 +731,64 @@ int xipfs_extended_driver_execv(const char *full_path, char *const argv[]) return ret; } +#ifdef XIPFS_ENABLE_SAFE_EXEC_SUPPORT + +int mem_manage_handler(void) +{ + uint32_t mmfar = SCB->MMFAR; + uint32_t cfsr = SCB->CFSR; + uintptr_t psp = __get_PSP(); + if (xipfs_mem_manage_handler((void *)psp, mmfar, cfsr) == 0) { + return 0; + } + + return -EINVAL; +} + +int svc_dispatch_handler(unsigned int svc_number, unsigned int *svc_args) +{ + switch (svc_number) { + case XIPFS_ENTER_SVC_NUMBER: { + void *crt0_ctx = (void *)svc_args[0]; + void *entry_point = (void *)svc_args[1]; + void *stack_top = (void *)svc_args[2]; + xipfs_safe_exec_enter(crt0_ctx, entry_point, stack_top); + return 0; + } + case XIPFS_SYSCALL_SVC_NUMBER: { + xipfs_syscall_dispatcher(svc_args); + return 0; + } + default: + return -ENOTSUP; + } +} + +int xipfs_extended_driver_safe_execv(const char *full_path, char *const argv[]) +{ + xipfs_mount_t mp; + const char *path; + int ret; + + if (full_path == NULL) { + return -EFAULT; + } + if ((ret = get_xipfs_mp(full_path, &mp)) < 0) { + return ret; + } + if ((path = get_rel_path(&mp, full_path)) == NULL) { + return -EIO; + } + + mutex_lock(mp.execution_mutex); + ret = xipfs_safe_execv(&mp, path, argv, xipfs_user_syscalls_table); + mutex_unlock(mp.execution_mutex); + + return ret; +} + +#endif /* XIPFS_ENABLE_SAFE_EXEC_SUPPORT */ + /* * File system driver structures */ diff --git a/sys/include/fs/xipfs_fs.h b/sys/include/fs/xipfs_fs.h index ce3f0827adc4..b0257212b6ba 100644 --- a/sys/include/fs/xipfs_fs.h +++ b/sys/include/fs/xipfs_fs.h @@ -115,7 +115,7 @@ int xipfs_construct_from_flashpage(mtd_flashpage_t *flashpage, const char *path, * * Allows to create a regular or executable new file within XiPFS. * - * @param full_path A full path such as `/dev/nvme0p0/my_new_file` + * @param full_path A full path such as `/nvme0p0/my_new_file` * * @param size The file size in bytes. * @@ -129,7 +129,7 @@ int xipfs_extended_driver_new_file(const char *full_path, uint32_t size, uint32_ /** * @brief Executes an executable file with arguments. * - * @param full_path A full path such as `/dev/nvme0p0/my_executable_file` + * @param full_path A full path such as `/nvme0p0/my_executable_file` * * @param argv Executable arguments. Cannot be NULL, argv[0] contains the executable filename. * @@ -138,6 +138,22 @@ int xipfs_extended_driver_new_file(const char *full_path, uint32_t size, uint32_ */ int xipfs_extended_driver_execv(const char *full_path, char *const argv[]); +#ifdef XIPFS_ENABLE_SAFE_EXEC_SUPPORT + +/** + * @brief Runs an executable file according to arguments with MPU-based memory isolation. + * + * @param[in] full_path A full path such as `/nvme0p0/my_executable_file` + * + * @param[in] argv Executable arguments. Cannot be NULL, argv[0] contains the executable filename. + * + * @retval Less than 0 on errors. + * @retval 0 on success. + */ +int xipfs_extended_driver_safe_execv(const char *full_path, char *const argv[]); + +#endif /* XIPFS_ENABLE_SAFE_EXEC_SUPPORT */ + #ifdef __cplusplus } #endif diff --git a/sys/shell/cmds/xipfs.c b/sys/shell/cmds/xipfs.c index e4acce9e4625..15d53614ccb7 100644 --- a/sys/shell/cmds/xipfs.c +++ b/sys/shell/cmds/xipfs.c @@ -29,22 +29,52 @@ static char *execute_file_handler_args[XIPFS_EXEC_ARGC_MAX]; +static void print_execute_file_usage(void) { +#ifdef XIPFS_ENABLE_SAFE_EXEC_SUPPORT + printf("Usage : execute [-p] xipfs_executable_filename [arg0] [arg1] ... [arg%d]\n", + (XIPFS_EXEC_ARGC_MAX - 1)); + printf("OPTION:\n"); + printf("\t-p\n"); + printf("\t\tRun the file with Memory Protection Unit.\n"); +#else + printf("Usage : execute xipfs_executable_filename [arg0] [arg1] ... [arg%d]\n", + (XIPFS_EXEC_ARGC_MAX - 1)); +#endif /* XIPFS_ENABLE_SAFE_EXEC_SUPPORT */ +} + static int _execute_file_handler(int argc, char **argv) { + int exe_filename_arg_pos = 1; + const char *exe_filename; + if ( (argc == 1) || (argc > XIPFS_EXEC_ARGC_MAX) ) { - printf("Usage %s xipfs_executable_filename [arg0] [arg1] ... [arg%d]\n", - argv[0], (XIPFS_EXEC_ARGC_MAX - 1)); - printf("\t- xipfs_executable_filename : filename of the desired XIPFS file to execute\n"); + print_execute_file_usage(); return 1; } +#ifdef XIPFS_ENABLE_SAFE_EXEC_SUPPORT + if ((argv[1][0] == '-') && argv[1][1] == 'p') { + exe_filename_arg_pos = 2; + } +#endif + + exe_filename = argv[exe_filename_arg_pos]; + memset(execute_file_handler_args, 0, sizeof(execute_file_handler_args)); - for (int i = 1; i