From 641c79e33cf4ec7f384828d13b707496be7f9fcd Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Thu, 15 Jun 2017 09:23:13 +0200 Subject: [PATCH] gpiomon: implement custom output formats Add a new argument allowing the user to specify the format of the output string printed on GPIO line events. Signed-off-by: Bartosz Golaszewski --- src/tools/gpiomon.c | 144 ++++++++++++++++++++++++++++++++++++------ tests/tests-gpiomon.c | 85 +++++++++++++++++++++++++ 2 files changed, 209 insertions(+), 20 deletions(-) diff --git a/src/tools/gpiomon.c b/src/tools/gpiomon.c index 6b984b9..b54ee2b 100644 --- a/src/tools/gpiomon.c +++ b/src/tools/gpiomon.c @@ -25,10 +25,11 @@ static const struct option longopts[] = { { "silent", no_argument, NULL, 's' }, { "rising-edge", no_argument, NULL, 'r' }, { "falling-edge", no_argument, NULL, 'f' }, + { "format", required_argument, NULL, 'F' }, { 0 }, }; -static const char *const shortopts = "+hvln:srf"; +static const char *const shortopts = "+hvln:srfF:"; static void print_help(void) { @@ -43,6 +44,13 @@ static void print_help(void) printf(" -s, --silent:\t\tdon't print event info\n"); printf(" -r, --rising-edge:\tonly process rising edge events\n"); printf(" -f, --falling-edge:\tonly process falling edge events\n"); + printf(" -F, --format=FMT\tspecify custom output format\n"); + printf("\n"); + printf("Format specifiers:\n"); + printf(" %%o: GPIO line offset\n"); + printf(" %%e: event type (0 - falling edge, 1 rising edge)\n"); + printf(" %%s: seconds part of the event timestamp\n"); + printf(" %%n: nanoseconds part of the event timestamp\n"); } static volatile bool do_run = true; @@ -59,33 +67,126 @@ struct callback_data { bool silent; bool watch_rising; bool watch_falling; + char *fmt; }; -static int event_callback(int type, const struct timespec *ts, void *data) +static void replace_fmt(char **base, size_t off, const char *new) { - struct callback_data *cbdata = data; - const char *evname = NULL; + size_t newlen, baselen; + char *tmp; + + newlen = strlen(new); + baselen = strlen(*base); + + if (newlen > 2) { + /* + * FIXME This should be done with realloc() but valgrind + * is reporting problems with using uninitialized memory. + * + * Also: it could be made more efficient by allocating more + * memory at the beginning and then doubling the size of the + * buffer once the previous one is exhausted. + */ + tmp = malloc(baselen + newlen + 1); + if (!tmp) + die("out of memory"); + + memset(tmp, 0, baselen + newlen + 1); + strcpy(tmp, *base); + free(*base); + *base = tmp; + } + + memmove(*base + off + newlen, *base + off + 2, baselen - off - 2); + strncpy(*base + off, new, newlen); - switch (type) { - case GPIOD_EVENT_CB_RISING_EDGE: - if (cbdata->watch_rising) { - evname = " RISING EDGE"; - cbdata->num_events_done++; + if (newlen < 2) + *(*base + baselen - 1) = '\0'; +} + +static void event_print_custom(int type, const struct timespec *ts, + struct callback_data *data) +{ + char repbuf[64], *str, *pos, fmt; + size_t off; + + str = strdup(data->fmt); + if (!str) + die("out of memory"); + + for (off = 0;;) { + pos = strchr(str + off, '%'); + if (!pos) + break; + + fmt = *(pos + 1); + off = pos - str; + + if (fmt == '%') { + off += 2; + continue; } - break; - case GPIOD_EVENT_CB_FALLING_EDGE: - if (cbdata->watch_falling) { - evname = "FALLING EDGE"; - cbdata->num_events_done++; + + switch (fmt) { + case 'o': + snprintf(repbuf, sizeof(repbuf), "%u", data->offset); + replace_fmt(&str, off, repbuf); + break; + case 'e': + if (type == GPIOD_EVENT_CB_RISING_EDGE) + snprintf(repbuf, sizeof(repbuf), "1"); + else + snprintf(repbuf, sizeof(repbuf), "0"); + replace_fmt(&str, off, repbuf); + break; + case 's': + snprintf(repbuf, sizeof(repbuf), "%ld", ts->tv_sec); + replace_fmt(&str, off, repbuf); + break; + case 'n': + snprintf(repbuf, sizeof(repbuf), "%ld", ts->tv_nsec); + replace_fmt(&str, off, repbuf); + break; + default: + off += 2; + continue; } - break; - default: - break; + + off += strlen(repbuf); } - if (evname && !cbdata->silent) - printf("event: %s offset: %u timestamp: [%8ld.%09ld]\n", - evname, cbdata->offset, ts->tv_sec, ts->tv_nsec); + printf("%s\n", str); + free(str); +} + +static void event_print_human_readable(int type, const struct timespec *ts, + struct callback_data *data) +{ + char *evname; + + if (type == GPIOD_EVENT_CB_RISING_EDGE) + evname = " RISING EDGE"; + else + evname = "FALLING EDGE"; + + printf("event: %s offset: %u timestamp: [%8ld.%09ld]\n", + evname, data->offset, ts->tv_sec, ts->tv_nsec); +} + +static int event_callback(int type, const struct timespec *ts, void *data) +{ + struct callback_data *cbdata = data; + + if ((type == GPIOD_EVENT_CB_FALLING_EDGE && cbdata->watch_falling) + || (type == GPIOD_EVENT_CB_RISING_EDGE && cbdata->watch_rising)) { + if (!cbdata->silent) { + if (cbdata->fmt) + event_print_custom(type, ts, cbdata); + else + event_print_human_readable(type, ts, cbdata); + } + cbdata->num_events_done++; + } if (cbdata->num_events_wanted && cbdata->num_events_done >= cbdata->num_events_wanted) @@ -139,6 +240,9 @@ int main(int argc, char **argv) case 'f': cbdata.watch_falling = true; break; + case 'F': + cbdata.fmt = optarg; + break; case '?': die("try %s --help", get_progname()); default: diff --git a/tests/tests-gpiomon.c b/tests/tests-gpiomon.c index c20d7bd..884c45a 100644 --- a/tests/tests-gpiomon.c +++ b/tests/tests-gpiomon.c @@ -190,3 +190,88 @@ static void gpiomon_more_than_one_line_given(void) TEST_DEFINE(gpiomon_more_than_one_line_given, "tools: gpiomon - more than one line given", 0, { 4 }); + +static void gpiomon_custom_format_event_and_offset(void) +{ + test_tool_run("gpiomon", "--num-events=1", "--format=%e %o", + test_chip_name(0), "3", (char *)NULL); + test_set_event(0, 3, TEST_EVENT_RISING, 100); + test_tool_wait(); + + TEST_ASSERT(test_tool_exited()); + TEST_ASSERT_RET_OK(test_tool_exit_status()); + TEST_ASSERT_NOT_NULL(test_tool_stdout()); + TEST_ASSERT_NULL(test_tool_stderr()); + TEST_ASSERT_STR_EQ(test_tool_stdout(), "1 3\n"); +} +TEST_DEFINE(gpiomon_custom_format_event_and_offset, + "tools: gpiomon - custom output format: event and offset", + 0, { 8, 8 }); + +static void gpiomon_custom_format_event_and_offset_joined(void) +{ + test_tool_run("gpiomon", "--num-events=1", "--format=%e%o", + test_chip_name(0), "3", (char *)NULL); + test_set_event(0, 3, TEST_EVENT_RISING, 100); + test_tool_wait(); + + TEST_ASSERT(test_tool_exited()); + TEST_ASSERT_RET_OK(test_tool_exit_status()); + TEST_ASSERT_NOT_NULL(test_tool_stdout()); + TEST_ASSERT_NULL(test_tool_stderr()); + TEST_ASSERT_STR_EQ(test_tool_stdout(), "13\n"); +} +TEST_DEFINE(gpiomon_custom_format_event_and_offset_joined, + "tools: gpiomon - custom output format: event and offset, joined strings", + 0, { 8, 8 }); + +static void gpiomon_custom_format_timestamp(void) +{ + test_tool_run("gpiomon", "--num-events=1", "--format=%e %o %s.%n", + test_chip_name(0), "3", (char *)NULL); + test_set_event(0, 3, TEST_EVENT_RISING, 100); + test_tool_wait(); + + TEST_ASSERT(test_tool_exited()); + TEST_ASSERT_RET_OK(test_tool_exit_status()); + TEST_ASSERT_NOT_NULL(test_tool_stdout()); + TEST_ASSERT_NULL(test_tool_stderr()); + TEST_ASSERT_REGEX_MATCH(test_tool_stdout(), "1 3 [0-9]+\\.[0-9]+"); +} +TEST_DEFINE(gpiomon_custom_format_timestamp, + "tools: gpiomon - custom output format: timestamp", + 0, { 8, 8 }); + +static void gpiomon_custom_format_double_percent(void) +{ + test_tool_run("gpiomon", "--num-events=1", "--format=%%e", + test_chip_name(0), "3", (char *)NULL); + test_set_event(0, 3, TEST_EVENT_RISING, 100); + test_tool_wait(); + + TEST_ASSERT(test_tool_exited()); + TEST_ASSERT_RET_OK(test_tool_exit_status()); + TEST_ASSERT_NOT_NULL(test_tool_stdout()); + TEST_ASSERT_NULL(test_tool_stderr()); + TEST_ASSERT_STR_EQ(test_tool_stdout(), "%e\n"); +} +TEST_DEFINE(gpiomon_custom_format_double_percent, + "tools: gpiomon - custom output format: double percent", + 0, { 8, 8 }); + +static void gpiomon_custom_format_unknown_specifier(void) +{ + test_tool_run("gpiomon", "--num-events=1", "--format=%w", + test_chip_name(0), "3", (char *)NULL); + test_set_event(0, 3, TEST_EVENT_RISING, 100); + test_tool_wait(); + + TEST_ASSERT(test_tool_exited()); + TEST_ASSERT_RET_OK(test_tool_exit_status()); + TEST_ASSERT_NOT_NULL(test_tool_stdout()); + TEST_ASSERT_NULL(test_tool_stderr()); + TEST_ASSERT_STR_EQ(test_tool_stdout(), "%w\n"); +} +TEST_DEFINE(gpiomon_custom_format_unknown_specifier, + "tools: gpiomon - custom output format: unknown specifier", + 0, { 8, 8 }); -- 2.30.2