#include "mod_perl.h"

#ifdef PERL_SAFE_STARTUP

static IV opset_len = 0;

static void opmask_add(char *bitmask)
{
    int i,j;
    int myopcode = 0;
    if(!opset_len)
	opset_len = (maxo + 7) / 8;

    for (i=0; i < opset_len; i++) {
	U16 bits = bitmask[i];
	if (!bits) {
	    myopcode += 8;
	    continue;
	}
	for (j=0; j < 8 && myopcode < maxo; )
	    op_mask[myopcode++] |= bits & (1 << j++);
    }
}

#ifdef PERL_DEFAULT_OPMASK

/*PerlOpmask directive is disabled*/
#define op_names_init()
#define get_op_bitspec(op,f) Nullsv
#define set_opset_bits(bitmap, bitspec, on, op)
#define read_opmask(s,p,f) NULL
char *mod_perl_set_opmask(request_rec *r, SV *sv)
{
    croak("Can't override Opmask");
}
#else

static HV *op_named_bits = Nullhv;
static void op_names_init(void)
{
    int i;
    if(op_named_bits) return;
    op_named_bits = newHV();
    for(i=0; i < maxo; ++i) {
	hv_store(op_named_bits, op_name[i], strlen(op_name[i]),
		 newSViv(i), 0);
    }
}

static SV *get_op_bitspec(char *opname, int fatal)
{
    SV **svp;
    int len = strlen(opname);
    svp = hv_fetch(op_named_bits, opname, len, 0);
    if (!svp || !SvOK(*svp)) {
	if(fatal)
	    croak("mod_perl: unknown operator name \"%s\"", opname);
	else
	    return Nullsv;
    }
    return *svp;
}

static void set_opset_bits(char *bitmap, SV *bitspec, int on, char *opname)
{
    if (SvIOK(bitspec)) {
	int myopcode = SvIV(bitspec);
	int offset = myopcode >> 3;
	int bit    = myopcode & 0x07;
	if (myopcode >= maxo || myopcode < 0)
	    croak("mod_perl: opcode \"%s\" value %d is invalid", 
		  opname, myopcode);
	if (on)
	    bitmap[offset] |= 1 << bit;
	else
	    bitmap[offset] &= ~(1 << bit);
    }
    else
	croak("mod_perl: invalid bitspec for \"%s\" (type %u)",
		opname, (unsigned)SvTYPE(bitspec));
}

static char *read_opmask(server_rec *s, pool *p, char *file)
{
#if HAS_MMN_130
    char opname[MAX_STRING_LEN];
    char *mask = (char *)ap_pcalloc(p, maxo);
    configfile_t *cfg = ap_pcfg_openfile(p, file);

    if(!cfg) {
	ap_log_error(APLOG_MARK, APLOG_CRIT, s,
		     "mod_perl: unable to open PerlOpmask file %s", file);
	exit(1);
    }

    op_names_init();
    while (!(ap_cfg_getline(opname, MAX_STRING_LEN, cfg))) {
	SV *bitspec;
	if(*opname == '#') continue;
	if((bitspec = get_op_bitspec(opname, TRUE))) {
	    set_opset_bits(mask, bitspec, TRUE, opname);
	}
    }
    ap_cfg_closefile(cfg);
    return mask;

#else
    croak("Need Apache 1.3.0+ to use PerlOpmask directive");
#endif /*HAS_MMN_130*/
}

static char *av2opmask(pool *p, AV *av)
{
    I32 i;
    char *mask;

    mask = (char *)ap_pcalloc(p, maxo);
    op_names_init();
    for(i=0; i<=AvFILL(av); i++) {
        SV *sv = *av_fetch(av, i, FALSE);
	char *opname = SvPV(sv,na);
	SV *bitspec;

	if((bitspec = get_op_bitspec(opname, TRUE))) {
	    set_opset_bits(mask, bitspec, TRUE, opname);
	}
    }
    return mask;
}

/*
 * $Mask ||= $r->set_opmask([qw(system backtick)]);
 * $r->set_opmask(\$Mask) if $Mask;
 * $r->set_opmask($filename)
 */
char *mod_perl_set_opmask(request_rec *r, SV *sv)
{
    char *mask;
#ifndef PERL_ORALL_OPMASK
    croak("Can't override Opmask");
#endif
    dOPMask;
    SAVEPPTR(op_mask);

    if(SvROK(sv)) {
	if(SvTYPE(SvRV(sv)) == SVt_PVAV) 
	    mask = av2opmask(r->pool, (AV*)SvRV(sv));
	else 
	    mask = SvPV((SV*)SvRV(sv),na);
    }
    else {
	mask = read_opmask(r->server, r->pool, SvPV(sv,na));
    }

    opmask_add(mask);
    MP_TRACE_g(mod_perl_dump_opmask());
    return mask;
}


#endif /*PERL_DEFAULT_OPMASK*/

#include "op_mask.c"

#ifdef PERL_DEFAULT_OPMASK
#define MP_HAS_OPMASK cls
#define MP_DEFAULT_OPMASK 1
#else
#define MP_HAS_OPMASK cls->PerlOpmask
#define MP_DEFAULT_OPMASK !strcasecmp(cls->PerlOpmask, "default")
#endif

#if 0
static char *default_opmask = NULL;

static void reset_default_opmask(void *data)
{
    char *mask = (char *)data;
    mask = NULL;
}
#endif

void mod_perl_init_opmask(server_rec *s, pool *p)
{
    dPSRV(s);
    char *local_opmask = NULL;

    if(!MP_HAS_OPMASK)
	return;

    if(MP_DEFAULT_OPMASK) {
#if 0
	if(!default_opmask) {
	    default_opmask = uudecode(p, MP_op_mask);
	    register_cleanup(p, (void*)default_opmask, 
			     reset_default_opmask, mod_perl_noop);
	}
#endif
	local_opmask = uudecode(p, MP_op_mask);
	MP_TRACE_g(fprintf(stderr, "mod_perl: using PerlOpmask %s\n",
		   cls->PerlOpmask ? cls->PerlOpmask : "__DEFAULT__"));
    }
    else {
	MP_TRACE_g(fprintf(stderr, "mod_perl: using PerlOpmask %s\n",
		   cls->PerlOpmask));
	local_opmask = read_opmask(s, p, 
				   server_root_relative(p, cls->PerlOpmask));
    }

    opmask_add(local_opmask);
}

void mod_perl_dump_opmask(void)
{
#ifdef PERL_TRACE
    int i;
    if(!op_mask) return;
    fprintf(stderr, "op_mask=\n");
    for(i=0; i < maxo; i++) {
	if(!op_mask[i]) continue;
	fprintf(stderr, "%s (%s)\n", op_name[i], op_desc[i]);
    }
#endif
}

#else

void mod_perl_init_opmask(server_rec *s, pool *p)
{
}

void mod_perl_dump_opmask(void)
{
}

char *mod_perl_set_opmask(request_rec *r, SV *sv)
{
    croak("Can't override Opmask");
	return NULL; /* C++ emits an error message otherwise
				  * because of a missing return value.
				  */
}
#endif /*PERL_SAFE_STARTUP*/