summaryrefslogtreecommitdiffstats
path: root/src/libserver/statfile.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libserver/statfile.c')
-rw-r--r--src/libserver/statfile.c927
1 files changed, 927 insertions, 0 deletions
diff --git a/src/libserver/statfile.c b/src/libserver/statfile.c
new file mode 100644
index 000000000..4c1cc13fb
--- /dev/null
+++ b/src/libserver/statfile.c
@@ -0,0 +1,927 @@
+/*
+ * Copyright (c) 2009-2012, Vsevolod Stakhov
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY AUTHOR ''AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#include "statfile.h"
+#include "main.h"
+
+#define RSPAMD_STATFILE_VERSION {'1', '2'}
+#define BACKUP_SUFFIX ".old"
+
+/* Maximum number of statistics files */
+#define STATFILES_MAX 255
+static void statfile_pool_set_block_common (
+ statfile_pool_t * pool, stat_file_t * file,
+ guint32 h1, guint32 h2,
+ time_t t, double value,
+ gboolean from_now);
+
+static gint
+cmpstatfile (const void *a, const void *b)
+{
+ const stat_file_t *s1 = a, *s2 = b;
+
+ return g_ascii_strcasecmp (s1->filename, s2->filename);
+}
+
+/* Convert statfile version 1.0 to statfile version 1.2, saving backup */
+struct stat_file_header_10 {
+ u_char magic[3]; /**< magic signature ('r' 's' 'd') */
+ u_char version[2]; /**< version of statfile */
+ u_char padding[3]; /**< padding */
+ guint64 create_time; /**< create time (time_t->guint64) */
+};
+
+static gboolean
+convert_statfile_10 (stat_file_t * file)
+{
+ gchar *backup_name;
+ struct stat st;
+ struct stat_file_header header = {
+ .magic = {'r', 's', 'd'},
+ .version = RSPAMD_STATFILE_VERSION,
+ .padding = {0, 0, 0},
+ .revision = 0,
+ .rev_time = 0
+ };
+
+
+ /* Format backup name */
+ backup_name = g_strdup_printf ("%s.%s", file->filename, BACKUP_SUFFIX);
+
+ msg_info ("convert old statfile %s to version %c.%c, backup in %s", file->filename,
+ header.version[0], header.version[1], backup_name);
+
+ if (stat (backup_name, &st) != -1) {
+ msg_info ("replace old %s", backup_name);
+ unlink (backup_name);
+ }
+
+ rename (file->filename, backup_name);
+ g_free (backup_name);
+
+ /* XXX: maybe race condition here */
+ unlock_file (file->fd, FALSE);
+ close (file->fd);
+ if ((file->fd = open (file->filename, O_RDWR | O_TRUNC | O_CREAT, S_IWUSR | S_IRUSR)) == -1) {
+ msg_info ("cannot create file %s, error %d, %s", file->filename, errno, strerror (errno));
+ return FALSE;
+ }
+ lock_file (file->fd, FALSE);
+ /* Now make new header and copy it to new file */
+ if (write (file->fd, &header, sizeof (header)) == -1) {
+ msg_info ("cannot write to file %s, error %d, %s", file->filename, errno, strerror (errno));
+ return FALSE;
+ }
+ /* Now write old map to new file */
+ if (write (file->fd, ((u_char *)file->map + sizeof (struct stat_file_header_10)),
+ file->len - sizeof (struct stat_file_header_10)) == -1) {
+ msg_info ("cannot write to file %s, error %d, %s", file->filename, errno, strerror (errno));
+ return FALSE;
+ }
+ /* Unmap old memory and map new */
+ munmap (file->map, file->len);
+ file->len = file->len + sizeof (struct stat_file_header) - sizeof (struct stat_file_header_10);
+#ifdef HAVE_MMAP_NOCORE
+ if ((file->map = mmap (NULL, file->len, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_NOCORE, file->fd, 0)) == MAP_FAILED) {
+#else
+ if ((file->map = mmap (NULL, file->len, PROT_READ | PROT_WRITE, MAP_SHARED, file->fd, 0)) == MAP_FAILED) {
+#endif
+ msg_info ("cannot mmap file %s, error %d, %s", file->filename, errno, strerror (errno));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* Check whether specified file is statistic file and calculate its len in blocks */
+static gint
+statfile_pool_check (stat_file_t * file)
+{
+ struct stat_file *f;
+ gchar *c;
+ static gchar valid_version[] = RSPAMD_STATFILE_VERSION;
+
+
+ if (!file || !file->map) {
+ return -1;
+ }
+
+ if (file->len < sizeof (struct stat_file)) {
+ msg_info ("file %s is too short to be stat file: %z", file->filename, file->len);
+ return -1;
+ }
+
+ f = (struct stat_file *)file->map;
+ c = f->header.magic;
+ /* Check magic and version */
+ if (*c++ != 'r' || *c++ != 's' || *c++ != 'd') {
+ msg_info ("file %s is invalid stat file", file->filename);
+ return -1;
+ }
+ /* Now check version and convert old version to new one (that can be used for sync */
+ if (*c == 1 && *(c + 1) == 0) {
+ if (!convert_statfile_10 (file)) {
+ return -1;
+ }
+ f = (struct stat_file *)file->map;
+ }
+ else if (memcmp (c, valid_version, sizeof (valid_version)) != 0) {
+ /* Unknown version */
+ msg_info ("file %s has invalid version %c.%c", file->filename, '0' + *c, '0' + *(c + 1));
+ return -1;
+ }
+
+ /* Check first section and set new offset */
+ file->cur_section.code = f->section.code;
+ file->cur_section.length = f->section.length;
+ if (file->cur_section.length * sizeof (struct stat_file_block) > file->len) {
+ msg_info ("file %s is truncated: %z, must be %z", file->filename, file->len, file->cur_section.length * sizeof (struct stat_file_block));
+ return -1;
+ }
+ file->seek_pos = sizeof (struct stat_file) - sizeof (struct stat_file_block);
+
+ return 0;
+}
+
+
+statfile_pool_t *
+statfile_pool_new (rspamd_mempool_t *pool, gboolean use_mlock)
+{
+ statfile_pool_t *new;
+
+ new = rspamd_mempool_alloc0 (pool, sizeof (statfile_pool_t));
+ new->pool = rspamd_mempool_new (rspamd_mempool_suggest_size ());
+ new->files = rspamd_mempool_alloc0 (new->pool, STATFILES_MAX * sizeof (stat_file_t));
+ new->lock = rspamd_mempool_get_mutex (new->pool);
+ new->mlock_ok = use_mlock;
+
+ return new;
+}
+
+static stat_file_t *
+statfile_pool_reindex (statfile_pool_t * pool, gchar *filename, size_t old_size, size_t size)
+{
+ gchar *backup;
+ gint fd;
+ stat_file_t *new;
+ u_char *map, *pos;
+ struct stat_file_block *block;
+ struct stat_file_header *header;
+
+ if (size <
+ sizeof (struct stat_file_header) + sizeof (struct stat_file_section) + sizeof (block)) {
+ msg_err ("file %s is too small to carry any statistic: %z", filename, size);
+ return NULL;
+ }
+
+ /* First of all rename old file */
+ rspamd_mempool_lock_mutex (pool->lock);
+
+ backup = g_strconcat (filename, ".old", NULL);
+ if (rename (filename, backup) == -1) {
+ msg_err ("cannot rename %s to %s: %s", filename, backup, strerror (errno));
+ g_free (backup);
+ rspamd_mempool_unlock_mutex (pool->lock);
+ return NULL;
+ }
+
+ rspamd_mempool_unlock_mutex (pool->lock);
+
+ /* Now create new file with required size */
+ if (statfile_pool_create (pool, filename, size) != 0) {
+ msg_err ("cannot create new file");
+ g_free (backup);
+ return NULL;
+ }
+ /* Now open new file and start copying */
+ fd = open (backup, O_RDONLY);
+ new = statfile_pool_open (pool, filename, size, TRUE);
+
+ if (fd == -1 || new == NULL) {
+ msg_err ("cannot open file: %s", strerror (errno));
+ g_free (backup);
+ return NULL;
+ }
+
+ /* Now start reading blocks from old statfile */
+ if ((map = mmap (NULL, old_size, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) {
+ msg_err ("cannot mmap file: %s", strerror (errno));
+ close (fd);
+ g_free (backup);
+ return NULL;
+ }
+
+ pos = map + (sizeof (struct stat_file) - sizeof (struct stat_file_block));
+ while (old_size - (pos - map) >= sizeof (struct stat_file_block)) {
+ block = (struct stat_file_block *)pos;
+ if (block->hash1 != 0 && block->value != 0) {
+ statfile_pool_set_block_common (pool, new, block->hash1, block->hash2, 0, block->value, FALSE);
+ }
+ pos += sizeof (block);
+ }
+
+ header = (struct stat_file_header *)map;
+ statfile_set_revision (new, header->revision, header->rev_time);
+
+ munmap (map, old_size);
+ close (fd);
+ unlink (backup);
+ g_free (backup);
+
+ return new;
+
+}
+
+/*
+ * Pre-load mmaped file into memory
+ */
+static void
+statfile_preload (stat_file_t *file)
+{
+ guint8 *pos, *end;
+ volatile guint8 t;
+ gsize size;
+
+ pos = (guint8 *)file->map;
+ end = (guint8 *)file->map + file->len;
+
+ if (madvise (pos, end - pos, MADV_SEQUENTIAL) == -1) {
+ msg_info ("madvise failed: %s", strerror (errno));
+ }
+ else {
+ /* Load pages of file */
+#ifdef HAVE_GETPAGESIZE
+ size = getpagesize ();
+#else
+ size = sysconf (_SC_PAGESIZE);
+#endif
+ while (pos < end) {
+ t = *pos;
+ (void)t;
+ pos += size;
+ }
+ }
+}
+
+stat_file_t *
+statfile_pool_open (statfile_pool_t * pool, gchar *filename, size_t size, gboolean forced)
+{
+ struct stat st;
+ stat_file_t *new_file;
+
+ if ((new_file = statfile_pool_is_open (pool, filename)) != NULL) {
+ return new_file;
+ }
+
+ if (pool->opened >= STATFILES_MAX - 1) {
+ msg_err ("reached hard coded limit of statfiles opened: %d", STATFILES_MAX);
+ return NULL;
+ }
+
+ if (stat (filename, &st) == -1) {
+ msg_info ("cannot stat file %s, error %s, %d", filename, strerror (errno), errno);
+ return NULL;
+ }
+
+ rspamd_mempool_lock_mutex (pool->lock);
+ if (!forced && labs (size - st.st_size) > (long)sizeof (struct stat_file) * 2
+ && size > sizeof (struct stat_file)) {
+ rspamd_mempool_unlock_mutex (pool->lock);
+ msg_warn ("need to reindex statfile old size: %Hz, new size: %Hz", (size_t)st.st_size, size);
+ return statfile_pool_reindex (pool, filename, st.st_size, size);
+ }
+ else if (size < sizeof (struct stat_file)) {
+ msg_err ("requested to shrink statfile to %Hz but it is too small", size);
+ }
+
+ new_file = &pool->files[pool->opened++];
+ bzero (new_file, sizeof (stat_file_t));
+ if ((new_file->fd = open (filename, O_RDWR)) == -1) {
+ msg_info ("cannot open file %s, error %d, %s", filename, errno, strerror (errno));
+ rspamd_mempool_unlock_mutex (pool->lock);
+ pool->opened--;
+ return NULL;
+ }
+
+ if ((new_file->map = mmap (NULL, st.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, new_file->fd, 0)) == MAP_FAILED) {
+ close (new_file->fd);
+ rspamd_mempool_unlock_mutex (pool->lock);
+ msg_info ("cannot mmap file %s, error %d, %s", filename, errno, strerror (errno));
+ pool->opened--;
+ return NULL;
+
+ }
+
+ rspamd_strlcpy (new_file->filename, filename, sizeof (new_file->filename));
+ new_file->len = st.st_size;
+ /* Try to lock pages in RAM */
+ if (pool->mlock_ok) {
+ if (mlock (new_file->map, new_file->len) == -1) {
+ msg_warn ("mlock of statfile failed, maybe you need to increase RLIMIT_MEMLOCK limit for a process: %s", strerror (errno));
+ pool->mlock_ok = FALSE;
+ }
+ }
+ /* Acquire lock for this operation */
+ lock_file (new_file->fd, FALSE);
+ if (statfile_pool_check (new_file) == -1) {
+ pool->opened--;
+ rspamd_mempool_unlock_mutex (pool->lock);
+ unlock_file (new_file->fd, FALSE);
+ munmap (new_file->map, st.st_size);
+ return NULL;
+ }
+ unlock_file (new_file->fd, FALSE);
+
+ new_file->open_time = time (NULL);
+ new_file->access_time = new_file->open_time;
+ new_file->lock = rspamd_mempool_get_mutex (pool->pool);
+
+ statfile_preload (new_file);
+
+ rspamd_mempool_unlock_mutex (pool->lock);
+
+ return statfile_pool_is_open (pool, filename);
+}
+
+gint
+statfile_pool_close (statfile_pool_t * pool, stat_file_t * file, gboolean keep_sorted)
+{
+ stat_file_t *pos;
+
+ if ((pos = statfile_pool_is_open (pool, file->filename)) == NULL) {
+ msg_info ("file %s is not opened", file->filename);
+ return -1;
+ }
+
+ rspamd_mempool_lock_mutex (pool->lock);
+
+ if (file->map) {
+ msg_info ("syncing statfile %s", file->filename);
+ msync (file->map, file->len, MS_ASYNC);
+ munmap (file->map, file->len);
+ }
+ if (file->fd != -1) {
+ close (file->fd);
+ }
+ /* Move the remain statfiles */
+ memmove (pos, ((guint8 *)pos) + sizeof (stat_file_t),
+ (--pool->opened - (pos - pool->files)) * sizeof (stat_file_t));
+
+ rspamd_mempool_unlock_mutex (pool->lock);
+
+ return 0;
+}
+
+gint
+statfile_pool_create (statfile_pool_t * pool, gchar *filename, size_t size)
+{
+ struct stat_file_header header = {
+ .magic = {'r', 's', 'd'},
+ .version = RSPAMD_STATFILE_VERSION,
+ .padding = {0, 0, 0},
+ .revision = 0,
+ .rev_time = 0,
+ .used_blocks = 0
+ };
+ struct stat_file_section section = {
+ .code = STATFILE_SECTION_COMMON,
+ };
+ struct stat_file_block block = { 0, 0, 0 };
+ gint fd;
+ guint buflen = 0, nblocks;
+ gchar *buf = NULL;
+
+ if (statfile_pool_is_open (pool, filename) != NULL) {
+ msg_info ("file %s is already opened", filename);
+ return 0;
+ }
+
+ if (size <
+ sizeof (struct stat_file_header) + sizeof (struct stat_file_section) + sizeof (block)) {
+ msg_err ("file %s is too small to carry any statistic: %z", filename, size);
+ return -1;
+ }
+
+ rspamd_mempool_lock_mutex (pool->lock);
+ nblocks = (size - sizeof (struct stat_file_header) - sizeof (struct stat_file_section)) / sizeof (struct stat_file_block);
+ header.total_blocks = nblocks;
+
+ if ((fd = open (filename, O_RDWR | O_TRUNC | O_CREAT, S_IWUSR | S_IRUSR)) == -1) {
+ msg_info ("cannot create file %s, error %d, %s", filename, errno, strerror (errno));
+ rspamd_mempool_unlock_mutex (pool->lock);
+ return -1;
+ }
+
+ rspamd_fallocate (fd, 0, sizeof (header) + sizeof (section) + sizeof (block) * nblocks);
+
+ header.create_time = (guint64) time (NULL);
+ if (write (fd, &header, sizeof (header)) == -1) {
+ msg_info ("cannot write header to file %s, error %d, %s", filename, errno, strerror (errno));
+ close (fd);
+ rspamd_mempool_unlock_mutex (pool->lock);
+ return -1;
+ }
+
+ section.length = (guint64) nblocks;
+ if (write (fd, &section, sizeof (section)) == -1) {
+ msg_info ("cannot write section header to file %s, error %d, %s", filename, errno, strerror (errno));
+ close (fd);
+ rspamd_mempool_unlock_mutex (pool->lock);
+ return -1;
+ }
+
+ /* Buffer for write 256 blocks at once */
+ if (nblocks > 256) {
+ buflen = sizeof (block) * 256;
+ buf = g_malloc0 (buflen);
+ }
+
+ while (nblocks) {
+ if (nblocks > 256) {
+ /* Just write buffer */
+ if (write (fd, buf, buflen) == -1) {
+ msg_info ("cannot write blocks buffer to file %s, error %d, %s", filename, errno, strerror (errno));
+ close (fd);
+ rspamd_mempool_unlock_mutex (pool->lock);
+ g_free (buf);
+ return -1;
+ }
+ nblocks -= 256;
+ }
+ else {
+ if (write (fd, &block, sizeof (block)) == -1) {
+ msg_info ("cannot write block to file %s, error %d, %s", filename, errno, strerror (errno));
+ close (fd);
+ if (buf) {
+ g_free (buf);
+ }
+ rspamd_mempool_unlock_mutex (pool->lock);
+ return -1;
+ }
+ nblocks --;
+ }
+ }
+
+ close (fd);
+ rspamd_mempool_unlock_mutex (pool->lock);
+
+ if (buf) {
+ g_free (buf);
+ }
+
+ return 0;
+}
+
+void
+statfile_pool_delete (statfile_pool_t * pool)
+{
+ gint i;
+
+ for (i = 0; i < pool->opened; i++) {
+ statfile_pool_close (pool, &pool->files[i], FALSE);
+ }
+ rspamd_mempool_delete (pool->pool);
+}
+
+void
+statfile_pool_lock_file (statfile_pool_t * pool, stat_file_t * file)
+{
+
+ rspamd_mempool_lock_mutex (file->lock);
+}
+
+void
+statfile_pool_unlock_file (statfile_pool_t * pool, stat_file_t * file)
+{
+
+ rspamd_mempool_unlock_mutex (file->lock);
+}
+
+double
+statfile_pool_get_block (statfile_pool_t * pool, stat_file_t * file, guint32 h1, guint32 h2, time_t now)
+{
+ struct stat_file_block *block;
+ guint i, blocknum;
+ u_char *c;
+
+
+ file->access_time = now;
+ if (!file->map) {
+ return 0;
+ }
+
+ blocknum = h1 % file->cur_section.length;
+ c = (u_char *) file->map + file->seek_pos + blocknum * sizeof (struct stat_file_block);
+ block = (struct stat_file_block *)c;
+
+ for (i = 0; i < CHAIN_LENGTH; i++) {
+ if (i + blocknum >= file->cur_section.length) {
+ break;
+ }
+ if (block->hash1 == h1 && block->hash2 == h2) {
+ return block->value;
+ }
+ c += sizeof (struct stat_file_block);
+ block = (struct stat_file_block *)c;
+ }
+
+
+ return 0;
+}
+
+static void
+statfile_pool_set_block_common (statfile_pool_t * pool, stat_file_t * file, guint32 h1, guint32 h2, time_t t, double value, gboolean from_now)
+{
+ struct stat_file_block *block, *to_expire = NULL;
+ struct stat_file_header *header;
+ guint i, blocknum;
+ u_char *c;
+ double min = G_MAXDOUBLE;
+
+ if (from_now) {
+ file->access_time = t;
+ }
+ if (!file->map) {
+ return;
+ }
+
+ blocknum = h1 % file->cur_section.length;
+ header = (struct stat_file_header *)file->map;
+ c = (u_char *) file->map + file->seek_pos + blocknum * sizeof (struct stat_file_block);
+ block = (struct stat_file_block *)c;
+
+ for (i = 0; i < CHAIN_LENGTH; i++) {
+ if (i + blocknum >= file->cur_section.length) {
+ /* Need to expire some block in chain */
+ msg_info ("chain %ud is full in statfile %s, starting expire", blocknum, file->filename);
+ break;
+ }
+ /* First try to find block in chain */
+ if (block->hash1 == h1 && block->hash2 == h2) {
+ block->value = value;
+ return;
+ }
+ /* Check whether we have a free block in chain */
+ if (block->hash1 == 0 && block->hash2 == 0) {
+ /* Write new block here */
+ msg_debug ("found free block %ud in chain %ud, set h1=%ud, h2=%ud", i, blocknum, h1, h2);
+ block->hash1 = h1;
+ block->hash2 = h2;
+ block->value = value;
+ header->used_blocks ++;
+
+ return;
+ }
+
+ /* Expire block with minimum value otherwise */
+ if (block->value < min) {
+ to_expire = block;
+ min = block->value;
+ }
+ c += sizeof (struct stat_file_block);
+ block = (struct stat_file_block *)c;
+ }
+
+ /* Try expire some block */
+ if (to_expire) {
+ block = to_expire;
+ }
+ else {
+ /* Expire first block in chain */
+ c = (u_char *) file->map + file->seek_pos + blocknum * sizeof (struct stat_file_block);
+ block = (struct stat_file_block *)c;
+ }
+
+ block->hash1 = h1;
+ block->hash2 = h2;
+ block->value = value;
+}
+
+void
+statfile_pool_set_block (statfile_pool_t * pool, stat_file_t * file, guint32 h1, guint32 h2, time_t now, double value)
+{
+ statfile_pool_set_block_common (pool, file, h1, h2, now, value, TRUE);
+}
+
+stat_file_t *
+statfile_pool_is_open (statfile_pool_t * pool, gchar *filename)
+{
+ static stat_file_t f, *ret;
+ rspamd_strlcpy (f.filename, filename, sizeof (f.filename));
+ ret = lfind (&f, pool->files, (size_t *)&pool->opened, sizeof (stat_file_t), cmpstatfile);
+ return ret;
+}
+
+guint32
+statfile_pool_get_section (statfile_pool_t * pool, stat_file_t * file)
+{
+
+ return file->cur_section.code;
+}
+
+gboolean
+statfile_pool_set_section (statfile_pool_t * pool, stat_file_t * file, guint32 code, gboolean from_begin)
+{
+ struct stat_file_section *sec;
+ off_t cur_offset;
+
+
+ /* Try to find section */
+ if (from_begin) {
+ cur_offset = sizeof (struct stat_file_header);
+ }
+ else {
+ cur_offset = file->seek_pos - sizeof (struct stat_file_section);
+ }
+ while (cur_offset < (off_t)file->len) {
+ sec = (struct stat_file_section *)((gchar *)file->map + cur_offset);
+ if (sec->code == code) {
+ file->cur_section.code = code;
+ file->cur_section.length = sec->length;
+ file->seek_pos = cur_offset + sizeof (struct stat_file_section);
+ return TRUE;
+ }
+ cur_offset += sec->length;
+ }
+
+ return FALSE;
+}
+
+gboolean
+statfile_pool_add_section (statfile_pool_t * pool, stat_file_t * file, guint32 code, guint64 length)
+{
+ struct stat_file_section sect;
+ struct stat_file_block block = { 0, 0, 0 };
+
+ if (lseek (file->fd, 0, SEEK_END) == -1) {
+ msg_info ("cannot lseek file %s, error %d, %s", file->filename, errno, strerror (errno));
+ return FALSE;
+ }
+
+ sect.code = code;
+ sect.length = length;
+
+ if (write (file->fd, &sect, sizeof (sect)) == -1) {
+ msg_info ("cannot write block to file %s, error %d, %s", file->filename, errno, strerror (errno));
+ return FALSE;
+ }
+
+ while (length--) {
+ if (write (file->fd, &block, sizeof (block)) == -1) {
+ msg_info ("cannot write block to file %s, error %d, %s", file->filename, errno, strerror (errno));
+ return FALSE;
+ }
+ }
+
+ /* Lock statfile to remap memory */
+ statfile_pool_lock_file (pool, file);
+ munmap (file->map, file->len);
+ fsync (file->fd);
+ file->len += length;
+
+ if ((file->map = mmap (NULL, file->len, PROT_READ | PROT_WRITE, MAP_SHARED, file->fd, 0)) == NULL) {
+ msg_info ("cannot mmap file %s, error %d, %s", file->filename, errno, strerror (errno));
+ return FALSE;
+ }
+ statfile_pool_unlock_file (pool, file);
+
+ return TRUE;
+
+}
+
+guint32
+statfile_get_section_by_name (const gchar *name)
+{
+ if (g_ascii_strcasecmp (name, "common") == 0) {
+ return STATFILE_SECTION_COMMON;
+ }
+ else if (g_ascii_strcasecmp (name, "header") == 0) {
+ return STATFILE_SECTION_HEADERS;
+ }
+ else if (g_ascii_strcasecmp (name, "url") == 0) {
+ return STATFILE_SECTION_URLS;
+ }
+ else if (g_ascii_strcasecmp (name, "regexp") == 0) {
+ return STATFILE_SECTION_REGEXP;
+ }
+
+ return 0;
+}
+
+gboolean
+statfile_set_revision (stat_file_t *file, guint64 rev, time_t time)
+{
+ struct stat_file_header *header;
+
+ if (file == NULL || file->map == NULL) {
+ return FALSE;
+ }
+
+ header = (struct stat_file_header *)file->map;
+
+ header->revision = rev;
+ header->rev_time = time;
+
+ return TRUE;
+}
+
+gboolean
+statfile_inc_revision (stat_file_t *file)
+{
+ struct stat_file_header *header;
+
+ if (file == NULL || file->map == NULL) {
+ return FALSE;
+ }
+
+ header = (struct stat_file_header *)file->map;
+
+ header->revision ++;
+
+ return TRUE;
+}
+
+gboolean
+statfile_get_revision (stat_file_t *file, guint64 *rev, time_t *time)
+{
+ struct stat_file_header *header;
+
+ if (file == NULL || file->map == NULL) {
+ return FALSE;
+ }
+
+ header = (struct stat_file_header *)file->map;
+
+ if (rev != NULL) {
+ *rev = header->revision;
+ }
+ if (time != NULL) {
+ *time = header->rev_time;
+ }
+
+ return TRUE;
+}
+
+guint64
+statfile_get_used_blocks (stat_file_t *file)
+{
+ struct stat_file_header *header;
+
+ if (file == NULL || file->map == NULL) {
+ return (guint64)-1;
+ }
+
+ header = (struct stat_file_header *)file->map;
+
+ return header->used_blocks;
+}
+
+guint64
+statfile_get_total_blocks (stat_file_t *file)
+{
+ struct stat_file_header *header;
+
+ if (file == NULL || file->map == NULL) {
+ return (guint64)-1;
+ }
+
+ header = (struct stat_file_header *)file->map;
+
+ /* If total blocks is 0 we have old version of header, so set total blocks correctly */
+ if (header->total_blocks == 0) {
+ header->total_blocks = file->cur_section.length;
+ }
+
+ return header->total_blocks;
+}
+
+static void
+statfile_pool_invalidate_callback (gint fd, short what, void *ud)
+{
+ statfile_pool_t *pool = ud;
+ stat_file_t *file;
+ gint i;
+
+ msg_info ("invalidating %d statfiles", pool->opened);
+
+ for (i = 0; i < pool->opened; i ++) {
+ file = &pool->files[i];
+ msync (file->map, file->len, MS_ASYNC);
+ }
+
+}
+
+
+void
+statfile_pool_plan_invalidate (statfile_pool_t *pool, time_t seconds, time_t jitter)
+{
+ gboolean pending;
+
+
+ if (pool->invalidate_event != NULL) {
+ pending = evtimer_pending (pool->invalidate_event, NULL);
+ if (pending) {
+ /* Replan event */
+ pool->invalidate_tv.tv_sec = seconds + g_random_int_range (0, jitter);
+ pool->invalidate_tv.tv_usec = 0;
+ evtimer_add (pool->invalidate_event, &pool->invalidate_tv);
+ }
+ }
+ else {
+ pool->invalidate_event = rspamd_mempool_alloc (pool->pool, sizeof (struct event));
+ pool->invalidate_tv.tv_sec = seconds + g_random_int_range (0, jitter);
+ pool->invalidate_tv.tv_usec = 0;
+ evtimer_set (pool->invalidate_event, statfile_pool_invalidate_callback, pool);
+ evtimer_add (pool->invalidate_event, &pool->invalidate_tv);
+ msg_info ("invalidate of statfile pool is planned in %d seconds", (gint)pool->invalidate_tv.tv_sec);
+ }
+}
+
+
+stat_file_t *
+get_statfile_by_symbol (statfile_pool_t *pool, struct classifier_config *ccf,
+ const gchar *symbol, struct statfile **st, gboolean try_create)
+{
+ stat_file_t *res = NULL;
+ GList *cur;
+
+ if (pool == NULL || ccf == NULL || symbol == NULL) {
+ msg_err ("invalid input arguments");
+ return NULL;
+ }
+
+ cur = g_list_first (ccf->statfiles);
+ while (cur) {
+ *st = cur->data;
+ if (strcmp (symbol, (*st)->symbol) == 0) {
+ break;
+ }
+ *st = NULL;
+ cur = g_list_next (cur);
+ }
+ if (*st == NULL) {
+ msg_info ("cannot find statfile with symbol %s", symbol);
+ return NULL;
+ }
+
+ if ((res = statfile_pool_is_open (pool, (*st)->path)) == NULL) {
+ if ((res = statfile_pool_open (pool, (*st)->path, (*st)->size, FALSE)) == NULL) {
+ msg_warn ("cannot open %s", (*st)->path);
+ if (try_create) {
+ if (statfile_pool_create (pool, (*st)->path, (*st)->size) == -1) {
+ msg_err ("cannot create statfile %s", (*st)->path);
+ return NULL;
+ }
+ res = statfile_pool_open (pool, (*st)->path, (*st)->size, FALSE);
+ if (res == NULL) {
+ msg_err ("cannot open statfile %s after creation", (*st)->path);
+ }
+ }
+ }
+ }
+
+ return res;
+}
+
+void
+statfile_pool_lockall (statfile_pool_t *pool)
+{
+ stat_file_t *file;
+ gint i;
+
+ if (pool->mlock_ok) {
+ for (i = 0; i < pool->opened; i ++) {
+ file = &pool->files[i];
+ if (mlock (file->map, file->len) == -1) {
+ msg_warn ("mlock of statfile failed, maybe you need to increase RLIMIT_MEMLOCK limit for a process: %s", strerror (errno));
+ pool->mlock_ok = FALSE;
+ return;
+ }
+ }
+ }
+ /* Do not try to lock if mlock failed */
+}
+