This patch backports the support/ directory as of the upstream commit below. (It does not include the required Makefile changes to enable test-in-container builds.) commit 00c86a37d1b63044e3169d1f2ebec23447c73f79 Author: Adhemerval Zanella Date: Wed Nov 7 11:09:02 2018 -0200 support: Fix printf format for TEST_COMPARE_STRING Fix the following on 32 bits targets: support_test_compare_string.c: In function ‘support_test_compare_string’: support_test_compare_string.c:80:37: error: format ‘%lu’ expects argument of type ‘long unsigned int’, but argument 2 has type ‘size_t’ {aka ‘unsigned int’} [-Werror=format=] printf (" string length: %lu bytes\n", left_length); ~~^ ~~~~~~~~~~~ %u Checked on arm-linux-gnueabihf. * support/support_test_compare_string.c (support_test_compare_string): Fix printf format. diff --git a/support/Makefile b/support/Makefile index 652d2cdf6945b2eb..2b663fbbfa334ea2 100644 --- a/support/Makefile +++ b/support/Makefile @@ -25,6 +25,7 @@ extra-libs-others = $(extra-libs) extra-libs-noinstall := $(extra-libs) libsupport-routines = \ + blob_repeat \ check \ check_addrinfo \ check_dns_packet \ @@ -43,6 +44,8 @@ libsupport-routines = \ support_capture_subprocess \ support_capture_subprocess_check \ support_chroot \ + support_copy_file_range \ + support_descriptor_supports_holes \ support_enter_mount_namespace \ support_enter_network_namespace \ support_format_address_family \ @@ -53,12 +56,14 @@ libsupport-routines = \ support_format_netent \ support_isolate_in_subprocess \ support_openpty \ + support_paths \ support_quote_blob \ support_record_failure \ support_run_diff \ support_shared_allocate \ support_test_compare_blob \ support_test_compare_failure \ + support_test_compare_string \ support_write_file_string \ support_test_main \ support_test_verify_impl \ @@ -72,6 +77,7 @@ libsupport-routines = \ xchroot \ xclose \ xconnect \ + xcopy_file_range \ xdlfcn \ xdup2 \ xfclose \ @@ -84,6 +90,7 @@ libsupport-routines = \ xmalloc \ xmemstream \ xmkdir \ + xmkdirp \ xmmap \ xmprotect \ xmunmap \ @@ -139,6 +146,7 @@ libsupport-routines = \ xsocket \ xstrdup \ xstrndup \ + xsymlink \ xsysconf \ xunlink \ xwaitpid \ @@ -151,15 +159,47 @@ ifeq ($(build-shared),yes) libsupport-inhibit-o += .o endif +CFLAGS-support_paths.c = \ + -DSRCDIR_PATH=\"`cd .. ; pwd`\" \ + -DOBJDIR_PATH=\"`cd $(objpfx)/..; pwd`\" \ + -DOBJDIR_ELF_LDSO_PATH=\"`cd $(objpfx)/..; pwd`/elf/$(rtld-installed-name)\" \ + -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 -lgcc_s $(libunwind) +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 \ + tst-support_blob_repeat \ tst-support_capture_subprocess \ tst-support_format_dns_packet \ tst-support_quote_blob \ tst-support_record_failure \ tst-test_compare \ tst-test_compare_blob \ + tst-test_compare_string \ tst-xreadlink \ ifeq ($(run-built-tests),yes) diff --git a/support/blob_repeat.c b/support/blob_repeat.c new file mode 100644 index 0000000000000000..16c1e448b990e386 --- /dev/null +++ b/support/blob_repeat.c @@ -0,0 +1,282 @@ +/* Repeating a memory blob, with alias mapping optimization. + Copyright (C) 2018 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Small allocations should use malloc directly instead of the mmap + optimization because mappings carry a lot of overhead. */ +static const size_t maximum_small_size = 4 * 1024 * 1024; + +/* Internal helper for fill. */ +static void +fill0 (char *target, const char *element, size_t element_size, + size_t count) +{ + while (count > 0) + { + memcpy (target, element, element_size); + target += element_size; + --count; + } +} + +/* Fill the buffer at TARGET with COUNT copies of the ELEMENT_SIZE + bytes starting at ELEMENT. */ +static void +fill (char *target, const char *element, size_t element_size, + size_t count) +{ + if (element_size == 0 || count == 0) + return; + else if (element_size == 1) + memset (target, element[0], count); + else if (element_size == sizeof (wchar_t)) + { + wchar_t wc; + memcpy (&wc, element, sizeof (wc)); + wmemset ((wchar_t *) target, wc, count); + } + else if (element_size < 1024 && count > 4096) + { + /* Use larger copies for really small element sizes. */ + char buffer[8192]; + size_t buffer_count = sizeof (buffer) / element_size; + fill0 (buffer, element, element_size, buffer_count); + while (count > 0) + { + size_t copy_count = buffer_count; + if (copy_count > count) + copy_count = count; + size_t copy_bytes = copy_count * element_size; + memcpy (target, buffer, copy_bytes); + target += copy_bytes; + count -= copy_count; + } + } + else + fill0 (target, element, element_size, count); +} + +/* Use malloc instead of mmap for small allocations and unusual size + combinations. */ +static struct support_blob_repeat +allocate_malloc (size_t total_size, const void *element, size_t element_size, + size_t count) +{ + void *buffer = malloc (total_size); + if (buffer == NULL) + return (struct support_blob_repeat) { 0 }; + fill (buffer, element, element_size, count); + return (struct support_blob_repeat) + { + .start = buffer, + .size = total_size, + .use_malloc = true + }; +} + +/* Return the least common multiple of PAGE_SIZE and ELEMENT_SIZE, + avoiding overflow. This assumes that PAGE_SIZE is a power of + two. */ +static size_t +minimum_stride_size (size_t page_size, size_t element_size) +{ + TEST_VERIFY_EXIT (page_size > 0); + TEST_VERIFY_EXIT (element_size > 0); + + /* Compute the number of trailing zeros common to both sizes. */ + unsigned int common_zeros = __builtin_ctzll (page_size | element_size); + + /* In the product, this power of two appears twice, but in the least + common multiple, it appears only once. Therefore, shift one + factor. */ + size_t multiple; + if (__builtin_mul_overflow (page_size >> common_zeros, element_size, + &multiple)) + return 0; + return multiple; +} + +/* Allocations larger than maximum_small_size potentially use mmap + with alias mappings. */ +static struct support_blob_repeat +allocate_big (size_t total_size, const void *element, size_t element_size, + size_t count) +{ + unsigned long page_size = xsysconf (_SC_PAGESIZE); + size_t stride_size = minimum_stride_size (page_size, element_size); + if (stride_size == 0) + { + errno = EOVERFLOW; + return (struct support_blob_repeat) { 0 }; + } + + /* Ensure that the stride size is at least maximum_small_size. This + is necessary to reduce the number of distinct mappings. */ + if (stride_size < maximum_small_size) + stride_size + = ((maximum_small_size + stride_size - 1) / stride_size) * stride_size; + + if (stride_size > total_size) + /* The mmap optimization would not save anything. */ + return allocate_malloc (total_size, element, element_size, count); + + /* Reserve the memory region. If we cannot create the mapping, + there is no reason to set up the backing file. */ + void *target = mmap (NULL, total_size, PROT_NONE, + MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + if (target == MAP_FAILED) + return (struct support_blob_repeat) { 0 }; + + /* Create the backing file for the repeated mapping. Call mkstemp + directly to remove the resources backing the temporary file + immediately, once support_blob_repeat_free is called. Using + create_temp_file would result in a warning during post-test + cleanup. */ + int fd; + { + char *temppath = xasprintf ("%s/support_blob_repeat-XXXXXX", test_dir); + fd = mkstemp (temppath); + if (fd < 0) + FAIL_EXIT1 ("mkstemp (\"%s\"): %m", temppath); + xunlink (temppath); + free (temppath); + } + + /* Make sure that there is backing storage, so that the fill + operation will not fault. */ + if (posix_fallocate (fd, 0, stride_size) != 0) + FAIL_EXIT1 ("posix_fallocate (%zu): %m", stride_size); + + /* The stride size must still be a multiple of the page size and + element size. */ + TEST_VERIFY_EXIT ((stride_size % page_size) == 0); + TEST_VERIFY_EXIT ((stride_size % element_size) == 0); + + /* Fill the backing store. */ + { + void *ptr = mmap (target, stride_size, PROT_READ | PROT_WRITE, + MAP_FIXED | MAP_FILE | MAP_SHARED, fd, 0); + if (ptr == MAP_FAILED) + { + int saved_errno = errno; + xmunmap (target, total_size); + xclose (fd); + errno = saved_errno; + return (struct support_blob_repeat) { 0 }; + } + if (ptr != target) + FAIL_EXIT1 ("mapping of %zu bytes moved from %p to %p", + stride_size, target, ptr); + + /* Write the repeating data. */ + fill (target, element, element_size, stride_size / element_size); + + /* Return to a PROT_NONE mapping, just to be on the safe side. */ + ptr = mmap (target, stride_size, PROT_NONE, + MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + if (ptr == MAP_FAILED) + FAIL_EXIT1 ("Failed to reinstate PROT_NONE mapping: %m"); + if (ptr != target) + FAIL_EXIT1 ("PROT_NONE mapping of %zu bytes moved from %p to %p", + stride_size, target, ptr); + } + + /* Create the alias mappings. */ + { + size_t remaining_size = total_size; + char *current = target; + int flags = MAP_FIXED | MAP_FILE | MAP_PRIVATE; +#ifdef MAP_NORESERVE + flags |= MAP_NORESERVE; +#endif + while (remaining_size > 0) + { + size_t to_map = stride_size; + if (to_map > remaining_size) + to_map = remaining_size; + void *ptr = mmap (current, to_map, PROT_READ | PROT_WRITE, + flags, fd, 0); + if (ptr == MAP_FAILED) + { + int saved_errno = errno; + xmunmap (target, total_size); + xclose (fd); + errno = saved_errno; + return (struct support_blob_repeat) { 0 }; + } + if (ptr != current) + FAIL_EXIT1 ("MAP_PRIVATE mapping of %zu bytes moved from %p to %p", + to_map, target, ptr); + remaining_size -= to_map; + current += to_map; + } + } + + xclose (fd); + + return (struct support_blob_repeat) + { + .start = target, + .size = total_size, + .use_malloc = false + }; +} + +struct support_blob_repeat +support_blob_repeat_allocate (const void *element, size_t element_size, + size_t count) +{ + size_t total_size; + if (__builtin_mul_overflow (element_size, count, &total_size)) + { + errno = EOVERFLOW; + return (struct support_blob_repeat) { 0 }; + } + if (total_size <= maximum_small_size) + return allocate_malloc (total_size, element, element_size, count); + else + return allocate_big (total_size, element, element_size, count); +} + +void +support_blob_repeat_free (struct support_blob_repeat *blob) +{ + if (blob->size > 0) + { + int saved_errno = errno; + if (blob->use_malloc) + free (blob->start); + else + xmunmap (blob->start, blob->size); + errno = saved_errno; + } + *blob = (struct support_blob_repeat) { 0 }; +} diff --git a/support/blob_repeat.h b/support/blob_repeat.h new file mode 100644 index 0000000000000000..8e9d7ff5f1e01f66 --- /dev/null +++ b/support/blob_repeat.h @@ -0,0 +1,44 @@ +/* Repeating a memory blob, with alias mapping optimization. + 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 + . */ + +#ifndef SUPPORT_BLOB_REPEAT_H +#define SUPPORT_BLOB_REPEAT_H + +#include +#include + +struct support_blob_repeat +{ + void *start; + size_t size; + bool use_malloc; +}; + +/* Return an allocation of COUNT elements, each of ELEMENT_SIZE bytes, + initialized with the bytes starting at ELEMENT. The memory is + writable (and thus counts towards the commit charge). In case of + on error, all members of the return struct are zero-initialized, + and errno is set accordingly. */ +struct support_blob_repeat support_blob_repeat_allocate (const void *element, + size_t element_size, + size_t count); + +/* Deallocate the blob created by support_blob_repeat_allocate. */ +void support_blob_repeat_free (struct support_blob_repeat *); + +#endif /* SUPPORT_BLOB_REPEAT_H */ diff --git a/support/check.h b/support/check.h index b3a4645e9255e90d..e6765289f2492501 100644 --- a/support/check.h +++ b/support/check.h @@ -163,6 +163,19 @@ void support_test_compare_blob (const void *left, const char *right_exp, const char *right_len_exp); +/* Compare the strings LEFT and RIGHT and report a test failure if + they are different. Also report failure if one of the arguments is + a null pointer and the other is not. The strings should be + reasonably short because on mismatch, both are printed. */ +#define TEST_COMPARE_STRING(left, right) \ + (support_test_compare_string (left, right, __FILE__, __LINE__, \ + #left, #right)) + +void support_test_compare_string (const char *left, const char *right, + const char *file, int line, + const char *left_expr, + const char *right_expr); + /* Internal function called by the test driver. */ int support_report_failure (int status) __attribute__ ((weak, warn_unused_result)); diff --git a/support/echo-container.c b/support/echo-container.c new file mode 100644 index 0000000000000000..e4d48df95722af2e --- /dev/null +++ b/support/echo-container.c @@ -0,0 +1,34 @@ +/* Minimal /bin/echo for in-container use. + Copyright (C) 2018 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include + +int +main (int argc, const char **argv) +{ + int i; + + for (i = 1; i < argc; i++) + { + if (i > 1) + putchar (' '); + fputs (argv[i], stdout); + } + putchar ('\n'); + return 0; +} diff --git a/support/links-dso-program-c.c b/support/links-dso-program-c.c new file mode 100644 index 0000000000000000..d28a28a0d09c743c --- /dev/null +++ b/support/links-dso-program-c.c @@ -0,0 +1,9 @@ +#include + +int +main (int argc, char **argv) +{ + /* Complexity to keep gcc from optimizing this away. */ + printf ("This is a test %s.\n", argc > 1 ? argv[1] : "null"); + return 0; +} diff --git a/support/links-dso-program.cc b/support/links-dso-program.cc new file mode 100644 index 0000000000000000..dba6976c0609a332 --- /dev/null +++ b/support/links-dso-program.cc @@ -0,0 +1,11 @@ +#include + +using namespace std; + +int +main (int argc, char **argv) +{ + /* Complexity to keep gcc from optimizing this away. */ + cout << (argc > 1 ? argv[1] : "null"); + return 0; +} diff --git a/support/shell-container.c b/support/shell-container.c new file mode 100644 index 0000000000000000..9bd90d3f60529079 --- /dev/null +++ b/support/shell-container.c @@ -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 + . */ + +#define _FILE_OFFSET_BITS 64 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* 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 (support_copy_file_range (sfd, 0, dfd, 0, st.st_size, 0) != st.st_size) + { + fprintf (stderr, "cp: cannot copy file %s to %s: %s\n", + sname, dname, strerror (errno)); + return 1; + } + + close (sfd); + close (dfd); + + chmod (dname, st.st_mode & 0777); + + return 0; + +} + +/* This is a list of all the built-in commands we understand. */ +static struct { + const char *name; + int (*func) (char **argv); +} builtin_funcs[] = { + { "true", true_func }, + { "echo", echo_func }, + { "cp", copy_func }, + { NULL, NULL } +}; + +/* Run one tokenized command. argv[0] is the command. argv is + NULL-terminated. */ +static void +run_command_array (char **argv) +{ + int i, j; + pid_t pid; + int status; + int (*builtin_func) (char **args); + + if (argv[0] == NULL) + return; + + builtin_func = NULL; + + int new_stdin = 0; + int new_stdout = 1; + int new_stderr = 2; + + dprintf (stderr, "run_command_array starting\n"); + for (i = 0; argv[i]; i++) + dprintf (stderr, " argv [%d] `%s'\n", i, argv[i]); + + for (j = i = 0; argv[i]; i++) + { + if (strcmp (argv[i], "<") == 0 && argv[i + 1]) + { + new_stdin = open (argv[i + 1], O_WRONLY|O_CREAT|O_TRUNC, 0777); + ++i; + continue; + } + if (strcmp (argv[i], ">") == 0 && argv[i + 1]) + { + new_stdout = open (argv[i + 1], O_WRONLY|O_CREAT|O_TRUNC, 0777); + ++i; + continue; + } + if (strcmp (argv[i], ">>") == 0 && argv[i + 1]) + { + new_stdout = open (argv[i + 1], O_WRONLY|O_CREAT|O_APPEND, 0777); + ++i; + continue; + } + if (strcmp (argv[i], "2>") == 0 && argv[i + 1]) + { + new_stderr = open (argv[i + 1], O_WRONLY|O_CREAT|O_TRUNC, 0777); + ++i; + continue; + } + argv[j++] = argv[i]; + } + argv[j] = NULL; + + + for (i = 0; builtin_funcs[i].name != NULL; i++) + if (strcmp (argv[0], builtin_funcs[i].name) == 0) + builtin_func = builtin_funcs[i].func; + + dprintf (stderr, "builtin %p argv0 `%s'\n", builtin_func, argv[0]); + + pid = fork (); + if (pid < 0) + { + fprintf (stderr, "sh: fork failed\n"); + exit (1); + } + + if (pid == 0) + { + if (new_stdin != 0) + { + dup2 (new_stdin, 0); + close (new_stdin); + } + if (new_stdout != 1) + { + dup2 (new_stdout, 1); + close (new_stdout); + } + if (new_stderr != 2) + { + dup2 (new_stderr, 2); + close (new_stdout); + } + + if (builtin_func != NULL) + exit (builtin_func (argv + 1)); + + execvp (argv[0], argv); + + fprintf (stderr, "sh: execing %s failed: %s", + argv[0], strerror (errno)); + exit (1); + } + + waitpid (pid, &status, 0); + + dprintf (stderr, "exiting run_command_array\n"); + + if (WIFEXITED (status)) + { + int rv = WEXITSTATUS (status); + if (rv) + exit (rv); + } + else + exit (1); +} + +/* Run one command-as-a-string, by tokenizing it. Limited to + MAX_ARG_COUNT arguments. Simple substitution is done of $1 to $9 + (as whole separate tokens) from iargs[]. Quoted strings work if + the quotes wrap whole tokens; i.e. "foo bar" but not foo" bar". */ +static void +run_command_string (const char *cmdline, const char **iargs) +{ + char *args[MAX_ARG_COUNT+1]; + int ap = 0; + const char *start, *end; + int nargs; + + for (nargs = 0; iargs[nargs] != NULL; ++nargs) + ; + + dprintf (stderr, "run_command_string starting: '%s'\n", cmdline); + + while (ap < MAX_ARG_COUNT) + { + /* If the argument is quoted, this is the quote character, else NUL. */ + int in_quote = 0; + + /* Skip whitespace up to the next token. */ + while (*cmdline && isspace (*cmdline)) + cmdline ++; + if (*cmdline == 0) + break; + + start = cmdline; + /* Check for quoted argument. */ + in_quote = (*cmdline == '\'' || *cmdline == '"') ? *cmdline : 0; + + /* Skip to end of token; either by whitespace or matching quote. */ + dprintf (stderr, "in_quote %d\n", in_quote); + while (*cmdline + && (!isspace (*cmdline) || in_quote)) + { + if (*cmdline == in_quote + && cmdline != start) + in_quote = 0; + dprintf (stderr, "[%c]%d ", *cmdline, in_quote); + cmdline ++; + } + dprintf (stderr, "\n"); + + /* Allocate space for this token and store it in args[]. */ + end = cmdline; + dprintf (stderr, "start<%s> end<%s>\n", start, end); + args[ap] = (char *) xmalloc (end - start + 1); + memcpy (args[ap], start, end - start); + args[ap][end - start] = 0; + + /* Strip off quotes, if found. */ + dprintf (stderr, "args[%d] = <%s>\n", ap, args[ap]); + if (args[ap][0] == '\'' + && args[ap][strlen (args[ap])-1] == '\'') + { + args[ap][strlen (args[ap])-1] = 0; + args[ap] ++; + } + + else if (args[ap][0] == '"' + && args[ap][strlen (args[ap])-1] == '"') + { + args[ap][strlen (args[ap])-1] = 0; + args[ap] ++; + } + + /* Replace positional parameters like $4. */ + else if (args[ap][0] == '$' + && isdigit (args[ap][1]) + && args[ap][2] == 0) + { + int a = args[ap][1] - '1'; + if (0 <= a && a < nargs) + args[ap] = strdup (iargs[a]); + } + + ap ++; + + if (*cmdline == 0) + break; + } + + /* Lastly, NULL terminate the array and run it. */ + args[ap] = NULL; + run_command_array (args); +} + +/* Run a script by reading lines and passing them to the above + function. */ +static void +run_script (const char *filename, const char **args) +{ + char line[MAX_LINE_LENGTH + 1]; + dprintf (stderr, "run_script starting: '%s'\n", filename); + FILE *f = fopen (filename, "r"); + if (f == NULL) + { + fprintf (stderr, "sh: %s: %s\n", filename, strerror (errno)); + exit (1); + } + while (fgets (line, sizeof (line), f) != NULL) + { + if (line[0] == '#') + { + dprintf (stderr, "comment: %s\n", line); + continue; + } + run_command_string (line, args); + } + fclose (f); +} + +int +main (int argc, const char **argv) +{ + int i; + + if (strcmp (argv[1], "--debug") == 0) + { + debug_mode = 1; + --argc; + ++argv; + } + + dprintf (stderr, "container-sh starting:\n"); + for (i = 0; i < argc; i++) + dprintf (stderr, " argv[%d] is `%s'\n", i, argv[i]); + + if (strcmp (argv[1], "-c") == 0) + run_command_string (argv[2], argv+3); + else + run_script (argv[1], argv+2); + + dprintf (stderr, "normal exit 0\n"); + return 0; +} diff --git a/support/support.h b/support/support.h index b61fe0735c9204de..9418cd11ef6e684d 100644 --- a/support/support.h +++ b/support/support.h @@ -25,6 +25,10 @@ #include #include +/* For mode_t. */ +#include +/* For ssize_t and off64_t. */ +#include __BEGIN_DECLS @@ -65,6 +69,12 @@ void support_write_file_string (const char *path, const char *contents); the result). */ char *support_quote_blob (const void *blob, size_t length); +/* Returns non-zero if the file descriptor is a regular file on a file + system which supports holes (that is, seeking and writing does not + allocate storage for the range of zeros). FD must refer to a + regular file open for writing, and initially empty. */ +int support_descriptor_supports_holes (int fd); + /* Error-checking wrapper functions which terminate the process on error. */ @@ -76,6 +86,23 @@ 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 path to the runtime linker used by the testsuite, + e.g. OBJDIR_PATH/elf/ld-linux-x86-64.so.2 */ +extern const char support_objdir_elf_ldso[]; + +/* 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[]; + +extern ssize_t support_copy_file_range (int, off64_t *, int, off64_t *, + size_t, unsigned int); + __END_DECLS #endif /* SUPPORT_H */ diff --git a/support/support_copy_file_range.c b/support/support_copy_file_range.c new file mode 100644 index 0000000000000000..9a1e39773e0481c9 --- /dev/null +++ b/support/support_copy_file_range.c @@ -0,0 +1,143 @@ +/* Simplified copy_file_range with cross-device copy. + Copyright (C) 2018 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#include +#include +#include +#include +#include +#include +#include + +ssize_t +support_copy_file_range (int infd, __off64_t *pinoff, + int outfd, __off64_t *poutoff, + size_t length, unsigned int flags) +{ + if (flags != 0) + { + errno = EINVAL; + return -1; + } + + struct stat64 instat; + struct stat64 outstat; + if (fstat64 (infd, &instat) != 0 || fstat64 (outfd, &outstat) != 0) + return -1; + if (S_ISDIR (instat.st_mode) || S_ISDIR (outstat.st_mode)) + { + errno = EISDIR; + return -1; + } + if (!S_ISREG (instat.st_mode) || !S_ISREG (outstat.st_mode)) + { + /* We need a regular input file so that the we can seek + backwards in case of a write failure. */ + errno = EINVAL; + return -1; + } + + /* The output descriptor must not have O_APPEND set. */ + if (fcntl (outfd, F_GETFL) & O_APPEND) + { + errno = EBADF; + return -1; + } + + /* Avoid an overflow in the result. */ + if (length > SSIZE_MAX) + length = SSIZE_MAX; + + /* Main copying loop. The buffer size is arbitrary and is a + trade-off between stack size consumption, cache usage, and + amortization of system call overhead. */ + size_t copied = 0; + char buf[8192]; + while (length > 0) + { + size_t to_read = length; + if (to_read > sizeof (buf)) + to_read = sizeof (buf); + + /* Fill the buffer. */ + ssize_t read_count; + if (pinoff == NULL) + read_count = read (infd, buf, to_read); + else + read_count = pread64 (infd, buf, to_read, *pinoff); + if (read_count == 0) + /* End of file reached prematurely. */ + return copied; + if (read_count < 0) + { + if (copied > 0) + /* Report the number of bytes copied so far. */ + return copied; + return -1; + } + if (pinoff != NULL) + *pinoff += read_count; + + /* Write the buffer part which was read to the destination. */ + char *end = buf + read_count; + for (char *p = buf; p < end; ) + { + ssize_t write_count; + if (poutoff == NULL) + write_count = write (outfd, p, end - p); + else + write_count = pwrite64 (outfd, p, end - p, *poutoff); + if (write_count < 0) + { + /* Adjust the input read position to match what we have + written, so that the caller can pick up after the + error. */ + size_t written = p - buf; + /* NB: This needs to be signed so that we can form the + negative value below. */ + ssize_t overread = read_count - written; + if (pinoff == NULL) + { + if (overread > 0) + { + /* We are on an error recovery path, so we + cannot deal with failure here. */ + int save_errno = errno; + (void) lseek64 (infd, -overread, SEEK_CUR); + errno = save_errno; + } + } + else /* pinoff != NULL */ + *pinoff -= overread; + + if (copied + written > 0) + /* Report the number of bytes copied so far. */ + return copied + written; + return -1; + } + p += write_count; + if (poutoff != NULL) + *poutoff += write_count; + } /* Write loop. */ + + copied += read_count; + length -= read_count; + } + return copied; +} diff --git a/support/support_descriptor_supports_holes.c b/support/support_descriptor_supports_holes.c new file mode 100644 index 0000000000000000..c7099ca67caf803c --- /dev/null +++ b/support/support_descriptor_supports_holes.c @@ -0,0 +1,87 @@ +/* Test for file system hole support. + Copyright (C) 2018 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#include +#include +#include +#include + +int +support_descriptor_supports_holes (int fd) +{ + enum + { + /* Write offset for the enlarged file. This value is arbitrary + and hopefully large enough to trigger the creation of holes. + We cannot use the file system block size as a reference here + because it is incorrect for network file systems. */ + write_offset = 16 * 1024 * 1024, + + /* Our write may add this number of additional blocks (see + block_limit below). */ + block_headroom = 8, + }; + + struct stat64 st; + xfstat (fd, &st); + if (!S_ISREG (st.st_mode)) + FAIL_EXIT1 ("descriptor %d does not refer to a regular file", fd); + if (st.st_size != 0) + FAIL_EXIT1 ("descriptor %d does not refer to an empty file", fd); + if (st.st_blocks > block_headroom) + FAIL_EXIT1 ("descriptor %d refers to a pre-allocated file (%lld blocks)", + fd, (long long int) st.st_blocks); + + /* Write a single byte at the start of the file to compute the block + usage for a single byte. */ + xlseek (fd, 0, SEEK_SET); + char b = '@'; + xwrite (fd, &b, 1); + /* Attempt to bypass delayed allocation. */ + TEST_COMPARE (fsync (fd), 0); + xfstat (fd, &st); + + /* This limit is arbitrary. The file system needs to store + somewhere that data exists at the write offset, and this may + moderately increase the number of blocks used by the file, in + proportion to the initial block count, but not in proportion to + the write offset. */ + unsigned long long int block_limit = 2 * st.st_blocks + block_headroom; + + /* Write a single byte at 16 megabytes. */ + xlseek (fd, write_offset, SEEK_SET); + xwrite (fd, &b, 1); + /* Attempt to bypass delayed allocation. */ + TEST_COMPARE (fsync (fd), 0); + xfstat (fd, &st); + bool supports_holes = st.st_blocks <= block_limit; + + /* Also check that extending the file does not fill up holes. */ + xftruncate (fd, 2 * write_offset); + /* Attempt to bypass delayed allocation. */ + TEST_COMPARE (fsync (fd), 0); + xfstat (fd, &st); + supports_holes = supports_holes && st.st_blocks <= block_limit; + + /* Return to a zero-length file. */ + xftruncate (fd, 0); + xlseek (fd, 0, SEEK_SET); + + return supports_holes; +} diff --git a/support/support_paths.c b/support/support_paths.c new file mode 100644 index 0000000000000000..6d0beb102c9b4bed --- /dev/null +++ b/support/support_paths.c @@ -0,0 +1,59 @@ +/* Various paths that might be needed. + Copyright (C) 2018 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#include + +/* The idea here is to make various makefile-level paths available to + support programs, as canonicalized absolute paths. */ + +/* These point to the TOP of the source/build tree, not your (or + support's) subdirectory. */ +#ifdef SRCDIR_PATH +const char support_srcdir_root[] = SRCDIR_PATH; +#else +# error please -DSRCDIR_PATH=something in the Makefile +#endif + +#ifdef OBJDIR_PATH +const char support_objdir_root[] = OBJDIR_PATH; +#else +# error please -DOBJDIR_PATH=something in the Makefile +#endif + +#ifdef OBJDIR_ELF_LDSO_PATH +/* Corresponds to the path to the runtime linker used by the testsuite, + e.g. OBJDIR_PATH/elf/ld-linux-x86-64.so.2 */ +const char support_objdir_elf_ldso[] = OBJDIR_ELF_LDSO_PATH; +#else +# error please -DOBJDIR_ELF_LDSO_PATH=something in the Makefile +#endif + +#ifdef INSTDIR_PATH +/* Corresponds to the --prefix= passed to configure. */ +const char support_install_prefix[] = INSTDIR_PATH; +#else +# error please -DINSTDIR_PATH=something in the Makefile +#endif + +#ifdef LIBDIR_PATH +/* Corresponds to the install's lib/ or lib64/ directory. */ +const char support_libdir_prefix[] = LIBDIR_PATH; +#else +# error please -DLIBDIR_PATH=something in the Makefile +#endif diff --git a/support/support_test_compare_string.c b/support/support_test_compare_string.c new file mode 100644 index 0000000000000000..a76ba8eda7782d9d --- /dev/null +++ b/support/support_test_compare_string.c @@ -0,0 +1,91 @@ +/* Check two strings for equality. + Copyright (C) 2018 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#include +#include +#include +#include +#include + +static void +report_length (const char *what, const char *str, size_t length) +{ + if (str == NULL) + printf (" %s string: NULL\n", what); + else + printf (" %s string: %zu bytes\n", what, length); +} + +static void +report_string (const char *what, const unsigned char *blob, + size_t length, const char *expr) +{ + if (length > 0) + { + printf (" %s (evaluated from %s):\n", what, expr); + char *quoted = support_quote_blob (blob, length); + printf (" \"%s\"\n", quoted); + free (quoted); + + fputs (" ", stdout); + for (size_t i = 0; i < length; ++i) + printf (" %02X", blob[i]); + putc ('\n', stdout); + } +} + +static size_t +string_length_or_zero (const char *str) +{ + if (str == NULL) + return 0; + else + return strlen (str); +} + +void +support_test_compare_string (const char *left, const char *right, + const char *file, int line, + const char *left_expr, const char *right_expr) +{ + /* Two null pointers are accepted. */ + if (left == NULL && right == NULL) + return; + + size_t left_length = string_length_or_zero (left); + size_t right_length = string_length_or_zero (right); + + if (left_length != right_length || left == NULL || right == NULL + || memcmp (left, right, left_length) != 0) + { + support_record_failure (); + printf ("%s:%d: error: blob comparison failed\n", file, line); + if (left_length == right_length && right != NULL && left != NULL) + printf (" string length: %zu bytes\n", left_length); + else + { + report_length ("left", left, left_length); + report_length ("right", right, right_length); + } + report_string ("left", (const unsigned char *) left, + left_length, left_expr); + report_string ("right", (const unsigned char *) right, + right_length, right_expr); + } +} diff --git a/support/test-container.c b/support/test-container.c new file mode 100644 index 0000000000000000..b58f0f7b3d1d4859 --- /dev/null +++ b/support/test-container.c @@ -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 + . */ + +#define _FILE_OFFSET_BITS 64 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __linux__ +#include +#endif + +#include +#include +#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); + + xcopy_file_range (sfd, 0, dfd, 0, st.st_size, 0); + + xclose (sfd); + xclose (dfd); + + if (chmod (dname, st.st_mode & 0777) < 0) + FAIL_EXIT1 ("chmod %s: %s\n", dname, strerror (errno)); + + times.actime = st.st_atime; + times.modtime = st.st_mtime; + if (utime (dname, ×) < 0) + FAIL_EXIT1 ("utime %s: %s\n", dname, strerror (errno)); +} + +/* We don't check *everything* about the two files to see if a copy is + needed, just the minimum to make sure we get the latest copy. */ +static int +need_sync (char *ap, char *bp, struct stat *a, struct stat *b) +{ + if ((a->st_mode & S_IFMT) != (b->st_mode & S_IFMT)) + return 1; + + if (S_ISLNK (a->st_mode)) + { + int rv; + char *al, *bl; + + if (a->st_size != b->st_size) + return 1; + + al = xreadlink (ap); + bl = xreadlink (bp); + rv = strcmp (al, bl); + free (al); + free (bl); + if (rv == 0) + return 0; /* links are same */ + return 1; /* links differ */ + } + + if (verbose) + { + if (a->st_size != b->st_size) + printf ("SIZE\n"); + if ((a->st_mode & 0777) != (b->st_mode & 0777)) + printf ("MODE\n"); + if (a->st_mtime != b->st_mtime) + printf ("TIME\n"); + } + + if (a->st_size == b->st_size + && ((a->st_mode & 0777) == (b->st_mode & 0777)) + && a->st_mtime == b->st_mtime) + return 0; + + return 1; +} + +static void +rsync_1 (path_buf * src, path_buf * dest, int and_delete) +{ + DIR *dir; + struct dirent *de; + struct stat s, d; + + r_append ("/", src); + r_append ("/", dest); + + if (verbose) + printf ("sync %s to %s %s\n", src->buf, dest->buf, + and_delete ? "and delete" : ""); + + size_t staillen = src->len; + + size_t dtaillen = dest->len; + + dir = opendir (src->buf); + + while ((de = readdir (dir)) != NULL) + { + if (strcmp (de->d_name, ".") == 0 + || strcmp (de->d_name, "..") == 0) + continue; + + src->len = staillen; + r_append (de->d_name, src); + dest->len = dtaillen; + r_append (de->d_name, dest); + + s.st_mode = ~0; + d.st_mode = ~0; + + if (lstat (src->buf, &s) != 0) + FAIL_EXIT1 ("%s obtained by readdir, but stat failed.\n", src->buf); + + /* It's OK if this one fails, since we know the file might be + missing. */ + lstat (dest->buf, &d); + + if (! need_sync (src->buf, dest->buf, &s, &d)) + { + if (S_ISDIR (s.st_mode)) + rsync_1 (src, dest, and_delete); + continue; + } + + if (d.st_mode != ~0) + switch (d.st_mode & S_IFMT) + { + case S_IFDIR: + if (!S_ISDIR (s.st_mode)) + { + if (verbose) + printf ("-D %s\n", dest->buf); + recursive_remove (dest->buf); + } + break; + + default: + if (verbose) + printf ("-F %s\n", dest->buf); + maybe_xunlink (dest->buf); + break; + } + + switch (s.st_mode & S_IFMT) + { + case S_IFREG: + if (verbose) + printf ("+F %s\n", dest->buf); + copy_one_file (src->buf, dest->buf); + break; + + case S_IFDIR: + if (verbose) + printf ("+D %s\n", dest->buf); + maybe_xmkdir (dest->buf, (s.st_mode & 0777) | 0700); + rsync_1 (src, dest, and_delete); + break; + + case S_IFLNK: + { + char *lp; + if (verbose) + printf ("+L %s\n", dest->buf); + lp = xreadlink (src->buf); + xsymlink (lp, dest->buf); + free (lp); + break; + } + + default: + break; + } + } + + closedir (dir); + src->len = staillen; + src->buf[staillen] = 0; + dest->len = dtaillen; + dest->buf[dtaillen] = 0; + + if (!and_delete) + return; + + /* The rest of this function removes any files/directories in DEST + that do not exist in SRC. This is triggered as part of a + preclean or postsclean step. */ + + dir = opendir (dest->buf); + + while ((de = readdir (dir)) != NULL) + { + if (strcmp (de->d_name, ".") == 0 + || strcmp (de->d_name, "..") == 0) + continue; + + src->len = staillen; + r_append (de->d_name, src); + dest->len = dtaillen; + r_append (de->d_name, dest); + + s.st_mode = ~0; + d.st_mode = ~0; + + lstat (src->buf, &s); + + if (lstat (dest->buf, &d) != 0) + FAIL_EXIT1 ("%s obtained by readdir, but stat failed.\n", dest->buf); + + if (s.st_mode == ~0) + { + /* dest exists and src doesn't, clean it. */ + switch (d.st_mode & S_IFMT) + { + case S_IFDIR: + if (!S_ISDIR (s.st_mode)) + { + if (verbose) + printf ("-D %s\n", dest->buf); + recursive_remove (dest->buf); + } + break; + + default: + if (verbose) + printf ("-F %s\n", dest->buf); + maybe_xunlink (dest->buf); + break; + } + } + } + + closedir (dir); +} + +static void +rsync (char *src, char *dest, int and_delete) +{ + r_setup (src, &spath); + r_setup (dest, &dpath); + + rsync_1 (&spath, &dpath, and_delete); +} + + +int +main (int argc, char **argv) +{ + pid_t child; + char *pristine_root_path; + char *new_root_path; + char *new_cwd_path; + char *new_objdir_path; + char *new_srcdir_path; + char **new_child_proc; + char *command_root; + char *command_base; + char *command_basename; + char *so_base; + int do_postclean = 0; + + uid_t original_uid; + gid_t original_gid; + int UMAP; + int GMAP; + /* Used for "%lld %lld 1" so need not be large. */ + char tmp[100]; + struct stat st; + int lock_fd; + + setbuf (stdout, NULL); + + /* The command line we're expecting looks like this: + env ld.so test-binary + + We need to peel off any "env" or "ld.so" portion of the command + line, and keep track of which env vars we should preserve and + which we drop. */ + + if (argc < 2) + { + fprintf (stderr, "Usage: containerize \n"); + exit (1); + } + + if (strcmp (argv[1], "-v") == 0) + { + verbose = 1; + ++argv; + --argc; + } + + if (strcmp (argv[1], "env") == 0) + { + ++argv; + --argc; + while (is_env_setting (argv[1])) + { + /* 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 (strcmp (argv[1], support_objdir_elf_ldso) == 0) + { + ++argv; + --argc; + while (argv[1][0] == '-') + { + if (strcmp (argv[1], "--library-path") == 0) + { + ++argv; + --argc; + } + ++argv; + --argc; + } + } + + pristine_root_path = strdup (concat (support_objdir_root, + "/testroot.pristine", NULL)); + new_root_path = strdup (concat (support_objdir_root, + "/testroot.root", NULL)); + new_cwd_path = get_current_dir_name (); + new_child_proc = argv + 1; + + lock_fd = open (concat (pristine_root_path, "/lock.fd", NULL), + O_CREAT | O_TRUNC | O_RDWR, 0666); + if (lock_fd < 0) + FAIL_EXIT1 ("Cannot create testroot lock.\n"); + + while (flock (lock_fd, LOCK_EX) != 0) + { + if (errno != EINTR) + FAIL_EXIT1 ("Cannot lock testroot.\n"); + } + + xmkdirp (new_root_path, 0755); + + /* We look for extra setup info in a subdir in the same spot as the + test, with the same name but a ".root" extension. This is that + directory. We try to look in the source tree if the path we're + given refers to the build tree, but we rely on the path to be + absolute. This is what the glibc makefiles do. */ + command_root = concat (argv[1], ".root", NULL); + if (strncmp (command_root, support_objdir_root, + strlen (support_objdir_root)) == 0 + && command_root[strlen (support_objdir_root)] == '/') + command_root = concat (support_srcdir_root, + argv[1] + strlen (support_objdir_root), + ".root", NULL); + command_root = strdup (command_root); + + /* This cuts off the ".root" we appended above. */ + command_base = strdup (command_root); + command_base[strlen (command_base) - 5] = 0; + + /* This is the basename of the test we're running. */ + command_basename = strrchr (command_base, '/'); + if (command_basename == NULL) + command_basename = command_base; + else + ++command_basename; + + /* Shared object base directory. */ + so_base = strdup (argv[1]); + if (strrchr (so_base, '/') != NULL) + strrchr (so_base, '/')[1] = 0; + + if (file_exists (concat (command_root, "/postclean.req", NULL))) + do_postclean = 1; + + rsync (pristine_root_path, new_root_path, + file_exists (concat (command_root, "/preclean.req", NULL))); + + if (stat (command_root, &st) >= 0 + && S_ISDIR (st.st_mode)) + rsync (command_root, new_root_path, 0); + + new_objdir_path = strdup (concat (new_root_path, + support_objdir_root, NULL)); + new_srcdir_path = strdup (concat (new_root_path, + support_srcdir_root, NULL)); + + /* new_cwd_path starts with '/' so no "/" needed between the two. */ + xmkdirp (concat (new_root_path, new_cwd_path, NULL), 0755); + xmkdirp (new_srcdir_path, 0755); + xmkdirp (new_objdir_path, 0755); + + original_uid = getuid (); + original_gid = getgid (); + + /* Handle the cp/mv/rm "script" here. */ + { + char *the_line = NULL; + size_t line_len = 0; + char *fname = concat (command_root, "/", + command_basename, ".script", NULL); + char *the_words[3]; + FILE *f = fopen (fname, "r"); + + if (verbose && f) + fprintf (stderr, "running %s\n", fname); + + if (f == NULL) + { + /* Try foo.script instead of foo.root/foo.script, as a shortcut. */ + fname = concat (command_base, ".script", NULL); + f = fopen (fname, "r"); + if (verbose && f) + fprintf (stderr, "running %s\n", fname); + } + + /* Note that we do NOT look for a Makefile-generated foo.script in + the build directory. If that is ever needed, this is the place + to add it. */ + + /* This is where we "interpret" the mini-script which is .script. */ + if (f != NULL) + { + while (getline (&the_line, &line_len, f) > 0) + { + int nt = tokenize (the_line, the_words, 3); + int i; + + for (i = 1; i < nt; ++i) + { + if (memcmp (the_words[i], "$B/", 3) == 0) + the_words[i] = concat (support_objdir_root, + the_words[i] + 2, NULL); + else if (memcmp (the_words[i], "$S/", 3) == 0) + the_words[i] = concat (support_srcdir_root, + the_words[i] + 2, NULL); + else if (memcmp (the_words[i], "$I/", 3) == 0) + the_words[i] = concat (new_root_path, + support_install_prefix, + the_words[i] + 2, NULL); + else if (memcmp (the_words[i], "$L/", 3) == 0) + the_words[i] = concat (new_root_path, + support_libdir_prefix, + the_words[i] + 2, NULL); + else if (the_words[i][0] == '/') + the_words[i] = concat (new_root_path, + the_words[i], NULL); + } + + if (nt == 3 && the_words[2][strlen (the_words[2]) - 1] == '/') + { + char *r = strrchr (the_words[1], '/'); + if (r) + the_words[2] = concat (the_words[2], r + 1, NULL); + else + the_words[2] = concat (the_words[2], the_words[1], NULL); + } + + if (nt == 2 && strcmp (the_words[0], "so") == 0) + { + the_words[2] = concat (new_root_path, support_libdir_prefix, + "/", the_words[1], NULL); + the_words[1] = concat (so_base, the_words[1], NULL); + copy_one_file (the_words[1], the_words[2]); + } + else if (nt == 3 && strcmp (the_words[0], "cp") == 0) + { + copy_one_file (the_words[1], the_words[2]); + } + else if (nt == 3 && strcmp (the_words[0], "mv") == 0) + { + if (rename (the_words[1], the_words[2]) < 0) + FAIL_EXIT1 ("rename %s -> %s: %s", the_words[1], + the_words[2], strerror (errno)); + } + else if (nt == 3 && strcmp (the_words[0], "chmod") == 0) + { + long int m; + m = strtol (the_words[1], NULL, 0); + if (chmod (the_words[2], m) < 0) + FAIL_EXIT1 ("chmod %s: %s\n", + the_words[2], strerror (errno)); + + } + else if (nt == 2 && strcmp (the_words[0], "rm") == 0) + { + maybe_xunlink (the_words[1]); + } + else if (nt > 0 && the_words[0][0] != '#') + { + printf ("\033[31minvalid [%s]\033[0m\n", the_words[0]); + } + } + fclose (f); + } + } + +#ifdef CLONE_NEWNS + /* The unshare here gives us our own spaces and capabilities. */ + if (unshare (CLONE_NEWUSER | CLONE_NEWPID | CLONE_NEWNS) < 0) + { + /* Older kernels may not support all the options, or security + policy may block this call. */ + if (errno == EINVAL || errno == EPERM) + FAIL_UNSUPPORTED ("unable to unshare user/fs: %s", strerror (errno)); + else + FAIL_EXIT1 ("unable to unshare user/fs: %s", strerror (errno)); + } +#else + /* Some targets may not support unshare at all. */ + FAIL_UNSUPPORTED ("unshare support missing"); +#endif + + /* Some systems, by default, all mounts leak out of the namespace. */ + if (mount ("none", "/", NULL, MS_REC | MS_PRIVATE, NULL) != 0) + FAIL_EXIT1 ("could not create a private mount namespace\n"); + + trymount (support_srcdir_root, new_srcdir_path); + trymount (support_objdir_root, new_objdir_path); + + xmkdirp (concat (new_root_path, "/dev", NULL), 0755); + devmount (new_root_path, "null"); + devmount (new_root_path, "zero"); + devmount (new_root_path, "urandom"); + + /* We're done with the "old" root, switch to the new one. */ + if (chroot (new_root_path) < 0) + FAIL_EXIT1 ("Can't chroot to %s - ", new_root_path); + + if (chdir (new_cwd_path) < 0) + FAIL_EXIT1 ("Can't cd to new %s - ", new_cwd_path); + + /* To complete the containerization, we need to fork () at least + once. We can't exec, nor can we somehow link the new child to + our parent. So we run the child and propogate it's exit status + up. */ + child = fork (); + if (child < 0) + FAIL_EXIT1 ("Unable to fork"); + else if (child > 0) + { + /* Parent. */ + int status; + waitpid (child, &status, 0); + + /* There's a bit of magic here, since the buildroot is mounted + in our space, the paths are still valid, and since the mounts + aren't recursive, it sees *only* the built root, not anything + we would normally se if we rsync'd to "/" like mounted /dev + files. */ + if (do_postclean) + rsync (pristine_root_path, new_root_path, 1); + + if (WIFEXITED (status)) + exit (WEXITSTATUS (status)); + + if (WIFSIGNALED (status)) + { + printf ("%%SIGNALLED%%\n"); + exit (77); + } + + printf ("%%EXITERROR%%\n"); + exit (78); + } + + /* The rest is the child process, which is now PID 1 and "in" the + new root. */ + + maybe_xmkdir ("/tmp", 0755); + + /* Now that we're pid 1 (effectively "root") we can mount /proc */ + maybe_xmkdir ("/proc", 0777); + if (mount ("proc", "/proc", "proc", 0, NULL) < 0) + FAIL_EXIT1 ("Unable to mount /proc: "); + + /* We map our original UID to the same UID in the container so we + can own our own files normally. */ + UMAP = open ("/proc/self/uid_map", O_WRONLY); + if (UMAP < 0) + FAIL_EXIT1 ("can't write to /proc/self/uid_map\n"); + + sprintf (tmp, "%lld %lld 1\n", + (long long) original_uid, (long long) original_uid); + write (UMAP, tmp, strlen (tmp)); + xclose (UMAP); + + /* We must disable setgroups () before we can map our groups, else we + get EPERM. */ + GMAP = open ("/proc/self/setgroups", O_WRONLY); + if (GMAP >= 0) + { + /* We support kernels old enough to not have this. */ + write (GMAP, "deny\n", 5); + xclose (GMAP); + } + + /* We map our original GID to the same GID in the container so we + can own our own files normally. */ + GMAP = open ("/proc/self/gid_map", O_WRONLY); + if (GMAP < 0) + FAIL_EXIT1 ("can't write to /proc/self/gid_map\n"); + + sprintf (tmp, "%lld %lld 1\n", + (long long) original_gid, (long long) original_gid); + write (GMAP, tmp, strlen (tmp)); + xclose (GMAP); + + /* Now run the child. */ + execvp (new_child_proc[0], new_child_proc); + + /* Or don't run the child? */ + FAIL_EXIT1 ("Unable to exec %s\n", new_child_proc[0]); + + /* Because gcc won't know error () never returns... */ + exit (EXIT_UNSUPPORTED); +} diff --git a/support/true-container.c b/support/true-container.c new file mode 100644 index 0000000000000000..57dc57e252a96acc --- /dev/null +++ b/support/true-container.c @@ -0,0 +1,26 @@ +/* Minimal /bin/true for in-container use. + Copyright (C) 2018 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +/* Implements the in-container /bin/true, which always returns true + (0). */ + +int +main (void) +{ + return 0; +} diff --git a/support/tst-support_blob_repeat.c b/support/tst-support_blob_repeat.c new file mode 100644 index 0000000000000000..1978c14488106ff2 --- /dev/null +++ b/support/tst-support_blob_repeat.c @@ -0,0 +1,85 @@ +/* Tests for + Copyright (C) 2018 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#include +#include + +static int +do_test (void) +{ + struct support_blob_repeat repeat + = support_blob_repeat_allocate ("5", 1, 5); + TEST_COMPARE_BLOB (repeat.start, repeat.size, "55555", 5); + support_blob_repeat_free (&repeat); + + repeat = support_blob_repeat_allocate ("ABC", 3, 3); + TEST_COMPARE_BLOB (repeat.start, repeat.size, "ABCABCABC", 9); + support_blob_repeat_free (&repeat); + + repeat = support_blob_repeat_allocate ("abc", 4, 3); + TEST_COMPARE_BLOB (repeat.start, repeat.size, "abc\0abc\0abc", 12); + support_blob_repeat_free (&repeat); + + size_t gigabyte = 1U << 30; + repeat = support_blob_repeat_allocate ("X", 1, gigabyte + 1); + if (repeat.start == NULL) + puts ("warning: not enough memory for 1 GiB mapping"); + else + { + TEST_COMPARE (repeat.size, gigabyte + 1); + { + unsigned char *p = repeat.start; + for (size_t i = 0; i < gigabyte + 1; ++i) + if (p[i] != 'X') + FAIL_EXIT1 ("invalid byte 0x%02x at %zu", p[i], i); + + /* Check that there is no sharing across the mapping. */ + p[0] = 'Y'; + p[1U << 24] = 'Z'; + for (size_t i = 0; i < gigabyte + 1; ++i) + if (i == 0) + TEST_COMPARE (p[i], 'Y'); + else if (i == 1U << 24) + TEST_COMPARE (p[i], 'Z'); + else if (p[i] != 'X') + FAIL_EXIT1 ("invalid byte 0x%02x at %zu", p[i], i); + } + } + support_blob_repeat_free (&repeat); + + repeat = support_blob_repeat_allocate ("012345678", 9, 10 * 1000 * 1000); + if (repeat.start == NULL) + puts ("warning: not enough memory for large mapping"); + else + { + unsigned char *p = repeat.start; + for (int i = 0; i < 10 * 1000 * 1000; ++i) + for (int j = 0; j <= 8; ++j) + if (p[i * 9 + j] != '0' + j) + { + printf ("error: element %d index %d\n", i, j); + TEST_COMPARE (p[i * 9 + j], '0' + j); + } + } + support_blob_repeat_free (&repeat); + + return 0; +} + +#include diff --git a/support/tst-test_compare_string.c b/support/tst-test_compare_string.c new file mode 100644 index 0000000000000000..2a4b258587a7c8ec --- /dev/null +++ b/support/tst-test_compare_string.c @@ -0,0 +1,107 @@ +/* Basic test for the TEST_COMPARE_STRING macro. + Copyright (C) 2018 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#include +#include + +static void +subprocess (void *closure) +{ + /* These tests should fail. They were chosen to cover differences + in length (with the same contents), single-bit mismatches, and + mismatching null pointers. */ + TEST_COMPARE_STRING ("", NULL); /* Line 29. */ + TEST_COMPARE_STRING ("X", ""); /* Line 30. */ + TEST_COMPARE_STRING (NULL, "X"); /* Line 31. */ + TEST_COMPARE_STRING ("abcd", "abcD"); /* Line 32. */ + TEST_COMPARE_STRING ("abcd", NULL); /* Line 33. */ + TEST_COMPARE_STRING (NULL, "abcd"); /* Line 34. */ +} + +/* Same contents, different addresses. */ +char buffer_abc_1[] = "abc"; +char buffer_abc_2[] = "abc"; + +static int +do_test (void) +{ + /* This should succeed. Even if the pointers and array contents are + different, zero-length inputs are not different. */ + TEST_COMPARE_STRING (NULL, NULL); + TEST_COMPARE_STRING ("", ""); + TEST_COMPARE_STRING (buffer_abc_1, buffer_abc_2); + TEST_COMPARE_STRING (buffer_abc_1, "abc"); + + struct support_capture_subprocess proc = support_capture_subprocess + (&subprocess, NULL); + + /* Discard the reported error. */ + support_record_failure_reset (); + + puts ("info: *** subprocess output starts ***"); + fputs (proc.out.buffer, stdout); + puts ("info: *** subprocess output ends ***"); + + TEST_VERIFY + (strcmp (proc.out.buffer, +"tst-test_compare_string.c:29: error: blob comparison failed\n" +" left string: 0 bytes\n" +" right string: NULL\n" +"tst-test_compare_string.c:30: error: blob comparison failed\n" +" left string: 1 bytes\n" +" right string: 0 bytes\n" +" left (evaluated from \"X\"):\n" +" \"X\"\n" +" 58\n" +"tst-test_compare_string.c:31: error: blob comparison failed\n" +" left string: NULL\n" +" right string: 1 bytes\n" +" right (evaluated from \"X\"):\n" +" \"X\"\n" +" 58\n" +"tst-test_compare_string.c:32: error: blob comparison failed\n" +" string length: 4 bytes\n" +" left (evaluated from \"abcd\"):\n" +" \"abcd\"\n" +" 61 62 63 64\n" +" right (evaluated from \"abcD\"):\n" +" \"abcD\"\n" +" 61 62 63 44\n" +"tst-test_compare_string.c:33: error: blob comparison failed\n" +" left string: 4 bytes\n" +" right string: NULL\n" +" left (evaluated from \"abcd\"):\n" +" \"abcd\"\n" +" 61 62 63 64\n" +"tst-test_compare_string.c:34: error: blob comparison failed\n" +" left string: NULL\n" +" right string: 4 bytes\n" +" right (evaluated from \"abcd\"):\n" +" \"abcd\"\n" +" 61 62 63 64\n" + ) == 0); + + /* Check that there is no output on standard error. */ + support_capture_subprocess_check (&proc, "TEST_COMPARE_STRING", + 0, sc_allow_stdout); + + return 0; +} + +#include diff --git a/support/xcopy_file_range.c b/support/xcopy_file_range.c new file mode 100644 index 0000000000000000..b3501a4d5ec3fdfd --- /dev/null +++ b/support/xcopy_file_range.c @@ -0,0 +1,32 @@ +/* copy_file_range with error checking. + Copyright (C) 2018 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#include +#include + +ssize_t +xcopy_file_range (int infd, off64_t *pinoff, int outfd, off64_t *poutoff, + size_t length, unsigned int flags) +{ + ssize_t status = support_copy_file_range (infd, pinoff, outfd, + poutoff, length, flags); + if (status == -1) + FAIL_EXIT1 ("cannot copy file: %m\n"); + return status; +} diff --git a/support/xmkdirp.c b/support/xmkdirp.c new file mode 100644 index 0000000000000000..fada0452eafe269e --- /dev/null +++ b/support/xmkdirp.c @@ -0,0 +1,66 @@ +/* Error-checking replacement for "mkdir -p". + Copyright (C) 2018 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#include +#include + +#include +#include +#include + +/* Equivalent of "mkdir -p". Any failures cause FAIL_EXIT1 so no + return code is needed. */ + +void +xmkdirp (const char *path, mode_t mode) +{ + struct stat s; + const char *slash_p; + int rv; + + if (path[0] == 0) + return; + + if (stat (path, &s) == 0) + { + if (S_ISDIR (s.st_mode)) + return; + errno = EEXIST; + FAIL_EXIT1 ("mkdir_p (\"%s\", 0%o): %m", path, mode); + } + + slash_p = strrchr (path, '/'); + if (slash_p != NULL) + { + while (slash_p > path && slash_p[-1] == '/') + --slash_p; + if (slash_p > path) + { + char *parent = xstrndup (path, slash_p - path); + xmkdirp (parent, mode); + free (parent); + } + } + + rv = mkdir (path, mode); + if (rv != 0) + FAIL_EXIT1 ("mkdir_p (\"%s\", 0%o): %m", path, mode); + + return; +} diff --git a/support/xsymlink.c b/support/xsymlink.c new file mode 100644 index 0000000000000000..0f3edf640a1a99a6 --- /dev/null +++ b/support/xsymlink.c @@ -0,0 +1,29 @@ +/* Error-checking replacement for "symlink". + Copyright (C) 2018 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#include + +#include + +void +xsymlink (const char *target, const char *linkpath) +{ + if (symlink (target, linkpath) < 0) + FAIL_EXIT1 ("symlink (\"%s\", \"%s\")", target, linkpath); +} diff --git a/support/xunistd.h b/support/xunistd.h index 5fe5dae818def4ec..f99f362cb4763c5b 100644 --- a/support/xunistd.h +++ b/support/xunistd.h @@ -43,6 +43,10 @@ void xunlink (const char *path); long xsysconf (int name); long long xlseek (int fd, long long offset, int whence); void xftruncate (int fd, long long length); +void xsymlink (const char *target, const char *linkpath); + +/* Equivalent of "mkdir -p". */ +void xmkdirp (const char *, mode_t); /* Read the link at PATH. The caller should free the returned string with free. */ @@ -60,6 +64,9 @@ void *xmmap (void *addr, size_t length, int prot, int flags, int fd); void xmprotect (void *addr, size_t length, int prot); void xmunmap (void *addr, size_t length); +ssize_t xcopy_file_range(int fd_in, loff_t *off_in, int fd_out, + loff_t *off_out, size_t len, unsigned int flags); + __END_DECLS #endif /* SUPPORT_XUNISTD_H */