summaryrefslogtreecommitdiff
path: root/libdm/libdm-report.c
diff options
context:
space:
mode:
Diffstat (limited to 'libdm/libdm-report.c')
-rw-r--r--libdm/libdm-report.c4497
1 files changed, 4335 insertions, 162 deletions
diff --git a/libdm/libdm-report.c b/libdm/libdm-report.c
index 0bafa86..8eaf589 100644
--- a/libdm/libdm-report.c
+++ b/libdm/libdm-report.c
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2002-2004 Sistina Software, Inc. All rights reserved.
- * Copyright (C) 2004-2007 Red Hat, Inc. All rights reserved.
+ * Copyright (C) 2004-2015 Red Hat, Inc. All rights reserved.
*
* This file is part of the device-mapper userspace tools.
*
@@ -10,22 +10,41 @@
*
* 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
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
-#include "dmlib.h"
+#include "libdm/misc/dmlib.h"
#include <ctype.h>
+#include <math.h> /* fabs() */
+#include <float.h> /* DBL_EPSILON */
+#include <time.h>
/*
* Internal flags
*/
#define RH_SORT_REQUIRED 0x00000100
#define RH_HEADINGS_PRINTED 0x00000200
+#define RH_FIELD_CALC_NEEDED 0x00000400
+#define RH_ALREADY_REPORTED 0x00000800
+
+struct selection {
+ struct dm_pool *mem;
+ struct selection_node *selection_root;
+ int add_new_fields;
+};
+
+struct report_group_item;
struct dm_report {
struct dm_pool *mem;
+ /**
+ * Cache the first row allocated so that all rows and fields
+ * can be disposed of in a single dm_pool_free() call.
+ */
+ struct row *first_row;
+
/* To report all available types */
#define REPORT_TYPES_ALL UINT32_MAX
uint32_t report_types;
@@ -44,27 +63,180 @@ struct dm_report {
/* Array of field definitions */
const struct dm_report_field_type *fields;
+ const char **canonical_field_ids;
const struct dm_report_object_type *types;
/* To store caller private data */
void *private;
+
+ /* Selection handle */
+ struct selection *selection;
+
+ /* Null-terminated array of reserved values */
+ const struct dm_report_reserved_value *reserved_values;
+ struct dm_hash_table *value_cache;
+
+ struct report_group_item *group_item;
+};
+
+struct dm_report_group {
+ dm_report_group_type_t type;
+ struct dm_pool *mem;
+ struct dm_list items;
+ int indent;
+};
+
+struct report_group_item {
+ struct dm_list list;
+ struct dm_report_group *group;
+ struct dm_report *report;
+ union store_u {
+ uint32_t orig_report_flags;
+ uint32_t finished_count;
+ } store;
+ struct report_group_item *parent;
+ unsigned output_done:1;
+ unsigned needs_closing:1;
+ void *data;
};
/*
* Internal per-field flags
*/
-#define FLD_HIDDEN 0x00000100
-#define FLD_SORT_KEY 0x00000200
-#define FLD_ASCENDING 0x00000400
-#define FLD_DESCENDING 0x00000800
+#define FLD_HIDDEN 0x00001000
+#define FLD_SORT_KEY 0x00002000
+#define FLD_ASCENDING 0x00004000
+#define FLD_DESCENDING 0x00008000
+#define FLD_COMPACTED 0x00010000
+#define FLD_COMPACT_ONE 0x00020000
struct field_properties {
struct dm_list list;
uint32_t field_num;
uint32_t sort_posn;
- int32_t width;
+ int32_t initial_width;
+ int32_t width; /* current width: adjusted by dm_report_object() */
const struct dm_report_object_type *type;
uint32_t flags;
+ int implicit;
+};
+
+/*
+ * Report selection
+ */
+struct op_def {
+ const char *string;
+ uint32_t flags;
+ const char *desc;
+};
+
+#define FLD_CMP_MASK 0x0FF00000
+#define FLD_CMP_UNCOMPARABLE 0x00100000
+#define FLD_CMP_EQUAL 0x00200000
+#define FLD_CMP_NOT 0x00400000
+#define FLD_CMP_GT 0x00800000
+#define FLD_CMP_LT 0x01000000
+#define FLD_CMP_REGEX 0x02000000
+#define FLD_CMP_NUMBER 0x04000000
+#define FLD_CMP_TIME 0x08000000
+/*
+ * #define FLD_CMP_STRING 0x10000000
+ * We could define FLD_CMP_STRING here for completeness here,
+ * but it's not needed - we can check operator compatibility with
+ * field type by using FLD_CMP_REGEX, FLD_CMP_NUMBER and
+ * FLD_CMP_TIME flags only.
+ */
+
+/*
+ * When defining operators, always define longer one before
+ * shorter one if one is a prefix of another!
+ * (e.g. =~ comes before =)
+*/
+static struct op_def _op_cmp[] = {
+ { "=~", FLD_CMP_REGEX, "Matching regular expression. [regex]" },
+ { "!~", FLD_CMP_REGEX|FLD_CMP_NOT, "Not matching regular expression. [regex]" },
+ { "=", FLD_CMP_EQUAL, "Equal to. [number, size, percent, string, string list, time]" },
+ { "!=", FLD_CMP_NOT|FLD_CMP_EQUAL, "Not equal to. [number, size, percent, string, string_list, time]" },
+ { ">=", FLD_CMP_NUMBER|FLD_CMP_TIME|FLD_CMP_GT|FLD_CMP_EQUAL, "Greater than or equal to. [number, size, percent, time]" },
+ { ">", FLD_CMP_NUMBER|FLD_CMP_TIME|FLD_CMP_GT, "Greater than. [number, size, percent, time]" },
+ { "<=", FLD_CMP_NUMBER|FLD_CMP_TIME|FLD_CMP_LT|FLD_CMP_EQUAL, "Less than or equal to. [number, size, percent, time]" },
+ { "<", FLD_CMP_NUMBER|FLD_CMP_TIME|FLD_CMP_LT, "Less than. [number, size, percent, time]" },
+ { "since", FLD_CMP_TIME|FLD_CMP_GT|FLD_CMP_EQUAL, "Since specified time (same as '>='). [time]" },
+ { "after", FLD_CMP_TIME|FLD_CMP_GT, "After specified time (same as '>'). [time]"},
+ { "until", FLD_CMP_TIME|FLD_CMP_LT|FLD_CMP_EQUAL, "Until specified time (same as '<='). [time]"},
+ { "before", FLD_CMP_TIME|FLD_CMP_LT, "Before specified time (same as '<'). [time]"},
+ { NULL, 0, NULL }
+};
+
+#define SEL_MASK 0x000000FF
+#define SEL_ITEM 0x00000001
+#define SEL_AND 0x00000002
+#define SEL_OR 0x00000004
+
+#define SEL_MODIFIER_MASK 0x00000F00
+#define SEL_MODIFIER_NOT 0x00000100
+
+#define SEL_PRECEDENCE_MASK 0x0000F000
+#define SEL_PRECEDENCE_PS 0x00001000
+#define SEL_PRECEDENCE_PE 0x00002000
+
+#define SEL_LIST_MASK 0x000F0000
+#define SEL_LIST_LS 0x00010000
+#define SEL_LIST_LE 0x00020000
+#define SEL_LIST_SUBSET_LS 0x00040000
+#define SEL_LIST_SUBSET_LE 0x00080000
+
+static struct op_def _op_log[] = {
+ { "&&", SEL_AND, "All fields must match" },
+ { ",", SEL_AND, "All fields must match" },
+ { "||", SEL_OR, "At least one field must match" },
+ { "#", SEL_OR, "At least one field must match" },
+ { "!", SEL_MODIFIER_NOT, "Logical negation" },
+ { "(", SEL_PRECEDENCE_PS, "Left parenthesis" },
+ { ")", SEL_PRECEDENCE_PE, "Right parenthesis" },
+ { "[", SEL_LIST_LS, "List start" },
+ { "]", SEL_LIST_LE, "List end"},
+ { "{", SEL_LIST_SUBSET_LS, "List subset start"},
+ { "}", SEL_LIST_SUBSET_LE, "List subset end"},
+ { NULL, 0, NULL},
+};
+
+struct selection_str_list {
+ struct dm_str_list str_list;
+ unsigned type; /* either SEL_AND or SEL_OR */
+};
+
+struct field_selection_value {
+ union value_u {
+ const char *s;
+ uint64_t i;
+ time_t t;
+ double d;
+ struct dm_regex *r;
+ struct selection_str_list *l;
+ } v;
+ struct field_selection_value *next;
+};
+
+struct field_selection {
+ struct field_properties *fp;
+ uint32_t flags;
+ struct field_selection_value *value;
+};
+
+struct selection_node {
+ struct dm_list list;
+ uint32_t type;
+ union selection_u {
+ struct field_selection *item;
+ struct dm_list set;
+ } selection;
+};
+
+struct reserved_value_wrapper {
+ const char *matched_name;
+ const struct dm_report_reserved_value *reserved;
+ const void *value;
};
/*
@@ -83,13 +255,72 @@ struct row {
struct dm_report *rh;
struct dm_list fields; /* Fields in display order */
struct dm_report_field *(*sort_fields)[]; /* Fields in sort order */
+ int selected;
+ struct dm_report_field *field_sel_status;
};
+/*
+ * Implicit report types and fields.
+ */
+#define SPECIAL_REPORT_TYPE 0x80000000
+#define SPECIAL_FIELD_SELECTED_ID "selected"
+#define SPECIAL_FIELD_HELP_ID "help"
+#define SPECIAL_FIELD_HELP_ALT_ID "?"
+
+static void *_null_returning_fn(void *obj __attribute__((unused)))
+{
+ return NULL;
+}
+
+static int _no_report_fn(struct dm_report *rh __attribute__((unused)),
+ struct dm_pool *mem __attribute__((unused)),
+ struct dm_report_field *field __attribute__((unused)),
+ const void *data __attribute__((unused)),
+ void *private __attribute__((unused)))
+{
+ return 1;
+}
+
+static int _selected_disp(struct dm_report *rh,
+ struct dm_pool *mem __attribute__((unused)),
+ struct dm_report_field *field,
+ const void *data,
+ void *private __attribute__((unused)))
+{
+ const struct row *row = (const struct row *)data;
+ return dm_report_field_int(rh, field, &row->selected);
+}
+
+static const struct dm_report_object_type _implicit_special_report_types[] = {
+ { SPECIAL_REPORT_TYPE, "Special", "special_", _null_returning_fn },
+ { 0, "", "", NULL }
+};
+
+static const struct dm_report_field_type _implicit_special_report_fields[] = {
+ { SPECIAL_REPORT_TYPE, DM_REPORT_FIELD_TYPE_NUMBER | FLD_CMP_UNCOMPARABLE , 0, 8, SPECIAL_FIELD_HELP_ID, "Help", _no_report_fn, "Show help." },
+ { SPECIAL_REPORT_TYPE, DM_REPORT_FIELD_TYPE_NUMBER | FLD_CMP_UNCOMPARABLE , 0, 8, SPECIAL_FIELD_HELP_ALT_ID, "Help", _no_report_fn, "Show help." },
+ { 0, 0, 0, 0, "", "", 0, 0}
+};
+
+static const struct dm_report_field_type _implicit_special_report_fields_with_selection[] = {
+ { SPECIAL_REPORT_TYPE, DM_REPORT_FIELD_TYPE_NUMBER, 0, 8, SPECIAL_FIELD_SELECTED_ID, "Selected", _selected_disp, "Set if item passes selection criteria." },
+ { SPECIAL_REPORT_TYPE, DM_REPORT_FIELD_TYPE_NUMBER | FLD_CMP_UNCOMPARABLE , 0, 8, SPECIAL_FIELD_HELP_ID, "Help", _no_report_fn, "Show help." },
+ { SPECIAL_REPORT_TYPE, DM_REPORT_FIELD_TYPE_NUMBER | FLD_CMP_UNCOMPARABLE , 0, 8, SPECIAL_FIELD_HELP_ALT_ID, "Help", _no_report_fn, "Show help." },
+ { 0, 0, 0, 0, "", "", 0, 0}
+};
+
+static const struct dm_report_object_type *_implicit_report_types = _implicit_special_report_types;
+static const struct dm_report_field_type *_implicit_report_fields = _implicit_special_report_fields;
+
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 = _implicit_report_types; t->data_fn; t++)
+ if (t->id == report_type)
+ return t;
+
for (t = rh->types; t->data_fn; t++)
if (t->id == report_type)
return t;
@@ -117,6 +348,305 @@ int dm_report_field_string(struct dm_report *rh,
return 1;
}
+int dm_report_field_percent(struct dm_report *rh,
+ struct dm_report_field *field,
+ const dm_percent_t *data)
+{
+ char *repstr;
+ uint64_t *sortval;
+
+ if (!(sortval = dm_pool_alloc(rh->mem, sizeof(uint64_t)))) {
+ log_error("dm_report_field_percent: dm_pool_alloc failed for sort_value.");
+ return 0;
+ }
+
+ *sortval = (uint64_t)(*data);
+
+ if (*data == DM_PERCENT_INVALID) {
+ dm_report_field_set_value(field, "", sortval);
+ return 1;
+ }
+
+ if (!(repstr = dm_pool_alloc(rh->mem, 8))) {
+ dm_pool_free(rh->mem, sortval);
+ log_error("dm_report_field_percent: dm_pool_alloc failed for percent report string.");
+ return 0;
+ }
+
+ if (dm_snprintf(repstr, 7, "%.2f", dm_percent_to_round_float(*data, 2)) < 0) {
+ dm_pool_free(rh->mem, sortval);
+ log_error("dm_report_field_percent: percentage too large.");
+ return 0;
+ }
+
+ dm_report_field_set_value(field, repstr, sortval);
+ return 1;
+}
+
+struct pos_len {
+ unsigned pos;
+ size_t len;
+};
+
+struct str_pos_len {
+ const char *str;
+ struct pos_len item;
+};
+
+struct str_list_sort_value {
+ const char *value;
+ struct pos_len *items;
+};
+
+static int _str_sort_cmp(const void *a, const void *b)
+{
+ return strcmp(((const struct str_pos_len *) a)->str, ((const struct str_pos_len *) b)->str);
+}
+
+#define FIELD_STRING_LIST_DEFAULT_DELIMITER ","
+
+static int _report_field_string_list(struct dm_report *rh,
+ struct dm_report_field *field,
+ const struct dm_list *data,
+ const char *delimiter,
+ int sort_repstr)
+{
+ static const char _error_msg_prefix[] = "_report_field_string_list: ";
+ unsigned int list_size, i, pos;
+ struct str_pos_len *arr = NULL;
+ struct dm_str_list *sl;
+ size_t delimiter_len, repstr_str_len, repstr_size;
+ char *repstr = NULL;
+ struct pos_len *repstr_extra;
+ struct str_list_sort_value *sortval = NULL;
+ int r = 0;
+
+ /*
+ * The 'field->report_string' has 2 parts:
+ *
+ * - string representing the whole string list
+ * (terminated by '\0' at its end as usual)
+ *
+ * - extra info beyond the end of the string representing
+ * position and length of each list item within the
+ * field->report_string (array of 'struct pos_len')
+ *
+ * We can use the extra info to unambiguously identify list items,
+ * the delimiter is not enough here as it's not assured it won't appear
+ * in list item itself. We will make use of this extra info in case
+ * we need to apply further formatting to the list in dm_report_output
+ * where the pure field->report_string is not enough for printout.
+ *
+ *
+ * The 'field->sort_value' contains a value of type 'struct
+ * str_list_sort_value' ('sortval'). This one has a pointer to the
+ * 'field->report_string' string ('sortval->value') and info
+ * about position and length of each list item within the string
+ * (array of 'struct pos_len').
+ *
+ *
+ * The 'field->report_string' is either in sorted or unsorted form,
+ * depending on 'sort_repstr' arg.
+ *
+ * The 'field->sort_value.items' is always in sorted form because
+ * we need that for effective sorting and selection.
+ *
+ * If 'field->report_string' is sorted, then field->report_string
+ * and field->sort_value.items share the same array of
+ * 'struct pos_len' (because they're both sorted the same way),
+ * otherwise, each one has its own array.
+ *
+ * The very first item in the array of 'struct pos_len' is always
+ * a pair denoting '[list_size,strlen(field->report_string)]'. The
+ * rest of items denote start and lenght of each item in the list.
+ *
+ *
+ * For example, if we have a list with "abc", "xy", "defgh"
+ * as input and delimiter is ",", we end up with either:
+ *
+ * A) if we don't want the report string sorted ('sort_repstr == 0'):
+ *
+ * - field->report_string = repstr
+ *
+ * repstr repstr_extra
+ * | |
+ * V V
+ * abc,xy,defgh\0{[3,12],[0,3],[4,2],[7,5]}
+ * |____________||________________________|
+ * string array of struct pos_len
+ * |____||________________|
+ * #items items
+ *
+ * - field->sort_value = sortval
+ *
+ * sortval->value = repstr
+ * sortval->items = {[3,12],[0,3],[7,5],[4,2]}
+ * (that is 'abc,defgh,xy')
+ *
+ *
+ * B) if we want the report string sorted ('sort_repstr == 1'):
+ *
+ * - field->report_string = repstr
+ *
+ * repstr repstr_extra
+ * | |
+ * V V
+ * abc,defgh,xy\0{[3,12],[0,3],[4,5],[10,2]}
+ * |____________||________________________|
+ * string array of struct pos_len
+ * |____||________________|
+ * #items items
+ *
+ * - field->sort_value = sortval
+ *
+ * sortval->value = repstr
+ * sortval->items = repstr_extra
+ * (that is 'abc,defgh,xy')
+ */
+
+ if (!delimiter)
+ delimiter = FIELD_STRING_LIST_DEFAULT_DELIMITER;
+ delimiter_len = strlen(delimiter);
+ list_size = dm_list_size(data);
+
+ if (!(sortval = dm_pool_alloc(rh->mem, sizeof(struct str_list_sort_value)))) {
+ log_error("%s failed to allocate sort value structure", _error_msg_prefix);
+ goto out;
+ }
+
+ /* zero items */
+ if (list_size == 0) {
+ field->report_string = sortval->value = "";
+ sortval->items = NULL;
+ field->sort_value = sortval;
+ return 1;
+ }
+
+ /* one item */
+ if (list_size == 1) {
+ sl = (struct dm_str_list *) dm_list_first(data);
+
+ repstr_str_len = strlen(sl->str);
+ repstr_size = repstr_str_len + 1 + (2 * sizeof(struct pos_len));
+
+ if (!(repstr = dm_pool_alloc(rh->mem, repstr_size))) {
+ log_error("%s failed to allocate report string structure", _error_msg_prefix);
+ goto out;
+ }
+ repstr_extra = (struct pos_len *) (repstr + repstr_str_len + 1);
+
+ memcpy(repstr, sl->str, repstr_str_len + 1);
+ memcpy(repstr_extra, &((struct pos_len) {.pos = 1, .len = repstr_str_len}), sizeof(struct pos_len));
+ memcpy(repstr_extra + 1, &((struct pos_len) {.pos = 0, .len = repstr_str_len}), sizeof(struct pos_len));
+
+ sortval->value = field->report_string = repstr;
+ sortval->items = repstr_extra;
+ field->sort_value = sortval;
+ return 1;
+ }
+
+ /* more than one item - allocate temporary array for string list items for further processing */
+ if (!(arr = dm_malloc(list_size * sizeof(struct str_pos_len)))) {
+ log_error("%s failed to allocate temporary array for processing", _error_msg_prefix);
+ goto out;
+ }
+
+ i = 0;
+ repstr_size = 0;
+ dm_list_iterate_items(sl, data) {
+ arr[i].str = sl->str;
+ repstr_size += (arr[i].item.len = strlen(sl->str));
+ i++;
+ }
+
+ /*
+ * At this point, repstr_size contains sum of lengths of all string list items.
+ * Now, add these to the repstr_size:
+ *
+ * --> sum of character count used by all delimiters: + ((list_size - 1) * delimiter_len)
+ *
+ * --> '\0' used at the end of the string list: + 1
+ *
+ * --> sum of structures used to keep info about pos and length of each string list item:
+ * [0, <list_size>] [<pos1>,<size1>] [<pos2>,<size2>] ...
+ * That is: + ((list_size + 1) * sizeof(struct pos_len))
+ */
+ repstr_size += ((list_size - 1) * delimiter_len);
+ repstr_str_len = repstr_size;
+ repstr_size += 1 + ((list_size + 1) * sizeof(struct pos_len));
+
+ if (sort_repstr)
+ qsort(arr, list_size, sizeof(struct str_pos_len), _str_sort_cmp);
+
+ if (!(repstr = dm_pool_alloc(rh->mem, repstr_size))) {
+ log_error("%s failed to allocate report string structure", _error_msg_prefix);
+ goto out;
+ }
+ repstr_extra = (struct pos_len *) (repstr + repstr_str_len + 1);
+
+ memcpy(repstr_extra, &(struct pos_len) {.pos = list_size, .len = repstr_str_len}, sizeof(struct pos_len));
+ for (i = 0, pos = 0; i < list_size; i++) {
+ arr[i].item.pos = pos;
+
+ memcpy(repstr + pos, arr[i].str, arr[i].item.len);
+ memcpy(repstr_extra + i + 1, &arr[i].item, sizeof(struct pos_len));
+
+ pos += arr[i].item.len;
+ if (i + 1 < list_size) {
+ memcpy(repstr + pos, delimiter, delimiter_len);
+ pos += delimiter_len;
+ }
+ }
+ *(repstr + pos) = '\0';
+
+ sortval->value = repstr;
+ if (sort_repstr)
+ sortval->items = repstr_extra;
+ else {
+ if (!(sortval->items = dm_pool_alloc(rh->mem, (list_size + 1) * sizeof(struct pos_len)))) {
+ log_error("%s failed to allocate array of items inside sort value structure",
+ _error_msg_prefix);
+ goto out;
+ }
+
+ qsort(arr, list_size, sizeof(struct str_pos_len), _str_sort_cmp);
+
+ sortval->items[0] = (struct pos_len) {.pos = list_size, .len = repstr_str_len};
+ for (i = 0; i < list_size; i++)
+ sortval->items[i+1] = arr[i].item;
+ }
+
+ field->report_string = repstr;
+ field->sort_value = sortval;
+ r = 1;
+out:
+ if (!r && sortval)
+ dm_pool_free(rh->mem, sortval);
+ dm_free(arr);
+ return r;
+}
+
+int dm_report_field_string_list(struct dm_report *rh,
+ struct dm_report_field *field,
+ const struct dm_list *data,
+ const char *delimiter)
+{
+ return _report_field_string_list(rh, field, data, delimiter, 1);
+}
+
+int dm_report_field_string_list_unsorted(struct dm_report *rh,
+ struct dm_report_field *field,
+ const struct dm_list *data,
+ const char *delimiter)
+{
+ /*
+ * The raw value is always sorted, just the string reported is unsorted.
+ * Having the raw value always sorted helps when matching selection list
+ * with selection criteria.
+ */
+ return _report_field_string_list(rh, field, data, delimiter, 0);
+}
+
int dm_report_field_int(struct dm_report *rh,
struct dm_report_field *field, const int *data)
{
@@ -221,7 +751,7 @@ int dm_report_field_uint64(struct dm_report *rh,
return 0;
}
- if (dm_snprintf(repstr, 21, "%" PRIu64 , value) < 0) {
+ if (dm_snprintf(repstr, 21, FMTu64 , value) < 0) {
log_error("dm_report_field_uint64: uint64 too big: %" PRIu64, value);
return 0;
}
@@ -240,32 +770,63 @@ void dm_report_field_set_value(struct dm_report_field *field, const void *value,
{
field->report_string = (const char *) value;
field->sort_value = sortvalue ? : value;
+
+ if ((field->sort_value == value) &&
+ (field->props->flags & DM_REPORT_FIELD_TYPE_NUMBER))
+ log_warn(INTERNAL_ERROR "Using string as sort value for numerical field.");
+}
+
+static const char *_get_field_type_name(unsigned field_type)
+{
+ switch (field_type) {
+ case DM_REPORT_FIELD_TYPE_STRING: return "string";
+ case DM_REPORT_FIELD_TYPE_NUMBER: return "number";
+ case DM_REPORT_FIELD_TYPE_SIZE: return "size";
+ case DM_REPORT_FIELD_TYPE_PERCENT: return "percent";
+ case DM_REPORT_FIELD_TYPE_TIME: return "time";
+ case DM_REPORT_FIELD_TYPE_STRING_LIST: return "string list";
+ default: return "unknown";
+ }
}
/*
* show help message
*/
-static void _display_fields(struct dm_report *rh)
+static size_t _get_longest_field_id_len(const struct dm_report_field_type *fields)
{
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 (f = 0; fields[f].report_fn; f++)
+ if (strlen(fields[f].id) > id_len)
+ id_len = strlen(fields[f].id);
+
+ return id_len;
+}
+
+static void _display_fields_more(struct dm_report *rh,
+ const struct dm_report_field_type *fields,
+ size_t id_len, int display_all_fields_item,
+ int display_field_types)
+{
+ uint32_t f;
+ const struct dm_report_object_type *type;
+ const char *desc, *last_desc = "";
+ for (f = 0; fields[f].report_fn; f++)
+ if (strlen(fields[f].id) > id_len)
+ id_len = strlen(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 = " ";
+ for (f = 0; fields[f].report_fn; f++) {
+ if (!(type = _find_type(rh, fields[f].type))) {
+ log_debug(INTERNAL_ERROR "Field type undefined.");
+ continue;
+ }
+ desc = (type->desc) ? : " ";
if (desc != last_desc) {
if (*last_desc)
log_warn(" ");
@@ -273,32 +834,62 @@ static void _display_fields(struct dm_report *rh)
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.");
+ if (display_all_fields_item && type->id != SPECIAL_REPORT_TYPE)
+ 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);
+ log_warn(" %-*s - %s%s%s%s%s", (int) id_len, fields[f].id, fields[f].desc,
+ display_field_types ? " [" : "",
+ display_field_types ? fields[f].flags & FLD_CMP_UNCOMPARABLE ? "unselectable " : "" : "",
+ display_field_types ? _get_field_type_name(fields[f].flags & DM_REPORT_FIELD_TYPE_MASK) : "",
+ display_field_types ? "]" : "");
last_desc = desc;
}
}
/*
+ * show help message
+ */
+static void _display_fields(struct dm_report *rh, int display_all_fields_item,
+ int display_field_types)
+{
+ size_t tmp, id_len = 0;
+
+ if ((tmp = _get_longest_field_id_len(_implicit_report_fields)) > id_len)
+ id_len = tmp;
+ if ((tmp = _get_longest_field_id_len(rh->fields)) > id_len)
+ id_len = tmp;
+
+ _display_fields_more(rh, rh->fields, id_len, display_all_fields_item,
+ display_field_types);
+ log_warn(" ");
+ _display_fields_more(rh, _implicit_report_fields, id_len,
+ display_all_fields_item, display_field_types);
+
+}
+
+/*
* Initialise report handle
*/
static int _copy_field(struct dm_report *rh, struct field_properties *dest,
- uint32_t field_num)
+ uint32_t field_num, int implicit)
{
+ const struct dm_report_field_type *fields = implicit ? _implicit_report_fields
+ : rh->fields;
+
dest->field_num = field_num;
- dest->width = rh->fields[field_num].width;
- dest->flags = rh->fields[field_num].flags & DM_REPORT_FIELD_MASK;
+ dest->initial_width = fields[field_num].width;
+ dest->width = fields[field_num].width; /* adjusted in _do_report_object() */
+ dest->flags = fields[field_num].flags & DM_REPORT_FIELD_MASK;
+ dest->implicit = implicit;
/* set object type method */
- dest->type = _find_type(rh, rh->fields[field_num].type);
+ dest->type = _find_type(rh, fields[field_num].type);
if (!dest->type) {
log_error("dm_report: field not match: %s",
- rh->fields[field_num].id);
+ fields[field_num].id);
return 0;
}
@@ -306,17 +897,18 @@ static int _copy_field(struct dm_report *rh, struct field_properties *dest,
}
static struct field_properties * _add_field(struct dm_report *rh,
- uint32_t field_num, uint32_t flags)
+ uint32_t field_num, int implicit,
+ uint32_t flags)
{
struct field_properties *fp;
- if (!(fp = dm_pool_zalloc(rh->mem, sizeof(struct field_properties)))) {
+ if (!(fp = dm_pool_zalloc(rh->mem, sizeof(*fp)))) {
log_error("dm_report: struct field_properties allocation "
"failed");
return NULL;
}
- if (!_copy_field(rh, fp, field_num)) {
+ if (!_copy_field(rh, fp, field_num, implicit)) {
stack;
dm_pool_free(rh->mem, fp);
return NULL;
@@ -336,25 +928,53 @@ static struct field_properties * _add_field(struct dm_report *rh,
return fp;
}
+static int _get_canonical_field_name(const char *field,
+ size_t flen,
+ char *canonical_field,
+ size_t fcanonical_len,
+ int *differs)
+{
+ size_t i;
+ int diff = 0;
+
+ for (i = 0; *field && flen; field++, flen--) {
+ if (*field == '_') {
+ diff = 1;
+ continue;
+ }
+ if ((i + 1) >= fcanonical_len) {
+ canonical_field[0] = '\0';
+ log_error("%s: field name too long.", field);
+ return 0;
+ }
+ canonical_field[i++] = *field;
+ }
+
+ canonical_field[i] = '\0';
+ if (differs)
+ *differs = diff;
+ return 1;
+}
+
/*
- * Compare name1 against name2 or prefix plus name2
- * name2 is not necessarily null-terminated.
- * len2 is the length of name2.
+ * Compare canonical_name1 against canonical_name2 or prefix
+ * plus canonical_name2. Canonical name is a name where all
+ * superfluous characters are removed (underscores for now).
+ * Both names are always null-terminated.
*/
-static int _is_same_field(const char *name1, const char *name2,
- size_t len2, const char *prefix)
+static int _is_same_field(const char *canonical_name1, const char *canonical_name2,
+ const char *prefix)
{
size_t prefix_len;
/* Exact match? */
- if (!strncasecmp(name1, name2, len2) && strlen(name1) == len2)
+ if (!strcasecmp(canonical_name1, canonical_name2))
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)
+ prefix_len = strlen(prefix) - 1;
+ if (!strncasecmp(prefix, canonical_name1, prefix_len) &&
+ !strcasecmp(canonical_name1 + prefix_len, canonical_name2))
return 1;
return 0;
@@ -363,10 +983,32 @@ static int _is_same_field(const char *name1, const char *name2,
/*
* Check for a report type prefix + "all" match.
*/
-static uint32_t _all_match(struct dm_report *rh, const char *field, size_t flen)
+static void _all_match_combine(const struct dm_report_object_type *types,
+ unsigned unprefixed_all_matched,
+ const char *field, size_t flen,
+ uint32_t *report_types)
{
- size_t prefix_len;
+ char field_canon[DM_REPORT_FIELD_TYPE_ID_LEN];
const struct dm_report_object_type *t;
+ size_t prefix_len;
+
+ if (!_get_canonical_field_name(field, flen, field_canon, sizeof(field_canon), NULL))
+ return;
+ flen = strlen(field_canon);
+
+ for (t = types; t->data_fn; t++) {
+ prefix_len = strlen(t->prefix) - 1;
+
+ if (!strncasecmp(t->prefix, field_canon, prefix_len) &&
+ ((unprefixed_all_matched && (flen == prefix_len)) ||
+ (!strncasecmp(field_canon + prefix_len, "all", 3) &&
+ (flen == prefix_len + 3))))
+ *report_types |= t->id;
+ }
+}
+
+static uint32_t _all_match(struct dm_report *rh, const char *field, size_t flen)
+{
uint32_t report_types = 0;
unsigned unprefixed_all_matched = 0;
@@ -382,15 +1024,7 @@ static uint32_t _all_match(struct dm_report *rh, const char *field, size_t flen)
}
/* Combine all report types that have a matching prefix. */
- for (t = rh->types; t->data_fn; t++) {
- prefix_len = strlen(t->prefix);
-
- if (!strncasecmp(t->prefix, field, prefix_len) &&
- ((unprefixed_all_matched && (flen == prefix_len)) ||
- (!strncasecmp(field + prefix_len, "all", 3) &&
- (flen == prefix_len + 3))))
- report_types |= t->id;
- }
+ _all_match_combine(rh->types, unprefixed_all_matched, field, flen, &report_types);
return report_types;
}
@@ -403,48 +1037,83 @@ 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))
+ if ((rh->fields[f].type & type) && !_add_field(rh, f, 0, 0))
return 0;
return 1;
}
+static int _get_field(struct dm_report *rh, const char *field, size_t flen,
+ uint32_t *f_ret, int *implicit)
+{
+ char field_canon[DM_REPORT_FIELD_TYPE_ID_LEN];
+ uint32_t f;
+
+ if (!flen)
+ return 0;
+
+ if (!_get_canonical_field_name(field, flen, field_canon, sizeof(field_canon), NULL))
+ return_0;
+
+ for (f = 0; _implicit_report_fields[f].report_fn; f++) {
+ if (_is_same_field(_implicit_report_fields[f].id, field_canon, rh->field_prefix)) {
+ *f_ret = f;
+ *implicit = 1;
+ return 1;
+ }
+ }
+
+ for (f = 0; rh->fields[f].report_fn; f++) {
+ if (_is_same_field(rh->canonical_field_ids[f], field_canon, rh->field_prefix)) {
+ *f_ret = f;
+ *implicit = 0;
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
static int _field_match(struct dm_report *rh, const char *field, size_t flen,
unsigned report_type_only)
{
uint32_t f, type;
+ int implicit;
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 ((_get_field(rh, field, flen, &f, &implicit))) {
+ if (report_type_only) {
+ rh->report_types |= implicit ? _implicit_report_fields[f].type
+ : rh->fields[f].type;
+ return 1;
}
+ return _add_field(rh, f, implicit, 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 _add_all_fields(rh, type);
}
return 0;
}
-static int _add_sort_key(struct dm_report *rh, uint32_t field_num,
+static int _add_sort_key(struct dm_report *rh, uint32_t field_num, int implicit,
uint32_t flags, unsigned report_type_only)
{
struct field_properties *fp, *found = NULL;
+ const struct dm_report_field_type *fields = implicit ? _implicit_report_fields
+ : rh->fields;
dm_list_iterate_items(fp, &rh->field_props) {
- if (fp->field_num == field_num) {
+ if ((fp->implicit == implicit) && (fp->field_num == field_num)) {
found = fp;
break;
}
@@ -452,8 +1121,8 @@ static int _add_sort_key(struct dm_report *rh, uint32_t field_num,
if (!found) {
if (report_type_only)
- rh->report_types |= rh->fields[field_num].type;
- else if (!(found = _add_field(rh, field_num, FLD_HIDDEN)))
+ rh->report_types |= fields[field_num].type;
+ else if (!(found = _add_field(rh, field_num, implicit, FLD_HIDDEN)))
return_0;
}
@@ -461,8 +1130,8 @@ static int _add_sort_key(struct dm_report *rh, uint32_t field_num,
return 1;
if (found->flags & FLD_SORT_KEY) {
- log_error("dm_report: Ignoring duplicate sort field: %s",
- rh->fields[field_num].id);
+ log_warn("dm_report: Ignoring duplicate sort field: %s.",
+ fields[field_num].id);
return 1;
}
@@ -476,6 +1145,7 @@ static int _add_sort_key(struct dm_report *rh, uint32_t field_num,
static int _key_match(struct dm_report *rh, const char *key, size_t len,
unsigned report_type_only)
{
+ char key_canon[DM_REPORT_FIELD_TYPE_ID_LEN];
uint32_t f;
uint32_t flags;
@@ -498,10 +1168,16 @@ static int _key_match(struct dm_report *rh, const char *key, size_t len,
return 0;
}
+ if (!_get_canonical_field_name(key, len, key_canon, sizeof(key_canon), NULL))
+ return_0;
+
+ for (f = 0; _implicit_report_fields[f].report_fn; f++)
+ if (_is_same_field(_implicit_report_fields[f].id, key_canon, rh->field_prefix))
+ return _add_sort_key(rh, f, 1, flags, report_type_only);
+
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);
+ if (_is_same_field(rh->canonical_field_ids[f], key_canon, rh->field_prefix))
+ return _add_sort_key(rh, f, 0, flags, report_type_only);
return 0;
}
@@ -523,11 +1199,9 @@ static int _parse_fields(struct dm_report *rh, const char *format,
we++;
if (!_field_match(rh, ws, (size_t) (we - ws), report_type_only)) {
- _display_fields(rh);
+ _display_fields(rh, 1, 0);
log_warn(" ");
- if (strcasecmp(ws, "help") && strcmp(ws, "?"))
- log_error("Unrecognised field: %.*s",
- (int) (we - ws), ws);
+ log_error("Unrecognised field: %.*s", (int) (we - ws), ws);
return 0;
}
}
@@ -552,8 +1226,9 @@ static int _parse_keys(struct dm_report *rh, const char *keys,
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);
+ _display_fields(rh, 1, 0);
+ log_warn(" ");
+ log_error("dm_report: Unrecognised field: %.*s", (int) (we - ws), ws);
return 0;
}
}
@@ -561,6 +1236,87 @@ static int _parse_keys(struct dm_report *rh, const char *keys,
return 1;
}
+static int _contains_reserved_report_type(const struct dm_report_object_type *types)
+{
+ const struct dm_report_object_type *type, *implicit_type;
+
+ for (implicit_type = _implicit_report_types; implicit_type->data_fn; implicit_type++) {
+ for (type = types; type->data_fn; type++) {
+ if (implicit_type->id & type->id) {
+ log_error(INTERNAL_ERROR "dm_report_init: definition of report "
+ "types given contains reserved identifier");
+ return 1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static void _dm_report_init_update_types(struct dm_report *rh, uint32_t *report_types)
+{
+ const struct dm_report_object_type *type;
+
+ if (!report_types)
+ return;
+
+ *report_types = rh->report_types;
+ /*
+ * Do not include implicit types as these are not understood by
+ * dm_report_init caller - the caller doesn't know how to check
+ * these types anyway.
+ */
+ for (type = _implicit_report_types; type->data_fn; type++)
+ *report_types &= ~type->id;
+}
+
+static int _help_requested(struct dm_report *rh)
+{
+ struct field_properties *fp;
+
+ dm_list_iterate_items(fp, &rh->field_props) {
+ if (fp->implicit &&
+ (!strcmp(_implicit_report_fields[fp->field_num].id, SPECIAL_FIELD_HELP_ID) ||
+ !strcmp(_implicit_report_fields[fp->field_num].id, SPECIAL_FIELD_HELP_ALT_ID)))
+ return 1;
+ }
+
+ return 0;
+}
+
+static int _canonicalize_field_ids(struct dm_report *rh)
+{
+ size_t registered_field_count = 0, i;
+ char canonical_field[DM_REPORT_FIELD_TYPE_ID_LEN];
+ char *canonical_field_dup;
+ int differs;
+
+ while (*rh->fields[registered_field_count].id)
+ registered_field_count++;
+
+ if (!(rh->canonical_field_ids = dm_pool_alloc(rh->mem, registered_field_count * sizeof(const char *)))) {
+ log_error("_canonicalize_field_ids: dm_pool_alloc failed");
+ return 0;
+ }
+
+ for (i = 0; i < registered_field_count; i++) {
+ if (!_get_canonical_field_name(rh->fields[i].id, strlen(rh->fields[i].id),
+ canonical_field, sizeof(canonical_field), &differs))
+ return_0;
+
+ if (differs) {
+ if (!(canonical_field_dup = dm_pool_strdup(rh->mem, canonical_field))) {
+ log_error("_canonicalize_field_dup: dm_pool_alloc failed.");
+ return 0;
+ }
+ rh->canonical_field_ids[i] = canonical_field_dup;
+ } else
+ rh->canonical_field_ids[i] = rh->fields[i].id;
+ }
+
+ 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,
@@ -573,9 +1329,12 @@ struct dm_report *dm_report_init(uint32_t *report_types,
struct dm_report *rh;
const struct dm_report_object_type *type;
+ if (_contains_reserved_report_type(types))
+ return_NULL;
+
if (!(rh = dm_zalloc(sizeof(*rh)))) {
log_error("dm_report_init: dm_malloc failed");
- return 0;
+ return NULL;
}
/*
@@ -604,6 +1363,8 @@ struct dm_report *dm_report_init(uint32_t *report_types,
if (output_flags & DM_REPORT_OUTPUT_BUFFERED)
rh->flags |= RH_SORT_REQUIRED;
+ rh->flags |= RH_FIELD_CALC_NEEDED;
+
dm_list_init(&rh->field_props);
dm_list_init(&rh->rows);
@@ -618,6 +1379,11 @@ struct dm_report *dm_report_init(uint32_t *report_types,
return NULL;
}
+ if (!_canonicalize_field_ids(rh)) {
+ dm_report_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.
@@ -636,15 +1402,26 @@ struct dm_report *dm_report_init(uint32_t *report_types,
return NULL;
}
- /* Return updated types value for further compatility check by caller */
- if (report_types)
- *report_types = rh->report_types;
+ /*
+ * Return updated types value for further compatility check by caller.
+ */
+ _dm_report_init_update_types(rh, report_types);
+
+ if (_help_requested(rh)) {
+ _display_fields(rh, 1, 0);
+ log_warn(" ");
+ rh->flags |= RH_ALREADY_REPORTED;
+ }
return rh;
}
void dm_report_free(struct dm_report *rh)
{
+ if (rh->selection)
+ dm_pool_destroy(rh->selection->mem);
+ if (rh->value_cache)
+ dm_hash_destroy(rh->value_cache);
dm_pool_destroy(rh->mem);
dm_free(rh);
}
@@ -680,96 +1457,2781 @@ int dm_report_set_output_field_name_prefix(struct dm_report *rh, const char *out
static void *_report_get_field_data(struct dm_report *rh,
struct field_properties *fp, void *object)
{
+ const struct dm_report_field_type *fields = fp->implicit ? _implicit_report_fields
+ : rh->fields;
+
char *ret = fp->type->data_fn(object);
if (!ret)
return NULL;
- return (void *)(ret + rh->fields[fp->field_num].offset);
+ return (void *)(ret + fields[fp->field_num].offset);
}
-int dm_report_object(struct dm_report *rh, void *object)
+static void *_report_get_implicit_field_data(struct dm_report *rh __attribute__((unused)),
+ struct field_properties *fp, struct row *row)
+{
+ if (!strcmp(_implicit_report_fields[fp->field_num].id, SPECIAL_FIELD_SELECTED_ID))
+ return row;
+
+ return NULL;
+}
+
+static int _dbl_equal(double d1, double d2)
+{
+ return fabs(d1 - d2) < DBL_EPSILON;
+}
+
+static int _dbl_greater(double d1, double d2)
+{
+ return (d1 > d2) && !_dbl_equal(d1, d2);
+}
+
+static int _dbl_less(double d1, double d2)
+{
+ return (d1 < d2) && !_dbl_equal(d1, d2);
+}
+
+static int _dbl_greater_or_equal(double d1, double d2)
+{
+ return _dbl_greater(d1, d2) || _dbl_equal(d1, d2);
+}
+
+static int _dbl_less_or_equal(double d1, double d2)
+{
+ return _dbl_less(d1, d2) || _dbl_equal(d1, d2);
+}
+
+#define _uint64 *(const uint64_t *)
+#define _uint64arr(var,index) ((const uint64_t *)(var))[(index)]
+#define _str (const char *)
+#define _dbl *(const double *)
+#define _dblarr(var,index) ((const double *)(var))[(index)]
+
+static int _do_check_value_is_strictly_reserved(unsigned type, const void *res_val, int res_range,
+ const void *val, struct field_selection *fs)
+{
+ int sel_range = fs ? fs->value->next != NULL : 0;
+
+ switch (type & DM_REPORT_FIELD_TYPE_MASK) {
+ case DM_REPORT_FIELD_TYPE_NUMBER:
+ if (res_range && sel_range) {
+ /* both reserved value and selection value are ranges */
+ if (((_uint64 val >= _uint64arr(res_val,0)) && (_uint64 val <= _uint64arr(res_val,1))) ||
+ (fs && ((fs->value->v.i == _uint64arr(res_val,0)) && (fs->value->next->v.i == _uint64arr(res_val,1)))))
+ return 1;
+ } else if (res_range) {
+ /* only reserved value is a range */
+ if (((_uint64 val >= _uint64arr(res_val,0)) && (_uint64 val <= _uint64arr(res_val,1))) ||
+ (fs && ((fs->value->v.i >= _uint64arr(res_val,0)) && (fs->value->v.i <= _uint64arr(res_val,1)))))
+ return 1;
+ } else if (sel_range) {
+ /* only selection value is a range */
+ if (((_uint64 val >= _uint64 res_val) && (_uint64 val <= _uint64 res_val)) ||
+ (fs && ((fs->value->v.i >= _uint64 res_val) && (fs->value->next->v.i <= _uint64 res_val))))
+ return 1;
+ } else {
+ /* neither selection value nor reserved value is a range */
+ if ((_uint64 val == _uint64 res_val) ||
+ (fs && (fs->value->v.i == _uint64 res_val)))
+ return 1;
+ }
+ break;
+
+ case DM_REPORT_FIELD_TYPE_STRING:
+ /* there are no ranges for string type yet */
+ if ((!strcmp(_str val, _str res_val)) ||
+ (fs && (!strcmp(fs->value->v.s, _str res_val))))
+ return 1;
+ break;
+
+ case DM_REPORT_FIELD_TYPE_SIZE:
+ if (res_range && sel_range) {
+ /* both reserved value and selection value are ranges */
+ if ((_dbl_greater_or_equal(_dbl val, _dblarr(res_val,0)) && _dbl_less_or_equal(_dbl val, _dblarr(res_val,1))) ||
+ (fs && (_dbl_equal(fs->value->v.d, _dblarr(res_val,0)) && (_dbl_equal(fs->value->next->v.d, _dblarr(res_val,1))))))
+ return 1;
+ } else if (res_range) {
+ /* only reserved value is a range */
+ if ((_dbl_greater_or_equal(_dbl val, _dblarr(res_val,0)) && _dbl_less_or_equal(_dbl val, _dblarr(res_val,1))) ||
+ (fs && (_dbl_greater_or_equal(fs->value->v.d, _dblarr(res_val,0)) && _dbl_less_or_equal(fs->value->v.d, _dblarr(res_val,1)))))
+ return 1;
+ } else if (sel_range) {
+ /* only selection value is a range */
+ if ((_dbl_greater_or_equal(_dbl val, _dbl res_val) && (_dbl_less_or_equal(_dbl val, _dbl res_val))) ||
+ (fs && (_dbl_greater_or_equal(fs->value->v.d, _dbl res_val) && _dbl_less_or_equal(fs->value->next->v.d, _dbl res_val))))
+ return 1;
+ } else {
+ /* neither selection value nor reserved value is a range */
+ if ((_dbl_equal(_dbl val, _dbl res_val)) ||
+ (fs && (_dbl_equal(fs->value->v.d, _dbl res_val))))
+ return 1;
+ }
+ break;
+
+ case DM_REPORT_FIELD_TYPE_STRING_LIST:
+ /* FIXME Add comparison for string list */
+ break;
+ case DM_REPORT_FIELD_TYPE_TIME:
+ /* FIXME Add comparison for time */
+ break;
+ }
+
+ return 0;
+}
+
+/*
+ * Used to check whether a value of certain type used in selection is reserved.
+ */
+static int _check_value_is_strictly_reserved(struct dm_report *rh, uint32_t field_num, unsigned type,
+ const void *val, struct field_selection *fs)
+{
+ const struct dm_report_reserved_value *iter = rh->reserved_values;
+ const struct dm_report_field_reserved_value *frv;
+ int res_range;
+
+ if (!iter)
+ return 0;
+
+ while (iter->value) {
+ /* Only check strict reserved values, not the weaker form ("named" reserved value). */
+ if (!(iter->type & DM_REPORT_FIELD_RESERVED_VALUE_NAMED)) {
+ res_range = iter->type & DM_REPORT_FIELD_RESERVED_VALUE_RANGE;
+ if ((iter->type & DM_REPORT_FIELD_TYPE_MASK) == DM_REPORT_FIELD_TYPE_NONE) {
+ frv = (const struct dm_report_field_reserved_value *) iter->value;
+ if (frv->field_num == field_num && _do_check_value_is_strictly_reserved(type, frv->value, res_range, val, fs))
+ return 1;
+ } else if (iter->type & type && _do_check_value_is_strictly_reserved(type, iter->value, res_range, val, fs))
+ return 1;
+ }
+ iter++;
+ }
+
+ return 0;
+}
+
+static int _cmp_field_int(struct dm_report *rh, uint32_t field_num, const char *field_id,
+ uint64_t val, struct field_selection *fs)
+{
+ int range = fs->value->next != NULL;
+ const uint64_t sel1 = fs->value->v.i;
+ const uint64_t sel2 = range ? fs->value->next->v.i : 0;
+
+ switch(fs->flags & FLD_CMP_MASK) {
+ case FLD_CMP_EQUAL:
+ return range ? ((val >= sel1) && (val <= sel2)) : val == sel1;
+
+ case FLD_CMP_NOT|FLD_CMP_EQUAL:
+ return range ? !((val >= sel1) && (val <= sel2)) : val != sel1;
+
+ case FLD_CMP_NUMBER|FLD_CMP_GT:
+ if (_check_value_is_strictly_reserved(rh, field_num, DM_REPORT_FIELD_TYPE_NUMBER, &val, fs))
+ return 0;
+ return range ? val > sel2 : val > sel1;
+
+ case FLD_CMP_NUMBER|FLD_CMP_GT|FLD_CMP_EQUAL:
+ if (_check_value_is_strictly_reserved(rh, field_num, DM_REPORT_FIELD_TYPE_NUMBER, &val, fs))
+ return 0;
+ return val >= sel1;
+
+ case FLD_CMP_NUMBER|FLD_CMP_LT:
+ if (_check_value_is_strictly_reserved(rh, field_num, DM_REPORT_FIELD_TYPE_NUMBER, &val, fs))
+ return 0;
+ return val < sel1;
+
+ case FLD_CMP_NUMBER|FLD_CMP_LT|FLD_CMP_EQUAL:
+ if (_check_value_is_strictly_reserved(rh, field_num, DM_REPORT_FIELD_TYPE_NUMBER, &val, fs))
+ return 0;
+ return range ? val <= sel2 : val <= sel1;
+
+ default:
+ log_error(INTERNAL_ERROR "_cmp_field_int: unsupported number "
+ "comparison type for field %s", field_id);
+ }
+
+ return 0;
+}
+
+static int _cmp_field_double(struct dm_report *rh, uint32_t field_num, const char *field_id,
+ double val, struct field_selection *fs)
+{
+ int range = fs->value->next != NULL;
+ double sel1 = fs->value->v.d;
+ double sel2 = range ? fs->value->next->v.d : 0;
+
+ switch(fs->flags & FLD_CMP_MASK) {
+ case FLD_CMP_EQUAL:
+ return range ? (_dbl_greater_or_equal(val, sel1) && _dbl_less_or_equal(val, sel2))
+ : _dbl_equal(val, sel1);
+
+ case FLD_CMP_NOT|FLD_CMP_EQUAL:
+ return range ? !(_dbl_greater_or_equal(val, sel1) && _dbl_less_or_equal(val, sel2))
+ : !_dbl_equal(val, sel1);
+
+ case FLD_CMP_NUMBER|FLD_CMP_GT:
+ if (_check_value_is_strictly_reserved(rh, field_num, DM_REPORT_FIELD_TYPE_SIZE, &val, fs))
+ return 0;
+ return range ? _dbl_greater(val, sel2)
+ : _dbl_greater(val, sel1);
+
+ case FLD_CMP_NUMBER|FLD_CMP_GT|FLD_CMP_EQUAL:
+ if (_check_value_is_strictly_reserved(rh, field_num, DM_REPORT_FIELD_TYPE_SIZE, &val, fs))
+ return 0;
+ return _dbl_greater_or_equal(val, sel1);
+
+ case FLD_CMP_NUMBER|FLD_CMP_LT:
+ if (_check_value_is_strictly_reserved(rh, field_num, DM_REPORT_FIELD_TYPE_SIZE, &val, fs))
+ return 0;
+ return _dbl_less(val, sel1);
+
+ case FLD_CMP_NUMBER|FLD_CMP_LT|FLD_CMP_EQUAL:
+ if (_check_value_is_strictly_reserved(rh, field_num, DM_REPORT_FIELD_TYPE_SIZE, &val, fs))
+ return 0;
+ return range ? _dbl_less_or_equal(val, sel2) : _dbl_less_or_equal(val, sel1);
+
+ default:
+ log_error(INTERNAL_ERROR "_cmp_field_double: unsupported number "
+ "comparison type for selection field %s", field_id);
+ }
+
+ return 0;
+}
+
+static int _cmp_field_string(struct dm_report *rh __attribute__((unused)),
+ uint32_t field_num, const char *field_id,
+ const char *val, struct field_selection *fs)
+{
+ const char *sel = fs->value->v.s;
+
+ switch (fs->flags & FLD_CMP_MASK) {
+ case FLD_CMP_EQUAL:
+ return !strcmp(val, sel);
+ case FLD_CMP_NOT|FLD_CMP_EQUAL:
+ return strcmp(val, sel);
+ default:
+ log_error(INTERNAL_ERROR "_cmp_field_string: unsupported string "
+ "comparison type for selection field %s", field_id);
+ }
+
+ return 0;
+}
+
+static int _cmp_field_time(struct dm_report *rh,
+ uint32_t field_num, const char *field_id,
+ time_t val, struct field_selection *fs)
+{
+ int range = fs->value->next != NULL;
+ time_t sel1 = fs->value->v.t;
+ time_t sel2 = range ? fs->value->next->v.t : 0;
+
+ switch(fs->flags & FLD_CMP_MASK) {
+ case FLD_CMP_EQUAL:
+ return range ? ((val >= sel1) && (val <= sel2)) : val == sel1;
+ case FLD_CMP_NOT|FLD_CMP_EQUAL:
+ return range ? ((val >= sel1) && (val <= sel2)) : val != sel1;
+ case FLD_CMP_TIME|FLD_CMP_GT:
+ if (_check_value_is_strictly_reserved(rh, field_num, DM_REPORT_FIELD_TYPE_TIME, &val, fs))
+ return 0;
+ return range ? val > sel2 : val > sel1;
+ case FLD_CMP_TIME|FLD_CMP_GT|FLD_CMP_EQUAL:
+ if (_check_value_is_strictly_reserved(rh, field_num, DM_REPORT_FIELD_TYPE_TIME, &val, fs))
+ return 0;
+ return val >= sel1;
+ case FLD_CMP_TIME|FLD_CMP_LT:
+ if (_check_value_is_strictly_reserved(rh, field_num, DM_REPORT_FIELD_TYPE_TIME, &val, fs))
+ return 0;
+ return val < sel1;
+ case FLD_CMP_TIME|FLD_CMP_LT|FLD_CMP_EQUAL:
+ if (_check_value_is_strictly_reserved(rh, field_num, DM_REPORT_FIELD_TYPE_TIME, &val, fs))
+ return 0;
+ return range ? val <= sel2 : val <= sel1;
+ default:
+ log_error(INTERNAL_ERROR "_cmp_field_time: unsupported time "
+ "comparison type for field %s", field_id);
+ }
+
+ return 0;
+}
+
+/* Matches if all items from selection string list match list value strictly 1:1. */
+static int _cmp_field_string_list_strict_all(const struct str_list_sort_value *val,
+ const struct selection_str_list *sel)
+{
+ unsigned int sel_list_size = dm_list_size(&sel->str_list.list);
+ struct dm_str_list *sel_item;
+ unsigned int i = 1;
+
+ if (!val->items) {
+ if (sel_list_size == 1) {
+ /* match blank string list with selection defined as blank string only */
+ sel_item = dm_list_item(dm_list_first(&sel->str_list.list), struct dm_str_list);
+ return !strcmp(sel_item->str, "");
+ }
+ return 0;
+ }
+
+ /* if item count differs, it's clear the lists do not match */
+ if (val->items[0].pos != sel_list_size)
+ return 0;
+
+ /* both lists are sorted so they either match 1:1 or not */
+ dm_list_iterate_items(sel_item, &sel->str_list.list) {
+ if ((strlen(sel_item->str) != val->items[i].len) ||
+ strncmp(sel_item->str, val->value + val->items[i].pos, val->items[i].len))
+ return 0;
+ i++;
+ }
+
+ return 1;
+}
+
+/* Matches if all items from selection string list match a subset of list value. */
+static int _cmp_field_string_list_subset_all(const struct str_list_sort_value *val,
+ const struct selection_str_list *sel)
{
+ unsigned int sel_list_size = dm_list_size(&sel->str_list.list);
+ struct dm_str_list *sel_item;
+ unsigned int i, last_found = 1;
+ int r = 0;
+
+ if (!val->items) {
+ if (sel_list_size == 1) {
+ /* match blank string list with selection defined as blank string only */
+ sel_item = dm_list_item(dm_list_first(&sel->str_list.list), struct dm_str_list);
+ return !strcmp(sel_item->str, "");
+ }
+ return 0;
+ }
+
+ /* check selection is a subset of the value */
+ dm_list_iterate_items(sel_item, &sel->str_list.list) {
+ r = 0;
+ for (i = last_found; i <= val->items[0].pos; i++) {
+ if ((strlen(sel_item->str) == val->items[i].len) &&
+ !strncmp(sel_item->str, val->value + val->items[i].pos, val->items[i].len)) {
+ last_found = i;
+ r = 1;
+ }
+ }
+ if (!r)
+ break;
+ }
+
+ return r;
+}
+
+/* Matches if any item from selection string list matches list value. */
+static int _cmp_field_string_list_any(const struct str_list_sort_value *val,
+ const struct selection_str_list *sel)
+{
+ struct dm_str_list *sel_item;
+ unsigned int i;
+
+ /* match blank string list with selection that contains blank string */
+ if (!val->items) {
+ dm_list_iterate_items(sel_item, &sel->str_list.list) {
+ if (!strcmp(sel_item->str, ""))
+ return 1;
+ }
+ return 0;
+ }
+
+ dm_list_iterate_items(sel_item, &sel->str_list.list) {
+ /*
+ * TODO: Optimize this so we don't need to compare the whole lists' content.
+ * Make use of the fact that the lists are sorted!
+ */
+ for (i = 1; i <= val->items[0].pos; i++) {
+ if ((strlen(sel_item->str) == val->items[i].len) &&
+ !strncmp(sel_item->str, val->value + val->items[i].pos, val->items[i].len))
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static int _cmp_field_string_list(struct dm_report *rh __attribute__((unused)),
+ uint32_t field_num, const char *field_id,
+ const struct str_list_sort_value *val,
+ struct field_selection *fs)
+{
+ const struct selection_str_list *sel = fs->value->v.l;
+ int subset, r;
+
+ switch (sel->type & SEL_LIST_MASK) {
+ case SEL_LIST_LS:
+ subset = 0;
+ break;
+ case SEL_LIST_SUBSET_LS:
+ subset = 1;
+ break;
+ default:
+ log_error(INTERNAL_ERROR "_cmp_field_string_list: unknown list type");
+ return 0;
+ }
+
+ switch (sel->type & SEL_MASK) {
+ case SEL_AND:
+ r = subset ? _cmp_field_string_list_subset_all(val, sel)
+ : _cmp_field_string_list_strict_all(val, sel);
+ break;
+ case SEL_OR:
+ r = _cmp_field_string_list_any(val, sel);
+ break;
+ default:
+ log_error(INTERNAL_ERROR "_cmp_field_string_list: unsupported string "
+ "list type found, expecting either AND or OR list for "
+ "selection field %s", field_id);
+ return 0;
+ }
+
+ return fs->flags & FLD_CMP_NOT ? !r : r;
+}
+
+static int _cmp_field_regex(const char *s, struct field_selection *fs)
+{
+ int match = dm_regex_match(fs->value->v.r, s) >= 0;
+ return fs->flags & FLD_CMP_NOT ? !match : match;
+}
+
+static int _compare_selection_field(struct dm_report *rh,
+ struct dm_report_field *f,
+ struct field_selection *fs)
+{
+ const struct dm_report_field_type *fields = f->props->implicit ? _implicit_report_fields
+ : rh->fields;
+ const char *field_id = fields[f->props->field_num].id;
+ int r = 0;
+
+ if (!f->sort_value) {
+ log_error("_compare_selection_field: field without value :%d",
+ f->props->field_num);
+ return 0;
+ }
+
+ if (fs->flags & FLD_CMP_REGEX)
+ r = _cmp_field_regex((const char *) f->sort_value, fs);
+ else {
+ switch(f->props->flags & DM_REPORT_FIELD_TYPE_MASK) {
+ case DM_REPORT_FIELD_TYPE_PERCENT:
+ /*
+ * Check against real percent values only.
+ * That means DM_PERCENT_0 <= percent <= DM_PERCENT_100.
+ */
+ if (*(const uint64_t *) f->sort_value > DM_PERCENT_100)
+ return 0;
+ /* fall through */
+ case DM_REPORT_FIELD_TYPE_NUMBER:
+ r = _cmp_field_int(rh, f->props->field_num, field_id, *(const uint64_t *) f->sort_value, fs);
+ break;
+ case DM_REPORT_FIELD_TYPE_SIZE:
+ r = _cmp_field_double(rh, f->props->field_num, field_id, *(const double *) f->sort_value, fs);
+ break;
+ case DM_REPORT_FIELD_TYPE_STRING:
+ r = _cmp_field_string(rh, f->props->field_num, field_id, (const char *) f->sort_value, fs);
+ break;
+ case DM_REPORT_FIELD_TYPE_STRING_LIST:
+ r = _cmp_field_string_list(rh, f->props->field_num, field_id, (const struct str_list_sort_value *) f->sort_value, fs);
+ break;
+ case DM_REPORT_FIELD_TYPE_TIME:
+ r = _cmp_field_time(rh, f->props->field_num, field_id, *(const time_t *) f->sort_value, fs);
+ break;
+ default:
+ log_error(INTERNAL_ERROR "_compare_selection_field: unknown field type for field %s", field_id);
+ }
+ }
+
+ return r;
+}
+
+static int _check_selection(struct dm_report *rh, struct selection_node *sn,
+ struct dm_list *fields)
+{
+ int r;
+ struct selection_node *iter_n;
+ struct dm_report_field *f;
+
+ switch (sn->type & SEL_MASK) {
+ case SEL_ITEM:
+ r = 1;
+ dm_list_iterate_items(f, fields) {
+ if (sn->selection.item->fp != f->props)
+ continue;
+ if (!_compare_selection_field(rh, f, sn->selection.item))
+ r = 0;
+ }
+ break;
+ case SEL_OR:
+ r = 0;
+ dm_list_iterate_items(iter_n, &sn->selection.set)
+ if ((r |= _check_selection(rh, iter_n, fields)))
+ break;
+ break;
+ case SEL_AND:
+ r = 1;
+ dm_list_iterate_items(iter_n, &sn->selection.set)
+ if (!(r &= _check_selection(rh, iter_n, fields)))
+ break;
+ break;
+ default:
+ log_error("Unsupported selection type");
+ return 0;
+ }
+
+ return (sn->type & SEL_MODIFIER_NOT) ? !r : r;
+}
+
+static int _check_report_selection(struct dm_report *rh, struct dm_list *fields)
+{
+ if (!rh->selection || !rh->selection->selection_root)
+ return 1;
+
+ return _check_selection(rh, rh->selection->selection_root, fields);
+}
+
+static int _do_report_object(struct dm_report *rh, void *object, int do_output, int *selected)
+{
+ const struct dm_report_field_type *fields;
struct field_properties *fp;
- struct row *row;
+ struct row *row = NULL;
struct dm_report_field *field;
void *data = NULL;
+ int r = 0;
if (!rh) {
- log_error(INTERNAL_ERROR "dm_report handler is NULL.");
+ log_error(INTERNAL_ERROR "_do_report_object: dm_report handler is NULL.");
+ return 0;
+ }
+
+ if (!do_output && !selected) {
+ log_error(INTERNAL_ERROR "_do_report_object: output not requested and "
+ "selected output variable is NULL too.");
return 0;
}
+ if (rh->flags & RH_ALREADY_REPORTED)
+ return 1;
+
if (!(row = dm_pool_zalloc(rh->mem, sizeof(*row)))) {
- log_error("dm_report_object: struct row allocation failed");
+ log_error("_do_report_object: struct row allocation failed");
return 0;
}
+ if (!rh->first_row)
+ rh->first_row = row;
+
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: "
+ log_error("_do_report_object: "
"row sort value structure allocation failed");
- return 0;
+ goto out;
}
dm_list_init(&row->fields);
- dm_list_add(&rh->rows, &row->list);
+ row->selected = 1;
/* 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: "
+ log_error("_do_report_object: "
"struct dm_report_field allocation failed");
- return 0;
+ goto out;
}
+
+ if (fp->implicit) {
+ fields = _implicit_report_fields;
+ if (!strcmp(fields[fp->field_num].id, SPECIAL_FIELD_SELECTED_ID))
+ row->field_sel_status = field;
+ } else
+ fields = rh->fields;
+
field->props = fp;
- data = _report_get_field_data(rh, fp, object);
- if (!data)
- return 0;
+ data = fp->implicit ? _report_get_implicit_field_data(rh, fp, row)
+ : _report_get_field_data(rh, fp, object);
+ if (!data) {
+ log_error("_do_report_object: "
+ "no data assigned to field %s",
+ fields[fp->field_num].id);
+ goto out;
+ }
- if (!rh->fields[fp->field_num].report_fn(rh, rh->mem,
+ if (!fields[fp->field_num].report_fn(rh, rh->mem,
field, data,
rh->private)) {
- log_error("dm_report_object: "
+ log_error("_do_report_object: "
"report function failed for field %s",
- rh->fields[fp->field_num].id);
- return 0;
+ fields[fp->field_num].id);
+ goto out;
}
- if (((int) strlen(field->report_string) > field->props->width))
- field->props->width = (int) strlen(field->report_string);
+ dm_list_add(&row->fields, &field->list);
+ }
- if ((rh->flags & RH_SORT_REQUIRED) &&
- (field->props->flags & FLD_SORT_KEY)) {
- (*row->sort_fields)[field->props->sort_posn] = field;
+ r = 1;
+
+ if (!_check_report_selection(rh, &row->fields)) {
+ row->selected = 0;
+
+ /*
+ * If the row is not selected, we still keep it for output if either:
+ * - we're displaying special "selected" field in the row,
+ * - or the report is supposed to be on output multiple times
+ * where each output can have a new selection defined.
+ */
+ if (!row->field_sel_status && !(rh->flags & DM_REPORT_OUTPUT_MULTIPLE_TIMES))
+ goto out;
+
+ if (row->field_sel_status) {
+ /*
+ * If field with id "selected" is reported,
+ * report the row although it does not pass
+ * the selection criteria.
+ * The "selected" field reports the result
+ * of the selection.
+ */
+ _implicit_report_fields[row->field_sel_status->props->field_num].report_fn(rh,
+ rh->mem, row->field_sel_status, row, rh->private);
+ /*
+ * If the "selected" field is not displayed, e.g.
+ * because it is part of the sort field list,
+ * skip the display of the row as usual unless
+ * we plan to do the output multiple times.
+ */
+ if ((row->field_sel_status->props->flags & FLD_HIDDEN) &&
+ !(rh->flags & DM_REPORT_OUTPUT_MULTIPLE_TIMES))
+ goto out;
}
- dm_list_add(&row->fields, &field->list);
}
+ if (!do_output)
+ goto out;
+
+ dm_list_add(&rh->rows, &row->list);
+
if (!(rh->flags & DM_REPORT_OUTPUT_BUFFERED))
return dm_report_output(rh);
+out:
+ if (selected)
+ *selected = row->selected;
+ if (!do_output || !r)
+ dm_pool_free(rh->mem, row);
+ return r;
+}
+
+static int _do_report_compact_fields(struct dm_report *rh, int global)
+{
+ struct dm_report_field *field;
+ struct field_properties *fp;
+ struct row *row;
+
+ if (!rh) {
+ log_error("dm_report_enable_compact_output: dm report handler is NULL.");
+ return 0;
+ }
+
+ if (!(rh->flags & DM_REPORT_OUTPUT_BUFFERED) ||
+ dm_list_empty(&rh->rows))
+ return 1;
+
+ /*
+ * At first, mark all fields with FLD_HIDDEN flag.
+ * Also, mark field with FLD_COMPACTED flag, but only
+ * the ones that didn't have FLD_HIDDEN set before.
+ * This prevents losing the original FLD_HIDDEN flag
+ * in next step...
+ */
+ dm_list_iterate_items(fp, &rh->field_props) {
+ if (fp->flags & FLD_HIDDEN)
+ continue;
+ if (global || (fp->flags & FLD_COMPACT_ONE))
+ fp->flags |= (FLD_COMPACTED | FLD_HIDDEN);
+ }
+
+ /*
+ * ...check each field in a row and if its report value
+ * is not empty, drop the FLD_COMPACTED and FLD_HIDDEN
+ * flag if FLD_COMPACTED flag is set. It's important
+ * to keep FLD_HIDDEN flag for the fields that were
+ * already marked with FLD_HIDDEN before - these don't
+ * have FLD_COMPACTED set - check this condition!
+ */
+ dm_list_iterate_items(row, &rh->rows) {
+ dm_list_iterate_items(field, &row->fields) {
+ if ((field->report_string && *field->report_string) &&
+ field->props->flags & FLD_COMPACTED)
+ field->props->flags &= ~(FLD_COMPACTED | FLD_HIDDEN);
+ }
+ }
+
+ /*
+ * The fields left with FLD_COMPACTED and FLD_HIDDEN flag are
+ * the ones which have blank value in all rows. The FLD_HIDDEN
+ * will cause such field to not be reported on output at all.
+ */
+
+ return 1;
+}
+
+int dm_report_compact_fields(struct dm_report *rh)
+{
+ return _do_report_compact_fields(rh, 1);
+}
+
+static int _field_to_compact_match(struct dm_report *rh, const char *field, size_t flen)
+{
+ struct field_properties *fp;
+ uint32_t f;
+ int implicit;
+
+ if ((_get_field(rh, field, flen, &f, &implicit))) {
+ dm_list_iterate_items(fp, &rh->field_props) {
+ if ((fp->implicit == implicit) && (fp->field_num == f)) {
+ fp->flags |= FLD_COMPACT_ONE;
+ break;
+ }
+ }
+ return 1;
+ }
+
+ return 0;
+}
+
+static int _parse_fields_to_compact(struct dm_report *rh, const char *fields)
+{
+ const char *ws; /* Word start */
+ const char *we = fields; /* Word end */
+
+ if (!fields)
+ return 1;
+
+ while (*we) {
+ while (*we && *we == ',')
+ we++;
+ ws = we;
+ while (*we && *we != ',')
+ we++;
+ if (!_field_to_compact_match(rh, ws, (size_t) (we - ws))) {
+ log_error("dm_report: Unrecognized field: %.*s", (int) (we - ws), ws);
+ return 0;
+ }
+ }
return 1;
}
+int dm_report_compact_given_fields(struct dm_report *rh, const char *fields)
+{
+ if (!_parse_fields_to_compact(rh, fields))
+ return_0;
+
+ return _do_report_compact_fields(rh, 0);
+}
+
+int dm_report_object(struct dm_report *rh, void *object)
+{
+ return _do_report_object(rh, object, 1, NULL);
+}
+
+int dm_report_object_is_selected(struct dm_report *rh, void *object, int do_output, int *selected)
+{
+ return _do_report_object(rh, object, do_output, selected);
+}
+
+/*
+ * Selection parsing
+ */
+
+/*
+ * Other tokens (FIELD, VALUE, STRING, NUMBER, REGEX)
+ * FIELD := <strings of alphabet, number and '_'>
+ * VALUE := NUMBER | STRING
+ * REGEX := <strings quoted by '"', '\'', '(', '{', '[' or unquoted>
+ * NUMBER := <strings of [0-9]> (because sort_value is unsigned)
+ * STRING := <strings quoted by '"', '\'' or unquoted>
+ */
+
+static const char * _skip_space(const char *s)
+{
+ while (*s && isspace(*s))
+ s++;
+ return s;
+}
+
+static int _tok_op(struct op_def *t, const char *s, const char **end,
+ uint32_t expect)
+{
+ size_t len;
+
+ s = _skip_space(s);
+
+ for (; t->string; t++) {
+ if (expect && !(t->flags & expect))
+ continue;
+
+ len = strlen(t->string);
+ if (!strncmp(s, t->string, len)) {
+ if (end)
+ *end = s + len;
+ return t->flags;
+ }
+ }
+
+ if (end)
+ *end = s;
+ return 0;
+}
+
+static int _tok_op_log(const char *s, const char **end, uint32_t expect)
+{
+ return _tok_op(_op_log, s, end, expect);
+}
+
+static int _tok_op_cmp(const char *s, const char **end)
+{
+ return _tok_op(_op_cmp, s, end, 0);
+}
+
+static char _get_and_skip_quote_char(char const **s)
+{
+ char c = 0;
+
+ if (**s == '"' || **s == '\'') {
+ c = **s;
+ (*s)++;
+ }
+
+ return c;
+}
+
+ /*
+ *
+ * Input:
+ * s - a pointer to the parsed string
+ * Output:
+ * begin - a pointer to the beginning of the token
+ * end - a pointer to the end of the token + 1
+ * or undefined if return value is NULL
+ * return value - a starting point of the next parsing or
+ * NULL if 's' doesn't match with token type
+ * (the parsing should be terminated)
+ */
+static const char *_tok_value_number(const char *s,
+ const char **begin, const char **end)
+
+{
+ int is_float = 0;
+
+ *begin = s;
+ while ((!is_float && (*s == '.') && ++is_float) || isdigit(*s))
+ s++;
+ *end = s;
+
+ if (*begin == *end)
+ return NULL;
+
+ return s;
+}
+
+/*
+ * Input:
+ * s - a pointer to the parsed string
+ * endchar - terminating character
+ * end_op_flags - terminating operator flags (see _op_log)
+ * (if endchar is non-zero then endflags is ignored)
+ * Output:
+ * begin - a pointer to the beginning of the token
+ * end - a pointer to the end of the token + 1
+ * end_op_flag_hit - the flag from endflags hit during parsing
+ * return value - a starting point of the next parsing
+ */
+static const char *_tok_value_string(const char *s,
+ const char **begin, const char **end,
+ const char endchar, uint32_t end_op_flags,
+ uint32_t *end_op_flag_hit)
+{
+ uint32_t flag_hit = 0;
+
+ *begin = s;
+
+ /*
+ * If endchar is defined, scan the string till
+ * the endchar or the end of string is hit.
+ * This is in case the string is quoted and we
+ * know exact character that is the stopper.
+ */
+ if (endchar) {
+ while (*s && *s != endchar)
+ s++;
+ if (*s != endchar) {
+ log_error("Missing end quote.");
+ return NULL;
+ }
+ *end = s;
+ s++;
+ } else {
+ /*
+ * If endchar is not defined then endchar is/are the
+ * operator/s as defined by 'endflags' arg or space char.
+ * This is in case the string is not quoted and
+ * we don't know which character is the exact stopper.
+ */
+ while (*s) {
+ if ((flag_hit = _tok_op(_op_log, s, NULL, end_op_flags)) || *s == ' ')
+ break;
+ s++;
+ }
+ *end = s;
+ /*
+ * If we hit one of the strings as defined by 'endflags'
+ * and if 'endflag_hit' arg is provided, save the exact
+ * string flag that was hit.
+ */
+ if (end_op_flag_hit)
+ *end_op_flag_hit = flag_hit;
+ }
+
+ return s;
+}
+
+static const char *_reserved_name(struct dm_report *rh,
+ const struct dm_report_reserved_value *reserved,
+ const struct dm_report_field_reserved_value *frv,
+ uint32_t field_num, const char *s, size_t len)
+{
+ dm_report_reserved_handler handler;
+ const char *canonical_name = NULL;
+ const char **name;
+ char *tmp_s;
+ char c;
+ int r;
+
+ name = reserved->names;
+ while (*name) {
+ if ((strlen(*name) == len) && !strncmp(*name, s, len))
+ return *name;
+ name++;
+ }
+
+ if (reserved->type & DM_REPORT_FIELD_RESERVED_VALUE_FUZZY_NAMES) {
+ handler = (dm_report_reserved_handler) (frv ? frv->value : reserved->value);
+ c = s[len];
+ tmp_s = (char *) s;
+ tmp_s[len] = '\0';
+ if ((r = handler(rh, rh->selection->mem, field_num,
+ DM_REPORT_RESERVED_PARSE_FUZZY_NAME,
+ tmp_s, (const void **) &canonical_name)) <= 0) {
+ if (r == -1)
+ log_error(INTERNAL_ERROR "%s reserved value handler for field %s has missing "
+ "implementation of DM_REPORT_RESERVED_PARSE_FUZZY_NAME action",
+ (reserved->type & DM_REPORT_FIELD_TYPE_MASK) ? "type-specific" : "field-specific",
+ rh->fields[field_num].id);
+ else
+ log_error("Error occured while processing %s reserved value handler for field %s",
+ (reserved->type & DM_REPORT_FIELD_TYPE_MASK) ? "type-specific" : "field-specific",
+ rh->fields[field_num].id);
+ }
+ tmp_s[len] = c;
+ if (r && canonical_name)
+ return canonical_name;
+ }
+
+ return NULL;
+}
+
+/*
+ * Used to replace a string representation of the reserved value
+ * found in selection with the exact reserved value of certain type.
+ */
+static const char *_get_reserved(struct dm_report *rh, unsigned type,
+ uint32_t field_num, int implicit,
+ const char *s, const char **begin, const char **end,
+ struct reserved_value_wrapper *rvw)
+{
+ const struct dm_report_reserved_value *iter = implicit ? NULL : rh->reserved_values;
+ const struct dm_report_field_reserved_value *frv;
+ const char *tmp_begin = NULL, *tmp_end = NULL, *tmp_s = s;
+ const char *name = NULL;
+ char c;
+
+ rvw->reserved = NULL;
+
+ if (!iter)
+ return s;
+
+ c = _get_and_skip_quote_char(&tmp_s);
+ if (!(tmp_s = _tok_value_string(tmp_s, &tmp_begin, &tmp_end, c, SEL_AND | SEL_OR | SEL_PRECEDENCE_PE, NULL)))
+ return s;
+
+ while (iter->value) {
+ if (!(iter->type & DM_REPORT_FIELD_TYPE_MASK)) {
+ /* DM_REPORT_FIELD_TYPE_NONE - per-field reserved value */
+ frv = (const struct dm_report_field_reserved_value *) iter->value;
+ if ((frv->field_num == field_num) && (name = _reserved_name(rh, iter, frv, field_num,
+ tmp_begin, tmp_end - tmp_begin)))
+ break;
+ } else if (iter->type & type) {
+ /* DM_REPORT_FIELD_TYPE_* - per-type reserved value */
+ if ((name = _reserved_name(rh, iter, NULL, field_num,
+ tmp_begin, tmp_end - tmp_begin)))
+ break;
+ }
+ iter++;
+ }
+
+ if (name) {
+ /* found! */
+ *begin = tmp_begin;
+ *end = tmp_end;
+ s = tmp_s;
+ rvw->reserved = iter;
+ rvw->matched_name = name;
+ }
+
+ return s;
+}
+
+float dm_percent_to_float(dm_percent_t percent)
+{
+ /* Add 0.f to prevent returning -0.00 */
+ return (float) percent / DM_PERCENT_1 + 0.f;
+}
+
+float dm_percent_to_round_float(dm_percent_t percent, unsigned digits)
+{
+ static const float power10[] = {
+ 1.f, .1f, .01f, .001f, .0001f, .00001f, .000001f,
+ .0000001f, .00000001f, .000000001f,
+ .0000000001f
+ };
+ float r;
+ float f = dm_percent_to_float(percent);
+
+ if (digits >= DM_ARRAY_SIZE(power10))
+ digits = DM_ARRAY_SIZE(power10) - 1; /* no better precision */
+
+ r = DM_PERCENT_1 * power10[digits];
+
+ if ((percent < r) && (percent > DM_PERCENT_0))
+ f = power10[digits];
+ else if ((percent > (DM_PERCENT_100 - r)) && (percent < DM_PERCENT_100))
+ f = (float) (DM_PERCENT_100 - r) / DM_PERCENT_1;
+
+ return f;
+}
+
+dm_percent_t dm_make_percent(uint64_t numerator, uint64_t denominator)
+{
+ dm_percent_t percent;
+
+ if (!denominator)
+ return DM_PERCENT_100; /* FIXME? */
+ if (!numerator)
+ return DM_PERCENT_0;
+ if (numerator == denominator)
+ return DM_PERCENT_100;
+ switch (percent = DM_PERCENT_100 * ((double) numerator / (double) denominator)) {
+ case DM_PERCENT_100:
+ return DM_PERCENT_100 - 1;
+ case DM_PERCENT_0:
+ return DM_PERCENT_0 + 1;
+ default:
+ return percent;
+ }
+}
+
+int dm_report_value_cache_set(struct dm_report *rh, const char *name, const void *data)
+{
+ if (!rh->value_cache && (!(rh->value_cache = dm_hash_create(64)))) {
+ log_error("Failed to create cache for values used during reporting.");
+ return 0;
+ }
+
+ return dm_hash_insert(rh->value_cache, name, (void *) data);
+}
+
+const void *dm_report_value_cache_get(struct dm_report *rh, const char *name)
+{
+ return (rh->value_cache) ? dm_hash_lookup(rh->value_cache, name) : NULL;
+}
+
+/*
+ * Used to check whether the reserved_values definition passed to
+ * dm_report_init_with_selection contains only supported reserved value types.
+ */
+static int _check_reserved_values_supported(const struct dm_report_field_type fields[],
+ const struct dm_report_reserved_value reserved_values[])
+{
+ const struct dm_report_reserved_value *iter;
+ const struct dm_report_field_reserved_value *field_res;
+ const struct dm_report_field_type *field;
+ static uint32_t supported_reserved_types = DM_REPORT_FIELD_TYPE_NUMBER |
+ DM_REPORT_FIELD_TYPE_SIZE |
+ DM_REPORT_FIELD_TYPE_PERCENT |
+ DM_REPORT_FIELD_TYPE_STRING |
+ DM_REPORT_FIELD_TYPE_TIME;
+ static uint32_t supported_reserved_types_with_range = DM_REPORT_FIELD_RESERVED_VALUE_RANGE |
+ DM_REPORT_FIELD_TYPE_NUMBER |
+ DM_REPORT_FIELD_TYPE_SIZE |
+ DM_REPORT_FIELD_TYPE_PERCENT |
+ DM_REPORT_FIELD_TYPE_TIME;
+
+
+ if (!reserved_values)
+ return 1;
+
+ iter = reserved_values;
+
+ while (iter->value) {
+ if (iter->type & DM_REPORT_FIELD_TYPE_MASK) {
+ if (!(iter->type & supported_reserved_types) ||
+ ((iter->type & DM_REPORT_FIELD_RESERVED_VALUE_RANGE) &&
+ !(iter->type & supported_reserved_types_with_range))) {
+ log_error(INTERNAL_ERROR "_check_reserved_values_supported: "
+ "global reserved value for type 0x%x not supported",
+ iter->type);
+ return 0;
+ }
+ } else {
+ field_res = (const struct dm_report_field_reserved_value *) iter->value;
+ field = &fields[field_res->field_num];
+ if (!(field->flags & supported_reserved_types) ||
+ ((iter->type & DM_REPORT_FIELD_RESERVED_VALUE_RANGE) &&
+ !(iter->type & supported_reserved_types_with_range))) {
+ log_error(INTERNAL_ERROR "_check_reserved_values_supported: "
+ "field-specific reserved value of type 0x%x for "
+ "field %s not supported",
+ field->flags & DM_REPORT_FIELD_TYPE_MASK, field->id);
+ return 0;
+ }
+ }
+ iter++;
+ }
+
+ return 1;
+}
+
+/*
+ * Input:
+ * ft - field type for which the value is parsed
+ * s - a pointer to the parsed string
+ * Output:
+ * begin - a pointer to the beginning of the token
+ * end - a pointer to the end of the token + 1
+ * flags - parsing flags
+ */
+static const char *_tok_value_regex(struct dm_report *rh,
+ const struct dm_report_field_type *ft,
+ const char *s, const char **begin,
+ const char **end, uint32_t *flags,
+ struct reserved_value_wrapper *rvw)
+{
+ char c;
+ rvw->reserved = NULL;
+
+ s = _skip_space(s);
+
+ if (!*s) {
+ log_error("Regular expression expected for selection field %s", ft->id);
+ return NULL;
+ }
+
+ switch (*s) {
+ case '(': c = ')'; break;
+ case '{': c = '}'; break;
+ case '[': c = ']'; break;
+ case '"': /* fall through */
+ case '\'': c = *s; break;
+ default: c = 0;
+ }
+
+ if (!(s = _tok_value_string(c ? s + 1 : s, begin, end, c, SEL_AND | SEL_OR | SEL_PRECEDENCE_PE, NULL))) {
+ log_error("Failed to parse regex value for selection field %s.", ft->id);
+ return NULL;
+ }
+
+ *flags |= DM_REPORT_FIELD_TYPE_STRING;
+ return s;
+}
+
+static int _str_list_item_cmp(const void *a, const void *b)
+{
+ const struct dm_str_list * const *item_a = (const struct dm_str_list * const *) a;
+ const struct dm_str_list * const *item_b = (const struct dm_str_list * const *) b;
+
+ return strcmp((*item_a)->str, (*item_b)->str);
+}
+
+static int _add_item_to_string_list(struct dm_pool *mem, const char *begin,
+ const char *end, struct dm_list *list)
+{
+ struct dm_str_list *item;
+
+ if (!(item = dm_pool_zalloc(mem, sizeof(*item))) ||
+ !(item->str = begin == end ? "" : dm_pool_strndup(mem, begin, end - begin))) {
+ log_error("_add_item_to_string_list: memory allocation failed for string list item");
+ return 0;
+ }
+ dm_list_add(list, &item->list);
+
+ return 1;
+}
+
+/*
+ * Input:
+ * ft - field type for which the value is parsed
+ * mem - memory pool to allocate from
+ * s - a pointer to the parsed string
+ * Output:
+ * begin - a pointer to the beginning of the token (whole list)
+ * end - a pointer to the end of the token + 1 (whole list)
+ * sel_str_list - the list of strings parsed
+ */
+static const char *_tok_value_string_list(const struct dm_report_field_type *ft,
+ struct dm_pool *mem, const char *s,
+ const char **begin, const char **end,
+ struct selection_str_list **sel_str_list)
+{
+ static const char _str_list_item_parsing_failed[] = "Failed to parse string list value "
+ "for selection field %s.";
+ struct selection_str_list *ssl = NULL;
+ struct dm_str_list *item;
+ const char *begin_item = NULL, *end_item = NULL, *tmp;
+ uint32_t op_flags, end_op_flag_expected, end_op_flag_hit = 0;
+ struct dm_str_list **arr;
+ size_t list_size;
+ unsigned int i;
+ int list_end = 0;
+ char c;
+
+ if (!(ssl = dm_pool_alloc(mem, sizeof(*ssl)))) {
+ log_error("_tok_value_string_list: memory allocation failed for selection list");
+ goto bad;
+ }
+ dm_list_init(&ssl->str_list.list);
+ ssl->type = 0;
+ *begin = s;
+
+ if (!(op_flags = _tok_op_log(s, &tmp, SEL_LIST_LS | SEL_LIST_SUBSET_LS))) {
+ /* Only one item - SEL_LIST_{SUBSET_}LS and SEL_LIST_{SUBSET_}LE not used */
+ c = _get_and_skip_quote_char(&s);
+ if (!(s = _tok_value_string(s, &begin_item, &end_item, c, SEL_AND | SEL_OR | SEL_PRECEDENCE_PE, NULL))) {
+ log_error(_str_list_item_parsing_failed, ft->id);
+ goto bad;
+ }
+ if (!_add_item_to_string_list(mem, begin_item, end_item, &ssl->str_list.list))
+ goto_bad;
+ ssl->type = SEL_OR | SEL_LIST_LS;
+ goto out;
+ }
+
+ /* More than one item - items enclosed in SEL_LIST_LS and SEL_LIST_LE
+ * or SEL_LIST_SUBSET_LS and SEL_LIST_SUBSET_LE.
+ * Each element is terminated by AND or OR operator or 'list end'.
+ * The first operator hit is then the one allowed for the whole list,
+ * no mixing allowed!
+ */
+
+ /* Are we using [] or {} for the list? */
+ end_op_flag_expected = (op_flags == SEL_LIST_LS) ? SEL_LIST_LE : SEL_LIST_SUBSET_LE;
+
+ op_flags = SEL_LIST_LE | SEL_LIST_SUBSET_LE | SEL_AND | SEL_OR;
+ s++;
+ while (*s) {
+ s = _skip_space(s);
+ c = _get_and_skip_quote_char(&s);
+ if (!(s = _tok_value_string(s, &begin_item, &end_item, c, op_flags, NULL))) {
+ log_error(_str_list_item_parsing_failed, ft->id);
+ goto bad;
+ }
+ s = _skip_space(s);
+
+ if (!(end_op_flag_hit = _tok_op_log(s, &tmp, op_flags))) {
+ log_error("Invalid operator in selection list.");
+ goto bad;
+ }
+
+ if (end_op_flag_hit & (SEL_LIST_LE | SEL_LIST_SUBSET_LE)) {
+ list_end = 1;
+ if (end_op_flag_hit != end_op_flag_expected) {
+ for (i = 0; _op_log[i].string; i++)
+ if (_op_log[i].flags == end_op_flag_expected)
+ break;
+ log_error("List ended with incorrect character, "
+ "expecting \'%s\'.", _op_log[i].string);
+ goto bad;
+ }
+ }
+
+ if (ssl->type) {
+ if (!list_end && !(ssl->type & end_op_flag_hit)) {
+ log_error("Only one type of logical operator allowed "
+ "in selection list at a time.");
+ goto bad;
+ }
+ } else {
+ if (list_end)
+ ssl->type = end_op_flag_expected == SEL_LIST_LE ? SEL_AND : SEL_OR;
+ else
+ ssl->type = end_op_flag_hit;
+ }
+
+ if (!_add_item_to_string_list(mem, begin_item, end_item, &ssl->str_list.list))
+ goto_bad;
+
+ s = tmp;
+
+ if (list_end)
+ break;
+ }
+
+ if (!(end_op_flag_hit & (SEL_LIST_LE | SEL_LIST_SUBSET_LE))) {
+ log_error("Missing list end for selection field %s", ft->id);
+ goto bad;
+ }
+
+ /* Store information whether [] or {} was used. */
+ if (end_op_flag_expected == SEL_LIST_LE)
+ ssl->type |= SEL_LIST_LS;
+ else
+ ssl->type |= SEL_LIST_SUBSET_LS;
+
+ /* Sort the list. */
+ if (!(list_size = dm_list_size(&ssl->str_list.list))) {
+ log_error(INTERNAL_ERROR "_tok_value_string_list: list has no items");
+ goto bad;
+ } else if (list_size == 1)
+ goto out;
+ if (!(arr = dm_malloc(sizeof(item) * list_size))) {
+ log_error("_tok_value_string_list: memory allocation failed for sort array");
+ goto bad;
+ }
+
+ i = 0;
+ dm_list_iterate_items(item, &ssl->str_list.list)
+ arr[i++] = item;
+ qsort(arr, list_size, sizeof(item), _str_list_item_cmp);
+ dm_list_init(&ssl->str_list.list);
+ for (i = 0; i < list_size; i++)
+ dm_list_add(&ssl->str_list.list, &arr[i]->list);
+
+ dm_free(arr);
+out:
+ *end = s;
+ if (sel_str_list)
+ *sel_str_list = ssl;
+
+ return s;
+bad:
+ *end = s;
+ if (ssl)
+ dm_pool_free(mem, ssl);
+ if (sel_str_list)
+ *sel_str_list = NULL;
+ return s;
+}
+
+struct time_value {
+ int range;
+ time_t t1;
+ time_t t2;
+};
+
+static const char *_out_of_range_msg = "Field selection value %s out of supported range for field %s.";
+
+/*
+ * Standard formatted date and time - ISO8601.
+ *
+ * date time timezone
+ *
+ * date:
+ * YYYY-MM-DD (or shortly YYYYMMDD)
+ * YYYY-MM (shortly YYYYMM), auto DD=1
+ * YYYY, auto MM=01 and DD=01
+ *
+ * time:
+ * hh:mm:ss (or shortly hhmmss)
+ * hh:mm (or shortly hhmm), auto ss=0
+ * hh (or shortly hh), auto mm=0, auto ss=0
+ *
+ * timezone:
+ * +hh:mm or -hh:mm (or shortly +hhmm or -hhmm)
+ * +hh or -hh
+*/
+
+#define DELIM_DATE '-'
+#define DELIM_TIME ':'
+
+static int _days_in_month[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
+
+static int _is_leap_year(long year)
+{
+ return (((year % 4==0) && (year % 100 != 0)) || (year % 400 == 0));
+}
+
+static int _get_days_in_month(long month, long year)
+{
+ return (month == 2 && _is_leap_year(year)) ? _days_in_month[month-1] + 1
+ : _days_in_month[month-1];
+}
+
+typedef enum {
+ RANGE_NONE,
+ RANGE_SECOND,
+ RANGE_MINUTE,
+ RANGE_HOUR,
+ RANGE_DAY,
+ RANGE_MONTH,
+ RANGE_YEAR
+} time_range_t;
+
+static char *_get_date(char *str, struct tm *tm, time_range_t *range)
+{
+ static const char incorrect_date_format_msg[] = "Incorrect date format.";
+ time_range_t tmp_range = RANGE_NONE;
+ long n1, n2 = -1, n3 = -1;
+ char *s = str, *end;
+ size_t len = 0;
+
+ if (!isdigit(*s))
+ /* we need a year at least */
+ return NULL;
+
+ n1 = strtol(s, &end, 10);
+ if (*end == DELIM_DATE) {
+ len += (4 - (end - s)); /* diff in length from standard YYYY */
+ s = end + 1;
+ if (isdigit(*s)) {
+ n2 = strtol(s, &end, 10);
+ len += (2 - (end - s)); /* diff in length from standard MM */
+ if (*end == DELIM_DATE) {
+ s = end + 1;
+ n3 = strtol(s, &end, 10);
+ len += (2 - (end - s)); /* diff in length from standard DD */
+ }
+ }
+ }
+
+ len = len + end - str;
+
+ /* variations from standard YYYY-MM-DD */
+ if (n3 == -1) {
+ if (n2 == -1) {
+ if (len == 4) {
+ /* YYYY */
+ tmp_range = RANGE_YEAR;
+ n3 = n2 = 1;
+ } else if (len == 6) {
+ /* YYYYMM */
+ tmp_range = RANGE_MONTH;
+ n3 = 1;
+ n2 = n1 % 100;
+ n1 = n1 / 100;
+ } else if (len == 8) {
+ tmp_range = RANGE_DAY;
+ /* YYYYMMDD */
+ n3 = n1 % 100;
+ n2 = (n1 / 100) % 100;
+ n1 = n1 / 10000;
+ } else {
+ log_error(incorrect_date_format_msg);
+ return NULL;
+ }
+ } else {
+ if (len == 7) {
+ tmp_range = RANGE_MONTH;
+ /* YYYY-MM */
+ n3 = 1;
+ } else {
+ log_error(incorrect_date_format_msg);
+ return NULL;
+ }
+ }
+ }
+
+ if (n2 < 1 || n2 > 12) {
+ log_error("Specified month out of range.");
+ return NULL;
+ }
+
+ if (n3 < 1 || n3 > _get_days_in_month(n2, n1)) {
+ log_error("Specified day out of range.");
+ return NULL;
+ }
+
+ if (tmp_range == RANGE_NONE)
+ tmp_range = RANGE_DAY;
+
+ tm->tm_year = n1 - 1900;
+ tm->tm_mon = n2 - 1;
+ tm->tm_mday = n3;
+ *range = tmp_range;
+
+ return (char *) _skip_space(end);
+}
+
+static char *_get_time(char *str, struct tm *tm, time_range_t *range)
+{
+ static const char incorrect_time_format_msg[] = "Incorrect time format.";
+ time_range_t tmp_range = RANGE_NONE;
+ long n1, n2 = -1, n3 = -1;
+ char *s = str, *end;
+ size_t len = 0;
+
+ if (!isdigit(*s)) {
+ /* time is not compulsory */
+ tm->tm_hour = tm->tm_min = tm->tm_sec = 0;
+ return (char *) _skip_space(s);
+ }
+
+ n1 = strtol(s, &end, 10);
+ if (*end == DELIM_TIME) {
+ len += (2 - (end - s)); /* diff in length from standard HH */
+ s = end + 1;
+ if (isdigit(*s)) {
+ n2 = strtol(s, &end, 10);
+ len += (2 - (end - s)); /* diff in length from standard MM */
+ if (*end == DELIM_TIME) {
+ s = end + 1;
+ n3 = strtol(s, &end, 10);
+ len += (2 - (end - s)); /* diff in length from standard SS */
+ }
+ }
+ }
+
+ len = len + end - str;
+
+ /* variations from standard HH:MM:SS */
+ if (n3 == -1) {
+ if (n2 == -1) {
+ if (len == 2) {
+ /* HH */
+ tmp_range = RANGE_HOUR;
+ n3 = n2 = 0;
+ } else if (len == 4) {
+ /* HHMM */
+ tmp_range = RANGE_MINUTE;
+ n3 = 0;
+ n2 = n1 % 100;
+ n1 = n1 / 100;
+ } else if (len == 6) {
+ /* HHMMSS */
+ tmp_range = RANGE_SECOND;
+ n3 = n1 % 100;
+ n2 = (n1 / 100) % 100;
+ n1 = n1 / 10000;
+ } else {
+ log_error(incorrect_time_format_msg);
+ return NULL;
+ }
+ } else {
+ if (len == 5) {
+ /* HH:MM */
+ tmp_range = RANGE_MINUTE;
+ n3 = 0;
+ } else {
+ log_error(incorrect_time_format_msg);
+ return NULL;
+ }
+ }
+ }
+
+ if (n1 < 0 || n1 > 23) {
+ log_error("Specified hours out of range.");
+ return NULL;
+ }
+
+ if (n2 < 0 || n2 > 60) {
+ log_error("Specified minutes out of range.");
+ return NULL;
+ }
+
+ if (n3 < 0 || n3 > 60) {
+ log_error("Specified seconds out of range.");
+ return NULL;
+ }
+
+ /* Just time without exact date is incomplete! */
+ if (*range != RANGE_DAY) {
+ log_error("Full date specification needed.");
+ return NULL;
+ }
+
+ tm->tm_hour = n1;
+ tm->tm_min = n2;
+ tm->tm_sec = n3;
+ *range = tmp_range;
+
+ return (char *) _skip_space(end);
+}
+
+/* The offset is always an absolute offset against GMT! */
+static char *_get_tz(char *str, int *tz_supplied, int *offset)
+{
+ long n1, n2 = -1;
+ char *s = str, *end;
+ int sign = 1; /* +HH:MM by default */
+ size_t len = 0;
+
+ *tz_supplied = 0;
+ *offset = 0;
+
+ if (!isdigit(*s)) {
+ if (*s == '+') {
+ sign = 1;
+ s = s + 1;
+ } else if (*s == '-') {
+ sign = -1;
+ s = s + 1;
+ } else
+ return (char *) _skip_space(s);
+ }
+
+ n1 = strtol(s, &end, 10);
+ if (*end == DELIM_TIME) {
+ len = (2 - (end - s)); /* diff in length from standard HH */
+ s = end + 1;
+ if (isdigit(*s)) {
+ n2 = strtol(s, &end, 10);
+ len = (2 - (end - s)); /* diff in length from standard MM */
+ }
+ }
+
+ len = len + end - s;
+
+ /* variations from standard HH:MM */
+ if (n2 == -1) {
+ if (len == 2) {
+ /* HH */
+ n2 = 0;
+ } else if (len == 4) {
+ /* HHMM */
+ n2 = n1 % 100;
+ n1 = n1 / 100;
+ } else
+ return NULL;
+ }
+
+ if (n2 < 0 || n2 > 60)
+ return NULL;
+
+ if (n1 < 0 || n1 > 14)
+ return NULL;
+
+ /* timezone offset in seconds */
+ *offset = sign * ((n1 * 3600) + (n2 * 60));
+ *tz_supplied = 1;
+ return (char *) _skip_space(end);
+}
+
+static int _local_tz_offset(time_t t_local)
+{
+ struct tm tm_gmt;
+ time_t t_gmt;
+
+ gmtime_r(&t_local, &tm_gmt);
+ t_gmt = mktime(&tm_gmt);
+
+ /*
+ * gmtime returns time that is adjusted
+ * for DST.Subtract this adjustment back
+ * to give us proper *absolute* offset
+ * for our local timezone.
+ */
+ if (tm_gmt.tm_isdst)
+ t_gmt -= 3600;
+
+ return t_local - t_gmt;
+}
+
+static void _get_final_time(time_range_t range, struct tm *tm,
+ int tz_supplied, int offset,
+ struct time_value *tval)
+{
+
+ struct tm tm_up = *tm;
+
+ switch (range) {
+ case RANGE_SECOND:
+ if (tm_up.tm_sec < 59) {
+ tm_up.tm_sec += 1;
+ break;
+ }
+ /* fall through */
+ case RANGE_MINUTE:
+ if (tm_up.tm_min < 59) {
+ tm_up.tm_min += 1;
+ break;
+ }
+ /* fall through */
+ case RANGE_HOUR:
+ if (tm_up.tm_hour < 23) {
+ tm_up.tm_hour += 1;
+ break;
+ }
+ /* fall through */
+ case RANGE_DAY:
+ if (tm_up.tm_mday < _get_days_in_month(tm_up.tm_mon, tm_up.tm_year)) {
+ tm_up.tm_mday += 1;
+ break;
+ }
+ /* fall through */
+ case RANGE_MONTH:
+ if (tm_up.tm_mon < 11) {
+ tm_up.tm_mon += 1;
+ break;
+ }
+ /* fall through */
+ case RANGE_YEAR:
+ tm_up.tm_year += 1;
+ break;
+ case RANGE_NONE:
+ /* nothing to do here */
+ break;
+ }
+
+ tval->range = (range != RANGE_NONE);
+ tval->t1 = mktime(tm);
+ tval->t2 = mktime(&tm_up) - 1;
+
+ if (tz_supplied) {
+ /*
+ * The 'offset' is with respect to the GMT.
+ * Calculate what the offset is with respect
+ * to our local timezone and adjust times
+ * so they represent time in our local timezone.
+ */
+ offset -= _local_tz_offset(tval->t1);
+ tval->t1 -= offset;
+ tval->t2 -= offset;
+ }
+}
+
+static int _parse_formatted_date_time(char *str, struct time_value *tval)
+{
+ time_range_t range = RANGE_NONE;
+ struct tm tm = {0};
+ int gmt_offset;
+ int tz_supplied;
+
+ tm.tm_year = tm.tm_mday = tm.tm_mon = -1;
+ tm.tm_hour = tm.tm_min = tm.tm_sec = -1;
+ tm.tm_isdst = tm.tm_wday = tm.tm_yday = -1;
+
+ if (!(str = _get_date(str, &tm, &range)))
+ return 0;
+
+ if (!(str = _get_time(str, &tm, &range)))
+ return 0;
+
+ if (!(str = _get_tz(str, &tz_supplied, &gmt_offset)))
+ return 0;
+
+ if (*str)
+ return 0;
+
+ _get_final_time(range, &tm, tz_supplied, gmt_offset, tval);
+
+ return 1;
+}
+
+static const char *_tok_value_time(const struct dm_report_field_type *ft,
+ struct dm_pool *mem, const char *s,
+ const char **begin, const char **end,
+ struct time_value *tval)
+{
+ char *time_str = NULL;
+ const char *r = NULL;
+ uint64_t t;
+ char c;
+
+ s = _skip_space(s);
+
+ if (*s == '@') {
+ /* Absolute time value in number of seconds since epoch. */
+ if (!(s = _tok_value_number(s+1, begin, end)))
+ goto_out;
+
+ if (!(time_str = dm_pool_strndup(mem, *begin, *end - *begin))) {
+ log_error("_tok_value_time: dm_pool_strndup failed");
+ goto out;
+ }
+
+ errno = 0;
+ if (((t = strtoull(time_str, NULL, 10)) == ULLONG_MAX) && errno == ERANGE) {
+ log_error(_out_of_range_msg, time_str, ft->id);
+ goto out;
+ }
+
+ tval->range = 0;
+ tval->t1 = (time_t) t;
+ tval->t2 = 0;
+ r = s;
+ } else {
+ c = _get_and_skip_quote_char(&s);
+ if (!(s = _tok_value_string(s, begin, end, c, SEL_AND | SEL_OR | SEL_PRECEDENCE_PE, NULL)))
+ goto_out;
+
+ if (!(time_str = dm_pool_strndup(mem, *begin, *end - *begin))) {
+ log_error("tok_value_time: dm_pool_strndup failed");
+ goto out;
+ }
+
+ if (!_parse_formatted_date_time(time_str, tval))
+ goto_out;
+ r = s;
+ }
+out:
+ if (time_str)
+ dm_pool_free(mem, time_str);
+ return r;
+}
+
+/*
+ * Input:
+ * ft - field type for which the value is parsed
+ * s - a pointer to the parsed string
+ * mem - memory pool to allocate from
+ * Output:
+ * begin - a pointer to the beginning of the token
+ * end - a pointer to the end of the token + 1
+ * flags - parsing flags
+ * custom - custom data specific to token type
+ * (e.g. size unit factor)
+ */
+static const char *_tok_value(struct dm_report *rh,
+ const struct dm_report_field_type *ft,
+ uint32_t field_num, int implicit,
+ const char *s,
+ const char **begin, const char **end,
+ uint32_t *flags,
+ struct reserved_value_wrapper *rvw,
+ struct dm_pool *mem, void *custom)
+{
+ int expected_type = ft->flags & DM_REPORT_FIELD_TYPE_MASK;
+ struct selection_str_list **str_list;
+ struct time_value *tval;
+ uint64_t *factor;
+ const char *tmp;
+ char c;
+
+ s = _skip_space(s);
+
+ s = _get_reserved(rh, expected_type, field_num, implicit, s, begin, end, rvw);
+ if (rvw->reserved) {
+ /*
+ * FLD_CMP_NUMBER shares operators with FLD_CMP_TIME,
+ * so adjust flags here based on expected type.
+ */
+ if (expected_type == DM_REPORT_FIELD_TYPE_TIME)
+ *flags &= ~FLD_CMP_NUMBER;
+ else if (expected_type == DM_REPORT_FIELD_TYPE_NUMBER)
+ *flags &= ~FLD_CMP_TIME;
+ *flags |= expected_type;
+ return s;
+ }
+
+ switch (expected_type) {
+
+ case DM_REPORT_FIELD_TYPE_STRING:
+ c = _get_and_skip_quote_char(&s);
+ if (!(s = _tok_value_string(s, begin, end, c, SEL_AND | SEL_OR | SEL_PRECEDENCE_PE, NULL))) {
+ log_error("Failed to parse string value "
+ "for selection field %s.", ft->id);
+ return NULL;
+ }
+ *flags |= DM_REPORT_FIELD_TYPE_STRING;
+ break;
+
+ case DM_REPORT_FIELD_TYPE_STRING_LIST:
+ if (!(str_list = (struct selection_str_list **) custom))
+ goto_bad;
+
+ s = _tok_value_string_list(ft, mem, s, begin, end, str_list);
+ if (!(*str_list)) {
+ log_error("Failed to parse string list value "
+ "for selection field %s.", ft->id);
+ return NULL;
+ }
+ *flags |= DM_REPORT_FIELD_TYPE_STRING_LIST;
+ break;
+
+ case DM_REPORT_FIELD_TYPE_NUMBER:
+ /* fall through */
+ case DM_REPORT_FIELD_TYPE_SIZE:
+ /* fall through */
+ case DM_REPORT_FIELD_TYPE_PERCENT:
+ if (!(s = _tok_value_number(s, begin, end))) {
+ log_error("Failed to parse numeric value "
+ "for selection field %s.", ft->id);
+ return NULL;
+ }
+
+ if (*s == DM_PERCENT_CHAR) {
+ s++;
+ c = DM_PERCENT_CHAR;
+ if (expected_type != DM_REPORT_FIELD_TYPE_PERCENT) {
+ log_error("Found percent value but %s value "
+ "expected for selection field %s.",
+ expected_type == DM_REPORT_FIELD_TYPE_NUMBER ?
+ "numeric" : "size", ft->id);
+ return NULL;
+ }
+ } else {
+ if (!(factor = (uint64_t *) custom))
+ goto_bad;
+
+ if ((*factor = dm_units_to_factor(s, &c, 0, &tmp))) {
+ s = tmp;
+ if (expected_type != DM_REPORT_FIELD_TYPE_SIZE) {
+ log_error("Found size unit specifier "
+ "but %s value expected for "
+ "selection field %s.",
+ expected_type == DM_REPORT_FIELD_TYPE_NUMBER ?
+ "numeric" : "percent", ft->id);
+ return NULL;
+ }
+ } else if (expected_type == DM_REPORT_FIELD_TYPE_SIZE) {
+ /*
+ * If size unit is not defined in the selection
+ * and the type expected is size, use use 'm'
+ * (1 MiB) for the unit by default. This is the
+ * same behaviour as seen in lvcreate -L <size>.
+ */
+ *factor = 1024*1024;
+ }
+ }
+
+ *flags |= expected_type;
+ /*
+ * FLD_CMP_NUMBER shares operators with FLD_CMP_TIME,
+ * but we have NUMBER here, so remove FLD_CMP_TIME.
+ */
+ *flags &= ~FLD_CMP_TIME;
+ break;
+
+ case DM_REPORT_FIELD_TYPE_TIME:
+ if (!(tval = (struct time_value *) custom))
+ goto_bad;
+
+ if (!(s = _tok_value_time(ft, mem, s, begin, end, tval))) {
+ log_error("Failed to parse time value "
+ "for selection field %s.", ft->id);
+ return NULL;
+ }
+
+ *flags |= DM_REPORT_FIELD_TYPE_TIME;
+ /*
+ * FLD_CMP_TIME shares operators with FLD_CMP_NUMBER,
+ * but we have TIME here, so remove FLD_CMP_NUMBER.
+ */
+ *flags &= ~FLD_CMP_NUMBER;
+ break;
+ }
+
+ return s;
+bad:
+ log_error(INTERNAL_ERROR "Forbidden NULL custom detected.");
+
+ return NULL;
+}
+
+/*
+ * Input:
+ * s - a pointer to the parsed string
+ * Output:
+ * begin - a pointer to the beginning of the token
+ * end - a pointer to the end of the token + 1
+ */
+static const char *_tok_field_name(const char *s,
+ const char **begin, const char **end)
+{
+ char c;
+ s = _skip_space(s);
+
+ *begin = s;
+ while ((c = *s) &&
+ (isalnum(c) || c == '_' || c == '-'))
+ s++;
+ *end = s;
+
+ if (*begin == *end)
+ return NULL;
+
+ return s;
+}
+
+static int _get_reserved_value(struct dm_report *rh, uint32_t field_num,
+ struct reserved_value_wrapper *rvw)
+{
+ const void *tmp_value;
+ dm_report_reserved_handler handler;
+ int r;
+
+ if (!rvw->reserved) {
+ rvw->value = NULL;
+ return 1;
+ }
+
+ if (rvw->reserved->type & DM_REPORT_FIELD_TYPE_MASK)
+ /* type reserved value */
+ tmp_value = rvw->reserved->value;
+ else
+ /* per-field reserved value */
+ tmp_value = ((const struct dm_report_field_reserved_value *) rvw->reserved->value)->value;
+
+ if (rvw->reserved->type & (DM_REPORT_FIELD_RESERVED_VALUE_DYNAMIC_VALUE | DM_REPORT_FIELD_RESERVED_VALUE_FUZZY_NAMES)) {
+ handler = (dm_report_reserved_handler) tmp_value;
+ if ((r = handler(rh, rh->selection->mem, field_num,
+ DM_REPORT_RESERVED_GET_DYNAMIC_VALUE,
+ rvw->matched_name, &tmp_value)) <= 0) {
+ if (r == -1)
+ log_error(INTERNAL_ERROR "%s reserved value handler for field %s has missing"
+ "implementation of DM_REPORT_RESERVED_GET_DYNAMIC_VALUE action",
+ (rvw->reserved->type) & DM_REPORT_FIELD_TYPE_MASK ? "type-specific" : "field-specific",
+ rh->fields[field_num].id);
+ else
+ log_error("Error occured while processing %s reserved value handler for field %s",
+ (rvw->reserved->type) & DM_REPORT_FIELD_TYPE_MASK ? "type-specific" : "field-specific",
+ rh->fields[field_num].id);
+ return 0;
+ }
+ }
+
+ rvw->value = tmp_value;
+ return 1;
+}
+
+static struct field_selection *_create_field_selection(struct dm_report *rh,
+ uint32_t field_num,
+ int implicit,
+ const char *v,
+ size_t len,
+ uint32_t flags,
+ struct reserved_value_wrapper *rvw,
+ void *custom)
+{
+ static const char *_field_selection_value_alloc_failed_msg = "dm_report: struct field_selection_value allocation failed for selection field %s";
+ const struct dm_report_field_type *fields = implicit ? _implicit_report_fields
+ : rh->fields;
+ struct field_properties *fp, *found = NULL;
+ struct field_selection *fs;
+ const char *field_id;
+ struct time_value *tval;
+ uint64_t factor;
+ char *s;
+
+ dm_list_iterate_items(fp, &rh->field_props) {
+ if ((fp->implicit == implicit) && (fp->field_num == field_num)) {
+ found = fp;
+ break;
+ }
+ }
+
+ /* The field is neither used in display options nor sort keys. */
+ if (!found) {
+ if (rh->selection->add_new_fields) {
+ if (!(found = _add_field(rh, field_num, implicit, FLD_HIDDEN)))
+ return NULL;
+ rh->report_types |= fields[field_num].type;
+ } else {
+ log_error("Unable to create selection with field \'%s\' "
+ "which is not included in current report.",
+ implicit ? _implicit_report_fields[field_num].id
+ : rh->fields[field_num].id);
+ return NULL;
+ }
+ }
+
+ field_id = fields[found->field_num].id;
+
+ if (!(found->flags & flags & DM_REPORT_FIELD_TYPE_MASK)) {
+ log_error("dm_report: incompatible comparison "
+ "type for selection field %s", field_id);
+ return NULL;
+ }
+
+ /* set up selection */
+ if (!(fs = dm_pool_zalloc(rh->selection->mem, sizeof(struct field_selection)))) {
+ log_error("dm_report: struct field_selection "
+ "allocation failed for selection field %s", field_id);
+ return NULL;
+ }
+
+ if (!(fs->value = dm_pool_zalloc(rh->selection->mem, sizeof(struct field_selection_value)))) {
+ log_error(_field_selection_value_alloc_failed_msg, field_id);
+ goto error;
+ }
+
+ if (((rvw->reserved && (rvw->reserved->type & DM_REPORT_FIELD_RESERVED_VALUE_RANGE)) ||
+ (((flags & DM_REPORT_FIELD_TYPE_MASK) == DM_REPORT_FIELD_TYPE_TIME) &&
+ custom && ((struct time_value *) custom)->range))
+ &&
+ !(fs->value->next = dm_pool_zalloc(rh->selection->mem, sizeof(struct field_selection_value)))) {
+ log_error(_field_selection_value_alloc_failed_msg, field_id);
+ goto error;
+ }
+
+ fs->fp = found;
+ fs->flags = flags;
+
+ if (!_get_reserved_value(rh, field_num, rvw)) {
+ log_error("dm_report: could not get reserved value "
+ "while processing selection field %s", field_id);
+ goto error;
+ }
+
+ /* store comparison operand */
+ if (flags & FLD_CMP_REGEX) {
+ /* REGEX */
+ if (!(s = dm_malloc(len + 1))) {
+ log_error("dm_report: dm_malloc failed to store "
+ "regex value for selection field %s", field_id);
+ goto error;
+ }
+ memcpy(s, v, len);
+ s[len] = '\0';
+
+ fs->value->v.r = dm_regex_create(rh->selection->mem, (const char * const *) &s, 1);
+ dm_free(s);
+ if (!fs->value->v.r) {
+ log_error("dm_report: failed to create regex "
+ "matcher for selection field %s", field_id);
+ goto error;
+ }
+ } else {
+ /* STRING, NUMBER, SIZE, PERCENT, STRING_LIST, TIME */
+ if (!(s = dm_pool_strndup(rh->selection->mem, v, len))) {
+ log_error("dm_report: dm_pool_strndup for value "
+ "of selection field %s", field_id);
+ goto error;
+ }
+
+ switch (flags & DM_REPORT_FIELD_TYPE_MASK) {
+ case DM_REPORT_FIELD_TYPE_STRING:
+ if (rvw->value) {
+ fs->value->v.s = (const char *) rvw->value;
+ if (rvw->reserved->type & DM_REPORT_FIELD_RESERVED_VALUE_RANGE)
+ fs->value->next->v.s = (((const char * const *) rvw->value)[1]);
+ dm_pool_free(rh->selection->mem, s);
+ } else {
+ fs->value->v.s = s;
+ if (_check_value_is_strictly_reserved(rh, field_num, DM_REPORT_FIELD_TYPE_STRING, fs->value->v.s, NULL)) {
+ log_error("String value %s found in selection is reserved.", fs->value->v.s);
+ goto error;
+ }
+ }
+ break;
+ case DM_REPORT_FIELD_TYPE_NUMBER:
+ if (rvw->value) {
+ fs->value->v.i = *(const uint64_t *) rvw->value;
+ if (rvw->reserved->type & DM_REPORT_FIELD_RESERVED_VALUE_RANGE)
+ fs->value->next->v.i = (((const uint64_t *) rvw->value)[1]);
+ } else {
+ errno = 0;
+ if (((fs->value->v.i = strtoull(s, NULL, 10)) == ULLONG_MAX) &&
+ (errno == ERANGE)) {
+ log_error(_out_of_range_msg, s, field_id);
+ goto error;
+ }
+ if (_check_value_is_strictly_reserved(rh, field_num, DM_REPORT_FIELD_TYPE_NUMBER, &fs->value->v.i, NULL)) {
+ log_error("Numeric value %" PRIu64 " found in selection is reserved.", fs->value->v.i);
+ goto error;
+ }
+ }
+ dm_pool_free(rh->selection->mem, s);
+ break;
+ case DM_REPORT_FIELD_TYPE_SIZE:
+ if (rvw->value) {
+ fs->value->v.d = *(const double *) rvw->value;
+ if (rvw->reserved->type & DM_REPORT_FIELD_RESERVED_VALUE_RANGE)
+ fs->value->next->v.d = (((const double *) rvw->value)[1]);
+ } else {
+ errno = 0;
+ fs->value->v.d = strtod(s, NULL);
+ if (errno == ERANGE) {
+ log_error(_out_of_range_msg, s, field_id);
+ goto error;
+ }
+ if (custom && (factor = *((const uint64_t *)custom)))
+ fs->value->v.d *= factor;
+ fs->value->v.d /= 512; /* store size in sectors! */
+ if (_check_value_is_strictly_reserved(rh, field_num, DM_REPORT_FIELD_TYPE_SIZE, &fs->value->v.d, NULL)) {
+ log_error("Size value %f found in selection is reserved.", fs->value->v.d);
+ goto error;
+ }
+ }
+ dm_pool_free(rh->selection->mem, s);
+ break;
+ case DM_REPORT_FIELD_TYPE_PERCENT:
+ if (rvw->value) {
+ fs->value->v.i = *(const uint64_t *) rvw->value;
+ if (rvw->reserved->type & DM_REPORT_FIELD_RESERVED_VALUE_RANGE)
+ fs->value->next->v.i = (((const uint64_t *) rvw->value)[1]);
+ } else {
+ errno = 0;
+ fs->value->v.d = strtod(s, NULL);
+ if ((errno == ERANGE) || (fs->value->v.d < 0) || (fs->value->v.d > 100)) {
+ log_error(_out_of_range_msg, s, field_id);
+ goto error;
+ }
+
+ fs->value->v.i = (dm_percent_t) (DM_PERCENT_1 * fs->value->v.d);
+
+ if (_check_value_is_strictly_reserved(rh, field_num, DM_REPORT_FIELD_TYPE_PERCENT, &fs->value->v.i, NULL)) {
+ log_error("Percent value %s found in selection is reserved.", s);
+ goto error;
+ }
+ }
+ break;
+ case DM_REPORT_FIELD_TYPE_STRING_LIST:
+ if (!custom)
+ goto_bad;
+ fs->value->v.l = *(struct selection_str_list **)custom;
+ if (_check_value_is_strictly_reserved(rh, field_num, DM_REPORT_FIELD_TYPE_STRING_LIST, fs->value->v.l, NULL)) {
+ log_error("String list value found in selection is reserved.");
+ goto error;
+ }
+ break;
+ case DM_REPORT_FIELD_TYPE_TIME:
+ if (rvw->value) {
+ fs->value->v.t = *(const time_t *) rvw->value;
+ if (rvw->reserved->type & DM_REPORT_FIELD_RESERVED_VALUE_RANGE)
+ fs->value->next->v.t = (((const time_t *) rvw->value)[1]);
+ } else {
+ if (!(tval = (struct time_value *) custom))
+ goto_bad;
+ fs->value->v.t = tval->t1;
+ if (tval->range)
+ fs->value->next->v.t = tval->t2;
+ if (_check_value_is_strictly_reserved(rh, field_num, DM_REPORT_FIELD_TYPE_TIME, &fs->value->v.t, NULL)) {
+ log_error("Time value found in selection is reserved.");
+ goto error;
+ }
+ }
+ break;
+ default:
+ log_error(INTERNAL_ERROR "_create_field_selection: "
+ "unknown type of selection field %s", field_id);
+ goto error;
+ }
+ }
+
+ return fs;
+bad:
+ log_error(INTERNAL_ERROR "Forbiden NULL custom detected.");
+error:
+ dm_pool_free(rh->selection->mem, fs);
+
+ return NULL;
+}
+
+static struct selection_node *_alloc_selection_node(struct dm_pool *mem, uint32_t type)
+{
+ struct selection_node *sn;
+
+ if (!(sn = dm_pool_zalloc(mem, sizeof(struct selection_node)))) {
+ log_error("dm_report: struct selection_node allocation failed");
+ return NULL;
+ }
+
+ dm_list_init(&sn->list);
+ sn->type = type;
+ if (!(type & SEL_ITEM))
+ dm_list_init(&sn->selection.set);
+
+ return sn;
+}
+
+static void _display_selection_help(struct dm_report *rh)
+{
+ static const char _grow_object_failed_msg[] = "_display_selection_help: dm_pool_grow_object failed";
+ struct op_def *t;
+ const struct dm_report_reserved_value *rv;
+ size_t len_all, len_final = 0;
+ const char **rvs;
+ char *rvs_all;
+
+ log_warn("Selection operands");
+ log_warn("------------------");
+ log_warn(" field - Reporting field.");
+ log_warn(" number - Non-negative integer value.");
+ log_warn(" size - Floating point value with units, 'm' unit used by default if not specified.");
+ log_warn(" percent - Non-negative integer with or without %% suffix.");
+ log_warn(" string - Characters quoted by \' or \" or unquoted.");
+ log_warn(" string list - Strings enclosed by [ ] or { } and elements delimited by either");
+ log_warn(" \"all items must match\" or \"at least one item must match\" operator.");
+ log_warn(" regular expression - Characters quoted by \' or \" or unquoted.");
+ log_warn(" ");
+ if (rh->reserved_values) {
+ log_warn("Reserved values");
+ log_warn("---------------");
+
+ for (rv = rh->reserved_values; rv->type; rv++) {
+ for (len_all = 0, rvs = rv->names; *rvs; rvs++)
+ len_all += strlen(*rvs) + 2;
+ if (len_all > len_final)
+ len_final = len_all;
+ }
+
+ for (rv = rh->reserved_values; rv->type; rv++) {
+ if (!dm_pool_begin_object(rh->mem, 256)) {
+ log_error("_display_selection_help: dm_pool_begin_object failed");
+ break;
+ }
+ for (rvs = rv->names; *rvs; rvs++) {
+ if (((rvs != rv->names) && !dm_pool_grow_object(rh->mem, ", ", 2)) ||
+ !dm_pool_grow_object(rh->mem, *rvs, strlen(*rvs))) {
+ log_error(_grow_object_failed_msg);
+ goto out_reserved_values;
+ }
+ }
+ if (!dm_pool_grow_object(rh->mem, "\0", 1)) {
+ log_error(_grow_object_failed_msg);
+ goto out_reserved_values;
+ }
+ rvs_all = dm_pool_end_object(rh->mem);
+
+ log_warn(" %-*s - %s [%s]", (int) len_final, rvs_all, rv->description,
+ _get_field_type_name(rv->type));
+ dm_pool_free(rh->mem, rvs_all);
+ }
+ log_warn(" ");
+ }
+out_reserved_values:
+ log_warn("Selection operators");
+ log_warn("-------------------");
+ log_warn(" Comparison operators:");
+ t = _op_cmp;
+ for (; t->string; t++)
+ log_warn(" %6s - %s", t->string, t->desc);
+ log_warn(" ");
+ log_warn(" Logical and grouping operators:");
+ t = _op_log;
+ for (; t->string; t++)
+ log_warn(" %4s - %s", t->string, t->desc);
+ log_warn(" ");
+}
+
+static const char _sel_syntax_error_at_msg[] = "Selection syntax error at '%s'.";
+static const char _sel_help_ref_msg[] = "Use \'help\' for selection to get more help.";
+
+/*
+ * Selection parser
+ *
+ * _parse_* functions
+ *
+ * Input:
+ * s - a pointer to the parsed string
+ * Output:
+ * next - a pointer used for next _parse_*'s input,
+ * next == s if return value is NULL
+ * return value - a filter node pointer,
+ * NULL if s doesn't match
+ */
+
+/*
+ * SELECTION := FIELD_NAME OP_CMP STRING |
+ * FIELD_NAME OP_CMP NUMBER |
+ * FIELD_NAME OP_REGEX REGEX
+ */
+static struct selection_node *_parse_selection(struct dm_report *rh,
+ const char *s,
+ const char **next)
+{
+ struct field_selection *fs;
+ struct selection_node *sn;
+ const char *ws, *we; /* field name */
+ const char *vs = NULL, *ve = NULL; /* value */
+ const char *last;
+ uint32_t flags, field_num;
+ int implicit;
+ const struct dm_report_field_type *ft;
+ struct selection_str_list *str_list;
+ struct reserved_value_wrapper rvw = {0};
+ struct time_value tval;
+ uint64_t factor;
+ void *custom = NULL;
+ char *tmp;
+ char c;
+
+ /* field name */
+ if (!(last = _tok_field_name(s, &ws, &we))) {
+ log_error("Expecting field name");
+ goto bad;
+ }
+
+ /* check if the field with given name exists */
+ if (!_get_field(rh, ws, (size_t) (we - ws), &field_num, &implicit)) {
+ c = we[0];
+ tmp = (char *) we;
+ tmp[0] = '\0';
+ _display_fields(rh, 0, 1);
+ log_warn(" ");
+ log_error("Unrecognised selection field: %s", ws);
+ tmp[0] = c;
+ goto bad;
+ }
+
+ if (implicit) {
+ ft = &_implicit_report_fields[field_num];
+ if (ft->flags & FLD_CMP_UNCOMPARABLE) {
+ c = we[0];
+ tmp = (char *) we;
+ tmp[0] = '\0';
+ _display_fields(rh, 0, 1);
+ log_warn(" ");
+ log_error("Selection field is uncomparable: %s.", ws);
+ tmp[0] = c;
+ goto bad;
+ }
+ } else
+ ft = &rh->fields[field_num];
+
+ /* comparison operator */
+ if (!(flags = _tok_op_cmp(we, &last))) {
+ _display_selection_help(rh);
+ log_error("Unrecognised comparison operator: %s", we);
+ goto bad;
+ }
+ if (!last) {
+ _display_selection_help(rh);
+ log_error("Missing value after operator");
+ goto bad;
+ }
+
+ /* comparison value */
+ if (flags & FLD_CMP_REGEX) {
+ /*
+ * REGEX value
+ */
+ if (!(last = _tok_value_regex(rh, ft, last, &vs, &ve, &flags, &rvw)))
+ goto_bad;
+ } else {
+ /*
+ * STRING, NUMBER, SIZE, PERCENT, STRING_LIST, TIME value
+ */
+ if (flags & FLD_CMP_NUMBER) {
+ if (!(ft->flags & (DM_REPORT_FIELD_TYPE_NUMBER |
+ DM_REPORT_FIELD_TYPE_SIZE |
+ DM_REPORT_FIELD_TYPE_PERCENT |
+ DM_REPORT_FIELD_TYPE_TIME))) {
+ _display_selection_help(rh);
+ log_error("Operator can be used only with number, size, time or percent fields: %s", ws);
+ goto bad;
+ }
+ } else if (flags & FLD_CMP_TIME) {
+ if (!(ft->flags & DM_REPORT_FIELD_TYPE_TIME)) {
+ _display_selection_help(rh);
+ log_error("Operator can be used only with time fields: %s", ws);
+ goto bad;
+ }
+ }
+
+ if (ft->flags == DM_REPORT_FIELD_TYPE_SIZE ||
+ ft->flags == DM_REPORT_FIELD_TYPE_NUMBER ||
+ ft->flags == DM_REPORT_FIELD_TYPE_PERCENT)
+ custom = &factor;
+ else if (ft->flags & DM_REPORT_FIELD_TYPE_TIME)
+ custom = &tval;
+ else if (ft->flags == DM_REPORT_FIELD_TYPE_STRING_LIST)
+ custom = &str_list;
+ else
+ custom = NULL;
+ if (!(last = _tok_value(rh, ft, field_num, implicit,
+ last, &vs, &ve, &flags,
+ &rvw, rh->selection->mem, custom)))
+ goto_bad;
+ }
+
+ *next = _skip_space(last);
+
+ /* create selection */
+ if (!(fs = _create_field_selection(rh, field_num, implicit, vs, (size_t) (ve - vs), flags, &rvw, custom)))
+ return_NULL;
+
+ /* create selection node */
+ if (!(sn = _alloc_selection_node(rh->selection->mem, SEL_ITEM)))
+ return_NULL;
+
+ /* add selection to selection node */
+ sn->selection.item = fs;
+
+ return sn;
+bad:
+ log_error(_sel_syntax_error_at_msg, s);
+ log_error(_sel_help_ref_msg);
+ *next = s;
+ return NULL;
+}
+
+static struct selection_node *_parse_or_ex(struct dm_report *rh,
+ const char *s,
+ const char **next,
+ struct selection_node *or_sn);
+
+static struct selection_node *_parse_ex(struct dm_report *rh,
+ const char *s,
+ const char **next)
+{
+ static const char _ps_expected_msg[] = "Syntax error: left parenthesis expected at \'%s\'";
+ static const char _pe_expected_msg[] = "Syntax error: right parenthesis expected at \'%s\'";
+ struct selection_node *sn = NULL;
+ uint32_t t;
+ const char *tmp = NULL;
+
+ t = _tok_op_log(s, next, SEL_MODIFIER_NOT | SEL_PRECEDENCE_PS);
+ if (t == SEL_MODIFIER_NOT) {
+ /* '!' '(' EXPRESSION ')' */
+ if (!_tok_op_log(*next, &tmp, SEL_PRECEDENCE_PS)) {
+ log_error(_ps_expected_msg, *next);
+ goto error;
+ }
+ if (!(sn = _parse_or_ex(rh, tmp, next, NULL)))
+ goto error;
+ sn->type |= SEL_MODIFIER_NOT;
+ if (!_tok_op_log(*next, &tmp, SEL_PRECEDENCE_PE)) {
+ log_error(_pe_expected_msg, *next);
+ goto error;
+ }
+ *next = tmp;
+ } else if (t == SEL_PRECEDENCE_PS) {
+ /* '(' EXPRESSION ')' */
+ if (!(sn = _parse_or_ex(rh, *next, &tmp, NULL)))
+ goto error;
+ if (!_tok_op_log(tmp, next, SEL_PRECEDENCE_PE)) {
+ log_error(_pe_expected_msg, *next);
+ goto error;
+ }
+ } else if ((s = _skip_space(s))) {
+ /* SELECTION */
+ sn = _parse_selection(rh, s, next);
+ } else {
+ sn = NULL;
+ *next = s;
+ }
+
+ return sn;
+error:
+ *next = s;
+ return NULL;
+}
+
+/* AND_EXPRESSION := EX (AND_OP AND_EXPRSSION) */
+static struct selection_node *_parse_and_ex(struct dm_report *rh,
+ const char *s,
+ const char **next,
+ struct selection_node *and_sn)
+{
+ struct selection_node *n;
+ const char *tmp = NULL;
+
+ n = _parse_ex(rh, s, next);
+ if (!n)
+ goto error;
+
+ if (!_tok_op_log(*next, &tmp, SEL_AND)) {
+ if (!and_sn)
+ return n;
+ dm_list_add(&and_sn->selection.set, &n->list);
+ return and_sn;
+ }
+
+ if (!and_sn) {
+ if (!(and_sn = _alloc_selection_node(rh->selection->mem, SEL_AND)))
+ goto error;
+ }
+ dm_list_add(&and_sn->selection.set, &n->list);
+
+ return _parse_and_ex(rh, tmp, next, and_sn);
+error:
+ *next = s;
+ return NULL;
+}
+
+/* OR_EXPRESSION := AND_EXPRESSION (OR_OP OR_EXPRESSION) */
+static struct selection_node *_parse_or_ex(struct dm_report *rh,
+ const char *s,
+ const char **next,
+ struct selection_node *or_sn)
+{
+ struct selection_node *n;
+ const char *tmp = NULL;
+
+ n = _parse_and_ex(rh, s, next, NULL);
+ if (!n)
+ goto error;
+
+ if (!_tok_op_log(*next, &tmp, SEL_OR)) {
+ if (!or_sn)
+ return n;
+ dm_list_add(&or_sn->selection.set, &n->list);
+ return or_sn;
+ }
+
+ if (!or_sn) {
+ if (!(or_sn = _alloc_selection_node(rh->selection->mem, SEL_OR)))
+ goto error;
+ }
+ dm_list_add(&or_sn->selection.set, &n->list);
+
+ return _parse_or_ex(rh, tmp, next, or_sn);
+error:
+ *next = s;
+ return NULL;
+}
+
+static int _alloc_rh_selection(struct dm_report *rh)
+{
+ if (!(rh->selection = dm_pool_zalloc(rh->mem, sizeof(struct selection))) ||
+ !(rh->selection->mem = dm_pool_create("report selection", 10 * 1024))) {
+ log_error("Failed to allocate report selection structure.");
+ if (rh->selection)
+ dm_pool_free(rh->mem, rh->selection);
+ return 0;
+ }
+
+ return 1;
+}
+
+#define SPECIAL_SELECTION_ALL "all"
+
+static int _report_set_selection(struct dm_report *rh, const char *selection, int add_new_fields)
+{
+ struct selection_node *root = NULL;
+ const char *fin, *next;
+
+ if (rh->selection) {
+ if (rh->selection->selection_root)
+ /* Trash any previous selection. */
+ dm_pool_free(rh->selection->mem, rh->selection->selection_root);
+ rh->selection->selection_root = NULL;
+ } else {
+ if (!_alloc_rh_selection(rh))
+ goto_bad;
+ }
+
+ if (!selection || !selection[0] || !strcasecmp(selection, SPECIAL_SELECTION_ALL))
+ return 1;
+
+ rh->selection->add_new_fields = add_new_fields;
+
+ if (!(root = _alloc_selection_node(rh->selection->mem, SEL_OR)))
+ return 0;
+
+ if (!_parse_or_ex(rh, selection, &fin, root))
+ goto_bad;
+
+ next = _skip_space(fin);
+ if (*next) {
+ log_error("Expecting logical operator");
+ log_error(_sel_syntax_error_at_msg, next);
+ log_error(_sel_help_ref_msg);
+ goto bad;
+ }
+
+ rh->selection->selection_root = root;
+ return 1;
+bad:
+ dm_pool_free(rh->selection->mem, root);
+ return 0;
+}
+
+static void _reset_field_props(struct dm_report *rh)
+{
+ struct field_properties *fp;
+ dm_list_iterate_items(fp, &rh->field_props)
+ fp->width = fp->initial_width;
+ rh->flags |= RH_FIELD_CALC_NEEDED;
+}
+
+int dm_report_set_selection(struct dm_report *rh, const char *selection)
+{
+ struct row *row;
+
+ if (!_report_set_selection(rh, selection, 0))
+ return_0;
+
+ _reset_field_props(rh);
+
+ dm_list_iterate_items(row, &rh->rows) {
+ row->selected = _check_report_selection(rh, &row->fields);
+ if (row->field_sel_status)
+ _implicit_report_fields[row->field_sel_status->props->field_num].report_fn(rh,
+ rh->mem, row->field_sel_status, row, rh->private);
+ }
+
+ return 1;
+}
+
+struct dm_report *dm_report_init_with_selection(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,
+ const char *selection,
+ const struct dm_report_reserved_value reserved_values[],
+ void *private_data)
+{
+ struct dm_report *rh;
+
+ _implicit_report_fields = _implicit_special_report_fields_with_selection;
+
+ if (!(rh = dm_report_init(report_types, types, fields, output_fields,
+ output_separator, output_flags, sort_keys, private_data)))
+ return NULL;
+
+ if (!selection || !selection[0]) {
+ rh->selection = NULL;
+ return rh;
+ }
+
+ if (!_check_reserved_values_supported(fields, reserved_values)) {
+ log_error(INTERNAL_ERROR "dm_report_init_with_selection: "
+ "trying to register unsupported reserved value type, "
+ "skipping report selection");
+ return rh;
+ }
+ rh->reserved_values = reserved_values;
+
+ if (!strcasecmp(selection, SPECIAL_FIELD_HELP_ID) ||
+ !strcmp(selection, SPECIAL_FIELD_HELP_ALT_ID)) {
+ _display_fields(rh, 0, 1);
+ log_warn(" ");
+ _display_selection_help(rh);
+ rh->flags |= RH_ALREADY_REPORTED;
+ return rh;
+ }
+
+ if (!_report_set_selection(rh, selection, 1))
+ goto_bad;
+
+ _dm_report_init_update_types(rh, report_types);
+
+ return rh;
+bad:
+ dm_report_free(rh);
+ return NULL;
+}
+
/*
* Print row of headings
*/
static int _report_headings(struct dm_report *rh)
{
+ const struct dm_report_field_type *fields;
struct field_properties *fp;
const char *heading;
char *buf = NULL;
size_t buf_size = 0;
- if (rh->flags & RH_HEADINGS_PRINTED)
- return 1;
-
rh->flags |= RH_HEADINGS_PRINTED;
if (!(rh->flags & DM_REPORT_OUTPUT_HEADINGS))
@@ -798,7 +4260,9 @@ static int _report_headings(struct dm_report *rh)
if (fp->flags & FLD_HIDDEN)
continue;
- heading = rh->fields[fp->field_num].heading;
+ fields = fp->implicit ? _implicit_report_fields : rh->fields;
+
+ heading = fields[fp->field_num].heading;
if (rh->flags & DM_REPORT_OUTPUT_ALIGNED) {
if (dm_snprintf(buf, buf_size, "%-*.*s",
fp->width, fp->width, heading) < 0) {
@@ -824,8 +4288,12 @@ static int _report_headings(struct dm_report *rh)
log_error("dm_report: Failed to generate report headings for printing");
goto bad;
}
- log_print("%s", (char *) dm_pool_end_object(rh->mem));
+ /* print all headings */
+ heading = (char *) dm_pool_end_object(rh->mem);
+ log_print("%s", heading);
+
+ dm_pool_free(rh->mem, (void *)heading);
dm_free(buf);
return 1;
@@ -836,6 +4304,48 @@ static int _report_headings(struct dm_report *rh)
return 0;
}
+static int _should_display_row(struct row *row)
+{
+ return row->field_sel_status || row->selected;
+}
+
+static void _recalculate_fields(struct dm_report *rh)
+{
+ struct row *row;
+ struct dm_report_field *field;
+ int len;
+
+ dm_list_iterate_items(row, &rh->rows) {
+ dm_list_iterate_items(field, &row->fields) {
+ if ((rh->flags & RH_SORT_REQUIRED) &&
+ (field->props->flags & FLD_SORT_KEY)) {
+ (*row->sort_fields)[field->props->sort_posn] = field;
+ }
+
+ if (_should_display_row(row)) {
+ len = (int) strlen(field->report_string);
+ if ((len > field->props->width))
+ field->props->width = len;
+
+ }
+ }
+ }
+
+ rh->flags &= ~RH_FIELD_CALC_NEEDED;
+}
+
+int dm_report_column_headings(struct dm_report *rh)
+{
+ /* Columns-as-rows does not use _report_headings. */
+ if (rh->flags & DM_REPORT_OUTPUT_COLUMNS_AS_ROWS)
+ return 1;
+
+ if (rh->flags & RH_FIELD_CALC_NEEDED)
+ _recalculate_fields(rh);
+
+ return _report_headings(rh);
+}
+
/*
* Sort rows of data
*/
@@ -849,7 +4359,9 @@ static int _row_compare(const void *a, const void *b)
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) {
+ if ((sfa->props->flags & DM_REPORT_FIELD_TYPE_NUMBER) ||
+ (sfa->props->flags & DM_REPORT_FIELD_TYPE_SIZE) ||
+ (sfa->props->flags & DM_REPORT_FIELD_TYPE_TIME)) {
const uint64_t numa =
*(const uint64_t *) sfa->sort_value;
const uint64_t numb =
@@ -863,7 +4375,9 @@ static int _row_compare(const void *a, const void *b)
} else { /* FLD_DESCENDING */
return (numa < numb) ? 1 : -1;
}
- } else { /* DM_REPORT_FIELD_TYPE_STRING */
+ } else {
+ /* DM_REPORT_FIELD_TYPE_STRING
+ * DM_REPORT_FIELD_TYPE_STRING_LIST */
const char *stra = (const char *) sfa->sort_value;
const char *strb = (const char *) sfb->sort_value;
int cmp = strcmp(stra, strb);
@@ -906,62 +4420,103 @@ static int _sort_rows(struct dm_report *rh)
return 1;
}
-/*
- * Produce report output
- */
-static int _output_field(struct dm_report *rh, struct dm_report_field *field)
+#define STANDARD_QUOTE "\'"
+#define STANDARD_PAIR "="
+
+#define JSON_INDENT_UNIT 4
+#define JSON_SPACE " "
+#define JSON_QUOTE "\""
+#define JSON_PAIR ":"
+#define JSON_SEPARATOR ","
+#define JSON_OBJECT_START "{"
+#define JSON_OBJECT_END "}"
+#define JSON_ARRAY_START "["
+#define JSON_ARRAY_END "]"
+#define JSON_ESCAPE_CHAR "\\"
+#define JSON_NULL "null"
+
+#define UNABLE_TO_EXTEND_OUTPUT_LINE_MSG "dm_report: Unable to extend output line"
+
+static int _is_basic_report(struct dm_report *rh)
+{
+ return rh->group_item &&
+ (rh->group_item->group->type == DM_REPORT_GROUP_BASIC);
+}
+
+static int _is_json_std_report(struct dm_report *rh)
+{
+ return rh->group_item &&
+ rh->group_item->group->type == DM_REPORT_GROUP_JSON_STD;
+}
+
+static int _is_json_report(struct dm_report *rh)
+{
+ return rh->group_item &&
+ (rh->group_item->group->type == DM_REPORT_GROUP_JSON ||
+ rh->group_item->group->type == DM_REPORT_GROUP_JSON_STD);
+}
+
+static int _is_pure_numeric_field(struct dm_report_field *field)
+{
+ return field->props->flags & (DM_REPORT_FIELD_TYPE_NUMBER | DM_REPORT_FIELD_TYPE_PERCENT);
+}
+
+static const char *_get_field_id(struct dm_report *rh, struct dm_report_field *field)
+{
+ const struct dm_report_field_type *fields = field->props->implicit ? _implicit_report_fields
+ : rh->fields;
+
+ return fields[field->props->field_num].id;
+}
+
+static int _output_field_basic_fmt(struct dm_report *rh, struct dm_report_field *field)
{
char *field_id;
int32_t width;
uint32_t align;
- const char *repstr;
char *buf = NULL;
size_t buf_size = 0;
if (rh->flags & DM_REPORT_OUTPUT_FIELD_NAME_PREFIX) {
- if (!(field_id = dm_strdup(rh->fields[field->props->field_num].id))) {
+ if (!(field_id = strdup(_get_field_id(rh, field)))) {
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");
+ log_error(UNABLE_TO_EXTEND_OUTPUT_LINE_MSG);
dm_free(field_id);
return 0;
}
if (!dm_pool_grow_object(rh->mem, _toupperstr(field_id), 0)) {
- log_error("dm_report: Unable to extend output line");
+ log_error(UNABLE_TO_EXTEND_OUTPUT_LINE_MSG);
dm_free(field_id);
return 0;
}
dm_free(field_id);
- if (!dm_pool_grow_object(rh->mem, "=", 1)) {
- log_error("dm_report: Unable to extend output line");
+ if (!dm_pool_grow_object(rh->mem, STANDARD_PAIR, 1)) {
+ log_error(UNABLE_TO_EXTEND_OUTPUT_LINE_MSG);
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");
+ !dm_pool_grow_object(rh->mem, STANDARD_QUOTE, 1)) {
+ log_error(UNABLE_TO_EXTEND_OUTPUT_LINE_MSG);
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 (rh->flags & DM_REPORT_OUTPUT_ALIGNED) {
if (!(align = field->props->flags & DM_REPORT_FIELD_ALIGN_MASK))
- align = (field->props->flags & DM_REPORT_FIELD_TYPE_NUMBER) ?
+ align = ((field->props->flags & DM_REPORT_FIELD_TYPE_NUMBER) ||
+ (field->props->flags & DM_REPORT_FIELD_TYPE_SIZE)) ?
DM_REPORT_FIELD_ALIGN_RIGHT : DM_REPORT_FIELD_ALIGN_LEFT;
+ width = field->props->width;
+
/* Including trailing '\0'! */
buf_size = width + 1;
if (!(buf = dm_malloc(buf_size))) {
@@ -971,44 +4526,216 @@ static int _output_field(struct dm_report *rh, struct dm_report_field *field)
if (align & DM_REPORT_FIELD_ALIGN_LEFT) {
if (dm_snprintf(buf, buf_size, "%-*.*s",
- width, width, repstr) < 0) {
+ width, width, field->report_string) < 0) {
log_error("dm_report: left-aligned snprintf() failed");
goto bad;
}
if (!dm_pool_grow_object(rh->mem, buf, width)) {
- log_error("dm_report: Unable to extend output line");
+ log_error(UNABLE_TO_EXTEND_OUTPUT_LINE_MSG);
goto bad;
}
} else if (align & DM_REPORT_FIELD_ALIGN_RIGHT) {
if (dm_snprintf(buf, buf_size, "%*.*s",
- width, width, repstr) < 0) {
+ width, width, field->report_string) < 0) {
log_error("dm_report: right-aligned snprintf() failed");
goto bad;
}
if (!dm_pool_grow_object(rh->mem, buf, width)) {
- log_error("dm_report: Unable to extend output line");
+ log_error(UNABLE_TO_EXTEND_OUTPUT_LINE_MSG);
goto bad;
}
}
+ } else {
+ if (!dm_pool_grow_object(rh->mem, field->report_string, 0)) {
+ log_error(UNABLE_TO_EXTEND_OUTPUT_LINE_MSG);
+ 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");
- goto bad;
+ if (rh->flags & DM_REPORT_OUTPUT_FIELD_NAME_PREFIX) {
+ if (!(rh->flags & DM_REPORT_OUTPUT_FIELD_UNQUOTED)) {
+ if (!dm_pool_grow_object(rh->mem, STANDARD_QUOTE, 1)) {
+ log_error(UNABLE_TO_EXTEND_OUTPUT_LINE_MSG);
+ goto bad;
+ }
}
+ }
dm_free(buf);
return 1;
-
bad:
dm_free(buf);
return 0;
}
+static int _safe_repstr_output(struct dm_report *rh, const char *repstr, size_t len)
+{
+ const char *p_repstr;
+ const char *repstr_end = len ? repstr + len : repstr + strlen(repstr);
+
+ /* Escape any JSON_QUOTE that may appear in reported string. */
+ while (1) {
+ if (!(p_repstr = memchr(repstr, JSON_QUOTE[0], repstr_end - repstr)))
+ break;
+
+ if (p_repstr > repstr) {
+ if (!dm_pool_grow_object(rh->mem, repstr, p_repstr - repstr)) {
+ log_error(UNABLE_TO_EXTEND_OUTPUT_LINE_MSG);
+ return 0;
+ }
+ }
+ if (!dm_pool_grow_object(rh->mem, JSON_ESCAPE_CHAR, 1) ||
+ !dm_pool_grow_object(rh->mem, JSON_QUOTE, 1)) {
+ log_error(UNABLE_TO_EXTEND_OUTPUT_LINE_MSG);
+ return 0;
+ }
+ repstr = p_repstr + 1;
+ }
+
+ if (!dm_pool_grow_object(rh->mem, repstr, repstr_end - repstr)) {
+ log_error(UNABLE_TO_EXTEND_OUTPUT_LINE_MSG);
+ return 0;
+ }
+
+ return 1;
+}
+
+static int _output_field_json_fmt(struct dm_report *rh, struct dm_report_field *field)
+{
+ const char *repstr;
+ size_t list_size, i;
+ struct pos_len *pos_len;
+
+ if (!dm_pool_grow_object(rh->mem, JSON_QUOTE, 1) ||
+ !dm_pool_grow_object(rh->mem, _get_field_id(rh, field), 0) ||
+ !dm_pool_grow_object(rh->mem, JSON_QUOTE, 1) ||
+ !dm_pool_grow_object(rh->mem, JSON_PAIR, 1)) {
+ log_error(UNABLE_TO_EXTEND_OUTPUT_LINE_MSG);
+ return 0;
+ }
+
+ if (field->props->flags & DM_REPORT_FIELD_TYPE_STRING_LIST) {
+ if (!_is_json_std_report(rh)) {
+
+ /* string list in JSON - report whole list as simple string in quotes */
+
+ if (!dm_pool_grow_object(rh->mem, JSON_QUOTE, 1)) {
+ log_error(UNABLE_TO_EXTEND_OUTPUT_LINE_MSG);
+ return 0;
+ }
+
+ if (!_safe_repstr_output(rh, field->report_string, 0))
+ return_0;
+
+ if (!dm_pool_grow_object(rh->mem, JSON_QUOTE, 1)) {
+ log_error(UNABLE_TO_EXTEND_OUTPUT_LINE_MSG);
+ return 0;
+ }
+
+ return 1;
+ }
+
+ /* string list in JSON_STD - report list as proper JSON array */
+
+ if (!dm_pool_grow_object(rh->mem, JSON_ARRAY_START, 1)) {
+ log_error(UNABLE_TO_EXTEND_OUTPUT_LINE_MSG);
+ return 0;
+ }
+
+ if (*field->report_string != 0) {
+ pos_len = (struct pos_len *) (field->report_string +
+ ((struct str_list_sort_value *) field->sort_value)->items[0].len + 1);
+ list_size = pos_len->pos;
+ } else
+ list_size = 0;
+
+ for (i = 0; i < list_size; i++) {
+ pos_len++;
+
+ if (i != 0) {
+ if (!dm_pool_grow_object(rh->mem, JSON_SEPARATOR, 1)) {
+ log_error(UNABLE_TO_EXTEND_OUTPUT_LINE_MSG);
+ return 0;
+ }
+ }
+
+ if (!dm_pool_grow_object(rh->mem, JSON_QUOTE, 1)) {
+ log_error(UNABLE_TO_EXTEND_OUTPUT_LINE_MSG);
+ return 0;
+ }
+
+ if (!_safe_repstr_output(rh, field->report_string + pos_len->pos, pos_len->len))
+ return_0;
+
+ if (!dm_pool_grow_object(rh->mem, JSON_QUOTE, 1)) {
+ log_error(UNABLE_TO_EXTEND_OUTPUT_LINE_MSG);
+ return 0;
+ }
+ }
+
+ if (!dm_pool_grow_object(rh->mem, JSON_ARRAY_END, 1)) {
+ log_error(UNABLE_TO_EXTEND_OUTPUT_LINE_MSG);
+ return 0;
+ }
+
+ return 1;
+ }
+
+ /* all other types than string list - handle both JSON and JSON_STD */
+
+ if (!(_is_json_std_report(rh) && _is_pure_numeric_field(field))) {
+ if (!dm_pool_grow_object(rh->mem, JSON_QUOTE, 1)) {
+ log_error(UNABLE_TO_EXTEND_OUTPUT_LINE_MSG);
+ return 0;
+ }
+ }
+
+ if (_is_json_std_report(rh) && _is_pure_numeric_field(field) && !*field->report_string)
+ repstr = JSON_NULL;
+ else
+ repstr = field->report_string;
+
+ if (!_safe_repstr_output(rh, repstr, 0))
+ return_0;
+
+ if (!(_is_json_std_report(rh) && _is_pure_numeric_field(field))) {
+ if (!dm_pool_grow_object(rh->mem, JSON_QUOTE, 1)) {
+ log_error(UNABLE_TO_EXTEND_OUTPUT_LINE_MSG);
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+/*
+ * Produce report output
+ */
+static int _output_field(struct dm_report *rh, struct dm_report_field *field)
+{
+ return _is_json_report(rh) ? _output_field_json_fmt(rh, field)
+ : _output_field_basic_fmt(rh, field);
+}
+
+static void _destroy_rows(struct dm_report *rh)
+{
+ /*
+ * free the first row allocated to this report: since this is a
+ * pool allocation this will also free all subsequently allocated
+ * rows from the report and any associated string data.
+ */
+ if (rh->first_row)
+ dm_pool_free(rh->mem, rh->first_row);
+ rh->first_row = NULL;
+ dm_list_init(&rh->rows);
+
+ /* Reset field widths to original values. */
+ _reset_field_props(rh);
+}
+
static int _output_as_rows(struct dm_report *rh)
{
+ const struct dm_report_field_type *fields;
struct field_properties *fp;
struct dm_report_field *field;
struct row *row;
@@ -1022,13 +4749,15 @@ static int _output_as_rows(struct dm_report *rh)
continue;
}
+ fields = fp->implicit ? _implicit_report_fields : rh->fields;
+
if (!dm_pool_begin_object(rh->mem, 512)) {
log_error("dm_report: Unable to allocate output line");
return 0;
}
if ((rh->flags & DM_REPORT_OUTPUT_HEADINGS)) {
- if (!dm_pool_grow_object(rh->mem, rh->fields[fp->field_num].heading, 0)) {
+ if (!dm_pool_grow_object(rh->mem, fields[fp->field_num].heading, 0)) {
log_error("dm_report: Failed to extend row for field name");
goto bad;
}
@@ -1047,7 +4776,7 @@ static int _output_as_rows(struct dm_report *rh)
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");
+ log_error(UNABLE_TO_EXTEND_OUTPUT_LINE_MSG);
goto bad;
}
}
@@ -1059,6 +4788,8 @@ static int _output_as_rows(struct dm_report *rh)
log_print("%s", (char *) dm_pool_end_object(rh->mem));
}
+ _destroy_rows(rh);
+
return 1;
bad:
@@ -1071,44 +4802,89 @@ 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;
+ struct dm_list *last_row;
+ int do_field_delim;
+ char *line;
/* If headings not printed yet, calculate field widths and print them */
if (!(rh->flags & RH_HEADINGS_PRINTED))
_report_headings(rh);
/* Print and clear buffer */
+ last_row = dm_list_last(&rh->rows);
dm_list_iterate_safe(rowh, rtmp, &rh->rows) {
+ row = dm_list_item(rowh, struct row);
+
+ if (!_should_display_row(row))
+ continue;
+
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);
+
+ if (_is_json_report(rh)) {
+ if (!dm_pool_grow_object(rh->mem, JSON_OBJECT_START, 0)) {
+ log_error(UNABLE_TO_EXTEND_OUTPUT_LINE_MSG);
+ goto bad;
+ }
+ }
+
+ do_field_delim = 0;
+
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 (do_field_delim) {
+ if (_is_json_report(rh)) {
+ if (!dm_pool_grow_object(rh->mem, JSON_SEPARATOR, 0) ||
+ !dm_pool_grow_object(rh->mem, JSON_SPACE, 0)) {
+ log_error(UNABLE_TO_EXTEND_OUTPUT_LINE_MSG);
+ goto bad;
+ }
+ } else {
+ if (!dm_pool_grow_object(rh->mem, rh->separator, 0)) {
+ log_error(UNABLE_TO_EXTEND_OUTPUT_LINE_MSG);
+ goto bad;
+ }
+ }
+ } else
+ do_field_delim = 1;
+
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;
- }
+ if (!(rh->flags & DM_REPORT_OUTPUT_MULTIPLE_TIMES))
+ dm_list_del(&field->list);
+ }
- dm_list_del(&field->list);
+ if (_is_json_report(rh)) {
+ if (!dm_pool_grow_object(rh->mem, JSON_OBJECT_END, 0)) {
+ log_error(UNABLE_TO_EXTEND_OUTPUT_LINE_MSG);
+ goto bad;
+ }
+ if (rowh != last_row &&
+ !dm_pool_grow_object(rh->mem, JSON_SEPARATOR, 0)) {
+ log_error(UNABLE_TO_EXTEND_OUTPUT_LINE_MSG);
+ goto bad;
+ }
}
+
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);
+
+ line = (char *) dm_pool_end_object(rh->mem);
+ log_print("%*s", rh->group_item ? rh->group_item->group->indent + (int) strlen(line) : 0, line);
+ if (!(rh->flags & DM_REPORT_OUTPUT_MULTIPLE_TIMES))
+ dm_list_del(&row->list);
}
- if (row)
- dm_pool_free(rh->mem, row);
+ if (!(rh->flags & DM_REPORT_OUTPUT_MULTIPLE_TIMES))
+ _destroy_rows(rh);
return 1;
@@ -1117,16 +4893,413 @@ static int _output_as_columns(struct dm_report *rh)
return 0;
}
-int dm_report_output(struct dm_report *rh)
+int dm_report_is_empty(struct dm_report *rh)
+{
+ return dm_list_empty(&rh->rows) ? 1 : 0;
+}
+
+static struct report_group_item *_get_topmost_report_group_item(struct dm_report_group *group)
+{
+ struct report_group_item *item;
+
+ if (group && !dm_list_empty(&group->items))
+ item = dm_list_item(dm_list_first(&group->items), struct report_group_item);
+ else
+ item = NULL;
+
+ return item;
+}
+
+static void _json_output_start(struct dm_report_group *group)
+{
+ if (!group->indent) {
+ log_print(JSON_OBJECT_START);
+ group->indent += JSON_INDENT_UNIT;
+ }
+}
+
+static int _json_output_array_start(struct dm_pool *mem, struct report_group_item *item)
+{
+ const char *name = (const char *) item->data;
+ char *output;
+
+ if (!dm_pool_begin_object(mem, 32)) {
+ log_error(UNABLE_TO_EXTEND_OUTPUT_LINE_MSG);
+ return 0;
+ }
+
+ if (!dm_pool_grow_object(mem, JSON_QUOTE, 1) ||
+ !dm_pool_grow_object(mem, name, 0) ||
+ !dm_pool_grow_object(mem, JSON_QUOTE JSON_PAIR JSON_SPACE JSON_ARRAY_START, 0) ||
+ !dm_pool_grow_object(mem, "\0", 1) ||
+ !(output = dm_pool_end_object(mem))) {
+ log_error(UNABLE_TO_EXTEND_OUTPUT_LINE_MSG);
+ goto bad;
+ }
+
+ if (item->parent->store.finished_count > 0)
+ log_print("%*s", item->group->indent + (int) sizeof(JSON_SEPARATOR) - 1, JSON_SEPARATOR);
+
+ if (item->parent->parent && item->parent->data) {
+ log_print("%*s", item->group->indent + (int) sizeof(JSON_OBJECT_START) - 1, JSON_OBJECT_START);
+ item->group->indent += JSON_INDENT_UNIT;
+ }
+
+ log_print("%*s", item->group->indent + (int) strlen(output), output);
+ item->group->indent += JSON_INDENT_UNIT;
+
+ dm_pool_free(mem, output);
+ return 1;
+bad:
+ dm_pool_abandon_object(mem);
+ return 0;
+}
+
+static int _prepare_json_report_output(struct dm_report *rh)
{
- if (dm_list_empty(&rh->rows))
+ _json_output_start(rh->group_item->group);
+
+ if (rh->group_item->output_done && dm_list_empty(&rh->rows))
return 1;
+ /*
+ * If this report is in JSON group, it must be at the
+ * top of the stack of reports so the output from
+ * different reports do not interleave with each other.
+ */
+ if (_get_topmost_report_group_item(rh->group_item->group) != rh->group_item) {
+ log_error("dm_report: dm_report_output: interleaved reports detected for JSON output");
+ return 0;
+ }
+
+ if (rh->group_item->needs_closing) {
+ log_error("dm_report: dm_report_output: unfinished JSON output detected");
+ return 0;
+ }
+
+ if (!_json_output_array_start(rh->mem, rh->group_item))
+ return_0;
+
+ rh->group_item->needs_closing = 1;
+ return 1;
+}
+
+static int _print_basic_report_header(struct dm_report *rh)
+{
+ const char *report_name = (const char *) rh->group_item->data;
+ size_t len = strlen(report_name);
+ char *underline;
+
+ if (!(underline = dm_pool_zalloc(rh->mem, len + 1)))
+ return_0;
+
+ memset(underline, '=', len);
+
+ if (rh->group_item->parent->store.finished_count > 0)
+ log_print("%s", "");
+ log_print("%s", report_name);
+ log_print("%s", underline);
+
+ dm_pool_free(rh->mem, underline);
+ return 1;
+}
+
+int dm_report_output(struct dm_report *rh)
+{
+ int r = 0;
+
+ if (_is_json_report(rh) &&
+ !_prepare_json_report_output(rh))
+ return_0;
+
+ if (dm_list_empty(&rh->rows)) {
+ r = 1;
+ goto out;
+ }
+
+ if (rh->flags & RH_FIELD_CALC_NEEDED)
+ _recalculate_fields(rh);
+
if ((rh->flags & RH_SORT_REQUIRED))
_sort_rows(rh);
+ if (_is_basic_report(rh) && !_print_basic_report_header(rh))
+ goto_out;
+
if ((rh->flags & DM_REPORT_OUTPUT_COLUMNS_AS_ROWS))
- return _output_as_rows(rh);
+ r = _output_as_rows(rh);
else
- return _output_as_columns(rh);
+ r = _output_as_columns(rh);
+out:
+ if (r && rh->group_item)
+ rh->group_item->output_done = 1;
+ return r;
+}
+
+void dm_report_destroy_rows(struct dm_report *rh)
+{
+ _destroy_rows(rh);
+}
+
+struct dm_report_group *dm_report_group_create(dm_report_group_type_t type, void *data)
+{
+ struct dm_report_group *group;
+ struct dm_pool *mem;
+ struct report_group_item *item;
+
+ if (!(mem = dm_pool_create("report_group", 1024))) {
+ log_error("dm_report: dm_report_init_group: failed to allocate mem pool");
+ return NULL;
+ }
+
+ if (!(group = dm_pool_zalloc(mem, sizeof(*group)))) {
+ log_error("dm_report: failed to allocate report group structure");
+ goto bad;
+ }
+
+ group->mem = mem;
+ group->type = type;
+ dm_list_init(&group->items);
+
+ if (!(item = dm_pool_zalloc(mem, sizeof(*item)))) {
+ log_error("dm_report: faile to allocate root report group item");
+ goto bad;
+ }
+
+ dm_list_add_h(&group->items, &item->list);
+
+ return group;
+bad:
+ dm_pool_destroy(mem);
+ return NULL;
+}
+
+static int _report_group_push_single(struct report_group_item *item, void *data)
+{
+ struct report_group_item *item_iter;
+ unsigned count = 0;
+
+ dm_list_iterate_items(item_iter, &item->group->items) {
+ if (item_iter->report)
+ count++;
+ }
+
+ if (count > 1) {
+ log_error("dm_report: unable to add more than one report "
+ "to current report group");
+ return 0;
+ }
+
+ return 1;
+}
+
+static int _report_group_push_basic(struct report_group_item *item, const char *name)
+{
+ if (item->report) {
+ if (!(item->report->flags & DM_REPORT_OUTPUT_BUFFERED))
+ item->report->flags &= ~(DM_REPORT_OUTPUT_MULTIPLE_TIMES);
+ } else {
+ if (!name && item->parent->store.finished_count > 0)
+ log_print("%s", "");
+ }
+
+ return 1;
+}
+
+static int _report_group_push_json(struct report_group_item *item, const char *name)
+{
+ if (name && !(item->data = dm_pool_strdup(item->group->mem, name))) {
+ log_error("dm_report: failed to duplicate json item name");
+ return 0;
+ }
+
+ if (item->report) {
+ item->report->flags &= ~(DM_REPORT_OUTPUT_ALIGNED |
+ DM_REPORT_OUTPUT_HEADINGS |
+ DM_REPORT_OUTPUT_COLUMNS_AS_ROWS);
+ item->report->flags |= DM_REPORT_OUTPUT_BUFFERED;
+ } else {
+ _json_output_start(item->group);
+ if (name) {
+ if (!_json_output_array_start(item->group->mem, item))
+ return_0;
+ } else {
+ if (!item->parent->parent) {
+ log_error("dm_report: can't use unnamed object at top level of JSON output");
+ return 0;
+ }
+ if (item->parent->store.finished_count > 0)
+ log_print("%*s", item->group->indent + (int) sizeof(JSON_SEPARATOR) - 1, JSON_SEPARATOR);
+ log_print("%*s", item->group->indent + (int) sizeof(JSON_OBJECT_START) - 1, JSON_OBJECT_START);
+ item->group->indent += JSON_INDENT_UNIT;
+ }
+
+ item->output_done = 1;
+ item->needs_closing = 1;
+ }
+
+ return 1;
+}
+
+int dm_report_group_push(struct dm_report_group *group, struct dm_report *report, void *data)
+{
+ struct report_group_item *item, *tmp_item;
+
+ if (!group)
+ return 1;
+
+ if (!(item = dm_pool_zalloc(group->mem, sizeof(*item)))) {
+ log_error("dm_report: dm_report_group_push: group item allocation failed");
+ return 0;
+ }
+
+ if ((item->report = report)) {
+ item->store.orig_report_flags = report->flags;
+ report->group_item = item;
+ }
+
+ item->group = group;
+ item->data = data;
+
+ dm_list_iterate_items(tmp_item, &group->items) {
+ if (!tmp_item->report) {
+ item->parent = tmp_item;
+ break;
+ }
+ }
+
+ dm_list_add_h(&group->items, &item->list);
+
+ switch (group->type) {
+ case DM_REPORT_GROUP_SINGLE:
+ if (!_report_group_push_single(item, data))
+ goto_bad;
+ break;
+ case DM_REPORT_GROUP_BASIC:
+ if (!_report_group_push_basic(item, data))
+ goto_bad;
+ break;
+ case DM_REPORT_GROUP_JSON:
+ case DM_REPORT_GROUP_JSON_STD:
+ if (!_report_group_push_json(item, data))
+ goto_bad;
+ break;
+ default:
+ goto_bad;
+ }
+
+ return 1;
+bad:
+ dm_list_del(&item->list);
+ dm_pool_free(group->mem, item);
+ return 0;
+}
+
+static int _report_group_pop_single(struct report_group_item *item)
+{
+ return 1;
+}
+
+static int _report_group_pop_basic(struct report_group_item *item)
+{
+ return 1;
+}
+
+static int _report_group_pop_json(struct report_group_item *item)
+{
+ if (item->output_done && item->needs_closing) {
+ if (item->data) {
+ item->group->indent -= JSON_INDENT_UNIT;
+ log_print("%*s", item->group->indent + (int) sizeof(JSON_ARRAY_END) - 1, JSON_ARRAY_END);
+ }
+ if (item->parent->data && item->parent->parent) {
+ item->group->indent -= JSON_INDENT_UNIT;
+ log_print("%*s", item->group->indent + (int) sizeof(JSON_OBJECT_END) - 1, JSON_OBJECT_END);
+ }
+ item->needs_closing = 0;
+ }
+
+ return 1;
+}
+
+int dm_report_group_pop(struct dm_report_group *group)
+{
+ struct report_group_item *item;
+
+ if (!group)
+ return 1;
+
+ if (!(item = _get_topmost_report_group_item(group))) {
+ log_error("dm_report: dm_report_group_pop: group has no items");
+ return 0;
+ }
+
+ switch (group->type) {
+ case DM_REPORT_GROUP_SINGLE:
+ if (!_report_group_pop_single(item))
+ return_0;
+ break;
+ case DM_REPORT_GROUP_BASIC:
+ if (!_report_group_pop_basic(item))
+ return_0;
+ break;
+ case DM_REPORT_GROUP_JSON:
+ case DM_REPORT_GROUP_JSON_STD:
+ if (!_report_group_pop_json(item))
+ return_0;
+ break;
+ default:
+ return 0;
+ }
+
+ dm_list_del(&item->list);
+
+ if (item->report) {
+ item->report->flags = item->store.orig_report_flags;
+ item->report->group_item = NULL;
+ }
+
+ if (item->parent)
+ item->parent->store.finished_count++;
+
+ dm_pool_free(group->mem, item);
+ return 1;
+}
+
+int dm_report_group_output_and_pop_all(struct dm_report_group *group)
+{
+ struct report_group_item *item, *tmp_item;
+
+ dm_list_iterate_items_safe(item, tmp_item, &group->items) {
+ if (!item->parent) {
+ item->store.finished_count = 0;
+ continue;
+ }
+ if (item->report && !dm_report_output(item->report))
+ return_0;
+ if (!dm_report_group_pop(group))
+ return_0;
+ }
+
+ if (group->type == DM_REPORT_GROUP_JSON || group->type == DM_REPORT_GROUP_JSON_STD) {
+ _json_output_start(group);
+ log_print(JSON_OBJECT_END);
+ group->indent -= JSON_INDENT_UNIT;
+ }
+
+ return 1;
+}
+
+int dm_report_group_destroy(struct dm_report_group *group)
+{
+ int r = 1;
+
+ if (!group)
+ return 1;
+
+ if (!dm_report_group_output_and_pop_all(group))
+ r = 0;
+
+ dm_pool_destroy(group->mem);
+ return r;
}