148 lines
3.4 KiB
C
Executable file
148 lines
3.4 KiB
C
Executable file
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* C++ stream style string builder used in KUnit for building messages.
|
|
*
|
|
* Copyright (C) 2018, Google LLC.
|
|
* Author: Brendan Higgins <brendanhiggins@google.com>
|
|
*/
|
|
|
|
#include <linux/list.h>
|
|
#include <linux/slab.h>
|
|
#include <test/string-stream.h>
|
|
|
|
static int string_stream_vadd(struct string_stream *this,
|
|
const char *fmt,
|
|
va_list args)
|
|
{
|
|
struct string_stream_fragment *fragment;
|
|
int len;
|
|
va_list args_for_counting;
|
|
unsigned long flags;
|
|
|
|
/* Make a copy because `vsnprintf` could change it */
|
|
va_copy(args_for_counting, args);
|
|
|
|
/* Need space for null byte. */
|
|
len = vsnprintf(NULL, 0, fmt, args_for_counting) + 1;
|
|
|
|
va_end(args_for_counting);
|
|
|
|
fragment = kmalloc(sizeof(*fragment), GFP_KERNEL);
|
|
if (!fragment)
|
|
return -ENOMEM;
|
|
|
|
fragment->fragment = kmalloc(len, GFP_KERNEL);
|
|
if (!fragment->fragment) {
|
|
kfree(fragment);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
len = vsnprintf(fragment->fragment, len, fmt, args);
|
|
spin_lock_irqsave(&this->lock, flags);
|
|
this->length += len;
|
|
list_add_tail(&fragment->node, &this->fragments);
|
|
spin_unlock_irqrestore(&this->lock, flags);
|
|
return 0;
|
|
}
|
|
|
|
static int string_stream_add(struct string_stream *this, const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
int result;
|
|
|
|
va_start(args, fmt);
|
|
result = string_stream_vadd(this, fmt, args);
|
|
va_end(args);
|
|
return result;
|
|
}
|
|
|
|
static void string_stream_clear(struct string_stream *this)
|
|
{
|
|
struct string_stream_fragment *fragment, *fragment_safe;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&this->lock, flags);
|
|
list_for_each_entry_safe(fragment,
|
|
fragment_safe,
|
|
&this->fragments,
|
|
node) {
|
|
list_del(&fragment->node);
|
|
kfree(fragment->fragment);
|
|
kfree(fragment);
|
|
}
|
|
this->length = 0;
|
|
spin_unlock_irqrestore(&this->lock, flags);
|
|
}
|
|
|
|
static char *string_stream_get_string(struct string_stream *this)
|
|
{
|
|
struct string_stream_fragment *fragment;
|
|
size_t buf_len = this->length + 1; /* +1 for null byte. */
|
|
char *buf;
|
|
unsigned long flags;
|
|
|
|
buf = kzalloc(buf_len, GFP_KERNEL);
|
|
if (!buf)
|
|
return NULL;
|
|
|
|
spin_lock_irqsave(&this->lock, flags);
|
|
list_for_each_entry(fragment, &this->fragments, node)
|
|
strlcat(buf, fragment->fragment, buf_len);
|
|
spin_unlock_irqrestore(&this->lock, flags);
|
|
|
|
return buf;
|
|
}
|
|
|
|
static bool string_stream_is_empty(struct string_stream *this)
|
|
{
|
|
bool is_empty;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&this->lock, flags);
|
|
is_empty = list_empty(&this->fragments);
|
|
spin_unlock_irqrestore(&this->lock, flags);
|
|
|
|
return is_empty;
|
|
}
|
|
|
|
void destroy_string_stream(struct string_stream *stream)
|
|
{
|
|
stream->clear(stream);
|
|
kfree(stream);
|
|
}
|
|
|
|
static void string_stream_destroy(struct kref *kref)
|
|
{
|
|
struct string_stream *stream = container_of(kref,
|
|
struct string_stream,
|
|
refcount);
|
|
destroy_string_stream(stream);
|
|
}
|
|
|
|
struct string_stream *new_string_stream(void)
|
|
{
|
|
struct string_stream *stream = kzalloc(sizeof(*stream), GFP_KERNEL);
|
|
|
|
if (!stream)
|
|
return NULL;
|
|
|
|
INIT_LIST_HEAD(&stream->fragments);
|
|
spin_lock_init(&stream->lock);
|
|
kref_init(&stream->refcount);
|
|
stream->add = string_stream_add;
|
|
stream->vadd = string_stream_vadd;
|
|
stream->get_string = string_stream_get_string;
|
|
stream->clear = string_stream_clear;
|
|
stream->is_empty = string_stream_is_empty;
|
|
return stream;
|
|
}
|
|
|
|
void string_stream_get(struct string_stream *stream)
|
|
{
|
|
kref_get(&stream->refcount);
|
|
}
|
|
|
|
int string_stream_put(struct string_stream *stream)
|
|
{
|
|
return kref_put(&stream->refcount, &string_stream_destroy);
|
|
}
|