1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * C++ stream style string builder used in KUnit for building messages. |
4 | * |
5 | * Copyright (C) 2019, Google LLC. |
6 | * Author: Brendan Higgins <brendanhiggins@google.com> |
7 | */ |
8 | |
9 | #include <kunit/static_stub.h> |
10 | #include <kunit/test.h> |
11 | #include <linux/list.h> |
12 | #include <linux/slab.h> |
13 | |
14 | #include "string-stream.h" |
15 | |
16 | |
17 | static struct string_stream_fragment *alloc_string_stream_fragment(int len, gfp_t gfp) |
18 | { |
19 | struct string_stream_fragment *frag; |
20 | |
21 | frag = kzalloc(size: sizeof(*frag), flags: gfp); |
22 | if (!frag) |
23 | return ERR_PTR(error: -ENOMEM); |
24 | |
25 | frag->fragment = kmalloc(size: len, flags: gfp); |
26 | if (!frag->fragment) { |
27 | kfree(objp: frag); |
28 | return ERR_PTR(error: -ENOMEM); |
29 | } |
30 | |
31 | return frag; |
32 | } |
33 | |
34 | static void string_stream_fragment_destroy(struct string_stream_fragment *frag) |
35 | { |
36 | list_del(entry: &frag->node); |
37 | kfree(objp: frag->fragment); |
38 | kfree(objp: frag); |
39 | } |
40 | |
41 | int string_stream_vadd(struct string_stream *stream, |
42 | const char *fmt, |
43 | va_list args) |
44 | { |
45 | struct string_stream_fragment *frag_container; |
46 | int buf_len, result_len; |
47 | va_list args_for_counting; |
48 | |
49 | /* Make a copy because `vsnprintf` could change it */ |
50 | va_copy(args_for_counting, args); |
51 | |
52 | /* Evaluate length of formatted string */ |
53 | buf_len = vsnprintf(NULL, size: 0, fmt, args: args_for_counting); |
54 | |
55 | va_end(args_for_counting); |
56 | |
57 | if (buf_len == 0) |
58 | return 0; |
59 | |
60 | /* Reserve one extra for possible appended newline. */ |
61 | if (stream->append_newlines) |
62 | buf_len++; |
63 | |
64 | /* Need space for null byte. */ |
65 | buf_len++; |
66 | |
67 | frag_container = alloc_string_stream_fragment(len: buf_len, gfp: stream->gfp); |
68 | if (IS_ERR(ptr: frag_container)) |
69 | return PTR_ERR(ptr: frag_container); |
70 | |
71 | if (stream->append_newlines) { |
72 | /* Don't include reserved newline byte in writeable length. */ |
73 | result_len = vsnprintf(buf: frag_container->fragment, size: buf_len - 1, fmt, args); |
74 | |
75 | /* Append newline if necessary. */ |
76 | if (frag_container->fragment[result_len - 1] != '\n') |
77 | result_len = strlcat(p: frag_container->fragment, q: "\n" , avail: buf_len); |
78 | } else { |
79 | result_len = vsnprintf(buf: frag_container->fragment, size: buf_len, fmt, args); |
80 | } |
81 | |
82 | spin_lock(lock: &stream->lock); |
83 | stream->length += result_len; |
84 | list_add_tail(new: &frag_container->node, head: &stream->fragments); |
85 | spin_unlock(lock: &stream->lock); |
86 | |
87 | return 0; |
88 | } |
89 | |
90 | int string_stream_add(struct string_stream *stream, const char *fmt, ...) |
91 | { |
92 | va_list args; |
93 | int result; |
94 | |
95 | va_start(args, fmt); |
96 | result = string_stream_vadd(stream, fmt, args); |
97 | va_end(args); |
98 | |
99 | return result; |
100 | } |
101 | |
102 | void string_stream_clear(struct string_stream *stream) |
103 | { |
104 | struct string_stream_fragment *frag_container, *frag_container_safe; |
105 | |
106 | spin_lock(lock: &stream->lock); |
107 | list_for_each_entry_safe(frag_container, |
108 | frag_container_safe, |
109 | &stream->fragments, |
110 | node) { |
111 | string_stream_fragment_destroy(frag: frag_container); |
112 | } |
113 | stream->length = 0; |
114 | spin_unlock(lock: &stream->lock); |
115 | } |
116 | |
117 | char *string_stream_get_string(struct string_stream *stream) |
118 | { |
119 | struct string_stream_fragment *frag_container; |
120 | size_t buf_len = stream->length + 1; /* +1 for null byte. */ |
121 | char *buf; |
122 | |
123 | buf = kzalloc(size: buf_len, flags: stream->gfp); |
124 | if (!buf) |
125 | return NULL; |
126 | |
127 | spin_lock(lock: &stream->lock); |
128 | list_for_each_entry(frag_container, &stream->fragments, node) |
129 | strlcat(p: buf, q: frag_container->fragment, avail: buf_len); |
130 | spin_unlock(lock: &stream->lock); |
131 | |
132 | return buf; |
133 | } |
134 | |
135 | int string_stream_append(struct string_stream *stream, |
136 | struct string_stream *other) |
137 | { |
138 | const char *other_content; |
139 | int ret; |
140 | |
141 | other_content = string_stream_get_string(stream: other); |
142 | |
143 | if (!other_content) |
144 | return -ENOMEM; |
145 | |
146 | ret = string_stream_add(stream, fmt: other_content); |
147 | kfree(objp: other_content); |
148 | |
149 | return ret; |
150 | } |
151 | |
152 | bool string_stream_is_empty(struct string_stream *stream) |
153 | { |
154 | return list_empty(head: &stream->fragments); |
155 | } |
156 | |
157 | struct string_stream *alloc_string_stream(gfp_t gfp) |
158 | { |
159 | struct string_stream *stream; |
160 | |
161 | stream = kzalloc(size: sizeof(*stream), flags: gfp); |
162 | if (!stream) |
163 | return ERR_PTR(error: -ENOMEM); |
164 | |
165 | stream->gfp = gfp; |
166 | INIT_LIST_HEAD(list: &stream->fragments); |
167 | spin_lock_init(&stream->lock); |
168 | |
169 | return stream; |
170 | } |
171 | |
172 | void string_stream_destroy(struct string_stream *stream) |
173 | { |
174 | KUNIT_STATIC_STUB_REDIRECT(string_stream_destroy, stream); |
175 | |
176 | if (IS_ERR_OR_NULL(ptr: stream)) |
177 | return; |
178 | |
179 | string_stream_clear(stream); |
180 | kfree(objp: stream); |
181 | } |
182 | |
183 | static void resource_free_string_stream(void *p) |
184 | { |
185 | struct string_stream *stream = p; |
186 | |
187 | string_stream_destroy(stream); |
188 | } |
189 | |
190 | struct string_stream *kunit_alloc_string_stream(struct kunit *test, gfp_t gfp) |
191 | { |
192 | struct string_stream *stream; |
193 | |
194 | stream = alloc_string_stream(gfp); |
195 | if (IS_ERR(ptr: stream)) |
196 | return stream; |
197 | |
198 | if (kunit_add_action_or_reset(test, action: resource_free_string_stream, ctx: stream) != 0) |
199 | return ERR_PTR(error: -ENOMEM); |
200 | |
201 | return stream; |
202 | } |
203 | |
204 | void kunit_free_string_stream(struct kunit *test, struct string_stream *stream) |
205 | { |
206 | kunit_release_action(test, action: resource_free_string_stream, ctx: (void *)stream); |
207 | } |
208 | |