2017-07-21 07:43:36 -07:00
|
|
|
/**************************************************************************/
|
|
|
|
/* */
|
|
|
|
/* OCaml */
|
|
|
|
/* */
|
|
|
|
/* Sebastien Hinderer, projet Gallium, INRIA Paris */
|
|
|
|
/* */
|
|
|
|
/* Copyright 2016 Institut National de Recherche en Informatique et */
|
|
|
|
/* en Automatique. */
|
|
|
|
/* */
|
|
|
|
/* All rights reserved. This file is distributed under the terms of */
|
|
|
|
/* the GNU Lesser General Public License version 2.1, with the */
|
|
|
|
/* special exception on linking described in the file LICENSE. */
|
|
|
|
/* */
|
|
|
|
/**************************************************************************/
|
|
|
|
|
|
|
|
/* Run programs with rediretions and timeouts under Unix */
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <limits.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/wait.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <stdarg.h>
|
|
|
|
#include <signal.h>
|
|
|
|
|
|
|
|
#include "run.h"
|
|
|
|
#include "run_common.h"
|
|
|
|
|
|
|
|
#define COREFILENAME "core"
|
|
|
|
|
|
|
|
static volatile int timeout_expired = 0;
|
|
|
|
|
|
|
|
#define error(msg, ...) \
|
|
|
|
error_with_location(__FILE__, __LINE__, settings, msg, ## __VA_ARGS__)
|
|
|
|
|
|
|
|
/*
|
|
|
|
Note: the ## __VA_ARGS__ construct is gcc specific.
|
|
|
|
For a more portable (but also more complex) solution, see
|
|
|
|
http://stackoverflow.com/questions/20818800/variadic-macro-and-trailing-comma
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void myperror_with_location(
|
|
|
|
const char *file, int line,
|
|
|
|
const command_settings *settings,
|
|
|
|
const char *msg, ...)
|
|
|
|
{
|
|
|
|
va_list ap;
|
|
|
|
Logger *logger = (settings->logger != NULL) ? settings->logger
|
|
|
|
: defaultLogger;
|
|
|
|
void *loggerData = settings->loggerData;
|
|
|
|
va_start(ap, msg);
|
|
|
|
mylog(logger, loggerData, "%s:%d: ", file, line);
|
|
|
|
logger(loggerData, msg, ap);
|
|
|
|
mylog(logger, loggerData, ": %s\n", strerror(errno));
|
|
|
|
va_end(ap);
|
|
|
|
}
|
|
|
|
|
|
|
|
#define myperror(msg, ...) \
|
|
|
|
myperror_with_location(__FILE__, __LINE__, settings, msg, ## __VA_ARGS__)
|
|
|
|
|
|
|
|
/* Same remark as for the error macro. */
|
|
|
|
|
2017-11-28 10:35:05 -08:00
|
|
|
#define child_error(msg, ...) \
|
|
|
|
myperror(msg, ## __VA_ARGS__); \
|
|
|
|
goto child_failed;
|
|
|
|
|
2017-07-21 07:43:36 -07:00
|
|
|
static void open_error_with_location(
|
|
|
|
const char *file, int line,
|
|
|
|
const command_settings *settings,
|
|
|
|
const char *msg)
|
|
|
|
{
|
|
|
|
myperror_with_location(file, line, settings, "Can not open %s", msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
#define open_error(filename) \
|
|
|
|
open_error_with_location(__FILE__, __LINE__, settings, filename)
|
|
|
|
|
|
|
|
static void realpath_error_with_location(
|
|
|
|
const char *file, int line,
|
|
|
|
const command_settings *settings,
|
|
|
|
const char *msg)
|
|
|
|
{
|
|
|
|
myperror_with_location(file, line, settings, "realpath(\"%s\") failed", msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
#define realpath_error(filename) \
|
|
|
|
realpath_error_with_location(__FILE__, __LINE__, settings, filename)
|
|
|
|
|
|
|
|
static void handle_alarm(int sig)
|
|
|
|
{
|
|
|
|
timeout_expired = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int paths_same_file(
|
|
|
|
const command_settings *settings, const char * path1, const char * path2)
|
|
|
|
{
|
|
|
|
int same_file = 0;
|
|
|
|
#ifdef __GLIBC__
|
|
|
|
char *realpath1, *realpath2;
|
|
|
|
realpath1 = realpath(path1, NULL);
|
|
|
|
if (realpath1 == NULL)
|
|
|
|
realpath_error(path1);
|
|
|
|
realpath2 = realpath(path2, NULL);
|
2019-02-15 05:46:01 -08:00
|
|
|
if (realpath2 == NULL)
|
2017-07-21 07:43:36 -07:00
|
|
|
{
|
|
|
|
free(realpath1);
|
2019-02-15 05:46:01 -08:00
|
|
|
if (errno == ENOENT) return 0;
|
|
|
|
else realpath_error(path2);
|
2017-07-21 07:43:36 -07:00
|
|
|
}
|
|
|
|
#else
|
|
|
|
char realpath1[PATH_MAX], realpath2[PATH_MAX];
|
|
|
|
if (realpath(path1, realpath1) == NULL)
|
|
|
|
realpath_error(path1);
|
2019-02-15 05:46:01 -08:00
|
|
|
if (realpath(path2, realpath2) == NULL)
|
|
|
|
{
|
|
|
|
if (errno == ENOENT) return 0;
|
|
|
|
else realpath_error(path2);
|
|
|
|
}
|
2017-07-21 07:43:36 -07:00
|
|
|
#endif /* __GLIBC__ */
|
|
|
|
if (strcmp(realpath1, realpath2) == 0)
|
|
|
|
same_file = 1;
|
|
|
|
#ifdef __GLIBC__
|
|
|
|
free(realpath1);
|
|
|
|
free(realpath2);
|
|
|
|
#endif /* __GLIBC__ */
|
|
|
|
return same_file;
|
|
|
|
}
|
|
|
|
|
2017-11-03 10:52:11 -07:00
|
|
|
static void update_environment(array local_env)
|
|
|
|
{
|
|
|
|
array envp;
|
|
|
|
for (envp = local_env; *envp != NULL; envp++) {
|
|
|
|
char *pos_eq = strchr(*envp, '=');
|
|
|
|
if (pos_eq != NULL) {
|
|
|
|
char *name, *value;
|
|
|
|
int name_length = pos_eq - *envp;
|
|
|
|
int l = strlen(*envp);
|
|
|
|
int value_length = l - (name_length +1);
|
|
|
|
name = malloc(name_length+1);
|
|
|
|
value = malloc(value_length+1);
|
|
|
|
memcpy(name, *envp, name_length);
|
|
|
|
name[name_length] = '\0';
|
|
|
|
memcpy(value, pos_eq + 1, value_length);
|
|
|
|
value[value_length] = '\0';
|
|
|
|
setenv(name, value, 1); /* 1 means overwrite */
|
2020-07-17 00:18:50 -07:00
|
|
|
free(name);
|
|
|
|
free(value);
|
2017-11-03 10:52:11 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-05 07:01:12 -08:00
|
|
|
/*
|
Fixing typos in various files (#2246)
Note: Typos found with https://github.com/codespell-project/codespell
Here is the (semi-manual) command used to get (and correct) the typos:
$ codespell -i 3 -w --skip=".png,.gif,./ocaml/boot,./ocaml/.git,./ocaml/manual/styles,./ocaml/manual/manual/htmlman" -L minimise,instal,contructor,"o'caml",cristal,pres,clos,cmo,uint,iff,te,objext,nto,nd,mut,upto,larg,exten,leage,mthod,delte,tim,atleast,langage,hten,iwth,mke,contant,succint,methids,eles,valu,clas,modul,que,classe,missings,froms,defaut,correspondance,differents,configury,reachs,cas,approche,normale,dur,millon,amin,oje,transfert
2019-02-13 05:04:56 -08:00
|
|
|
This function should return an exitcode that can itself be returned
|
2017-12-05 07:01:12 -08:00
|
|
|
to its father through the exit system call.
|
|
|
|
So it returns 0 to report success and 1 to report an error
|
|
|
|
|
|
|
|
*/
|
2017-07-21 07:43:36 -07:00
|
|
|
static int run_command_child(const command_settings *settings)
|
|
|
|
{
|
|
|
|
int stdin_fd = -1, stdout_fd = -1, stderr_fd = -1; /* -1 = no redir */
|
|
|
|
int inputFlags = O_RDONLY;
|
|
|
|
int outputFlags =
|
|
|
|
O_CREAT | O_WRONLY | (settings->append ? O_APPEND : O_TRUNC);
|
|
|
|
int inputMode = 0400, outputMode = 0666;
|
|
|
|
|
2017-12-05 07:01:12 -08:00
|
|
|
if (setpgid(0, 0) == -1)
|
|
|
|
{
|
|
|
|
child_error("setpgid");
|
|
|
|
}
|
2017-07-21 07:43:36 -07:00
|
|
|
|
|
|
|
if (is_defined(settings->stdin_filename))
|
|
|
|
{
|
|
|
|
stdin_fd = open(settings->stdin_filename, inputFlags, inputMode);
|
|
|
|
if (stdin_fd < 0)
|
2017-11-28 10:35:05 -08:00
|
|
|
{
|
2017-07-21 07:43:36 -07:00
|
|
|
open_error(settings->stdin_filename);
|
2017-11-28 10:35:05 -08:00
|
|
|
goto child_failed;
|
|
|
|
}
|
2017-12-05 07:01:12 -08:00
|
|
|
if (dup2(stdin_fd, STDIN_FILENO) == -1)
|
|
|
|
{
|
|
|
|
child_error("dup2 for stdin");
|
|
|
|
}
|
2017-07-21 07:43:36 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (is_defined(settings->stdout_filename))
|
|
|
|
{
|
|
|
|
stdout_fd = open(settings->stdout_filename, outputFlags, outputMode);
|
2017-11-28 10:35:05 -08:00
|
|
|
if (stdout_fd < 0) {
|
2017-07-21 07:43:36 -07:00
|
|
|
open_error(settings->stdout_filename);
|
2017-11-28 10:35:05 -08:00
|
|
|
goto child_failed;
|
|
|
|
}
|
2017-12-05 07:01:12 -08:00
|
|
|
if (dup2(stdout_fd, STDOUT_FILENO) == -1)
|
|
|
|
{
|
|
|
|
child_error("dup2 for stdout");
|
|
|
|
}
|
2017-07-21 07:43:36 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (is_defined(settings->stderr_filename))
|
|
|
|
{
|
|
|
|
if (stdout_fd != -1)
|
|
|
|
{
|
|
|
|
if (paths_same_file(
|
|
|
|
settings, settings->stdout_filename,settings->stderr_filename))
|
|
|
|
stderr_fd = stdout_fd;
|
|
|
|
}
|
|
|
|
if (stderr_fd == -1)
|
|
|
|
{
|
|
|
|
stderr_fd = open(settings->stderr_filename, outputFlags, outputMode);
|
2017-11-28 10:35:05 -08:00
|
|
|
if (stderr_fd == -1)
|
|
|
|
{
|
|
|
|
open_error(settings->stderr_filename);
|
|
|
|
goto child_failed;
|
|
|
|
}
|
2017-07-21 07:43:36 -07:00
|
|
|
}
|
2017-12-05 07:01:12 -08:00
|
|
|
if (dup2(stderr_fd, STDERR_FILENO) == -1)
|
|
|
|
{
|
|
|
|
child_error("dup2 for stderr");
|
|
|
|
}
|
2017-07-21 07:43:36 -07:00
|
|
|
}
|
|
|
|
|
2017-11-03 10:52:11 -07:00
|
|
|
update_environment(settings->envp);
|
|
|
|
|
2017-11-28 10:35:05 -08:00
|
|
|
execvp(settings->program, settings->argv);
|
2017-07-21 07:43:36 -07:00
|
|
|
|
|
|
|
myperror("Cannot execute %s", settings->program);
|
2017-11-28 10:35:05 -08:00
|
|
|
|
|
|
|
child_failed:
|
2017-12-05 07:01:12 -08:00
|
|
|
return 1;
|
2017-07-21 07:43:36 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Handles the termination of a process. Arguments:
|
|
|
|
* The pid of the terminated process
|
|
|
|
* Its termination status as returned by wait(2)
|
|
|
|
* A string giving a prefix for the core file name.
|
|
|
|
(the file will be called prefix.pid.core but may come from a
|
Fixing typos in various files (#2246)
Note: Typos found with https://github.com/codespell-project/codespell
Here is the (semi-manual) command used to get (and correct) the typos:
$ codespell -i 3 -w --skip=".png,.gif,./ocaml/boot,./ocaml/.git,./ocaml/manual/styles,./ocaml/manual/manual/htmlman" -L minimise,instal,contructor,"o'caml",cristal,pres,clos,cmo,uint,iff,te,objext,nto,nd,mut,upto,larg,exten,leage,mthod,delte,tim,atleast,langage,hten,iwth,mke,contant,succint,methids,eles,valu,clas,modul,que,classe,missings,froms,defaut,correspondance,differents,configury,reachs,cas,approche,normale,dur,millon,amin,oje,transfert
2019-02-13 05:04:56 -08:00
|
|
|
different process)
|
2017-07-21 07:43:36 -07:00
|
|
|
* Returns the code to return if this is the child process
|
|
|
|
*/
|
|
|
|
static int handle_process_termination(
|
|
|
|
const command_settings *settings,
|
|
|
|
pid_t pid, int status, const char *corefilename_prefix)
|
|
|
|
{
|
|
|
|
int signal, core = 0;
|
|
|
|
char *corestr;
|
|
|
|
|
|
|
|
if (WIFEXITED(status)) return WEXITSTATUS(status);
|
|
|
|
|
|
|
|
if ( !WIFSIGNALED(status) )
|
2020-04-20 03:00:03 -07:00
|
|
|
error("Process %lld neither terminated normally nor received a" \
|
|
|
|
"signal!?", (long long) pid);
|
2017-07-21 07:43:36 -07:00
|
|
|
|
|
|
|
/* From here we know that the process terminated due to a signal */
|
|
|
|
signal = WTERMSIG(status);
|
|
|
|
#ifdef WCOREDUMP
|
|
|
|
core = WCOREDUMP(status);
|
|
|
|
#endif /* WCOREDUMP */
|
|
|
|
corestr = core ? "" : "no ";
|
|
|
|
fprintf(stderr,
|
2020-04-20 03:00:03 -07:00
|
|
|
"Process %lld got signal %d(%s), %score dumped\n",
|
|
|
|
(long long) pid, signal, strsignal(signal), corestr
|
2017-07-21 07:43:36 -07:00
|
|
|
);
|
|
|
|
|
|
|
|
if (core)
|
|
|
|
{
|
|
|
|
if ( access(COREFILENAME, F_OK) == -1)
|
|
|
|
fprintf(stderr, "Could not find core file.\n");
|
|
|
|
else {
|
2019-02-11 01:11:24 -08:00
|
|
|
size_t corefile_len = strlen(corefilename_prefix) + 128;
|
|
|
|
char * corefile = malloc(corefile_len);
|
|
|
|
if (corefile == NULL)
|
|
|
|
fprintf(stderr, "Out of memory while processing core file.\n");
|
|
|
|
else {
|
|
|
|
snprintf(corefile, corefile_len,
|
2020-04-20 03:00:03 -07:00
|
|
|
"%s.%lld.core", corefilename_prefix, (long long) pid);
|
2019-02-11 01:11:24 -08:00
|
|
|
if ( rename(COREFILENAME, corefile) == -1)
|
|
|
|
fprintf(stderr, "The core file exists but could not be renamed.\n");
|
|
|
|
else
|
|
|
|
fprintf(stderr,"The core file has been renamed to %s\n", corefile);
|
|
|
|
free(corefile);
|
|
|
|
}
|
2017-07-21 07:43:36 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return -signal;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int run_command_parent(const command_settings *settings, pid_t child_pid)
|
|
|
|
{
|
|
|
|
int waiting = 1, status, code, child_code = 0;
|
|
|
|
pid_t pid;
|
|
|
|
|
|
|
|
if (settings->timeout>0)
|
|
|
|
{
|
|
|
|
struct sigaction action;
|
|
|
|
action.sa_handler = handle_alarm;
|
|
|
|
sigemptyset(&action.sa_mask);
|
|
|
|
action.sa_flags = SA_RESETHAND;
|
|
|
|
if (sigaction(SIGALRM, &action, NULL) == -1) myperror("sigaction");
|
|
|
|
if (alarm(settings->timeout) == -1) myperror("alarm");
|
|
|
|
}
|
|
|
|
|
|
|
|
while (waiting)
|
|
|
|
{
|
|
|
|
pid = wait(&status);
|
|
|
|
if (pid == -1)
|
|
|
|
{
|
2017-11-02 07:02:37 -07:00
|
|
|
switch (errno)
|
2017-07-21 07:43:36 -07:00
|
|
|
{
|
|
|
|
case EINTR:
|
|
|
|
if ((settings->timeout > 0) && (timeout_expired))
|
|
|
|
{
|
|
|
|
timeout_expired = 0;
|
|
|
|
fprintf(stderr, "Timeout expired, killing all child processes");
|
|
|
|
if (kill(-child_pid, SIGKILL) == -1) myperror("kill");
|
|
|
|
};
|
|
|
|
break;
|
|
|
|
case ECHILD:
|
|
|
|
waiting = 0;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
myperror("wait");
|
|
|
|
}
|
|
|
|
} else { /* Got a pid */
|
|
|
|
code = handle_process_termination(
|
|
|
|
settings, pid, status, settings->program);
|
|
|
|
if (pid == child_pid) child_code = code;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return child_code;
|
|
|
|
}
|
|
|
|
|
|
|
|
int run_command(const command_settings *settings)
|
|
|
|
{
|
|
|
|
pid_t child_pid = fork();
|
2017-11-28 10:35:05 -08:00
|
|
|
|
|
|
|
switch (child_pid)
|
|
|
|
{
|
|
|
|
case -1:
|
|
|
|
myperror("fork");
|
|
|
|
return -1;
|
|
|
|
case 0: /* child process */
|
2017-12-05 07:01:12 -08:00
|
|
|
exit( run_command_child(settings) );
|
2017-11-28 10:35:05 -08:00
|
|
|
default:
|
|
|
|
return run_command_parent(settings, child_pid);
|
|
|
|
}
|
2017-07-21 07:43:36 -07:00
|
|
|
}
|