glib-unix: New Unix-specific API
authorColin Walters <walters@verbum.org>
Wed, 16 Mar 2011 17:54:28 +0000 (13:54 -0400)
committerColin Walters <walters@verbum.org>
Wed, 27 Apr 2011 17:29:38 +0000 (13:29 -0400)
GLib historically has been designed to be "mostly" portable; there
are some functions only available on Unix like g_io_channel_unix_new(),
but these are typically paired with obvious counterparts for Win32.

However, as GLib is used not only by portable software, but components
targeting Unix (or even just Linux), there are a few cases where it
would be very convenient if GLib shipped built-in functionality.

This initial patch is a basic wrapper around pipe2(), including
fallbacks for older kernels.  This pairs well with the
existing g_spawn_*() API and its child_setup functionality.

However, in the future, I want to add a signal() wrapper here,
complete with proxying the signal to a mainloop.  I have initial code
for this, but doing it sanely (including factoring out gmain.c's
private worker thread), is a complex task, and I don't want to block
on that.

See also gwin32.h for Win32 specific functionality.

https://bugzilla.gnome.org/show_bug.cgi?id=644941

docs/reference/glib/glib-sections.txt
glib/Makefile.am
glib/glib-unix.c [new file with mode: 0644]
glib/glib-unix.h [new file with mode: 0644]
glib/glib.symbols
glib/gmain.c
glib/tests/Makefile.am
glib/tests/unix.c [new file with mode: 0644]

index 037f577..9873393 100644 (file)
@@ -1924,6 +1924,14 @@ g_win32_ftruncate
 
 </SECTION>
 
+<SECTION>
+<TITLE>UNIX Compatibility Functions</TITLE>
+<FILE>gunix</FILE>
+G_UNIX_ERROR
+g_unix_pipe_flags
+
+</SECTION>
+
 # Data Structures
 
 <SECTION>
index 6c53e17..ba76729 100644 (file)
@@ -206,6 +206,9 @@ libglib_2_0_la_SOURCES =    \
        gprintf.c               \
        gprintfint.h
 
+if OS_UNIX
+libglib_2_0_la_SOURCES += glib-unix.c
+endif  
 
 EXTRA_libglib_2_0_la_SOURCES = \
        giounix.c       \
@@ -219,6 +222,10 @@ glibinclude_HEADERS =   \
        glib-object.h   \
        glib.h
 
+if OS_UNIX
+glibinclude_HEADERS += glib-unix.h
+endif
+
 glibsubincludedir=$(includedir)/glib-2.0/glib
 glibsubinclude_HEADERS =   \
        galloca.h       \
diff --git a/glib/glib-unix.c b/glib/glib-unix.c
new file mode 100644 (file)
index 0000000..4006a0d
--- /dev/null
@@ -0,0 +1,135 @@
+/* GLIB - Library of useful routines for C programming
+ * Copyright (C) 2011 Red Hat, Inc.
+ *
+ * glib-unix.c: Unix specific API wrappers and convenience functions 
+ *
+ * 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.
+ *
+ * Authors: Colin Walters <walters@verbum.org>
+ */
+
+#include "config.h"
+
+#include "glib-unix.h"
+
+#include <string.h>
+
+/**
+ * SECTION:gunix
+ * @short_description: Unix-specific utilities and integration
+ * @include: glib-unix.h
+ *
+ * Most of GLib is intended to be portable; in constrast, this set of
+ * functions is designed for programs which explicitly target UNIX, or
+ * are using it to build higher level abstractions which would be
+ * conditionally compiled if the platform matches G_OS_UNIX.
+ *
+ * To use these functions, you must explicitly include the
+ * "glib-unix.h" header.
+ */
+
+GQuark
+g_unix_error_quark (void)
+{
+  return g_quark_from_static_string ("g-unix-error-quark");
+}
+
+static gboolean
+g_unix_set_error_from_errno (GError **error)
+{
+  int saved_errno = errno;
+  g_set_error_literal (error,
+                      G_UNIX_ERROR,
+                      0,
+                      g_strerror (errno));
+  errno = saved_errno;
+  return FALSE;
+}
+
+static gboolean
+g_unix_set_error_from_errno_saved (GError **error,
+                                   int      saved_errno)
+{
+  g_set_error_literal (error,
+                      G_UNIX_ERROR,
+                      0,
+                      g_strerror (saved_errno));
+  errno = saved_errno;
+  return FALSE;
+}
+
+/**
+ * g_unix_pipe_flags:
+ * @fds: Array of two integers
+ * @flags: Bitfield of file descriptor flags, see "man 2 fcntl"
+ * @error: a #GError
+ *
+ * Similar to the Unix pipe() call, but on modern systems like
+ * Linux uses the pipe2 system call, which atomically creates
+ * a pipe with the configured flags.  The only supported flag
+ * currently is FD_CLOEXEC.  If for example you want to configure
+ * O_NONBLOCK, that must still be done separately with fcntl().
+ *
+ * Note in particular this function does *not* take O_CLOEXEC, it
+ * takes FD_CLOEXEC as if for fcntl(); these are different on
+ * Linux/glibc.
+ *
+ * Returns: %TRUE on success, %FALSE if not (and errno will be set).
+ *
+ * Since: 2.30
+ */
+gboolean
+g_unix_pipe_flags (int     *fds,
+                  int      flags,
+                  GError **error)
+{
+  int ecode;
+
+  /* We only support FD_CLOEXEC */
+  g_return_val_if_fail ((flags & (FD_CLOEXEC)) == flags, FALSE);
+
+#ifdef HAVE_PIPE2
+  {
+    int pipe2_flags = 0;
+    if (flags & FD_CLOEXEC)
+      pipe2_flags |= O_CLOEXEC;
+    /* Atomic */
+    ecode = pipe2 (fds, pipe2_flags);
+    if (ecode == -1 && errno != ENOSYS)
+      return g_unix_set_error_from_errno (error);
+    /* Fall through on -ENOSYS, we must be running on an old kernel */
+  }
+#endif
+  ecode = pipe (fds);
+  if (ecode == -1)
+    return g_unix_set_error_from_errno (error);
+  ecode = fcntl (fds[0], flags);
+  if (ecode == -1)
+    {
+      int saved_errno = errno;
+      close (fds[0]);
+      return g_unix_set_error_from_errno_saved (error, saved_errno);
+    }
+  ecode = fcntl (fds[0], flags);
+  if (ecode == -1)
+    {
+      int saved_errno = errno;
+      close (fds[0]);
+      close (fds[1]);
+      return g_unix_set_error_from_errno_saved (error, saved_errno);
+    }
+  return TRUE;
+}
diff --git a/glib/glib-unix.h b/glib/glib-unix.h
new file mode 100644 (file)
index 0000000..f2f784d
--- /dev/null
@@ -0,0 +1,70 @@
+/* glib-unix.h - Unix specific integration
+ * Copyright (C) 2011 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library 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 __G_UNIX_H__
+#define __G_UNIX_H__
+
+/* We need to include the UNIX headers needed to use the APIs below,
+ * but we also take this opportunity to include a wide selection of
+ * other UNIX headers.  If one of the headers below is broken on some
+ * system, work around it here (or better, fix the system or tell
+ * people to use a better one).
+ */
+#ifndef _GNU_SOURCE
+#define _G_GNU_SOURCE_TEMPORARILY_DEFINED
+#define _GNU_SOURCE
+#endif
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <errno.h>
+#include <sys/wait.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#ifdef _G_GNU_SOURCE_TEMPORARILY_DEFINED
+#undef _GNU_SOURCE
+#undef _G_GNU_SOURCE_TEMPORARILY_DEFINED
+#endif
+
+#include <glib.h>
+
+/**
+ * G_UNIX_ERROR:
+ * 
+ * Error domain for API in the "g_unix_" namespace.  Note that there
+ * is no exported enumeration mapping "errno".  Instead, all functions
+ * ensure that "errno" is relevant.  The code for all G_UNIX_ERROR is
+ * always 0, and the error message is always generated via
+ * g_strerror().
+ *
+ * It is expected that most code will not look at "errno" from these
+ * APIs. Important cases where one would want to differentiate between
+ * errors are already covered by existing cross-platform GLib API,
+ * such as e.g. GFile wrapping "ENOENT".  However, it is provided for
+ * completeness, at least.
+ */
+#define G_UNIX_ERROR (g_unix_error_quark())
+
+GQuark g_unix_error_quark (void);
+
+gboolean g_unix_pipe_flags (int      *fds,
+                           int       flags,
+                           GError  **error);
+
+#endif
index 6c86667..be3acfa 100644 (file)
@@ -1978,6 +1978,15 @@ g_hostname_to_unicode
 #endif
 #endif
 
+#if IN_HEADER(__G_UNIX_H__)
+#if IN_FILE(__G_UNIX_C__)
+#ifdef G_OS_UNIX
+g_unix_pipe_flags
+g_unix_error_quark
+#endif
+#endif
+#endif
+
 #ifdef INCLUDE_VARIABLES
 g_ascii_table
 g_utf8_skip
index 718c36c..c045e1c 100644 (file)
 #define G_MAIN_POLL_DEBUG
 #endif
 
+#ifdef G_OS_UNIX
+#include "glib-unix.h"
+#endif
+
 #include <signal.h>
 #include <sys/types.h>
 #include <time.h>
 #include <sys/wait.h>
 #endif /* G_OS_BEOS */
 
-#ifdef G_OS_UNIX
-#include <fcntl.h>
-#include <sys/wait.h>
-#endif
-
 #include "gmain.h"
 
 #include "garray.h"
@@ -515,23 +514,14 @@ g_main_context_unref (GMainContext *context)
 static void 
 g_main_context_init_pipe (GMainContext *context)
 {
+  GError *error = NULL;
+
 # ifndef G_OS_WIN32
   if (context->wake_up_pipe[0] != -1)
     return;
 
-#ifdef HAVE_PIPE2
-  /* if this fails, we fall through and try pipe */
-  pipe2 (context->wake_up_pipe, O_CLOEXEC);
-#endif
-  if (context->wake_up_pipe[0] == -1)
-    {
-      if (pipe (context->wake_up_pipe) < 0)
-        g_error ("Cannot create pipe main loop wake-up: %s\n",
-                g_strerror (errno));
-      fcntl (context->wake_up_pipe[0], F_SETFD, FD_CLOEXEC);
-      fcntl (context->wake_up_pipe[1], F_SETFD, FD_CLOEXEC);
-    }
+  if (!g_unix_pipe_flags (context->wake_up_pipe, FD_CLOEXEC, &error))
+    g_error ("Cannot create pipe main loop wake-up: %s", error->message);
 
   context->wake_up_rec.fd = context->wake_up_pipe[0];
   context->wake_up_rec.events = G_IO_IN;
@@ -4334,8 +4324,8 @@ g_child_watch_source_init_multi_threaded (void)
 
   g_assert (g_thread_supported());
 
-  if (pipe (child_watch_wake_up_pipe) < 0)
-    g_error ("Cannot create wake up pipe: %s\n", g_strerror (errno));
+  if (!g_unix_pipe_flags (child_watch_wake_up_pipe, FD_CLOEXEC, &error))
+    g_error ("Cannot create wake up pipe: %s\n", error->message);
   fcntl (child_watch_wake_up_pipe[1], F_SETFL, O_NONBLOCK | fcntl (child_watch_wake_up_pipe[1], F_GETFL));
 
   /* We create a helper thread that polls on the wakeup pipe indefinitely */
index ea7da1f..ca42eb4 100644 (file)
@@ -170,6 +170,9 @@ sort_LDADD     = $(progs_ldadd)
 
 if OS_UNIX
 
+TEST_PROGS    += unix
+unix_LDADD  = $(progs_ldadd)
+
 # some testing of gtester funcitonality
 XMLLINT=xmllint
 gtester-xmllint-check: # check testreport xml with xmllint if present
diff --git a/glib/tests/unix.c b/glib/tests/unix.c
new file mode 100644 (file)
index 0000000..2dc2364
--- /dev/null
@@ -0,0 +1,60 @@
+/* 
+ * Copyright (C) 2011 Red Hat, Inc.
+ *
+ * This work is provided "as is"; redistribution and modification
+ * in whole or in part, in any medium, physical or electronic is
+ * permitted without restriction.
+ *
+ * This work 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.
+ *
+ * In no event shall the authors or contributors be liable for any
+ * direct, indirect, incidental, special, exemplary, or consequential
+ * damages (including, but not limited to, procurement of substitute
+ * goods or services; loss of use, data, or profits; or business
+ * interruption) however caused and on any theory of liability, whether
+ * in contract, strict liability, or tort (including negligence or
+ * otherwise) arising in any way out of the use of this software, even
+ * if advised of the possibility of such damage.
+ *
+ * Author: Colin Walters <walters@verbum.org> 
+ */
+
+#include "config.h"
+
+#include "glib-unix.h"
+#include <string.h>
+
+static void
+test_pipe (void)
+{
+  GError *error = NULL;
+  int pipefd[2];
+  char buf[1024];
+  ssize_t bytes_read;
+
+  g_unix_pipe_flags (pipefd, FD_CLOEXEC, NULL);
+  g_assert_no_error (error);
+    
+  write (pipefd[1], "hello", sizeof ("hello"));
+  memset (buf, 0, sizeof (buf));
+  bytes_read = read (pipefd[0], buf, sizeof(buf) - 1);
+  g_assert_cmpint (bytes_read, >, 0);
+  
+  close (pipefd[0]);
+  close (pipefd[1]);
+
+  g_assert (g_str_has_prefix (buf, "hello"));
+}
+
+int
+main (int   argc,
+      char *argv[])
+{
+  g_test_init (&argc, &argv, NULL);
+
+  g_test_add_func ("/glib-unix/pipe", test_pipe);
+
+  return g_test_run();
+}