summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Rajnoha <prajnoha@redhat.com>2015-07-03 10:43:07 +0200
committerPeter Rajnoha <prajnoha@redhat.com>2015-07-03 10:51:31 +0200
commit9cee94372a7e45b6a40478ec17de1c142ed268d4 (patch)
tree967e8aacce2513f552e1f6280c0fb8078cf3b923
parent3b1422c45c6c782f5134e74439ccdd6766085d4c (diff)
downloadlvm2-9cee94372a7e45b6a40478ec17de1c142ed268d4.tar.gz
report: select: add handler to recognize fuzzy time specification
Recognize date and time specification within selection criteria that is formulated in a more free-form way besides to the original basic YYYY-MM-DD HH:MM format that libdevmapper supports. Currently, this free-form format is recognized for lv_time field. Users are able to use expressions from this set: - weekday names ("Sunday" - "Saturday" or abbreviated as "Sun" - "Sat") - labels for points in time ("noon", "midnight") - labels for a day relative to current day ("today", "yesterday") - points back in time with relative offset from today (N is a number) ( "N" "seconds"/"minutes"/"hours"/"days"/"weeks"/"years" "ago") ( "N" "secs"/"mins"/"hrs" ... "ago") ( "N" "s"/"m"/"h" ... "ago") - time specification either in hh:mm:ss format or with AM/PM suffixes - month names ("January" - "December" or abbreviated as "Jan" - "Dec") For example: $ date Fri Jul 3 10:11:13 CEST 2015 $ lvmconfig --type full report/time_format time_format="%a %Y-%m-%d %T %z %Z [%s]" $ lvs LV VG Time lvol0 vg Fri 2014-08-22 21:25:41 +0200 CEST [1408735541] lvol2 vg Sun 2015-04-26 14:52:20 +0200 CEST [1430052740] root fedora Wed 2015-05-27 08:09:21 +0200 CEST [1432706961] swap fedora Wed 2015-05-27 08:09:21 +0200 CEST [1432706961] lvol1 vg Tue 2015-06-30 03:25:43 +0200 CEST [1435627543] lvol3 vg Tue 2015-06-30 14:52:23 +0200 CEST [1435668743] lvol6 vg Wed 2015-07-01 13:35:56 +0200 CEST [1435750556] lvol4 vg Thu 2015-07-02 12:12:02 +0200 CEST [1435831922] lvol5 vg Thu 2015-07-02 14:30:32 +0200 CEST [1435840232] $ lvs -S 'time=yesterday' LV VG Time lvol4 vg Thu 2015-07-02 12:12:02 +0200 CEST [1435831922] lvol5 vg Thu 2015-07-02 14:30:32 +0200 CEST [1435840232] $ lvs -S 'time since "June 30"' LV VG Time lvol1 vg Tue 2015-06-30 03:25:43 +0200 CEST [1435627543] lvol3 vg Tue 2015-06-30 14:52:23 +0200 CEST [1435668743] lvol6 vg Wed 2015-07-01 13:35:56 +0200 CEST [1435750556] lvol4 vg Thu 2015-07-02 12:12:02 +0200 CEST [1435831922] lvol5 vg Thu 2015-07-02 14:30:32 +0200 CEST [1435840232] $ lvs -S 'time since "noon June 30"' LV VG Time lvol3 vg Tue 2015-06-30 14:52:23 +0200 CEST [1435668743] lvol6 vg Wed 2015-07-01 13:35:56 +0200 CEST [1435750556] lvol4 vg Thu 2015-07-02 12:12:02 +0200 CEST [1435831922] lvol5 vg Thu 2015-07-02 14:30:32 +0200 CEST [1435840232] $ lvs -S 'time since "2 July 9AM"' LV VG Time lvol4 vg Thu 2015-07-02 12:12:02 +0200 CEST [1435831922] lvol5 vg Thu 2015-07-02 14:30:32 +0200 CEST [1435840232] $ lvs -S 'time since "2 July 1PM"' LV VG Time lvol5 vg Thu 2015-07-02 14:30:32 +0200 CEST [1435840232] ...and so on.
-rw-r--r--WHATS_NEW1
-rw-r--r--lib/report/report.c1034
-rw-r--r--lib/report/values.h1
-rw-r--r--libdm/libdm-report.c6
4 files changed, 1040 insertions, 2 deletions
diff --git a/WHATS_NEW b/WHATS_NEW
index 6c822c49e..6a33e6d7f 100644
--- a/WHATS_NEW
+++ b/WHATS_NEW
@@ -1,5 +1,6 @@
Version 2.02.124 -
=================================
+ Recognize free-form date/time values for lv_time field in selection criteria.
Fix regression in select to match string fields if using synonyms (2.02.123).
Fix regression when printing more lv names via display_lvname (2.02.122).
Add missing error logging to unlock_vg and sync_local_dev_names callers.
diff --git a/lib/report/report.c b/lib/report/report.c
index 4d1b8eb57..80d189aa6 100644
--- a/lib/report/report.c
+++ b/lib/report/report.c
@@ -27,6 +27,7 @@
#include <stddef.h> /* offsetof() */
#include <float.h> /* DBL_MAX */
+#include <time.h>
struct lvm_report_object {
struct volume_group *vg;
@@ -64,6 +65,1039 @@ static const double _siz_max = DBL_MAX;
*/
static const int32_t _reserved_num_undef_32 = INT32_C(-1);
+typedef enum {
+ /* top-level identification */
+ TIME_NULL,
+ TIME_NUM,
+ TIME_STR,
+
+ /* direct numeric value */
+ TIME_NUM__START,
+ TIME_NUM_MULTIPLIER,
+ TIME_NUM_MULTIPLIER_NEGATIVE,
+ TIME_NUM_DAY,
+ TIME_NUM_YEAR,
+ TIME_NUM__END,
+
+ /* direct string value */
+ TIME_STR_TIMEZONE,
+
+ /* time frame strings */
+ TIME_FRAME__START,
+ TIME_FRAME_AGO,
+ TIME_FRAME__END,
+
+ /* labels for dates */
+ TIME_LABEL_DATE__START,
+
+ TIME_LABEL_DATE_TODAY,
+ TIME_LABEL_DATE_YESTERDAY,
+
+ /* weekday name strings */
+ TIME_WEEKDAY__START,
+ TIME_WEEKDAY_SUNDAY,
+ TIME_WEEKDAY_MONDAY,
+ TIME_WEEKDAY_TUESDAY,
+ TIME_WEEKDAY_WEDNESDAY,
+ TIME_WEEKDAY_THURSDAY,
+ TIME_WEEKDAY_FRIDAY,
+ TIME_WEEKDAY_SATURDAY,
+ TIME_WEEKDAY__END,
+
+ TIME_LABEL_DATE__END,
+
+ /* labels for times */
+ TIME_LABEL_TIME__START,
+ TIME_LABEL_TIME_NOON,
+ TIME_LABEL_TIME_MIDNIGHT,
+ TIME_LABEL_TIME__END,
+
+ /* time unit strings */
+ TIME_UNIT__START,
+ TIME_UNIT_SECOND,
+ TIME_UNIT_SECOND_REL,
+ TIME_UNIT_MINUTE,
+ TIME_UNIT_MINUTE_REL,
+ TIME_UNIT_HOUR,
+ TIME_UNIT_HOUR_REL,
+ TIME_UNIT_AM,
+ TIME_UNIT_PM,
+ TIME_UNIT_DAY,
+ TIME_UNIT_WEEK,
+ TIME_UNIT_MONTH,
+ TIME_UNIT_YEAR,
+ TIME_UNIT_TZ_MINUTE,
+ TIME_UNIT_TZ_HOUR,
+ TIME_UNIT__END,
+
+ /* month name strings */
+ TIME_MONTH__START,
+ TIME_MONTH_JANUARY,
+ TIME_MONTH_FEBRUARY,
+ TIME_MONTH_MARCH,
+ TIME_MONTH_APRIL,
+ TIME_MONTH_MAY,
+ TIME_MONTH_JUNE,
+ TIME_MONTH_JULY,
+ TIME_MONTH_AUGUST,
+ TIME_MONTH_SEPTEMBER,
+ TIME_MONTH_OCTOBER,
+ TIME_MONTH_NOVEMBER,
+ TIME_MONTH_DECEMBER,
+ TIME_MONTH__END,
+} time_id_t;
+
+#define TIME_PROP_DATE 0x00000001 /* date-related */
+#define TIME_PROP_TIME 0x00000002 /* time-related */
+#define TIME_PROP_ABS 0x00000004 /* absolute value */
+#define TIME_PROP_REL 0x00000008 /* relative value */
+
+struct time_prop {
+ time_id_t id;
+ uint32_t prop_flags;
+ time_id_t granularity;
+};
+
+#define ADD_TIME_PROP(id, flags, granularity) [id] = {id, flags, granularity},
+
+static const struct time_prop _time_props[] = {
+ ADD_TIME_PROP(TIME_NULL, 0, TIME_NULL)
+ ADD_TIME_PROP(TIME_NUM, 0, TIME_NULL)
+ ADD_TIME_PROP(TIME_STR, 0, TIME_NULL)
+
+ ADD_TIME_PROP(TIME_NUM_MULTIPLIER, 0, TIME_NULL)
+ ADD_TIME_PROP(TIME_NUM_MULTIPLIER_NEGATIVE, 0, TIME_NULL)
+ ADD_TIME_PROP(TIME_NUM_DAY, TIME_PROP_DATE | TIME_PROP_ABS, TIME_UNIT_DAY)
+ ADD_TIME_PROP(TIME_NUM_YEAR, TIME_PROP_DATE | TIME_PROP_ABS, TIME_UNIT_YEAR)
+
+ ADD_TIME_PROP(TIME_STR_TIMEZONE, TIME_PROP_TIME | TIME_PROP_ABS, TIME_NULL)
+
+ ADD_TIME_PROP(TIME_FRAME_AGO, TIME_PROP_DATE | TIME_PROP_TIME | TIME_PROP_REL, TIME_NULL)
+
+ ADD_TIME_PROP(TIME_LABEL_DATE_TODAY, TIME_PROP_DATE | TIME_PROP_ABS, TIME_UNIT_DAY)
+ ADD_TIME_PROP(TIME_LABEL_DATE_YESTERDAY, TIME_PROP_DATE | TIME_PROP_ABS, TIME_UNIT_DAY)
+ ADD_TIME_PROP(TIME_WEEKDAY_SUNDAY, TIME_PROP_DATE | TIME_PROP_ABS, TIME_UNIT_DAY)
+ ADD_TIME_PROP(TIME_WEEKDAY_MONDAY, TIME_PROP_DATE | TIME_PROP_ABS, TIME_UNIT_DAY)
+ ADD_TIME_PROP(TIME_WEEKDAY_TUESDAY, TIME_PROP_DATE | TIME_PROP_ABS, TIME_UNIT_DAY)
+ ADD_TIME_PROP(TIME_WEEKDAY_WEDNESDAY, TIME_PROP_DATE | TIME_PROP_ABS, TIME_UNIT_DAY)
+ ADD_TIME_PROP(TIME_WEEKDAY_THURSDAY, TIME_PROP_DATE | TIME_PROP_ABS, TIME_UNIT_DAY)
+ ADD_TIME_PROP(TIME_WEEKDAY_FRIDAY, TIME_PROP_DATE | TIME_PROP_ABS, TIME_UNIT_DAY)
+ ADD_TIME_PROP(TIME_WEEKDAY_SATURDAY, TIME_PROP_DATE | TIME_PROP_ABS, TIME_UNIT_DAY)
+
+ ADD_TIME_PROP(TIME_LABEL_TIME_NOON, TIME_PROP_TIME | TIME_PROP_ABS, TIME_UNIT_SECOND)
+ ADD_TIME_PROP(TIME_LABEL_TIME_MIDNIGHT, TIME_PROP_TIME | TIME_PROP_ABS, TIME_UNIT_SECOND)
+
+ ADD_TIME_PROP(TIME_UNIT_SECOND, TIME_PROP_TIME | TIME_PROP_ABS, TIME_UNIT_SECOND)
+ ADD_TIME_PROP(TIME_UNIT_SECOND_REL, TIME_PROP_TIME | TIME_PROP_REL, TIME_UNIT_SECOND)
+ ADD_TIME_PROP(TIME_UNIT_MINUTE, TIME_PROP_TIME | TIME_PROP_ABS, TIME_UNIT_MINUTE)
+ ADD_TIME_PROP(TIME_UNIT_MINUTE_REL, TIME_PROP_TIME | TIME_PROP_REL, TIME_UNIT_MINUTE)
+ ADD_TIME_PROP(TIME_UNIT_HOUR, TIME_PROP_TIME | TIME_PROP_ABS, TIME_UNIT_HOUR)
+ ADD_TIME_PROP(TIME_UNIT_HOUR_REL, TIME_PROP_TIME | TIME_PROP_REL, TIME_UNIT_HOUR)
+ ADD_TIME_PROP(TIME_UNIT_AM, TIME_PROP_TIME | TIME_PROP_ABS, TIME_UNIT_HOUR)
+ ADD_TIME_PROP(TIME_UNIT_PM, TIME_PROP_TIME | TIME_PROP_ABS, TIME_UNIT_HOUR)
+ ADD_TIME_PROP(TIME_UNIT_DAY, TIME_PROP_DATE | TIME_PROP_REL, TIME_UNIT_DAY)
+ ADD_TIME_PROP(TIME_UNIT_WEEK, TIME_PROP_DATE | TIME_PROP_REL, TIME_UNIT_WEEK)
+ ADD_TIME_PROP(TIME_UNIT_MONTH, TIME_PROP_DATE | TIME_PROP_REL, TIME_UNIT_MONTH)
+ ADD_TIME_PROP(TIME_UNIT_YEAR, TIME_PROP_DATE | TIME_PROP_REL, TIME_UNIT_YEAR)
+ ADD_TIME_PROP(TIME_UNIT_TZ_MINUTE, TIME_PROP_TIME | TIME_PROP_ABS, TIME_NULL)
+ ADD_TIME_PROP(TIME_UNIT_TZ_HOUR, TIME_PROP_TIME | TIME_PROP_ABS, TIME_NULL)
+
+ ADD_TIME_PROP(TIME_MONTH_JANUARY, TIME_PROP_DATE | TIME_PROP_ABS, TIME_UNIT_MONTH)
+ ADD_TIME_PROP(TIME_MONTH_FEBRUARY, TIME_PROP_DATE | TIME_PROP_ABS, TIME_UNIT_MONTH)
+ ADD_TIME_PROP(TIME_MONTH_MARCH, TIME_PROP_DATE | TIME_PROP_ABS, TIME_UNIT_MONTH)
+ ADD_TIME_PROP(TIME_MONTH_APRIL, TIME_PROP_DATE | TIME_PROP_ABS, TIME_UNIT_MONTH)
+ ADD_TIME_PROP(TIME_MONTH_MAY, TIME_PROP_DATE | TIME_PROP_ABS, TIME_UNIT_MONTH)
+ ADD_TIME_PROP(TIME_MONTH_JUNE, TIME_PROP_DATE | TIME_PROP_ABS, TIME_UNIT_MONTH)
+ ADD_TIME_PROP(TIME_MONTH_JULY, TIME_PROP_DATE | TIME_PROP_ABS, TIME_UNIT_MONTH)
+ ADD_TIME_PROP(TIME_MONTH_AUGUST, TIME_PROP_DATE | TIME_PROP_ABS, TIME_UNIT_MONTH)
+ ADD_TIME_PROP(TIME_MONTH_SEPTEMBER, TIME_PROP_DATE | TIME_PROP_ABS, TIME_UNIT_MONTH)
+ ADD_TIME_PROP(TIME_MONTH_OCTOBER, TIME_PROP_DATE | TIME_PROP_ABS, TIME_UNIT_MONTH)
+ ADD_TIME_PROP(TIME_MONTH_NOVEMBER, TIME_PROP_DATE | TIME_PROP_ABS, TIME_UNIT_MONTH)
+ ADD_TIME_PROP(TIME_MONTH_DECEMBER, TIME_PROP_DATE | TIME_PROP_ABS, TIME_UNIT_MONTH)
+};
+
+#define TIME_REG_PLURAL_S 0x00000001 /* also recognize plural form with "s" suffix */
+
+struct time_reg {
+ const char *name;
+ const struct time_prop *prop;
+ uint32_t reg_flags;
+};
+
+#define TIME_PROP(id) (_time_props + id)
+
+static const struct time_reg _time_reg[] = {
+ /*
+ * Group of tokens representing time frame and used
+ * with relative date/time to specify different flavours
+ * of relativity.
+ */
+ {"ago", TIME_PROP(TIME_FRAME_AGO), 0},
+
+ /*
+ * Group of tokens labeling some date and used
+ * instead of direct absolute specification.
+ */
+ {"today", TIME_PROP(TIME_LABEL_DATE_TODAY), 0}, /* 0:00 - 23:59:59 for current date */
+ {"yesterday", TIME_PROP(TIME_LABEL_DATE_YESTERDAY), 0}, /* 0:00 - 23:59:59 for current date minus 1 day*/
+
+ /*
+ * Group of tokens labeling some date - weekday
+ * names used to build up date.
+ */
+ {"Sunday", TIME_PROP(TIME_WEEKDAY_SUNDAY), TIME_REG_PLURAL_S},
+ {"Sun", TIME_PROP(TIME_WEEKDAY_SUNDAY), 0},
+ {"Monday", TIME_PROP(TIME_WEEKDAY_MONDAY), TIME_REG_PLURAL_S},
+ {"Mon", TIME_PROP(TIME_WEEKDAY_MONDAY), 0},
+ {"Tuesday", TIME_PROP(TIME_WEEKDAY_TUESDAY), TIME_REG_PLURAL_S},
+ {"Tue", TIME_PROP(TIME_WEEKDAY_TUESDAY), 0},
+ {"Wednesday", TIME_PROP(TIME_WEEKDAY_WEDNESDAY), TIME_REG_PLURAL_S},
+ {"Wed", TIME_PROP(TIME_WEEKDAY_WEDNESDAY), 0},
+ {"Thursday", TIME_PROP(TIME_WEEKDAY_THURSDAY), TIME_REG_PLURAL_S},
+ {"Thu", TIME_PROP(TIME_WEEKDAY_THURSDAY), 0},
+ {"Friday", TIME_PROP(TIME_WEEKDAY_FRIDAY), TIME_REG_PLURAL_S},
+ {"Fri", TIME_PROP(TIME_WEEKDAY_FRIDAY), 0},
+ {"Saturday", TIME_PROP(TIME_WEEKDAY_SATURDAY), TIME_REG_PLURAL_S},
+ {"Sat", TIME_PROP(TIME_WEEKDAY_SATURDAY), 0},
+
+ /*
+ * Group of tokens labeling some time and used
+ * instead of direct absolute specification.
+ */
+ {"noon", TIME_PROP(TIME_LABEL_TIME_NOON), TIME_REG_PLURAL_S}, /* 12:00:00 */
+ {"midnight", TIME_PROP(TIME_LABEL_TIME_MIDNIGHT), TIME_REG_PLURAL_S}, /* 00:00:00 */
+
+ /*
+ * Group of tokens used to build up time. Most of these
+ * are used either as relative or absolute time units.
+ * The absolute ones are always used with TIME_FRAME_*
+ * token, otherwise the unit is relative.
+ */
+ {"second", TIME_PROP(TIME_UNIT_SECOND), TIME_REG_PLURAL_S},
+ {"sec", TIME_PROP(TIME_UNIT_SECOND), TIME_REG_PLURAL_S},
+ {"s", TIME_PROP(TIME_UNIT_SECOND), 0},
+ {"minute", TIME_PROP(TIME_UNIT_MINUTE), TIME_REG_PLURAL_S},
+ {"min", TIME_PROP(TIME_UNIT_MINUTE), TIME_REG_PLURAL_S},
+ {"m", TIME_PROP(TIME_UNIT_MINUTE), 0},
+ {"hour", TIME_PROP(TIME_UNIT_HOUR), TIME_REG_PLURAL_S},
+ {"hr", TIME_PROP(TIME_UNIT_HOUR), TIME_REG_PLURAL_S},
+ {"h", TIME_PROP(TIME_UNIT_HOUR), 0},
+ {"AM", TIME_PROP(TIME_UNIT_AM), 0},
+ {"PM", TIME_PROP(TIME_UNIT_PM), 0},
+
+ /*
+ * Group of tokens used to build up date.
+ * These are all relative ones.
+ */
+ {"day", TIME_PROP(TIME_UNIT_DAY), TIME_REG_PLURAL_S},
+ {"week", TIME_PROP(TIME_UNIT_WEEK), TIME_REG_PLURAL_S},
+ {"month", TIME_PROP(TIME_UNIT_MONTH), TIME_REG_PLURAL_S},
+ {"year", TIME_PROP(TIME_UNIT_YEAR), TIME_REG_PLURAL_S},
+ {"yr", TIME_PROP(TIME_UNIT_YEAR), TIME_REG_PLURAL_S},
+
+ /*
+ * Group of tokes used to build up date.
+ * These are all absolute.
+ */
+ {"January", TIME_PROP(TIME_MONTH_JANUARY), 0},
+ {"Jan", TIME_PROP(TIME_MONTH_JANUARY), 0},
+ {"February", TIME_PROP(TIME_MONTH_FEBRUARY), 0},
+ {"Feb", TIME_PROP(TIME_MONTH_FEBRUARY), 0},
+ {"March", TIME_PROP(TIME_MONTH_MARCH), 0},
+ {"Mar", TIME_PROP(TIME_MONTH_MARCH), 0},
+ {"April", TIME_PROP(TIME_MONTH_APRIL), 0},
+ {"Apr", TIME_PROP(TIME_MONTH_APRIL), 0},
+ {"May", TIME_PROP(TIME_MONTH_MAY), 0},
+ {"June", TIME_PROP(TIME_MONTH_JUNE), 0},
+ {"Jun", TIME_PROP(TIME_MONTH_JUNE), 0},
+ {"July", TIME_PROP(TIME_MONTH_JULY), 0},
+ {"Jul", TIME_PROP(TIME_MONTH_JULY), 0},
+ {"August", TIME_PROP(TIME_MONTH_AUGUST), 0},
+ {"Aug", TIME_PROP(TIME_MONTH_AUGUST), 0},
+ {"September", TIME_PROP(TIME_MONTH_SEPTEMBER), 0},
+ {"Sep", TIME_PROP(TIME_MONTH_SEPTEMBER), 0},
+ {"October", TIME_PROP(TIME_MONTH_OCTOBER), 0},
+ {"Oct", TIME_PROP(TIME_MONTH_OCTOBER), 0},
+ {"November", TIME_PROP(TIME_MONTH_NOVEMBER), 0},
+ {"Nov", TIME_PROP(TIME_MONTH_NOVEMBER), 0},
+ {"December", TIME_PROP(TIME_MONTH_DECEMBER), 0},
+ {"Dec", TIME_PROP(TIME_MONTH_DECEMBER), 0},
+ {NULL, TIME_PROP(TIME_NULL), 0},
+};
+
+struct time_item {
+ struct dm_list list;
+ const struct time_prop *prop;
+ const char *s;
+ size_t len;
+};
+
+struct time_info {
+ struct dm_pool *mem;
+ struct dm_list *ti_list;
+ time_t *now;
+ time_id_t min_abs_date_granularity;
+ time_id_t max_abs_date_granularity;
+ time_id_t min_abs_time_granularity;
+ time_id_t min_rel_time_granularity;
+};
+
+static int _is_time_num(time_id_t id)
+{
+ return ((id > TIME_NUM__START) && (id < TIME_NUM__END));
+};
+
+/*
+static int _is_time_frame(time_id_t id)
+{
+ return ((id > TIME_FRAME__START) && (id < TIME_FRAME__END));
+};
+*/
+
+static int _is_time_label_date(time_id_t id)
+{
+ return ((id > TIME_LABEL_DATE__START) && (id < TIME_LABEL_DATE__END));
+};
+
+static int _is_time_label_time(time_id_t id)
+{
+ return ((id > TIME_LABEL_TIME__START) && (id < TIME_LABEL_TIME__END));
+};
+
+static int _is_time_unit(time_id_t id)
+{
+ return ((id > TIME_UNIT__START) && (id < TIME_UNIT__END));
+};
+
+static int _is_time_weekday(time_id_t id)
+{
+ return ((id > TIME_WEEKDAY__START) && (id < TIME_WEEKDAY__END));
+};
+
+static int _is_time_month(time_id_t id)
+{
+ return ((id > TIME_MONTH__START) && (id < TIME_MONTH__END));
+};
+
+static const char *_skip_space(const char *s)
+{
+ while (*s && isspace(*s))
+ s++;
+ return s;
+}
+
+/* Move till delim or space */
+static const char *_move_till_item_end(const char *s)
+{
+ char c = *s;
+ int is_num = isdigit(c);
+
+ /*
+ * Allow numbers to be attached to next token, for example
+ * it's correct to write "12 hours" as well as "12hours".
+ */
+ while (c && !isspace(c) && (is_num ? (is_num = isdigit(c)) : 1))
+ c = *++s;
+
+ return s;
+}
+
+static struct time_item *_alloc_time_item(struct dm_pool *mem, time_id_t id,
+ const char *s, size_t len)
+{
+ struct time_item *ti;
+
+ if (!(ti = dm_pool_zalloc(mem, sizeof(struct time_item)))) {
+ log_error("alloc_time_item: dm_pool_zalloc failed");
+ return NULL;
+ }
+
+ ti->prop = &_time_props[id];
+ ti->s = s;
+ ti->len = len;
+
+ return ti;
+}
+
+static int _add_time_part_to_list(struct dm_pool *mem, struct dm_list *list,
+ time_id_t id, int minus, const char *s, size_t len)
+{
+ struct time_item *ti1, *ti2;
+
+ if (!(ti1 = _alloc_time_item(mem, minus ? TIME_NUM_MULTIPLIER_NEGATIVE
+ : TIME_NUM_MULTIPLIER, s, len)) ||
+ !(ti2 = _alloc_time_item(mem, id, s + len, 0)))
+ return 0;
+ dm_list_add(list, &ti1->list);
+ dm_list_add(list, &ti2->list);
+
+ return 1;
+}
+
+static int _get_time(struct dm_pool *mem, const char **str,
+ struct dm_list *list, int tz)
+{
+ const char *end, *s = *str;
+ int r = 0;
+
+ /* hour */
+ end = _move_till_item_end(s);
+ if (!_add_time_part_to_list(mem, list, tz ? TIME_UNIT_TZ_HOUR : TIME_UNIT_HOUR,
+ tz == -1, s, end - s))
+ goto out;
+
+ /* minute */
+ if (*end != ':')
+ /* minute required */
+ goto out;
+ s = end + 1;
+ end = _move_till_item_end(s);
+ if (!_add_time_part_to_list(mem, list, tz ? TIME_UNIT_TZ_MINUTE : TIME_UNIT_MINUTE,
+ tz == -1, s, end - s))
+ goto out;
+
+ /* second */
+ if (*end != ':') {
+ /* second not required */
+ s = end + 1;
+ r = 1;
+ goto out;
+ } else if (tz)
+ /* timezone does not have seconds */
+ goto out;
+
+ s = end + 1;
+ end = _move_till_item_end(s);
+ if (!_add_time_part_to_list(mem, list, TIME_UNIT_SECOND, 0, s, end - s))
+ goto out;
+
+ s = end + 1;
+ r = 1;
+out:
+ *str = s;
+ return r;
+}
+
+static int _preparse_fuzzy_time(const char *s, struct time_info *info)
+{
+ struct dm_list *list;
+ struct time_item *ti;
+ const char *end;
+ int fuzzy = 0;
+ time_id_t id;
+ size_t len;
+ int r = 0;
+ char c;
+
+ if (!(list = dm_pool_alloc(info->mem, sizeof(struct dm_list)))) {
+ log_error("_preparse_fuzzy_time: dm_pool_alloc failed");
+ goto out;
+ }
+ dm_list_init(list);
+ s = _skip_space(s);
+
+ while ((c = *s)) {
+ /*
+ * If the string consists of -:+, digits or spaces,
+ * it's not worth looking for fuzzy names here -
+ * it's standard YYYY-MM-DD HH:MM:SS +-HH:MM format
+ * and that is parseable by libdm directly.
+ */
+ if (!(isdigit(c) || (c == '-') || (c == ':') || (c == '+')))
+ fuzzy = 1;
+
+ end = _move_till_item_end(s);
+
+ if (isalpha(c))
+ id = TIME_STR;
+ else if (isdigit(c)) {
+ if (*end == ':') {
+ /* we have time */
+ if (!_get_time(info->mem, &s, list, 0))
+ goto out;
+ continue;
+ }
+ /* we have some other number */
+ id = TIME_NUM;
+ } else if ((c == '-') || (c == '+')) {
+ s++;
+ /* we have timezone */
+ if (!_get_time(info->mem, &s, list, (c == '-') ? -1 : 1))
+ goto out;
+ continue;
+ } else
+ goto out;
+
+ len = end - s;
+ if (!(ti = _alloc_time_item(info->mem, id, s, len)))
+ goto out;
+ dm_list_add(list, &ti->list);
+ s += len;
+ s = _skip_space(s);
+ }
+
+ info->ti_list = list;
+ r = 1;
+out:
+ if (!(r && fuzzy)) {
+ dm_pool_free(info->mem, list);
+ return 0;
+ }
+
+ return 1;
+}
+
+static int _match_time_str(struct dm_list *ti_list, struct time_item *ti)
+{
+ struct time_item *ti_context_p = (struct time_item *) dm_list_prev(ti_list, &ti->list);
+ size_t reg_len;
+ int i;
+
+ ti->prop = TIME_PROP(TIME_NULL);
+
+ for (i = 0; _time_reg[i].name; i++) {
+ reg_len = strlen(_time_reg[i].name);
+ if ((ti->len != reg_len) &&
+ !((_time_reg[i].reg_flags & TIME_REG_PLURAL_S) &&
+ (ti->len == reg_len+1) && (ti->s[reg_len] == 's')))
+ continue;
+
+ if (!strncasecmp(ti->s, _time_reg[i].name, reg_len)) {
+ ti->prop = _time_reg[i].prop;
+ if ((ti->prop->id > TIME_UNIT__START) && (ti->prop->id < TIME_UNIT__END) &&
+ ti_context_p && (ti_context_p->prop->id == TIME_NUM))
+ ti_context_p->prop = TIME_PROP(TIME_NUM_MULTIPLIER);
+ break;
+ }
+ }
+
+ return ti->prop->id;
+}
+
+static int _match_time_num(struct dm_list *ti_list, struct time_item *ti)
+{
+ struct time_item *ti_context_p = (struct time_item *) dm_list_prev(ti_list, &ti->list);
+ struct time_item *ti_context_n = (struct time_item *) dm_list_next(ti_list, &ti->list);
+ struct time_item *ti_context_nn = ti_context_n ? (struct time_item *) dm_list_next(ti_list, &ti_context_n->list) : NULL;
+
+ if (ti_context_n &&
+ (ti_context_n->prop->id > TIME_MONTH__START) &&
+ (ti_context_n->prop->id < TIME_MONTH__END)) {
+ if (ti_context_nn && ti_context_nn->prop->id == TIME_NUM) {
+ if (ti->len < ti_context_nn->len) {
+ /* 24 Feb 2015 */
+ ti->prop = TIME_PROP(TIME_NUM_DAY);
+ ti_context_nn->prop = TIME_PROP(TIME_NUM_YEAR);
+ } else {
+ /* 2015 Feb 24 */
+ ti->prop = TIME_PROP(TIME_NUM_YEAR);
+ ti_context_nn->prop = TIME_PROP(TIME_NUM_DAY);
+ }
+ } else {
+ if (ti->len <= 2)
+ /* 24 Feb */
+ ti->prop = TIME_PROP(TIME_NUM_DAY);
+ else
+ /* 2015 Feb */
+ ti->prop = TIME_PROP(TIME_NUM_YEAR);
+ }
+ } else if (ti_context_p &&
+ (ti_context_p->prop->id > TIME_MONTH__START) &&
+ (ti_context_p->prop->id < TIME_MONTH__END)) {
+ if (ti->len <= 2)
+ /* Feb 24 */
+ ti->prop = TIME_PROP(TIME_NUM_DAY);
+ else
+ /* Feb 2015 */
+ ti->prop = TIME_PROP(TIME_NUM_YEAR);
+ } else
+ ti->prop = TIME_PROP(TIME_NUM_YEAR);
+
+ return ti->prop->id;
+}
+
+static void _detect_time_granularity(struct time_info *info, struct time_item *ti)
+{
+ time_id_t gran = ti->prop->granularity;
+ int is_date, is_abs, is_rel;
+
+ if (gran == TIME_NULL)
+ return;
+
+ is_date = ti->prop->prop_flags & TIME_PROP_DATE;
+ is_abs = ti->prop->prop_flags & TIME_PROP_ABS;
+ is_rel = ti->prop->prop_flags & TIME_PROP_REL;
+
+ if (is_date && is_abs) {
+ if (gran > info->max_abs_date_granularity)
+ info->max_abs_date_granularity = gran;
+ if (gran < info->min_abs_date_granularity)
+ info->min_abs_date_granularity = gran;
+ } else {
+ if (is_abs && (gran < info->min_abs_time_granularity))
+ info->min_abs_time_granularity = gran;
+ else if (is_rel && (gran < info->min_rel_time_granularity))
+ info->min_rel_time_granularity = gran;
+ }
+}
+
+static void _change_to_relative(struct time_info *info, struct time_item *ti)
+{
+ struct time_item *ti2;
+
+ ti2 = ti;
+ while ((ti2 = (struct time_item *) dm_list_prev(info->ti_list, &ti2->list))) {
+ if (ti2->prop->id == TIME_FRAME_AGO)
+ break;
+
+ switch (ti2->prop->id) {
+ case TIME_UNIT_SECOND:
+ ti2->prop = TIME_PROP(TIME_UNIT_SECOND_REL);
+ break;
+ case TIME_UNIT_MINUTE:
+ ti2->prop = TIME_PROP(TIME_UNIT_MINUTE_REL);
+ break;
+ case TIME_UNIT_HOUR:
+ ti2->prop = TIME_PROP(TIME_UNIT_HOUR_REL);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+static int _recognize_time_items(struct time_info *info)
+{
+ struct time_item *ti;
+
+ /*
+ * At first, try to recognize strings.
+ * Also, if there are any items which may be absolute or
+ * relative and we have "TIME_FRAME_AGO", change them to relative.
+ */
+ dm_list_iterate_items(ti, info->ti_list) {
+ if ((ti->prop->id == TIME_STR) && !_match_time_str(info->ti_list, ti)) {
+ log_error("Unrecognized string in date/time "
+ "specification at \"%s\".", ti->s);
+ return 0;
+ }
+ if (ti->prop->id == TIME_FRAME_AGO)
+ _change_to_relative(info, ti);
+ }
+
+ /*
+ * Now, recognize any numbers and be sensitive to the context
+ * given by strings we recognized before. Also, detect time
+ * granularity used (both for absolute and/or relative parts).
+ */
+ dm_list_iterate_items(ti, info->ti_list) {
+ if ((ti->prop->id == TIME_NUM) && !_match_time_num(info->ti_list, ti)) {
+ log_error("Unrecognized number in date/time "
+ "specification at \"%s\".", ti->s);
+ return 0;
+ }
+ _detect_time_granularity(info, ti);
+ }
+
+ return 1;
+}
+
+static int _check_time_items(struct time_info *info)
+{
+ struct time_item *ti;
+ uint32_t flags;
+ int rel;
+ int date_is_relative = -1, time_is_relative = -1;
+ int label_time = 0, label_date = 0;
+
+ dm_list_iterate_items(ti, info->ti_list) {
+ flags = ti->prop->prop_flags;
+ rel = flags & TIME_PROP_REL;
+
+ if (flags & TIME_PROP_DATE) {
+ if (date_is_relative < 0)
+ date_is_relative = rel;
+ else if ((date_is_relative ^ rel) &&
+ (info->max_abs_date_granularity >= info->min_rel_time_granularity)) {
+ log_error("Mixed absolute and relative date "
+ "specification found at \"%s\".", ti->s);
+ return 0;
+ }
+
+ /* Date label can be used only once and not mixed with other date spec. */
+ if (label_date) {
+ log_error("Ambiguous date specification found at \"%s\".", ti->s);
+ return 0;
+ } else if (_is_time_label_date(ti->prop->id))
+ label_date = 1;
+ }
+
+ else if (flags & TIME_PROP_TIME) {
+ if (time_is_relative < 0)
+ time_is_relative = rel;
+ else if ((time_is_relative ^ rel)) {
+ log_error("Mixed absolute and relative time "
+ "specification found at \"%s\".", ti->s);
+ return 0;
+ }
+
+ /* Time label can be used only once and not mixed with other time spec. */
+ if (label_time) {
+ log_error("Ambiguous time specification found at \"%s\".", ti->s);
+ return 0;
+ } else if (_is_time_label_time(ti->prop->id))
+ label_time = 1;
+ }
+ }
+
+ return 1;
+}
+
+#define CACHE_ID_TIME_NOW "time_now"
+
+static time_t *_get_now(struct dm_report *rh, struct dm_pool *mem)
+{
+ const void *cached_obj;
+ time_t *now;
+
+ if (!(cached_obj = dm_report_value_cache_get(rh, CACHE_ID_TIME_NOW))) {
+ if (!(now = dm_pool_zalloc(mem, sizeof(time_t)))) {
+ log_error("_get_now: dm_pool_zalloc failed");
+ return NULL;
+ }
+ time(now);
+ if (!dm_report_value_cache_set(rh, CACHE_ID_TIME_NOW, now)) {
+ log_error("_get_now: failed to cache current time");
+ return NULL;
+ }
+ } else
+ now = (time_t *) cached_obj;
+
+ return now;
+}
+
+static void _adjust_time_for_granularity(struct time_info *info, struct tm *tm, time_t *t)
+{
+ switch (info->min_abs_date_granularity) {
+ case TIME_UNIT_YEAR:
+ tm->tm_mon = 0;
+ /* fall through */
+ case TIME_UNIT_MONTH:
+ tm->tm_mday = 1;
+ break;
+ default:
+ break;
+ }
+
+ switch (info->min_abs_time_granularity) {
+ case TIME_UNIT_HOUR:
+ tm->tm_min = 0;
+ /* fall through */
+ case TIME_UNIT_MINUTE:
+ tm->tm_sec = 0;
+ break;
+ case TIME_UNIT__END:
+ if (info->min_rel_time_granularity == TIME_UNIT__END)
+ tm->tm_hour = tm->tm_min = tm->tm_sec = 0;
+ break;
+ default:
+ break;
+ }
+
+ if ((info->min_abs_time_granularity == TIME_UNIT__END) &&
+ (info->min_rel_time_granularity >= TIME_UNIT_DAY) &&
+ (info->min_rel_time_granularity <= TIME_UNIT_YEAR))
+ tm->tm_hour = tm->tm_min = tm->tm_sec = 0;
+}
+
+#define SECS_PER_MINUTE 60
+#define SECS_PER_HOUR 3600
+#define SECS_PER_DAY 86400
+
+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];
+}
+
+static void _get_resulting_time_span(struct time_info *info,
+ struct tm *tm, time_t t,
+ time_t *t_result1, time_t *t_result2)
+{
+ time_t t1 = mktime(tm) - t;
+ time_t t2 = t1;
+ struct tm tmp;
+
+ if (info->min_abs_time_granularity != TIME_UNIT__END) {
+ if (info->min_abs_time_granularity == TIME_UNIT_MINUTE)
+ t2 += (SECS_PER_MINUTE - 1);
+ else if (info->min_abs_time_granularity == TIME_UNIT_HOUR)
+ t2 += (SECS_PER_HOUR - 1);
+ } else if (info->min_rel_time_granularity != TIME_UNIT__END) {
+ if (info->min_rel_time_granularity == TIME_UNIT_MINUTE)
+ t1 -= (SECS_PER_MINUTE + 1);
+ else if (info->min_rel_time_granularity == TIME_UNIT_HOUR)
+ t1 -= (SECS_PER_HOUR + 1);
+ else if ((info->min_rel_time_granularity >= TIME_UNIT_DAY) &&
+ (info->min_rel_time_granularity <= TIME_UNIT_YEAR))
+ t2 += (SECS_PER_DAY - 1);
+ } else {
+ if (info->min_abs_date_granularity == TIME_UNIT_MONTH)
+ t2 += (SECS_PER_DAY * _get_days_in_month(tm->tm_mon + 1, tm->tm_year) - 1);
+ else if (info->min_abs_date_granularity != TIME_UNIT__END)
+ t2 += (SECS_PER_DAY - 1);
+ }
+
+ /* Adjust for DST if needed. */
+ localtime_r(&t1, &tmp);
+ if (tmp.tm_isdst)
+ t1 -= SECS_PER_HOUR;
+ localtime_r(&t2, &tmp);
+ if (tmp.tm_isdst)
+ t2 -= SECS_PER_HOUR;
+
+ *t_result1 = t1;
+ *t_result2 = t2;
+}
+
+static int _translate_time_items(struct dm_report *rh, struct time_info *info,
+ const char **data_out)
+{
+ struct time_item *ti, *ti_p = NULL;
+ long multiplier = 1;
+ struct tm tm_now;
+ time_id_t id;
+ char *end;
+ long num;
+ struct tm tm; /* absolute time */
+ time_t t = 0; /* offset into past before absolute time */
+ time_t t1, t2;
+ char buf[32];
+
+ localtime_r(info->now, &tm_now);
+ tm = tm_now;
+ tm.tm_isdst = 0; /* we'll adjust for dst later */
+ tm.tm_wday = tm.tm_yday = -1;
+
+ dm_list_iterate_items(ti, info->ti_list) {
+ id = ti->prop->id;
+
+ if (_is_time_num(id)) {
+ num = strtol(ti->s, &end, 10);
+ switch (id) {
+ case TIME_NUM_MULTIPLIER_NEGATIVE:
+ multiplier = -num;
+ break;
+ case TIME_NUM_MULTIPLIER:
+ multiplier = num;
+ break;
+ case TIME_NUM_DAY:
+ tm.tm_mday = num;
+ break;
+ case TIME_NUM_YEAR:
+ tm.tm_year = num - 1900;
+ break;
+ default:
+ break;
+ }
+ } else if (_is_time_month(id)) {
+ tm.tm_mon = id - TIME_MONTH__START - 1;
+ } else if (_is_time_label_date(id)) {
+ if (_is_time_weekday(id)) {
+ num = id - TIME_WEEKDAY__START - 1;
+ if (tm_now.tm_wday < num)
+ num = 7 - num + tm_now.tm_wday;
+ else
+ num = tm_now.tm_wday - num;
+ t += num * SECS_PER_DAY;
+ } else switch (id) {
+ case TIME_LABEL_DATE_YESTERDAY:
+ t += SECS_PER_DAY;
+ break;
+ case TIME_LABEL_DATE_TODAY:
+ /* Nothing to do here - we started with today. */
+ break;
+ default:
+ break;
+ }
+ } else if (_is_time_label_time(id)) {
+ switch (id) {
+ case TIME_LABEL_TIME_NOON:
+ tm.tm_hour = 12;
+ tm.tm_min = tm.tm_sec = 0;
+ break;
+ case TIME_LABEL_TIME_MIDNIGHT:
+ tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
+ break;
+ default:
+ break;
+ }
+ } else if (_is_time_unit(id)) {
+ switch (id) {
+ case TIME_UNIT_SECOND:
+ tm.tm_sec = multiplier;
+ break;
+ case TIME_UNIT_SECOND_REL:
+ t += multiplier;
+ break;
+ case TIME_UNIT_MINUTE:
+ tm.tm_min = multiplier;
+ break;
+ case TIME_UNIT_MINUTE_REL:
+ t += (multiplier * SECS_PER_MINUTE);
+ break;
+ case TIME_UNIT_HOUR:
+ tm.tm_hour = multiplier;
+ break;
+ case TIME_UNIT_HOUR_REL:
+ t += (multiplier * SECS_PER_HOUR);
+ break;
+ case TIME_UNIT_AM:
+ if (ti_p->prop->id == TIME_NUM_MULTIPLIER)
+ tm.tm_hour = multiplier;
+ break;
+ case TIME_UNIT_PM:
+ if (_is_time_unit(ti_p->prop->id))
+ t -= 12 * SECS_PER_HOUR;
+ else if (ti_p->prop->id == TIME_NUM_MULTIPLIER)
+ tm.tm_hour = multiplier + 12;
+ break;
+ case TIME_UNIT_DAY:
+ t += multiplier * SECS_PER_DAY;
+ break;
+ case TIME_UNIT_WEEK:
+ t += multiplier * 7 * SECS_PER_DAY;
+ break;
+ case TIME_UNIT_MONTH:
+ /* if months > 12, convert to years first */
+ num = multiplier / 12;
+ tm.tm_year -= num;
+
+ num = multiplier % 12;
+ if (num > (tm.tm_mon + 1)) {
+ tm.tm_year--;
+ tm.tm_mon = 12 - num + tm.tm_mon;
+ } else
+ tm.tm_mon -= num;
+ break;
+ case TIME_UNIT_YEAR:
+ tm.tm_year -= multiplier;
+ break;
+ default:
+ break;
+ }
+ }
+
+ ti_p = ti;
+ }
+
+ _adjust_time_for_granularity(info, &tm, &t);
+ _get_resulting_time_span(info, &tm, t, &t1, &t2);
+
+ dm_pool_free(info->mem, info->ti_list);
+ info->ti_list = NULL;
+
+ dm_snprintf(buf, sizeof(buf), "@%ld:@%ld", t1, t2);
+ if (!(*data_out = dm_pool_strdup(info->mem, buf))) {
+ log_error("_translate_time_items: dm_pool_strdup failed");
+ return 0;
+ }
+
+ return 1;
+}
+
+static const char *_lv_time_handler_parse_fuzzy_name(struct dm_report *rh,
+ struct dm_pool *mem,
+ const char *data_in)
+{
+ const char *s = data_in;
+ const char *data_out = NULL;
+ struct time_info info = {.mem = mem,
+ .ti_list = NULL,
+ .now = _get_now(rh, mem),
+ .min_abs_date_granularity = TIME_UNIT__END,
+ .max_abs_date_granularity = TIME_UNIT__START,
+ .min_abs_time_granularity = TIME_UNIT__END,
+ .min_rel_time_granularity = TIME_UNIT__END};
+
+ if (!info.now)
+ goto_out;
+
+ /* recognize top-level parts - string/number/time/timezone? */
+ if (!_preparse_fuzzy_time(s, &info))
+ goto out;
+
+ /* recognize each part in more detail, also look at the context around if needed */
+ if (!_recognize_time_items(&info))
+ goto out;
+
+ /* check if the combination of items is allowed or whether it makes sense at all */
+ if (!_check_time_items(&info))
+ goto out;
+
+ /* translate items into final time range */
+ if (!_translate_time_items(rh, &info, &data_out))
+ goto out;
+out:
+ if (info.ti_list)
+ dm_pool_free(info.mem, info.ti_list);
+ return data_out;
+}
+
+static void *_lv_time_handler_get_dynamic_value(struct dm_report *rh,
+ struct dm_pool *mem,
+ const char *data_in)
+{
+ time_t t1, t2;
+ time_t *result;
+
+ if (sscanf(data_in, "@%ld:@%ld", &t1, &t2) != 2) {
+ log_error("Failed to get value for parsed time specification.");
+ return NULL;
+ }
+
+ if (!(result = dm_pool_alloc(mem, 2 * sizeof(time_t)))) {
+ log_error("Failed to allocate space to store time range.");
+ return NULL;
+ }
+
+ result[0] = t1;
+ result[1] = t2;
+
+ return result;
+}
+
+static int lv_time_handler(struct dm_report *rh, struct dm_pool *mem,
+ uint32_t field_num,
+ dm_report_reserved_action_t action,
+ const void *data_in, const void **data_out)
+{
+ *data_out = NULL;
+ if (!data_in)
+ return 1;
+
+ switch (action) {
+ case DM_REPORT_RESERVED_PARSE_FUZZY_NAME:
+ *data_out = _lv_time_handler_parse_fuzzy_name(rh, mem, data_in);
+ break;
+ case DM_REPORT_RESERVED_GET_DYNAMIC_VALUE:
+ if (!(*data_out = _lv_time_handler_get_dynamic_value(rh, mem, data_in)))
+ return 0;
+ break;
+ default:
+ return -1;
+ }
+
+ return 1;
+}
+
/*
* Get type reserved value - the value returned is the direct value of that type.
*/
diff --git a/lib/report/values.h b/lib/report/values.h
index 742cb5035..728760c5b 100644
--- a/lib/report/values.h
+++ b/lib/report/values.h
@@ -87,6 +87,7 @@ FIELD_RESERVED_VALUE(NOFLAG, lv_read_ahead, lv_read_ahead_auto, "", &_siz_max, "
FIELD_RESERVED_VALUE(NAMED, lv_when_full, lv_when_full_error, "", "error", "error", "error when full", "error if no space")
FIELD_RESERVED_VALUE(NAMED, lv_when_full, lv_when_full_queue, "", "queue", "queue", "queue when full", "queue if no space")
FIELD_RESERVED_VALUE(NOFLAG, lv_when_full, lv_when_full_undef, "", "", "", "undefined")
+FIELD_RESERVED_VALUE(NAMED | RANGE | FUZZY | DYNAMIC, lv_time, lv_time_fuzzy, "", lv_time_handler, NULL)
/* Reserved values for SEG fields */
FIELD_RESERVED_VALUE(NOFLAG, cache_policy, cache_policy_undef, "", "", "", "undefined")
diff --git a/libdm/libdm-report.c b/libdm/libdm-report.c
index 2ec98d697..7afbb8d9a 100644
--- a/libdm/libdm-report.c
+++ b/libdm/libdm-report.c
@@ -2273,11 +2273,13 @@ static int _check_reserved_values_supported(const struct dm_report_field_type fi
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_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_PERCENT |
+ DM_REPORT_FIELD_TYPE_TIME;
if (!reserved_values)