/*
 * tclXfcntl.c
 *
 * Extended Tcl fcntl command.
 *-----------------------------------------------------------------------------
 * Copyright 1991-1999 Karl Lehenbauer and Mark Diekhans.
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted, provided
 * that the above copyright notice appear in all copies.  Karl Lehenbauer and
 * Mark Diekhans make no representations about the suitability of this
 * software for any purpose.  It is provided "as is" without express or
 * implied warranty.
 *-----------------------------------------------------------------------------
 * $Id: tclXfcntl.c,v 1.1 2001/10/24 23:31:48 hobbs Exp $
 *-----------------------------------------------------------------------------
 */

#include "tclExtdInt.h"

/*
 * Attributes sets used by fcntl command.
 */
#define ATTR_ERROR           -1  /* Error parsing attributes.   */

#define ATTR_RDONLY           1  /* Access checks desired.      */
#define ATTR_WRONLY           2
#define ATTR_RDWR             3
#define ATTR_READ             4
#define ATTR_WRITE            5
#define ATTR_APPEND           6
#define ATTR_CLOEXEC          7
#define ATTR_NOBUF            8
#define ATTR_LINEBUF          9
#define ATTR_NONBLOCK        10
#define ATTR_KEEPALIVE       11

/*
 * The maximum length of any attribute name.
 */
#define MAX_ATTR_NAME_LEN  20

/*
 * Table of attribute names and values.
 */
static struct {
    char *name;
    int   id;
    int   modifiable;
} TclXfcntlAttrNames [] = {
    {"RDONLY",    ATTR_RDONLY,    FALSE},
    {"WRONLY",    ATTR_WRONLY,    FALSE},
    {"RDWR",      ATTR_RDWR,      FALSE},
    {"READ",      ATTR_READ,      FALSE},
    {"WRITE",     ATTR_WRITE,     FALSE},
    {"APPEND",    ATTR_APPEND,    TRUE},
    {"CLOEXEC",   ATTR_CLOEXEC,   TRUE},
    {"NONBLOCK",  ATTR_NONBLOCK,  TRUE},
    {"LINEBUF",   ATTR_LINEBUF,   TRUE},
    {"NOBUF",     ATTR_NOBUF,     TRUE},
    {"KEEPALIVE", ATTR_KEEPALIVE, TRUE},
    {NULL,        0,              FALSE}};

/*
 * Prototypes of internal functions.
 */
static int
XlateFcntlAttr  (Tcl_Interp  *interp,
                 char        *attrName,
                 int          modify);

static int
GetFcntlAttr (Tcl_Interp  *interp,
              Tcl_Channel  channel,
              int          mode,
              int          attrib);

static int
SetFcntlAttrObj (Tcl_Interp  *interp,
                 Tcl_Channel  channel,
                 int          attrib,
                 Tcl_Obj     *valueObj);

static int 
TclX_FcntlObjCmd (ClientData clientData, 
                  Tcl_Interp *interp,
                  int objc,
                  Tcl_Obj *const objv[]);


/*-----------------------------------------------------------------------------
 * XlateFcntlAttr --
 *    Translate an fcntl attribute to an numberic id.
 *
 * Parameters:
 *   o interp - Tcl interp, errors in result
 *   o attrName - The attrbute name to translate, maybe upper or lower case.
 *   o modify - Will the attribute be modified
 * Result:
 *   The number associated with the attirbute, or ATTR_ERROR is an error
 * occures.
 *-----------------------------------------------------------------------------
 */
static int
XlateFcntlAttr (Tcl_Interp *interp, char *attrName, int modify)
{
    char attrNameUp [MAX_ATTR_NAME_LEN];
    int idx;

    if (strlen (attrName) >= MAX_ATTR_NAME_LEN)
        goto invalidAttrName;
    
    TclX_UpShift (attrNameUp, attrName);
    
    for (idx = 0; TclXfcntlAttrNames [idx].name != NULL; idx++) {
        if (STREQU (attrNameUp, TclXfcntlAttrNames [idx].name)) {
            if (modify && !TclXfcntlAttrNames [idx].modifiable) {
                TclX_AppendObjResult (interp, "Attribute \"", attrName,
                                      "\" may not be altered after open",
                                      (char *) NULL);
                return ATTR_ERROR;
            }
            return TclXfcntlAttrNames [idx].id;
        }
    }

    /*
     * Invalid attribute.
     */
  invalidAttrName:
    TclX_AppendObjResult (interp, "unknown attribute name \"", attrName,
                          "\", expected one of ", (char *) NULL);

    for (idx = 0; TclXfcntlAttrNames [idx + 1].name != NULL; idx++) {
        TclX_AppendObjResult (interp, TclXfcntlAttrNames [idx].name, ", ",
                              (char *) NULL);
    }
    TclX_AppendObjResult (interp, "or ", TclXfcntlAttrNames [idx].name, (char *) NULL);
    return ATTR_ERROR;
}

/*-----------------------------------------------------------------------------
 * GetFcntlAttr --
 *    Return the value of a specified fcntl attribute.
 *
 * Parameters:
 *   o interp - Tcl interpreter, value is returned in the result
 *   o channel - The channel to check.
 *   o mode - Channel access mode.
 *   o attrib - Attribute to get.
 * Result:
 *   TCL_OK or TCL_ERROR
 *-----------------------------------------------------------------------------
 */
static int
GetFcntlAttr (Tcl_Interp *interp, Tcl_Channel channel, int mode, int attrib)
{
    int value, optValue;

    switch (attrib) {
      case ATTR_RDONLY:
        value = (mode & TCL_READABLE) && !(mode & TCL_WRITABLE);
        break;
      case ATTR_WRONLY:
        value = (mode & TCL_WRITABLE) && !(mode & TCL_READABLE);
        break;
      case ATTR_RDWR:
        value = (mode & TCL_READABLE) && (mode & TCL_WRITABLE);
        break;
      case ATTR_READ:
        value =  (mode & TCL_READABLE);
        break;
      case ATTR_WRITE:
        value = (mode & TCL_WRITABLE);
        break;
      case ATTR_APPEND:
        if (TclXOSGetAppend (interp, channel, &value) != TCL_OK)
            return TCL_ERROR;
        break;
      case ATTR_CLOEXEC:
        if (TclXOSGetCloseOnExec (interp, channel, &value) != TCL_OK)
            return TCL_ERROR;
        break;
      case ATTR_NONBLOCK:
        if (TclX_GetChannelOption (interp, channel, TCLX_COPT_BLOCKING,
                                   &optValue) != TCL_OK)
            return TCL_ERROR;
        value = (optValue == TCLX_MODE_NONBLOCKING);
        break;
      case ATTR_NOBUF:
        if (TclX_GetChannelOption (interp, channel, TCLX_COPT_BUFFERING,
                                   &optValue) != TCL_OK)
            return TCL_ERROR;
        value = (optValue == TCLX_BUFFERING_NONE);
        break;
      case ATTR_LINEBUF:
        if (TclX_GetChannelOption (interp, channel, TCLX_COPT_BUFFERING,
                                   &optValue) != TCL_OK)
            return TCL_ERROR;
        value = (optValue == TCLX_BUFFERING_LINE);
        break;
      case ATTR_KEEPALIVE:
        {
        socklen_t len;
        if (TclXOSgetsockopt (interp, channel, SO_KEEPALIVE, &len) != TCL_OK)
            return TCL_ERROR;
        value = len;
        }
        break;
      default:
        Tcl_Panic ("bug in fcntl get attrib");
        value = 0; /* suppress compiler warning for initialized value */
    }

    Tcl_SetIntObj (Tcl_GetObjResult (interp), value != 0);
    return TCL_OK;
}

/*-----------------------------------------------------------------------------
 * SetFcntlAttrObj --
 *    Set the the attributes on a channel.
 *
 * Parameters:
 *   o interp - Tcl interpreter, value is returned in the result
 *   o channel - The channel to check.
 *   o attrib - Atrribute to set.
 *   o valueStr - Object value (all are boolean now).
 * Result:
 *   TCL_OK or TCL_ERROR.
 *-----------------------------------------------------------------------------
 */
static int
SetFcntlAttrObj (Tcl_Interp *interp,
                 Tcl_Channel channel,
                 int attrib,
                 Tcl_Obj *valueObj)
{
    int value;

    if (Tcl_GetBooleanFromObj (interp, valueObj, &value) != TCL_OK)
        return TCL_ERROR;

    switch (attrib) {
      case ATTR_APPEND:
        if (TclXOSSetAppend (interp, channel, value) != TCL_OK)
            return TCL_ERROR;
        return TCL_OK;
      case ATTR_CLOEXEC:
        if (TclXOSSetCloseOnExec (interp, channel, value) != TCL_OK)
            return TCL_ERROR;
        return TCL_OK;
      case ATTR_NONBLOCK:
        return TclX_SetChannelOption (interp, channel, TCLX_COPT_BLOCKING,
                                      value ? TCLX_MODE_NONBLOCKING :
                                              TCLX_MODE_BLOCKING);
      case ATTR_NOBUF:
        return TclX_SetChannelOption (interp, channel, TCLX_COPT_BUFFERING,
                                      value ? TCLX_BUFFERING_NONE :
                                              TCLX_BUFFERING_FULL);
      case ATTR_LINEBUF:
        return TclX_SetChannelOption (interp, channel, TCLX_COPT_BUFFERING,
                                      value ? TCLX_BUFFERING_LINE :
                                              TCLX_BUFFERING_FULL);
      case ATTR_KEEPALIVE:
        return TclXOSsetsockopt (interp, channel, SO_KEEPALIVE, value);
      default:
        Tcl_Panic ("buf in fcntl set attrib");
    }
    return TCL_ERROR;  /* Should never be reached */
}

/*-----------------------------------------------------------------------------
 * TclX_FcntlObjCmd --
 *     Implements the fcntl TCL command:
 *         fcntl handle attribute ?value?
 *-----------------------------------------------------------------------------
 */
static int
TclX_FcntlObjCmd (ClientData clientData,
                  Tcl_Interp *interp,
                  int objc,
                  Tcl_Obj *const objv[])
{
    Tcl_Channel  channel;
    int          mode;
    int          attrib;
    char        *channelString;
    char        *fcntlAttributes;

    if ((objc < 3) || (objc > 4))
	return TclX_WrongArgs (interp, objv [0],
                               "handle attribute ?value?");

    channelString = Tcl_GetStringFromObj (objv[1], NULL);

    channel = Tcl_GetChannel (interp, channelString, &mode);
    if (channel == NULL) {
	return TCL_ERROR;
    }

    fcntlAttributes = Tcl_GetStringFromObj (objv[2], NULL);
    attrib = XlateFcntlAttr (interp, fcntlAttributes, (objc == 4));
    if (attrib == ATTR_ERROR)
        return TCL_ERROR;

    if (objc == 3) {    
        if (GetFcntlAttr (interp, channel, mode, attrib) != TCL_OK)
            return TCL_ERROR;
    } else {
        if (SetFcntlAttrObj (interp, channel, attrib, objv[3]) != TCL_OK)
            return TCL_ERROR;
    }
    return TCL_OK;
}


/*-----------------------------------------------------------------------------
 * TclX_FcntlInit --
 *     Initialize the fcntl command.
 *-----------------------------------------------------------------------------
 */
void
TclX_FcntlInit (Tcl_Interp *interp)
{
    Tcl_CreateObjCommand (interp, 
			  "fcntl",
			  TclX_FcntlObjCmd,
                          (ClientData) NULL,
			  (Tcl_CmdDeleteProc*) NULL);
}


/* vim: set ts=4 sw=4 sts=4 et : */
