// crm_expr_syscall.c - Controllable Regex Mutilator, version v1.0
// Copyright 2001-2006 William S. Yerazunis, all rights reserved.
//
// This software is licensed to the public under the Free Software
// Foundation's GNU GPL, version 2. You may obtain a copy of the
// GPL by visiting the Free Software Foundations web site at
// www.fsf.org, and a copy is included in this distribution.
//
// Other licenses may be negotiated; contact the
// author for details.
//
// include some standard files
#include "crm114_sysincludes.h"
// include any local crm114 configuration file
#include "crm114_config.h"
// include the crm114 data structures file
#include "crm114_structs.h"
// and include the routine declarations file
#include "crm114.h"
// the command line argc, argv
extern int prog_argc;
extern char **prog_argv;
// the auxilliary input buffer (for WINDOW input)
extern char *newinputbuf;
// the globals used when we need a big buffer - allocated once, used
// wherever needed. These are sized to the same size as the data window.
extern char *inbuf;
extern char *outbuf;
extern char *tempbuf;
#ifdef WIN32
typedef struct
{
HANDLE to_minion;
char *inbuf;
long inlen;
long internal_trace;
long keep_proc;
} pusherparams;
typedef struct
{
HANDLE from_minion;
int timeout;
} suckerparams;
DWORD WINAPI pusher_proc(LPVOID lpParameter)
{
DWORD bytesWritten;
pusherparams *p = (pusherparams *)lpParameter;
WriteFile(p->to_minion, p->inbuf, p->inlen, &bytesWritten, NULL);
free(p->inbuf);
if (p->internal_trace)
fprintf (stderr, "pusher: input sent to minion.\n");
// if we don't want to keep this proc, we close it's input, and
// wait for it to exit.
if (! p->keep_proc)
{
CloseHandle (p->to_minion);
if (internal_trace)
fprintf (stderr, "minion input pipe closed\n");
}
if (p->internal_trace)
fprintf (stderr, "pusher: exiting pusher\n");
return 0;
}
DWORD WINAPI sucker_proc(LPVOID lpParameter)
{
DWORD bytesRead;
suckerparams *p = (suckerparams *)lpParameter;
char *outbuf = malloc(sizeof(char) * 8192);
// we're in the sucker process here- just throw away
// everything till we get EOF, then exit.
while (1)
{
Sleep (p->timeout);
ReadFile(p->from_minion, outbuf,
8192, &bytesRead, NULL);
if (bytesRead == 0) break;
};
return 0;
}
#endif
int crm_expr_syscall ( CSL_CELL *csl, ARGPARSE_BLOCK *apb)
{
// Go off and fork a process, sending that process
// one pattern evaluated as input, and then accepting
// all the returns from that process as the new value
// for a variable.
//
// syntax is:
// exec (:to:) (:from:) (:ctl:) /commandline/
long inlen;
long outlen;
char from_var [MAX_VARNAME];
char sys_cmd [MAX_PATTERN];
long cmd_len;
char keep_buf [MAX_PATTERN];
long keep_len;
char exp_keep_buf[MAX_PATTERN];
long exp_keep_len;
long vstart;
long vlen;
long done, charsread;
int keep_proc;
int async_mode;
int to_minion[2];
int from_minion[2];
pid_t minion;
int minion_exit_status;
pid_t pusher;
pid_t sucker;
pid_t random_child;
int status;
long timeout;
#ifdef WIN32
SECURITY_ATTRIBUTES pipeSecAttr;
HANDLE hminion;
#endif
#ifdef POSIX
if (user_trace)
fprintf (stderr, "executing an SYSCALL statement");
timeout = MINION_SLEEP_USEC;
// clean up any prior processes - note that
// we don't keep track of these. For that matter, we have
// no context to keep track of 'em.
//
while ( (random_child = waitpid ( 0, &status, WNOHANG)) > 0 );
#endif
#ifdef WIN32
timeout = MINION_SLEEP_USEC / 1000; // need milliseconds for Sleep()
if (MINION_SLEEP_USEC > 0 && timeout == 0)
{
timeout = 1;
}
#endif
// get the flags
//
keep_proc = 0;
if (apb->sflags & CRM_KEEP)
{
if (user_trace)
fprintf (stderr, "Keeping the process around if possible\n");
keep_proc = 1;
};
async_mode = 0;
if (apb->sflags & CRM_ASYNC)
{
if (user_trace)
fprintf (stderr, "Letting the process go off on it's own");
async_mode = 1;
};
// Sanity check - <async> is incompatible with <keep>
//
if (keep_proc && async_mode)
{
nonfatalerror ("This syscall uses both async and keep, but async is "
"incompatible with keep. Since keep is safer"
"we will use that.\n",
"You need to fix this program.");
async_mode = 0;
};
// get the input variable(s)
//
crm_get_pgm_arg (inbuf, data_window_size, apb->p1start, apb->p1len);
inlen = crm_nexpandvar (inbuf, apb->p1len, data_window_size);
if (user_trace)
fprintf (stderr, " command's input wil be: ***%s***\n", inbuf);
// now get the name of the variable where the return will be
// placed... this is a crock and should be fixed someday.
// the output goes only into a single var (the first one)
// so we extract that
//
crm_get_pgm_arg (from_var, MAX_PATTERN, apb->p2start, apb->p2len);
outlen = crm_nexpandvar (from_var, apb->p2len, MAX_PATTERN);
done = 0;
vstart = 0;
while (from_var[vstart] < 0x021 && from_var[vstart] > 0x0 )
vstart++;
vlen = 0;
while (from_var[vstart+vlen] >= 0x021)
vlen++;
memmove (from_var, &from_var[vstart], vlen);
from_var[vlen] = '\000';
if (user_trace)
fprintf (stderr, " command output will overwrite var ***%s***\n",
from_var);
// now get the name of the variable (if it exists) where
// the kept-around minion process's pipes and pid are stored.
crm_get_pgm_arg (keep_buf, MAX_PATTERN, apb->p3start, apb->p3len);
keep_len = crm_nexpandvar (keep_buf, apb->p3len, MAX_PATTERN);
if (user_trace)
fprintf (stderr, " command status kept in var ***%s***\n",
keep_buf);
// get the command to execute
//
crm_get_pgm_arg (sys_cmd, MAX_PATTERN, apb->s1start, apb->s1len);
cmd_len = crm_nexpandvar (sys_cmd, apb->s1len, MAX_PATTERN);
if (user_trace)
fprintf (stderr, " command will be ***%s***\n", sys_cmd);
// Do we reuse an already-existing process? Check to see if the
// keeper variable has it... note that we have to :* prefix it
// and expand it again.
minion = 0;
to_minion[0] = 0;
from_minion[1] = 0;
exp_keep_buf [0] = '\000';
// this is 8-bit-safe because vars are never wchars.
strcat (exp_keep_buf, ":*");
strncat (exp_keep_buf, keep_buf, keep_len);
exp_keep_len = crm_nexpandvar (exp_keep_buf, keep_len+2, MAX_PATTERN);
sscanf (exp_keep_buf, "MINION PROC PID: %d from-pipe: %d to-pipe: %d",
&minion,
&from_minion[0],
&to_minion[1]);
#ifdef POSIX
// if, no minion already existing, we create
// communications pipes and launch the subprocess. This
// code borrows concepts from both liblaunch and from
// netcat (thanks, *Hobbit*!)
//
if (minion == 0)
{
long status1, status2;
if (user_trace)
fprintf (stderr, " Must start a new minion.\n");
status1 = pipe (to_minion);
status2 = pipe (from_minion);
if (status1 > 0 || status2 > 0)
{
nonfatalerror ("Problem setting up the to/from pipes to a minion. ",
"Perhaps the system file descriptor table is full?");
return (1);
};
minion = fork();
if (minion < 0)
{
nonfatalerror ("Tried to fork your minion, but it failed.",
"Your system may have run out of process slots");
return (1);
};
if (minion == 0)
{ // START OF IN THE MINION
//
// if minion == 0, then We're in the minion here
int retcode;
long vstart, vlen;
long varline;
// close the ends of the pipes we don't need.
//
// NOTE: if this gets messed up, you end up with a race
// condition, because both master and minion processes
// can both read and write both pipes (effectively a
// process could write something out, then read it again
// right back out of the pipe)! So, it's REALLY REALLY
// IMPORTANT that you use two pipe structures, (one for
// each direction) and you keep track of which process
// should write to which pipe!!!
//
close (to_minion[1]);
close (from_minion[0]);
dup2 (to_minion[0], fileno(stdin));
dup2 (from_minion[1], fileno(stdout));
// Are we a syscall to a :label:, or should we invoke the
// shell on an external command?
//
crm_nextword (sys_cmd, strlen (sys_cmd), 0, &vstart, &vlen);
varline = crm_lookupvarline (vht, sys_cmd, vstart, vlen);
if (varline > 0)
{
// sys_cmd[vstart+vlen] = '\0';
if (user_trace)
fprintf (stderr, "FORK transferring control to line %s\n",
&sys_cmd[vstart]);
// set the current pid and parent pid.
{
char pidstr [32];
long pid;
pid = (long) getpid();
sprintf (pidstr, "%ld", pid);
crm_set_temp_var (":_pid:", pidstr);
if (user_trace)
fprintf (stderr, "My new PID is %s\n", pidstr);
pid = (long) getppid();
sprintf (pidstr, "%ld", pid);
crm_set_temp_var (":_ppid:", pidstr);
}
// See if we have redirection of stdin and stdout
while (crm_nextword (sys_cmd, strlen (sys_cmd), vstart+vlen,
&vstart, &vlen))
{
char filename[MAX_PATTERN];
if (sys_cmd[vstart] == '<')
{
strncpy (filename, &sys_cmd[vstart+1], vlen);
filename[vlen-1] = '\0';
if (user_trace)
fprintf (stderr, "Redirecting minion stdin to %s\n",
filename);
freopen (filename, "rb", stdin);
};
if (sys_cmd[vstart] == '>')
{
if (sys_cmd[vstart+1] != '>')
{
strncpy (filename, &sys_cmd[vstart+1], vlen);
filename[vlen-1] = '\0';
if (user_trace)
fprintf (stderr,
"Redirecting minion stdout to %s\n",
filename);
freopen (filename, "wb", stdout);
}
else
{
strncpy (filename, &sys_cmd[vstart+2], vlen);
filename[vlen-2] = '\0';
if (user_trace)
fprintf (stderr,
"Appending minion stdout to %s\n",
filename);
freopen (filename, "a+", stdout);
}
};
}
csl->cstmt = varline;
// and note that this isn't a failure.
csl->aliusstk [ csl->mct[csl->cstmt]->nest_level ] = 1;
// The minion's real work should now start; get out of
// the syscall code and go run something real. :)
return (0);
}
else
{
if (user_trace)
fprintf (stderr, "Systemcalling on shell command %s\n",
sys_cmd);
retcode = system (sys_cmd);
//
// This code only ever happens if an error occurs...
//
if (retcode == -1 )
{
char errstr [4096];
sprintf (errstr,
"The command was >%s< and returned exit code %d .",
sys_cmd, WEXITSTATUS (retcode));
nonfatalerror ("This program tried a shell command that "
"didn't run correctly. ",
errstr);
if (engine_exit_base != 0)
{
exit (engine_exit_base + 11);
}
else
exit (WEXITSTATUS (retcode ));
};
exit ( WEXITSTATUS (retcode) );
};
}; // END OF IN THE MINION
}
else
{
if (user_trace)
fprintf (stderr, " reusing old minion PID: %d\n", minion);
};
// Now, we're out of the minion for sure.
// so we close the pipe ends we know we won't be using.
if (to_minion[0] != 0)
{
close (to_minion[0]);
close (from_minion[1]);
};
//
// launch "pusher" process to send the buffer to the minion
// (this hint from Dave Soderberg). This avoids the deadly
// embrace situation where both processes are waiting to read
// (or, equally, both processes have written and filled up
// their buffers, and are now held up waiting for the other
// process to empty some space in the output buffer)
//
if (strlen (inbuf) > 0)
{
pusher = fork ();
// we're in the "input pusher" process if we got here.
// shove the input buffer out to the minion
if (pusher == 0)
{
write (to_minion[1], inbuf, inlen );
if (internal_trace)
fprintf (stderr, "pusher: input sent to minion.\n");
close (to_minion[1]);
if (internal_trace)
fprintf (stderr, "pusher: minion input pipe closed\n");
if (internal_trace)
fprintf (stderr, "pusher: exiting pusher\n");
// The pusher always exits with success, so do NOT
// do not use the engine_exit_base value
exit ( EXIT_SUCCESS );
};
};
// now we're out of the pusher process.
// if we don't want to keep this proc, we close it's input, and
// wait for it to exit.
if (! keep_proc)
{
close (to_minion[1]);
if (internal_trace)
fprintf (stderr, "minion input pipe closed\n");
}
// and see what is in the pipe for us.
outbuf[0] = '\000';
done = 0;
outlen = 0;
// grot grot grot this only works if varnames are not widechars
if (strlen (from_var) > 0)
{
if (async_mode == 0 && keep_proc == 0)
{
usleep (timeout);
// synchronous read- read till we hit EOF, which is read
// returning a char count of zero.
readloop:
if (internal_trace) fprintf (stderr, "SYNCH READ ");
usleep (timeout);
charsread =
read (from_minion[0],
&outbuf[done],
(data_window_size >> SYSCALL_WINDOW_RATIO) - done - 2);
done = done + charsread;
if ( charsread > 0
&& done + 2 < (data_window_size >> SYSCALL_WINDOW_RATIO))
goto readloop;
if (done < 0) done = 0;
outbuf [done] = '\000';
outlen = done ;
};
if (keep_proc == 1 || async_mode == 1)
{
// we're in either 'keep' 'async' mode. Set nonblocking mode, then
// read it once; then put it back in regular mode.
//fcntl (from_minion[0], F_SETFL, O_NONBLOCK);
// usleep (timeout);
charsread = read (from_minion[0],
&outbuf[done],
(data_window_size >> SYSCALL_WINDOW_RATIO));
done = charsread;
if (done < 0) done = 0;
outbuf [done] = '\000';
outlen = done ;
//fcntl (from_minion[0], F_SETFL, 0);
};
// If the minion process managed to fill our buffer, and we
// aren't "keep"ing it around, OR if the process is "async",
// then we should also launch a sucker process to
// asynchronously eat all of the stuff we couldn't get into
// the buffer. The sucker proc just reads stuff and throws it
// away asynchronously... and exits when it gets EOF.
//
if ( async_mode ||
(outlen >= ((data_window_size >> SYSCALL_WINDOW_RATIO) - 2 )
&& keep_proc == 0))
{
sucker = fork ();
if (sucker == 0)
{
// we're in the sucker process here- just throw away
// everything till we get EOF, then exit.
while (1)
{
usleep (timeout);
charsread = read (from_minion[0],
&outbuf[0],
data_window_size >> SYSCALL_WINDOW_RATIO );
// in the sucker here, don't use engine_exit_base exit
if (charsread == 0) exit (EXIT_SUCCESS);
};
};
};
// and set the returned value into from_var.
if (user_trace)
fprintf (stderr, "SYSCALL output: %ld chars ---%s---.\n ",
outlen, outbuf);
if (internal_trace)
fprintf (stderr, " storing return str in var %s\n", from_var);
crm_destructive_alter_nvariable ( from_var, vlen, outbuf, outlen);
};
// Record useful minion data, if possible.
if (strlen (keep_buf) > 0)
{
sprintf (exp_keep_buf,
"MINION PROC PID: %d from-pipe: %d to-pipe: %d",
minion,
from_minion[0],
to_minion[1]);
if (internal_trace)
fprintf (stderr, " saving minion state: %s \n",
exp_keep_buf);
crm_destructive_alter_nvariable (keep_buf, keep_len,
exp_keep_buf,
strlen (exp_keep_buf));
};
// If we're keeping this minion process around, record the useful
// information, like pid, in and out pipes, etc.
if (keep_proc || async_mode)
{
}
else
{
if (internal_trace)
fprintf (stderr, "No keep, no async, so not keeping minion, closing everything.\n");
// de-zombify any dead minions;
waitpid ( minion, &minion_exit_status, 0);
// we're not keeping it around, so close the pipe.
//
close (from_minion [0]);
if ( crm_vht_lookup (vht, keep_buf, strlen (keep_buf)))
{
char exit_value_string[MAX_VARNAME];
if (internal_trace)
fprintf (stderr, "minion waitpid result :%d; whacking %s\n",
minion_exit_status,
keep_buf);
sprintf (exit_value_string, "DEAD MINION, EXIT CODE: %d",
WEXITSTATUS (minion_exit_status));
if (keep_len > 0)
crm_destructive_alter_nvariable (keep_buf, keep_len,
exit_value_string,
strlen (exit_value_string));
};
};
#endif
#ifdef WIN32
// if, no minion already existing, we create
// communications pipes and launch the subprocess. This
// code borrows concepts from both liblaunch and from
// netcat (thanks, *Hobbit*!)
//
if (minion == 0)
{
int retcode;
long vstart, vlen;
long varline;
if (user_trace)
fprintf (stderr, " Must start a new minion.\n");
pipeSecAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
pipeSecAttr.bInheritHandle = TRUE;
pipeSecAttr.lpSecurityDescriptor = NULL;
status = CreatePipe(&to_minion[0], &to_minion[1], &pipeSecAttr, 2^10 * 32);
status = CreatePipe(&from_minion[0], &from_minion[1], &pipeSecAttr, 2^10 * 32);
crm_nextword (sys_cmd, strlen (sys_cmd), 0, &vstart, &vlen);
varline = crm_lookupvarline (vht, sys_cmd, vstart, vlen);
if (varline > 0)
{
fatalerror (" Sorry, syscall to a label isn't implemented in this version", "");
}
else
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
HANDLE stdout_save, stdin_save;
HANDLE to_minion_write, from_minion_read;
stdout_save = GetStdHandle(STD_OUTPUT_HANDLE);
SetStdHandle(STD_OUTPUT_HANDLE, from_minion[1]);
stdin_save = GetStdHandle(STD_INPUT_HANDLE);
SetStdHandle(STD_INPUT_HANDLE, to_minion[0]);
DuplicateHandle(GetCurrentProcess(), from_minion[0], GetCurrentProcess(), &from_minion_read , 0, FALSE, DUPLICATE_SAME_ACCESS);
CloseHandle(from_minion[0]);
from_minion[0] = from_minion_read;
DuplicateHandle(GetCurrentProcess(), to_minion[1], GetCurrentProcess(), &to_minion_write , 0, FALSE, DUPLICATE_SAME_ACCESS);
CloseHandle(to_minion[1]);
to_minion[1] = to_minion_write;
if (user_trace)
fprintf (stderr, "systemcalling on shell command %s\n",
sys_cmd);
ZeroMemory( &si, sizeof(si) );
si.cb = sizeof(si);
ZeroMemory( &pi, sizeof(pi) );
retcode = CreateProcess(NULL, sys_cmd, NULL, NULL, TRUE , NULL, NULL, NULL, &si, &pi);
if (!retcode)
{
char errstr [4096];
sprintf (errstr, "The command was >>%s<< and returned exit code %d .",
sys_cmd, retcode);
fatalerror ("This program tried a shell command that "
"didn't run correctly. ",
errstr);
{ if (engine_exit_base != 0)
{
exit (engine_exit_base + 13);
}
else
exit ( EXIT_FAILURE );
}
}
else
{
minion = pi.dwProcessId;
hminion = pi.hProcess;
SetStdHandle(STD_OUTPUT_HANDLE, stdout_save);
SetStdHandle(STD_INPUT_HANDLE, stdin_save);
CloseHandle(pi.hThread);
}
};
}
else
{
if (user_trace)
fprintf (stderr, " reusing old minion PID: %d\n", minion);
hminion = OpenProcess(PROCESS_ALL_ACCESS, 0, minion);
if (hminion == NULL)
fatalerror("Couldn't open the existing minion process", "");
};
// Now, we're out of the minion for sure.
// so we close the pipe ends we know we won't be using.
if (to_minion[0] != 0)
{
CloseHandle (to_minion[0]);
CloseHandle (from_minion[1]);
};
//
// launch "pusher" process to send the buffer to the minion
// (this hint from Dave Soderberg). This avoids the deadly
// embrace situation where both processes are waiting to read
// (or, equally, both processes have written and filled up
// their buffers, and are now held up waiting for the other
// process to empty some space in the output buffer)
//
if (strlen (inbuf) > 0)
{
HANDLE hThread;
pusherparams pp;
char *inbuf_copy = malloc(sizeof(char) * inlen+1);
int i;
//Since the pusher thread may continue executing after the
//syscall statement has finished, we need to make a copy of
//inbuf for the pusher thread to use. The pusher process will
//free the memory.
for (i=0; i<inlen; i++)
inbuf_copy[i] = inbuf[i];
inbuf_copy[inlen] = 0;
pp.inbuf = inbuf_copy;
pp.inlen = inlen;
pp.internal_trace = internal_trace;
pp.keep_proc = keep_proc;
pp.to_minion = to_minion[1];
CreateThread(NULL, 0, pusher_proc , &pp , 0, &hThread);
}
// and see what is in the pipe for us.
outbuf[0] = '\000';
done = 0;
outlen = 0;
// grot grot grot this only works if varnames are not widechars
if (strlen (from_var) > 0)
{
if (async_mode == 0)
{
Sleep (timeout);
// synchronous read- read till we hit EOF, which is read
// returning a char count of zero.
readloop:
if (internal_trace) fprintf (stderr, "SYNCH READ ");
Sleep (timeout);
charsread = 0;
ReadFile(from_minion[0],
outbuf + done,
(data_window_size >> SYSCALL_WINDOW_RATIO) - done - 2,
&charsread, NULL);
done = done + charsread;
if (charsread > 0 && done + 2 < (data_window_size >> SYSCALL_WINDOW_RATIO))
goto readloop;
if (done < 0) done = 0;
outbuf [done] = '\000';
outlen = done ;
}
else
{
// we're in 'async' mode. Just grab what we can
ReadFile(from_minion[0],
&outbuf[done],
(data_window_size >> SYSCALL_WINDOW_RATIO), &charsread, NULL);
done = charsread;
if (done < 0) done = 0;
outbuf [done] = '\000';
outlen = done ;
}
// If the minion process managed to fill our buffer, and we
// aren't "keep"ing it around, OR if the process is "async",
// then we should also launch a sucker process to
// asynchronously eat all of the stuff we couldn't get into
// the buffer. The sucker proc just reads stuff and throws it
// away asynchronously... and exits when it gets EOF.
//
if ( async_mode || (outlen >= ((data_window_size >> SYSCALL_WINDOW_RATIO) - 2 )
&& keep_proc == 0))
{
HANDLE hThread;
suckerparams sp;
sp.from_minion = from_minion[0];
sp.timeout = timeout;
CreateThread(NULL, 0, sucker_proc , &sp , NULL, &hThread);
}
// and set the returned value into from_var.
if (user_trace)
fprintf (stderr, "SYSCALL output: %ld chars ---%s---.\n ",
outlen, outbuf);
if (internal_trace)
fprintf (stderr, " storing return str in var %s\n", from_var);
crm_destructive_alter_nvariable ( from_var, vlen, outbuf, outlen);
}
// Record useful minion data, if possible.
if (strlen (keep_buf) > 0)
{
sprintf (exp_keep_buf,
"MINION PROC PID: %d from-pipe: %d to-pipe: %d",
minion,
from_minion[0],
to_minion[1]);
if (internal_trace)
fprintf (stderr, " saving minion state: %s \n", exp_keep_buf);
crm_destructive_alter_nvariable (keep_buf, keep_len,
exp_keep_buf,
strlen (exp_keep_buf));
};
// If we're keeping this minion process around, record the useful
// information, like pid, in and out pipes, etc.
if (!keep_proc && !async_mode)
{
DWORD exit_code;
if (internal_trace)
fprintf (stderr, "No keep, no async, so not keeping minion, closing everything.\n");
// no, we're not keeping it around, so close the pipe.
//
CloseHandle(from_minion [0]);
WaitForSingleObject(hminion, INFINITE);
if (!GetExitCodeProcess(hminion, &exit_code))
{
DWORD error = GetLastError();
}
if ( crm_vht_lookup (vht, keep_buf, strlen (keep_buf)))
{
char exit_value_string[MAX_VARNAME];
if (internal_trace)
fprintf (stderr, "minion exit code :%d; whacking %s\n",
exit_code,
keep_buf);
sprintf (exit_value_string, "DEAD MINION, EXIT CODE: %d",
exit_code);
if (keep_len > 0)
crm_destructive_alter_nvariable (keep_buf, keep_len,
exit_value_string,
strlen (exit_value_string));
};
CloseHandle(hminion);
};
#endif
return (0);
};
syntax highlighted by Code2HTML, v. 0.9.1