RCS_ID_C="$Id: agnet.c,v 3.1 93/10/07 19:24:08 ppessi Exp $";
/*
 * agnet.c --- agnet main program and arexx interface
 *
 * Author: ppessi <Pekka.Pessi@hut.fi>
 *
 * Copyright (c) 1993 OHT-AmiTCP/IP Group,
 *                    Helsinki University of Technology, Finland.
 *                    All rights reserved.
 *
 * Created      : Sat Feb 20 18:30:59 1993 ppessi
 * Last modified: Thu Oct  7 18:23:33 1993 ppessi
 *
 * $Log:	agnet.c,v $
 * Revision 3.1  93/10/07  19:24:08  ppessi
 * Release 2.1 version
 * 
 * Revision 2.1  93/05/14  16:46:32  ppessi
 * Release version.
 * 
 * Revision 2.0  93/03/10  16:35:03 16:35:03  ppessi (Pekka Pessi)
 * Prototype release.
 */

#include <string.h>
#include <stdarg.h>

#include <dos/dostags.h>
#include <dos/rdargs.h>
#include <intuition/intuition.h>
#include <rexx/storage.h>
#include <rexx/rxslib.h>

#include <clib/exec_protos.h>
#include <clib/dos_protos.h>
#include <clib/utility_protos.h>
#include <clib/intuition_protos.h>

#ifdef __SASC
#include <pragmas/exec_sysbase_pragmas.h>
#include <pragmas/dos_pragmas.h>
#include <pragmas/utility_pragmas.h>
#include <pragmas/intuition_pragmas.h>
#endif

#include "agnet.h"
#include "agnet_protos.h"
#include "agnet_rev.h"
#include "bases.h"

static ULONG init_arexx(VOID);
static VOID poll_arexx(VOID);
static VOID deinit_arexx(VOID);

/*
 * We start as a CLI.
 * Assembler stub routine does a MakeLibrary and
 * initializes needed librarybases
 */
LONG ASM main(REG(a6)struct AgnetDevice *adb)
{
  struct Process *proc;
  struct IOSana2Req *io;
  ULONG waitmask, rexxsignal, signals;
  UBYTE devsignal;
  UBYTE myname[sizeof(AGNETDEVNAME)+5];

  AgnetDeviceBase = adb;

  proc = (struct Process *)FindTask(0L);

  /* Initialize the device base */
  adb->ad_Device.lib_Node.ln_Pri  = AGNET_DEV_PRI;
  adb->ad_Device.lib_Node.ln_Type = NT_DEVICE;
  adb->ad_Device.lib_Node.ln_Name = myname;
  adb->ad_Device.lib_Version  = VERSION;
  adb->ad_Device.lib_Revision = REVISION;
  adb->ad_Device.lib_IdString = VSTRING;
  adb->ad_Task = (struct Task *)proc;

  /* Set up our unit message port */
  /* Attempt to allocate a signal bit for our Unit MsgPort. */
  devsignal = AllocSignal(-1L);
  if (devsignal == -1) {
    return 20;
  }
  NewList(&adb->ad_MsgPort.mp_MsgList);
  adb->ad_MsgPort.mp_SigBit = devsignal;
  adb->ad_MsgPort.mp_SigTask = (struct Task *)proc;
  adb->ad_MsgPort.mp_Flags = PA_SIGNAL;

  /* Initialize our device base semafore */
  InitSemaphore(&adb->ad_Lock);

  InitLRandom();

#ifdef AGREXX
  /* Initialize Arexx port */
  rexxsignal = init_arexx();
#else
  rexxsignal = 0;
#endif
  /* OK, we are ready to add agnet.device to system */
  {
    int i, n = sizeof(AGNETDEVNAME) - 1;
    strcpy(myname, AGNETDEVNAME);

    Forbid();
    for (i = 0; i < 9; i++) {
      if (!FindName(&adb->ad_SysBase->DeviceList, myname))
	break;
      myname[n] = '.'; myname[n+1] = '1' + i; myname[n+2] = '\0';
    }
    AddDevice(adb);
    Permit();
  }

  waitmask = (1<<devsignal) | rexxsignal | SIGBREAKF_CTRL_F ;

  while (TRUE) {
    signals = Wait(waitmask);	/* wait for device command */

    if (signals & SIGBREAKF_CTRL_F) {
      if (DoExpunge(adb))
	break;
    }

    while (io = (struct IOSana2Req *)GetMsg(&adb->ad_MsgPort)) 
      PerformIO(io);

#ifdef AGREXX
    if (signals & rexxsignal)
      poll_arexx();
#endif
  }

  /* Clean up */
#ifdef AGREXX
  deinit_arexx();
#endif
  FreeSignal(devsignal);
  return 0;
}

/* Local prototypes */
static LONG ParseConfig(struct AgnetDevUnit *, struct RDArgs *, STRPTR*);
static BOOL GetBitAddress(UBYTE *addr, UBYTE *string, LONG bitsize);
static char *SanaSprintf(register char *ap, int len);
static ULONG csprintf(struct CSource *buf, const char *fmt, ...);

#define CONFIG_ENV_TEMPLATE "ENV:SANA2/config%ld.agnet"

#define UNINITIALIZED 0xffffffff

/*
 *  ReadConfig
 *
 *  Attempt to read in the driver's configuration file.
 *
 *  The files are named by ENV:SANA2/agnet%d.config where %d is the decimal
 *  representation of the device's unit number.
 *
 *  Return FALSE upon error.
 */
BOOL ReadConfig(struct AgnetDevUnit *adu)
{
  UBYTE *fbuf = NULL;
  UBYTE buff[40];
  struct CSource csbuff = { NULL, sizeof(buff), 0L };
  BPTR ConfigFile;
  LONG offset, len;
  BOOL ok = FALSE;

  csbuff.CS_Buffer = buff;

  /* This configures unit by default values */
  adu->adu_HardwareType = UNINITIALIZED;

  /* Create the name of our config file.. */
  csprintf(&csbuff, CONFIG_ENV_TEMPLATE, (ULONG)adu->adu_UnitNum);

  /* ...and open it. */
  ConfigFile = Open(buff, MODE_OLDFILE);
  if (!ConfigFile) 
    return TRUE;

  len = (Seek(ConfigFile, 0L, 1), Seek(ConfigFile, 0L, 0));
  if (len >= 0 && (fbuf = AllocMem(len+2, MEMF_PUBLIC))) {
    Seek(ConfigFile, 0L, -1);
    if (Read(ConfigFile, fbuf, len) == len) {
      ok = TRUE;
      fbuf[len] = '\n';
      fbuf[len+1] = '\0';
    } 
  }
  Close(ConfigFile);

  if (ok) { 
    struct RDArgs *rdargs = AllocDosObject(DOS_RDARGS, NULL);
    STRPTR *errmsg;

    if (rdargs) {
      /* Remove Comment lines */ 
      for (offset = 0; offset < len;) {
	if (fbuf[offset] == '#')
	  while(fbuf[offset] != '\n')
	    fbuf[offset++] = ' ';
	else
	  while(fbuf[offset++] != '\n');
	fbuf[offset - 1] = ' '; /* remove linefeeds in file */
      }
      /* Add sentinel */
      fbuf[len] = '\n';

      rdargs->RDA_Source.CS_Buffer = fbuf;
      rdargs->RDA_Source.CS_Length = len + 1;
      rdargs->RDA_Source.CS_CurChr = 0;

      if (ParseConfig(adu, rdargs, &errmsg)) {
	struct EasyStruct es;
	APTR args[2];
	args[0] = errmsg;
	args[1] = buff;
	es.es_StructSize = sizeof(es);
	es.es_Flags = 0;
	es.es_Title = AGNETDEVNAME;
	es.es_TextFormat="Error %s in \nthe configuration file\n%s";
	es.es_GadgetFormat="Okay";
	EasyRequestArgs(NULL, &es, 0, args);
	ok = FALSE;
      }
    }
    FreeDosObject(DOS_RDARGS, rdargs);
  }

  if (fbuf) FreeMem(fbuf, len+2);
  return ok;
}

#define MYREXXNAME "agnet"
#define MYREXXEXTENSION "agnet"

/* Error strings */
#define ERR_MALFORMED "Malformed command line"
#define ERR_SYNTAX    "Syntax error"
#define ERR_WIRE      "Illegal wiretype"
#define ERR_ADDRESS   "Illegal address"
#define ERR_UNIT      "Illegal unit number"
#define ERR_INITUNIT  "Error initializing unit"
#define ERR_MEMORY    "Memory exhausted"
#define ERR_OPEN      "Unit is currently opened"
#define ERR_VALUE     "Illegal value"

#ifdef AGREXX
/*
 * Arexx interface functions to agnet.device 
 */

#include	<rexx/storage.h>
#include	<rexx/rxslib.h>
#include        <clib/rexxsyslib_protos.h>
#ifdef __SASC
#include	<pragmas/rexxsyslib_pragmas.h>
#endif
#include	"SimpleRexx.h"

AREXXCONTEXT	RexxContext;

static LONG ParseRexx(UBYTE *arg, UBYTE **errstr, UBYTE **result);
static LONG ParseQuery(struct AgnetDevUnit *, struct RDArgs *,
		       STRPTR *,STRPTR *);
/*
 * Initialize Arexx port
 */
static ULONG 
init_arexx(VOID)
{
  ULONG rexxsignal;

  RexxContext = InitARexx(MYREXXNAME, MYREXXEXTENSION);

  if (!RexxContext)
    return 0L;

  rexxsignal = ARexxSignal(RexxContext);

  return rexxsignal;
}

/* 
 * Free ARexx port
 */
static VOID 
deinit_arexx(VOID)
{
  if (RexxContext)
    FreeARexx(RexxContext);
}

/* 
 * Poll Arexx port
 */
static VOID 
poll_arexx(VOID)
{
  struct RexxMsg *rmsg;

  /*
   * Process the ARexx messages...
   */
  while (rmsg = GetARexxMsg(RexxContext)) {
    UBYTE *error = NULL, *result = NULL;
    LONG errlevel=0;

    if (errlevel = ParseRexx(ARG0(rmsg), &error, &result)) {
      SetARexxLastError(RexxContext, rmsg, error);
    }
    ReplyARexxMsg(RexxContext, rmsg, result, errlevel);
  }
}

/*
 * Rexx commands
 */

#define REXXKEYWORDS "U=UNIT,Q=QUERY,E=EXIT=EXPUNGE"
#define KEYWORDLEN  16

#define KEY_UNIT 0
#define KEY_QUERY 1
#define KEY_EXIT 2

/*
 * Parse the ARexx command 
 */
static LONG
ParseRexx(UBYTE *arg, UBYTE **errstr, UBYTE **result)
{
  struct AgnetDevice *adb = AgnetDeviceBase;
  LONG errlevel = 0;
  UBYTE Buffer[KEYWORDLEN];
  LONG len = LengthArgstring(arg) + 2;
  UBYTE *cmd = AllocMem(len, 0);
  struct RDArgs *rdargs = AllocDosObject(DOS_RDARGS, NULL);
  LONG unit, keyword;
  struct AgnetDevUnit *adu;

  if (!cmd || !rdargs) {
    errlevel = 40;
    *errstr = ERR_MEMORY;
  } else {
    memcpy(cmd, arg, len - 2);
    /* Add sentinel */
    cmd[len-2] = '\n'; cmd[len-1] = '\0';
    rdargs->RDA_Source.CS_Buffer = cmd;
    rdargs->RDA_Source.CS_Length = len - 1;
    rdargs->RDA_Source.CS_CurChr = 0;
    /* First, parse the command keyword */
    if (ReadItem(Buffer, sizeof(Buffer), &rdargs->RDA_Source) <= 0 ||
	(keyword = FindArg(REXXKEYWORDS, Buffer)) < 0) {
      errlevel = 20;
      *errstr = ERR_MALFORMED;
    } else {
      switch (keyword) {
      case KEY_UNIT:
      case KEY_QUERY:
	/* Parse unit number */
	if ((len = StrToLong(cmd + rdargs->RDA_Source.CS_CurChr, &unit)) < 0 ||
	    unit >= AD_MAXUNITS) {
	  errlevel = 20;
	  *errstr = ERR_UNIT;
	  break;
	}
	rdargs->RDA_Source.CS_CurChr += len;

	if (!(adu = adb->ad_Units[unit])) 
	  /* If there is no opened unit, we init one */
	  if (keyword == KEY_QUERY || !(adu = InitUnit(unit))) {
	    /* We got no unit structure, there was an initialization
	       problem, we must give up */
	    *errstr = ERR_INITUNIT;
	    errlevel = 20;
	    break;
	  }
	if (keyword == KEY_UNIT) {
	  /* Parse the rest of the line with ParseConfig */
	  errlevel = ParseConfig(adu, rdargs, errstr);
	  break;
	} else {
	  errlevel = ParseQuery(adb->ad_Units[unit], rdargs, errstr, result);
	  break;
	}
      case KEY_EXIT:
	/* We try to expunge */
	SetSignal(SIGBREAKF_CTRL_F, SIGBREAKF_CTRL_F);
	break;
      }
    } 
  } 
    
  if (cmd) FreeMem(cmd, len);
  if (rdargs) FreeDosObject(DOS_RDARGS, rdargs);

  return errlevel;
}
#endif /* AGREXX */

/* 
 * Configuration parameters
 */
#define CONFIG_ARGS              20	/* # of args in CONFIG_TEMPLATE */
#define CONFIG_TEMPLATE "WIRE/K" \
  ",MTU/N/K,MINTU/N/K,BPS/N/K,ADDR=ADDRESS/K" \
  ",DELAY/N/K,DEV=DEVIATION/N/K,ERRORS/K/N,LOSS/K/N,DST=DSTUNIT/N"

#define CMD_WIRE      0
#define CMD_MTU       1	/* Maximum transfer unit */
#define CMD_MINTU     2	/* Minimum Trransfer unit */
#define CMD_BPS       3	/* Bits per second */
#define CMD_ADDR      4	/* Address string */
#define CMD_DELAY     5	/* Delay in ms */
#define CMD_DEVIATION 6	/* Deviation in ms */
#define CMD_ERRORS    7 /* Bit error probability */
#define CMD_LOSS      8	/* Packet loss probability */
#define CMD_PPUNIT    9	/* Destination unit */

#define CMD_ALL      10 /* All values queried */
 		      
#define WIRE_TEMPLATE   "LOOPBACK,ETHERNET,IEEE802,ARCNET" \
  ",LOCALTALK,AMOKNET" \
  ",PPP,SLIP,CSLIP" \

#define WIRE_LOOPBACK  0
#define WIRE_ETHERNET  1
#define WIRE_IEEE802   2
#define WIRE_ARCNET    3
#define WIRE_LOCALTALK 4
#define WIRE_AMOKNET   5
#define WIRE_PPP       6
#define WIRE_SLIP      7
#define WIRE_CSLIP     8

#ifdef AGREXX
#include <exec/initializers.h>

#define MAXQUERYLEN 512

#define QUERY_TEMPLATE "WIRE/S" \
  ",MTU/S,MINTU/S,BPS/S,ADDR=ADDRESS/S" \
  ",DELAY/S,DEV=DEVIATION/S,ERRORS/S,LOSS/S,DST=DSTUNIT/S,ALL/S"

static struct {
  STRPTR title;
  WORD offset;
  enum { nulong, wire, address } type;
} query_titles[] = {
  { "WIRE",     OFFSET(AgnetDevUnit, adu_HardwareType), wire },
  { "MTU",      OFFSET(AgnetDevUnit, adu_MaxTU),        nulong },
  { "MINTU",    OFFSET(AgnetDevUnit, adu_MinTU),        nulong },
  { "BPS",      OFFSET(AgnetDevUnit, adu_BPS),          nulong },
  { "ADDRESS",  OFFSET(AgnetDevUnit, adu_Addr),         address },
  { "DELAY",    OFFSET(AgnetDevUnit, adu_Delay),        nulong },
  { "DEVIATION",OFFSET(AgnetDevUnit, adu_Deviation),    nulong },
  { "ERRORS",   OFFSET(AgnetDevUnit, adu_Errors),       nulong },
  { "LOSS",     OFFSET(AgnetDevUnit, adu_Loss),         nulong },
  { "DSTUNIT",  OFFSET(AgnetDevUnit, adu_PPUnit),       nulong }
};

#define AT_OFFSET(X,Y) ((UBYTE *)X + Y)
#define ULONG_AT(X,Y) (*(ULONG*)AT_OFFSET(X,Y))

static struct {
  ULONG  type;
  STRPTR name;
} wirenames[] = {
  { S2WireType_LoopBack,  "Loopback" },
  { S2WireType_Ethernet,  "Ethernet" },
  { S2WireType_IEEE802,   "IEEE802" },
  { S2WireType_Arcnet,    "Arcnet" },
  { S2WireType_LocalTalk, "LocalTalk" },
  { S2WireType_AmokNet,   "AmokNet" },
  { S2WireType_PPP,       "PPP" },
  { S2WireType_SLIP,      "SLIP" },
  { S2WireType_CSLIP,     "CSLIP" },
  { S2WireType_LoopBack,  "Unknown" }
};
#endif

/*
 * Parse a configuration command
 * adu = NULL if parse an ARexx command
 * return differs from zero if an error
 */
static LONG 
ParseConfig(struct AgnetDevUnit *adu, struct RDArgs *rdargs, 
	    STRPTR *errormessage)
{
  LONG args[CONFIG_ARGS] = {0};
  LONG error; 
  LONG ppunit;
  UBYTE address[MAX_ADDR_BYTES] = {0};
  UWORD addrfieldsize;
  ULONG hardwaretype, maxtu, mintu, bps, delay, deviation, errors, loss;
  BOOL virgin;
  LONG wire;

  /* Parse the file or the line...*/
  rdargs = ReadArgs(CONFIG_TEMPLATE, args, rdargs);
  if (error = !rdargs) {
    if (errormessage)
      *errormessage = ERR_SYNTAX;
    return error;
  }

  hardwaretype = adu->adu_HardwareType;
  addrfieldsize = adu->adu_AddrFieldSize;
  ppunit = adu->adu_PPUnit;
  maxtu = adu->adu_MaxTU;
  mintu = adu->adu_MinTU;
  bps   = adu->adu_BPS;
  delay = adu->adu_Delay;
  deviation = adu->adu_Deviation;
  errors    = adu->adu_Errors;
  loss      = adu->adu_Loss ;
  memcpy(address, adu->adu_Addr, MAX_ADDR_BYTES);

  virgin = hardwaretype == UNINITIALIZED;

  if (args[CMD_WIRE]) {
    wire = FindArg(WIRE_TEMPLATE, args[CMD_WIRE]);
  } else if (virgin) {
    wire = S2WireType_Ethernet;
  }

  if (args[CMD_WIRE] || virgin) {
    switch (wire) {
    case WIRE_LOOPBACK:
      /* This is simple loopback type */
      hardwaretype = S2WireType_LoopBack;
      addrfieldsize = 0;	/* No addresses */
      maxtu = 1024;
      bps   = 100000000;	/* it's fast like a hell...*/
      mintu = 0;
      /* There is no need for address but we zero it anyways */
      memset(address, 0, MAX_ADDR_BYTES);
      break;

    case WIRE_ETHERNET:
      /* This is default */
      hardwaretype = S2WireType_Ethernet;
      addrfieldsize = 48;
      maxtu = 1500;
      bps   = 10000000;		/* 10 Mb/s */
      mintu = 1;
      /* Ethernet address is same as unit number */
      memset(address + 6, 0, MAX_ADDR_BYTES-2);
      memcpy(address, "ETHER", 5);
      address[5] = adu->adu_UnitNum;
      ppunit = -1;
      break;

    case WIRE_IEEE802:
      /* Wiretype is IEEE802 */
      hardwaretype  = S2WireType_IEEE802;
      addrfieldsize = 48;
      maxtu = 1500;
      bps   = 10000000;		/* 10 Mb/s */
      mintu = 1;
      /* IEEE802 address is same as unit number */
      memset(address+6, 0, MAX_ADDR_BYTES-6);
      memcpy(address, "IEEE8", 5);
      address[5] = adu->adu_UnitNum;
      ppunit = -1;
      break;

    case WIRE_ARCNET:
      hardwaretype  = S2WireType_Arcnet;
      addrfieldsize = 8;
      maxtu = 506;
      bps   = 5000000L;		/* 5 Mb/s */
      mintu = 1;
      /* Arcnet address is got from unit number */
      memset(adu->adu_Addr, 0, MAX_ADDR_BYTES);
      adu->adu_Addr[0] = 255 - adu->adu_UnitNum;
      ppunit = -1;
      break;

    case WIRE_LOCALTALK:
      /* Don't know much, but... */
      hardwaretype  = S2WireType_LocalTalk;
      addrfieldsize = 8;
      maxtu = 256;
      bps   = 128000L;		/* 128 kb/s */
      mintu = 1;
      /* Localnet address is got from unit number */
      memset(address, 0, MAX_ADDR_BYTES);
      address[0] = adu->adu_UnitNum;
      ppunit = -1;
      break;

      /* Amoknet parameters are all got with RaHi method */
    case WIRE_AMOKNET:
      /* Don't know much, but... */
      hardwaretype  = S2WireType_AmokNet;
      addrfieldsize = 8;
      maxtu = 512;
      bps   = 128000L;		/* 128 kb/s */
      mintu = 1;
      /* Amoknet address is got from unit number */
      memset(address, 0, MAX_ADDR_BYTES);
      address[0] = adu->adu_UnitNum;
      break;

    case WIRE_PPP:
      hardwaretype = S2WireType_PPP; goto serials;
    case WIRE_SLIP:
      hardwaretype = S2WireType_SLIP; goto serials;
    case WIRE_CSLIP:
      hardwaretype = S2WireType_CSLIP; 
    serials: 
      addrfieldsize = 32;
      maxtu = 1006;
      bps   = 9600L;		/* 9600 b/s */
      mintu = 1;
      /* SLIP address is set during config */
      memset(address, 0, MAX_ADDR_BYTES);
      break;
    default:
      if (errormessage)
	*errormessage = ERR_WIRE;
      error = 5;
      goto free_and_exit;
    }
  }
  if (args[CMD_MTU]) {
    maxtu = *(ULONG *)args[CMD_MTU];
  }

  if (args[CMD_MINTU]) {
    mintu = *(ULONG *)args[CMD_MINTU];
  }

  if (args[CMD_BPS]) {
    bps = *(ULONG *)args[CMD_BPS];
  }

  if (args[CMD_ADDR]) {
    memset(address, 0, MAX_ADDR_BYTES);
    if (!GetBitAddress(address, (UBYTE *)args[CMD_ADDR], addrfieldsize)) {
      if (errormessage)
	*errormessage = ERR_ADDRESS;
      error = 7;
      goto free_and_exit;
    }
  }

  if (args[CMD_DELAY]) {
    delay = *(ULONG *)args[CMD_DELAY];
    /* maximum delay is 0x800000 ms, that is fairly over an hour */
    if (delay >= 0x800000)
      delay = 0x7fffff;
  }

  if (args[CMD_DEVIATION]) {
    deviation = *(ULONG *)args[CMD_DEVIATION];
  }

  if (args[CMD_ERRORS]) {
    errors = *(ULONG *)args[CMD_ERRORS];
    if (errors > ERRORS_MAX) {
      error = 5;
      *errormessage = ERR_VALUE;
      goto free_and_exit;
    }
  }

  if (args[CMD_LOSS]) {
    loss = *(ULONG *)args[CMD_LOSS];
    if (loss > LOSS_MAX) {
      error = 5;
      *errormessage = ERR_VALUE;
      goto free_and_exit;
    }
  }

  if (args[CMD_PPUNIT]) {
    ppunit = *(ULONG *)args[CMD_PPUNIT];
  }

  if (!virgin) {
    LockUnit(adu);
    /* 
     * Changing the hardware type or reducing the MTU requires 
     * that the unit is not currently open
     */
    if ((adu->adu_HardwareType != hardwaretype ||
	 adu->adu_MaxTU > maxtu) &&
	adu->adu_Unit.unit_OpenCnt != 0 ) {
      UnlockUnit(adu);
      error = 5;
      *errormessage = ERR_OPEN;
      goto free_and_exit;
    }
  }

  adu->adu_HardwareType = hardwaretype;
  adu->adu_AddrFieldSize = addrfieldsize;
  adu->adu_PPUnit = ppunit;
  adu->adu_MinTU = mintu;
  adu->adu_BPS   = bps;
  adu->adu_Delay = delay;
  adu->adu_Deviation = deviation;
  adu->adu_Errors = errors;
  adu->adu_Loss  = loss;
  memcpy(adu->adu_Addr, address, MAX_ADDR_BYTES);

  /*
   * Enlarging the MTU requires offlining the device temporarily
   */
  if (!virgin && adu->adu_MaxTU < maxtu) {
    DoOffline(adu);
    adu->adu_MaxTU = maxtu;
    DoOnline(adu);
  } else {
    adu->adu_MaxTU = maxtu;
  }

  if (!virgin)
    UnlockUnit(adu);

 free_and_exit:
  if (rdargs) 
    FreeArgs(rdargs);
  return error;
}

#ifdef AGREXX
/*
 * Parse Query Command
 */
static LONG
ParseQuery(struct AgnetDevUnit *adu, 
	   struct RDArgs *rdargs,
	   STRPTR *errstr,
	   STRPTR *result)
{
  LONG errlevel = 0;
  LONG args[CONFIG_ARGS] = {0};
  char res[MAXQUERYLEN];
  struct CSource csres = { NULL, sizeof(res), 0L };
  BOOL all;
  LONG i;

  csres.CS_Buffer = res;

  /* Parse the file or the line...*/
  rdargs = ReadArgs(QUERY_TEMPLATE, args, rdargs);
  if (errlevel = !rdargs) {
    *errstr = ERR_SYNTAX;
    return errlevel;
  }

  all = args[CMD_ALL];
  for (i = CMD_WIRE; i < CMD_ALL; i++) 
    if (all || args[i]) {
      if (csres.CS_CurChr != 0 && csres.CS_CurChr < csres.CS_Length) 
	res[csres.CS_CurChr++] = ' ';
      switch (query_titles[i].type) {
      case nulong:
	csprintf(&csres, "%s=%ld", query_titles[i].title, 
		       ULONG_AT(adu, query_titles[i].offset));
	break;

      case wire:
	{
	  int j; int type = ULONG_AT(adu, query_titles[i].offset);
	  for (j = 0; j < sizeof(wirenames)/sizeof(wirenames[0]) - 2; j++) 
	    if (wirenames[j].type == type) 
	      break;
	  csprintf(&csres, "%s=%s", query_titles[i].title, 
		  wirenames[j].name);
	}
	break;

      case address:
	csprintf(&csres, "%s=%s", query_titles[i].title, 
		SanaSprintf(AT_OFFSET(adu,query_titles[i].offset),
			    adu->adu_AddrFieldSize + 7 >> 3));
	break;
      }
    }

  *result = CreateArgstring(res, csres.CS_CurChr);
  if (!*result) {
    *errstr = ERR_MEMORY;
    return 40;
  }
  return errlevel;
}

#endif /* AGREXX */

/*
 * Read hexadecimal address string into array.
 *
 * UBYTE *addr     = address array
 * UBYTE *string   = hexadecimal address string, 
 *                   each byte sparated by colon (":")
 * LONG   bitsize  = address length in bits
 *
 * Return true if no errors.
 * If there is error, may trash the address.
 */
static BOOL 
GetBitAddress(UBYTE *addr, UBYTE *string, LONG bitsize)
{
  LONG bitsperbyte; UWORD next; UWORD chars;
  UBYTE c;

  memset(addr, 0, (bitsize + 7) >> 3);

  while (bitsize > 0) {
    bitsperbyte = bitsize < 8 ? bitsize : 8;
    chars = next = 0;
    while ((c = *string - '0') <= 9 
	  || (c = c - 'A' + '0' + 10) >= 9 && c < 16  /*ABCDEF*/
	  || (c = c - 'a' + 'A') >= 9 && c < 16) /*abcdef*/ {
      chars++; string++;
      next = (next << 4) + c;
      if (next >= 1 << bitsperbyte)
	return FALSE;
    }
    if (!chars)
      return FALSE;

    *addr++ = next << (8 - bitsperbyte);
    bitsize -= bitsperbyte;
    if (*string != ':')
      break;
    string++;
  }
  if (bitsize || *string) return FALSE;
  return TRUE;
}

/*
 * Print Hardware Address
 */
static char *
SanaSprintf(register char *ap, int len)
{
  const char *digits = "0123456789ABCDEF";
  register i;
  static char addrbuf[17*3];
  register unsigned char *cp = addrbuf;

  for (i = 0; i < len; ) {
    *cp++ = digits[*ap >> 4];
    *cp++ = digits[*ap++ & 0xf];
    i++;
    if (i < len) 
      *cp++ = ':';
  }
  *cp = 0;
  return (addrbuf);
}

void ASM stuffchar(REG(d0) char ch, REG(a3) struct CSource * sc)
{
  if (sc->CS_CurChr < sc->CS_Length 
      && (sc->CS_Buffer[sc->CS_CurChr] = ch))
    sc->CS_CurChr++;
}

static ULONG
csprintf(struct CSource *buf, const char *fmt, ...)
{
  ULONG start = buf->CS_CurChr;
  va_list ap;

  va_start(ap, fmt);
  RawDoFmt((STRPTR)fmt, ap, stuffchar, buf);
  va_end(ap);

  buf->CS_Buffer[buf->CS_CurChr] = '\0';
  return buf->CS_CurChr - start;
}

