caf80ae6 |
/*
* $Id$
*
|
c430ed7b |
* Fifo server is a very powerful tool used to access easily
* ser's internals via textual interface, similarly to
* how internals of many operating systems are accessible
* via the proc file system. This might be used for
* making ser do things for you (such as initiating new
* transaction from webpages) or inspect server's health.
*
* FIFO server allows new functionality to be registered
* with it -- thats what register_fifo_cmd is good for.
* Remember, the initialization must take place before
* forking; best in init_module functions. When a function
* is registered, it can be always evoked by sending its
* name prefixed by colon to the FIFO.
*
* There are few commands already implemented in core.
* These are 'uptime' for looking at how long the server
* is alive and 'print' for debugging purposes.
*
* Every command sent to FIFO must be sent atomically to
* avoid intermixing with other commands and MUST be
* terminated by empty line so that the server is to able
* to find its end if it does not understand the command.
*
* File test/transaction.fifo illustrates example of use
* of t_uac command (part of TM module).
|
caf80ae6 |
*/
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
|
90b0b10d |
#include <time.h>
|
1c507b72 |
#include <stdarg.h>
|
caf80ae6 |
#include "dprint.h"
#include "ut.h"
#include "error.h"
#include "config.h"
#include "globals.h"
#include "fifo_server.h"
#include "mem/mem.h"
|
43d0c484 |
#include "sr_module.h"
|
f51155cf |
#include "pt.h"
|
caf80ae6 |
/* FIFO server vars */
|
48ee4ab3 |
char *fifo="/tmp/ser_fifo"; /* FIFO name */
|
c430ed7b |
int fifo_mode=S_IRUSR | S_IWUSR | S_IRGRP |
S_IWGRP | S_IROTH | S_IWOTH;
|
caf80ae6 |
pid_t fifo_pid;
/* file descriptors */
static int fifo_read=0;
static int fifo_write=0;
static FILE *fifo_stream;
/* list of fifo command */
static struct fifo_command *cmd_list=0;
|
90b0b10d |
/* up time */
static time_t up_since;
|
23348f83 |
static char up_since_ctime[MAX_CTIME_LEN];
|
90b0b10d |
|
caf80ae6 |
static struct fifo_command *lookup_fifo_cmd( char *name )
{
struct fifo_command *c;
for(c=cmd_list; c; c=c->next) {
if (strcasecmp(c->name, name)==0) return c;
}
return 0;
}
int register_fifo_cmd(fifo_cmd f, char *cmd_name, void *param)
{
struct fifo_command *new_cmd;
if (lookup_fifo_cmd(cmd_name)) {
LOG(L_ERR, "ERROR: register_fifo_cmd: attempt to register synonyms\n");
return E_BUG;
}
new_cmd=malloc(sizeof(struct fifo_command));
if (new_cmd==0) {
LOG(L_ERR, "ERROR: register_fifo_cmd: out of mem\n");
return E_OUT_OF_MEM;
}
new_cmd->f=f;
new_cmd->name=cmd_name;
new_cmd->param=param;
new_cmd->next=cmd_list;
cmd_list=new_cmd;
|
d95321e4 |
DBG("DEBUG: register_fifo_cmd: new command (%s) registered\n", cmd_name );
|
caf80ae6 |
return 1;
}
int read_line( char *b, int max, FILE *stream, int *read )
{
int len;
|
3b8fb572 |
int retry_cnt;
retry_cnt=0;
retry:
|
caf80ae6 |
if (fgets(b, max, stream)==NULL) {
LOG(L_ERR, "ERROR: fifo_server fgets failed: %s\n",
strerror(errno));
|
3b8fb572 |
/* on Linux, fgets sometimes returns ESPIPE -- give
it few more chances
*/
if (errno==ESPIPE) {
retry_cnt++;
if (retry_cnt<4) goto retry;
}
|
caf80ae6 |
kill(0, SIGTERM);
}
/* if we did not read whole line, our buffer is too small
and we cannot process the request; consume the remainder of
request
*/
len=strlen(b);
if (len && !(b[len-1]=='\n' || b[len-1]=='\r')) {
LOG(L_ERR, "ERROR: read_line: request line too long\n");
return 0;
}
/* trim from right */
while(len) {
if(b[len-1]=='\n' || b[len-1]=='\r'
|| b[len-1]==' ' || b[len-1]=='\t' ) {
len--;
b[len]=0;
} else break;
}
*read=len;
return 1;
}
static void consume_request( FILE *stream )
{
int len;
char buffer[MAX_CONSUME_BUFFER];
while(!read_line(buffer, MAX_CONSUME_BUFFER, stream, &len));
#ifdef _OBSOLETED
int eol_count;
eol_count=0;
/* each request must be terminated by two EoLs */
while(eol_count!=2) {
/* read until EoL is encountered */
while(!read_line(buffer, MAX_CONSUME_BUFFER, stream, &len));
eol_count=len==0?eol_count+1:1;
}
#endif
}
int read_eol( FILE *stream )
{
int len;
char buffer[MAX_CONSUME_BUFFER];
if (!read_line(buffer, MAX_CONSUME_BUFFER, stream, &len) || len!=0) {
LOG(L_ERR, "ERROR: read_eol: EOL expected: %.10s...\n",
buffer );
return 0;
}
return 1;
}
|
c430ed7b |
/* read from input until empty line is encountered */
|
caf80ae6 |
int read_line_set(char *buf, int max_len, FILE *fifo, int *len)
{
int set_len;
char *c;
int line_len;
c=buf;set_len=0;
while(1) {
if (!read_line(c,max_len,fifo,&line_len)) {
LOG(L_ERR, "ERROR: fifo_server: line expected\n");
return 0;
}
/* end encountered ... return */
if (line_len==0) {
*len=set_len;
return 1;
}
max_len-=line_len; c+=line_len; set_len+=line_len;
if (max_len<CRLF_LEN) {
LOG(L_ERR, "ERROR: fifo_server: no place for CRLF\n");
return 0;
}
memcpy(c, CRLF, CRLF_LEN);
max_len-=CRLF_LEN; c+=CRLF_LEN; set_len+=CRLF_LEN;
}
}
|
c430ed7b |
/* read from input until line with only dot in it is encountered */
int read_body(char *buf, int max_len, FILE *fifo, int *len)
{
int set_len;
char *c;
int line_len;
c=buf;set_len=0;
while(1) {
if (!read_line(c,max_len,fifo,&line_len)) {
LOG(L_ERR, "ERROR: fifo_server: line expected\n");
return 0;
}
/* end encountered ... return */
if (line_len==1 && *c=='.') {
*len=set_len;
return 1;
}
max_len-=line_len; c+=line_len; set_len+=line_len;
if (max_len<CRLF_LEN) {
LOG(L_ERR, "ERROR: fifo_server: no place for CRLF\n");
return 0;
}
memcpy(c, CRLF, CRLF_LEN);
max_len-=CRLF_LEN; c+=CRLF_LEN; set_len+=CRLF_LEN;
}
}
|
caf80ae6 |
static char *trim_filename( char * file )
{
int prefix_len, fn_len;
char *new_fn;
/* we only allow files in "/tmp" -- any directory
changes are not welcome
*/
if (strchr(file,'.') || strchr(file,'/')
|| strchr(file, '\\')) {
LOG(L_ERR, "ERROR: trim_filename: forbidden filename: %s\n"
, file);
return 0;
}
prefix_len=strlen(FIFO_DIR); fn_len=strlen(file);
new_fn=pkg_malloc(prefix_len+fn_len+1);
if (new_fn==0) {
LOG(L_ERR, "ERROR: trim_filename: no mem\n");
return 0;
}
memcpy(new_fn, FIFO_DIR, prefix_len);
memcpy(new_fn+prefix_len, file, fn_len );
new_fn[prefix_len+fn_len]=0;
return new_fn;
}
|
4e8314bf |
/* tell FIFO client what happened via reply pipe */
|
1c507b72 |
void fifo_reply( char *reply_fifo, char *reply_fmt, ... )
|
4e8314bf |
{
FILE *file_handle;
|
1c507b72 |
int r;
va_list ap;
|
4e8314bf |
file_handle=open_reply_pipe(reply_fifo);
if (file_handle==0) {
LOG(L_ERR, "ERROR: fifo_reply: no reply pipe %s\n",
fifo);
return;
}
|
1c507b72 |
retry:
va_start(ap, reply_fmt);
r=vfprintf(file_handle, reply_fmt, ap);
va_end(ap);
if (r<=0) {
|
4e8314bf |
LOG(L_ERR, "ERROR: fifo_error: write error (%s): %s\n",
fifo, strerror(errno));
|
1c507b72 |
if ((errno==EINTR)||(errno==EAGAIN)||(errno==EWOULDBLOCK)) {
goto retry;
}
|
4e8314bf |
}
fclose(file_handle);
}
|
90b0b10d |
FILE *open_reply_pipe( char *pipe_name )
{
|
1c507b72 |
int fifofd;
|
90b0b10d |
FILE *file_handle;
|
1c507b72 |
int retries=FIFO_REPLY_RETRIES;
if (!pipe_name || *pipe_name==0) {
|
90b0b10d |
DBG("DEBUG: open_reply_pipe: no file to write to about missing cmd\n");
return 0;
}
|
1c507b72 |
tryagain:
fifofd=open( pipe_name, O_WRONLY | O_NONBLOCK );
if (fifofd==-1) {
/* retry several times if client is not yet ready for getting
feedback via a reply pipe
*/
if (errno==ENXIO) {
/* give up on the client - we can't afford server blocking */
if (retries==0) {
LOG(L_ERR, "ERROR: open_reply_pipe: no client at %s\n",
pipe_name );
return 0;
}
/* don't be noisy on the very first try */
if (retries!=FIFO_REPLY_RETRIES)
DBG("DEBUG: open_reply_pipe: retry countdown: %d\n", retries );
sleep_us( FIFO_REPLY_WAIT );
retries--;
goto tryagain;
}
/* some other opening error */
LOG(L_ERR, "ERROR: open_reply_pipe: open error (%s): %s\n",
pipe_name, strerror(errno));
return 0;
}
file_handle=fdopen( fifofd, "w");
|
90b0b10d |
if (file_handle==NULL) {
LOG(L_ERR, "ERROR: open_reply_pipe: open error (%s): %s\n",
pipe_name, strerror(errno));
return 0;
}
return file_handle;
}
|
caf80ae6 |
static void fifo_server(FILE *fifo_stream)
{
char buf[MAX_FIFO_COMMAND];
int line_len;
char *file_sep, *command, *file;
struct fifo_command *f;
file_sep=command=file=0;
while(1) {
/* commands must look this way ':<command>:[filename]' */
if (!read_line(buf, MAX_FIFO_COMMAND, fifo_stream, &line_len)) {
/* line breaking must have failed -- consume the rest
and proceed to a new request
*/
LOG(L_ERR, "ERROR: fifo_server: command expected\n");
goto consume;
}
if (line_len==0) {
|
90b0b10d |
LOG(L_INFO, "INFO: fifo_server: command empty\n");
|
caf80ae6 |
continue;
}
if (line_len<3) {
LOG(L_ERR, "ERROR: fifo_server: command must have at least 3 chars\n");
goto consume;
}
if (*buf!=CMD_SEPARATOR) {
|
1380e79e |
LOG(L_ERR, "ERROR: fifo_server: command must begin with %c: %.*s\n",
CMD_SEPARATOR, line_len, buf );
|
caf80ae6 |
goto consume;
}
command=buf+1;
file_sep=strchr(command, CMD_SEPARATOR );
if (file_sep==NULL) {
LOG(L_ERR, "ERROR: fifo_server: file separator missing\n");
goto consume;
}
if (file_sep==command) {
LOG(L_ERR, "ERROR: fifo_server: empty command\n");
goto consume;
}
if (*(file_sep+1)==0) file=NULL;
else {
file=file_sep+1;
file=trim_filename(file);
if (file==0) {
LOG(L_ERR, "ERROR: fifo_server: trimming filename\n");
goto consume;
}
}
/* make command zero-terminated */
*file_sep=0;
f=lookup_fifo_cmd( command );
if (f==0) {
LOG(L_ERR, "ERROR: fifo_server: command %s is not available\n",
command);
|
1c507b72 |
fifo_reply(file, "[%s not available]\n", command);
|
caf80ae6 |
goto consume;
}
if (f->f(fifo_stream, file)<0) {
LOG(L_ERR, "ERROR: fifo_server: command (%s) "
"processing failed\n", command );
goto consume;
}
consume:
if (file) { pkg_free(file); file=0;}
consume_request(fifo_stream);
}
}
int open_fifo_server()
{
|
23348f83 |
char *t;
|
caf80ae6 |
if (fifo==NULL) {
DBG("TM: open_uac_fifo: no fifo will be opened\n");
/* everything is ok, we just do not want to start */
return 1;
}
|
606ba517 |
if (strlen(fifo)==0) {
DBG("TM: open_uac_fifo: fifo disabled\n");
return 1;
}
|
caf80ae6 |
DBG("TM: open_uac_fifo: opening fifo...\n");
if ((mkfifo(fifo, fifo_mode)<0) && (errno!=EEXIST)) {
|
c430ed7b |
LOG(L_ERR, "ERROR: open_fifo_server; can't create FIFO: "
"%s (mode=%d)\n",
strerror(errno), fifo_mode);
|
caf80ae6 |
return -1;
|
c430ed7b |
}
DBG("DEBUG: fifo %s opened, mode=%d\n", fifo, fifo_mode );
|
90b0b10d |
time(&up_since);
|
23348f83 |
t=ctime(&up_since);
if (strlen(t)+1>=MAX_CTIME_LEN) {
LOG(L_ERR, "ERROR: open_fifo_server: "
|
3a7d29ef |
"too long date %d\n", strlen(t));
|
23348f83 |
return -1;
}
memcpy(up_since_ctime,t,strlen(t)+1);
|
caf80ae6 |
process_no++;
fifo_pid=fork();
if (fifo_pid<0) {
LOG(L_ERR, "ERROR: open_fifo_server: failure to fork: %s\n",
strerror(errno));
return -1;
}
if (fifo_pid==0) { /* child == FIFO server */
LOG(L_INFO, "INFO: fifo process starting: %d\n", getpid());
|
1380e79e |
/* call per-child module initialization too -- some
FIFO commands may need it
*/
|
db249450 |
if (init_child(process_no) < 0 ) {
|
1380e79e |
LOG(L_ERR, "ERROR: open_uac_fifo: init_child failed\n");
return -1;
}
|
caf80ae6 |
fifo_read=open(fifo, O_RDONLY, 0);
if (fifo_read<0) {
|
1380e79e |
LOG(L_ERR, "ERROR: open_uac_fifo: fifo_read did not open: %s\n",
|
caf80ae6 |
strerror(errno));
return -1;
}
fifo_stream=fdopen(fifo_read, "r" );
if (fifo_stream==NULL) {
LOG(L_ERR, "SER: open_uac_fifo: fdopen failed: %s\n",
strerror(errno));
return -1;
}
|
1c507b72 |
/* a real server doesn't die if writing to reply fifo fails */
signal(SIGPIPE, SIG_IGN);
|
f1d96242 |
LOG(L_INFO, "SER: open_uac_fifo: fifo server up at %s...\n",
fifo);
|
caf80ae6 |
fifo_server( fifo_stream ); /* never retruns */
}
/* dad process */
|
f51155cf |
pt[process_no].pid=fifo_pid;
strncpy(pt[process_no].desc, "fifo server", MAX_PT_DESC );
|
caf80ae6 |
/* make sure the read fifo will not close */
fifo_write=open(fifo, O_WRONLY, 0);
if (fifo_write<0) {
LOG(L_ERR, "SER: open_uac_fifo: fifo_write did not open: %s\n",
strerror(errno));
return -1;
}
return 1;
}
|
3165311a |
static int print_version_cmd( FILE *stream, char *response_file )
{
if (response_file) {
|
4e8314bf |
fifo_reply(response_file, SERVER_HDR CRLF );
|
3165311a |
} else {
LOG(L_ERR, "ERROR: no file for print_version_cmd\n");
}
return 1;
}
|
caf80ae6 |
/* diagnostic and hello-world FIFO command */
|
90b0b10d |
static int print_fifo_cmd( FILE *stream, char *response_file )
|
caf80ae6 |
{
char text[MAX_PRINT_TEXT];
int text_len;
/* expect one line which will be printed out */
|
4e8314bf |
if (response_file==0 || *response_file==0 ) {
LOG(L_ERR, "ERROR: print_fifo_cmd: null file\n");
return -1;
}
|
caf80ae6 |
if (!read_line(text, MAX_PRINT_TEXT, stream, &text_len)) {
|
4e8314bf |
fifo_reply(response_file,
"ERROR: print_fifo_cmd: too big text");
|
caf80ae6 |
return -1;
}
/* now the work begins */
if (response_file) {
|
4e8314bf |
fifo_reply(response_file, text );
|
caf80ae6 |
} else {
LOG(L_INFO, "INFO: print_fifo_cmd: %.*s\n",
text_len, text );
}
return 1;
}
|
90b0b10d |
static int uptime_fifo_cmd( FILE *stream, char *response_file )
{
time_t now;
if (response_file==0 || *response_file==0 ) {
LOG(L_ERR, "ERROR: uptime_fifo_cmd: null file\n");
return -1;
}
|
1c507b72 |
time(&now);
fifo_reply( response_file, "Now: %sUp Since: %sUp time: %.0f [sec]\n",
|
23348f83 |
ctime(&now), up_since_ctime, difftime(now, up_since) );
|
1c507b72 |
|
90b0b10d |
return 1;
}
|
114eaa41 |
static int which_fifo_cmd(FILE *stream, char *response_file )
{
FILE *reply_pipe;
struct fifo_command *c;
if (response_file==0 || *response_file==0 ) {
LOG(L_ERR, "ERROR: which_fifo_cmd: null file\n");
return -1;
}
reply_pipe=open_reply_pipe(response_file);
if (reply_pipe==NULL) {
|
f51155cf |
LOG(L_ERR, "ERROR: which_fifo_cmd: opening reply pipe (%s) failed\n",
|
114eaa41 |
response_file );
return -1;
}
fputs( "------ Begin of registered FIFO commands -----------\n", reply_pipe);
for(c=cmd_list; c; c=c->next) {
fprintf( reply_pipe, "%s\n", c->name );
}
fputs( "------ End of registered FIFO commands -----------\n", reply_pipe);
fclose(reply_pipe);
return 1;
}
|
f51155cf |
static int ps_fifo_cmd(FILE *stream, char *response_file )
{
FILE *reply_pipe;
int p;
if (response_file==0 || *response_file==0 ) {
LOG(L_ERR, "ERROR: ps_fifo_cmd: null file\n");
return -1;
}
reply_pipe=open_reply_pipe(response_file);
if (reply_pipe==NULL) {
LOG(L_ERR, "ERROR: ps_fifo_cmd: opening reply pipe (%s) failed\n",
response_file );
return -1;
}
for (p=0; p<process_count();p++)
fprintf( reply_pipe, "%d\t%d\t%s\n",
p, pt[p].pid, pt[p].desc );
fclose(reply_pipe);
return 1;
}
|
90b0b10d |
int register_core_fifo()
{
if (register_fifo_cmd(print_fifo_cmd, FIFO_PRINT, 0)<0) {
|
f51155cf |
LOG(L_CRIT, "unable to register '%s' FIFO cmd\n", FIFO_PRINT);
|
90b0b10d |
return -1;
}
if (register_fifo_cmd(uptime_fifo_cmd, FIFO_UPTIME, 0)<0) {
|
f51155cf |
LOG(L_CRIT, "unable to register '%s' FIFO cmd\n", FIFO_UPTIME);
|
90b0b10d |
return -1;
}
|
3165311a |
if (register_fifo_cmd(print_version_cmd, FIFO_VERSION, 0)<0) {
|
f51155cf |
LOG(L_CRIT, "unable to register '%s' FIFO cmd\n", FIFO_VERSION);
|
3165311a |
return -1;
}
|
114eaa41 |
if (register_fifo_cmd(which_fifo_cmd, FIFO_WHICH, 0)<0) {
|
f51155cf |
LOG(L_CRIT, "unable to register '%s' FIFO cmd\n", FIFO_WHICH);
return -1;
}
if (register_fifo_cmd(ps_fifo_cmd, FIFO_PS, 0)<0) {
LOG(L_CRIT, "unable to register '%s' FIFO cmd\n", FIFO_PS);
|
114eaa41 |
return -1;
}
|
90b0b10d |
return 1;
}
|