diff --git a/boards/native/board_init.c b/boards/native/board_init.c index 0bb579c7aa35..5f04fe72ad27 100644 --- a/boards/native/board_init.c +++ b/boards/native/board_init.c @@ -48,36 +48,27 @@ MTD_XFA_ADD(mtd0_dev.base, 0); /* littlefs support */ #if defined(MODULE_LITTLEFS) -VFS_AUTO_MOUNT(littlefs, VFS_MTD(mtd0_dev), VFS_DEFAULT_NVM(0), 0); +VFS_AUTO_MOUNT(littlefs, VFS_MTD(mtd0_dev), CONFIG_NATIVE_MOUNTPOINT, 0); /* littlefs2 support */ #elif defined(MODULE_LITTLEFS2) -VFS_AUTO_MOUNT(littlefs2, VFS_MTD(mtd0_dev), VFS_DEFAULT_NVM(0), 0); +VFS_AUTO_MOUNT(littlefs2, VFS_MTD(mtd0_dev), CONFIG_NATIVE_MOUNTPOINT, 0); /* spiffs support */ #elif defined(MODULE_SPIFFS) -VFS_AUTO_MOUNT(spiffs, VFS_MTD(mtd0_dev), VFS_DEFAULT_NVM(0), 0); +VFS_AUTO_MOUNT(spiffs, VFS_MTD(mtd0_dev), CONFIG_NATIVE_MOUNTPOINT, 0); /* FAT support */ #elif defined(MODULE_FATFS_VFS) -VFS_AUTO_MOUNT(fatfs, VFS_MTD(mtd0_dev), VFS_DEFAULT_NVM(0), 0); +VFS_AUTO_MOUNT(fatfs, VFS_MTD(mtd0_dev), CONFIG_NATIVE_MOUNTPOINT, 0); /* ext2/3/4 support */ #elif defined(MODULE_LWEXT4) -VFS_AUTO_MOUNT(lwext4, VFS_MTD(mtd0_dev), VFS_DEFAULT_NVM(0), 0); +VFS_AUTO_MOUNT(lwext4, VFS_MTD(mtd0_dev), CONFIG_NATIVE_MOUNTPOINT, 0); /* host fs pass-through */ #elif defined(MODULE_FS_NATIVE) -VFS_AUTO_MOUNT(native, { .hostpath = FS_NATIVE_DIR }, VFS_DEFAULT_NVM(0), 0); +VFS_AUTO_MOUNT(native, { .hostpath = FS_NATIVE_DIR }, CONFIG_NATIVE_MOUNTPOINT, 0); #endif #endif /* MODULE_VFS_DEFAULT */ - -/** - * Nothing to initialize at the moment. - * Turns the red LED on and the green LED off. - */ -void board_init(void) -{ - puts("RIOT native board initialized."); -} diff --git a/boards/native/include/board.h b/boards/native/include/board.h index 426d18c9f099..3d51e190edf4 100644 --- a/boards/native/include/board.h +++ b/boards/native/include/board.h @@ -99,6 +99,13 @@ void _native_LED_RED_TOGGLE(void); #endif /** @} */ +/** + * @brief The mountpoint at which the native fs gets mounted + */ +#ifndef CONFIG_NATIVE_MOUNTPOINT +#define CONFIG_NATIVE_MOUNTPOINT VFS_DEFAULT_NVM(0) +#endif + #if defined(MODULE_SPIFFS) || DOXYGEN /** * @name SPIFFS default configuration diff --git a/core/lib/init.c b/core/lib/init.c index e6e4c026a964..a65cfbf6c920 100644 --- a/core/lib/init.c +++ b/core/lib/init.c @@ -43,8 +43,6 @@ RIOT_VERSION ")" #endif -extern int main(void); - static char main_stack[THREAD_STACKSIZE_MAIN]; static char idle_stack[THREAD_STACKSIZE_IDLE]; @@ -60,7 +58,15 @@ static void *main_trampoline(void *arg) LOG_INFO(CONFIG_BOOT_MSG_STRING "\n"); } +#ifdef CPU_NATIVE + extern int _native_argc_main; + extern char **_native_argv_main; + extern int main(int argc, char **argv); + int res = main(_native_argc_main, _native_argv_main); +#else + extern int main(void); int res = main(); +#endif if (IS_USED(MODULE_TEST_UTILS_MAIN_EXIT_CB)) { void test_utils_main_exit_cb(int res); diff --git a/cpu/native/irq_cpu.c b/cpu/native/irq_cpu.c index c5e404115ae4..845fd4d84e18 100644 --- a/cpu/native/irq_cpu.c +++ b/cpu/native/irq_cpu.c @@ -555,6 +555,6 @@ void native_interrupt_init(void) err(EXIT_FAILURE, "native_interrupt_init: sigaction"); } - puts("RIOT native interrupts/signals initialized."); + DEBUG_PUTS("RIOT native interrupts/signals initialized."); } /** @} */ diff --git a/cpu/native/periph/pm.c b/cpu/native/periph/pm.c index a1215163effd..0ce3155415c8 100644 --- a/cpu/native/periph/pm.c +++ b/cpu/native/periph/pm.c @@ -66,7 +66,7 @@ void pm_set(unsigned mode) void pm_off(void) { - puts("\nnative: exiting"); + DEBUG_PUTS("\nnative: exiting"); #ifdef MODULE_PERIPH_SPIDEV_LINUX spidev_linux_teardown(); #endif diff --git a/cpu/native/startup.c b/cpu/native/startup.c index 5ee0515ff40e..0fd4843f477f 100644 --- a/cpu/native/startup.c +++ b/cpu/native/startup.c @@ -466,6 +466,10 @@ static void _reset_handler(void) pm_reboot(); } +/* allow to pass arguments to the application */ +int _native_argc_main; +char **_native_argv_main; + __attribute__((constructor)) static void startup(int argc, char **argv, char **envp) { _native_init_syscalls(); @@ -718,8 +722,12 @@ __attribute__((constructor)) static void startup(int argc, char **argv, char **e netdev_tap_params[taps].tap_name = &argv[optind + i]; netdev_tap_params[taps].wired = true; } + optind += taps; #endif + _native_argc_main = argc - optind; + _native_argv_main = &argv[optind]; + #ifdef MODULE_PERIPH_EEPROM eeprom_native_read(); #endif @@ -729,7 +737,7 @@ __attribute__((constructor)) static void startup(int argc, char **argv, char **e register_interrupt(SIGUSR1, _reset_handler); - puts("RIOT native hardware initialization complete.\n"); + DEBUG_PUTS("RIOT native hardware initialization complete.\n"); irq_enable(); kernel_init(); } diff --git a/examples/riot_as_cli_tool/Makefile b/examples/riot_as_cli_tool/Makefile new file mode 100644 index 000000000000..fde0ec166b2e --- /dev/null +++ b/examples/riot_as_cli_tool/Makefile @@ -0,0 +1,47 @@ +# name of your application +APPLICATION = riot_as_cli_tool + +# If no BOARD is found in the environment, use this default: +BOARD ?= native + +# This has to be the absolute path to the RIOT base directory: +RIOTBASE ?= $(CURDIR)/../.. + +# Include packages that pull up and auto-init the link layer. +USEMODULE += netdev_default +USEMODULE += auto_init_gnrc_netif +# Specify the mandatory networking modules for IPv6 +USEMODULE += gnrc_ipv6_default +# Additional networking modules that can be dropped if not needed +USEMODULE += gnrc_icmpv6_echo + +USEMODULE += auto_init_sock_dns # add fall-back DNS server +USEMODULE += sock_dns # include DNS client +USEMODULE += gnrc_ipv6_nib_dns # include RDNSS option handling + +USEMODULE += nanocoap_sock +USEMODULE += nanocoap_vfs + +USEMODULE += vfs_default + +USEMODULE += shell +USEMODULE += shell_cmds_default + +# export the whole fs to RIOT +CFLAGS += -DFS_NATIVE_DIR=\"/\" +# mount it as '/' in RIOT to match the host +CFLAGS += -DCONFIG_NATIVE_MOUNTPOINT=\"/\" +# use the current working directory as download destination +CFLAGS += -DCONFIG_NCGET_DEFAULT_DATA_DIR=\"/proc/self/cwd/\" + +# terminate RIOT with main() +CFLAGS += -DCONFIG_CORE_EXIT_WITH_MAIN=1 +# don't be verbose +CFLAGS += -DCONFIG_SKIP_BOOT_MSG=1 + +RIOT_TERMINAL = native +BOARD_WHITELIST = native native64 + +DEVELHELP ?= 1 + +include $(RIOTBASE)/Makefile.include diff --git a/examples/riot_as_cli_tool/README.md b/examples/riot_as_cli_tool/README.md new file mode 100644 index 000000000000..9f9dcfef3afe --- /dev/null +++ b/examples/riot_as_cli_tool/README.md @@ -0,0 +1,24 @@ +RIOT as a CLI tool +================== + +This example demonstrates running RIOT as a one-shot CLI tool. +This can be used to easiely integrate RIOT into a CI pipeline for integration tests +with software that is supposed to interact with RIOT. + +This example demonstrates running the nanoCoAP CLI commands from the host system. + +For this the whole host file system is exported to RIOT, we also need to create a +virtual network interface by running + + sudo dist/tools/tapsetup/tapsetup -u eth0 + +where `eth0` is the network interface used for the internet uplink. +If no internet connectivity is needed, this can be left out. + +RIOT commands can be invoked e.g. via + + bin/native/nanocoap_tool.elf tap0 ping riot-os.org + +or via the provided wrapper scripts + + ./ping.sh riot-os.org diff --git a/examples/riot_as_cli_tool/main.c b/examples/riot_as_cli_tool/main.c new file mode 100644 index 000000000000..e88039f6b0cf --- /dev/null +++ b/examples/riot_as_cli_tool/main.c @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2024 ML!PA Consulting GmbH + * + * 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 + * directory for more details. + */ + +/** + * @ingroup examples + * @{ + * + * @file + * @brief Example application to access RIOT shell commands from Linux + * + * @author Benjamin Valentin + * + * @} + */ + +#include +#include "shell.h" +#include "ztimer.h" + +int main(int argc, char **argv) +{ + if (argc < 1) { + printf("usage: [args]\n"); + return 0; + } + + /* argv[0] will be the first 'user' argument to native */ + shell_command_handler_t handler = shell_find_handler(NULL, argv[0]); + if (handler == NULL) { + return -EINVAL; + } + + /* wait some time for the network to be ready */ + ztimer_sleep(ZTIMER_MSEC, 1000); + + msg_t msg_queue[8]; + msg_init_queue(msg_queue, ARRAY_SIZE(msg_queue)); + + return handler(argc, argv); +} diff --git a/examples/riot_as_cli_tool/ncget.sh b/examples/riot_as_cli_tool/ncget.sh new file mode 100755 index 000000000000..10a09316a7d8 --- /dev/null +++ b/examples/riot_as_cli_tool/ncget.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +bin/native/nanocoap_tool.elf tap0 ncget -- $* diff --git a/examples/riot_as_cli_tool/ncput.sh b/examples/riot_as_cli_tool/ncput.sh new file mode 100755 index 000000000000..4242d87208f6 --- /dev/null +++ b/examples/riot_as_cli_tool/ncput.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +bin/native/nanocoap_tool.elf tap0 ncput -- $* diff --git a/examples/riot_as_cli_tool/ping.sh b/examples/riot_as_cli_tool/ping.sh new file mode 100755 index 000000000000..8fd0dacdd96f --- /dev/null +++ b/examples/riot_as_cli_tool/ping.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +bin/native/nanocoap_tool.elf tap0 ping -- $* diff --git a/sys/include/shell.h b/sys/include/shell.h index d8f3bfc53b8d..ac2cb693af6b 100644 --- a/sys/include/shell.h +++ b/sys/include/shell.h @@ -91,7 +91,7 @@ extern "C" { * passing stdin (`isrpipe_t stdin_isrpipe`) does not support backpressure * and overflows silently. As a consequence, commands through such terminals * appear to be truncated at @ref STDIO_RX_BUFSIZE bytes (defaulting to 64) - * unless the command is sent in parts (on many terminals, by presing Ctrl-D + * unless the command is sent in parts (on many terminals, by pressing Ctrl-D * half way through the command). * * For example, this affects systems with direct USB stdio (@ref @@ -225,6 +225,17 @@ static inline void shell_run(const shell_command_t *commands, shell_run_forever(commands, line_buf, len); } +/** + * @brief Searches for a handler function associated with a command + * + * @param[in] command_list ptr to array of command structs + * @param[in] command the command name to search for + * + * @returns handler function associated with @p command + * NULL if the command could not be found + */ +shell_command_handler_t shell_find_handler(const shell_command_t *command_list, + const char *command); /** * @brief Parse and run a line of text as a shell command with * arguments. diff --git a/sys/shell/cmds/nanocoap_vfs.c b/sys/shell/cmds/nanocoap_vfs.c index 592b7b2bd7be..6d1afe75a068 100644 --- a/sys/shell/cmds/nanocoap_vfs.c +++ b/sys/shell/cmds/nanocoap_vfs.c @@ -169,19 +169,34 @@ static int _nanocoap_put_handler(int argc, char **argv) file = argv[1]; url = argv[2]; + /* append filename to remote URL */ if (_is_dir(url)) { const char *basename = strrchr(file, '/'); if (basename == NULL) { - return -EINVAL; + basename = file; + } else { + ++basename; } + if (snprintf(buffer, sizeof(buffer), "%s%s", - url, basename + 1) >= (int)sizeof(buffer)) { + url, basename) >= (int)sizeof(buffer)) { puts("Constructed URI too long"); return -ENOBUFS; } url = buffer; } + /* file path is relative to CONFIG_NCGET_DEFAULT_DATA_DIR */ + if (file[0] != '/') { + if ((unsigned)snprintf(work_buf, sizeof(work_buf), "%s%s", + CONFIG_NCGET_DEFAULT_DATA_DIR, file) >= sizeof(work_buf)) { + puts("Constructed URI too long"); + return -ENOBUFS; + } + file = work_buf; + } + + /* read from 'stdin' / 3rd argument */ if (strcmp(file, "-") == 0) { if (argc < 4) { printf("Usage: %s - \n", argv[0]); diff --git a/sys/shell/shell.c b/sys/shell/shell.c index 90dcaceb4888..7f0759e360df 100644 --- a/sys/shell/shell.c +++ b/sys/shell/shell.c @@ -90,7 +90,7 @@ static enum parse_state escape_toggle(enum parse_state s) } static shell_command_handler_t search_commands(const shell_command_t *entry, - char *command) + const char *command) { for (; entry->name != NULL; entry++) { if (strcmp(entry->name, command) == 0) { @@ -100,7 +100,7 @@ static shell_command_handler_t search_commands(const shell_command_t *entry, return NULL; } -static shell_command_handler_t search_commands_xfa(char *command) +static shell_command_handler_t search_commands_xfa(const char *command) { unsigned n = XFA_LEN(shell_command_t*, shell_commands_xfa); @@ -113,11 +113,13 @@ static shell_command_handler_t search_commands_xfa(char *command) return NULL; } -static shell_command_handler_t find_handler( - const shell_command_t *command_list, char *command) +shell_command_handler_t shell_find_handler(const shell_command_t *command_list, + const char *command) { shell_command_handler_t handler = NULL; + assert(command); + if (command_list != NULL) { handler = search_commands(command_list, command); } @@ -326,7 +328,7 @@ int shell_handle_input_line(const shell_command_t *command_list, char *line) argv[argc] = NULL; /* then we call the appropriate handler */ - shell_command_handler_t handler = find_handler(command_list, argv[0]); + shell_command_handler_t handler = shell_find_handler(command_list, argv[0]); if (handler != NULL) { if (IS_USED(MODULE_SHELL_HOOKS)) { shell_pre_command_hook(argc, argv);