diff options
author | Jo-Philipp Wich <jo@mein.io> | 2023-10-10 16:48:15 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-10-10 16:48:15 +0200 |
commit | fcd39e290f58e3c598b976fb78a8fefaad3946ba (patch) | |
tree | cfa6dcae54b2f8748853da7fabfc1aae7c022308 | |
parent | 9369b380423fbe785d40d80a1c3ad06ad980394b (diff) | |
parent | 8f21cfa9571e8fc3518ede92356d97e09f4713de (diff) |
Merge pull request #174 from jow-/log-lib
lib: introduce log library
-rw-r--r-- | CMakeLists.txt | 525 | ||||
-rw-r--r-- | lib/log.c | 1074 |
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 +} |