mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2025-04-01 13:23:07 +02:00
819 lines
20 KiB
C
819 lines
20 KiB
C
/*
|
||
* Copyright (C) 2011 Red Hat, Inc.
|
||
* 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.
|
||
*
|
||
* Author: Colin Walters <walters@verbum.org>
|
||
*/
|
||
|
||
#include "config.h"
|
||
|
||
#include "glib-private.h"
|
||
#include "glib-unix.h"
|
||
#include "gstdio.h"
|
||
|
||
#include <string.h>
|
||
#include <pwd.h>
|
||
#include <unistd.h>
|
||
|
||
#include "testutils.h"
|
||
|
||
static void
|
||
async_signal_safe_message (const char *message)
|
||
{
|
||
if (write (2, message, strlen (message)) < 0 ||
|
||
write (2, "\n", 1) < 0)
|
||
{
|
||
/* ignore: not much we can do */
|
||
}
|
||
}
|
||
|
||
static void
|
||
test_closefrom (void)
|
||
{
|
||
/* Enough file descriptors to be confident that we're operating on
|
||
* all of them */
|
||
const int N_FDS = 20;
|
||
int *fds;
|
||
int fd;
|
||
int i;
|
||
pid_t child;
|
||
int wait_status;
|
||
|
||
/* The loop that populates @fds with pipes assumes this */
|
||
g_assert (N_FDS % 2 == 0);
|
||
|
||
g_test_summary ("Test g_closefrom(), g_fdwalk_set_cloexec()");
|
||
g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/3247");
|
||
|
||
for (fd = 0; fd <= 2; fd++)
|
||
{
|
||
int flags;
|
||
|
||
g_assert_no_errno ((flags = fcntl (fd, F_GETFD)));
|
||
g_assert_no_errno (fcntl (fd, F_SETFD, flags & ~FD_CLOEXEC));
|
||
}
|
||
|
||
fds = g_new0 (int, N_FDS);
|
||
|
||
for (i = 0; i < N_FDS; i += 2)
|
||
{
|
||
GError *error = NULL;
|
||
int pipefd[2];
|
||
int res;
|
||
|
||
/* Intentionally neither O_CLOEXEC nor FD_CLOEXEC */
|
||
res = g_unix_open_pipe (pipefd, 0, &error);
|
||
g_assert (res);
|
||
g_assert_no_error (error);
|
||
g_clear_error (&error);
|
||
fds[i] = pipefd[0];
|
||
fds[i + 1] = pipefd[1];
|
||
}
|
||
|
||
child = fork ();
|
||
|
||
/* Child process exits with status = 100 + the first wrong fd,
|
||
* or 0 if all were correct */
|
||
if (child == 0)
|
||
{
|
||
for (i = 0; i < N_FDS; i++)
|
||
{
|
||
int flags = fcntl (fds[i], F_GETFD);
|
||
|
||
if (flags == -1)
|
||
{
|
||
async_signal_safe_message ("fd should not have been closed");
|
||
_exit (100 + fds[i]);
|
||
}
|
||
|
||
if (flags & FD_CLOEXEC)
|
||
{
|
||
async_signal_safe_message ("fd should not have been close-on-exec yet");
|
||
_exit (100 + fds[i]);
|
||
}
|
||
}
|
||
|
||
g_fdwalk_set_cloexec (3);
|
||
|
||
for (i = 0; i < N_FDS; i++)
|
||
{
|
||
int flags = fcntl (fds[i], F_GETFD);
|
||
|
||
if (flags == -1)
|
||
{
|
||
async_signal_safe_message ("fd should not have been closed");
|
||
_exit (100 + fds[i]);
|
||
}
|
||
|
||
if (!(flags & FD_CLOEXEC))
|
||
{
|
||
async_signal_safe_message ("fd should have been close-on-exec");
|
||
_exit (100 + fds[i]);
|
||
}
|
||
}
|
||
|
||
g_closefrom (3);
|
||
|
||
for (fd = 0; fd <= 2; fd++)
|
||
{
|
||
int flags = fcntl (fd, F_GETFD);
|
||
|
||
if (flags == -1)
|
||
{
|
||
async_signal_safe_message ("fd should not have been closed");
|
||
_exit (100 + fd);
|
||
}
|
||
|
||
if (flags & FD_CLOEXEC)
|
||
{
|
||
async_signal_safe_message ("fd should not have been close-on-exec");
|
||
_exit (100 + fd);
|
||
}
|
||
}
|
||
|
||
for (i = 0; i < N_FDS; i++)
|
||
{
|
||
if (fcntl (fds[i], F_GETFD) != -1 || errno != EBADF)
|
||
{
|
||
async_signal_safe_message ("fd should have been closed");
|
||
_exit (100 + fds[i]);
|
||
}
|
||
}
|
||
|
||
_exit (0);
|
||
}
|
||
|
||
g_assert_no_errno (waitpid (child, &wait_status, 0));
|
||
|
||
if (WIFEXITED (wait_status))
|
||
{
|
||
int exit_status = WEXITSTATUS (wait_status);
|
||
|
||
if (exit_status != 0)
|
||
g_test_fail_printf ("File descriptor %d in incorrect state", exit_status - 100);
|
||
}
|
||
else
|
||
{
|
||
g_test_fail_printf ("Unexpected wait status %d", wait_status);
|
||
}
|
||
|
||
for (i = 0; i < N_FDS; i++)
|
||
{
|
||
GError *error = NULL;
|
||
|
||
g_close (fds[i], &error);
|
||
g_assert_no_error (error);
|
||
g_clear_error (&error);
|
||
}
|
||
|
||
g_free (fds);
|
||
|
||
if (g_test_undefined ())
|
||
{
|
||
g_test_trap_subprocess ("/glib-unix/closefrom/subprocess/einval",
|
||
0, G_TEST_SUBPROCESS_DEFAULT);
|
||
g_test_trap_assert_passed ();
|
||
}
|
||
}
|
||
|
||
static void
|
||
test_closefrom_subprocess_einval (void)
|
||
{
|
||
int res;
|
||
int errsv;
|
||
|
||
g_log_set_always_fatal (G_LOG_FATAL_MASK);
|
||
g_log_set_fatal_mask ("GLib", G_LOG_FATAL_MASK);
|
||
|
||
errno = 0;
|
||
res = g_closefrom (-1);
|
||
errsv = errno;
|
||
g_assert_cmpint (res, ==, -1);
|
||
g_assert_cmpint (errsv, ==, EINVAL);
|
||
|
||
errno = 0;
|
||
res = g_fdwalk_set_cloexec (-42);
|
||
errsv = errno;
|
||
g_assert_cmpint (res, ==, -1);
|
||
g_assert_cmpint (errsv, ==, EINVAL);
|
||
}
|
||
|
||
static void
|
||
test_pipe (void)
|
||
{
|
||
GError *error = NULL;
|
||
int pipefd[2];
|
||
char buf[1024];
|
||
gssize bytes_read;
|
||
gboolean res;
|
||
|
||
res = g_unix_open_pipe (pipefd, O_CLOEXEC, &error);
|
||
g_assert (res);
|
||
g_assert_no_error (error);
|
||
|
||
g_assert_cmpint (write (pipefd[1], "hello", sizeof ("hello")), ==, sizeof ("hello"));
|
||
memset (buf, 0, sizeof (buf));
|
||
bytes_read = read (pipefd[0], buf, sizeof(buf) - 1);
|
||
g_assert_cmpint (bytes_read, >, 0);
|
||
buf[bytes_read] = '\0';
|
||
|
||
close (pipefd[0]);
|
||
close (pipefd[1]);
|
||
|
||
g_assert (g_str_has_prefix (buf, "hello"));
|
||
}
|
||
|
||
static void
|
||
test_pipe_fd_cloexec (void)
|
||
{
|
||
GError *error = NULL;
|
||
int pipefd[2];
|
||
char buf[1024];
|
||
gssize bytes_read;
|
||
gboolean res;
|
||
|
||
g_test_summary ("Test that FD_CLOEXEC is still accepted as an argument to g_unix_open_pipe()");
|
||
g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/merge_requests/3459");
|
||
|
||
res = g_unix_open_pipe (pipefd, FD_CLOEXEC, &error);
|
||
g_assert (res);
|
||
g_assert_no_error (error);
|
||
|
||
g_assert_cmpint (write (pipefd[1], "hello", sizeof ("hello")), ==, sizeof ("hello"));
|
||
memset (buf, 0, sizeof (buf));
|
||
bytes_read = read (pipefd[0], buf, sizeof(buf) - 1);
|
||
g_assert_cmpint (bytes_read, >, 0);
|
||
buf[bytes_read] = '\0';
|
||
|
||
close (pipefd[0]);
|
||
close (pipefd[1]);
|
||
|
||
g_assert_true (g_str_has_prefix (buf, "hello"));
|
||
}
|
||
|
||
static void
|
||
test_pipe_stdio_overwrite (void)
|
||
{
|
||
GError *error = NULL;
|
||
int pipefd[2], ret;
|
||
gboolean res;
|
||
int stdin_fd;
|
||
|
||
|
||
g_test_summary ("Test that g_unix_open_pipe() will use the first available FD, even if it’s stdin/stdout/stderr");
|
||
g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/2795");
|
||
|
||
stdin_fd = dup (STDIN_FILENO);
|
||
g_assert_cmpint (stdin_fd, >, 0);
|
||
|
||
g_close (STDIN_FILENO, &error);
|
||
g_assert_no_error (error);
|
||
|
||
res = g_unix_open_pipe (pipefd, O_CLOEXEC, &error);
|
||
g_assert_no_error (error);
|
||
g_assert_true (res);
|
||
|
||
g_assert_cmpint (pipefd[0], ==, STDIN_FILENO);
|
||
|
||
g_close (pipefd[0], &error);
|
||
g_assert_no_error (error);
|
||
|
||
g_close (pipefd[1], &error);
|
||
g_assert_no_error (error);
|
||
|
||
ret = dup2 (stdin_fd, STDIN_FILENO);
|
||
g_assert_cmpint (ret, >=, 0);
|
||
|
||
g_close (stdin_fd, &error);
|
||
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)
|
||
{
|
||
GError *error = NULL;
|
||
gboolean res;
|
||
|
||
res = g_unix_set_fd_nonblocking (123456, TRUE, &error);
|
||
g_assert_cmpint (errno, ==, EBADF);
|
||
g_assert (!res);
|
||
g_assert_error (error, G_UNIX_ERROR, 0);
|
||
g_clear_error (&error);
|
||
}
|
||
|
||
static void
|
||
test_nonblocking (void)
|
||
{
|
||
GError *error = NULL;
|
||
int pipefd[2];
|
||
gboolean res;
|
||
int flags;
|
||
|
||
res = g_unix_open_pipe (pipefd, O_CLOEXEC, &error);
|
||
g_assert (res);
|
||
g_assert_no_error (error);
|
||
|
||
res = g_unix_set_fd_nonblocking (pipefd[0], TRUE, &error);
|
||
g_assert (res);
|
||
g_assert_no_error (error);
|
||
|
||
flags = fcntl (pipefd[0], F_GETFL);
|
||
g_assert_cmpint (flags, !=, -1);
|
||
g_assert (flags & O_NONBLOCK);
|
||
|
||
res = g_unix_set_fd_nonblocking (pipefd[0], FALSE, &error);
|
||
g_assert (res);
|
||
g_assert_no_error (error);
|
||
|
||
flags = fcntl (pipefd[0], F_GETFL);
|
||
g_assert_cmpint (flags, !=, -1);
|
||
g_assert (!(flags & O_NONBLOCK));
|
||
|
||
close (pipefd[0]);
|
||
close (pipefd[1]);
|
||
}
|
||
|
||
static gboolean sig_received = FALSE;
|
||
static gboolean sig_timeout = FALSE;
|
||
static int sig_counter = 0;
|
||
|
||
static gboolean
|
||
on_sig_received (gpointer user_data)
|
||
{
|
||
GMainLoop *loop = user_data;
|
||
g_main_loop_quit (loop);
|
||
sig_received = TRUE;
|
||
sig_counter ++;
|
||
return G_SOURCE_REMOVE;
|
||
}
|
||
|
||
static gboolean
|
||
on_sig_timeout (gpointer data)
|
||
{
|
||
GMainLoop *loop = data;
|
||
g_main_loop_quit (loop);
|
||
sig_timeout = TRUE;
|
||
return G_SOURCE_REMOVE;
|
||
}
|
||
|
||
static gboolean
|
||
exit_mainloop (gpointer data)
|
||
{
|
||
GMainLoop *loop = data;
|
||
g_main_loop_quit (loop);
|
||
return G_SOURCE_REMOVE;
|
||
}
|
||
|
||
static gboolean
|
||
on_sig_received_2 (gpointer data)
|
||
{
|
||
GMainLoop *loop = data;
|
||
|
||
sig_counter ++;
|
||
if (sig_counter == 2)
|
||
g_main_loop_quit (loop);
|
||
return G_SOURCE_REMOVE;
|
||
}
|
||
|
||
static void
|
||
test_signal (int signum)
|
||
{
|
||
GMainLoop *mainloop;
|
||
int id;
|
||
|
||
mainloop = g_main_loop_new (NULL, FALSE);
|
||
|
||
sig_received = FALSE;
|
||
sig_counter = 0;
|
||
g_unix_signal_add (signum, on_sig_received, mainloop);
|
||
kill (getpid (), signum);
|
||
g_assert (!sig_received);
|
||
id = g_timeout_add (5000, on_sig_timeout, mainloop);
|
||
g_main_loop_run (mainloop);
|
||
g_assert (sig_received);
|
||
sig_received = FALSE;
|
||
g_source_remove (id);
|
||
|
||
/* Ensure we don't get double delivery */
|
||
g_timeout_add (500, exit_mainloop, mainloop);
|
||
g_main_loop_run (mainloop);
|
||
g_assert (!sig_received);
|
||
|
||
/* Ensure that two sources for the same signal get it */
|
||
sig_counter = 0;
|
||
g_unix_signal_add (signum, on_sig_received_2, mainloop);
|
||
g_unix_signal_add (signum, on_sig_received_2, mainloop);
|
||
id = g_timeout_add (5000, on_sig_timeout, mainloop);
|
||
|
||
kill (getpid (), signum);
|
||
g_main_loop_run (mainloop);
|
||
g_assert_cmpint (sig_counter, ==, 2);
|
||
g_source_remove (id);
|
||
|
||
g_main_loop_unref (mainloop);
|
||
}
|
||
|
||
static void
|
||
test_sighup (void)
|
||
{
|
||
test_signal (SIGHUP);
|
||
}
|
||
|
||
static void
|
||
test_sigterm (void)
|
||
{
|
||
test_signal (SIGTERM);
|
||
}
|
||
|
||
static void
|
||
test_sighup_add_remove (void)
|
||
{
|
||
guint id;
|
||
struct sigaction action;
|
||
|
||
sig_received = FALSE;
|
||
id = g_unix_signal_add (SIGHUP, on_sig_received, NULL);
|
||
g_source_remove (id);
|
||
|
||
sigaction (SIGHUP, NULL, &action);
|
||
g_assert (action.sa_handler == SIG_DFL);
|
||
}
|
||
|
||
static gboolean
|
||
nested_idle (gpointer data)
|
||
{
|
||
GMainLoop *nested;
|
||
GMainContext *context;
|
||
GSource *source;
|
||
|
||
context = g_main_context_new ();
|
||
nested = g_main_loop_new (context, FALSE);
|
||
|
||
source = g_unix_signal_source_new (SIGHUP);
|
||
g_source_set_callback (source, on_sig_received, nested, NULL);
|
||
g_source_attach (source, context);
|
||
g_source_unref (source);
|
||
|
||
kill (getpid (), SIGHUP);
|
||
g_main_loop_run (nested);
|
||
g_assert_cmpint (sig_counter, ==, 1);
|
||
|
||
g_main_loop_unref (nested);
|
||
g_main_context_unref (context);
|
||
|
||
return G_SOURCE_REMOVE;
|
||
}
|
||
|
||
static void
|
||
test_sighup_nested (void)
|
||
{
|
||
GMainLoop *mainloop;
|
||
|
||
mainloop = g_main_loop_new (NULL, FALSE);
|
||
|
||
sig_counter = 0;
|
||
sig_received = FALSE;
|
||
g_unix_signal_add (SIGHUP, on_sig_received, mainloop);
|
||
g_idle_add (nested_idle, mainloop);
|
||
|
||
g_main_loop_run (mainloop);
|
||
g_assert_cmpint (sig_counter, ==, 2);
|
||
|
||
g_main_loop_unref (mainloop);
|
||
}
|
||
|
||
static gboolean
|
||
on_sigwinch_received (gpointer data)
|
||
{
|
||
GMainLoop *loop = (GMainLoop *) data;
|
||
|
||
sig_counter ++;
|
||
|
||
if (sig_counter == 1)
|
||
kill (getpid (), SIGWINCH);
|
||
else if (sig_counter == 2)
|
||
g_main_loop_quit (loop);
|
||
else if (sig_counter > 2)
|
||
g_assert_not_reached ();
|
||
|
||
/* Increase the time window in which an issue could happen. */
|
||
g_usleep (G_USEC_PER_SEC);
|
||
|
||
return G_SOURCE_CONTINUE;
|
||
}
|
||
|
||
static void
|
||
test_callback_after_signal (void)
|
||
{
|
||
/* Checks that user signal callback is invoked *after* receiving a signal.
|
||
* In other words a new signal is never merged with the one being currently
|
||
* dispatched or whose dispatch had already finished. */
|
||
|
||
GMainLoop *mainloop;
|
||
GMainContext *context;
|
||
GSource *source;
|
||
|
||
sig_counter = 0;
|
||
|
||
context = g_main_context_new ();
|
||
mainloop = g_main_loop_new (context, FALSE);
|
||
|
||
source = g_unix_signal_source_new (SIGWINCH);
|
||
g_source_set_callback (source, on_sigwinch_received, mainloop, NULL);
|
||
g_source_attach (source, context);
|
||
g_source_unref (source);
|
||
|
||
g_assert_cmpint (sig_counter, ==, 0);
|
||
kill (getpid (), SIGWINCH);
|
||
g_main_loop_run (mainloop);
|
||
g_assert_cmpint (sig_counter, ==, 2);
|
||
|
||
g_main_loop_unref (mainloop);
|
||
g_main_context_unref (context);
|
||
}
|
||
|
||
static void
|
||
test_get_passwd_entry_root (void)
|
||
{
|
||
struct passwd *pwd;
|
||
GError *local_error = NULL;
|
||
|
||
g_test_summary ("Tests that g_unix_get_passwd_entry() works for a "
|
||
"known-existing username.");
|
||
|
||
pwd = g_unix_get_passwd_entry ("root", &local_error);
|
||
g_assert_no_error (local_error);
|
||
|
||
g_assert_cmpstr (pwd->pw_name, ==, "root");
|
||
g_assert_cmpuint (pwd->pw_uid, ==, 0);
|
||
|
||
g_free (pwd);
|
||
}
|
||
|
||
static void
|
||
test_get_passwd_entry_nonexistent (void)
|
||
{
|
||
struct passwd *pwd;
|
||
GError *local_error = NULL;
|
||
|
||
g_test_summary ("Tests that g_unix_get_passwd_entry() returns an error for a "
|
||
"nonexistent username.");
|
||
|
||
pwd = g_unix_get_passwd_entry ("thisusernamedoesntexist", &local_error);
|
||
g_assert_error (local_error, G_UNIX_ERROR, 0);
|
||
g_assert_null (pwd);
|
||
|
||
g_clear_error (&local_error);
|
||
}
|
||
|
||
static void
|
||
_child_wait_watch_cb (GPid pid,
|
||
gint wait_status,
|
||
gpointer user_data)
|
||
{
|
||
gboolean *p_got_callback = user_data;
|
||
|
||
g_assert_nonnull (p_got_callback);
|
||
g_assert_false (*p_got_callback);
|
||
*p_got_callback = TRUE;
|
||
}
|
||
|
||
static void
|
||
test_child_wait (void)
|
||
{
|
||
gboolean r;
|
||
GPid pid;
|
||
guint id;
|
||
pid_t pid2;
|
||
int wstatus;
|
||
gboolean got_callback = FALSE;
|
||
gboolean iterate_maincontext = g_test_rand_bit ();
|
||
char **argv;
|
||
int errsv;
|
||
|
||
/* - We spawn a trivial child process that exits after a short time.
|
||
* - We schedule a g_child_watch_add()
|
||
* - we may iterate the GMainContext a bit. Randomly we either get the
|
||
* child-watcher callback or not.
|
||
* - if we didn't get the callback, we g_source_remove() the child watcher.
|
||
*
|
||
* Afterwards, if the callback didn't fire, we check that we are able to waitpid()
|
||
* on the process ourselves. Of course, if the child watcher notified, the waitpid()
|
||
* will fail with ECHILD.
|
||
*/
|
||
|
||
argv = g_test_rand_bit () ? ((char *[]){ "/bin/sleep", "0.05", NULL }) : ((char *[]){ "/bin/true", NULL });
|
||
|
||
r = g_spawn_async (NULL,
|
||
argv,
|
||
NULL,
|
||
G_SPAWN_DO_NOT_REAP_CHILD,
|
||
NULL,
|
||
NULL,
|
||
&pid,
|
||
NULL);
|
||
if (!r)
|
||
{
|
||
/* Some odd system without /bin/sleep? Skip the test. */
|
||
g_test_skip ("failure to spawn test process in test_child_wait()");
|
||
return;
|
||
}
|
||
|
||
g_assert_cmpint (pid, >=, 1);
|
||
|
||
if (g_test_rand_bit ())
|
||
g_usleep (g_test_rand_int_range (0, (G_USEC_PER_SEC / 10)));
|
||
|
||
id = g_child_watch_add (pid, _child_wait_watch_cb, &got_callback);
|
||
|
||
if (g_test_rand_bit ())
|
||
g_usleep (g_test_rand_int_range (0, (G_USEC_PER_SEC / 10)));
|
||
|
||
if (iterate_maincontext)
|
||
{
|
||
gint64 start_usec = g_get_monotonic_time ();
|
||
gint64 end_usec = start_usec + g_test_rand_int_range (0, (G_USEC_PER_SEC / 10));
|
||
|
||
while (!got_callback && g_get_monotonic_time () < end_usec)
|
||
g_main_context_iteration (NULL, FALSE);
|
||
}
|
||
|
||
if (!got_callback)
|
||
g_source_remove (id);
|
||
|
||
errno = 0;
|
||
pid2 = waitpid (pid, &wstatus, 0);
|
||
errsv = errno;
|
||
if (got_callback)
|
||
{
|
||
g_assert_true (iterate_maincontext);
|
||
g_assert_cmpint (errsv, ==, ECHILD);
|
||
g_assert_cmpint (pid2, <, 0);
|
||
}
|
||
else
|
||
{
|
||
g_assert_cmpint (errsv, ==, 0);
|
||
g_assert_cmpint (pid2, ==, pid);
|
||
g_assert_true (WIFEXITED (wstatus));
|
||
g_assert_cmpint (WEXITSTATUS (wstatus), ==, 0);
|
||
}
|
||
}
|
||
|
||
int
|
||
main (int argc,
|
||
char *argv[])
|
||
{
|
||
g_test_init (&argc, &argv, NULL);
|
||
|
||
g_test_add_func ("/glib-unix/closefrom", test_closefrom);
|
||
g_test_add_func ("/glib-unix/closefrom/subprocess/einval",
|
||
test_closefrom_subprocess_einval);
|
||
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);
|
||
g_test_add_func ("/glib-unix/sigterm", test_sigterm);
|
||
g_test_add_func ("/glib-unix/sighup_again", test_sighup);
|
||
g_test_add_func ("/glib-unix/sighup_add_remove", test_sighup_add_remove);
|
||
g_test_add_func ("/glib-unix/sighup_nested", test_sighup_nested);
|
||
g_test_add_func ("/glib-unix/callback_after_signal", test_callback_after_signal);
|
||
g_test_add_func ("/glib-unix/get-passwd-entry/root", test_get_passwd_entry_root);
|
||
g_test_add_func ("/glib-unix/get-passwd-entry/nonexistent", test_get_passwd_entry_nonexistent);
|
||
g_test_add_func ("/glib-unix/child-wait", test_child_wait);
|
||
|
||
return g_test_run();
|
||
}
|