/*      $NetBSD: libdm-report.c,v 1.1.1.3 2009/12/02 00:26:08 haad Exp $        */

/*
* Copyright (C) 2002-2004 Sistina Software, Inc. All rights reserved.
* Copyright (C) 2004-2007 Red Hat, Inc. All rights reserved.
*
* This file is part of the device-mapper userspace tools.
*
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU Lesser General Public License v.2.1.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include "dmlib.h"

#include <ctype.h>

/*
* Internal flags
*/
#define RH_SORT_REQUIRED        0x00000100
#define RH_HEADINGS_PRINTED     0x00000200

struct dm_report {
       struct dm_pool *mem;

       uint32_t report_types;
       const char *output_field_name_prefix;
       const char *field_prefix;
       uint32_t flags;
       const char *separator;

       uint32_t keys_count;

       /* Ordered list of fields needed for this report */
       struct dm_list field_props;

       /* Rows of report data */
       struct dm_list rows;

       /* Array of field definitions */
       const struct dm_report_field_type *fields;
       const struct dm_report_object_type *types;

       /* To store caller private data */
       void *private;
};

/*
* Internal per-field flags
*/
#define FLD_HIDDEN      0x00000100
#define FLD_SORT_KEY    0x00000200
#define FLD_ASCENDING   0x00000400
#define FLD_DESCENDING  0x00000800

struct field_properties {
       struct dm_list list;
       uint32_t field_num;
       uint32_t sort_posn;
       int32_t width;
       const struct dm_report_object_type *type;
       uint32_t flags;
};

/*
* Report data field
*/
struct dm_report_field {
       struct dm_list list;
       struct field_properties *props;

       const char *report_string;      /* Formatted ready for display */
       const void *sort_value;         /* Raw value for sorting */
};

struct row {
       struct dm_list list;
       struct dm_report *rh;
       struct dm_list fields;                    /* Fields in display order */
       struct dm_report_field *(*sort_fields)[]; /* Fields in sort order */
};

static const struct dm_report_object_type *_find_type(struct dm_report *rh,
                                                     uint32_t report_type)
{
       const struct dm_report_object_type *t;

       for (t = rh->types; t->data_fn; t++)
               if (t->id == report_type)
                       return t;

       return NULL;
}

/*
* Data-munging functions to prepare each data type for display and sorting
*/

int dm_report_field_string(struct dm_report *rh,
                          struct dm_report_field *field, const char **data)
{
       char *repstr;

       if (!(repstr = dm_pool_strdup(rh->mem, *data))) {
               log_error("dm_report_field_string: dm_pool_strdup failed");
               return 0;
       }

       field->report_string = repstr;
       field->sort_value = (const void *) field->report_string;

       return 1;
}

int dm_report_field_int(struct dm_report *rh,
                       struct dm_report_field *field, const int *data)
{
       const int value = *data;
       uint64_t *sortval;
       char *repstr;

       if (!(repstr = dm_pool_zalloc(rh->mem, 13))) {
               log_error("dm_report_field_int: dm_pool_alloc failed");
               return 0;
       }

       if (!(sortval = dm_pool_alloc(rh->mem, sizeof(int64_t)))) {
               log_error("dm_report_field_int: dm_pool_alloc failed");
               return 0;
       }

       if (dm_snprintf(repstr, 12, "%d", value) < 0) {
               log_error("dm_report_field_int: int too big: %d", value);
               return 0;
       }

       *sortval = (const uint64_t) value;
       field->sort_value = sortval;
       field->report_string = repstr;

       return 1;
}

int dm_report_field_uint32(struct dm_report *rh,
                          struct dm_report_field *field, const uint32_t *data)
{
       const uint32_t value = *data;
       uint64_t *sortval;
       char *repstr;

       if (!(repstr = dm_pool_zalloc(rh->mem, 12))) {
               log_error("dm_report_field_uint32: dm_pool_alloc failed");
               return 0;
       }

       if (!(sortval = dm_pool_alloc(rh->mem, sizeof(uint64_t)))) {
               log_error("dm_report_field_uint32: dm_pool_alloc failed");
               return 0;
       }

       if (dm_snprintf(repstr, 11, "%u", value) < 0) {
               log_error("dm_report_field_uint32: uint32 too big: %u", value);
               return 0;
       }

       *sortval = (const uint64_t) value;
       field->sort_value = sortval;
       field->report_string = repstr;

       return 1;
}

int dm_report_field_int32(struct dm_report *rh,
                         struct dm_report_field *field, const int32_t *data)
{
       const int32_t value = *data;
       uint64_t *sortval;
       char *repstr;

       if (!(repstr = dm_pool_zalloc(rh->mem, 13))) {
               log_error("dm_report_field_int32: dm_pool_alloc failed");
               return 0;
       }

       if (!(sortval = dm_pool_alloc(rh->mem, sizeof(int64_t)))) {
               log_error("dm_report_field_int32: dm_pool_alloc failed");
               return 0;
       }

       if (dm_snprintf(repstr, 12, "%d", value) < 0) {
               log_error("dm_report_field_int32: int32 too big: %d", value);
               return 0;
       }

       *sortval = (const uint64_t) value;
       field->sort_value = sortval;
       field->report_string = repstr;

       return 1;
}

int dm_report_field_uint64(struct dm_report *rh,
                          struct dm_report_field *field, const uint64_t *data)
{
       const int value = *data;
       uint64_t *sortval;
       char *repstr;

       if (!(repstr = dm_pool_zalloc(rh->mem, 22))) {
               log_error("dm_report_field_uint64: dm_pool_alloc failed");
               return 0;
       }

       if (!(sortval = dm_pool_alloc(rh->mem, sizeof(uint64_t)))) {
               log_error("dm_report_field_uint64: dm_pool_alloc failed");
               return 0;
       }

       if (dm_snprintf(repstr, 21, "%d", value) < 0) {
               log_error("dm_report_field_uint64: uint64 too big: %d", value);
               return 0;
       }

       *sortval = (const uint64_t) value;
       field->sort_value = sortval;
       field->report_string = repstr;

       return 1;
}

/*
* Helper functions for custom report functions
*/
void dm_report_field_set_value(struct dm_report_field *field, const void *value, const void *sortvalue)
{
       field->report_string = (const char *) value;
       field->sort_value = sortvalue ? : value;
}

/*
* show help message
*/
static void _display_fields(struct dm_report *rh)
{
       uint32_t f;
       const struct dm_report_object_type *type;
       const char *desc, *last_desc = "";
       size_t id_len = 0;

       for (f = 0; rh->fields[f].report_fn; f++)
               if (strlen(rh->fields[f].id) > id_len)
                       id_len = strlen(rh->fields[f].id);


       for (type = rh->types; type->data_fn; type++)
               if (strlen(type->prefix) + 3 > id_len)
                       id_len = strlen(type->prefix) + 3;

       for (f = 0; rh->fields[f].report_fn; f++) {
               if ((type = _find_type(rh, rh->fields[f].type)) && type->desc)
                       desc = type->desc;
               else
                       desc = " ";
               if (desc != last_desc) {
                       if (*last_desc)
                               log_warn(" ");
                       log_warn("%s Fields", desc);
                       log_warn("%*.*s", (int) strlen(desc) + 7,
                                (int) strlen(desc) + 7,
                                "-------------------------------------------------------------------------------");
                       log_warn("  %sall%-*s - %s", type->prefix,
                                (int) (id_len - 3 - strlen(type->prefix)), "",
                                "All fields in this section.");
               }

               /* FIXME Add line-wrapping at terminal width (or 80 cols) */
               log_warn("  %-*s - %s", (int) id_len, rh->fields[f].id, rh->fields[f].desc);
               last_desc = desc;
       }
}

/*
* Initialise report handle
*/
static int _copy_field(struct dm_report *rh, struct field_properties *dest,
                      uint32_t field_num)
{
       dest->field_num = field_num;
       dest->width = rh->fields[field_num].width;
       dest->flags = rh->fields[field_num].flags & DM_REPORT_FIELD_MASK;

       /* set object type method */
       dest->type = _find_type(rh, rh->fields[field_num].type);
       if (!dest->type) {
               log_error("dm_report: field not match: %s",
                         rh->fields[field_num].id);
               return 0;
       }

       return 1;
}

static struct field_properties * _add_field(struct dm_report *rh,
                                           uint32_t field_num, uint32_t flags)
{
       struct field_properties *fp;

       if (!(fp = dm_pool_zalloc(rh->mem, sizeof(struct field_properties)))) {
               log_error("dm_report: struct field_properties allocation "
                         "failed");
               return NULL;
       }

       if (!_copy_field(rh, fp, field_num)) {
               stack;
               dm_pool_free(rh->mem, fp);
               return NULL;
       }

       fp->flags |= flags;

       /*
        * Place hidden fields at the front so dm_list_end() will
        * tell us when we've reached the last visible field.
        */
       if (fp->flags & FLD_HIDDEN)
               dm_list_add_h(&rh->field_props, &fp->list);
       else
               dm_list_add(&rh->field_props, &fp->list);

       return fp;
}

/*
* Compare name1 against name2 or prefix plus name2
* name2 is not necessarily null-terminated.
* len2 is the length of name2.
*/
static int _is_same_field(const char *name1, const char *name2,
                         size_t len2, const char *prefix)
{
       size_t prefix_len;

       /* Exact match? */
       if (!strncasecmp(name1, name2, len2) && strlen(name1) == len2)
               return 1;

       /* Match including prefix? */
       prefix_len = strlen(prefix);
       if (!strncasecmp(prefix, name1, prefix_len) &&
           !strncasecmp(name1 + prefix_len, name2, len2) &&
           strlen(name1) == prefix_len + len2)
               return 1;

       return 0;
}

/*
* Check for a report type prefix + "all" match.
*/
static uint32_t _all_match(struct dm_report *rh, const char *field, size_t flen)
{
       size_t prefix_len;
       const struct dm_report_object_type *t;
       char prefixed_all[32];

       if (!strncasecmp(field, "all", 3) && flen == 3) {
               if (strlen(rh->field_prefix)) {
                       strcpy(prefixed_all, rh->field_prefix);
                       strcat(prefixed_all, "all");
                       /*
                        * Add also prefix to receive all attributes
                        * (e.g.LABEL/PVS use the same prefix)
                        */
                       return rh->report_types |
                              _all_match(rh, prefixed_all,
                                         strlen(prefixed_all));
               } else
                       return rh->report_types;
       }

       for (t = rh->types; t->data_fn; t++) {
               prefix_len = strlen(t->prefix);
               if (!strncasecmp(t->prefix, field, prefix_len) &&
                   !strncasecmp(field + prefix_len, "all", 3) &&
                   flen == prefix_len + 3)
                       return t->id;
       }

       return 0;
}

/*
* Add all fields with a matching type.
*/
static int _add_all_fields(struct dm_report *rh, uint32_t type)
{
       uint32_t f;

       for (f = 0; rh->fields[f].report_fn; f++)
               if ((rh->fields[f].type & type) && !_add_field(rh, f, 0))
                       return 0;

       return 1;
}

static int _field_match(struct dm_report *rh, const char *field, size_t flen,
                       unsigned report_type_only)
{
       uint32_t f, type;

       if (!flen)
               return 0;

       for (f = 0; rh->fields[f].report_fn; f++)
               if (_is_same_field(rh->fields[f].id, field, flen,
                                  rh->field_prefix)) {
                       if (report_type_only) {
                               rh->report_types |= rh->fields[f].type;
                               return 1;
                       } else
                               return _add_field(rh, f, 0) ? 1 : 0;
               }

       if ((type = _all_match(rh, field, flen))) {
               if (report_type_only) {
                       rh->report_types |= type;
                       return 1;
               } else
                       return  _add_all_fields(rh, type);
       }

       return 0;
}

static int _add_sort_key(struct dm_report *rh, uint32_t field_num,
                        uint32_t flags, unsigned report_type_only)
{
       struct field_properties *fp, *found = NULL;

       dm_list_iterate_items(fp, &rh->field_props) {
               if (fp->field_num == field_num) {
                       found = fp;
                       break;
               }
       }

       if (!found) {
               if (report_type_only)
                       rh->report_types |= rh->fields[field_num].type;
               else if (!(found = _add_field(rh, field_num, FLD_HIDDEN)))
                       return_0;
       }

       if (report_type_only)
               return 1;

       if (found->flags & FLD_SORT_KEY) {
               log_error("dm_report: Ignoring duplicate sort field: %s",
                         rh->fields[field_num].id);
               return 1;
       }

       found->flags |= FLD_SORT_KEY;
       found->sort_posn = rh->keys_count++;
       found->flags |= flags;

       return 1;
}

static int _key_match(struct dm_report *rh, const char *key, size_t len,
                     unsigned report_type_only)
{
       uint32_t f;
       uint32_t flags;

       if (!len)
               return 0;

       if (*key == '+') {
               key++;
               len--;
               flags = FLD_ASCENDING;
       } else if (*key == '-') {
               key++;
               len--;
               flags = FLD_DESCENDING;
       } else
               flags = FLD_ASCENDING;

       if (!len) {
               log_error("dm_report: Missing sort field name");
               return 0;
       }

       for (f = 0; rh->fields[f].report_fn; f++)
               if (_is_same_field(rh->fields[f].id, key, len,
                                  rh->field_prefix))
                       return _add_sort_key(rh, f, flags, report_type_only);

       return 0;
}

static int _parse_fields(struct dm_report *rh, const char *format,
                        unsigned report_type_only)
{
       const char *ws;         /* Word start */
       const char *we = format;        /* Word end */

       while (*we) {
               /* Allow consecutive commas */
               while (*we && *we == ',')
                       we++;

               /* start of the field name */
               ws = we;
               while (*we && *we != ',')
                       we++;

               if (!_field_match(rh, ws, (size_t) (we - ws), report_type_only)) {
                       _display_fields(rh);
                       log_warn(" ");
                       if (strcasecmp(ws, "help") && strcmp(ws, "?"))
                               log_error("Unrecognised field: %.*s",
                                         (int) (we - ws), ws);
                       return 0;
               }
       }

       return 1;
}

static int _parse_keys(struct dm_report *rh, const char *keys,
                      unsigned report_type_only)
{
       const char *ws;         /* Word start */
       const char *we = keys;  /* Word end */

       while (*we) {
               /* Allow consecutive commas */
               while (*we && *we == ',')
                       we++;
               ws = we;
               while (*we && *we != ',')
                       we++;
               if (!_key_match(rh, ws, (size_t) (we - ws), report_type_only)) {
                       log_error("dm_report: Unrecognised field: %.*s",
                                 (int) (we - ws), ws);
                       return 0;
               }
       }

       return 1;
}

struct dm_report *dm_report_init(uint32_t *report_types,
                                const struct dm_report_object_type *types,
                                const struct dm_report_field_type *fields,
                                const char *output_fields,
                                const char *output_separator,
                                uint32_t output_flags,
                                const char *sort_keys,
                                void *private)
{
       struct dm_report *rh;
       const struct dm_report_object_type *type;

       if (!(rh = dm_malloc(sizeof(*rh)))) {
               log_error("dm_report_init: dm_malloc failed");
               return 0;
       }
       memset(rh, 0, sizeof(*rh));

       /*
        * rh->report_types is updated in _parse_fields() and _parse_keys()
        * to contain all types corresponding to the fields specified by
        * fields or keys.
        */
       if (report_types)
               rh->report_types = *report_types;

       rh->separator = output_separator;
       rh->fields = fields;
       rh->types = types;
       rh->private = private;

       rh->flags |= output_flags & DM_REPORT_OUTPUT_MASK;

       /* With columns_as_rows we must buffer and not align. */
       if (output_flags & DM_REPORT_OUTPUT_COLUMNS_AS_ROWS) {
               if (!(output_flags & DM_REPORT_OUTPUT_BUFFERED))
                       rh->flags |= DM_REPORT_OUTPUT_BUFFERED;
               if (output_flags & DM_REPORT_OUTPUT_ALIGNED)
                       rh->flags &= ~DM_REPORT_OUTPUT_ALIGNED;
       }

       if (output_flags & DM_REPORT_OUTPUT_BUFFERED)
               rh->flags |= RH_SORT_REQUIRED;

       dm_list_init(&rh->field_props);
       dm_list_init(&rh->rows);

       if ((type = _find_type(rh, rh->report_types)) && type->prefix)
               rh->field_prefix = type->prefix;
       else
               rh->field_prefix = "";

       if (!(rh->mem = dm_pool_create("report", 10 * 1024))) {
               log_error("dm_report_init: allocation of memory pool failed");
               dm_free(rh);
               return NULL;
       }

       /*
        * To keep the code needed to add the "all" field to a minimum, we parse
        * the field lists twice.  The first time we only update the report type.
        * FIXME Use one pass instead and expand the "all" field afterwards.
        */
       if (!_parse_fields(rh, output_fields, 1) ||
           !_parse_keys(rh, sort_keys, 1)) {
               dm_report_free(rh);
               return NULL;
       }

       /* Generate list of fields for output based on format string & flags */
       if (!_parse_fields(rh, output_fields, 0) ||
           !_parse_keys(rh, sort_keys, 0)) {
               dm_report_free(rh);
               return NULL;
       }

       /* Return updated types value for further compatility check by caller */
       if (report_types)
               *report_types = rh->report_types;

       return rh;
}

void dm_report_free(struct dm_report *rh)
{
       dm_pool_destroy(rh->mem);
       dm_free(rh);
}

static char *_toupperstr(char *str)
{
       char *u = str;

       do
               *u = toupper(*u);
       while (*u++);

       return str;
}

int dm_report_set_output_field_name_prefix(struct dm_report *rh, const char *output_field_name_prefix)
{
       char *prefix;

       if (!(prefix = dm_pool_strdup(rh->mem, output_field_name_prefix))) {
               log_error("dm_report_set_output_field_name_prefix: dm_pool_strdup failed");
               return 0;
       }

       rh->output_field_name_prefix = _toupperstr(prefix);

       return 1;
}

/*
* Create a row of data for an object
*/
static void * _report_get_field_data(struct dm_report *rh,
                             struct field_properties *fp, void *object)
{
       void *ret = fp->type->data_fn(object);

       if (!ret)
               return NULL;

       return ret + rh->fields[fp->field_num].offset;
}

int dm_report_object(struct dm_report *rh, void *object)
{
       struct field_properties *fp;
       struct row *row;
       struct dm_report_field *field;
       void *data = NULL;

       if (!(row = dm_pool_zalloc(rh->mem, sizeof(*row)))) {
               log_error("dm_report_object: struct row allocation failed");
               return 0;
       }

       row->rh = rh;

       if ((rh->flags & RH_SORT_REQUIRED) &&
           !(row->sort_fields =
               dm_pool_zalloc(rh->mem, sizeof(struct dm_report_field *) *
                              rh->keys_count))) {
               log_error("dm_report_object: "
                         "row sort value structure allocation failed");
               return 0;
       }

       dm_list_init(&row->fields);
       dm_list_add(&rh->rows, &row->list);

       /* For each field to be displayed, call its report_fn */
       dm_list_iterate_items(fp, &rh->field_props) {
               if (!(field = dm_pool_zalloc(rh->mem, sizeof(*field)))) {
                       log_error("dm_report_object: "
                                 "struct dm_report_field allocation failed");
                       return 0;
               }
               field->props = fp;

               data = _report_get_field_data(rh, fp, object);
               if (!data)
                       return 0;

               if (!rh->fields[fp->field_num].report_fn(rh, rh->mem,
                                                        field, data,
                                                        rh->private)) {
                       log_error("dm_report_object: "
                                 "report function failed for field %s",
                                 rh->fields[fp->field_num].id);
                       return 0;
               }

               if ((strlen(field->report_string) > field->props->width))
                       field->props->width = strlen(field->report_string);

               if ((rh->flags & RH_SORT_REQUIRED) &&
                   (field->props->flags & FLD_SORT_KEY)) {
                       (*row->sort_fields)[field->props->sort_posn] = field;
               }
               dm_list_add(&row->fields, &field->list);
       }

       if (!(rh->flags & DM_REPORT_OUTPUT_BUFFERED))
               return dm_report_output(rh);

       return 1;
}

/*
* Print row of headings
*/
static int _report_headings(struct dm_report *rh)
{
       struct field_properties *fp;
       const char *heading;
       char buf[1024];

       if (rh->flags & RH_HEADINGS_PRINTED)
               return 1;

       rh->flags |= RH_HEADINGS_PRINTED;

       if (!(rh->flags & DM_REPORT_OUTPUT_HEADINGS))
               return 1;

       if (!dm_pool_begin_object(rh->mem, 128)) {
               log_error("dm_report: "
                         "dm_pool_begin_object failed for headings");
               return 0;
       }

       /* First heading line */
       dm_list_iterate_items(fp, &rh->field_props) {
               if (fp->flags & FLD_HIDDEN)
                       continue;

               heading = rh->fields[fp->field_num].heading;
               if (rh->flags & DM_REPORT_OUTPUT_ALIGNED) {
                       if (dm_snprintf(buf, sizeof(buf), "%-*.*s",
                                        fp->width, fp->width, heading) < 0) {
                               log_error("dm_report: snprintf heading failed");
                               goto bad;
                       }
                       if (!dm_pool_grow_object(rh->mem, buf, fp->width)) {
                               log_error("dm_report: Failed to generate report headings for printing");
                               goto bad;
                       }
               } else if (!dm_pool_grow_object(rh->mem, heading, 0)) {
                       log_error("dm_report: Failed to generate report headings for printing");
                       goto bad;
               }

               if (!dm_list_end(&rh->field_props, &fp->list))
                       if (!dm_pool_grow_object(rh->mem, rh->separator, 0)) {
                               log_error("dm_report: Failed to generate report headings for printing");
                               goto bad;
                       }
       }
       if (!dm_pool_grow_object(rh->mem, "\0", 1)) {
               log_error("dm_report: Failed to generate report headings for printing");
               goto bad;
       }
       log_print("%s", (char *) dm_pool_end_object(rh->mem));

       return 1;

     bad:
       dm_pool_abandon_object(rh->mem);
       return 0;
}

/*
* Sort rows of data
*/
static int _row_compare(const void *a, const void *b)
{
       const struct row *rowa = *(const struct row **) a;
       const struct row *rowb = *(const struct row **) b;
       const struct dm_report_field *sfa, *sfb;
       uint32_t cnt;

       for (cnt = 0; cnt < rowa->rh->keys_count; cnt++) {
               sfa = (*rowa->sort_fields)[cnt];
               sfb = (*rowb->sort_fields)[cnt];
               if (sfa->props->flags & DM_REPORT_FIELD_TYPE_NUMBER) {
                       const uint64_t numa =
                           *(const uint64_t *) sfa->sort_value;
                       const uint64_t numb =
                           *(const uint64_t *) sfb->sort_value;

                       if (numa == numb)
                               continue;

                       if (sfa->props->flags & FLD_ASCENDING) {
                               return (numa > numb) ? 1 : -1;
                       } else {        /* FLD_DESCENDING */
                               return (numa < numb) ? 1 : -1;
                       }
               } else {        /* DM_REPORT_FIELD_TYPE_STRING */
                       const char *stra = (const char *) sfa->sort_value;
                       const char *strb = (const char *) sfb->sort_value;
                       int cmp = strcmp(stra, strb);

                       if (!cmp)
                               continue;

                       if (sfa->props->flags & FLD_ASCENDING) {
                               return (cmp > 0) ? 1 : -1;
                       } else {        /* FLD_DESCENDING */
                               return (cmp < 0) ? 1 : -1;
                       }
               }
       }

       return 0;               /* Identical */
}

static int _sort_rows(struct dm_report *rh)
{
       struct row *(*rows)[];
       uint32_t count = 0;
       struct row *row;

       if (!(rows = dm_pool_alloc(rh->mem, sizeof(**rows) *
                               dm_list_size(&rh->rows)))) {
               log_error("dm_report: sort array allocation failed");
               return 0;
       }

       dm_list_iterate_items(row, &rh->rows)
               (*rows)[count++] = row;

       qsort(rows, count, sizeof(**rows), _row_compare);

       dm_list_init(&rh->rows);
       while (count--)
               dm_list_add_h(&rh->rows, &(*rows)[count]->list);

       return 1;
}

/*
* Produce report output
*/
static int _output_field(struct dm_report *rh, struct dm_report_field *field)
{
       char *field_id;
       int32_t width;
       uint32_t align;
       const char *repstr;
       char buf[4096];

       if (rh->flags & DM_REPORT_OUTPUT_FIELD_NAME_PREFIX) {
               if (!(field_id = strdup(rh->fields[field->props->field_num].id))) {
                       log_error("dm_report: Failed to copy field name");
                       return 0;
               }

               if (!dm_pool_grow_object(rh->mem, rh->output_field_name_prefix, 0)) {
                       log_error("dm_report: Unable to extend output line");
                       return 0;
               }

               if (!dm_pool_grow_object(rh->mem, _toupperstr(field_id), 0)) {
                       log_error("dm_report: Unable to extend output line");
                       return 0;
               }

               free(field_id);

               if (!dm_pool_grow_object(rh->mem, "=", 1)) {
                       log_error("dm_report: Unable to extend output line");
                       return 0;
               }

               if (!(rh->flags & DM_REPORT_OUTPUT_FIELD_UNQUOTED) &&
                   !dm_pool_grow_object(rh->mem, "\'", 1)) {
                       log_error("dm_report: Unable to extend output line");
                       return 0;
               }
       }

       repstr = field->report_string;
       width = field->props->width;
       if (!(rh->flags & DM_REPORT_OUTPUT_ALIGNED)) {
               if (!dm_pool_grow_object(rh->mem, repstr, 0)) {
                       log_error("dm_report: Unable to extend output line");
                       return 0;
               }
       } else {
               if (!(align = field->props->flags & DM_REPORT_FIELD_ALIGN_MASK))
                       align = (field->props->flags & DM_REPORT_FIELD_TYPE_NUMBER) ?
                               DM_REPORT_FIELD_ALIGN_RIGHT : DM_REPORT_FIELD_ALIGN_LEFT;
               if (align & DM_REPORT_FIELD_ALIGN_LEFT) {
                       if (dm_snprintf(buf, sizeof(buf), "%-*.*s",
                                        width, width, repstr) < 0) {
                               log_error("dm_report: left-aligned snprintf() failed");
                               return 0;
                       }
                       if (!dm_pool_grow_object(rh->mem, buf, width)) {
                               log_error("dm_report: Unable to extend output line");
                               return 0;
                       }
               } else if (align & DM_REPORT_FIELD_ALIGN_RIGHT) {
                       if (dm_snprintf(buf, sizeof(buf), "%*.*s",
                                        width, width, repstr) < 0) {
                               log_error("dm_report: right-aligned snprintf() failed");
                               return 0;
                       }
                       if (!dm_pool_grow_object(rh->mem, buf, width)) {
                               log_error("dm_report: Unable to extend output line");
                               return 0;
                       }
               }
       }

       if ((rh->flags & DM_REPORT_OUTPUT_FIELD_NAME_PREFIX) &&
           !(rh->flags & DM_REPORT_OUTPUT_FIELD_UNQUOTED))
               if (!dm_pool_grow_object(rh->mem, "\'", 1)) {
                       log_error("dm_report: Unable to extend output line");
                       return 0;
               }

       return 1;
}

static int _output_as_rows(struct dm_report *rh)
{
       struct field_properties *fp;
       struct dm_report_field *field;
       struct row *row;

       if (!dm_pool_begin_object(rh->mem, 512)) {
               log_error("dm_report: Unable to allocate output line");
               return 0;
       }

       dm_list_iterate_items(fp, &rh->field_props) {
               if (fp->flags & FLD_HIDDEN) {
                       dm_list_iterate_items(row, &rh->rows) {
                               field = dm_list_item(dm_list_first(&row->fields), struct dm_report_field);
                               dm_list_del(&field->list);
                       }
                       continue;
               }

               if ((rh->flags & DM_REPORT_OUTPUT_HEADINGS)) {
                       if (!dm_pool_grow_object(rh->mem, rh->fields[fp->field_num].heading, 0)) {
                               log_error("dm_report: Failed to extend row for field name");
                               goto bad;
                       }
                       if (!dm_pool_grow_object(rh->mem, rh->separator, 0)) {
                               log_error("dm_report: Failed to extend row with separator");
                               goto bad;
                       }
               }

               dm_list_iterate_items(row, &rh->rows) {
                       if ((field = dm_list_item(dm_list_first(&row->fields), struct dm_report_field))) {
                               if (!_output_field(rh, field))
                                       goto bad;
                               dm_list_del(&field->list);
                       }

                       if (!dm_list_end(&rh->rows, &row->list))
                               if (!dm_pool_grow_object(rh->mem, rh->separator, 0)) {
                                       log_error("dm_report: Unable to extend output line");
                                       goto bad;
                               }
               }

               if (!dm_pool_grow_object(rh->mem, "\0", 1)) {
                       log_error("dm_report: Failed to terminate row");
                       goto bad;
               }
               log_print("%s", (char *) dm_pool_end_object(rh->mem));
       }

       return 1;

     bad:
       dm_pool_abandon_object(rh->mem);
       return 0;
}

static int _output_as_columns(struct dm_report *rh)
{
       struct dm_list *fh, *rowh, *ftmp, *rtmp;
       struct row *row = NULL;
       struct dm_report_field *field;

       /* If headings not printed yet, calculate field widths and print them */
       if (!(rh->flags & RH_HEADINGS_PRINTED))
               _report_headings(rh);

       /* Print and clear buffer */
       dm_list_iterate_safe(rowh, rtmp, &rh->rows) {
               if (!dm_pool_begin_object(rh->mem, 512)) {
                       log_error("dm_report: Unable to allocate output line");
                       return 0;
               }
               row = dm_list_item(rowh, struct row);
               dm_list_iterate_safe(fh, ftmp, &row->fields) {
                       field = dm_list_item(fh, struct dm_report_field);
                       if (field->props->flags & FLD_HIDDEN)
                               continue;

                       if (!_output_field(rh, field))
                               goto bad;

                       if (!dm_list_end(&row->fields, fh))
                               if (!dm_pool_grow_object(rh->mem, rh->separator, 0)) {
                                       log_error("dm_report: Unable to extend output line");
                                       goto bad;
                               }

                       dm_list_del(&field->list);
               }
               if (!dm_pool_grow_object(rh->mem, "\0", 1)) {
                       log_error("dm_report: Unable to terminate output line");
                       goto bad;
               }
               log_print("%s", (char *) dm_pool_end_object(rh->mem));
               dm_list_del(&row->list);
       }

       if (row)
               dm_pool_free(rh->mem, row);

       return 1;

     bad:
       dm_pool_abandon_object(rh->mem);
       return 0;
}

int dm_report_output(struct dm_report *rh)
{
       if (dm_list_empty(&rh->rows))
               return 1;

       if ((rh->flags & RH_SORT_REQUIRED))
               _sort_rows(rh);

       if ((rh->flags & DM_REPORT_OUTPUT_COLUMNS_AS_ROWS))
               return _output_as_rows(rh);
       else
               return _output_as_columns(rh);
}