diff options
author | Colin Walters <walters@verbum.org> | 2014-02-05 06:58:51 -0500 |
---|---|---|
committer | Colin Walters <walters@verbum.org> | 2014-02-06 04:12:18 -0500 |
commit | 9363cfc28ede912e2f06d4ccb42a646bb8a4bd2e (patch) | |
tree | 94d03e2391e4cdee87a8345013e3ac5b6e21cef4 /src | |
parent | 856b8f9431a63c7807fb3859ed6de2a0f3abfb3b (diff) | |
download | libgsystem-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.c | 443 | ||||
-rw-r--r-- | src/gsystem-console.h | 59 | ||||
-rw-r--r-- | src/gsystem-file-utils.c | 1644 | ||||
-rw-r--r-- | src/gsystem-file-utils.h | 173 | ||||
-rw-r--r-- | src/gsystem-glib-compat.h | 54 | ||||
-rw-r--r-- | src/gsystem-local-alloc.c | 72 | ||||
-rw-r--r-- | src/gsystem-local-alloc.h | 164 | ||||
-rw-r--r-- | src/gsystem-log.c | 167 | ||||
-rw-r--r-- | src/gsystem-log.h | 42 | ||||
-rw-r--r-- | src/gsystem-shutil.c | 460 | ||||
-rw-r--r-- | src/gsystem-shutil.h | 47 | ||||
-rw-r--r-- | src/gsystem-subprocess-context-private.h | 65 | ||||
-rw-r--r-- | src/gsystem-subprocess-context.c | 501 | ||||
-rw-r--r-- | src/gsystem-subprocess-context.h | 128 | ||||
-rw-r--r-- | src/gsystem-subprocess.c | 966 | ||||
-rw-r--r-- | src/gsystem-subprocess.h | 101 | ||||
-rw-r--r-- | src/libgsystem.h | 49 | ||||
-rw-r--r-- | src/libgsystem.pc.in | 11 |
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=<fdno string></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 |