blob: 372ff1d4b5c8359bddec16d0e577df6902d5584e [file] [log] [blame]
/* Test of execute.
Copyright (C) 2020-2021 Free Software Foundation, Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, see <https://www.gnu.org/licenses/>. */
#include <config.h>
#include "execute.h"
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#if defined _WIN32 && ! defined __CYGWIN__
/* Get _isatty, _getcwd. */
# include <io.h>
#endif
#include "read-file.h"
#include "macros.h"
/* The name of the "always silent" device. */
#if defined _WIN32 && ! defined __CYGWIN__
/* Native Windows API. */
# define DEV_NULL "NUL"
#else
/* Unix API. */
# define DEV_NULL "/dev/null"
#endif
#define BASE "test-execute"
int
main (int argc, char *argv[])
{
if (argc != 3)
{
fprintf (stderr, "%s: need 2 arguments\n", argv[0]);
return 2;
}
char *prog_path = argv[1];
const char *progname = "test-execute-child";
int test = atoi (argv[2]);
switch (test)
{
case 14:
case 15:
case 16:
/* Close file descriptors that have been inherited from the parent
process and that would cause failures in test-execute-child.c.
Such file descriptors have been seen:
- with GNU make, when invoked as 'make -j N' with j > 1,
- in some versions of the KDE desktop environment,
- on NetBSD,
- in MacPorts with the "trace mode" enabled.
*/
#if HAVE_CLOSE_RANGE
if (close_range (3, 20 - 1, 0) < 0)
#endif
{
int fd;
for (fd = 3; fd < 20; fd++)
close (fd);
}
default:
break;
}
switch (test)
{
case 0:
{
/* Check an invocation without arguments. Check the exit code. */
const char *prog_argv[2] = { prog_path, NULL };
int ret = execute (progname, prog_argv[0], prog_argv, NULL,
false, false, false, false, true, false, NULL);
ASSERT (ret == 40);
}
break;
case 1:
{
/* Check an invocation of a non-existent program. */
const char *prog_argv[3] = { "./non-existent", NULL };
int ret = execute (progname, prog_argv[0], prog_argv, NULL,
false, false, false, false, true, false, NULL);
ASSERT (ret == 127);
}
break;
case 2:
{
/* Check argument passing. */
const char *prog_argv[13] =
{
prog_path,
"2",
"abc def",
"abc\"def\"ghi",
"xyz\"",
"abc\\def\\ghi",
"xyz\\",
"???",
"***",
"",
"foo",
"",
NULL
};
int ret = execute (progname, prog_argv[0], prog_argv, NULL,
false, false, false, false, true, false, NULL);
ASSERT (ret == 0);
}
break;
case 3:
#if !(defined _WIN32 && !defined __CYGWIN__)
{
/* Check SIGPIPE handling with ignore_sigpipe = false. */
const char *prog_argv[3] = { prog_path, "3", NULL };
int termsig = 0x7DEADBEE;
int ret = execute (progname, prog_argv[0], prog_argv, NULL,
false, false, false, false, true, false, &termsig);
ASSERT (ret == 127);
ASSERT (termsig == SIGPIPE);
}
#endif
break;
case 4:
#if !(defined _WIN32 && !defined __CYGWIN__)
{
/* Check SIGPIPE handling with ignore_sigpipe = true. */
const char *prog_argv[3] = { prog_path, "4", NULL };
int termsig = 0x7DEADBEE;
int ret = execute (progname, prog_argv[0], prog_argv, NULL,
true, false, false, false, true, false, &termsig);
ASSERT (ret == 0);
ASSERT (termsig == SIGPIPE);
}
#endif
break;
case 5:
{
/* Check other signal. */
const char *prog_argv[3] = { prog_path, "5", NULL };
int termsig = 0x7DEADBEE;
int ret = execute (progname, prog_argv[0], prog_argv, NULL,
false, false, false, false, true, false, &termsig);
ASSERT (ret == 127);
#if defined _WIN32 && !defined __CYGWIN__
ASSERT (termsig == SIGTERM); /* dummy, from WTERMSIG in <sys/wait.h> */
#else
ASSERT (termsig == SIGINT);
#endif
}
break;
case 6:
{
/* Check stdin is inherited. */
FILE *fp = fopen (BASE ".tmp", "w");
fputs ("Foo", fp);
ASSERT (fclose (fp) == 0);
fp = freopen (BASE ".tmp", "r", stdin);
ASSERT (fp != NULL);
const char *prog_argv[3] = { prog_path, "6", NULL };
int ret = execute (progname, prog_argv[0], prog_argv, NULL,
false, false, false, false, true, false, NULL);
ASSERT (ret == 0);
ASSERT (fclose (stdin) == 0);
ASSERT (remove (BASE ".tmp") == 0);
}
break;
case 7:
{
/* Check null_stdin = true. */
FILE *fp = fopen (BASE ".tmp", "w");
fputs ("Foo", fp);
ASSERT (fclose (fp) == 0);
fp = freopen (BASE ".tmp", "r", stdin);
ASSERT (fp != NULL);
const char *prog_argv[3] = { prog_path, "7", NULL };
int ret = execute (progname, prog_argv[0], prog_argv, NULL,
false, true, false, false, true, false, NULL);
ASSERT (ret == 0);
ASSERT (fclose (stdin) == 0);
ASSERT (remove (BASE ".tmp") == 0);
}
break;
case 8:
{
/* Check stdout is inherited, part 1 (regular file). */
FILE *fp = freopen (BASE ".tmp", "w", stdout);
ASSERT (fp != NULL);
const char *prog_argv[3] = { prog_path, "8", NULL };
int ret = execute (progname, prog_argv[0], prog_argv, NULL,
false, false, false, false, true, false, NULL);
ASSERT (ret == 0);
ASSERT (fclose (stdout) == 0);
size_t length;
char *contents = read_file (BASE ".tmp", 0, &length);
ASSERT (length == 3 && memcmp (contents, "bar", 3) == 0);
ASSERT (remove (BASE ".tmp") == 0);
}
break;
case 9:
{
/* Check stdout is inherited, part 2 (device). */
FILE *fp = freopen (DEV_NULL, "w", stdout);
ASSERT (fp != NULL);
const char *prog_argv[3] = { prog_path, "9", NULL };
int ret = execute (progname, prog_argv[0], prog_argv, NULL,
false, false, false, false, true, false, NULL);
ASSERT (ret == 0);
}
break;
case 10:
{
/* Check null_stdout = true. */
FILE *fp = freopen (BASE ".tmp", "w", stdout);
ASSERT (fp != NULL);
const char *prog_argv[3] = { prog_path, "10", NULL };
int ret = execute (progname, prog_argv[0], prog_argv, NULL,
false, false, true, false, true, false, NULL);
ASSERT (ret == 0);
ASSERT (fclose (stdout) == 0);
size_t length;
(void) read_file (BASE ".tmp", 0, &length);
ASSERT (length == 0);
ASSERT (remove (BASE ".tmp") == 0);
}
break;
case 11:
{
/* Check stderr is inherited, part 1 (regular file). */
FILE *fp = freopen (BASE ".tmp", "w", stderr);
ASSERT (fp != NULL);
const char *prog_argv[3] = { prog_path, "11", NULL };
int ret = execute (progname, prog_argv[0], prog_argv, NULL,
false, false, false, false, true, false, NULL);
ASSERT (ret == 0);
ASSERT (fclose (stderr) == 0);
size_t length;
char *contents = read_file (BASE ".tmp", 0, &length);
ASSERT (length == 3 && memcmp (contents, "bar", 3) == 0);
ASSERT (remove (BASE ".tmp") == 0);
}
break;
case 12:
{
/* Check stderr is inherited, part 2 (device). */
FILE *fp = freopen (DEV_NULL, "w", stderr);
ASSERT (fp != NULL);
const char *prog_argv[3] = { prog_path, "12", NULL };
int ret = execute (progname, prog_argv[0], prog_argv, NULL,
false, false, false, false, true, false, NULL);
ASSERT (ret == 0);
}
break;
case 13:
{
/* Check null_stderr = true. */
FILE *fp = freopen (BASE ".tmp", "w", stderr);
ASSERT (fp != NULL);
const char *prog_argv[3] = { prog_path, "13", NULL };
int ret = execute (progname, prog_argv[0], prog_argv, NULL,
false, false, false, true, true, false, NULL);
ASSERT (ret == 0);
ASSERT (fclose (stderr) == 0);
size_t length;
(void) read_file (BASE ".tmp", 0, &length);
ASSERT (length == 0);
ASSERT (remove (BASE ".tmp") == 0);
}
break;
case 14:
{
/* Check file descriptors >= 3 can be inherited. */
ASSERT (dup2 (STDOUT_FILENO, 10) >= 0);
const char *prog_argv[3] = { prog_path, "14", NULL };
int ret = execute (progname, prog_argv[0], prog_argv, NULL,
true, false, false, false, true, false, NULL);
ASSERT (ret == 0);
}
break;
case 15:
{
/* Check file descriptors >= 3 can be inherited. */
ASSERT (fcntl (STDOUT_FILENO, F_DUPFD, 10) >= 0);
const char *prog_argv[3] = { prog_path, "15", NULL };
int ret = execute (progname, prog_argv[0], prog_argv, NULL,
true, false, false, false, true, false, NULL);
ASSERT (ret == 0);
}
break;
case 16:
{
/* Check file descriptors >= 3 with O_CLOEXEC bit are not inherited. */
ASSERT (fcntl (STDOUT_FILENO, F_DUPFD_CLOEXEC, 10) >= 0);
const char *prog_argv[3] = { prog_path, "16", NULL };
int ret = execute (progname, prog_argv[0], prog_argv, NULL,
true, false, false, false, true, false, NULL);
ASSERT (ret == 0);
}
break;
case 17:
{
/* Check that file descriptors >= 3, open for reading, can be inherited,
including the file position. */
FILE *fp = fopen (BASE ".tmp", "w");
fputs ("Foobar", fp);
ASSERT (fclose (fp) == 0);
int fd = open (BASE ".tmp", O_RDONLY);
ASSERT (fd >= 0 && fd < 10);
ASSERT (dup2 (fd, 10) >= 0);
close (fd);
fd = 10;
char buf[2];
ASSERT (read (fd, buf, sizeof (buf)) == sizeof (buf));
/* The file position is now 2. */
const char *prog_argv[3] = { prog_path, "17", NULL };
int ret = execute (progname, prog_argv[0], prog_argv, NULL,
false, false, false, false, true, false, NULL);
ASSERT (ret == 0);
close (fd);
ASSERT (remove (BASE ".tmp") == 0);
}
break;
case 18:
{
/* Check that file descriptors >= 3, open for writing, can be inherited,
including the file position. */
remove (BASE ".tmp");
int fd = open (BASE ".tmp", O_RDWR | O_CREAT | O_TRUNC, 0600);
ASSERT (fd >= 0 && fd < 10);
ASSERT (dup2 (fd, 10) >= 0);
close (fd);
fd = 10;
ASSERT (write (fd, "Foo", 3) == 3);
/* The file position is now 3. */
const char *prog_argv[3] = { prog_path, "18", NULL };
int ret = execute (progname, prog_argv[0], prog_argv, NULL,
false, false, false, false, true, false, NULL);
ASSERT (ret == 0);
close (fd);
size_t length;
char *contents = read_file (BASE ".tmp", 0, &length);
ASSERT (length == 6 && memcmp (contents, "Foobar", 6) == 0);
ASSERT (remove (BASE ".tmp") == 0);
}
break;
case 19:
{
/* Check that file descriptors >= 3, when inherited, preserve their
isatty() property, part 1 (regular file). */
FILE *fp = fopen (BASE ".tmp", "w");
fputs ("Foo", fp);
ASSERT (fclose (fp) == 0);
int fd_in = open (BASE ".tmp", O_RDONLY);
ASSERT (fd_in >= 0 && fd_in < 10);
int fd_out = open (BASE ".tmp", O_WRONLY | O_APPEND);
ASSERT (fd_out >= 0 && fd_out < 10);
ASSERT (dup2 (fd_in, 10) >= 0);
close (fd_in);
fd_in = 10;
ASSERT (dup2 (fd_out, 11) >= 0);
close (fd_out);
fd_out = 11;
const char *prog_argv[3] = { prog_path, "19", NULL };
int ret = execute (progname, prog_argv[0], prog_argv, NULL,
false, false, false, false, true, false, NULL);
#if defined _WIN32 && ! defined __CYGWIN__
ASSERT (ret == 4 + 2 * (_isatty (10) != 0) + (_isatty (11) != 0));
#else
ASSERT (ret == 4 + 2 * (isatty (10) != 0) + (isatty (11) != 0));
#endif
close (fd_in);
close (fd_out);
ASSERT (remove (BASE ".tmp") == 0);
}
break;
case 20:
{
/* Check that file descriptors >= 3, when inherited, preserve their
isatty() property, part 2 (character devices). */
ASSERT (dup2 (STDIN_FILENO, 10) >= 0);
int fd_in = 10;
ASSERT (dup2 (STDOUT_FILENO, 11) >= 0);
int fd_out = 11;
const char *prog_argv[3] = { prog_path, "20", NULL };
int ret = execute (progname, prog_argv[0], prog_argv, NULL,
false, false, false, false, true, false, NULL);
#if defined _WIN32 && ! defined __CYGWIN__
ASSERT (ret == 4 + 2 * (_isatty (10) != 0) + (_isatty (11) != 0));
#else
ASSERT (ret == 4 + 2 * (isatty (10) != 0) + (isatty (11) != 0));
#endif
close (fd_in);
close (fd_out);
}
break;
case 21:
{
/* Check execution in a different directory. */
rmdir (BASE ".sub");
ASSERT (mkdir (BASE ".sub", 0700) == 0);
char cwd[1024];
#if defined _WIN32 && ! defined __CYGWIN__
ASSERT (_getcwd (cwd, sizeof (cwd)) != NULL);
#else
ASSERT (getcwd (cwd, sizeof (cwd)) != NULL);
#endif
const char *prog_argv[4] = { prog_path, "21", cwd, NULL };
int ret = execute (progname, prog_argv[0], prog_argv, BASE ".sub",
false, false, false, false, true, false, NULL);
ASSERT (ret == 0);
ASSERT (rmdir (BASE ".sub") == 0);
}
break;
default:
ASSERT (false);
}
return 0;
}