/* atmsigd.c - ATM signaling demon */

/* Written 1995-1997 by Werner Almesberger, EPFL-LRC */


#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <limits.h>
#include <sys/types.h>
#include <linux/atm.h>

#include "atm.h"
#include "atmd.h"
#include "qlib.h"

#include "io.h"
#include "proto.h"
#include "saal.h"
#include "trace.h"


#define COMPONENT "SIGD"
#define CONFIG_FILE "/etc/atmsigd.conf"


extern int yyparse(void);
extern FILE *yyin;

int net = 0;
int debug = 0;
int pretty = A2T_PRETTY | A2T_NAME | A2T_LOCAL;
int sig_pcr = -1; /* obsolete */
const char *sig_qos = NULL;
const char *dump_dir = NULL;


/* ------------------------------ SAAL relays ------------------------------ */


static SAAL_DSC saal;


static void q_estab_conf(void *user_data,void *uu_data,int uu_length)
{
    saal_okay();
}


static void q_rel_ind(void *user_data,void *uu_data,int uu_length)
{
    saal_failure();
    saal_estab_req(&saal,NULL,0);
}


static void q_restart(void *user_data,void *uu_data,int uu_length,int ind)
{
    saal_failure();
    if (!ind) saal_okay(); /* actually, ind should probably never be zero */
}


void from_net(void *buffer,int size)
{
    saal_pdu(&saal,buffer,size);
}


void to_signaling(void *msg,int size)
{
    trace_uni("TO NETWORK",msg,size);
    diag(COMPONENT,DIAG_DEBUG,"TO SAAL: %s (0x%02x) CR 0x%06x (%d bytes)",
      mid2name(((unsigned char *) msg)[5]),((unsigned char *) msg)[5],
      (((unsigned char *) msg)[2] << 16) | (((unsigned char *) msg)[3] << 8) |
      ((unsigned char *) msg)[4],size);
    saal_send(&saal,msg,size);
}


static void q_data_ind(void *user_data,void *data,int length)
{
    trace_uni("FROM NETWORK",data,length);
    to_uni(data,length);
}


static void q_cpcs_send(void *user_data,void *data,int length)
{
    to_net(data,length);
}


static SAAL_USER_OPS ops = {
    NULL, /* no q_estab_ind - 5.5.6.9 says 5.5.6.11 and 5.5.6.11 says "may" */
    q_estab_conf,
    q_rel_ind,
    NULL, /* no q_rel_conf - what to do ? */
    q_restart,
    q_data_ind,
    NULL, /* no q_unitdata */
    q_cpcs_send
};


/* -------------------------------- signals -------------------------------- */


static volatile int got_usr1 = 0,got_usr2 = 0;


static void dump_addr(FILE *file,const char *label,struct sockaddr_atmsvc *addr)
{
    char buffer[MAX_ATM_ADDR_LEN+1];
    int i;

    if (!atmsvc_addr_in_use(*addr)) return;
    fprintf(file,"  %s ",label);
    if (atm2text(buffer,MAX_ATM_ADDR_LEN+1,(struct sockaddr *) addr,A2T_NAME |
      A2T_PRETTY | A2T_LOCAL) >= 0) fprintf(file,"%s\n",buffer);
    else {
	fprintf(file,"<invalid:");
	for (i = 0; i < sizeof(*addr); i++)
	    fprintf(file," %02X",((unsigned char *) addr)[i]);
	fprintf(file,">\n");
    }
}


static void dump_sap(FILE *file,const char *label,struct atm_sap *sap)
{
    char buffer[MAX_ATM_SAP_LEN+1];
    int i;

    fprintf(file,"  %s ",label);
    if (sap2text(buffer,MAX_ATM_SAP_LEN+1,sap,S2T_NAME | S2T_LOCAL) >= 0)
	fprintf(file,"%s\n",buffer);
    else {
	fprintf(file,"<invalid:");
	for (i = 0; i < sizeof(*sap); i++)
	    fprintf(file," %02X",((unsigned char *) sap)[i]);
	fprintf(file,">\n");
    }
}


static void dump_status(FILE *file,const char *banner)
{
    SOCKET *walk;

    if (sockets) fprintf(file,"%s\n\n",banner);
    for (walk = sockets; walk; walk = walk->next) {
	fprintf(file,"0x%lx: %s, CR 0x%06lX, PVC %d.%d.%d\n",walk->id,
	  state_name[walk->state],walk->call_ref,walk->pvc.sap_addr.itf,
	  walk->pvc.sap_addr.vpi,walk->pvc.sap_addr.vci);
        dump_addr(file,"local ",&walk->local);
        dump_addr(file,"remote",&walk->remote);
        dump_sap(file,"sap",&walk->sap);
    }
}


static void dump_trace(FILE *file,const char *banner)
{
    static int busy = 0;
    char *trace;

    if (busy++) abort();
    trace = get_trace();
    if (trace) {
	fprintf(file,"%s\n\n",banner);
	fprintf(file,"%s",trace);
    }
    busy--;
}


void poll_signals(void)
{
    static status_num = 0,trace_num = 0;
    char path[PATH_MAX+1];
    FILE *file;

    if (got_usr1) {
	got_usr1 = 0;
	if (!dump_dir) file = stderr;
	else {
	    sprintf(path,"atmsigd.%d.status.%d",getpid(),status_num++);
	    if ((file = fopen(path,"w")))
		diag(COMPONENT,DIAG_INFO,"Dumping to %s",path);
	    else {
		perror(path);
		file = stderr;
	    }
	}
	dump_status(file,"Status dump (on SIGUSR1)");
	if (file != stderr) (void) fclose(file);
    }
    if (got_usr2) {
	pid_t pid;

	got_usr2 = 0;
	if (!dump_dir) file = stderr;
	else {
	    sprintf(path,"atmsigd.%d.trace.%d",getpid(),trace_num++);
	    if ((file = fopen(path,"w")))
		diag(COMPONENT,DIAG_INFO,"Dumping to %s",path);
	    else {
		perror(path);
		file = stderr;
	    }
	}
	if (!(pid = fork()))
	     dump_trace(file,"Message trace (on SIGUSR2)");
	else if (pid < 0) perror("fork");
	if (file != stderr) (void) fclose(file);
	if (!pid) exit(0);
    }
}


static void handle_signal(int sig)
{
    switch (sig) {
	case SIGUSR1:
	    got_usr1 = 1;
	    break;
	case SIGUSR2:
	    got_usr2 = 1;
	    break;
	default:
	    break;
    }
}


static void setup_signals(void)
{
    struct sigaction act;

    (void) signal(SIGCHLD,SIG_IGN); /* reap children automatially */
    act.sa_handler = handle_signal;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    if (sigaction(SIGUSR1,&act,NULL) < 0) {
	perror("sigaction");
	exit(1);
    }
    if (sigaction(SIGUSR2,&act,NULL) < 0) {
	perror("sigaction");
	exit(1);
    }
}


/* ------------------------------- main ...  ------------------------------- */


static void trace_on_exit(int status,void *dummy)
{
    char path[PATH_MAX+1];
    FILE *file;

    if (!status) return;
    if (dump_dir) file = stderr;
    else {
	sprintf(path,"atmsigd.%d.trace.exit",getpid());
	if (!(file = fopen(path,"w"))) {
	    perror(path);
	    file = stderr;
	}
    }
    dump_trace(file,"Message trace (after error exit)");
    if (file != stderr) (void) fclose(file);
}


static void usage(const char *name)
{
    fprintf(stderr,"usage: %s [ -b ] [ -c config_file ] [ -d ] "
      "[ -D dump_dir ]\n"
      "  [ -l logfile ] [ -n ] [ -N ]  [ -q qos ] [ -t trace_length ]\n"
      "  [ [itf.]vpi.vci [ input output ] ]\n",name);
    exit(1);
}


int main(int argc,char **argv)
{
    const char *config_file;
    char *end;
    int c,background;

    set_application("atmsigd");
    config_file = CONFIG_FILE;
    dump_dir = NULL;
    background = 0;
    memset(&signaling_pvc,0,sizeof(signaling_pvc));
    signaling_pvc.sap_addr.vci = 5;
    /* 1st pass to get the -c option */
    while ((c = getopt(argc,argv,"bc:dD:l:nNP:q:t:")) != EOF)
	if (c == 'c') config_file = optarg;
    if (!(yyin = fopen(config_file,"r")))
	diag(COMPONENT,DIAG_WARN,"%s not found. - Using defaults.",config_file);
    else if (yyparse()) {
	    diag(COMPONENT,DIAG_FATAL,"Error in config file. - Aborting.");
	    return 1;
	}
    /* process all other options but -c */
    optind = 0;
    while ((c = getopt(argc,argv,"bc:dD:l:nNP:q:t:")) != EOF)
	switch (c) {
	    case 'b':
		background = 1;
		break;
	    case 'c':
		/* already handled */
		break;
	    case 'd':
		set_verbosity(NULL,DIAG_DEBUG);
		set_verbosity("QMSG",DIAG_INFO);
		set_verbosity("SSCOP",DIAG_INFO);
		debug = 1;
		/*q_dump = 1;*/
		break;
	    case 'D':
		dump_dir = optarg;
		if (!trace_size) trace_size = DEFAULT_TRACE_SIZE;
		break;
	    case 'l':
		set_logfile(optarg);
		break;
	    case 'n':
		pretty = A2T_PRETTY;
		break;
	    case 'N':
		net = 1;
		break;
	    case 'q':
		if (sig_pcr != -1) usage(argv[0]);
		sig_qos = optarg;
		break;
	    case 'P': /* obsolete */
		if (sig_qos) usage(argv[0]);
		sig_pcr = strtol(optarg,&end,0);
		if (*end) usage(argv[0]);
		break;
	    case 't':
		trace_size = strtol(optarg,&end,0);
		if (*end) usage(argv[0]);
		break;
	    default:
		usage(argv[0]);
	}
    if (optind < argc) {
	if (text2atm(argv[optind],(struct sockaddr *) &signaling_pvc,
	  sizeof(signaling_pvc),T2A_PVC) < 0)
	    diag(COMPONENT,DIAG_FATAL,"text2atm \"%s\": failed",argv[optind]);
	optind++;
	if (optind == argc-2) {
	    open_input(argv[optind]);
	    open_output(argv[optind+1]);
	    optind += 2;
	}
    }
    if (optind != argc) usage(argv[0]);
    diag(COMPONENT,DIAG_INFO,"Linux ATM signaling "
#ifdef UNI30
      "UNI 3.0"
#endif
#ifdef UNI31
      "UNI 3.1"
#ifdef ALLOW_UNI30
      "+3.0compat"
#endif
#endif
#ifdef UNI40
      "UNI 4.0"
#ifdef Q2963_1
      "+Q.2931.1"
#endif
#endif
      "/AAL5, version " VERSION);
    if (dump_dir)
	if (chdir(dump_dir) < 0)
	    diag(COMPONENT,DIAG_ERROR,"chdir %s: %s",dump_dir,strerror(errno));
    diag(COMPONENT,DIAG_INFO,"Acting as %s side",net ? "NETWORK" : "USER");
    if (open_all()) return 1;
    init_current_time();
    init_addr();
    q_start();
    start_saal(&saal,&ops,NULL);
    saal_estab_req(&saal,NULL,0);
    setup_signals();
    if (background) {
	pid_t pid;

	pid = fork();
	if (pid < 0)
	    diag(COMPONENT,DIAG_FATAL,"fork: %s",strerror(errno));
	if (pid) {
	    diag(COMPONENT,DIAG_DEBUG,"Backgrounding (PID %d)",pid);
	    exit(0);
	}
    }
    (void) on_exit(trace_on_exit,NULL);
    poll_loop();
    close_all();
    stop_saal(&saal);
    return 0;
}
