{ "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)
{
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;
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)
case 'f':
cbdata.watch_falling = true;
break;
+ case 'F':
+ cbdata.fmt = optarg;
+ break;
case '?':
die("try %s --help", get_progname());
default:
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 });