pespin has submitted this change. ( https://gerrit.osmocom.org/c/libosmocore/+/41813?usp=email )
Change subject: Add Emscripten build support and JS callback logging backend ......................................................................
Add Emscripten build support and JS callback logging backend
This change enables building libosmocore for sandboxed, non-POSIX environments, specifically WebAssembly targets produced via the Emscripten toolchain.
The broader motivation is to allow partial execution of selected Osmocom components in isolated runtime environments where direct access to hardware and traditional operating system facilities (filesystem, sockets, privileged execution) is not available.
One intended use case is running a GSM 2G base station where the radio-facing components are executed inside a web environment, while the remaining Osmocom stack and core network components continue to run unchanged on a conventional backend server. In this model, highly stripped-down variants of osmo-bts and osmo-trx are built as static WebAssembly libraries and executed in the browser, while depending on core libraries such as libosmocore, libosmo-netif, and libosmo-abis.
A practical advantage of this approach is that no deployment or privileged setup is required on the radio endpoint side. A user can instantiate a radio endpoint with minimal configuration, while all stateful logic and operational complexity remains centralized on the backend. Multiple such radio endpoints may connect to the same backend core network, effectively forming a single logical network from the core network perspective, independent of the physical location of the radio endpoints.
Existing libosmocore build logic and platform assumptions rely on the availability of POSIX APIs and OS services which are not present in WebAssembly runtimes. This currently prevents libosmocore from being built for such targets without targeted, build-time adjustments. This patch introduces the minimal set of changes required to enable such builds, without affecting native platforms.
As part of this groundwork, a minimal callback-based logging hook is introduced. When building for Emscripten, this hook allows forwarding log messages to an external environment via a user-provided JavaScript callback. This enables integration with browser-side logging or UI infrastructure without introducing new logging backends or runtime dependencies. For all other build targets, the hook resolves to a no-op implementation and does not alter existing logging behavior.
No runtime behavior, protocol semantics, or network interactions are changed by this patch. All modifications are strictly limited to build-time and platform-specific code paths and are only active when targeting Emscripten. Native builds and existing deployment scenarios remain unaffected.
This patch is intended as groundwork. Follow-up changes, proposed separately and incrementally, may extend similar support to other Osmocom components such as libosmo-netif, libosmo-abis, osmo-bts, and osmo-trx, while keeping all such changes optional and isolated from native builds.
Change-Id: Ia8d5f4bb6570b5e055826f3a051e5e5896866e31 --- M .gitignore M configure.ac M include/osmocom/core/logging.h M src/core/Makefile.am M src/core/libosmocore.map A src/core/logging_emscripten.c M src/vty/logging_vty.c 7 files changed, 158 insertions(+), 0 deletions(-)
Approvals: Jenkins Builder: Verified pespin: Looks good to me, but someone else must approve laforge: Looks good to me, approved
diff --git a/.gitignore b/.gitignore index 907743f..91535e8 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ *.la *.pc *.pyc +*.wasm aclocal.m4 acinclude.m4 aminclude.am diff --git a/configure.ac b/configure.ac index 18807f8..b32f304 100644 --- a/configure.ac +++ b/configure.ac @@ -25,6 +25,18 @@ AC_PROG_INSTALL LT_INIT([pic-only disable-static])
+dnl Detect emscripten compiler +case "$CC" in +*emcc*) + emscripten=yes + ;; +*) + emscripten=no + ;; +esac +AM_CONDITIONAL(HAVE_EMSCRIPTEN, test "x$emscripten" = "xyes") +AC_SUBST([HAVE_EMSCRIPTEN], [$emscripten]) + AC_CONFIG_MACRO_DIR([m4])
dnl patching ${archive_cmds} to affect generation of file "libtool" to fix linking with clang @@ -268,6 +280,18 @@ ENABLE_GNUTLS_DEFAULT="no" fi
+if test "x$emscripten" = "xyes" +then + ENABLE_SERIAL_DEFAULT="no" + ENABLE_GNUTLS_DEFAULT="no" + ENABLE_GB_DEFAULT="no" + ENABLE_LIBMNL_DEFAULT="no" + ENABLE_LIBSCTP_DEFAULT="no" + ENABLE_LIBUSB_DEFAULT="no" + ENABLE_PCSC_DEFAULT="no" + ENABLE_URING_DEFAULT="no" +fi + AC_ARG_ENABLE([uring], [AS_HELP_STRING([--disable-uring], [Build without io_uring support])], [ENABLE_URING=$enableval], [ENABLE_URING=$ENABLE_URING_DEFAULT]) AS_IF([test "x$ENABLE_URING" = "xyes"], [ diff --git a/include/osmocom/core/logging.h b/include/osmocom/core/logging.h index 286f646..ac6e553 100644 --- a/include/osmocom/core/logging.h +++ b/include/osmocom/core/logging.h @@ -282,6 +282,7 @@ LOG_TGT_TYPE_STRRB, /*!< osmo_strrb-backed logging */ LOG_TGT_TYPE_GSMTAP, /*!< GSMTAP network logging */ LOG_TGT_TYPE_SYSTEMD, /*!< systemd journal logging */ + LOG_TGT_TYPE_EMSCRIPTEN, /*!< Emscripten logging using JS callback */ };
/*! Whether/how to log the source filename (and line number). */ @@ -451,6 +452,7 @@ bool ofd_wq_mode, bool add_sink); struct log_target *log_target_create_systemd(bool raw); +struct log_target *log_target_create_emscripten(void); void log_target_systemd_set_raw(struct log_target *target, bool raw); int log_target_file_reopen(struct log_target *tgt); int log_target_file_switch_to_stream(struct log_target *tgt); diff --git a/src/core/Makefile.am b/src/core/Makefile.am index 1c0bfed..fd18be8 100644 --- a/src/core/Makefile.am +++ b/src/core/Makefile.am @@ -82,6 +82,10 @@ probes.d \ $(NULL)
+if HAVE_EMSCRIPTEN +libosmocore_la_SOURCES += logging_emscripten.c +endif + if HAVE_SSSE3 libosmocore_la_SOURCES += conv_acc_sse.c if HAVE_SSE4_1 diff --git a/src/core/libosmocore.map b/src/core/libosmocore.map index f6e15f0..5a3c3e8 100644 --- a/src/core/libosmocore.map +++ b/src/core/libosmocore.map @@ -101,6 +101,7 @@ log_target_create_stderr; log_target_create_syslog; log_target_create_systemd; +log_target_create_emscripten; log_target_destroy; log_target_file_reopen; log_target_file_switch_to_stream; diff --git a/src/core/logging_emscripten.c b/src/core/logging_emscripten.c new file mode 100644 index 0000000..a186fd2 --- /dev/null +++ b/src/core/logging_emscripten.c @@ -0,0 +1,79 @@ +/*! \file logging_emscripten.c + * Logging support code using a JS callback. This module sends log + * messages to a JavaScript callback named `on_log` + * with interface on_log(const char *subsys, int level, const char *msg). + * */ +/* + * (C) 2026 by Timur Davydov dtv.comp@gmail.com + * All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/*! \addtogroup logging + * @{ + * \file logging_emscripten.c */ + +#include <stdarg.h> +#include <stdio.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/logging.h> +#include <osmocom/core/logging_internal.h> + +#include <emscripten.h> + +EM_JS(void, on_log_wrapper, (const char *subsys, int level, const char *msg), { + return on_log(subsys, level, msg); +}); + +static void _emscripten_raw_output(struct log_target *target, int subsys, + unsigned int level, const char *file, + int line, int cont, const char *format, + va_list ap) +{ + char msg[MAX_LOG_SIZE]; + const char *subsys_name = log_category_name(subsys); + int rc; + + rc = vsnprintf(msg, sizeof(msg), format, ap); + if (rc <= 0) + return; + if (rc >= sizeof(msg)) + rc = sizeof(msg) - 1; + + /* Drop newline at the end if exists: */ + if (msg[rc - 1] == '\n') + msg[rc - 1] = '\0'; + + on_log_wrapper(subsys_name ? subsys_name : "", level, msg); +} + +/*! Create a new logging target for JS callback logging (uses `on_log`) + * \returns Log target in case of success, NULL in case of error + */ +struct log_target *log_target_create_emscripten(void) +{ + struct log_target *target; + + target = log_target_create(); + if (!target) + return NULL; + + target->type = LOG_TGT_TYPE_EMSCRIPTEN; + target->raw_output = _emscripten_raw_output; + + return target; +} + +/* @} */ diff --git a/src/vty/logging_vty.c b/src/vty/logging_vty.c index 6fd7abc..3fa62a1 100644 --- a/src/vty/logging_vty.c +++ b/src/vty/logging_vty.c @@ -1032,6 +1032,46 @@ RET_WITH_UNLOCK(CMD_SUCCESS); }
+#if defined(__EMSCRIPTEN__) +DEFUN(cfg_log_emscripten, cfg_log_emscripten_cmd, + "log emscripten", + LOG_STR "Logging via EMSCRIPTEN\n") +{ + struct log_target *tgt; + + log_tgt_mutex_lock(); + tgt = log_target_create_emscripten(); + if (!tgt) { + vty_out(vty, "%% Unable to create EMSCRIPTEN log target%s", VTY_NEWLINE); + RET_WITH_UNLOCK(CMD_WARNING); + } + log_add_target(tgt); + + vty->index = tgt; + vty->node = CFG_LOG_NODE; + + RET_WITH_UNLOCK(CMD_SUCCESS); +} + +DEFUN(cfg_no_log_emscripten, cfg_no_log_emscripten_cmd, + "no log emscripten", + NO_STR LOG_STR "Logging via EMSCRIPTEN\n") +{ + struct log_target *tgt; + + log_tgt_mutex_lock(); + tgt = log_target_find(LOG_TGT_TYPE_EMSCRIPTEN, NULL); + if (tgt == NULL) { + vty_out(vty, "%% Unable to find EMSCRIPTEN log target%s", VTY_NEWLINE); + RET_WITH_UNLOCK(CMD_WARNING); + } + + log_target_destroy(tgt); + + RET_WITH_UNLOCK(CMD_SUCCESS); +} +#endif /* defined(__EMSCRIPTEN__) */ + static int config_write_log_single(struct vty *vty, struct log_target *tgt) { char level_buf[128]; @@ -1084,6 +1124,9 @@ tgt->sd_journal.raw ? " raw" : "", VTY_NEWLINE); break; + case LOG_TGT_TYPE_EMSCRIPTEN: + vty_out(vty, "log emscripten%s", VTY_NEWLINE); + break; }
vty_out(vty, " logging filter all %u%s", @@ -1311,4 +1354,8 @@ install_lib_element(CONFIG_NODE, &cfg_no_log_systemd_journal_cmd); install_lib_element(CONFIG_NODE, &cfg_log_gsmtap_cmd); install_lib_element(CONFIG_NODE, &cfg_no_log_gsmtap_cmd); +#if defined(__EMSCRIPTEN__) + install_lib_element(CONFIG_NODE, &cfg_log_emscripten_cmd); + install_lib_element(CONFIG_NODE, &cfg_no_log_emscripten_cmd); +#endif /* defined(__EMSCRIPTEN__) */ }