mirror of
git://sourceware.org/git/lvm2.git
synced 2024-11-05 06:54:26 +03:00
a763420786
Use literals for printf messages.
5343 lines
141 KiB
C
5343 lines
141 KiB
C
/*
|
|
* Copyright (C) 2002-2004 Sistina Software, Inc. All rights reserved.
|
|
* Copyright (C) 2004-2015 Red Hat, Inc. All rights reserved.
|
|
*
|
|
* This file is part of the device-mapper userspace tools.
|
|
*
|
|
* This copyrighted material is made available to anyone wishing to use,
|
|
* modify, copy, or redistribute it subject to the terms and conditions
|
|
* of the GNU Lesser General Public License v.2.1.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#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;
|
|
const char *output_field_name_prefix;
|
|
const char *field_prefix;
|
|
uint32_t flags;
|
|
const char *separator;
|
|
|
|
uint32_t keys_count;
|
|
|
|
/* Ordered list of fields needed for this report */
|
|
struct dm_list field_props;
|
|
|
|
/* Rows of report data */
|
|
struct dm_list rows;
|
|
|
|
/* Array of field definitions */
|
|
const struct dm_report_field_type *fields;
|
|
const 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 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 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 const 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 const 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;
|
|
};
|
|
|
|
/*
|
|
* Report data field
|
|
*/
|
|
struct dm_report_field {
|
|
struct dm_list list;
|
|
struct field_properties *props;
|
|
|
|
const char *report_string; /* Formatted ready for display */
|
|
const void *sort_value; /* Raw value for sorting */
|
|
};
|
|
|
|
struct row {
|
|
struct dm_list list;
|
|
struct dm_report *rh;
|
|
struct dm_list fields; /* Fields in display order */
|
|
struct dm_report_field *(*sort_fields)[]; /* Fields in sort order */
|
|
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;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Data-munging functions to prepare each data type for display and sorting
|
|
*/
|
|
|
|
int dm_report_field_string(struct dm_report *rh,
|
|
struct dm_report_field *field, const char *const *data)
|
|
{
|
|
char *repstr;
|
|
|
|
if (!(repstr = dm_pool_strdup(rh->mem, *data))) {
|
|
log_error("dm_report_field_string: dm_pool_strdup failed");
|
|
return 0;
|
|
}
|
|
|
|
field->report_string = repstr;
|
|
field->sort_value = (const void *) field->report_string;
|
|
|
|
return 1;
|
|
}
|
|
|
|
int dm_report_field_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)
|
|
{
|
|
const int value = *data;
|
|
uint64_t *sortval;
|
|
char *repstr;
|
|
|
|
if (!(repstr = dm_pool_zalloc(rh->mem, 13))) {
|
|
log_error("dm_report_field_int: dm_pool_alloc failed");
|
|
return 0;
|
|
}
|
|
|
|
if (!(sortval = dm_pool_alloc(rh->mem, sizeof(int64_t)))) {
|
|
log_error("dm_report_field_int: dm_pool_alloc failed");
|
|
return 0;
|
|
}
|
|
|
|
if (dm_snprintf(repstr, 12, "%d", value) < 0) {
|
|
log_error("dm_report_field_int: int too big: %d", value);
|
|
return 0;
|
|
}
|
|
|
|
*sortval = (uint64_t) value;
|
|
field->sort_value = sortval;
|
|
field->report_string = repstr;
|
|
|
|
return 1;
|
|
}
|
|
|
|
int dm_report_field_uint32(struct dm_report *rh,
|
|
struct dm_report_field *field, const uint32_t *data)
|
|
{
|
|
const uint32_t value = *data;
|
|
uint64_t *sortval;
|
|
char *repstr;
|
|
|
|
if (!(repstr = dm_pool_zalloc(rh->mem, 12))) {
|
|
log_error("dm_report_field_uint32: dm_pool_alloc failed");
|
|
return 0;
|
|
}
|
|
|
|
if (!(sortval = dm_pool_alloc(rh->mem, sizeof(uint64_t)))) {
|
|
log_error("dm_report_field_uint32: dm_pool_alloc failed");
|
|
return 0;
|
|
}
|
|
|
|
if (dm_snprintf(repstr, 11, "%u", value) < 0) {
|
|
log_error("dm_report_field_uint32: uint32 too big: %u", value);
|
|
return 0;
|
|
}
|
|
|
|
*sortval = (uint64_t) value;
|
|
field->sort_value = sortval;
|
|
field->report_string = repstr;
|
|
|
|
return 1;
|
|
}
|
|
|
|
int dm_report_field_int32(struct dm_report *rh,
|
|
struct dm_report_field *field, const int32_t *data)
|
|
{
|
|
const int32_t value = *data;
|
|
uint64_t *sortval;
|
|
char *repstr;
|
|
|
|
if (!(repstr = dm_pool_zalloc(rh->mem, 13))) {
|
|
log_error("dm_report_field_int32: dm_pool_alloc failed");
|
|
return 0;
|
|
}
|
|
|
|
if (!(sortval = dm_pool_alloc(rh->mem, sizeof(int64_t)))) {
|
|
log_error("dm_report_field_int32: dm_pool_alloc failed");
|
|
return 0;
|
|
}
|
|
|
|
if (dm_snprintf(repstr, 12, "%d", value) < 0) {
|
|
log_error("dm_report_field_int32: int32 too big: %d", value);
|
|
return 0;
|
|
}
|
|
|
|
*sortval = (uint64_t) value;
|
|
field->sort_value = sortval;
|
|
field->report_string = repstr;
|
|
|
|
return 1;
|
|
}
|
|
|
|
int dm_report_field_uint64(struct dm_report *rh,
|
|
struct dm_report_field *field, const uint64_t *data)
|
|
{
|
|
const uint64_t value = *data;
|
|
uint64_t *sortval;
|
|
char *repstr;
|
|
|
|
if (!(repstr = dm_pool_zalloc(rh->mem, 22))) {
|
|
log_error("dm_report_field_uint64: dm_pool_alloc failed");
|
|
return 0;
|
|
}
|
|
|
|
if (!(sortval = dm_pool_alloc(rh->mem, sizeof(uint64_t)))) {
|
|
log_error("dm_report_field_uint64: dm_pool_alloc failed");
|
|
return 0;
|
|
}
|
|
|
|
if (dm_snprintf(repstr, 21, FMTu64 , value) < 0) {
|
|
log_error("dm_report_field_uint64: uint64 too big: %" PRIu64, value);
|
|
return 0;
|
|
}
|
|
|
|
*sortval = value;
|
|
field->sort_value = sortval;
|
|
field->report_string = repstr;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Helper functions for custom report functions
|
|
*/
|
|
void dm_report_field_set_value(struct dm_report_field *field, const void *value, const void *sortvalue)
|
|
{
|
|
field->report_string = (const char *) value;
|
|
field->sort_value = sortvalue ? : value;
|
|
|
|
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 size_t _get_longest_field_id_len(const struct dm_report_field_type *fields)
|
|
{
|
|
uint32_t f;
|
|
size_t l, id_len = 0;
|
|
|
|
for (f = 0; fields[f].report_fn; f++)
|
|
if ((l = strlen(fields[f].id)) > id_len)
|
|
id_len = l;
|
|
|
|
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;
|
|
size_t l;
|
|
const struct dm_report_object_type *type;
|
|
const char *desc, *last_desc = "";
|
|
|
|
for (f = 0; fields[f].report_fn; f++)
|
|
if ((l = strlen(fields[f].id)) > id_len)
|
|
id_len = l;
|
|
|
|
for (type = rh->types; type->data_fn; type++)
|
|
if ((l = strlen(type->prefix) + 3) > id_len)
|
|
id_len = l;
|
|
|
|
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(" ");
|
|
log_warn("%s Fields", desc);
|
|
log_warn("%*.*s", (int) strlen(desc) + 7,
|
|
(int) strlen(desc) + 7,
|
|
"-------------------------------------------------------------------------------");
|
|
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%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, int implicit)
|
|
{
|
|
const struct dm_report_field_type *fields = implicit ? _implicit_report_fields
|
|
: rh->fields;
|
|
|
|
dest->field_num = field_num;
|
|
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, fields[field_num].type);
|
|
if (!dest->type) {
|
|
log_error("dm_report: field not match: %s",
|
|
fields[field_num].id);
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static struct field_properties * _add_field(struct dm_report *rh,
|
|
uint32_t field_num, int implicit,
|
|
uint32_t flags)
|
|
{
|
|
struct field_properties *fp;
|
|
|
|
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, implicit)) {
|
|
stack;
|
|
dm_pool_free(rh->mem, fp);
|
|
return NULL;
|
|
}
|
|
|
|
fp->flags |= flags;
|
|
|
|
/*
|
|
* Place hidden fields at the front so dm_list_end() will
|
|
* tell us when we've reached the last visible field.
|
|
*/
|
|
if (fp->flags & FLD_HIDDEN)
|
|
dm_list_add_h(&rh->field_props, &fp->list);
|
|
else
|
|
dm_list_add(&rh->field_props, &fp->list);
|
|
|
|
return fp;
|
|
}
|
|
|
|
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 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 *canonical_name1, const char *canonical_name2,
|
|
const char *prefix, size_t prefix_len)
|
|
{
|
|
/* Exact match? */
|
|
if (!strcasecmp(canonical_name1, canonical_name2))
|
|
return 1;
|
|
|
|
/* Match including prefix? */
|
|
if (!strncasecmp(prefix, canonical_name1, prefix_len) &&
|
|
!strcasecmp(canonical_name1 + prefix_len, canonical_name2))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Check for a report type prefix + "all" match.
|
|
*/
|
|
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)
|
|
{
|
|
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;
|
|
|
|
if (!strncasecmp(field, "all", 3) && flen == 3) {
|
|
/* If there's no report prefix, match all report types */
|
|
if (!(flen = strlen(rh->field_prefix)))
|
|
return rh->report_types ? : REPORT_TYPES_ALL;
|
|
|
|
/* otherwise include all fields beginning with the report prefix. */
|
|
unprefixed_all_matched = 1;
|
|
field = rh->field_prefix;
|
|
report_types = rh->report_types;
|
|
}
|
|
|
|
/* Combine all report types that have a matching prefix. */
|
|
_all_match_combine(rh->types, unprefixed_all_matched, field, flen, &report_types);
|
|
|
|
return report_types;
|
|
}
|
|
|
|
/*
|
|
* Add all fields with a matching type.
|
|
*/
|
|
static int _add_all_fields(struct dm_report *rh, uint32_t type)
|
|
{
|
|
uint32_t f;
|
|
|
|
for (f = 0; rh->fields[f].report_fn; f++)
|
|
if ((rh->fields[f].type & type) && !_add_field(rh, f, 0, 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;
|
|
size_t prefix_len;
|
|
|
|
if (!flen)
|
|
return 0;
|
|
|
|
if (!_get_canonical_field_name(field, flen, field_canon, sizeof(field_canon), NULL))
|
|
return_0;
|
|
|
|
prefix_len = strlen(rh->field_prefix) - 1;
|
|
for (f = 0; _implicit_report_fields[f].report_fn; f++) {
|
|
if (_is_same_field(_implicit_report_fields[f].id, field_canon, rh->field_prefix, prefix_len)) {
|
|
*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, prefix_len)) {
|
|
*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;
|
|
|
|
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;
|
|
}
|
|
|
|
return _add_all_fields(rh, type);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
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->implicit == implicit) && (fp->field_num == field_num)) {
|
|
found = fp;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
if (report_type_only)
|
|
rh->report_types |= fields[field_num].type;
|
|
else if (!(found = _add_field(rh, field_num, implicit, FLD_HIDDEN)))
|
|
return_0;
|
|
}
|
|
|
|
if (report_type_only)
|
|
return 1;
|
|
|
|
if (found->flags & FLD_SORT_KEY) {
|
|
log_warn("dm_report: Ignoring duplicate sort field: %s.",
|
|
fields[field_num].id);
|
|
return 1;
|
|
}
|
|
|
|
found->flags |= FLD_SORT_KEY;
|
|
found->sort_posn = rh->keys_count++;
|
|
found->flags |= flags;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int _key_match(struct dm_report *rh, const char *key, size_t len,
|
|
unsigned report_type_only)
|
|
{
|
|
int implicit;
|
|
uint32_t f;
|
|
uint32_t flags;
|
|
|
|
if (!len)
|
|
return 0;
|
|
|
|
if (*key == '+') {
|
|
key++;
|
|
len--;
|
|
flags = FLD_ASCENDING;
|
|
} else if (*key == '-') {
|
|
key++;
|
|
len--;
|
|
flags = FLD_DESCENDING;
|
|
} else
|
|
flags = FLD_ASCENDING;
|
|
|
|
if (!len) {
|
|
log_error("dm_report: Missing sort field name");
|
|
return 0;
|
|
}
|
|
|
|
if (_get_field(rh, key, len, &f, &implicit))
|
|
return _add_sort_key(rh, f, implicit, flags, report_type_only);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int _parse_fields(struct dm_report *rh, const char *format,
|
|
unsigned report_type_only)
|
|
{
|
|
const char *ws; /* Word start */
|
|
const char *we = format; /* Word end */
|
|
|
|
while (*we) {
|
|
/* Allow consecutive commas */
|
|
while (*we && *we == ',')
|
|
we++;
|
|
|
|
/* start of the field name */
|
|
ws = we;
|
|
while (*we && *we != ',')
|
|
we++;
|
|
|
|
if (!_field_match(rh, ws, (size_t) (we - ws), report_type_only)) {
|
|
_display_fields(rh, 1, 0);
|
|
log_warn(" ");
|
|
log_error("Unrecognised field: %.*s", (int) (we - ws), ws);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int _parse_keys(struct dm_report *rh, const char *keys,
|
|
unsigned report_type_only)
|
|
{
|
|
const char *ws; /* Word start */
|
|
const char *we = keys; /* Word end */
|
|
|
|
if (!keys)
|
|
return 1;
|
|
|
|
while (*we) {
|
|
/* Allow consecutive commas */
|
|
while (*we && *we == ',')
|
|
we++;
|
|
ws = we;
|
|
while (*we && *we != ',')
|
|
we++;
|
|
if (!_key_match(rh, ws, (size_t) (we - ws), report_type_only)) {
|
|
_display_fields(rh, 1, 0);
|
|
log_warn(" ");
|
|
log_error("dm_report: Unrecognised field: %.*s", (int) (we - ws), ws);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
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,
|
|
const char *output_fields,
|
|
const char *output_separator,
|
|
uint32_t output_flags,
|
|
const char *sort_keys,
|
|
void *private_data)
|
|
{
|
|
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 NULL;
|
|
}
|
|
|
|
/*
|
|
* rh->report_types is updated in _parse_fields() and _parse_keys()
|
|
* to contain all types corresponding to the fields specified by
|
|
* fields or keys.
|
|
*/
|
|
if (report_types)
|
|
rh->report_types = *report_types;
|
|
|
|
rh->separator = output_separator;
|
|
rh->fields = fields;
|
|
rh->types = types;
|
|
rh->private = private_data;
|
|
|
|
rh->flags |= output_flags & DM_REPORT_OUTPUT_MASK;
|
|
|
|
/* With columns_as_rows we must buffer and not align. */
|
|
if (output_flags & DM_REPORT_OUTPUT_COLUMNS_AS_ROWS) {
|
|
if (!(output_flags & DM_REPORT_OUTPUT_BUFFERED))
|
|
rh->flags |= DM_REPORT_OUTPUT_BUFFERED;
|
|
if (output_flags & DM_REPORT_OUTPUT_ALIGNED)
|
|
rh->flags &= ~DM_REPORT_OUTPUT_ALIGNED;
|
|
}
|
|
|
|
if (output_flags & DM_REPORT_OUTPUT_BUFFERED)
|
|
rh->flags |= RH_SORT_REQUIRED;
|
|
|
|
rh->flags |= RH_FIELD_CALC_NEEDED;
|
|
|
|
dm_list_init(&rh->field_props);
|
|
dm_list_init(&rh->rows);
|
|
|
|
if ((type = _find_type(rh, rh->report_types)) && type->prefix)
|
|
rh->field_prefix = type->prefix;
|
|
else
|
|
rh->field_prefix = "";
|
|
|
|
if (!(rh->mem = dm_pool_create("report", 10 * 1024))) {
|
|
log_error("dm_report_init: allocation of memory pool failed");
|
|
dm_free(rh);
|
|
return NULL;
|
|
}
|
|
|
|
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.
|
|
* FIXME Use one pass instead and expand the "all" field afterwards.
|
|
*/
|
|
if (!_parse_fields(rh, output_fields, 1) ||
|
|
!_parse_keys(rh, sort_keys, 1)) {
|
|
dm_report_free(rh);
|
|
return NULL;
|
|
}
|
|
|
|
/* Generate list of fields for output based on format string & flags */
|
|
if (!_parse_fields(rh, output_fields, 0) ||
|
|
!_parse_keys(rh, sort_keys, 0)) {
|
|
dm_report_free(rh);
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Return updated types value for further compatility check by caller.
|
|
*/
|
|
_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);
|
|
}
|
|
|
|
static char *_toupperstr(char *str)
|
|
{
|
|
char *u = str;
|
|
|
|
do
|
|
*u = toupper(*u);
|
|
while (*u++);
|
|
|
|
return str;
|
|
}
|
|
|
|
int dm_report_set_output_field_name_prefix(struct dm_report *rh, const char *output_field_name_prefix)
|
|
{
|
|
char *prefix;
|
|
|
|
if (!(prefix = dm_pool_strdup(rh->mem, output_field_name_prefix))) {
|
|
log_error("dm_report_set_output_field_name_prefix: dm_pool_strdup failed");
|
|
return 0;
|
|
}
|
|
|
|
rh->output_field_name_prefix = _toupperstr(prefix);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Create a row of data for an object
|
|
*/
|
|
static void *_report_get_field_data(struct dm_report *rh,
|
|
struct field_properties *fp, void *object)
|
|
{
|
|
const struct dm_report_field_type *fields = fp->implicit ? _implicit_report_fields
|
|
: rh->fields;
|
|
char *ret;
|
|
|
|
if (!object) {
|
|
log_error(INTERNAL_ERROR "_report_get_field_data: missing object.");
|
|
return NULL;
|
|
}
|
|
|
|
if (!(ret = fp->type->data_fn(object)))
|
|
return NULL;
|
|
|
|
return (void *)(ret + fields[fp->field_num].offset);
|
|
}
|
|
|
|
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 = NULL;
|
|
struct dm_report_field *field;
|
|
void *data = NULL;
|
|
int r = 0;
|
|
|
|
if (!rh) {
|
|
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("_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("_do_report_object: "
|
|
"row sort value structure allocation failed");
|
|
goto out;
|
|
}
|
|
|
|
dm_list_init(&row->fields);
|
|
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("_do_report_object: "
|
|
"struct dm_report_field allocation failed");
|
|
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 = 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 (!fields[fp->field_num].report_fn(rh, rh->mem,
|
|
field, data,
|
|
rh->private)) {
|
|
log_error("_do_report_object: "
|
|
"report function failed for field %s",
|
|
fields[fp->field_num].id);
|
|
goto out;
|
|
}
|
|
|
|
dm_list_add(&row->fields, &field->list);
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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(const 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;
|
|
const 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;
|
|
const 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)))) {
|
|