From patchwork Thu Aug 16 17:59:41 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: DJ Delorie X-Patchwork-Id: 28938 Received: (qmail 108530 invoked by alias); 16 Aug 2018 17:59:56 -0000 Mailing-List: contact libc-alpha-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Unsubscribe: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: libc-alpha-owner@sourceware.org Delivered-To: mailing list libc-alpha@sourceware.org Received: (qmail 108314 invoked by uid 89); 16 Aug 2018 17:59:54 -0000 Authentication-Results: sourceware.org; auth=none X-Spam-SWARE-Status: No, score=-23.0 required=5.0 tests=AWL, BAYES_00, GIT_PATCH_0, GIT_PATCH_1, GIT_PATCH_2, GIT_PATCH_3, KAM_SHORT, SPF_HELO_PASS, UNSUBSCRIBE_BODY autolearn=ham version=3.3.2 spammy= X-HELO: mx1.redhat.com From: DJ Delorie To: libc-alpha@sourceware.org Cc: fweimer@redhat.com, Joseph Myers , carlos@redhat.com Subject: V8 test-in-container patch Date: Thu, 16 Aug 2018 13:59:41 -0400 Message-ID: MIME-Version: 1.0 This still uses CLONE_NEWPID. I recall spending a lot of time trying to get the "su-less sudo" working correctly, and it's fragile but works as-is. * Makefile (testroot.pristine): New rules to initialize the test-in-container "testroot". * Makerules (all-testsuite): Add tests-container. * Rules (tests): Add tests-container. (binaries-all-tests): Likewise. (tests-container): New, run these tests in the testroot container. * support/Makefile (others): Add *-container, support_paths.c, xmkdirp, and links-dso-program. * support/links-dso-program-c.c: New. * support/links-dso-program.cc: New. * support/test-container.c: New. * support/shell-container.c: New. * support/echo-container.c: New. * support/true-container.c: New. * support/xmkdirp.c: New. * support/xsymlink.c: New. * support/support_paths.c: New. * support/support.h: Add support paths prototypes. * support/xunistd.h: Add xmkdirp () and xsymlink (). * nss/tst-nss-test3.c: Convert to test-in-container. * nss/tst-nss-test3.root/: New. Reviewed-by: Carlos O'Donell diff --git a/Makefile b/Makefile index d3f25a525a..5290434f26 100644 --- a/Makefile +++ b/Makefile @@ -340,6 +340,52 @@ define summarize-tests @! egrep -q -v '^(X?PASS|XFAIL|UNSUPPORTED):' $(objpfx)$1 endef +# The intention here is to do ONE install of our build into the +# testroot.pristine/ directory, then rsync (internal to +# support/test-container) that to testroot.root/ at the start of each +# test. That way we can promise each test a "clean" install, without +# having to do the install for each test. +# +# In addition, we have to copy some files (which we build) into this +# root in addition to what glibc installs. For example, many tests +# require /bin/sh be present, and any shared objects that /bin/sh +# depends on. We also build a "test" program in either C or (if +# available) C++ just so we can copy in any shared objects (which we +# do not build) that GCC-compiled programs depend on. + +$(tests-container) $(addsuffix /tests,$(subdirs)) : \ + $(objpfx)testroot.pristine/install.stamp +$(objpfx)testroot.pristine/install.stamp : + test -d $(objpfx)testroot.pristine || \ + mkdir $(objpfx)testroot.pristine + # We need a working /bin/sh for some of the tests. + test -d $(objpfx)testroot.pristine/bin || \ + mkdir $(objpfx)testroot.pristine/bin + cp $(objpfx)support/shell-container $(objpfx)testroot.pristine/bin/sh + cp $(objpfx)support/echo-container $(objpfx)testroot.pristine/bin/echo + cp $(objpfx)support/true-container $(objpfx)testroot.pristine/bin/true + # Copy these DSOs first so we can overwrite them with our own. + for dso in `$(test-wrapper-env) LD_TRACE_LOADED_OBJECTS=1 \ + $(objpfx)elf/$(rtld-installed-name) \ + $(objpfx)testroot.pristine/bin/sh \ + | grep / | sed 's/^[^/]*//' | sed 's/ .*//'` ;\ + do \ + test -d `dirname $(objpfx)testroot.pristine$$dso` || \ + mkdir -p `dirname $(objpfx)testroot.pristine$$dso` ;\ + $(test-wrapper) cp $$dso $(objpfx)testroot.pristine$$dso ;\ + done + for dso in `$(test-wrapper-env) LD_TRACE_LOADED_OBJECTS=1 \ + $(objpfx)elf/$(rtld-installed-name) \ + $(objpfx)support/links-dso-program \ + | grep / | sed 's/^[^/]*//' | sed 's/ .*//'` ;\ + do \ + test -d `dirname $(objpfx)testroot.pristine$$dso` || \ + mkdir -p `dirname $(objpfx)testroot.pristine$$dso` ;\ + $(test-wrapper) cp $$dso $(objpfx)testroot.pristine$$dso ;\ + done + $(MAKE) install DESTDIR=$(objpfx)testroot.pristine + touch $(objpfx)testroot.pristine/install.stamp + tests-special-notdir = $(patsubst $(objpfx)%, %, $(tests-special)) tests: $(tests-special) $(..)scripts/merge-test-results.sh -s $(objpfx) "" \ diff --git a/Makerules b/Makerules index a10a0b4d70..5d6434c74b 100644 --- a/Makerules +++ b/Makerules @@ -1369,7 +1369,8 @@ xcheck: xtests # The only difference between MODULE_NAME=testsuite and MODULE_NAME=nonlib is # that almost all internal declarations from config.h, libc-symbols.h, and # include/*.h are not available to 'testsuite' code, but are to 'nonlib' code. -all-testsuite := $(strip $(tests) $(xtests) $(test-srcs) $(test-extras)) +all-testsuite := $(strip $(tests) $(xtests) $(test-srcs) $(test-extras) \ + $(tests-container)) ifneq (,$(all-testsuite)) cpp-srcs-left = $(all-testsuite) lib := testsuite diff --git a/Rules b/Rules index 706c8a749d..d4dc2b6f45 100644 --- a/Rules +++ b/Rules @@ -130,12 +130,14 @@ others: $(py-const) ifeq ($(run-built-tests),no) tests: $(addprefix $(objpfx),$(filter-out $(tests-unsupported), \ - $(tests) $(tests-internal)) \ + $(tests) $(tests-internal) \ + $(tests-container)) \ $(test-srcs)) $(tests-special) \ $(tests-printers-programs) xtests: tests $(xtests-special) else tests: $(tests:%=$(objpfx)%.out) $(tests-internal:%=$(objpfx)%.out) \ + $(tests-container:%=$(objpfx)%.out) \ $(tests-special) $(tests-printers-out) xtests: tests $(xtests:%=$(objpfx)%.out) $(xtests-special) endif @@ -149,7 +151,8 @@ tests-expected = $(tests) $(tests-internal) $(tests-printers) endif tests: $(..)scripts/merge-test-results.sh -s $(objpfx) $(subdir) \ - $(sort $(tests-expected) $(tests-special-notdir:.out=)) \ + $(sort $(tests-expected) $(tests-special-notdir:.out=) \ + $(tests-container)) \ > $(objpfx)subdir-tests.sum xtests: $(..)scripts/merge-test-results.sh -s $(objpfx) $(subdir) \ @@ -158,7 +161,8 @@ xtests: ifeq ($(build-programs),yes) binaries-all-notests = $(others) $(sysdep-others) -binaries-all-tests = $(tests) $(tests-internal) $(xtests) $(test-srcs) +binaries-all-tests = $(tests) $(tests-internal) $(xtests) $(test-srcs) \ + $(tests-container) binaries-all = $(binaries-all-notests) $(binaries-all-tests) binaries-static-notests = $(others-static) binaries-static-tests = $(tests-static) $(xtests-static) @@ -248,6 +252,16 @@ $(objpfx)%.out: /dev/null $(objpfx)% # Make it 2nd arg for canned sequence. $(make-test-out) > $@; \ $(evaluate-test) + +# Any tests that require an isolated container (chroot) in which to +# run, should be added to tests-container. +$(tests-container:%=$(objpfx)%.out): $(objpfx)%.out : $(if $(wildcard $(objpfx)%.files),$(objpfx)%.files,/dev/null) $(objpfx)% + $(test-wrapper-env) $(run-program-env) $(run-via-rtld-prefix) \ + $(common-objpfx)support/test-container env $(run-program-env) $($*-ENV) \ + $(host-test-program-cmd) $($*-ARGS) > $@; \ + $(evaluate-test) + + # tests-unsupported lists tests that we will not try to build at all in # this configuration. Note this runs every time because it does not # actually create its target. The dependency on Makefile is meant to diff --git a/nss/Makefile b/nss/Makefile index 5209fc0456..e00a4f768f 100644 --- a/nss/Makefile +++ b/nss/Makefile @@ -55,11 +55,13 @@ tests-internal = tst-field tests = test-netdb test-digits-dots tst-nss-getpwent bug17079 \ tst-nss-test1 \ tst-nss-test2 \ - tst-nss-test3 \ tst-nss-test4 \ tst-nss-test5 xtests = bug-erange +tests-container = \ + tst-nss-test3 + # Tests which need libdl ifeq (yes,$(build-shared)) tests += tst-nss-files-hosts-erange diff --git a/nss/tst-nss-test3.c b/nss/tst-nss-test3.c index d9d708ae7b..4112231778 100644 --- a/nss/tst-nss-test3.c +++ b/nss/tst-nss-test3.c @@ -107,7 +107,11 @@ do_test (void) int i; struct group *g = NULL; - __nss_configure_lookup ("group", "test1"); +/* Previously we used __nss_configure_lookup to isolate the test + from the host environment and to get it to lookup from our new + test1 NSS service module, but now this test is run in a different + root filesystem via the test-container support and we directly + configure the use of the test1 NSS service. */ setgrent (); diff --git a/nss/tst-nss-test3.root/etc/nsswitch.conf b/nss/tst-nss-test3.root/etc/nsswitch.conf new file mode 100644 index 0000000000..5e08fe5eea --- /dev/null +++ b/nss/tst-nss-test3.root/etc/nsswitch.conf @@ -0,0 +1 @@ +group test1 diff --git a/nss/tst-nss-test3.root/tst-nss-test3.script b/nss/tst-nss-test3.root/tst-nss-test3.script new file mode 100644 index 0000000000..a10beb1e6c --- /dev/null +++ b/nss/tst-nss-test3.root/tst-nss-test3.script @@ -0,0 +1,2 @@ +cp $B/nss/libnss_test1.so $L/libnss_test1.so.2 +cp $B/nss/libnss_test2.so $L/libnss_test2.so.2 diff --git a/support/Makefile b/support/Makefile index 652d2cdf69..2c937761ab 100644 --- a/support/Makefile +++ b/support/Makefile @@ -53,6 +53,7 @@ libsupport-routines = \ support_format_netent \ support_isolate_in_subprocess \ support_openpty \ + support_paths \ support_quote_blob \ support_record_failure \ support_run_diff \ @@ -84,6 +85,7 @@ libsupport-routines = \ xmalloc \ xmemstream \ xmkdir \ + xmkdirp \ xmmap \ xmprotect \ xmunmap \ @@ -139,6 +141,7 @@ libsupport-routines = \ xsocket \ xstrdup \ xstrndup \ + xsymlink \ xsysconf \ xunlink \ xwaitpid \ @@ -151,6 +154,42 @@ ifeq ($(build-shared),yes) libsupport-inhibit-o += .o endif +CFLAGS-support_paths.c = \ + -DSRCDIR_PATH=\"`cd .. ; pwd`\" \ + -DOBJDIR_PATH=\"`cd $(objpfx)/..; pwd`\" \ + -DINSTDIR_PATH=\"$(prefix)\" \ + -DLIBDIR_PATH=\"$(libdir)\" + +others: \ + $(objpfx)test-container \ + $(objpfx)links-dso-program \ + $(objpfx)shell-container \ + $(objpfx)echo-container \ + $(objpfx)true-container + +LDLIBS-test-container = $(libsupport) + +others += test-container +others-noinstall += test-container + +others += shell-container echo-container true-container +others-noinstall += shell-container echo-container true-container + +$(objpfx)test-container : $(libsupport) +$(objpfx)shell-container : $(libsupport) +$(objpfx)echo-container : $(libsupport) +$(objpfx)true-container : $(libsupport) + +# This exists only so we can guess which OS DSOs we need to copy into +# the testing container. +ifeq (,$(CXX)) +$(objpfx)links-dso-program : $(objpfx)links-dso-program-c.o + $(LINK.o) -o $@ $^ +else +$(objpfx)links-dso-program : $(objpfx)links-dso-program.o + $(LINK.o) -o $@ $^ -lstdc++ +endif + tests = \ README-testing \ tst-support-namespace \ diff --git a/support/echo-container.c b/support/echo-container.c new file mode 100644 index 0000000000..e4d48df957 --- /dev/null +++ b/support/echo-container.c @@ -0,0 +1,34 @@ +/* Minimal /bin/echo for in-container use. + Copyright (C) 2018 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C 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. + + The GNU C 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 the GNU C Library; if not, see + . */ + +#include + +int +main (int argc, const char **argv) +{ + int i; + + for (i = 1; i < argc; i++) + { + if (i > 1) + putchar (' '); + fputs (argv[i], stdout); + } + putchar ('\n'); + return 0; +} diff --git a/support/links-dso-program-c.c b/support/links-dso-program-c.c new file mode 100644 index 0000000000..d28a28a0d0 --- /dev/null +++ b/support/links-dso-program-c.c @@ -0,0 +1,9 @@ +#include + +int +main (int argc, char **argv) +{ + /* Complexity to keep gcc from optimizing this away. */ + printf ("This is a test %s.\n", argc > 1 ? argv[1] : "null"); + return 0; +} diff --git a/support/links-dso-program.cc b/support/links-dso-program.cc new file mode 100644 index 0000000000..dba6976c06 --- /dev/null +++ b/support/links-dso-program.cc @@ -0,0 +1,11 @@ +#include + +using namespace std; + +int +main (int argc, char **argv) +{ + /* Complexity to keep gcc from optimizing this away. */ + cout << (argc > 1 ? argv[1] : "null"); + return 0; +} diff --git a/support/shell-container.c b/support/shell-container.c new file mode 100644 index 0000000000..483c9b9aca --- /dev/null +++ b/support/shell-container.c @@ -0,0 +1,396 @@ +/* Minimal /bin/sh for in-container use. + Copyright (C) 2018 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C 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. + + The GNU C 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 the GNU C Library; if not, see + . */ + +#define _FILE_OFFSET_BITS 64 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* Design considerations + + General rule: optimize for developer time, not run time. + + Specifically: + + * Don't worry about slow algorithms + * Don't worry about free'ing memory + * Don't implement anything the testsuite doesn't need. + * Line and argument counts are limited, see below. + +*/ + +#define MAX_ARG_COUNT 100 +#define MAX_LINE_LENGTH 1000 + +/* Debugging is enabled via --debug, which must be the first argument. */ +static int debug_mode = 0; +#define dprintf if (debug_mode) fprintf + +/* Emulate the "/bin/true" command. Arguments are ignored. */ +static int +true_func (char **argv) +{ + return 0; +} + +/* Emulate the "/bin/echo" command. Options are ignored, arguments + are printed to stdout. */ +static int +echo_func (char **argv) +{ + int i; + + for (i = 0; argv[i]; i++) + { + if (i > 0) + putchar (' '); + fputs (argv[i], stdout); + } + putchar ('\n'); + + return 0; +} + +/* Emulate the "/bin/cp" command. Options are ignored. Only copies + one source file to one destination file. Directory destinations + are not supported. */ +static int +copy_func (char **argv) +{ + char *sname = argv[0]; + char *dname = argv[1]; + int sfd, dfd; + struct stat st; + + sfd = open (sname, O_RDONLY); + if (sfd < 0) + { + fprintf (stderr, "cp: unable to open %s for reading: %s\n", + sname, strerror (errno)); + return 1; + } + + if (fstat (sfd, &st) < 0) + { + fprintf (stderr, "cp: unable to fstat %s: %s\n", + sname, strerror (errno)); + return 1; + } + + dfd = open (dname, O_WRONLY | O_TRUNC | O_CREAT, 0600); + if (dfd < 0) + { + fprintf (stderr, "cp: unable to open %s for writing: %s\n", + dname, strerror (errno)); + return 1; + } + + if (copy_file_range (sfd, 0, dfd, 0, st.st_size, 0) != st.st_size) + { + fprintf (stderr, "cp: cannot copy file %s to %s: %s\n", + sname, dname, strerror (errno)); + return 1; + } + + close (sfd); + close (dfd); + + chmod (dname, st.st_mode & 0777); + + return 0; + +} + +/* This is a list of all the built-in commands we understand. */ +static struct { + const char *name; + int (*func) (char **argv); +} builtin_funcs[] = { + { "true", true_func }, + { "echo", echo_func }, + { "cp", copy_func }, + { NULL, NULL } +}; + +/* Run one tokenized command. argv[0] is the command. argv is + NULL-terminated. */ +static void +run_command_array (char **argv) +{ + int i, j; + pid_t pid; + int status; + int (*builtin_func) (char **args); + + if (argv[0] == NULL) + return; + + builtin_func = NULL; + + int new_stdin = 0; + int new_stdout = 1; + int new_stderr = 2; + + dprintf (stderr, "run_command_array starting\n"); + for (i = 0; argv[i]; i++) + dprintf (stderr, " argv [%d] `%s'\n", i, argv[i]); + + for (j = i = 0; argv[i]; i++) + { + if (strcmp (argv[i], "<") == 0 && argv[i + 1]) + { + new_stdin = open (argv[i + 1], O_WRONLY|O_CREAT|O_TRUNC, 0777); + ++i; + continue; + } + if (strcmp (argv[i], ">") == 0 && argv[i + 1]) + { + new_stdout = open (argv[i + 1], O_WRONLY|O_CREAT|O_TRUNC, 0777); + ++i; + continue; + } + if (strcmp (argv[i], ">>") == 0 && argv[i + 1]) + { + new_stdout = open (argv[i + 1], O_WRONLY|O_CREAT|O_APPEND, 0777); + ++i; + continue; + } + if (strcmp (argv[i], "2>") == 0 && argv[i + 1]) + { + new_stderr = open (argv[i + 1], O_WRONLY|O_CREAT|O_TRUNC, 0777); + ++i; + continue; + } + argv[j++] = argv[i]; + } + argv[j] = NULL; + + + for (i = 0; builtin_funcs[i].name != NULL; i++) + if (strcmp (argv[0], builtin_funcs[i].name) == 0) + builtin_func = builtin_funcs[i].func; + + dprintf (stderr, "builtin %p argv0 `%s'\n", builtin_func, argv[0]); + + pid = fork (); + if (pid < 0) + { + fprintf (stderr, "sh; fork failed\n"); + exit (1); + } + + if (pid == 0) + { + if (new_stdin != 0) + { + dup2 (new_stdin, 0); + close (new_stdin); + } + if (new_stdout != 1) + { + dup2 (new_stdout, 1); + close (new_stdout); + } + if (new_stderr != 2) + { + dup2 (new_stderr, 2); + close (new_stdout); + } + + if (builtin_func != NULL) + exit (builtin_func (argv + 1)); + + execvp (argv[0], argv); + + fprintf (stderr, "sh: execing %s failed: %s", + argv[0], strerror (errno)); + exit (1); + } + + waitpid (pid, &status, 0); + + dprintf (stderr, "exiting run_command_array\n"); + + if (WIFEXITED (status)) + { + int rv = WEXITSTATUS (status); + if (rv) + exit (rv); + } + else + exit (1); +} + +/* Run one command-as-a-string, by tokenizing it. Limited to + MAX_ARG_COUNT arguments. Simple substitution is done of $1 to $9 + (as whole separate tokens) from iargs[]. Quoted strings work if + the quotes wrap whole tokens; i.e. "foo bar" but not foo" bar". */ +static void +run_command_string (const char *cmdline, const char **iargs) +{ + char *args[MAX_ARG_COUNT+1]; + int ap = 0; + const char *start, *end; + int nargs; + + for (nargs = 0; iargs[nargs] != NULL; ++nargs) + ; + + dprintf (stderr, "run_command_string starting: '%s'\n", cmdline); + + while (ap < MAX_ARG_COUNT) + { + /* If the argument is quoted, this is the quote character, else NUL. */ + int in_quote = 0; + + /* Skip whitespace up to the next token. */ + while (*cmdline && isspace (*cmdline)) + cmdline ++; + if (*cmdline == 0) + break; + + start = cmdline; + /* Check for quoted argument. */ + in_quote = (*cmdline == '\'' || *cmdline == '"') ? *cmdline : 0; + + /* Skip to end of token; either by whitespace or matching quote. */ + dprintf (stderr, "in_quote %d\n", in_quote); + while (*cmdline + && (!isspace (*cmdline) || in_quote)) + { + if (*cmdline == in_quote + && cmdline != start) + in_quote = 0; + dprintf (stderr, "[%c]%d ", *cmdline, in_quote); + cmdline ++; + } + dprintf (stderr, "\n"); + + /* Allocate space for this token and store it in args[]. */ + end = cmdline; + dprintf (stderr, "start<%s> end<%s>\n", start, end); + args[ap] = (char *) xmalloc (end - start + 1); + memcpy (args[ap], start, end - start); + args[ap][end - start] = 0; + + /* Strip off quotes, if found. */ + dprintf (stderr, "args[%d] = <%s>\n", ap, args[ap]); + if (args[ap][0] == '\'' + && args[ap][strlen (args[ap])-1] == '\'') + { + args[ap][strlen (args[ap])-1] = 0; + args[ap] ++; + } + + else if (args[ap][0] == '"' + && args[ap][strlen (args[ap])-1] == '"') + { + args[ap][strlen (args[ap])-1] = 0; + args[ap] ++; + } + + /* Replace positional parameters like $4. */ + else if (args[ap][0] == '$' + && isdigit (args[ap][1]) + && args[ap][2] == 0) + { + int a = args[ap][1] - '1'; + if (0 <= a && a < nargs) + args[ap] = strdup (iargs[a]); + } + + ap ++; + + if (*cmdline == 0) + break; + } + + /* Lastly, NULL terminate the array and run it. */ + args[ap] = NULL; + run_command_array (args); +} + +/* Run a script by reading lines and passing them to the above + function. */ +static void +run_script (const char *filename, const char **args) +{ + char line[MAX_LINE_LENGTH + 1]; + dprintf (stderr, "run_script starting: '%s'\n", filename); + FILE *f = fopen (filename, "r"); + if (f == NULL) + { + fprintf (stderr, "sh: %s: %s\n", filename, strerror (errno)); + exit (1); + } + while (fgets (line, sizeof (line), f) != NULL) + { + if (line[0] == '#') + { + dprintf (stderr, "comment: %s\n", line); + continue; + } + run_command_string (line, args); + } + fclose (f); +} + +int +main (int argc, const char **argv) +{ + int i; + + if (strcmp (argv[1], "--debug") == 0) + { + debug_mode = 1; + --argc; + ++argv; + } + + dprintf (stderr, "container-sh starting:\n"); + for (i = 0; i < argc; i++) + dprintf (stderr, " argv[%d] is `%s'\n", i, argv[i]); + + if (strcmp (argv[1], "-c") == 0) + run_command_string (argv[2], argv+3); + else + run_script (argv[1], argv+2); + + dprintf (stderr, "normal exit 0\n"); + return 0; +} diff --git a/support/support.h b/support/support.h index b61fe0735c..1403510f11 100644 --- a/support/support.h +++ b/support/support.h @@ -25,6 +25,8 @@ #include #include +/* For mode_t. */ +#include __BEGIN_DECLS @@ -76,6 +78,16 @@ char *xasprintf (const char *format, ...) char *xstrdup (const char *); char *xstrndup (const char *, size_t); +/* These point to the TOP of the source/build tree, not your (or + support's) subdirectory. */ +extern const char support_srcdir_root[]; +extern const char support_objdir_root[]; + +/* Corresponds to the --prefix= passed to configure. */ +extern const char support_install_prefix[]; +/* Corresponds to the install's lib/ or lib64/ directory. */ +extern const char support_libdir_prefix[]; + __END_DECLS #endif /* SUPPORT_H */ diff --git a/support/support_paths.c b/support/support_paths.c new file mode 100644 index 0000000000..a1c22315bd --- /dev/null +++ b/support/support_paths.c @@ -0,0 +1,51 @@ +/* Various paths that might be needed. + Copyright (C) 2018 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C 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. + + The GNU C 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 the GNU C Library; if not, see + . */ + +#include +#include + +/* The idea here is to make various makefile-level paths available to + support programs, as canonicalized absolute paths. */ + +/* These point to the TOP of the source/build tree, not your (or + support's) subdirectory. */ +#ifdef SRCDIR_PATH +const char support_srcdir_root[] = SRCDIR_PATH; +#else +# error please -DSRCDIR_PATH=something in the Makefile +#endif + +#ifdef OBJDIR_PATH +const char support_objdir_root[] = OBJDIR_PATH; +#else +# error please -DOBJDIR_PATH=something in the Makefile +#endif + +#ifdef INSTDIR_PATH +/* Corresponds to the --prefix= passed to configure. */ +const char support_install_prefix[] = INSTDIR_PATH; +#else +# error please -DINSTDIR_PATH=something in the Makefile +#endif + +#ifdef LIBDIR_PATH +/* Corresponds to the install's lib/ or lib64/ directory. */ +const char support_libdir_prefix[] = LIBDIR_PATH; +#else +# error please -DLIBDIR_PATH=something in the Makefile +#endif diff --git a/support/test-container.c b/support/test-container.c new file mode 100644 index 0000000000..237f50c5b7 --- /dev/null +++ b/support/test-container.c @@ -0,0 +1,982 @@ +/* Run a test case in an isolated namespace. + Copyright (C) 2018 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C 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. + + The GNU C 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 the GNU C Library; if not, see + . */ + +#define _FILE_OFFSET_BITS 64 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "check.h" +#include "test-driver.h" + +int verbose = 0; + +/* Running a test in a container is tricky. There are two main + categories of things to do: + + 1. "Once" actions, like setting up the container and doing an + install into it. + + 2. "Per-test" actions, like copying in support files and + configuring the container. + + + "Once" actions: + + * mkdir $buildroot/testroot.pristine/ + * install into it + * rsync to $buildroot/testroot.root/ + + "Per-test" actions: + * maybe rsync to $buildroot/testroot.root/ + * copy support files and test binary + * chroot/unshare + * set up any mounts (like /proc) + + Magic files: + + For test $srcdir/foo/mytest.c we look for $srcdir/foo/mytest.root + and, if found... + + * mytest.root/ is rsync'd into container + * mytest.root/preclean.req causes fresh rsync (with delete) before + test if present + * mytest.root/mytset.script has a list of "commands" to run: + syntax: + # comment + mv FILE FILE + cp FILE FILE + rm FILE + FILE must start with $B/, $S/, $I/, $L/, or / + (expands to build dir, source dir, install dir, library dir + (in container), or container's root) + * mytest.root/postclean.req causes fresh rsync (with delete) after + test if present + + Note that $srcdir/foo/mytest.script may be used instead of a + $srcdir/foo/mytest.root/mytest.script in the sysroot template, if + there is no other reason for a sysroot. + + Design goals: + + * independent of other packages which may not be installed (like + rsync or Docker, or even "cp") + + * Simple, easy to review code (i.e. prefer simple naive code over + complex efficient code) + + * The current implementation ist parallel-make-safe, but only in + that it uses a lock to prevent parallel access to the testroot. */ + + +/* Utility Functions */ + +/* Like xunlink, but it's OK if the file already doesn't exist. */ +void +maybe_xunlink (const char *path) +{ + int rv = unlink (path); + if (rv < 0 && errno != ENOENT) + FAIL_EXIT1 ("unlink (\"%s\"): %m", path); +} + +/* Like xmkdir, but it's OK if the directory already exists. */ +void +maybe_xmkdir (const char *path, mode_t mode) +{ + struct stat st; + + if (stat (path, &st) == 0 + && S_ISDIR (st.st_mode)) + return; + xmkdir (path, mode); +} + +/* Temporarily concatenate multiple strings into one. Allows up to 10 + temporary results; use strdup () if you need them to be + permanent. */ +static char * +concat (const char *str, ...) +{ + /* Assume initialized to NULL/zero. */ + static char *bufs[10]; + static size_t buflens[10]; + static int bufn = 0; + int n; + size_t len; + va_list ap, ap2; + char *cp; + char *next; + + va_start (ap, str); + va_copy (ap2, ap); + + n = bufn; + bufn = (bufn + 1) % 10; + len = strlen (str); + + while ((next = va_arg (ap, char *)) != NULL) + len = len + strlen (next); + + va_end (ap); + + if (bufs[n] == NULL) + { + bufs[n] = xmalloc (len + 1); /* NUL */ + buflens[n] = len + 1; + } + else if (buflens[n] < len + 1) + { + bufs[n] = xrealloc (bufs[n], len + 1); /* NUL */ + buflens[n] = len + 1; + } + + strcpy (bufs[n], str); + cp = strchr (bufs[n], '\0'); + while ((next = va_arg (ap2, char *)) != NULL) + { + strcpy (cp, next); + cp = strchr (cp, '\0'); + } + *cp = 0; + va_end (ap2); + + return bufs[n]; +} + +/* Try to mount SRC onto DEST. */ +static void +trymount (const char *src, const char *dest) +{ + if (mount (src, dest, "", MS_BIND, NULL) < 0) + FAIL_EXIT1 ("can't mount %s onto %s\n", src, dest); +} + +/* Special case of above for devices like /dev/zero where we have to + mount a device over a device, not a directory over a directory. */ +static void +devmount (const char *new_root_path, const char *which) +{ + int fd; + fd = open (concat (new_root_path, "/dev/", which, NULL), + O_CREAT | O_TRUNC | O_RDWR, 0777); + xclose (fd); + + trymount (concat ("/dev/", which, NULL), + concat (new_root_path, "/dev/", which, NULL)); +} + +/* Returns true if the string "looks like" an environement variable + being set. */ +static int +is_env_setting (const char *a) +{ + int count_name = 0; + + while (*a) + { + if (isalnum (*a) || *a == '_') + ++count_name; + else if (*a == '=' && count_name > 0) + return 1; + else + return 0; + ++a; + } + return 0; +} + +/* Break the_line into words and store in the_words. Max nwords, + returns actual count. */ +static int +tokenize (char *the_line, char **the_words, int nwords) +{ + int rv = 0; + + while (nwords > 0) + { + /* Skip leading whitespace, if any. */ + while (*the_line && isspace (*the_line)) + ++the_line; + + /* End of line? */ + if (*the_line == 0) + return rv; + + /* THE_LINE points to a non-whitespace character, so we have a + word. */ + *the_words = the_line; + ++the_words; + nwords--; + ++rv; + + /* Skip leading whitespace, if any. */ + while (*the_line && ! isspace (*the_line)) + ++the_line; + + /* We now point at the trailing NUL *or* some whitespace. */ + if (*the_line == 0) + return rv; + + /* It was whitespace, skip and keep tokenizing. */ + *the_line++ = 0; + } + + /* We get here if we filled the words buffer. */ + return rv; +} + + +/* Mini-RSYNC implementation. Optimize later. */ + +/* A few routines for an "rsync buffer" which stores the paths we're + working on. We continuously grow and shrink the paths in each + buffer so there's lot of re-use. */ + +/* We rely on "initialized to zero" to set these up. */ +typedef struct +{ + char *buf; + size_t len; + size_t size; +} path_buf; + +static path_buf spath, dpath; + +static void +r_setup (char *path, path_buf * pb) +{ + size_t len = strlen (path); + if (pb->buf == NULL || pb->size < len + 1) + { + /* Round up. This is an arbitrary number, just to keep from + reallocing too often. */ + size_t sz = ALIGN_UP (len + 1, 512); + if (pb->buf == NULL) + pb->buf = (char *) xmalloc (sz); + else + pb->buf = (char *) xrealloc (pb->buf, sz); + if (pb->buf == NULL) + FAIL_EXIT1 ("Out of memory while rsyncing\n"); + + pb->size = sz; + } + strcpy (pb->buf, path); + pb->len = len; +} + +static void +r_append (const char *path, path_buf * pb) +{ + size_t len = strlen (path) + pb->len; + if (pb->size < len + 1) + { + /* Round up */ + size_t sz = ALIGN_UP (len + 1, 512); + pb->buf = (char *) xrealloc (pb->buf, sz); + if (pb->buf == NULL) + FAIL_EXIT1 ("Out of memory while rsyncing\n"); + + pb->size = sz; + } + strcpy (pb->buf + pb->len, path); + pb->len = len; +} + +static int +file_exists (char *path) +{ + struct stat st; + if (lstat (path, &st) == 0) + return 1; + return 0; +} + +static void +recursive_remove (char *path) +{ + pid_t child; + int status; + + child = fork (); + + switch (child) { + case -1: + FAIL_EXIT1 ("Unable to fork"); + case 0: + /* Child. */ + execlp ("rm", "rm", "-rf", path, NULL); + default: + /* Parent. */ + waitpid (child, &status, 0); + /* "rm" would have already printed a suitable error message. */ + if (! WIFEXITED (status) + || WEXITSTATUS (status) != 0) + exit (1); + + break; + } +} + +/* Used for both rsync and the mytest.script "cp" command. */ +static void +copy_one_file (const char *sname, const char *dname) +{ + int sfd, dfd; + struct stat st; + struct utimbuf times; + + sfd = open (sname, O_RDONLY); + if (sfd < 0) + FAIL_EXIT1 ("unable to open %s for reading\n", sname); + + if (fstat (sfd, &st) < 0) + FAIL_EXIT1 ("unable to fstat %s\n", sname); + + dfd = open (dname, O_WRONLY | O_TRUNC | O_CREAT, 0600); + if (dfd < 0) + FAIL_EXIT1 ("unable to open %s for writing\n", dname); + + if (copy_file_range (sfd, 0, dfd, 0, st.st_size, 0) != st.st_size) + FAIL_EXIT1 ("cannot copy file %s to %s\n", sname, dname); + + xclose (sfd); + xclose (dfd); + + if (chmod (dname, st.st_mode & 0777) < 0) + FAIL_EXIT1 ("chmod %s: %s\n", dname, strerror (errno)); + + times.actime = st.st_atime; + times.modtime = st.st_mtime; + if (utime (dname, ×) < 0) + FAIL_EXIT1 ("utime %s: %s\n", dname, strerror (errno)); +} + +/* We don't check *everything* about the two files to see if a copy is + needed, just the minimum to make sure we get the latest copy. */ +static int +need_sync (char *ap, char *bp, struct stat *a, struct stat *b) +{ + if ((a->st_mode & S_IFMT) != (b->st_mode & S_IFMT)) + return 1; + + if (S_ISLNK (a->st_mode)) + { + int rv; + char *al, *bl; + + if (a->st_size != b->st_size) + return 1; + + al = xreadlink (ap); + bl = xreadlink (bp); + rv = strcmp (al, bl); + free (al); + free (bl); + if (rv == 0) + return 0; /* links are same */ + return 1; /* links differ */ + } + + if (verbose) + { + if (a->st_size != b->st_size) + printf ("SIZE\n"); + if ((a->st_mode & 0777) != (b->st_mode & 0777)) + printf ("MODE\n"); + if (a->st_mtime != b->st_mtime) + printf ("TIME\n"); + } + + if (a->st_size == b->st_size + && ((a->st_mode & 0777) == (b->st_mode & 0777)) + && a->st_mtime == b->st_mtime) + return 0; + + return 1; +} + +static void +rsync_1 (path_buf * src, path_buf * dest, int and_delete) +{ + DIR *dir; + struct dirent *de; + struct stat s, d; + + r_append ("/", src); + r_append ("/", dest); + + if (verbose) + printf ("sync %s to %s %s\n", src->buf, dest->buf, + and_delete ? "and delete" : ""); + + size_t staillen = src->len; + + size_t dtaillen = dest->len; + + dir = opendir (src->buf); + + while ((de = readdir (dir)) != NULL) + { + if (strcmp (de->d_name, ".") == 0 + || strcmp (de->d_name, "..") == 0) + continue; + + src->len = staillen; + r_append (de->d_name, src); + dest->len = dtaillen; + r_append (de->d_name, dest); + + s.st_mode = ~0; + d.st_mode = ~0; + + if (lstat (src->buf, &s) != 0) + FAIL_EXIT1 ("%s obtained by readdir, but stat failed.\n", src->buf); + + /* It's OK if this one fails, since we know the file might be + missing. */ + lstat (dest->buf, &d); + + if (! need_sync (src->buf, dest->buf, &s, &d)) + { + if (S_ISDIR (s.st_mode)) + rsync_1 (src, dest, and_delete); + continue; + } + + if (d.st_mode != ~0) + switch (d.st_mode & S_IFMT) + { + case S_IFDIR: + if (!S_ISDIR (s.st_mode)) + { + if (verbose) + printf ("-D %s\n", dest->buf); + recursive_remove (dest->buf); + } + break; + + default: + if (verbose) + printf ("-F %s\n", dest->buf); + maybe_xunlink (dest->buf); + break; + } + + switch (s.st_mode & S_IFMT) + { + case S_IFREG: + if (verbose) + printf ("+F %s\n", dest->buf); + copy_one_file (src->buf, dest->buf); + break; + + case S_IFDIR: + if (verbose) + printf ("+D %s\n", dest->buf); + maybe_xmkdir (dest->buf, (s.st_mode & 0777) | 0700); + rsync_1 (src, dest, and_delete); + break; + + case S_IFLNK: + { + char *lp; + if (verbose) + printf ("+L %s\n", dest->buf); + lp = xreadlink (src->buf); + xsymlink (lp, dest->buf); + free (lp); + break; + } + + default: + break; + } + } + + closedir (dir); + src->len = staillen; + src->buf[staillen] = 0; + dest->len = dtaillen; + dest->buf[dtaillen] = 0; + + if (!and_delete) + return; + + /* The rest of this function removes any files/directories in DEST + that do not exist in SRC. This is triggered as part of a + preclean or postsclean step. */ + + dir = opendir (dest->buf); + + while ((de = readdir (dir)) != NULL) + { + if (strcmp (de->d_name, ".") == 0 + || strcmp (de->d_name, "..") == 0) + continue; + + src->len = staillen; + r_append (de->d_name, src); + dest->len = dtaillen; + r_append (de->d_name, dest); + + s.st_mode = ~0; + d.st_mode = ~0; + + lstat (src->buf, &s); + + if (lstat (dest->buf, &d) != 0) + FAIL_EXIT1 ("%s obtained by readdir, but stat failed.\n", dest->buf); + + if (s.st_mode == ~0) + { + /* dest exists and src doesn't, clean it. */ + switch (d.st_mode & S_IFMT) + { + case S_IFDIR: + if (!S_ISDIR (s.st_mode)) + { + if (verbose) + printf ("-D %s\n", dest->buf); + recursive_remove (dest->buf); + } + break; + + default: + if (verbose) + printf ("-F %s\n", dest->buf); + maybe_xunlink (dest->buf); + break; + } + } + } + + closedir (dir); +} + +static void +rsync (char *src, char *dest, int and_delete) +{ + r_setup (src, &spath); + r_setup (dest, &dpath); + + rsync_1 (&spath, &dpath, and_delete); +} + + +int +main (int argc, char **argv) +{ + pid_t child; + char *pristine_root_path; + char *new_root_path; + char *new_cwd_path; + char *new_objdir_path; + char *new_srcdir_path; + char **new_child_proc; + char *command_root; + char *command_base; + char *command_basename; + char *so_base; + int do_postclean = 0; + + uid_t original_uid; + gid_t original_gid; + int UMAP; + int GMAP; + /* Used for "%lld %lld 1" so need not be large. */ + char tmp[100]; + struct stat st; + int lock_fd; + + setbuf (stdout, NULL); + + /* The command line we're expecting looks like this: + env ld.so test-binary + + We need to peel off any "env" or "ld.so" portion of the command + line, and keep track of which env vars we should preserve and + which we drop. */ + + if (argc < 2) + { + fprintf (stderr, "Usage: containerize \n"); + exit (1); + } + + if (strcmp (argv[1], "-v") == 0) + { + verbose = 1; + ++argv; + --argc; + } + + if (strcmp (argv[1], "env") == 0) + { + ++argv; + --argc; + while (is_env_setting (argv[1])) + { + /* List variables we do NOT want to propogate. */ +#if 0 + /* until we discover why locale/iconv tests don't + work against an installed tree... */ + if (memcmp (argv[1], "GCONV_PATH=", 11) + && memcmp (argv[1], "LOCPATH=", 8)) +#endif + { + /* Need to keep these. Note that putenv stores a + pointer to our argv. */ + putenv (argv[1]); + } + ++argv; + --argc; + } + } + + if (strncmp (argv[1], concat (support_objdir_root, "/elf/ld-linux-", NULL), + strlen (support_objdir_root) + 14) == 0) + { + ++argv; + --argc; + while (argv[1][0] == '-') + { + if (strcmp (argv[1], "--library-path") == 0) + { + ++argv; + --argc; + } + ++argv; + --argc; + } + } + + pristine_root_path = strdup (concat (support_objdir_root, + "/testroot.pristine", NULL)); + new_root_path = strdup (concat (support_objdir_root, + "/testroot.root", NULL)); + new_cwd_path = get_current_dir_name (); + new_child_proc = argv + 1; + + lock_fd = open (concat (pristine_root_path, "/lock.fd", NULL), + O_CREAT | O_TRUNC | O_RDWR, 0666); + if (lock_fd < 0) + FAIL_EXIT1 ("Cannot create testroot lock.\n"); + + while (flock (lock_fd, LOCK_EX) != 0) + { + if (errno != EINTR) + FAIL_EXIT1 ("Cannot lock testroot.\n"); + } + + xmkdirp (new_root_path, 0755); + + /* We look for extra setup info in a subdir in the same spot as the + test, with the same name but a ".root" extension. This is that + directory. We try to look in the source tree if the path we're + given refers to the build tree, but we rely on the path to be + absolute. This is what the glibc makefiles do. */ + command_root = concat (argv[1], ".root", NULL); + if (strncmp (command_root, support_objdir_root, + strlen (support_objdir_root)) == 0 + && command_root[strlen (support_objdir_root)] == '/') + command_root = concat (support_srcdir_root, + argv[1] + strlen (support_objdir_root), + ".root", NULL); + command_root = strdup (command_root); + + /* This cuts off the ".root" we appended above. */ + command_base = strdup (command_root); + command_base[strlen (command_base) - 5] = 0; + + /* This is the basename of the test we're running. */ + command_basename = strrchr (command_base, '/'); + if (command_basename == NULL) + command_basename = command_base; + else + ++command_basename; + + /* Shared object base directory. */ + so_base = strdup (argv[1]); + if (strrchr (so_base, '/') != NULL) + strrchr (so_base, '/')[1] = 0; + + if (file_exists (concat (command_root, "/postclean.req", NULL))) + do_postclean = 1; + + rsync (pristine_root_path, new_root_path, + file_exists (concat (command_root, "/preclean.req", NULL))); + + if (stat (command_root, &st) >= 0 + && S_ISDIR (st.st_mode)) + rsync (command_root, new_root_path, 0); + + new_objdir_path = strdup (concat (new_root_path, + support_objdir_root, NULL)); + new_srcdir_path = strdup (concat (new_root_path, + support_srcdir_root, NULL)); + + /* new_cwd_path starts with '/' so no "/" needed between the two. */ + xmkdirp (concat (new_root_path, new_cwd_path, NULL), 0755); + xmkdirp (new_srcdir_path, 0755); + xmkdirp (new_objdir_path, 0755); + + original_uid = getuid (); + original_gid = getgid (); + + /* Handle the cp/mv/rm "script" here. */ + { + char *the_line = NULL; + size_t line_len = 0; + char *fname = concat (command_root, "/", + command_basename, ".script", NULL); + char *the_words[3]; + FILE *f = fopen (fname, "r"); + + if (verbose && f) + fprintf (stderr, "running %s\n", fname); + + if (f == NULL) + { + /* Try foo.script instead of foo.root/foo.script, as a shortcut. */ + fname = concat (command_base, ".script", NULL); + f = fopen (fname, "r"); + if (verbose && f) + fprintf (stderr, "running %s\n", fname); + } + + /* Note that we do NOT look for a Makefile-generated foo.script in + the build directory. If that is ever needed, this is the place + to add it. */ + + /* This is where we "interpret" the mini-script which is .script. */ + if (f != NULL) + { + while (getline (&the_line, &line_len, f) > 0) + { + int nt = tokenize (the_line, the_words, 3); + int i; + + for (i = 1; i < nt; ++i) + { + if (memcmp (the_words[i], "$B/", 3) == 0) + the_words[i] = concat (support_objdir_root, + the_words[i] + 2, NULL); + else if (memcmp (the_words[i], "$S/", 3) == 0) + the_words[i] = concat (support_srcdir_root, + the_words[i] + 2, NULL); + else if (memcmp (the_words[i], "$I/", 3) == 0) + the_words[i] = concat (new_root_path, + support_install_prefix, + the_words[i] + 2, NULL); + else if (memcmp (the_words[i], "$L/", 3) == 0) + the_words[i] = concat (new_root_path, + support_libdir_prefix, + the_words[i] + 2, NULL); + else if (the_words[i][0] == '/') + the_words[i] = concat (new_root_path, + the_words[i], NULL); + } + + if (nt == 3 && the_words[2][strlen (the_words[2]) - 1] == '/') + { + char *r = strrchr (the_words[1], '/'); + if (r) + the_words[2] = concat (the_words[2], r + 1, NULL); + else + the_words[2] = concat (the_words[2], the_words[1], NULL); + } + + if (nt == 2 && strcmp (the_words[0], "so") == 0) + { + the_words[2] = concat (new_root_path, support_libdir_prefix, + "/", the_words[1], NULL); + the_words[1] = concat (so_base, the_words[1], NULL); + copy_one_file (the_words[1], the_words[2]); + } + else if (nt == 3 && strcmp (the_words[0], "cp") == 0) + { + copy_one_file (the_words[1], the_words[2]); + } + else if (nt == 3 && strcmp (the_words[0], "mv") == 0) + { + if (rename (the_words[1], the_words[2]) < 0) + FAIL_EXIT1 ("rename %s -> %s: %s", the_words[1], + the_words[2], strerror (errno)); + } + else if (nt == 3 && strcmp (the_words[0], "chmod") == 0) + { + long int m; + m = strtol (the_words[1], NULL, 0); + if (chmod (the_words[2], m) < 0) + FAIL_EXIT1 ("chmod %s: %s\n", + the_words[2], strerror (errno)); + + } + else if (nt == 2 && strcmp (the_words[0], "rm") == 0) + { + maybe_xunlink (the_words[1]); + } + else if (nt > 0 && the_words[0][0] != '#') + { + printf ("\033[31minvalid [%s]\033[0m\n", the_words[0]); + } + } + fclose (f); + } + } + +#ifdef CLONE_NEWNS + /* The unshare here gives us our own spaces and capabilities. */ + if (unshare (CLONE_NEWUSER | CLONE_NEWPID | CLONE_NEWNS) < 0) + { + /* Older kernels may not support all the options. */ + if (errno == EINVAL) + FAIL_UNSUPPORTED ("unable to unshare user/fs: %s", strerror (errno)); + else + FAIL_EXIT1 ("unable to unshare user/fs: %s", strerror (errno)); + } +#else + /* Some targets may not support unshare at all. */ + FAIL_UNSUPPORTED ("unshare support missing"); +#endif + + /* Some systems, by default, all mounts leak out of the namespace. */ + if (mount ("none", "/", NULL, MS_REC | MS_PRIVATE, NULL) != 0) + FAIL_EXIT1 ("could not create a private mount namespace\n"); + + trymount (support_srcdir_root, new_srcdir_path); + trymount (support_objdir_root, new_objdir_path); + + xmkdirp (concat (new_root_path, "/dev", NULL), 0755); + devmount (new_root_path, "null"); + devmount (new_root_path, "zero"); + devmount (new_root_path, "urandom"); + + /* We're done with the "old" root, switch to the new one. */ + if (chroot (new_root_path) < 0) + FAIL_EXIT1 ("Can't chroot to %s - ", new_root_path); + + if (chdir (new_cwd_path) < 0) + FAIL_EXIT1 ("Can't cd to new %s - ", new_cwd_path); + + /* To complete the containerization, we need to fork () at least + once. We can't exec, nor can we somehow link the new child to + our parent. So we run the child and propogate it's exit status + up. */ + child = fork (); + if (child < 0) + FAIL_EXIT1 ("Unable to fork"); + else if (child > 0) + { + /* Parent. */ + int status; + waitpid (child, &status, 0); + + /* There's a bit of magic here, since the buildroot is mounted + in our space, the paths are still valid, and since the mounts + aren't recursive, it sees *only* the built root, not anything + we would normally se if we rsync'd to "/" like mounted /dev + files. */ + if (do_postclean) + rsync (pristine_root_path, new_root_path, 1); + + if (WIFEXITED (status)) + exit (WEXITSTATUS (status)); + + if (WIFSIGNALED (status)) + { + printf ("%%SIGNALLED%%\n"); + exit (77); + } + + printf ("%%EXITERROR%%\n"); + exit (78); + } + + /* The rest is the child process, which is now PID 1 and "in" the + new root. */ + + maybe_xmkdir ("/tmp", 0755); + + /* Now that we're pid 1 (effectively "root") we can mount /proc */ + maybe_xmkdir ("/proc", 0777); + if (mount ("proc", "/proc", "proc", 0, NULL) < 0) + FAIL_EXIT1 ("Unable to mount /proc: "); + + /* We map our original UID to the same UID in the container so we + can own our own files normally. */ + UMAP = open ("/proc/self/uid_map", O_WRONLY); + if (UMAP < 0) + FAIL_EXIT1 ("can't write to /proc/self/uid_map\n"); + + sprintf (tmp, "%lld %lld 1\n", + (long long) original_uid, (long long) original_uid); + write (UMAP, tmp, strlen (tmp)); + xclose (UMAP); + + /* We must disable setgroups () before we can map our groups, else we + get EPERM. */ + GMAP = open ("/proc/self/setgroups", O_WRONLY); + if (GMAP >= 0) + { + /* We support kernels old enough to not have this. */ + write (GMAP, "deny\n", 5); + xclose (GMAP); + } + + /* We map our original GID to the same GID in the container so we + can own our own files normally. */ + GMAP = open ("/proc/self/gid_map", O_WRONLY); + if (GMAP < 0) + FAIL_EXIT1 ("can't write to /proc/self/gid_map\n"); + + sprintf (tmp, "%lld %lld 1\n", + (long long) original_gid, (long long) original_gid); + write (GMAP, tmp, strlen (tmp)); + xclose (GMAP); + + /* Now run the child. */ + execvp (new_child_proc[0], new_child_proc); + + /* Or don't run the child? */ + FAIL_EXIT1 ("Unable to exec %s\n", new_child_proc[0]); + + /* Because gcc won't know error () never returns... */ + exit (EXIT_UNSUPPORTED); +} diff --git a/support/true-container.c b/support/true-container.c new file mode 100644 index 0000000000..57dc57e252 --- /dev/null +++ b/support/true-container.c @@ -0,0 +1,26 @@ +/* Minimal /bin/true for in-container use. + Copyright (C) 2018 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C 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. + + The GNU C 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 the GNU C Library; if not, see + . */ + +/* Implements the in-container /bin/true, which always returns true + (0). */ + +int +main (void) +{ + return 0; +} diff --git a/support/xmkdirp.c b/support/xmkdirp.c new file mode 100644 index 0000000000..fada0452ea --- /dev/null +++ b/support/xmkdirp.c @@ -0,0 +1,66 @@ +/* Error-checking replacement for "mkdir -p". + Copyright (C) 2018 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C 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. + + The GNU C 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 the GNU C Library; if not, see + . */ + +#include +#include +#include + +#include +#include +#include + +/* Equivalent of "mkdir -p". Any failures cause FAIL_EXIT1 so no + return code is needed. */ + +void +xmkdirp (const char *path, mode_t mode) +{ + struct stat s; + const char *slash_p; + int rv; + + if (path[0] == 0) + return; + + if (stat (path, &s) == 0) + { + if (S_ISDIR (s.st_mode)) + return; + errno = EEXIST; + FAIL_EXIT1 ("mkdir_p (\"%s\", 0%o): %m", path, mode); + } + + slash_p = strrchr (path, '/'); + if (slash_p != NULL) + { + while (slash_p > path && slash_p[-1] == '/') + --slash_p; + if (slash_p > path) + { + char *parent = xstrndup (path, slash_p - path); + xmkdirp (parent, mode); + free (parent); + } + } + + rv = mkdir (path, mode); + if (rv != 0) + FAIL_EXIT1 ("mkdir_p (\"%s\", 0%o): %m", path, mode); + + return; +} diff --git a/support/xsymlink.c b/support/xsymlink.c new file mode 100644 index 0000000000..0f3edf640a --- /dev/null +++ b/support/xsymlink.c @@ -0,0 +1,29 @@ +/* Error-checking replacement for "symlink". + Copyright (C) 2018 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C 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. + + The GNU C 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 the GNU C Library; if not, see + . */ + +#include +#include + +#include + +void +xsymlink (const char *target, const char *linkpath) +{ + if (symlink (target, linkpath) < 0) + FAIL_EXIT1 ("symlink (\"%s\", \"%s\")", target, linkpath); +} diff --git a/support/xunistd.h b/support/xunistd.h index 5fe5dae818..cdd4e8d92d 100644 --- a/support/xunistd.h +++ b/support/xunistd.h @@ -43,6 +43,10 @@ void xunlink (const char *path); long xsysconf (int name); long long xlseek (int fd, long long offset, int whence); void xftruncate (int fd, long long length); +void xsymlink (const char *target, const char *linkpath); + +/* Equivalent of "mkdir -p". */ +void xmkdirp (const char *, mode_t); /* Read the link at PATH. The caller should free the returned string with free. */