-
Simon McVittie authored
Signed-off-by:
Simon McVittie <smcv@collabora.com>
Simon McVittie authoredSigned-off-by:
Simon McVittie <smcv@collabora.com>
glib-backports.c 13.95 KiB
/*
* Backports from GLib
*
* Copyright 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
* Copyright 1997-2000 GLib team
* Copyright 2000 Red Hat, Inc.
* Copyright 2013-2019 Collabora Ltd.
* Copyright 2018 Georges Basile Stavracas Neto
* Copyright 2018 Philip Withnall
* g_execvpe implementation based on GNU libc execvp:
* Copyright 1991, 92, 95, 96, 97, 98, 99 Free Software Foundation, Inc.
*
* 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.1 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, see <http://www.gnu.org/licenses/>.
*/
#include "glib-backports.h"
#include <errno.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#include <glib-object.h>
/* We have no internationalization */
#define _(x) x
#if !GLIB_CHECK_VERSION (2, 34, 0)
G_DEFINE_QUARK (g-spawn-exit-error-quark, my_g_spawn_exit_error)
#endif
#if !GLIB_CHECK_VERSION (2, 36, 0)
/**
* g_close:
* @fd: A file descriptor
* @error: a #GError
*
* This wraps the close() call; in case of error, %errno will be
* preserved, but the error will also be stored as a #GError in @error.
*
* Besides using #GError, there is another major reason to prefer this
* function over the call provided by the system; on Unix, it will
* attempt to correctly handle %EINTR, which has platform-specific
* semantics.
*
* Returns: %TRUE on success, %FALSE if there was an error.
*
* Since: 2.36
*/
gboolean
my_g_close (gint fd,
GError **error)
{
int res;
res = close (fd);
/* Just ignore EINTR for now; a retry loop is the wrong thing to do
* on Linux at least. Anyone who wants to add a conditional check
* for e.g. HP-UX is welcome to do so later...
*
* http://lkml.indiana.edu/hypermail/linux/kernel/0509.1/0877.html
* https://bugzilla.gnome.org/show_bug.cgi?id=682819
* http://utcc.utoronto.ca/~cks/space/blog/unix/CloseEINTR
* https://sites.google.com/site/michaelsafyan/software-engineering/checkforeintrwheninvokingclosethinkagain
*/
if (G_UNLIKELY (res == -1 && errno == EINTR))
return TRUE;
else if (res == -1)
{
int errsv = errno;
g_set_error_literal (error, G_FILE_ERROR,
g_file_error_from_errno (errsv),
g_strerror (errsv));
errno = errsv;
return FALSE;
}
return TRUE;
}
#endif
#if !GLIB_CHECK_VERSION (2, 34, 0)
/**
* g_spawn_check_exit_status:
* @exit_status: An exit code as returned from g_spawn_sync()
* @error: a #GError
*
* Set @error if @exit_status indicates the child exited abnormally
* (e.g. with a nonzero exit code, or via a fatal signal).
*
* The g_spawn_sync() and g_child_watch_add() family of APIs return an
* exit status for subprocesses encoded in a platform-specific way.
* On Unix, this is guaranteed to be in the same format waitpid() returns,
* and on Windows it is guaranteed to be the result of GetExitCodeProcess().
*
* Prior to the introduction of this function in GLib 2.34, interpreting
* @exit_status required use of platform-specific APIs, which is problematic
* for software using GLib as a cross-platform layer.
*
* Additionally, many programs simply want to determine whether or not
* the child exited successfully, and either propagate a #GError or
* print a message to standard error. In that common case, this function
* can be used. Note that the error message in @error will contain
* human-readable information about the exit status.
*
* The @domain and @code of @error have special semantics in the case
* where the process has an "exit code", as opposed to being killed by
* a signal. On Unix, this happens if WIFEXITED() would be true of
* @exit_status. On Windows, it is always the case.
*
* The special semantics are that the actual exit code will be the
* code set in @error, and the domain will be %G_SPAWN_EXIT_ERROR.
* This allows you to differentiate between different exit codes.
*
* If the process was terminated by some means other than an exit
* status, the domain will be %G_SPAWN_ERROR, and the code will be
* %G_SPAWN_ERROR_FAILED.
*
* This function just offers convenience; you can of course also check
* the available platform via a macro such as %G_OS_UNIX, and use
* WIFEXITED() and WEXITSTATUS() on @exit_status directly. Do not attempt
* to scan or parse the error message string; it may be translated and/or
* change in future versions of GLib.
*
* Returns: %TRUE if child exited successfully, %FALSE otherwise (and
* @error will be set)
*
* Since: 2.34
*/
gboolean
my_g_spawn_check_exit_status (gint exit_status,
GError **error)
{
gboolean ret = FALSE;
if (WIFEXITED (exit_status))
{
if (WEXITSTATUS (exit_status) != 0)
{
g_set_error (error, G_SPAWN_EXIT_ERROR, WEXITSTATUS (exit_status),
_("Child process exited with code %ld"),
(long) WEXITSTATUS (exit_status));
goto out;
}
}
else if (WIFSIGNALED (exit_status))
{
g_set_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED,
_("Child process killed by signal %ld"),
(long) WTERMSIG (exit_status));
goto out;
}
else if (WIFSTOPPED (exit_status))
{
g_set_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED,
_("Child process stopped by signal %ld"),
(long) WSTOPSIG (exit_status));
goto out;
}
else
{
g_set_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED,
_("Child process exited abnormally"));
goto out;
}
ret = TRUE;
out:
return ret;
}
#endif
#if !GLIB_CHECK_VERSION (2, 40, 0)
/*
* A reimplementation of g_ptr_array_insert()
*/
void
my_g_ptr_array_insert (GPtrArray *arr,
gint index_,
gpointer data)
{
g_return_if_fail (arr != NULL);
g_return_if_fail (index_ >= -1);
g_return_if_fail (index_ <= (gint) arr->len);
if (index_ < 0)
index_ = arr->len;
if ((guint) index_ == arr->len)
{
g_ptr_array_add (arr, data);
}
else
{
g_ptr_array_add (arr, NULL);
memmove (&(arr->pdata[index_ + 1]),
&(arr->pdata[index_]),
(arr->len - 1 - index_) * sizeof (gpointer));
arr->pdata[index_] = data;
}
}
#endif
#if !GLIB_CHECK_VERSION (2, 36, 0)
/**
* g_dbus_address_escape_value:
* @string: an unescaped string to be included in a D-Bus address
* as the value in a key-value pair
*
* Escape @string so it can appear in a D-Bus address as the value
* part of a key-value pair.
*
* For instance, if @string is `/run/bus-for-:0`,
* this function would return `/run/bus-for-%3A0`,
* which could be used in a D-Bus address like
* `unix:nonce-tcp:host=127.0.0.1,port=42,noncefile=/run/bus-for-%3A0`.
*
* Returns: (transfer full): a copy of @string with all
* non-optionally-escaped bytes escaped
*
* Since: 2.36
*/
gchar *
my_g_dbus_address_escape_value (const gchar *string)
{
GString *s;
gsize i;
g_return_val_if_fail (string != NULL, NULL);
/* There will often not be anything needing escaping at all. */
s = g_string_sized_new (strlen (string));
/* D-Bus address escaping is mostly the same as URI escaping... */
g_string_append_uri_escaped (s, string, "\\/", FALSE);
/* ... but '~' is an unreserved character in URIs, but a
* non-optionally-escaped character in D-Bus addresses. */
for (i = 0; i < s->len; i++)
{
if (G_UNLIKELY (s->str[i] == '~'))
{
s->str[i] = '%';
g_string_insert (s, i + 1, "7E");
i += 2;
}
}
return g_string_free (s, FALSE);
}
#endif
#if !GLIB_CHECK_VERSION(2, 36, 0)
/*
* Reimplement GUnixFDSourceFunc API in terms of older GLib versions
*/
typedef struct
{
GSource parent;
GPollFD pollfd;
} MyUnixFDSource;
static gboolean
my_unix_fd_source_prepare (GSource *source,
int *timeout_out)
{
if (timeout_out != NULL)
*timeout_out = -1;
/* We don't know we're ready to be dispatched until we have polled. */
return FALSE;
}
static gboolean
my_unix_fd_source_check (GSource *source)
{
MyUnixFDSource *self = (MyUnixFDSource *) source;
/* We're ready to be dispatched if an event occurred. */
return (self->pollfd.revents != 0);
}
static gboolean
my_unix_fd_source_dispatch (GSource *source,
GSourceFunc callback,
gpointer user_data)
{
MyUnixFDSource *self = (MyUnixFDSource *) source;
MyGUnixFDSourceFunc func = (MyGUnixFDSourceFunc) G_CALLBACK (callback);
g_return_val_if_fail (func != NULL, G_SOURCE_REMOVE);
return func (self->pollfd.fd, self->pollfd.revents, user_data);
}
static void
my_unix_fd_source_finalize (GSource *source)
{
}
static GSourceFuncs my_unix_fd_source_funcs =
{
my_unix_fd_source_prepare,
my_unix_fd_source_check,
my_unix_fd_source_dispatch,
my_unix_fd_source_finalize
};
GSource *
my_g_unix_fd_source_new (int fd,
GIOCondition condition)
{
GSource *source = g_source_new (&my_unix_fd_source_funcs,
sizeof (MyUnixFDSource));
MyUnixFDSource *self = (MyUnixFDSource *) source;
self->pollfd.fd = fd;
self->pollfd.events = condition;
g_source_add_poll (source, &self->pollfd);
return source;
}
guint
my_g_unix_fd_add_full (int priority,
int fd,
GIOCondition condition,
MyGUnixFDSourceFunc func,
gpointer user_data,
GDestroyNotify destroy)
{
GSource *source;
guint ret;
g_return_val_if_fail (fd >= 0, 0);
g_return_val_if_fail (func != NULL, 0);
source = my_g_unix_fd_source_new (fd, condition);
g_source_set_callback (source, (GSourceFunc) G_CALLBACK (func),
user_data, destroy);
g_source_set_priority (source, priority);
ret = g_source_attach (source, NULL);
g_source_unref (source);
return ret;
}
#endif
#if !GLIB_CHECK_VERSION(2, 58, 0)
/**
* g_canonicalize_filename:
* @filename: (type filename): the name of the file
* @relative_to: (type filename) (nullable): the relative directory, or %NULL
* to use the current working directory
*
* Gets the canonical file name from @filename. All triple slashes are turned into
* single slashes, and all `..` and `.`s resolved against @relative_to.
*
* Symlinks are not followed, and the returned path is guaranteed to be absolute.
*
* If @filename is an absolute path, @relative_to is ignored. Otherwise,
* @relative_to will be prepended to @filename to make it absolute. @relative_to
* must be an absolute path, or %NULL. If @relative_to is %NULL, it'll fallback
* to g_get_current_dir().
*
* This function never fails, and will canonicalize file paths even if they don't
* exist.
*
* No file system I/O is done.
*
* Returns: (type filename) (transfer full): a newly allocated string with the
* canonical file path
* Since: 2.58
*/
gchar *
my_g_canonicalize_filename (const gchar *filename,
const gchar *relative_to)
{
gchar *canon, *start, *p, *q;
guint i;
g_return_val_if_fail (relative_to == NULL || g_path_is_absolute (relative_to), NULL);
if (!g_path_is_absolute (filename))
{
gchar *cwd_allocated = NULL;
const gchar *cwd;
if (relative_to != NULL)
cwd = relative_to;
else
cwd = cwd_allocated = g_get_current_dir ();
canon = g_build_filename (cwd, filename, NULL);
g_free (cwd_allocated);
}
else
{
canon = g_strdup (filename);
}
start = (char *)g_path_skip_root (canon);
if (start == NULL)
{
/* This shouldn't really happen, as g_get_current_dir() should
return an absolute pathname, but bug 573843 shows this is
not always happening */
g_free (canon);
return g_build_filename (G_DIR_SEPARATOR_S, filename, NULL);
}
/* POSIX allows double slashes at the start to
* mean something special (as does windows too).
* So, "//" != "/", but more than two slashes
* is treated as "/".
*/
i = 0;
for (p = start - 1;
(p >= canon) &&
G_IS_DIR_SEPARATOR (*p);
p--)
i++;
if (i > 2)
{
i -= 1;
start -= i;
memmove (start, start+i, strlen (start+i) + 1);
}
/* Make sure we're using the canonical dir separator */
p++;
while (p < start && G_IS_DIR_SEPARATOR (*p))
*p++ = G_DIR_SEPARATOR;
p = start;
while (*p != 0)
{
if (p[0] == '.' && (p[1] == 0 || G_IS_DIR_SEPARATOR (p[1])))
{
memmove (p, p+1, strlen (p+1)+1);
}
else if (p[0] == '.' && p[1] == '.' && (p[2] == 0 || G_IS_DIR_SEPARATOR (p[2])))
{
q = p + 2;
/* Skip previous separator */
p = p - 2;
if (p < start)
p = start;
while (p > start && !G_IS_DIR_SEPARATOR (*p))
p--;
if (G_IS_DIR_SEPARATOR (*p))
*p++ = G_DIR_SEPARATOR;
memmove (p, q, strlen (q)+1);
}
else
{
/* Skip until next separator */
while (*p != 0 && !G_IS_DIR_SEPARATOR (*p))
p++;
if (*p != 0)
{
/* Canonicalize one separator */
*p++ = G_DIR_SEPARATOR;
}
}
/* Remove additional separators */
q = p;
while (*q && G_IS_DIR_SEPARATOR (*q))
q++;
if (p != q)
memmove (p, q, strlen (q) + 1);
}
/* Remove trailing slashes */
if (p > start && G_IS_DIR_SEPARATOR (*(p-1)))
*(p-1) = 0;
return canon;
}
#endif