--- /dev/null
+/*#############################################################################
+# #
+# collecty - A system statistics collection daemon for IPFire #
+# Copyright (C) 2025 IPFire Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "ctx.h"
+#include "buffer.h"
+
+struct collecty_buffer {
+ collecty_ctx* ctx;
+ int nrefs;
+
+ // File Handle
+ FILE* f;
+
+ // Flags
+ enum {
+ COLLECTY_BUFFER_LOCKED = (1 << 0),
+ } flags;
+
+ // Data & Length
+ char* data;
+ size_t length;
+
+ // Pointer
+ off_t p;
+};
+
+static void collecty_buffer_free(collecty_buffer* self) {
+ if (self->f)
+ fclose(self->f);
+ if (self->data)
+ free(self->data);
+ if (self->ctx)
+ collecty_ctx_unref(self->ctx);
+ free(self);
+}
+
+int collecty_buffer_create(collecty_buffer** buffer, collecty_ctx* ctx) {
+ collecty_buffer* self = NULL;
+
+ // Allocate some memory
+ self = calloc(1, sizeof(*self));
+ if (!self)
+ return -errno;
+
+ // Initialize the reference counter
+ self->nrefs = 1;
+
+ // Store a reference to the context
+ self->ctx = collecty_ctx_ref(ctx);
+
+ // Return pointer
+ *buffer = self;
+ return 0;
+}
+
+collecty_buffer* collecty_buffer_ref(collecty_buffer* self) {
+ ++self->nrefs;
+ return self;
+}
+
+collecty_buffer* collecty_buffer_unref(collecty_buffer* self) {
+ if (--self->nrefs > 0)
+ return self;
+
+ collecty_buffer_free(self);
+ return NULL;
+}
+
+static int collecty_buffer_has_flag(collecty_buffer* self, int flag) {
+ return self->flags & flag;
+}
+
+static void collecty_buffer_lock(collecty_buffer* self) {
+ self->flags |= COLLECTY_BUFFER_LOCKED;
+}
+
+static ssize_t __collecty_buffer_read(void* cookie, char* buffer, size_t length) {
+ collecty_buffer* self = cookie;
+
+ // Determine how much data is left
+ size_t bytes_left = self->length - self->p;
+
+ // Cap reading beyond the end
+ if (length > bytes_left)
+ length = bytes_left;
+
+ // We have reached the end already
+ if (!length)
+ return 0;
+
+ // Check if p is in range
+ if (self->p < 0 || self->p > (off_t)self->length) {
+ errno = -ERANGE;
+ return -1;
+ }
+
+ // Copy the data to the buffer
+ memcpy(buffer, self->data + self->p, length);
+
+ // Advance p
+ self->p += length;
+
+ return length;
+}
+
+static int __collecty_buffer_seek(void* cookie, off_t* offset, int whence) {
+ collecty_buffer* self = cookie;
+
+ // Update p
+ switch (whence) {
+ case SEEK_SET:
+ self->p = *offset;
+ break;
+
+ case SEEK_CUR:
+ self->p += *offset;
+ break;
+
+ case SEEK_END:
+ self->p = self->length + *offset;
+ }
+
+ // Update offset
+ *offset = self->p;
+
+ return 0;
+}
+
+static int __collecty_buffer_close(void* cookie) {
+ collecty_buffer* self = cookie;
+
+ // Drop the reference to the buffer
+ collecty_buffer_unref(self);
+
+ return 0;
+}
+
+cookie_io_functions_t collecty_buffer_io_funcs = {
+ .read = __collecty_buffer_read,
+ .seek = __collecty_buffer_seek,
+ .close = __collecty_buffer_close,
+};
+
+FILE* collecty_buffer_fopen(collecty_buffer* self, const char* mode) {
+ // Check if mode was passed
+ if (!mode) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ // We only support read mode
+ if (!(*mode == 'r')) {
+ errno = -ENOTSUP;
+ return NULL;
+ }
+
+ // Lock the buffer
+ collecty_buffer_lock(self);
+
+ // Reset p
+ self->p = 0;
+
+ // Return a file handle
+ return fopencookie(collecty_buffer_ref(self), mode, collecty_buffer_io_funcs);
+}
+
+int collecty_buffer_write(collecty_buffer* self, const char* chunk, size_t length) {
+ char* data = NULL;
+ int r;
+
+ // Cannot write if the buffer is locked
+ if (collecty_buffer_has_flag(self, COLLECTY_BUFFER_LOCKED))
+ return -ENOTSUP;
+
+ // Increase the size of the buffer
+ data = realloc(self->data, self->length + length);
+ if (!data) {
+ ERROR(self->ctx, "Failed to increase the buffer size: %m\n");
+ r = -errno;
+ goto ERROR;
+ }
+
+ // Copy the chunk
+ memcpy(data + self->length, chunk, length);
+
+ // Update the data pointer
+ self->data = data;
+
+ // Increase the size of the buffer
+ self->length += length;
+
+ // Done
+ return 0;
+
+ERROR:
+ if (data)
+ free(data);
+
+ return r;
+}
--- /dev/null
+/*#############################################################################
+# #
+# collecty - A system statistics collection daemon for IPFire #
+# Copyright (C) 2025 IPFire Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+#############################################################################*/
+
+#ifndef COLLECTY_BUFFER_H
+#define COLLECTY_BUFFER_H
+
+#include <stdio.h>
+
+typedef struct collecty_buffer collecty_buffer;
+
+#include "ctx.h"
+
+int collecty_buffer_create(collecty_buffer** buffer, collecty_ctx* ctx);
+
+collecty_buffer* collecty_buffer_ref(collecty_buffer* self);
+collecty_buffer* collecty_buffer_unref(collecty_buffer* self);
+
+FILE* collecty_buffer_fopen(collecty_buffer* self, const char* mode);
+
+int collecty_buffer_write(collecty_buffer* self, const char* chunk, size_t length);
+
+#endif /* COLLECTY_BUFFER_H */