diff --git a/tools/Makefile.am b/tools/Makefile.am index b40d5345e7..4de72a46c7 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -1,177 +1,178 @@ # # Copyright 2004-2022 the Pacemaker project contributors # # The version control history for this file may have further details. # # This source code is licensed under the GNU General Public License version 2 # or later (GPLv2+) WITHOUT ANY WARRANTY. # include $(top_srcdir)/mk/common.mk include $(top_srcdir)/mk/man.mk if BUILD_SYSTEMD systemdsystemunit_DATA = crm_mon.service endif noinst_HEADERS = crm_mon.h crm_resource.h pcmkdir = $(datadir)/$(PACKAGE) pcmk_DATA = report.common report.collector sbin_SCRIPTS = crm_report crm_standby crm_master crm_failcount if BUILD_CIBSECRETS sbin_SCRIPTS += cibsecret endif noinst_SCRIPTS = pcmk_simtimes EXTRA_DIST = attrd_updater.8.inc \ crm_attribute.8.inc \ crm_diff.8.inc \ crm_error.8.inc \ crm_mon.8.inc \ crm_node.8.inc \ crm_resource.8.inc \ crm_rule.8.inc \ crm_simulate.8.inc \ crm_ticket.8.inc \ crm_verify.8.inc \ crmadmin.8.inc \ fix-manpages \ + iso8601.8.inc \ stonith_admin.8.inc sbin_PROGRAMS = attrd_updater \ cibadmin \ crmadmin \ crm_simulate \ crm_attribute \ crm_diff \ crm_error \ crm_mon \ crm_node \ crm_resource \ crm_rule \ crm_shadow \ crm_verify \ crm_ticket \ iso8601 \ stonith_admin if BUILD_SERVICELOG sbin_PROGRAMS += notifyServicelogEvent endif if BUILD_OPENIPMI_SERVICELOG sbin_PROGRAMS += ipmiservicelogd endif ## SOURCES # A few tools are just thin wrappers around crm_attribute. # This makes their help get updated when crm_attribute changes # (see mk/common.mk). MAN8DEPS = crm_attribute crmadmin_SOURCES = crmadmin.c crmadmin_LDADD = $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la \ $(top_builddir)/lib/pacemaker/libpacemaker.la crm_error_SOURCES = crm_error.c crm_error_LDADD = $(top_builddir)/lib/common/libcrmcommon.la cibadmin_SOURCES = cibadmin.c cibadmin_LDADD = $(top_builddir)/lib/pacemaker/libpacemaker.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_shadow_SOURCES = crm_shadow.c crm_shadow_LDADD = $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_node_SOURCES = crm_node.c crm_node_LDADD = $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_simulate_SOURCES = crm_simulate.c crm_simulate_LDADD = $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/pacemaker/libpacemaker.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_diff_SOURCES = crm_diff.c crm_diff_LDADD = $(top_builddir)/lib/common/libcrmcommon.la crm_mon_SOURCES = crm_mon.c crm_mon_curses.c crm_mon_LDADD = $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/fencing/libstonithd.la \ $(top_builddir)/lib/pacemaker/libpacemaker.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la \ $(CURSESLIBS) crm_verify_SOURCES = crm_verify.c crm_verify_LDADD = $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/pacemaker/libpacemaker.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_attribute_SOURCES = crm_attribute.c crm_attribute_LDADD = $(top_builddir)/lib/cluster/libcrmcluster.la \ $(top_builddir)/lib/pacemaker/libpacemaker.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_resource_SOURCES = crm_resource.c \ crm_resource_ban.c \ crm_resource_print.c \ crm_resource_runtime.c crm_resource_LDADD = $(top_builddir)/lib/pengine/libpe_rules.la \ $(top_builddir)/lib/fencing/libstonithd.la \ $(top_builddir)/lib/lrmd/liblrmd.la \ $(top_builddir)/lib/services/libcrmservice.la \ $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/pacemaker/libpacemaker.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_rule_SOURCES = crm_rule.c crm_rule_LDADD = $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/pengine/libpe_rules.la \ $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/common/libcrmcommon.la iso8601_SOURCES = iso8601.c iso8601_LDADD = $(top_builddir)/lib/common/libcrmcommon.la attrd_updater_SOURCES = attrd_updater.c attrd_updater_LDADD = $(top_builddir)/lib/pacemaker/libpacemaker.la \ $(top_builddir)/lib/common/libcrmcommon.la crm_ticket_SOURCES = crm_ticket.c crm_ticket_LDADD = $(top_builddir)/lib/pengine/libpe_rules.la \ $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/pacemaker/libpacemaker.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/common/libcrmcommon.la stonith_admin_SOURCES = stonith_admin.c stonith_admin_LDADD = $(top_builddir)/lib/pacemaker/libpacemaker.la \ $(top_builddir)/lib/cib/libcib.la \ $(top_builddir)/lib/pengine/libpe_status.la \ $(top_builddir)/lib/fencing/libstonithd.la \ $(top_builddir)/lib/common/libcrmcommon.la if BUILD_SERVICELOG notifyServicelogEvent_SOURCES = notifyServicelogEvent.c notifyServicelogEvent_CFLAGS = $(SERVICELOG_CFLAGS) notifyServicelogEvent_LDADD = $(top_builddir)/lib/common/libcrmcommon.la $(SERVICELOG_LIBS) endif if BUILD_OPENIPMI_SERVICELOG ipmiservicelogd_SOURCES = ipmiservicelogd.c ipmiservicelogd_CFLAGS = $(OPENIPMI_SERVICELOG_CFLAGS) $(SERVICELOG_CFLAGS) ipmiservicelogd_LDFLAGS = $(top_builddir)/lib/common/libcrmcommon.la $(OPENIPMI_SERVICELOG_LIBS) $(SERVICELOG_LIBS) endif CLEANFILES = $(man8_MANS) diff --git a/tools/iso8601.8.inc b/tools/iso8601.8.inc new file mode 100644 index 0000000000..faa7f7655c --- /dev/null +++ b/tools/iso8601.8.inc @@ -0,0 +1,5 @@ +[synopsis] +iso8601 [options] + +/dates and times/ +.SH OPTIONS diff --git a/tools/iso8601.c b/tools/iso8601.c index 717ac51b2c..c3983232bc 100644 --- a/tools/iso8601.c +++ b/tools/iso8601.c @@ -1,291 +1,282 @@ /* * Copyright 2005-2022 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ #include #include +#include #include #include /* CRM_ASSERT */ #include +#define SUMMARY "Display and parse ISO 8601 dates and times" + struct { - const char *date_time_s; - const char *duration_s; - const char *expected_s; - const char *period_s; + char *date_time_s; + gchar *duration_s; + gchar *expected_s; + gchar *period_s; int print_options; } options; -static pcmk__cli_option_t long_options[] = { - // long option, argument type, storage, short option, description, flags - { - "help", no_argument, NULL, '?', - "\tThis text", pcmk__option_default - }, - { - "version", no_argument, NULL, '$', - "\tVersion information", pcmk__option_default - }, - { - "verbose", no_argument, NULL, 'V', - "\tIncrease debug output", pcmk__option_default - }, - { - "-spacer-", no_argument, NULL, '-', - "\nCommands:", pcmk__option_default - }, - { - "now", no_argument, NULL, 'n', - "\tDisplay the current date/time", pcmk__option_default - }, - { - "date", required_argument, NULL, 'd', - "Parse an ISO 8601 date/time (for example, " - "'2019-09-24 00:30:00 +01:00' or '2019-040')", - pcmk__option_default - }, - { - "period", required_argument, NULL, 'p', - "Parse an ISO 8601 period (interval) with start time (for example, " - "'2005-040/2005-043')", - pcmk__option_default - }, - { - "duration", required_argument, NULL, 'D', - "Parse an ISO 8601 duration (for example, 'P1M')", pcmk__option_default - }, - { - "expected", required_argument, NULL, 'E', - "Exit with error status if result does not match this text. " - "Requires: -n or -d", - pcmk__option_default - }, - { - "-spacer-", no_argument, NULL, '-', - "\nOutput Modifiers:", pcmk__option_default - }, - { - "seconds", no_argument, NULL, 's', - "\tShow result as a seconds since 0000-001 00:00:00Z", - pcmk__option_default - }, - { - "epoch", no_argument, NULL, 'S', - "\tShow result as a seconds since EPOCH (1970-001 00:00:00Z)", - pcmk__option_default - }, - { - "local", no_argument, NULL, 'L', - "\tShow result as a 'local' date/time", pcmk__option_default - }, - { - "ordinal", no_argument, NULL, 'O', - "\tShow result as an 'ordinal' date/time", pcmk__option_default - }, - { - "week", no_argument, NULL, 'W', - "\tShow result as an 'calendar week' date/time", pcmk__option_default - }, - { - "-spacer-", no_argument, NULL, '-', - "\nFor more information on the ISO 8601 standard, see " - "https://en.wikipedia.org/wiki/ISO_8601", - pcmk__option_default - }, - { 0, 0, 0, 0 } +#define INDENT " " + +static gboolean +date_now_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { + if (pcmk__str_any_of(option_name, "--now", "-n", NULL)) { + pcmk__str_update(&options.date_time_s, "now"); + } else if (pcmk__str_any_of(option_name, "--date", "-d", NULL)) { + pcmk__str_update(&options.date_time_s, optarg); + } + + return TRUE; +} + +static gboolean +modifier_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { + if (pcmk__str_any_of(option_name, "--seconds", "-s", NULL)) { + options.print_options |= crm_time_seconds; + } else if (pcmk__str_any_of(option_name, "--epoch", "-S", NULL)) { + options.print_options |= crm_time_epoch; + } else if (pcmk__str_any_of(option_name, "--local", "-L", NULL)) { + options.print_options |= crm_time_log_with_timezone; + } else if (pcmk__str_any_of(option_name, "--ordinal", "-O", NULL)) { + options.print_options |= crm_time_ordinal; + } else if (pcmk__str_any_of(option_name, "--week", "-W", NULL)) { + options.print_options |= crm_time_weeks; + } + + return TRUE; +} + +static GOptionEntry command_entries[] = { + { "now", 'n', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, date_now_cb, + "Display the current date/time", + NULL }, + + { "date", 'd', 0, G_OPTION_ARG_CALLBACK, date_now_cb, + "Parse an ISO 8601 date/time (for example,\n" + INDENT "'2019-09-24 00:30:00 +01:00' or '2019-040')", + "DATE" }, + + { "period", 'p', 0, G_OPTION_ARG_STRING, &options.period_s, + "Parse an ISO 8601 period (interval) with start time (for example,\n" + INDENT "'2005-040/2005-043')", + "PERIOD" }, + + { "duration", 'D', 0, G_OPTION_ARG_STRING, &options.duration_s, + "Parse an ISO 8601 duration (for example, 'P1M')", + "DURATION" }, + + { "expected", 'E', 0, G_OPTION_ARG_STRING, &options.expected_s, + "Exit with error status if result does not match this text.\n" + INDENT "Requires: -n or -d", + "TEXT" }, + + { NULL } +}; + +static GOptionEntry modifier_entries[] = { + { "seconds", 's', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, modifier_cb, + "Show result as a seconds since 0000-001 00:00:00Z", + NULL }, + + { "epoch", 'S', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, modifier_cb, + "Show result as a seconds since EPOCH (1970-001 00:00:00Z)", + NULL }, + + { "local", 'L', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, modifier_cb, + "Show result as a 'local' date/time", + NULL }, + + { "ordinal", 'O', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, modifier_cb, + "Show result as an 'ordinal' date/time", + NULL }, + + { "week", 'W', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, modifier_cb, + "Show result as an 'calendar week' date/time", + NULL }, + + { NULL } }; static void log_time_period(int log_level, crm_time_period_t * dtp, int flags) { char *start = crm_time_as_string(dtp->start, flags); char *end = crm_time_as_string(dtp->end, flags); CRM_ASSERT(start != NULL && end != NULL); do_crm_log(log_level, "Period: %s to %s", start, end); free(start); free(end); } +static GOptionContext * +build_arg_context(pcmk__common_args_t *args) { + GOptionContext *context = NULL; + + const char *description = "For more information on the ISO 8601 standard, see " \ + "https://en.wikipedia.org/wiki/ISO_8601"; + + context = pcmk__build_arg_context(args, NULL, NULL, NULL); + g_option_context_set_description(context, description); + + pcmk__add_arg_group(context, "commands", "Commands:", + "Show command options", command_entries); + pcmk__add_arg_group(context, "modifiers", "Output modifiers:", + "Show output modifiers", modifier_entries); + + return context; +} + int main(int argc, char **argv) { crm_exit_t exit_code = CRM_EX_OK; - int argerr = 0; - int flag; - int index = 0; crm_time_t *duration = NULL; crm_time_t *date_time = NULL; GError *error = NULL; - pcmk__cli_init_logging("iso8601", 0); - pcmk__set_cli_options(NULL, " [options] ", long_options, - "display and parse ISO 8601 dates and times"); + pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY); + GOptionContext *context = build_arg_context(args); + gchar **processed_args = pcmk__cmdline_preproc(argv, "dpDE"); - if (argc < 2) { - argerr++; + if (!g_option_context_parse_strv(context, &processed_args, &error)) { + exit_code = CRM_EX_USAGE; + goto done; } - while (1) { - flag = pcmk__next_cli_option(argc, argv, &index, NULL); - if (flag == -1) - break; - - switch (flag) { - case 'V': - crm_bump_log_level(argc, argv); - break; - case '?': - case '$': - pcmk__cli_help(flag, CRM_EX_OK); - break; - case 'n': - options.date_time_s = "now"; - break; - case 'd': - options.date_time_s = optarg; - break; - case 'p': - options.period_s = optarg; - break; - case 'D': - options.duration_s = optarg; - break; - case 'E': - options.expected_s = optarg; - break; - case 'S': - options.print_options |= crm_time_epoch; - break; - case 's': - options.print_options |= crm_time_seconds; - break; - case 'W': - options.print_options |= crm_time_weeks; - break; - case 'O': - options.print_options |= crm_time_ordinal; - break; - case 'L': - options.print_options |= crm_time_log_with_timezone; - break; - break; - } + pcmk__cli_init_logging("iso8601", args->verbosity); + + if (args->version) { + g_strfreev(processed_args); + pcmk__free_arg_context(context); + /* FIXME: When iso8601 is converted to use formatted output, this can go. */ + pcmk__cli_help('v', CRM_EX_OK); } if (pcmk__str_eq("now", options.date_time_s, pcmk__str_casei)) { date_time = crm_time_new(NULL); if (date_time == NULL) { exit_code = CRM_EX_SOFTWARE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Internal error: couldn't determine 'now'!"); goto done; } crm_time_log(LOG_TRACE, "Current date/time", date_time, crm_time_ordinal | crm_time_log_date | crm_time_log_timeofday); crm_time_log(LOG_STDOUT, "Current date/time", date_time, options.print_options | crm_time_log_date | crm_time_log_timeofday); } else if (options.date_time_s) { date_time = crm_time_new(options.date_time_s); if (date_time == NULL) { exit_code = CRM_EX_INVALID_PARAM; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Invalid date/time specified: %s", options.date_time_s); goto done; } crm_time_log(LOG_TRACE, "Date", date_time, crm_time_ordinal | crm_time_log_date | crm_time_log_timeofday); crm_time_log(LOG_STDOUT, "Date", date_time, options.print_options | crm_time_log_date | crm_time_log_timeofday); } if (options.duration_s) { duration = crm_time_parse_duration(options.duration_s); if (duration == NULL) { exit_code = CRM_EX_INVALID_PARAM; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Invalid duration specified: %s", options.duration_s); goto done; } crm_time_log(LOG_TRACE, "Duration", duration, crm_time_log_duration); crm_time_log(LOG_STDOUT, "Duration", duration, options.print_options | crm_time_log_duration); } if (options.period_s) { crm_time_period_t *period = crm_time_parse_period(options.period_s); if (period == NULL) { exit_code = CRM_EX_INVALID_PARAM; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Invalid interval specified: %s", options.period_s); goto done; } log_time_period(LOG_TRACE, period, options.print_options | crm_time_log_date | crm_time_log_timeofday); log_time_period(LOG_STDOUT, period, options.print_options | crm_time_log_date | crm_time_log_timeofday); crm_time_free_period(period); } if (date_time && duration) { crm_time_t *later = crm_time_add(date_time, duration); if (later == NULL) { exit_code = CRM_EX_SOFTWARE; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Unable to calculate ending time of %s plus %s", options.date_time_s, options.duration_s); goto done; } crm_time_log(LOG_TRACE, "Duration ends at", later, crm_time_ordinal | crm_time_log_date | crm_time_log_timeofday); crm_time_log(LOG_STDOUT, "Duration ends at", later, options.print_options | crm_time_log_date | crm_time_log_timeofday | crm_time_log_with_timezone); if (options.expected_s) { char *dt_s = crm_time_as_string(later, options.print_options | crm_time_log_date | crm_time_log_timeofday); if (!pcmk__str_eq(options.expected_s, dt_s, pcmk__str_casei)) { exit_code = CRM_EX_ERROR; goto done; } free(dt_s); } crm_time_free(later); } else if (date_time && options.expected_s) { char *dt_s = crm_time_as_string(date_time, options.print_options | crm_time_log_date | crm_time_log_timeofday); if (!pcmk__str_eq(options.expected_s, dt_s, pcmk__str_casei)) { exit_code = CRM_EX_ERROR; goto done; } free(dt_s); } done: crm_time_free(date_time); crm_time_free(duration); + g_strfreev(processed_args); + pcmk__free_arg_context(context); + + free(options.date_time_s); + g_free(options.duration_s); + g_free(options.expected_s); + g_free(options.period_s); + pcmk__output_and_clear_error(error, NULL); crm_exit(exit_code); }