summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorColin Walters <walters@verbum.org>2014-02-05 06:58:51 -0500
committerColin Walters <walters@verbum.org>2014-02-06 04:12:18 -0500
commit9363cfc28ede912e2f06d4ccb42a646bb8a4bd2e (patch)
tree94d03e2391e4cdee87a8345013e3ac5b6e21cef4 /src
parent856b8f9431a63c7807fb3859ed6de2a0f3abfb3b (diff)
downloadlibgsystem-9363cfc28ede912e2f06d4ccb42a646bb8a4bd2e.tar.gz
Rework to be an installed libraryv2014.1
See https://mail.gnome.org/archives/desktop-devel-list/2014-February/msg00028.html
Diffstat (limited to 'src')
-rw-r--r--src/gsystem-console.c443
-rw-r--r--src/gsystem-console.h59
-rw-r--r--src/gsystem-file-utils.c1644
-rw-r--r--src/gsystem-file-utils.h173
-rw-r--r--src/gsystem-glib-compat.h54
-rw-r--r--src/gsystem-local-alloc.c72
-rw-r--r--src/gsystem-local-alloc.h164
-rw-r--r--src/gsystem-log.c167
-rw-r--r--src/gsystem-log.h42
-rw-r--r--src/gsystem-shutil.c460
-rw-r--r--src/gsystem-shutil.h47
-rw-r--r--src/gsystem-subprocess-context-private.h65
-rw-r--r--src/gsystem-subprocess-context.c501
-rw-r--r--src/gsystem-subprocess-context.h128
-rw-r--r--src/gsystem-subprocess.c966
-rw-r--r--src/gsystem-subprocess.h101
-rw-r--r--src/libgsystem.h49
-rw-r--r--src/libgsystem.pc.in11
18 files changed, 5146 insertions, 0 deletions
diff --git a/src/gsystem-console.c b/src/gsystem-console.c
new file mode 100644
index 0000000..35477eb
--- /dev/null
+++ b/src/gsystem-console.c
@@ -0,0 +1,443 @@
+/* -*- 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"
+
+#define _GSYSTEM_NO_LOCAL_ALLOC
+#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[3] = { (guint8)'\n', 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 < (gsize) 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;
+ towrite--;
+ }
+ }
+
+ 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/src/gsystem-console.h b/src/gsystem-console.h
new file mode 100644
index 0000000..a22b2d4
--- /dev/null
+++ b/src/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/src/gsystem-file-utils.c b/src/gsystem-file-utils.c
new file mode 100644
index 0000000..0260603
--- /dev/null
+++ b/src/gsystem-file-utils.c
@@ -0,0 +1,1644 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2012 William Jon McCann <mccann@redhat.com>
+ * Copyright (C) 2012 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"
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <string.h>
+
+#define _GSYSTEM_NO_LOCAL_ALLOC
+#include "libgsystem.h"
+#include "gsystem-glib-compat.h"
+#include <glib/gstdio.h>
+#include <gio/gunixinputstream.h>
+#include <gio/gfiledescriptorbased.h>
+#include <gio/gunixoutputstream.h>
+#include <glib-unix.h>
+#include <limits.h>
+#include <dirent.h>
+#ifdef GSYSTEM_CONFIG_XATTRS
+#include <attr/xattr.h>
+#endif
+
+static int
+close_nointr (int fd)
+{
+ int res;
+ /* Note this is NOT actually a retry loop.
+ * See: https://bugzilla.gnome.org/show_bug.cgi?id=682819
+ */
+ res = close (fd);
+ /* Just ignore EINTR...on Linux, retrying is wrong. */
+ if (res == EINTR)
+ res = 0;
+ return res;
+}
+
+static void
+close_nointr_noerror (int fd)
+{
+ (void) close_nointr (fd);
+}
+
+static int
+open_nointr (const char *path, int flags, mode_t mode)
+{
+ int res;
+ do
+ res = open (path, flags, mode);
+ while (G_UNLIKELY (res == -1 && errno == EINTR));
+ return res;
+}
+
+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));
+}
+
+/**
+ * gs_file_openat_noatime:
+ * @dfd: File descriptor for directory
+ * @name: Pathname, relative to @dfd
+ * @ret_fd: (out): Returned file descriptor
+ * @cancellable: Cancellable
+ * @error: Error
+ *
+ * Wrapper for openat() using %O_RDONLY with %O_NOATIME if available.
+ */
+gboolean
+gs_file_openat_noatime (int dfd,
+ const char *name,
+ int *ret_fd,
+ GCancellable *cancellable,
+ GError **error)
+{
+ int fd;
+
+#ifdef O_NOATIME
+ do
+ fd = openat (dfd, name, O_RDONLY | O_NOATIME, 0);
+ while (G_UNLIKELY (fd == -1 && errno == EINTR));
+ /* Only the owner or superuser may use O_NOATIME; so we may get
+ * EPERM. EINVAL may happen if the kernel is really old...
+ */
+ if (fd == -1 && (errno == EPERM || errno == EINVAL))
+#endif
+ do
+ fd = openat (dfd, name, O_RDONLY, 0);
+ while (G_UNLIKELY (fd == -1 && errno == EINTR));
+
+ if (fd == -1)
+ {
+ _set_error_from_errno (error);
+ return FALSE;
+ }
+ else
+ {
+ *ret_fd = fd;
+ return TRUE;
+ }
+}
+
+/**
+ * gs_file_read_noatime:
+ * @file: a #GFile
+ * @cancellable: a #GCancellable
+ * @error: a #GError
+ *
+ * Like g_file_read(), but try to avoid updating the file's
+ * access time. This should be used by background scanning
+ * components such as search indexers, antivirus programs, etc.
+ *
+ * Returns: (transfer full): A new input stream, or %NULL on error
+ */
+GInputStream *
+gs_file_read_noatime (GFile *file,
+ GCancellable *cancellable,
+ GError **error)
+{
+ const char *path = NULL;
+ int fd;
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return NULL;
+
+ path = gs_file_get_path_cached (file);
+ if (path == NULL)
+ {
+ char *uri;
+ uri = g_file_get_uri (file);
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_NOENT,
+ "%s has no associated path", uri);
+ g_free (uri);
+ return NULL;
+ }
+
+ if (!gs_file_openat_noatime (AT_FDCWD, path, &fd, cancellable, error))
+ return NULL;
+
+ return g_unix_input_stream_new (fd, TRUE);
+}
+
+/**
+ * gs_stream_fstat:
+ * @stream: A stream containing a Unix file descriptor
+ * @stbuf: Memory location to write stat buffer
+ * @cancellable:
+ * @error:
+ *
+ * Some streams created via libgsystem are #GUnixInputStream; these do
+ * not support e.g. g_file_input_stream_query_info(). This function
+ * allows dropping to the raw unix fstat() call for these types of
+ * streams, while still conveniently wrapped with the normal GLib
+ * handling of @cancellable and @error.
+ */
+gboolean
+gs_stream_fstat (GFileDescriptorBased *stream,
+ struct stat *stbuf,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ int fd;
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ goto out;
+
+ fd = g_file_descriptor_based_get_fd (stream);
+
+ if (fstat (fd, stbuf) == -1)
+ {
+ _set_error_from_errno (error);
+ goto out;
+ }
+
+ ret = TRUE;
+ out:
+ return ret;
+}
+
+/**
+ * gs_file_map_noatime: (skip)
+ * @file: a #GFile
+ * @cancellable: a #GCancellable
+ * @error: a #GError
+ *
+ * Like g_mapped_file_new(), but try to avoid updating the file's
+ * access time. This should be used by background scanning
+ * components such as search indexers, antivirus programs, etc.
+ *
+ * Returns: (transfer full): A new mapped file, or %NULL on error
+ */
+GMappedFile *
+gs_file_map_noatime (GFile *file,
+ GCancellable *cancellable,
+ GError **error)
+{
+ const char *path;
+ int fd;
+ GMappedFile *ret;
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return NULL;
+
+ path = gs_file_get_path_cached (file);
+ if (path == NULL)
+ return NULL;
+
+ if (!gs_file_openat_noatime (AT_FDCWD, path, &fd, cancellable, error))
+ return NULL;
+
+ ret = g_mapped_file_new_from_fd (fd, FALSE, error);
+ close_nointr_noerror (fd); /* Ignore errors - we always want to close */
+
+ return ret;
+}
+
+#if GLIB_CHECK_VERSION(2,34,0)
+/**
+ * gs_file_map_readonly:
+ * @file: a #GFile
+ * @cancellable:
+ * @error:
+ *
+ * Return a #GBytes which references a readonly view of the contents of
+ * @file. This function uses #GMappedFile internally.
+ *
+ * Returns: (transfer full): a newly referenced #GBytes
+ */
+GBytes *
+gs_file_map_readonly (GFile *file,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GMappedFile *mfile;
+ GBytes *ret;
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return NULL;
+
+ mfile = g_mapped_file_new (gs_file_get_path_cached (file), FALSE, error);
+ if (!mfile)
+ return NULL;
+
+ ret = g_mapped_file_get_bytes (mfile);
+ g_mapped_file_unref (mfile);
+ return ret;
+}
+#endif
+
+/**
+ * gs_file_sync_data:
+ * @file: a #GFile
+ * @cancellable:
+ * @error:
+ *
+ * Wraps the UNIX fsync() function (or fdatasync(), if available), which
+ * ensures that the data in @file is on non-volatile storage.
+ */
+gboolean
+gs_file_sync_data (GFile *file,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ int res;
+ int fd = -1;
+
+ if (!gs_file_openat_noatime (AT_FDCWD, gs_file_get_path_cached (file), &fd,
+ cancellable, error))
+ goto out;
+
+ do
+ {
+#ifdef __linux
+ res = fdatasync (fd);
+#else
+ res = fsync (fd);
+#endif
+ }
+ while (G_UNLIKELY (res != 0 && errno == EINTR));
+ if (res != 0)
+ {
+ _set_error_from_errno (error);
+ goto out;
+ }
+
+ res = close_nointr (fd);
+ if (res != 0)
+ {
+ _set_error_from_errno (error);
+ goto out;
+ }
+ fd = -1;
+
+ ret = TRUE;
+ out:
+ if (fd != -1)
+ close_nointr_noerror (fd);
+ return ret;
+}
+
+/**
+ * gs_file_create:
+ * @file: Path to non-existent file
+ * @mode: Unix access permissions
+ * @out_stream: (out) (transfer full) (allow-none): Newly created output, or %NULL
+ * @cancellable: a #GCancellable
+ * @error: a #GError
+ *
+ * Like g_file_create(), except this function allows specifying the
+ * access mode. This allows atomically creating private files.
+ */
+gboolean
+gs_file_create (GFile *file,
+ int mode,
+ GOutputStream **out_stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ int fd;
+ GOutputStream *ret_stream = NULL;
+
+ fd = open_nointr (gs_file_get_path_cached (file), O_WRONLY | O_CREAT | O_EXCL, mode);
+ if (fd < 0)
+ {
+ _set_error_from_errno (error);
+ goto out;
+ }
+
+ if (fchmod (fd, mode) < 0)
+ {
+ close (fd);
+ _set_error_from_errno (error);
+ goto out;
+ }
+
+ ret_stream = g_unix_output_stream_new (fd, TRUE);
+
+ ret = TRUE;
+ gs_transfer_out_value (out_stream, &ret_stream);
+ out:
+ g_clear_object (&ret_stream);
+ return ret;
+}
+
+static const char *
+get_default_tmp_prefix (void)
+{
+ static char *tmpprefix = NULL;
+
+ if (g_once_init_enter (&tmpprefix))
+ {
+ const char *prgname = g_get_prgname ();
+ const char *p;
+ char *prefix;
+ char *iter;
+
+ if (prgname)
+ {
+ p = strrchr (prgname, '/');
+ if (p)
+ prgname = p + 1;
+ }
+ else
+ prgname = "";
+
+ prefix = g_strdup_printf ("tmp-%s%u-", prgname, getuid ());
+ for (iter = prefix; *iter; iter++)
+ {
+ char c = *iter;
+ if (c == ' ')
+ *iter = '_';
+ }
+
+ g_once_init_leave (&tmpprefix, prefix);
+ }
+
+ return tmpprefix;
+}
+
+/**
+ * gs_fileutil_gen_tmp_name:
+ * @prefix: (allow-none): String prepended to the result
+ * @suffix: (allow-none): String suffixed to the result
+ *
+ * Generate a name suitable for use as a temporary file. This
+ * function does no I/O; it is not guaranteed that a file with that
+ * name does not exist.
+ */
+char *
+gs_fileutil_gen_tmp_name (const char *prefix,
+ const char *suffix)
+{
+ static const char table[] = "ABCEDEFGHIJKLMNOPQRSTUVWXYZabcedefghijklmnopqrstuvwxyz0123456789";
+ GString *str = g_string_new ("");
+ guint i;
+
+ if (!prefix)
+ prefix = get_default_tmp_prefix ();
+ if (!suffix)
+ suffix = "tmp";
+
+ g_string_append (str, prefix);
+ for (i = 0; i < 8; i++)
+ {
+ int offset = g_random_int_range (0, sizeof (table) - 1);
+ g_string_append_c (str, (guint8)table[offset]);
+ }
+ g_string_append_c (str, '.');
+ g_string_append (str, suffix);
+
+ return g_string_free (str, FALSE);
+}
+
+/**
+ * gs_file_open_dir_fd:
+ * @path: Directory name
+ * @out_fd: (out): File descriptor for directory
+ * @cancellable: Cancellable
+ * @error: Error
+ *
+ * On success, sets @out_fd to a file descriptor for the directory
+ * that can be used with UNIX functions such as openat().
+ */
+gboolean
+gs_file_open_dir_fd (GFile *path,
+ int *out_fd,
+ GCancellable *cancellable,
+ GError **error)
+{
+ /* Linux specific probably */
+ *out_fd = open (gs_file_get_path_cached (path), O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC);
+ if (*out_fd == -1)
+ {
+ _set_error_from_errno (error);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/**
+ * gs_file_open_in_tmpdir_at:
+ * @tmpdir_fd: Directory to place temporary file
+ * @mode: Default mode (will be affected by umask)
+ * @out_name: (out) (transfer full): Newly created file name
+ * @out_stream: (out) (transfer full) (allow-none): Newly created output stream
+ * @cancellable:
+ * @error:
+ *
+ * Like g_file_open_tmp(), except the file will be created in the
+ * provided @tmpdir, and allows specification of the Unix @mode, which
+ * means private files may be created. Return values will be stored
+ * in @out_name, and optionally @out_stream.
+ */
+gboolean
+gs_file_open_in_tmpdir_at (int tmpdir_fd,
+ int mode,
+ char **out_name,
+ GOutputStream **out_stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ const int max_attempts = 128;
+ int i;
+ char *tmp_name = NULL;
+ int fd;
+
+ /* 128 attempts seems reasonable... */
+ for (i = 0; i < max_attempts; i++)
+ {
+ g_free (tmp_name);
+ tmp_name = gs_fileutil_gen_tmp_name (NULL, NULL);
+
+ do
+ fd = openat (tmpdir_fd, tmp_name, O_WRONLY | O_CREAT | O_EXCL, mode);
+ while (fd == -1 && errno == EINTR);
+ if (fd < 0 && errno != EEXIST)
+ {
+ _set_error_from_errno (error);
+ goto out;
+ }
+ else if (fd != -1)
+ break;
+ }
+ if (i == max_attempts)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Exhausted attempts to open temporary file");
+ goto out;
+ }
+
+ ret = TRUE;
+ gs_transfer_out_value (out_name, &tmp_name);
+ if (out_stream)
+ *out_stream = g_unix_output_stream_new (fd, TRUE);
+ else
+ (void) close (fd);
+ out:
+ g_free (tmp_name);
+ return ret;
+}
+
+/**
+ * gs_file_open_in_tmpdir:
+ * @tmpdir: Directory to place temporary file
+ * @mode: Default mode (will be affected by umask)
+ * @out_file: (out) (transfer full): Newly created file path
+ * @out_stream: (out) (transfer full) (allow-none): Newly created output stream
+ * @cancellable:
+ * @error:
+ *
+ * Like g_file_open_tmp(), except the file will be created in the
+ * provided @tmpdir, and allows specification of the Unix @mode, which
+ * means private files may be created. Return values will be stored
+ * in @out_file, and optionally @out_stream.
+ */
+gboolean
+gs_file_open_in_tmpdir (GFile *tmpdir,
+ int mode,
+ GFile **out_file,
+ GOutputStream **out_stream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ DIR *d = NULL;
+ int dfd = -1;
+ char *tmp_name = NULL;
+ GOutputStream *ret_stream = NULL;
+
+ d = opendir (gs_file_get_path_cached (tmpdir));
+ if (!d)
+ {
+ _set_error_from_errno (error);
+ goto out;
+ }
+ dfd = dirfd (d);
+
+ if (!gs_file_open_in_tmpdir_at (dfd, mode, &tmp_name,
+ out_stream ? &ret_stream : NULL,
+ cancellable, error))
+ goto out;
+
+ ret = TRUE;
+ *out_file = g_file_get_child (tmpdir, tmp_name);
+ gs_transfer_out_value (out_stream, &ret_stream);
+ out:
+ if (d) (void) closedir (d);
+ g_clear_object (&ret_stream);
+ g_free (tmp_name);
+ return ret;
+}
+
+static gboolean
+linkcopy_internal_attempt (GFile *src,
+ GFile *dest,
+ GFile *dest_parent,
+ GFileCopyFlags flags,
+ gboolean sync_data,
+ gboolean enable_guestfs_fuse_workaround,
+ gboolean *out_try_again,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ int res;
+ char *tmp_name = NULL;
+ GFile *tmp_dest = NULL;
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ goto out;
+
+ tmp_name = gs_fileutil_gen_tmp_name (NULL, NULL);
+ tmp_dest = g_file_get_child (dest_parent, tmp_name);
+
+ res = link (gs_file_get_path_cached (src), gs_file_get_path_cached (tmp_dest));
+ if (res == -1)
+ {
+ if (errno == EEXIST)
+ {
+ /* Nothing, fall through */
+ *out_try_again = TRUE;
+ ret = TRUE;
+ goto out;
+ }
+ else if (errno == EXDEV || errno == EMLINK || errno == EPERM
+ || (enable_guestfs_fuse_workaround && errno == ENOENT))
+ {
+ if (!g_file_copy (src, tmp_dest, flags,
+ cancellable, NULL, NULL, error))
+ goto out;
+ }
+ else
+ {
+ _set_error_from_errno (error);
+ goto out;
+ }
+ }
+
+ if (sync_data)
+ {
+ /* Now, we need to fsync */
+ if (!gs_file_sync_data (tmp_dest, cancellable, error))
+ goto out;
+ }
+
+ if (!gs_file_rename (tmp_dest, dest, cancellable, error))
+ goto out;
+
+ ret = TRUE;
+ *out_try_again = FALSE;
+ out:
+ g_clear_pointer (&tmp_name, g_free);
+ g_clear_object (&tmp_dest);
+ return ret;
+}
+
+static gboolean
+linkcopy_internal (GFile *src,
+ GFile *dest,
+ GFileCopyFlags flags,
+ gboolean sync_data,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ gboolean dest_exists;
+ int i;
+ gboolean enable_guestfs_fuse_workaround;
+ struct stat src_stat;
+ struct stat dest_stat;
+ GFile *dest_parent = NULL;
+
+ flags |= G_FILE_COPY_NOFOLLOW_SYMLINKS;
+
+ g_return_val_if_fail ((flags & (G_FILE_COPY_BACKUP | G_FILE_COPY_TARGET_DEFAULT_PERMS)) == 0, FALSE);
+
+ dest_parent = g_file_get_parent (dest);
+
+ if (lstat (gs_file_get_path_cached (src), &src_stat) == -1)
+ {
+ int errsv = errno;
+ g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errno),
+ g_strerror (errsv));
+ goto out;
+ }
+
+ if (lstat (gs_file_get_path_cached (dest), &dest_stat) == -1)
+ dest_exists = FALSE;
+ else
+ dest_exists = TRUE;
+
+ if (((flags & G_FILE_COPY_OVERWRITE) == 0) && dest_exists)
+ {
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_EXISTS,
+ "File exists");
+ goto out;
+ }
+
+ /* Work around the behavior of link() where it's a no-op if src and
+ * dest are the same.
+ */
+ if (dest_exists &&
+ src_stat.st_dev == dest_stat.st_dev &&
+ src_stat.st_ino == dest_stat.st_ino)
+ {
+ ret = TRUE;
+ goto out;
+ }
+
+ enable_guestfs_fuse_workaround = getenv ("LIBGSYSTEM_ENABLE_GUESTFS_FUSE_WORKAROUND") != NULL;
+
+ /* 128 attempts seems reasonable... */
+ for (i = 0; i < 128; i++)
+ {
+ gboolean tryagain = FALSE;
+
+ if (!linkcopy_internal_attempt (src, dest, dest_parent,
+ flags, sync_data,
+ enable_guestfs_fuse_workaround,
+ &tryagain,
+ cancellable, error))
+ goto out;
+
+ if (!tryagain)
+ break;
+ }
+
+ ret = TRUE;
+ out:
+ g_clear_object (&dest_parent);
+ return ret;
+
+}
+
+/**
+ * gs_file_linkcopy:
+ * @src: Source file
+ * @dest: Destination file
+ * @flags: flags
+ * @cancellable:
+ * @error:
+ *
+ * First tries to use the UNIX link() call, but if the files are on
+ * separate devices, fall back to copying via g_file_copy().
+ *
+ * The given @flags have different semantics than those documented
+ * when hardlinking is used. Specifically, both
+ * #G_FILE_COPY_TARGET_DEFAULT_PERMS and #G_FILE_COPY_BACKUP are not
+ * supported. #G_FILE_COPY_NOFOLLOW_SYMLINKS treated as if it was
+ * always given - if you want to follow symbolic links, you will need
+ * to resolve them manually.
+ *
+ * Beware - do not use this function if @src may be modified, and it's
+ * undesirable for the changes to also be reflected in @dest. The
+ * best use of this function is in the case where @src and @dest are
+ * read-only, or where @src is a temporary file, and you want to put
+ * it in the final place.
+ */
+gboolean
+gs_file_linkcopy (GFile *src,
+ GFile *dest,
+ GFileCopyFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ return linkcopy_internal (src, dest, flags, FALSE, cancellable, error);
+}
+
+/**
+ * gs_file_linkcopy_sync_data:
+ * @src: Source file
+ * @dest: Destination file
+ * @flags: flags
+ * @cancellable:
+ * @error:
+ *
+ * This function is similar to gs_file_linkcopy(), except it also uses
+ * gs_file_sync_data() to ensure that @dest is in stable storage
+ * before it is moved into place.
+ */
+gboolean
+gs_file_linkcopy_sync_data (GFile *src,
+ GFile *dest,
+ GFileCopyFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ return linkcopy_internal (src, dest, flags, TRUE, cancellable, error);
+}
+
+static char *
+gs_file_get_target_path (GFile *file)
+{
+ GFileInfo *info;
+ const char *target;
+ char *path;
+
+ info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI, G_FILE_QUERY_INFO_NONE, NULL, NULL);
+ if (info == NULL)
+ return NULL;
+ target = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI);
+ path = g_filename_from_uri (target, NULL, NULL);
+ g_object_unref (info);
+
+ return path;
+}
+
+G_LOCK_DEFINE_STATIC (pathname_cache);
+
+/**
+ * gs_file_get_path_cached:
+ *
+ * Like g_file_get_path(), but returns a constant copy so callers
+ * don't need to free the result.
+ */
+const char *
+gs_file_get_path_cached (GFile *file)
+{
+ const char *path;
+ static GQuark _file_path_quark = 0;
+
+ if (G_UNLIKELY (_file_path_quark) == 0)
+ _file_path_quark = g_quark_from_static_string ("gsystem-file-path");
+
+ G_LOCK (pathname_cache);
+
+ path = g_object_get_qdata ((GObject*)file, _file_path_quark);
+ if (!path)
+ {
+ if (g_file_has_uri_scheme (file, "trash") ||
+ g_file_has_uri_scheme (file, "recent"))
+ path = gs_file_get_target_path (file);
+ else
+ path = g_file_get_path (file);
+ if (path == NULL)
+ {
+ G_UNLOCK (pathname_cache);
+ return NULL;
+ }
+ g_object_set_qdata_full ((GObject*)file, _file_path_quark, (char*)path, (GDestroyNotify)g_free);
+ }
+
+ G_UNLOCK (pathname_cache);
+
+ return path;
+}
+
+/**
+ * gs_file_get_basename_cached:
+ *
+ * Like g_file_get_basename(), but returns a constant copy so callers
+ * don't need to free the result.
+ */
+const char *
+gs_file_get_basename_cached (GFile *file)
+{
+ const char *name;
+ static GQuark _file_name_quark = 0;
+
+ if (G_UNLIKELY (_file_name_quark) == 0)
+ _file_name_quark = g_quark_from_static_string ("gsystem-file-name");
+
+ G_LOCK (pathname_cache);
+
+ name = g_object_get_qdata ((GObject*)file, _file_name_quark);
+ if (!name)
+ {
+ name = g_file_get_basename (file);
+ g_object_set_qdata_full ((GObject*)file, _file_name_quark, (char*)name, (GDestroyNotify)g_free);
+ }
+
+ G_UNLOCK (pathname_cache);
+
+ return name;
+}
+
+/**
+ * gs_file_enumerator_iterate:
+ * @direnum: an open #GFileEnumerator
+ * @out_info: (out) (transfer none) (allow-none): Output location for the next #GFileInfo
+ * @out_child: (out) (transfer none) (allow-none): Output location for the next #GFile, or %NULL
+ * @cancellable: a #GCancellable
+ * @error: a #GError
+ *
+ * This is a version of g_file_enumerator_next_file() that's easier to
+ * use correctly from C programs. With g_file_enumerator_next_file(),
+ * the gboolean return value signifies "end of iteration or error", which
+ * requires allocation of a temporary #GError.
+ *
+ * In contrast, with this function, a %FALSE return from
+ * gs_file_enumerator_iterate() <emphasis>always</emphasis> means
+ * "error". End of iteration is signaled by @out_info being %NULL.
+ *
+ * Another crucial difference is that the references for @out_info and
+ * @out_child are owned by @direnum (they are cached as hidden
+ * properties). You must not unref them in your own code. This makes
+ * memory management significantly easier for C code in combination
+ * with loops.
+ *
+ * Finally, this function optionally allows retrieving a #GFile as
+ * well.
+ *
+ * The code pattern for correctly using gs_file_enumerator_iterate() from C
+ * is:
+ *
+ * |[
+ * direnum = g_file_enumerate_children (file, ...);
+ * while (TRUE)
+ * {
+ * GFileInfo *info;
+ * if (!gs_file_enumerator_iterate (direnum, &info, NULL, cancellable, error))
+ * goto out;
+ * if (!info)
+ * break;
+ * ... do stuff with "info"; do not unref it! ...
+ * }
+ *
+ * out:
+ * g_object_unref (direnum); // Note: frees the last @info
+ * ]|
+ */
+gboolean
+gs_file_enumerator_iterate (GFileEnumerator *direnum,
+ GFileInfo **out_info,
+ GFile **out_child,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ GError *temp_error = NULL;
+
+ static GQuark cached_info_quark;
+ static GQuark cached_child_quark;
+ static gsize quarks_initialized;
+
+ g_return_val_if_fail (direnum != NULL, FALSE);
+ g_return_val_if_fail (out_info != NULL, FALSE);
+
+ if (g_once_init_enter (&quarks_initialized))
+ {
+ cached_info_quark = g_quark_from_static_string ("gsystem-cached-info");
+ cached_child_quark = g_quark_from_static_string ("gsystem-cached-child");
+ g_once_init_leave (&quarks_initialized, 1);
+ }
+
+
+ *out_info = g_file_enumerator_next_file (direnum, cancellable, &temp_error);
+ if (out_child)
+ *out_child = NULL;
+ if (temp_error != NULL)
+ {
+ g_propagate_error (error, temp_error);
+ goto out;
+ }
+ else if (*out_info != NULL)
+ {
+ g_object_set_qdata_full ((GObject*)direnum, cached_info_quark, *out_info, (GDestroyNotify)g_object_unref);
+ if (out_child != NULL)
+ {
+ const char *name = g_file_info_get_name (*out_info);
+ *out_child = g_file_get_child (g_file_enumerator_get_container (direnum), name);
+ g_object_set_qdata_full ((GObject*)direnum, cached_child_quark, *out_child, (GDestroyNotify)g_object_unref);
+ }
+ }
+
+ ret = TRUE;
+ out:
+ return ret;
+}
+
+/**
+ * gs_file_rename:
+ * @from: Current path
+ * @to: New path
+ * @cancellable: a #GCancellable
+ * @error: a #GError
+ *
+ * This function wraps the raw Unix function rename().
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ */
+gboolean
+gs_file_rename (GFile *from,
+ GFile *to,
+ GCancellable *cancellable,
+ GError **error)
+{
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return FALSE;
+
+ if (rename (gs_file_get_path_cached (from),
+ gs_file_get_path_cached (to)) < 0)
+ {
+ _set_error_from_errno (error);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/**
+ * gs_file_unlink:
+ * @path: Path to file
+ * @cancellable: a #GCancellable
+ * @error: a #GError
+ *
+ * Like g_file_delete(), except this function does not follow Unix
+ * symbolic links, and will delete a symbolic link even if it's
+ * pointing to a nonexistent file. In other words, this function
+ * merely wraps the raw Unix function unlink().
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ */
+gboolean
+gs_file_unlink (GFile *path,
+ GCancellable *cancellable,
+ GError **error)
+{
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return FALSE;
+
+ if (unlink (gs_file_get_path_cached (path)) < 0)
+ {
+ _set_error_from_errno (error);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static gboolean
+chown_internal (GFile *path,
+ gboolean dereference_links,
+ guint32 owner,
+ guint32 group,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ int res;
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return FALSE;
+
+ do
+ if (dereference_links)
+ res = chown (gs_file_get_path_cached (path), owner, group);
+ else
+ res = lchown (gs_file_get_path_cached (path), owner, group);
+ while (G_UNLIKELY (res != 0 && errno == EINTR));
+
+ if (res < 0)
+ {
+ _set_error_from_errno (error);
+ goto out;
+ }
+
+ ret = TRUE;
+ out:
+ return ret;
+}
+
+/**
+ * gs_file_chown:
+ * @path: Path to file
+ * @owner: UNIX owner
+ * @group: UNIX group
+ * @cancellable: a #GCancellable
+ * @error: a #GError
+ *
+ * Merely wraps UNIX chown().
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ */
+gboolean
+gs_file_chown (GFile *path,
+ guint32 owner,
+ guint32 group,
+ GCancellable *cancellable,
+ GError **error)
+{
+ return chown_internal (path, TRUE, owner, group, cancellable, error);
+}
+
+/**
+ * gs_file_lchown:
+ * @path: Path to file
+ * @owner: UNIX owner
+ * @group: UNIX group
+ * @cancellable: a #GCancellable
+ * @error: a #GError
+ *
+ * Merely wraps UNIX lchown().
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ */
+gboolean
+gs_file_lchown (GFile *path,
+ guint32 owner,
+ guint32 group,
+ GCancellable *cancellable,
+ GError **error)
+{
+ return chown_internal (path, FALSE, owner, group, cancellable, error);
+}
+
+/**
+ * gs_file_chmod:
+ * @path: Path to file
+ * @mode: UNIX mode
+ * @cancellable: a #GCancellable
+ * @error: a #GError
+ *
+ * Merely wraps UNIX chmod().
+ *
+ * Returns: %TRUE on success, %FALSE on error
+ */
+gboolean
+gs_file_chmod (GFile *path,
+ guint mode,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ int res;
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return FALSE;
+
+ do
+ res = chmod (gs_file_get_path_cached (path), mode);
+ while (G_UNLIKELY (res != 0 && errno == EINTR));
+
+ if (res < 0)
+ {
+ _set_error_from_errno (error);
+ goto out;
+ }
+
+ ret = TRUE;
+ out:
+ return ret;
+}
+
+/**
+ * gs_file_ensure_directory:
+ * @dir: Path to create as directory
+ * @with_parents: Also create parent directories
+ * @cancellable: a #GCancellable
+ * @error: a #GError
+ *
+ * Like g_file_make_directory(), except does not throw an error if the
+ * directory already exists.
+ */
+gboolean
+gs_file_ensure_directory (GFile *dir,
+ gboolean with_parents,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ GError *temp_error = NULL;
+ GFile *parent = NULL;
+
+ if (!g_file_make_directory (dir, cancellable, &temp_error))
+ {
+ if (with_parents &&
+ g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+ {
+ g_clear_error (&temp_error);
+
+ parent = g_file_get_parent (dir);
+ if (parent)
+ {
+ if (!gs_file_ensure_directory (parent, TRUE, cancellable, error))
+ goto out;
+ }
+ if (!gs_file_ensure_directory (dir, FALSE, cancellable, error))
+ goto out;
+ }
+ else if (!g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_EXISTS))
+ {
+ g_propagate_error (error, temp_error);
+ goto out;
+ }
+ else
+ g_clear_error (&temp_error);
+ }
+
+ ret = TRUE;
+ out:
+ g_clear_object (&parent);
+ return ret;
+}
+
+/**
+ * gs_file_ensure_directory_mode:
+ * @dir: Path to create as directory
+ * @mode: Create directory with these permissions
+ * @cancellable: a #GCancellable
+ * @error: a #GError
+ *
+ * Wraps UNIX mkdir() function with support for @cancellable, and
+ * uses @error instead of errno.
+ */
+gboolean
+gs_file_ensure_directory_mode (GFile *dir,
+ guint mode,
+ GCancellable *cancellable,
+ GError **error)
+{
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return FALSE;
+
+ if (mkdir (gs_file_get_path_cached (dir), mode) == -1 && errno != EEXIST)
+ {
+ _set_error_from_errno (error);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/**
+ * gs_file_load_contents_utf8:
+ * @file: Path to file whose contents must be UTF-8
+ * @cancellable:
+ * @error:
+ *
+ * Like g_file_load_contents(), except validates the contents are
+ * UTF-8.
+ */
+gchar *
+gs_file_load_contents_utf8 (GFile *file,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ gsize len;
+ char *ret_contents = NULL;
+
+ if (!g_file_load_contents (file, cancellable, &ret_contents, &len,
+ NULL, error))
+ goto out;
+ if (!g_utf8_validate (ret_contents, len, NULL))
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_DATA,
+ "Invalid UTF-8");
+ goto out;
+ }
+
+ ret = TRUE;
+ out:
+ if (!ret)
+ {
+ g_free (ret_contents);
+ return NULL;
+ }
+ return ret_contents;
+}
+
+static int
+path_common_directory (char *one,
+ char *two)
+{
+ int dir_index = 0;
+ int i = 0;
+
+ while (*one && *two)
+ {
+ if (*one != *two)
+ break;
+ if (*one == '/')
+ dir_index = i + 1;
+
+ one++;
+ two++;
+ i++;
+ }
+
+ return dir_index;
+}
+
+/**
+ * gs_file_get_relpath:
+ * @one: The first #GFile
+ * @two: The second #GFile
+ *
+ * Like gs_file_get_relative_path(), but does not mandate that
+ * the two files have any parent in common. This function will
+ * instead insert "../" where appropriate.
+ *
+ * Returns: (transfer full): The relative path between the two.
+ */
+gchar *
+gs_file_get_relpath (GFile *one,
+ GFile *two)
+{
+ gchar *simple_path;
+ gchar *one_path, *one_suffix;
+ gchar *two_path, *two_suffix;
+ GString *path;
+ int i;
+
+ simple_path = g_file_get_relative_path (one, two);
+ if (simple_path)
+ return simple_path;
+
+ one_path = g_file_get_path (one);
+ two_path = g_file_get_path (two);
+
+ i = path_common_directory (one_path, two_path);
+ one_suffix = one_path + i;
+ two_suffix = two_path + i;
+
+ path = g_string_new ("");
+
+ /* For every leftover path segment one has, append "../" so
+ * that we reach the same directory. */
+ while (*one_suffix)
+ {
+ g_string_append (path, "../");
+ one_suffix = strchr (one_suffix, '/');
+ if (one_suffix == NULL)
+ break;
+ one_suffix++;
+ }
+
+ /* And now append the leftover stuff on two's side. */
+ g_string_append (path, two_suffix);
+
+ g_free (one_path);
+ g_free (two_path);
+
+ return g_string_free (path, FALSE);
+}
+
+/**
+ * gs_file_realpath:
+ * @file: A #GFile
+ *
+ * Return a #GFile that contains the same path with symlinks
+ * followed. That is, it's a #GFile whose path is the result
+ * of calling realpath() on @file.
+ *
+ * Returns: (allow-none) (transfer full): A new #GFile or %NULL if @file is invalid
+ */
+GFile *
+gs_file_realpath (GFile *file)
+{
+ gchar *path;
+ gchar path_real[PATH_MAX];
+
+ path = g_file_get_path (file);
+
+ if (realpath ((const char *) path, path_real) == NULL)
+ {
+ g_free (path);
+ return NULL;
+ }
+
+ g_free (path);
+ return g_file_new_for_path (path_real);
+}
+
+#ifdef GSYSTEM_CONFIG_XATTRS
+static char *
+canonicalize_xattrs (char *xattr_string,
+ size_t len)
+{
+ char *p;
+ GSList *xattrs = NULL;
+ GSList *iter;
+ GString *result;
+
+ result = g_string_new (0);
+
+ p = xattr_string;
+ while (p < xattr_string+len)
+ {
+ xattrs = g_slist_prepend (xattrs, p);
+ p += strlen (p) + 1;
+ }
+
+ xattrs = g_slist_sort (xattrs, (GCompareFunc) strcmp);
+ for (iter = xattrs; iter; iter = iter->next) {
+ g_string_append (result, iter->data);
+ g_string_append_c (result, '\0');
+ }
+
+ g_slist_free (xattrs);
+ return g_string_free (result, FALSE);
+}
+
+static GVariant *
+variant_new_ay_bytes (GBytes *bytes)
+{
+ gsize size;
+ gconstpointer data;
+ data = g_bytes_get_data (bytes, &size);
+ g_bytes_ref (bytes);
+ return g_variant_new_from_data (G_VARIANT_TYPE ("ay"), data, size,
+ TRUE, (GDestroyNotify)g_bytes_unref, bytes);
+}
+
+static gboolean
+read_xattr_name_array (const char *path,
+ const char *xattrs,
+ size_t len,
+ GVariantBuilder *builder,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ const char *p;
+
+ p = xattrs;
+ while (p < xattrs+len)
+ {
+ ssize_t bytes_read;
+ char *buf;
+ GBytes *bytes = NULL;
+
+ bytes_read = lgetxattr (path, p, NULL, 0);
+ if (bytes_read < 0)
+ {
+ _set_error_from_errno (error);
+ g_prefix_error (error, "lgetxattr (%s, %s) failed: ", path, p);
+ goto out;
+ }
+ if (bytes_read == 0)
+ continue;
+
+ buf = g_malloc (bytes_read);
+ bytes = g_bytes_new_take (buf, bytes_read);
+ if (lgetxattr (path, p, buf, bytes_read) < 0)
+ {
+ g_bytes_unref (bytes);
+ _set_error_from_errno (error);
+ g_prefix_error (error, "lgetxattr (%s, %s) failed: ", path, p);
+ goto out;
+ }
+
+ g_variant_builder_add (builder, "(@ay@ay)",
+ g_variant_new_bytestring (p),
+ variant_new_ay_bytes (bytes));
+
+ p = p + strlen (p) + 1;
+ g_bytes_unref (bytes);
+ }
+
+ ret = TRUE;
+ out:
+ return ret;
+}
+#endif
+
+static gboolean
+get_xattrs_impl (GFile *f,
+ GVariantBuilder *builder,
+ GCancellable *cancellable,
+ GError **error)
+{
+#ifdef GSYSTEM_CONFIG_XATTRS
+ gboolean ret = FALSE;
+ const char *path;
+ ssize_t bytes_read;
+ char *xattr_names = NULL;
+ char *xattr_names_canonical = NULL;
+
+ path = gs_file_get_path_cached (f);
+
+ bytes_read = llistxattr (path, NULL, 0);
+
+ if (bytes_read < 0)
+ {
+ if (errno != ENOTSUP)
+ {
+ _set_error_from_errno (error);
+ g_prefix_error (error, "llistxattr (%s) failed: ", path);
+ goto out;
+ }
+ }
+ else if (bytes_read > 0)
+ {
+ xattr_names = g_malloc (bytes_read);
+ if (llistxattr (path, xattr_names, bytes_read) < 0)
+ {
+ _set_error_from_errno (error);
+ g_prefix_error (error, "llistxattr (%s) failed: ", path);
+ goto out;
+ }
+ xattr_names_canonical = canonicalize_xattrs (xattr_names, bytes_read);
+
+ if (!read_xattr_name_array (path, xattr_names_canonical, bytes_read, builder, error))
+ goto out;
+ }
+
+ ret = TRUE;
+ out:
+ g_clear_pointer (&xattr_names, g_free);
+ g_clear_pointer (&xattr_names_canonical, g_free);
+ return ret;
+#else
+ return TRUE;
+#endif
+}
+
+/**
+ * gs_file_get_all_xattrs:
+ * @f: a #GFile
+ * @out_xattrs: (out): A new #GVariant containing the extended attributes
+ * @cancellable: Cancellable
+ * @error: Error
+ *
+ * Read all extended attributes of @f in a canonical sorted order, and
+ * set @out_xattrs with the result.
+ *
+ * If the filesystem does not support extended attributes, @out_xattrs
+ * will have 0 elements, and this function will return successfully.
+ */
+gboolean
+gs_file_get_all_xattrs (GFile *f,
+ GVariant **out_xattrs,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ GVariantBuilder builder;
+ gboolean builder_initialized = FALSE;
+ GVariant *ret_xattrs = NULL;
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ayay)"));
+ builder_initialized = TRUE;
+
+ if (!get_xattrs_impl (f, &builder,
+ cancellable, error))
+ goto out;
+
+ ret_xattrs = g_variant_builder_end (&builder);
+ builder_initialized = FALSE;
+ g_variant_ref_sink (ret_xattrs);
+
+ ret = TRUE;
+ gs_transfer_out_value (out_xattrs, &ret_xattrs);
+ out:
+ g_clear_pointer (&ret_xattrs, g_variant_unref);
+ if (!builder_initialized)
+ g_variant_builder_clear (&builder);
+ return ret;
+}
+
+/**
+ * gs_fd_set_all_xattrs:
+ * @fd: File descriptor
+ * @xattrs: Extended attributes
+ * @cancellable: Cancellable
+ * @error: Error
+ *
+ * For each attribute in @xattrs, set its value on the file or
+ * directory referred to by @fd. This function does not remove any
+ * attributes not in @xattrs.
+ */
+gboolean
+gs_fd_set_all_xattrs (int fd,
+ GVariant *xattrs,
+ GCancellable *cancellable,
+ GError **error)
+{
+#ifdef GSYSTEM_CONFIG_XATTRS
+ gboolean ret = FALSE;
+ int i, n;
+
+ n = g_variant_n_children (xattrs);
+ for (i = 0; i < n; i++)
+ {
+ const guint8* name;
+ const guint8* value_data;
+ GVariant *value = NULL;
+ gsize value_len;
+ int res;
+
+ g_variant_get_child (xattrs, i, "(^&ay@ay)",
+ &name, &value);
+ value_data = g_variant_get_fixed_array (value, &value_len, 1);
+
+ do
+ res = fsetxattr (fd, (char*)name, (char*)value_data, value_len, 0);
+ while (G_UNLIKELY (res == -1 && errno == EINTR));
+ g_variant_unref (value);
+ if (G_UNLIKELY (res == -1))
+ {
+ _set_error_from_errno (error);
+ goto out;
+ }
+ }
+
+ ret = TRUE;
+ out:
+ return ret;
+#else
+ return TRUE;
+#endif
+}
+
+/**
+ * gs_file_set_all_xattrs:
+ * @file: File descriptor
+ * @xattrs: Extended attributes
+ * @cancellable: Cancellable
+ * @error: Error
+ *
+ * For each attribute in @xattrs, set its value on the file or
+ * directory referred to by @file. This function does not remove any
+ * attributes not in @xattrs.
+ */
+gboolean
+gs_file_set_all_xattrs (GFile *file,
+ GVariant *xattrs,
+ GCancellable *cancellable,
+ GError **error)
+{
+#ifdef GSYSTEM_CONFIG_XATTRS
+ gboolean ret = FALSE;
+ const char *path;
+ int i, n;
+
+ path = gs_file_get_path_cached (file);
+
+ n = g_variant_n_children (xattrs);
+ for (i = 0; i < n; i++)
+ {
+ const guint8* name;
+ GVariant *value;
+ const guint8* value_data;
+ gsize value_len;
+ gboolean loop_err;
+
+ g_variant_get_child (xattrs, i, "(^&ay@ay)",
+ &name, &value);
+ value_data = g_variant_get_fixed_array (value, &value_len, 1);
+
+ loop_err = lsetxattr (path, (char*)name, (char*)value_data, value_len, 0) < 0;
+ g_clear_pointer (&value, (GDestroyNotify) g_variant_unref);
+ if (loop_err)
+ {
+ _set_error_from_errno (error);
+ g_prefix_error (error, "lsetxattr (%s, %s) failed: ", path, name);
+ goto out;
+ }
+ }
+
+ ret = TRUE;
+ out:
+ return ret;
+#else
+ return TRUE;
+#endif
+}
diff --git a/src/gsystem-file-utils.h b/src/gsystem-file-utils.h
new file mode 100644
index 0000000..deb0a48
--- /dev/null
+++ b/src/gsystem-file-utils.h
@@ -0,0 +1,173 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2012 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_FILE_UTILS_H__
+#define __GSYSTEM_FILE_UTILS_H__
+
+#include <gio/gio.h>
+#include <sys/stat.h>
+
+G_BEGIN_DECLS
+
+const char *gs_file_get_path_cached (GFile *file);
+
+const char *gs_file_get_basename_cached (GFile *file);
+
+gboolean gs_file_enumerator_iterate (GFileEnumerator *direnum,
+ GFileInfo **out_info,
+ GFile **out_child,
+ GCancellable *cancellable,
+ GError **error);
+
+gboolean gs_file_openat_noatime (int dfd,
+ const char *name,
+ int *ret_fd,
+ GCancellable *cancellable,
+ GError **error);
+
+GInputStream *gs_file_read_noatime (GFile *file,
+ GCancellable *cancellable,
+ GError **error);
+GMappedFile *gs_file_map_noatime (GFile *file,
+ GCancellable *cancellable,
+ GError **error);
+
+#ifndef __GI_SCANNER__
+gboolean gs_stream_fstat (GFileDescriptorBased *stream,
+ struct stat *out_stbuf,
+ GCancellable *cancellable,
+ GError **error);
+
+#endif
+
+#if GLIB_CHECK_VERSION(2,34,0)
+GBytes *gs_file_map_readonly (GFile *file,
+ GCancellable *cancellable,
+ GError **error);
+#endif
+
+gboolean gs_file_sync_data (GFile *file,
+ GCancellable *cancellable,
+ GError **error);
+
+char * gs_fileutil_gen_tmp_name (const char *prefix,
+ const char *suffix);
+
+gboolean gs_file_open_dir_fd (GFile *path,
+ int *out_fd,
+ GCancellable *cancellable,
+ GError **error);
+
+gboolean gs_file_open_in_tmpdir_at (int tmpdir_fd,
+ int mode,
+ char **out_name,
+ GOutputStream **out_stream,
+ GCancellable *cancellable,
+ GError **error);
+
+gboolean gs_file_open_in_tmpdir (GFile *tmpdir,
+ int mode,
+ GFile **out_file,
+ GOutputStream **out_stream,
+ GCancellable *cancellable,
+ GError **error);
+
+gboolean gs_file_create (GFile *file,
+ int mode,
+ GOutputStream **out_stream,
+ GCancellable *cancellable,
+ GError **error);
+
+gboolean gs_file_linkcopy (GFile *src,
+ GFile *dest,
+ GFileCopyFlags flags,
+ GCancellable *cancellable,
+ GError **error);
+
+gboolean gs_file_linkcopy_sync_data (GFile *src,
+ GFile *dest,
+ GFileCopyFlags flags,
+ GCancellable *cancellable,
+ GError **error);
+
+gboolean gs_file_rename (GFile *from,
+ GFile *to,
+ GCancellable *cancellable,
+ GError **error);
+
+gboolean gs_file_unlink (GFile *path,
+ GCancellable *cancellable,
+ GError **error);
+
+gboolean gs_file_chown (GFile *path,
+ guint32 owner,
+ guint32 group,
+ GCancellable *cancellable,
+ GError **error);
+
+gboolean gs_file_lchown (GFile *path,
+ guint32 owner,
+ guint32 group,
+ GCancellable *cancellable,
+ GError **error);
+
+gboolean gs_file_chmod (GFile *path,
+ guint mode,
+ GCancellable *cancellable,
+ GError **error);
+
+gboolean gs_file_ensure_directory (GFile *dir,
+ gboolean with_parents,
+ GCancellable *cancellable,
+ GError **error);
+
+gboolean gs_file_ensure_directory_mode (GFile *dir,
+ guint mode,
+ GCancellable *cancellable,
+ GError **error);
+
+gchar *gs_file_load_contents_utf8 (GFile *file,
+ GCancellable *cancellable,
+ GError **error);
+
+gchar *gs_file_get_relpath (GFile *one,
+ GFile *two);
+
+GFile * gs_file_realpath (GFile *file);
+
+gboolean gs_file_get_all_xattrs (GFile *f,
+ GVariant **out_xattrs,
+ GCancellable *cancellable,
+ GError **error);
+
+gboolean gs_fd_set_all_xattrs (int fd,
+ GVariant *xattrs,
+ GCancellable *cancellable,
+ GError **error);
+
+gboolean gs_file_set_all_xattrs (GFile *file,
+ GVariant *xattrs,
+ GCancellable *cancellable,
+ GError **error);
+
+
+G_END_DECLS
+
+#endif
diff --git a/src/gsystem-glib-compat.h b/src/gsystem-glib-compat.h
new file mode 100644
index 0000000..6fd59c6
--- /dev/null
+++ b/src/gsystem-glib-compat.h
@@ -0,0 +1,54 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2012,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 __LIBGSYSTEM_GLIB_COMPAT__
+#define __LIBGSYSTEM_GLIB_COMPAT__
+
+#include <gio/gio.h>
+
+#if !GLIB_CHECK_VERSION(2,34,0)
+static inline void
+g_type_ensure (GType type)
+{
+ if (G_UNLIKELY (type == (GType)-1))
+ g_error ("can't happen");
+}
+
+#define g_clear_pointer(pp, destroy) \
+ G_STMT_START { \
+ G_STATIC_ASSERT (sizeof *(pp) == sizeof (gpointer)); \
+ /* Only one access, please */ \
+ gpointer *_pp = (gpointer *) (pp); \
+ gpointer _p; \
+ /* This assignment is needed to avoid a gcc warning */ \
+ GDestroyNotify _destroy = (GDestroyNotify) (destroy); \
+ \
+ (void) (0 ? (gpointer) *(pp) : 0); \
+ do \
+ _p = g_atomic_pointer_get (_pp); \
+ while G_UNLIKELY (!g_atomic_pointer_compare_and_exchange (_pp, _p, NULL)); \
+ \
+ if (_p) \
+ _destroy (_p); \
+ } G_STMT_END
+
+#endif
+
+#endif
diff --git a/src/gsystem-local-alloc.c b/src/gsystem-local-alloc.c
new file mode 100644
index 0000000..add3fcb
--- /dev/null
+++ b/src/gsystem-local-alloc.c
@@ -0,0 +1,72 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2012 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 "gsystem-local-alloc.h"
+
+/**
+ * SECTION:gslocalalloc
+ * @title: GSystem local allocation
+ * @short_description: Release local variables automatically when they go out of scope
+ *
+ * These macros leverage the GCC extension __attribute__ ((cleanup))
+ * to allow calling a cleanup function such as g_free() when a
+ * variable goes out of scope. See <ulink
+ * url="http://gcc.gnu.org/onlinedocs/gcc/Variable-Attributes.html">
+ * for more information on the attribute.
+ *
+ * The provided macros make it easy to use the cleanup attribute for
+ * types that come with GLib. The primary two are #gs_free and
+ * #gs_unref_object, which correspond to g_free() and
+ * g_object_unref(), respectively.
+ *
+ * The rationale behind this is that particularly when handling error
+ * paths, it can be very tricky to ensure the right variables are
+ * freed. With this, one simply applies gs_lobj to a
+ * locally-allocated #GFile for example, and it will be automatically
+ * unreferenced when it goes out of scope.
+ *
+ * Note - you should only use these macros for <emphasis>stack
+ * allocated</emphasis> variables. They don't provide garbage
+ * collection or let you avoid freeing things. They're simply a
+ * compiler assisted deterministic mechanism for calling a cleanup
+ * function when a stack frame ends.
+ *
+ * <example id="gs-lfree"><title>Calling g_free automatically</title>
+ * <programlisting>
+ *
+ * GFile *
+ * create_file (GError **error)
+ * {
+ * gs_free char *random_id = NULL;
+ *
+ * if (!prepare_file (error))
+ * return NULL;
+ *
+ * random_id = alloc_random_id ();
+ *
+ * return create_file_real (error);
+ * // Note that random_id is freed here automatically
+ * }
+ * </programlisting>
+ * </example>
+ *
+ */
diff --git a/src/gsystem-local-alloc.h b/src/gsystem-local-alloc.h
new file mode 100644
index 0000000..34db297
--- /dev/null
+++ b/src/gsystem-local-alloc.h
@@ -0,0 +1,164 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2012 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_LOCAL_ALLOC_H__
+#define __GSYSTEM_LOCAL_ALLOC_H__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define GS_DEFINE_CLEANUP_FUNCTION(Type, name, func) \
+ static inline void name (void *v) \
+ { \
+ func (*(Type*)v); \
+ }
+
+#define GS_DEFINE_CLEANUP_FUNCTION0(Type, name, func) \
+ static inline void name (void *v) \
+ { \
+ if (*(Type*)v) \
+ func (*(Type*)v); \
+ }
+
+/* These functions shouldn't be invoked directly;
+ * they are stubs that:
+ * 1) Take a pointer to the location (typically itself a pointer).
+ * 2) Provide %NULL-safety where it doesn't exist already (e.g. g_object_unref)
+ */
+GS_DEFINE_CLEANUP_FUNCTION0(GArray*, gs_local_array_unref, g_array_unref)
+GS_DEFINE_CLEANUP_FUNCTION0(GBytes*, gs_local_bytes_unref, g_bytes_unref)
+GS_DEFINE_CLEANUP_FUNCTION0(GChecksum*, gs_local_checksum_free, g_checksum_free)
+GS_DEFINE_CLEANUP_FUNCTION0(GError*, gs_local_free_error, g_error_free)
+GS_DEFINE_CLEANUP_FUNCTION0(GHashTable*, gs_local_hashtable_unref, g_hash_table_unref)
+GS_DEFINE_CLEANUP_FUNCTION0(GObject*, gs_local_obj_unref, g_object_unref)
+GS_DEFINE_CLEANUP_FUNCTION0(GPtrArray*, gs_local_ptrarray_unref, g_ptr_array_unref)
+GS_DEFINE_CLEANUP_FUNCTION0(GVariant*, gs_local_variant_unref, g_variant_unref)
+GS_DEFINE_CLEANUP_FUNCTION0(GVariantBuilder*, gs_local_variant_builder_unref, g_variant_builder_unref)
+GS_DEFINE_CLEANUP_FUNCTION0(GVariantIter*, gs_local_variant_iter_free, g_variant_iter_free)
+
+GS_DEFINE_CLEANUP_FUNCTION(char**, gs_local_strfreev, g_strfreev)
+GS_DEFINE_CLEANUP_FUNCTION(void*, gs_local_free, g_free)
+
+/**
+ * gs_free:
+ *
+ * Call g_free() on a variable location when it goes out of scope.
+ */
+#define gs_free __attribute__ ((cleanup(gs_local_free)))
+
+/**
+ * gs_unref_object:
+ *
+ * Call g_object_unref() on a variable location when it goes out of
+ * scope. Note that unlike g_object_unref(), the variable may be
+ * %NULL.
+ */
+#define gs_unref_object __attribute__ ((cleanup(gs_local_obj_unref)))
+
+/**
+ * gs_unref_variant:
+ *
+ * Call g_variant_unref() on a variable location when it goes out of
+ * scope. Note that unlike g_variant_unref(), the variable may be
+ * %NULL.
+ */
+#define gs_unref_variant __attribute__ ((cleanup(gs_local_variant_unref)))
+
+/**
+ * gs_free_variant_iter:
+ *
+ * Call g_variant_iter_free() on a variable location when it goes out of
+ * scope.
+ */
+#define gs_free_variant_iter __attribute__ ((cleanup(gs_local_variant_iter_free)))
+
+/**
+ * gs_free_variant_builder:
+ *
+ * Call g_variant_builder_unref() on a variable location when it goes out of
+ * scope.
+ */
+#define gs_unref_variant_builder __attribute__ ((cleanup(gs_local_variant_builder_unref)))
+
+/**
+ * gs_unref_array:
+ *
+ * Call g_array_unref() on a variable location when it goes out of
+ * scope. Note that unlike g_array_unref(), the variable may be
+ * %NULL.
+
+ */
+#define gs_unref_array __attribute__ ((cleanup(gs_local_array_unref)))
+
+/**
+ * gs_unref_ptrarray:
+ *
+ * Call g_ptr_array_unref() on a variable location when it goes out of
+ * scope. Note that unlike g_ptr_array_unref(), the variable may be
+ * %NULL.
+
+ */
+#define gs_unref_ptrarray __attribute__ ((cleanup(gs_local_ptrarray_unref)))
+
+/**
+ * gs_unref_hashtable:
+ *
+ * Call g_hash_table_unref() on a variable location when it goes out
+ * of scope. Note that unlike g_hash_table_unref(), the variable may
+ * be %NULL.
+ */
+#define gs_unref_hashtable __attribute__ ((cleanup(gs_local_hashtable_unref)))
+
+/**
+ * gs_free_checksum:
+ *
+ * Call g_checksum_free() on a variable location when it goes out
+ * of scope. Note that unlike g_checksum_free(), the variable may
+ * be %NULL.
+ */
+#define gs_free_checksum __attribute__ ((cleanup(gs_local_checksum_free)))
+
+/**
+ * gs_unref_bytes:
+ *
+ * Call g_bytes_unref() on a variable location when it goes out
+ * of scope. Note that unlike g_bytes_unref(), the variable may
+ * be %NULL.
+ */
+#define gs_unref_bytes __attribute__ ((cleanup(gs_local_bytes_unref)))
+
+/**
+ * gs_strfreev:
+ *
+ * Call g_strfreev() on a variable location when it goes out of scope.
+ */
+#define gs_strfreev __attribute__ ((cleanup(gs_local_strfreev)))
+
+/**
+ * gs_free_error:
+ *
+ * Call g_error_free() on a variable location when it goes out of scope.
+ */
+#define gs_free_error __attribute__ ((cleanup(gs_local_free_error)))
+
+G_END_DECLS
+
+#endif
diff --git a/src/gsystem-log.c b/src/gsystem-log.c
new file mode 100644
index 0000000..7b03145
--- /dev/null
+++ b/src/gsystem-log.c
@@ -0,0 +1,167 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2012 William Jon McCann <mccann@redhat.com>
+ * Copyright (C) 2012 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"
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#ifdef ENABLE_SYSTEMD_JOURNAL
+#define SD_JOURNAL_SUPPRESS_LOCATION
+#include <systemd/sd-journal.h>
+#endif
+#include <glib-unix.h>
+
+#define _GSYSTEM_NO_LOCAL_ALLOC
+#include "libgsystem.h"
+
+/**
+ * gs_log_structured:
+ * @message: Text message to send
+ * @keys: (allow-none) (array zero-terminated=1) (element-type utf8): Optional structured data
+ *
+ * Log structured data in an operating-system specific fashion. The
+ * parameter @opts should be an array of UTF-8 KEY=VALUE strings.
+ * This function does not support binary data. See
+ * http://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html
+ * for more information about fields that can be used on a systemd
+ * system.
+ */
+void
+gs_log_structured (const char *message,
+ const char *const *keys)
+{
+#ifdef ENABLE_SYSTEMD_JOURNAL
+ const char *const*iter;
+ char *msgkey;
+ guint i, n_opts;
+ struct iovec *iovs;
+
+ for (n_opts = 0, iter = keys; *iter; iter++, n_opts++)
+ ;
+
+ n_opts++; /* Add one for MESSAGE= */
+ iovs = g_alloca (sizeof (struct iovec) * n_opts);
+
+ for (i = 0, iter = keys; *iter; iter++, i++) {
+ iovs[i].iov_base = (char*)keys[i];
+ iovs[i].iov_len = strlen (keys[i]);
+ }
+ g_assert(i == n_opts-1);
+ msgkey = g_strconcat ("MESSAGE=", message, NULL);
+ iovs[i].iov_base = msgkey;
+ iovs[i].iov_len = strlen (msgkey);
+
+ // The code location isn't useful since we're wrapping
+ sd_journal_sendv (iovs, n_opts);
+
+ g_free (msgkey);
+#else
+ g_print ("%s\n", message);
+#endif
+}
+
+/**
+ * gs_stdout_is_journal:
+ *
+ * Use this function when you want your code to behave differently
+ * depeneding on whether your program was started as a systemd unit,
+ * or e.g. interactively at a terminal.
+ *
+ * Returns: %TRUE if stdout is (probably) connnected to the systemd journal
+ */
+gboolean
+gs_stdout_is_journal (void)
+{
+ static gsize initialized;
+ static gboolean stdout_is_socket;
+
+ if (g_once_init_enter (&initialized))
+ {
+ guint64 pid = (guint64) getpid ();
+ char *fdpath = g_strdup_printf ("/proc/%" G_GUINT64_FORMAT "/fd/1", pid);
+ char buf[1024];
+ ssize_t bytes_read;
+
+ if ((bytes_read = readlink (fdpath, buf, sizeof(buf) - 1)) != -1)
+ {
+ buf[bytes_read] = '\0';
+ stdout_is_socket = g_str_has_prefix (buf, "socket:");
+ }
+ else
+ stdout_is_socket = FALSE;
+
+ g_free (fdpath);
+ g_once_init_leave (&initialized, TRUE);
+ }
+
+ return stdout_is_socket;
+}
+
+/**
+ * gs_log_structured_print:
+ * @message: A message to log
+ * @keys: (allow-none) (array zero-terminated=1) (element-type utf8): Optional structured data
+ *
+ * Like gs_log_structured(), but also print to standard output (if it
+ * is not already connected to the system log).
+ */
+void
+gs_log_structured_print (const char *message,
+ const char *const *keys)
+{
+ gs_log_structured (message, keys);
+
+#ifdef ENABLE_SYSTEMD_JOURNAL
+ if (!gs_stdout_is_journal ())
+ g_print ("%s\n", message);
+#endif
+}
+
+/**
+ * gs_log_structured_print_id_v:
+ * @message_id: A unique MESSAGE_ID
+ * @format: A format string
+ *
+ * The provided @message_id is a unique MESSAGE_ID (see <ulink url="http://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html"> for more information).
+ *
+ * This function otherwise acts as gs_log_structured_print(), taking
+ * @format as a format string.
+ */
+void
+gs_log_structured_print_id_v (const char *message_id,
+ const char *format,
+ ...)
+{
+ char *keys[] = { NULL, NULL };
+ char *msg;
+ va_list args;
+
+ va_start (args, format);
+ msg = g_strdup_vprintf (format, args);
+ va_end (args);
+
+ keys[0] = g_strconcat ("MESSAGE_ID=", message_id, NULL);
+ gs_log_structured_print (msg, (const char *const *)keys);
+ g_free (keys[0]);
+ g_free (msg);
+}
diff --git a/src/gsystem-log.h b/src/gsystem-log.h
new file mode 100644
index 0000000..80cfc34
--- /dev/null
+++ b/src/gsystem-log.h
@@ -0,0 +1,42 @@
+/* -*- 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_LOG_H__
+#define __GSYSTEM_LOG_H__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+gboolean gs_stdout_is_journal (void);
+
+void gs_log_structured (const char *message,
+ const char *const *keys);
+
+void gs_log_structured_print (const char *message,
+ const char *const *keys);
+
+void gs_log_structured_print_id_v (const char *message_id,
+ const char *format,
+ ...) G_GNUC_PRINTF (2, 3);
+
+G_END_DECLS
+
+#endif
diff --git a/src/gsystem-shutil.c b/src/gsystem-shutil.c
new file mode 100644
index 0000000..8029dd9
--- /dev/null
+++ b/src/gsystem-shutil.c
@@ -0,0 +1,460 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2012 William Jon McCann <mccann@redhat.com>
+ * Copyright (C) 2012 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"
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#define _GSYSTEM_NO_LOCAL_ALLOC
+#include "libgsystem.h"
+#include "gsystem-glib-compat.h"
+#include <glib-unix.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <fcntl.h>
+
+/* Taken from systemd/src/shared/util.h */
+union dirent_storage {
+ struct dirent dent;
+ guint8 storage[offsetof(struct dirent, d_name) +
+ ((NAME_MAX + 1 + sizeof(long)) & ~(sizeof(long) - 1))];
+};
+
+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));
+}
+
+static gboolean
+copy_xattrs_from_file_to_fd (GFile *src,
+ int dest_fd,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ GVariant *src_xattrs = NULL;
+
+ if (!gs_file_get_all_xattrs (src, &src_xattrs, cancellable, error))
+ goto out;
+
+ if (src_xattrs)
+ {
+ if (!gs_fd_set_all_xattrs (dest_fd, src_xattrs, cancellable, error))
+ goto out;
+ }
+
+ ret = TRUE;
+ out:
+ g_clear_pointer (&src_xattrs, g_variant_unref);
+ return ret;
+}
+
+typedef enum {
+ GS_CP_MODE_NONE,
+ GS_CP_MODE_HARDLINK,
+ GS_CP_MODE_COPY_ALL
+} GsCpMode;
+
+static gboolean
+cp_internal (GFile *src,
+ GFile *dest,
+ GsCpMode mode,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ GFileEnumerator *enumerator = NULL;
+ GFileInfo *src_info = NULL;
+ GFile *dest_child = NULL;
+ int dest_dfd = -1;
+ int r;
+
+ enumerator = g_file_enumerate_children (src, "standard::type,standard::name,unix::uid,unix::gid,unix::mode",
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ cancellable, error);
+ if (!enumerator)
+ goto out;
+
+ src_info = g_file_query_info (src, "standard::name,unix::mode,unix::uid,unix::gid," \
+ "time::modified,time::modified-usec,time::access,time::access-usec",
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ cancellable, error);
+ if (!src_info)
+ goto out;
+
+ do
+ r = mkdir (gs_file_get_path_cached (dest), 0755);
+ while (G_UNLIKELY (r == -1 && errno == EINTR));
+ if (r == -1)
+ {
+ _set_error_from_errno (error);
+ goto out;
+ }
+
+ if (mode != GS_CP_MODE_NONE)
+ {
+ if (!gs_file_open_dir_fd (dest, &dest_dfd,
+ cancellable, error))
+ goto out;
+
+ do
+ r = fchown (dest_dfd,
+ g_file_info_get_attribute_uint32 (src_info, "unix::uid"),
+ g_file_info_get_attribute_uint32 (src_info, "unix::gid"));
+ while (G_UNLIKELY (r == -1 && errno == EINTR));
+ if (r == -1)
+ {
+ _set_error_from_errno (error);
+ goto out;
+ }
+
+ do
+ r = fchmod (dest_dfd, g_file_info_get_attribute_uint32 (src_info, "unix::mode"));
+ while (G_UNLIKELY (r == -1 && errno == EINTR));
+
+ if (!copy_xattrs_from_file_to_fd (src, dest_dfd, cancellable, error))
+ goto out;
+
+ if (dest_dfd != -1)
+ {
+ (void) close (dest_dfd);
+ dest_dfd = -1;
+ }
+ }
+
+ while (TRUE)
+ {
+ GFileInfo *file_info = NULL;
+ GFile *src_child = NULL;
+
+ if (!gs_file_enumerator_iterate (enumerator, &file_info, &src_child,
+ cancellable, error))
+ goto out;
+ if (!file_info)
+ break;
+
+ if (dest_child) g_object_unref (dest_child);
+ dest_child = g_file_get_child (dest, g_file_info_get_name (file_info));
+
+ if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_DIRECTORY)
+ {
+ if (!cp_internal (src_child, dest_child, mode,
+ cancellable, error))
+ goto out;
+ }
+ else
+ {
+ gboolean did_link = FALSE;
+ (void) unlink (gs_file_get_path_cached (dest_child));
+ if (mode == GS_CP_MODE_HARDLINK)
+ {
+ if (link (gs_file_get_path_cached (src_child), gs_file_get_path_cached (dest_child)) == -1)
+ {
+ if (!(errno == EMLINK || errno == EXDEV))
+ {
+ int errsv = errno;
+ g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
+ g_strerror (errsv));
+ goto out;
+ }
+ /* We failed to hardlink; fall back to copying all; this will
+ * affect subsequent directory copies too.
+ */
+ mode = GS_CP_MODE_COPY_ALL;
+ }
+ else
+ did_link = TRUE;
+ }
+ if (!did_link)
+ {
+ GFileCopyFlags copyflags = G_FILE_COPY_OVERWRITE | G_FILE_COPY_NOFOLLOW_SYMLINKS;
+ if (mode == GS_CP_MODE_COPY_ALL)
+ copyflags |= G_FILE_COPY_ALL_METADATA;
+ if (!g_file_copy (src_child, dest_child, copyflags,
+ cancellable, NULL, NULL, error))
+ goto out;
+ }
+ }
+ }
+
+ ret = TRUE;
+ out:
+ if (dest_dfd != -1)
+ (void) close (dest_dfd);
+ g_clear_object (&src_info);
+ g_clear_object (&enumerator);
+ g_clear_object (&dest_child);
+ return ret;
+}
+
+/**
+ * gs_shutil_cp_al_or_fallback:
+ * @src: Source path
+ * @dest: Destination path
+ * @cancellable:
+ * @error:
+ *
+ * Recursively copy path @src (which must be a directory) to the
+ * target @dest. If possible, hardlinks are used; if a hardlink is
+ * not possible, a regular copy is created. Any existing files are
+ * overwritten.
+ *
+ * Returns: %TRUE on success
+ */
+gboolean
+gs_shutil_cp_al_or_fallback (GFile *src,
+ GFile *dest,
+ GCancellable *cancellable,
+ GError **error)
+{
+ return cp_internal (src, dest, GS_CP_MODE_HARDLINK,
+ cancellable, error);
+}
+
+/**
+ * gs_shutil_cp_a:
+ * @src: Source path
+ * @dest: Destination path
+ * @cancellable:
+ * @error:
+ *
+ * Recursively copy path @src (which must be a directory) to the
+ * target @dest. Any existing files are overwritten.
+ *
+ * Returns: %TRUE on success
+ */
+gboolean
+gs_shutil_cp_a (GFile *src,
+ GFile *dest,
+ GCancellable *cancellable,
+ GError **error)
+{
+ return cp_internal (src, dest, GS_CP_MODE_COPY_ALL,
+ cancellable, error);
+}
+
+static unsigned char
+struct_stat_to_dt (struct stat *stbuf)
+{
+ if (S_ISDIR (stbuf->st_mode))
+ return DT_DIR;
+ if (S_ISREG (stbuf->st_mode))
+ return DT_REG;
+ if (S_ISCHR (stbuf->st_mode))
+ return DT_CHR;
+ if (S_ISBLK (stbuf->st_mode))
+ return DT_BLK;
+ if (S_ISFIFO (stbuf->st_mode))
+ return DT_FIFO;
+ if (S_ISLNK (stbuf->st_mode))
+ return DT_LNK;
+ if (S_ISSOCK (stbuf->st_mode))
+ return DT_SOCK;
+ return DT_UNKNOWN;
+}
+
+static gboolean
+gs_shutil_rm_rf_children (DIR *dir,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ int dfd;
+ DIR *child_dir = NULL;
+ struct dirent *dent;
+ union dirent_storage buf;
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ goto out;
+
+ dfd = dirfd (dir);
+
+ while (readdir_r (dir, &buf.dent, &dent) == 0)
+ {
+ if (dent == NULL)
+ break;
+ if (dent->d_type == DT_UNKNOWN)
+ {
+ struct stat stbuf;
+ if (fstatat (dfd, dent->d_name, &stbuf, AT_SYMLINK_NOFOLLOW) == -1)
+ {
+ int errsv = errno;
+ if (errsv == ENOENT)
+ continue;
+ else
+ {
+ g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
+ g_strerror (errsv));
+ goto out;
+ }
+ }
+ dent->d_type = struct_stat_to_dt (&stbuf);
+ /* Assume unknown types are just treated like regular files */
+ if (dent->d_type == DT_UNKNOWN)
+ dent->d_type = DT_REG;
+ }
+
+ if (strcmp (dent->d_name, ".") == 0 || strcmp (dent->d_name, "..") == 0)
+ continue;
+
+ if (dent->d_type == DT_DIR)
+ {
+ int child_dfd = openat (dfd, dent->d_name, O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW);
+
+ if (child_dfd == -1)
+ {
+ if (errno == ENOENT)
+ continue;
+ else
+ {
+ int errsv = errno;
+ g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
+ g_strerror (errsv));
+ goto out;
+ }
+ }
+
+ child_dir = fdopendir (child_dfd);
+ if (!child_dir)
+ {
+ int errsv = errno;
+ g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
+ g_strerror (errsv));
+ goto out;
+ }
+
+ if (!gs_shutil_rm_rf_children (child_dir, cancellable, error))
+ goto out;
+
+ if (unlinkat (dfd, dent->d_name, AT_REMOVEDIR) == -1)
+ {
+ int errsv = errno;
+ g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
+ g_strerror (errsv));
+ goto out;
+ }
+
+ (void) closedir (child_dir);
+ child_dir = NULL;
+ }
+ else
+ {
+ if (unlinkat (dfd, dent->d_name, 0) == -1)
+ {
+ int errsv = errno;
+ if (errno != ENOENT)
+ {
+ g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
+ g_strerror (errsv));
+ goto out;
+ }
+ }
+ }
+ }
+ /* Ignore error result from readdir_r, that's what others
+ * seem to do =(
+ */
+
+ ret = TRUE;
+ out:
+ if (child_dir) (void) closedir (child_dir);
+ return ret;
+}
+
+/**
+ * gs_shutil_rm_rf:
+ * @path: A file or directory
+ * @cancellable:
+ * @error:
+ *
+ * Recursively delete the filename referenced by @path; it may be a
+ * file or directory. No error is thrown if @path does not exist.
+ */
+gboolean
+gs_shutil_rm_rf (GFile *path,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ int dfd = -1;
+ DIR *d = NULL;
+
+ /* With O_NOFOLLOW first */
+ dfd = openat (AT_FDCWD, gs_file_get_path_cached (path),
+ O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW);
+
+ if (dfd == -1)
+ {
+ int errsv = errno;
+ if (errsv == ENOENT)
+ {
+ ;
+ }
+ else if (errsv == ENOTDIR || errsv == ELOOP)
+ {
+ if (!gs_file_unlink (path, cancellable, error))
+ goto out;
+ }
+ else
+ {
+ g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
+ g_strerror (errsv));
+ goto out;
+ }
+ }
+ else
+ {
+ d = fdopendir (dfd);
+ if (!d)
+ {
+ int errsv = errno;
+ g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
+ g_strerror (errsv));
+ goto out;
+ }
+
+ if (!gs_shutil_rm_rf_children (d, cancellable, error))
+ goto out;
+
+ if (rmdir (gs_file_get_path_cached (path)) == -1)
+ {
+ int errsv = errno;
+ if (errsv != ENOENT)
+ {
+ g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
+ g_strerror (errsv));
+ goto out;
+ }
+ }
+ }
+
+ ret = TRUE;
+ out:
+ if (d) (void) closedir (d);
+ return ret;
+}
+
diff --git a/src/gsystem-shutil.h b/src/gsystem-shutil.h
new file mode 100644
index 0000000..3cdea77
--- /dev/null
+++ b/src/gsystem-shutil.h
@@ -0,0 +1,47 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2012 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_SHUTIL_H__
+#define __GSYSTEM_SHUTIL_H__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+gboolean
+gs_shutil_cp_al_or_fallback (GFile *src,
+ GFile *dest,
+ GCancellable *cancellable,
+ GError **error);
+
+gboolean
+gs_shutil_cp_a (GFile *src,
+ GFile *dest,
+ GCancellable *cancellable,
+ GError **error);
+
+gboolean
+gs_shutil_rm_rf (GFile *path,
+ GCancellable *cancellable,
+ GError **error);
+
+G_END_DECLS
+
+#endif
diff --git a/src/gsystem-subprocess-context-private.h b/src/gsystem-subprocess-context-private.h
new file mode 100644
index 0000000..719df45
--- /dev/null
+++ b/src/gsystem-subprocess-context-private.h
@@ -0,0 +1,65 @@
+/* GIO - GLib Input, Output and Streaming Library
+ *
+ * Copyright (C) 2012 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 __GS_SUBPROCESS_CONTEXT_PRIVATE_H__
+#define __GS_SUBPROCESS_CONTEXT_PRIVATE_H__
+
+#include "gsystem-subprocess-context.h"
+
+G_BEGIN_DECLS
+
+struct _GSSubprocessContext
+{
+ GObject parent;
+
+ GSpawnFlags flags;
+ gchar **argv;
+ gboolean has_argv0;
+ char **envp;
+ char *cwd;
+
+ GSSubprocessStreamDisposition stdin_disposition;
+ GSSubprocessStreamDisposition stdout_disposition;
+ GSSubprocessStreamDisposition stderr_disposition;
+
+ guint keep_descriptors : 1;
+ guint search_path : 1;
+ guint search_path_from_envp : 1;
+ guint unused_flags : 29;
+
+ gint stdin_fd;
+ gchar *stdin_path;
+
+ gint stdout_fd;
+ gchar *stdout_path;
+
+ gint stderr_fd;
+ gchar *stderr_path;
+
+ GArray *postfork_close_fds;
+ GArray *inherit_fds;
+
+ GSpawnChildSetupFunc child_setup_func;
+ gpointer child_setup_data;
+};
+
+G_END_DECLS
+
+#endif
diff --git a/src/gsystem-subprocess-context.c b/src/gsystem-subprocess-context.c
new file mode 100644
index 0000000..90ca716
--- /dev/null
+++ b/src/gsystem-subprocess-context.c
@@ -0,0 +1,501 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2012 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"
+
+#if GLIB_CHECK_VERSION(2,34,0)
+
+#ifdef G_OS_UNIX
+#include <gio/gunixoutputstream.h>
+#include <gio/gfiledescriptorbased.h>
+#include <gio/gunixinputstream.h>
+#include <glib-unix.h>
+#endif
+
+/**
+ * SECTION:gssubprocesscontext
+ * @title: GSSubprocess Context
+ * @short_description: Environment options for launching a child process
+ *
+ * This class contains a set of options for launching child processes,
+ * such as where its standard input and output will be directed, the
+ * argument list, the environment, and more.
+ *
+ * While the #GSSubprocess class has high level functions covering
+ * popular cases, use of this class allows access to more advanced
+ * options. It can also be used to launch multiple subprocesses with
+ * a similar configuration.
+ *
+ * Since: 2.36
+ */
+
+#include "config.h"
+
+#include "gsystem-subprocess-context-private.h"
+#include "gsystem-subprocess.h"
+
+#include <string.h>
+
+typedef GObjectClass GSSubprocessContextClass;
+
+G_DEFINE_TYPE (GSSubprocessContext, gs_subprocess_context, G_TYPE_OBJECT);
+
+enum
+{
+ PROP_0,
+ PROP_ARGV,
+ N_PROPS
+};
+
+static GParamSpec *gs_subprocess_context_pspecs[N_PROPS];
+
+/**
+ * gs_subprocess_context_new:
+ * @argv: Argument list
+ *
+ * Returns: (transfer full): A new instance of a #GSSubprocessContext.
+ */
+GSSubprocessContext *
+gs_subprocess_context_new (gchar **argv)
+{
+ g_return_val_if_fail (argv != NULL && argv[0] != NULL, NULL);
+
+ return g_object_new (GS_TYPE_SUBPROCESS_CONTEXT,
+ "argv", argv,
+ NULL);
+}
+
+GSSubprocessContext *
+gs_subprocess_context_newv (const gchar *first_arg,
+ ...)
+{
+ GSSubprocessContext *result;
+ va_list args;
+
+ g_return_val_if_fail (first_arg != NULL, NULL);
+
+ va_start (args, first_arg);
+ result = gs_subprocess_context_newa (first_arg, args);
+ va_end (args);
+
+ return result;
+}
+
+/**
+ * gs_subprocess_context_newa:
+ * @first_arg: First argument
+ * @args: a va_list
+ *
+ * Returns: (transfer full): A new instance of a #GSSubprocessContext.
+ */
+GSSubprocessContext *
+gs_subprocess_context_newa (const gchar *first_arg,
+ va_list args)
+{
+ GSSubprocessContext *result;
+ GPtrArray *argv;
+
+ g_return_val_if_fail (first_arg != NULL, NULL);
+
+ argv = g_ptr_array_new ();
+ do
+ g_ptr_array_add (argv, (gchar*)first_arg);
+ while ((first_arg = va_arg (args, const gchar *)) != NULL);
+ g_ptr_array_add (argv, NULL);
+
+ result = gs_subprocess_context_new ((gchar**)argv->pdata);
+
+ return result;
+}
+
+#ifdef G_OS_UNIX
+GSSubprocessContext *
+gs_subprocess_context_new_argv0 (const gchar *argv0,
+ gchar **argv)
+{
+ GSSubprocessContext *result;
+ GPtrArray *real_argv;
+ gchar **iter;
+
+ g_return_val_if_fail (argv0 != NULL, NULL);
+ g_return_val_if_fail (argv != NULL && argv[0] != NULL, NULL);
+
+ real_argv = g_ptr_array_new ();
+ g_ptr_array_add (real_argv, (gchar*)argv0);
+ for (iter = argv; *iter; iter++)
+ g_ptr_array_add (real_argv, (gchar*) *iter);
+ g_ptr_array_add (real_argv, NULL);
+
+ result = g_object_new (GS_TYPE_SUBPROCESS_CONTEXT,
+ "argv", real_argv->pdata,
+ NULL);
+ result->has_argv0 = TRUE;
+
+ return result;
+}
+#endif
+
+static void
+gs_subprocess_context_init (GSSubprocessContext *self)
+{
+ self->stdin_fd = -1;
+ self->stdout_fd = -1;
+ self->stderr_fd = -1;
+ self->stdout_disposition = GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT;
+ self->stderr_disposition = GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT;
+ self->postfork_close_fds = g_array_new (FALSE, FALSE, sizeof (int));
+ self->inherit_fds = g_array_new (FALSE, FALSE, sizeof (int));
+}
+
+static void
+gs_subprocess_context_finalize (GObject *object)
+{
+ GSSubprocessContext *self = GS_SUBPROCESS_CONTEXT (object);
+
+ g_strfreev (self->argv);
+ g_strfreev (self->envp);
+ g_free (self->cwd);
+
+ g_free (self->stdin_path);
+ g_free (self->stdout_path);
+ g_free (self->stderr_path);
+
+ g_array_unref (self->postfork_close_fds);
+ g_array_unref (self->inherit_fds);
+
+ if (G_OBJECT_CLASS (gs_subprocess_context_parent_class)->finalize != NULL)
+ G_OBJECT_CLASS (gs_subprocess_context_parent_class)->finalize (object);
+}
+
+static void
+gs_subprocess_context_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GSSubprocessContext *self = GS_SUBPROCESS_CONTEXT (object);
+
+ switch (prop_id)
+ {
+ case PROP_ARGV:
+ self->argv = (gchar**) g_value_dup_boxed (value);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+gs_subprocess_context_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GSSubprocessContext *self = GS_SUBPROCESS_CONTEXT (object);
+
+ switch (prop_id)
+ {
+ case PROP_ARGV:
+ g_value_set_boxed (value, self->argv);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+gs_subprocess_context_class_init (GSSubprocessContextClass *class)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (class);
+
+ gobject_class->finalize = gs_subprocess_context_finalize;
+ gobject_class->get_property = gs_subprocess_context_get_property;
+ gobject_class->set_property = gs_subprocess_context_set_property;
+
+ /**
+ * GSSubprocessContext:argv:
+ *
+ * Array of arguments passed to child process; must have at least
+ * one element. The first element has special handling - if it is
+ * an not absolute path ( as determined by g_path_is_absolute() ),
+ * then the system search path will be used. See
+ * %G_SPAWN_SEARCH_PATH.
+ *
+ * Note that in order to use the Unix-specific argv0 functionality,
+ * you must use the setter function
+ * gs_subprocess_context_set_args_and_argv0(). For more information
+ * about this, see %G_SPAWN_FILE_AND_ARGV_ZERO.
+ *
+ * Since: 2.36
+ */
+ gs_subprocess_context_pspecs[PROP_ARGV] = g_param_spec_boxed ("argv", "Arguments", "Arguments for child process", G_TYPE_STRV,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (gobject_class, N_PROPS, gs_subprocess_context_pspecs);
+}
+
+/**
+ * gs_subprocess_context_argv_append:
+ * @self:
+ * @arg: An argument
+ *
+ * Append an argument to the child's argument vector.
+ */
+void
+gs_subprocess_context_argv_append (GSSubprocessContext *self,
+ const gchar *arg)
+{
+ GPtrArray *new_argv = g_ptr_array_new ();
+ gchar **iter;
+
+ for (iter = self->argv; *iter; iter++)
+ g_ptr_array_add (new_argv, *iter);
+ g_ptr_array_add (new_argv, g_strdup (arg));
+ g_ptr_array_add (new_argv, NULL);
+
+ /* Don't free elements */
+ g_free (self->argv);
+ self->argv = (char**)g_ptr_array_free (new_argv, FALSE);
+}
+
+/* Environment */
+
+/**
+ * gs_subprocess_context_set_environment:
+ * @self:
+ * @environ: (array zero-terminated=1) (element-type utf8): Environment KEY=VALUE pairs
+ *
+ * Replace the environment that will be used for the child process.
+ * The default is to inherit the current process.
+ */
+void
+gs_subprocess_context_set_environment (GSSubprocessContext *self,
+ gchar **env)
+{
+ g_strfreev (self->envp);
+ self->envp = g_strdupv (env);
+}
+
+void
+gs_subprocess_context_set_cwd (GSSubprocessContext *self,
+ const gchar *cwd)
+{
+ g_free (self->cwd);
+ self->cwd = g_strdup (cwd);
+}
+
+void
+gs_subprocess_context_set_keep_descriptors (GSSubprocessContext *self,
+ gboolean keep_descriptors)
+
+{
+ self->keep_descriptors = keep_descriptors ? 1 : 0;
+}
+
+void
+gs_subprocess_context_set_search_path (GSSubprocessContext *self,
+ gboolean search_path,
+ gboolean search_path_from_envp)
+{
+ self->search_path = search_path ? 1 : 0;
+ self->search_path_from_envp = search_path_from_envp ? 1 : 0;
+}
+
+void
+gs_subprocess_context_set_stdin_disposition (GSSubprocessContext *self,
+ GSSubprocessStreamDisposition disposition)
+{
+ g_return_if_fail (disposition != GS_SUBPROCESS_STREAM_DISPOSITION_STDERR_MERGE);
+ self->stdin_disposition = disposition;
+}
+
+void
+gs_subprocess_context_set_stdout_disposition (GSSubprocessContext *self,
+ GSSubprocessStreamDisposition disposition)
+{
+ g_return_if_fail (disposition != GS_SUBPROCESS_STREAM_DISPOSITION_STDERR_MERGE);
+ self->stdout_disposition = disposition;
+}
+
+void
+gs_subprocess_context_set_stderr_disposition (GSSubprocessContext *self,
+ GSSubprocessStreamDisposition disposition)
+{
+ self->stderr_disposition = disposition;
+}
+
+#ifdef G_OS_UNIX
+void
+gs_subprocess_context_set_stdin_file_path (GSSubprocessContext *self,
+ const gchar *path)
+{
+ self->stdin_disposition = GS_SUBPROCESS_STREAM_DISPOSITION_NULL;
+ g_free (self->stdin_path);
+ self->stdin_path = g_strdup (path);
+}
+
+void
+gs_subprocess_context_set_stdin_fd (GSSubprocessContext *self,
+ gint fd)
+{
+ self->stdin_disposition = GS_SUBPROCESS_STREAM_DISPOSITION_NULL;
+ self->stdin_fd = fd;
+}
+
+void
+gs_subprocess_context_set_stdout_file_path (GSSubprocessContext *self,
+ const gchar *path)
+{
+ self->stdout_disposition = GS_SUBPROCESS_STREAM_DISPOSITION_NULL;
+ g_free (self->stdout_path);
+ self->stdout_path = g_strdup (path);
+}
+
+void
+gs_subprocess_context_set_stdout_fd (GSSubprocessContext *self,
+ gint fd)
+{
+ self->stdout_disposition = GS_SUBPROCESS_STREAM_DISPOSITION_NULL;
+ self->stdout_fd = fd;
+}
+
+void
+gs_subprocess_context_set_stderr_file_path (GSSubprocessContext *self,
+ const gchar *path)
+{
+ self->stderr_disposition = GS_SUBPROCESS_STREAM_DISPOSITION_NULL;
+ g_free (self->stderr_path);
+ self->stderr_path = g_strdup (path);
+}
+
+void
+gs_subprocess_context_set_stderr_fd (GSSubprocessContext *self,
+ gint fd)
+{
+ self->stderr_disposition = GS_SUBPROCESS_STREAM_DISPOSITION_NULL;
+ self->stderr_fd = fd;
+}
+#endif
+
+#ifdef G_OS_UNIX
+/**
+ * gs_subprocess_context_set_child_setup: (skip)
+ * @self:
+ * @child_setup: Function to call in the newly forked child, before execve()
+ * @user_data: Data passed to child
+ *
+ * FIXME - note extensive restricitons on GSpawnChildSetupFunc here
+ */
+void
+gs_subprocess_context_set_child_setup (GSSubprocessContext *self,
+ GSpawnChildSetupFunc child_setup,
+ gpointer user_data)
+{
+ self->child_setup_func = child_setup;
+ self->child_setup_data = user_data;
+}
+
+static gboolean
+open_pipe_internal (GSSubprocessContext *self,
+ gboolean for_read,
+ void **out_stream,
+ gint *out_fdno,
+ GError **error)
+{
+ int pipefds[2];
+
+ g_return_val_if_fail (out_stream != NULL, FALSE);
+ g_return_val_if_fail (out_fdno != NULL, FALSE);
+
+ if (!g_unix_open_pipe (pipefds, FD_CLOEXEC, error))
+ return FALSE;
+
+ if (for_read)
+ {
+ *out_stream = g_unix_input_stream_new (pipefds[0], TRUE);
+ *out_fdno = pipefds[1];
+ }
+ else
+ {
+ *out_stream = g_unix_output_stream_new (pipefds[1], TRUE);
+ *out_fdno = pipefds[0];
+ }
+ g_array_append_val (self->inherit_fds, *out_fdno);
+ g_array_append_val (self->postfork_close_fds, *out_fdno);
+
+ return TRUE;
+}
+
+/**
+ * gs_subprocess_context_open_pipe_read:
+ * @self:
+ * @out_stream: (out) (transfer full): A newly referenced output stream
+ * @out_fdno: (out): File descriptor number for the subprocess side of the pipe
+ *
+ * This allows you to open a pipe between the parent and child
+ * processes, independent of the standard streams. For this function,
+ * the pipe is set up so that the parent can read, and the child can
+ * write. For the opposite version, see
+ * gs_subprocess_context_open_pipe_write().
+ *
+ * The returned @out_fdno is the file descriptor number that the child
+ * will see; you need to communicate this number via a separate
+ * channel, such as the argument list. For example, if you're using
+ * this pipe to send a password, provide
+ * <literal>--password-fd=&lt;fdno string&gt;</literal>.
+ *
+ * Returns: %TRUE on success, %FALSE on error (and @error will be set)
+ */
+gboolean
+gs_subprocess_context_open_pipe_read (GSSubprocessContext *self,
+ GInputStream **out_stream,
+ gint *out_fdno,
+ GError **error)
+{
+ return open_pipe_internal (self, TRUE, (void**)out_stream, out_fdno, error);
+}
+
+/**
+ * gs_subprocess_context_open_pipe_write:
+ * @self:
+ * @out_stream: (out) (transfer full): A newly referenced stream
+ * @out_fdno: (out): File descriptor number for the subprocess side of the pipe
+ *
+ * Like gs_subprocess_context_open_pipe_read(), but returns a writable
+ * channel from which the child process can read.
+ *
+ * Returns: %TRUE on success, %FALSE on error (and @error will be set)
+ */
+gboolean
+gs_subprocess_context_open_pipe_write (GSSubprocessContext *self,
+ GOutputStream **out_stream,
+ gint *out_fdno,
+ GError **error)
+{
+ return open_pipe_internal (self, FALSE, (void**)out_stream, out_fdno, error);
+}
+
+#endif
+
+#endif
diff --git a/src/gsystem-subprocess-context.h b/src/gsystem-subprocess-context.h
new file mode 100644
index 0000000..b8a2401
--- /dev/null
+++ b/src/gsystem-subprocess-context.h
@@ -0,0 +1,128 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2012 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_SUBPROCESS_CONTEXT_H__
+#define __GSYSTEM_SUBPROCESS_CONTEXT_H__
+
+#include <gio/gio.h>
+
+#if GLIB_CHECK_VERSION(2,34,0)
+
+G_BEGIN_DECLS
+
+#define GS_TYPE_SUBPROCESS_CONTEXT (gs_subprocess_context_get_type ())
+#define GS_SUBPROCESS_CONTEXT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GS_TYPE_SUBPROCESS_CONTEXT, GSSubprocessContext))
+#define GS_IS_SUBPROCESS_CONTEXT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GS_TYPE_SUBPROCESS_CONTEXT))
+
+typedef struct _GSSubprocessContext GSSubprocessContext;
+
+/**
+ * GSSubprocessStreamDisposition:
+ * @GS_SUBPROCESS_STREAM_DISPOSITION_NULL: Redirect to operating system's null output stream
+ * @GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT: Keep the stream from the parent process
+ * @GS_SUBPROCESS_STREAM_DISPOSITION_PIPE: Open a private unidirectional channel between the processes
+ * @GS_SUBPROCESS_STREAM_DISPOSITION_STDERR_MERGE: Only applicable to standard error; causes it to be merged with standard output
+ *
+ * Flags to define the behaviour of the standard input/output/error of
+ * a #GSSubprocess.
+ *
+ * Since: 2.36
+ **/
+typedef enum {
+ GS_SUBPROCESS_STREAM_DISPOSITION_NULL,
+ GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT,
+ GS_SUBPROCESS_STREAM_DISPOSITION_PIPE,
+ GS_SUBPROCESS_STREAM_DISPOSITION_STDERR_MERGE
+} GSSubprocessStreamDisposition;
+
+GType gs_subprocess_context_get_type (void) G_GNUC_CONST;
+
+GSSubprocessContext * gs_subprocess_context_new (gchar **argv);
+GSSubprocessContext * gs_subprocess_context_newv (const gchar *first_arg,
+ ...);
+GSSubprocessContext * gs_subprocess_context_newa (const gchar *first_arg,
+ va_list args);
+
+#ifdef G_OS_UNIX
+GSSubprocessContext * gs_subprocess_context_new_argv0 (const gchar *argv0,
+ gchar **argv);
+#endif
+
+void gs_subprocess_context_argv_append (GSSubprocessContext *self,
+ const gchar *arg);
+
+/* Environment */
+
+void gs_subprocess_context_set_environment (GSSubprocessContext *self,
+ gchar **environ);
+void gs_subprocess_context_set_cwd (GSSubprocessContext *self,
+ const gchar *cwd);
+void gs_subprocess_context_set_keep_descriptors (GSSubprocessContext *self,
+ gboolean keep_descriptors);
+void gs_subprocess_context_set_search_path (GSSubprocessContext *self,
+ gboolean search_path,
+ gboolean search_path_from_envp);
+
+/* Basic I/O control */
+
+void gs_subprocess_context_set_stdin_disposition (GSSubprocessContext *self,
+ GSSubprocessStreamDisposition disposition);
+void gs_subprocess_context_set_stdout_disposition (GSSubprocessContext *self,
+ GSSubprocessStreamDisposition disposition);
+void gs_subprocess_context_set_stderr_disposition (GSSubprocessContext *self,
+ GSSubprocessStreamDisposition disposition);
+
+/* Extended I/O control, only available on UNIX */
+
+#ifdef G_OS_UNIX
+void gs_subprocess_context_set_stdin_file_path (GSSubprocessContext *self,
+ const gchar *path);
+void gs_subprocess_context_set_stdin_fd (GSSubprocessContext *self,
+ gint fd);
+void gs_subprocess_context_set_stdout_file_path (GSSubprocessContext *self,
+ const gchar *path);
+void gs_subprocess_context_set_stdout_fd (GSSubprocessContext *self,
+ gint fd);
+void gs_subprocess_context_set_stderr_file_path (GSSubprocessContext *self,
+ const gchar *path);
+void gs_subprocess_context_set_stderr_fd (GSSubprocessContext *self,
+ gint fd);
+
+gboolean gs_subprocess_context_open_pipe_read (GSSubprocessContext *self,
+ GInputStream **out_stream,
+ gint *out_fdno,
+ GError **error);
+gboolean gs_subprocess_context_open_pipe_write (GSSubprocessContext *self,
+ GOutputStream **out_stream,
+ gint *out_fdno,
+ GError **error);
+#endif
+
+/* Child setup, only available on UNIX */
+#ifdef G_OS_UNIX
+void gs_subprocess_context_set_child_setup (GSSubprocessContext *self,
+ GSpawnChildSetupFunc child_setup,
+ gpointer user_data);
+#endif
+
+G_END_DECLS
+
+#endif
+#endif
diff --git a/src/gsystem-subprocess.c b/src/gsystem-subprocess.c
new file mode 100644
index 0000000..4ec680f
--- /dev/null
+++ b/src/gsystem-subprocess.c
@@ -0,0 +1,966 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* GIO - GLib Input, Output and Streaming Library
+ *
+ * Copyright © 2012 Red Hat, Inc.
+ * Copyright © 2012 Canonical Limited
+ *
+ * This program 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 licence or (at
+ * your option) any later version.
+ *
+ * See the included COPYING file for more information.
+ *
+ * Authors: Colin Walters <walters@verbum.org>
+ * Ryan Lortie <desrt@desrt.ca>
+ */
+
+#include "config.h"
+
+#define _GSYSTEM_NO_LOCAL_ALLOC
+#include "libgsystem.h"
+
+#if GLIB_CHECK_VERSION(2,34,0)
+
+/**
+ * SECTION:gssubprocess
+ * @title: GSSubprocess
+ * @short_description: Create child processes and monitor their status
+ *
+ * This class wraps the lower-level g_spawn_async_with_pipes() API,
+ * providing a more modern GIO-style API, such as returning
+ * #GInputStream objects for child output pipes.
+ *
+ * One major advantage that GIO brings over the core GLib library is
+ * comprehensive API for asynchronous I/O, such
+ * g_output_stream_splice_async(). This makes GSubprocess
+ * significantly more powerful and flexible than equivalent APIs in
+ * some other languages such as the <literal>subprocess.py</literal>
+ * included with Python. For example, using #GSubprocess one could
+ * create two child processes, reading standard output from the first,
+ * processing it, and writing to the input stream of the second, all
+ * without blocking the main loop.
+ *
+ * Since: 2.36
+ */
+
+#include "config.h"
+
+#include "gsystem-subprocess.h"
+#include "gsystem-subprocess-context-private.h"
+
+#include <string.h>
+#ifdef G_OS_UNIX
+#include <gio/gunixoutputstream.h>
+#include <gio/gfiledescriptorbased.h>
+#include <gio/gunixinputstream.h>
+#include <glib-unix.h>
+#endif
+#include <fcntl.h>
+#ifdef G_OS_WIN32
+#define _WIN32_WINNT 0x0500
+#include <windows.h>
+#include "giowin32-priv.h"
+#endif
+
+#ifndef O_BINARY
+#define O_BINARY 0
+#endif
+
+static void initable_iface_init (GInitableIface *initable_iface);
+
+typedef GObjectClass GSSubprocessClass;
+
+#ifdef G_OS_UNIX
+static void
+gs_subprocess_unix_queue_waitpid (GSSubprocess *self);
+#endif
+
+struct _GSSubprocess
+{
+ GObject parent;
+
+ GSSubprocessContext *context;
+ GPid pid;
+
+ guint pid_valid : 1;
+ guint reaped_child : 1;
+ guint unused : 30;
+
+ /* These are the streams created if a pipe is requested via flags. */
+ GOutputStream *stdin_pipe;
+ GInputStream *stdout_pipe;
+ GInputStream *stderr_pipe;
+};
+
+G_DEFINE_TYPE_WITH_CODE (GSSubprocess, gs_subprocess, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init));
+
+enum
+{
+ PROP_0,
+ PROP_CONTEXT,
+ N_PROPS
+};
+
+static GParamSpec *gs_subprocess_pspecs[N_PROPS];
+
+static void
+gs_subprocess_init (GSSubprocess *self)
+{
+}
+
+static void
+gs_subprocess_finalize (GObject *object)
+{
+ GSSubprocess *self = GS_SUBPROCESS (object);
+
+ if (self->pid_valid)
+ {
+#ifdef G_OS_UNIX
+ /* Here we need to actually call waitpid() to clean up the
+ * zombie. In case the child hasn't actually exited, defer this
+ * cleanup to the worker thread.
+ */
+ if (!self->reaped_child)
+ gs_subprocess_unix_queue_waitpid (self);
+#endif
+ g_spawn_close_pid (self->pid);
+ }
+
+ g_clear_object (&self->stdin_pipe);
+ g_clear_object (&self->stdout_pipe);
+ g_clear_object (&self->stderr_pipe);
+
+ if (G_OBJECT_CLASS (gs_subprocess_parent_class)->finalize != NULL)
+ G_OBJECT_CLASS (gs_subprocess_parent_class)->finalize (object);
+}
+
+static void
+gs_subprocess_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GSSubprocess *self = GS_SUBPROCESS (object);
+
+ switch (prop_id)
+ {
+ case PROP_CONTEXT:
+ self->context = g_value_dup_object (value);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+gs_subprocess_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GSSubprocess *self = GS_SUBPROCESS (object);
+
+ switch (prop_id)
+ {
+ case PROP_CONTEXT:
+ g_value_set_object (value, self->context);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+gs_subprocess_class_init (GSSubprocessClass *class)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (class);
+
+ gobject_class->finalize = gs_subprocess_finalize;
+ gobject_class->get_property = gs_subprocess_get_property;
+ gobject_class->set_property = gs_subprocess_set_property;
+
+ /**
+ * GSSubprocess:context:
+ *
+ *
+ * Since: 2.36
+ */
+ gs_subprocess_pspecs[PROP_CONTEXT] = g_param_spec_object ("context", "Context", "Subprocess options", GS_TYPE_SUBPROCESS_CONTEXT,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (gobject_class, N_PROPS, gs_subprocess_pspecs);
+}
+
+#ifdef G_OS_UNIX
+
+static gboolean
+gs_subprocess_unix_waitpid_dummy (gpointer data)
+{
+ return FALSE;
+}
+
+static void
+gs_subprocess_unix_queue_waitpid (GSSubprocess *self)
+{
+ GMainContext *worker_context;
+ GSource *waitpid_source;
+
+#ifdef GLIB_COMPILATION
+ worker_context = GLIB_PRIVATE_CALL (g_get_worker_context) ();
+#else
+ worker_context = g_main_context_get_thread_default ();
+#endif
+ waitpid_source = g_child_watch_source_new (self->pid);
+ g_source_set_callback (waitpid_source, gs_subprocess_unix_waitpid_dummy, NULL, NULL);
+ g_source_attach (waitpid_source, worker_context);
+ g_source_unref (waitpid_source);
+}
+
+#endif
+
+static GInputStream *
+platform_input_stream_from_spawn_fd (gint fd)
+{
+ if (fd < 0)
+ return NULL;
+
+#ifdef G_OS_UNIX
+ return g_unix_input_stream_new (fd, TRUE);
+#else
+ return g_win32_input_stream_new_from_fd (fd, TRUE);
+#endif
+}
+
+static GOutputStream *
+platform_output_stream_from_spawn_fd (gint fd)
+{
+ if (fd < 0)
+ return NULL;
+
+#ifdef G_OS_UNIX
+ return g_unix_output_stream_new (fd, TRUE);
+#else
+ return g_win32_output_stream_new_from_fd (fd, TRUE);
+#endif
+}
+
+#ifdef G_OS_UNIX
+static gint
+unix_open_file (const char *filename,
+ gint mode,
+ GError **error)
+{
+ gint my_fd;
+
+ do
+ my_fd = open (filename, mode | O_BINARY | O_CLOEXEC, 0666);
+ while (my_fd == -1 && errno == EINTR);
+
+ /* If we return -1 we should also set the error */
+ if (my_fd < 0)
+ {
+ gint saved_errno = errno;
+ char *display_name;
+
+ display_name = g_filename_display_name (filename);
+ g_set_error (error, G_IO_ERROR, g_io_error_from_errno (saved_errno),
+ "Error opening file '%s': %s", display_name,
+ g_strerror (saved_errno));
+ g_free (display_name);
+ /* fall through... */
+ }
+
+ return my_fd;
+}
+#endif
+
+typedef struct
+{
+ gint fds[3];
+ GArray *inherit_fds;
+ GSpawnChildSetupFunc child_setup_func;
+ gpointer child_setup_data;
+} ChildData;
+
+static void
+child_setup (gpointer user_data)
+{
+ ChildData *child_data = user_data;
+ guint i;
+ gint result;
+
+ /* We're on the child side now. "Rename" the file descriptors in
+ * child_data.fds[] to stdin/stdout/stderr.
+ *
+ * We don't close the originals. It's possible that the originals
+ * should not be closed and if they should be closed then they should
+ * have been created O_CLOEXEC.
+ */
+ for (i = 0; i < 3; i++)
+ {
+ if (child_data->fds[i] != -1 && child_data->fds[i] != (int) i)
+ {
+ do
+ result = dup2 (child_data->fds[i], i);
+ while (G_UNLIKELY (result == -1 && errno == EINTR));
+ }
+ }
+
+ /* Unset the CLOEXEC flag for the child *should* inherit */
+ for (i = 0; i < child_data->inherit_fds->len; i++)
+ {
+ int fd = g_array_index (child_data->inherit_fds, int, i);
+ int flags;
+
+ do
+ flags = fcntl (fd, F_GETFL);
+ while (G_UNLIKELY (flags == -1 && errno == EINTR));
+
+ flags &= ~FD_CLOEXEC;
+
+ do
+ result = fcntl (fd, F_SETFD, flags);
+ while (G_UNLIKELY (result == -1 && errno == EINTR));
+ }
+
+ if (child_data->child_setup_func)
+ child_data->child_setup_func (child_data->child_setup_data);
+}
+
+static gboolean
+initable_init (GInitable *initable,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GSSubprocess *self = GS_SUBPROCESS (initable);
+ ChildData child_data = { { -1, -1, -1 } };
+ gint *pipe_ptrs[3] = { NULL, NULL, NULL };
+ gint pipe_fds[3] = { -1, -1, -1 };
+ gint close_fds[3] = { -1, -1, -1 };
+ GSpawnFlags spawn_flags = 0;
+ gboolean success = FALSE;
+ guint i;
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return FALSE;
+
+ /* We must setup the three fds that will end up in the child as stdin,
+ * stdout and stderr.
+ *
+ * First, stdin.
+ */
+#ifdef G_OS_UNIX
+ if (self->context->stdin_fd != -1)
+ child_data.fds[0] = self->context->stdin_fd;
+ else if (self->context->stdin_path != NULL)
+ {
+ child_data.fds[0] = close_fds[0] = unix_open_file (self->context->stdin_path,
+ O_RDONLY, error);
+ if (child_data.fds[0] == -1)
+ goto out;
+ }
+ else
+#endif
+ if (self->context->stdin_disposition == GS_SUBPROCESS_STREAM_DISPOSITION_NULL)
+ ; /* nothing */
+ else if (self->context->stdin_disposition == GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT)
+ spawn_flags |= G_SPAWN_CHILD_INHERITS_STDIN;
+ else if (self->context->stdin_disposition == GS_SUBPROCESS_STREAM_DISPOSITION_PIPE)
+ pipe_ptrs[0] = &pipe_fds[0];
+ else
+ g_assert_not_reached ();
+
+ /* Next, stdout. */
+#ifdef G_OS_UNIX
+ if (self->context->stdout_fd != -1)
+ child_data.fds[1] = self->context->stdout_fd;
+ else if (self->context->stdout_path != NULL)
+ {
+ child_data.fds[1] = close_fds[1] = unix_open_file (self->context->stdout_path,
+ O_CREAT | O_WRONLY, error);
+ if (child_data.fds[1] == -1)
+ goto out;
+ }
+ else
+#endif
+ if (self->context->stdout_disposition == GS_SUBPROCESS_STREAM_DISPOSITION_NULL)
+ spawn_flags |= G_SPAWN_STDOUT_TO_DEV_NULL;
+ else if (self->context->stdout_disposition == GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT)
+ ; /* Nothing */
+ else if (self->context->stdout_disposition == GS_SUBPROCESS_STREAM_DISPOSITION_PIPE)
+ pipe_ptrs[1] = &pipe_fds[1];
+ else
+ g_assert_not_reached ();
+
+ /* Finally, stderr. */
+#ifdef G_OS_UNIX
+ if (self->context->stderr_fd != -1)
+ child_data.fds[2] = self->context->stderr_fd;
+ else if (self->context->stderr_path != NULL)
+ {
+ child_data.fds[2] = close_fds[2] = unix_open_file (self->context->stderr_path,
+ O_CREAT | O_WRONLY, error);
+ if (child_data.fds[2] == -1)
+ goto out;
+ }
+ else
+#endif
+ if (self->context->stderr_disposition == GS_SUBPROCESS_STREAM_DISPOSITION_NULL)
+ spawn_flags |= G_SPAWN_STDERR_TO_DEV_NULL;
+ else if (self->context->stderr_disposition == GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT)
+ ; /* Nothing */
+ else if (self->context->stderr_disposition == GS_SUBPROCESS_STREAM_DISPOSITION_PIPE)
+ pipe_ptrs[2] = &pipe_fds[2];
+ else if (self->context->stderr_disposition == GS_SUBPROCESS_STREAM_DISPOSITION_STDERR_MERGE)
+ /* This will work because stderr gets setup after stdout. */
+ child_data.fds[2] = 1;
+ else
+ g_assert_not_reached ();
+
+ child_data.inherit_fds = self->context->inherit_fds;
+
+ if (self->context->keep_descriptors)
+ spawn_flags |= G_SPAWN_LEAVE_DESCRIPTORS_OPEN;
+
+ if (self->context->search_path)
+ spawn_flags |= G_SPAWN_SEARCH_PATH;
+ else if (self->context->search_path_from_envp)
+ spawn_flags |= G_SPAWN_SEARCH_PATH_FROM_ENVP;
+ else if (!g_path_is_absolute (((gchar**)self->context->argv)[0]))
+ spawn_flags |= G_SPAWN_SEARCH_PATH;
+
+ if (self->context->has_argv0)
+ spawn_flags |= G_SPAWN_FILE_AND_ARGV_ZERO;
+
+ spawn_flags |= G_SPAWN_DO_NOT_REAP_CHILD;
+#ifdef GLIB_COMPILATION
+ spawn_flags |= G_SPAWN_CLOEXEC_PIPES;
+#endif
+
+ child_data.child_setup_func = self->context->child_setup_func;
+ child_data.child_setup_data = self->context->child_setup_data;
+ success = g_spawn_async_with_pipes (self->context->cwd,
+ (char**)self->context->argv,
+ self->context->envp,
+ spawn_flags,
+ child_setup, &child_data,
+ &self->pid,
+ pipe_ptrs[0], pipe_ptrs[1], pipe_ptrs[2],
+ error);
+ if (success)
+ self->pid_valid = TRUE;
+
+out:
+ for (i = 0; i < 3; i++)
+ if (close_fds[i] != -1)
+ close (close_fds[i]);
+
+ for (i = 0; i < self->context->postfork_close_fds->len; i++)
+ (void) close (g_array_index (self->context->postfork_close_fds, int, i));
+
+ self->stdin_pipe = platform_output_stream_from_spawn_fd (pipe_fds[0]);
+ self->stdout_pipe = platform_input_stream_from_spawn_fd (pipe_fds[1]);
+ self->stderr_pipe = platform_input_stream_from_spawn_fd (pipe_fds[2]);
+
+ return success;
+}
+
+static void
+initable_iface_init (GInitableIface *initable_iface)
+{
+ initable_iface->init = initable_init;
+}
+
+/**
+ * gs_subprocess_new:
+ *
+ * Create a new process, using the parameters specified by
+ * GSSubprocessContext.
+ *
+ * Returns: (transfer full): A newly created %GSSubprocess, or %NULL on error (and @error will be set)
+ *
+ * Since: 2.36
+ */
+GSSubprocess *
+gs_subprocess_new (GSSubprocessContext *context,
+ GCancellable *cancellable,
+ GError **error)
+{
+ return g_initable_new (GS_TYPE_SUBPROCESS,
+ cancellable, error,
+ "context", context,
+ NULL);
+}
+
+/**
+ * gs_subprocess_get_pid:
+ * @self: a #GSSubprocess
+ *
+ * The identifier for this child process; it is valid as long as the
+ * process @self is referenced. In particular, do
+ * <emphasis>not</emphasis> call g_spawn_close_pid() on this value;
+ * that is handled internally.
+ *
+ * On some Unix versions, it is possible for there to be a race
+ * condition where waitpid() may have been called to collect the child
+ * before any watches (such as that installed by
+ * gs_subprocess_add_watch()) have fired. If you are planning to use
+ * native functions such as kill() on the pid, your program should
+ * gracefully handle an %ESRCH result to mitigate this.
+ *
+ * If you want to request process termination, using the high level
+ * gs_subprocess_request_exit() and gs_subprocess_force_exit() API is
+ * recommended.
+ *
+ * Returns: Operating-system specific identifier for child process
+ *
+ * Since: 2.36
+ */
+GPid
+gs_subprocess_get_pid (GSSubprocess *self)
+{
+ g_return_val_if_fail (GS_IS_SUBPROCESS (self), 0);
+
+ return self->pid;
+}
+
+/**
+ * gs_subprocess_get_stdin_pipe:
+ *
+ * Returns: (transfer none): Pipe
+ */
+GOutputStream *
+gs_subprocess_get_stdin_pipe (GSSubprocess *self)
+{
+ g_return_val_if_fail (GS_IS_SUBPROCESS (self), NULL);
+ g_return_val_if_fail (self->stdin_pipe, NULL);
+
+ return self->stdin_pipe;
+}
+
+/**
+ * gs_subprocess_get_stdout_pipe:
+ *
+ * Returns: (transfer none): Pipe
+ */
+GInputStream *
+gs_subprocess_get_stdout_pipe (GSSubprocess *self)
+{
+ g_return_val_if_fail (GS_IS_SUBPROCESS (self), NULL);
+ g_return_val_if_fail (self->stdout_pipe, NULL);
+
+ return self->stdout_pipe;
+}
+
+/**
+ * gs_subprocess_get_stderr_pipe:
+ *
+ * Returns: (transfer none): Pipe
+ */
+GInputStream *
+gs_subprocess_get_stderr_pipe (GSSubprocess *self)
+{
+ g_return_val_if_fail (GS_IS_SUBPROCESS (self), NULL);
+ g_return_val_if_fail (self->stderr_pipe, NULL);
+
+ return self->stderr_pipe;
+}
+
+typedef struct {
+ GSSubprocess *self;
+ GCancellable *cancellable;
+ GSimpleAsyncResult *result;
+} GSSubprocessWatchData;
+
+static gboolean
+gs_subprocess_on_child_exited (GPid pid,
+ gint status_code,
+ gpointer user_data)
+{
+ GSSubprocessWatchData *data = user_data;
+ GError *error = NULL;
+
+ if (g_cancellable_set_error_if_cancelled (data->cancellable, &error))
+ {
+ g_simple_async_result_take_error (data->result, error);
+ }
+ else
+ {
+ data->self->reaped_child = TRUE;
+
+ g_simple_async_result_set_op_res_gssize (data->result, status_code);
+ }
+
+ g_simple_async_result_complete (data->result);
+
+ g_object_unref (data->result);
+ g_object_unref (data->self);
+ g_free (data);
+
+ return FALSE;
+}
+
+/**
+ * gs_subprocess_wait:
+ * @self: a #GSSubprocess
+ * @cancellable: a #GCancellable
+ * @callback: Invoked when process exits, or @cancellable is cancelled
+ * @user_data: Data for @callback
+ *
+ * Start an asynchronous wait for the subprocess @self to exit.
+ *
+ * Since: 2.36
+ */
+void
+gs_subprocess_wait (GSSubprocess *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSource *source;
+ GSSubprocessWatchData *data;
+
+ data = g_new0 (GSSubprocessWatchData, 1);
+
+ data->self = g_object_ref (self);
+ data->result = g_simple_async_result_new ((GObject*)self, callback, user_data,
+ gs_subprocess_wait);
+
+ source = g_child_watch_source_new (self->pid);
+
+ g_source_set_callback (source, (GSourceFunc)gs_subprocess_on_child_exited,
+ data, NULL);
+ if (cancellable)
+ {
+ GSource *cancellable_source;
+
+ data->cancellable = g_object_ref (cancellable);
+
+ cancellable_source = g_cancellable_source_new (cancellable);
+ g_source_add_child_source (source, cancellable_source);
+ g_source_unref (cancellable_source);
+ }
+
+ g_source_attach (source, g_main_context_get_thread_default ());
+ g_source_unref (source);
+}
+
+/**
+ * gs_subprocess_wait_finish:
+ * @self: a #GSSubprocess
+ * @result: a #GAsyncResult
+ * @out_exit_status: (out): Exit status of the process encoded in platform-specific way
+ * @error: a #GError
+ *
+ * The exit status of the process will be stored in @out_exit_status.
+ * See the documentation of g_spawn_check_exit_status() for more
+ * details.
+ *
+ * Note that @error is not set if the process exits abnormally; you
+ * must use g_spawn_check_exit_status() for that.
+ *
+ * Since: 2.36
+ */
+gboolean
+gs_subprocess_wait_finish (GSSubprocess *self,
+ GAsyncResult *result,
+ int *out_exit_status,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+
+ if (g_simple_async_result_propagate_error (simple, error))
+ return FALSE;
+
+ *out_exit_status = g_simple_async_result_get_op_res_gssize (simple);
+
+ return TRUE;
+}
+
+typedef struct {
+ GMainLoop *loop;
+ gint *exit_status_ptr;
+ gboolean caught_error;
+ GError **error;
+} GSSubprocessSyncWaitData;
+
+static void
+gs_subprocess_on_sync_wait_complete (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GSSubprocessSyncWaitData *data = user_data;
+
+ if (!gs_subprocess_wait_finish ((GSSubprocess*)object, result,
+ data->exit_status_ptr, data->error))
+ data->caught_error = TRUE;
+
+ g_main_loop_quit (data->loop);
+}
+
+/**
+ * gs_subprocess_wait_sync:
+ * @self: a #GSSubprocess
+ * @out_exit_status: (out): Platform-specific exit code
+ * @cancellable: a #GCancellable
+ * @error: a #GError
+ *
+ * Synchronously wait for the subprocess to terminate, returning the
+ * status code in @out_exit_status. See the documentation of
+ * g_spawn_check_exit_status() for how to interpret it. Note that if
+ * @error is set, then @out_exit_status will be left uninitialized.
+ *
+ * Returns: %TRUE on success, %FALSE if @cancellable was cancelled
+ *
+ * Since: 2.36
+ */
+gboolean
+gs_subprocess_wait_sync (GSSubprocess *self,
+ int *out_exit_status,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ gboolean pushed_thread_default = FALSE;
+ GMainContext *context = NULL;
+ GSSubprocessSyncWaitData data;
+
+ memset (&data, 0, sizeof (data));
+
+ g_return_val_if_fail (GS_IS_SUBPROCESS (self), FALSE);
+
+ if (g_cancellable_set_error_if_cancelled (cancellable, error))
+ return FALSE;
+
+ context = g_main_context_new ();
+ g_main_context_push_thread_default (context);
+ pushed_thread_default = TRUE;
+
+ data.exit_status_ptr = out_exit_status;
+ data.loop = g_main_loop_new (context, TRUE);
+ data.error = error;
+
+ gs_subprocess_wait (self, cancellable,
+ gs_subprocess_on_sync_wait_complete, &data);
+
+ g_main_loop_run (data.loop);
+
+ if (data.caught_error)
+ goto out;
+
+ ret = TRUE;
+ out:
+ if (pushed_thread_default)
+ g_main_context_pop_thread_default (context);
+ if (context)
+ g_main_context_unref (context);
+ if (data.loop)
+ g_main_loop_unref (data.loop);
+
+ return ret;
+}
+
+/**
+ * gs_subprocess_wait_sync_check:
+ * @self: a #GSSubprocess
+ * @cancellable: a #GCancellable
+ * @error: a #GError
+ *
+ * Combines gs_subprocess_wait_sync() with g_spawn_check_exit_status().
+ *
+ * Returns: %TRUE on success, %FALSE if process exited abnormally, or @cancellable was cancelled
+ *
+ * Since: 2.36
+ */
+gboolean
+gs_subprocess_wait_sync_check (GSSubprocess *self,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ int exit_status;
+
+ if (!gs_subprocess_wait_sync (self, &exit_status, cancellable, error))
+ goto out;
+
+ if (!g_spawn_check_exit_status (exit_status, error))
+ goto out;
+
+ ret = TRUE;
+ out:
+ return ret;
+}
+
+/**
+ * gs_subprocess_request_exit:
+ * @self: a #GSSubprocess
+ *
+ * This API uses an operating-system specific mechanism to request
+ * that the subprocess gracefully exit. This API is not available on
+ * all operating systems; for those not supported, it will do nothing
+ * and return %FALSE. Portable code should handle this situation
+ * gracefully. For example, if you are communicating via input or
+ * output pipe with the child, many programs will automatically exit
+ * when one of their standard input or output are closed.
+ *
+ * On Unix, this API sends %SIGTERM.
+ *
+ * A %TRUE return value does <emphasis>not</emphasis> mean the
+ * subprocess has exited, merely that an exit request was initiated.
+ * You can use gs_subprocess_add_watch() to monitor the status of the
+ * process after calling this function.
+ *
+ * This function returns %TRUE if the process has already exited.
+ *
+ * Returns: %TRUE if the operation is supported, %FALSE otherwise.
+ *
+ * Since: 2.36
+ */
+gboolean
+gs_subprocess_request_exit (GSSubprocess *self)
+{
+ g_return_val_if_fail (GS_IS_SUBPROCESS (self), FALSE);
+
+#ifdef G_OS_UNIX
+ (void) kill (self->pid, SIGTERM);
+ return TRUE;
+#else
+ return FALSE;
+#endif
+}
+
+/**
+ * gs_subprocess_force_exit:
+ * @self: a #GSSubprocess
+ *
+ * Use an operating-system specific method to attempt an immediate,
+ * forceful termination of the process. There is no mechanism to
+ * determine whether or not the request itself was successful;
+ * however, you can use gs_subprocess_wait() to monitor the status of
+ * the process after calling this function.
+ *
+ * On Unix, this function sends %SIGKILL.
+ */
+void
+gs_subprocess_force_exit (GSSubprocess *self)
+{
+ g_return_if_fail (GS_IS_SUBPROCESS (self));
+
+#if !defined(GLIB_COMPIATION)
+ {
+ int ret;
+ do
+ ret = kill (self->pid, SIGKILL);
+ while (ret == -1 && errno == EINTR);
+ }
+#elif defined(G_OS_UNIX)
+ GLIB_PRIVATE_CALL (g_main_send_signal) (self->pid, SIGKILL);
+#else
+ TerminateProcess (self->pid, 1);
+#endif
+}
+
+GSSubprocess *
+gs_subprocess_new_simple_argl (GSSubprocessStreamDisposition stdout_disposition,
+ GSSubprocessStreamDisposition stderr_disposition,
+ GCancellable *cancellable,
+ GError **error,
+ const gchar *first_arg,
+ ...)
+{
+ va_list args;
+ GSSubprocess *result;
+ GSSubprocessContext *context;
+
+ va_start (args, first_arg);
+ context = gs_subprocess_context_newa (first_arg, args);
+ va_end (args);
+ result = gs_subprocess_new (context, cancellable, error);
+ g_object_unref (context);
+
+ return result;
+}
+
+/**
+ * gs_subprocess_new_simple_argv:
+ * @argv: (array zero-terminated=1) (element-type utf8): Argument array
+ * @stdout_disposition: Where to redirect stdout
+ * @stderr_disposition: Where to redirect stdout
+ * @error: a #GError
+ *
+ * Create a new subprocess using the provided argument array and
+ * stream dispositions.
+ */
+GSSubprocess *
+gs_subprocess_new_simple_argv (gchar **argv,
+ GSSubprocessStreamDisposition stdout_disposition,
+ GSSubprocessStreamDisposition stderr_disposition,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GSSubprocessContext *context;
+ GSSubprocess *result;
+
+ context = gs_subprocess_context_new (argv);
+ gs_subprocess_context_set_stdout_disposition (context, stdout_disposition);
+ gs_subprocess_context_set_stderr_disposition (context, stderr_disposition);
+
+ result = gs_subprocess_new (context, cancellable, error);
+ g_object_unref (context);
+
+ return result;
+}
+
+/**
+ * gs_subprocess_simple_run_sync:
+ * @cwd: Current working directory
+ * @stdin_disposition: What to do with standard input
+ * @cancellable: a #GCancellable
+ * @error: a #GError
+ * @first_arg: First argument
+ * @...: Remaining arguments, %NULL terminated
+ *
+ * Run a process synchronously, throw an error if it fails.
+ */
+gboolean
+gs_subprocess_simple_run_sync (const char *cwd,
+ GSSubprocessStreamDisposition stdin_disposition,
+ GCancellable *cancellable,
+ GError **error,
+ const char *first_arg,
+ ...)
+{
+ gboolean ret = FALSE;
+ va_list args;
+ GSSubprocess *proc = NULL;
+ GSSubprocessContext *context = NULL;
+
+ va_start (args, first_arg);
+ context = gs_subprocess_context_newa (first_arg, args);
+ va_end (args);
+ gs_subprocess_context_set_stdin_disposition (context, stdin_disposition);
+ gs_subprocess_context_set_cwd (context, cwd);
+ proc = gs_subprocess_new (context, cancellable, error);
+ if (!proc)
+ goto out;
+
+ if (!gs_subprocess_wait_sync_check (proc, cancellable, error))
+ goto out;
+
+ ret = TRUE;
+ out:
+ g_object_unref (context);
+ if (proc)
+ g_object_unref (proc);
+ return ret;
+}
+
+#endif
diff --git a/src/gsystem-subprocess.h b/src/gsystem-subprocess.h
new file mode 100644
index 0000000..f248f7b
--- /dev/null
+++ b/src/gsystem-subprocess.h
@@ -0,0 +1,101 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2012 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_SUBPROCESS_H__
+#define __GSYSTEM_SUBPROCESS_H__
+
+#include <gio/gio.h>
+
+#if GLIB_CHECK_VERSION(2,34,0)
+
+#include "gsystem-subprocess-context.h"
+
+G_BEGIN_DECLS
+
+#define GS_TYPE_SUBPROCESS (gs_subprocess_get_type ())
+#define GS_SUBPROCESS(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GS_TYPE_SUBPROCESS, GSSubprocess))
+#define GS_IS_SUBPROCESS(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GS_TYPE_SUBPROCESS))
+
+typedef struct _GSSubprocess GSSubprocess;
+
+GType gs_subprocess_get_type (void) G_GNUC_CONST;
+
+/**** Core API ****/
+
+GSSubprocess * gs_subprocess_new (GSSubprocessContext *context,
+ GCancellable *cancellable,
+ GError **error);
+
+GOutputStream * gs_subprocess_get_stdin_pipe (GSSubprocess *self);
+
+GInputStream * gs_subprocess_get_stdout_pipe (GSSubprocess *self);
+
+GInputStream * gs_subprocess_get_stderr_pipe (GSSubprocess *self);
+
+void gs_subprocess_wait (GSSubprocess *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gboolean gs_subprocess_wait_finish (GSSubprocess *self,
+ GAsyncResult *result,
+ int *out_exit_status,
+ GError **error);
+
+gboolean gs_subprocess_wait_sync (GSSubprocess *self,
+ int *out_exit_status,
+ GCancellable *cancellable,
+ GError **error);
+
+gboolean gs_subprocess_wait_sync_check (GSSubprocess *self,
+ GCancellable *cancellable,
+ GError **error);
+
+GPid gs_subprocess_get_pid (GSSubprocess *self);
+
+gboolean gs_subprocess_request_exit (GSSubprocess *self);
+
+void gs_subprocess_force_exit (GSSubprocess *self);
+
+/**** High level helpers ****/
+
+GSSubprocess * gs_subprocess_new_simple_argl (GSSubprocessStreamDisposition stdout_disposition,
+ GSSubprocessStreamDisposition stderr_disposition,
+ GCancellable *cancellable,
+ GError **error,
+ const char *first_arg,
+ ...) G_GNUC_NULL_TERMINATED;
+GSSubprocess * gs_subprocess_new_simple_argv (char **argv,
+ GSSubprocessStreamDisposition stdout_disposition,
+ GSSubprocessStreamDisposition stderr_disposition,
+ GCancellable *cancellable,
+ GError **error);
+
+gboolean gs_subprocess_simple_run_sync (const char *cwd,
+ GSSubprocessStreamDisposition stdin_disposition,
+ GCancellable *cancellable,
+ GError **error,
+ const char *first_arg,
+ ...) G_GNUC_NULL_TERMINATED;
+
+G_END_DECLS
+
+#endif
+#endif
diff --git a/src/libgsystem.h b/src/libgsystem.h
new file mode 100644
index 0000000..60884b6
--- /dev/null
+++ b/src/libgsystem.h
@@ -0,0 +1,49 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2012,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 __LIBGSYSTEM__
+#define __LIBGSYSTEM__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define gs_transfer_out_value(outp, srcp) G_STMT_START { \
+ if (outp) \
+ { \
+ *(outp) = *(srcp); \
+ *(srcp) = NULL; \
+ } \
+ } G_STMT_END;
+
+#include <gsystem-console.h>
+#include <gsystem-file-utils.h>
+#include <gsystem-shutil.h>
+#if GLIB_CHECK_VERSION(2,34,0)
+#include <gsystem-subprocess.h>
+#endif
+#include <gsystem-log.h>
+#ifndef _GSYSTEM_NO_LOCAL_ALLOC
+#include <gsystem-local-alloc.h>
+#endif
+
+G_END_DECLS
+
+#endif
diff --git a/src/libgsystem.pc.in b/src/libgsystem.pc.in
new file mode 100644
index 0000000..abf8575
--- /dev/null
+++ b/src/libgsystem.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: libgsystem
+Description: https://live.gnome.org/Projects/libgsystem
+Version: @VERSION@
+Requires: gio-unix-2.0
+Libs: -L${libdir} -lgsystem
+Cflags: -I${includedir}/libgsystem