[libgsystem] GSConsole: New API to access system console



commit 87bf968f3470657e5a80d3f937b2142f9f6fb53b
Author: Colin Walters <walters verbum org>
Date:   Thu Jan 24 15:17:13 2013 -0500

    GSConsole: New API to access system console
    
    At present, only implemented for Unix.

 Makefile-libgsystem.am |    2 +
 gsystem-console.c      |  441 ++++++++++++++++++++++++++++++++++++++++++++++++
 gsystem-console.h      |   59 +++++++
 libgsystem.h           |    1 +
 4 files changed, 503 insertions(+), 0 deletions(-)
---
diff --git a/Makefile-libgsystem.am b/Makefile-libgsystem.am
index adc997f..83e8928 100644
--- a/Makefile-libgsystem.am
+++ b/Makefile-libgsystem.am
@@ -20,6 +20,8 @@ EXTRA_DIST += $(libgsystem_srcpath)/README $(libgsystem_srcpath)/COPYING
 libgsystem_la_SOURCES = \
 	$(libgsystem_srcpath)/gsystem-local-alloc.h \
 	$(libgsystem_srcpath)/gsystem-local-alloc.c \
+	$(libgsystem_srcpath)/gsystem-console.h \
+	$(libgsystem_srcpath)/gsystem-console.c \
 	$(libgsystem_srcpath)/gsystem-file-utils.h \
 	$(libgsystem_srcpath)/gsystem-file-utils.c \
 	$(libgsystem_srcpath)/gsystem-shutil.h \
diff --git a/gsystem-console.c b/gsystem-console.c
new file mode 100644
index 0000000..589fe41
--- /dev/null
+++ b/gsystem-console.c
@@ -0,0 +1,441 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2013 Colin Walters <walters verbum org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include "libgsystem.h"
+
+/**
+ * SECTION:gsconsole
+ * @title: GSConsole
+ * @short_description: Interact with standard input/output as well as terminal
+ *
+ * First, this class offers API to access the standard input and
+ * output/error, streams as #GInputStream and #GOutputStream
+ * respectively.
+ *
+ * In the case where the process is connected to a controlling
+ * terminal, the gs_console_get() API is available, which exposes a
+ * number of additional features such as no-echo password reading.
+ *
+ * Since: 2.36
+ */
+
+#include "config.h"
+
+#include "gsystem-console.h"
+
+#include <string.h>
+#ifdef G_OS_UNIX
+#include <gio/gunixoutputstream.h>
+#include <gio/gfiledescriptorbased.h>
+#include <gio/gunixinputstream.h>
+#include <glib-unix.h>
+#include <unistd.h>
+#include <termios.h>
+#endif
+#include <fcntl.h>
+
+typedef GObjectClass GSConsoleClass;
+
+struct _GSConsole
+{
+  GObject parent;
+
+  gboolean in_status_line;
+  gssize last_line_written;
+};
+
+G_DEFINE_TYPE (GSConsole, gs_console, G_TYPE_OBJECT);
+
+static void
+gs_console_init (GSConsole  *self)
+{
+  self->last_line_written = -1;
+}
+
+static void
+gs_console_class_init (GSConsoleClass *class)
+{
+}
+
+/**
+ * gs_console_get:
+ *
+ * If the current process has an interactive console, return the
+ * singleton #GSConsole instance.  On Unix, this is equivalent to
+ * isatty().  For all other cases, such as pipes, sockets, /dev/null,
+ * this function will return %NULL.
+ *
+ * Returns: (transfer none): The console instance, or %NULL if not interactive
+ *
+ * Since: 2.36
+ */
+GSConsole *
+gs_console_get (void)
+{
+  static gsize checked = 0;
+  static GSConsole *instance = NULL;
+
+  if (g_once_init_enter (&checked))
+    {
+#ifdef G_OS_UNIX
+      if (isatty (0) && isatty (1))
+        instance = g_object_new (GS_TYPE_CONSOLE, NULL);
+#endif
+      g_once_init_leave (&checked, 1);
+    }
+  
+  return (GSConsole*) instance;
+}
+
+/**
+ * gs_console_get_stdin:
+ *
+ * Returns: (transfer none): The singleton stream connected to standard input
+ */
+GInputStream *
+gs_console_get_stdin (void)
+{
+#ifdef G_OS_UNIX
+  static gsize instance = 0;
+
+  if (g_once_init_enter (&instance))
+    g_once_init_leave (&instance, (gsize) g_unix_input_stream_new (0, FALSE));
+  
+  return (GInputStream*) instance;
+#else
+  g_error ("not implemented");
+#endif
+}
+
+/**
+ * gs_console_get_stdout:
+ *
+ * Returns: (transfer none): The singleton stream connected to standard output
+ */
+GOutputStream *
+gs_console_get_stdout (void)
+{
+#ifdef G_OS_UNIX
+  static gsize instance = 0;
+
+  if (g_once_init_enter (&instance))
+    g_once_init_leave (&instance, (gsize) g_unix_output_stream_new (1, FALSE));
+  
+  return (GOutputStream*) instance;
+#else
+  g_error ("not implemented");
+#endif
+}
+
+/**
+ * gs_console_get_stderr:
+ *
+ * Returns: (transfer none): The singleton stream connected to standard error
+ */
+GOutputStream *
+gs_console_get_stderr (void)
+{
+#ifdef G_OS_UNIX
+  static gsize instance = 0;
+
+  if (g_once_init_enter (&instance))
+    g_once_init_leave (&instance, (gsize) g_unix_output_stream_new (2, FALSE));
+  
+  return (GOutputStream*) instance;
+#else
+  g_error ("not implemented");
+#endif
+}
+
+#ifdef G_OS_UNIX
+static inline void
+_set_error_from_errno (GError **error)
+{
+  int errsv = errno;
+  g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
+                       g_strerror (errsv));
+}
+#endif
+
+/**
+ * gs_console_read_password:
+ * @console: the #GSConsole
+ * @prompt: A string to output before reading the password
+ * @error: a #GError
+ *
+ * Write @prompt to standard output, then switch output echo off, read
+ * a result string, then switch output echo back on.
+ *
+ * Returns: A string, or %NULL on error
+ */
+char *
+gs_console_read_password (GSConsole     *console,
+                          const char    *prompt,
+                          GCancellable  *cancellable,
+                          GError       **error)
+{
+#ifdef G_OS_UNIX
+  gboolean ret = FALSE;
+  /* This code is modified from that found in
+   * polkit/src/polkittextagentlistener.c, reused under the LGPL v2.1
+   */
+  int res;
+  struct termios ts, ots;
+  GInputStream *in;
+  GOutputStream *out;
+  GString *str = NULL;
+  gsize bytes_written;
+  gboolean reset_terminal = FALSE;
+
+  in = gs_console_get_stdin ();
+  out = gs_console_get_stdout ();
+
+  if (!g_output_stream_write_all (out, prompt, strlen (prompt), &bytes_written,
+                                  cancellable, error))
+    goto out;
+  if (!g_output_stream_flush (out, cancellable, error))
+    goto out;
+
+  /* TODO: We really ought to block SIGINT and STGSTP (and probably
+   *       other signals too) so we can restore the terminal (since we
+   *       turn off echoing). See e.g. Advanced Programming in the
+   *       UNIX Environment 2nd edition (Steves and Rago) section
+   *       18.10, pg 660 where this is suggested. See also various
+   *       getpass(3) implementations
+   *
+   *       However, since we are a library routine the user could have
+   *       multiple threads - in fact, typical usage of
+   *       PolkitAgentTextListener is to run it in a thread. And
+   *       unfortunately threads and POSIX signals is a royal PITA.
+   *
+   *       Maybe we could fork(2) and ask for the password in the
+   *       child and send it back to the parent over a pipe? (we are
+   *       guaranteed that there is only one thread in the child
+   *       process).
+   *
+   *       (Side benefit of doing this in a child process is that we
+   *       could avoid blocking the thread where the
+   *       PolkitAgentTextListener object is being serviced from. But
+   *       since this class is normally used in a dedicated thread
+   *       it doesn't really matter *anyway*.)
+   *
+   *       Anyway, On modern Linux not doing this doesn't seem to be a
+   *       problem - looks like modern shells restore echoing anyway
+   *       on the first input. So maybe it's not even worth solving
+   *       the problem.
+   */
+
+  do
+    res = tcgetattr (1, &ts);
+  while (G_UNLIKELY (res == -1 && errno == EINTR));
+  if (res == -1)
+    {
+      _set_error_from_errno (error);
+      goto out;
+    }
+  ots = ts;
+  ts.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
+  do
+    res = tcsetattr (1, TCSAFLUSH, &ts);
+  while (G_UNLIKELY (res == -1 && errno == EINTR));
+  if (res == -1)
+    {
+      _set_error_from_errno (error);
+      goto out;
+    }
+
+  /* After this point, we'll need to clean up the terminal in case of
+   * error.
+   */
+  reset_terminal = TRUE;
+
+  str = g_string_new (NULL);
+  while (TRUE)
+    {
+      gssize bytes_read;
+      guint8 buf[1];
+
+      /* FIXME - we should probably be converting from the system
+       * codeset, in case it's not UTF-8.
+       */
+      bytes_read = g_input_stream_read (in, buf, sizeof (buf),
+                                        cancellable, error);
+      if (bytes_read < 0)
+        goto out;
+      else if (bytes_read == 0)
+        {
+          g_set_error (error, G_IO_ERROR, G_IO_ERROR_CLOSED,
+                       "End of stream while reading password");
+          goto out;
+        }
+      else if (buf[0] == '\n')
+        {
+          break;
+        }
+      else
+        {
+          g_string_append_c (str, buf[0]);
+        }
+    }
+
+  ret = TRUE;
+ out:
+  if (reset_terminal)
+    {
+      do
+        res = tcsetattr (1, TCSAFLUSH, &ots);
+      while (G_UNLIKELY (res == -1 && errno == EINTR));
+      if (res == -1)
+        {
+          _set_error_from_errno (error);
+          g_string_free (str, TRUE);
+          return NULL;
+        }
+    }
+
+  if (!ret)
+    {
+      g_string_free (str, TRUE);
+      return NULL;
+    }
+  else
+    {
+      return g_string_free (str, FALSE);
+    }
+#else
+  g_error ("not implemented");
+#endif
+}
+
+/**
+ * gs_console_begin_status_line:
+ * @console: the #GSConsole
+ * @line: String to output
+ *
+ * The primary use case for this function is to output periodic
+ * "status" or "progress" information.  The first time this function
+ * is called, @line will be output normally.  Subsequent invocations
+ * will overwrite the previous.
+ *
+ * You must invoke gs_console_end_status_line() to return the console
+ * to normal mode.  In particular, concurrent use of this function and
+ * the stream returned by gs_console_get_stdout() results in undefined
+ * behavior.
+ */
+gboolean
+gs_console_begin_status_line (GSConsole     *console,
+                              const char    *line,
+                              GCancellable  *cancellable,
+                              GError       **error)
+{
+#ifdef G_OS_UNIX
+  gboolean ret = FALSE;
+  gsize linelen;
+  GOutputStream *out;
+  gsize bytes_written;
+
+  out = gs_console_get_stdout ();
+
+  if (!console->in_status_line)
+    {
+      guint8 buf[2] = { 0x1B, 0x37 };
+      if (!g_output_stream_write_all (out, buf, sizeof (buf), &bytes_written,
+                                      cancellable, error))
+        goto out;
+      console->in_status_line = TRUE;
+      console->last_line_written = -1;
+    }
+
+  {
+    guint8 buf[2] = { 0x1B, 0x38 };
+    if (!g_output_stream_write_all (out, buf, sizeof (buf), &bytes_written,
+                                    cancellable, error))
+      goto out;
+  }
+
+  linelen = strlen (line);
+  if (!g_output_stream_write_all (out, line, linelen, &bytes_written,
+                                  cancellable, error))
+    goto out;
+
+  /* Now we need to pad with spaces enough to overwrite our last line
+   */
+  if (console->last_line_written >= 0
+      && linelen < console->last_line_written)
+    {
+      gsize towrite = console->last_line_written - linelen;
+      const char c = ' ';
+      while (towrite > 0)
+        {
+          if (!g_output_stream_write_all (out, &c, 1, &bytes_written,
+                                          cancellable, error))
+            goto out;
+        }
+    }
+  
+  console->last_line_written = linelen;
+  
+  ret = TRUE;
+ out:
+  return ret;
+#else
+  g_error ("not implemented");
+#endif
+}
+
+/**
+ * gs_console_end_status_line:
+ * @console: the #GSConsole
+ *
+ * Complete a series of invocations of gs_console_begin_status_line(),
+ * returning the stream to normal mode.  The last printed status line
+ * remains on the console; if this is not desired, print an empty
+ * string to clear it before invoking this function.
+ */
+gboolean
+gs_console_end_status_line (GSConsole     *console,
+                            GCancellable  *cancellable,
+                            GError       **error)
+{
+#ifdef G_OS_UNIX  
+  gboolean ret = FALSE;
+  GOutputStream *out;
+  gsize bytes_written;
+  char c = '\n';
+
+  g_return_val_if_fail (console->in_status_line, FALSE);
+
+  out = gs_console_get_stdout ();
+
+  if (!g_output_stream_write_all (out, &c, 1, &bytes_written,
+                                  cancellable, error))
+    goto out;
+
+  console->in_status_line = FALSE;
+
+  ret = TRUE;
+ out:
+  return ret;
+#else
+  g_error ("not implemented");
+#endif
+}
diff --git a/gsystem-console.h b/gsystem-console.h
new file mode 100644
index 0000000..a22b2d4
--- /dev/null
+++ b/gsystem-console.h
@@ -0,0 +1,59 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2013 Colin Walters <walters verbum org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GSYSTEM_CONSOLE_H__
+#define __GSYSTEM_CONSOLE_H__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define GS_TYPE_CONSOLE         (gs_console_get_type ())
+#define GS_CONSOLE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GS_TYPE_CONSOLE, GSConsole))
+#define GS_IS_CONSOLE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GS_TYPE_CONSOLE))
+
+typedef struct _GSConsole GSConsole;
+
+GType            gs_console_get_type (void) G_GNUC_CONST;
+
+GInputStream *   gs_console_get_stdin (void);
+GOutputStream *  gs_console_get_stdout (void);
+GOutputStream *  gs_console_get_stderr (void);
+
+GSConsole *      gs_console_get (void);
+
+char *           gs_console_read_password (GSConsole     *console,
+                                           const char    *prompt,
+                                           GCancellable  *cancellable,
+                                           GError       **error);
+
+gboolean         gs_console_begin_status_line (GSConsole     *console,
+                                               const char    *line,
+                                               GCancellable  *cancellable,
+                                               GError       **error);
+
+gboolean         gs_console_end_status_line (GSConsole      *console,
+                                             GCancellable   *cancellable,
+                                             GError        **error);
+
+
+G_END_DECLS
+
+#endif
diff --git a/libgsystem.h b/libgsystem.h
index 8c45633..5871a4b 100644
--- a/libgsystem.h
+++ b/libgsystem.h
@@ -25,6 +25,7 @@
 
 G_BEGIN_DECLS
 
+#include <gsystem-console.h>
 #include <gsystem-file-utils.h>
 #include <gsystem-shutil.h>
 #include <gsystem-subprocess.h>



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]