2019-09-23 12:02:33 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* C + + stream style string builder used in KUnit for building messages .
*
* Copyright ( C ) 2019 , Google LLC .
* Author : Brendan Higgins < brendanhiggins @ google . com >
*/
# include <kunit/test.h>
# include <linux/list.h>
# include <linux/slab.h>
2020-01-07 01:28:18 +03:00
# include "string-stream.h"
2019-09-23 12:02:33 +03:00
struct string_stream_fragment_alloc_context {
struct kunit * test ;
int len ;
gfp_t gfp ;
} ;
static int string_stream_fragment_init ( struct kunit_resource * res ,
void * context )
{
struct string_stream_fragment_alloc_context * ctx = context ;
struct string_stream_fragment * frag ;
frag = kunit_kzalloc ( ctx - > test , sizeof ( * frag ) , ctx - > gfp ) ;
if ( ! frag )
return - ENOMEM ;
frag - > test = ctx - > test ;
frag - > fragment = kunit_kmalloc ( ctx - > test , ctx - > len , ctx - > gfp ) ;
if ( ! frag - > fragment )
return - ENOMEM ;
2020-05-30 00:46:20 +03:00
res - > data = frag ;
2019-09-23 12:02:33 +03:00
return 0 ;
}
static void string_stream_fragment_free ( struct kunit_resource * res )
{
2020-05-30 00:46:20 +03:00
struct string_stream_fragment * frag = res - > data ;
2019-09-23 12:02:33 +03:00
list_del ( & frag - > node ) ;
kunit_kfree ( frag - > test , frag - > fragment ) ;
kunit_kfree ( frag - > test , frag ) ;
}
static struct string_stream_fragment * alloc_string_stream_fragment (
struct kunit * test , int len , gfp_t gfp )
{
struct string_stream_fragment_alloc_context context = {
. test = test ,
. len = len ,
. gfp = gfp
} ;
return kunit_alloc_resource ( test ,
string_stream_fragment_init ,
string_stream_fragment_free ,
gfp ,
& context ) ;
}
static int string_stream_fragment_destroy ( struct string_stream_fragment * frag )
{
2020-05-30 00:46:20 +03:00
return kunit_destroy_resource ( frag - > test ,
2019-09-23 12:02:33 +03:00
kunit_resource_instance_match ,
frag ) ;
}
int string_stream_vadd ( struct string_stream * stream ,
const char * fmt ,
va_list args )
{
struct string_stream_fragment * frag_container ;
int len ;
va_list args_for_counting ;
/* 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 ) ;
frag_container = alloc_string_stream_fragment ( stream - > test ,
len ,
stream - > gfp ) ;
if ( ! frag_container )
return - ENOMEM ;
len = vsnprintf ( frag_container - > fragment , len , fmt , args ) ;
spin_lock ( & stream - > lock ) ;
stream - > length + = len ;
list_add_tail ( & frag_container - > node , & stream - > fragments ) ;
spin_unlock ( & stream - > lock ) ;
return 0 ;
}
int string_stream_add ( struct string_stream * stream , const char * fmt , . . . )
{
va_list args ;
int result ;
va_start ( args , fmt ) ;
result = string_stream_vadd ( stream , fmt , args ) ;
va_end ( args ) ;
return result ;
}
static void string_stream_clear ( struct string_stream * stream )
{
struct string_stream_fragment * frag_container , * frag_container_safe ;
spin_lock ( & stream - > lock ) ;
list_for_each_entry_safe ( frag_container ,
frag_container_safe ,
& stream - > fragments ,
node ) {
string_stream_fragment_destroy ( frag_container ) ;
}
stream - > length = 0 ;
spin_unlock ( & stream - > lock ) ;
}
char * string_stream_get_string ( struct string_stream * stream )
{
struct string_stream_fragment * frag_container ;
size_t buf_len = stream - > length + 1 ; /* +1 for null byte. */
char * buf ;
buf = kunit_kzalloc ( stream - > test , buf_len , stream - > gfp ) ;
if ( ! buf )
return NULL ;
spin_lock ( & stream - > lock ) ;
list_for_each_entry ( frag_container , & stream - > fragments , node )
strlcat ( buf , frag_container - > fragment , buf_len ) ;
spin_unlock ( & stream - > lock ) ;
return buf ;
}
int string_stream_append ( struct string_stream * stream ,
struct string_stream * other )
{
const char * other_content ;
other_content = string_stream_get_string ( other ) ;
if ( ! other_content )
return - ENOMEM ;
return string_stream_add ( stream , other_content ) ;
}
bool string_stream_is_empty ( struct string_stream * stream )
{
return list_empty ( & stream - > fragments ) ;
}
struct string_stream_alloc_context {
struct kunit * test ;
gfp_t gfp ;
} ;
static int string_stream_init ( struct kunit_resource * res , void * context )
{
struct string_stream * stream ;
struct string_stream_alloc_context * ctx = context ;
stream = kunit_kzalloc ( ctx - > test , sizeof ( * stream ) , ctx - > gfp ) ;
if ( ! stream )
return - ENOMEM ;
2020-05-30 00:46:20 +03:00
res - > data = stream ;
2019-09-23 12:02:33 +03:00
stream - > gfp = ctx - > gfp ;
stream - > test = ctx - > test ;
INIT_LIST_HEAD ( & stream - > fragments ) ;
spin_lock_init ( & stream - > lock ) ;
return 0 ;
}
static void string_stream_free ( struct kunit_resource * res )
{
2020-05-30 00:46:20 +03:00
struct string_stream * stream = res - > data ;
2019-09-23 12:02:33 +03:00
string_stream_clear ( stream ) ;
}
struct string_stream * alloc_string_stream ( struct kunit * test , gfp_t gfp )
{
struct string_stream_alloc_context context = {
. test = test ,
. gfp = gfp
} ;
return kunit_alloc_resource ( test ,
string_stream_init ,
string_stream_free ,
gfp ,
& context ) ;
}
int string_stream_destroy ( struct string_stream * stream )
{
2020-05-30 00:46:20 +03:00
return kunit_destroy_resource ( stream - > test ,
2019-09-23 12:02:33 +03:00
kunit_resource_instance_match ,
stream ) ;
}