diff options
-rw-r--r-- | tests/Makefile.am.inc | 25 | ||||
-rw-r--r-- | tests/hold-lock.c | 217 | ||||
-rw-r--r-- | tests/test-instance.c | 125 |
3 files changed, 365 insertions, 2 deletions
diff --git a/tests/Makefile.am.inc b/tests/Makefile.am.inc index 61aaba12..1e303479 100644 --- a/tests/Makefile.am.inc +++ b/tests/Makefile.am.inc @@ -86,6 +86,14 @@ test_exports_CFLAGS = $(testcommon_CFLAGS) test_exports_LDADD = $(testcommon_LDADD) libtestlib.la test_exports_SOURCES = tests/test-exports.c +test_instance_CFLAGS = $(testcommon_CFLAGS) +test_instance_LDADD = $(testcommon_LDADD) libtestlib.la +test_instance_SOURCES = tests/test-instance.c + +tests_hold_lock_CFLAGS = $(AM_CFLAGS) $(BASE_CFLAGS) +tests_hold_lock_LDADD = $(AM_LDADD) $(BASE_LIBS) libglnx.la +tests_hold_lock_SOURCES = tests/hold-lock.c + tests_httpcache_CFLAGS = $(AM_CFLAGS) $(BASE_CFLAGS) $(OSTREE_CFLAGS) $(SOUP_CFLAGS) $(JSON_CFLAGS) $(APPSTREAM_GLIB_CFLAGS) \ -DFLATPAK_COMPILATION \ -DLOCALEDIR=\"$(localedir)\" @@ -267,8 +275,21 @@ test_scripts = ${TEST_MATRIX} dist_test_scripts = ${TEST_MATRIX_DIST} dist_installed_test_extra_scripts += ${TEST_MATRIX_EXTRA_DIST} -test_programs = testlibrary testcommon test-exports -test_extra_programs = tests/httpcache tests/test-update-portal tests/test-portal-impl tests/test-authenticator tests/list-unused +test_programs = \ + test-exports \ + test-instance \ + testcommon \ + testlibrary \ + $(NULL) + +test_extra_programs = \ + tests/hold-lock \ + tests/httpcache \ + tests/list-unused \ + tests/test-authenticator \ + tests/test-portal-impl \ + tests/test-update-portal \ + $(NULL) @VALGRIND_CHECK_RULES@ VALGRIND_SUPPRESSIONS_FILES=tests/flatpak.supp tests/glib.supp diff --git a/tests/hold-lock.c b/tests/hold-lock.c new file mode 100644 index 00000000..a4f7593b --- /dev/null +++ b/tests/hold-lock.c @@ -0,0 +1,217 @@ +/* + * Copyright © 2019-2021 Collabora Ltd. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * 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.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 "config.h" + +#include <glib.h> +#include <glib/gstdio.h> + +#include <fcntl.h> +#include <fcntl.h> +#include <sysexits.h> +#include <sys/prctl.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "libglnx/libglnx.h" + +static GArray *global_locks = NULL; +static gboolean opt_wait = FALSE; +static gboolean opt_write = FALSE; + +static gboolean +opt_fd_cb (const char *name, + const char *value, + gpointer data, + GError **error) +{ + char *endptr; + gint64 i64 = g_ascii_strtoll (value, &endptr, 10); + int fd; + int fd_flags; + + g_return_val_if_fail (global_locks != NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + g_return_val_if_fail (value != NULL, FALSE); + + if (i64 < 0 || i64 > G_MAXINT || endptr == value || *endptr != '\0') + { + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, + "Integer out of range or invalid: %s", value); + return FALSE; + } + + fd = (int) i64; + + fd_flags = fcntl (fd, F_GETFD); + + if (fd_flags < 0) + return glnx_throw_errno_prefix (error, "Unable to receive --fd %d", fd); + + if ((fd_flags & FD_CLOEXEC) == 0 + && fcntl (fd, F_SETFD, fd_flags | FD_CLOEXEC) != 0) + return glnx_throw_errno_prefix (error, + "Unable to configure --fd %d for " + "close-on-exec", + fd); + + g_array_append_val (global_locks, fd); + return TRUE; +} + +static gboolean +opt_lock_file_cb (const char *name, + const char *value, + gpointer data, + GError **error) +{ + int open_flags = O_CLOEXEC | O_CREAT | O_NOCTTY | O_RDWR; + int fd; + int cmd; + struct flock l = + { + .l_type = F_RDLCK, + .l_whence = SEEK_SET, + .l_start = 0, + .l_len = 0, + }; + + g_return_val_if_fail (global_locks != NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + g_return_val_if_fail (value != NULL, FALSE); + + fd = TEMP_FAILURE_RETRY (open (value, open_flags, 0644)); + + if (fd < 0) + return glnx_throw_errno_prefix (error, "open %s", value); + + if (opt_write) + l.l_type = F_WRLCK; + + if (opt_wait) + cmd = F_SETLKW; + else + cmd = F_SETLK; + + if (TEMP_FAILURE_RETRY (fcntl (fd, cmd, &l)) < 0) + { + if (errno == EACCES || errno == EAGAIN) + g_set_error (error, G_IO_ERROR, G_IO_ERROR_BUSY, + "Unable to lock %s: file is busy", value); + else + glnx_throw_errno_prefix (error, "lock %s", value); + + close (fd); + return FALSE; + } + + g_array_append_val (global_locks, fd); + return TRUE; +} + +static GOptionEntry options[] = +{ + { "fd", '\0', + G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, opt_fd_cb, + "Take a file descriptor, already locked if desired, and keep it " + "open. May be repeated.", + NULL }, + + { "wait", '\0', + G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_wait, + "Wait for each subsequent lock file.", + NULL }, + { "no-wait", '\0', + G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &opt_wait, + "Exit unsuccessfully if a lock-file is busy [default].", + NULL }, + + { "write", '\0', + G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_write, + "Lock each subsequent lock file for write access.", + NULL }, + { "no-write", '\0', + G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &opt_write, + "Lock each subsequent lock file for read-only access [default].", + NULL }, + + { "lock-file", '\0', + G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, opt_lock_file_cb, + "Open the given file and lock it, affected by options appearing " + "earlier on the command-line. May be repeated.", + NULL }, + + { NULL } +}; + +int +main (int argc, + char *argv[]) +{ + g_autoptr(GArray) locks = NULL; + g_autoptr(GOptionContext) context = NULL; + g_autoptr(GError) local_error = NULL; + GError **error = &local_error; + int ret = EX_USAGE; + + locks = g_array_new (FALSE, FALSE, sizeof (int)); + global_locks = locks; + + context = g_option_context_new (NULL); + g_option_context_add_main_entries (context, options, NULL); + + if (!g_option_context_parse (context, &argc, &argv, error)) + { + if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_BUSY)) + ret = EX_TEMPFAIL; + else if (local_error->domain == G_OPTION_ERROR) + ret = EX_USAGE; + else + ret = EX_UNAVAILABLE; + + goto out; + } + + ret = EX_UNAVAILABLE; + + /* Self-destruct when parent exits */ + if (prctl (PR_SET_PDEATHSIG, SIGTERM, 0, 0, 0) != 0) + { + glnx_throw_errno_prefix (error, + "Unable to set parent death signal"); + goto out; + } + + /* Signal to caller that we are ready */ + fclose (stdout); + + while (TRUE) + pause (); + + g_assert_not_reached (); + +out: + global_locks = NULL; + + if (local_error != NULL) + g_warning ("%s", local_error->message); + + return ret; +} diff --git a/tests/test-instance.c b/tests/test-instance.c new file mode 100644 index 00000000..70b90d2f --- /dev/null +++ b/tests/test-instance.c @@ -0,0 +1,125 @@ +/* + * Copyright © 2021 Collabora Ltd. + * + * 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.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 "config.h" + +#include <fcntl.h> +#include <stdio.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> +#include <utime.h> + +#include <glib.h> +#include <glib/gstdio.h> + +#include "flatpak.h" +#include "flatpak-instance-private.h" + +#include "libglnx/libglnx.h" + +#include "testlib.h" + +static void +test_gc (void) +{ + g_autoptr(GBytes) bytes = NULL; + g_autoptr(GError) error = NULL; + g_autoptr(GPtrArray) instances = NULL; + g_autofree char *base_dir = flatpak_instance_get_instances_directory (); + g_autofree char *alive_dir = g_build_filename (base_dir, "1", NULL); + g_autofree char *alive_lock = g_build_filename (alive_dir, ".ref", NULL); + g_autofree char *dead_dir = g_build_filename (base_dir, "2", NULL); + g_autofree char *dead_lock = g_build_filename (dead_dir, ".ref", NULL); + g_autofree char *hold_lock = g_test_build_filename (G_TEST_BUILT, "hold-lock", NULL); + struct utimbuf a_while_ago = {}; + const char *hold_lock_argv[] = { "hold-lock", "--lock-file", ".ref", NULL }; + GPid pid = -1; + int stdout_fd = -1; + int wstatus = 0; + FlatpakInstance *instance; + struct stat stat_buf; + + g_assert_no_errno (g_mkdir_with_parents (alive_dir, 0700)); + g_assert_no_errno (g_mkdir_with_parents (dead_dir, 0700)); + g_file_set_contents (alive_lock, "", 0, &error); + g_assert_no_error (error); + g_file_set_contents (dead_lock, "", 0, &error); + g_assert_no_error (error); + + hold_lock_argv[0] = hold_lock; + hold_lock_argv[2] = alive_lock; + g_spawn_async_with_pipes (NULL, + (gchar **) hold_lock_argv, + NULL, + G_SPAWN_DO_NOT_REAP_CHILD, + NULL, + NULL, + &pid, + NULL, + &stdout_fd, + NULL, + &error); + g_assert_no_error (error); + g_assert_cmpint (pid, >, 1); + g_assert_cmpint (stdout_fd, >=, 0); + + /* Wait for the child to be ready */ + bytes = glnx_fd_readall_bytes (stdout_fd, NULL, &error); + g_assert_no_error (error); + + /* Pretend the locks were created in early 1970, to bypass the workaround + * for a race */ + g_assert_no_errno (g_utime (alive_lock, &a_while_ago)); + g_assert_no_errno (g_utime (dead_lock, &a_while_ago)); + + /* This has the side-effect of GC'ing instances */ + instances = flatpak_instance_get_all (); + + g_assert_no_errno (stat (alive_dir, &stat_buf)); + g_assert_cmpint (stat (dead_dir, &stat_buf) == 0 ? 0 : errno, ==, ENOENT); + + g_assert_cmpuint (instances->len, ==, 1); + instance = g_ptr_array_index (instances, 0); + g_assert_true (FLATPAK_IS_INSTANCE (instance)); + g_assert_cmpstr (flatpak_instance_get_id (instance), ==, "1"); + + kill (pid, SIGTERM); + g_assert_no_errno (waitpid (pid, &wstatus, 0)); + g_assert_true (WIFSIGNALED (wstatus)); + g_assert_cmpint (WTERMSIG (wstatus), ==, SIGTERM); + g_spawn_close_pid (pid); +} + +int +main (int argc, char *argv[]) +{ + int res; + + isolated_test_dir_global_setup (); + + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/instance/gc", test_gc); + + res = g_test_run (); + + isolated_test_dir_global_teardown (); + + return res; +} |