]> git.ipfire.org Git - thirdparty/glibc.git/blame - support/test-container.c
support/shell-container.c: Return 127 if execve fails
[thirdparty/glibc.git] / support / test-container.c
CommitLineData
561b0bec 1/* Run a test case in an isolated namespace.
d614a753 2 Copyright (C) 2018-2020 Free Software Foundation, Inc.
561b0bec
DD
3 This file is part of the GNU C Library.
4
5 The GNU C Library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
9
10 The GNU C Library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public
16 License along with the GNU C Library; if not, see
5a82c748 17 <https://www.gnu.org/licenses/>. */
561b0bec
DD
18
19#define _FILE_OFFSET_BITS 64
20
21#include <stdio.h>
22#include <stdlib.h>
23#include <string.h>
24#include <sched.h>
25#include <sys/syscall.h>
26#include <unistd.h>
27#include <sys/types.h>
28#include <dirent.h>
29#include <string.h>
30#include <sys/stat.h>
31#include <sys/fcntl.h>
32#include <sys/file.h>
33#include <sys/wait.h>
34#include <stdarg.h>
35#include <sys/sysmacros.h>
36#include <ctype.h>
37#include <utime.h>
38#include <errno.h>
39#include <error.h>
599f7bee 40#include <libc-pointer-arith.h>
561b0bec
DD
41
42#ifdef __linux__
43#include <sys/mount.h>
44#endif
45
46#include <support/support.h>
47#include <support/xunistd.h>
48#include "check.h"
49#include "test-driver.h"
50
51#ifndef __linux__
52#define mount(s,t,fs,f,d) no_mount()
53int no_mount (void)
54{
55 FAIL_UNSUPPORTED("mount not supported; port needed");
56}
57#endif
58
59int verbose = 0;
60
61/* Running a test in a container is tricky. There are two main
62 categories of things to do:
63
64 1. "Once" actions, like setting up the container and doing an
65 install into it.
66
67 2. "Per-test" actions, like copying in support files and
68 configuring the container.
69
70
71 "Once" actions:
72
73 * mkdir $buildroot/testroot.pristine/
74 * install into it
75 * rsync to $buildroot/testroot.root/
76
77 "Per-test" actions:
78 * maybe rsync to $buildroot/testroot.root/
79 * copy support files and test binary
80 * chroot/unshare
81 * set up any mounts (like /proc)
82
83 Magic files:
84
85 For test $srcdir/foo/mytest.c we look for $srcdir/foo/mytest.root
86 and, if found...
87
88 * mytest.root/ is rsync'd into container
89 * mytest.root/preclean.req causes fresh rsync (with delete) before
90 test if present
b2e93de0 91 * mytest.root/mytest.script has a list of "commands" to run:
561b0bec
DD
92 syntax:
93 # comment
b2e93de0 94 su
561b0bec
DD
95 mv FILE FILE
96 cp FILE FILE
97 rm FILE
4f79b3e2
DD
98 cwd PATH
99 exec FILE
561b0bec
DD
100 FILE must start with $B/, $S/, $I/, $L/, or /
101 (expands to build dir, source dir, install dir, library dir
102 (in container), or container's root)
b2e93de0
DD
103 details:
104 - '#': A comment.
105 - 'su': Enables running test as root in the container.
106 - 'mv': A minimal move files command.
107 - 'cp': A minimal copy files command.
108 - 'rm': A minimal remove files command.
4f79b3e2
DD
109 - 'cwd': set test working directory
110 - 'exec': change test binary location (may end in /)
561b0bec
DD
111 * mytest.root/postclean.req causes fresh rsync (with delete) after
112 test if present
113
114 Note that $srcdir/foo/mytest.script may be used instead of a
115 $srcdir/foo/mytest.root/mytest.script in the sysroot template, if
116 there is no other reason for a sysroot.
117
118 Design goals:
119
120 * independent of other packages which may not be installed (like
121 rsync or Docker, or even "cp")
122
123 * Simple, easy to review code (i.e. prefer simple naive code over
124 complex efficient code)
125
126 * The current implementation ist parallel-make-safe, but only in
127 that it uses a lock to prevent parallel access to the testroot. */
128
129\f
130/* Utility Functions */
131
132/* Like xunlink, but it's OK if the file already doesn't exist. */
133void
134maybe_xunlink (const char *path)
135{
136 int rv = unlink (path);
137 if (rv < 0 && errno != ENOENT)
138 FAIL_EXIT1 ("unlink (\"%s\"): %m", path);
139}
140
141/* Like xmkdir, but it's OK if the directory already exists. */
142void
143maybe_xmkdir (const char *path, mode_t mode)
144{
145 struct stat st;
146
147 if (stat (path, &st) == 0
148 && S_ISDIR (st.st_mode))
149 return;
150 xmkdir (path, mode);
151}
152
153/* Temporarily concatenate multiple strings into one. Allows up to 10
4f79b3e2 154 temporary results; use xstrdup () if you need them to be
561b0bec
DD
155 permanent. */
156static char *
157concat (const char *str, ...)
158{
159 /* Assume initialized to NULL/zero. */
160 static char *bufs[10];
161 static size_t buflens[10];
162 static int bufn = 0;
163 int n;
164 size_t len;
165 va_list ap, ap2;
166 char *cp;
167 char *next;
168
169 va_start (ap, str);
170 va_copy (ap2, ap);
171
172 n = bufn;
173 bufn = (bufn + 1) % 10;
174 len = strlen (str);
175
176 while ((next = va_arg (ap, char *)) != NULL)
177 len = len + strlen (next);
178
179 va_end (ap);
180
181 if (bufs[n] == NULL)
182 {
183 bufs[n] = xmalloc (len + 1); /* NUL */
184 buflens[n] = len + 1;
185 }
186 else if (buflens[n] < len + 1)
187 {
188 bufs[n] = xrealloc (bufs[n], len + 1); /* NUL */
189 buflens[n] = len + 1;
190 }
191
192 strcpy (bufs[n], str);
193 cp = strchr (bufs[n], '\0');
194 while ((next = va_arg (ap2, char *)) != NULL)
195 {
196 strcpy (cp, next);
197 cp = strchr (cp, '\0');
198 }
199 *cp = 0;
200 va_end (ap2);
201
202 return bufs[n];
203}
204
205/* Try to mount SRC onto DEST. */
206static void
207trymount (const char *src, const char *dest)
208{
209 if (mount (src, dest, "", MS_BIND, NULL) < 0)
210 FAIL_EXIT1 ("can't mount %s onto %s\n", src, dest);
211}
212
213/* Special case of above for devices like /dev/zero where we have to
214 mount a device over a device, not a directory over a directory. */
215static void
216devmount (const char *new_root_path, const char *which)
217{
218 int fd;
219 fd = open (concat (new_root_path, "/dev/", which, NULL),
220 O_CREAT | O_TRUNC | O_RDWR, 0777);
221 xclose (fd);
222
223 trymount (concat ("/dev/", which, NULL),
224 concat (new_root_path, "/dev/", which, NULL));
225}
226
227/* Returns true if the string "looks like" an environement variable
228 being set. */
229static int
230is_env_setting (const char *a)
231{
232 int count_name = 0;
233
234 while (*a)
235 {
236 if (isalnum (*a) || *a == '_')
237 ++count_name;
238 else if (*a == '=' && count_name > 0)
239 return 1;
240 else
241 return 0;
242 ++a;
243 }
244 return 0;
245}
246
247/* Break the_line into words and store in the_words. Max nwords,
248 returns actual count. */
249static int
250tokenize (char *the_line, char **the_words, int nwords)
251{
252 int rv = 0;
253
254 while (nwords > 0)
255 {
256 /* Skip leading whitespace, if any. */
257 while (*the_line && isspace (*the_line))
258 ++the_line;
259
260 /* End of line? */
261 if (*the_line == 0)
262 return rv;
263
264 /* THE_LINE points to a non-whitespace character, so we have a
265 word. */
266 *the_words = the_line;
267 ++the_words;
268 nwords--;
269 ++rv;
270
271 /* Skip leading whitespace, if any. */
272 while (*the_line && ! isspace (*the_line))
273 ++the_line;
274
275 /* We now point at the trailing NUL *or* some whitespace. */
276 if (*the_line == 0)
277 return rv;
278
279 /* It was whitespace, skip and keep tokenizing. */
280 *the_line++ = 0;
281 }
282
283 /* We get here if we filled the words buffer. */
284 return rv;
285}
286
287\f
288/* Mini-RSYNC implementation. Optimize later. */
289
290/* A few routines for an "rsync buffer" which stores the paths we're
291 working on. We continuously grow and shrink the paths in each
292 buffer so there's lot of re-use. */
293
294/* We rely on "initialized to zero" to set these up. */
295typedef struct
296{
297 char *buf;
298 size_t len;
299 size_t size;
300} path_buf;
301
302static path_buf spath, dpath;
303
304static void
305r_setup (char *path, path_buf * pb)
306{
307 size_t len = strlen (path);
308 if (pb->buf == NULL || pb->size < len + 1)
309 {
310 /* Round up. This is an arbitrary number, just to keep from
311 reallocing too often. */
312 size_t sz = ALIGN_UP (len + 1, 512);
313 if (pb->buf == NULL)
314 pb->buf = (char *) xmalloc (sz);
315 else
316 pb->buf = (char *) xrealloc (pb->buf, sz);
317 if (pb->buf == NULL)
318 FAIL_EXIT1 ("Out of memory while rsyncing\n");
319
320 pb->size = sz;
321 }
322 strcpy (pb->buf, path);
323 pb->len = len;
324}
325
326static void
327r_append (const char *path, path_buf * pb)
328{
329 size_t len = strlen (path) + pb->len;
330 if (pb->size < len + 1)
331 {
332 /* Round up */
333 size_t sz = ALIGN_UP (len + 1, 512);
334 pb->buf = (char *) xrealloc (pb->buf, sz);
335 if (pb->buf == NULL)
336 FAIL_EXIT1 ("Out of memory while rsyncing\n");
337
338 pb->size = sz;
339 }
340 strcpy (pb->buf + pb->len, path);
341 pb->len = len;
342}
343
344static int
345file_exists (char *path)
346{
347 struct stat st;
348 if (lstat (path, &st) == 0)
349 return 1;
350 return 0;
351}
352
353static void
354recursive_remove (char *path)
355{
356 pid_t child;
357 int status;
358
359 child = fork ();
360
361 switch (child) {
362 case -1:
b2e93de0 363 perror("fork");
561b0bec
DD
364 FAIL_EXIT1 ("Unable to fork");
365 case 0:
366 /* Child. */
367 execlp ("rm", "rm", "-rf", path, NULL);
86140c62 368 FAIL_EXIT1 ("exec rm: %m");
561b0bec
DD
369 default:
370 /* Parent. */
371 waitpid (child, &status, 0);
372 /* "rm" would have already printed a suitable error message. */
373 if (! WIFEXITED (status)
374 || WEXITSTATUS (status) != 0)
375 exit (1);
376
377 break;
378 }
379}
380
381/* Used for both rsync and the mytest.script "cp" command. */
382static void
383copy_one_file (const char *sname, const char *dname)
384{
385 int sfd, dfd;
386 struct stat st;
387 struct utimbuf times;
388
389 sfd = open (sname, O_RDONLY);
390 if (sfd < 0)
391 FAIL_EXIT1 ("unable to open %s for reading\n", sname);
392
393 if (fstat (sfd, &st) < 0)
394 FAIL_EXIT1 ("unable to fstat %s\n", sname);
395
396 dfd = open (dname, O_WRONLY | O_TRUNC | O_CREAT, 0600);
397 if (dfd < 0)
398 FAIL_EXIT1 ("unable to open %s for writing\n", dname);
399
81b9d87b 400 xcopy_file_range (sfd, 0, dfd, 0, st.st_size, 0);
561b0bec
DD
401
402 xclose (sfd);
403 xclose (dfd);
404
405 if (chmod (dname, st.st_mode & 0777) < 0)
406 FAIL_EXIT1 ("chmod %s: %s\n", dname, strerror (errno));
407
408 times.actime = st.st_atime;
409 times.modtime = st.st_mtime;
410 if (utime (dname, &times) < 0)
411 FAIL_EXIT1 ("utime %s: %s\n", dname, strerror (errno));
412}
413
414/* We don't check *everything* about the two files to see if a copy is
415 needed, just the minimum to make sure we get the latest copy. */
416static int
417need_sync (char *ap, char *bp, struct stat *a, struct stat *b)
418{
419 if ((a->st_mode & S_IFMT) != (b->st_mode & S_IFMT))
420 return 1;
421
422 if (S_ISLNK (a->st_mode))
423 {
424 int rv;
425 char *al, *bl;
426
427 if (a->st_size != b->st_size)
428 return 1;
429
430 al = xreadlink (ap);
431 bl = xreadlink (bp);
432 rv = strcmp (al, bl);
433 free (al);
434 free (bl);
435 if (rv == 0)
436 return 0; /* links are same */
437 return 1; /* links differ */
438 }
439
440 if (verbose)
441 {
442 if (a->st_size != b->st_size)
443 printf ("SIZE\n");
444 if ((a->st_mode & 0777) != (b->st_mode & 0777))
445 printf ("MODE\n");
446 if (a->st_mtime != b->st_mtime)
447 printf ("TIME\n");
448 }
449
450 if (a->st_size == b->st_size
451 && ((a->st_mode & 0777) == (b->st_mode & 0777))
452 && a->st_mtime == b->st_mtime)
453 return 0;
454
455 return 1;
456}
457
458static void
459rsync_1 (path_buf * src, path_buf * dest, int and_delete)
460{
461 DIR *dir;
462 struct dirent *de;
463 struct stat s, d;
464
465 r_append ("/", src);
466 r_append ("/", dest);
467
468 if (verbose)
469 printf ("sync %s to %s %s\n", src->buf, dest->buf,
470 and_delete ? "and delete" : "");
471
472 size_t staillen = src->len;
473
474 size_t dtaillen = dest->len;
475
476 dir = opendir (src->buf);
477
478 while ((de = readdir (dir)) != NULL)
479 {
480 if (strcmp (de->d_name, ".") == 0
481 || strcmp (de->d_name, "..") == 0)
482 continue;
483
484 src->len = staillen;
485 r_append (de->d_name, src);
486 dest->len = dtaillen;
487 r_append (de->d_name, dest);
488
489 s.st_mode = ~0;
490 d.st_mode = ~0;
491
492 if (lstat (src->buf, &s) != 0)
493 FAIL_EXIT1 ("%s obtained by readdir, but stat failed.\n", src->buf);
494
495 /* It's OK if this one fails, since we know the file might be
496 missing. */
497 lstat (dest->buf, &d);
498
499 if (! need_sync (src->buf, dest->buf, &s, &d))
500 {
501 if (S_ISDIR (s.st_mode))
502 rsync_1 (src, dest, and_delete);
503 continue;
504 }
505
506 if (d.st_mode != ~0)
507 switch (d.st_mode & S_IFMT)
508 {
509 case S_IFDIR:
510 if (!S_ISDIR (s.st_mode))
511 {
512 if (verbose)
513 printf ("-D %s\n", dest->buf);
514 recursive_remove (dest->buf);
515 }
516 break;
517
518 default:
519 if (verbose)
520 printf ("-F %s\n", dest->buf);
521 maybe_xunlink (dest->buf);
522 break;
523 }
524
525 switch (s.st_mode & S_IFMT)
526 {
527 case S_IFREG:
528 if (verbose)
529 printf ("+F %s\n", dest->buf);
530 copy_one_file (src->buf, dest->buf);
531 break;
532
533 case S_IFDIR:
534 if (verbose)
535 printf ("+D %s\n", dest->buf);
536 maybe_xmkdir (dest->buf, (s.st_mode & 0777) | 0700);
537 rsync_1 (src, dest, and_delete);
538 break;
539
540 case S_IFLNK:
541 {
542 char *lp;
543 if (verbose)
544 printf ("+L %s\n", dest->buf);
545 lp = xreadlink (src->buf);
546 xsymlink (lp, dest->buf);
547 free (lp);
548 break;
549 }
550
551 default:
552 break;
553 }
554 }
555
556 closedir (dir);
557 src->len = staillen;
558 src->buf[staillen] = 0;
559 dest->len = dtaillen;
560 dest->buf[dtaillen] = 0;
561
562 if (!and_delete)
563 return;
564
565 /* The rest of this function removes any files/directories in DEST
566 that do not exist in SRC. This is triggered as part of a
567 preclean or postsclean step. */
568
569 dir = opendir (dest->buf);
570
571 while ((de = readdir (dir)) != NULL)
572 {
573 if (strcmp (de->d_name, ".") == 0
574 || strcmp (de->d_name, "..") == 0)
575 continue;
576
577 src->len = staillen;
578 r_append (de->d_name, src);
579 dest->len = dtaillen;
580 r_append (de->d_name, dest);
581
582 s.st_mode = ~0;
583 d.st_mode = ~0;
584
585 lstat (src->buf, &s);
586
587 if (lstat (dest->buf, &d) != 0)
588 FAIL_EXIT1 ("%s obtained by readdir, but stat failed.\n", dest->buf);
589
590 if (s.st_mode == ~0)
591 {
592 /* dest exists and src doesn't, clean it. */
593 switch (d.st_mode & S_IFMT)
594 {
595 case S_IFDIR:
596 if (!S_ISDIR (s.st_mode))
597 {
598 if (verbose)
599 printf ("-D %s\n", dest->buf);
600 recursive_remove (dest->buf);
601 }
602 break;
603
604 default:
605 if (verbose)
606 printf ("-F %s\n", dest->buf);
607 maybe_xunlink (dest->buf);
608 break;
609 }
610 }
611 }
612
613 closedir (dir);
614}
615
616static void
617rsync (char *src, char *dest, int and_delete)
618{
619 r_setup (src, &spath);
620 r_setup (dest, &dpath);
621
622 rsync_1 (&spath, &dpath, and_delete);
623}
624
625\f
b2e93de0
DD
626
627/* See if we can detect what the user needs to do to get unshare
628 support working for us. */
629void
630check_for_unshare_hints (void)
631{
632 FILE *f;
633 int i;
634
635 /* Default Debian Linux disables user namespaces, but allows a way
636 to enable them. */
637 f = fopen ("/proc/sys/kernel/unprivileged_userns_clone", "r");
638 if (f != NULL)
639 {
640 i = 99; /* Sentinel. */
641 fscanf (f, "%d", &i);
642 if (i == 0)
643 {
644 printf ("To enable test-container, please run this as root:\n");
645 printf (" echo 1 > /proc/sys/kernel/unprivileged_userns_clone\n");
646 }
647 fclose (f);
648 return;
649 }
650
651 /* ALT Linux has an alternate way of doing the same. */
652 f = fopen ("/proc/sys/kernel/userns_restrict", "r");
653 if (f != NULL)
654 {
655 i = 99; /* Sentinel. */
656 fscanf (f, "%d", &i);
657 if (i == 1)
658 {
659 printf ("To enable test-container, please run this as root:\n");
660 printf (" echo 0 > /proc/sys/kernel/userns_restrict\n");
661 }
662 fclose (f);
663 return;
664 }
665}
666
561b0bec
DD
667int
668main (int argc, char **argv)
669{
670 pid_t child;
671 char *pristine_root_path;
672 char *new_root_path;
673 char *new_cwd_path;
674 char *new_objdir_path;
675 char *new_srcdir_path;
676 char **new_child_proc;
4f79b3e2 677 char *new_child_exec;
561b0bec
DD
678 char *command_root;
679 char *command_base;
680 char *command_basename;
681 char *so_base;
682 int do_postclean = 0;
4f79b3e2 683 char *change_cwd = NULL;
561b0bec 684
4052fa22
DD
685 int pipes[2];
686 char pid_buf[20];
687
561b0bec
DD
688 uid_t original_uid;
689 gid_t original_gid;
b2e93de0
DD
690 /* If set, the test runs as root instead of the user running the testsuite. */
691 int be_su = 0;
561b0bec
DD
692 int UMAP;
693 int GMAP;
694 /* Used for "%lld %lld 1" so need not be large. */
695 char tmp[100];
696 struct stat st;
697 int lock_fd;
698
699 setbuf (stdout, NULL);
700
701 /* The command line we're expecting looks like this:
702 env <set some vars> ld.so <library path> test-binary
703
704 We need to peel off any "env" or "ld.so" portion of the command
705 line, and keep track of which env vars we should preserve and
706 which we drop. */
707
708 if (argc < 2)
709 {
4f79b3e2 710 fprintf (stderr, "Usage: test-container <program to run> <args...>\n");
561b0bec
DD
711 exit (1);
712 }
713
714 if (strcmp (argv[1], "-v") == 0)
715 {
716 verbose = 1;
717 ++argv;
718 --argc;
719 }
720
721 if (strcmp (argv[1], "env") == 0)
722 {
723 ++argv;
724 --argc;
725 while (is_env_setting (argv[1]))
726 {
727 /* If there are variables we do NOT want to propogate, this
728 is where the test for them goes. */
729 {
730 /* Need to keep these. Note that putenv stores a
731 pointer to our argv. */
732 putenv (argv[1]);
733 }
734 ++argv;
735 --argc;
736 }
737 }
738
e7624d70 739 if (strcmp (argv[1], support_objdir_elf_ldso) == 0)
561b0bec
DD
740 {
741 ++argv;
742 --argc;
743 while (argv[1][0] == '-')
744 {
745 if (strcmp (argv[1], "--library-path") == 0)
746 {
747 ++argv;
748 --argc;
749 }
750 ++argv;
751 --argc;
752 }
753 }
754
4f79b3e2 755 pristine_root_path = xstrdup (concat (support_objdir_root,
561b0bec 756 "/testroot.pristine", NULL));
4f79b3e2 757 new_root_path = xstrdup (concat (support_objdir_root,
561b0bec
DD
758 "/testroot.root", NULL));
759 new_cwd_path = get_current_dir_name ();
760 new_child_proc = argv + 1;
4f79b3e2 761 new_child_exec = argv[1];
561b0bec
DD
762
763 lock_fd = open (concat (pristine_root_path, "/lock.fd", NULL),
764 O_CREAT | O_TRUNC | O_RDWR, 0666);
765 if (lock_fd < 0)
766 FAIL_EXIT1 ("Cannot create testroot lock.\n");
767
768 while (flock (lock_fd, LOCK_EX) != 0)
769 {
770 if (errno != EINTR)
771 FAIL_EXIT1 ("Cannot lock testroot.\n");
772 }
773
774 xmkdirp (new_root_path, 0755);
775
776 /* We look for extra setup info in a subdir in the same spot as the
777 test, with the same name but a ".root" extension. This is that
778 directory. We try to look in the source tree if the path we're
779 given refers to the build tree, but we rely on the path to be
780 absolute. This is what the glibc makefiles do. */
781 command_root = concat (argv[1], ".root", NULL);
782 if (strncmp (command_root, support_objdir_root,
783 strlen (support_objdir_root)) == 0
784 && command_root[strlen (support_objdir_root)] == '/')
785 command_root = concat (support_srcdir_root,
786 argv[1] + strlen (support_objdir_root),
787 ".root", NULL);
4f79b3e2 788 command_root = xstrdup (command_root);
561b0bec
DD
789
790 /* This cuts off the ".root" we appended above. */
4f79b3e2 791 command_base = xstrdup (command_root);
561b0bec
DD
792 command_base[strlen (command_base) - 5] = 0;
793
794 /* This is the basename of the test we're running. */
795 command_basename = strrchr (command_base, '/');
796 if (command_basename == NULL)
797 command_basename = command_base;
798 else
799 ++command_basename;
800
801 /* Shared object base directory. */
4f79b3e2 802 so_base = xstrdup (argv[1]);
561b0bec
DD
803 if (strrchr (so_base, '/') != NULL)
804 strrchr (so_base, '/')[1] = 0;
805
806 if (file_exists (concat (command_root, "/postclean.req", NULL)))
807 do_postclean = 1;
808
809 rsync (pristine_root_path, new_root_path,
810 file_exists (concat (command_root, "/preclean.req", NULL)));
811
812 if (stat (command_root, &st) >= 0
813 && S_ISDIR (st.st_mode))
814 rsync (command_root, new_root_path, 0);
815
4f79b3e2 816 new_objdir_path = xstrdup (concat (new_root_path,
561b0bec 817 support_objdir_root, NULL));
4f79b3e2 818 new_srcdir_path = xstrdup (concat (new_root_path,
561b0bec
DD
819 support_srcdir_root, NULL));
820
821 /* new_cwd_path starts with '/' so no "/" needed between the two. */
822 xmkdirp (concat (new_root_path, new_cwd_path, NULL), 0755);
823 xmkdirp (new_srcdir_path, 0755);
824 xmkdirp (new_objdir_path, 0755);
825
826 original_uid = getuid ();
827 original_gid = getgid ();
828
829 /* Handle the cp/mv/rm "script" here. */
830 {
831 char *the_line = NULL;
832 size_t line_len = 0;
833 char *fname = concat (command_root, "/",
834 command_basename, ".script", NULL);
835 char *the_words[3];
836 FILE *f = fopen (fname, "r");
837
838 if (verbose && f)
839 fprintf (stderr, "running %s\n", fname);
840
841 if (f == NULL)
842 {
843 /* Try foo.script instead of foo.root/foo.script, as a shortcut. */
844 fname = concat (command_base, ".script", NULL);
845 f = fopen (fname, "r");
846 if (verbose && f)
847 fprintf (stderr, "running %s\n", fname);
848 }
849
850 /* Note that we do NOT look for a Makefile-generated foo.script in
851 the build directory. If that is ever needed, this is the place
852 to add it. */
853
854 /* This is where we "interpret" the mini-script which is <test>.script. */
855 if (f != NULL)
856 {
857 while (getline (&the_line, &line_len, f) > 0)
858 {
859 int nt = tokenize (the_line, the_words, 3);
860 int i;
861
862 for (i = 1; i < nt; ++i)
863 {
864 if (memcmp (the_words[i], "$B/", 3) == 0)
865 the_words[i] = concat (support_objdir_root,
866 the_words[i] + 2, NULL);
867 else if (memcmp (the_words[i], "$S/", 3) == 0)
868 the_words[i] = concat (support_srcdir_root,
869 the_words[i] + 2, NULL);
870 else if (memcmp (the_words[i], "$I/", 3) == 0)
871 the_words[i] = concat (new_root_path,
872 support_install_prefix,
873 the_words[i] + 2, NULL);
874 else if (memcmp (the_words[i], "$L/", 3) == 0)
875 the_words[i] = concat (new_root_path,
876 support_libdir_prefix,
877 the_words[i] + 2, NULL);
4f79b3e2
DD
878 /* "exec" and "cwd" use inside-root paths. */
879 else if (strcmp (the_words[0], "exec") != 0
880 && strcmp (the_words[0], "cwd") != 0
881 && the_words[i][0] == '/')
561b0bec
DD
882 the_words[i] = concat (new_root_path,
883 the_words[i], NULL);
884 }
885
886 if (nt == 3 && the_words[2][strlen (the_words[2]) - 1] == '/')
887 {
888 char *r = strrchr (the_words[1], '/');
889 if (r)
890 the_words[2] = concat (the_words[2], r + 1, NULL);
891 else
892 the_words[2] = concat (the_words[2], the_words[1], NULL);
893 }
894
895 if (nt == 2 && strcmp (the_words[0], "so") == 0)
896 {
897 the_words[2] = concat (new_root_path, support_libdir_prefix,
898 "/", the_words[1], NULL);
899 the_words[1] = concat (so_base, the_words[1], NULL);
900 copy_one_file (the_words[1], the_words[2]);
901 }
902 else if (nt == 3 && strcmp (the_words[0], "cp") == 0)
903 {
904 copy_one_file (the_words[1], the_words[2]);
905 }
906 else if (nt == 3 && strcmp (the_words[0], "mv") == 0)
907 {
908 if (rename (the_words[1], the_words[2]) < 0)
909 FAIL_EXIT1 ("rename %s -> %s: %s", the_words[1],
910 the_words[2], strerror (errno));
911 }
912 else if (nt == 3 && strcmp (the_words[0], "chmod") == 0)
913 {
914 long int m;
915 m = strtol (the_words[1], NULL, 0);
916 if (chmod (the_words[2], m) < 0)
917 FAIL_EXIT1 ("chmod %s: %s\n",
918 the_words[2], strerror (errno));
919
920 }
921 else if (nt == 2 && strcmp (the_words[0], "rm") == 0)
922 {
923 maybe_xunlink (the_words[1]);
924 }
4f79b3e2
DD
925 else if (nt >= 2 && strcmp (the_words[0], "exec") == 0)
926 {
927 /* The first argument is the desired location and name
928 of the test binary as we wish to exec it; we will
929 copy the binary there. The second (optional)
930 argument is the value to pass as argv[0], it
931 defaults to the same as the first argument. */
932 char *new_exec_path = the_words[1];
933
934 /* If the new exec path ends with a slash, that's the
935 * directory, and use the old test base name. */
936 if (new_exec_path [strlen(new_exec_path) - 1] == '/')
937 new_exec_path = concat (new_exec_path,
938 basename (new_child_proc[0]),
939 NULL);
940
941
942 /* new_child_proc is in the build tree, so has the
943 same path inside the chroot as outside. The new
944 exec path is, by definition, relative to the
945 chroot. */
946 copy_one_file (new_child_proc[0], concat (new_root_path,
947 new_exec_path,
948 NULL));
949
950 new_child_exec = xstrdup (new_exec_path);
951 if (the_words[2])
952 new_child_proc[0] = xstrdup (the_words[2]);
953 else
954 new_child_proc[0] = new_child_exec;
955 }
956 else if (nt == 2 && strcmp (the_words[0], "cwd") == 0)
957 {
958 change_cwd = xstrdup (the_words[1]);
959 }
b2e93de0
DD
960 else if (nt == 1 && strcmp (the_words[0], "su") == 0)
961 {
962 be_su = 1;
963 }
561b0bec
DD
964 else if (nt > 0 && the_words[0][0] != '#')
965 {
4f79b3e2
DD
966 fprintf (stderr, "\033[31minvalid [%s]\033[0m\n", the_words[0]);
967 exit (1);
561b0bec
DD
968 }
969 }
970 fclose (f);
971 }
972 }
973
304c61a2
DD
974 if (do_postclean)
975 {
976 pid_t pc_pid = fork ();
977
978 if (pc_pid < 0)
979 {
980 FAIL_EXIT1 ("Can't fork for post-clean");
981 }
982 else if (pc_pid > 0)
983 {
984 /* Parent. */
985 int status;
986 waitpid (pc_pid, &status, 0);
987
988 /* Child has exited, we can post-clean the test root. */
989 printf("running post-clean rsync\n");
990 rsync (pristine_root_path, new_root_path, 1);
991
992 if (WIFEXITED (status))
993 exit (WEXITSTATUS (status));
994
995 if (WIFSIGNALED (status))
996 {
997 printf ("%%SIGNALLED%%\n");
998 exit (77);
999 }
1000
1001 printf ("%%EXITERROR%%\n");
1002 exit (78);
1003 }
1004
1005 /* Child continues. */
1006 }
1007
1008 /* This is the last point in the program where we're still in the
1009 "normal" namespace. */
1010
561b0bec
DD
1011#ifdef CLONE_NEWNS
1012 /* The unshare here gives us our own spaces and capabilities. */
1013 if (unshare (CLONE_NEWUSER | CLONE_NEWPID | CLONE_NEWNS) < 0)
1014 {
d6c44c3d
FW
1015 /* Older kernels may not support all the options, or security
1016 policy may block this call. */
1017 if (errno == EINVAL || errno == EPERM)
b2e93de0
DD
1018 {
1019 int saved_errno = errno;
1020 if (errno == EPERM)
1021 check_for_unshare_hints ();
1022 FAIL_UNSUPPORTED ("unable to unshare user/fs: %s", strerror (saved_errno));
1023 }
561b0bec
DD
1024 else
1025 FAIL_EXIT1 ("unable to unshare user/fs: %s", strerror (errno));
1026 }
1027#else
1028 /* Some targets may not support unshare at all. */
1029 FAIL_UNSUPPORTED ("unshare support missing");
1030#endif
1031
1032 /* Some systems, by default, all mounts leak out of the namespace. */
1033 if (mount ("none", "/", NULL, MS_REC | MS_PRIVATE, NULL) != 0)
1034 FAIL_EXIT1 ("could not create a private mount namespace\n");
1035
1036 trymount (support_srcdir_root, new_srcdir_path);
1037 trymount (support_objdir_root, new_objdir_path);
1038
1039 xmkdirp (concat (new_root_path, "/dev", NULL), 0755);
1040 devmount (new_root_path, "null");
1041 devmount (new_root_path, "zero");
1042 devmount (new_root_path, "urandom");
1043
1044 /* We're done with the "old" root, switch to the new one. */
1045 if (chroot (new_root_path) < 0)
1046 FAIL_EXIT1 ("Can't chroot to %s - ", new_root_path);
1047
1048 if (chdir (new_cwd_path) < 0)
1049 FAIL_EXIT1 ("Can't cd to new %s - ", new_cwd_path);
1050
4052fa22
DD
1051 /* This is to pass the "outside" PID to the child, which will be PID
1052 1. */
1053 if (pipe2 (pipes, O_CLOEXEC) < 0)
1054 FAIL_EXIT1 ("Can't create pid pipe");
1055
561b0bec
DD
1056 /* To complete the containerization, we need to fork () at least
1057 once. We can't exec, nor can we somehow link the new child to
1058 our parent. So we run the child and propogate it's exit status
1059 up. */
1060 child = fork ();
1061 if (child < 0)
1062 FAIL_EXIT1 ("Unable to fork");
1063 else if (child > 0)
1064 {
1065 /* Parent. */
1066 int status;
4052fa22
DD
1067
1068 /* Send the child's "outside" pid to it. */
1069 write (pipes[1], &child, sizeof(child));
1070 close (pipes[0]);
1071 close (pipes[1]);
1072
561b0bec
DD
1073 waitpid (child, &status, 0);
1074
561b0bec
DD
1075 if (WIFEXITED (status))
1076 exit (WEXITSTATUS (status));
1077
1078 if (WIFSIGNALED (status))
1079 {
1080 printf ("%%SIGNALLED%%\n");
1081 exit (77);
1082 }
1083
1084 printf ("%%EXITERROR%%\n");
1085 exit (78);
1086 }
1087
1088 /* The rest is the child process, which is now PID 1 and "in" the
1089 new root. */
1090
4052fa22
DD
1091 /* Get our "outside" pid from our parent. We use this to help with
1092 debugging from outside the container. */
1093 read (pipes[0], &child, sizeof(child));
1094 close (pipes[0]);
1095 close (pipes[1]);
1096 sprintf (pid_buf, "%lu", (long unsigned)child);
1097 setenv ("PID_OUTSIDE_CONTAINER", pid_buf, 0);
1098
561b0bec
DD
1099 maybe_xmkdir ("/tmp", 0755);
1100
1101 /* Now that we're pid 1 (effectively "root") we can mount /proc */
1102 maybe_xmkdir ("/proc", 0777);
1103 if (mount ("proc", "/proc", "proc", 0, NULL) < 0)
1104 FAIL_EXIT1 ("Unable to mount /proc: ");
1105
1106 /* We map our original UID to the same UID in the container so we
1107 can own our own files normally. */
1108 UMAP = open ("/proc/self/uid_map", O_WRONLY);
1109 if (UMAP < 0)
1110 FAIL_EXIT1 ("can't write to /proc/self/uid_map\n");
1111
1112 sprintf (tmp, "%lld %lld 1\n",
b2e93de0 1113 (long long) (be_su ? 0 : original_uid), (long long) original_uid);
561b0bec
DD
1114 write (UMAP, tmp, strlen (tmp));
1115 xclose (UMAP);
1116
1117 /* We must disable setgroups () before we can map our groups, else we
1118 get EPERM. */
1119 GMAP = open ("/proc/self/setgroups", O_WRONLY);
1120 if (GMAP >= 0)
1121 {
1122 /* We support kernels old enough to not have this. */
1123 write (GMAP, "deny\n", 5);
1124 xclose (GMAP);
1125 }
1126
1127 /* We map our original GID to the same GID in the container so we
1128 can own our own files normally. */
1129 GMAP = open ("/proc/self/gid_map", O_WRONLY);
1130 if (GMAP < 0)
1131 FAIL_EXIT1 ("can't write to /proc/self/gid_map\n");
1132
1133 sprintf (tmp, "%lld %lld 1\n",
b2e93de0 1134 (long long) (be_su ? 0 : original_gid), (long long) original_gid);
561b0bec
DD
1135 write (GMAP, tmp, strlen (tmp));
1136 xclose (GMAP);
1137
4f79b3e2
DD
1138 if (change_cwd)
1139 {
1140 if (chdir (change_cwd) < 0)
1141 FAIL_EXIT1 ("Can't cd to %s inside container - ", change_cwd);
1142 }
1143
561b0bec 1144 /* Now run the child. */
4f79b3e2 1145 execvp (new_child_exec, new_child_proc);
561b0bec
DD
1146
1147 /* Or don't run the child? */
083d644d 1148 FAIL_EXIT1 ("Unable to exec %s: %s\n", new_child_exec, strerror (errno));
561b0bec
DD
1149
1150 /* Because gcc won't know error () never returns... */
1151 exit (EXIT_UNSUPPORTED);
1152}