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