From 9363cfc28ede912e2f06d4ccb42a646bb8a4bd2e Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Wed, 5 Feb 2014 06:58:51 -0500 Subject: Rework to be an installed library See https://mail.gnome.org/archives/desktop-devel-list/2014-February/msg00028.html --- Makefile-decls.am | 50 + Makefile-libgsystem.am | 64 +- Makefile.am | 34 + Makefile.dist-packaging | 39 + autogen.sh | 34 + configure.ac | 67 ++ gsystem-console.c | 443 -------- gsystem-console.h | 59 -- gsystem-file-utils.c | 1644 ------------------------------ gsystem-file-utils.h | 172 ---- gsystem-glib-compat.h | 54 - gsystem-local-alloc.c | 72 -- gsystem-local-alloc.h | 164 --- gsystem-log.c | 167 --- gsystem-log.h | 42 - gsystem-shutil.c | 460 --------- gsystem-shutil.h | 47 - gsystem-subprocess-context-private.h | 65 -- gsystem-subprocess-context.c | 501 --------- gsystem-subprocess-context.h | 128 --- gsystem-subprocess.c | 966 ------------------ gsystem-subprocess.h | 101 -- libgsystem.h | 49 - packaging/libgsystem.spec.in | 62 ++ packaging/rpmbuild-cwd | 11 + src/gsystem-console.c | 443 ++++++++ src/gsystem-console.h | 59 ++ src/gsystem-file-utils.c | 1644 ++++++++++++++++++++++++++++++ src/gsystem-file-utils.h | 173 ++++ src/gsystem-glib-compat.h | 54 + src/gsystem-local-alloc.c | 72 ++ src/gsystem-local-alloc.h | 164 +++ src/gsystem-log.c | 167 +++ src/gsystem-log.h | 42 + src/gsystem-shutil.c | 460 +++++++++ src/gsystem-shutil.h | 47 + src/gsystem-subprocess-context-private.h | 65 ++ src/gsystem-subprocess-context.c | 501 +++++++++ src/gsystem-subprocess-context.h | 128 +++ src/gsystem-subprocess.c | 966 ++++++++++++++++++ src/gsystem-subprocess.h | 101 ++ src/libgsystem.h | 49 + src/libgsystem.pc.in | 11 + 43 files changed, 5485 insertions(+), 5156 deletions(-) create mode 100644 Makefile-decls.am create mode 100644 Makefile.am create mode 100644 Makefile.dist-packaging create mode 100755 autogen.sh create mode 100644 configure.ac delete mode 100644 gsystem-console.c delete mode 100644 gsystem-console.h delete mode 100644 gsystem-file-utils.c delete mode 100644 gsystem-file-utils.h delete mode 100644 gsystem-glib-compat.h delete mode 100644 gsystem-local-alloc.c delete mode 100644 gsystem-local-alloc.h delete mode 100644 gsystem-log.c delete mode 100644 gsystem-log.h delete mode 100644 gsystem-shutil.c delete mode 100644 gsystem-shutil.h delete mode 100644 gsystem-subprocess-context-private.h delete mode 100644 gsystem-subprocess-context.c delete mode 100644 gsystem-subprocess-context.h delete mode 100644 gsystem-subprocess.c delete mode 100644 gsystem-subprocess.h delete mode 100644 libgsystem.h create mode 100644 packaging/libgsystem.spec.in create mode 100755 packaging/rpmbuild-cwd create mode 100644 src/gsystem-console.c create mode 100644 src/gsystem-console.h create mode 100644 src/gsystem-file-utils.c create mode 100644 src/gsystem-file-utils.h create mode 100644 src/gsystem-glib-compat.h create mode 100644 src/gsystem-local-alloc.c create mode 100644 src/gsystem-local-alloc.h create mode 100644 src/gsystem-log.c create mode 100644 src/gsystem-log.h create mode 100644 src/gsystem-shutil.c create mode 100644 src/gsystem-shutil.h create mode 100644 src/gsystem-subprocess-context-private.h create mode 100644 src/gsystem-subprocess-context.c create mode 100644 src/gsystem-subprocess-context.h create mode 100644 src/gsystem-subprocess.c create mode 100644 src/gsystem-subprocess.h create mode 100644 src/libgsystem.h create mode 100644 src/libgsystem.pc.in diff --git a/Makefile-decls.am b/Makefile-decls.am new file mode 100644 index 0000000..a57466c --- /dev/null +++ b/Makefile-decls.am @@ -0,0 +1,50 @@ +# Copyright (C) 2011,2014 Colin Walters +# +# 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. + +# Common variables +AM_CPPFLAGS = +AM_CFLAGS = +DISTCHECK_CONFIGURE_FLAGS = +SUBDIRS = +NULL = +BUILT_SOURCES = +MANPAGES = +CLEANFILES = +EXTRA_DIST = +bin_PROGRAMS = +sbin_PROGRAMS = +bin_SCRIPTS = +lib_LTLIBRARIES = +libexec_PROGRAMS = +noinst_LTLIBRARIES = +noinst_PROGRAMS = +privlibdir = $(pkglibdir) +privlib_LTLIBRARIES = +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = +INTROSPECTION_GIRS = +girdir = $(datadir)/gir-1.0 +gir_DATA = +typelibdir = $(libdir)/girepository-1.0 +typelib_DATA = +gsettings_SCHEMAS = +# git.mk +GITIGNOREFILES = + +# This is a special facility to chain together hooks easily +INSTALL_DATA_HOOKS = +install-data-hook: $(INSTALL_DATA_HOOKS) diff --git a/Makefile-libgsystem.am b/Makefile-libgsystem.am index 092522c..b75d517 100644 --- a/Makefile-libgsystem.am +++ b/Makefile-libgsystem.am @@ -1,4 +1,4 @@ -# Copyright (C) 2012 Colin Walters +# Copyright (C) 2012,2014 Colin Walters # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -15,28 +15,48 @@ # Free Software Foundation, Inc., 59 Temple Place - Suite 330, # Boston, MA 02111-1307, USA. -EXTRA_DIST += $(libgsystem_srcpath)/README $(libgsystem_srcpath)/COPYING +lib_LTLIBRARIES += libgsystem.la + +libgsystemheaderdir = $(includedir)/libgsystem +libgsystemheader_HEADERS = \ + src/gsystem-local-alloc.h \ + src/gsystem-console.h \ + src/gsystem-file-utils.h \ + src/gsystem-glib-compat.h \ + src/gsystem-shutil.h \ + src/gsystem-log.h \ + src/gsystem-subprocess-context.h \ + src/gsystem-subprocess.h \ + src/libgsystem.h \ + $(NULL) libgsystem_la_SOURCES = \ - $(libgsystem_srcpath)/gsystem-local-alloc.h \ - $(libgsystem_srcpath)/gsystem-local-alloc.c \ - $(libgsystem_srcpath)/gsystem-console.h \ - $(libgsystem_srcpath)/gsystem-console.c \ - $(libgsystem_srcpath)/gsystem-file-utils.h \ - $(libgsystem_srcpath)/gsystem-file-utils.c \ - $(libgsystem_srcpath)/gsystem-glib-compat.h \ - $(libgsystem_srcpath)/gsystem-shutil.h \ - $(libgsystem_srcpath)/gsystem-shutil.c \ - $(libgsystem_srcpath)/gsystem-log.h \ - $(libgsystem_srcpath)/gsystem-log.c \ - $(libgsystem_srcpath)/gsystem-subprocess-context.h \ - $(libgsystem_srcpath)/gsystem-subprocess-context-private.h \ - $(libgsystem_srcpath)/gsystem-subprocess-context.c \ - $(libgsystem_srcpath)/gsystem-subprocess.h \ - $(libgsystem_srcpath)/gsystem-subprocess.c \ - $(libgsystem_srcpath)/libgsystem.h \ + src/gsystem-local-alloc.c \ + src/gsystem-console.c \ + src/gsystem-file-utils.c \ + src/gsystem-shutil.c \ + src/gsystem-log.c \ + src/gsystem-subprocess-context-private.h \ + src/gsystem-subprocess-context.c \ + src/gsystem-subprocess.c \ $(NULL) -libgsystem_la_CFLAGS = $(AM_CFLAGS) $(libgsystem_cflags) -libgsystem_la_LDFLAGS = -avoid-version -Bsymbolic-functions -export-symbols-regex "^gs_" -no-undefined -export-dynamic -libgsystem_la_LIBADD = $(libgsystem_libs) +libgsystem_la_CFLAGS = $(AM_CFLAGS) $(BUILDDEP_GIO_UNIX_CFLAGS) $(BUILDDEP_SYSTEMD_JOURNAL_CFLAGS) -I$(srcdir)/src -DGSYSTEM_CONFIG_XATTRS +libgsystem_la_LDFLAGS = -version-info 0:0:0 -Bsymbolic-functions -export-symbols-regex "^gs_" -no-undefined -export-dynamic +libgsystem_la_LIBADD = $(BUILDDEP_GIO_UNIX_LIBS) $(BUILDDEP_SYSTEMD_JOURNAL_LIBS) + +pkgconfig_DATA += src/libgsystem.pc + +if BUILDOPT_INTROSPECTION +GSystem-1.0.gir: libgsystem.la Makefile +GSystem_1_0_gir_EXPORT_PACKAGES = libgsystem +GSystem_1_0_gir_INCLUDES = Gio-2.0 +GSystem_1_0_gir_CFLAGS = $(libgsystem_la_CFLAGS) +GSystem_1_0_gir_LIBS = libgsystem.la +GSystem_1_0_gir_SCANNERFLAGS = --warn-all --identifier-prefix=GS --symbol-prefix=gs +GSystem_1_0_gir_FILES = $(libgsystemheader_HEADERS) $(filter-out %-private.h,$(libgsystem_la_SOURCES)) +INTROSPECTION_GIRS += GSystem-1.0.gir +gir_DATA += GSystem-1.0.gir +typelib_DATA += GSystem-1.0.typelib +CLEANFILES += $(gir_DATA) $(typelib_DATA) +endif diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..472fbc1 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,34 @@ +# Copyright (C) 2011,2014 Colin Walters +# +# 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 Makefile-decls.am + +ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS} +AM_CPPFLAGS += -DDATADIR='"$(datadir)"' -DLIBEXECDIR='"$(libexecdir)"' \ + -DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_34 -DGLIB_VERSION_MAX_ALLOWED=GLIB_VERSION_2_34 +AM_CFLAGS += $(WARN_CFLAGS) +DISTCHECK_CONFIGURE_FLAGS += --enable-gtk-doc --disable-maintainer-mode + +SUBDIRS += . + +EXTRA_DIST += autogen.sh COPYING README + +if BUILDOPT_INTROSPECTION +include $(INTROSPECTION_MAKEFILE) +endif + +include Makefile-libgsystem.am diff --git a/Makefile.dist-packaging b/Makefile.dist-packaging new file mode 100644 index 0000000..75a52f6 --- /dev/null +++ b/Makefile.dist-packaging @@ -0,0 +1,39 @@ +# -*- mode: Makefile -*- + +GITREV = $$(git describe --always --tags) +GITREV_FOR_PKG = $(shell echo "$(GITREV)" | sed -e 's,-,\.,g' -e 's,^v,,') + +srcdir=$(shell pwd) +PACKAGE=$(shell basename $(srcdir)) + +PKG_VER = $(PACKAGE)-$(GITREV_FOR_PKG) + +dist-snapshot: + set -x; \ + echo "PACKAGE=$(PACKAGE)"; \ + TARFILE_TMP=$(PKG_VER).tar.tmp; \ + echo "Archiving $(PACKAGE) at $(GITREV)"; \ + (cd $(srcdir); git archive --format=tar --prefix=$(PKG_VER)/ $(GITREV)) > $${TARFILE_TMP}; \ + (cd $$(git rev-parse --show-toplevel); git submodule status) | while read line; do \ + rev=$$(echo $$line | cut -f 1 -d ' '); path=$$(echo $$line | cut -f 2 -d ' '); \ + echo "Archiving $${path} at $${rev}"; \ + (cd $(srcdir)/$$path; git archive --format=tar --prefix=$(PKG_VER)/$$path/ $${rev}) > submodule.tar; \ + tar -A -f $${TARFILE_TMP} submodule.tar; \ + rm submodule.tar; \ + done; \ + mv $(PKG_VER).tar{.tmp,}; \ + rm -f $(PKG_VER).tar.xz; \ + xz $(PKG_VER).tar + +srpm: dist-snapshot + (cd $(srcdir)/packaging; \ + cp ../$(PKG_VER).tar.xz . ; \ + sed -e "s,^Version:.*,Version: $(GITREV_FOR_PKG)," $(PACKAGE).spec.in > $(PACKAGE).spec; \ + ./rpmbuild-cwd -bs $(PACKAGE).spec) + +rpm: srpm + $(srcdir)/packaging/rpmbuild-cwd --rebuild packaging/$(PKG_VER)*.src.rpm + +buildinstall: rpm + sudo yum localinstall $(PKG_VER)*.src.rpm + diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..fe690e9 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,34 @@ +#!/bin/sh + +test -n "$srcdir" || srcdir=`dirname "$0"` +test -n "$srcdir" || srcdir=. + +olddir=`pwd` +cd $srcdir + +AUTORECONF=`which autoreconf` +if test -z $AUTORECONF; then + echo "*** No autoreconf found, please intall it ***" + exit 1 +fi + +set -e + +mkdir -p m4 + +GTKDOCIZE=$(which gtkdocize 2>/dev/null) +if test -z $GTKDOCIZE; then + echo "You don't have gtk-doc installed, and thus won't be able to generate the documentation." + rm -f gtk-doc.make + cat > gtk-doc.make <@]), + [], [with_systemd_journal=auto]) +AS_IF([test x$with_systemd_journal != xno], [ + PKG_CHECK_MODULES([SYSTEMD_JOURNAL], [libsystemd-journal >= 200], have_systemd_journal=yes, have_systemd_journal=no) + ]) +AM_CONDITIONAL(ENABLE_SYSTEMD_JOURNAL, test x$have_systemd_journal = xyes) + +AC_CONFIG_FILES([ +Makefile +src/libgsystem.pc +]) +AC_OUTPUT + +echo " + libgsystem $VERSION + ==================== + + introspection: $found_introspection + systemd journal: $have_systemd_journal +" diff --git a/gsystem-console.c b/gsystem-console.c deleted file mode 100644 index 35477eb..0000000 --- a/gsystem-console.c +++ /dev/null @@ -1,443 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- - * - * Copyright (C) 2013 Colin Walters - * - * 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 -#ifdef G_OS_UNIX -#include -#include -#include -#include -#include -#include -#endif -#include - -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/gsystem-console.h b/gsystem-console.h deleted file mode 100644 index a22b2d4..0000000 --- a/gsystem-console.h +++ /dev/null @@ -1,59 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- - * - * Copyright (C) 2013 Colin Walters - * - * 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 - -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/gsystem-file-utils.c b/gsystem-file-utils.c deleted file mode 100644 index 0260603..0000000 --- a/gsystem-file-utils.c +++ /dev/null @@ -1,1644 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- - * - * Copyright (C) 2012 William Jon McCann - * Copyright (C) 2012 Colin Walters - * - * 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 - -#define _GSYSTEM_NO_LOCAL_ALLOC -#include "libgsystem.h" -#include "gsystem-glib-compat.h" -#include -#include -#include -#include -#include -#include -#include -#ifdef GSYSTEM_CONFIG_XATTRS -#include -#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() always 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/gsystem-file-utils.h b/gsystem-file-utils.h deleted file mode 100644 index 021aebb..0000000 --- a/gsystem-file-utils.h +++ /dev/null @@ -1,172 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- - * - * Copyright (C) 2012 Colin Walters . - * - * 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 -#include - -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/gsystem-glib-compat.h b/gsystem-glib-compat.h deleted file mode 100644 index 6fd59c6..0000000 --- a/gsystem-glib-compat.h +++ /dev/null @@ -1,54 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- - * - * Copyright (C) 2012,2013 Colin Walters . - * - * 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 - -#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/gsystem-local-alloc.c b/gsystem-local-alloc.c deleted file mode 100644 index add3fcb..0000000 --- a/gsystem-local-alloc.c +++ /dev/null @@ -1,72 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- - * - * Copyright (C) 2012 Colin Walters - * - * 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 - * 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 stack - * allocated 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. - * - * Calling g_free automatically - * - * - * 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 - * } - * - * - * - */ diff --git a/gsystem-local-alloc.h b/gsystem-local-alloc.h deleted file mode 100644 index 34db297..0000000 --- a/gsystem-local-alloc.h +++ /dev/null @@ -1,164 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- - * - * Copyright (C) 2012 Colin Walters . - * - * 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 - -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/gsystem-log.c b/gsystem-log.c deleted file mode 100644 index 7b03145..0000000 --- a/gsystem-log.c +++ /dev/null @@ -1,167 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- - * - * Copyright (C) 2012 William Jon McCann - * Copyright (C) 2012 Colin Walters - * - * 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 -#endif -#include - -#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 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/gsystem-log.h b/gsystem-log.h deleted file mode 100644 index 80cfc34..0000000 --- a/gsystem-log.h +++ /dev/null @@ -1,42 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- - * - * Copyright (C) 2013 Colin Walters . - * - * 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 - -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/gsystem-shutil.c b/gsystem-shutil.c deleted file mode 100644 index 8029dd9..0000000 --- a/gsystem-shutil.c +++ /dev/null @@ -1,460 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- - * - * Copyright (C) 2012 William Jon McCann - * Copyright (C) 2012 Colin Walters - * - * 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 -#include -#include -#include -#include - -/* 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/gsystem-shutil.h b/gsystem-shutil.h deleted file mode 100644 index 3cdea77..0000000 --- a/gsystem-shutil.h +++ /dev/null @@ -1,47 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- - * - * Copyright (C) 2012 Colin Walters . - * - * 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 - -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/gsystem-subprocess-context-private.h b/gsystem-subprocess-context-private.h deleted file mode 100644 index 719df45..0000000 --- a/gsystem-subprocess-context-private.h +++ /dev/null @@ -1,65 +0,0 @@ -/* GIO - GLib Input, Output and Streaming Library - * - * Copyright (C) 2012 Colin Walters - * - * 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/gsystem-subprocess-context.c b/gsystem-subprocess-context.c deleted file mode 100644 index 90ca716..0000000 --- a/gsystem-subprocess-context.c +++ /dev/null @@ -1,501 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- - * - * Copyright (C) 2012 Colin Walters - * - * 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 -#include -#include -#include -#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 - -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 - * --password-fd=<fdno string>. - * - * 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/gsystem-subprocess-context.h b/gsystem-subprocess-context.h deleted file mode 100644 index b8a2401..0000000 --- a/gsystem-subprocess-context.h +++ /dev/null @@ -1,128 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- - * - * Copyright (C) 2012 Colin Walters . - * - * 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 - -#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/gsystem-subprocess.c b/gsystem-subprocess.c deleted file mode 100644 index 4ec680f..0000000 --- a/gsystem-subprocess.c +++ /dev/null @@ -1,966 +0,0 @@ -/* -*- 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 - * Ryan Lortie - */ - -#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 subprocess.py - * 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 -#ifdef G_OS_UNIX -#include -#include -#include -#include -#endif -#include -#ifdef G_OS_WIN32 -#define _WIN32_WINNT 0x0500 -#include -#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 - * not 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 not 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/gsystem-subprocess.h b/gsystem-subprocess.h deleted file mode 100644 index f248f7b..0000000 --- a/gsystem-subprocess.h +++ /dev/null @@ -1,101 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- - * - * Copyright (C) 2012 Colin Walters . - * - * 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 - -#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/libgsystem.h b/libgsystem.h deleted file mode 100644 index 60884b6..0000000 --- a/libgsystem.h +++ /dev/null @@ -1,49 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- - * - * Copyright (C) 2012,2013 Colin Walters . - * - * 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 - -G_BEGIN_DECLS - -#define gs_transfer_out_value(outp, srcp) G_STMT_START { \ - if (outp) \ - { \ - *(outp) = *(srcp); \ - *(srcp) = NULL; \ - } \ - } G_STMT_END; - -#include -#include -#include -#if GLIB_CHECK_VERSION(2,34,0) -#include -#endif -#include -#ifndef _GSYSTEM_NO_LOCAL_ALLOC -#include -#endif - -G_END_DECLS - -#endif diff --git a/packaging/libgsystem.spec.in b/packaging/libgsystem.spec.in new file mode 100644 index 0000000..3284784 --- /dev/null +++ b/packaging/libgsystem.spec.in @@ -0,0 +1,62 @@ +Summary: GIO-based library with Unix/Linux specific API +Name: libgsystem +Version: 2014.1 +Release: 1%{?dist} +#VCS: git:git://git.gnome.org/libgsystem +Source0: http://ftp.gnome.org/pub/GNOME/sources/libgsystem/%{version}/libgsystem-%{version}.tar.xz +License: LGPLv2+ +URL: https://wiki.gnome.org/Projects/LibGSystem + +# We always run autogen.sh +BuildRequires: autoconf automake libtool +# For docs +BuildRequires: gtk-doc +# Core requirements +BuildRequires: pkgconfig(gio-unix-2.0) +BuildRequires: libattr-devel +BuildRequires: /usr/bin/g-ir-scanner + +%description +LibGSystem is a GIO-based library usable as a "git submodule", +targeted primarily for use by operating system components. + +%package devel +Summary: Development headers for %{name} +Group: Development/Libraries +Requires: %{name} = %{version}-%{release} + +%description devel +The %{name}-devel package includes the header files for the %{name} library. + +%prep +%autosetup + +%build +env NOCONFIGURE=1 ./autogen.sh +%configure --disable-silent-rules \ + --enable-gtk-doc +make %{?_smp_mflags} + +%install +make install DESTDIR=$RPM_BUILD_ROOT INSTALL="install -p -c" +find $RPM_BUILD_ROOT -name '*.la' -delete + +%clean +rm -rf $RPM_BUILD_ROOT + +%post +/sbin/ldconfig + +%postun +/sbin/ldconfig + +%files +%doc COPYING README +%{_libdir}/*.so.* +%{_libdir}/girepository-1.0/*.typelib + +%files devel +%{_libdir}/lib*.so +%{_includedir}/* +%{_libdir}/pkgconfig/* +%{_datadir}/gir-1.0/*.gir diff --git a/packaging/rpmbuild-cwd b/packaging/rpmbuild-cwd new file mode 100755 index 0000000..d0805bb --- /dev/null +++ b/packaging/rpmbuild-cwd @@ -0,0 +1,11 @@ +#!/bin/sh +# rpmbuild-cwd: +# Run "rpmbuild", defining all RPM variables to use the current directory. +# This matches Fedora's system. +# +# Licensed under the new-BSD license (http://www.opensource.org/licenses/bsd-license.php) +# Copyright (C) 2010 Red Hat, Inc. +# Written by Colin Walters + +pwd=$(pwd) +exec rpmbuild --define "_sourcedir ${pwd}" --define "_specdir ${pwd}" --define "_builddir ${pwd}" --define "_srcrpmdir ${pwd}" --define "_rpmdir ${pwd}" --define "_buildrootdir ${pwd}/.build" "$@" 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 + * + * 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 +#ifdef G_OS_UNIX +#include +#include +#include +#include +#include +#include +#endif +#include + +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 + * + * 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 + +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 + * Copyright (C) 2012 Colin Walters + * + * 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 + +#define _GSYSTEM_NO_LOCAL_ALLOC +#include "libgsystem.h" +#include "gsystem-glib-compat.h" +#include +#include +#include +#include +#include +#include +#include +#ifdef GSYSTEM_CONFIG_XATTRS +#include +#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() always 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 . + * + * 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 +#include + +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 . + * + * 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 + +#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 + * + * 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 + * 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 stack + * allocated 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. + * + * Calling g_free automatically + * + * + * 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 + * } + * + * + * + */ 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 . + * + * 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 + +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 + * Copyright (C) 2012 Colin Walters + * + * 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 +#endif +#include + +#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 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 . + * + * 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 + +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 + * Copyright (C) 2012 Colin Walters + * + * 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 +#include +#include +#include +#include + +/* 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 . + * + * 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 + +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 + * + * 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 + * + * 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 +#include +#include +#include +#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 + +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 + * --password-fd=<fdno string>. + * + * 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 . + * + * 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 + +#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 + * Ryan Lortie + */ + +#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 subprocess.py + * 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 +#ifdef G_OS_UNIX +#include +#include +#include +#include +#endif +#include +#ifdef G_OS_WIN32 +#define _WIN32_WINNT 0x0500 +#include +#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 + * not 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 not 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 . + * + * 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 + +#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 . + * + * 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 + +G_BEGIN_DECLS + +#define gs_transfer_out_value(outp, srcp) G_STMT_START { \ + if (outp) \ + { \ + *(outp) = *(srcp); \ + *(srcp) = NULL; \ + } \ + } G_STMT_END; + +#include +#include +#include +#if GLIB_CHECK_VERSION(2,34,0) +#include +#endif +#include +#ifndef _GSYSTEM_NO_LOCAL_ALLOC +#include +#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 -- cgit v1.2.1