/** 
 * -- Almost-Non-Volatile Environment Options
 * 
 *  Copyright (C) Tektronix, Inc. 2001 - 2001. All rights reserved.
 *
 *  @see     GNU LGPL
 *  @author  Tektronix CTE              @(#) %derived_by: guidod %
 *  @version %version: bln_mpt1!1.32 %
 *    (%date_modified: Wed Jul 17 15:48:59 2002 %)
 *
 *  @description
 *       the openfirmware standard specifies some means to add
 *       options to a non-volatile ram-area (nvram) that are used
 *       in the bootup sequence of the forth-based bootsystem.
 *       Here we add an internal API for looking for bootup options,
 *       an internal API to add bootup options through commandline
 *       processing, and an external API to change the bootup options
 *       for a COLD reboot or an APPLICATION specific sequence.
 *       The external API will try to follow openfirmware as closely
 *       as possible without having a real non-volatile ram-area.
 *       Instead there is a session-struct that can be changed and
 *       from which multiple forth-threads can be instantiated
 *       later using those options. The forth-instantion processing
 *       is not supposed to scan for commandline options, which is
 *       a mere necessity in embedded enviroments where option
 *       transferal is done in a completly different way and where
 *       the forth thread is never killed but just stopped or
 *       restarted for various reasons. Even that there is no real
 *       nvram we add a cold-options reboot-area in this wordset.
 *       The option-ram is organized just along a normal dictionary
 *       just need to add a wordlist-handlestruct to find the
 *       definitions in this dictlike ram-portion.
 */
/*@{*/
#if defined(__version_control__) && defined(__GNUC__)
static char* id __attribute__((unused)) = 
"@(#) $Id: %full_filespec:  option-ext.c~bln_mpt1!1.32:csrc:bln_12xx!1 % $";
#endif

#define _P4_SOURCE 1

#include <pfe/pfe-base.h>
#include <pfe/option-ext.h>

#include <stdlib.h>
#include <string.h>

#include <pfe/logging.h>

#ifdef __vxworks
#include <sysSymTbl.h>
#else
#include <errno.h>
#endif
#include <ctype.h>

#ifndef _export
typedef p4_Session p4_Options;
#endif

#define OPT (opt->opt)

#define IS_VALUE_RT(X) (*P4_TO_CODE(X) == PFX(p4_value_RT) \
                     || 
*P4_TO_CODE(X) == PFX(p4_constant_RT))

FCode_RT (p4_string_RT)
{   FX_USE_BODY_ADDR {
    p4char* str = (p4char*) FX_POP_BODY_ADDR;
    FX_PUSH (str);
    FX_PUSH (strlen(str));
}}
static FCode (p4_string) { /* dummy */ }
P4RUNTIME1(p4_string, p4_string_RT); 
/* ready for FX_GET_RT optimization */
/*
 * returns the CFA of the option if found
 */
_export p4xt 
p4_search_option (const p4char* nm, int l, p4_Options* opt)
{
    auto char upper[32];

    if (l < 32) 
    { memcpy (upper, nm, l); p4_upper (upper, l); }
    else 
    { *(int*)upper = 0; }

    { /* compare with dict-sub:search_thread called by p4_search_wordlist */
        p4char* t = OPT.link;
        while (t)
        {
            if (!(*_FFA(t) & P4xSMUDGED) && NFACNT(*t) == l) 
            { 
                if (! memcmp (nm, t+1, l) || ! memcmp (upper, t+1, l))
                    return p4_name_from (t);

                /* omitted extra strncmpi here... and no warning... */
            }
            t = *p4_name_to_link (t);
        }
    }
    return 0;
}
/*
 * create a new header in the option-dict and return
 * the xt. Unlike other header-creations, here we never
 * smudge the name, and the caller is responsible to
 * setup the value of the CFA. If no header could be
 * created then the function will return null - check for that!
 */
_export p4xt
p4_create_option (const p4char* name, int len, int size, p4_Options* opt)
{
    /* compare with dict-sub:p4_header_comma */

    if (len == 0 || len > NFACNTMAX 
      || OPT.dictlimit < OPT.dp + len + 2*sizeof(p4char) + 4*sizeof(p4cell) )
        return 0; /* invalid or dict exhausted */

# if defined PFE_WITH_FFA
    OPT.dp += 2; OPT.dp += len; while (! P4_ALIGNED(OPT.dp)) OPT.dp++;
    memmove (OPT.dp-len, name, len);
    OPT.last = OPT.dp-len -1;
    *OPT.last = len;
    OPT.last[-1] = '\x80';
# elif defined PFE_WITH_FIG
    OPT.dp += 1; OPT.dp += len; while (! P4_ALIGNED(OPT.dp)) OPT.dp++;
    memmove (OPT.dp-len, name, len);
    OPT.last = OPT.dp-len -1;
    *OPT.last = len;
    *OPT.last |= '\x80';
#else
    OPT.last = OPT.dp++;
    if (name != OPT.dp) memcpy (OPT.dp, name, len);
    *OPT.last = len;
    *OPT.last |= '\x80';
    OPT.dp += len; while (! P4_ALIGNED(OPT.dp)) OPT.dp++;
#endif

    *P4_VAR(pfe_lfa_t*,OPT.dp)++ = OPT.link;
    OPT.link = OPT.last;
    return P4_VAR(p4xt,OPT.dp)++; 
}
/*
 * search the option value in the option-ram, if nothing
 * is found then return the argument default. The option-ram
 * is not changed.
 * (in vxworks/k12xx: lookup also p4__default_<optionname> datasymbol)
 * (in posixish os: lookup also PFE_<OPTIONNAME> environment variable)
 */
_export p4cell
p4_search_option_value (const p4char* nm, int l, 
                        p4cell defval, p4_Options* opt)
{
    p4xt xt = p4_search_option (nm, l, opt);
    if (xt && IS_VALUE_RT(xt)) return *P4_TO_BODY(xt);
    /* else return deval; */
    if (l >= 32) return defval;

    { /* generic option passing via vx-start symbols-settings */
#      ifdef __vxworks 
        long* symval;
        const p4char prefix[] = "p4__default_";
#      else
        const p4char prefix[] = "pfe_default_";
#      endif
#       define strlen_prefix (sizeof(prefix)-1)
        p4char symbol[strlen_prefix+32+5];
        p4char* s;

        if (*nm == '/') { 
            memcpy (&symbol[0], prefix, strlen_prefix);
            memcpy (&symbol[strlen_prefix], nm+1, l-1);
            memcpy (&symbol[strlen_prefix+l-1], "_size", 6);
        }else{
            memcpy (&symbol[0], prefix, sizeof(prefix)-1);
            memcpy (&symbol[strlen_prefix], nm, l);
            /*    */ symbol[strlen_prefix+l] = '\0';
        }
        /* forth-symbols may contain non-alnums, need to sanitize */
        for (s=symbol; *s; s++) if (! isalnum(*(p4char*)s)) *s = '_';

#      ifdef __vxworks
        if (symFindByName (sysSymTbl, symbol, (char**) &symval, 0) == OK)
            if (symval) 
            {
                P4_info4 ("seen '%.*s' = %ld (%s)", l, nm, *symval, symbol);
                return *symval;
            }
#      else
#       ifndef _toupper
#       define _toupper(X) toupper(X)
#       endif
        for (s=symbol; *s; s++) if (islower(*s)) *s = _toupper(*s);

        if ((s=getenv(symbol)))
        {
            int newval;  errno = 0; newval = atoi (s);
            if (! errno)
            {
                P4_info4 ("seen '%.*s' = %d (%s)", l, nm, newval, symbol);
                return newval;
            }
        }
#      endif
        P4_info4 ("keep '%.*s' = %ld (%s not found)",  l, nm, (long)defval, 
                  symbol);
    }
    return defval;
}
/*
 * return the value of an option that is already in the
 * option-ram. If no option is found, add the default we
 * give as an argument, and return this parameter as the result.
 */
_export p4cell
p4_create_option_value (const p4char* nm, int l, 
                        p4cell defval, p4_Options* opt)
{
    p4xt xt = p4_search_option (nm, l, opt);
    if (xt && IS_VALUE_RT(xt))
        return *P4_TO_BODY(xt);
    else{
        xt = p4_create_option (nm, l, sizeof(p4cell), opt);
        if (! xt) return defval; 
	P4_XT_VALUE(xt) = FX_GET_RT (p4_value);
        return (( *(p4cell*)OPT.dp = defval ));
    }
}
/*
 * change the value of an option that is already in the
 * option-ram. If no option-var is found, create that option
 * and set the var to the argument value. Return the xt, or 
 * null if the option-ram is filled up.
 */
_export p4xt
p4_change_option_value (const p4char* nm, int l, 
                        p4cell defval, p4_Options* opt)
{
    p4xt xt = p4_search_option (nm, l, opt);
    if (! xt || ! IS_VALUE_RT(xt))
    {
        xt = p4_create_option (nm, l, sizeof(p4cell), opt);
        if (! xt) return 0; 
	P4_XT_VALUE(xt) = FX_GET_RT (p4_value);
    }
    *P4_TO_BODY(xt) = defval;
    return xt;
}
/*
 * search the option value in the option-ram, if nothing
 * is found then return the argument default. The option-ram
 * is not changed.
 */
_export const p4char*
p4_search_option_string (const p4char* nm, int l, 
                         const p4char* defval, p4_Options* opt)
{
    p4xt xt = p4_search_option (nm, l, opt);
    if (!xt || *P4_TO_CODE(xt) != PFX(p4_string_RT)) return defval;
    return ((void*) P4_TO_BODY(xt));
}
/*
 * return the value of an option that is already in the
 * option-ram. If no option is found, add the default we
 * give as an argument. The string is zero-terminated (!!)
 */
_export const p4char*
p4_create_option_string (const p4char* nm, int l, 
                         const p4char* defval, p4_Options* opt)
{
    p4xt xt = p4_search_option (nm, l, opt);
    if (xt && *P4_TO_CODE(xt) == PFX(p4_string_RT))
        return ((void*) P4_TO_BODY(xt));
    else{
        xt = p4_create_option (nm, l, sizeof(strlen(defval))+1, opt);
        if (! xt) return defval; 
	P4_XT_VALUE(xt) = FX_GET_RT (p4_string);
        memcpy (OPT.dp, defval, strlen(defval) +1);
        return ((void*) P4_TO_BODY(xt));
    }
}
/*
 * change the value of an option that is already in the
 * option-ram. If no option var is found, create that var
 * and set it to the argument string. The string is 
 * zero-terminated. The return is the xt or zero if
 * no option-var could be created.
 */
_export p4xt
p4_change_option_string (const p4char* nm, int l, 
                         const p4char* defval, p4_Options* opt)
{
    p4xt xt = p4_search_option (nm, l, opt);
    if (! xt || *P4_TO_CODE(xt) != PFX(p4_string_RT)
      || (*P4_TO_LINK(xt) && strlen(defval)+1 
        > ((p4char*)_FFA(*P4_TO_LINK(xt)) - (p4char*)P4_TO_BODY(xt))))
    {
        xt = p4_create_option (nm, l, strlen(defval)+1, opt);
        if (! xt) return 0; 
	P4_XT_VALUE(xt) = FX_GET_RT (p4_string);
    }
     
    memcpy ((void*)P4_TO_BODY(xt), defval, strlen(defval) +1);
    return xt;
}
/* ---------------------------------------------------------------- */
/*
 * a string shall be converted into a cell that should flag the
 * size of something like the dictionary or stack. The second
 * argument gives an idea about the default if no size-specifier
 * has been provided. This routine does match the arg-option
 * processing of gforth - had an e-mail exchange with anton ertl 
 * about this. Does return null on any errors.
 */
_export p4ucell
p4_convsize (const char* s, p4ucell elemsize)
{/* converts s of the format [0-9]+[bekMGT]? (e.g. 25k) into the number
    of bytes.  the letter at the end indicates the unit, where e stands
    for the element size. default is e */
    char *endp;
    p4ucell n,m;

    m = elemsize;
    n = strtoul(s,&endp,0);
    if (endp != NULL) {
        if (*endp == 'b' || *endp == 'c')
        { m = 1; if (*++endp == 'e') m *= elemsize; }
        else if (*endp == 'k' || *endp == 'K')
        { m = 1024; if (*++endp == 'e') m *= elemsize; }
        else if (*endp == 'M')
        { m = 1024*1024; if (*++endp == 'e') m *= elemsize; }
        else if (*endp == 'G')
        { m = 1024*1024*1024; if (*++endp == 'e') m *= elemsize;  }
        else if (*endp == 'T') {
#         if (PFE_SIZEOF_VOIDP > 4)
            { m = 1024L*1024*1024*1024; endp++; if (*++endp) m *= elemsize; }
#         else
            P4_fail1 ("size specification \"%s\" "
                      "too large for this machine\n", endp);
            return 0;
#         endif
        } 
        if (*endp != 'e' && *endp != 'b' && *endp != 'B' && *endp != 0) {
            P4_fail2 ("cannot grok size specification %s: "
                      "invalid unit \"%s\"\n", s, endp);
            return 0;
        }
    }
    return m * n;
}
P4_LISTWORDS (option) =
{
     (, ),
     (,	 ),
};
P4_COUNTWORDS (option, "Option Words For Almost-Non-Volatile Environment");
/*@}*/
/* 
 * Local variables:
 * c-file-style: "stroustrup"
 * End:
 */