/*
Copyright (c) 2000-2013 "independIT Integrative Technologies GmbH",
Authors: Ronald Jeninga, Dieter Stubler
schedulix Enterprise Job Scheduling System
independIT Integrative Technologies GmbH [http://www.independit.de]
mailto:contact@independit.de
This file is part of schedulix
schedulix is free software:
you can redistribute it and/or modify it under the terms of the
GNU Affero General Public License as published by the
Free Software Foundation, either version 3 of the License,
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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifndef WINDOWS
#include
#include
#include
#include
#endif
#include
#ifdef WINDOWS
#include
#endif
#ifndef OPEN_MAX
#define OPEN_MAX 256
#endif
#ifndef WINDOWS
#define NULLDEVICE "/dev/null"
#define SIGNUM_MIN SIGHUP
#define SIGNUM_MAX SIGSYS
#else
#define NULLDEVICE "NUL"
#endif
#define ARGS_NOTREAD -1
#define ARGS_RUN 0
#define ARGS_VERSION 1
#define ARGS_HELP 2
#define BOOTTIME_SYSTEM 'S'
#define BOOTTIME_FILE 'F'
#define BOOTTIME_NONE 'N'
#define TIMESTAMP_LEADIN '['
#define TIMESTAMP_LEADOUT ']'
#define RETRY_OPEN_TIME 1
#define RETRY_LOCK_TIME 1
#define JOBEXECUTORLOG "JOBEXECUTORLOG"
#define false (1 == 0)
#define true (0 == 0)
#ifdef WINDOWS
#define sleep(a) Sleep((a) * 1000)
#define Fread(a,b,c,d,e) (ReadFile((d), (a), (b) * (c), (&e), NULL), e)
#define Fwrite(a,b,c,d,e) (WriteFile((d), (a), (b) * (c), (&e), NULL), e)
#define Fseek(a,b,c) SetFilePointer((a), (b), NULL, (c))
#define Hprintf hprintf
#define Strerror winStrerror
#else
#define FILE_BEGIN SEEK_SET
#define FILE_CURRENT SEEK_CUR
#define FILE_END SEEK_END
#define Fread(a,b,c,d,e) fread((a), (b), (c), (d))
#define Fwrite(a,b,c,d,e) fwrite((a), (b), (c), (d))
#define Fseek(a, b, c) fseek((a), (b), (c))
#define Hprintf fprintf
#define Strerror strerror
#endif
/*
Warnings can be reported to the taskfile
Errors must be reported to the taskfile since further processing isn't possible
Fatals lead to a hard exit
*/
#define STATUS_OK 0
#define SEVERITY_WARNING 1
#define SEVERITY_FATAL 3
typedef struct _callstatus {
int severity; /* mandatory field, the severity of the error */
int msg; /* mandatory field, contains error code (see below) */
int syserror; /* optional field, contains errno if applicable */
char *msg2; /* optional field, contains additional information */
} callstatus;
typedef struct _errmsg_t {
const char *msg;
#define T_NONE 0
#define T_INT 1
#define T_STRING 2
#define T_BOTH 3
int argType;
} errmsg_t;
errmsg_t message[] = {
#define MSG_NO_ERROR 0
{ "No error", T_NONE },
#define INVALID_TASKFILE 1
{ "Invalid taskfile (doesn't exist, isn't readable or writable (%d / %s))", T_BOTH },
#define INVALID_FD 2
{ "Invalid file descriptor (open() succeeded, fd invalid)", T_NONE },
#define TFWRITE_FAILED 3
{ "Write to taskfile failed", T_NONE },
#define TFCLOSE_FAILED 4
{ "Close of taskfile failed (%d / %s)", T_BOTH },
#define TF_EMPTY 5
{ "Task file empty", T_NONE },
#define TFREAD_ERROR 6
{ "Error on read (%d / %s)", T_BOTH },
#define TFSYNTAX_ERROR 7
{ "Syntax error in taskfile (%s)", T_STRING },
#define OUT_OF_MEM 8
{ "memory allocation failed", T_NONE },
#define WRONG_ARGS 9
{ "Wrong number or type of arguments", T_NONE },
#define TF_INCOMPLETE 10
{ "Taskfile seems to be incomplete", T_NONE },
#define FORK_FAILED 11
{ "Couldn't create process; fork() failed", T_NONE },
#define LOGOPEN_FAILED 12
{ "Couldn't open logfile (%d / %s)", T_BOTH },
#define CHDIR_FAILED 13
{ "Couldn't chdir to working directory (%d / %s)", T_BOTH },
#define ERROPEN_FAILED 14
{ "Couldn't open error logfile (%d / %s)", T_BOTH },
#define DUP_FAILED 15
{ "Couldn't redirect stderr to stdout (%d / %s)", T_BOTH },
#define EXEC_FAILED 16
{ "Couldn't execute command (%d / %s)", T_BOTH },
#define SEEK_FAILED 17
{ "Couldn't seek to logical end of file (%d / %s)", T_BOTH },
#define WRITE_FAILED 18
{ "Couldn't write into taskfile (%d / %s)", T_BOTH },
#define TFMISSING_VALUE 19
{ "Mandatory value missing in taskfile (key %s)", T_STRING },
#define INVALID_BOOTTIME 20
{ "Invalid boottime (too long)", T_NONE },
#define TFUNLOCK_FAILED 21
{ "Couldn't release taskfile lock", T_NONE },
#define CHLDWAIT_FAILED 22
{ "Wait for child process failed", T_NONE },
#define SET_SIGNAL_FAILED 23
{ "Set signal handler failed", T_NONE }
};
const char *ARG_VERSION1 = "--version";
const char *ARG_VERSION2 = "-v";
const char *ARG_HELP1 = "--help";
const char *ARG_HELP2 = "-h";
/* some protocol fields */
unsigned char boottimeHow = 'N';
const char *COMMAND = "command";
const char *ARGUMENT = "argument";
const char *WORKDIR = "workdir";
const char *USEPATH = "usepath";
const char *VERBOSELOGS = "verboselogs";
const char *LOGFILE = "logfile";
const char *LOGFILEAPPEND = "logfile_append";
const char *ERRLOG = "errlog";
const char *ERRLOGAPPEND = "errlog_append";
const char *SAMELOGS = "samelogs";
const char *EXECPID = "execpid";
const char *EXTPID = "extpid";
const char *RETURNCODE = "returncode";
const char *S_ERROR = "error";
const char *STATUS = "status";
const char *STATUS_TX = "status_tx";
const char *INCOMPLETE = "incomplete";
const char *COMPLETE = "complete";
const char *STATUS_RUNNING = "RUNNING";
const char *STATUS_FINISHED = "FINISHED";
const char *STATUS_ERROR = "ERROR";
const char *STATUS_CHILD_ERROR = "CHILD_ERROR";
#define TF_BUFSIZE 16384
#define MAXLENGTH 8192
struct _global {
char *boottime;
time_t myStartTime;
/* Job describing fields */
char *command;
char **argument;
int num_args;
int argsize;
char *workdir;
int usepath;
int verboselogs;
char *logfile;
int logfileappend;
char *errlog;
int errlogappend;
int samelogs;
char *execpid;
char *extpid;
char *returncode;
char *error;
char *jstatus;
char *jstatus_tx;
int complete;
/* some globals for parsing the taskfile
since we're single threaded, a global state is ok
*/
#ifndef WINDOWS
int buflen;
#define HANDLE FILE*
#else
DWORD buflen;
#endif
int bufpos;
int filepos;
char *taskfileName;
HANDLE taskfile;
char taskfileBuf[TF_BUFSIZE];
char tfWriteBuf[TF_BUFSIZE];
#ifndef WINDOWS
char *errorTaskfilePath;
char *errorTaskfileName;
int etflinked;
#endif
} global;
HANDLE myLog = NULL;
/* prototypes */
const char *getUsage();
const char *getVersion();
void initFields();
int checkArgs(callstatus *status, int argc, char *argv[]);
HANDLE openTaskfile(callstatus *status);
void closeTaskfile(callstatus *status, HANDLE taskfile);
#ifndef WINDOWS
void createErrorTaskfileName(char *tfn);
void createTfLink();
void reportSysUse(callstatus *status, struct rusage usage);
#endif
void advance(callstatus *status);
void readTimestamp(callstatus *status);
void readWhiteSpace(callstatus *status);
void readKey(callstatus *status, char *key);
void readLength(callstatus *status, char *lgth);
void readValue(callstatus *status, int lgth, char *value);
char *renderError(callstatus *status);
void processTaskfile(callstatus *status);
void evaluateTaskfile(callstatus *status);
void appendTaskfile(callstatus *status, const char *key, char *value, int alreadyOpen);
void redirect(callstatus *status);
void openLog(callstatus *status);
char *getUniquePid(callstatus *status, pid_t pid);
void run(callstatus *status);
void printJobFields();
void default_all_signals(callstatus *status);
void ignore_all_signals(callstatus *status);
#ifndef WINDOWS
void set_all_signals(struct sigaction aktschn, callstatus *status);
#else
void set_all_signals (void (__cdecl *aktschn) (int), callstatus *status);
char *winStrerror(DWORD errorno);
DWORD hprintf(HANDLE wrc, char *format, ...);
#endif
char *Strdup(callstatus *status, char *src);
const char *getUsage()
{
/* TODO: Generate this from jobserver.jexecutor.Jexecutor.java (?)*/
return "Usage:\n" \
"jobexecutor [--version|-v] [--help|-h] [ [boottime]]\n" \
"\n" \
"Exactly one of the optional argument sets must be specified\n" \
"i.e. either the version request, or this help request or some specification on what to do\n";
}
const char *getVersion()
{
/* TODO: Generate this from server.SystemEnvironment.java or jobserver.jexecutor.Jexecutor.java (?)*/
/* First line protocol version must match with value expected from java jobserver agent !!! */
return "Jobserver (executor) 2.9 (1.1)\n" \
"Copyright (C) 2017 independIT Integrative Technologies GmbH\n" \
"All rights reserved\n";
}
void initFields()
{
global.command = NULL;
global.argument = NULL;
global.num_args = 0;
global.argsize = 0;
global.workdir = NULL;
global.usepath = false;
global.verboselogs = false;
global.logfile = NULL;
global.logfileappend = false;
global.errlog = NULL;
global.errlogappend = false;
global.samelogs = false;
global.execpid = NULL;
global.extpid = NULL;
global.returncode = NULL;
global.error = NULL;
global.jstatus = NULL;
global.jstatus_tx = NULL;
global.complete = false;
global.taskfile = NULL;
global.buflen = 0;
global.bufpos = 0;
global.filepos = -1;
#ifndef WINDOWS
global.etflinked = false;
global.errorTaskfilePath = NULL;
global.errorTaskfileName = NULL;
#endif
}
#ifndef WINDOWS
void createErrorTaskfileName(char *tfn)
{
static const char *errorDir = "errorTaskfiles";
global.errorTaskfileName = NULL;
global.errorTaskfilePath = NULL;
char *dirname;
char *filename;
int len;
dirname = strdup(tfn);
if (dirname == NULL) return; /* no mem; no safety net */
len = (int) strlen(dirname);
while (len > 0 && dirname[len - 1] != '/') {
len--;
dirname[len] = '\0';
}
if (len == 0) return; /* no path; Name should be full qualified though */
filename = tfn + len;
len += (int) strlen(errorDir);
global.errorTaskfilePath = (char *) malloc(len + 1);
if (global.errorTaskfilePath == NULL) {
free(dirname);
return;
}
global.errorTaskfilePath[0] = '\0';
strcat(global.errorTaskfilePath, dirname);
strcat(global.errorTaskfilePath, errorDir);
free(dirname);
global.errorTaskfileName = (char *) malloc(len + 1 + strlen(filename) + 1);
if (global.errorTaskfileName == NULL) {
free(global.errorTaskfilePath);
global.errorTaskfilePath = NULL;
return;
}
strcat(global.errorTaskfileName, global.errorTaskfilePath);
strcat(global.errorTaskfileName, "/");
strcat(global.errorTaskfileName, filename);
}
void createTfLink()
{
/* if we already had an error, we already have a link */
/* if we couldn't determine the errorTaskfilePath, we don't try to link */
if (global.etflinked || global.errorTaskfilePath == NULL)
return;
/* create the errorTaskfilePath Directory first. */
/* if the return value == 0 or errno == EEXIST we can (try to) continue */
if (mkdir(global.errorTaskfilePath, 0700) != 0) {
if (errno != EEXIST) return;
}
if (link(global.taskfileName, global.errorTaskfileName) != 0) {
/* no real means for error processing here (yet?) */
/* but the next time (if there is one) we might succeed */
/* But is possible we linked the taskfile in a previous run */
/* That taskfile has to be removed manually */
if (errno != EEXIST)
return;
}
global.etflinked = true;
}
#endif
char *renderError(callstatus *status)
{
char *msg;
int len;
errmsg_t *e;
char *syserr = NULL;
e = &message[status->msg];
len = (int) strlen(e->msg) + 1; /* \0 */
switch (e->argType) {
case T_NONE:
break;
case T_STRING:
if (status->msg2 == NULL)
status->msg2 = (char *) "";
else
len += (int) strlen(status->msg2);
break;
case T_INT:
len += 10; /* max length of a 4 byte int */
break;
case T_BOTH:
len += 10;
syserr = strdup(Strerror(status->syserror)); /* we need a copy, could get overwritten; accepted memory leak */
if (syserr == NULL)
syserr = (char *) "(unable to retrieve system error message)";
len += (int) strlen(syserr);
break;
}
msg = (char *) malloc(len);
if (msg != NULL) {
memset(msg, 0, len);
switch (e->argType) {
case T_NONE:
snprintf(msg, len, e->msg);
break;
case T_STRING:
snprintf(msg, len, e->msg, status->msg2);
break;
case T_INT:
snprintf(msg, len, e->msg, status->syserror);
break;
case T_BOTH:
snprintf(msg, len, e->msg, status->syserror, syserr);
break;
}
} else
msg = (char *) "unable to render error message";
return msg; /* we generously accept this memory leak. The program will terminate soon anyway */
}
#ifdef WINDOWS
char *winStrerror(DWORD errorno)
{
LPTSTR result = NULL;
DWORD retSize;
retSize=FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|
FORMAT_MESSAGE_FROM_SYSTEM|
FORMAT_MESSAGE_ARGUMENT_ARRAY,
NULL,
errorno,
LANG_NEUTRAL,
(LPTSTR) &result,
0,
NULL );
if (!retSize || result == NULL) {
return (char *) "(Failed to render error message)";
}
result[strlen(result)-2]='\0'; //remove cr and newline character
return (char *) result;
}
DWORD hprintf(HANDLE wrc, char *format, ...)
{
#define BUFSIZE 2048
va_list ap;
char buf[BUFSIZE];
DWORD nc; /* number of chars */
DWORD nw; /* number written */
if (wrc == INVALID_HANDLE_VALUE) return (DWORD) -1;
va_start(ap, format);
nc = vsnprintf(buf, BUFSIZE, format, ap);
va_end(ap);
if (nc < 0) return nc;
if (nc == BUFSIZE) {
/* would have been too long; we simply truncate it since it's only used for error messages */
buf[BUFSIZE - 1] = '\0';
}
if (!WriteFile(wrc, buf, nc, &nw, NULL)) {
/* process write error */;
}
if (nc != nw) {
/* hm, what to do here ??? */;
}
return nw;
}
#endif
char *Strdup(callstatus *status, char *src)
{
char *trg;
if (*src == '\0') {
status->severity = SEVERITY_WARNING;
status->msg = TFMISSING_VALUE;
trg = (char *) malloc(1);
*trg = '\0';
return trg;
}
trg = strdup(src);
if (trg == NULL) {
status->severity = SEVERITY_FATAL;
status->msg = OUT_OF_MEM;
}
return trg;
}
#ifndef WINDOWS
void set_all_signals (struct sigaction aktschn, callstatus *status)
{
int rc;
int signum;
for (signum = SIGNUM_MIN; signum <= SIGNUM_MAX; ++signum) {
if ((signum != SIGKILL) && (signum != SIGSTOP) && (signum != SIGCHLD)) {
rc = sigaction (signum, &aktschn, NULL);
if (rc) {
status->severity = SEVERITY_WARNING;
status->msg = SET_SIGNAL_FAILED;
}
}
}
}
void ignore_all_signals (callstatus *status)
{
struct sigaction aktschn;
#ifdef BSD
sigemptyset (&aktschn.sa_mask);
aktschn.sa_flags = 0;
#endif
aktschn.sa_handler = SIG_IGN;
set_all_signals (aktschn, status);
}
void default_all_signals (callstatus *status)
{
struct sigaction aktschn;
#ifdef BSD
sigemptyset (&aktschn.sa_mask);
aktschn.sa_flags = 0;
#endif
aktschn.sa_handler = SIG_DFL;
set_all_signals (aktschn, status);
}
#else
void set_all_signals (void (__cdecl *aktschn) (int), callstatus *status)
{
if (signal (SIGABRT, aktschn) == SIG_ERR) {
status->severity = SEVERITY_WARNING;
status->msg = SET_SIGNAL_FAILED;
}
if (signal (SIGFPE, aktschn) == SIG_ERR) {
status->severity = SEVERITY_WARNING;
status->msg = SET_SIGNAL_FAILED;
}
if (signal (SIGILL, aktschn) == SIG_ERR) {
status->severity = SEVERITY_WARNING;
status->msg = SET_SIGNAL_FAILED;
}
if (signal (SIGINT, aktschn) == SIG_ERR) {
status->severity = SEVERITY_WARNING;
status->msg = SET_SIGNAL_FAILED;
}
if (signal (SIGSEGV, aktschn) == SIG_ERR) {
status->severity = SEVERITY_WARNING;
status->msg = SET_SIGNAL_FAILED;
}
if (signal (SIGTERM, aktschn) == SIG_ERR) {
status->severity = SEVERITY_WARNING;
status->msg = SET_SIGNAL_FAILED;
}
}
void ignore_all_signals (callstatus *status)
{
set_all_signals (SIG_IGN, status);
}
void default_all_signals (callstatus *status)
{
set_all_signals (SIG_DFL, status);
}
#endif
void printJobFields()
{
int i;
Hprintf(myLog, "command = %s\n", global.command != NULL ? global.command : "NULL");
Hprintf(myLog, "arguments:\n");
if (global.argument == NULL)
Hprintf(myLog, "\tNULL\n");
else {
for (i = 0; i < global.num_args; ++i)
Hprintf(myLog, "\t%2.2d: %s\n", i, global.argument[i]);
}
Hprintf(myLog, "workdir = %s\n", global.workdir != NULL ? global.workdir : "NULL");
Hprintf(myLog, "usepath = %s\n", global.usepath == false ? "false" : "true");
Hprintf(myLog, "verboselogs = %s\n", global.verboselogs == false ? "false" : "true");
Hprintf(myLog, "logfile = %s\n", global.logfile != NULL ? global.logfile : "NULL");
Hprintf(myLog, "logfileappend = %s\n", global.logfileappend == false ? "false" : "true");
Hprintf(myLog, "errlog = %s\n", global.errlog != NULL ? global.errlog : "NULL");
Hprintf(myLog, "errlogappend = %s\n", global.errlogappend == false ? "false" : "true");
Hprintf(myLog, "samelogs = %s\n", global.samelogs == false ? "false" : "true");
Hprintf(myLog, "execpid = %s\n", global.execpid != NULL ? global.execpid : "NULL");
Hprintf(myLog, "extpid = %s\n", global.extpid != NULL ? global.extpid : "NULL");
Hprintf(myLog, "returncode = %s\n", global.returncode != NULL ? global.returncode : "NULL");
Hprintf(myLog, "error = %s\n", global.error != NULL ? global.error : "NULL");
Hprintf(myLog, "jstatus = %s\n", global.jstatus != NULL ? global.jstatus : "NULL");
Hprintf(myLog, "jstatus_tx = %s\n", global.jstatus_tx != NULL ? global.jstatus_tx : "NULL");
Hprintf(myLog, "complete = %s\n", global.complete == false ? "false" : "true");
}
void addArgument(callstatus *status, char *value)
{
#define ARGCHUNK 32
int i;
if (global.argument == NULL) {
global.argument = (char **) malloc(ARGCHUNK * sizeof(char *));
if (global.argument == NULL) {
status->severity = SEVERITY_FATAL;
status->msg = OUT_OF_MEM;
return;
}
global.argsize = ARGCHUNK;
for (i = global.num_args; i < global.argsize; ++i) global.argument[i] = NULL;
} else {
if (global.num_args == global.argsize) {
global.argument = (char **) realloc(global.argument, (global.argsize + ARGCHUNK) * sizeof(char*));
if (global.argument == NULL) {
status->severity = SEVERITY_FATAL;
status->msg = OUT_OF_MEM;
return;
}
global.argsize += ARGCHUNK;
/* make valid pointers; we don't rely on side effects */
for (i = global.num_args; i < global.argsize; ++i) global.argument[i] = NULL;
}
}
global.argument[global.num_args] = Strdup(status, value);
if (status->severity != SEVERITY_FATAL) {
status->severity = STATUS_OK;
global.num_args++;
}
return;
}
int checkArgs(callstatus *status, int argc, char *argv[])
{
status->severity = STATUS_OK;
status->msg = MSG_NO_ERROR;
if (argc <= 4 && argc > 1) { /* we have the command + 1, 2 or 3 parameters */
if (!strcmp(argv[1], ARG_VERSION1) || !strcmp(argv[1], ARG_VERSION2)) {
return ARGS_VERSION;
}
if (!strcmp(argv[1], ARG_HELP1) || !strcmp(argv[1], ARG_HELP2)) {
return ARGS_HELP;
}
if (argc < 3) {
status->severity = SEVERITY_FATAL;
status->msg = WRONG_ARGS;
return ARGS_NOTREAD;
}
boottimeHow = (unsigned char) toupper((unsigned char) argv[1][0]);
if (!(boottimeHow == BOOTTIME_SYSTEM ||
boottimeHow == BOOTTIME_FILE ||
boottimeHow == BOOTTIME_NONE)) {
status->severity = SEVERITY_FATAL;
status->msg = WRONG_ARGS;
return ARGS_NOTREAD;
}
global.taskfileName = argv[2];
#ifndef WINDOWS
createErrorTaskfileName(global.taskfileName);
#endif
if (argc == 4) {
global.boottime = argv[3];
if (strlen(global.boottime) > 19) {
status->severity = SEVERITY_FATAL;
status->msg = INVALID_BOOTTIME;
return ARGS_NOTREAD;
}
} else
global.boottime = NULL;
processTaskfile(status);
if (status->severity == SEVERITY_FATAL) {
return ARGS_NOTREAD;
}
if (!global.complete) {
status->severity = SEVERITY_FATAL;
status->msg = TF_INCOMPLETE;
return ARGS_NOTREAD;
}
return ARGS_RUN;
}
status->severity = SEVERITY_FATAL;
status->msg = WRONG_ARGS;
return ARGS_NOTREAD;
}
/*------------------------------------------------------------------------------*/
/* Taskfile Routines */
/*------------------------------------------------------------------------------*/
HANDLE openTaskfile(callstatus *status)
{
HANDLE taskfile = NULL;
int retry_cnt = 0;
#ifndef WINDOWS
struct flock lock;
int tffd; /* File Descriptor of Taskfile, needed for fcntl */
#endif
/* reset some bookkeeping fields */
global.filepos = -1;
global.bufpos = 0;
global.buflen = 0;
while (1) {
#ifndef WINDOWS
#ifdef O_RSYNC
tffd = open(global.taskfileName, O_RDWR|O_SYNC|O_RSYNC);
#else
tffd = open(global.taskfileName, O_RDWR|O_SYNC);
#endif
if (tffd < 0) {
exit(1);
}
taskfile = fdopen(tffd, "r+"); /* we might or might not write */
/* but open for write is required for an exclusive lock */
if (taskfile != NULL) break; /* fopen() succeeded */
/* hard errors, won't change over time */
if (errno == ENOENT || /* File does not exist */
errno == EACCES || /* Permission denied */
errno == EISDIR || /* file is a directory */
errno == ELOOP /* link chain too long */
) {
status->severity = SEVERITY_FATAL;
status->msg = INVALID_TASKFILE;
status->syserror = errno;
return NULL;
}
#else
/*
* Excerpt form MSDN Documentation:
* As stated previously, if the lpSecurityAttributes parameter is NULL,
* the handle returned by CreateFile cannot be inherited by any child
* processes your application may create.
* This is exactly the behaviour we need (here)
*/
taskfile = CreateFile (
global.taskfileName,
GENERIC_READ | GENERIC_WRITE, // access mode
FILE_SHARE_READ | FILE_SHARE_WRITE, // share mode; share all access to prevent the open() to fail
NULL, // security attributes -> not inheritable
OPEN_EXISTING, // how to create
FILE_ATTRIBUTE_NORMAL, // file attributes
NULL); // handle to template file
if (taskfile != INVALID_HANDLE_VALUE) {
// we've openend the file. Now lock it to get private access
OVERLAPPED regionStart;
DWORD fsLow = 0xFFFFFFFF, fsHigh = 0xFFFFFFFF;
regionStart.Offset = 0;
regionStart.OffsetHigh = 0;
regionStart.hEvent = (HANDLE)0;
if (!LockFileEx(taskfile, LOCKFILE_EXCLUSIVE_LOCK, 0, fsLow, fsHigh, ®ionStart)) {
/* shouldn't fail, but block until the lock is granted */ ;
if (myLog != NULL) Hprintf(myLog, "Couldn't lock file > %s < (%d)\n", global.taskfileName, GetLastError());
}
break;
}
const DWORD errn = GetLastError();
if (errn != ERROR_TOO_MANY_OPEN_FILES &&
errn != ERROR_SHARING_VIOLATION &&
errn != ERROR_LOCK_VIOLATION) { // Not a soft error?
status->severity = SEVERITY_FATAL;
status->msg = INVALID_TASKFILE;
status->syserror = errn;
if ((errn == ERROR_FILE_NOT_FOUND) && (retry_cnt < 11)) {
if (myLog != NULL) Hprintf(myLog, "Couldn't open file > %s <\n", global.taskfileName);
} else
return NULL;
}
#endif
/* Soft errors might vanish after a while */
/* we increase the wait time, 1 second at a time, if errors occur persistently */
sleep(RETRY_OPEN_TIME + retry_cnt);
retry_cnt++;
}
/* now lock the entire file */
/* For windows this is done implicitly when opening the file via CreateFile */
#ifndef WINDOWS
retry_cnt = 0; /* reset retry count */
tffd = fileno(taskfile);
lock.l_type = F_WRLCK;
lock.l_whence = FILE_BEGIN;
lock.l_start = 0;
lock.l_len = 0; /* Specifying 0 for l_len has the special meaning:
lock all bytes starting at the location specified by
l_whence and l_start through to the end of file, no
matter how large the file grows
*/
while (1) {
if (fcntl(tffd, F_SETLKW, &lock) == 0) break; /* we wait for the lock */
if (errno == EBADF) { /* Bad File Descriptor -> invalid internal state (not our fault) */
status->severity = SEVERITY_FATAL;
status->msg = INVALID_FD;
return NULL;
}
/* Soft errors might vanish after a while */
/* we increase the wait time, 1 second at a time, if errors occur persistently */
sleep(RETRY_LOCK_TIME + retry_cnt);
retry_cnt++;
}
#endif
return taskfile;
}
void closeTaskfile(callstatus *status, HANDLE taskfile)
{
if (taskfile != NULL) {
#ifndef WINDOWS
if (fflush(taskfile) != 0) { /* releases locks */
#else
if (FlushFileBuffers(taskfile) == 0) { /* releases locks */
#endif
status->severity = SEVERITY_FATAL;
status->msg = TFWRITE_FAILED;
return;
}
#ifndef WINDOWS
if (fclose(taskfile) != 0) { /* releases locks */
#else
/* release lock first; we have flushed already, hence no side effects should occur */
OVERLAPPED regionStart;
DWORD fsLow = 0xFFFFFFFF, fsHigh = 0xFFFFFFFF;
regionStart.Offset = 0;
regionStart.OffsetHigh = 0;
regionStart.hEvent = (HANDLE)0;
if (!UnlockFileEx(taskfile, 0, fsLow, fsHigh, ®ionStart)) {
/* shouldn't fail */ ;
}
if (CloseHandle(taskfile) == 0) {
#endif
status->severity = SEVERITY_WARNING;
status->msg = TFCLOSE_FAILED;
#ifndef WINDOWS
status->syserror = errno;
#else
status->syserror = GetLastError();
#endif
if (myLog != NULL) Hprintf(myLog, "%s\n", renderError(status));
}
}
/* reset the fileposition describing fields */
global.filepos = -1;
global.bufpos = 0;
global.buflen = 0;
return;
}
/*
taskfile Syntax:
file: entrylist
entrylist: entry
| entrylist entry
entry: [timestamp] KEY \n
| [timestamp] KEY =value \n
| [timestamp] KEY 'length =value \n
Because of the simplicity of the syntax, we do a recursive descent parser
Spaces are optional. Everything after the equals sign is regarded value.
This is also true for spaces following the equals sign
*/
void advance(callstatus *status)
{
if (global.bufpos == -1) {
return; /* after EOF we only return EOF */
}
/* are we at the end of the buffer? */
if (global.filepos == -1 || global.bufpos == global.buflen) {
if (global.filepos == -1)
Fseek(global.taskfile, 0, FILE_BEGIN);
else
Fseek(global.taskfile, global.filepos, FILE_BEGIN);
/* read a chunk of the taskfile into a buffer */
if ((global.buflen = (int) Fread(global.taskfileBuf, sizeof(char), TF_BUFSIZE, global.taskfile, global.buflen)) == 0) {
#ifndef WINDOWS
if (feof(global.taskfile)) {
#endif
/* taskfile empty ? */
if (global.filepos == -1) { /* we have EOF and didn't read a byte before */
status->severity = SEVERITY_FATAL;
status->msg = TF_EMPTY;
return;
}
global.bufpos = -1; /* we're at EOF */
return;
#ifndef WINDOWS
} else {
/* error on read */
status->severity = SEVERITY_FATAL;
status->msg = TFREAD_ERROR;
status->syserror = ferror(global.taskfile);
return;
}
#endif
}
/* this way filepos - buflen + bufpos == position in the file of the current character (taskfileBuf[bufpos]) */
global.bufpos = 0;
if (global.filepos == -1)
global.filepos = global.buflen;
else
global.filepos += global.buflen;
} else {
global.bufpos++;
}
return;
}
void readTimestamp(callstatus *status)
{
/* since we actually don't need the timestamp, we skip it */
while (global.bufpos >= 0 && global.taskfileBuf[global.bufpos] != TIMESTAMP_LEADOUT) {
advance(status);
if (status->severity != STATUS_OK) return;
}
/* we must eat the last character of the timestamp too */
advance(status);
if (status->severity != STATUS_OK) return;
return;
}
void readWhiteSpace(callstatus *status)
{
while (global.bufpos >= 0 && (global.taskfileBuf[global.bufpos] == ' ' ||
global.taskfileBuf[global.bufpos] == '\t' ||
global.taskfileBuf[global.bufpos] == '\r' ||
global.taskfileBuf[global.bufpos] == 0)) {
advance(status);
if (status->severity != STATUS_OK) return;
}
return;
}
void readKey(callstatus *status, char *key)
{
int i = 0;
while (global.bufpos >= 0 && i < MAXLENGTH &&
!isspace(global.taskfileBuf[global.bufpos]) &&
global.taskfileBuf[global.bufpos] != '\'' &&
global.taskfileBuf[global.bufpos] != '=') {
*(key + i) = (unsigned char) tolower(global.taskfileBuf[global.bufpos]);
advance(status);
if (status->severity != STATUS_OK) return;
i++;
}
return;
}
void readLength(callstatus *status, char *lgth)
{
int i = 0;
while (global.bufpos >= 0 && i < MAXLENGTH && isdigit(global.taskfileBuf[global.bufpos])) {
*(lgth + i) = global.taskfileBuf[global.bufpos];
advance(status);
if (status->severity != STATUS_OK) return;
i++;
}
return;
}
void readValue(callstatus *status, int lgth, char *value)
{
int i = 0;
while (global.bufpos >= 0 && i < MAXLENGTH && (i < lgth || (lgth < 0 && global.taskfileBuf[global.bufpos] != '\n'))) {
*(value + i) = global.taskfileBuf[global.bufpos];
advance(status);
if (status->severity != STATUS_OK) return;
i++;
}
*(value + i) = '\0';
return;
}
void processEntry(callstatus *status)
{
static char key[MAXLENGTH];
static char vlength[MAXLENGTH];
int length;
static char value[MAXLENGTH];
callstatus mystatus;
/* reset contents */
memset(&(key[0]), 0, MAXLENGTH);
memset(&(vlength[0]), 0, MAXLENGTH);
memset(&(value[0]), 0, MAXLENGTH);
readTimestamp(status);
if (status->severity != STATUS_OK) return;
readWhiteSpace(status);
if (status->severity != STATUS_OK) return;
readKey(status, &(key[0]));
if (status->severity != STATUS_OK) return;
readWhiteSpace(status);
if (status->severity != STATUS_OK) return;
switch (global.taskfileBuf[global.bufpos]) {
case '\n':
/* Entry is ready */
advance(status); /* skip the newline */
if (status->severity != STATUS_OK) return;
/* we accept an EOF instead of a '\n' */
break;
case '\'':
advance(status); /* skip the quote */
if (status->severity != STATUS_OK) return;
if (global.bufpos < 0) {
status->severity = SEVERITY_FATAL;
status->msg = TFSYNTAX_ERROR;
status->msg2 = (char *) "Unexpected EOF, expected a number";
return;
}
readWhiteSpace(status);
if (status->severity != STATUS_OK) return;
readLength(status, &(vlength[0]));
if (status->severity != STATUS_OK) return;
length = atoi(vlength);
readWhiteSpace(status);
if (status->severity != STATUS_OK) return;
if (global.taskfileBuf[global.bufpos] != '=') {
status->severity = SEVERITY_FATAL;
status->msg = TFSYNTAX_ERROR;
status->msg2 = (char *) "Unexpected token, expected an equal sign";
return;
}
advance(status); /* skip the equals sign */
if (status->severity != STATUS_OK) return;
if (global.bufpos < 1) {
status->severity = SEVERITY_FATAL;
status->msg = TFSYNTAX_ERROR;
status->msg2 = (char *) "Unexpected EOF, expected a value";
return;
}
readValue(status, length, &(value[0]));
if (status->severity != STATUS_OK) return;
advance(status); /* skip the newline (if any) */
if (status->severity != STATUS_OK) return;
/* we accept an EOF instead of a '\n' */
break;
case '=':
advance(status); /* skip the equals sign */
if (status->severity != STATUS_OK) return;
if (global.bufpos < 1) {
status->severity = SEVERITY_FATAL;
status->msg = TFSYNTAX_ERROR;
status->msg2 = (char *) "Unexpected EOF, expected a value";
return;
}
readValue(status, -1, &(value[0])); /* -1 means: no length specified; read up to newline */
if (status->severity != STATUS_OK) return;
advance(status); /* skip the newline */
if (status->severity != STATUS_OK) return;
/* we accept an EOF instead of a '\n' */
break;
default:
/* Syntax error */
status->severity = SEVERITY_FATAL;
status->msg = TFSYNTAX_ERROR;
status->msg2 = (char *) "Unexpected Value, expected an equal sign, a quote or a newline";
return;
}
/* we now should have a key and an optional value */
if (!strcmp(key, INCOMPLETE)) global.complete = false;
else if (!strcmp(key, COMMAND)) global.command = Strdup(status, value);
else if (!strcmp(key, ARGUMENT)) addArgument(status, value);
else if (!strcmp(key, WORKDIR)) global.workdir = Strdup(status, value);
else if (!strcmp(key, USEPATH)) global.usepath = true;
else if (!strcmp(key, VERBOSELOGS)) global.verboselogs = true;
else if (!strcmp(key, LOGFILE)) global.logfile = Strdup(status, value);
else if (!strcmp(key, LOGFILEAPPEND)) global.logfileappend = true;
else if (!strcmp(key, ERRLOG)) global.errlog = Strdup(status, value);
else if (!strcmp(key, ERRLOGAPPEND)) global.errlogappend = true;
else if (!strcmp(key, SAMELOGS)) global.samelogs = true;
else if (!strcmp(key, EXECPID)) global.execpid = Strdup(status, value);
else if (!strcmp(key, EXTPID)) global.extpid = Strdup(status, value);
else if (!strcmp(key, RETURNCODE)) global.returncode = Strdup(status, value);
else if (!strcmp(key, S_ERROR)) global.error = Strdup(status, value);
else if (!strcmp(key, STATUS)) global.jstatus = Strdup(status, value);
else if (!strcmp(key, STATUS_TX)) global.jstatus_tx = Strdup(status, value);
else if (!strcmp(key, COMPLETE)) global.complete = true;
if (status->msg == TFMISSING_VALUE) {
/* add some more information to the occurred error */
status->msg2 = Strdup(&mystatus, key);
}
return;
}
void evaluateTaskfile(callstatus *status)
{
advance(status);
if (status->severity != STATUS_OK) return;
while (global.bufpos >= 0 && global.taskfileBuf[global.bufpos] != '\0') {
processEntry(status);
if (status->severity != STATUS_OK) {
#ifndef WINDOWS
/* since something's wrong with the taskfile, we save a reference in the
// errorTaskfiles directory
// No need to check success, if it succeeds it's ok, if not, things don't get worse
*/
createTfLink();
#endif
return;
}
}
return;
}
void processTaskfile(callstatus *status)
{
global.taskfile = openTaskfile(status);
if (status->severity != STATUS_OK) return;
evaluateTaskfile(status);
if (status->severity != STATUS_OK) return;
closeTaskfile(status, global.taskfile);
if (status->severity != STATUS_OK) return;
return;
}
char *getTimestamp(time_t tim, int local)
{
static char buf [64];
#if !defined SOLARIS && !defined AIX
#ifdef WINDOWS
_tzset();
#endif
const struct tm *timeval;
if (local)
timeval = localtime (&tim);
else
timeval = gmtime(&tim);
#ifdef __GNUC__
snprintf (buf, sizeof (buf), "%c%2.2d-%2.2d-%4.4d %2.2d:%2.2d:%2.2d %s%c",
#else
snprintf (buf, sizeof (buf), "%c%02.2d-%02.2d-%04.4d %02.2d:%02.2d:%02.2d %s%c",
#endif
TIMESTAMP_LEADIN,
timeval->tm_mday,
timeval->tm_mon + 1,
timeval->tm_year + 1900,
timeval->tm_hour,
timeval->tm_min,
timeval->tm_sec,
#ifdef WINDOWS
(local ? (timeval->tm_isdst > 0 ? _tzname[1] : _tzname[0]) : "GMT"),
#else
timeval->tm_zone,
#endif
TIMESTAMP_LEADOUT);
#else
static const char *gmtformat = "%d-%m-%Y %H:%M:%S GMT";
static const char *localformat = "%d-%m-%Y %H:%M:%S %Z";
char *format = (char *) (local ? localformat : gmtformat);
buf [0] = TIMESTAMP_LEADIN;
const size_t len = strftime (buf + 1, sizeof (buf) - 3 * sizeof (char), format, local ? localtime (&tim) : gmtime(&tim));
buf [len + 1] = TIMESTAMP_LEADOUT;
buf [len + 2] = '\0';
#endif
return buf;
}
void appendTaskfile(callstatus *status, const char *key, char *value, int alreadyOpen)
{
int numBytes;
int seekpos;
#ifdef WINDOWS
DWORD bytesWritten;
#else
int bytesWritten;
#endif
if (alreadyOpen == 0) {
global.taskfile = openTaskfile(status);
if (status->severity != STATUS_OK) return;
} else {
/* reset file position */
Fseek(global.taskfile, 0, FILE_BEGIN);
global.filepos = -1;
global.bufpos = 0;
global.buflen = 0;
}
advance(status);
if (status->severity != STATUS_OK) return;
/* this loop advances until eof or a null byte is read */
while (global.bufpos >= 0 && global.taskfileBuf[global.bufpos] != '\0') {
advance(status);
if (status->severity != STATUS_OK) return;
}
seekpos = global.filepos - global.buflen + global.bufpos;
if (Fseek(global.taskfile, seekpos, FILE_BEGIN) < 0) {
status->severity = SEVERITY_FATAL;
status->msg = SEEK_FAILED;
status->syserror = errno;
return;
}
if ((numBytes = snprintf(global.tfWriteBuf, TF_BUFSIZE, "%s %s=%s\n", getTimestamp(time(NULL), 0), key, value)) >= TF_BUFSIZE) {
status->severity = SEVERITY_FATAL;
status->msg = WRITE_FAILED;
status->syserror = errno;
return;
}
global.tfWriteBuf[numBytes] = '\0';
bytesWritten = (int) Fwrite(global.tfWriteBuf, 1, numBytes, global.taskfile, bytesWritten);
if (bytesWritten != numBytes) {
status->severity = SEVERITY_FATAL;
status->msg = WRITE_FAILED;
status->syserror = errno;
return;
}
if (alreadyOpen == 0) {
closeTaskfile(status, global.taskfile);
} else {
#ifndef WINDOWS
fflush(global.taskfile);
#else
FlushFileBuffers(global.taskfile);
#endif
}
return;
}
void redirect(callstatus *status)
{
if (global.workdir != NULL) {
if (chdir(global.workdir) < 0) {
status->severity = SEVERITY_FATAL;
status->msg = CHDIR_FAILED;
status->syserror = errno;
return;
}
}
#ifndef WINDOWS
#define APPENDFLAG "a"
#else
#define APPENDFLAG "a+"
#endif
if (!freopen(global.logfile == NULL ? NULLDEVICE : global.logfile , global.logfileappend ? APPENDFLAG : "w", stdout)) {
status->severity = SEVERITY_FATAL;
status->msg = LOGOPEN_FAILED;
status->syserror = errno;
return;
}
#ifdef WINDOWS
// excerpt from visual C documentation:
//
// When a file is opened with the "a" or "a+" access type, all write operations take place
// at the end of the file. Although the file pointer can be repositioned using fseek or rewind,
// the file pointer is always moved back to the end of the file before any write operation is
// carried out. Thus, existing data cannot be overwritten.
//
// since this statement is wrong, we'll have to do the seek manually
if (global.logfileappend) fseek(stdout, 0, SEEK_END);
#endif
if (!global.samelogs) {
if (!freopen(global.errlog == NULL ? NULLDEVICE : global.errlog , global.errlogappend ? APPENDFLAG : "w", stderr)) {
status->severity = SEVERITY_FATAL;
status->msg = ERROPEN_FAILED;
status->syserror = errno;
return;
}
#ifdef WINDOWS
if (global.errlogappend) fseek(stderr, 0, SEEK_END);
#endif
} else {
if (dup2(fileno(stdout), fileno(stderr)) < 0) {
status->severity = SEVERITY_FATAL;
status->msg = DUP_FAILED;
status->syserror = errno;
return;
}
}
}
void openLog(callstatus *status)
{
char *pl;
char privateLog[PATH_MAX + 1];
int myLogFd;
mode_t mode = S_IRUSR | S_IWUSR;
pl = getenv(JOBEXECUTORLOG);
if (pl == NULL) {
myLog = NULL;
return;
} else
snprintf(privateLog, PATH_MAX, "%s.%d", pl, (int) getpid());
#ifndef WINDOWS
myLogFd = open(privateLog, O_WRONLY|O_SYNC|O_CREAT, mode);
if (myLogFd < 0) {
myLog = NULL; /* TODO: differentiate between causes */
return;
}
/* myLog will be NULL if this fails */
myLog = fdopen(myLogFd, "w");
#else
myLog = CreateFile (
privateLog,
GENERIC_WRITE, // access mode
FILE_SHARE_READ, // share mode
NULL, // security attributes
CREATE_ALWAYS, // how to create
FILE_ATTRIBUTE_NORMAL, // file attributes
NULL); // handle to template file
if (myLog == INVALID_HANDLE_VALUE) {
myLog = NULL;
}
#endif
return;
}
void closeLog(callstatus *status)
{
if (myLog != NULL)
#ifndef WINDOWS
fclose(myLog);
#else
CloseHandle(myLog);
#endif
return;
}
char *getUniquePid(callstatus *status, pid_t pid)
{
static char buf[64]; /* starttime + sep + how + boottime + sep + pid + \0
// 20 1 1 20 1 20 1
*/
static const char *pattern = "%d@%c%s+%ld";
snprintf(buf, 63, pattern, (int) pid, boottimeHow, (global.boottime == NULL ? "0" : global.boottime), global.myStartTime);
/* TODO Error handling */
return buf;
}
#ifndef WINDOWS
void reportSysUse(callstatus *status, struct rusage usage)
{
/*
struct rusage {
struct timeval ru_utime; // user CPU time used
struct timeval ru_stime; // system CPU time used
long ru_maxrss; // maximum resident set size
long ru_ixrss; // integral shared memory size
long ru_idrss; // integral unshared data size
long ru_isrss; // integral unshared stack size
long ru_minflt; // page reclaims (soft page faults)
long ru_majflt; // page faults (hard page faults)
long ru_nswap; // swaps
long ru_inblock; // block input operations
long ru_oublock; // block output operations
long ru_msgsnd; // IPC messages sent
long ru_msgrcv; // IPC messages received
long ru_nsignals; // signals received
long ru_nvcsw; // voluntary context switches
long ru_nivcsw; // involuntary context switches
};
*/
fprintf(stderr, "user CPU : %ld / %ld\n", (long) usage.ru_utime.tv_sec, (long) usage.ru_utime.tv_usec);
fprintf(stderr, "system CPU: %ld / %ld\n", (long) usage.ru_stime.tv_sec, (long) usage.ru_stime.tv_usec);
fprintf(stderr, "RSS : %ld\n", (long) usage.ru_maxrss);
fprintf(stderr, "inblock : %ld\n", (long) usage.ru_inblock);
fprintf(stderr, "oublock : %ld\n", (long) usage.ru_oublock);
}
#endif
void run(callstatus *status)
{
char **argv;
int i;
pid_t cpid;
#ifndef WINDOWS
int exitcode;
int fd, fds;
struct rusage usage;
#else
DWORD exitcode;
#endif
char *execpid;
char buf[20]; /* for the exit code, assumed maximally a 64 byte int */
int exitstatus;
execpid = getUniquePid(status, getpid());
if (status->severity != STATUS_OK) return;
/* We open and lock the taskfile here.
* Afterwards, we'll close it in both the parent and
* after some more appends, in the child (Unix Version).
* Although this seems to decrease concurrency, this is
* needed for W95/W98 and some more platforms, where no
* modern Java (like 1.6) is available. It turned out that
* Java 1.4 sometimes starts multiple processes within _one_
* RunTime.exec() call.
* Since we lock here, and release first when we wrote the
* RUNNING state, this can be used to detect multiple starts
* of a jobexecutor (for the same taskfile)
*/
global.taskfile = openTaskfile(status);
if (status->severity != STATUS_OK) return; /* TODO: better error handling */
appendTaskfile(status, EXECPID, execpid, 1);
if (status->severity != STATUS_OK) return;
/* 1. build command array */
argv = (char **) malloc((1 + global.num_args + 1) * sizeof(char*));
if (argv == NULL) {
status->severity = SEVERITY_FATAL;
status->msg = OUT_OF_MEM;
return;
}
argv[0] = global.command;
for (i = 0; i < global.num_args; ++i) {
argv[i+1] = global.argument[i];
}
argv[i + 1] = NULL;
/* 2. redirect stdout, stderr */
redirect(status);
if (status->severity != STATUS_OK) return;
#ifndef WINDOWS
/* release the lock from the taskfile first */
/* closeTaskfile(status, global.taskfile); */
fflush(global.taskfile);
/* now we fork() and the child will do the rest */
cpid = fork();
if (cpid < 0) {
/* something went wrong */
status->severity = SEVERITY_FATAL;
status->msg = FORK_FAILED;
status->syserror = errno;
return;
}
if (cpid > 0) {
/* parent -> wait for child */
/* release the lock from the taskfile first */
closeTaskfile(status, global.taskfile);
exitcode = -1;
while (exitcode == -1) {
exitstatus = 0;
waitpid(cpid, &exitstatus, 0);
if (WIFEXITED (exitstatus))
exitcode = WEXITSTATUS (exitstatus);
else if (WIFSIGNALED (exitstatus))
exitcode = 128 + WTERMSIG (exitstatus);
}
if (global.verboselogs) {
fprintf(stdout, "------- %s End (%d) --------\n", getTimestamp(time(NULL), 1), exitcode);
if (getrusage(RUSAGE_CHILDREN, &usage) == 0) {
/* report system usage */
reportSysUse(status, usage);
if (status->severity != STATUS_OK) {
fprintf(stderr, "Error reporting system usage\n");
}
}
}
/* something might have gone wrong starting the process
so we read the taskfile again and ask for an error
if an error has been set, we don't add any information,
if not, we write the exit code and status
*/
processTaskfile(status);
if (global.error != NULL)
return;
snprintf(buf, 20, "%d", exitcode);
global.taskfile = openTaskfile(status);
if (status->severity != STATUS_OK) return;
appendTaskfile(status, RETURNCODE, buf, 1);
if (status->severity != STATUS_OK) return;
appendTaskfile(status, STATUS, (char *) STATUS_FINISHED, 1);
if (status->severity != STATUS_OK) return;
closeTaskfile(status, global.taskfile);
return;
}
global.myStartTime = time(NULL); /* we're in the child, so we can overwrite the start time */
global.taskfile = openTaskfile(status);
default_all_signals (status);
if (status->severity != STATUS_OK) {
if (myLog != NULL) Hprintf(myLog, "%s\n", renderError(status));
status->severity = STATUS_OK;
status->msg = MSG_NO_ERROR;
}
global.extpid = getUniquePid(status, getpid());
if (status->severity != STATUS_OK) return;
appendTaskfile(status, EXTPID, global.extpid, 1);
if (status->severity != STATUS_OK) return;
appendTaskfile(status, STATUS, (char *) STATUS_RUNNING, 1);
if (status->severity != STATUS_OK) return;
closeTaskfile(status, global.taskfile);
/* since the task file has been closed here, all filehandles other than
the standard filehandles should be closed.
It is not guaranteed this is the case though. So we'll close all non standard
file handles here
*/
if ((fds = getdtablesize()) == -1) fds = OPEN_MAX;
for (fd = 3; fd < fds; ++fd) {
close(fd); // ignore errors; most fd's aren't open
}
/* start a new session. This way we will be in a new process group
and we (and our children) can be easily killed by a "kill -PID".
We also don't have a controlling tty. This is normally that what
we want. (and it closes some potential security issues)
*/
if (setsid() == -1) {
/* could not set group, permission thing */
/* we ignore this for now */
}
if (global.verboselogs) {
fprintf(stdout, "------- %s Start --------\n", getTimestamp(time(NULL), 1));
fflush(stdout); /* seems to be necessary */
}
/* 3. start user process */
if (global.usepath) {
execvp(global.command, argv);
} else {
execv(global.command, argv);
}
status->severity = SEVERITY_FATAL;
status->msg = EXEC_FAILED;
status->syserror = errno;
#else
if (global.verboselogs) {
fprintf(stdout, "------- %s Start --------\n", getTimestamp(time(NULL), 1));
fflush(stdout); /* seems to be necessary */
}
// It seems that Windows only really *appends* to files if something has happend with them before the child is started,
// even if the file has been reopen()ed with "a"! (I really *LOVE* that too!!!)
// So by explicitly moving the filepointer to the end of the stream, even Windows can't refuse to *append* child's output...
else {
fseek (stdout, 0, SEEK_END);
fseek (stderr, 0, SEEK_END);
}
/* build commandline (windows doesn't know what to do with a correctly parsed command array) */
size_t size = strlen(argv[0]) + sizeof ('\0');
for (i = 1; argv[i]; ++i)
size += strlen (" ") + strlen (argv[i]);
char *const cmdline = (char *) malloc (size);
if (! cmdline) {
status->severity = SEVERITY_FATAL;
status->msg = OUT_OF_MEM;
return;
}
int ofs = sprintf (cmdline, "%s", argv[0]);
for (i = 1; argv[i]; ++i)
ofs += sprintf (cmdline + ofs, " %s", argv[i]);
/* now we need the "module" if windows shouldn't search the path */
char *module = global.usepath ? NULL : global.command;
if (module && (*module == '"')) { // module *must not* have quotes...
char *unquoted = Strdup(status, module + 1);
if (status->severity != STATUS_OK) {
return;
}
unquoted[strlen(unquoted) - 1] = '\0';
module = unquoted;
}
/* start the child process */
STARTUPINFO si;
ZeroMemory (&si, sizeof (si));
si.cb = sizeof (si);
PROCESS_INFORMATION pi;
if (! CreateProcess (
module, // module name
cmdline, // command line
NULL, // don't inherit process handle
NULL, // don't inherit thread handle
TRUE, // do inherit open handles
0, // creation flags
NULL, // use parent's environment
NULL, // use parent's current directory
&si,
&pi)) {
status->severity = SEVERITY_FATAL;
status->msg = EXEC_FAILED;
status->syserror = GetLastError();
return;
}
global.extpid = getUniquePid(status, pi.dwProcessId);
if (status->severity != STATUS_OK) return;
/* The taskfile has been opened already */
appendTaskfile(status, EXTPID, global.extpid, 1);
if (status->severity != STATUS_OK) return;
appendTaskfile(status, STATUS, (char *) STATUS_RUNNING, 1);
if (status->severity != STATUS_OK) return;
closeTaskfile(status, global.taskfile);
CloseHandle (pi.hThread);
/* now wait for the child to terminate */
if (WaitForSingleObject (pi.hProcess, INFINITE) == WAIT_FAILED) {
status->severity = SEVERITY_FATAL;
status->msg = CHLDWAIT_FAILED;
return;
}
if (! GetExitCodeProcess (pi.hProcess, &exitcode)) {
status->severity = SEVERITY_FATAL;
status->msg = CHLDWAIT_FAILED;
return;
}
snprintf(buf, 20, "%d", exitcode);
global.taskfile = openTaskfile(status);
if (status->severity != STATUS_OK) return;
appendTaskfile(status, RETURNCODE, buf, 1);
if (status->severity != STATUS_OK) return;
appendTaskfile(status, STATUS, (char *) STATUS_FINISHED, 1);
if (status->severity != STATUS_OK) return;
closeTaskfile(status, global.taskfile);
CloseHandle (pi.hProcess); /* ignore errors */
if (global.verboselogs) {
fprintf(stdout, "------- %s End (%d) --------\n", getTimestamp(time(NULL), 1), exitcode);
fflush(stdout);
}
#endif
return;
}
int main(int argc, char *argv[])
{
callstatus status;
int argsType = ARGS_NOTREAD;
global.myStartTime = time(NULL);
initFields();
status.severity = STATUS_OK;
status.msg = MSG_NO_ERROR;
openLog(&status);
argsType = checkArgs(&status, argc, argv);
switch (argsType) {
case ARGS_NOTREAD:
/* some error occurred; TODO: be more specific in error message */
fprintf(stderr, "%s\n", renderError(&status));
fprintf(stderr, "%s", getUsage());
exit(1);
case ARGS_VERSION:
fprintf(stdout, "%s", getVersion());
exit(0);
case ARGS_HELP:
fprintf(stdout, "%s", getUsage());
exit(0);
case ARGS_RUN:
if (myLog != NULL)
printJobFields();
ignore_all_signals (&status);
if (status.severity != STATUS_OK) {
if (myLog != NULL) Hprintf(myLog, "%s\n", renderError(&status));
status.severity = STATUS_OK;
status.msg = MSG_NO_ERROR;
}
run(&status);
if (status.severity != STATUS_OK) {
/* TODO write error */
if (myLog != NULL) Hprintf(myLog, "%s\n", renderError(&status));
}
closeLog(&status);
break;
default:
/* cannot happen */
fprintf(stderr, "Argument evaluation returned an unexpected value : %d\n", argsType);
fprintf(stderr, "%s", getUsage());
exit(1);
}
if (status.severity == SEVERITY_FATAL) {
/* we now try to report the error to the scheduling server by writing it into
the taskfile. This might or might not be successful. We hope for the best.
All non job related errors (incorrect call of jobexecutor) are handled in
the previous switch() statement.
*/
callstatus tmp;
tmp.severity = STATUS_OK;
tmp.msg = MSG_NO_ERROR;
#ifndef WINDOWS
global.taskfile = openTaskfile(&tmp);
if (tmp.severity != STATUS_OK) return status.msg;
#endif
appendTaskfile(&tmp, S_ERROR, renderError(&status), 1);
if (tmp.severity != STATUS_OK) return status.msg;
appendTaskfile(&tmp, STATUS, (char *) STATUS_ERROR, 1);
if (tmp.severity != STATUS_OK) return status.msg;
closeTaskfile(&tmp, global.taskfile);
/* return status.msg; */ /* TODO: interpret the exit code of the jobexecutor within jobserver */
}
return 0;
}