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