/* Tests for posix_spawn signal handling. Copyright (C) 2021-2024 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 #include #include #include /* Nonzero if the program gets called via `exec'. */ static int restart; /* Hold the four initial argument used to respawn the process, plus the extra '--direct' and '--restart', and a final NULL. */ static char *initial_argv[7]; static int initial_argv_count; #define CMDLINE_OPTIONS \ { "restart", no_argument, &restart, 1 }, #define NFDS 100 static int parse_fd (const char *str) { char *endptr; long unsigned int fd = strtoul (str, &endptr, 10); if (*endptr != '\0' || fd > INT_MAX) FAIL_EXIT1 ("invalid file descriptor value: %s", str); return fd; } /* Called on process re-execution. The arguments are the expected opened file descriptors. */ _Noreturn static void handle_restart (int argc, char *argv[]) { TEST_VERIFY (argc > 0); int lowfd = parse_fd (argv[0]); size_t nfds = argc > 1 ? argc - 1 : 0; struct fd_t { int fd; _Bool found; } *fds = xmalloc (sizeof (struct fd_t) * nfds); for (int i = 0; i < nfds; i++) { fds[i].fd = parse_fd (argv[i + 1]); fds[i].found = false; } DIR *dirp = opendir (FD_TO_FILENAME_PREFIX); if (dirp == NULL) FAIL_EXIT1 ("opendir (\"" FD_TO_FILENAME_PREFIX "\"): %m"); while (true) { errno = 0; struct dirent64 *e = readdir64 (dirp); if (e == NULL) { if (errno != 0) FAIL_EXIT1 ("readdir: %m"); break; } if (e->d_name[0] == '.') continue; char *endptr; long int fd = strtol (e->d_name, &endptr, 10); if (*endptr != '\0' || fd < 0 || fd > INT_MAX) FAIL_EXIT1 ("readdir: invalid file descriptor name: /proc/self/fd/%s", e->d_name); /* Ignore the descriptors not in the range of the opened files. */ if (fd < lowfd || fd == dirfd (dirp)) continue; bool found = false; for (int i = 0; i < nfds; i++) if (fds[i].fd == fd) fds[i].found = found = true; if (!found) { char *path = xasprintf ("/proc/self/fd/%s", e->d_name); char *resolved = xreadlink (path); FAIL_EXIT1 ("unexpected open file descriptor %ld: %s", fd, resolved); } } closedir (dirp); for (int i = 0; i < nfds; i++) if (!fds[i].found) FAIL_EXIT1 ("file descriptor %d not opened", fds[i].fd); free (fds); exit (EXIT_SUCCESS); } static void spawn_closefrom_test (posix_spawn_file_actions_t *fa, int lowfd, int highfd, int *extrafds, size_t nextrafds) { /* 3 or 7 elements from initial_argv: + path to ld.so optional + --library-path optional + the library path optional + application name + --direct + --restart + lowest opened file descriptor + up to 2 * maximum_fd arguments (the expected open file descriptors), plus NULL. */ int argv_size = initial_argv_count + 2 * NFDS + 1; char *args[argv_size]; int argc = 0; for (char **arg = initial_argv; *arg != NULL; arg++) args[argc++] = *arg; args[argc++] = xasprintf ("%d", lowfd); for (int i = lowfd; i < highfd; i++) args[argc++] = xasprintf ("%d", i); for (int i = 0; i < nextrafds; i++) args[argc++] = xasprintf ("%d", extrafds[i]); args[argc] = NULL; TEST_VERIFY (argc < argv_size); PID_T_TYPE pid; siginfo_t sinfo; TEST_COMPARE (POSIX_SPAWN (&pid, args[0], fa, NULL, args, environ), 0); TEST_COMPARE (WAITID (P_PID, pid, &sinfo, WEXITED), 0); TEST_COMPARE (sinfo.si_code, CLD_EXITED); TEST_COMPARE (sinfo.si_status, 0); } static void do_test_closefrom (void) { int lowfd = support_open_dev_null_range (NFDS, O_RDONLY, 0600); const int half_fd = lowfd + NFDS / 2; /* Close half of the descriptors and check result. */ { posix_spawn_file_actions_t fa; TEST_COMPARE (posix_spawn_file_actions_init (&fa), 0); int ret = posix_spawn_file_actions_addclosefrom_np (&fa, half_fd); if (ret == EINVAL) /* Hurd currently does not support closefrom fileaction. */ FAIL_UNSUPPORTED ("posix_spawn_file_actions_addclosefrom_np unsupported"); TEST_COMPARE (ret, 0); spawn_closefrom_test (&fa, lowfd, half_fd, NULL, 0); TEST_COMPARE (posix_spawn_file_actions_destroy (&fa), 0); } /* Create some gaps, close up to a threshold, and check result. */ xclose (lowfd + 57); xclose (lowfd + 78); xclose (lowfd + 81); xclose (lowfd + 82); xclose (lowfd + 84); xclose (lowfd + 90); { posix_spawn_file_actions_t fa; TEST_COMPARE (posix_spawn_file_actions_init (&fa), 0); TEST_COMPARE (posix_spawn_file_actions_addclosefrom_np (&fa, half_fd), 0); spawn_closefrom_test (&fa, lowfd, half_fd, NULL, 0); TEST_COMPARE (posix_spawn_file_actions_destroy (&fa), 0); } /* Close the remaining but the last one. */ { posix_spawn_file_actions_t fa; TEST_COMPARE (posix_spawn_file_actions_init (&fa), 0); TEST_COMPARE (posix_spawn_file_actions_addclosefrom_np (&fa, lowfd + 1), 0); spawn_closefrom_test (&fa, lowfd, lowfd + 1, NULL, 0); TEST_COMPARE (posix_spawn_file_actions_destroy (&fa), 0); } /* Close everything. */ { posix_spawn_file_actions_t fa; TEST_COMPARE (posix_spawn_file_actions_init (&fa), 0); TEST_COMPARE (posix_spawn_file_actions_addclosefrom_np (&fa, lowfd), 0); spawn_closefrom_test (&fa, lowfd, lowfd, NULL, 0); TEST_COMPARE (posix_spawn_file_actions_destroy (&fa), 0); } /* Close a range and add some file actions. */ { posix_spawn_file_actions_t fa; TEST_COMPARE (posix_spawn_file_actions_init (&fa), 0); TEST_COMPARE (posix_spawn_file_actions_addclosefrom_np (&fa, lowfd + 1), 0); TEST_COMPARE (posix_spawn_file_actions_addopen (&fa, lowfd, "/dev/null", 0666, O_RDONLY), 0); TEST_COMPARE (posix_spawn_file_actions_adddup2 (&fa, lowfd, lowfd + 1), 0); TEST_COMPARE (posix_spawn_file_actions_addopen (&fa, lowfd, "/dev/null", 0666, O_RDONLY), 0); spawn_closefrom_test (&fa, lowfd, lowfd, (int[]){lowfd, lowfd + 1}, 2); TEST_COMPARE (posix_spawn_file_actions_destroy (&fa), 0); } } static int do_test (int argc, char *argv[]) { /* We must have either: - one or four parameters if called initially: + argv[1]: path for ld.so optional + argv[2]: "--library-path" optional + argv[3]: the library path optional + argv[4]: the application name - six parameters left if called through re-execution: + argv[1]: the application name + argv[2]: the lowest file descriptor expected + argv[3]: first expected open file descriptor optional + argv[n]: last expected open file descriptor optional * When built with --enable-hardcoded-path-in-tests or issued without using the loader directly. */ if (restart) /* Ignore the application name. */ handle_restart (argc - 1, &argv[1]); TEST_VERIFY_EXIT (argc == 2 || argc == 5); int i; for (i = 0; i < argc - 1; i++) initial_argv[i] = argv[i + 1]; initial_argv[i++] = (char *) "--direct"; initial_argv[i++] = (char *) "--restart"; initial_argv_count = i; do_test_closefrom (); return 0; } #define TEST_FUNCTION_ARGV do_test #include