diff --git a/crmd/notify.c b/crmd/notify.c index 03dd2af4c3..649befcb47 100644 --- a/crmd/notify.c +++ b/crmd/notify.c @@ -1,857 +1,652 @@ /* * Copyright (C) 2015 Andrew Beekhof * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include "notify.h" #include "crmd_messages.h" static char *notify_script = NULL; static char *notify_target = NULL; static GListPtr notify_list = NULL; static int alerts_inflight = 0; static gboolean draining_alerts = FALSE; static guint max_alert_timeout = CRMD_NOTIFY_DEFAULT_TIMEOUT_MS; typedef struct { char *name; char *value; } envvar_t; typedef struct { char *id; char *path; int timeout; char *tstamp_format; char *recipient; GListPtr envvars; } notify_entry_t; enum notify_keys_e{ CRM_notify_recipient = 0, CRM_notify_node, CRM_notify_nodeid, CRM_notify_rsc, CRM_notify_task, CRM_notify_interval, CRM_notify_desc, CRM_notify_status, CRM_notify_target_rc, CRM_notify_rc, CRM_notify_kind, CRM_notify_version, CRM_notify_node_sequence, CRM_notify_timestamp }; /* * to allow script compatibility we can have more than one * set of environment variables */ static const char *notify_keys[][3] = { [CRM_notify_recipient] = {"CRM_notify_recipient", "CRM_alert_recipient", NULL}, [CRM_notify_node] = {"CRM_notify_node", "CRM_alert_node", NULL}, [CRM_notify_nodeid] = {"CRM_notify_nodeid", "CRM_alert_nodeid", NULL}, [CRM_notify_rsc] = {"CRM_notify_rsc", "CRM_alert_rsc", NULL}, [CRM_notify_task] = {"CRM_notify_task", "CRM_alert_task", NULL}, [CRM_notify_interval] = {"CRM_notify_interval", "CRM_alert_interval", NULL}, [CRM_notify_desc] = {"CRM_notify_desc", "CRM_alert_desc", NULL}, [CRM_notify_status] = {"CRM_notify_status", "CRM_alert_status", NULL}, [CRM_notify_target_rc] = {"CRM_notify_target_rc", "CRM_alert_target_rc", NULL}, [CRM_notify_rc] = {"CRM_notify_rc", "CRM_alert_rc", NULL}, [CRM_notify_kind] = {"CRM_notify_kind", "CRM_alert_kind", NULL}, [CRM_notify_version] = {"CRM_notify_version", "CRM_alert_version", NULL}, [CRM_notify_node_sequence] = {"CRM_notify_node_sequence", "CRM_alert_node_sequence", NULL}, [CRM_notify_timestamp] = {"CRM_notify_timestamp", "CRM_alert_timestamp", NULL} }; -/* - * higher accuracy time stuff to be generalized and moved to - * e.g. lib/common/utils.c|iso8601.c - */ - -#include -#include -#include -#include - -typedef struct crm_time_us crm_time_hr_t; -crm_time_hr_t *crm_time_hr_convert(crm_time_hr_t *target, crm_time_t *dt); -void crm_time_set_hr_dt(crm_time_t *target, crm_time_hr_t *hr_dt); -crm_time_hr_t *crm_time_timeval_hr_convert(crm_time_hr_t *target, - struct timeval *tv); -crm_time_hr_t *crm_time_hr_new(const char *date_time); -void crm_time_hr_free(crm_time_hr_t * hr_dt); -char *crm_time_format_hr(const char *format, crm_time_hr_t * hr_dt); - -crm_time_t *parse_date(const char *date_str); /* in iso8601.c global but - not in header */ - -struct crm_time_us { - int years; - int months; /* Only for durations */ - int days; - int seconds; - int offset; /* Seconds */ - bool duration; - int useconds; -}; - -struct crm_time_s { - int years; - int months; /* Only for durations */ - int days; - int seconds; - int offset; /* Seconds */ - bool duration; -}; - - - -static void -ha_get_tm_time( struct tm *target, crm_time_t *source) -{ - *target = (struct tm) { - .tm_year = source->years - 1900, - .tm_yday = source->days - 1, - .tm_sec = source->seconds % 60, - .tm_min = ( source->seconds / 60 ) % 60, - .tm_hour = source->seconds / 60 / 60, - -#if defined(HAVE_STRUCT_TM_TM_GMTOFF) - .tm_gmtoff = source->offset -#endif - }; -} - -crm_time_hr_t * -crm_time_hr_convert(crm_time_hr_t *target, crm_time_t *dt) -{ - crm_time_hr_t *hr_dt = NULL; - - if (dt) { - hr_dt = target?target:calloc(1, sizeof(crm_time_hr_t)); - if (hr_dt) { - *hr_dt = (crm_time_hr_t) { - .years = dt->years, - .months = dt->months, - .days = dt->days, - .seconds = dt->seconds, - .offset = dt->offset, - .duration = dt->duration - }; - } - } - - return hr_dt; -} - -void -crm_time_set_hr_dt(crm_time_t *target, crm_time_hr_t *hr_dt) -{ - CRM_ASSERT((hr_dt) && (target)); - *target = (crm_time_t) { - .years = hr_dt->years, - .months = hr_dt->months, - .days = hr_dt->days, - .seconds = hr_dt->seconds, - .offset = hr_dt->offset, - .duration = hr_dt->duration - }; -} - -crm_time_hr_t * -crm_time_timeval_hr_convert(crm_time_hr_t *target, struct timeval *tv) -{ - crm_time_t dt; - crm_time_hr_t *ret; - - crm_time_set_timet(&dt, &tv->tv_sec); - ret = crm_time_hr_convert(target, &dt); - if (ret) { - ret->useconds = tv->tv_usec; - } - return ret; -} - -crm_time_hr_t * -crm_time_hr_new(const char *date_time) -{ - crm_time_hr_t *hr_dt = NULL; - struct timeval tv_now; - - if (!date_time) { - if (gettimeofday(&tv_now, NULL) == 0) { - hr_dt = crm_time_timeval_hr_convert(NULL, &tv_now); - } - } else { - crm_time_t *dt; - - dt = parse_date(date_time); - hr_dt = crm_time_hr_convert(NULL, dt); - crm_time_free(dt); - } - return hr_dt; -} - -void -crm_time_hr_free(crm_time_hr_t * hr_dt) -{ - free(hr_dt); -} - -char * -crm_time_format_hr(const char *format, crm_time_hr_t * hr_dt) -{ - const char *mark_s; - int max = 128, scanned_pos = 0, printed_pos = 0, fmt_pos = 0, - date_len = 0, nano_digits = 0, fmt_len; - char nano_s[10], date_s[max+1], nanofmt_s[5] = "%", *tmp_fmt_s; - struct tm tm; - crm_time_t dt; - - if (!format) { - return NULL; - } - crm_time_set_hr_dt(&dt, hr_dt); - ha_get_tm_time(&tm, &dt); - sprintf(nano_s, "%06d000", hr_dt->useconds); - - while ((format[scanned_pos]) != '\0') { - fmt_len = 0; - mark_s = strchr(&format[scanned_pos], '%'); - if (mark_s) { - fmt_pos = mark_s - format; - fmt_len = 1; - while ((format[fmt_pos+fmt_len] != '\0') && - (format[fmt_pos+fmt_len] >= '0') && - (format[fmt_pos+fmt_len] <= '9')) { - fmt_len++; - } - scanned_pos = fmt_pos + fmt_len + 1; - if (format[fmt_pos+fmt_len] == 'N') { - nano_digits = atoi(&format[fmt_pos+1]); - nano_digits = (nano_digits > 6)?6:nano_digits; - nano_digits = (nano_digits < 0)?0:nano_digits; - sprintf(&nanofmt_s[1], ".%ds", nano_digits); - } else { - if (format[scanned_pos] != '\0') { - continue; - } - fmt_pos = scanned_pos; /* print till end */ - } - } else { - scanned_pos = strlen(format); - fmt_pos = scanned_pos; /* print till end */ - } - tmp_fmt_s = strndup(&format[printed_pos], fmt_pos - printed_pos); -#ifdef GCC_FORMAT_NONLITERAL_CHECKING_ENABLED -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wformat-nonliteral" -#endif - date_len += strftime(&date_s[date_len], max-date_len, tmp_fmt_s, &tm); -#ifdef GCC_FORMAT_NONLITERAL_CHECKING_ENABLED -#pragma GCC diagnostic pop -#endif - printed_pos = scanned_pos; - free(tmp_fmt_s); - if (nano_digits) { -#ifdef GCC_FORMAT_NONLITERAL_CHECKING_ENABLED -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wformat-nonliteral" -#endif - date_len += snprintf(&date_s[date_len], max-date_len, - nanofmt_s, nano_s); -#ifdef GCC_FORMAT_NONLITERAL_CHECKING_ENABLED -#pragma GCC diagnostic pop -#endif - nano_digits = 0; - } - } - - return (date_len == 0)?NULL:strdup(date_s); -} +#include /* * end of possibly generic time-handling stuff */ /* * syncronize local data with cib */ static void free_envvar_entry(envvar_t *entry) { free(entry->name); free(entry->value); free(entry); } static void free_notify_list_entry(notify_entry_t *entry) { free(entry->id); free(entry->path); free(entry->tstamp_format); free(entry->recipient); if (entry->envvars) { g_list_free_full(entry->envvars, (GDestroyNotify) free_envvar_entry); } free(entry); } static void free_notify_list() { if (notify_list) { g_list_free_full(notify_list, (GDestroyNotify) free_notify_list_entry); notify_list = NULL; } } static gpointer copy_envvar_entry(envvar_t * src, gpointer data) { envvar_t *dst = calloc(1, sizeof(envvar_t)); CRM_ASSERT(dst); dst->name = strdup(src->name); dst->value = src->value?strdup(src->value):NULL; return (gpointer) dst; } static GListPtr add_dup_envvar(GListPtr envvar_list, envvar_t *entry) { return g_list_prepend(envvar_list, copy_envvar_entry(entry, NULL)); } static GListPtr drop_envvars(GListPtr envvar_list, int count) { int i; for (i = 0; (envvar_list) && ((count < 0) || (i < count)); i++) { free_envvar_entry((envvar_t *) g_list_first(envvar_list)->data); envvar_list = g_list_delete_link(envvar_list, g_list_first(envvar_list)); } return envvar_list; } static GListPtr copy_envvar_list_remove_dupes(GListPtr src) { GListPtr dst = NULL, ls, ld; /* we are adding to the front so variable dupes coming via * recipient-section have got precedence over those in the * global section - we don't expect that many variables here * that it pays off to go for a hash-table to make dupe elimination * more efficient - maybe later when we might decide to do more * with the variables than cycling through them */ for (ls = g_list_first(src); ls; ls = g_list_next(ls)) { for (ld = g_list_first(dst); ld; ld = g_list_next(ld)) { if (!strcmp(((envvar_t *)(ls->data))->name, ((envvar_t *)(ld->data))->name)) { break; } } if (!ld) { dst = g_list_prepend(dst, copy_envvar_entry((envvar_t *)(ls->data), NULL)); } } return dst; } static void add_dup_notify_list_entry(notify_entry_t *entry) { notify_entry_t *new_entry = (notify_entry_t *) calloc(1, sizeof(notify_entry_t)); CRM_ASSERT(new_entry); *new_entry = (notify_entry_t) { .id = strdup(entry->id), .path = strdup(entry->path), .timeout = entry->timeout, .tstamp_format = entry->tstamp_format?strdup(entry->tstamp_format):NULL, .recipient = entry->recipient?strdup(entry->recipient):NULL, .envvars = entry->envvars? copy_envvar_list_remove_dupes(entry->envvars) :NULL }; notify_list = g_list_prepend(notify_list, new_entry); } static GListPtr get_envvars_from_cib(xmlNode *basenode, GListPtr list, int *count) { xmlNode *envvar; xmlNode *pair; if ((!basenode) || (!(envvar = first_named_child(basenode, XML_TAG_ATTR_SETS)))) { return list; } for (pair = first_named_child(envvar, XML_CIB_TAG_NVPAIR); pair; pair = __xml_next(pair)) { envvar_t envvar_entry = (envvar_t) { .name = (char *) crm_element_value(pair, XML_NVPAIR_ATTR_NAME), .value = (char *) crm_element_value(pair, XML_NVPAIR_ATTR_VALUE) }; crm_trace("Found environment variable %s = '%s'", envvar_entry.name, envvar_entry.value?envvar_entry.value:""); (*count)++; list = add_dup_envvar(list, &envvar_entry); } return list; } static GHashTable * get_meta_attrs_from_cib(xmlNode *basenode, notify_entry_t *entry, guint *max_timeout) { GHashTable *config_hash = g_hash_table_new_full(crm_str_hash, g_str_equal, g_hash_destroy_str, g_hash_destroy_str); crm_time_t *now = crm_time_new(NULL); const char *value = NULL; unpack_instance_attributes(basenode, basenode, XML_TAG_META_SETS, NULL, config_hash, NULL, FALSE, now); value = g_hash_table_lookup(config_hash, XML_ALERT_ATTR_TIMEOUT); if (value) { entry->timeout = crm_get_msec(value); if (entry->timeout <= 0) { if (entry->timeout == 0) { crm_trace("Setting timeout to default %dmsec", CRMD_NOTIFY_DEFAULT_TIMEOUT_MS); } else { crm_warn("Invalid timeout value setting to default %dmsec", CRMD_NOTIFY_DEFAULT_TIMEOUT_MS); } entry->timeout = CRMD_NOTIFY_DEFAULT_TIMEOUT_MS; } else { crm_trace("Found timeout %dmsec", entry->timeout); } if (entry->timeout > *max_timeout) { *max_timeout = entry->timeout; } } value = g_hash_table_lookup(config_hash, XML_ALERT_ATTR_TSTAMP_FORMAT); if (value) { /* hard to do any checks here as merely anything can * can be a valid time-format-string */ entry->tstamp_format = (char *) value; crm_trace("Found timestamp format string '%s'", value); } crm_time_free(now); return config_hash; /* keep hash as long as strings are needed */ } void parse_notifications(xmlNode *notifications) { xmlNode *notify; notify_entry_t entry; guint max_timeout = 0; free_notify_list(); max_alert_timeout = CRMD_NOTIFY_DEFAULT_TIMEOUT_MS; if (notifications) { crm_info("We have an alerts section in the cib"); if (notify_script) { crm_warn("Cib contains configuration for Legacy Notifications " "which is overruled by alerts section"); } } else { crm_info("No optional alerts section in cib"); if (notify_script) { entry = (notify_entry_t) { .id = (char *) "legacy_notification", .path = notify_script, .timeout = CRMD_NOTIFY_DEFAULT_TIMEOUT_MS, .recipient = notify_target }; add_dup_notify_list_entry(&entry); crm_info("Legacy Notifications enabled"); } return; } for (notify = first_named_child(notifications, XML_CIB_TAG_ALERT); notify; notify = __xml_next(notify)) { xmlNode *recipient; int recipients = 0, envvars = 0; GHashTable *config_hash = NULL; entry = (notify_entry_t) { .id = (char *) crm_element_value(notify, XML_ATTR_ID), .path = (char *) crm_element_value(notify, XML_ALERT_ATTR_PATH), .timeout = CRMD_NOTIFY_DEFAULT_TIMEOUT_MS, .tstamp_format = (char *) CRMD_NOTIFY_DEFAULT_TSTAMP_FORMAT }; entry.envvars = get_envvars_from_cib(notify, entry.envvars, &envvars); config_hash = get_meta_attrs_from_cib(notify, &entry, &max_timeout); crm_debug("Found alert: id=%s, path=%s, timeout=%d, " "tstamp_format=%s, %d additional environment variables", entry.id, entry.path, entry.timeout, entry.tstamp_format, envvars); for (recipient = first_named_child(notify, XML_CIB_TAG_ALERT_RECIPIENT); recipient; recipient = __xml_next(recipient)) { int envvars_added = 0; entry.recipient = (char *) crm_element_value(recipient, XML_ALERT_ATTR_REC_VALUE); recipients++; entry.envvars = get_envvars_from_cib(recipient, entry.envvars, &envvars_added); { notify_entry_t recipient_entry = entry; GHashTable *config_hash = get_meta_attrs_from_cib(recipient, &recipient_entry, &max_timeout); add_dup_notify_list_entry(&recipient_entry); crm_debug("Alert has recipient: id=%s, value=%s, " "%d additional environment variables", crm_element_value(recipient, XML_ATTR_ID), recipient_entry.recipient, envvars_added); g_hash_table_destroy(config_hash); } entry.envvars = drop_envvars(entry.envvars, envvars_added); } if (recipients == 0) { add_dup_notify_list_entry(&entry); } drop_envvars(entry.envvars, -1); g_hash_table_destroy(config_hash); } if (max_timeout > 0) { max_alert_timeout = max_timeout; } } /* * end of synchronization of local data with cib */ void crmd_enable_notifications(const char *script, const char *target) { free(notify_script); notify_script = ((script) && (strcmp(script,"/dev/null")))?strdup(script):NULL; free(notify_target); notify_target = (target != NULL)?strdup(target):NULL; } static void set_alert_key(enum notify_keys_e name, const char *value) { const char **key; for (key = notify_keys[name]; *key; key++) { crm_trace("Setting alert key %s = '%s'", *key, value); if (value) { setenv(*key, value, 1); } else { unsetenv(*key); } } } static void set_alert_key_int(enum notify_keys_e name, int value) { char *s = crm_itoa(value); set_alert_key(name, s); free(s); } static void unset_alert_keys() { const char **key; enum notify_keys_e name; for(name = 0; name < DIMOF(notify_keys); name++) { for(key = notify_keys[name]; *key; key++) { crm_trace("Unsetting alert key %s", *key); unsetenv(*key); } } } static void set_envvar_list(GListPtr envvars) { GListPtr l; for (l = g_list_first(envvars); l; l = g_list_next(l)) { envvar_t *entry = (envvar_t *)(l->data); crm_trace("Setting environment variable %s = '%s'", entry->name, entry->value?entry->value:""); if (entry->value) { setenv(entry->name, entry->value, 1); } else { unsetenv(entry->name); } } } static void unset_envvar_list(GListPtr envvars) { GListPtr l; for (l = g_list_first(envvars); l; l = g_list_next(l)) { envvar_t *entry = (envvar_t *)(l->data); crm_trace("Unsetting environment variable %s", entry->name); unsetenv(entry->name); } } static void crmd_notify_complete(svc_action_t *op) { alerts_inflight--; if(op->rc == 0) { crm_info("Alert %d (%s) complete", op->sequence, op->agent); } else { crm_warn("Alert %d (%s) failed: %d", op->sequence, op->agent, op->rc); } } static void send_notifications(const char *kind) { svc_action_t *notify = NULL; static int operations = 0; GListPtr l; crm_time_hr_t *now = crm_time_hr_new(NULL); set_alert_key(CRM_notify_kind, kind); set_alert_key(CRM_notify_version, VERSION); for (l = g_list_first(notify_list); l; l = g_list_next(l)) { notify_entry_t *entry = (notify_entry_t *)(l->data); char *timestamp = crm_time_format_hr(entry->tstamp_format, now); operations++; if (!draining_alerts) { crm_debug("Sending '%s' alert to '%s' via '%s'", kind, entry->recipient, entry->path); set_alert_key(CRM_notify_recipient, entry->recipient); set_alert_key_int(CRM_notify_node_sequence, operations); set_alert_key(CRM_notify_timestamp, timestamp); notify = services_action_create_generic(entry->path, NULL); notify->timeout = entry->timeout; notify->standard = strdup("event"); notify->id = strdup(entry->id); notify->agent = strdup(entry->path); notify->sequence = operations; set_envvar_list(entry->envvars); alerts_inflight++; if(services_action_async(notify, &crmd_notify_complete) == FALSE) { services_action_free(notify); alerts_inflight--; } unset_envvar_list(entry->envvars); } else { crm_warn("Ignoring '%s' alert to '%s' via '%s' received " "while shutting down", kind, entry->recipient, entry->path); } free(timestamp); } unset_alert_keys(); if (now) { free(now); } } void crmd_notify_node_event(crm_node_t *node) { if(!notify_list) { return; } set_alert_key(CRM_notify_node, node->uname); set_alert_key_int(CRM_notify_nodeid, node->id); set_alert_key(CRM_notify_desc, node->state); send_notifications("node"); } void crmd_notify_fencing_op(stonith_event_t * e) { char *desc = NULL; if (!notify_list) { return; } desc = crm_strdup_printf( "Operation %s of %s by %s for %s@%s: %s (ref=%s)", e->action, e->target, e->executioner ? e->executioner : "", e->client_origin, e->origin, pcmk_strerror(e->result), e->id); set_alert_key(CRM_notify_node, e->target); set_alert_key(CRM_notify_task, e->operation); set_alert_key(CRM_notify_desc, desc); set_alert_key_int(CRM_notify_rc, e->result); send_notifications("fencing"); free(desc); } void crmd_notify_resource_op(const char *node, lrmd_event_data_t * op) { int target_rc = 0; if(!notify_list) { return; } target_rc = rsc_op_expected_rc(op); if(op->interval == 0 && target_rc == op->rc && safe_str_eq(op->op_type, RSC_STATUS)) { /* Leave it up to the script if they want to notify for * 'failed' probes, only swallow ones for which the result was * unexpected. * * Even if we find a resource running, it was probably because * someone erased the status section. */ return; } set_alert_key(CRM_notify_node, node); set_alert_key(CRM_notify_rsc, op->rsc_id); set_alert_key(CRM_notify_task, op->op_type); set_alert_key_int(CRM_notify_interval, op->interval); set_alert_key_int(CRM_notify_target_rc, target_rc); set_alert_key_int(CRM_notify_status, op->op_status); set_alert_key_int(CRM_notify_rc, op->rc); if(op->op_status == PCMK_LRM_OP_DONE) { set_alert_key(CRM_notify_desc, services_ocf_exitcode_str(op->rc)); } else { set_alert_key(CRM_notify_desc, services_lrm_status_str(op->op_status)); } send_notifications("resource"); } static gboolean alert_drain_timeout_callback(gpointer user_data) { gboolean *timeout_popped = (gboolean *) user_data; *timeout_popped = TRUE; return FALSE; } void crmd_drain_alerts(GMainContext *ctx) { guint timer; gboolean timeout_popped = FALSE; draining_alerts = TRUE; timer = g_timeout_add(max_alert_timeout + 5000, alert_drain_timeout_callback, (gpointer) &timeout_popped); while(alerts_inflight && !timeout_popped) { crm_trace("Draining mainloop while still %d alerts are in flight (timeout=%dms)", alerts_inflight, max_alert_timeout + 5000); g_main_context_iteration(ctx, TRUE); } if (!timeout_popped && (timer > 0)) { g_source_remove(timer); } } diff --git a/include/crm/common/iso8601_internal.h b/include/crm/common/iso8601_internal.h new file mode 100644 index 0000000000..9a195941c1 --- /dev/null +++ b/include/crm/common/iso8601_internal.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2015 Andrew Beekhof + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include + +typedef struct crm_time_us crm_time_hr_t; +crm_time_hr_t *crm_time_hr_convert(crm_time_hr_t *target, crm_time_t *dt); +void crm_time_set_hr_dt(crm_time_t *target, crm_time_hr_t *hr_dt); +crm_time_hr_t *crm_time_timeval_hr_convert(crm_time_hr_t *target, + struct timeval *tv); +crm_time_hr_t *crm_time_hr_new(const char *date_time); +void crm_time_hr_free(crm_time_hr_t * hr_dt); +char *crm_time_format_hr(const char *format, crm_time_hr_t * hr_dt); + +crm_time_t *parse_date(const char *date_str); /* in iso8601.c global but + not in header */ + +struct crm_time_us { + int years; + int months; /* Only for durations */ + int days; + int seconds; + int offset; /* Seconds */ + bool duration; + int useconds; +}; diff --git a/lib/common/iso8601.c b/lib/common/iso8601.c index 47f863ed31..a873b0913a 100644 --- a/lib/common/iso8601.c +++ b/lib/common/iso8601.c @@ -1,1283 +1,1448 @@ /* * Copyright (C) 2005 Andrew Beekhof * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * Primary reference: * http://en.wikipedia.org/wiki/ISO_8601 (as at 2005-08-01) * * Secondary references: * http://hydracen.com/dx/iso8601.htm * http://www.personal.ecu.edu/mccartyr/ISOwdALG.txt * http://www.personal.ecu.edu/mccartyr/isowdcal.html * http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm * */ #include #include #include #include #include +#include /* * Andrew's code was originally written for OSes whose "struct tm" contains: * long tm_gmtoff; :: Seconds east of UTC * const char *tm_zone; :: Timezone abbreviation * Some OSes lack these, instead having: * time_t (or long) timezone; :: "difference between UTC and local standard time" * char *tzname[2] = { "...", "..." }; * I (David Lee) confess to not understanding the details. So my attempted * generalisations for where their use is necessary may be flawed. * * 1. Does "difference between ..." subtract the same or opposite way? * 2. Should it use "altzone" instead of "timezone"? * 3. Should it use tzname[0] or tzname[1]? Interaction with timezone/altzone? */ #if defined(HAVE_STRUCT_TM_TM_GMTOFF) # define GMTOFF(tm) ((tm)->tm_gmtoff) #else /* Note: extern variable; macro argument not actually used. */ # define GMTOFF(tm) (-timezone+daylight) #endif struct crm_time_s { int years; int months; /* Only for durations */ int days; int seconds; int offset; /* Seconds */ bool duration; }; char *crm_time_as_string(crm_time_t * date_time, int flags); crm_time_t *parse_date(const char *date_str); gboolean check_for_ordinal(const char *str); static crm_time_t * crm_get_utc_time(crm_time_t * dt) { crm_time_t *utc = calloc(1, sizeof(crm_time_t)); utc->years = dt->years; utc->days = dt->days; utc->seconds = dt->seconds; utc->offset = 0; if (dt->offset) { crm_time_add_seconds(utc, -dt->offset); } else { /* Durations (which are the only things that can include months, never have a timezone */ utc->months = dt->months; } crm_time_log(LOG_TRACE, "utc-source", dt, crm_time_log_date | crm_time_log_timeofday | crm_time_log_with_timezone); crm_time_log(LOG_TRACE, "utc-target", utc, crm_time_log_date | crm_time_log_timeofday | crm_time_log_with_timezone); return utc; } crm_time_t * crm_time_new(const char *date_time) { time_t tm_now; crm_time_t *dt = NULL; tzset(); if (date_time == NULL) { tm_now = time(NULL); dt = calloc(1, sizeof(crm_time_t)); crm_time_set_timet(dt, &tm_now); } else { dt = parse_date(date_time); } return dt; } void crm_time_free(crm_time_t * dt) { if (dt == NULL) { return; } free(dt); } static int year_days(int year) { int d = 365; if (crm_time_leapyear(year)) { d++; } return d; } /* http://www.personal.ecu.edu/mccartyr/ISOwdALG.txt * * 5. Find the Jan1Weekday for Y (Monday=1, Sunday=7) * YY = (Y-1) % 100 * C = (Y-1) - YY * G = YY + YY/4 * Jan1Weekday = 1 + (((((C / 100) % 4) x 5) + G) % 7) */ int crm_time_january1_weekday(int year) { int YY = (year - 1) % 100; int C = (year - 1) - YY; int G = YY + YY / 4; int jan1 = 1 + (((((C / 100) % 4) * 5) + G) % 7); crm_trace("YY=%d, C=%d, G=%d", YY, C, G); crm_trace("January 1 %.4d: %d", year, jan1); return jan1; } int crm_time_weeks_in_year(int year) { int weeks = 52; int jan1 = crm_time_january1_weekday(year); /* if jan1 == thursday */ if (jan1 == 4) { weeks++; } else { jan1 = crm_time_january1_weekday(year + 1); /* if dec31 == thursday aka. jan1 of next year is a friday */ if (jan1 == 5) { weeks++; } } return weeks; } int month_days[14] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 29 }; int crm_time_days_in_month(int month, int year) { if (month == 2 && crm_time_leapyear(year)) { month = 13; } return month_days[month]; } bool crm_time_leapyear(int year) { gboolean is_leap = FALSE; if (year % 4 == 0) { is_leap = TRUE; } if (year % 100 == 0 && year % 400 != 0) { is_leap = FALSE; } return is_leap; } static uint32_t get_ordinal_days(uint32_t y, uint32_t m, uint32_t d) { int lpc; for (lpc = 1; lpc < m; lpc++) { d += crm_time_days_in_month(lpc, y); } return d; } void crm_time_log_alias(int log_level, const char *file, const char *function, int line, const char *prefix, crm_time_t * date_time, int flags) { char *date_s = crm_time_as_string(date_time, flags); if (log_level < LOG_CRIT) { printf("%s%s%s\n", prefix ? prefix : "", prefix ? ": " : "", date_s ? date_s : "__invalid_date__"); } else { do_crm_log_alias(log_level, file, function, line, "%s%s%s", prefix ? prefix : "", prefix ? ": " : "", date_s ? date_s : "__invalid_date__"); } free(date_s); } static int crm_time_get_sec(int sec, uint * h, uint * m, uint * s) { uint hours, minutes, seconds; if (sec < 0) { seconds = 0 - sec; } else { seconds = sec; } hours = seconds / (60 * 60); seconds -= 60 * 60 * hours; minutes = seconds / (60); seconds -= 60 * minutes; crm_trace("%d == %.2d:%.2d:%.2d", sec, hours, minutes, seconds); *h = hours; *m = minutes; *s = seconds; return TRUE; } int crm_time_get_timeofday(crm_time_t * dt, uint * h, uint * m, uint * s) { return crm_time_get_sec(dt->seconds, h, m, s); } int crm_time_get_timezone(crm_time_t * dt, uint * h, uint * m) { uint s; return crm_time_get_sec(dt->seconds, h, m, &s); } long long crm_time_get_seconds(crm_time_t * dt) { int lpc; crm_time_t *utc = NULL; long long in_seconds = 0; utc = crm_get_utc_time(dt); for (lpc = 1; lpc < utc->years; lpc++) { int dmax = year_days(lpc); in_seconds += 60 * 60 * 24 * dmax; } /* utc->months is an offset that can only be set for a duration * By definiton, the value is variable depending on the date to * which it is applied * * Force 30-day months so that something vaguely sane happens * for anyone that tries to use a month in this way */ if (utc->months > 0) { in_seconds += 60 * 60 * 24 * 30 * utc->months; } if (utc->days > 0) { in_seconds += 60 * 60 * 24 * (utc->days - 1); } in_seconds += utc->seconds; crm_time_free(utc); return in_seconds; } #define EPOCH_SECONDS 62135596800ULL /* Calculated using crm_time_get_seconds() */ long long crm_time_get_seconds_since_epoch(crm_time_t * dt) { return crm_time_get_seconds(dt) - EPOCH_SECONDS; } int crm_time_get_gregorian(crm_time_t * dt, uint * y, uint * m, uint * d) { int months = 0; int days = dt->days; if(dt->years != 0) { for (months = 1; months <= 12 && days > 0; months++) { int mdays = crm_time_days_in_month(months, dt->years); if (mdays >= days) { break; } else { days -= mdays; } } } else if (dt->months) { /* This is a duration including months, don't convert the days field */ months = dt->months; } else { /* This is a duration not including months, still don't convert the days field */ } *y = dt->years; *m = months; *d = days; crm_trace("%.4d-%.3d -> %.4d-%.2d-%.2d", dt->years, dt->days, dt->years, months, days); return TRUE; } int crm_time_get_ordinal(crm_time_t * dt, uint * y, uint * d) { *y = dt->years; *d = dt->days; return TRUE; } int crm_time_get_isoweek(crm_time_t * dt, uint * y, uint * w, uint * d) { /* * Monday 29 December 2008 is written "2009-W01-1" * Sunday 3 January 2010 is written "2009-W53-7" */ int year_num = 0; int jan1 = crm_time_january1_weekday(dt->years); int h = -1; CRM_CHECK(dt->days > 0, return FALSE); /* 6. Find the Weekday for Y M D */ h = dt->days + jan1 - 1; *d = 1 + ((h - 1) % 7); /* 7. Find if Y M D falls in YearNumber Y-1, WeekNumber 52 or 53 */ if (dt->days <= (8 - jan1) && jan1 > 4) { crm_trace("year--, jan1=%d", jan1); year_num = dt->years - 1; *w = crm_time_weeks_in_year(year_num); } else { year_num = dt->years; } /* 8. Find if Y M D falls in YearNumber Y+1, WeekNumber 1 */ if (year_num == dt->years) { int dmax = year_days(year_num); int correction = 4 - *d; if ((dmax - dt->days) < correction) { crm_trace("year++, jan1=%d, i=%d vs. %d", jan1, dmax - dt->days, correction); year_num = dt->years + 1; *w = 1; } } /* 9. Find if Y M D falls in YearNumber Y, WeekNumber 1 through 53 */ if (year_num == dt->years) { int j = dt->days + (7 - *d) + (jan1 - 1); *w = j / 7; if (jan1 > 4) { *w -= 1; } } *y = year_num; crm_trace("Converted %.4d-%.3d to %.4d-W%.2d-%d", dt->years, dt->days, *y, *w, *d); return TRUE; } char * crm_time_as_string(crm_time_t * date_time, int flags) { char *date_s = NULL; char *time_s = NULL; char *offset_s = NULL; char *result_s = NULL; crm_time_t *dt = NULL; crm_time_t *utc = NULL; if (date_time == NULL) { return strdup(""); } else if (date_time->offset && (flags & crm_time_log_with_timezone) == 0) { crm_trace("UTC conversion"); utc = crm_get_utc_time(date_time); dt = utc; } else { dt = date_time; } CRM_CHECK(dt != NULL, return NULL); if (flags & crm_time_log_duration) { uint h = 0, m = 0, s = 0; int offset = 0, max = 128; date_s = calloc(1, max+1); crm_time_get_sec(dt->seconds, &h, &m, &s); if (date_s == NULL) { goto done; } if(dt->years) { offset += snprintf(date_s+offset, max-offset, "%4d year%s ", dt->years, dt->years>1?"s":""); } if(dt->months) { offset += snprintf(date_s+offset, max-offset, "%2d month%s ", dt->months, dt->months>1?"s":""); } if(dt->days) { offset += snprintf(date_s+offset, max-offset, "%2d day%s ", dt->days, dt->days>1?"s":""); } if(dt->seconds) { offset += snprintf(date_s+offset, max-offset, "%d seconds ( ", dt->seconds); if(h) { offset += snprintf(date_s+offset, max-offset, "%d hour%s ", h, h>1?"s":""); } if(m) { offset += snprintf(date_s+offset, max-offset, "%d minute%s ", m, m>1?"s":""); } if(s) { offset += snprintf(date_s+offset, max-offset, "%d second%s ", s, s>1?"s":""); } offset += snprintf(date_s+offset, max-offset, ")"); } goto done; } if (flags & crm_time_log_date) { date_s = calloc(1, 32); if (date_s == NULL) { goto done; } else if (flags & crm_time_seconds) { unsigned long long s = crm_time_get_seconds(date_time); snprintf(date_s, 31, "%lld", s); /* Durations may not be +ve */ goto done; } else if (flags & crm_time_epoch) { unsigned long long s = crm_time_get_seconds_since_epoch(date_time); snprintf(date_s, 31, "%lld", s); /* Durations may not be +ve */ goto done; } else if (flags & crm_time_weeks) { /* YYYY-Www-D */ uint y, w, d; if (crm_time_get_isoweek(dt, &y, &w, &d)) { snprintf(date_s, 31, "%d-W%.2d-%d", y, w, d); } } else if (flags & crm_time_ordinal) { /* YYYY-DDD */ uint y, d; if (crm_time_get_ordinal(dt, &y, &d)) { snprintf(date_s, 31, "%d-%.3d", y, d); } } else { /* YYYY-MM-DD */ uint y, m, d; if (crm_time_get_gregorian(dt, &y, &m, &d)) { snprintf(date_s, 31, "%.4d-%.2d-%.2d", y, m, d); } } } if (flags & crm_time_log_timeofday) { uint h, m, s; time_s = calloc(1, 32); if (time_s == NULL) { goto cleanup; } if (crm_time_get_timeofday(dt, &h, &m, &s)) { snprintf(time_s, 31, "%.2d:%.2d:%.2d", h, m, s); } if (dt->offset != 0) { crm_time_get_sec(dt->offset, &h, &m, &s); } offset_s = calloc(1, 32); if ((flags & crm_time_log_with_timezone) == 0 || dt->offset == 0) { crm_trace("flags %6x %6x", flags, crm_time_log_with_timezone); snprintf(offset_s, 31, "Z"); } else { snprintf(offset_s, 31, " %c%.2d:%.2d", dt->offset < 0 ? '-' : '+', h, m); } } done: result_s = calloc(1, 100); snprintf(result_s, 100, "%s%s%s%s", date_s ? date_s : "", (date_s != NULL && time_s != NULL) ? " " : "", time_s ? time_s : "", offset_s ? offset_s : ""); cleanup: free(date_s); free(time_s); free(offset_s); crm_time_free(utc); return result_s; } static int crm_time_parse_sec(const char *time_str) { int rc; uint hour = 0; uint minute = 0; uint second = 0; rc = sscanf(time_str, "%d:%d:%d", &hour, &minute, &second); if (rc == 1) { rc = sscanf(time_str, "%2d%2d%2d", &hour, &minute, &second); } if (rc > 0 && rc < 4) { crm_trace("Got valid time: %.2d:%.2d:%.2d", hour, minute, second); if (hour >= 24) { crm_err("Invalid hour: %d", hour); } else if (minute >= 60) { crm_err("Invalid minute: %d", minute); } else if (second >= 60) { crm_err("Invalid second: %d", second); } else { second += (minute * 60); second += (hour * 60 * 60); } } else { crm_err("Bad time: %s (%d)", time_str, rc); } return second; } static int crm_time_parse_offset(const char *offset_str) { int offset = 0; tzset(); if (offset_str == NULL) { #if defined(HAVE_STRUCT_TM_TM_GMTOFF) time_t now = time(NULL); struct tm *now_tm = localtime(&now); #endif int h_offset = GMTOFF(now_tm) / (3600); int m_offset = (GMTOFF(now_tm) - (3600 * h_offset)) / (60); if (h_offset < 0 && m_offset < 0) { m_offset = 0 - m_offset; } offset += (60 * 60 * h_offset); offset += (60 * m_offset); } else if (offset_str[0] == 'Z') { } else if (offset_str[0] == '+' || offset_str[0] == '-' || isdigit((int)offset_str[0])) { gboolean negate = FALSE; if (offset_str[0] == '-') { negate = TRUE; offset_str++; } offset = crm_time_parse_sec(offset_str); if (negate) { offset = 0 - offset; } } return offset; } static crm_time_t * crm_time_parse(const char *time_str, crm_time_t * a_time) { uint h, m, s; char *offset_s = NULL; crm_time_t *dt = a_time; tzset(); if (a_time == NULL) { dt = calloc(1, sizeof(crm_time_t)); } if (time_str) { dt->seconds = crm_time_parse_sec(time_str); offset_s = strstr(time_str, "Z"); if (offset_s == NULL) { offset_s = strstr(time_str, " "); } } if (offset_s) { while (isspace(offset_s[0])) { offset_s++; } } dt->offset = crm_time_parse_offset(offset_s); crm_time_get_sec(dt->offset, &h, &m, &s); crm_trace("Got tz: %c%2.d:%.2d", dt->offset < 0 ? '-' : '+', h, m); return dt; } crm_time_t * parse_date(const char *date_str) { char *time_s; crm_time_t *dt = NULL; int year = 0; int month = 0; int week = 0; int day = 0; int rc = 0; CRM_CHECK(date_str != NULL, return NULL); CRM_CHECK(strlen(date_str) > 0, return NULL); if (date_str[0] == 'T' || date_str[2] == ':') { /* Just a time supplied - Infer current date */ dt = crm_time_new(NULL); dt = crm_time_parse(date_str, dt); goto done; } else { dt = calloc(1, sizeof(crm_time_t)); } if (safe_str_eq("epoch", date_str)) { dt->days = 1; dt->years = 1970; crm_time_log(LOG_TRACE, "Unpacked", dt, crm_time_log_date | crm_time_log_timeofday); return dt; } /* YYYY-MM-DD */ rc = sscanf(date_str, "%d-%d-%d", &year, &month, &day); if (rc == 1) { /* YYYYMMDD */ rc = sscanf(date_str, "%4d%2d%2d", &year, &month, &day); } if (rc == 3) { if (month > 12) { crm_err("Invalid month: %d", month); } else if (day > 31) { crm_err("Invalid day: %d", day); } else { dt->years = year; dt->days = get_ordinal_days(year, month, day); crm_trace("Got gergorian date: %.4d-%.3d", year, dt->days); } goto done; } /* YYYY-DDD */ rc = sscanf(date_str, "%d-%d", &year, &day); if (rc == 2) { crm_trace("Got ordinal date"); if (day > year_days(year)) { crm_err("Invalid day: %d (max=%d)", day, year_days(year)); } else { dt->days = day; dt->years = year; } goto done; } /* YYYY-Www-D */ rc = sscanf(date_str, "%d-W%d-%d", &year, &week, &day); if (rc == 3) { crm_trace("Got week date"); if (week > crm_time_weeks_in_year(year)) { crm_err("Invalid week: %d (max=%d)", week, crm_time_weeks_in_year(year)); } else if (day < 1 || day > 7) { crm_err("Invalid day: %d", day); } else { /* * http://en.wikipedia.org/wiki/ISO_week_date * * Monday 29 December 2008 is written "2009-W01-1" * Sunday 3 January 2010 is written "2009-W53-7" * * Saturday 27 September 2008 is written "2008-W37-6" * * http://en.wikipedia.org/wiki/ISO_week_date * If 1 January is on a Monday, Tuesday, Wednesday or Thursday, it is in week 01. * If 1 January is on a Friday, Saturday or Sunday, it is in week 52 or 53 of the previous year. */ int jan1 = crm_time_january1_weekday(year); crm_trace("Jan 1 = %d", jan1); dt->years = year; crm_time_add_days(dt, (week - 1) * 7); if (jan1 <= 4) { crm_time_add_days(dt, 1 - jan1); } else { crm_time_add_days(dt, 8 - jan1); } crm_time_add_days(dt, day); } goto done; } crm_err("Couldn't parse %s", date_str); done: time_s = strstr(date_str, " "); if (time_s == NULL) { time_s = strstr(date_str, "T"); } if (dt && time_s) { time_s++; crm_time_parse(time_s, dt); } crm_time_log(LOG_TRACE, "Unpacked", dt, crm_time_log_date | crm_time_log_timeofday); CRM_CHECK(crm_time_check(dt), return NULL); return dt; } static int parse_int(const char *str, int field_width, int uppper_bound, int *result) { int lpc = 0; int offset = 0; int intermediate = 0; gboolean fraction = FALSE; gboolean negate = FALSE; CRM_CHECK(str != NULL, return FALSE); CRM_CHECK(result != NULL, return FALSE); *result = 0; if (strlen(str) <= 0) { return FALSE; } if (str[offset] == 'T') { offset++; } if (str[offset] == '.' || str[offset] == ',') { fraction = TRUE; field_width = -1; offset++; } else if (str[offset] == '-') { negate = TRUE; offset++; } else if (str[offset] == '+' || str[offset] == ':') { offset++; } for (; (fraction || lpc < field_width) && isdigit((int)str[offset]); lpc++) { if (fraction) { intermediate = (str[offset] - '0') / (10 ^ lpc); } else { *result *= 10; intermediate = str[offset] - '0'; } *result += intermediate; offset++; } if (fraction) { *result = (int)(*result * uppper_bound); } else if (uppper_bound > 0 && *result > uppper_bound) { *result = uppper_bound; } if (negate) { *result = 0 - *result; } if (lpc > 0) { crm_trace("Found int: %d. Stopped at str[%d]='%c'", *result, lpc, str[lpc]); return offset; } return 0; } crm_time_t * crm_time_parse_duration(const char *interval_str) { gboolean is_time = FALSE; crm_time_t *diff = NULL; CRM_CHECK(interval_str != NULL, goto bail); CRM_CHECK(strlen(interval_str) > 0, goto bail); CRM_CHECK(interval_str[0] == 'P', goto bail); interval_str++; diff = calloc(1, sizeof(crm_time_t)); while (isspace((int)interval_str[0]) == FALSE) { int an_int = 0, rc; char ch = 0; if (interval_str[0] == 'T') { is_time = TRUE; interval_str++; } rc = parse_int(interval_str, 10, 0, &an_int); if (rc == 0) { break; } interval_str += rc; ch = interval_str[0]; interval_str++; crm_trace("Testing %c=%d, rc=%d", ch, an_int, rc); switch (ch) { case 0: return diff; break; case 'Y': diff->years = an_int; break; case 'M': if (is_time) { /* Minutes */ diff->seconds += an_int * 60; } else { diff->months = an_int; } break; case 'W': diff->days += an_int * 7; break; case 'D': diff->days += an_int; break; case 'H': diff->seconds += an_int * 60 * 60; break; case 'S': diff->seconds += an_int; break; default: goto bail; break; } } return diff; bail: free(diff); return NULL; } crm_time_period_t * crm_time_parse_period(const char *period_str) { gboolean invalid = FALSE; const char *original = period_str; crm_time_period_t *period = NULL; CRM_CHECK(period_str != NULL, return NULL); CRM_CHECK(strlen(period_str) > 0, return NULL); tzset(); period = calloc(1, sizeof(crm_time_period_t)); if (period_str[0] == 'P') { period->diff = crm_time_parse_duration(period_str); } else { period->start = parse_date(period_str); } period_str = strstr(original, "/"); if (period_str) { CRM_CHECK(period_str[0] == '/', invalid = TRUE; goto bail); period_str++; if (period_str[0] == 'P') { period->diff = crm_time_parse_duration(period_str); } else { period->end = parse_date(period_str); } } else if (period->diff != NULL) { /* just aduration starting from now */ period->start = crm_time_new(NULL); } else { invalid = TRUE; CRM_CHECK(period_str != NULL, goto bail); } /* sanity checks */ if (period->start == NULL && period->end == NULL) { crm_err("Invalid time period: %s", original); invalid = TRUE; } else if (period->start == NULL && period->diff == NULL) { crm_err("Invalid time period: %s", original); invalid = TRUE; } else if (period->end == NULL && period->diff == NULL) { crm_err("Invalid time period: %s", original); invalid = TRUE; } bail: if (invalid) { free(period->start); free(period->end); free(period->diff); free(period); return NULL; } if (period->end == NULL && period->diff == NULL) { } if (period->start == NULL) { period->start = crm_time_subtract(period->end, period->diff); } else if (period->end == NULL) { period->end = crm_time_add(period->start, period->diff); } crm_time_check(period->start); crm_time_check(period->end); return period; } void crm_time_set(crm_time_t * target, crm_time_t * source) { crm_trace("target=%p, source=%p", target, source); CRM_CHECK(target != NULL && source != NULL, return); target->years = source->years; target->days = source->days; target->months = source->months; /* Only for durations */ target->seconds = source->seconds; target->offset = source->offset; crm_time_log(LOG_TRACE, "source", source, crm_time_log_date | crm_time_log_timeofday | crm_time_log_with_timezone); crm_time_log(LOG_TRACE, "target", target, crm_time_log_date | crm_time_log_timeofday | crm_time_log_with_timezone); } static void ha_set_tm_time(crm_time_t * target, struct tm *source) { int h_offset = 0; int m_offset = 0; /* Ensure target is fully initialized */ target->years = 0; target->months = 0; target->days = 0; target->seconds = 0; target->offset = 0; target->duration = FALSE; if (source->tm_year > 0) { /* years since 1900 */ target->years = 1900 + source->tm_year; } if (source->tm_yday >= 0) { /* days since January 1 [0-365] */ target->days = 1 + source->tm_yday; } if (source->tm_hour >= 0) { target->seconds += 60 * 60 * source->tm_hour; } if (source->tm_min >= 0) { target->seconds += 60 * source->tm_min; } if (source->tm_sec >= 0) { target->seconds += source->tm_sec; } /* tm_gmtoff == offset from UTC in seconds */ h_offset = GMTOFF(source) / (3600); m_offset = (GMTOFF(source) - (3600 * h_offset)) / (60); crm_trace("Offset (s): %ld, offset (hh:mm): %.2d:%.2d", GMTOFF(source), h_offset, m_offset); target->offset += 60 * 60 * h_offset; target->offset += 60 * m_offset; } void crm_time_set_timet(crm_time_t * target, time_t * source) { ha_set_tm_time(target, localtime(source)); } crm_time_t * crm_time_add(crm_time_t * dt, crm_time_t * value) { crm_time_t *utc = NULL; crm_time_t *answer = NULL; CRM_CHECK(dt != NULL && value != NULL, return NULL); answer = calloc(1, sizeof(crm_time_t)); crm_time_set(answer, dt); utc = crm_get_utc_time(value); answer->years += utc->years; crm_time_add_months(answer, utc->months); crm_time_add_days(answer, utc->days); crm_time_add_seconds(answer, utc->seconds); crm_time_free(utc); return answer; } crm_time_t * crm_time_calculate_duration(crm_time_t * dt, crm_time_t * value) { crm_time_t *utc = NULL; crm_time_t *answer = NULL; CRM_CHECK(dt != NULL && value != NULL, return NULL); utc = crm_get_utc_time(value); answer = crm_get_utc_time(dt); answer->duration = TRUE; answer->years -= utc->years; if(utc->months != 0) { crm_time_add_months(answer, -utc->months); } crm_time_add_days(answer, -utc->days); crm_time_add_seconds(answer, -utc->seconds); crm_time_free(utc); return answer; } crm_time_t * crm_time_subtract(crm_time_t * dt, crm_time_t * value) { crm_time_t *utc = NULL; crm_time_t *answer = NULL; CRM_CHECK(dt != NULL && value != NULL, return NULL); answer = calloc(1, sizeof(crm_time_t)); crm_time_set(answer, dt); utc = crm_get_utc_time(value); answer->years -= utc->years; if(utc->months != 0) { crm_time_add_months(answer, -utc->months); } crm_time_add_days(answer, -utc->days); crm_time_add_seconds(answer, -utc->seconds); return answer; } bool crm_time_check(crm_time_t * dt) { int ydays = 0; CRM_CHECK(dt != NULL, return FALSE); ydays = year_days(dt->years); crm_trace("max ydays: %d", ydays); CRM_CHECK(dt->days > 0, return FALSE); CRM_CHECK(dt->days <= ydays, return FALSE); CRM_CHECK(dt->seconds >= 0, return FALSE); CRM_CHECK(dt->seconds < 24 * 60 * 60, return FALSE); return TRUE; } #define do_cmp_field(l, r, field) \ if(rc == 0) { \ if(l->field > r->field) { \ crm_trace("%s: %d > %d", \ #field, l->field, r->field); \ rc = 1; \ } else if(l->field < r->field) { \ crm_trace("%s: %d < %d", \ #field, l->field, r->field); \ rc = -1; \ } \ } int crm_time_compare(crm_time_t * a, crm_time_t * b) { int rc = 0; crm_time_t *t1 = NULL; crm_time_t *t2 = NULL; if (a == NULL && b == NULL) { return 0; } else if (a == NULL) { return -1; } else if (b == NULL) { return 1; } t1 = crm_get_utc_time(a); t2 = crm_get_utc_time(b); do_cmp_field(t1, t2, years); do_cmp_field(t1, t2, days); do_cmp_field(t1, t2, seconds); crm_time_free(t1); crm_time_free(t2); return rc; } void crm_time_add_seconds(crm_time_t * a_time, int extra) { int days = 0; int seconds = 24 * 60 * 60; crm_trace("Adding %d seconds to %d (max=%d)", extra, a_time->seconds, seconds); a_time->seconds += extra; while (a_time->seconds >= seconds) { a_time->seconds -= seconds; days++; } while (a_time->seconds < 0) { a_time->seconds += seconds; days--; } crm_time_add_days(a_time, days); } void crm_time_add_days(crm_time_t * a_time, int extra) { int lower_bound = 1; int ydays = crm_time_leapyear(a_time->years) ? 366 : 365; crm_trace("Adding %d days to %.4d-%.3d", extra, a_time->years, a_time->days); a_time->days += extra; while (a_time->days > ydays) { a_time->years++; a_time->days -= ydays; ydays = crm_time_leapyear(a_time->years) ? 366 : 365; } if(a_time->duration) { lower_bound = 0; } while (a_time->days < lower_bound) { a_time->years--; a_time->days += crm_time_leapyear(a_time->years) ? 366 : 365; } } void crm_time_add_months(crm_time_t * a_time, int extra) { int lpc; uint32_t y, m, d, dmax; crm_time_get_gregorian(a_time, &y, &m, &d); crm_trace("Adding %d months to %.4d-%.2d-%.2d", extra, y, m, d); if (extra > 0) { for (lpc = extra; lpc > 0; lpc--) { m++; if (m == 13) { m = 1; y++; } } } else { for (lpc = -extra; lpc > 0; lpc--) { m--; if (m == 0) { m = 12; y--; } } } dmax = crm_time_days_in_month(m, y); if (dmax < d) { /* Preserve day-of-month unless the month doesn't have enough days */ d = dmax; } crm_trace("Calculated %.4d-%.2d-%.2d", y, m, d); a_time->years = y; a_time->days = get_ordinal_days(y, m, d); crm_time_get_gregorian(a_time, &y, &m, &d); crm_trace("Got %.4d-%.2d-%.2d", y, m, d); } void crm_time_add_minutes(crm_time_t * a_time, int extra) { crm_time_add_seconds(a_time, extra * 60); } void crm_time_add_hours(crm_time_t * a_time, int extra) { crm_time_add_seconds(a_time, extra * 60 * 60); } void crm_time_add_weeks(crm_time_t * a_time, int extra) { crm_time_add_days(a_time, extra * 7); } void crm_time_add_years(crm_time_t * a_time, int extra) { a_time->years += extra; } + +static void +ha_get_tm_time( struct tm *target, crm_time_t *source) +{ + *target = (struct tm) { + .tm_year = source->years - 1900, + .tm_yday = source->days - 1, + .tm_sec = source->seconds % 60, + .tm_min = ( source->seconds / 60 ) % 60, + .tm_hour = source->seconds / 60 / 60, + +#if defined(HAVE_STRUCT_TM_TM_GMTOFF) + .tm_gmtoff = source->offset +#endif + }; +} + +crm_time_hr_t * +crm_time_hr_convert(crm_time_hr_t *target, crm_time_t *dt) +{ + crm_time_hr_t *hr_dt = NULL; + + if (dt) { + hr_dt = target?target:calloc(1, sizeof(crm_time_hr_t)); + if (hr_dt) { + *hr_dt = (crm_time_hr_t) { + .years = dt->years, + .months = dt->months, + .days = dt->days, + .seconds = dt->seconds, + .offset = dt->offset, + .duration = dt->duration + }; + } + } + + return hr_dt; +} + +void +crm_time_set_hr_dt(crm_time_t *target, crm_time_hr_t *hr_dt) +{ + CRM_ASSERT((hr_dt) && (target)); + *target = (crm_time_t) { + .years = hr_dt->years, + .months = hr_dt->months, + .days = hr_dt->days, + .seconds = hr_dt->seconds, + .offset = hr_dt->offset, + .duration = hr_dt->duration + }; +} + +crm_time_hr_t * +crm_time_timeval_hr_convert(crm_time_hr_t *target, struct timeval *tv) +{ + crm_time_t dt; + crm_time_hr_t *ret; + + crm_time_set_timet(&dt, &tv->tv_sec); + ret = crm_time_hr_convert(target, &dt); + if (ret) { + ret->useconds = tv->tv_usec; + } + return ret; +} + +crm_time_hr_t * +crm_time_hr_new(const char *date_time) +{ + crm_time_hr_t *hr_dt = NULL; + struct timeval tv_now; + + if (!date_time) { + if (gettimeofday(&tv_now, NULL) == 0) { + hr_dt = crm_time_timeval_hr_convert(NULL, &tv_now); + } + } else { + crm_time_t *dt; + + dt = parse_date(date_time); + hr_dt = crm_time_hr_convert(NULL, dt); + crm_time_free(dt); + } + return hr_dt; +} + +void +crm_time_hr_free(crm_time_hr_t * hr_dt) +{ + free(hr_dt); +} + +char * +crm_time_format_hr(const char *format, crm_time_hr_t * hr_dt) +{ + const char *mark_s; + int max = 128, scanned_pos = 0, printed_pos = 0, fmt_pos = 0, + date_len = 0, nano_digits = 0, fmt_len; + char nano_s[10], date_s[max+1], nanofmt_s[5] = "%", *tmp_fmt_s; + struct tm tm; + crm_time_t dt; + + if (!format) { + return NULL; + } + crm_time_set_hr_dt(&dt, hr_dt); + ha_get_tm_time(&tm, &dt); + sprintf(nano_s, "%06d000", hr_dt->useconds); + + while ((format[scanned_pos]) != '\0') { + fmt_len = 0; + mark_s = strchr(&format[scanned_pos], '%'); + if (mark_s) { + fmt_pos = mark_s - format; + fmt_len = 1; + while ((format[fmt_pos+fmt_len] != '\0') && + (format[fmt_pos+fmt_len] >= '0') && + (format[fmt_pos+fmt_len] <= '9')) { + fmt_len++; + } + scanned_pos = fmt_pos + fmt_len + 1; + if (format[fmt_pos+fmt_len] == 'N') { + nano_digits = atoi(&format[fmt_pos+1]); + nano_digits = (nano_digits > 6)?6:nano_digits; + nano_digits = (nano_digits < 0)?0:nano_digits; + sprintf(&nanofmt_s[1], ".%ds", nano_digits); + } else { + if (format[scanned_pos] != '\0') { + continue; + } + fmt_pos = scanned_pos; /* print till end */ + } + } else { + scanned_pos = strlen(format); + fmt_pos = scanned_pos; /* print till end */ + } + tmp_fmt_s = strndup(&format[printed_pos], fmt_pos - printed_pos); +#ifdef GCC_FORMAT_NONLITERAL_CHECKING_ENABLED +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" +#endif + date_len += strftime(&date_s[date_len], max-date_len, tmp_fmt_s, &tm); +#ifdef GCC_FORMAT_NONLITERAL_CHECKING_ENABLED +#pragma GCC diagnostic pop +#endif + printed_pos = scanned_pos; + free(tmp_fmt_s); + if (nano_digits) { +#ifdef GCC_FORMAT_NONLITERAL_CHECKING_ENABLED +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" +#endif + date_len += snprintf(&date_s[date_len], max-date_len, + nanofmt_s, nano_s); +#ifdef GCC_FORMAT_NONLITERAL_CHECKING_ENABLED +#pragma GCC diagnostic pop +#endif + nano_digits = 0; + } + } + + return (date_len == 0)?NULL:strdup(date_s); +}