Merge branch 'wip/smcv/auto-unix-pipe' into 'main'

glib-unix: Add convenience API for pipes

See merge request GNOME/glib!3457
This commit is contained in:
Philip Withnall 2023-10-09 18:00:36 +00:00
commit 9b51346237
8 changed files with 462 additions and 113 deletions

View File

@ -2296,6 +2296,16 @@ G_UNIX_ERROR
g_unix_open_pipe
g_unix_set_fd_nonblocking
<SUBSECTION>
GUnixPipe
G_UNIX_PIPE_INIT
GUnixPipeEnd
g_unix_pipe_open
g_unix_pipe_get
g_unix_pipe_steal
g_unix_pipe_close
g_unix_pipe_clear
<SUBSECTION>
g_unix_signal_add
g_unix_signal_add_full

View File

@ -35,6 +35,7 @@ if get_option('gtk_doc')
'glib-init.h',
'gconstructor.h',
'gconstructorprivate.h',
'testutils.h',
'valgrind.h',
'gutilsprivate.h',
'gvalgrind.h',

View File

@ -1,5 +1,6 @@
/* GLIB - Library of useful routines for C programming
* Copyright (C) 2011 Red Hat, Inc.
* Copyright 2023 Collabora Ltd.
*
* glib-unix.c: UNIX specific API wrappers and convenience functions
*
@ -87,6 +88,9 @@ g_unix_set_error_from_errno (GError **error,
* you wanted to configure `O_NONBLOCK` then that had to be done separately with
* `fcntl()`.
*
* Since GLib 2.80, the constants %G_UNIX_PIPE_END_READ and
* %G_UNIX_PIPE_END_WRITE can be used as mnemonic indexes in @fds.
*
* It is a programmer error to call this function with unsupported flags, and a
* critical warning will be raised.
*

View File

@ -1,5 +1,6 @@
/* glib-unix.h - Unix specific integration
* Copyright (C) 2011 Red Hat, Inc.
* Copyright 2023 Collabora Ltd.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
@ -33,6 +34,7 @@
#include <fcntl.h>
#include <glib.h>
#include <gstdio.h>
#ifndef G_OS_UNIX
#error "This header may only be used on UNIX"
@ -120,6 +122,212 @@ GLIB_AVAILABLE_IN_2_64
struct passwd *g_unix_get_passwd_entry (const gchar *user_name,
GError **error);
/**
* GUnixPipe:
* @fds: A pair of file descriptors, each negative if closed or not yet opened.
* The file descriptor with index %G_UNIX_PIPE_END_READ is readable.
* The file descriptor with index %G_UNIX_PIPE_END_WRITE is writable.
*
* A Unix pipe. The advantage of this type over `int[2]` is that it can
* be closed automatically when it goes out of scope, using `g_auto(GUnixPipe)`,
* on compilers that support that feature.
*
* Since: 2.80
*/
GLIB_AVAILABLE_TYPE_IN_2_80
typedef struct {
int fds[2];
} GUnixPipe;
/**
* GUnixPipeEnd:
* @G_UNIX_PIPE_END_READ: The readable file descriptor 0
* @G_UNIX_PIPE_END_WRITE: The writable file descriptor 1
*
* Mnemonic constants for the ends of a Unix pipe.
*
* Since: 2.80
*/
GLIB_AVAILABLE_TYPE_IN_2_80
typedef enum
{
G_UNIX_PIPE_END_READ = 0,
G_UNIX_PIPE_END_WRITE = 1
} GUnixPipeEnd;
/**
* G_UNIX_PIPE_INIT:
*
* Initializer for a #GUnixPipe that has not yet been opened.
* Both of its file descriptors are initialized to `-1` (invalid),
* the same as if they had been closed.
*
* Since: 2.80
*/
#define G_UNIX_PIPE_INIT { { -1, -1 } } GLIB_AVAILABLE_MACRO_IN_2_80
/* Suppress "Not available before" warnings when declaring the
* implementations */
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
/**
* g_unix_pipe_open:
* @self: A pair of file descriptors
* @flags: Flags to pass to g_unix_open_pipe(), typically `FD_CLOEXEC`
* @error: Used to report an error on failure
*
* Open a pipe. This is the same as g_unix_open_pipe(), but uses the
* #GUnixPipe data structure.
*
* Returns: %TRUE on success
*
* Since: 2.80
*/
GLIB_AVAILABLE_STATIC_INLINE_IN_2_80
static inline gboolean
g_unix_pipe_open (GUnixPipe *self,
int flags,
GError **error)
{
return g_unix_open_pipe (self->fds, flags, error);
}
/**
* g_unix_pipe_get:
* @self: A pair of file descriptors
* @end: One of the ends of the pipe
*
* Return one of the ends of the pipe. It remains owned by @self.
*
* This function is async-signal safe (see [`signal(7)`](man:signal(7)) and
* [`signal-safety(7)`](man:signal-safety(7))), making it safe to call from a
* signal handler or a #GSpawnChildSetupFunc.
*
* This function preserves the value of `errno`.
*
* Returns: a non-negative file descriptor owned by @self, which must not
* be closed by the caller, or a negative number if the corresponding
* end of the pipe was already closed or stolen
*
* Since: 2.80
*/
GLIB_AVAILABLE_STATIC_INLINE_IN_2_80
static inline int
g_unix_pipe_get (GUnixPipe *self,
GUnixPipeEnd end)
{
return self->fds[end];
}
/**
* g_unix_pipe_steal:
* @self: A pair of file descriptors
* @end: One of the ends of the pipe
*
* Return one of the ends of the pipe. It becomes owned by the caller,
* and the file descriptor in the data structure is set to `-1`,
* similar to g_steal_fd().
*
* This function is async-signal safe (see [`signal(7)`](man:signal(7)) and
* [`signal-safety(7)`](man:signal-safety(7))), making it safe to call from a
* signal handler or a #GSpawnChildSetupFunc.
*
* This function preserves the value of `errno`.
*
* Returns: a non-negative file descriptor, which becomes owned by the
* caller and must be closed by the caller if required, or a negative
* number if the corresponding end of the pipe was already closed or stolen
*
* Since: 2.80
*/
GLIB_AVAILABLE_STATIC_INLINE_IN_2_80
static inline int
g_unix_pipe_steal (GUnixPipe *self,
GUnixPipeEnd end)
{
return g_steal_fd (&self->fds[end]);
}
/**
* g_unix_pipe_close:
* @self: A pair of file descriptors
* @end: One of the ends of the pipe
* @error: Optionally used to report an error on failure
*
* Close one of the ends of the pipe and set the relevant member of @fds
* to `-1` before returning, equivalent to g_clear_fd().
*
* Like g_close(), if closing the file descriptor fails, the error is
* stored in both %errno and @error. If this function succeeds,
* %errno is undefined.
*
* This function is async-signal safe if @error is %NULL and the relevant
* member of @fds is either negative or a valid open file descriptor.
* This makes it safe to call from a signal handler or a #GSpawnChildSetupFunc
* under those conditions.
* See [`signal(7)`](man:signal(7)) and
* [`signal-safety(7)`](man:signal-safety(7)) for more details.
*
* To close both file descriptors and ignore any errors, use
* g_unix_pipe_clear() instead.
*
* Returns: %TRUE on success
*
* Since: 2.80
*/
GLIB_AVAILABLE_STATIC_INLINE_IN_2_80
static inline gboolean
g_unix_pipe_close (GUnixPipe *self,
GUnixPipeEnd end,
GError **error)
{
return g_clear_fd (&self->fds[end], error);
}
/**
* g_unix_pipe_clear:
* @self: a #GUnixPipe
*
* Close both ends of the pipe, unless they have already been closed or
* stolen. Any errors are ignored: use g_unix_pipe_close() or g_clear_fd()
* if error-handling is required.
*
* This function is async-signal safe if @error is %NULL and each member
* of @fds are either negative or a valid open file descriptor.
* As a result, it is safe to call this function or use `g_auto(GUnixPipe)`
* (on compilers that support it) in a signal handler or a
* #GSpawnChildSetupFunc, as long as those conditions are ensured to be true.
* See [`signal(7)`](man:signal(7)) and
* [`signal-safety(7)`](man:signal-safety(7)) for more details.
*
* This function preserves the value of `errno`.
*
* Since: 2.80
*/
GLIB_AVAILABLE_STATIC_INLINE_IN_2_80
static inline void
g_unix_pipe_clear (GUnixPipe *self)
{
/* Don't overwrite thread-local errno if closing the fd fails */
int errsv = errno;
if (!g_unix_pipe_close (self, G_UNIX_PIPE_END_READ, NULL))
{
/* ignore */
}
if (!g_unix_pipe_close (self, G_UNIX_PIPE_END_WRITE, NULL))
{
/* ignore */
}
errno = errsv;
}
G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (GUnixPipe, g_unix_pipe_clear)
G_GNUC_END_IGNORE_DEPRECATIONS
G_END_DECLS
#endif /* __G_UNIX_H__ */

View File

@ -263,23 +263,6 @@ g_spawn_async (const gchar *working_directory,
error);
}
/* Avoids a danger in threaded situations (calling close()
* on a file descriptor twice, and another thread has
* re-opened it since the first close)
*
* This function is called between fork() and exec() and hence must be
* async-signal-safe (see signal-safety(7)).
*/
static void
close_and_invalidate (gint *fd)
{
if (*fd < 0)
return;
g_close (*fd, NULL);
*fd = -1;
}
/* Some versions of OS X define READ_OK in public headers */
#undef READ_OK
@ -484,8 +467,7 @@ g_spawn_sync (const gchar *working_directory,
failed = TRUE;
break;
case READ_EOF:
close_and_invalidate (&outpipe);
outpipe = -1;
g_clear_fd (&outpipe, NULL);
break;
default:
break;
@ -503,8 +485,7 @@ g_spawn_sync (const gchar *working_directory,
failed = TRUE;
break;
case READ_EOF:
close_and_invalidate (&errpipe);
errpipe = -1;
g_clear_fd (&errpipe, NULL);
break;
default:
break;
@ -516,12 +497,9 @@ g_spawn_sync (const gchar *working_directory,
}
/* These should only be open still if we had an error. */
if (outpipe >= 0)
close_and_invalidate (&outpipe);
if (errpipe >= 0)
close_and_invalidate (&errpipe);
g_clear_fd (&outpipe, NULL);
g_clear_fd (&errpipe, NULL);
/* Wait for child to exit, even if we have
* an error pending.
*/
@ -1822,7 +1800,7 @@ do_exec (gint child_err_report_fd,
if (safe_dup2 (read_null, 0) < 0)
write_err_and_exit (child_err_report_fd,
CHILD_DUPFD_FAILED);
close_and_invalidate (&read_null);
g_clear_fd (&read_null, NULL);
}
/* Like with stdin above, it's possible the caller assigned
@ -1859,7 +1837,7 @@ do_exec (gint child_err_report_fd,
if (safe_dup2 (write_null, 1) < 0)
write_err_and_exit (child_err_report_fd,
CHILD_DUPFD_FAILED);
close_and_invalidate (&write_null);
g_clear_fd (&write_null, NULL);
}
if (IS_STD_FILENO (stderr_fd) && stderr_fd != STDERR_FILENO)
@ -1891,7 +1869,7 @@ do_exec (gint child_err_report_fd,
if (safe_dup2 (write_null, 2) < 0)
write_err_and_exit (child_err_report_fd,
CHILD_DUPFD_FAILED);
close_and_invalidate (&write_null);
g_clear_fd (&write_null, NULL);
}
/* Close all file descriptors but stdin, stdout and stderr, and any of source_fds,
@ -1982,7 +1960,7 @@ do_exec (gint child_err_report_fd,
if (safe_dup2 (source_fds[i], target_fds[i]) < 0)
write_err_and_exit (child_err_report_fd, CHILD_DUPFD_FAILED);
close_and_invalidate (&source_fds[i]);
g_clear_fd (&source_fds[i], NULL);
}
}
}
@ -2272,12 +2250,12 @@ do_posix_spawn (const gchar * const *argv,
out_close_fds:
for (i = 0; i < num_parent_close_fds; i++)
close_and_invalidate (&parent_close_fds [i]);
g_clear_fd (&parent_close_fds[i], NULL);
if (duped_source_fds != NULL)
{
for (i = 0; i < n_fds; i++)
close_and_invalidate (&duped_source_fds[i]);
g_clear_fd (&duped_source_fds[i], NULL);
g_free (duped_source_fds);
}
@ -2290,6 +2268,16 @@ out_free_spawnattr:
}
#endif /* POSIX_SPAWN_AVAILABLE */
static gboolean
source_fds_collide_with_pipe (const GUnixPipe *pipefd,
const int *source_fds,
gsize n_fds,
GError **error)
{
return (_g_spawn_invalid_source_fd (pipefd->fds[G_UNIX_PIPE_END_READ], source_fds, n_fds, error) ||
_g_spawn_invalid_source_fd (pipefd->fds[G_UNIX_PIPE_END_WRITE], source_fds, n_fds, error));
}
static gboolean
fork_exec (gboolean intermediate_child,
const gchar *working_directory,
@ -2318,8 +2306,8 @@ fork_exec (gboolean intermediate_child,
GError **error)
{
GPid pid = -1;
gint child_err_report_pipe[2] = { -1, -1 };
gint child_pid_report_pipe[2] = { -1, -1 };
GUnixPipe child_err_report_pipe = G_UNIX_PIPE_INIT;
GUnixPipe child_pid_report_pipe = G_UNIX_PIPE_INIT;
guint pipe_flags = cloexec_pipes ? FD_CLOEXEC : 0;
gint status;
const gchar *chosen_search_path;
@ -2329,9 +2317,9 @@ fork_exec (gboolean intermediate_child,
gchar **argv_buffer = NULL;
gchar **argv_buffer_heap = NULL;
gsize argv_buffer_len = 0;
gint stdin_pipe[2] = { -1, -1 };
gint stdout_pipe[2] = { -1, -1 };
gint stderr_pipe[2] = { -1, -1 };
GUnixPipe stdin_pipe = G_UNIX_PIPE_INIT;
GUnixPipe stdout_pipe = G_UNIX_PIPE_INIT;
GUnixPipe stderr_pipe = G_UNIX_PIPE_INIT;
gint child_close_fds[4] = { -1, -1, -1, -1 };
gint n_child_close_fds = 0;
gint *source_fds_copy = NULL;
@ -2344,35 +2332,32 @@ fork_exec (gboolean intermediate_child,
/* If pipes have been requested, open them */
if (stdin_pipe_out != NULL)
{
if (!g_unix_open_pipe (stdin_pipe, pipe_flags, error))
if (!g_unix_pipe_open (&stdin_pipe, pipe_flags, error))
goto cleanup_and_fail;
if (_g_spawn_invalid_source_fd (stdin_pipe[0], source_fds, n_fds, error) ||
_g_spawn_invalid_source_fd (stdin_pipe[1], source_fds, n_fds, error))
if (source_fds_collide_with_pipe (&stdin_pipe, source_fds, n_fds, error))
goto cleanup_and_fail;
child_close_fds[n_child_close_fds++] = stdin_pipe[1];
stdin_fd = stdin_pipe[0];
child_close_fds[n_child_close_fds++] = g_unix_pipe_get (&stdin_pipe, G_UNIX_PIPE_END_WRITE);
stdin_fd = g_unix_pipe_get (&stdin_pipe, G_UNIX_PIPE_END_READ);
}
if (stdout_pipe_out != NULL)
{
if (!g_unix_open_pipe (stdout_pipe, pipe_flags, error))
if (!g_unix_pipe_open (&stdout_pipe, pipe_flags, error))
goto cleanup_and_fail;
if (_g_spawn_invalid_source_fd (stdout_pipe[0], source_fds, n_fds, error) ||
_g_spawn_invalid_source_fd (stdout_pipe[1], source_fds, n_fds, error))
if (source_fds_collide_with_pipe (&stdout_pipe, source_fds, n_fds, error))
goto cleanup_and_fail;
child_close_fds[n_child_close_fds++] = stdout_pipe[0];
stdout_fd = stdout_pipe[1];
child_close_fds[n_child_close_fds++] = g_unix_pipe_get (&stdout_pipe, G_UNIX_PIPE_END_READ);
stdout_fd = g_unix_pipe_get (&stdout_pipe, G_UNIX_PIPE_END_WRITE);
}
if (stderr_pipe_out != NULL)
{
if (!g_unix_open_pipe (stderr_pipe, pipe_flags, error))
if (!g_unix_pipe_open (&stderr_pipe, pipe_flags, error))
goto cleanup_and_fail;
if (_g_spawn_invalid_source_fd (stderr_pipe[0], source_fds, n_fds, error) ||
_g_spawn_invalid_source_fd (stderr_pipe[1], source_fds, n_fds, error))
if (source_fds_collide_with_pipe (&stderr_pipe, source_fds, n_fds, error))
goto cleanup_and_fail;
child_close_fds[n_child_close_fds++] = stderr_pipe[0];
stderr_fd = stderr_pipe[1];
child_close_fds[n_child_close_fds++] = g_unix_pipe_get (&stderr_pipe, G_UNIX_PIPE_END_READ);
stderr_fd = g_unix_pipe_get (&stderr_pipe, G_UNIX_PIPE_END_WRITE);
}
child_close_fds[n_child_close_fds++] = -1;
@ -2510,18 +2495,16 @@ fork_exec (gboolean intermediate_child,
if (n_fds > 0)
memcpy (source_fds_copy, source_fds, sizeof (*source_fds) * n_fds);
if (!g_unix_open_pipe (child_err_report_pipe, pipe_flags, error))
if (!g_unix_pipe_open (&child_err_report_pipe, pipe_flags, error))
goto cleanup_and_fail;
if (_g_spawn_invalid_source_fd (child_err_report_pipe[0], source_fds, n_fds, error) ||
_g_spawn_invalid_source_fd (child_err_report_pipe[1], source_fds, n_fds, error))
if (source_fds_collide_with_pipe (&child_err_report_pipe, source_fds, n_fds, error))
goto cleanup_and_fail;
if (intermediate_child)
{
if (!g_unix_open_pipe (child_pid_report_pipe, pipe_flags, error))
if (!g_unix_pipe_open (&child_pid_report_pipe, pipe_flags, error))
goto cleanup_and_fail;
if (_g_spawn_invalid_source_fd (child_pid_report_pipe[0], source_fds, n_fds, error) ||
_g_spawn_invalid_source_fd (child_pid_report_pipe[1], source_fds, n_fds, error))
if (source_fds_collide_with_pipe (&child_pid_report_pipe, source_fds, n_fds, error))
goto cleanup_and_fail;
}
@ -2560,13 +2543,13 @@ fork_exec (gboolean intermediate_child,
* not needed in the close_descriptors case,
* though
*/
close_and_invalidate (&child_err_report_pipe[0]);
close_and_invalidate (&child_pid_report_pipe[0]);
g_unix_pipe_close (&child_err_report_pipe, G_UNIX_PIPE_END_READ, NULL);
g_unix_pipe_close (&child_pid_report_pipe, G_UNIX_PIPE_END_READ, NULL);
if (child_close_fds[0] != -1)
{
int i = -1;
while (child_close_fds[++i] != -1)
close_and_invalidate (&child_close_fds[i]);
g_clear_fd (&child_close_fds[i], NULL);
}
if (intermediate_child)
@ -2583,16 +2566,16 @@ fork_exec (gboolean intermediate_child,
if (grandchild_pid < 0)
{
/* report -1 as child PID */
write_all (child_pid_report_pipe[1], &grandchild_pid,
sizeof(grandchild_pid));
write_all (g_unix_pipe_get (&child_pid_report_pipe, G_UNIX_PIPE_END_WRITE),
&grandchild_pid, sizeof(grandchild_pid));
write_err_and_exit (child_err_report_pipe[1],
write_err_and_exit (g_unix_pipe_get (&child_err_report_pipe, G_UNIX_PIPE_END_WRITE),
CHILD_FORK_FAILED);
}
else if (grandchild_pid == 0)
{
close_and_invalidate (&child_pid_report_pipe[1]);
do_exec (child_err_report_pipe[1],
g_unix_pipe_close (&child_pid_report_pipe, G_UNIX_PIPE_END_WRITE, NULL);
do_exec (g_unix_pipe_get (&child_err_report_pipe, G_UNIX_PIPE_END_WRITE),
stdin_fd,
stdout_fd,
stderr_fd,
@ -2617,8 +2600,9 @@ fork_exec (gboolean intermediate_child,
}
else
{
write_all (child_pid_report_pipe[1], &grandchild_pid, sizeof(grandchild_pid));
close_and_invalidate (&child_pid_report_pipe[1]);
write_all (g_unix_pipe_get (&child_pid_report_pipe, G_UNIX_PIPE_END_WRITE),
&grandchild_pid, sizeof(grandchild_pid));
g_unix_pipe_close (&child_pid_report_pipe, G_UNIX_PIPE_END_WRITE, NULL);
_exit (0);
}
@ -2628,7 +2612,7 @@ fork_exec (gboolean intermediate_child,
/* Just run the child.
*/
do_exec (child_err_report_pipe[1],
do_exec (g_unix_pipe_get (&child_err_report_pipe, G_UNIX_PIPE_END_WRITE),
stdin_fd,
stdout_fd,
stderr_fd,
@ -2660,8 +2644,8 @@ fork_exec (gboolean intermediate_child,
gint n_ints = 0;
/* Close the uncared-about ends of the pipes */
close_and_invalidate (&child_err_report_pipe[1]);
close_and_invalidate (&child_pid_report_pipe[1]);
g_unix_pipe_close (&child_err_report_pipe, G_UNIX_PIPE_END_WRITE, NULL);
g_unix_pipe_close (&child_pid_report_pipe, G_UNIX_PIPE_END_WRITE, NULL);
/* If we had an intermediate child, reap it */
if (intermediate_child)
@ -2679,7 +2663,7 @@ fork_exec (gboolean intermediate_child,
}
if (!read_ints (child_err_report_pipe[0],
if (!read_ints (g_unix_pipe_get (&child_err_report_pipe, G_UNIX_PIPE_END_READ),
buf, 2, &n_ints,
error))
goto cleanup_and_fail;
@ -2760,7 +2744,7 @@ fork_exec (gboolean intermediate_child,
{
n_ints = 0;
if (!read_ints (child_pid_report_pipe[0],
if (!read_ints (g_unix_pipe_get (&child_pid_report_pipe, G_UNIX_PIPE_END_READ),
buf, 1, &n_ints, error))
goto cleanup_and_fail;
@ -2783,8 +2767,8 @@ fork_exec (gboolean intermediate_child,
}
/* Success against all odds! return the information */
close_and_invalidate (&child_err_report_pipe[0]);
close_and_invalidate (&child_pid_report_pipe[0]);
g_unix_pipe_close (&child_err_report_pipe, G_UNIX_PIPE_END_READ, NULL);
g_unix_pipe_close (&child_pid_report_pipe, G_UNIX_PIPE_END_READ, NULL);
g_free (search_path_buffer_heap);
g_free (argv_buffer_heap);
@ -2798,18 +2782,18 @@ fork_exec (gboolean intermediate_child,
success:
/* Close the uncared-about ends of the pipes */
close_and_invalidate (&stdin_pipe[0]);
close_and_invalidate (&stdout_pipe[1]);
close_and_invalidate (&stderr_pipe[1]);
g_unix_pipe_close (&stdin_pipe, G_UNIX_PIPE_END_READ, NULL);
g_unix_pipe_close (&stdout_pipe, G_UNIX_PIPE_END_WRITE, NULL);
g_unix_pipe_close (&stderr_pipe, G_UNIX_PIPE_END_WRITE, NULL);
if (stdin_pipe_out != NULL)
*stdin_pipe_out = g_steal_fd (&stdin_pipe[1]);
*stdin_pipe_out = g_unix_pipe_steal (&stdin_pipe, G_UNIX_PIPE_END_WRITE);
if (stdout_pipe_out != NULL)
*stdout_pipe_out = g_steal_fd (&stdout_pipe[0]);
*stdout_pipe_out = g_unix_pipe_steal (&stdout_pipe, G_UNIX_PIPE_END_READ);
if (stderr_pipe_out != NULL)
*stderr_pipe_out = g_steal_fd (&stderr_pipe[0]);
*stderr_pipe_out = g_unix_pipe_steal (&stderr_pipe, G_UNIX_PIPE_END_READ);
return TRUE;
@ -2833,17 +2817,11 @@ success:
}
}
close_and_invalidate (&stdin_pipe[0]);
close_and_invalidate (&stdin_pipe[1]);
close_and_invalidate (&stdout_pipe[0]);
close_and_invalidate (&stdout_pipe[1]);
close_and_invalidate (&stderr_pipe[0]);
close_and_invalidate (&stderr_pipe[1]);
close_and_invalidate (&child_err_report_pipe[0]);
close_and_invalidate (&child_err_report_pipe[1]);
close_and_invalidate (&child_pid_report_pipe[0]);
close_and_invalidate (&child_pid_report_pipe[1]);
g_unix_pipe_clear (&stdin_pipe);
g_unix_pipe_clear (&stdout_pipe);
g_unix_pipe_clear (&stderr_pipe);
g_unix_pipe_clear (&child_err_report_pipe);
g_unix_pipe_clear (&child_pid_report_pipe);
g_clear_pointer (&search_path_buffer_heap, g_free);
g_clear_pointer (&argv_buffer_heap, g_free);

View File

@ -60,6 +60,8 @@
#define G_TEST_DIR_MODE (S_IWRITE | S_IREAD)
#endif
#include "testutils.h"
#define S G_DIR_SEPARATOR_S
static void
@ -2473,26 +2475,6 @@ test_win32_zero_terminate_symlink (void)
#endif
static void
assert_fd_was_closed (int fd)
{
/* We can't tell a fd was really closed without behaving as though it
* was still valid */
if (g_test_undefined ())
{
int result, errsv;
GWin32InvalidParameterHandler handler;
GLIB_PRIVATE_CALL (g_win32_push_empty_invalid_parameter_handler) (&handler);
result = g_fsync (fd);
errsv = errno;
GLIB_PRIVATE_CALL (g_win32_pop_invalid_parameter_handler) (&handler);
g_assert_cmpint (result, !=, 0);
g_assert_cmpint (errsv, ==, EBADF);
}
}
static void
test_clear_fd_ebadf (void)
{

50
glib/tests/testutils.h Normal file
View File

@ -0,0 +1,50 @@
/*
* Copyright 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
* Copyright 2023 Collabora Ltd.
*
* SPDX-License-Identifier: LicenseRef-old-glib-tests
*
* This work is provided "as is"; redistribution and modification
* in whole or in part, in any medium, physical or electronic is
* permitted without restriction.
*
* This work is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* In no event shall the authors or contributors be liable for any
* direct, indirect, incidental, special, exemplary, or consequential
* damages (including, but not limited to, procurement of substitute
* goods or services; loss of use, data, or profits; or business
* interruption) however caused and on any theory of liability, whether
* in contract, strict liability, or tort (including negligence or
* otherwise) arising in any way out of the use of this software, even
* if advised of the possibility of such damage.
*/
#include "config.h"
#include <errno.h>
#include "glib-private.h"
#include "gstdio.h"
static inline void
assert_fd_was_closed (int fd)
{
/* We can't tell a fd was really closed without behaving as though it
* was still valid */
if (g_test_undefined ())
{
int result, errsv;
GWin32InvalidParameterHandler handler;
GLIB_PRIVATE_CALL (g_win32_push_empty_invalid_parameter_handler) (&handler);
result = g_fsync (fd);
errsv = errno;
GLIB_PRIVATE_CALL (g_win32_pop_invalid_parameter_handler) (&handler);
g_assert_cmpint (result, !=, 0);
g_assert_cmpint (errsv, ==, EBADF);
}
}

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2011 Red Hat, Inc.
* Copyright 2023 Collabora Ltd.
*
* SPDX-License-Identifier: LicenseRef-old-glib-tests
*
@ -25,6 +26,7 @@
#include "config.h"
#include "glib-private.h"
#include "glib-unix.h"
#include "gstdio.h"
@ -32,6 +34,8 @@
#include <pwd.h>
#include <unistd.h>
#include "testutils.h"
static void
test_pipe (void)
{
@ -122,6 +126,116 @@ test_pipe_stdio_overwrite (void)
g_assert_no_error (error);
}
static void
test_pipe_struct (void)
{
GError *error = NULL;
GUnixPipe pair = G_UNIX_PIPE_INIT;
char buf[1024];
gssize bytes_read;
gboolean res;
int read_end = -1; /* owned */
int write_end = -1; /* unowned */
int errsv;
g_test_summary ("Test GUnixPipe structure");
res = g_unix_pipe_open (&pair, FD_CLOEXEC, &error);
g_assert_no_error (error);
g_assert_true (res);
read_end = g_unix_pipe_steal (&pair, G_UNIX_PIPE_END_READ);
g_assert_cmpint (read_end, >=, 0);
g_assert_cmpint (g_unix_pipe_steal (&pair, G_UNIX_PIPE_END_READ), ==, -1);
g_assert_cmpint (g_unix_pipe_get (&pair, G_UNIX_PIPE_END_READ), ==, -1);
write_end = g_unix_pipe_get (&pair, G_UNIX_PIPE_END_WRITE);
g_assert_cmpint (write_end, >=, 0);
g_assert_cmpint (g_unix_pipe_get (&pair, G_UNIX_PIPE_END_WRITE), ==, write_end);
g_assert_cmpint (write (write_end, "hello", sizeof ("hello")), ==, sizeof ("hello"));
memset (buf, 0, sizeof (buf));
bytes_read = read (read_end, buf, sizeof(buf) - 1);
g_assert_cmpint (bytes_read, ==, sizeof ("hello"));
buf[bytes_read] = '\0';
/* This is one of the few errno values guaranteed by Standard C.
* We set it here to check that g_unix_pipe_clear doesn't alter errno. */
errno = EILSEQ;
g_unix_pipe_clear (&pair);
errsv = errno;
g_assert_cmpint (errsv, ==, EILSEQ);
g_assert_cmpint (pair.fds[0], ==, -1);
g_assert_cmpint (pair.fds[1], ==, -1);
/* The read end wasn't closed, because it was stolen first */
g_clear_fd (&read_end, &error);
g_assert_no_error (error);
/* The write end was closed, because it wasn't stolen */
assert_fd_was_closed (write_end);
g_assert_cmpstr (buf, ==, "hello");
}
static void
test_pipe_struct_auto (void)
{
#ifdef g_autofree
int i;
g_test_summary ("Test g_auto(GUnixPipe)");
/* Let g_auto close the read end, the write end, neither, or both */
for (i = 0; i < 4; i++)
{
int read_end = -1; /* unowned */
int write_end = -1; /* unowned */
int errsv;
{
g_auto(GUnixPipe) pair = G_UNIX_PIPE_INIT;
GError *error = NULL;
gboolean res;
res = g_unix_pipe_open (&pair, FD_CLOEXEC, &error);
g_assert_no_error (error);
g_assert_true (res);
read_end = pair.fds[G_UNIX_PIPE_END_READ];
g_assert_cmpint (read_end, >=, 0);
write_end = pair.fds[G_UNIX_PIPE_END_WRITE];
g_assert_cmpint (write_end, >=, 0);
if (i & 1)
{
/* This also exercises g_unix_pipe_close() with error */
res = g_unix_pipe_close (&pair, G_UNIX_PIPE_END_READ, &error);
g_assert_no_error (error);
g_assert_true (res);
}
/* This also exercises g_unix_pipe_close() without error */
if (i & 2)
g_unix_pipe_close (&pair, G_UNIX_PIPE_END_WRITE, NULL);
/* This is one of the few errno values guaranteed by Standard C.
* We set it here to check that a g_auto(GUnixPipe) close doesn't
* alter errno. */
errno = EILSEQ;
}
errsv = errno;
g_assert_cmpint (errsv, ==, EILSEQ);
assert_fd_was_closed (read_end);
assert_fd_was_closed (write_end);
}
#else
g_test_skip ("g_auto not supported by compiler");
#endif
}
static void
test_error (void)
{
@ -502,6 +616,8 @@ main (int argc,
g_test_add_func ("/glib-unix/pipe", test_pipe);
g_test_add_func ("/glib-unix/pipe/fd-cloexec", test_pipe_fd_cloexec);
g_test_add_func ("/glib-unix/pipe-stdio-overwrite", test_pipe_stdio_overwrite);
g_test_add_func ("/glib-unix/pipe-struct", test_pipe_struct);
g_test_add_func ("/glib-unix/pipe-struct-auto", test_pipe_struct_auto);
g_test_add_func ("/glib-unix/error", test_error);
g_test_add_func ("/glib-unix/nonblocking", test_nonblocking);
g_test_add_func ("/glib-unix/sighup", test_sighup);