bool parse_actions(SV **spec, int n_spec, struct action *actions, size_t *n_actions, char *aux_buf, size_t *aux_len) {
bool success;
size_t action_pos= 0;
size_t aux_pos= 0;
int spec_i;
// Default is one SIGALRM to self
if (!spec || n_spec == 0) {
if (*n_actions) {
actions[0].op= ACT_KILL;
actions[0].orig_idx= 0;
actions[0].act.kill.signal= SIGALRM;
actions[0].act.kill.pid= getpid();
}
action_pos++;
}
else for (spec_i= 0; spec_i < n_spec; spec_i++) {
AV *action_spec;
const char *act_name= NULL;
STRLEN act_namelen= 0;
SV **el;
size_t n_el;
pid_t common_pid;
int common_op, common_signal;
// Get the arrayref for the next action
if (!(spec[spec_i] && SvROK(spec[spec_i]) && SvTYPE(SvRV(spec[spec_i])) == SVt_PVAV))
croak("Actions must be arrayrefs");
// Get the 'command' name of the action
action_spec= (AV*) SvRV(spec[spec_i]);
n_el= av_count(action_spec);
if (n_el < 1 || !(el= av_fetch(action_spec, 0, 0)) || !SvPOK(*el))
croak("First element of action must be a string");
act_name= SvPV(*el, act_namelen);
// Dispatch based on the command
switch (act_namelen) {
case 3:
if (strcmp(act_name, "sig") == 0) {
if (n_el > 2)
croak("Too many parameters for 'sig' action");
common_signal= (n_el == 2 && (el= av_fetch(action_spec, 1, 0)) != NULL && SvOK(*el))?
parse_signal(*el) : SIGALRM;
common_pid= getpid();
goto parse_kill_common;
}
if (strcmp(act_name, "run") == 0) {
common_op= ACT_RUN;
goto parse_run_common;
}
case 4:
if (strcmp(act_name, "kill") == 0) {
if (n_el != 3)
croak("Expected 2 parameters for 'kill' action");
el= av_fetch(action_spec, 1, 0);
if (!el || !SvOK(*el))
croak("Expected Signal as first parameter to 'kill'");
common_signal= parse_signal(*el);
el= av_fetch(action_spec, 2, 0);
if (!el || !SvIOK(*el))
croak("Expected PID as second parameter to 'kill'");
common_pid= SvIV(*el);
goto parse_kill_common;
}
if (strcmp(act_name, "exec") == 0) {
common_op= ACT_EXEC;
goto parse_run_common;
}
case 5:
if (strcmp(act_name, "sleep") == 0) {
if (n_el != 2)
croak("Expected 1 parameter to 'sleep' action");
el= av_fetch(action_spec, 1, 0);
if (!el || !SvOK(*el) || !looks_like_number(*el))
croak("Expected number of seconds in 'sleep' action");
if (action_pos < *n_actions) {
actions[action_pos].op= ACT_SLEEP;
actions[action_pos].orig_idx= spec_i;
actions[action_pos].act.slp.seconds= SvNV(*el);
}
++action_pos;
continue;
}
if (strcmp(act_name, "close") == 0) {
common_op= ACT_x_CLOSE;
goto parse_close_common;
}
case 6:
// The repeat feature opens the possibility of infinite busy-loops,
// and probably creates more problems than it solves.
//if (strcmp(act_name, "repeat") == 0) {
// int act_count= spec_i;
// if (!act_count)
// croak("'repeat' cannot be the first action");
// if (n_el != 1) { // default is to repeat all, via act_count=i above
// if (n_el != 2)
// croak("Expected 0 or 1 parameters to 'repeat'");
// el= av_fetch(action_spec, 1, 0);
// if (!el || !SvOK(*el) || !looks_like_number(*el) || SvIV(*el) <= 0)
// croak("Expected positive integer of actions to repeat");
// act_count= SvIV(*el);
// }
// if (action_pos < *n_actions) {
// int dest_act_idx, dest_spec_idx= spec_i - act_count;
// // Locate the first action record with orig_idx == dest_spec_idx;
// for (dest_act_idx= 0; dest_act_idx < spec_i; dest_act_idx++)
// if (actions[dest_act_idx].orig_idx == dest_spec_idx)
// break;
// actions[action_pos].op= ACT_JUMP;
// actions[action_pos].orig_idx= spec_i;
// actions[action_pos].act.jmp.idx= dest_act_idx;
// }
// ++action_pos;
// continue;
//}
if (strcmp(act_name, "shut_r") == 0) {
common_op= ACT_x_SHUT_R;
goto parse_close_common;
}
if (strcmp(act_name, "shut_w") == 0) {
common_op= ACT_x_SHUT_W;
goto parse_close_common;
}
case 7:
if (strcmp(act_name, "shut_rw") == 0) {
common_op= ACT_x_SHUT_RW;
goto parse_close_common;
}
default:
croak("Unknown command '%s' in action list", act_name);
}
if (0) parse_kill_common: { // arrive from 'kill' and 'sig'
// common_signal, common_pid will be set.
// Is there an available action?
if (action_pos < *n_actions) {
actions[action_pos].op= ACT_KILL;
actions[action_pos].orig_idx= spec_i;
actions[action_pos].act.kill.signal= common_signal;
actions[action_pos].act.kill.pid= common_pid;
}
++action_pos;
}
if (0) parse_run_common: { // arrive from 'run' and 'exec'
char **argv= NULL, *str;
STRLEN len;
int j, argc= n_el-1;
// common_op will be set.
if (n_el < 2)
croak("Expected at least one parameter for '%s'", act_name);
// Align to pointer boundary within aux_buf
aux_pos += sizeof(void*) - 1;
aux_pos &= ~(sizeof(void*) - 1);
// allocate an array of char* within aux_buf
// argv remains NULL if there isn't room for it
if (aux_pos + sizeof(void*) * n_el <= *aux_len)
argv= (char**)(aux_buf + aux_pos);
aux_pos += sizeof(void*) * (argc+1);
// size up each of the strings, and copy them to the buffer if space available
for (j= 0; j < argc; j++) {
el= av_fetch(action_spec, j+1, 0);
if (!el || !*el || !SvOK(*el))
croak("Found undef element in arguments for '%s'", act_name);
str= SvPV(*el, len);
if (argv && aux_pos + len + 1 <= *aux_len) {
argv[j]= aux_buf + aux_pos;
memcpy(argv[j], str, len+1);
}
aux_pos += len+1;
}
// argv lists must end with NULL
if (argv)
argv[argc]= NULL;
// store in an action if space remaining.
if (action_pos < *n_actions) {
actions[action_pos].op= common_op;
actions[action_pos].orig_idx= spec_i;
actions[action_pos].act.run.argc= argc;
actions[action_pos].act.run.argv= argv;
}
++action_pos;
}
if (0) parse_close_common: { // arrive from 'close', 'shut_r', 'shut_w', 'shut_rw'
int j;
const char *str;
STRLEN len;
// common_op will be set, and can be ORed with the variant
if (n_el < 2)
croak("Expected 1 or more parameters to '%s'", act_name);
// Each parameter is another action_fd or action_sockname action
for (j= 1; j < n_el; j++) {
el= av_fetch(action_spec, j, 0);
if (!el || !*el || !SvOK(*el))
croak("'%s' parameter %d is undefined", act_name, j-1);
// If not a ref...
if (!SvROK(*el)) {
// Is it a file descriptor integer?
if (looks_like_number(*el)) {
IV fd= SvIV(*el);
if (fd >= 0 && fd < 0x10000) {
if (action_pos < *n_actions) {
actions[action_pos].op= common_op | ACT_FD_x;
actions[action_pos].orig_idx= spec_i;
actions[action_pos].act.fd.fd= fd;
}
++action_pos;
continue;
}
}
str= SvPV(*el, len);
// Is the length one of struct sockaddr_in, sockaddr_un, or sockaddr?
if (len == sizeof(struct sockaddr)
|| len == sizeof(struct sockaddr_in)
|| len == sizeof(struct sockaddr_un)
) {
if (action_pos < *n_actions) {
actions[action_pos].op= common_op | ACT_PNAME_x;
actions[action_pos].orig_idx= spec_i;
actions[action_pos].act.nam.addr_len= len;
actions[action_pos].act.nam.addr= (struct sockaddr*) (aux_buf+aux_pos);
}
if (aux_pos + len <= *aux_len)
memcpy((aux_buf+aux_pos), str, len);
aux_pos += len;
action_pos++;
continue;
}
// TODO: parse host:port strings
}
else {
// Try getting a file descriptor from the ref
int fd= fileno_from_sv(*el);
if (fd >= 0) {
if (action_pos < *n_actions) {
actions[action_pos].op= common_op | ACT_FD_x;
actions[action_pos].orig_idx= spec_i;
actions[action_pos].act.fd.fd= fd;
}
++action_pos;
continue;
}
// TODO: allow notation for socket self-name like { sockname => $x }
// and maybe allow a full pair of { sockname => $x, peername => $y }
// or even partial matches like { port => $num }
}
str= SvPV(*el, len);
croak("Invalid parameter to '%s': '%s'; must be integer (fileno), file handle, or socket name like from getpeername", act_name, str);
}
}
}
success= (action_pos <= *n_actions) && (aux_pos <= *aux_len);
*n_actions= action_pos;
*aux_len= aux_pos;
return success;
}
bool execute_action(struct action *act, bool resume, struct timespec *now_ts, struct socketalarm *parent) {
int low= act->op & 0xF;
int high= act->op & ~0xF;
int how;
char msgbuf[128];
switch (high) {
case ACT_KILL:
if (kill(act->act.kill.pid, act->act.kill.signal) != 0)
perror("kill");
return true; // move to next action
case ACT_SLEEP: {
lazy_build_now_ts(now_ts);
// On initial entry to this action, use current time to calculate the wake time
if (!resume) {
double t_seconds;
t_seconds= (double) now_ts->tv_sec + .000000001 * now_ts->tv_nsec;
// Add seconds to this time
t_seconds += act->act.slp.seconds;
parent->wake_ts.tv_sec= (time_t) t_seconds;
parent->wake_ts.tv_nsec= (t_seconds - (long) t_seconds) * 1000000000;
if (!parent->wake_ts.tv_nsec)
parent->wake_ts.tv_nsec= 1; // because using tv_nsec as a defined-test
return false; // come back later
}
// Else see whether we have reached that time yet
if (now_ts->tv_sec > parent->wake_ts.tv_sec
|| (now_ts->tv_sec == parent->wake_ts.tv_sec && now_ts->tv_nsec >= parent->wake_ts.tv_nsec))
return true; // reached end_ts
return false; // still waiting
}
//case ACT_JUMP:
// parent->cur_action= act->act.jmp.idx - 1; // parent will ++ after we return true
// return true;
case ACT_FD_x:
switch (low) {
case ACT_x_SHUT_R: how= SHUT_RD; if (0)
case ACT_x_SHUT_W: how= SHUT_WR; if (0)
case ACT_x_SHUT_RW: how= SHUT_RDWR;
if (shutdown(act->act.fd.fd, how) < 0) perror("shutdown");
break;
default:
if (close(act->act.fd.fd) < 0) perror("close");
}
return true;
case ACT_PNAME_x:
case ACT_SNAME_x: {
struct sockaddr_storage addr;
socklen_t len;
int ret, i;
for (i= 0; i < 1024; i++) {
len= sizeof(addr);
ret= high == ACT_PNAME_x? getpeername(i, (struct sockaddr*)&addr, &len)
: getsockname(i, (struct sockaddr*)&addr, &len);
if (ret == 0 && len == act->act.nam.addr_len
&& memcmp(act->act.nam.addr, &addr, len) == 0
) {
switch (low) {
case ACT_x_SHUT_R: how= SHUT_RD; if (0)
case ACT_x_SHUT_W: how= SHUT_WR; if (0)
case ACT_x_SHUT_RW: how= SHUT_RDWR;
if (shutdown(i, how) < 0) perror("shutdown");
break;
default:
if (close(i) < 0) perror("close");
}
}
}
return true;
}
case ACT_EXEC: {
char **argv= act->act.run.argv;
if (act->op == ACT_RUN) {
// double-fork, so that parent can reap child, and grandchild gets cleaned up by init()
pid_t child, gchild;
if ((child= fork()) < 0) { // fork failure
perror("fork");
return true;
}
else if (child > 0) { // parent - wait for immediate child to return
int status= -1;
waitpid(child, &status, 0);
if (status != 0)
perror("waitpid"); // not accurate, but probably not going to happen unless child fails to fork
return true;
}
else if ((gchild= fork()) != 0) { // second fork
if (gchild < 0) perror("fork");
_exit(gchild < 0? 1 : 0); // immediately exit
}
// else we are the grandchild now
}
close(0);
open("/dev/null", O_RDONLY);
execvp(argv[0], argv);
perror("exec"); // if we got here, it failed. Log the error.
_exit(1); // make sure we don't continue this process.
}
default: {
int unused= write(2, msgbuf, snprintf(msgbuf, sizeof(msgbuf), "BUG: No such action code %d", act->op));
(void) unused;
return true; // pretend success; false would cause it to come back to this action later
}}
}
const char *act_fd_variant_name(int variant) {
switch (variant) {
case ACT_x_CLOSE: return "close";
case ACT_x_SHUT_R: return "shut_r";
case ACT_x_SHUT_W: return "shut_w";
case ACT_x_SHUT_RW: return "shut_rw";
default: return "BUG";
}
}
const char *act_fd_variant_description(int variant) {
switch (variant) {
case ACT_x_CLOSE: return "close";
case ACT_x_SHUT_R: return "shutdown SHUT_RD";
case ACT_x_SHUT_W: return "shutdown SHUT_WR";
case ACT_x_SHUT_RW: return "shutdown SHUT_RDWR";
default: return "BUG";
}
}
static void inflate_action(struct action *act, AV *dest) {
int low= act->op & 0xF;
int high= act->op & ~0xF;
int i;
switch (high) {
case ACT_KILL: av_extend(dest, 2);
av_push(dest, newSVpvs("kill"));
av_push(dest, newSViv(act->act.kill.signal));
av_push(dest, newSViv(act->act.kill.pid));
return;
case ACT_SLEEP: av_extend(dest, 1);
av_push(dest, newSVpvs("sleep"));
av_push(dest, newSVnv(act->act.slp.seconds));
return;
//case ACT_JUMP: return snprintf(buffer, buflen, "goto %d", (int)act->act.jmp.idx);
case ACT_FD_x: av_extend(dest, 1);
av_push(dest, newSVpv(act_fd_variant_name(low), 0));
av_push(dest, newSViv(act->act.fd.fd));
return;
case ACT_PNAME_x: av_extend(dest, 1);
av_push(dest, newSVpv(act_fd_variant_name(low), 0));
av_push(dest, newSVpvn((char*)act->act.nam.addr, act->act.nam.addr_len));
return;
case ACT_EXEC: av_extend(dest, act->act.run.argc);
av_push(dest, newSVpv(act->op == ACT_RUN? "run":"exec", 0));
for (i= 0; i < act->act.run.argc; i++)
av_push(dest, newSVpv(act->act.run.argv[i], 0));
return;
default:
croak("BUG: action code %d", act->op);
}
}
int snprint_action(char *buffer, size_t buflen, struct action *act) {
int low= act->op & 0xF;
int high= act->op & ~0xF;
switch (high) {
case ACT_KILL: return snprintf(buffer, buflen, "kill sig=%d pid=%d", (int)act->act.kill.signal, (int) act->act.kill.pid);
case ACT_SLEEP: return snprintf(buffer, buflen, "sleep %.3lfs", (double)act->act.slp.seconds);
//case ACT_JUMP: return snprintf(buffer, buflen, "goto %d", (int)act->act.jmp.idx);
case ACT_FD_x: return snprintf(buffer, buflen, "%s %d", act_fd_variant_description(low), act->act.fd.fd);
case ACT_PNAME_x:
case ACT_SNAME_x: {
int pos= snprintf(buffer, buflen, "%s %s ",
act_fd_variant_description(low),
high == ACT_PNAME_x? "peername":"sockname"
);
return pos + snprint_sockaddr(buffer+pos, buflen > pos? buflen-pos : 0, act->act.nam.addr);
}
case ACT_EXEC: {
int i, pos= snprintf(buffer, buflen, "%sexec(", act->op == ACT_RUN? "fork,fork," : "");
for (i= 0; i < act->act.run.argc; i++) {
pos += snprintf(buffer+pos, buflen > pos? buflen-pos : 0, "'%s',", act->act.run.argv[i]);
}
// if still in bounds, overwrite final character with ')'
if (pos < buflen)
buffer[pos-1]= ')';
return pos;
}
default:
return snprintf(buffer, buflen, "BUG: action code %d", act->op);
}
}