diff --git a/tests/meson.build b/tests/meson.build index 8b2bc8d6f9905cd7ba3f4998550998c3ed42d82c..64bceaefd0983658ee251789ad4d9aceed75c43f 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -43,6 +43,7 @@ tests = [ compiled_tests = [ 'bwrap-lock', + 'resolve-in-sysroot', 'utils', ] diff --git a/tests/resolve-in-sysroot.c b/tests/resolve-in-sysroot.c new file mode 100644 index 0000000000000000000000000000000000000000..7256764fc52b179c53d23805120401b020017add --- /dev/null +++ b/tests/resolve-in-sysroot.c @@ -0,0 +1,273 @@ +/* + * Copyright © 2019-2020 Collabora Ltd. + * + * SPDX-License-Identifier: MIT + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include <stdlib.h> +#include <unistd.h> + +#include <glib.h> +#include <glib/gstdio.h> + +#include "glib-backports.h" +#include "libglnx/libglnx.h" + +#include "resolve-in-sysroot.h" +#include "test-utils.h" +#include "utils.h" + +typedef struct +{ + TestsOpenFdSet old_fds; +} Fixture; + +static void +setup (Fixture *f, + gconstpointer context) +{ + f->old_fds = tests_check_fd_leaks_enter (); +} + +static void +teardown (Fixture *f, + gconstpointer context) +{ + tests_check_fd_leaks_leave (f->old_fds); +} + +static gboolean +fd_same_as_rel_path_nofollow (int fd, + int dfd, + const gchar *path) +{ + GStatBuf fd_buffer, path_buffer; + + return (fstat (fd, &fd_buffer) == 0 + && fstatat (dfd, path, &path_buffer, AT_SYMLINK_NOFOLLOW) == 0 + && fd_buffer.st_dev == path_buffer.st_dev + && fd_buffer.st_ino == path_buffer.st_ino); +} + +typedef struct +{ + const char *name; + const char *target; +} Symlink; + +typedef enum +{ + RESOLVE_ERROR_DOMAIN_NONE, + RESOLVE_ERROR_DOMAIN_GIO, +} ResolveErrorDomain; + +typedef enum +{ + RESOLVE_CALL_FLAGS_IGNORE_PATH = (1 << 0), + RESOLVE_CALL_FLAGS_NONE = 0 +} ResolveCallFlags; + +typedef struct +{ + struct + { + const char *path; + PvResolveFlags flags; + ResolveCallFlags test_flags; + } call; + struct + { + const char *path; + int code; + } expect; +} ResolveTest; + +static void +test_resolve_in_sysroot (Fixture *f, + gconstpointer context) +{ + static const char * const prepare_dirs[] = + { + "a/b/c/d/e", + "a/b2/c2/d2/e2", + }; + static const Symlink prepare_symlinks[] = + { + { "a/b/symlink_to_c", "c" }, + { "a/b/symlink_to_b2", "../b2" }, + { "a/b/symlink_to_c2", "../../a/b2/c2" }, + { "a/b/abs_symlink_to_run", "/run" }, + { "a/b/long_symlink_to_dev", "../../../../../../../../../../../dev" }, + { "x", "create_me" }, + }; + static const ResolveTest tests[] = + { + { { "a/b/c/d" }, { "a/b/c/d" } }, + { + { "a/b/c/d", PV_RESOLVE_FLAGS_NONE, RESOLVE_CALL_FLAGS_IGNORE_PATH }, + { "a/b/c/d" }, + }, + { { "a/b/c/d", PV_RESOLVE_FLAGS_MKDIR_P }, { "a/b/c/d" } }, + { + { "a/b/c/d", PV_RESOLVE_FLAGS_MKDIR_P, RESOLVE_CALL_FLAGS_IGNORE_PATH }, + { "a/b/c/d" }, + }, + { { "create_me" }, { NULL, G_IO_ERROR_NOT_FOUND } }, + { + { "create_me", PV_RESOLVE_FLAGS_NONE, RESOLVE_CALL_FLAGS_IGNORE_PATH }, + { NULL, G_IO_ERROR_NOT_FOUND } + }, + { { "a/b/c/d", PV_RESOLVE_FLAGS_MKDIR_P }, { "a/b/c/d" } }, + { { "a/b///////.////./././///././c/d" }, { "a/b/c/d" } }, + { { "/a/b///////.////././../b2////././c2/d2" }, { "a/b2/c2/d2" } }, + { { "a/b/c/d/e/f" }, { NULL, G_IO_ERROR_NOT_FOUND } }, + { { "a/b/c/d/e/f", PV_RESOLVE_FLAGS_MKDIR_P }, { "a/b/c/d/e/f" } }, + { { "a/b/c/d/e/f" }, { "a/b/c/d/e/f" } }, + { { "a/b/c/d/e/f", PV_RESOLVE_FLAGS_MKDIR_P }, { "a/b/c/d/e/f" } }, + { { "a3/b3/c3" }, { NULL, G_IO_ERROR_NOT_FOUND } }, + { { "a3/b3/c3", PV_RESOLVE_FLAGS_MKDIR_P }, { "a3/b3/c3" } }, + { { "a/b/symlink_to_c" }, { "a/b/c" } }, + { { "a/b/symlink_to_c/d" }, { "a/b/c/d" } }, + { + { "a/b/symlink_to_c/d", PV_RESOLVE_FLAGS_KEEP_FINAL_SYMLINK }, + { "a/b/c/d" } + }, + { + { "a/b/symlink_to_c/d", PV_RESOLVE_FLAGS_REJECT_SYMLINKS }, + { NULL, G_IO_ERROR_TOO_MANY_LINKS } + }, + { { "a/b/symlink_to_b2" }, { "a/b2" } }, + { { "a/b/symlink_to_c2" }, { "a/b2/c2" } }, + { { "a/b/abs_symlink_to_run" }, { NULL, G_IO_ERROR_NOT_FOUND } }, + { + { "a/b/abs_symlink_to_run", PV_RESOLVE_FLAGS_KEEP_FINAL_SYMLINK }, + { "a/b/abs_symlink_to_run" } + }, + { { "run" }, { NULL, G_IO_ERROR_NOT_FOUND } }, /* Wasn't created yet */ + { { "a/b/abs_symlink_to_run", PV_RESOLVE_FLAGS_MKDIR_P }, { "run" } }, + { { "a/b/abs_symlink_to_run/host" }, { NULL, G_IO_ERROR_NOT_FOUND } }, + { { "a/b/abs_symlink_to_run/host", PV_RESOLVE_FLAGS_MKDIR_P }, { "run/host" } }, + { { "a/b/long_symlink_to_dev" }, { NULL, G_IO_ERROR_NOT_FOUND } }, + { { "a/b/long_symlink_to_dev/shm" }, { NULL, G_IO_ERROR_NOT_FOUND } }, + { { "a/b/long_symlink_to_dev/shm", PV_RESOLVE_FLAGS_MKDIR_P }, { "dev/shm" } }, + { { "a/b/../b2/c2/../c3", PV_RESOLVE_FLAGS_MKDIR_P }, { "a/b2/c3" } }, + { { "x" }, { NULL, G_IO_ERROR_NOT_FOUND } }, + { { "x", PV_RESOLVE_FLAGS_KEEP_FINAL_SYMLINK }, { "x" } }, + /* This is a bit odd: unlike mkdir -p, we create targets for dangling + * symlinks. It's easier to do this than not, and for pressure-vessel's + * use-case it probably even makes more sense than not. */ + { { "x/y" }, { NULL, G_IO_ERROR_NOT_FOUND } }, + { { "x/y", PV_RESOLVE_FLAGS_MKDIR_P }, { "create_me/y" } }, + }; + g_autoptr(GError) error = NULL; + g_auto(GLnxTmpDir) tmpdir = { FALSE }; + gsize i; + + glnx_mkdtemp ("test-XXXXXX", 0700, &tmpdir, &error); + g_assert_no_error (error); + + for (i = 0; i < G_N_ELEMENTS (prepare_dirs); i++) + { + const char *it = prepare_dirs[i]; + + glnx_shutil_mkdir_p_at (tmpdir.fd, it, 0700, NULL, &error); + g_assert_no_error (error); + } + + for (i = 0; i < G_N_ELEMENTS (prepare_symlinks); i++) + { + const Symlink *it = &prepare_symlinks[i]; + + if (symlinkat (it->target, tmpdir.fd, it->name) != 0) + g_error ("symlinkat %s: %s", it->name, g_strerror (errno)); + } + + for (i = 0; i < G_N_ELEMENTS (tests); i++) + { + const ResolveTest *it = &tests[i]; + glnx_autofd int fd = -1; + g_autofree gchar *path = NULL; + gchar **out_path; + g_autoptr(GString) description = g_string_new (""); + TestsOpenFdSet old_fds; + + old_fds = tests_check_fd_leaks_enter (); + + if (it->call.flags & PV_RESOLVE_FLAGS_MKDIR_P) + g_string_append (description, " (creating directories)"); + + if (it->call.flags & PV_RESOLVE_FLAGS_KEEP_FINAL_SYMLINK) + g_string_append (description, " (not following final symlink)"); + + if (it->call.flags & PV_RESOLVE_FLAGS_REJECT_SYMLINKS) + g_string_append (description, " (not following any symlink)"); + + g_test_message ("%" G_GSIZE_FORMAT ": Resolving %s%s", + i, it->call.path, description->str); + + if (it->call.test_flags & RESOLVE_CALL_FLAGS_IGNORE_PATH) + out_path = NULL; + else + out_path = &path; + + fd = pv_resolve_in_sysroot (tmpdir.fd, it->call.path, + it->call.flags, out_path, &error); + + if (it->expect.path != NULL) + { + g_assert_no_error (error); + g_assert_cmpint (fd, >=, 0); + + if (out_path != NULL) + g_assert_cmpstr (*out_path, ==, it->expect.path); + + g_assert_true (fd_same_as_rel_path_nofollow (fd, tmpdir.fd, + it->expect.path)); + } + else + { + g_assert_error (error, G_IO_ERROR, it->expect.code); + g_assert_cmpint (fd, ==, -1); + + if (out_path != NULL) + g_assert_cmpstr (*out_path, ==, NULL); + + g_clear_error (&error); + } + + glnx_close_fd (&fd); + tests_check_fd_leaks_leave (old_fds); + } +} + +int +main (int argc, + char **argv) +{ + pv_avoid_gvfs (); + + g_test_init (&argc, &argv, NULL); + g_test_add ("/resolve-in-sysroot", Fixture, NULL, + setup, test_resolve_in_sysroot, teardown); + + return g_test_run (); +}