diff --git a/crmd/subsystems.c b/crmd/subsystems.c index a4d07b3b2d..5bef64751d 100644 --- a/crmd/subsystems.c +++ b/crmd/subsystems.c @@ -1,182 +1,186 @@ /* * Copyright (C) 2004 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 /* for access */ #include /* for calls to open */ #include /* for calls to open */ #include /* for calls to open */ #include /* for getpwuid */ #include /* for initgroups */ #include #include #include /* for getrlimit */ #include #include #include /* for getrlimit */ #include #include #include #include #include #include #include #include #include static void crmd_child_exit(mainloop_child_t * p, pid_t pid, int core, int signo, int exitcode) { /* struct crm_subsystem_s *the_subsystem = mainloop_child_userdata(p); */ const char *name = mainloop_child_name(p); if (signo) { crm_notice("Child process %s terminated with signal %d (pid=%d, core=%d)", name, signo, pid, core); } else { do_crm_log(exitcode == 0 ? LOG_INFO : LOG_ERR, "Child process %s exited (pid=%d, rc=%d)", name, pid, exitcode); } } gboolean stop_subsystem(struct crm_subsystem_s *the_subsystem, gboolean force_quit) { int quit_signal = SIGTERM; crm_trace("Stopping sub-system \"%s\"", the_subsystem->name); clear_bit(fsa_input_register, the_subsystem->flag_required); if (the_subsystem->pid <= 0) { crm_trace("Client %s not running", the_subsystem->name); return FALSE; } if (is_set(fsa_input_register, the_subsystem->flag_connected) == FALSE) { /* running but not yet connected */ crm_debug("Stopping %s before it had connected", the_subsystem->name); } /* if(force_quit && the_subsystem->sent_kill == FALSE) { quit_signal = SIGKILL; } else if(force_quit) { crm_debug("Already sent -KILL to %s: [%d]", the_subsystem->name, the_subsystem->pid); } */ errno = 0; if (kill(the_subsystem->pid, quit_signal) == 0) { crm_info("Sent -TERM to %s: [%d]", the_subsystem->name, the_subsystem->pid); the_subsystem->sent_kill = TRUE; } else { crm_perror(LOG_ERR, "Sent -TERM to %s: [%d]", the_subsystem->name, the_subsystem->pid); } return TRUE; } gboolean start_subsystem(struct crm_subsystem_s * the_subsystem) { pid_t pid; struct stat buf; int s_res; unsigned int j; struct rlimit oflimits; const char *devnull = "/dev/null"; crm_info("Starting sub-system \"%s\"", the_subsystem->name); if (the_subsystem->pid > 0) { crm_warn("Client %s already running as pid %d", the_subsystem->name, (int)the_subsystem->pid); /* starting a started X is not an error */ return TRUE; } /* * We want to ensure that the exec will succeed before * we bother forking. */ if (access(the_subsystem->path, F_OK | X_OK) != 0) { crm_perror(LOG_ERR, "Cannot (access) exec %s", the_subsystem->path); return FALSE; } s_res = stat(the_subsystem->command, &buf); if (s_res != 0) { crm_perror(LOG_ERR, "Cannot (stat) exec %s", the_subsystem->command); return FALSE; } /* We need to fork so we can make child procs not real time */ switch (pid = fork()) { case -1: crm_err("Cannot fork."); return FALSE; default: /* Parent */ mainloop_child_add(pid, 0, the_subsystem->name, the_subsystem, crmd_child_exit); crm_trace("Client %s is has pid: %d", the_subsystem->name, pid); the_subsystem->pid = pid; return TRUE; case 0: /* Child */ /* create a new process group to avoid * being interupted by heartbeat */ setpgid(0, 0); break; } crm_debug("Executing \"%s (%s)\" (pid %d)", the_subsystem->command, the_subsystem->name, (int)getpid()); /* A precautionary measure */ getrlimit(RLIMIT_NOFILE, &oflimits); for (j = 0; j < oflimits.rlim_cur; ++j) { close(j); } (void)open(devnull, O_RDONLY); /* Stdin: fd 0 */ (void)open(devnull, O_WRONLY); /* Stdout: fd 1 */ (void)open(devnull, O_WRONLY); /* Stderr: fd 2 */ { - char *opts[] = { strdup(the_subsystem->command), NULL }; + char *opts[2]; + + opts[0] = strdup(the_subsystem->command); + opts[1] = NULL; + /* coverity[toctou] The call to stat() is a fail-fast, not a race */ (void)execvp(the_subsystem->command, opts); } /* Should not happen */ crm_perror(LOG_ERR, "FATAL: Cannot exec %s", the_subsystem->command); return crm_exit(DAEMON_RESPAWN_STOP); /* Suppress respawning */ } diff --git a/crmd/throttle.c b/crmd/throttle.c index 6e2e09754b..bb873e0c58 100644 --- a/crmd/throttle.c +++ b/crmd/throttle.c @@ -1,715 +1,715 @@ /* * Copyright (C) 2013 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 #include #include #include #include #include #include enum throttle_state_e { throttle_extreme = 0x1000, throttle_high = 0x0100, throttle_med = 0x0010, throttle_low = 0x0001, throttle_none = 0x0000, }; struct throttle_record_s { int max; enum throttle_state_e mode; char *node; }; int throttle_job_max = 0; float throttle_load_target = 0.0; #define THROTTLE_FACTOR_LOW 1.2 #define THROTTLE_FACTOR_MEDIUM 1.6 #define THROTTLE_FACTOR_HIGH 2.0 GHashTable *throttle_records = NULL; mainloop_timer_t *throttle_timer = NULL; int throttle_num_cores(void) { static int cores = 0; char buffer[256]; FILE *stream = NULL; const char *cpufile = "/proc/cpuinfo"; if(cores) { return cores; } stream = fopen(cpufile, "r"); if(stream == NULL) { int rc = errno; crm_warn("Couldn't read %s, assuming a single processor: %s (%d)", cpufile, pcmk_strerror(rc), rc); return 1; } while (fgets(buffer, sizeof(buffer), stream)) { if(strstr(buffer, "processor") == buffer) { cores++; } } fclose(stream); if(cores == 0) { crm_warn("No processors found in %s, assuming 1", cpufile); return 1; } return cores; } static char *find_cib_loadfile(void) { DIR *dp; struct dirent *entry; struct stat statbuf; char *match = NULL; dp = opendir("/proc"); if (!dp) { /* no proc directory to search through */ crm_notice("Can not read /proc directory to track existing components"); return FALSE; } while ((entry = readdir(dp)) != NULL) { char procpath[128]; char value[64]; char key[16]; FILE *file; int pid; strcpy(procpath, "/proc/"); /* strlen("/proc/") + strlen("/status") + 1 = 14 * 128 - 14 = 114 */ strncat(procpath, entry->d_name, 114); if (lstat(procpath, &statbuf)) { continue; } if (!S_ISDIR(statbuf.st_mode) || !isdigit(entry->d_name[0])) { continue; } strcat(procpath, "/status"); file = fopen(procpath, "r"); if (!file) { continue; } if (fscanf(file, "%15s%63s", key, value) != 2) { fclose(file); continue; } fclose(file); if (safe_str_neq("cib", value)) { continue; } pid = atoi(entry->d_name); if (pid <= 0) { continue; } match = g_strdup_printf("/proc/%d/stat", pid); break; } closedir(dp); return match; } static bool throttle_cib_load(float *load) { /* /proc/[pid]/stat Status information about the process. This is used by ps(1). It is defined in /usr/src/linux/fs/proc/array.c. The fields, in order, with their proper scanf(3) format specifiers, are: pid %d (1) The process ID. comm %s (2) The filename of the executable, in parentheses. This is visible whether or not the executable is swapped out. state %c (3) One character from the string "RSDZTW" where R is running, S is sleeping in an interruptible wait, D is waiting in uninterruptible disk sleep, Z is zombie, T is traced or stopped (on a signal), and W is paging. ppid %d (4) The PID of the parent. pgrp %d (5) The process group ID of the process. session %d (6) The session ID of the process. tty_nr %d (7) The controlling terminal of the process. (The minor device number is contained in the combination of bits 31 to 20 and 7 to 0; the major device number is in bits 15 to 8.) tpgid %d (8) The ID of the foreground process group of the controlling terminal of the process. flags %u (%lu before Linux 2.6.22) (9) The kernel flags word of the process. For bit meanings, see the PF_* defines in the Linux kernel source file include/linux/sched.h. Details depend on the kernel version. minflt %lu (10) The number of minor faults the process has made which have not required loading a memory page from disk. cminflt %lu (11) The number of minor faults that the process's waited-for children have made. majflt %lu (12) The number of major faults the process has made which have required loading a memory page from disk. cmajflt %lu (13) The number of major faults that the process's waited-for children have made. utime %lu (14) Amount of time that this process has been scheduled in user mode, measured in clock ticks (divide by sysconf(_SC_CLK_TCK)). This includes guest time, guest_time (time spent running a virtual CPU, see below), so that applications that are not aware of the guest time field do not lose that time from their calculations. stime %lu (15) Amount of time that this process has been scheduled in kernel mode, measured in clock ticks (divide by sysconf(_SC_CLK_TCK)). */ static char *loadfile = NULL; static time_t last_call = 0; static long ticks_per_s = 0; static unsigned long last_utime, last_stime; char buffer[64*1024]; FILE *stream = NULL; time_t now = time(NULL); if(load == NULL) { return FALSE; } else { *load = 0.0; } if(loadfile == NULL) { last_call = 0; last_utime = 0; last_stime = 0; loadfile = find_cib_loadfile(); ticks_per_s = sysconf(_SC_CLK_TCK); crm_trace("Found %s", loadfile); } stream = fopen(loadfile, "r"); if(stream == NULL) { int rc = errno; crm_warn("Couldn't read %s: %s (%d)", loadfile, pcmk_strerror(rc), rc); free(loadfile); loadfile = NULL; return FALSE; } if(fgets(buffer, sizeof(buffer), stream)) { char *comm = calloc(1, 256); char state = 0; int rc = 0, pid = 0, ppid = 0, pgrp = 0, session = 0, tty_nr = 0, tpgid = 0; unsigned long flags = 0, minflt = 0, cminflt = 0, majflt = 0, cmajflt = 0, utime = 0, stime = 0; rc = sscanf(buffer, "%d %[^ ] %c %d %d %d %d %d %lu %lu %lu %lu %lu %lu %lu", &pid, comm, &state, &ppid, &pgrp, &session, &tty_nr, &tpgid, &flags, &minflt, &cminflt, &majflt, &cmajflt, &utime, &stime); free(comm); if(rc != 15) { crm_err("Only %d of 15 fields found in %s", rc, loadfile); fclose(stream); return FALSE; } else if(last_call > 0 && last_call < now && last_utime <= utime && last_stime <= stime) { time_t elapsed = now - last_call; unsigned long delta_utime = utime - last_utime; unsigned long delta_stime = stime - last_stime; *load = (delta_utime + delta_stime); /* Cast to a float before division */ *load /= ticks_per_s; *load /= elapsed; crm_debug("cib load: %f (%lu ticks in %ds)", *load, delta_utime + delta_stime, elapsed); } else { crm_debug("Init %lu + %lu ticks at %d (%lu tps)", utime, stime, now, ticks_per_s); } last_call = now; last_utime = utime; last_stime = stime; fclose(stream); return TRUE; } fclose(stream); return FALSE; } static bool throttle_load_avg(float *load) { char buffer[256]; FILE *stream = NULL; const char *loadfile = "/proc/loadavg"; if(load == NULL) { return FALSE; } stream = fopen(loadfile, "r"); if(stream == NULL) { int rc = errno; crm_warn("Couldn't read %s: %s (%d)", loadfile, pcmk_strerror(rc), rc); return FALSE; } if(fgets(buffer, sizeof(buffer), stream)) { char *nl = strstr(buffer, "\n"); /* Grab the 1-minute average, ignore the rest */ *load = strtof(buffer, NULL); if(nl) { nl[0] = 0; } crm_debug("Current load is %f (full: %s)", *load, buffer); fclose(stream); return TRUE; } fclose(stream); return FALSE; } static bool throttle_io_load(float *load, unsigned int *blocked) { char buffer[64*1024]; FILE *stream = NULL; const char *loadfile = "/proc/stat"; if(load == NULL) { return FALSE; } stream = fopen(loadfile, "r"); if(stream == NULL) { int rc = errno; crm_warn("Couldn't read %s: %s (%d)", loadfile, pcmk_strerror(rc), rc); return FALSE; } if(fgets(buffer, sizeof(buffer), stream)) { /* Borrowed from procps-ng's sysinfo.c */ char *b = NULL; - long long cpu_use = 0; - long long cpu_nic = 0; - long long cpu_sys = 0; - long long cpu_idl = 0; - long long cpu_iow = 0; /* not separated out until the 2.5.41 kernel */ - long long cpu_xxx = 0; /* not separated out until the 2.6.0-test4 kernel */ - long long cpu_yyy = 0; /* not separated out until the 2.6.0-test4 kernel */ - long long cpu_zzz = 0; /* not separated out until the 2.6.11 kernel */ + unsigned long long cpu_use = 0; + unsigned long long cpu_nic = 0; + unsigned long long cpu_sys = 0; + unsigned long long cpu_idl = 0; + unsigned long long cpu_iow = 0; /* not separated out until the 2.5.41 kernel */ + unsigned long long cpu_xxx = 0; /* not separated out until the 2.6.0-test4 kernel */ + unsigned long long cpu_yyy = 0; /* not separated out until the 2.6.0-test4 kernel */ + unsigned long long cpu_zzz = 0; /* not separated out until the 2.6.11 kernel */ long long divo2 = 0; long long duse = 0; long long dsys = 0; long long didl =0; long long diow =0; long long dstl = 0; long long Div = 0; b = strstr(buffer, "cpu "); if(b) sscanf(b, "cpu %Lu %Lu %Lu %Lu %Lu %Lu %Lu %Lu", &cpu_use, &cpu_nic, &cpu_sys, &cpu_idl, &cpu_iow, &cpu_xxx, &cpu_yyy, &cpu_zzz); if(blocked) { b = strstr(buffer, "procs_blocked "); if(b) sscanf(b, "procs_blocked %u", blocked); } duse = cpu_use + cpu_nic; dsys = cpu_sys + cpu_xxx + cpu_yyy; didl = cpu_idl; diow = cpu_iow; dstl = cpu_zzz; Div = duse + dsys + didl + diow + dstl; if (!Div) Div = 1, didl = 1; divo2 = Div / 2UL; /* vmstat output: * * procs -----------memory---------- ---swap-- -----io---- -system-- ----cpu---- * r b swpd free buff cache si so bi bo in cs us sy id wa * 1 0 5537800 958592 204180 1737740 1 1 12 15 0 0 2 1 97 0 * * The last four columns are calculated as: * * (unsigned)( (100*duse + divo2) / Div ), * (unsigned)( (100*dsys + divo2) / Div ), * (unsigned)( (100*didl + divo2) / Div ), * (unsigned)( (100*diow + divo2) / Div ) * */ *load = (diow + divo2) / Div; crm_debug("Current IO load is %f", *load); fclose(stream); return TRUE; } fclose(stream); return FALSE; } static enum throttle_state_e throttle_handle_load(float load, const char *desc, int cores) { float adjusted_load = load; if(cores <= 0) { /* No fudging of the supplied load value */ } else if(cores == 1) { /* On a single core machine, a load of 1.0 is already too high */ adjusted_load = load * THROTTLE_FACTOR_MEDIUM; } else { /* Normalize the load to be per-core */ adjusted_load = load / cores; } if(adjusted_load > THROTTLE_FACTOR_HIGH * throttle_load_target) { crm_notice("High %s detected: %f", desc, load); return throttle_high; } else if(adjusted_load > THROTTLE_FACTOR_MEDIUM * throttle_load_target) { crm_info("Moderate %s detected: %f", desc, load); return throttle_med; } else if(adjusted_load > THROTTLE_FACTOR_LOW * throttle_load_target) { crm_debug("Noticable %s detected: %f", desc, load); return throttle_low; } crm_trace("Negligable %s detected: %f", desc, adjusted_load); return throttle_none; } static enum throttle_state_e throttle_mode(void) { float load; unsigned int blocked = 0; int cores = throttle_num_cores(); enum throttle_state_e mode = throttle_none; if(throttle_cib_load(&load)) { float cib_max_cpu = 0.95; const char *desc = "CIB load"; /* The CIB is a single threaded task and thus cannot consume * more than 100% of a CPU (and 1/cores of the overall system * load). * * On a many cored system, the CIB might therefor be maxed out * (causing operations to fail or appear to fail) even though * the overall system load is still reasonable. * * Therefor the 'normal' thresholds can not apply here and we * need a special case. */ if(cores == 1) { cib_max_cpu = 0.4; } if(throttle_load_target > 0.0 && throttle_load_target < cib_max_cpu) { cib_max_cpu = throttle_load_target; } if(load > 1.5 * cib_max_cpu) { /* Can only happen on machines with a low number of cores */ crm_notice("Extreme %s detected: %f", desc, load); mode |= throttle_extreme; } else if(load > cib_max_cpu) { crm_notice("High %s detected: %f", desc, load); mode |= throttle_high; } else if(load > cib_max_cpu * 0.9) { crm_info("Moderate %s detected: %f", desc, load); mode |= throttle_med; } else if(load > cib_max_cpu * 0.8) { crm_debug("Noticable %s detected: %f", desc, load); mode |= throttle_low; } else { crm_trace("Negligable %s detected: %f", desc, load); } } if(throttle_load_target <= 0) { /* If we ever make this a valid value, the cluster will at least behave as expected */ return mode; } if(throttle_load_avg(&load)) { mode |= throttle_handle_load(load, "CPU load", cores); } if(throttle_io_load(&load, &blocked)) { mode |= throttle_handle_load(load, "IO load", 0); mode |= throttle_handle_load(blocked, "blocked IO ratio", cores); } if(mode & throttle_extreme) { return throttle_extreme; } else if(mode & throttle_high) { return throttle_high; } else if(mode & throttle_med) { return throttle_med; } else if(mode & throttle_low) { return throttle_low; } return throttle_none; } static void throttle_send_command(enum throttle_state_e mode) { xmlNode *xml = NULL; xml = create_request(CRM_OP_THROTTLE, NULL, NULL, CRM_SYSTEM_CRMD, CRM_SYSTEM_CRMD, NULL); crm_xml_add_int(xml, F_CRM_THROTTLE_MODE, mode); crm_xml_add_int(xml, F_CRM_THROTTLE_MAX, throttle_job_max); send_cluster_message(NULL, crm_msg_crmd, xml, TRUE); free_xml(xml); crm_info("Updated throttle state to %.4x", mode); } static gboolean throttle_timer_cb(gpointer data) { static bool send_updates = FALSE; static enum throttle_state_e last = -1; enum throttle_state_e now = throttle_none; if(send_updates == FALSE) { /* Optimize for the true case */ if(compare_version(fsa_our_dc_version, "3.0.8") < 0) { crm_trace("DC version %s doesn't support throttling", fsa_our_dc_version); } else { send_updates = TRUE; } } if(send_updates) { now = throttle_mode(); } if(send_updates && now != last) { crm_debug("New throttle mode: %.4x (was %.4x)", now, last); throttle_send_command(now); last = now; } return TRUE; } static void throttle_record_free(gpointer p) { struct throttle_record_s *r = p; free(r->node); free(r); } void throttle_update_job_max(const char *preference) { int max = 0; throttle_job_max = 2 * throttle_num_cores(); if(preference) { /* Global preference from the CIB */ max = crm_int_helper(preference, NULL); if(max > 0) { throttle_job_max = max; } } preference = getenv("LRMD_MAX_CHILDREN"); if(preference) { /* Legacy env variable */ max = crm_int_helper(preference, NULL); if(max > 0) { throttle_job_max = max; } } preference = getenv("PCMK_node_action_limit"); if(preference) { /* Per-node override */ max = crm_int_helper(preference, NULL); if(max > 0) { throttle_job_max = max; } } } void throttle_init(void) { throttle_records = g_hash_table_new_full( crm_str_hash, g_str_equal, NULL, throttle_record_free); throttle_timer = mainloop_timer_add("throttle", 30* 1000, TRUE, throttle_timer_cb, NULL); throttle_update_job_max(NULL); mainloop_timer_start(throttle_timer); } void throttle_fini(void) { mainloop_timer_del(throttle_timer); throttle_timer = NULL; g_hash_table_destroy(throttle_records); throttle_records = NULL; } int throttle_get_total_job_limit(int l) { /* Cluster-wide limit */ GHashTableIter iter; int limit = l; int peers = crm_active_peers(); struct throttle_record_s *r = NULL; g_hash_table_iter_init(&iter, throttle_records); while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &r)) { switch(r->mode) { case throttle_extreme: if(limit == 0 || limit > peers/4) { limit = QB_MAX(1, peers/4); } break; case throttle_high: if(limit == 0 || limit > peers/2) { limit = QB_MAX(1, peers/2); } break; default: break; } } if(limit == l) { /* crm_trace("No change to batch-limit=%d", limit); */ } else if(l == 0) { crm_trace("Using batch-limit=%d", limit); } else { crm_trace("Using batch-limit=%d instead of %d", limit, l); } return limit; } int throttle_get_job_limit(const char *node) { int jobs = 1; struct throttle_record_s *r = NULL; r = g_hash_table_lookup(throttle_records, node); if(r == NULL) { r = calloc(1, sizeof(struct throttle_record_s)); r->node = strdup(node); r->mode = throttle_low; r->max = throttle_job_max; crm_trace("Defaulting to local values for unknown node %s", node); g_hash_table_insert(throttle_records, r->node, r); } switch(r->mode) { case throttle_extreme: case throttle_high: jobs = 1; /* At least one job must always be allowed */ break; case throttle_med: jobs = QB_MAX(1, r->max / 4); break; case throttle_low: jobs = QB_MAX(1, r->max / 2); break; case throttle_none: jobs = QB_MAX(1, r->max); break; default: crm_err("Unknown throttle mode %.4x on %s", r->mode, node); break; } return jobs; } void throttle_update(xmlNode *xml) { int max = 0; enum throttle_state_e mode = 0; struct throttle_record_s *r = NULL; const char *from = crm_element_value(xml, F_CRM_HOST_FROM); crm_element_value_int(xml, F_CRM_THROTTLE_MODE, (int*)&mode); crm_element_value_int(xml, F_CRM_THROTTLE_MAX, &max); r = g_hash_table_lookup(throttle_records, from); if(r == NULL) { r = calloc(1, sizeof(struct throttle_record_s)); r->node = strdup(from); g_hash_table_insert(throttle_records, r->node, r); } r->max = max; r->mode = mode; crm_debug("Host %s supports a maximum of %d jobs and throttle mode %.4x. New job limit is %d", from, max, mode, throttle_get_job_limit(from)); } diff --git a/lib/common/iso8601.c b/lib/common/iso8601.c index 831400791f..769e01b667 100644 --- a/lib/common/iso8601.c +++ b/lib/common/iso8601.c @@ -1,1275 +1,1276 @@ /* * 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 /* * 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, offset=%d", 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; 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 = 0; 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; }