summaryrefslogtreecommitdiffstats
path: root/src/libutil/util.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libutil/util.c')
-rw-r--r--src/libutil/util.c2746
1 files changed, 2746 insertions, 0 deletions
diff --git a/src/libutil/util.c b/src/libutil/util.c
new file mode 100644
index 0000000..04200e3
--- /dev/null
+++ b/src/libutil/util.c
@@ -0,0 +1,2746 @@
+/*
+ * Copyright 2024 Vsevolod Stakhov
+ *
+ * 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.
+ */
+#include "config.h"
+#include "util.h"
+#include "unix-std.h"
+
+#include "ottery.h"
+#include "cryptobox.h"
+#include "contrib/libev/ev.h"
+
+#ifdef HAVE_TERMIOS_H
+#include <termios.h>
+#endif
+#ifdef HAVE_READPASSPHRASE_H
+#include <readpassphrase.h>
+#endif
+/* libutil */
+#ifdef HAVE_LIBUTIL_H
+#include <libutil.h>
+#endif
+#ifdef __APPLE__
+#include <mach/mach_time.h>
+#include <mach/mach_init.h>
+#include <mach/thread_act.h>
+#include <mach/mach_port.h>
+#endif
+/* poll */
+#ifdef HAVE_POLL_H
+#include <poll.h>
+#endif
+
+#ifdef HAVE_SIGINFO_H
+#include <siginfo.h>
+#endif
+/* sys/wait */
+#ifdef HAVE_SYS_WAIT_H
+#include <sys/wait.h>
+#endif
+/* sys/resource.h */
+#ifdef HAVE_SYS_RESOURCE_H
+#include <sys/resource.h>
+#endif
+#ifdef HAVE_RDTSC
+#ifdef __x86_64__
+#include <x86intrin.h>
+#endif
+#endif
+
+#include <math.h> /* for pow */
+#include <glob.h> /* in fact, we require this file ultimately */
+
+#include "zlib.h"
+#include "contrib/uthash/utlist.h"
+#include "blas-config.h"
+
+/* Check log messages intensity once per minute */
+#define CHECK_TIME 60
+/* More than 2 log messages per second */
+#define BUF_INTENSITY 2
+/* Default connect timeout for sync sockets */
+#define CONNECT_TIMEOUT 3
+
+/*
+ * Should be defined in a single point
+ */
+const struct rspamd_controller_pbkdf pbkdf_list[] = {
+ {.name = "PBKDF2-blake2b",
+ .alias = "pbkdf2",
+ .description = "standard CPU intensive \"slow\" KDF using blake2b hash function",
+ .type = RSPAMD_CRYPTOBOX_PBKDF2,
+ .id = RSPAMD_PBKDF_ID_V1,
+ .complexity = 16000,
+ .salt_len = 20,
+ .key_len = rspamd_cryptobox_HASHBYTES / 2},
+ {.name = "Catena-Butterfly",
+ .alias = "catena",
+ .description = "modern CPU and memory intensive KDF",
+ .type = RSPAMD_CRYPTOBOX_CATENA,
+ .id = RSPAMD_PBKDF_ID_V2,
+ .complexity = 10,
+ .salt_len = 20,
+ .key_len = rspamd_cryptobox_HASHBYTES / 2}};
+
+gint rspamd_socket_nonblocking(gint fd)
+{
+ gint ofl;
+
+ ofl = fcntl(fd, F_GETFL, 0);
+
+ if (fcntl(fd, F_SETFL, ofl | O_NONBLOCK) == -1) {
+ return -1;
+ }
+ return 0;
+}
+
+gint rspamd_socket_blocking(gint fd)
+{
+ gint ofl;
+
+ ofl = fcntl(fd, F_GETFL, 0);
+
+ if (fcntl(fd, F_SETFL, ofl & (~O_NONBLOCK)) == -1) {
+ return -1;
+ }
+ return 0;
+}
+
+gint rspamd_socket_poll(gint fd, gint timeout, short events)
+{
+ gint r;
+ struct pollfd fds[1];
+
+ fds->fd = fd;
+ fds->events = events;
+ fds->revents = 0;
+ while ((r = poll(fds, 1, timeout)) < 0) {
+ if (errno != EINTR) {
+ break;
+ }
+ }
+
+ return r;
+}
+
+gint rspamd_socket_create(gint af, gint type, gint protocol, gboolean async)
+{
+ gint fd;
+
+ fd = socket(af, type, protocol);
+ if (fd == -1) {
+ return -1;
+ }
+
+ /* Set close on exec */
+ if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) {
+ close(fd);
+ return -1;
+ }
+ if (async) {
+ if (rspamd_socket_nonblocking(fd) == -1) {
+ close(fd);
+ return -1;
+ }
+ }
+
+ return fd;
+}
+
+static gint
+rspamd_inet_socket_create(gint type, struct addrinfo *addr, gboolean is_server,
+ gboolean async, GList **list)
+{
+ gint fd = -1, r, on = 1, s_error;
+ struct addrinfo *cur;
+ gpointer ptr;
+ socklen_t optlen;
+
+ cur = addr;
+ while (cur) {
+ /* Create socket */
+ fd = rspamd_socket_create(cur->ai_family, type, cur->ai_protocol, TRUE);
+ if (fd == -1) {
+ goto out;
+ }
+
+ if (is_server) {
+ (void) setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const void *) &on,
+ sizeof(gint));
+#ifdef HAVE_IPV6_V6ONLY
+ if (cur->ai_family == AF_INET6) {
+ setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (const void *) &on,
+ sizeof(gint));
+ }
+#endif
+ r = bind(fd, cur->ai_addr, cur->ai_addrlen);
+ }
+ else {
+ r = connect(fd, cur->ai_addr, cur->ai_addrlen);
+ }
+
+ if (r == -1) {
+ if (errno != EINPROGRESS) {
+ goto out;
+ }
+ if (!async) {
+ /* Try to poll */
+ if (rspamd_socket_poll(fd, CONNECT_TIMEOUT * 1000,
+ POLLOUT) <= 0) {
+ errno = ETIMEDOUT;
+ goto out;
+ }
+ else {
+ /* Make synced again */
+ if (rspamd_socket_blocking(fd) < 0) {
+ goto out;
+ }
+ }
+ }
+ }
+ else {
+ /* Still need to check SO_ERROR on socket */
+ optlen = sizeof(s_error);
+
+ if (getsockopt(fd, SOL_SOCKET, SO_ERROR, (void *) &s_error, &optlen) != -1) {
+ if (s_error) {
+ errno = s_error;
+ goto out;
+ }
+ }
+ }
+ if (list == NULL) {
+ /* Go out immediately */
+ break;
+ }
+ else if (fd != -1) {
+ ptr = GINT_TO_POINTER(fd);
+ *list = g_list_prepend(*list, ptr);
+ cur = cur->ai_next;
+ continue;
+ }
+ out:
+ if (fd != -1) {
+ close(fd);
+ }
+ fd = -1;
+ cur = cur->ai_next;
+ }
+
+ return (fd);
+}
+
+gint rspamd_socket_tcp(struct addrinfo *addr, gboolean is_server, gboolean async)
+{
+ return rspamd_inet_socket_create(SOCK_STREAM, addr, is_server, async, NULL);
+}
+
+gint rspamd_socket_udp(struct addrinfo *addr, gboolean is_server, gboolean async)
+{
+ return rspamd_inet_socket_create(SOCK_DGRAM, addr, is_server, async, NULL);
+}
+
+gint rspamd_socket_unix(const gchar *path,
+ struct sockaddr_un *addr,
+ gint type,
+ gboolean is_server,
+ gboolean async)
+{
+
+ socklen_t optlen;
+ gint fd = -1, s_error, r, serrno, on = 1;
+ struct stat st;
+
+ if (path == NULL)
+ return -1;
+
+ addr->sun_family = AF_UNIX;
+
+ rspamd_strlcpy(addr->sun_path, path, sizeof(addr->sun_path));
+#ifdef FREEBSD
+ addr->sun_len = SUN_LEN(addr);
+#endif
+
+ if (is_server) {
+ /* Unlink socket if it exists already */
+ if (lstat(addr->sun_path, &st) != -1) {
+ if (S_ISSOCK(st.st_mode)) {
+ if (unlink(addr->sun_path) == -1) {
+ goto out;
+ }
+ }
+ else {
+ goto out;
+ }
+ }
+ }
+ fd = socket(PF_LOCAL, type, 0);
+
+ if (fd == -1) {
+ return -1;
+ }
+
+ if (rspamd_socket_nonblocking(fd) < 0) {
+ goto out;
+ }
+
+ /* Set close on exec */
+ if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) {
+ goto out;
+ }
+ if (is_server) {
+ (void) setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const void *) &on,
+ sizeof(gint));
+ r = bind(fd, (struct sockaddr *) addr, SUN_LEN(addr));
+ }
+ else {
+ r = connect(fd, (struct sockaddr *) addr, SUN_LEN(addr));
+ }
+
+ if (r == -1) {
+ if (errno != EINPROGRESS) {
+ goto out;
+ }
+ if (!async) {
+ /* Try to poll */
+ if (rspamd_socket_poll(fd, CONNECT_TIMEOUT * 1000, POLLOUT) <= 0) {
+ errno = ETIMEDOUT;
+ goto out;
+ }
+ else {
+ /* Make synced again */
+ if (rspamd_socket_blocking(fd) < 0) {
+ goto out;
+ }
+ }
+ }
+ }
+ else {
+ /* Still need to check SO_ERROR on socket */
+ optlen = sizeof(s_error);
+
+ if (getsockopt(fd, SOL_SOCKET, SO_ERROR, (void *) &s_error, &optlen) != -1) {
+ if (s_error) {
+ errno = s_error;
+ goto out;
+ }
+ }
+ }
+
+
+ return (fd);
+
+out:
+ serrno = errno;
+ if (fd != -1) {
+ close(fd);
+ }
+ errno = serrno;
+ return (-1);
+}
+
+static int
+rspamd_prefer_v4_hack(const struct addrinfo *a1, const struct addrinfo *a2)
+{
+ return a1->ai_addr->sa_family - a2->ai_addr->sa_family;
+}
+
+/**
+ * Make a universal socket
+ * @param credits host, ip or path to unix socket
+ * @param port port (used for network sockets)
+ * @param async make this socket async
+ * @param is_server make this socket as server socket
+ * @param try_resolve try name resolution for a socket (BLOCKING)
+ */
+gint rspamd_socket(const gchar *credits, guint16 port,
+ gint type, gboolean async, gboolean is_server, gboolean try_resolve)
+{
+ struct sockaddr_un un;
+ struct stat st;
+ struct addrinfo hints, *res;
+ gint r;
+ gchar portbuf[8];
+
+ if (*credits == '/') {
+ if (is_server) {
+ return rspamd_socket_unix(credits, &un, type, is_server, async);
+ }
+ else {
+ r = stat(credits, &st);
+ if (r == -1) {
+ /* Unix socket doesn't exists it must be created first */
+ errno = ENOENT;
+ return -1;
+ }
+ else {
+ if ((st.st_mode & S_IFSOCK) == 0) {
+ /* Path is not valid socket */
+ errno = EINVAL;
+ return -1;
+ }
+ else {
+ return rspamd_socket_unix(credits,
+ &un,
+ type,
+ is_server,
+ async);
+ }
+ }
+ }
+ }
+ else {
+ /* TCP related part */
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
+ hints.ai_socktype = type; /* Type of the socket */
+ hints.ai_flags = is_server ? AI_PASSIVE : 0;
+ hints.ai_protocol = 0; /* Any protocol */
+ hints.ai_canonname = NULL;
+ hints.ai_addr = NULL;
+ hints.ai_next = NULL;
+
+ if (!try_resolve) {
+ hints.ai_flags |= AI_NUMERICHOST | AI_NUMERICSERV;
+ }
+
+ rspamd_snprintf(portbuf, sizeof(portbuf), "%d", (int) port);
+ if ((r = getaddrinfo(credits, portbuf, &hints, &res)) == 0) {
+ LL_SORT2(res, rspamd_prefer_v4_hack, ai_next);
+ r = rspamd_inet_socket_create(type, res, is_server, async, NULL);
+ freeaddrinfo(res);
+ return r;
+ }
+ else {
+ return -1;
+ }
+ }
+}
+
+gboolean
+rspamd_socketpair(gint pair[2], gint af)
+{
+ gint r = -1, serrno;
+
+#ifdef HAVE_SOCK_SEQPACKET
+ if (af == SOCK_SEQPACKET) {
+ r = socketpair(AF_LOCAL, SOCK_SEQPACKET, 0, pair);
+
+ if (r == -1) {
+ r = socketpair(AF_LOCAL, SOCK_DGRAM, 0, pair);
+ }
+ }
+#endif
+ if (r == -1) {
+ r = socketpair(AF_LOCAL, af, 0, pair);
+ }
+
+ if (r == -1) {
+ return -1;
+ }
+
+ /* Set close on exec */
+ if (fcntl(pair[0], F_SETFD, FD_CLOEXEC) == -1) {
+ goto out;
+ }
+ if (fcntl(pair[1], F_SETFD, FD_CLOEXEC) == -1) {
+ goto out;
+ }
+
+ return TRUE;
+
+out:
+ serrno = errno;
+ close(pair[0]);
+ close(pair[1]);
+ errno = serrno;
+
+ return FALSE;
+}
+
+#ifdef HAVE_SA_SIGINFO
+void rspamd_signals_init(struct sigaction *signals, void (*sig_handler)(gint,
+ siginfo_t *,
+ void *))
+#else
+void rspamd_signals_init(struct sigaction *signals, void (*sig_handler)(gint))
+#endif
+{
+ struct sigaction sigpipe_act;
+ /* Setting up signal handlers */
+ /* SIGUSR1 - reopen config file */
+ /* SIGUSR2 - worker is ready for accept */
+ sigemptyset(&signals->sa_mask);
+ sigaddset(&signals->sa_mask, SIGTERM);
+ sigaddset(&signals->sa_mask, SIGINT);
+ sigaddset(&signals->sa_mask, SIGHUP);
+ sigaddset(&signals->sa_mask, SIGCHLD);
+ sigaddset(&signals->sa_mask, SIGUSR1);
+ sigaddset(&signals->sa_mask, SIGUSR2);
+ sigaddset(&signals->sa_mask, SIGALRM);
+#ifdef SIGPOLL
+ sigaddset(&signals->sa_mask, SIGPOLL);
+#endif
+#ifdef SIGIO
+ sigaddset(&signals->sa_mask, SIGIO);
+#endif
+
+#ifdef HAVE_SA_SIGINFO
+ signals->sa_flags = SA_SIGINFO;
+ signals->sa_handler = NULL;
+ signals->sa_sigaction = sig_handler;
+#else
+ signals->sa_handler = sig_handler;
+ signals->sa_flags = 0;
+#endif
+ sigaction(SIGTERM, signals, NULL);
+ sigaction(SIGINT, signals, NULL);
+ sigaction(SIGHUP, signals, NULL);
+ sigaction(SIGCHLD, signals, NULL);
+ sigaction(SIGUSR1, signals, NULL);
+ sigaction(SIGUSR2, signals, NULL);
+ sigaction(SIGALRM, signals, NULL);
+#ifdef SIGPOLL
+ sigaction(SIGPOLL, signals, NULL);
+#endif
+#ifdef SIGIO
+ sigaction(SIGIO, signals, NULL);
+#endif
+
+ /* Ignore SIGPIPE as we handle write errors manually */
+ sigemptyset(&sigpipe_act.sa_mask);
+ sigaddset(&sigpipe_act.sa_mask, SIGPIPE);
+ sigpipe_act.sa_handler = SIG_IGN;
+ sigpipe_act.sa_flags = 0;
+ sigaction(SIGPIPE, &sigpipe_act, NULL);
+}
+
+#ifndef HAVE_SETPROCTITLE
+
+#ifdef LINUX
+static gchar *title_buffer = NULL;
+static size_t title_buffer_size = 0;
+static gchar *title_progname, *title_progname_full;
+gchar **old_environ = NULL;
+
+static void
+rspamd_title_dtor(gpointer d)
+{
+ /* Restore old environment */
+ if (old_environ != NULL) {
+ environ = old_environ;
+ }
+
+ gchar **env = (gchar **) d;
+ guint i;
+
+ for (i = 0; env[i] != NULL; i++) {
+ g_free(env[i]);
+ }
+
+ g_free(env);
+}
+#endif /* ifdef LINUX */
+
+#endif /* ifndef HAVE_SETPROCTITLE */
+
+gint rspamd_init_title(rspamd_mempool_t *pool,
+ gint argc, gchar *argv[], gchar *envp[])
+{
+#if defined(LINUX) && !defined(HAVE_SETPROCTITLE)
+ gchar *begin_of_buffer = 0, *end_of_buffer = 0;
+ gint i;
+
+ for (i = 0; i < argc; ++i) {
+ if (!begin_of_buffer) {
+ begin_of_buffer = argv[i];
+ }
+ if (!end_of_buffer || end_of_buffer + 1 == argv[i]) {
+ end_of_buffer = argv[i] + strlen(argv[i]);
+ }
+ }
+
+ for (i = 0; envp[i]; ++i) {
+ if (!begin_of_buffer) {
+ begin_of_buffer = envp[i];
+ }
+ if (!end_of_buffer || end_of_buffer + 1 == envp[i]) {
+ end_of_buffer = envp[i] + strlen(envp[i]);
+ }
+ }
+
+ if (!end_of_buffer) {
+ return 0;
+ }
+
+ gchar **new_environ = g_malloc((i + 1) * sizeof(envp[0]));
+
+ for (i = 0; envp[i]; ++i) {
+ new_environ[i] = g_strdup(envp[i]);
+ }
+
+ new_environ[i] = NULL;
+
+ if (program_invocation_name) {
+ title_progname_full = g_strdup(program_invocation_name);
+
+ gchar *p = strrchr(title_progname_full, '/');
+
+ if (p) {
+ title_progname = p + 1;
+ }
+ else {
+ title_progname = title_progname_full;
+ }
+
+ program_invocation_name = title_progname_full;
+ program_invocation_short_name = title_progname;
+ }
+
+ old_environ = environ;
+ environ = new_environ;
+ title_buffer = begin_of_buffer;
+ title_buffer_size = end_of_buffer - begin_of_buffer;
+
+ rspamd_mempool_add_destructor(pool,
+ rspamd_title_dtor,
+ new_environ);
+#endif
+
+ return 0;
+}
+
+gint rspamd_setproctitle(const gchar *fmt, ...)
+{
+#ifdef HAVE_SETPROCTITLE
+ if (fmt) {
+ static char titlebuf[4096];
+ va_list ap;
+
+ va_start(ap, fmt);
+ rspamd_vsnprintf(titlebuf, sizeof(titlebuf), fmt, ap);
+ va_end(ap);
+
+ setproctitle("%s", titlebuf);
+ }
+#else
+#if defined(LINUX)
+ if (!title_buffer || !title_buffer_size) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ memset(title_buffer, '\0', title_buffer_size);
+
+ ssize_t written;
+
+ if (fmt) {
+ va_list ap;
+
+ written = rspamd_snprintf(title_buffer,
+ title_buffer_size,
+ "%s: ",
+ title_progname);
+ if (written < 0 || (size_t) written >= title_buffer_size)
+ return -1;
+
+ va_start(ap, fmt);
+ rspamd_vsnprintf(title_buffer + written,
+ title_buffer_size - written,
+ fmt,
+ ap);
+ va_end(ap);
+ }
+ else {
+ written = rspamd_snprintf(title_buffer,
+ title_buffer_size,
+ "%s",
+ title_progname);
+ if (written < 0 || (size_t) written >= title_buffer_size)
+ return -1;
+ }
+
+ written = strlen(title_buffer);
+ memset(title_buffer + written, '\0', title_buffer_size - written);
+#elif defined(__APPLE__)
+ /* OSX is broken, ignore this brain damaged system */
+#else
+ /* Last resort (usually broken, but eh...) */
+ GString *dest;
+ va_list ap;
+
+ dest = g_string_new("");
+ va_start(ap, fmt);
+ rspamd_vprintf_gstring(dest, fmt, ap);
+ va_end(ap);
+
+ g_set_prgname(dest->str);
+ g_string_free(dest, TRUE);
+
+#endif /* defined(LINUX) */
+
+#endif /* HAVE_SETPROCTITLE */
+ return 0;
+}
+
+
+#ifndef HAVE_PIDFILE
+static gint _rspamd_pidfile_remove(rspamd_pidfh_t *pfh, gint freeit);
+
+static gint
+rspamd_pidfile_verify(rspamd_pidfh_t *pfh)
+{
+ struct stat sb;
+
+ if (pfh == NULL || pfh->pf_fd == -1)
+ return (-1);
+ /*
+ * Check remembered descriptor.
+ */
+ if (fstat(pfh->pf_fd, &sb) == -1)
+ return (errno);
+ if (sb.st_dev != pfh->pf_dev || sb.st_ino != pfh->pf_ino)
+ return -1;
+ return 0;
+}
+
+static gint
+rspamd_pidfile_read(const gchar *path, pid_t *pidptr)
+{
+ gchar buf[16], *endptr;
+ gint error, fd, i;
+
+ fd = open(path, O_RDONLY);
+ if (fd == -1)
+ return (errno);
+
+ i = read(fd, buf, sizeof(buf) - 1);
+ error = errno; /* Remember errno in case close() wants to change it. */
+ close(fd);
+ if (i == -1)
+ return error;
+ else if (i == 0)
+ return EAGAIN;
+ buf[i] = '\0';
+
+ *pidptr = strtol(buf, &endptr, 10);
+ if (endptr != &buf[i])
+ return EINVAL;
+
+ return 0;
+}
+
+rspamd_pidfh_t *
+rspamd_pidfile_open(const gchar *path, mode_t mode, pid_t *pidptr)
+{
+ rspamd_pidfh_t *pfh;
+ struct stat sb;
+ gint error, fd, len, count;
+ struct timespec rqtp;
+
+ pfh = g_malloc(sizeof(*pfh));
+ if (pfh == NULL)
+ return NULL;
+
+ if (path == NULL)
+ len = snprintf(pfh->pf_path,
+ sizeof(pfh->pf_path),
+ "/var/run/%s.pid",
+ g_get_prgname());
+ else
+ len = snprintf(pfh->pf_path, sizeof(pfh->pf_path), "%s", path);
+ if (len >= (gint) sizeof(pfh->pf_path)) {
+ g_free(pfh);
+ errno = ENAMETOOLONG;
+ return NULL;
+ }
+
+ /*
+ * Open the PID file and obtain exclusive lock.
+ * We truncate PID file here only to remove old PID immediately,
+ * PID file will be truncated again in pidfile_write(), so
+ * pidfile_write() can be called multiple times.
+ */
+ fd = open(pfh->pf_path, O_WRONLY | O_CREAT | O_TRUNC | O_NONBLOCK, mode);
+ rspamd_file_lock(fd, TRUE);
+ if (fd == -1) {
+ count = 0;
+ rqtp.tv_sec = 0;
+ rqtp.tv_nsec = 5000000;
+ if (errno == EWOULDBLOCK && pidptr != NULL) {
+ again:
+ errno = rspamd_pidfile_read(pfh->pf_path, pidptr);
+ if (errno == 0)
+ errno = EEXIST;
+ else if (errno == EAGAIN) {
+ if (++count <= 3) {
+ nanosleep(&rqtp, 0);
+ goto again;
+ }
+ }
+ }
+ g_free(pfh);
+ return NULL;
+ }
+ /*
+ * Remember file information, so in pidfile_write() we are sure we write
+ * to the proper descriptor.
+ */
+ if (fstat(fd, &sb) == -1) {
+ error = errno;
+ unlink(pfh->pf_path);
+ close(fd);
+ g_free(pfh);
+ errno = error;
+ return NULL;
+ }
+
+ pfh->pf_fd = fd;
+ pfh->pf_dev = sb.st_dev;
+ pfh->pf_ino = sb.st_ino;
+
+ return pfh;
+}
+
+gint rspamd_pidfile_write(rspamd_pidfh_t *pfh)
+{
+ gchar pidstr[16];
+ gint error, fd;
+
+ /*
+ * Check remembered descriptor, so we don't overwrite some other
+ * file if pidfile was closed and descriptor reused.
+ */
+ errno = rspamd_pidfile_verify(pfh);
+ if (errno != 0) {
+ /*
+ * Don't close descriptor, because we are not sure if it's ours.
+ */
+ return -1;
+ }
+ fd = pfh->pf_fd;
+
+ /*
+ * Truncate PID file, so multiple calls of pidfile_write() are allowed.
+ */
+ if (ftruncate(fd, 0) == -1) {
+ error = errno;
+ _rspamd_pidfile_remove(pfh, 0);
+ errno = error;
+ return -1;
+ }
+
+ rspamd_snprintf(pidstr, sizeof(pidstr), "%P", getpid());
+ if (pwrite(fd, pidstr, strlen(pidstr), 0) != (ssize_t) strlen(pidstr)) {
+ error = errno;
+ _rspamd_pidfile_remove(pfh, 0);
+ errno = error;
+ return -1;
+ }
+
+ return 0;
+}
+
+gint rspamd_pidfile_close(rspamd_pidfh_t *pfh)
+{
+ gint error;
+
+ error = rspamd_pidfile_verify(pfh);
+ if (error != 0) {
+ errno = error;
+ return -1;
+ }
+
+ if (close(pfh->pf_fd) == -1)
+ error = errno;
+ g_free(pfh);
+ if (error != 0) {
+ errno = error;
+ return -1;
+ }
+ return 0;
+}
+
+static gint
+_rspamd_pidfile_remove(rspamd_pidfh_t *pfh, gint freeit)
+{
+ gint error;
+
+ error = rspamd_pidfile_verify(pfh);
+ if (error != 0) {
+ errno = error;
+ return -1;
+ }
+
+ if (unlink(pfh->pf_path) == -1)
+ error = errno;
+ if (!rspamd_file_unlock(pfh->pf_fd, FALSE)) {
+ if (error == 0)
+ error = errno;
+ }
+ if (close(pfh->pf_fd) == -1) {
+ if (error == 0)
+ error = errno;
+ }
+ if (freeit)
+ g_free(pfh);
+ else
+ pfh->pf_fd = -1;
+ if (error != 0) {
+ errno = error;
+ return -1;
+ }
+ return 0;
+}
+
+gint rspamd_pidfile_remove(rspamd_pidfh_t *pfh)
+{
+
+ return (_rspamd_pidfile_remove(pfh, 1));
+}
+#endif
+
+/* Replace %r with rcpt value and %f with from value, new string is allocated in pool */
+gchar *
+resolve_stat_filename(rspamd_mempool_t *pool,
+ gchar *pattern,
+ gchar *rcpt,
+ gchar *from)
+{
+ gint need_to_format = 0, len = 0;
+ gint rcptlen, fromlen;
+ gchar *c = pattern, *new, *s;
+
+ if (rcpt) {
+ rcptlen = strlen(rcpt);
+ }
+ else {
+ rcptlen = 0;
+ }
+
+ if (from) {
+ fromlen = strlen(from);
+ }
+ else {
+ fromlen = 0;
+ }
+
+ /* Calculate length */
+ while (*c++) {
+ if (*c == '%' && *(c + 1) == 'r') {
+ len += rcptlen;
+ c += 2;
+ need_to_format = 1;
+ continue;
+ }
+ else if (*c == '%' && *(c + 1) == 'f') {
+ len += fromlen;
+ c += 2;
+ need_to_format = 1;
+ continue;
+ }
+ len++;
+ }
+
+ /* Do not allocate extra memory if we do not need to format string */
+ if (!need_to_format) {
+ return pattern;
+ }
+
+ /* Allocate new string */
+ new = rspamd_mempool_alloc(pool, len);
+ c = pattern;
+ s = new;
+
+ /* Format string */
+ while (*c++) {
+ if (*c == '%' && *(c + 1) == 'r') {
+ c += 2;
+ memcpy(s, rcpt, rcptlen);
+ s += rcptlen;
+ continue;
+ }
+ *s++ = *c;
+ }
+
+ *s = '\0';
+
+ return new;
+}
+
+const gchar *
+rspamd_log_check_time(gdouble start, gdouble end, gint resolution)
+{
+ gdouble diff;
+ static gchar res[64];
+ gchar fmt[32];
+
+ diff = (end - start) * 1000.0;
+
+ rspamd_snprintf(fmt, sizeof(fmt), "%%.%dfms", resolution);
+ rspamd_snprintf(res, sizeof(res), fmt, diff);
+
+ return (const gchar *) res;
+}
+
+
+#ifdef HAVE_FLOCK
+/* Flock version */
+gboolean
+rspamd_file_lock(gint fd, gboolean async)
+{
+ gint flags;
+
+ if (async) {
+ flags = LOCK_EX | LOCK_NB;
+ }
+ else {
+ flags = LOCK_EX;
+ }
+
+ if (flock(fd, flags) == -1) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+rspamd_file_unlock(gint fd, gboolean async)
+{
+ gint flags;
+
+ if (async) {
+ flags = LOCK_UN | LOCK_NB;
+ }
+ else {
+ flags = LOCK_UN;
+ }
+
+ if (flock(fd, flags) == -1) {
+ if (async && errno == EAGAIN) {
+ return FALSE;
+ }
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
+#else /* HAVE_FLOCK */
+/* Fctnl version */
+gboolean
+rspamd_file_lock(gint fd, gboolean async)
+{
+ struct flock fl = {
+ .l_type = F_WRLCK,
+ .l_whence = SEEK_SET,
+ .l_start = 0,
+ .l_len = 0};
+
+ if (fcntl(fd, async ? F_SETLK : F_SETLKW, &fl) == -1) {
+ if (async && (errno == EAGAIN || errno == EACCES)) {
+ return FALSE;
+ }
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+rspamd_file_unlock(gint fd, gboolean async)
+{
+ struct flock fl = {
+ .l_type = F_UNLCK,
+ .l_whence = SEEK_SET,
+ .l_start = 0,
+ .l_len = 0};
+
+ if (fcntl(fd, async ? F_SETLK : F_SETLKW, &fl) == -1) {
+ if (async && (errno == EAGAIN || errno == EACCES)) {
+ return FALSE;
+ }
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
+#endif /* HAVE_FLOCK */
+
+
+#if ((GLIB_MAJOR_VERSION == 2) && (GLIB_MINOR_VERSION < 22))
+void g_ptr_array_unref(GPtrArray *array)
+{
+ g_ptr_array_free(array, TRUE);
+}
+gboolean
+g_int64_equal(gconstpointer v1, gconstpointer v2)
+{
+ return *((const gint64 *) v1) == *((const gint64 *) v2);
+}
+guint g_int64_hash(gconstpointer v)
+{
+ guint64 v64 = *(guint64 *) v;
+
+ return (guint) (v ^ (v >> 32));
+}
+#endif
+#if ((GLIB_MAJOR_VERSION == 2) && (GLIB_MINOR_VERSION < 14))
+void g_queue_clear(GQueue *queue)
+{
+ g_return_if_fail(queue != NULL);
+
+ g_list_free(queue->head);
+ queue->head = queue->tail = NULL;
+ queue->length = 0;
+}
+#endif
+#if ((GLIB_MAJOR_VERSION == 2) && (GLIB_MINOR_VERSION < 30))
+GPtrArray *
+g_ptr_array_new_full(guint reserved_size,
+ GDestroyNotify element_free_func)
+{
+ GPtrArray *array;
+
+ array = g_ptr_array_sized_new(reserved_size);
+ g_ptr_array_set_free_func(array, element_free_func);
+
+ return array;
+}
+#endif
+#if ((GLIB_MAJOR_VERSION == 2) && (GLIB_MINOR_VERSION < 32))
+void g_queue_free_full(GQueue *queue, GDestroyNotify free_func)
+{
+ GList *cur;
+
+ cur = queue->head;
+
+ while (cur) {
+ free_func(cur->data);
+ cur = g_list_next(cur);
+ }
+
+ g_queue_free(queue);
+}
+#endif
+
+#if ((GLIB_MAJOR_VERSION == 2) && (GLIB_MINOR_VERSION < 40))
+void g_ptr_array_insert(GPtrArray *array, gint index_, gpointer data)
+{
+ g_return_if_fail(array);
+ g_return_if_fail(index_ >= -1);
+ g_return_if_fail(index_ <= (gint) array->len);
+
+ g_ptr_array_set_size(array, array->len + 1);
+
+ if (index_ < 0) {
+ index_ = array->len;
+ }
+
+ if (index_ < array->len) {
+ memmove(&(array->pdata[index_ + 1]), &(array->pdata[index_]),
+ (array->len - index_) * sizeof(gpointer));
+ }
+
+ array->pdata[index_] = data;
+}
+#endif
+
+#if ((GLIB_MAJOR_VERSION == 2) && (GLIB_MINOR_VERSION < 32))
+const gchar *
+g_environ_getenv(gchar **envp, const gchar *variable)
+{
+ gsize len;
+ gint i;
+
+ if (envp == NULL) {
+ return NULL;
+ }
+
+ len = strlen(variable);
+
+ for (i = 0; envp[i]; i++) {
+ if (strncmp(envp[i], variable, len) == 0 && envp[i][len] == '=') {
+ return envp[i] + len + 1;
+ }
+ }
+
+ return NULL;
+}
+#endif
+
+gint rspamd_fallocate(gint fd, off_t offset, off_t len)
+{
+#if defined(HAVE_FALLOCATE)
+ return fallocate(fd, 0, offset, len);
+#elif defined(HAVE_POSIX_FALLOCATE)
+ return posix_fallocate(fd, offset, len);
+#else
+ /* Return 0 as nothing can be done on this system */
+ return 0;
+#endif
+}
+
+
+/**
+ * Create new mutex
+ * @return mutex or NULL
+ */
+inline rspamd_mutex_t *
+rspamd_mutex_new(void)
+{
+ rspamd_mutex_t *new;
+
+ new = g_malloc0(sizeof(rspamd_mutex_t));
+#if ((GLIB_MAJOR_VERSION == 2) && (GLIB_MINOR_VERSION > 30))
+ g_mutex_init(&new->mtx);
+#else
+ g_static_mutex_init(&new->mtx);
+#endif
+
+ return new;
+}
+
+/**
+ * Lock mutex
+ * @param mtx
+ */
+inline void
+rspamd_mutex_lock(rspamd_mutex_t *mtx)
+{
+#if ((GLIB_MAJOR_VERSION == 2) && (GLIB_MINOR_VERSION > 30))
+ g_mutex_lock(&mtx->mtx);
+#else
+ g_static_mutex_lock(&mtx->mtx);
+#endif
+}
+
+/**
+ * Unlock mutex
+ * @param mtx
+ */
+inline void
+rspamd_mutex_unlock(rspamd_mutex_t *mtx)
+{
+#if ((GLIB_MAJOR_VERSION == 2) && (GLIB_MINOR_VERSION > 30))
+ g_mutex_unlock(&mtx->mtx);
+#else
+ g_static_mutex_unlock(&mtx->mtx);
+#endif
+}
+
+void rspamd_mutex_free(rspamd_mutex_t *mtx)
+{
+#if ((GLIB_MAJOR_VERSION == 2) && (GLIB_MINOR_VERSION > 30))
+ g_mutex_clear(&mtx->mtx);
+#endif
+ g_free(mtx);
+}
+
+struct rspamd_thread_data {
+ gchar *name;
+ gint id;
+ GThreadFunc func;
+ gpointer data;
+};
+
+static gpointer
+rspamd_thread_func(gpointer ud)
+{
+ struct rspamd_thread_data *td = ud;
+ sigset_t s_mask;
+
+ /* Ignore signals in thread */
+ sigemptyset(&s_mask);
+ sigaddset(&s_mask, SIGINT);
+ sigaddset(&s_mask, SIGHUP);
+ sigaddset(&s_mask, SIGCHLD);
+ sigaddset(&s_mask, SIGUSR1);
+ sigaddset(&s_mask, SIGUSR2);
+ sigaddset(&s_mask, SIGALRM);
+ sigaddset(&s_mask, SIGPIPE);
+
+ pthread_sigmask(SIG_BLOCK, &s_mask, NULL);
+
+ ud = td->func(td->data);
+ g_free(td->name);
+ g_free(td);
+
+ return ud;
+}
+
+struct hash_copy_callback_data {
+ gpointer (*key_copy_func)(gconstpointer data, gpointer ud);
+ gpointer (*value_copy_func)(gconstpointer data, gpointer ud);
+ gpointer ud;
+ GHashTable *dst;
+};
+
+static void
+copy_foreach_callback(gpointer key, gpointer value, gpointer ud)
+{
+ struct hash_copy_callback_data *cb = ud;
+ gpointer nkey, nvalue;
+
+ nkey = cb->key_copy_func ? cb->key_copy_func(key, cb->ud) : (gpointer) key;
+ nvalue =
+ cb->value_copy_func ? cb->value_copy_func(value,
+ cb->ud)
+ : (gpointer) value;
+ g_hash_table_insert(cb->dst, nkey, nvalue);
+}
+/**
+ * Deep copy of one hash table to another
+ * @param src source hash
+ * @param dst destination hash
+ * @param key_copy_func function called to copy or modify keys (or NULL)
+ * @param value_copy_func function called to copy or modify values (or NULL)
+ * @param ud user data for copy functions
+ */
+void rspamd_hash_table_copy(GHashTable *src, GHashTable *dst,
+ gpointer (*key_copy_func)(gconstpointer data, gpointer ud),
+ gpointer (*value_copy_func)(gconstpointer data, gpointer ud),
+ gpointer ud)
+{
+ struct hash_copy_callback_data cb;
+ if (src != NULL && dst != NULL) {
+ cb.key_copy_func = key_copy_func;
+ cb.value_copy_func = value_copy_func;
+ cb.ud = ud;
+ cb.dst = dst;
+ g_hash_table_foreach(src, copy_foreach_callback, &cb);
+ }
+}
+
+static volatile sig_atomic_t saved_signo[NSIG];
+
+static void
+read_pass_tmp_sig_handler(int s)
+{
+
+ saved_signo[s] = 1;
+}
+
+#ifndef _PATH_TTY
+#define _PATH_TTY "/dev/tty"
+#endif
+
+gint rspamd_read_passphrase_with_prompt(const gchar *prompt, gchar *buf, gint size, bool echo, gpointer key)
+{
+#ifdef HAVE_READPASSPHRASE_H
+ int flags = echo ? RPP_ECHO_ON : RPP_ECHO_OFF;
+ if (readpassphrase(prompt, buf, size, flags | RPP_REQUIRE_TTY) == NULL) {
+ return 0;
+ }
+
+ return strlen(buf);
+#else
+ struct sigaction sa, savealrm, saveint, savehup, savequit, saveterm;
+ struct sigaction savetstp, savettin, savettou, savepipe;
+ struct termios term, oterm;
+ gint input, output, i;
+ gchar *end, *p, ch;
+
+restart:
+ if ((input = output = open(_PATH_TTY, O_RDWR)) == -1) {
+ errno = ENOTTY;
+ return 0;
+ }
+
+ (void) fcntl(input, F_SETFD, FD_CLOEXEC);
+
+ /* Turn echo off */
+ if (tcgetattr(input, &oterm) != 0) {
+ close(input);
+ errno = ENOTTY;
+ return 0;
+ }
+
+ memcpy(&term, &oterm, sizeof(term));
+
+ if (!echo) {
+ term.c_lflag &= ~(ECHO | ECHONL);
+ }
+
+ if (tcsetattr(input, TCSAFLUSH, &term) == -1) {
+ errno = ENOTTY;
+ close(input);
+ return 0;
+ }
+
+ g_assert(write(output, prompt, sizeof("Enter passphrase: ") - 1) != -1);
+
+ /* Save the current sighandler */
+ for (i = 0; i < NSIG; i++) {
+ saved_signo[i] = 0;
+ }
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sa.sa_handler = read_pass_tmp_sig_handler;
+ (void) sigaction(SIGALRM, &sa, &savealrm);
+ (void) sigaction(SIGHUP, &sa, &savehup);
+ (void) sigaction(SIGINT, &sa, &saveint);
+ (void) sigaction(SIGPIPE, &sa, &savepipe);
+ (void) sigaction(SIGQUIT, &sa, &savequit);
+ (void) sigaction(SIGTERM, &sa, &saveterm);
+ (void) sigaction(SIGTSTP, &sa, &savetstp);
+ (void) sigaction(SIGTTIN, &sa, &savettin);
+ (void) sigaction(SIGTTOU, &sa, &savettou);
+
+ /* Now read a passphrase */
+ p = buf;
+ end = p + size - 1;
+ while (read(input, &ch, 1) == 1 && ch != '\n' && ch != '\r') {
+ if (p < end) {
+ *p++ = ch;
+ }
+ }
+ *p = '\0';
+ g_assert(write(output, "\n", 1) == 1);
+
+ /* Restore terminal state */
+ if (memcmp(&term, &oterm, sizeof(term)) != 0) {
+ while (tcsetattr(input, TCSAFLUSH, &oterm) == -1 &&
+ errno == EINTR && !saved_signo[SIGTTOU])
+ ;
+ }
+
+ /* Restore signal handlers */
+ (void) sigaction(SIGALRM, &savealrm, NULL);
+ (void) sigaction(SIGHUP, &savehup, NULL);
+ (void) sigaction(SIGINT, &saveint, NULL);
+ (void) sigaction(SIGQUIT, &savequit, NULL);
+ (void) sigaction(SIGPIPE, &savepipe, NULL);
+ (void) sigaction(SIGTERM, &saveterm, NULL);
+ (void) sigaction(SIGTSTP, &savetstp, NULL);
+ (void) sigaction(SIGTTIN, &savettin, NULL);
+ (void) sigaction(SIGTTOU, &savettou, NULL);
+
+ close(input);
+
+ /* Send signals pending */
+ for (i = 0; i < NSIG; i++) {
+ if (saved_signo[i]) {
+ kill(getpid(), i);
+ switch (i) {
+ case SIGTSTP:
+ case SIGTTIN:
+ case SIGTTOU:
+ goto restart;
+ }
+ }
+ }
+
+ return (p - buf);
+#endif
+}
+
+#ifdef HAVE_CLOCK_GETTIME
+#ifdef CLOCK_MONOTONIC_COARSE
+#define RSPAMD_FAST_MONOTONIC_CLOCK CLOCK_MONOTONIC_COARSE
+#elif defined(CLOCK_MONOTONIC_FAST)
+#define RSPAMD_FAST_MONOTONIC_CLOCK CLOCK_MONOTONIC_FAST
+#else
+#define RSPAMD_FAST_MONOTONIC_CLOCK CLOCK_MONOTONIC
+#endif
+#endif
+
+gdouble
+rspamd_get_ticks(gboolean rdtsc_ok)
+{
+ gdouble res;
+
+#ifdef HAVE_RDTSC
+#ifdef __x86_64__
+ guint64 r64;
+
+ if (rdtsc_ok) {
+ __builtin_ia32_lfence();
+ r64 = __rdtsc();
+ /* Preserve lower 52 bits */
+ res = r64 & ((1ULL << 53) - 1);
+ return res;
+ }
+#endif
+#endif
+#ifdef HAVE_CLOCK_GETTIME
+ struct timespec ts;
+ gint clk_id = RSPAMD_FAST_MONOTONIC_CLOCK;
+
+ clock_gettime(clk_id, &ts);
+
+ if (rdtsc_ok) {
+ res = (double) ts.tv_sec * 1e9 + ts.tv_nsec;
+ }
+ else {
+ res = (double) ts.tv_sec + ts.tv_nsec / 1000000000.;
+ }
+#elif defined(__APPLE__)
+ if (rdtsc_ok) {
+ res = mach_absolute_time();
+ }
+ else {
+ res = mach_absolute_time() / 1000000000.;
+ }
+#else
+ struct timeval tv;
+
+ (void) gettimeofday(&tv, NULL);
+ if (rdtsc_ok) {
+ res = (double) ts.tv_sec * 1e9 + tv.tv_usec * 1e3;
+ }
+ else {
+ res = (double) tv.tv_sec + tv.tv_usec / 1000000.;
+ }
+#endif
+
+ return res;
+}
+
+gdouble
+rspamd_get_virtual_ticks(void)
+{
+ gdouble res;
+
+#ifdef HAVE_CLOCK_GETTIME
+ struct timespec ts;
+ static clockid_t cid = (clockid_t) -1;
+ if (cid == (clockid_t) -1) {
+#ifdef HAVE_CLOCK_GETCPUCLOCKID
+ if (clock_getcpuclockid(0, &cid) == -1) {
+#endif
+#ifdef CLOCK_PROCESS_CPUTIME_ID
+ cid = CLOCK_PROCESS_CPUTIME_ID;
+#elif defined(CLOCK_PROF)
+ cid = CLOCK_PROF;
+#else
+ cid = CLOCK_REALTIME;
+#endif
+#ifdef HAVE_CLOCK_GETCPUCLOCKID
+ }
+#endif
+ }
+
+ clock_gettime(cid, &ts);
+ res = (double) ts.tv_sec + ts.tv_nsec / 1000000000.;
+#elif defined(__APPLE__)
+ thread_port_t thread = mach_thread_self();
+
+ mach_msg_type_number_t count = THREAD_BASIC_INFO_COUNT;
+ thread_basic_info_data_t info;
+ if (thread_info(thread, THREAD_BASIC_INFO, (thread_info_t) &info, &count) != KERN_SUCCESS) {
+ return -1;
+ }
+
+ res = info.user_time.seconds + info.system_time.seconds;
+ res += ((gdouble) (info.user_time.microseconds + info.system_time.microseconds)) / 1e6;
+ mach_port_deallocate(mach_task_self(), thread);
+#elif defined(HAVE_RUSAGE_SELF)
+ struct rusage rusage;
+ if (getrusage(RUSAGE_SELF, &rusage) != -1) {
+ res = (double) rusage.ru_utime.tv_sec +
+ (double) rusage.ru_utime.tv_usec / 1000000.0;
+ }
+#else
+ res = clock() / (double) CLOCKS_PER_SEC;
+#endif
+
+ return res;
+}
+
+gdouble
+rspamd_get_calendar_ticks(void)
+{
+ gdouble res;
+#ifdef HAVE_CLOCK_GETTIME
+ struct timespec ts;
+
+ clock_gettime(CLOCK_REALTIME, &ts);
+ res = ts_to_double(&ts);
+#else
+ struct timeval tv;
+
+ if (gettimeofday(&tv, NULL) == 0) {
+ res = tv_to_double(&tv);
+ }
+ else {
+ res = time(NULL);
+ }
+#endif
+
+ return res;
+}
+
+void rspamd_random_hex(gchar *buf, guint64 len)
+{
+ static const gchar hexdigests[16] = "0123456789abcdef";
+ gint64 i;
+
+ g_assert(len > 0);
+
+ ottery_rand_bytes((void *) buf, ceil(len / 2.0));
+
+ for (i = (gint64) len - 1; i >= 0; i -= 2) {
+ buf[i] = hexdigests[buf[i / 2] & 0xf];
+
+ if (i > 0) {
+ buf[i - 1] = hexdigests[(buf[i / 2] >> 4) & 0xf];
+ }
+ }
+}
+
+gint rspamd_shmem_mkstemp(gchar *pattern)
+{
+ gint fd = -1;
+ gchar *nbuf, *xpos;
+ gsize blen;
+
+ xpos = strchr(pattern, 'X');
+
+ if (xpos == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ blen = strlen(pattern);
+ nbuf = g_malloc(blen + 1);
+ rspamd_strlcpy(nbuf, pattern, blen + 1);
+ xpos = nbuf + (xpos - pattern);
+
+ for (;;) {
+ rspamd_random_hex(xpos, blen - (xpos - nbuf));
+
+ fd = shm_open(nbuf, O_RDWR | O_EXCL | O_CREAT, 0600);
+
+ if (fd != -1) {
+ rspamd_strlcpy(pattern, nbuf, blen + 1);
+ break;
+ }
+ else if (errno != EEXIST) {
+ g_free(nbuf);
+
+ return -1;
+ }
+ }
+
+ g_free(nbuf);
+
+ return fd;
+}
+
+void rspamd_ptr_array_free_hard(gpointer p)
+{
+ GPtrArray *ar = (GPtrArray *) p;
+
+ g_ptr_array_free(ar, TRUE);
+}
+
+void rspamd_array_free_hard(gpointer p)
+{
+ GArray *ar = (GArray *) p;
+
+ g_array_free(ar, TRUE);
+}
+
+void rspamd_gstring_free_hard(gpointer p)
+{
+ GString *ar = (GString *) p;
+
+ g_string_free(ar, TRUE);
+}
+
+void rspamd_gerror_free_maybe(gpointer p)
+{
+ GError **err;
+
+ if (p) {
+ err = (GError **) p;
+
+ if (*err) {
+ g_error_free(*err);
+ }
+ }
+}
+
+/*
+ * Openblas creates threads that are not supported by
+ * jemalloc allocator (aside of being bloody stupid). So this hack
+ * is intended to set number of threads to one by default.
+ * FIXME: is it legit to do so in ctor?
+ */
+#ifdef HAVE_OPENBLAS_SET_NUM_THREADS
+extern void openblas_set_num_threads(int num_threads);
+RSPAMD_CONSTRUCTOR(openblas_thread_fix_ctor)
+{
+ openblas_set_num_threads(1);
+}
+#endif
+#ifdef HAVE_BLI_THREAD_SET_NUM_THREADS
+extern void bli_thread_set_num_threads(int num_threads);
+RSPAMD_CONSTRUCTOR(blis_thread_fix_ctor)
+{
+ bli_thread_set_num_threads(1);
+}
+#endif
+
+guint64
+rspamd_hash_seed(void)
+{
+#if 0
+ static guint64 seed;
+
+ if (seed == 0) {
+ seed = ottery_rand_uint64 ();
+ }
+#endif
+
+ /* Proved to be random, I promise! */
+ /*
+ * TODO: discover if it worth to use random seed on run
+ * with ordinary hash function or we need to switch to
+ * siphash1-3 or other slow cooker function...
+ */
+ return 0xabf9727ba290690bULL;
+}
+
+static inline gdouble
+rspamd_double_from_int64(guint64 x)
+{
+ const union {
+ guint64 i;
+ double d;
+ } u = {
+ .i = G_GUINT64_CONSTANT(0x3FF) << 52 | x >> 12};
+
+ return u.d - 1.0;
+}
+
+gdouble
+rspamd_random_double(void)
+{
+ guint64 rnd_int;
+
+ rnd_int = ottery_rand_uint64();
+
+ return rspamd_double_from_int64(rnd_int);
+}
+
+
+static guint64 *
+rspamd_fast_random_seed(void)
+{
+ static guint64 seed;
+
+ if (G_UNLIKELY(seed == 0)) {
+ ottery_rand_bytes((void *) &seed, sizeof(seed));
+ }
+
+ return &seed;
+}
+
+/* wyrand */
+inline uint64_t
+rspamd_random_uint64_fast_seed(uint64_t *seed)
+{
+ *seed += UINT64_C(0xa0761d6478bd642f);
+#ifdef __SIZEOF_INT128__
+#if defined(__aarch64__)
+ uint64_t lo, hi, p = *seed ^ UINT64_C(0xe7037ed1a0b428db), v = *seed;
+ lo = v * p;
+ __asm__("umulh %0, %1, %2"
+ : "=r"(hi)
+ : "r"(v), "r"(p));
+ return lo ^ hi;
+#else
+ __uint128_t t = (__uint128_t) *seed * (*seed ^ UINT64_C(0xe7037ed1a0b428db));
+ return (t >> 64) ^ t;
+#endif
+#else
+ /* Implementation of 64x64->128-bit multiplication by four 32x32->64
+ * bit multiplication. */
+ uint64_t lo, hi, p = *seed ^ UINT64_C(0xe7037ed1a0b428db), v = *seed;
+ uint64_t hv = v >> 32, hp = p >> 32;
+ uint64_t lv = (uint32_t) v, lp = (uint32_t) p;
+ uint64_t rh = hv * hp;
+ uint64_t rm_0 = hv * lp;
+ uint64_t rm_1 = hp * lv;
+ uint64_t rl = lv * lp;
+ uint64_t t;
+
+ /* We could ignore a carry bit here if we did not care about the
+ same hash for 32-bit and 64-bit targets. */
+ t = rl + (rm_0 << 32);
+ lo = t + (rm_1 << 32);
+ hi = rh + (rm_0 >> 32) + (rm_1 >> 32);
+ return lo ^ hi;
+#endif
+}
+
+gdouble
+rspamd_random_double_fast(void)
+{
+ return rspamd_random_double_fast_seed(rspamd_fast_random_seed());
+}
+
+/* xoshiro256+ */
+inline gdouble
+rspamd_random_double_fast_seed(guint64 *seed)
+{
+ return rspamd_double_from_int64(rspamd_random_uint64_fast_seed(seed));
+}
+
+guint64
+rspamd_random_uint64_fast(void)
+{
+ return rspamd_random_uint64_fast_seed(rspamd_fast_random_seed());
+}
+
+void rspamd_random_seed_fast(void)
+{
+ (void) rspamd_fast_random_seed();
+}
+
+gdouble
+rspamd_time_jitter(gdouble in, gdouble jitter)
+{
+ if (jitter == 0) {
+ jitter = in;
+ }
+
+ return in + jitter * rspamd_random_double();
+}
+
+gboolean
+rspamd_constant_memcmp(const void *a, const void *b, gsize len)
+{
+ gsize lena, lenb, i;
+ guint16 d, r = 0, m;
+ guint16 v;
+ const guint8 *aa = (const guint8 *) a,
+ *bb = (const guint8 *) b;
+
+ if (len == 0) {
+ lena = strlen((const char *) a);
+ lenb = strlen((const char *) b);
+
+ if (lena != lenb) {
+ return FALSE;
+ }
+
+ len = lena;
+ }
+
+ for (i = 0; i < len; i++) {
+ v = ((guint16) (guint8) r) + 255;
+ m = v / 256 - 1;
+ d = (guint16) ((int) aa[i] - (int) bb[i]);
+ r |= (d & m);
+ }
+
+ return (((gint32) (guint16) ((guint32) r + 0x8000) - 0x8000) == 0);
+}
+
+int rspamd_file_xopen(const char *fname, int oflags, guint mode,
+ gboolean allow_symlink)
+{
+ struct stat sb;
+ int fd, flags = oflags;
+
+ if (!(oflags & O_CREAT)) {
+ if (lstat(fname, &sb) == -1) {
+
+ if (errno != ENOENT) {
+ return (-1);
+ }
+ }
+ else if (!S_ISREG(sb.st_mode)) {
+ if (S_ISLNK(sb.st_mode)) {
+ if (!allow_symlink) {
+ return -1;
+ }
+ }
+ else {
+ return -1;
+ }
+ }
+ }
+
+#ifdef HAVE_OCLOEXEC
+ flags |= O_CLOEXEC;
+#endif
+
+#ifdef HAVE_ONOFOLLOW
+ if (!allow_symlink) {
+ flags |= O_NOFOLLOW;
+ fd = open(fname, flags, mode);
+ }
+ else {
+ fd = open(fname, flags, mode);
+ }
+#else
+ fd = open(fname, flags, mode);
+#endif
+
+#ifndef HAVE_OCLOEXEC
+ int serrno;
+ if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) {
+ serrno = errno;
+ close(fd);
+ errno = serrno;
+
+ return -1;
+ }
+#endif
+
+ return (fd);
+}
+
+gpointer
+rspamd_file_xmap(const char *fname, guint mode, gsize *size,
+ gboolean allow_symlink)
+{
+ gint fd;
+ struct stat sb;
+ gpointer map;
+
+ g_assert(fname != NULL);
+ g_assert(size != NULL);
+
+ if (mode & PROT_WRITE) {
+ fd = rspamd_file_xopen(fname, O_RDWR, 0, allow_symlink);
+ }
+ else {
+ fd = rspamd_file_xopen(fname, O_RDONLY, 0, allow_symlink);
+ }
+
+ if (fd == -1) {
+ return NULL;
+ }
+
+ if (fstat(fd, &sb) == -1 || !S_ISREG(sb.st_mode)) {
+ close(fd);
+ *size = (gsize) -1;
+
+ return NULL;
+ }
+
+ if (sb.st_size == 0) {
+ close(fd);
+ *size = (gsize) 0;
+
+ return NULL;
+ }
+
+ map = mmap(NULL, sb.st_size, mode, MAP_SHARED, fd, 0);
+ close(fd);
+
+ if (map == MAP_FAILED) {
+ return NULL;
+ }
+
+ *size = sb.st_size;
+
+ return map;
+}
+
+
+gpointer
+rspamd_shmem_xmap(const char *fname, guint mode,
+ gsize *size)
+{
+ gint fd;
+ struct stat sb;
+ gpointer map;
+
+ g_assert(fname != NULL);
+ g_assert(size != NULL);
+
+#ifdef HAVE_SANE_SHMEM
+ if (mode & PROT_WRITE) {
+ fd = shm_open(fname, O_RDWR, 0);
+ }
+ else {
+ fd = shm_open(fname, O_RDONLY, 0);
+ }
+#else
+ if (mode & PROT_WRITE) {
+ fd = open(fname, O_RDWR, 0);
+ }
+ else {
+ fd = open(fname, O_RDONLY, 0);
+ }
+#endif
+
+ if (fd == -1) {
+ return NULL;
+ }
+
+ if (fstat(fd, &sb) == -1) {
+ close(fd);
+
+ return NULL;
+ }
+
+ map = mmap(NULL, sb.st_size, mode, MAP_SHARED, fd, 0);
+ close(fd);
+
+ if (map == MAP_FAILED) {
+ return NULL;
+ }
+
+ *size = sb.st_size;
+
+ return map;
+}
+
+/*
+ * A(x - 0.5)^4 + B(x - 0.5)^3 + C(x - 0.5)^2 + D(x - 0.5)
+ * A = 32,
+ * B = -6
+ * C = -7
+ * D = 3
+ * y = 32(x - 0.5)^4 - 6(x - 0.5)^3 - 7(x - 0.5)^2 + 3(x - 0.5)
+ *
+ * New approach:
+ * y = ((x - bias)*2)^8
+ */
+gdouble
+rspamd_normalize_probability(gdouble x, gdouble bias)
+{
+ gdouble xx;
+
+ xx = (x - bias) * 2.0;
+
+ return pow(xx, 8);
+}
+
+/*
+ * Calculations from musl libc
+ */
+guint64
+rspamd_tm_to_time(const struct tm *tm, glong tz)
+{
+ guint64 result;
+ gboolean is_leap = FALSE;
+ gint leaps, y = tm->tm_year, cycles, rem, centuries;
+ glong offset = (tz / 100) * 3600 + (tz % 100) * 60;
+
+ /* How many seconds in each month from the beginning of the year */
+ static const gint secs_through_month[] = {
+ 0, 31 * 86400, 59 * 86400, 90 * 86400,
+ 120 * 86400, 151 * 86400, 181 * 86400, 212 * 86400,
+ 243 * 86400, 273 * 86400, 304 * 86400, 334 * 86400};
+
+ /* Convert year */
+ if (tm->tm_year - 2ULL <= 136) {
+ leaps = (y - 68) / 4;
+
+ if (!((y - 68) & 3)) {
+ leaps--;
+ is_leap = 1;
+ }
+
+ result = 31536000 * (y - 70) + 86400 * leaps;
+ }
+ else {
+ cycles = (y - 100) / 400;
+ rem = (y - 100) % 400;
+ if (rem < 0) {
+ cycles--;
+ rem += 400;
+ }
+
+ if (!rem) {
+ is_leap = 1;
+ centuries = 0;
+ leaps = 0;
+ }
+ else {
+ if (rem >= 200) {
+ if (rem >= 300) {
+ centuries = 3;
+ rem -= 300;
+ }
+ else {
+ centuries = 2;
+ rem -= 200;
+ }
+ }
+ else {
+ if (rem >= 100) {
+ centuries = 1;
+ rem -= 100;
+ }
+ else {
+ centuries = 0;
+ }
+ }
+
+ if (!rem) {
+ is_leap = 1;
+ leaps = 0;
+ }
+ else {
+ leaps = rem / 4U;
+ rem %= 4U;
+ is_leap = !rem;
+ }
+ }
+
+ leaps += 97 * cycles + 24 * centuries - (gint) is_leap;
+ result = (y - 100) * 31536000LL + leaps * 86400LL + 946684800 + 86400;
+ }
+
+ /* Now convert months to seconds */
+ result += secs_through_month[tm->tm_mon];
+ /* One more day */
+ if (is_leap && tm->tm_mon >= 2) {
+ result += 86400;
+ }
+
+ result += 86400LL * (tm->tm_mday - 1);
+ result += 3600LL * tm->tm_hour;
+ result += 60LL * tm->tm_min;
+ result += tm->tm_sec;
+
+ /* Now apply tz offset */
+ result -= offset;
+
+ return result;
+}
+
+
+void rspamd_gmtime(gint64 ts, struct tm *dest)
+{
+ guint64 days, secs, years;
+ int remdays, remsecs, remyears;
+ int leap_400_cycles, leap_100_cycles, leap_4_cycles;
+ int months;
+ int wday, yday, leap;
+ /* From March */
+ static const uint8_t days_in_month[] = {31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31, 29};
+ static const guint64 leap_epoch = 946684800ULL + 86400 * (31 + 29);
+ static const guint64 days_per_400y = 365 * 400 + 97;
+ static const guint64 days_per_100y = 365 * 100 + 24;
+ static const guint64 days_per_4y = 365 * 4 + 1;
+
+ secs = ts - leap_epoch;
+ days = secs / 86400;
+ remsecs = secs % 86400;
+
+ if (remsecs < 0) {
+ remsecs += 86400;
+ days--;
+ }
+
+ wday = (3 + days) % 7;
+ if (wday < 0) {
+ wday += 7;
+ }
+
+ /* Deal with gregorian adjustments */
+ leap_400_cycles = days / days_per_400y;
+ remdays = days % days_per_400y;
+
+ if (remdays < 0) {
+ remdays += days_per_400y;
+ leap_400_cycles--;
+ }
+
+ leap_100_cycles = remdays / days_per_100y;
+ if (leap_100_cycles == 4) {
+ /* 400 years */
+ leap_100_cycles--;
+ }
+
+ remdays -= leap_100_cycles * days_per_100y;
+
+ leap_4_cycles = remdays / days_per_4y;
+ if (leap_4_cycles == 25) {
+ /* 100 years */
+ leap_4_cycles--;
+ }
+ remdays -= leap_4_cycles * days_per_4y;
+
+ remyears = remdays / 365;
+ if (remyears == 4) {
+ /* Ordinary leap year */
+ remyears--;
+ }
+ remdays -= remyears * 365;
+
+ leap = !remyears && (leap_4_cycles || !leap_100_cycles);
+ yday = remdays + 31 + 28 + leap;
+
+ if (yday >= 365 + leap) {
+ yday -= 365 + leap;
+ }
+
+ years = remyears + 4 * leap_4_cycles + 100 * leap_100_cycles +
+ 400ULL * leap_400_cycles;
+
+ for (months = 0; days_in_month[months] <= remdays; months++) {
+ remdays -= days_in_month[months];
+ }
+
+ if (months >= 10) {
+ months -= 12;
+ years++;
+ }
+
+ dest->tm_year = years + 100;
+ dest->tm_mon = months + 2;
+ dest->tm_mday = remdays + 1;
+ dest->tm_wday = wday;
+ dest->tm_yday = yday;
+
+ dest->tm_hour = remsecs / 3600;
+ dest->tm_min = remsecs / 60 % 60;
+ dest->tm_sec = remsecs % 60;
+#if !defined(__sun)
+ dest->tm_gmtoff = 0;
+ dest->tm_zone = "GMT";
+#endif
+}
+
+void rspamd_localtime(gint64 ts, struct tm *dest)
+{
+ time_t t = ts;
+ localtime_r(&t, dest);
+}
+
+gboolean
+rspamd_fstring_gzip(rspamd_fstring_t **in)
+{
+ z_stream strm;
+ rspamd_fstring_t *buf = *in;
+ int ret;
+ unsigned tmp_remain;
+ unsigned char temp[BUFSIZ];
+
+ memset(&strm, 0, sizeof(strm));
+ ret = deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
+ MAX_WBITS + 16, MAX_MEM_LEVEL - 1, Z_DEFAULT_STRATEGY);
+
+ if (ret != Z_OK) {
+ return FALSE;
+ }
+
+ if (buf->allocated < deflateBound(&strm, buf->len)) {
+ buf = rspamd_fstring_grow(buf, deflateBound(&strm, buf->len));
+ *in = buf;
+ }
+
+ strm.next_in = buf->str;
+ strm.avail_in = buf->len;
+
+ strm.next_out = temp;
+ strm.avail_out = sizeof(temp) > buf->allocated ? buf->allocated : sizeof(temp);
+ ret = deflate(&strm, Z_FINISH);
+ if (ret == Z_STREAM_ERROR) {
+ deflateEnd(&strm);
+ return FALSE;
+ }
+
+ /* Try to compress in-place */
+ tmp_remain = strm.next_out - temp;
+ if (tmp_remain <= (strm.avail_in ? buf->len - strm.avail_in : buf->allocated)) {
+ memcpy(buf->str, temp, tmp_remain);
+ strm.next_out = (unsigned char *) buf->str + tmp_remain;
+ tmp_remain = 0;
+ while (ret == Z_OK) {
+ strm.avail_out = strm.avail_in ? strm.next_in - strm.next_out : ((unsigned char *) buf->str + buf->allocated) - strm.next_out;
+ ret = deflate(&strm, Z_FINISH);
+ }
+ if (ret != Z_BUF_ERROR || strm.avail_in == 0) {
+ buf->len = strm.next_out - (unsigned char *) buf->str;
+ *in = buf;
+ deflateEnd(&strm);
+
+ return ret == Z_STREAM_END;
+ }
+ }
+
+ /*
+ * The case when input and output has caught each other, hold the remaining
+ * in a temporary buffer and compress it separately
+ */
+ unsigned char *hold = g_malloc(strm.avail_in);
+ memcpy(hold, strm.next_in, strm.avail_in);
+ strm.next_in = hold;
+ if (tmp_remain) {
+ memcpy(buf->str, temp, tmp_remain);
+ strm.next_out = (unsigned char *) buf->str + tmp_remain;
+ }
+ strm.avail_out = ((unsigned char *) buf->str + buf->allocated) - strm.next_out;
+ ret = deflate(&strm, Z_FINISH);
+ g_free(hold);
+ buf->len = strm.next_out - (unsigned char *) buf->str;
+ *in = buf;
+ deflateEnd(&strm);
+
+ return ret == Z_STREAM_END;
+}
+
+gboolean
+rspamd_fstring_gunzip(rspamd_fstring_t **in)
+{
+ z_stream strm;
+ rspamd_fstring_t *buf = *in, *out = rspamd_fstring_sized_new((*in)->len);
+ int ret;
+
+ memset(&strm, 0, sizeof(strm));
+ ret = inflateInit2(&strm, MAX_WBITS + 16);
+
+ if (ret != Z_OK) {
+ return FALSE;
+ }
+
+ strm.next_in = buf->str;
+ strm.avail_in = buf->len;
+
+ gsize total_out = 0;
+
+ do {
+ strm.next_out = out->str + total_out;
+ strm.avail_out = out->allocated - total_out;
+
+ ret = inflate(&strm, Z_NO_FLUSH);
+ if (ret != Z_OK && ret != Z_STREAM_END && ret != Z_BUF_ERROR) {
+ break;
+ }
+
+ gsize out_remain = strm.avail_out;
+ total_out = out->allocated - out_remain;
+ if (out_remain == 0 && ret != Z_STREAM_END) {
+ out = rspamd_fstring_grow(out, out->allocated * 2);
+ }
+
+ } while (ret != Z_STREAM_END);
+
+ if (ret == Z_STREAM_END) {
+ *in = out;
+ out->len = total_out;
+ rspamd_fstring_free(buf);
+ }
+ else {
+ /* Revert */
+ *in = buf;
+ rspamd_fstring_free(out);
+ }
+
+ inflateEnd(&strm);
+
+ return ret == Z_STREAM_END;
+}
+
+static gboolean
+rspamd_glob_dir(const gchar *full_path, const gchar *pattern,
+ gboolean recursive, guint rec_len,
+ GPtrArray *res, GError **err)
+{
+ glob_t globbuf;
+ const gchar *path;
+ static gchar pathbuf[PATH_MAX]; /* Static to help recursion */
+ guint i;
+ gint rc;
+ static const guint rec_lim = 16;
+ struct stat st;
+
+ if (rec_len > rec_lim) {
+ g_set_error(err, g_quark_from_static_string("glob"), EOVERFLOW,
+ "maximum nesting is reached: %d", rec_lim);
+
+ return FALSE;
+ }
+
+ memset(&globbuf, 0, sizeof(globbuf));
+
+ if ((rc = glob(full_path, 0, NULL, &globbuf)) != 0) {
+
+ if (rc != GLOB_NOMATCH) {
+ g_set_error(err, g_quark_from_static_string("glob"), errno,
+ "glob %s failed: %s", full_path, strerror(errno));
+ globfree(&globbuf);
+
+ return FALSE;
+ }
+ else {
+ globfree(&globbuf);
+
+ return TRUE;
+ }
+ }
+
+ for (i = 0; i < globbuf.gl_pathc; i++) {
+ path = globbuf.gl_pathv[i];
+
+ if (stat(path, &st) == -1) {
+ if (errno == EPERM || errno == EACCES || errno == ELOOP) {
+ /* Silently ignore */
+ continue;
+ }
+
+ g_set_error(err, g_quark_from_static_string("glob"), errno,
+ "stat %s failed: %s", path, strerror(errno));
+ globfree(&globbuf);
+
+ return FALSE;
+ }
+
+ if (S_ISREG(st.st_mode)) {
+ g_ptr_array_add(res, g_strdup(path));
+ }
+ else if (recursive && S_ISDIR(st.st_mode)) {
+ rspamd_snprintf(pathbuf, sizeof(pathbuf), "%s%c%s",
+ path, G_DIR_SEPARATOR, pattern);
+
+ if (!rspamd_glob_dir(full_path, pattern, recursive, rec_len + 1,
+ res, err)) {
+ globfree(&globbuf);
+
+ return FALSE;
+ }
+ }
+ }
+
+ globfree(&globbuf);
+
+ return TRUE;
+}
+
+GPtrArray *
+rspamd_glob_path(const gchar *dir,
+ const gchar *pattern,
+ gboolean recursive,
+ GError **err)
+{
+ gchar path[PATH_MAX];
+ GPtrArray *res;
+
+ res = g_ptr_array_new_full(32, (GDestroyNotify) g_free);
+ rspamd_snprintf(path, sizeof(path), "%s%c%s", dir, G_DIR_SEPARATOR, pattern);
+
+ if (!rspamd_glob_dir(path, pattern, recursive, 0, res, err)) {
+ g_ptr_array_free(res, TRUE);
+
+ return NULL;
+ }
+
+ return res;
+}
+
+double
+rspamd_set_counter(struct rspamd_counter_data *cd, gdouble value)
+{
+ gdouble cerr;
+
+ /* Cumulative moving average using per-process counter data */
+ if (cd->number == 0) {
+ cd->mean = 0;
+ cd->stddev = 0;
+ }
+
+ cd->mean += (value - cd->mean) / (gdouble) (++cd->number);
+ cerr = (value - cd->mean) * (value - cd->mean);
+ cd->stddev += (cerr - cd->stddev) / (gdouble) (cd->number);
+
+ return cd->mean;
+}
+
+float rspamd_set_counter_ema(struct rspamd_counter_data *cd,
+ float value,
+ float alpha)
+{
+ float diff, incr;
+
+ /* Cumulative moving average using per-process counter data */
+ if (cd->number == 0) {
+ cd->mean = 0;
+ cd->stddev = 0;
+ }
+
+ diff = value - cd->mean;
+ incr = diff * alpha;
+ cd->mean += incr;
+ cd->stddev = (1.0f - alpha) * (cd->stddev + diff * incr);
+ cd->number++;
+
+ return cd->mean;
+}
+
+void rspamd_ptr_array_shuffle(GPtrArray *ar)
+{
+ if (ar->len < 2) {
+ return;
+ }
+
+ guint n = ar->len;
+
+ for (guint i = 0; i < n - 1; i++) {
+ guint j = i + rspamd_random_uint64_fast() % (n - i);
+ gpointer t = g_ptr_array_index(ar, j);
+ g_ptr_array_index(ar, j) = g_ptr_array_index(ar, i);
+ g_ptr_array_index(ar, i) = t;
+ }
+}
+
+float rspamd_sum_floats(float *ar, gsize *nelts)
+{
+ float sum = 0.0f;
+ volatile float c = 0.0f; /* We don't want any optimisations around c */
+ gsize cnt = 0;
+
+ for (gsize i = 0; i < *nelts; i++) {
+ float elt = ar[i];
+
+ if (!isnan(elt)) {
+ cnt++;
+ float y = elt - c;
+ float t = sum + y;
+ c = (t - sum) - y;
+ sum = t;
+ }
+ }
+
+ *nelts = cnt;
+ return sum;
+}
+
+void rspamd_normalize_path_inplace(gchar *path, guint len, gsize *nlen)
+{
+ const gchar *p, *end, *slash = NULL, *dot = NULL;
+ gchar *o;
+ enum {
+ st_normal = 0,
+ st_got_dot,
+ st_got_dot_dot,
+ st_got_slash,
+ st_got_slash_slash,
+ } state = st_normal;
+
+ p = path;
+ end = path + len;
+ o = path;
+
+ while (p < end) {
+ switch (state) {
+ case st_normal:
+ if (G_UNLIKELY(*p == '/')) {
+ state = st_got_slash;
+ slash = p;
+ }
+ else if (G_UNLIKELY(*p == '.')) {
+ state = st_got_dot;
+ dot = p;
+ }
+ else {
+ *o++ = *p;
+ }
+ p++;
+ break;
+ case st_got_slash:
+ if (G_UNLIKELY(*p == '/')) {
+ /* Ignore double slash */
+ *o++ = *p;
+ state = st_got_slash_slash;
+ }
+ else if (G_UNLIKELY(*p == '.')) {
+ dot = p;
+ state = st_got_dot;
+ }
+ else {
+ *o++ = '/';
+ *o++ = *p;
+ slash = NULL;
+ dot = NULL;
+ state = st_normal;
+ }
+ p++;
+ break;
+ case st_got_slash_slash:
+ if (G_LIKELY(*p != '/')) {
+ slash = p - 1;
+ dot = NULL;
+ state = st_normal;
+ continue;
+ }
+ p++;
+ break;
+ case st_got_dot:
+ if (G_UNLIKELY(*p == '/')) {
+ /* Remove any /./ or ./ paths */
+ if (((o > path && *(o - 1) != '/') || (o == path)) && slash) {
+ /* Preserve one slash */
+ *o++ = '/';
+ }
+
+ slash = p;
+ dot = NULL;
+ /* Ignore last slash */
+ state = st_normal;
+ }
+ else if (*p == '.') {
+ /* Double dot character */
+ state = st_got_dot_dot;
+ }
+ else {
+ /* We have something like .some or /.some */
+ if (dot && p > dot) {
+ if (slash == dot - 1 && (o > path && *(o - 1) != '/')) {
+ /* /.blah */
+ memmove(o, slash, p - slash);
+ o += p - slash;
+ }
+ else {
+ memmove(o, dot, p - dot);
+ o += p - dot;
+ }
+ }
+
+ slash = NULL;
+ dot = NULL;
+ state = st_normal;
+ continue;
+ }
+
+ p++;
+ break;
+ case st_got_dot_dot:
+ if (*p == '/') {
+ /* We have something like /../ or ../ */
+ if (slash) {
+ /* We need to remove the last component from o if it is there */
+ if (o > path + 2 && *(o - 1) == '/') {
+ slash = rspamd_memrchr(path, '/', o - path - 2);
+ }
+ else if (o > path + 1) {
+ slash = rspamd_memrchr(path, '/', o - path - 1);
+ }
+ else {
+ slash = NULL;
+ }
+
+ if (slash) {
+ o = (gchar *) slash;
+ }
+ /* Otherwise we keep these dots */
+ slash = p;
+ state = st_got_slash;
+ }
+ else {
+ /* We have something like bla../, so we need to copy it as is */
+ if (o > path && dot && p > dot) {
+ memmove(o, dot, p - dot);
+ o += p - dot;
+ }
+
+ slash = NULL;
+ dot = NULL;
+ state = st_normal;
+ continue;
+ }
+ }
+ else {
+ /* We have something like ..bla or ... */
+ if (slash) {
+ *o++ = '/';
+ }
+
+ if (dot && p > dot) {
+ memmove(o, dot, p - dot);
+ o += p - dot;
+ }
+
+ slash = NULL;
+ dot = NULL;
+ state = st_normal;
+ continue;
+ }
+
+ p++;
+ break;
+ }
+ }
+
+ /* Leftover */
+ switch (state) {
+ case st_got_dot_dot:
+ /* Trailing .. */
+ if (slash) {
+ /* We need to remove the last component from o if it is there */
+ if (o > path + 2 && *(o - 1) == '/') {
+ slash = rspamd_memrchr(path, '/', o - path - 2);
+ }
+ else if (o > path + 1) {
+ slash = rspamd_memrchr(path, '/', o - path - 1);
+ }
+ else {
+ if (o == path) {
+ /* Corner case */
+ *o++ = '/';
+ }
+
+ slash = NULL;
+ }
+
+ if (slash) {
+ /* Remove last / */
+ o = (gchar *) slash;
+ }
+ }
+ else {
+ /* Corner case */
+ if (o == path) {
+ *o++ = '/';
+ }
+ else {
+ if (dot && p > dot) {
+ memmove(o, dot, p - dot);
+ o += p - dot;
+ }
+ }
+ }
+ break;
+ case st_got_dot:
+ if (slash) {
+ /* /. -> must be / */
+ *o++ = '/';
+ }
+ else {
+ if (o > path) {
+ *o++ = '.';
+ }
+ }
+ break;
+ case st_got_slash:
+ *o++ = '/';
+ break;
+ default:
+#if 0
+ if (o > path + 1 && *(o - 1) == '/') {
+ o --;
+ }
+#endif
+ break;
+ }
+
+ if (nlen) {
+ *nlen = (o - path);
+ }
+}