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 <[email protected]>
+ * 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__) */
 }

--
To view, visit https://gerrit.osmocom.org/c/libosmocore/+/41813?usp=email
To unsubscribe, or for help writing mail filters, visit 
https://gerrit.osmocom.org/settings?usp=email

Gerrit-MessageType: merged
Gerrit-Project: libosmocore
Gerrit-Branch: master
Gerrit-Change-Id: Ia8d5f4bb6570b5e055826f3a051e5e5896866e31
Gerrit-Change-Number: 41813
Gerrit-PatchSet: 27
Gerrit-Owner: Timur Davydov <[email protected]>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: fixeria <[email protected]>
Gerrit-Reviewer: laforge <[email protected]>
Gerrit-Reviewer: neels <[email protected]>
Gerrit-Reviewer: osmith <[email protected]>
Gerrit-Reviewer: pespin <[email protected]>

Reply via email to