summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorYu Watanabe <watanabe.yu+github@gmail.com>2023-05-26 08:22:03 +0200
committerYu Watanabe <watanabe.yu+github@gmail.com>2023-05-31 23:48:43 +0200
commitabe72100cfc292093153d48a132a5ab1b5f61dd5 (patch)
tree335ff28128c4ebce11c04d4691dd24695d6600ca
parentcalendarspec: rename arguments (diff)
downloadsystemd-abe72100cfc292093153d48a132a5ab1b5f61dd5.tar.xz
systemd-abe72100cfc292093153d48a132a5ab1b5f61dd5.zip
util: introduce memstream-util
There is many pitfalls in using memstream. Let's introduce a wrapper to make us safely use it.
-rw-r--r--src/basic/memstream-util.c75
-rw-r--r--src/basic/memstream-util.h27
-rw-r--r--src/basic/meson.build1
-rw-r--r--src/test/meson.build1
-rw-r--r--src/test/test-memstream-util.c60
5 files changed, 164 insertions, 0 deletions
diff --git a/src/basic/memstream-util.c b/src/basic/memstream-util.c
new file mode 100644
index 0000000000..4e147fd78f
--- /dev/null
+++ b/src/basic/memstream-util.c
@@ -0,0 +1,75 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "alloc-util.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "log.h"
+#include "memstream-util.h"
+
+void memstream_done(MemStream *m) {
+ assert(m);
+
+ /* First, close file stream, as the buffer may be reallocated on close. */
+ safe_fclose(m->f);
+
+ /* Then, free buffer. */
+ free(m->buf);
+}
+
+FILE* memstream_init(MemStream *m) {
+ assert(m);
+ assert(!m->f);
+
+ m->f = open_memstream_unlocked(&m->buf, &m->sz);
+ return m->f;
+}
+
+int memstream_finalize(MemStream *m, char **ret_buf, size_t *ret_size) {
+ int r;
+
+ assert(m);
+ assert(m->f);
+ assert(ret_buf);
+
+ /* Add terminating NUL, so that the output buffer is a valid string. */
+ fputc('\0', m->f);
+
+ r = fflush_and_check(m->f);
+ if (r < 0)
+ return r;
+
+ m->f = safe_fclose(m->f);
+
+ /* On fclose(), the buffer may be reallocated, and may trigger OOM. */
+ if (!m->buf)
+ return -ENOMEM;
+
+ assert(m->sz > 0);
+
+ *ret_buf = TAKE_PTR(m->buf);
+ if (ret_size)
+ *ret_size = m->sz - 1;
+
+ m->sz = 0; /* For safety when the MemStream object will be reused later. */
+ return 0;
+}
+
+int memstream_dump_internal(
+ int level,
+ int error,
+ const char *file,
+ int line,
+ const char *func,
+ MemStream *m) {
+
+ _cleanup_free_ char *buf = NULL;
+ int r;
+
+ assert(m);
+
+ r = memstream_finalize(m, &buf, NULL);
+ if (r < 0)
+ return log_full_errno(level, r, "Failed to flush memstream: %m: %m");
+
+ return log_dump_internal(level, error, file, line, func, buf);
+}
diff --git a/src/basic/memstream-util.h b/src/basic/memstream-util.h
new file mode 100644
index 0000000000..1aa5651bcf
--- /dev/null
+++ b/src/basic/memstream-util.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <stdio.h>
+
+#include "macro.h"
+
+typedef struct MemStream {
+ FILE *f;
+ char *buf;
+ size_t sz;
+} MemStream;
+
+void memstream_done(MemStream *m);
+FILE* memstream_init(MemStream *m);
+int memstream_finalize(MemStream *m, char **ret_buf, size_t *ret_size);
+
+/* This finalizes the passed memstream. */
+int memstream_dump_internal(
+ int level,
+ int error,
+ const char *file,
+ int line,
+ const char *func,
+ MemStream *m);
+#define memstream_dump(level, m) \
+ memstream_dump_internal(level, 0, PROJECT_FILE, __LINE__, __func__, m)
diff --git a/src/basic/meson.build b/src/basic/meson.build
index 9358a40001..5577c6bc78 100644
--- a/src/basic/meson.build
+++ b/src/basic/meson.build
@@ -53,6 +53,7 @@ basic_sources = files(
'memfd-util.c',
'memory-util.c',
'mempool.c',
+ 'memstream-util.c',
'mkdir.c',
'mountpoint-util.c',
'namespace-util.c',
diff --git a/src/test/meson.build b/src/test/meson.build
index d7eb2a857d..84d642bf6e 100644
--- a/src/test/meson.build
+++ b/src/test/meson.build
@@ -115,6 +115,7 @@ simple_tests += files(
'test-memfd-util.c',
'test-memory-util.c',
'test-mempool.c',
+ 'test-memstream-util.c',
'test-mkdir.c',
'test-modhex.c',
'test-mountpoint-util.c',
diff --git a/src/test/test-memstream-util.c b/src/test/test-memstream-util.c
new file mode 100644
index 0000000000..254bdcaa15
--- /dev/null
+++ b/src/test/test-memstream-util.c
@@ -0,0 +1,60 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "memstream-util.h"
+#include "string-util.h"
+#include "tests.h"
+
+TEST(memstream_done) {
+ _cleanup_(memstream_done) MemStream m = {};
+
+ assert_se(memstream_init(&m));
+}
+
+TEST(memstream_empty) {
+ _cleanup_(memstream_done) MemStream m = {};
+ _cleanup_free_ char *buf = NULL;
+ size_t sz;
+
+ assert_se(memstream_init(&m));
+ assert_se(memstream_finalize(&m, &buf, &sz) >= 0);
+ assert_se(streq(buf, ""));
+ assert_se(sz == 0);
+}
+
+TEST(memstream) {
+ _cleanup_(memstream_done) MemStream m = {};
+ _cleanup_free_ char *buf = NULL;
+ size_t sz;
+ FILE *f;
+
+ assert_se(f = memstream_init(&m));
+ fputs("hoge", f);
+ fputs("おはよう!", f);
+ fputs(u8"😀😀😀", f);
+ assert_se(memstream_finalize(&m, &buf, &sz) >= 0);
+ assert_se(streq(buf, u8"hogeおはよう!😀😀😀"));
+ assert_se(sz == strlen(u8"hogeおはよう!😀😀😀"));
+
+ buf = mfree(buf);
+
+ assert_se(f = memstream_init(&m));
+ fputs("second", f);
+ assert_se(memstream_finalize(&m, &buf, &sz) >= 0);
+ assert_se(streq(buf, "second"));
+ assert_se(sz == strlen("second"));
+}
+
+TEST(memstream_dump) {
+ _cleanup_(memstream_done) MemStream m = {};
+ FILE *f;
+
+ assert_se(f = memstream_init(&m));
+ fputs("first", f);
+ assert_se(memstream_dump(LOG_DEBUG, &m) >= 0);
+
+ assert_se(f = memstream_init(&m));
+ fputs("second", f);
+ assert_se(memstream_dump(LOG_DEBUG, &m) >= 0);
+}
+
+DEFINE_TEST_MAIN(LOG_DEBUG);