summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt525
-rw-r--r--lib/log.c1074
2 files changed, 1351 insertions, 248 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9c2aead..ce907dd 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2,256 +2,285 @@ cmake_minimum_required(VERSION 3.13)
include(CheckFunctionExists)
include(CheckSymbolExists)
-PROJECT(ucode C)
-ADD_DEFINITIONS(-Os -Wall -Werror --std=gnu99 -ffunction-sections -fwrapv -D_GNU_SOURCE)
-
-IF(CMAKE_C_COMPILER_VERSION VERSION_GREATER 6)
- ADD_DEFINITIONS(-Wextra -Werror=implicit-function-declaration)
- ADD_DEFINITIONS(-Wformat -Werror=format-security -Werror=format-nonliteral)
-ENDIF()
-ADD_DEFINITIONS(-Wmissing-declarations -Wno-error=unused-variable -Wno-unused-parameter)
-
-INCLUDE_DIRECTORIES(include)
-
-OPTION(COMPILE_SUPPORT "Support compilation from source" ON)
-
-IF(NOT COMPILE_SUPPORT)
- ADD_DEFINITIONS(-DNO_COMPILE)
-ENDIF()
-
-OPTION(DEBUG_SUPPORT "Debug plugin support" ON)
-OPTION(FS_SUPPORT "Filesystem plugin support" ON)
-OPTION(MATH_SUPPORT "Math plugin support" ON)
-OPTION(UBUS_SUPPORT "Ubus plugin support" ON)
-OPTION(UCI_SUPPORT "UCI plugin support" ON)
-OPTION(RTNL_SUPPORT "Route Netlink plugin support" ${LINUX})
-OPTION(NL80211_SUPPORT "Wireless Netlink plugin support" ${LINUX})
-OPTION(RESOLV_SUPPORT "NS resolve plugin support" ON)
-OPTION(STRUCT_SUPPORT "Struct plugin support" ON)
-OPTION(ULOOP_SUPPORT "Uloop plugin support" ON)
-
-SET(LIB_SEARCH_PATH "${CMAKE_INSTALL_PREFIX}/lib/ucode/*.so:${CMAKE_INSTALL_PREFIX}/share/ucode/*.uc:./*.so:./*.uc" CACHE STRING "Default library search path")
-STRING(REPLACE ":" "\", \"" LIB_SEARCH_DEFINE "${LIB_SEARCH_PATH}")
-ADD_DEFINITIONS(-DLIB_SEARCH_PATH="${LIB_SEARCH_DEFINE}")
-
-IF(APPLE)
- SET(UCODE_MODULE_LINK_OPTIONS "LINKER:-undefined,dynamic_lookup")
- ADD_DEFINITIONS(-DBIND_8_COMPAT)
-ELSE()
- SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "-Wl,--gc-sections")
-ENDIF()
-
-IF(DEBUG)
- ADD_DEFINITIONS(-DDEBUG -g3 -O0)
-ELSE()
- ADD_DEFINITIONS(-DNDEBUG)
-ENDIF()
-
-INCLUDE(FindPkgConfig)
-PKG_CHECK_MODULES(JSONC REQUIRED json-c)
-INCLUDE_DIRECTORIES(${JSONC_INCLUDE_DIRS})
-
-SET(UCODE_SOURCES lexer.c lib.c vm.c chunk.c vallist.c compiler.c source.c types.c program.c platform.c)
-ADD_LIBRARY(libucode SHARED ${UCODE_SOURCES})
-SET(SOVERSION 0 CACHE STRING "Override ucode library version")
-SET_TARGET_PROPERTIES(libucode PROPERTIES OUTPUT_NAME ucode SOVERSION ${SOVERSION})
-TARGET_LINK_LIBRARIES(libucode ${JSONC_LINK_LIBRARIES})
-
-SET(CLI_SOURCES main.c)
-ADD_EXECUTABLE(ucode ${CLI_SOURCES})
-TARGET_LINK_LIBRARIES(ucode libucode ${JSONC_LINK_LIBRARIES})
-
-CHECK_FUNCTION_EXISTS(dlopen DLOPEN_FUNCTION_EXISTS)
-IF (NOT DLOPEN_FUNCTION_EXISTS)
- TARGET_LINK_LIBRARIES(libucode dl)
-ENDIF()
-
-CHECK_FUNCTION_EXISTS(fmod FMOD_FUNCTION_EXISTS)
-IF (NOT FMOD_FUNCTION_EXISTS)
- TARGET_LINK_LIBRARIES(libucode m)
-ENDIF()
-
-SET(CMAKE_REQUIRED_INCLUDES ${JSONC_INCLUDE_DIRS})
-SET(CMAKE_REQUIRED_LIBRARIES ${JSONC_LINK_LIBRARIES})
-CHECK_SYMBOL_EXISTS(json_tokener_get_parse_end "json-c/json.h" HAVE_PARSE_END)
-IF(HAVE_PARSE_END)
- ADD_DEFINITIONS(-DHAVE_PARSE_END)
-ENDIF()
-CHECK_SYMBOL_EXISTS(json_object_new_array_ext "json-c/json.h" HAVE_ARRAY_EXT)
-IF(HAVE_ARRAY_EXT)
- ADD_DEFINITIONS(-DHAVE_ARRAY_EXT)
-ENDIF()
-CHECK_SYMBOL_EXISTS(json_object_new_uint64 "json-c/json.h" HAVE_JSON_UINT64)
-IF(HAVE_JSON_UINT64)
- ADD_DEFINITIONS(-DHAVE_JSON_UINT64)
-ENDIF()
-UNSET(CMAKE_REQUIRED_INCLUDES)
-UNSET(CMAKE_REQUIRED_LIBRARIES)
-
-SET(LIBRARIES "")
-
-IF(DEBUG_SUPPORT)
- SET(LIBRARIES ${LIBRARIES} debug_lib)
- ADD_LIBRARY(debug_lib MODULE lib/debug.c)
- SET_TARGET_PROPERTIES(debug_lib PROPERTIES OUTPUT_NAME debug PREFIX "")
- TARGET_LINK_OPTIONS(debug_lib PRIVATE ${UCODE_MODULE_LINK_OPTIONS})
- FIND_LIBRARY(ubox NAMES ubox)
- IF(ubox)
- FIND_PATH(uloop_include_dir NAMES libubox/uloop.h)
- INCLUDE_DIRECTORIES(${uloop_include_dir})
- TARGET_LINK_LIBRARIES(debug_lib ${ubox} ${libucode})
- SET_TARGET_PROPERTIES(debug_lib PROPERTIES COMPILE_DEFINITIONS HAVE_ULOOP)
- ENDIF()
-ENDIF()
-
-IF(FS_SUPPORT)
- SET(LIBRARIES ${LIBRARIES} fs_lib)
- ADD_LIBRARY(fs_lib MODULE lib/fs.c)
- SET_TARGET_PROPERTIES(fs_lib PROPERTIES OUTPUT_NAME fs PREFIX "")
- TARGET_LINK_OPTIONS(fs_lib PRIVATE ${UCODE_MODULE_LINK_OPTIONS})
-ENDIF()
-
-IF(MATH_SUPPORT)
- SET(LIBRARIES ${LIBRARIES} math_lib)
- ADD_LIBRARY(math_lib MODULE lib/math.c)
- SET_TARGET_PROPERTIES(math_lib PROPERTIES OUTPUT_NAME math PREFIX "")
- TARGET_LINK_OPTIONS(math_lib PRIVATE ${UCODE_MODULE_LINK_OPTIONS})
- CHECK_FUNCTION_EXISTS(ceil CEIL_FUNCTION_EXISTS)
- IF (NOT CEIL_FUNCTION_EXISTS)
- TARGET_LINK_LIBRARIES(math_lib m)
- ENDIF()
-ENDIF()
-
-IF(UBUS_SUPPORT)
- FIND_LIBRARY(ubus NAMES ubus)
- FIND_LIBRARY(blobmsg_json NAMES blobmsg_json)
- FIND_PATH(ubus_include_dir NAMES libubus.h)
- INCLUDE_DIRECTORIES(${ubus_include_dir})
- SET(LIBRARIES ${LIBRARIES} ubus_lib)
- ADD_LIBRARY(ubus_lib MODULE lib/ubus.c)
- SET_TARGET_PROPERTIES(ubus_lib PROPERTIES OUTPUT_NAME ubus PREFIX "")
- TARGET_LINK_OPTIONS(ubus_lib PRIVATE ${UCODE_MODULE_LINK_OPTIONS})
- TARGET_LINK_LIBRARIES(ubus_lib ${ubus} ${blobmsg_json})
- FILE(WRITE "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/test.c" "
+project(ucode C)
+add_definitions(-Os -Wall -Werror --std=gnu99 -ffunction-sections -fwrapv -D_GNU_SOURCE)
+
+if(CMAKE_C_COMPILER_VERSION VERSION_GREATER 6)
+ add_definitions(-Wextra -Werror=implicit-function-declaration)
+ add_definitions(-Wformat -Werror=format-security -Werror=format-nonliteral)
+endif()
+add_definitions(-Wmissing-declarations -Wno-error=unused-variable -Wno-unused-parameter)
+
+include_directories(include)
+
+option(COMPILE_SUPPORT "Support compilation from source" ON)
+
+if(NOT COMPILE_SUPPORT)
+ add_definitions(-DNO_COMPILE)
+endif()
+
+find_library(libuci NAMES uci)
+find_library(libubox NAMES ubox)
+find_library(libubus NAMES ubus)
+find_library(libblobmsg_json NAMES blobmsg_json)
+
+if(LINUX)
+ find_library(libnl_tiny NAMES nl-tiny)
+
+ if(libnl_tiny AND libubox)
+ set(DEFAULT_NL_SUPPORT ON)
+ endif()
+endif()
+
+if(libuci AND libubox)
+ set(DEFAULT_UCI_SUPPORT ON)
+endif()
+
+if(libubus AND libblobmsg_json)
+ set(DEFAULT_UBUS_SUPPORT ON)
+endif()
+
+if(libubox)
+ set(DEFAULT_ULOOP_SUPPORT ON)
+endif()
+
+option(DEBUG_SUPPORT "Debug plugin support" ON)
+option(FS_SUPPORT "Filesystem plugin support" ON)
+option(MATH_SUPPORT "Math plugin support" ON)
+option(UBUS_SUPPORT "Ubus plugin support" ${DEFAULT_UBUS_SUPPORT})
+option(UCI_SUPPORT "UCI plugin support" ${DEFAULT_UCI_SUPPORT})
+option(RTNL_SUPPORT "Route Netlink plugin support" ${DEFAULT_NL_SUPPORT})
+option(NL80211_SUPPORT "Wireless Netlink plugin support" ${DEFAULT_NL_SUPPORT})
+option(RESOLV_SUPPORT "NS resolve plugin support" ON)
+option(STRUCT_SUPPORT "Struct plugin support" ON)
+option(ULOOP_SUPPORT "Uloop plugin support" ${DEFAULT_ULOOP_SUPPORT})
+option(LOG_SUPPORT "Log plugin support" ON)
+
+set(LIB_SEARCH_PATH "${CMAKE_INSTALL_PREFIX}/lib/ucode/*.so:${CMAKE_INSTALL_PREFIX}/share/ucode/*.uc:./*.so:./*.uc" CACHE STRING "Default library search path")
+string(REPLACE ":" "\", \"" LIB_SEARCH_DEFINE "${LIB_SEARCH_PATH}")
+add_definitions(-DLIB_SEARCH_PATH="${LIB_SEARCH_DEFINE}")
+
+if(APPLE)
+ set(UCODE_MODULE_LINK_OPTIONS "LINKER:-undefined,dynamic_lookup")
+ add_definitions(-DBIND_8_COMPAT)
+else()
+ set(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "-Wl,--gc-sections")
+endif()
+
+if(DEBUG)
+ add_definitions(-DDEBUG -g3 -O0)
+else()
+ add_definitions(-DNDEBUG)
+endif()
+
+include(FindPkgConfig)
+pkg_check_modules(JSONC REQUIRED json-c)
+include_directories(${JSONC_INCLUDE_DIRS})
+
+set(UCODE_SOURCES lexer.c lib.c vm.c chunk.c vallist.c compiler.c source.c types.c program.c platform.c)
+add_library(libucode SHARED ${UCODE_SOURCES})
+set(SOVERSION 0 CACHE STRING "Override ucode library version")
+set_target_properties(libucode PROPERTIES OUTPUT_NAME ucode SOVERSION ${SOVERSION})
+target_link_libraries(libucode ${JSONC_LINK_LIBRARIES})
+
+set(CLI_SOURCES main.c)
+add_executable(ucode ${CLI_SOURCES})
+target_link_libraries(ucode libucode ${JSONC_LINK_LIBRARIES})
+
+check_function_exists(dlopen DLOPEN_FUNCTION_EXISTS)
+if(NOT DLOPEN_FUNCTION_EXISTS)
+ target_link_libraries(libucode dl)
+endif()
+
+check_function_exists(fmod FMOD_FUNCTION_EXISTS)
+if(NOT FMOD_FUNCTION_EXISTS)
+ target_link_libraries(libucode m)
+endif()
+
+set(CMAKE_REQUIRED_INCLUDES ${JSONC_INCLUDE_DIRS})
+set(CMAKE_REQUIRED_LIBRARIES ${JSONC_LINK_LIBRARIES})
+check_symbol_exists(json_tokener_get_parse_end "json-c/json.h" HAVE_PARSE_END)
+if(HAVE_PARSE_END)
+ add_definitions(-DHAVE_PARSE_END)
+endif()
+check_symbol_exists(json_object_new_array_ext "json-c/json.h" HAVE_ARRAY_EXT)
+if(HAVE_ARRAY_EXT)
+ add_definitions(-DHAVE_ARRAY_EXT)
+endif()
+check_symbol_exists(json_object_new_uint64 "json-c/json.h" HAVE_JSON_UINT64)
+if(HAVE_JSON_UINT64)
+ add_definitions(-DHAVE_JSON_UINT64)
+endif()
+unset(CMAKE_REQUIRED_INCLUDES)
+unset(CMAKE_REQUIRED_LIBRARIES)
+
+set(LIBRARIES "")
+
+if(DEBUG_SUPPORT)
+ set(LIBRARIES ${LIBRARIES} debug_lib)
+ add_library(debug_lib MODULE lib/debug.c)
+ set_target_properties(debug_lib PROPERTIES OUTPUT_NAME debug PREFIX "")
+ target_link_options(debug_lib PRIVATE ${UCODE_MODULE_LINK_OPTIONS})
+ if(libubox)
+ find_path(uloop_include_dir NAMES libubox/uloop.h)
+ include_directories(${uloop_include_dir})
+ target_link_libraries(debug_lib ${libubox} ${libucode})
+ set_target_properties(debug_lib PROPERTIES COMPILE_DEFINITIONS HAVE_ULOOP)
+ endif()
+endif()
+
+if(FS_SUPPORT)
+ set(LIBRARIES ${LIBRARIES} fs_lib)
+ add_library(fs_lib MODULE lib/fs.c)
+ set_target_properties(fs_lib PROPERTIES OUTPUT_NAME fs PREFIX "")
+ target_link_options(fs_lib PRIVATE ${UCODE_MODULE_LINK_OPTIONS})
+endif()
+
+if(MATH_SUPPORT)
+ set(LIBRARIES ${LIBRARIES} math_lib)
+ add_library(math_lib MODULE lib/math.c)
+ set_target_properties(math_lib PROPERTIES OUTPUT_NAME math PREFIX "")
+ target_link_options(math_lib PRIVATE ${UCODE_MODULE_LINK_OPTIONS})
+ check_function_exists(ceil CEIL_FUNCTION_EXISTS)
+ if(NOT CEIL_FUNCTION_EXISTS)
+ target_link_libraries(math_lib m)
+ endif()
+endif()
+
+if(UBUS_SUPPORT)
+ find_path(ubus_include_dir NAMES libubus.h)
+ include_directories(${ubus_include_dir})
+ set(LIBRARIES ${LIBRARIES} ubus_lib)
+ add_library(ubus_lib MODULE lib/ubus.c)
+ set_target_properties(ubus_lib PROPERTIES OUTPUT_NAME ubus PREFIX "")
+ target_link_options(ubus_lib PRIVATE ${UCODE_MODULE_LINK_OPTIONS})
+ target_link_libraries(ubus_lib ${libubus} ${libblobmsg_json})
+ file(WRITE "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/test.c" "
#include <libubus.h>
int main() { return UBUS_STATUS_NO_MEMORY; }
")
- TRY_COMPILE(HAVE_NEW_UBUS_STATUS_CODES
+ try_compile(HAVE_NEW_UBUS_STATUS_CODES
${CMAKE_BINARY_DIR}
"${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/test.c")
- IF(HAVE_NEW_UBUS_STATUS_CODES)
- ADD_DEFINITIONS(-DHAVE_NEW_UBUS_STATUS_CODES)
- ENDIF()
-ENDIF()
-
-IF(UCI_SUPPORT)
- FIND_LIBRARY(uci NAMES uci)
- FIND_LIBRARY(ubox NAMES ubox)
- FIND_PATH(uci_include_dir uci.h)
- INCLUDE_DIRECTORIES(${uci_include_dir})
- SET(LIBRARIES ${LIBRARIES} uci_lib)
- ADD_LIBRARY(uci_lib MODULE lib/uci.c)
- SET_TARGET_PROPERTIES(uci_lib PROPERTIES OUTPUT_NAME uci PREFIX "")
- TARGET_LINK_OPTIONS(uci_lib PRIVATE ${UCODE_MODULE_LINK_OPTIONS})
- TARGET_LINK_LIBRARIES(uci_lib ${uci} ${ubox})
-ENDIF()
-
-IF(RTNL_SUPPORT)
- FIND_LIBRARY(nl NAMES nl-tiny)
- FIND_LIBRARY(ubox NAMES ubox)
- FIND_PATH(nl_include_dir NAMES netlink/msg.h PATH_SUFFIXES libnl-tiny)
- INCLUDE_DIRECTORIES(${nl_include_dir})
- SET(LIBRARIES ${LIBRARIES} rtnl_lib)
- ADD_LIBRARY(rtnl_lib MODULE lib/rtnl.c)
- SET_TARGET_PROPERTIES(rtnl_lib PROPERTIES OUTPUT_NAME rtnl PREFIX "")
- TARGET_LINK_OPTIONS(rtnl_lib PRIVATE ${UCODE_MODULE_LINK_OPTIONS})
- TARGET_LINK_LIBRARIES(rtnl_lib ${nl} ${ubox})
-ENDIF()
-
-IF(NL80211_SUPPORT)
- FIND_LIBRARY(nl NAMES nl-tiny)
- FIND_LIBRARY(ubox NAMES ubox)
- FIND_PATH(nl_include_dir NAMES netlink/msg.h PATH_SUFFIXES libnl-tiny)
- INCLUDE_DIRECTORIES(${nl_include_dir})
- SET(LIBRARIES ${LIBRARIES} nl80211_lib)
- ADD_LIBRARY(nl80211_lib MODULE lib/nl80211.c)
- SET_TARGET_PROPERTIES(nl80211_lib PROPERTIES OUTPUT_NAME nl80211 PREFIX "")
- TARGET_LINK_OPTIONS(nl80211_lib PRIVATE ${UCODE_MODULE_LINK_OPTIONS})
- TARGET_LINK_LIBRARIES(nl80211_lib ${nl} ${ubox})
-ENDIF()
-
-IF(RESOLV_SUPPORT)
- SET(LIBRARIES ${LIBRARIES} resolv_lib)
- ADD_LIBRARY(resolv_lib MODULE lib/resolv.c)
- SET_TARGET_PROPERTIES(resolv_lib PROPERTIES OUTPUT_NAME resolv PREFIX "")
- TARGET_LINK_OPTIONS(resolv_lib PRIVATE ${UCODE_MODULE_LINK_OPTIONS})
- CHECK_FUNCTION_EXISTS(res_mkquery RES_MKQUERY_FUNCTION_EXISTS)
- CHECK_FUNCTION_EXISTS(ns_initparse NS_INITARSE_FUNCTION_EXISTS)
- CHECK_FUNCTION_EXISTS(clock_gettime CLOCK_GETTIME_FUNCTION_EXISTS)
- IF (NOT RES_MKQUERY_FUNCTION_EXISTS OR NOT NS_INITARSE_FUNCTION_EXISTS)
- TARGET_LINK_LIBRARIES(resolv_lib resolv)
- ENDIF()
- IF (NOT CLOCK_GETTIME_FUNCTION_EXISTS)
- TARGET_LINK_LIBRARIES(resolv_lib rt)
- ENDIF()
-ENDIF()
-
-IF(STRUCT_SUPPORT)
- SET(LIBRARIES ${LIBRARIES} struct_lib)
- ADD_LIBRARY(struct_lib MODULE lib/struct.c)
- SET_TARGET_PROPERTIES(struct_lib PROPERTIES OUTPUT_NAME struct PREFIX "")
- TARGET_LINK_OPTIONS(struct_lib PRIVATE ${UCODE_MODULE_LINK_OPTIONS})
- CHECK_FUNCTION_EXISTS(frexp FREXP_FUNCTION_EXISTS)
- IF (NOT FREXP_FUNCTION_EXISTS)
- TARGET_LINK_LIBRARIES(struct_lib m)
- ENDIF()
-ENDIF()
-
-IF(ULOOP_SUPPORT)
- FIND_LIBRARY(ubox NAMES ubox)
- FIND_PATH(uloop_include_dir NAMES libubox/uloop.h)
- INCLUDE_DIRECTORIES(${uloop_include_dir})
- SET(LIBRARIES ${LIBRARIES} uloop_lib)
- ADD_LIBRARY(uloop_lib MODULE lib/uloop.c)
- SET_TARGET_PROPERTIES(uloop_lib PROPERTIES OUTPUT_NAME uloop PREFIX "")
- TARGET_LINK_OPTIONS(uloop_lib PRIVATE ${UCODE_MODULE_LINK_OPTIONS})
- SET(CMAKE_REQUIRED_LIBRARIES ${ubox})
- CHECK_FUNCTION_EXISTS(uloop_timeout_remaining64 REMAINING64_FUNCTION_EXISTS)
- UNSET(CMAKE_REQUIRED_LIBRARIES)
- IF (REMAINING64_FUNCTION_EXISTS)
- TARGET_COMPILE_DEFINITIONS(uloop_lib PUBLIC HAVE_ULOOP_TIMEOUT_REMAINING64)
- ENDIF()
- TARGET_LINK_LIBRARIES(uloop_lib ${ubox})
-ENDIF()
-
-IF(UNIT_TESTING)
- ENABLE_TESTING()
- ADD_DEFINITIONS(-DUNIT_TESTING)
- ADD_SUBDIRECTORY(tests)
- LIST(APPEND CMAKE_CTEST_ARGUMENTS "--output-on-failure")
-
- IF(CMAKE_C_COMPILER_ID STREQUAL "Clang")
- ADD_EXECUTABLE(ucode-san ${CLI_SOURCES} ${UCODE_SOURCES})
- SET_PROPERTY(TARGET ucode-san PROPERTY ENABLE_EXPORTS 1)
- TARGET_LINK_LIBRARIES(ucode-san ${JSONC_LINK_LIBRARIES})
- TARGET_COMPILE_OPTIONS(ucode-san PRIVATE -g -fno-omit-frame-pointer -fsanitize=undefined,address,leak -fno-sanitize-recover=all)
- TARGET_LINK_OPTIONS(ucode-san PRIVATE -fsanitize=undefined,address,leak)
- ENDIF()
-ENDIF()
-
-INSTALL(TARGETS ucode RUNTIME DESTINATION bin)
-INSTALL(TARGETS libucode LIBRARY DESTINATION lib)
-INSTALL(TARGETS ${LIBRARIES} LIBRARY DESTINATION lib/ucode)
-
-ADD_CUSTOM_TARGET(utpl ALL COMMAND ${CMAKE_COMMAND} -E create_symlink ucode utpl)
-INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/utpl DESTINATION bin)
-
-IF(COMPILE_SUPPORT)
- ADD_CUSTOM_TARGET(ucc ALL COMMAND ${CMAKE_COMMAND} -E create_symlink ucode ucc)
- INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/ucc DESTINATION bin)
-ENDIF()
-
-FILE(GLOB UCODE_HEADERS "include/ucode/*.h")
-INSTALL(FILES ${UCODE_HEADERS} DESTINATION include/ucode)
-
-ADD_SUBDIRECTORY(examples)
+ if(HAVE_NEW_UBUS_STATUS_CODES)
+ add_definitions(-DHAVE_NEW_UBUS_STATUS_CODES)
+ endif()
+endif()
+
+if(UCI_SUPPORT)
+ find_path(uci_include_dir uci.h)
+ include_directories(${uci_include_dir})
+ set(LIBRARIES ${LIBRARIES} uci_lib)
+ add_library(uci_lib MODULE lib/uci.c)
+ set_target_properties(uci_lib PROPERTIES OUTPUT_NAME uci PREFIX "")
+ target_link_options(uci_lib PRIVATE ${UCODE_MODULE_LINK_OPTIONS})
+ target_link_libraries(uci_lib ${libuci} ${libubox})
+endif()
+
+if(RTNL_SUPPORT)
+ find_path(nl_include_dir NAMES netlink/msg.h PATH_SUFFIXES libnl-tiny)
+ include_directories(${nl_include_dir})
+ set(LIBRARIES ${LIBRARIES} rtnl_lib)
+ add_library(rtnl_lib MODULE lib/rtnl.c)
+ set_target_properties(rtnl_lib PROPERTIES OUTPUT_NAME rtnl PREFIX "")
+ target_link_options(rtnl_lib PRIVATE ${UCODE_MODULE_LINK_OPTIONS})
+ target_link_libraries(rtnl_lib ${libnl_tiny} ${libubox})
+endif()
+
+if(NL80211_SUPPORT)
+ find_path(nl_include_dir NAMES netlink/msg.h PATH_SUFFIXES libnl-tiny)
+ include_directories(${nl_include_dir})
+ set(LIBRARIES ${LIBRARIES} nl80211_lib)
+ add_library(nl80211_lib MODULE lib/nl80211.c)
+ set_target_properties(nl80211_lib PROPERTIES OUTPUT_NAME nl80211 PREFIX "")
+ target_link_options(nl80211_lib PRIVATE ${UCODE_MODULE_LINK_OPTIONS})
+ target_link_libraries(nl80211_lib ${libnl_tiny} ${libubox})
+endif()
+
+if(RESOLV_SUPPORT)
+ set(LIBRARIES ${LIBRARIES} resolv_lib)
+ add_library(resolv_lib MODULE lib/resolv.c)
+ set_target_properties(resolv_lib PROPERTIES OUTPUT_NAME resolv PREFIX "")
+ target_link_options(resolv_lib PRIVATE ${UCODE_MODULE_LINK_OPTIONS})
+ check_function_exists(res_mkquery RES_MKQUERY_FUNCTION_EXISTS)
+ check_function_exists(ns_initparse NS_INITARSE_FUNCTION_EXISTS)
+ check_function_exists(clock_gettime CLOCK_GETTIME_FUNCTION_EXISTS)
+ if(NOT RES_MKQUERY_FUNCTION_EXISTS OR NOT NS_INITARSE_FUNCTION_EXISTS)
+ target_link_libraries(resolv_lib resolv)
+ endif()
+ if(NOT CLOCK_GETTIME_FUNCTION_EXISTS)
+ target_link_libraries(resolv_lib rt)
+ endif()
+endif()
+
+if(STRUCT_SUPPORT)
+ set(LIBRARIES ${LIBRARIES} struct_lib)
+ add_library(struct_lib MODULE lib/struct.c)
+ set_target_properties(struct_lib PROPERTIES OUTPUT_NAME struct PREFIX "")
+ target_link_options(struct_lib PRIVATE ${UCODE_MODULE_LINK_OPTIONS})
+ check_function_exists(frexp FREXP_FUNCTION_EXISTS)
+ if(NOT FREXP_FUNCTION_EXISTS)
+ target_link_libraries(struct_lib m)
+ endif()
+endif()
+
+if(ULOOP_SUPPORT)
+ find_path(uloop_include_dir NAMES libubox/uloop.h)
+ include_directories(${uloop_include_dir})
+ set(LIBRARIES ${LIBRARIES} uloop_lib)
+ add_library(uloop_lib MODULE lib/uloop.c)
+ set_target_properties(uloop_lib PROPERTIES OUTPUT_NAME uloop PREFIX "")
+ target_link_options(uloop_lib PRIVATE ${UCODE_MODULE_LINK_OPTIONS})
+ set(CMAKE_REQUIRED_LIBRARIES ${libubox})
+ check_function_exists(uloop_timeout_remaining64 REMAINING64_FUNCTION_EXISTS)
+ unset(CMAKE_REQUIRED_LIBRARIES)
+ if(REMAINING64_FUNCTION_EXISTS)
+ target_compile_definitions(uloop_lib PUBLIC HAVE_ULOOP_TIMEOUT_REMAINING64)
+ endif()
+ target_link_libraries(uloop_lib ${libubox})
+endif()
+
+if(LOG_SUPPORT)
+ set(LIBRARIES ${LIBRARIES} log_lib)
+ add_library(log_lib MODULE lib/log.c)
+ set_target_properties(log_lib PROPERTIES OUTPUT_NAME log PREFIX "")
+ target_link_options(log_lib PRIVATE ${UCODE_MODULE_LINK_OPTIONS})
+ if(libubox)
+ find_path(ulog_include_dir NAMES libubox/ulog.h)
+ include_directories(${ulog_include_dir})
+ target_link_libraries(log_lib ${libubox})
+ set_target_properties(log_lib PROPERTIES COMPILE_DEFINITIONS HAVE_ULOG)
+ endif()
+endif()
+
+if(UNIT_TESTING)
+ enable_testing()
+ add_definitions(-DUNIT_TESTING)
+ add_subdirectory(tests)
+ list(APPEND CMAKE_CTEST_ARGUMENTS "--output-on-failure")
+
+ if(CMAKE_C_COMPILER_ID STREQUAL "Clang")
+ add_executable(ucode-san ${CLI_SOURCES} ${UCODE_SOURCES})
+ set_property(TARGET ucode-san PROPERTY ENABLE_EXPORTS 1)
+ target_link_libraries(ucode-san ${JSONC_LINK_LIBRARIES})
+ target_compile_options(ucode-san PRIVATE -g -fno-omit-frame-pointer -fsanitize=undefined,address,leak -fno-sanitize-recover=all)
+ target_link_options(ucode-san PRIVATE -fsanitize=undefined,address,leak)
+ endif()
+endif()
+
+install(TARGETS ucode RUNTIME DESTINATION bin)
+install(TARGETS libucode LIBRARY DESTINATION lib)
+install(TARGETS ${LIBRARIES} LIBRARY DESTINATION lib/ucode)
+
+add_custom_target(utpl ALL COMMAND ${CMAKE_COMMAND} -E create_symlink ucode utpl)
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/utpl DESTINATION bin)
+
+if(COMPILE_SUPPORT)
+ add_custom_target(ucc ALL COMMAND ${CMAKE_COMMAND} -E create_symlink ucode ucc)
+ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/ucc DESTINATION bin)
+endif()
+
+file(GLOB UCODE_HEADERS "include/ucode/*.h")
+install(FILES ${UCODE_HEADERS} DESTINATION include/ucode)
+
+add_subdirectory(examples)
diff --git a/lib/log.c b/lib/log.c
new file mode 100644
index 0000000..8cd196d
--- /dev/null
+++ b/lib/log.c
@@ -0,0 +1,1074 @@
+/*
+ * Copyright (C) 2023 Jo-Philipp Wich <jo@mein.io>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/**
+ * # System logging functions
+ *
+ * The `log` module provides bindings to the POSIX syslog functions `openlog()`,
+ * `syslog()` and `closelog()` as well as - when available - the OpenWrt
+ * specific ulog library functions.
+ *
+ * Functions can be individually imported and directly accessed using the
+ * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#named_import named import}
+ * syntax:
+ *
+ * ```
+ * import { openlog, syslog, LOG_PID, LOG_USER, LOG_ERR } from 'log';
+ *
+ * openlog("my-log-ident", LOG_PID, LOG_USER);
+ * syslog(LOG_ERR, "An error occurred!");
+ *
+ * // OpenWrt specific ulog functions
+ * import { ulog_open, ulog, ULOG_SYSLOG, LOG_DAEMON, LOG_INFO } from 'log';
+ *
+ * ulog_open(ULOG_SYSLOG, LOG_DAEMON, "my-log-ident");
+ * ulog(LOG_INFO, "The current epoch is %d", time());
+ * ```
+ *
+ * Alternatively, the module namespace can be imported
+ * using a wildcard import statement:
+ *
+ * ```
+ * import * as log from 'log';
+ *
+ * log.openlog("my-log-ident", log.LOG_PID, log.LOG_USER);
+ * log.syslog(log.LOG_ERR, "An error occurred!");
+ *
+ * // OpenWrt specific ulog functions
+ * log.ulog_open(log.ULOG_SYSLOG, log.LOG_DAEMON, "my-log-ident");
+ * log.ulog(log.LOG_INFO, "The current epoch is %d", time());
+ * ```
+ *
+ * Additionally, the log module namespace may also be imported by invoking the
+ * `ucode` interpreter with the `-llog` switch.
+ *
+ * ## Constants
+ *
+ * The `log` module declares a number of numeric constants to specify logging
+ * facility, priority and option values, as well as ulog specific channels.
+ *
+ * ### Syslog Options
+ *
+ * | Constant Name | Description |
+ * |---------------|---------------------------------------------------------|
+ * | `LOG_PID` | Include PID with each message. |
+ * | `LOG_CONS` | Log to console if error occurs while sending to syslog. |
+ * | `LOG_NDELAY` | Open the connection to the logger immediately. |
+ * | `LOG_ODELAY` | Delay open until the first message is logged. |
+ * | `LOG_NOWAIT` | Do not wait for child processes created during logging. |
+ *
+ * ### Syslog Facilities
+ *
+ * | Constant Name | Description |
+ * |----------------|--------------------------------------------------|
+ * | `LOG_AUTH` | Authentication/authorization messages. |
+ * | `LOG_AUTHPRIV` | Private authentication messages. |
+ * | `LOG_CRON` | Clock daemon (cron and at commands). |
+ * | `LOG_DAEMON` | System daemons without separate facility values. |
+ * | `LOG_FTP` | FTP server daemon. |
+ * | `LOG_KERN` | Kernel messages. |
+ * | `LOG_LPR` | Line printer subsystem. |
+ * | `LOG_MAIL` | Mail system. |
+ * | `LOG_NEWS` | Network news subsystem. |
+ * | `LOG_SYSLOG` | Messages generated internally by syslogd. |
+ * | `LOG_USER` | Generic user-level messages. |
+ * | `LOG_UUCP` | UUCP subsystem. |
+ * | `LOG_LOCAL0` | Local use 0 (custom facility). |
+ * | `LOG_LOCAL1` | Local use 1 (custom facility). |
+ * | `LOG_LOCAL2` | Local use 2 (custom facility). |
+ * | `LOG_LOCAL3` | Local use 3 (custom facility). |
+ * | `LOG_LOCAL4` | Local use 4 (custom facility). |
+ * | `LOG_LOCAL5` | Local use 5 (custom facility). |
+ * | `LOG_LOCAL6` | Local use 6 (custom facility). |
+ * | `LOG_LOCAL7` | Local use 7 (custom facility). |
+ *
+ * ### Syslog Priorities
+ *
+ * | Constant Name | Description |
+ * |---------------|-------------------------------------|
+ * | `LOG_EMERG` | System is unusable. |
+ * | `LOG_ALERT` | Action must be taken immediately. |
+ * | `LOG_CRIT` | Critical conditions. |
+ * | `LOG_ERR` | Error conditions. |
+ * | `LOG_WARNING` | Warning conditions. |
+ * | `LOG_NOTICE` | Normal, but significant, condition. |
+ * | `LOG_INFO` | Informational message. |
+ * | `LOG_DEBUG` | Debug-level message. |
+ *
+ * ### Ulog channels
+ *
+ * | Constant Name | Description |
+ * |---------------|--------------------------------------|
+ * | `ULOG_KMSG` | Log messages to `/dev/kmsg` (dmesg). |
+ * | `ULOG_STDIO` | Log messages to stdout. |
+ * | `ULOG_SYSLOG` | Log messages to syslog. |
+ *
+ * @module log
+ */
+
+#include <syslog.h>
+#include <errno.h>
+
+#ifdef HAVE_ULOG
+#include <libubox/ulog.h>
+#endif
+
+#include "ucode/module.h"
+
+
+static char log_ident[32];
+
+/**
+ * The following log option strings are recognized:
+ *
+ * | Log Option | Description |
+ * |------------|------------------------------------------------------------|
+ * | `"pid"` | Include PID with each message. |
+ * | `"cons"` | Log to console if an error occurs while sending to syslog. |
+ * | `"ndelay"` | Open the connection to the logger immediately. |
+ * | `"odelay"` | Delay open until the first message is logged. |
+ * | `"nowait"` | Do not wait for child processes created during logging. |
+ *
+ * @typedef {string} module:log.LogOption
+ * @enum {module:log.LogOption}
+ *
+ */
+static const struct { const char *name; int value; } log_options[] = {
+ { "pid", LOG_PID },
+ { "cons", LOG_CONS },
+ { "ndelay", LOG_NDELAY },
+ { "odelay", LOG_ODELAY },
+ { "nowait", LOG_NOWAIT },
+};
+
+/**
+ * The following log facility strings are recognized:
+ *
+ * | Facility | Description |
+ * |--------------|--------------------------------------------------|
+ * | `"auth"` | Authentication/authorization messages. |
+ * | `"authpriv"` | Private authentication messages. |
+ * | `"cron"` | Clock daemon (cron and at commands). |
+ * | `"daemon"` | System daemons without separate facility values. |
+ * | `"ftp"` | FTP server daemon. |
+ * | `"kern"` | Kernel messages. |
+ * | `"lpr"` | Line printer subsystem. |
+ * | `"mail"` | Mail system. |
+ * | `"news"` | Network news subsystem. |
+ * | `"syslog"` | Messages generated internally by syslogd. |
+ * | `"user"` | Generic user-level messages. |
+ * | `"uucp"` | UUCP subsystem. |
+ * | `"local0"` | Local use 0 (custom facility). |
+ * | `"local1"` | Local use 1 (custom facility). |
+ * | `"local2"` | Local use 2 (custom facility). |
+ * | `"local3"` | Local use 3 (custom facility). |
+ * | `"local4"` | Local use 4 (custom facility). |
+ * | `"local5"` | Local use 5 (custom facility). |
+ * | `"local6"` | Local use 6 (custom facility). |
+ * | `"local7"` | Local use 7 (custom facility). |
+ *
+ * @typedef {string} module:log.LogFacility
+ * @enum {module:log.LogFacility}
+ */
+static const struct { const char *name; int value; } log_facilities[] = {
+ { "auth", LOG_AUTH },
+#ifdef LOG_AUTHPRIV
+ { "authpriv", LOG_AUTHPRIV },
+#endif
+ { "cron", LOG_CRON },
+ { "daemon", LOG_DAEMON },
+#ifdef LOG_FTP
+ { "ftp", LOG_FTP },
+#endif
+ { "kern", LOG_KERN },
+ { "lpr", LOG_LPR },
+ { "mail", LOG_MAIL },
+ { "news", LOG_NEWS },
+ { "syslog", LOG_SYSLOG },
+ { "user", LOG_USER },
+ { "uucp", LOG_UUCP },
+ { "local0", LOG_LOCAL0 },
+ { "local1", LOG_LOCAL1 },
+ { "local2", LOG_LOCAL2 },
+ { "local3", LOG_LOCAL3 },
+ { "local4", LOG_LOCAL4 },
+ { "local5", LOG_LOCAL5 },
+ { "local6", LOG_LOCAL6 },
+ { "local7", LOG_LOCAL7 },
+};
+
+/**
+ * The following log priority strings are recognized:
+ *
+ * | Priority | Description |
+ * |-------------|-------------------------------------|
+ * | `"emerg"` | System is unusable. |
+ * | `"alert"` | Action must be taken immediately. |
+ * | `"crit"` | Critical conditions. |
+ * | `"err"` | Error conditions. |
+ * | `"warning"` | Warning conditions. |
+ * | `"notice"` | Normal, but significant, condition. |
+ * | `"info"` | Informational message. |
+ * | `"debug"` | Debug-level message. |
+ *
+ * @typedef {string} module:log.LogPriority
+ * @enum {module:log.LogPriority}
+ */
+static const struct { const char *name; int value; } log_priorities[] = {
+ { "emerg", LOG_EMERG },
+ { "alert", LOG_ALERT },
+ { "crit", LOG_CRIT },
+ { "err", LOG_ERR },
+ { "warning", LOG_WARNING },
+ { "notice", LOG_NOTICE },
+ { "info", LOG_INFO },
+ { "debug", LOG_DEBUG },
+};
+
+
+static int
+parse_facility(uc_value_t *facility)
+{
+ char *s;
+ int rv;
+
+ switch (ucv_type(facility)) {
+ case UC_STRING:
+ s = ucv_string_get(facility);
+
+ for (size_t i = 0; i < ARRAY_SIZE(log_facilities); i++)
+ if (s && !strcasecmp(s, log_facilities[i].name))
+ return log_facilities[i].value;
+
+ return -1;
+
+ case UC_INTEGER:
+ rv = ucv_int64_get(facility);
+
+ if (errno == ERANGE || rv < 0)
+ return -1;
+
+ return rv;
+
+ case UC_NULL:
+ return 0;
+
+ default:
+ return -1;
+ }
+}
+
+static int
+parse_options(uc_value_t *option)
+{
+ char *s;
+ int rv;
+
+ switch (ucv_type(option)) {
+ case UC_ARRAY:
+ rv = 0;
+
+ for (size_t i = 0; i < ucv_array_length(option); i++) {
+ uc_value_t *opt = ucv_array_get(option, i);
+ char *s = ucv_string_get(opt);
+
+ for (size_t j = 0; j < ARRAY_SIZE(log_options); j++) {
+ if (s && !strcasecmp(log_options[j].name, s))
+ rv |= log_options[j].value;
+ else
+ return -1;
+ }
+ }
+
+ return rv;
+
+ case UC_STRING:
+ s = ucv_string_get(option);
+
+ for (size_t i = 0; i < ARRAY_SIZE(log_options); i++)
+ if (s && !strcasecmp(s, log_options[i].name))
+ return log_options[i].value;
+
+ return -1;
+
+ case UC_INTEGER:
+ rv = ucv_int64_get(option);
+
+ if (errno == ERANGE || rv < 0)
+ return -1;
+
+ return rv;
+
+ case UC_NULL:
+ return 0;
+
+ default:
+ return -1;
+ }
+}
+
+static int
+parse_priority(uc_value_t *priority)
+{
+ char *s;
+ int rv;
+
+ switch (ucv_type(priority)) {
+ case UC_STRING:
+ s = ucv_string_get(priority);
+
+ for (size_t i = 0; i < ARRAY_SIZE(log_priorities); i++)
+ if (s && !strcasecmp(s, log_priorities[i].name))
+ return log_priorities[i].value;
+
+ return -1;
+
+ case UC_INTEGER:
+ rv = ucv_int64_get(priority);
+
+ if (errno == ERANGE || rv < 0)
+ return -1;
+
+ return rv;
+
+ case UC_NULL:
+ return LOG_INFO;
+
+ default:
+ return -1;
+ }
+}
+
+static char *
+parse_ident(uc_vm_t *vm, uc_value_t *ident)
+{
+ if (!ident)
+ return NULL;
+
+ char *s = ucv_to_string(vm, ident);
+
+ snprintf(log_ident, sizeof(log_ident), "%s", s ? s : "");
+ free(s);
+
+ return log_ident[0] ? log_ident : NULL;
+}
+
+/**
+ * Open connection to system logger.
+ *
+ * The `openlog()` function instructs the program to establish a connection to
+ * the system log service and configures the default facility and identification
+ * for use in subsequent log operations. It may be omitted, in which case the
+ * first call to `syslog()` will implicitly call `openlog()` with a default
+ * ident value representing the program name and a default `LOG_USER` facility.
+ *
+ * The log option argument may be either a single string value containing an
+ * option name, an array of option name strings or a numeric value representing
+ * a bitmask of `LOG_*` option constants.
+ *
+ * The facility argument may be either a single string value containing a
+ * facility name or one of the numeric `LOG_*` facility constants in the module
+ * namespace.
+ *
+ * Returns `true` if the system `openlog()` function was invoked.
+ *
+ * Returns `false` if an invalid argument, such as an unrecognized option or
+ * facility name, was provided.
+ *
+ * @function module:log#openlog
+ *
+ * @param {string} [ident]
+ * A string identifying the program name. If omitted, the name of the calling
+ * process is used by default.
+ *
+ * @param {number|module:log.LogOption|module:log.LogOption[]} [options]
+ * Logging options to use.
+ *
+ * See {@link module:log.LogOption|LogOption} for recognized option names.
+ *
+ * @param {number|module:log.LogFacility} [facility="user"]
+ * The facility to use for log messages generated by subsequent syslog calls.
+ *
+ * See {@link module:log.LogFacility|LogFacility} for recognized facility names.
+ *
+ * @returns {boolean}
+ *
+ * @example
+ * // Example usage of openlog function
+ * openlog("myapp", LOG_PID | LOG_NDELAY, LOG_LOCAL0);
+ *
+ * // Using option names instead of bitmask and LOG_USER facility
+ * openlog("myapp", [ "pid", "ndelay" ], "user");
+ */
+static uc_value_t *
+uc_openlog(uc_vm_t *vm, size_t nargs)
+{
+ char *ident = parse_ident(vm, uc_fn_arg(0));
+ int options = parse_options(uc_fn_arg(1));
+ int facility = parse_facility(uc_fn_arg(2));
+
+ if (options == -1 || facility == -1)
+ return ucv_boolean_new(false);
+
+ openlog(ident, options, facility);
+
+ return ucv_boolean_new(true);
+}
+
+/**
+ * Log a message to the system logger.
+ *
+ * This function logs a message to the system logger. The function behaves in a
+ * sprintf-like manner, allowing the use of format strings and associated
+ * arguments to construct log messages.
+ *
+ * If the `openlog` function has not been called explicitly before, `syslog()`
+ * implicitly calls `openlog()`, using a default ident and `LOG_USER` facility
+ * value before logging the message.
+ *
+ * If the `format` argument is not a string and not `null`, it will be
+ * implicitly converted to a string and logged as-is, without further format
+ * string processing.
+ *
+ * Returns `true` if a message was passed to the system `syslog()` function.
+ *
+ * Returns `false` if an invalid priority value or an empty message was given.
+ *
+ * @function module:log#syslog
+ *
+ * @param {number|module:log.LogPriority} priority
+ * Log message priority. May be either a number value (potentially bitwise OR-ed
+ * with a log facility constant) which is passed as-is to the system `syslog()`
+ * function or a priority name string.
+ *
+ * See {@link module:log.LogPriority|LogPriority} for recognized priority names.
+ *
+ * @param {*} format
+ * The sprintf-like format string for the log message, or any other, non-null,
+ * non-string value type which will be implicitly stringified and logged as-is.
+ *
+ * @param {...*} [args]
+ * In case a format string value was provided in the previous argument, then
+ * all subsequent arguments are used to replace the placeholders in the format
+ * string.
+ *
+ * @returns {boolean}
+ *
+ * @example
+ * // Example usage of syslog function with format string and arguments
+ * const username = "user123";
+ * const errorCode = 404;
+ * syslog(LOG_ERR, "User %s encountered error: %d", username, errorCode);
+ *
+ * // If openlog has not been called explicitly, it is implicitly called with defaults:
+ * syslog(LOG_INFO, "This message will be logged with default settings.");
+ *
+ * // Selectively override used facility by OR-ing numeric constant
+ * const password =" secret";
+ * syslog(LOG_DEBUG|LOG_AUTHPRIV, "The password %s has been wrong", secret);
+ *
+ * // Using priority names for logging
+ * syslog("emerg", "System shutdown imminent!");
+ *
+ * // Implicit stringification
+ * syslog("debug", { foo: 1, bar: true, baz: [1, 2, 3] });
+ */
+static uc_value_t *
+uc_syslog(uc_vm_t *vm, size_t nargs)
+{
+ int priority = parse_priority(uc_fn_arg(0));
+
+ if (priority == -1 || nargs < 2)
+ return ucv_boolean_new(false);
+
+ uc_value_t *fmt = uc_fn_arg(1), *msg;
+ uc_cfn_ptr_t fmtfn;
+ char *s;
+
+ switch (ucv_type(fmt)) {
+ case UC_STRING:
+ fmtfn = uc_stdlib_function("sprintf");
+ msg = fmtfn(vm, nargs - 1);
+
+ if (msg) {
+ syslog(priority, "%s", ucv_string_get(msg));
+ ucv_put(msg);
+
+ return ucv_boolean_new(true);
+ }
+
+ break;
+
+ case UC_NULL:
+ break;
+
+ default:
+ s = ucv_to_string(vm, fmt);
+
+ if (s) {
+ syslog(priority, "%s", s);
+ free(s);
+
+ return ucv_boolean_new(true);
+ }
+
+ break;
+ }
+
+ return ucv_boolean_new(false);
+}
+
+/**
+ * Close connection to system logger.
+ *
+ * The usage of this function is optional, and usually an explicit log
+ * connection tear down is not required.
+ *
+ * @function module:log#closelog
+ */
+static uc_value_t *
+uc_closelog(uc_vm_t *vm, size_t nargs)
+{
+ closelog();
+
+ return NULL;
+}
+
+
+#ifdef HAVE_ULOG
+/**
+ * The following ulog channel strings are recognized:
+ *
+ * | Channel | Description |
+ * |------------|---------------------------------------------------|
+ * | `"kmsg"` | Log to `/dev/kmsg`, log messages appear in dmesg. |
+ * | `"syslog"` | Use standard `syslog()` mechanism. |
+ * | `"stdio"` | Use stderr for log output. |
+ *
+ * @typedef {string} module:log.UlogChannel
+ * @enum {module:log.UlogChannel}
+ */
+static const struct { const char *name; int value; } ulog_channels[] = {
+ { "kmsg", ULOG_KMSG },
+ { "syslog", ULOG_SYSLOG },
+ { "stdio", ULOG_STDIO },
+};
+
+static int
+parse_channels(uc_value_t *channels)
+{
+ char *s;
+ int rv;
+
+ switch (ucv_type(channels)) {
+ case UC_ARRAY:
+ rv = 0;
+
+ for (size_t i = 0; i < ucv_array_length(channels); i++) {
+ uc_value_t *channel = ucv_array_get(channels, i);
+ char *s = ucv_string_get(channel);
+
+ for (size_t j = 0; j < ARRAY_SIZE(ulog_channels); j++) {
+ if (s && !strcasecmp(s, ulog_channels[j].name))
+ rv |= ulog_channels[j].value;
+ else
+ return -1;
+ }
+ }
+
+ return rv;
+
+ case UC_STRING:
+ s = ucv_string_get(channels);
+
+ for (size_t i = 0; i < ARRAY_SIZE(ulog_channels); i++)
+ if (s && !strcasecmp(s, ulog_channels[i].name))
+ return ulog_channels[i].value;
+
+ return -1;
+
+ case UC_INTEGER:
+ rv = ucv_uint64_get(channels);
+
+ if (errno == ERANGE)
+ return -1;
+
+ return rv & (ULOG_KMSG|ULOG_STDIO|ULOG_SYSLOG);
+
+ case UC_NULL:
+ return 0;
+
+ default:
+ return -1;
+ }
+}
+
+/**
+ * Configure ulog logger.
+ *
+ * This functions configures the ulog mechanism and is analogeous to using the
+ * `openlog()` function in conjuncton with `syslog()`.
+ *
+ * The `ulog_open()` function is OpenWrt specific and may not be present on
+ * other systems. Use `openlog()` and `syslog()` instead for portability to
+ * non-OpenWrt environments.
+ *
+ * A program may use multiple channels to simultaneously output messages using
+ * different means. The channel argument may either be a single string value
+ * containing a channel name, an array of channel names or a numeric value
+ * representing a bitmask of `ULOG_*` channel constants.
+ *
+ * The facility argument may be either a single string value containing a
+ * facility name or one of the numeric `LOG_*` facility constants in the module
+ * namespace.
+ *
+ * The default facility value varies, depending on the execution context of the
+ * program. In OpenWrt's preinit boot phase, or when stdout is not connected to
+ * an interactive terminal, the facility defaults to `"daemon"` (`LOG_DAEMON`),
+ * otherwise to `"user"` (`LOG_USER`).
+ *
+ * Likewise, the default channel is selected depending on the context. During
+ * OpenWrt's preinit boot phase, the `"kmsg"` channel is used, for interactive
+ * terminals the `"stdio"` one and for all other cases the `"syslog"` channel
+ * is selected.
+ *
+ * Returns `true` if ulog was configured.
+ *
+ * Returns `false` if an invalid argument, such as an unrecognized channel or
+ * facility name, was provided.
+ *
+ * @function module:log#ulog_open
+ *
+ * @param {number|module:log.UlogChannel|module:log.UlogChannel[]} [channel]
+ * Specifies the log channels to use.
+ *
+ * See {@link module:log.UlogChannel|UlogChannel} for recognized channel names.
+ *
+ * @param {number|module:log.LogFacility} [facility]
+ * The facility to use for log messages generated by subsequent `ulog()` calls.
+ *
+ * See {@link module:log.LogFacility|LogFacility} for recognized facility names.
+ *
+ * @param {string} [ident]
+ * A string identifying the program name. If omitted, the name of the calling
+ * process is used by default.
+ *
+ * @returns {boolean}
+ *
+ * @example
+ * // Log to dmesg and stderr
+ * ulog_open(["stdio", "kmsg"], "daemon", "my-program");
+ *
+ * // Use numeric constants and use implicit default ident
+ * ulog_open(ULOG_SYSLOG, LOG_LOCAL0);
+ */
+static uc_value_t *
+uc_ulog_open(uc_vm_t *vm, size_t nargs)
+{
+ int channels = parse_channels(uc_fn_arg(0));
+ int facility = parse_facility(uc_fn_arg(1));
+ char *ident = parse_ident(vm, uc_fn_arg(2));
+
+ if (channels == -1 || facility == -1)
+ return ucv_boolean_new(false);
+
+ ulog_open(channels, facility, ident);
+
+ return ucv_boolean_new(true);
+}
+
+static uc_value_t *
+uc_ulog_log_common(uc_vm_t *vm, size_t nargs, int priority)
+{
+ uc_value_t *fmt = uc_fn_arg(0), *msg;
+ uc_cfn_ptr_t fmtfn;
+ char *s;
+
+ switch (ucv_type(fmt)) {
+ case UC_STRING:
+ fmtfn = uc_stdlib_function("sprintf");
+ msg = fmtfn(vm, nargs);
+
+ if (msg) {
+ ulog(priority, "%s", ucv_string_get(msg));
+ ucv_put(msg);
+
+ return ucv_boolean_new(true);
+ }
+
+ break;
+
+ case UC_NULL:
+ break;
+
+ default:
+ s = ucv_to_string(vm, fmt);
+
+ if (s) {
+ ulog(priority, "%s", s);
+ free(s);
+
+ return ucv_boolean_new(true);
+ }
+
+ break;
+ }
+
+ return ucv_boolean_new(false);
+}
+
+/**
+ * Log a message via the ulog mechanism.
+ *
+ * The `ulog()` function outputs the given log message to all configured ulog
+ * channels unless the given priority level exceeds the globally configured ulog
+ * priority threshold. See {@link module:log#ulog_threshold|ulog_threshold()}
+ * for details.
+ *
+ * The `ulog()` function is OpenWrt specific and may not be present on other
+ * systems. Use `syslog()` instead for portability to non-OpenWrt environments.
+ *
+ * Like `syslog()`, the function behaves in a sprintf-like manner, allowing the
+ * use of format strings and associated arguments to construct log messages.
+ *
+ * If the `ulog_open()` function has not been called explicitly before, `ulog()`
+ * implicitly configures certain defaults, see
+ * {@link module:log#ulog_open|ulog_open()} for a detailled description.
+ *
+ * If the `format` argument is not a string and not `null`, it will be
+ * implicitly converted to a string and logged as-is, without further format
+ * string processing.
+ *
+ * Returns `true` if a message was passed to the underlying `ulog()` function.
+ *
+ * Returns `false` if an invalid priority value or an empty message was given.
+ *
+ * @function module:log#ulog
+ *
+ * @param {number|module:log.LogPriority} priority
+ * Log message priority. May be either a number value or a priority name string.
+ *
+ * See {@link module:log.LogPriority|LogPriority} for recognized priority names.
+ *
+ * @param {*} format
+ * The sprintf-like format string for the log message, or any other, non-null,
+ * non-string value type which will be implicitly stringified and logged as-is.
+ *
+ * @param {...*} [args]
+ * In case a format string value was provided in the previous argument, then
+ * all subsequent arguments are used to replace the placeholders in the format
+ * string.
+ *
+ * @returns {boolean}
+ *
+ * @example
+ * // Example usage of ulog function with format string and arguments
+ * const username = "user123";
+ * const errorCode = 404;
+ * ulog(LOG_ERR, "User %s encountered error: %d", username, errorCode);
+ *
+ * // Using priority names for logging
+ * ulog("err", "General error encountered");
+ *
+ * // Implicit stringification
+ * ulog("debug", { foo: 1, bar: true, baz: [1, 2, 3] });
+ *
+ * @see module:log#ulog_open
+ * @see module:log#ulog_threshold
+ * @see module:log#syslog
+ */
+static uc_value_t *
+uc_ulog_log(uc_vm_t *vm, size_t nargs)
+{
+ int priority = parse_priority(uc_fn_arg(0));
+
+ if (priority == -1 || nargs < 2)
+ return ucv_boolean_new(false);
+
+ return uc_ulog_log_common(vm, nargs - 1, priority);
+}
+
+/**
+ * Close ulog logger.
+ *
+ * Resets the ulog channels, the default facility and the log ident value to
+ * defaults.
+ *
+ * In case the `"syslog"` channel has been configured, the underlying
+ * `closelog()` function will be invoked.
+ *
+ * The usage of this function is optional, and usually an explicit ulog teardown
+ * is not required.
+ *
+ * The `ulog_close()` function is OpenWrt specific and may not be present on
+ * other systems. Use `closelog()` in conjunction with `syslog()` instead for
+ * portability to non-OpenWrt environments.
+ *
+ * @function module:log#ulog_close
+ *
+ * @see module:log#closelog
+ */
+static uc_value_t *
+uc_ulog_close(uc_vm_t *vm, size_t nargs)
+{
+ ulog_close();
+
+ return NULL;
+}
+
+/**
+ * Set ulog priority threshold.
+ *
+ * This function configures the application wide log message threshold for log
+ * messages emitted with `ulog()`. Any message with a priority higher (= less
+ * severe) than the threshold priority will be discarded. This is useful to
+ * implement application wide verbosity settings without having to wrap `ulog()`
+ * invocations into a helper function or guarding code.
+ *
+ * When no explicit threshold has been set, `LOG_DEBUG` is used by default,
+ * allowing log messages with all known priorities.
+ *
+ * The `ulog_threshold()` function is OpenWrt specific and may not be present on
+ * other systems. There is no syslog equivalent to this ulog specific threshold
+ * mechanism.
+ *
+ * The priority argument may be either a string value containing a priority name
+ * or one of the numeric `LOG_*` priority constants in the module namespace.
+ *
+ * Returns `true` if a threshold was set.
+ *
+ * Returns `false` if an invalid priority value was given.
+ *
+ * @function module:log#ulog_threshold
+ *
+ * @param {number|module:log.LogPriority} [priority]
+ * The priority threshold to configure.
+ *
+ * See {@link module:log.LogPriority|LogPriority} for recognized priority names.
+ *
+ * @returns {boolean}
+ *
+ * @example
+ * // Set threshold to "warning" or more severe
+ * ulog_threshold(LOG_WARNING);
+ *
+ * // This message will be supressed
+ * ulog(LOG_DEBUG, "Testing thresholds");
+ *
+ * // Using priority name
+ * ulog_threshold("debug");
+ */
+static uc_value_t *
+uc_ulog_threshold(uc_vm_t *vm, size_t nargs)
+{
+ int priority = parse_priority(uc_fn_arg(0));
+
+ if (priority == -1)
+ return ucv_boolean_new(false);
+
+ ulog_threshold(priority);
+
+ return ucv_boolean_new(true);
+}
+
+/**
+ * Invoke ulog with LOG_INFO.
+ *
+ * This function is convenience wrapper for `ulog(LOG_INFO, ...)`.
+ *
+ * See {@link module:log#ulog|ulog()} for details.
+ *
+ * @function module:log#INFO
+ *
+ * @param {*} format
+ * The sprintf-like format string for the log message, or any other, non-null,
+ * non-string value type which will be implicitly stringified and logged as-is.
+ *
+ * @param {...*} [args]
+ * In case a format string value was provided in the previous argument, then
+ * all subsequent arguments are used to replace the placeholders in the format
+ * string.
+ *
+ * @returns {boolean}
+ *
+ * @example
+ * INFO("This is an info log message");
+ */
+static uc_value_t *
+uc_ulog_INFO(uc_vm_t *vm, size_t nargs)
+{
+ return uc_ulog_log_common(vm, nargs, LOG_INFO);
+}
+
+/**
+ * Invoke ulog with LOG_NOTICE.
+ *
+ * This function is convenience wrapper for `ulog(LOG_NOTICE, ...)`.
+ *
+ * See {@link module:log#ulog|ulog()} for details.
+ *
+ * @function module:log#NOTE
+ *
+ * @param {*} format
+ * The sprintf-like format string for the log message, or any other, non-null,
+ * non-string value type which will be implicitly stringified and logged as-is.
+ *
+ * @param {...*} [args]
+ * In case a format string value was provided in the previous argument, then
+ * all subsequent arguments are used to replace the placeholders in the format
+ * string.
+ *
+ * @returns {boolean}
+ *
+ * @example
+ * NOTE("This is a notification log message");
+ */
+static uc_value_t *
+uc_ulog_NOTE(uc_vm_t *vm, size_t nargs)
+{
+ return uc_ulog_log_common(vm, nargs, LOG_NOTICE);
+}
+
+/**
+ * Invoke ulog with LOG_WARNING.
+ *
+ * This function is convenience wrapper for `ulog(LOG_WARNING, ...)`.
+ *
+ * See {@link module:log#ulog|ulog()} for details.
+ *
+ * @function module:log#WARN
+ *
+ * @param {*} format
+ * The sprintf-like format string for the log message, or any other, non-null,
+ * non-string value type which will be implicitly stringified and logged as-is.
+ *
+ * @param {...*} [args]
+ * In case a format string value was provided in the previous argument, then
+ * all subsequent arguments are used to replace the placeholders in the format
+ * string.
+ *
+ * @returns {boolean}
+ *
+ * @example
+ * WARN("This is a warning");
+ */
+static uc_value_t *
+uc_ulog_WARN(uc_vm_t *vm, size_t nargs)
+{
+ return uc_ulog_log_common(vm, nargs, LOG_WARNING);
+}
+
+/**
+ * Invoke ulog with LOG_ERR.
+ *
+ * This function is convenience wrapper for `ulog(LOG_ERR, ...)`.
+ *
+ * See {@link module:log#ulog|ulog()} for details.
+ *
+ * @function module:log#ERR
+ *
+ * @param {*} format
+ * The sprintf-like format string for the log message, or any other, non-null,
+ * non-string value type which will be implicitly stringified and logged as-is.
+ *
+ * @param {...*} [args]
+ * In case a format string value was provided in the previous argument, then
+ * all subsequent arguments are used to replace the placeholders in the format
+ * string.
+ *
+ * @returns {boolean}
+ *
+ * @example
+ * ERR("This is an error!");
+ */
+static uc_value_t *
+uc_ulog_ERR(uc_vm_t *vm, size_t nargs)
+{
+ return uc_ulog_log_common(vm, nargs, LOG_ERR);
+}
+#endif
+
+
+static const uc_function_list_t global_fns[] = {
+ { "openlog", uc_openlog },
+ { "syslog", uc_syslog },
+ { "closelog", uc_closelog },
+
+#ifdef HAVE_ULOG
+ { "ulog_open", uc_ulog_open },
+ { "ulog", uc_ulog_log },
+ { "ulog_close", uc_ulog_close },
+ { "ulog_threshold", uc_ulog_threshold },
+ { "INFO", uc_ulog_INFO },
+ { "NOTE", uc_ulog_NOTE },
+ { "WARN", uc_ulog_WARN },
+ { "ERR", uc_ulog_ERR },
+#endif
+};
+
+
+void uc_module_init(uc_vm_t *vm, uc_value_t *scope)
+{
+ uc_function_list_register(scope, global_fns);
+
+#define ADD_CONST(x) ucv_object_add(scope, #x, ucv_int64_new(x))
+
+ ADD_CONST(LOG_PID);
+ ADD_CONST(LOG_CONS);
+ ADD_CONST(LOG_NDELAY);
+ ADD_CONST(LOG_ODELAY);
+ ADD_CONST(LOG_NOWAIT);
+
+ ADD_CONST(LOG_AUTH);
+#ifdef LOG_AUTHPRIV
+ ADD_CONST(LOG_AUTHPRIV);
+#endif
+ ADD_CONST(LOG_CRON);
+ ADD_CONST(LOG_DAEMON);
+#ifdef LOG_FTP
+ ADD_CONST(LOG_FTP);
+#endif
+ ADD_CONST(LOG_KERN);
+ ADD_CONST(LOG_LPR);
+ ADD_CONST(LOG_MAIL);
+ ADD_CONST(LOG_NEWS);
+ ADD_CONST(LOG_SYSLOG);
+ ADD_CONST(LOG_USER);
+ ADD_CONST(LOG_UUCP);
+ ADD_CONST(LOG_LOCAL0);
+ ADD_CONST(LOG_LOCAL1);
+ ADD_CONST(LOG_LOCAL2);
+ ADD_CONST(LOG_LOCAL3);
+ ADD_CONST(LOG_LOCAL4);
+ ADD_CONST(LOG_LOCAL5);
+ ADD_CONST(LOG_LOCAL6);
+ ADD_CONST(LOG_LOCAL7);
+
+ ADD_CONST(LOG_EMERG);
+ ADD_CONST(LOG_ALERT);
+ ADD_CONST(LOG_CRIT);
+ ADD_CONST(LOG_ERR);
+ ADD_CONST(LOG_WARNING);
+ ADD_CONST(LOG_NOTICE);
+ ADD_CONST(LOG_INFO);
+ ADD_CONST(LOG_DEBUG);
+
+#ifdef HAVE_ULOG
+ ADD_CONST(ULOG_KMSG);
+ ADD_CONST(ULOG_SYSLOG);
+ ADD_CONST(ULOG_STDIO);
+#endif
+}