fix(userspace): add portable compat wrappers for gmtime_r, localtime_r, strerror_r

The previous commits used gmtime_r/localtime_r (unavailable on Windows)
and assumed the GNU strerror_r variant returning char* (only on glibc).
This broke macOS, musl, WASM, and Win32 builds.

Add userspace/engine/compat.h with portable inline wrappers:
- falco_gmtime_r / falco_localtime_r: use gmtime_s/localtime_s on Win32
- falco_strerror_r: returns const char* on all platforms, detecting
  glibc via __GLIBC__ (not _GNU_SOURCE alone, since musl defines
  _GNU_SOURCE but provides the XSI variant returning int)

Also fixes a pre-existing bug in create_signal_handlers.cpp where
the GNU strerror_r return value was incorrectly compared to 0 and
the actual error string was discarded.

Signed-off-by: Leonardo Grasso <me@leonardograsso.com>
This commit is contained in:
Leonardo Grasso
2026-04-09 16:55:35 +02:00
parent 26daebb1a9
commit abdf5512c8
9 changed files with 82 additions and 17 deletions

59
userspace/engine/compat.h Normal file
View File

@@ -0,0 +1,59 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright (C) 2025 The Falco Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#pragma once
#include <ctime>
#include <cstring>
// Portable gmtime_r: Windows provides gmtime_s with reversed arg order.
inline struct tm* falco_gmtime_r(const time_t* timer, struct tm* buf) {
#ifdef _WIN32
return gmtime_s(buf, timer) == 0 ? buf : nullptr;
#else
return gmtime_r(timer, buf);
#endif
}
// Portable localtime_r: Windows provides localtime_s with reversed arg order.
inline struct tm* falco_localtime_r(const time_t* timer, struct tm* buf) {
#ifdef _WIN32
return localtime_s(buf, timer) == 0 ? buf : nullptr;
#else
return localtime_r(timer, buf);
#endif
}
// Portable strerror_r: returns const char* on all platforms.
//
// - glibc with _GNU_SOURCE: returns char* that may point to buf or a static string
// - musl/macOS/WASM (XSI): returns int, always writes to buf
// - Windows: no strerror_r, uses strerror_s instead
//
// We check __GLIBC__ (not _GNU_SOURCE alone) because musl defines _GNU_SOURCE
// but always provides the XSI variant.
inline const char* falco_strerror_r(int errnum, char* buf, size_t len) {
#if defined(__GLIBC__) && defined(_GNU_SOURCE)
return strerror_r(errnum, buf, len);
#elif defined(_WIN32)
strerror_s(buf, len, errnum);
return buf;
#else
strerror_r(errnum, buf, len);
return buf;
#endif
}

View File

@@ -17,6 +17,7 @@ limitations under the License.
#include <nlohmann/json.hpp>
#include "compat.h"
#include "formats.h"
#include "falco_engine.h"
@@ -99,7 +100,7 @@ std::string falco_formats::format_event(sinsp_evt *evt,
std::string iso8601evttime;
struct tm tm_buf;
gmtime_r(&evttime, &tm_buf);
falco_gmtime_r(&evttime, &tm_buf);
strftime(time_sec, sizeof(time_sec), "%FT%T", &tm_buf);
snprintf(time_ns, sizeof(time_ns), ".%09luZ", evt->get_ts() % 1000000000);
iso8601evttime = time_sec;

View File

@@ -16,6 +16,7 @@ limitations under the License.
*/
#include <ctime>
#include "compat.h"
#include "logger.h"
#include "falco_common.h"
@@ -123,13 +124,13 @@ void falco_logger::log(falco_logger::level priority, const std::string&& msg) {
if(falco_logger::time_format_iso_8601) {
char buf[sizeof "YYYY-MM-DDTHH:MM:SS-0000"];
struct tm gtm;
if(gmtime_r(&result, &gtm) != NULL &&
if(falco_gmtime_r(&result, &gtm) != NULL &&
(strftime(buf, sizeof(buf), "%FT%T%z", &gtm) != 0)) {
fprintf(stderr, "%s: %s", buf, copy.c_str());
}
} else {
struct tm ltm;
localtime_r(&result, &ltm);
falco_localtime_r(&result, &ltm);
char tstr[std::size("WWW MMM DD HH:mm:ss YYYY")];
std::strftime(std::data(tstr), std::size(tstr), "%a %b %d %H:%M:%S %Y", &ltm);
fprintf(stderr, "%s: %s", tstr, copy.c_str());

View File

@@ -18,6 +18,7 @@ limitations under the License.
#include <functional>
#include "actions.h"
#include "compat.h"
#include "../app.h"
#include "../signals.h"
@@ -49,12 +50,10 @@ bool create_handler(int sig, void (*func)(int), run_result& ret) {
#ifdef __linux__
if(signal(sig, func) == SIG_ERR) {
char errbuf[1024];
if(strerror_r(errno, errbuf, sizeof(errbuf)) != 0) {
snprintf(errbuf, sizeof(errbuf) - 1, "Errno %d", errno);
}
const char* errstr = falco_strerror_r(errno, errbuf, sizeof(errbuf));
ret = run_result::fatal(std::string("Could not create signal handler for ") +
strsignal(sig) + ": " + errbuf);
strsignal(sig) + ": " + errstr);
}
#endif
return ret.success;

View File

@@ -16,6 +16,7 @@ limitations under the License.
*/
#include "actions.h"
#include "compat.h"
#include "helpers.h"
#include "../app.h"
#include <fstream>
@@ -38,7 +39,7 @@ falco::app::run_result falco::app::actions::print_kernel_version(const falco::ap
// We don't want to fail, we just need to log something
int saved_errno = errno;
char errbuf[256];
const char* errstr = strerror_r(saved_errno, errbuf, sizeof(errbuf));
const char* errstr = falco_strerror_r(saved_errno, errbuf, sizeof(errbuf));
falco_logger::log(falco_logger::level::INFO,
"Cannot read under '/proc/version' (err_message: '" +
std::string(errstr) + "', err_code: " +

View File

@@ -20,6 +20,7 @@ limitations under the License.
#else
#include <windows.h>
#endif
#include "compat.h"
#include <iostream>
#include "actions.h"
@@ -96,7 +97,7 @@ falco::app::run_result falco::app::actions::print_support(falco::app::state& s)
if(get_sysinfo(support) != 0) {
char errbuf[256];
const char* errstr = strerror_r(errno, errbuf, sizeof(errbuf));
const char* errstr = falco_strerror_r(errno, errbuf, sizeof(errbuf));
return run_result::fatal(std::string("Could not get system info: ") + errstr);
}

View File

@@ -16,6 +16,7 @@ limitations under the License.
*/
#include "falco_outputs.h"
#include "compat.h"
#include "config_falco.h"
#include "formats.h"
@@ -182,7 +183,7 @@ void falco_outputs::handle_msg(uint64_t ts,
std::string iso8601evttime;
struct tm tm_buf;
gmtime_r(&evttime, &tm_buf);
falco_gmtime_r(&evttime, &tm_buf);
strftime(time_sec, sizeof(time_sec), "%FT%T", &tm_buf);
snprintf(time_ns, sizeof(time_ns), ".%09luZ", ts % 1000000000);
iso8601evttime = time_sec;

View File

@@ -16,6 +16,7 @@ limitations under the License.
*/
#include "outputs_program.h"
#include "compat.h"
#include "logger.h"
#include <stdio.h>
#include <cerrno>
@@ -27,7 +28,7 @@ void falco::outputs::output_program::open_pfile() {
if(m_pfile == nullptr) {
char errbuf[256];
const char* errstr = strerror_r(errno, errbuf, sizeof(errbuf));
const char* errstr = falco_strerror_r(errno, errbuf, sizeof(errbuf));
falco_logger::log(falco_logger::level::ERR,
"Failed to open program output: " + m_oc.options["program"] +
" (error: " + errstr + ")");

View File

@@ -24,6 +24,7 @@ limitations under the License.
#include <nlohmann/json.hpp>
#include "compat.h"
#include "falco_common.h"
#include "stats_writer.h"
#include "logger.h"
@@ -72,7 +73,7 @@ bool stats_writer::init_ticker(uint32_t interval_msec, std::string& err) {
handler.sa_handler = &timer_handler;
if(sigaction(SIGALRM, &handler, NULL) == -1) {
char errbuf[256];
const char* errstr = strerror_r(errno, errbuf, sizeof(errbuf));
const char* errstr = falco_strerror_r(errno, errbuf, sizeof(errbuf));
err = std::string("Could not set up signal handler for periodic timer: ") + errstr;
return false;
}
@@ -96,7 +97,7 @@ bool stats_writer::init_ticker(uint32_t interval_msec, std::string& err) {
handler.sa_handler = &timer_handler;
if(sigaction(SIGALRM, &handler, NULL) == -1) {
char errbuf[256];
const char* errstr = strerror_r(errno, errbuf, sizeof(errbuf));
const char* errstr = falco_strerror_r(errno, errbuf, sizeof(errbuf));
err = std::string("Could not set up signal handler for periodic timer: ") + errstr;
return false;
}
@@ -124,7 +125,7 @@ bool stats_writer::init_ticker(uint32_t interval_msec, std::string& err) {
handler.sa_handler = &timer_handler;
if(sigaction(SIGALRM, &handler, NULL) == -1) {
char errbuf[256];
const char* errstr = strerror_r(errno, errbuf, sizeof(errbuf));
const char* errstr = falco_strerror_r(errno, errbuf, sizeof(errbuf));
err = std::string("Could not set up signal handler for periodic timer: ") + errstr;
return false;
}
@@ -138,7 +139,7 @@ bool stats_writer::init_ticker(uint32_t interval_msec, std::string& err) {
if(s_timerid_exists) {
if(timer_delete(s_timerid) == -1) {
char errbuf[256];
const char* errstr = strerror_r(errno, errbuf, sizeof(errbuf));
const char* errstr = falco_strerror_r(errno, errbuf, sizeof(errbuf));
err = std::string("Could not delete previous timer: ") + errstr;
return false;
}
@@ -147,7 +148,7 @@ bool stats_writer::init_ticker(uint32_t interval_msec, std::string& err) {
if(timer_create(CLOCK_MONOTONIC, &sev, &s_timerid) == -1) {
char errbuf[256];
const char* errstr = strerror_r(errno, errbuf, sizeof(errbuf));
const char* errstr = falco_strerror_r(errno, errbuf, sizeof(errbuf));
err = std::string("Could not create periodic timer: ") + errstr;
return false;
}
@@ -159,7 +160,7 @@ bool stats_writer::init_ticker(uint32_t interval_msec, std::string& err) {
if(timer_settime(s_timerid, 0, &timer, NULL) == -1) {
char errbuf[256];
const char* errstr = strerror_r(errno, errbuf, sizeof(errbuf));
const char* errstr = falco_strerror_r(errno, errbuf, sizeof(errbuf));
err = std::string("Could not set up periodic timer: ") + errstr;
return false;
}