Add test-in-container infrastructure.

* Makefile (testroot.pristine): New rules to initialize the
test-in-container "testroot".
* Makerules (all-testsuite): Add tests-container.
* Rules (tests-expected): 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.
This commit is contained in:
DJ Delorie 2018-07-06 01:10:41 -04:00
parent b35d3509ca
commit 561b0bec44
20 changed files with 1779 additions and 6 deletions

View File

@ -1,3 +1,28 @@
2018-08-22 DJ Delorie <dj@redhat.com>
* Makefile (testroot.pristine): New rules to initialize the
test-in-container "testroot".
* Makerules (all-testsuite): Add tests-container.
* Rules (tests-expected): 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.
2018-08-22 Paul Eggert <eggert@cs.ucla.edu>
regex: port Gnulib code to z/OS POSIX environment

View File

@ -340,6 +340,62 @@ 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 additional programs including /bin/sh, /bin/true, and
# /bin/echo, all of which we build below to limit library dependencies
# to just those things in glibc and language support libraries which
# we also copy into the into the rootfs. To determine what language
# support libraries we need we 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.
ifeq (,$(CXX))
LINKS_DSO_PROGRAM = links-dso-program-c
else
LINKS_DSO_PROGRAM = links-dso-program
endif
$(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) "" \

View File

@ -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

21
Rules
View File

@ -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
@ -145,7 +147,8 @@ xtests-special-notdir = $(patsubst $(objpfx)%, %, $(xtests-special))
ifeq ($(run-built-tests),no)
tests-expected =
else
tests-expected = $(tests) $(tests-internal) $(tests-printers)
tests-expected = $(tests) $(tests-internal) $(tests-printers) \
$(tests-container)
endif
tests:
$(..)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,17 @@ $(objpfx)%.out: /dev/null $(objpfx)% # Make it 2nd arg for canned sequence.
$(make-test-out) > $@; \
$(evaluate-test)
# Any tests that require an isolated container (filesystem, network
# and pid namespaces) 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

View File

@ -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

View File

@ -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 ();

View File

@ -0,0 +1 @@
group test1

View File

@ -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

View File

@ -54,6 +54,7 @@ libsupport-routines = \
support_format_netent \
support_isolate_in_subprocess \
support_openpty \
support_paths \
support_quote_blob \
support_record_failure \
support_run_diff \
@ -85,6 +86,7 @@ libsupport-routines = \
xmalloc \
xmemstream \
xmkdir \
xmkdirp \
xmmap \
xmprotect \
xmunmap \
@ -140,6 +142,7 @@ libsupport-routines = \
xsocket \
xstrdup \
xstrndup \
xsymlink \
xsysconf \
xunlink \
xwaitpid \
@ -152,6 +155,45 @@ 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)\"
ifeq (,$(CXX))
LINKS_DSO_PROGRAM = links-dso-program-c
else
LINKS_DSO_PROGRAM = links-dso-program
LDLIBS-links-dso-program = -lstdc++ -lgcc_s
endif
others: \
$(objpfx)test-container \
$(objpfx)shell-container \
$(objpfx)echo-container \
$(objpfx)true-container \
$(objpfx)$(LINKS_DSO_PROGRAM)
ifeq ($(build-programs),yes)
endif
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
others += $(LINKS_DSO_PROGRAM)
others-noinstall += $(LINKS_DSO_PROGRAM)
$(objpfx)test-container : $(libsupport)
$(objpfx)shell-container : $(libsupport)
$(objpfx)echo-container : $(libsupport)
$(objpfx)true-container : $(libsupport)
tests = \
README-testing \
tst-support-namespace \

34
support/echo-container.c Normal file
View File

@ -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
<http://www.gnu.org/licenses/>. */
#include <stdio.h>
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;
}

View File

@ -0,0 +1,9 @@
#include <stdio.h>
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;
}

View File

@ -0,0 +1,11 @@
#include <iostream>
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;
}

395
support/shell-container.c Normal file
View File

@ -0,0 +1,395 @@
/* 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
<http://www.gnu.org/licenses/>. */
#define _FILE_OFFSET_BITS 64
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sched.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
#include <sys/file.h>
#include <sys/wait.h>
#include <stdarg.h>
#include <sys/sysmacros.h>
#include <ctype.h>
#include <utime.h>
#include <errno.h>
#include <error.h>
#include <support/support.h>
/* 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;
}

View File

@ -25,6 +25,8 @@
#include <stddef.h>
#include <sys/cdefs.h>
/* For mode_t. */
#include <sys/stat.h>
__BEGIN_DECLS
@ -82,6 +84,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 */

51
support/support_paths.c Normal file
View File

@ -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
<http://www.gnu.org/licenses/>. */
#include <support/support.h>
#include <support/check.h>
/* 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

988
support/test-container.c Normal file
View File

@ -0,0 +1,988 @@
/* 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
<http://www.gnu.org/licenses/>. */
#define _FILE_OFFSET_BITS 64
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sched.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
#include <sys/file.h>
#include <sys/wait.h>
#include <stdarg.h>
#include <sys/sysmacros.h>
#include <ctype.h>
#include <utime.h>
#include <errno.h>
#include <error.h>
#ifdef __linux__
#include <sys/mount.h>
#endif
#include <support/support.h>
#include <support/xunistd.h>
#include "check.h"
#include "test-driver.h"
#ifndef __linux__
#define mount(s,t,fs,f,d) no_mount()
int no_mount (void)
{
FAIL_UNSUPPORTED("mount not supported; port needed");
}
#endif
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, &times) < 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 <set some vars> ld.so <library path> 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 <program to run> <args...>\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]))
{
/* If there are variables we do NOT want to propogate, this
is where the test for them goes. */
{
/* 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 <test>.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);
}

26
support/true-container.c Normal file
View File

@ -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
<http://www.gnu.org/licenses/>. */
/* Implements the in-container /bin/true, which always returns true
(0). */
int
main (void)
{
return 0;
}

66
support/xmkdirp.c Normal file
View File

@ -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
<http://www.gnu.org/licenses/>. */
#include <support/support.h>
#include <support/check.h>
#include <support/xunistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
/* 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;
}

29
support/xsymlink.c Normal file
View File

@ -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
<http://www.gnu.org/licenses/>. */
#include <support/support.h>
#include <support/check.h>
#include <unistd.h>
void
xsymlink (const char *target, const char *linkpath)
{
if (symlink (target, linkpath) < 0)
FAIL_EXIT1 ("symlink (\"%s\", \"%s\")", target, linkpath);
}

View File

@ -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. */