lib: Add the new CbmWriter type

In order to help reduce the complexity of clr-boot-manager, much of which
actually revolves around handling string and malloc failures, we now add
a new CbmWriter type.

Effectively, this is a memory based write stream, designed to allow failure.
It is designed to allow multiple append/printf style invocations, and then
only testing for memory failures when this has all been done. As such this
can vastly simplify the obtuse code used to build up configuration files
within clr-boot-manager (syslinux handler being a perfect example.)

The style of the API usage largely follows that of the CbmMappedFile system,
in attempting to avoid as many copies as possible, by using a pointer to an
anonymous stack struct, and associated open/close methods. Due to the way
that the memstream API works, we require both a free *and* a close.

The close method is responsible for finalizing the buffer, and cleaning up
the associated file resource. This in turn ensures the null terminator is
placed into the final buffer. The free method is then hooked up into the
autofree system to ensure we never leak.

Signed-off-by: Ikey Doherty <michael.i.doherty@intel.com>
This commit is contained in:
Ikey Doherty
2017-01-17 12:37:33 -08:00
committed by William Douglas
parent 4da647b66c
commit 9edd1875fc
4 changed files with 266 additions and 0 deletions
+2
View File
@@ -104,6 +104,8 @@ libcbm_la_SOURCES = \
src/lib/os-release.c \
src/lib/log.h \
src/lib/log.c \
src/lib/writer.h \
src/lib/writer.c \
src/lib/util.h
libcbm_la_CFLAGS = \
+119
View File
@@ -0,0 +1,119 @@
/*
* This file is part of clr-boot-manager.
*
* Copyright © 2017 Intel Corporation
*
* clr-boot-manager is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1
* of the License, or (at your option) any later version.
*/
#define _GNU_SOURCE
#include "writer.h"
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
bool cbm_writer_open(CbmWriter *writer)
{
if (!writer) {
return false;
}
if (writer->buffer || writer->memstream) {
return false;
}
writer->memstream = open_memstream(&writer->buffer, &writer->buffer_n);
if (!writer->memstream) {
writer->error = ENOMEM;
return false;
}
return true;
}
void cbm_writer_free(CbmWriter *self)
{
if (!self) {
return;
}
cbm_writer_close(self);
free(self->buffer);
}
void cbm_writer_close(CbmWriter *self)
{
if (!self) {
return;
}
if (!self->memstream) {
return;
}
fclose(self->memstream);
self->memstream = NULL;
}
void cbm_writer_append(CbmWriter *self, const char *s)
{
if (!self || self->error != 0) {
return;
}
/* Set EBADF as we tried to use a closed memstream */
if (!self->memstream) {
self->error = EBADF;
return;
}
if (fprintf(self->memstream, "%s", s) < 0) {
self->error = errno;
}
}
void cbm_writer_append_printf(CbmWriter *self, const char *fmt, ...)
{
if (!self || self->error != 0) {
return;
}
/* Set EBADF as we tried to use a closed memstream */
if (!self->memstream) {
self->error = EBADF;
return;
}
va_list va;
va_start(va, fmt);
if (vfprintf(self->memstream, fmt, va) < 0) {
self->error = errno;
}
va_end(va);
}
int cbm_writer_error(CbmWriter *self)
{
if (self) {
return self->error;
}
/* Assume enomem with pointer issues */
return ENOMEM;
}
/*
* Editor modelines - https://www.wireshark.org/tools/modelines.html
*
* Local variables:
* c-basic-offset: 8
* tab-width: 8
* indent-tabs-mode: nil
* End:
*
* vi: set shiftwidth=8 tabstop=8 expandtab:
* :indentSize=8:tabSize=8:noTabs=true:
*/
+75
View File
@@ -0,0 +1,75 @@
/*
* This file is part of clr-boot-manager.
*
* Copyright © 2017 Intel Corporation
*
* clr-boot-manager is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1
* of the License, or (at your option) any later version.
*/
#pragma once
#include "nica/util.h"
#define _GNU_SOURCE
typedef struct CbmWriter {
FILE *memstream;
char *buffer;
size_t buffer_n;
int error;
} CbmWriter;
#define CBM_WRITER_INIT &(CbmWriter){ 0 };
/**
* Construct a new CbmWriter
*/
bool cbm_writer_open(CbmWriter *writer);
/**
* Clean up a previously allocated CbmWriter
*/
void cbm_writer_free(CbmWriter *writer);
/**
* Close the writer, which will ensure that the buffer is NULL terminated.
* No more writes are possible after this close.
*/
void cbm_writer_close(CbmWriter *writer);
/**
* Append string to the buffer
*/
void cbm_writer_append(CbmWriter *writer, const char *s);
/**
* Append, printf style, to the buffer
*/
void cbm_writer_append_printf(CbmWriter *writer, const char *fmt, ...)
__attribute__((format(printf, 2, 3)));
/**
* Return an error that may exist in the stream, otherwise 0.
* This allows utilising CbmWriter in a failsafe fashion, and checking the
* error once only.
*/
int cbm_writer_error(CbmWriter *writer);
/* Convenience: Automatically clean up the CbmWriter */
DEF_AUTOFREE(CbmWriter, cbm_writer_free)
/*
* Editor modelines - https://www.wireshark.org/tools/modelines.html
*
* Local variables:
* c-basic-offset: 8
* tab-width: 8
* indent-tabs-mode: nil
* End:
*
* vi: set shiftwidth=8 tabstop=8 expandtab:
* :indentSize=8:tabSize=8:noTabs=true:
*/
+70
View File
@@ -11,6 +11,7 @@
#define _GNU_SOURCE
#include <check.h>
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
@@ -23,6 +24,7 @@
#include "nica/files.h"
#include "util.h"
#include "util.h"
#include "writer.h"
#include "harness.h"
@@ -522,6 +524,68 @@ START_TEST(bootman_timeout_test)
}
END_TEST
START_TEST(bootman_writer_simple_test)
{
autofree(CbmWriter) *writer = CBM_WRITER_INIT;
fail_if(!cbm_writer_open(writer), "Failed to create writer");
cbm_writer_append(writer, "Bob");
cbm_writer_append(writer, "-");
cbm_writer_append(writer, "Jim");
fail_if(cbm_writer_error(writer) != 0, "Error should be 0");
cbm_writer_close(writer);
fail_if(!writer->buffer, "Failed to get writer data");
fail_if(!streq(writer->buffer, "Bob-Jim"), "Returned data is incorrect");
}
END_TEST
START_TEST(bootman_writer_printf_test)
{
autofree(CbmWriter) *writer = CBM_WRITER_INIT;
fail_if(!cbm_writer_open(writer), "Failed to create writer");
cbm_writer_append_printf(writer, "%s = %d", "Jim", 12);
fail_if(cbm_writer_error(writer) != 0, "Error should be 0");
cbm_writer_close(writer);
fail_if(!writer->buffer, "Failed to get writer data");
fail_if(!streq(writer->buffer, "Jim = 12"), "Returned data is incorrect");
}
END_TEST
START_TEST(bootman_writer_mut_test)
{
autofree(CbmWriter) *writer = CBM_WRITER_INIT;
char *data = NULL;
fail_if(!cbm_writer_open(writer), "Failed to create writer");
cbm_writer_append(writer, "One");
cbm_writer_append(writer, "Two");
cbm_writer_close(writer);
data = writer->buffer;
fail_if(!data, "Failed to get data");
/* Should actually result in EBADF */
cbm_writer_append(writer, "Three");
fail_if(cbm_writer_error(writer) == 0, "Error should be zero");
/* The expected behaviour */
fail_if(cbm_writer_error(writer) != EBADF, "Invalid error on closed stream");
cbm_writer_close(writer);
fail_if(!writer->buffer, "Failed to get comparison");
/* Test mutability */
fail_if(!streq(data, "OneTwo"), "Invalid return data");
fail_if(writer->buffer != data, "Pointers mutated between writes");
fail_if(!streq(data, writer->buffer), "Returned data does not match comparison data");
}
END_TEST
static Suite *core_suite(void)
{
Suite *s = NULL;
@@ -547,6 +611,12 @@ static Suite *core_suite(void)
tcase_add_test(tc, bootman_timeout_test);
suite_add_tcase(s, tc);
tc = tcase_create("bootman_writer_functions");
tcase_add_test(tc, bootman_writer_simple_test);
tcase_add_test(tc, bootman_writer_printf_test);
tcase_add_test(tc, bootman_writer_mut_test);
suite_add_tcase(s, tc);
return s;
}