1d1fadef1SBrendan Higgins // SPDX-License-Identifier: GPL-2.0
2d1fadef1SBrendan Higgins /*
3d1fadef1SBrendan Higgins * C++ stream style string builder used in KUnit for building messages.
4d1fadef1SBrendan Higgins *
5d1fadef1SBrendan Higgins * Copyright (C) 2019, Google LLC.
6d1fadef1SBrendan Higgins * Author: Brendan Higgins <brendanhiggins@google.com>
7d1fadef1SBrendan Higgins */
8d1fadef1SBrendan Higgins
9d1fadef1SBrendan Higgins #include <kunit/test.h>
10d1fadef1SBrendan Higgins #include <linux/list.h>
11d1fadef1SBrendan Higgins #include <linux/slab.h>
12d1fadef1SBrendan Higgins
13109fb06fSAlan Maguire #include "string-stream.h"
14109fb06fSAlan Maguire
15d1fadef1SBrendan Higgins
alloc_string_stream_fragment(struct kunit * test,int len,gfp_t gfp)16d1fadef1SBrendan Higgins static struct string_stream_fragment *alloc_string_stream_fragment(
17d1fadef1SBrendan Higgins struct kunit *test, int len, gfp_t gfp)
18d1fadef1SBrendan Higgins {
1978b1c658SDavid Gow struct string_stream_fragment *frag;
20d1fadef1SBrendan Higgins
2178b1c658SDavid Gow frag = kunit_kzalloc(test, sizeof(*frag), gfp);
2278b1c658SDavid Gow if (!frag)
2378b1c658SDavid Gow return ERR_PTR(-ENOMEM);
2478b1c658SDavid Gow
2578b1c658SDavid Gow frag->fragment = kunit_kmalloc(test, len, gfp);
26*93ef8305SYoungJun.park if (!frag->fragment) {
27*93ef8305SYoungJun.park kunit_kfree(test, frag);
2878b1c658SDavid Gow return ERR_PTR(-ENOMEM);
29*93ef8305SYoungJun.park }
3078b1c658SDavid Gow
3178b1c658SDavid Gow return frag;
32d1fadef1SBrendan Higgins }
33d1fadef1SBrendan Higgins
string_stream_fragment_destroy(struct kunit * test,struct string_stream_fragment * frag)344db4598bSDaniel Latypov static void string_stream_fragment_destroy(struct kunit *test,
354db4598bSDaniel Latypov struct string_stream_fragment *frag)
36d1fadef1SBrendan Higgins {
3778b1c658SDavid Gow list_del(&frag->node);
384db4598bSDaniel Latypov kunit_kfree(test, frag->fragment);
394db4598bSDaniel Latypov kunit_kfree(test, frag);
40d1fadef1SBrendan Higgins }
41d1fadef1SBrendan Higgins
string_stream_vadd(struct string_stream * stream,const char * fmt,va_list args)42d1fadef1SBrendan Higgins int string_stream_vadd(struct string_stream *stream,
43d1fadef1SBrendan Higgins const char *fmt,
44d1fadef1SBrendan Higgins va_list args)
45d1fadef1SBrendan Higgins {
46d1fadef1SBrendan Higgins struct string_stream_fragment *frag_container;
47d1fadef1SBrendan Higgins int len;
48d1fadef1SBrendan Higgins va_list args_for_counting;
49d1fadef1SBrendan Higgins
50d1fadef1SBrendan Higgins /* Make a copy because `vsnprintf` could change it */
51d1fadef1SBrendan Higgins va_copy(args_for_counting, args);
52d1fadef1SBrendan Higgins
53d1fadef1SBrendan Higgins /* Need space for null byte. */
54d1fadef1SBrendan Higgins len = vsnprintf(NULL, 0, fmt, args_for_counting) + 1;
55d1fadef1SBrendan Higgins
56d1fadef1SBrendan Higgins va_end(args_for_counting);
57d1fadef1SBrendan Higgins
58d1fadef1SBrendan Higgins frag_container = alloc_string_stream_fragment(stream->test,
59d1fadef1SBrendan Higgins len,
60d1fadef1SBrendan Higgins stream->gfp);
6161888776SDan Carpenter if (IS_ERR(frag_container))
6261888776SDan Carpenter return PTR_ERR(frag_container);
63d1fadef1SBrendan Higgins
64d1fadef1SBrendan Higgins len = vsnprintf(frag_container->fragment, len, fmt, args);
65d1fadef1SBrendan Higgins spin_lock(&stream->lock);
66d1fadef1SBrendan Higgins stream->length += len;
67d1fadef1SBrendan Higgins list_add_tail(&frag_container->node, &stream->fragments);
68d1fadef1SBrendan Higgins spin_unlock(&stream->lock);
69d1fadef1SBrendan Higgins
70d1fadef1SBrendan Higgins return 0;
71d1fadef1SBrendan Higgins }
72d1fadef1SBrendan Higgins
string_stream_add(struct string_stream * stream,const char * fmt,...)73d1fadef1SBrendan Higgins int string_stream_add(struct string_stream *stream, const char *fmt, ...)
74d1fadef1SBrendan Higgins {
75d1fadef1SBrendan Higgins va_list args;
76d1fadef1SBrendan Higgins int result;
77d1fadef1SBrendan Higgins
78d1fadef1SBrendan Higgins va_start(args, fmt);
79d1fadef1SBrendan Higgins result = string_stream_vadd(stream, fmt, args);
80d1fadef1SBrendan Higgins va_end(args);
81d1fadef1SBrendan Higgins
82d1fadef1SBrendan Higgins return result;
83d1fadef1SBrendan Higgins }
84d1fadef1SBrendan Higgins
string_stream_clear(struct string_stream * stream)85d1fadef1SBrendan Higgins static void string_stream_clear(struct string_stream *stream)
86d1fadef1SBrendan Higgins {
87d1fadef1SBrendan Higgins struct string_stream_fragment *frag_container, *frag_container_safe;
88d1fadef1SBrendan Higgins
89d1fadef1SBrendan Higgins spin_lock(&stream->lock);
90d1fadef1SBrendan Higgins list_for_each_entry_safe(frag_container,
91d1fadef1SBrendan Higgins frag_container_safe,
92d1fadef1SBrendan Higgins &stream->fragments,
93d1fadef1SBrendan Higgins node) {
944db4598bSDaniel Latypov string_stream_fragment_destroy(stream->test, frag_container);
95d1fadef1SBrendan Higgins }
96d1fadef1SBrendan Higgins stream->length = 0;
97d1fadef1SBrendan Higgins spin_unlock(&stream->lock);
98d1fadef1SBrendan Higgins }
99d1fadef1SBrendan Higgins
string_stream_get_string(struct string_stream * stream)100d1fadef1SBrendan Higgins char *string_stream_get_string(struct string_stream *stream)
101d1fadef1SBrendan Higgins {
102d1fadef1SBrendan Higgins struct string_stream_fragment *frag_container;
103d1fadef1SBrendan Higgins size_t buf_len = stream->length + 1; /* +1 for null byte. */
104d1fadef1SBrendan Higgins char *buf;
105d1fadef1SBrendan Higgins
106d1fadef1SBrendan Higgins buf = kunit_kzalloc(stream->test, buf_len, stream->gfp);
107d1fadef1SBrendan Higgins if (!buf)
108d1fadef1SBrendan Higgins return NULL;
109d1fadef1SBrendan Higgins
110d1fadef1SBrendan Higgins spin_lock(&stream->lock);
111d1fadef1SBrendan Higgins list_for_each_entry(frag_container, &stream->fragments, node)
112d1fadef1SBrendan Higgins strlcat(buf, frag_container->fragment, buf_len);
113d1fadef1SBrendan Higgins spin_unlock(&stream->lock);
114d1fadef1SBrendan Higgins
115d1fadef1SBrendan Higgins return buf;
116d1fadef1SBrendan Higgins }
117d1fadef1SBrendan Higgins
string_stream_append(struct string_stream * stream,struct string_stream * other)118d1fadef1SBrendan Higgins int string_stream_append(struct string_stream *stream,
119d1fadef1SBrendan Higgins struct string_stream *other)
120d1fadef1SBrendan Higgins {
121d1fadef1SBrendan Higgins const char *other_content;
122d1fadef1SBrendan Higgins
123d1fadef1SBrendan Higgins other_content = string_stream_get_string(other);
124d1fadef1SBrendan Higgins
125d1fadef1SBrendan Higgins if (!other_content)
126d1fadef1SBrendan Higgins return -ENOMEM;
127d1fadef1SBrendan Higgins
128d1fadef1SBrendan Higgins return string_stream_add(stream, other_content);
129d1fadef1SBrendan Higgins }
130d1fadef1SBrendan Higgins
string_stream_is_empty(struct string_stream * stream)131d1fadef1SBrendan Higgins bool string_stream_is_empty(struct string_stream *stream)
132d1fadef1SBrendan Higgins {
133d1fadef1SBrendan Higgins return list_empty(&stream->fragments);
134d1fadef1SBrendan Higgins }
135d1fadef1SBrendan Higgins
alloc_string_stream(struct kunit * test,gfp_t gfp)13678b1c658SDavid Gow struct string_stream *alloc_string_stream(struct kunit *test, gfp_t gfp)
137d1fadef1SBrendan Higgins {
138d1fadef1SBrendan Higgins struct string_stream *stream;
139d1fadef1SBrendan Higgins
14078b1c658SDavid Gow stream = kunit_kzalloc(test, sizeof(*stream), gfp);
141d1fadef1SBrendan Higgins if (!stream)
14278b1c658SDavid Gow return ERR_PTR(-ENOMEM);
143d1fadef1SBrendan Higgins
14478b1c658SDavid Gow stream->gfp = gfp;
14578b1c658SDavid Gow stream->test = test;
146d1fadef1SBrendan Higgins INIT_LIST_HEAD(&stream->fragments);
147d1fadef1SBrendan Higgins spin_lock_init(&stream->lock);
148d1fadef1SBrendan Higgins
14978b1c658SDavid Gow return stream;
150d1fadef1SBrendan Higgins }
151d1fadef1SBrendan Higgins
string_stream_destroy(struct string_stream * stream)15278b1c658SDavid Gow void string_stream_destroy(struct string_stream *stream)
153d1fadef1SBrendan Higgins {
154d1fadef1SBrendan Higgins string_stream_clear(stream);
155d1fadef1SBrendan Higgins }
156