ell.lists.linux.dev archive mirror
 help / color / mirror / Atom feed
* [RFC PATCH v5] Input/Output Terminal Abstraction
@ 2024-03-31 23:47 Grant Erickson
  2024-03-31 23:47 ` [RFC PATCH v5] term: Initial revision Grant Erickson
                   ` (3 more replies)
  0 siblings, 4 replies; 5+ messages in thread
From: Grant Erickson @ 2024-03-31 23:47 UTC (permalink / raw)
  To: ell; +Cc: Marcel Holtmann

This expands on Marcel Holtman's 2023-12-22 RFCv4 patch for an
input/output terminal abstraction.

Substantive changes from the v4 version:

  * Return status is 'int' rather than 'bool' with 0 as success and <
    0 as failure where the failures are negated POSIX error
    numbers. This removes the need for system integrators to intuit
    their own errors on a previously-false Boolean return status.

  * Makes 'l_term_set_{input,output}' public functions.

  * Adds observer functions for the terminal rows and columns.

  * Adds a modifier function for the terminal bounds.

  * Adds:

      - l_term_io_func_t
      - l_term_set_io_handler
      - l_term_process
      - l_term_io_callback

    Collectively, these make the abstraction run loop adaptable with a
    default callback that can be easily used with ELL's run loop,
    'l_term_io_callback'.

  * Adds 'l_term_[v]print' which complete the output interface.

    Of course, its always possible to perform this outside of an atop
    the existing functions; however, this eliminates integrators from
    having to manage the buffers necessary to do this.

This has proven to work well with the RFCv4 l_edit interfaces and the
corresponding demo-{cli,edit} applications.

Grant Erickson (4):
  term: Initial revision.
  ell: Add include directive for 'ell/term.h'.
  ell/Makefile: Added 'term.[ch]' to HEADERS and SOURCES.
  term: Added 'l_term_*' symbols.

 Makefile.am |   2 +
 ell/ell.h   |   1 +
 ell/ell.sym |  21 +++
 ell/term.c  | 487 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 ell/term.h  |  69 ++++++++
 5 files changed, 580 insertions(+)
 create mode 100644 ell/term.c
 create mode 100644 ell/term.h

-- 
2.42.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [RFC PATCH v5] term: Initial revision.
  2024-03-31 23:47 [RFC PATCH v5] Input/Output Terminal Abstraction Grant Erickson
@ 2024-03-31 23:47 ` Grant Erickson
  2024-03-31 23:47 ` [RFC PATCH v5] ell: Add include directive for 'ell/term.h' Grant Erickson
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 5+ messages in thread
From: Grant Erickson @ 2024-03-31 23:47 UTC (permalink / raw)
  To: ell; +Cc: Marcel Holtmann

This is the intial revision of 'term.[hc]' an input/output terminal
abstraction.
---
 ell/term.c | 487 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 ell/term.h |  69 ++++++++
 2 files changed, 556 insertions(+)
 create mode 100644 ell/term.c
 create mode 100644 ell/term.h

diff --git a/ell/term.c b/ell/term.c
new file mode 100644
index 000000000000..65ddbcbb7600
--- /dev/null
+++ b/ell/term.c
@@ -0,0 +1,487 @@
+/*
+ * Embedded Linux library
+ * Copyright (C) 2023-2024  Intel Corporation
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <termios.h>
+#include <sys/ioctl.h>
+
+#include "private.h"
+#include "signal.h"
+#include "io.h"
+#include "term.h"
+
+// MARK: Preprocessor Definitions
+
+#define IO_HANDLER(term, fd, readable, writable) \
+	do {										 \
+		if (term && term->io_handler) {			 \
+			term->io_handler(term,				 \
+							 fd,				 \
+							 readable,			 \
+							 writable,			 \
+							 term->io_data);	 \
+		}										 \
+	} while (0);
+
+// MARK: Type Declarations
+
+struct term_ops {
+	bool color_support;
+	bool use_sigwinch;
+	int (*get_winsize) (int fd, unsigned short *row, unsigned short *col);
+	int (*get_attr) (int fd, struct termios *c);
+	int (*set_attr) (int fd, const struct termios *c);
+};
+
+static int null_get_winsize(int fd, unsigned short *row, unsigned short *col)
+{
+	if (row) *row = 24;
+	if (col) *col = 80;
+	return 0;
+}
+
+static int null_get_attr(int fd, struct termios *c)
+{
+	return 0;
+}
+
+static int null_set_attr(int fd, const struct termios *c)
+{
+	return 0;
+}
+
+static const struct term_ops default_null_ops = {
+	.color_support	= false,
+	.use_sigwinch	= false,
+	.get_winsize	= null_get_winsize,
+	.get_attr		= null_get_attr,
+	.set_attr		= null_set_attr,
+};
+
+static int tty_get_winsize(int fd, unsigned short *row, unsigned short *col)
+{
+	struct winsize ws;
+	int res;
+
+	res = ioctl(fd, TIOCGWINSZ, &ws);
+	if (!res) {
+		if (row) *row = ws.ws_row;
+		if (col) *col = ws.ws_col;
+	}
+	else
+		res = -errno;
+
+	return res;
+}
+
+static int tty_get_attr(int fd, struct termios *c)
+{
+	int retval = 0;
+
+	if (tcgetattr(fd, c) != 0)
+		retval = -errno;
+
+	return retval;
+}
+
+static int tty_set_attr(int fd, const struct termios *c)
+{
+	int retval = 0;
+
+	if (tcsetattr(fd, TCSANOW, c) != 0)
+		retval = -errno;
+
+	return retval;
+}
+
+static const struct term_ops default_tty_ops = {
+	.color_support	= true,
+	.use_sigwinch	= true,
+	.get_winsize	= tty_get_winsize,
+	.get_attr		= tty_get_attr,
+	.set_attr		= tty_set_attr,
+};
+
+struct l_term {
+	int in_fd;
+	int out_fd;
+	l_term_io_func_t io_handler;
+	void *io_data;
+	const struct term_ops *in_ops;
+	const struct term_ops *out_ops;
+	struct termios in_termios;
+	struct termios out_termios;
+	unsigned short num_row;
+	unsigned short num_col;
+	struct l_signal *sigwinch;
+	bool is_running;
+	char key_buf[8];
+	size_t key_len;
+	l_term_key_func_t key_handler;
+	void *key_data;
+};
+
+LIB_EXPORT struct l_term *l_term_new(void)
+{
+	struct l_term *term;
+
+	term = l_new(struct l_term, 1);
+
+	term->in_fd = -1;
+	term->in_ops = NULL;
+
+	term->out_fd = -1;
+	term->out_ops = NULL;
+
+	term->is_running = false;
+
+	return term;
+}
+
+LIB_EXPORT void l_term_free(struct l_term *term)
+{
+	if (!term)
+		return;
+
+	l_free(term);
+}
+
+LIB_EXPORT int l_term_set_io_handler(struct l_term *term,
+				l_term_io_func_t handler, void *user_data)
+{
+	if (!term)
+		return -EINVAL;
+
+	term->io_handler = handler;
+	term->io_data = user_data;
+
+	return 0;
+}
+
+LIB_EXPORT int l_term_set_input(struct l_term *term, int fd)
+{
+	if (!term)
+		return -EINVAL;
+
+	if (fd < 0)
+		return -EBADF;
+
+	term->in_fd = fd;
+	term->in_ops = NULL;
+
+	IO_HANDLER(term, fd, 1, 0);
+
+	return 0;
+}
+
+LIB_EXPORT int l_term_set_output(struct l_term *term, int fd)
+{
+	if (!term)
+		return -EINVAL;
+
+	if (fd < 0)
+		return -EBADF;
+
+	term->out_fd = fd;
+	term->out_ops = NULL;
+
+	IO_HANDLER(term, fd, 0, 1);
+
+	return 0;
+}
+
+LIB_EXPORT int l_term_set_input_stdin(struct l_term *term)
+{
+	return l_term_set_input(term, STDIN_FILENO);
+}
+
+LIB_EXPORT int l_term_set_output_stdout(struct l_term *term)
+{
+	return l_term_set_output(term, STDOUT_FILENO);
+}
+
+LIB_EXPORT int l_term_set_key_handler(struct l_term *term,
+				l_term_key_func_t handler, void *user_data)
+{
+	if (!term)
+		return -EINVAL;
+
+	term->key_handler = handler;
+	term->key_data = user_data;
+
+	return 0;
+}
+
+LIB_EXPORT bool l_term_io_callback(struct l_io *io, void *user_data)
+{
+	struct l_term *term = user_data;
+
+	l_term_process(term);
+
+	return true;
+}
+
+static void sigwinch_handler(void *user_data)
+{
+	struct l_term *term = user_data;
+
+	term->out_ops->get_winsize(term->out_fd,
+					&term->num_row, &term->num_col);
+}
+
+LIB_EXPORT int l_term_open(struct l_term *term)
+{
+	struct termios termios;
+	int retval = 0;
+
+	if (!term)
+		return -EINVAL;
+
+	/* Missing input or output file descriptor is a non-recoverable
+	 * situation at this point.
+	 */
+	if (term->in_fd < 0 || term->out_fd < 0)
+		return -EBADF;
+
+	/* If no input operations are provided, fallback to use TTY
+	 * defaults or null setting.
+	 */
+	if (!term->in_ops) {
+		if (isatty(term->in_fd))
+			term->in_ops = &default_tty_ops;
+		else
+			term->in_ops = &default_null_ops;
+	}
+
+	/* If no output operations are provided, fallback to use TTY
+	 * defaults or null setting.
+	 */
+	if (!term->out_ops) {
+		if (isatty(term->out_fd))
+			term->out_ops = &default_tty_ops;
+		else
+			term->out_ops = &default_null_ops;
+	}
+
+	/* Save current termios setting of input */
+	memset(&term->in_termios, 0, sizeof(term->in_termios));
+	retval = term->in_ops->get_attr(term->in_fd, &term->in_termios);
+	if (retval < 0)
+		return retval;
+
+	/* Save current termios setting of output */
+	memset(&term->out_termios, 0, sizeof(term->out_termios));
+	retval = term->out_ops->get_attr(term->out_fd, &term->out_termios);
+	if (retval < 0)
+		return retval;
+
+	/* Disable canonical mode (ICANON), disable echoing of input
+	 * characters (ECHO) and disable generating signals.
+	 *
+	 * In noncanonical mode input is available immediately (without
+	 * the user having to type a line-delimiter character), no input
+	 * processing is performed, and line editing is disabled.
+	 *
+	 * When any of the characters INTR, QUIT, SUSP, or DSUSP are
+	 * received, don't generate the corresponding signal.
+	 */
+	memcpy(&termios, &term->in_termios, sizeof(termios));
+	termios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG);
+	retval = term->in_ops->set_attr(term->in_fd, &termios);
+	if (retval < 0)
+		return retval;
+
+	/* Send TIOCGWINSZ ioctl to retrieve col and row number */
+	retval = term->out_ops->get_winsize(term->out_fd,
+					&term->num_row, &term->num_col);
+	if (retval < 0)
+		return retval;
+
+	/* Setup SIGWINCH window resize signal handler if supported */
+	if (term->out_ops->use_sigwinch)
+		term->sigwinch = l_signal_create(SIGWINCH, sigwinch_handler,
+								term, NULL);
+
+	IO_HANDLER(term, term->in_fd, 1, 0);
+	IO_HANDLER(term, term->out_fd, 0, 1);
+
+	term->is_running = true;
+
+	return retval;
+}
+
+LIB_EXPORT int l_term_close(struct l_term *term)
+{
+	int retval = 0;
+
+	if (!term)
+		return -EINVAL;
+
+	term->is_running = false;
+
+	IO_HANDLER(term, term->in_fd, 0, 0);
+	IO_HANDLER(term, term->out_fd, 0, 0);
+
+	/* Remove SIGWINCH window resize signal handler */
+	if (term->out_ops->use_sigwinch)
+		l_signal_remove(term->sigwinch);
+
+	/* Restore previous termios setting from input and output */
+	retval = term->in_ops->set_attr(term->in_fd, &term->in_termios);
+	if (retval < 0)
+		return retval;
+
+	retval = term->out_ops->set_attr(term->out_fd, &term->out_termios);
+	if (retval < 0)
+		return retval;
+
+	return retval;
+}
+
+LIB_EXPORT void l_term_process(struct l_term *term)
+{
+	wchar_t wstr[2];
+	ssize_t len;
+	mbstate_t ps;
+
+	if (!term)
+		return;
+
+	len = read(term->in_fd, term->key_buf + term->key_len,
+					sizeof(term->key_buf) - term->key_len);
+	if (len < 0)
+		return;
+
+	term->key_len += len;
+
+	while (term->key_len > 0) {
+		memset(&ps, 0, sizeof(ps));
+
+		len = mbrtowc(wstr, term->key_buf, term->key_len, &ps);
+		if (len < 0)
+			break;
+
+		memmove(term->key_buf, term->key_buf + len,
+						term->key_len - len);
+		term->key_len -= len;
+
+		if (term->key_handler) {
+			wint_t wch = wstr[0];
+			term->key_handler(term, wch, term->key_data);
+		}
+	}
+}
+
+LIB_EXPORT int l_term_putnstr(struct l_term *term, const char *str, size_t n)
+{
+	ssize_t res;
+
+	if (!term)
+		return -EINVAL;
+
+	if (!term->is_running)
+		return -EPERM;
+
+	res = write(term->out_fd, str, n);
+	if (res < 0)
+		return -errno;
+
+	return 0;
+}
+
+LIB_EXPORT int l_term_putstr(struct l_term *term, const char *str)
+{
+	if (!str)
+		return -EINVAL;
+
+	return l_term_putnstr(term, str, strlen(str));
+}
+
+LIB_EXPORT int l_term_putchar(struct l_term *term, int ch)
+{
+	char c = ch;
+
+	return l_term_putnstr(term, &c, 1);
+}
+
+LIB_EXPORT int l_term_print(struct l_term *term, const char *str, ...)
+{
+	va_list ap;
+	int retval;
+
+	va_start(ap, str);
+
+	retval = l_term_vprint(term, str, ap);
+
+	va_end(ap);
+
+	return retval;
+}
+
+LIB_EXPORT int l_term_vprint(struct l_term *term, const char *str, va_list ap)
+{
+	if (!term || !str)
+		return -EINVAL;
+
+	if (!term->is_running)
+		return -EPERM;
+
+	if (vdprintf(term->out_fd, str, ap) < 0)
+		return -errno;
+
+	return 0;
+}
+
+LIB_EXPORT int l_term_set_bounds(struct l_term *term, uint16_t rows,
+				uint16_t columns)
+{
+	if (!term)
+		return -EINVAL;
+
+	term->num_row = rows;
+	term->num_col = columns;
+
+	return 0;
+}
+
+LIB_EXPORT int l_term_get_rows(struct l_term *term, uint16_t *rows)
+{
+	int retval = 0;
+
+	if (!term)
+		return -EINVAL;
+
+	if (!term->out_ops)
+		return -ENOSYS;
+
+	retval = term->out_ops->get_winsize(term->out_fd, rows, NULL);
+
+	return retval;
+}
+
+LIB_EXPORT int l_term_get_columns(struct l_term *term, uint16_t *columns)
+{
+	int retval = 0;
+
+	if (!term)
+		return -EINVAL;
+
+	if (!term->out_ops)
+		return -ENOSYS;
+
+	retval = term->out_ops->get_winsize(term->out_fd, NULL, columns);
+
+	return retval;
+}
diff --git a/ell/term.h b/ell/term.h
new file mode 100644
index 000000000000..89ed6c97c7ce
--- /dev/null
+++ b/ell/term.h
@@ -0,0 +1,69 @@
+/*
+ * Embedded Linux library
+ * Copyright (C) 2023-2024  Intel Corporation
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __ELL_TERM_H
+#define __ELL_TERM_H
+
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <wchar.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct l_term;
+
+struct l_term *l_term_new(void);
+void l_term_free(struct l_term *term);
+
+typedef void (*l_term_io_func_t)(struct l_term *term,
+                                 int fd,
+                                 bool readable,
+                                 bool writable,
+                                 void *user_data);
+
+int  l_term_set_io_handler(struct l_term *term,
+                           l_term_io_func_t handler,
+                           void *user_data);
+
+int  l_term_set_input(struct l_term *term, int fd);
+int  l_term_set_output(struct l_term *term, int fd);
+
+int  l_term_set_input_stdin(struct l_term *term);
+int  l_term_set_output_stdout(struct l_term *term);
+
+typedef void (*l_term_key_func_t) (struct l_term *term, wint_t wch, void *user_data);
+
+int  l_term_set_key_handler(struct l_term *term,
+				l_term_key_func_t handler, void *user_data);
+
+int  l_term_open(struct l_term *term);
+int  l_term_close(struct l_term *term);
+
+bool l_term_io_callback(struct l_io *io, void *user_data);
+
+void l_term_process(struct l_term *term);
+
+int  l_term_putnstr(struct l_term *term, const char *str, size_t n);
+int  l_term_putstr(struct l_term *term, const char *str);
+int  l_term_putchar(struct l_term *term, int ch);
+int  l_term_print(struct l_term *term, const char *str, ...);
+int  l_term_vprint(struct l_term *term, const char *str, va_list ap);
+
+int  l_term_set_bounds(struct l_term *term, uint16_t rows, uint16_t columns);
+
+int  l_term_get_rows(struct l_term *term, uint16_t *rows);
+int  l_term_get_columns(struct l_term *term, uint16_t *columns);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __ELL_TERM_H */
-- 
2.42.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [RFC PATCH v5] ell: Add include directive for 'ell/term.h'.
  2024-03-31 23:47 [RFC PATCH v5] Input/Output Terminal Abstraction Grant Erickson
  2024-03-31 23:47 ` [RFC PATCH v5] term: Initial revision Grant Erickson
@ 2024-03-31 23:47 ` Grant Erickson
  2024-03-31 23:47 ` [RFC PATCH v5] ell/Makefile: Added 'term.[ch]' to HEADERS and SOURCES Grant Erickson
  2024-03-31 23:47 ` [RFC PATCH v5] term: Added 'l_term_*' symbols Grant Erickson
  3 siblings, 0 replies; 5+ messages in thread
From: Grant Erickson @ 2024-03-31 23:47 UTC (permalink / raw)
  To: ell; +Cc: Marcel Holtmann

---
 ell/ell.h | 1 +
 1 file changed, 1 insertion(+)

diff --git a/ell/ell.h b/ell/ell.h
index f67339105e8f..c648715e9bce 100644
--- a/ell/ell.h
+++ b/ell/ell.h
@@ -46,6 +46,7 @@
 #include <ell/ecc.h>
 #include <ell/ecdh.h>
 #include <ell/time.h>
+#include <ell/term.h>
 #include <ell/gpio.h>
 #include <ell/path.h>
 #include <ell/acd.h>
-- 
2.42.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [RFC PATCH v5] ell/Makefile: Added 'term.[ch]' to HEADERS and SOURCES.
  2024-03-31 23:47 [RFC PATCH v5] Input/Output Terminal Abstraction Grant Erickson
  2024-03-31 23:47 ` [RFC PATCH v5] term: Initial revision Grant Erickson
  2024-03-31 23:47 ` [RFC PATCH v5] ell: Add include directive for 'ell/term.h' Grant Erickson
@ 2024-03-31 23:47 ` Grant Erickson
  2024-03-31 23:47 ` [RFC PATCH v5] term: Added 'l_term_*' symbols Grant Erickson
  3 siblings, 0 replies; 5+ messages in thread
From: Grant Erickson @ 2024-03-31 23:47 UTC (permalink / raw)
  To: ell; +Cc: Marcel Holtmann

---
 Makefile.am | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/Makefile.am b/Makefile.am
index 6c86e94e963e..5059682eec5f 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -55,6 +55,7 @@ pkginclude_HEADERS = ell/ell.h \
 			ell/ecc.h \
 			ell/ecdh.h \
 			ell/time.h \
+			ell/term.h \
 			ell/gpio.h \
 			ell/path.h \
 			ell/icmp6.h \
@@ -145,6 +146,7 @@ ell_libell_la_SOURCES = $(linux_headers) \
 			ell/ecdh.c \
 			ell/time.c \
 			ell/time-private.h \
+			ell/term.c \
 			ell/gpio.c \
 			ell/path.c \
 			ell/icmp6.c \
-- 
2.42.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [RFC PATCH v5] term: Added 'l_term_*' symbols.
  2024-03-31 23:47 [RFC PATCH v5] Input/Output Terminal Abstraction Grant Erickson
                   ` (2 preceding siblings ...)
  2024-03-31 23:47 ` [RFC PATCH v5] ell/Makefile: Added 'term.[ch]' to HEADERS and SOURCES Grant Erickson
@ 2024-03-31 23:47 ` Grant Erickson
  3 siblings, 0 replies; 5+ messages in thread
From: Grant Erickson @ 2024-03-31 23:47 UTC (permalink / raw)
  To: ell; +Cc: Marcel Holtmann

---
 ell/ell.sym | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)

diff --git a/ell/ell.sym b/ell/ell.sym
index 28b55d36719b..2767bf55e079 100644
--- a/ell/ell.sym
+++ b/ell/ell.sym
@@ -623,6 +623,27 @@ global:
 	l_ecdh_generate_shared_secret;
 	/* time */
 	l_time_now;
+	/* term */
+	l_term_new;
+	l_term_free;
+	l_term_set_io_handler;
+	l_term_set_input;
+	l_term_set_input_stdin;
+	l_term_set_output;
+	l_term_set_output_stdout;
+	l_term_set_key_handler;
+	l_term_open;
+	l_term_close;
+	l_term_io_callback;
+	l_term_process;
+	l_term_putnstr;
+	l_term_putstr;
+	l_term_putchar;
+	l_term_print;
+	l_term_vprint;
+	l_term_set_bounds;
+	l_term_get_rows;
+	l_term_get_columns;
 	/* gpio */
 	l_gpio_chips_with_line_label;
 	l_gpio_chip_new;
-- 
2.42.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2024-03-31 23:47 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-03-31 23:47 [RFC PATCH v5] Input/Output Terminal Abstraction Grant Erickson
2024-03-31 23:47 ` [RFC PATCH v5] term: Initial revision Grant Erickson
2024-03-31 23:47 ` [RFC PATCH v5] ell: Add include directive for 'ell/term.h' Grant Erickson
2024-03-31 23:47 ` [RFC PATCH v5] ell/Makefile: Added 'term.[ch]' to HEADERS and SOURCES Grant Erickson
2024-03-31 23:47 ` [RFC PATCH v5] term: Added 'l_term_*' symbols Grant Erickson

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).