The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/*

Copyright (C) 2008, 2009, 2012 by Thomas Pfau < tfpfau@gmail.com >

This module is free software.  You can redistribute it and/or modify
it under the terms of the Artistic License 2.0.  For details, see the
full text of the Artistic License in the file LICENSE.

This module is distributed in the hope that it will be useful but it
is provided "as is"and without any express or implied warranties.

*/

#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include "ppport.h"

#include <starlet.h>
#include <str$routines.h>
#include <lib$routines.h>
#include <lnmdef.h>
#include <descrip.h>

/* this table is used to translate attributes */
#define WHERE_LOG_DEF	1
#define WHERE_LOG_TX	2
#define WHERE_EQV_DEF	4
#define WHERE_EQV_TX	8
#define WHERE_TBL_DEF	16
struct {
    int where_used;
    int value;
    char *name;
} attributes[] = {
    { WHERE_LOG_DEF|WHERE_LOG_TX|WHERE_TBL_DEF,
	LNM$M_CONFINE,     "CONFINE" },
    { WHERE_LOG_TX,
	LNM$M_CRELOG,      "CRELOG" },
    { WHERE_LOG_DEF|WHERE_LOG_TX|WHERE_TBL_DEF,
	LNM$M_NO_ALIAS,    "NO_ALIAS" },
    { WHERE_LOG_TX,
	LNM$M_TABLE,       "TABLE" },
    { WHERE_LOG_TX,
	LNM$M_CLUSTERWIDE, "CLUSTERWIDE" },
    { WHERE_EQV_DEF|WHERE_EQV_TX,
	LNM$M_CONCEALED,   "CONCEALED" },
    { WHERE_EQV_TX,
	LNM$M_EXISTS,      "EXISTS" },
    { WHERE_EQV_DEF|WHERE_EQV_TX,
	LNM$M_TERMINAL,    "TERMINAL" },
    { WHERE_TBL_DEF,
 	LNM$M_CREATE_IF,   "CREATE_IF" },
};
#define NUM_ATTRS (sizeof(attributes)/sizeof(attributes[0]))

/* itemlist */
typedef struct {
    short buffer_size;
    short item_code;
    void *buffer;
    short *return_length;
} itemlist;

/* access modes */
static char *access_modes[] = { "KERNEL", "EXECUTIVE", "SUPERVISOR", "USER" };

/* create hash entries for attributes */
SV *store_attributes(int attr, int which)
{
    int i;
    HV *hv = newHV();
    for (i=0; i<NUM_ATTRS; i++)
    {
        if (attributes[i].where_used & which)
        {
            hv_store(hv, attributes[i].name, strlen(attributes[i].name),
                    (attr & attributes[i].value) ? newSViv(1) : &PL_sv_undef,
                     0);
        }
    }
    return newRV_noinc((SV *) hv);
}            

/* translate hash entries into an attributes mask */
int translate_attributes(HV *hv, int which)
{
    int attr = 0, i;
    for (i=0; i<NUM_ATTRS; i++)
    {
        SV **sv;
	if (!(attributes[i].where_used & which))
	    continue;
        sv = hv_fetch(hv, attributes[i].name, strlen(attributes[i].name), 0);
        if (sv && SvTRUE(*sv))
            attr |= attributes[i].value;
    }
    return attr;
}

/* get an entry from a hash and return as descriptor */
struct dsc$descriptor *get_hash_string_desc(HV *hv, const char *key)
{
    SV **sv;
    struct dsc$descriptor *desc;
    sv = hv_fetch(hv, key, strlen(key), 0);
    if (sv == 0) return 0;
    desc = (struct dsc$descriptor *)calloc(1,sizeof(struct dsc$descriptor));
    desc->dsc$b_class = DSC$K_CLASS_S;
    desc->dsc$b_dtype = DSC$K_DTYPE_T;
    desc->dsc$a_pointer = SvPV_nolen(*sv);
    desc->dsc$w_length = SvCUR(*sv);
    return desc;
}

/* free a string descriptor */
void free_desc(struct dsc$descriptor *desc)
{
    if (desc->dsc$b_class = DSC$K_CLASS_D)
        str$free1_dx(desc);
    free(desc);
}

/* get an entry from a hash and return as (pointer to) integer */
int *get_hash_int(HV *hv, const char *key)
{
    SV **sv;
    int *i;
    sv = hv_fetch(hv, key, strlen(key), 0);
    if (sv == 0) return 0;
    if (!SvIOK(*sv)) return 0;
    i = malloc(sizeof(int));
    *i = SvIV(*sv);
    return i;
}

/* find the specified access mode and return its index */
int find_access_mode(SV *sv)
{
    int i;
    for (i=0; i<4; i++)
    {
        if (strncmp(access_modes[i], SvPV_nolen(sv), SvCUR(sv)) == 0)
            return i;
    }
    return -1;
}

/*

The hash looks like this:

    lognam -> string
    table -> string
    acmode -> string
    attr -> hash
        confine -> integer
        crelog -> integer
        no_alias -> integer
        table -> integer
        clusterwide -> integer
    equiv -> array of hashes
        string -> string
        attr -> hash
            concealed -> integer
            terminal -> integer

For input to define, equiv can be removed, string can be moved up, and
the equivalence attributes can be merged into the logical name
attributes.

*/

MODULE = VMS::Logical		PACKAGE = VMS::Logical		

# translate a logical name and return a hash
SV *
translate(lnm)
    SV *lnm
  CODE:
    struct dsc$descriptor *lognam_d = NULL, *tabnam_d = NULL;
    int attr = 0, acmode_v, *acmode = NULL;
    itemlist *il;
    char r_acmode = 0;
    int r_attr = 0;
    char equiv[255], tabnam[31];
    short equiv_len=0, tabnam_len=0;
    struct TX {
        int index;
        int attr;
        short equiv_len;
        char equiv[255];
    } *tx;
    int *index;
    int max_index = 0;
    int sts;
    STRLEN len;
    HV *hv;

    if (!SvROK(lnm))
    {
	# arg is not a reference, assume a string
        lognam_d = calloc(1, sizeof(struct dsc$descriptor));
        lognam_d->dsc$a_pointer = SvPVx(lnm, len);
        lognam_d->dsc$w_length = len;
        lognam_d->dsc$b_dtype = DSC$K_DTYPE_T;
        lognam_d->dsc$b_class = DSC$K_CLASS_S;
    } else {
        # arg is a reference, make sure it's a hash
        HV *opt;
        SV **sv;
        if (SvTYPE(SvRV(lnm)) != SVt_PVHV)
        {
            croak("Argument must be a string or a hash ref");
        }
        opt = (HV *) SvRV(lnm);
        lognam_d = get_hash_string_desc(opt, "lognam");
        sv = hv_fetch(opt, "case_blind", 10, 0);
        if (sv && SvIOK(*sv) && SvIV(*sv))
            attr |= LNM$M_CASE_BLIND;
        sv = hv_fetch(opt, "interlocked", 11, 0);
        if (sv && SvIOK(*sv) && SvIV(*sv))
            attr |= LNM$M_INTERLOCKED;
        tabnam_d = get_hash_string_desc(opt, "table");
        sv = hv_fetch(opt, "acmode", 6, 0);
        if (sv)
        {
            acmode_v = find_access_mode(*sv);
            if (acmode_v == -1)
                croak("Invalid access mode");
            acmode = &acmode_v;
        }
    }
    if (tabnam_d == NULL)
    {
        tabnam_d = calloc(1, sizeof(struct dsc$descriptor));
        tabnam_d->dsc$a_pointer = "LNM$FILE_DEV";
        tabnam_d->dsc$w_length = strlen(tabnam_d->dsc$a_pointer);
        tabnam_d->dsc$b_dtype = DSC$K_DTYPE_T;
        tabnam_d->dsc$b_class = DSC$K_CLASS_S;
    }
    il = calloc(7, sizeof(itemlist));

    il[0].item_code = LNM$_ACMODE;
    il[0].buffer = &r_acmode;
    il[0].buffer_size = sizeof(r_acmode);

    il[1].item_code = LNM$_MAX_INDEX;
    il[1].buffer = &max_index;
    il[1].buffer_size = sizeof(max_index);

    il[2].item_code = LNM$_TABLE;
    il[2].buffer = tabnam;
    il[2].buffer_size = sizeof(tabnam);
    il[2].return_length = &tabnam_len;

    tx = calloc(1, sizeof(struct TX));
    tx->index = 0;

    il[3].item_code = LNM$_INDEX;
    il[3].buffer = &tx->index;
    il[3].buffer_size = sizeof(tx->index);

    il[4].item_code = LNM$_ATTRIBUTES;
    il[4].buffer = &tx->attr;
    il[4].buffer_size = sizeof(tx->attr);

    il[5].item_code = LNM$_STRING;
    il[5].buffer = tx->equiv;
    il[5].buffer_size = sizeof(tx->equiv);
    il[5].return_length = &tx->equiv_len;

    sts = sys$trnlnm(&attr, tabnam_d, lognam_d, acmode, il);
    free(il);
    if (!(sts & 1))
    {
	RETVAL = &PL_sv_undef;
	SETERRNO(EVMSERR,sts);
    }
    else
    {
	hv = newHV();
	hv_store(hv, "sts", 3, newSViv(sts), 0);
	hv_store(hv, "lognam", 6, newSVpvn(lognam_d->dsc$a_pointer,
					   lognam_d->dsc$w_length), 0);
	hv_store(hv, "table", 5, newSVpvn(tabnam, tabnam_len), 0);
	hv_store(hv, "acmode", 6, newSVpv(access_modes[r_acmode], 0), 0);
	hv_store(hv, "max_index", 9, newSViv(max_index), 0);
	hv_store(hv, "attr", 4, store_attributes(tx->attr, WHERE_LOG_TX), 0);

	if (max_index >= 0)
	{
	    int i,ilp;
	    AV *av = newAV();
	    av_extend(av, max_index);
	    HV *hvt = newHV();
	    hv_store(hvt, "attr", 4, store_attributes(tx->attr, WHERE_EQV_TX), 0);
	    hv_store(hvt, "string", 6, newSVpvn(tx->equiv, tx->equiv_len), 0);
	    av_push(av, newRV_noinc((SV *) hvt));
	    free(tx);
	    tx = calloc(max_index, sizeof(struct TX));
	    il = calloc((max_index * 3) + 1, sizeof(itemlist));
	    for (i=0,ilp=0; i<max_index; i++)
	    {
		tx[i].index = i+1;
		il[ilp].item_code = LNM$_INDEX;
		il[ilp].buffer = &tx[i].index;
		il[ilp].buffer_size = sizeof(tx[i].index);
		ilp++;
		il[ilp].item_code = LNM$_ATTRIBUTES;
		il[ilp].buffer = &tx[i].attr;
		il[ilp].buffer_size = sizeof(tx[i].attr);
		ilp++;
		il[ilp].item_code = LNM$_STRING;
		il[ilp].buffer = tx[i].equiv;
		il[ilp].buffer_size = sizeof(tx[i].equiv);
		il[ilp].return_length = &tx[i].equiv_len;
		ilp++;
	    }
	    sts = sys$trnlnm(&attr, tabnam_d, lognam_d, acmode, il);
	    free(il);
	    for (i=0; i<max_index; i++)
	    {
		hvt = newHV();
		hv_store(hvt, "attr", 4,
			 store_attributes(tx[i].attr, WHERE_EQV_TX), 0);
		hv_store(hvt, "string", 6,
			 newSVpvn(tx[i].equiv, tx[i].equiv_len), 0);
		av_push(av, newRV_noinc((SV *) hvt));
	    }
	    hv_store(hv, "equiv", 5, newRV_noinc((SV *) av), 0);
	}
    	RETVAL = newRV_noinc((SV *) hv);
    }
    free(tx);

  OUTPUT:
    RETVAL

# define a logical name
SV *
define(lnm)
    SV *lnm
  CODE:
    HV *hv;
    SV **sv;
    struct dsc$descriptor *tabnam_d, *lognam_d;
    char table[32];
    short table_len;
    int l_attr_v = 0, *l_attr = NULL;
    int acmode_v, *acmode = NULL;
    itemlist *il = NULL;
    int *attr;
    int sts;

    if (!SvROK(lnm) || (SvTYPE(SvRV(lnm)) != SVt_PVHV))
        croak("Argument must be a hash ref");
    hv = (HV *) SvRV(lnm);
    tabnam_d = get_hash_string_desc(hv, "table");
    lognam_d = get_hash_string_desc(hv, "lognam");
    sv = hv_fetch(hv, "acmode", 6, 0);
    if (sv)
    {
        acmode_v = find_access_mode(*sv);
        if (acmode_v == -1)
            croak("Invalid access mode");
        acmode = &acmode_v;
    }
    sv = hv_fetch(hv, "attr", 4, 0);
    if (sv)
    {
        if (!SvROK(*sv) || (SvTYPE(SvRV(*sv)) != SVt_PVHV))
            croak("attr must be a hash ref");
        l_attr_v = translate_attributes((HV *)SvRV(*sv), WHERE_LOG_DEF);
        l_attr = &l_attr_v;
    }
    sv = hv_fetch(hv, "equiv", 5, 0);
    if (sv)
    {
        AV *av;
        int ilp = 0,ap=0,cnt,i;
        if (!SvROK(*sv) || (SvTYPE(SvRV(*sv)) != SVt_PVAV))
            croak("equiv must be an array ref");
        av = (AV *) SvRV(*sv);
        cnt = av_len(av) + 1;
        il = calloc(2*cnt+2, sizeof(itemlist));
        attr = calloc(cnt, sizeof(attr[0]));
        for (i=0;i<cnt;i++)
        {
            SV **sv2;
            sv = av_fetch(av, i, 0);
            if (!SvROK(*sv) || (SvTYPE(SvRV(*sv)) != SVt_PVHV))
                croak("equiv must contain hash refs");
            sv2 = hv_fetch((HV *)SvRV(*sv), "string", 6, 0);
            il[ilp].item_code = LNM$_STRING;
            il[ilp].buffer = SvPV_nolen(*sv2);
            il[ilp].buffer_size= SvCUR(*sv2);
            sv2 = hv_fetch((HV *)SvRV(*sv), "attr", 4, 0);
            if (sv2)
            {
                if (!SvROK(*sv2) || (SvTYPE(SvRV(*sv2)) != SVt_PVHV))
                    croak("attr must be a hash ref");
                attr[ap] = translate_attributes((HV *)SvRV(*sv2),
						WHERE_EQV_DEF);
                il[ilp+1] = il[ilp];
                il[ilp].item_code = LNM$_ATTRIBUTES;
                il[ilp].buffer = &attr[ap];
                il[ilp].buffer_size = sizeof(attr[ap]);
                ap++;
                ilp++;
            }
            ilp++;
        }
	il[ilp].item_code = LNM$_TABLE;
	il[ilp].buffer = table;
	il[ilp].buffer_size = sizeof(table);
	il[ilp].return_length = &table_len;
    }
    else if (sv = hv_fetch(hv, "string", 6, 0))
    {
        il = calloc(4, sizeof(itemlist));
        attr = calloc(1, sizeof(attr[0]));
	il[0].item_code = LNM$_TABLE;
	il[0].buffer = table;
	il[0].buffer_size = sizeof(table);
	il[0].return_length = &table_len;
        il[1].item_code = LNM$_STRING;
        il[1].buffer = SvPV_nolen(*sv);
        il[1].buffer_size = SvCUR(*sv);
        sv = hv_fetch(hv, "attr", 4, 0);
        if (sv)
        {
            if (!SvROK(*sv) || (SvTYPE(SvRV(*sv)) != SVt_PVHV))
                croak("attr must be a hash ref");
            *attr = translate_attributes((HV *)SvRV(*sv), WHERE_EQV_DEF);
            il[2] = il[1];
            il[1].item_code = LNM$_ATTRIBUTES;
            il[1].buffer = attr;
            il[1].buffer_size = sizeof(attr[0]);
        }
    } else {
        croak("Can't find equivalence string[s]");
    }
    sts = sys$crelnm(l_attr, tabnam_d, lognam_d, acmode, il);
    free(il);
    free(attr);
    if (sts & 1)
    {
	RETVAL = newSVpvn(table, table_len);
	SETERRNO(0,sts);
    }
    else
    {
	RETVAL = &PL_sv_undef;
	SETERRNO(EVMSERR,sts);
    }
  OUTPUT:
    RETVAL

# deassign a logical name
SV *
deassign(lnm)
    SV *lnm
  CODE:
    HV *hv;
    SV **sv;
    struct dsc$descriptor *tabnam_d, *lognam_d;
    int acmode_v, *acmode = NULL;
    itemlist *il = NULL;
    int sts;

    if (!SvROK(lnm) || (SvTYPE(SvRV(lnm)) != SVt_PVHV))
        croak("Argument must be a hash ref");
    hv = (HV *) SvRV(lnm);
    tabnam_d = get_hash_string_desc(hv, "table");
    lognam_d = get_hash_string_desc(hv, "lognam");
    sv = hv_fetch(hv, "acmode", 6, 0);
    if (sv)
    {
        acmode_v = find_access_mode(*sv);
        if (acmode_v == -1)
            croak("Invalid access mode");
        acmode = &acmode_v;
    }
    sts = sys$dellnm(tabnam_d, lognam_d, acmode);
    free(il);
    if (sts & 1)
	RETVAL = newSViv(sts);
    else
    {
	RETVAL = &PL_sv_undef;
	SETERRNO(EVMSERR,sts);
    }
  OUTPUT:
    RETVAL

# create a logical name table
SV *
create_table(lnm)
    SV *lnm
  CODE:
    HV *hv;
    SV **sv;
    struct dsc$descriptor *tabnam_d, *partab_d;
    char resnam[32];
    $DESCRIPTOR(resnam_d, resnam);
    short reslen;
    int l_attr_v = 0, *l_attr = NULL;
    int quota_v = 0, *quota = NULL;
    int acmode_v, *acmode = NULL;
    int sts;

    if (!SvROK(lnm) || (SvTYPE(SvRV(lnm)) != SVt_PVHV))
        croak("Argument must be a hash ref");
    hv = (HV *) SvRV(lnm);
    sv = hv_fetch(hv, "quota", 5, 0);
    if (sv)
    {
        quota_v = SvIV(*sv);
	quota = &quota_v;
    }
    sv = hv_fetch(hv, "attr", 4, 0);
    if (sv)
    {
        if (!SvROK(*sv) || (SvTYPE(SvRV(*sv)) != SVt_PVHV))
            croak("attr must be a hash ref");
	l_attr_v = translate_attributes((HV *)SvRV(*sv), WHERE_TBL_DEF);
        l_attr = &l_attr_v;
    }
    tabnam_d = get_hash_string_desc(hv, "table");
    partab_d = get_hash_string_desc(hv, "partab");
    sv = hv_fetch(hv, "acmode", 6, 0);
    if (sv)
    {
        acmode_v = find_access_mode(*sv);
        if (acmode_v == -1)
            croak("Invalid access mode");
        acmode = &acmode_v;
    }
    sts = sys$crelnt(l_attr, &resnam_d, &reslen, quota, /* promask */0,
		     tabnam_d, partab_d, acmode);
    if (sts & 1)
    {
	RETVAL = newSVpvn(resnam,reslen);
	SETERRNO(0,sts);
    }
    else
    {
	RETVAL = &PL_sv_undef;
	SETERRNO(EVMSERR,sts);
    }
    if (tabnam_d)
        free_desc(tabnam_d);
    if (partab_d)
        free_desc(partab_d);
  OUTPUT:
    RETVAL