summaryrefslogtreecommitdiffstats
path: root/cpukit
diff options
context:
space:
mode:
authorChris Johns <chrisj@rtems.org>2014-09-15 08:50:28 +1000
committerChris Johns <chrisj@rtems.org>2014-09-16 15:19:58 +1000
commitebf9a057b5095df43632752d327e7c297b9fd78b (patch)
tree2b09f2946fd8a78eef7d1f76ba0df9db029cdae3 /cpukit
parentshell: Add a ping command. (diff)
downloadrtems-ebf9a057b5095df43632752d327e7c297b9fd78b.tar.bz2
libmisc: Add a stdio redirector helper.
This module makes it easy to redirect and capture stdout, stderr or any other fd in your application. The captured data can be sent off board, for example using syslog, or buffered and displayed in a web page.
Diffstat (limited to 'cpukit')
-rw-r--r--cpukit/Makefile.am2
-rw-r--r--cpukit/libmisc/Makefile.am4
-rw-r--r--cpukit/libmisc/redirector/stdio-redirect.c333
-rw-r--r--cpukit/libmisc/redirector/stdio-redirect.h115
-rw-r--r--cpukit/preinstall.am4
5 files changed, 458 insertions, 0 deletions
diff --git a/cpukit/Makefile.am b/cpukit/Makefile.am
index 2693d46f68..93b07c8a5d 100644
--- a/cpukit/Makefile.am
+++ b/cpukit/Makefile.am
@@ -175,6 +175,8 @@ include_rtems_HEADERS += libmisc/mouse/serial_mouse.h
## libqos
include_rtems_HEADERS += libqos/qreslib.h
+## redirector
+include_rtems_HEADERS += libmisc/redirector/stdio-redirect.h
## shell
if LIBSHELL
include_rtems_HEADERS += libmisc/shell/shell.h
diff --git a/cpukit/libmisc/Makefile.am b/cpukit/libmisc/Makefile.am
index bcf8ff018f..1091109748 100644
--- a/cpukit/libmisc/Makefile.am
+++ b/cpukit/libmisc/Makefile.am
@@ -71,6 +71,10 @@ noinst_LIBRARIES += libmouse.a
libmouse_a_SOURCES = mouse/mouse_parser.c mouse/serial_mouse.c
EXTRA_DIST += mouse/README
+## redirector
+noinst_LIBRARIES += libredirector.a
+libredirector_a_SOURCES = redirector/stdio-redirect.c redirector/stdio-redirect.h
+
## shell
if LIBSHELL
noinst_LIBRARIES += libshell.a
diff --git a/cpukit/libmisc/redirector/stdio-redirect.c b/cpukit/libmisc/redirector/stdio-redirect.c
new file mode 100644
index 0000000000..e6610cbba1
--- /dev/null
+++ b/cpukit/libmisc/redirector/stdio-redirect.c
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2014 Chris Johns (chrisj@rtems.org)
+ *
+ * The license and distribution terms for this file may be
+ * found in the file LICENSE in this distribution.
+ *
+ * This software with is provided ``as is'' and with NO WARRANTY.
+ */
+
+/*
+ * RTEMS std redirector.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <rtems.h>
+#include <rtems/error.h>
+
+#include "stdio-redirect.h"
+
+#define RTEMS_STDIO_REDIRECT_LOCK_ATTRIBS \
+ (RTEMS_PRIORITY | RTEMS_BINARY_SEMAPHORE | \
+ RTEMS_INHERIT_PRIORITY | RTEMS_NO_PRIORITY_CEILING | RTEMS_LOCAL)
+
+#define RTEMS_STDIO_REDIRECT_RUNNING (1 << 0)
+#define RTEMS_STDIO_REDIRECT_FINISHED (1 << 1)
+
+static bool
+rtems_stdio_redirect_lock(rtems_stdio_redirect_t* sr)
+{
+ rtems_status_code sc = rtems_semaphore_obtain (sr->lock,
+ RTEMS_WAIT,
+ RTEMS_NO_TIMEOUT);
+ if (sc != RTEMS_SUCCESSFUL)
+ {
+ fprintf(stderr, "error: stdio-redirect: lock failed: %s\n", rtems_status_text(sc));
+ return false;
+ }
+ return true;
+}
+
+static bool
+rtems_stdio_redirect_unlock(rtems_stdio_redirect_t* sr)
+{
+ rtems_status_code sc = rtems_semaphore_release (sr->lock);
+ if (sc != RTEMS_SUCCESSFUL)
+ {
+ fprintf(stderr, "error: stdio-redirect: unlock failed: %s\n", rtems_status_text(sc));
+ return false;
+ }
+ return true;
+}
+
+static bool
+rtems_stdio_redirect_write (rtems_stdio_redirect_t* sr, const char* buf, ssize_t len)
+{
+ if (!rtems_stdio_redirect_lock(sr))
+ return false;
+
+ if (sr->buffer)
+ {
+ if (len >= sr->buffer_size)
+ {
+ ssize_t offset = len - sr->buffer_size;
+ memcpy (sr->buffer, buf + offset, sr->buffer_size);
+ sr->in = 0;
+ sr->full = true;
+ }
+ else
+ {
+ if ((sr->in + len) > sr->buffer_size)
+ {
+ ssize_t bytes = sr->buffer_size - sr->in;
+ memcpy (sr->buffer + sr->in, buf, bytes);
+ buf += bytes;
+ len -= bytes;
+ sr->in = 0;
+ sr->full = true;
+ }
+ else
+ {
+ memcpy (sr->buffer + sr->in, buf, len);
+ sr->in += len;
+ }
+ }
+ }
+
+ if (sr->handler)
+ sr->handler(buf, len);
+
+ return rtems_stdio_redirect_lock(sr);
+}
+
+static rtems_task
+rtems_stdio_redirect_reader(rtems_task_argument arg)
+{
+ rtems_stdio_redirect_t* sr = (rtems_stdio_redirect_t*) arg;
+
+ while (sr->state & RTEMS_STDIO_REDIRECT_RUNNING)
+ {
+ ssize_t r = read (sr->pipe[0], sr->input, sr->input_size);
+
+ if (r <= 0)
+ break;
+
+ if (sr->echo)
+ write (sr->fd_dup, sr->input, r);
+
+ if (!rtems_stdio_redirect_write (sr, sr->input, r))
+ break;
+ }
+
+ sr->state |= RTEMS_STDIO_REDIRECT_FINISHED;
+
+ rtems_task_delete(RTEMS_SELF);
+}
+
+rtems_stdio_redirect_t*
+rtems_stdio_redirect_open(int fd,
+ rtems_task_priority priority,
+ size_t stack_size,
+ ssize_t input_size,
+ ssize_t buffer_size,
+ bool echo,
+ rtems_stdio_redirect_handler handler)
+{
+ rtems_stdio_redirect_t* sr;
+ rtems_name name;
+ rtems_status_code sc;
+
+ sr = malloc(sizeof(rtems_stdio_redirect_t));
+ if (!sr)
+ {
+ fprintf(stderr, "error: stdio-redirect: no memory\n");
+ return NULL;
+ }
+
+ memset(sr, 0, sizeof(*sr));
+
+ sr->input_size = input_size;
+ sr->input = malloc(input_size);
+ if (!sr->input)
+ {
+ fprintf(stderr, "error: stdio-redirect: no memory for input\n");
+ free(sr);
+ return NULL;
+ }
+
+ if (buffer_size)
+ {
+ sr->buffer_size = buffer_size;
+ sr->buffer = malloc(buffer_size);
+ if (!sr->buffer)
+ {
+ fprintf(stderr, "error: stdio-redirect: no memory for buffer\n");
+ free(sr->input);
+ free(sr);
+ return NULL;
+ }
+ }
+
+ sr->fd = fd;
+
+ sr->fd_dup = dup(fd);
+ if (sr->fd_dup < 0)
+ {
+ fprintf(stderr, "error: stdio-redirect: dup: %s\n", strerror(errno));
+ free(sr->buffer);
+ free(sr->input);
+ free(sr);
+ return NULL;
+ }
+
+ if (pipe(sr->pipe) < 0)
+ {
+ fprintf(stderr, "error: stdio-redirect: pipe create: %s\n", strerror(errno));
+ free(sr->buffer);
+ free(sr->input);
+ free(sr);
+ return NULL;
+
+ }
+
+ sr->echo = echo;
+ sr->handler = handler;
+
+ sc = rtems_semaphore_create (rtems_build_name ('R', 'S', 'R', 'l'),
+ 1, RTEMS_STDIO_REDIRECT_LOCK_ATTRIBS, 0,
+ &sr->lock);
+ if (sc != RTEMS_SUCCESSFUL)
+ {
+ fprintf(stderr, "error: stdio-redirect: lock create: %s\n", rtems_status_text(sc));
+ free(sr->buffer);
+ free(sr->input);
+ free(sr);
+ return NULL;
+ }
+
+ name = rtems_build_name ('S', 'R', '0' + (fd / 10), '0' + (fd % 10));
+ sc = rtems_task_create (name,
+ priority,
+ stack_size,
+ RTEMS_PREEMPT | RTEMS_NO_TIMESLICE | RTEMS_NO_ASR,
+ RTEMS_LOCAL | RTEMS_NO_FLOATING_POINT,
+ &sr->reader);
+ if (sc != RTEMS_SUCCESSFUL)
+ {
+ fprintf(stderr, "error: stdio-redirect: reader create: %s\n", rtems_status_text(sc));
+ rtems_semaphore_delete(sr->lock);
+ free(sr->buffer);
+ free(sr->input);
+ free(sr);
+ return NULL;
+ }
+
+ sr->state |= RTEMS_STDIO_REDIRECT_RUNNING;
+
+ if (dup2 (sr->pipe[1], fd) < 0)
+ {
+ fprintf(stderr, "error: stdio-redirect: dup2: %s\n", strerror(errno));
+ free(sr->buffer);
+ free(sr->input);
+ free(sr);
+ return NULL;
+ }
+
+ sc = rtems_task_start (sr->reader,
+ rtems_stdio_redirect_reader,
+ (rtems_task_argument) sr);
+
+ if (sc != RTEMS_SUCCESSFUL)
+ {
+ fprintf(stderr, "error: stdio-redirect: reader start: %s\n", rtems_status_text(sc));
+ rtems_task_delete(sr->reader);
+ rtems_semaphore_delete(sr->lock);
+ free(sr->buffer);
+ free(sr->input);
+ free(sr);
+ return NULL;
+ }
+
+ return sr;
+}
+
+void
+rtems_stdio_redirect_close(rtems_stdio_redirect_t* sr)
+{
+ if (rtems_stdio_redirect_lock(sr))
+ {
+ sr->state &= ~RTEMS_STDIO_REDIRECT_RUNNING;
+ close(sr->pipe[0]);
+
+ rtems_stdio_redirect_unlock(sr);
+
+ while (sr->state & RTEMS_STDIO_REDIRECT_FINISHED)
+ {
+ usleep(250 * 1000 * 1000);
+ }
+
+ rtems_stdio_redirect_lock(sr);
+
+ dup2(sr->fd, sr->fd_dup);
+
+ free(sr->buffer);
+ free(sr->input);
+
+ rtems_stdio_redirect_unlock(sr);
+
+ rtems_semaphore_delete(sr->lock);
+
+ free(sr);
+ }
+}
+
+ssize_t
+rtems_stdio_redirect_read(rtems_stdio_redirect_t* sr,
+ char* buffer,
+ ssize_t length)
+{
+ ssize_t written = 0;
+
+ if (!rtems_stdio_redirect_lock(sr))
+ return written;
+
+ if (sr->buffer)
+ {
+ ssize_t rem = 0;
+
+ if (sr->full)
+ {
+ if (length < sr->buffer_size)
+ {
+ if (length > sr->in)
+ {
+ rem = length - sr->in;
+ memcpy (buffer, sr->buffer + sr->buffer_size - rem, rem);
+ }
+
+ memcpy (buffer + rem, sr->buffer, sr->in);
+ written = length;
+ }
+ else
+ {
+ rem = sr->buffer_size - sr->in;
+ memcpy (buffer, sr->buffer + sr->in, rem);
+ memcpy (buffer + rem, sr->buffer, sr->in);
+ written = sr->buffer_size;
+ }
+
+ sr->full = false;
+ }
+ else if (length < sr->in)
+ {
+ rem = sr->in - length;
+ memcpy (buffer, sr->buffer + rem, length);
+ written = length;
+ }
+ else
+ {
+ memcpy (buffer, sr->buffer, sr->in);
+ written = sr->in;
+ }
+ }
+
+ rtems_stdio_redirect_unlock(sr);
+
+ return written;
+}
diff --git a/cpukit/libmisc/redirector/stdio-redirect.h b/cpukit/libmisc/redirector/stdio-redirect.h
new file mode 100644
index 0000000000..cf3d0cfcd4
--- /dev/null
+++ b/cpukit/libmisc/redirector/stdio-redirect.h
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2014 Chris Johns (chrisj@rtems.org)
+ *
+ * The license and distribution terms for this file may be
+ * found in the file LICENSE in this distribution.
+ *
+ * This software with is provided ``as is'' and with NO WARRANTY.
+ */
+
+/*
+ * @brief Redirect an stdio file decriptor.
+ *
+ * The is a helper module of code design to redirect an stdio file
+ * descriptor. You can optionally have the data buffered and/or you can provide
+ * a handler function that is called when data arrives.
+ *
+ * The module uses standard POSIX calls to implement the redirection and if the
+ * threading was POSIX based the code would be portable. Currently the code
+ * uses RTEMS threads.
+ *
+ * Redirecting stderr and stdout is useful in embedded system because you can
+ * transport the output off your device or create a web interface that can
+ * display the output. This can be a very powerful diagnostic and support tool.
+ *
+ * The implementation does:
+ *
+ * 1. Duplicate the file descriptor (fd) to redirect using the dup call. The
+ * duplicated desciptor is used to echo the output out the existing path.
+ *
+ * 2. Create a pipe using the pipe call.
+ *
+ * 3. Duplicate the pipe's writer file descriptor to user's file
+ * descriptor. This results in any writes to the user's fd being written to
+ * the pipe.
+ *
+ * 4. Create a reader task that blocks on the pipe. It optionally calls a
+ * handler and if configured buffers the data.
+ */
+
+#if !defined(RTEMS_STDIO_REDIRECT_H)
+#define RTEMS_STDIO_REDIRECT_H
+
+#include <stdbool.h>
+#include <rtems.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/*
+ * Handler called whenever redirected data arrives.
+ *
+ * @param buffer The data.
+ * @param length The amount of data in the buffer.
+ */
+typedef void (*rtems_stdio_redirect_handler)(const char* buffer,
+ ssize_t length);
+
+/*
+ * Redirector data.
+ */
+typedef struct
+{
+ volatile uint32_t state; /**< The state. */
+ rtems_id reader; /**< The reader thread. */
+ rtems_id lock; /**< Lock for this struct. */
+ int fd; /**< The file descriptor to redirect. */
+ int fd_dup; /**< Duplicated fd to write to. */
+ int pipe[2]; /**< The pipe to the reader thread. */
+ char* input; /**< The input buffer the reader uses. */
+ ssize_t input_size; /**< The input buffer size. */
+ char* buffer; /**< Captured redirected data. */
+ ssize_t buffer_size; /**< Capture buffer size. */
+ ssize_t in; /**< Buffer in index. */
+ bool full; /**< The buffer is full. */
+ bool echo; /**< Echo the data out the existing path. */
+ rtems_stdio_redirect_handler handler; /**< Redirected data handler. */
+} rtems_stdio_redirect_t;
+
+/*
+ * Open a redirector returning the handle to it.
+ *
+ * @param fd The file descriptor to redirect.
+ * @param priority The priority of the reader thread.
+ */
+rtems_stdio_redirect_t* rtems_stdio_redirect_open(int fd,
+ rtems_task_priority priority,
+ size_t stack_size,
+ ssize_t input_size,
+ ssize_t buffer_size,
+ bool echo,
+ rtems_stdio_redirect_handler handler);
+
+/*
+ * Close the redirector.
+ */
+void rtems_stdio_redirect_close(rtems_stdio_redirect_t* sr);
+
+/*
+ * Get data from the capture buffer. Data read is removed from the buffer.
+ *
+ * @param sr The stdio redirection handle.
+ * @param buffer The buffer data is written into.
+ * @param length The size of the buffer.
+ * @return ssize_t The amount of data written and -1 or an error.
+ */
+ssize_t rtems_stdio_redirect_read(rtems_stdio_redirect_t* sr,
+ char* buffer,
+ ssize_t length);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif
diff --git a/cpukit/preinstall.am b/cpukit/preinstall.am
index ccbb47fc14..1d9cd5366e 100644
--- a/cpukit/preinstall.am
+++ b/cpukit/preinstall.am
@@ -374,6 +374,10 @@ $(PROJECT_INCLUDE)/rtems/qreslib.h: libqos/qreslib.h $(PROJECT_INCLUDE)/rtems/$(
$(INSTALL_DATA) $< $(PROJECT_INCLUDE)/rtems/qreslib.h
PREINSTALL_FILES += $(PROJECT_INCLUDE)/rtems/qreslib.h
+$(PROJECT_INCLUDE)/rtems/stdio-redirect.h: libmisc/redirector/stdio-redirect.h $(PROJECT_INCLUDE)/rtems/$(dirstamp)
+ $(INSTALL_DATA) $< $(PROJECT_INCLUDE)/rtems/stdio-redirect.h
+PREINSTALL_FILES += $(PROJECT_INCLUDE)/rtems/stdio-redirect.h
+
if LIBSHELL
$(PROJECT_INCLUDE)/rtems/shell.h: libmisc/shell/shell.h $(PROJECT_INCLUDE)/rtems/$(dirstamp)
$(INSTALL_DATA) $< $(PROJECT_INCLUDE)/rtems/shell.h