// crm_expr_syscall.c - Controllable Regex Mutilator, version v1.0 // Copyright 2001-2004 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; 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; if (user_trace) fprintf (stderr, "executing an SYSCALL statement"); // 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 ); // 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 - is incompatible with // 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]); // 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) { if (user_trace) fprintf (stderr, " Must start a new minion.\n"); status = pipe (to_minion); status = pipe (from_minion); minion = fork(); if (minion == 0) { // We're in the minion here; close the ends of the // pipes we don't use. 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!!! int retcode; close (to_minion[1]); close (from_minion[0]); dup2 (to_minion[0], fileno(stdin)); dup2 (from_minion[1], fileno(stdout)); if (user_trace) fprintf (stderr, "systemcalling on %s\n", sys_cmd); retcode = system (sys_cmd); if (retcode == -1) exit ( EXIT_FAILURE ); exit ( retcode ); }; } 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"); 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"); } timeout = MINION_SLEEP_USEC; usleep (timeout); // 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) { // 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 ; } else { // we're in '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 ); 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, "Not keeping minion, closing everything.\n"); // no, we're not keeping it around, so close the pipe. // close (from_minion [0]); // and de-zombify any dead minions; // waitpid ( minion, &minion_exit_status, 0); if ( crm_vht_lookup (vht, keep_buf, strlen (keep_buf))) { char exit_value_string[MAX_VARNAME]; if (internal_trace) fprintf (stderr, "and whacking %s\n", keep_buf); sprintf (exit_value_string, "DEAD MINION, EXIT CODE: %d", minion_exit_status); if (keep_len > 0) crm_destructive_alter_nvariable (keep_buf, keep_len, exit_value_string, strlen (exit_value_string)); }; }; return (0); };