mirror of
https://sourceware.org/git/glibc.git
synced 2024-11-26 19:23:34 +08:00
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:
parent
b35d3509ca
commit
561b0bec44
25
ChangeLog
25
ChangeLog
@ -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
|
||||
|
56
Makefile
56
Makefile
@ -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) "" \
|
||||
|
@ -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
21
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
|
||||
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 ();
|
||||
|
||||
|
1
nss/tst-nss-test3.root/etc/nsswitch.conf
Normal file
1
nss/tst-nss-test3.root/etc/nsswitch.conf
Normal file
@ -0,0 +1 @@
|
||||
group test1
|
2
nss/tst-nss-test3.root/tst-nss-test3.script
Normal file
2
nss/tst-nss-test3.root/tst-nss-test3.script
Normal 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
|
@ -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
34
support/echo-container.c
Normal 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;
|
||||
}
|
9
support/links-dso-program-c.c
Normal file
9
support/links-dso-program-c.c
Normal 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;
|
||||
}
|
11
support/links-dso-program.cc
Normal file
11
support/links-dso-program.cc
Normal 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
395
support/shell-container.c
Normal 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;
|
||||
}
|
@ -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
51
support/support_paths.c
Normal 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
988
support/test-container.c
Normal 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, ×) < 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
26
support/true-container.c
Normal 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
66
support/xmkdirp.c
Normal 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
29
support/xsymlink.c
Normal 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);
|
||||
}
|
@ -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. */
|
||||
|
Loading…
Reference in New Issue
Block a user