/* $XFree86: xc/programs/Xserver/Xprint/attributes.c,v 1.23 2005/10/14 15:16:15 tsi Exp $ */
/*
(c) Copyright 1996 Hewlett-Packard Company
(c) Copyright 1996 International Business Machines Corp.
(c) Copyright 1996 Sun Microsystems, Inc.
(c) Copyright 1996 Novell, Inc.
(c) Copyright 1996 Digital Equipment Corp.
(c) Copyright 1996 Fujitsu Limited
(c) Copyright 1996 Hitachi, Ltd.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Except as contained in this notice, the names of the copyright holders shall
not be used in advertising or otherwise to promote the sale, use or other
dealings in this Software without prior written authorization from said
copyright holders.
*/
/*******************************************************************
**
**    *********************************************************
**    *
**    *  File:		attributes.c
**    *
**    *  Contents:
**    *                 Implementation of the attribute store for Xp.
**    *
**    *  Copyright:	Copyright 1995 Hewlett-Packard Company
**    *
**    *********************************************************
** 
********************************************************************/

#include <X11/Xproto.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <pwd.h>
#if (defined(sun) && defined(SVR4)) || (defined(__SCO__))
#include <wchar.h>
#endif

#include "scrnintstr.h"

#include <X11/extensions/Printstr.h>

#include "attributes.h"

#include <X11/Xlib.h>
#include <X11/Xresource.h>
#include "Xrm.c"

static XrmDatabase CopyDb(XrmDatabase inDb);

extern XrmDatabase XpSpoolerGetServerAttributes(void);

static unsigned long attrGeneration = 0;

typedef struct {
    XrmDatabase *pDb;
    char *qualifier;
    char *modelId;
} DbEnumStruct;

typedef struct {
    char *stringDb;
    int nextPos;
    int space;
} StringDbStruct;

typedef struct _printerAttrs {
    struct _printerAttrs *next;
    char *name;
    char *qualifier;
    XrmDatabase printerAttrs;
    XrmDatabase docAttrs;
    XrmDatabase jobAttrs;
} PrAttrs, *PrAttrPtr;

static PrAttrPtr attrList = (PrAttrPtr)NULL;

typedef struct _systemAttrs {
    XrmDatabase doc;
    XrmDatabase job;
    XrmDatabase printers;
    XrmDatabase server;
} SysAttrs, *SysAttrsPtr;

SysAttrs systemAttributes;

/*
 * attrCtxtPrivIndex hold the attribute store's context private index.
 * This index is allocated at the time the attribute store is initialized.
 */
static int attrCtxtPrivIndex;

/*
 * The ContextAttrs structure descibes the context private space reserved
 * by the attribute store.
 */
typedef struct _contextAttrs {
    XrmDatabase printerAttrs;
    XrmDatabase docAttrs;
    XrmDatabase jobAttrs;
    XrmDatabase pageAttrs;
} ContextAttrs, *ContextAttrPtr;

/*
 * XPDIR is relative to (i.e. is a subdir of) XPRINTDIR/$LANG.
 */
static const char XPDIR[] = "/print";
/*
 * The following files/directories define or are within subdirectories of the 
 * above-defined XPDIR.
 */
static const char XPPRINTERATTRFILE[] = "/attributes/printer";
static const char XPJOBATTRFILE[] = "/attributes/job";
static const char XPDOCATTRFILE[] = "/attributes/document";
static const char XPMODELDIR[] = "/models";

static char NULL_STRING[] = "\0";

/*
 * XpGetConfigDir returns a string containing the path name of the print
 * server configuration directory.  If the useLocale parameter is False
 * the it returns the path to the "/C" directory.  If the useLocale
 * parameter is True it returns the path to the directory associated with
 * $LANG.  It makes no attempt to ensure that the directory actually exists.
 */
char *
XpGetConfigDir(Bool useLocale)
{ 
    char *dirName, *langName, *langDir, *configDir;
    Bool freeLangDir = False;

    if(useLocale == False) langDir = "/C";
    else 
    {
	if((langName = getenv("LANG")) == (char *)NULL)
	    return (char *)NULL;
	else
	{
	    if(strcmp(langName, "C") == 0)
		return (char *)NULL;
	    langDir = (char *)xalloc(strlen(langName) + 2);
	    sprintf(langDir, "/%s", langName);
	    freeLangDir = True;
	}
    }
    
    /*
     * If the XPCONFIGDIR environment variable is not set, then use the
     * compile-time constant XPRINTDIR.  XPRINTDIR is passed in on the
     * compile command line, and is defined in $(TOP)/config/cf/Project.tmpl.
     */
    if((configDir = getenv("XPCONFIGDIR")) == (char *)NULL)
	configDir = XPRINTDIR;

    dirName = (char *)xalloc(strlen(configDir) + strlen(XPDIR) + 
			      strlen(langDir) + 1);
    sprintf(dirName, "%s%s%s", configDir, langDir, XPDIR);

    if(freeLangDir == True)
	xfree(langDir);

    return dirName;
}

/*
 * GetMergedDatabase reads and merges xrmdb files from the top-level printer
 * config directory, and from the directory associated with the current
 * locale (if other than the top-level).
 */
static XrmDatabase
GetMergedDatabase(const char *attrName)
{
    char *dirName, *fileName;
    XrmDatabase db;

    if((dirName = XpGetConfigDir(False)) == (char *)NULL)
	return (XrmDatabase)NULL;
    if((fileName = (char *)xalloc(strlen(dirName) + strlen(attrName) + 1)) ==
       (char *)NULL)
	return (XrmDatabase)NULL;
    sprintf(fileName, "%s%s", dirName, attrName);
    db = XrmGetFileDatabase(fileName);
    xfree(fileName);
    xfree(dirName);

    if((dirName = XpGetConfigDir(True)) == (char *)NULL) 
	return db;
    if((fileName = (char *)xalloc(strlen(dirName) + strlen(attrName) + 1)) ==
       (char *)NULL)
	return db;
    sprintf(fileName, "%s%s", dirName, attrName);
    (void)XrmCombineFileDatabase(fileName, &db, True);
    xfree(fileName);
    xfree(dirName);

    return db;
}

/*
 * BuildSystemAttributes reads the on-disk configuration files for printers,
 * initial job, and initial document attributes.  The resulting xrm 
 * databases are then dissected as needed for each printer.
 * It also allocates a contextPrivate space for the attributes,
 * reserving space to store pointers to the attribute stores for
 * the context.
 */
static void
BuildSystemAttributes(void)
{
    if(systemAttributes.printers != (XrmDatabase)NULL)
	XrmDestroyDatabase(systemAttributes.printers);
    systemAttributes.printers = GetMergedDatabase(XPPRINTERATTRFILE);
    if(systemAttributes.job != (XrmDatabase)NULL)
	XrmDestroyDatabase(systemAttributes.job);
    systemAttributes.job = GetMergedDatabase(XPJOBATTRFILE);
    if(systemAttributes.doc != (XrmDatabase)NULL)
	XrmDestroyDatabase(systemAttributes.doc);
    systemAttributes.doc = GetMergedDatabase(XPDOCATTRFILE);
    if(systemAttributes.server != (XrmDatabase)NULL)
	XrmDestroyDatabase(systemAttributes.server);
    systemAttributes.server = XpSpoolerGetServerAttributes();
    return;
}

/*
 * AddDbEntry is called by XrmEnumerateDatabase, and adds the supplied
 * database entry to the database pointed to within the "DbEnumStruct"
 * passed as the client_data (aka "closure").
 */
static Bool
AddDbEntry(
    XrmDatabase *sourceDB,
    XrmBindingList bindings,
    XrmQuarkList quarks,
    XrmRepresentation *type,
    XrmValue *value,
    XPointer client_data)
{
    DbEnumStruct *pEnumStruct = (DbEnumStruct *)client_data;
    XrmName xrm_name[5];
    XrmClass xrm_class[5];
    XrmBinding xrm_bind[3];
    XrmValue realVal;
    XrmRepresentation rep_type;

    xrm_name[0] = XrmStringToQuark (pEnumStruct->qualifier);
    xrm_class[0] = XrmStringToQuark (pEnumStruct->modelId);

    for(;*quarks; quarks++)
	xrm_name[1] = xrm_class[1] = *quarks;

    xrm_name[2] = (XrmQuark)NULL;
    xrm_class[2] = (XrmQuark)NULL;

    if(XrmQGetResource (*sourceDB, xrm_name, xrm_class, &rep_type, &realVal))
    {
        xrm_bind[0] = XrmBindLoosely;

	xrm_name[0] = xrm_name[1];
	xrm_name[1] = NULLQUARK;

        XrmQPutStringResource(pEnumStruct->pDb, xrm_bind, xrm_name, 
			      (char *)realVal.addr);
    }
    
    return FALSE;
}

/*
 * BuildPrinterAttrs - builds and returns an XrmDatabase for the printer
 * of the specified name/qualifier, if we have enough information.
 * If we don't have a model-config
 * file, then just enumerate the systemAttributes->printers database, 
 * otherwise read in the model-config database and merge into it the
 * systemAttributes->printers database.  This database is then enumerated
 * with the printer qualifier (and the model name as class if we have it), and
 * the resulting elements are stored into the database for this particular
 * printer.
 */
static XrmDatabase
BuildPrinterAttrs(
    char *printerName,
    char *qualifierName)
{
    XrmDatabase printerDB = (XrmDatabase)NULL;

    if(systemAttributes.printers != (XrmDatabase)NULL)
    {
        char *fileName;
        XrmDatabase modelDB = (XrmDatabase)NULL;
        XrmName xrm_name[5], xrm_class[2];
        XrmRepresentation rep_type;
        XrmValue value;
        DbEnumStruct enumStruct;
        Bool freeModelDB = False;
        /*
         * Build the initial db based on the model-config files
         */
        xrm_name[0] = XrmStringToQuark (qualifierName);
        xrm_name[1] = XrmStringToQuark ("xp-model-identifier");
        xrm_name[2] = (XrmQuark)NULL;
        XrmQGetResource (systemAttributes.printers, xrm_name, xrm_name, 
			 &rep_type, &value);

        if(value.addr != (XPointer)NULL)
        {
            fileName = (char *)xalloc(strlen(XPMODELDIR) + 
				      strlen((char *)value.addr) + 
				      strlen("model-config") + 3);
	    sprintf(fileName, "%s/%s/%s", XPMODELDIR, value.addr,
		    "model-config");
	    modelDB = GetMergedDatabase(fileName);
            xfree(fileName);
	    if(modelDB != (XrmDatabase)NULL)
	    {
		XrmDatabase tempDB = (XrmDatabase)NULL;
		/*
		 * have to make a temp copy because MergeDatabase destroys
		 * the "source" database. Merge in the printers DB
		 */
		tempDB = CopyDb(systemAttributes.printers);
		XrmMergeDatabases(tempDB, &modelDB);
		freeModelDB = True;
	    }
        }

	/*
	 * Check to see if we knew the name AND found a database file
	 */
	if(modelDB == (XrmDatabase)NULL)
	     modelDB = systemAttributes.printers;

        xrm_name[0] = XrmStringToQuark (qualifierName);
	xrm_name[1] = (XrmQuark)NULL;
	xrm_class[0] = XrmStringToQuark((char *)value.addr);
	xrm_class[1] = (XrmQuark)NULL;
	enumStruct.pDb = &printerDB;
	enumStruct.qualifier = (char *)qualifierName;
	enumStruct.modelId = (char *)value.addr;
	XrmEnumerateDatabase(modelDB, xrm_name, xrm_class, XrmEnumAllLevels,
			     AddDbEntry, (XPointer) &enumStruct);

        if(freeModelDB == True) XrmDestroyDatabase(modelDB);
    }
    XrmPutStringResource(&printerDB, "*printer-name", printerName);
    XrmPutStringResource(&printerDB, "*qualifier", qualifierName);
    return printerDB;
}

/*
 * BuildABase - builds an XrmDatabase by enumerating the supplied sourceBase
 * database for elements relevant for the printer named by printerName,
 * and deriving a class for printerName from the model declared in the
 * systemAttributes.printers database.  If no model is defined for this
 * printer then the printerName is used as the class as well.
 * 
 * This is used to build the initial value document and initial value
 * job attribute databases for each printer by searching the system
 * level doc and job databases.
 */
static XrmDatabase
BuildABase(
    char *printerName,
    char *qualifierName,
    XrmDatabase sourceBase)
{
    XrmDatabase builtDB = (XrmDatabase)NULL;

    if(sourceBase != (XrmDatabase)NULL)
    {
        XrmName xrm_name[5], xrm_class[2];
        XrmRepresentation rep_type;
        XrmValue value;
        DbEnumStruct enumStruct;

        /*
         * Retrieve the model name for use as the class.
         */
        xrm_name[0] = XrmStringToQuark (printerName);
        xrm_name[1] = XrmStringToQuark ("xp-model-identifier");
        xrm_name[2] = (XrmQuark)NULL;
        XrmQGetResource (systemAttributes.printers, xrm_name, xrm_name, 
			 &rep_type, &value);
	/*
	 * if we have a model name then use it as the class, otherwise
	 * just use the printer name as the class as well as the name.
	 */
        if(value.addr != (XPointer)NULL)
	    xrm_class[0] = XrmStringToQuark((char *)value.addr);
	else
	    xrm_class[0] = xrm_name[0];
	xrm_class[1] = (XrmQuark)NULL;

	xrm_name[1] = (XrmQuark)NULL;

	enumStruct.pDb = &builtDB;
	enumStruct.qualifier = (char *)qualifierName;
	enumStruct.modelId = (char *)value.addr;
	XrmEnumerateDatabase(sourceBase, xrm_name, xrm_class, XrmEnumAllLevels,
			     AddDbEntry, (XPointer) &enumStruct);
    }

    XrmPutStringResource(&builtDB, "*qualifier", qualifierName);

    return builtDB;
}

/*
 * FreeAttrList is called upon server recycle, and frees the printer
 * databases stored in the global attrList.
 */
static void
FreeAttrList(void)
{
    PrAttrPtr pAttr, pNext;

    for(pAttr = attrList, pNext = attrList; 
	pAttr != (PrAttrPtr)NULL; 
	pAttr = pNext)
    {
	pNext = pAttr->next;
	if(pAttr->printerAttrs != (XrmDatabase)NULL)
	    XrmDestroyDatabase(pAttr->printerAttrs);
	if(pAttr->docAttrs != (XrmDatabase)NULL)
	    XrmDestroyDatabase(pAttr->docAttrs);
	if(pAttr->jobAttrs != (XrmDatabase)NULL)
	    XrmDestroyDatabase(pAttr->jobAttrs);
	xfree(pAttr->name);
	xfree(pAttr->qualifier);
	xfree(pAttr);
    }
    attrList = (PrAttrPtr)NULL;
}

/*
 * XpRehashAttributes - frees the per-printer attribute list and
 * calls BuildSystemAttributes to rebuild the overall attribute
 * store.  It is expected that a caller of this will follow it
 * by calling XpBuildAttributeStore for a new list of printers.
 */
int
XpRehashAttributes(void)
{
    if(attrList != (PrAttrPtr)NULL)
        FreeAttrList();
    BuildSystemAttributes();
    return Success;
}

/*
 * XpBuildAttributeStore - creates the attribute database associated
 * with the specified printer.  The first time this is called it
 * calls BuildSystemAttributes to create the system-level databases.
 */
void
XpBuildAttributeStore(
    char *printerName,
    char *qualifierName)
{
    PrAttrPtr pAttr;

    if((pAttr = (PrAttrPtr)xalloc(sizeof(PrAttrs))) == (PrAttrPtr)NULL)
	return;

    if(attrGeneration != serverGeneration)
    {
	if(attrList != (PrAttrPtr)NULL)
	    FreeAttrList();
        attrCtxtPrivIndex = XpAllocateContextPrivateIndex();
        XpAllocateContextPrivate(attrCtxtPrivIndex, sizeof(ContextAttrs));
	BuildSystemAttributes();

	attrGeneration = serverGeneration;
    }

    if(attrList == (PrAttrPtr)NULL)
    {
	pAttr->next = (PrAttrPtr)NULL;
	attrList = pAttr;
    }
    else
    {
	pAttr->next = attrList;
	attrList = pAttr;
    }

    pAttr->name = strdup(printerName);
    pAttr->qualifier = strdup(qualifierName);
    pAttr->printerAttrs = BuildPrinterAttrs(printerName, qualifierName);
    pAttr->docAttrs = BuildABase(printerName, qualifierName, 
				 systemAttributes.doc);
    pAttr->jobAttrs = BuildABase(printerName, qualifierName,
				 systemAttributes.job);
}


static Bool
StoreEntry(
    XrmDatabase *sourceDB,
    XrmBindingList bindings,
    XrmQuarkList quarks,
    XrmRepresentation *type,
    XrmValue *value,
    XPointer client_data)
{
    XrmDatabase *outDb = (XrmDatabase *)client_data;

    XrmQPutStringResource(outDb, bindings, quarks, (char *)value->addr);
    
    return FALSE;
}

/*
 * XpCopyDb - makes a copy of the specified XrmDatabase and returns
 * the copy.
 */
static XrmDatabase
CopyDb(XrmDatabase inDb)
{
    XrmDatabase outDb = (XrmDatabase)NULL;
    XrmQuark empty = NULLQUARK;

    (void)XrmEnumerateDatabase(inDb, &empty, &empty, XrmEnumAllLevels,
			       StoreEntry, (XPointer) &outDb);
    return outDb;
}

/* 
 * XpInitAttributes - initializes the attribute store for the specified
 * context.  It does this by making copies of the printer, doc, and job
 * attributes databases for the printer associated with the context.
 */
void
XpInitAttributes(XpContextPtr pContext)
{
    ContextAttrPtr pCtxtAttrs;
    PrAttrPtr pPrAttr = attrList;

    /* Initialize all the pointers to NULL */
    pCtxtAttrs = (ContextAttrPtr)pContext->devPrivates[attrCtxtPrivIndex].ptr;
    (void)memset((void *)pCtxtAttrs, 0, (size_t) sizeof(ContextAttrs));

    for(pPrAttr = attrList; pPrAttr != (PrAttrPtr)NULL; pPrAttr = pPrAttr->next)
	if(!strcmp(pPrAttr->name, pContext->printerName)) break;

    if(pPrAttr != (PrAttrPtr)NULL)
    {
	pCtxtAttrs->printerAttrs = CopyDb(pPrAttr->printerAttrs);
	pCtxtAttrs->docAttrs = CopyDb(pPrAttr->docAttrs);
	pCtxtAttrs->jobAttrs = CopyDb(pPrAttr->jobAttrs);
    }
}

void
XpDestroyAttributes(
    XpContextPtr pContext)
{
    ContextAttrPtr pCtxtAttrs;

    pCtxtAttrs = (ContextAttrPtr)pContext->devPrivates[attrCtxtPrivIndex].ptr;

    if(pCtxtAttrs->printerAttrs != (XrmDatabase)NULL)
	XrmDestroyDatabase(pCtxtAttrs->printerAttrs);
    if(pCtxtAttrs->docAttrs != (XrmDatabase)NULL)
	XrmDestroyDatabase(pCtxtAttrs->docAttrs);
    if(pCtxtAttrs->jobAttrs != (XrmDatabase)NULL)
	XrmDestroyDatabase(pCtxtAttrs->jobAttrs);
    if(pCtxtAttrs->pageAttrs != (XrmDatabase)NULL)
	XrmDestroyDatabase(pCtxtAttrs->pageAttrs);
}

/*
 * XpGetOneAttribute returns the string value of the specified attribute
 * in the specified class for the specified print context.  If the attribute
 * doesn't exist in the database for this context, or if the class database
 * doesn't exist for this context, then NULL is returned.  The caller must
 * not free the returned string, as the returned pointer points into the
 * database.  This function can also return a value from the server attributes,
 * in which case the pContext parameter is ignored.
 */
char *
XpGetOneAttribute(
     XpContextPtr pContext,
     XPAttributes class,
     char *attributeName)
{
    ContextAttrPtr pCtxtAttrs;
    XrmDatabase db = (XrmDatabase)NULL;
    XrmName xrm_name[3];
    XrmRepresentation rep_type;
    XrmValue value;

    if(class == XPServerAttr)
    {
        if(systemAttributes.server == (XrmDatabase)NULL) 
	    return NULL_STRING;

        xrm_name[0] = XrmStringToQuark (attributeName);
        xrm_name[1] = (XrmQuark)NULL;
        XrmQGetResource(systemAttributes.server, xrm_name, xrm_name, 
			&rep_type, &value);

        if(value.addr == (char *)NULL) 
	    return NULL_STRING;
        return (char *)value.addr;
    }
    else
    {
        pCtxtAttrs=(ContextAttrPtr)pContext->devPrivates[attrCtxtPrivIndex].ptr;
        switch(class)
        {
	    case XPPrinterAttr:
	        db = pCtxtAttrs->printerAttrs;
	        break;
	    case XPDocAttr:
	        db = pCtxtAttrs->docAttrs;
	        break;
	    case XPJobAttr:
	        db = pCtxtAttrs->jobAttrs;
	        break;
	    case XPPageAttr:
	        db = pCtxtAttrs->pageAttrs;
	        break;
	    default:
	        break;
        }
    }
    if(db == (XrmDatabase)NULL) 
	return NULL_STRING;

    xrm_name[0] = XrmStringToQuark ("qualifier");
    xrm_name[1] = (XrmQuark)NULL;
    XrmQGetResource(db, xrm_name, xrm_name, &rep_type, &value);

    xrm_name[0] = XrmStringToQuark (value.addr);
    xrm_name[1] = XrmStringToQuark (attributeName);
    xrm_name[2] = (XrmQuark)NULL;
    if(XrmQGetResource(db, xrm_name, xrm_name, &rep_type, &value))
	return (char *)value.addr;
    else
        return NULL_STRING;
}

/*
 * XpPutOneAttribute updates one attribute for the specified
 * context and class. This function is intended for use by the attribute
 * validation module which updates the XrmDatabases directly. This
 * function does not recognize XPServerAttr.
 */
void
XpPutOneAttribute(
	XpContextPtr pContext,
	XPAttributes class,
	const char* attributeName,
	const char* value)
{
    ContextAttrPtr pCtxtAttrs;
    XrmDatabase db;
    XrmBinding bindings[1];
    XrmQuark quarks[2];
    
    pCtxtAttrs = (ContextAttrPtr)pContext->devPrivates[attrCtxtPrivIndex].ptr;
    switch(class)
    {
    case XPPrinterAttr:
	db = pCtxtAttrs->printerAttrs;
	break;
    case XPDocAttr:
	db = pCtxtAttrs->docAttrs;
	break;
    case XPJobAttr:
	db = pCtxtAttrs->jobAttrs;
	break;
    case XPPageAttr:
	db =  pCtxtAttrs->pageAttrs;
	break;
    default:
	return;
    }
    bindings[0] = XrmBindLoosely;
    quarks[0] = XrmStringToQuark(attributeName);
    quarks[1] = (XrmQuark)NULL;
    XrmQPutStringResource(&db, bindings, quarks, value ? value : "");
}

    

/*******************************************************************************
 *
 * The following routines: ExpandSpace, PutString, PutByte, and AppendEntry
 * form the functional core of the GetAttributes routine.  Xrm does not
 * supply a routine to form a string database from an XrmDatabase, except
 * by writing the database to a file.  This code avoids the file system
 * overhead, but is a bit clunky in its memory management.
 *
 ******************************************************************************/

/*
 * ExpandSpace expands the memory allocated for the string database in
 * the StringDbStruct passed in, and updates the "space" field of the
 * struct to indicate the new amount of space available.
 */
static Bool
ExpandSpace(
    StringDbStruct *pStr)
{
    char *newSpace;

    if((newSpace = (char *)xrealloc(pStr->stringDb, pStr->nextPos + pStr->space
				    + 1024)) == (char *)NULL)
	return False;
    pStr->space += 1024;
    pStr->stringDb = newSpace;
    return True;
}

/*
 * PutString puts the contents of a null-terminated string into the string
 * database in the StringDbStruct passed in.  If there is insufficient room
 * for the string, ExpandSpace is called, and the nextPos and space fields
 * are updated.
 */
static void
PutString(
    StringDbStruct *pStr,
    char *pString)
{
    int len = strlen(pString);

    if(len >= pStr->space)
	if(!ExpandSpace(pStr))
	    return;
    strcpy(&pStr->stringDb[pStr->nextPos], pString);
    pStr->nextPos += len;
    pStr->space -= len;
}

/*
 * PutByte puts a single byte value in to the string database in the passed-in
 * StringDbStruct.  ExpandSpace is called if there is insufficient room for
 * the byte, and the nextPos and space fields are updated.
 */
static void
PutByte(
    StringDbStruct *pStr,
    char byte)
{
    if(pStr->space <= 1)
	if(!ExpandSpace(pStr))
	    return;
    pStr->stringDb[pStr->nextPos] = byte;
    pStr->nextPos++;
    pStr->space--;
}

/*
 * AppendEntry is called by XrmEnumerateDatabase, and serves to append
 * a database entry onto a string database.  The passed-in "closure"
 * struct contains a pointer to the string, and a count of the remaining
 * bytes.  If there are insufficient remaining bytes then the struct
 * is realloced, and the count of the space remaining is updated.
 * Database elements of types other than String are ignored!
 * This code is based directly on that in "DumpEntry" in Xrm.c.
 */
static Bool
AppendEntry(
    XrmDatabase         *db,
    XrmBindingList      bindings,
    XrmQuarkList        quarks,
    XrmRepresentation   *type,
    XrmValuePtr         value,
    XPointer            data)
{
    StringDbStruct *pEnumStr = (StringDbStruct *)data;
    Bool        firstNameSeen;
    unsigned int i;
    char *s, c;

    if (*type != XrmQString)
	return False;

    for (firstNameSeen = False; *quarks; bindings++, quarks++) {
        if (*bindings == XrmBindLoosely) {
	    PutString(pEnumStr, "*");
        } else if (firstNameSeen) {
	    PutString(pEnumStr, ".");
        }
        firstNameSeen = True;
	PutString(pEnumStr, XrmQuarkToString(*quarks));
    }
    s = value->addr;
    i = value->size;
    PutString(pEnumStr, ":\t");
    if(i) i--;

    if (i && (*s == ' ' || *s == '\t'))
        PutByte(pEnumStr, '\\'); /* preserve leading whitespace */

    while (i--) {
        c = *s++;
        if (c == '\n') {
            if (i)
                PutString(pEnumStr, "\\n\\\n");
            else
                PutString(pEnumStr, "\\n");
        } else if (c == '\\')
            PutString(pEnumStr, "\\\\");
        else if ((c < ' ' && c != '\t') ||
                 ((unsigned char)c >= 0x7f && (unsigned char)c < 0xa0))
	{
	    char temp[4];
            (void) sprintf(temp, "\\%03o", (unsigned char)c);
	    PutString(pEnumStr, temp);
	}
        else
            PutByte(pEnumStr, c);
    }
    PutByte(pEnumStr, '\n');
    pEnumStr->stringDb[pEnumStr->nextPos] = (char)'\0';
    return False;
}

/*
 * XpGetAttributes returns a string database version of the Xrm database
 * for the specified context and class.  This function can also return the
 * contents of the server attributes, in which case the pContext parameter
 * is ignored. 
 *
 * The caller is responsible for freeing the returned string, 
 * unlike XpGetOneAttribute, where the caller must not free the string.
 */
char *
XpGetAttributes(
     XpContextPtr pContext,
     XPAttributes class)
{
    ContextAttrPtr pCtxtAttrs;
    XrmDatabase db = (XrmDatabase)NULL;
    StringDbStruct enumStruct;
    XrmQuark empty = NULLQUARK;

    if(class == XPServerAttr)
	db = systemAttributes.server;
    else
    {
        pCtxtAttrs=(ContextAttrPtr)pContext->devPrivates[attrCtxtPrivIndex].ptr;
        switch(class)
        {
	    case XPServerAttr:
	        db = systemAttributes.server;
	        break;
	    case XPPrinterAttr:
	        db = pCtxtAttrs->printerAttrs;
	        break;
	    case XPDocAttr:
	        db = pCtxtAttrs->docAttrs;
	        break;
	    case XPJobAttr:
	        db = pCtxtAttrs->jobAttrs;
	        break;
	    case XPPageAttr:
	        db = pCtxtAttrs->pageAttrs;
	        break;
	    default:
	        break;
        }
    }
    if(db == (XrmDatabase)NULL) 
    {
	char *retval = (char *)xalloc(1);
	retval[0] = (char)'\0';
	return retval;
    }

    if((enumStruct.stringDb = (char *)xalloc(1024)) == (char *)NULL)
	return (char *)NULL;
    enumStruct.stringDb[0] = (char)'\0';
    enumStruct.nextPos = 0;
    enumStruct.space = 1024;
    (void)XrmEnumerateDatabase(db, &empty, &empty, XrmEnumAllLevels,
			       AppendEntry, (XPointer) &enumStruct);

    return enumStruct.stringDb;
}

int
XpAugmentAttributes(
     XpContextPtr pContext,
     XPAttributes class,
     char *attributes)
{
    XrmDatabase db;
    ContextAttrPtr pCtxtAttrs;

    db = XrmGetStringDatabase(attributes);
    if(db == (XrmDatabase)NULL) return BadAlloc;

    pCtxtAttrs = (ContextAttrPtr)pContext->devPrivates[attrCtxtPrivIndex].ptr;
    switch(class)
    {
	case XPPrinterAttr:
	    XrmMergeDatabases(db, &pCtxtAttrs->printerAttrs);
	    break;
	case XPDocAttr:
	    XrmMergeDatabases(db, &pCtxtAttrs->docAttrs);
	    break;
	case XPJobAttr:
	    XrmMergeDatabases(db, &pCtxtAttrs->jobAttrs);
	    break;
	case XPPageAttr:
	    XrmMergeDatabases(db, &pCtxtAttrs->pageAttrs);
	    break;
	default:
	    break;
    }
    return Success;
}

/*
 * XpSetAttributes - sets the attribute stores for a specified context.
 */
int
XpSetAttributes(
     XpContextPtr pContext,
     XPAttributes class,
     char *attributes)
{
    XrmDatabase db;
    ContextAttrPtr pCtxtAttrs;

    db = XrmGetStringDatabase(attributes);
    if(db == (XrmDatabase)NULL) return BadAlloc;

    pCtxtAttrs=(ContextAttrPtr)pContext->devPrivates[attrCtxtPrivIndex].ptr;
    switch(class)
    {
	case XPPrinterAttr:
	    if(pCtxtAttrs->printerAttrs != (XrmDatabase)NULL)
	        XrmDestroyDatabase(pCtxtAttrs->printerAttrs);
	    pCtxtAttrs->printerAttrs = db;
	    break;
	case XPDocAttr:
	    if(pCtxtAttrs->docAttrs != (XrmDatabase)NULL)
	        XrmDestroyDatabase(pCtxtAttrs->docAttrs);
	    pCtxtAttrs->docAttrs = db;
	    break;
	case XPJobAttr:
	    if(pCtxtAttrs->jobAttrs != (XrmDatabase)NULL)
	        XrmDestroyDatabase(pCtxtAttrs->jobAttrs);
	    pCtxtAttrs->jobAttrs = db;
	    break;
	case XPPageAttr:
	    if(pCtxtAttrs->pageAttrs != (XrmDatabase)NULL)
	        XrmDestroyDatabase(pCtxtAttrs->pageAttrs);
	    pCtxtAttrs->pageAttrs = db;
	    break;
	default:
	    break;
    }
    return Success;
}

void
XpAddPrinterAttribute(
    char *printerName,
    char *printerQualifier,
    char *attributeName,
    char *attributeValue)
{
    PrAttrPtr pAttr;

    for(pAttr = attrList; pAttr != (PrAttrPtr)NULL; pAttr = pAttr->next)
    {
	if(!strcmp(printerQualifier, pAttr->qualifier))
	{
            XrmPutStringResource(&pAttr->printerAttrs, attributeName, 
				 attributeValue);
	    break;
	}
    }
}

const char *
XpGetPrinterAttribute(const char *printerName,
		      const char *attribute)
{
    PrAttrPtr pAttr;
    XrmValue value;
    char *type;

    for(pAttr = attrList; pAttr != (PrAttrPtr)NULL; pAttr = pAttr->next)
    {
        if(!strcmp(printerName, pAttr->qualifier))
        {
	    char *attrStr;

	    attrStr = (char *)xalloc(strlen(printerName) + strlen(attribute) +
				     2);
	    sprintf(attrStr, "%s.%s", printerName, attribute);
            XrmGetResource(pAttr->printerAttrs, attrStr, attrStr,
                           &type, &value);
	    xfree(attrStr);
            break;
        }
    }
    if(value.addr != (XPointer)NULL && strlen(value.addr) != 0)
	return value.addr;
    else
      return "";
}

/*******************************************************************************
 *
 * The following routines are not attribute routines, but are rather
 * spooler interface functions.  They should presumably move to 
 * a SpoolerIf.c of some similarly named file.
 *
 ******************************************************************************/
#include <locale.h>

static char serverAttrStr[] = "*document-attributes-supported:	copy-count\n\
*job-attributes-supported:	job-name job-owner\
 notification-profile xp-spooler-command-options\n\
*multiple-documents-supported:	False";

XrmDatabase
XpSpoolerGetServerAttributes(void)
{
    char *totalAttrs, *localeName;
    XrmDatabase db;

    localeName = setlocale(LC_CTYPE, (char *)NULL);
    if(!localeName || strlen(localeName) == 0)
	localeName = "C";

    if((totalAttrs = (char *)xalloc(strlen(serverAttrStr) + strlen(localeName)
				    + 11)) == (char *)NULL)
	return (XrmDatabase)NULL;
    sprintf(totalAttrs, "%s\n%s\t%s", serverAttrStr, "*locale:", localeName);

    db =  XrmGetStringDatabase(totalAttrs);
    xfree(totalAttrs);
    return db;
}

/*
 * SendFileToCommand takes three character pointers - the file name,
 * the command to execute,
 * and the "argv" style NULL-terminated vector of arguments for the command.
 * The command is exec'd, and the file contents are sent to the command
 * via stdin.
 *
 * WARNING:  This function will try to adopt the userId of the supplied
 *           user name prior to exec'ing the supplied command.
 */
static void
SendFileToCommand(
    char *fileName,
    char *pCommand,
    char **argVector,
    char *userName)
{
    pid_t childPid;
    int pipefd[2];
    int status;
    struct stat statBuf;
    FILE *fp, *outPipe;

    if(pipe(pipefd))
	return;

    if(stat(fileName, &statBuf) < 0 || (int)statBuf.st_size == 0)
    {
	    close(pipefd[0]);
	    close(pipefd[1]);
	    return;
    }

    fp = fopen(fileName, "r");
    if(fp == (FILE *)NULL)
    {
        close(pipefd[0]);
        close(pipefd[1]);
        return;
    }
    
    if((childPid = fork()) == 0)
    {
        close(pipefd[1]);      
	close(0);
	dup(pipefd[0]);
	close(pipefd[0]);

	/*
	 * If a user name is specified, try to set our uid to match that
	 * user name.  This is to allow e.g. a banner page to show the
	 * name of the printing user rather than the user who started
	 * the print server.
	 */
	if(userName)
	{
	    if(geteuid() == (uid_t)0)
	    {
	        struct passwd *pPasswd;

	        if((pPasswd = getpwnam(userName)))
	        {
		    setuid((uid_t)pPasswd->pw_uid);
	        }
	    }
	}
	/* return BadAlloc? */
	if (execv(pCommand, argVector) == -1) {
	    FatalError("unable to exec '%s'", pCommand);
	}
    }
    else
    {
	(void) close(pipefd[0]);

 	outPipe = fdopen(pipefd[1], "w");
	(void) TransferBytes(fp, outPipe, (int)statBuf.st_size);
	
	(void) fclose(outPipe);
	(void) fclose(fp);

        (void) waitpid(childPid, &status, 0);
    }
    return;
}

/*
 * ReplaceAllKeywords causes all the predefined keywords (e.g. %options%)
 * to be replaced with the appropriate values derived from the attribute
 * store for the supplied print context.  The ReplaceAnyString utility
 * routine is used to perform the actual replacements.
 */

static char *
ReplaceAllKeywords(
    XpContextPtr pContext,
    char *command)
{
    char *cmdOpt;

    cmdOpt = XpGetOneAttribute(pContext, XPPrinterAttr, 
			       "xp-spooler-printer-name");
    if(cmdOpt != (char *)NULL && strlen(cmdOpt) != 0)
        command = ReplaceAnyString(command, "%printer-name%", cmdOpt);
    else
        command = ReplaceAnyString(command, "%printer-name%", 
			           pContext->printerName);

    cmdOpt = XpGetOneAttribute(pContext, XPDocAttr, "copy-count");
    if(cmdOpt != (char *)NULL && strlen(cmdOpt) != 0)
        command = ReplaceAnyString(command, "%copy-count%", cmdOpt);
    else
        command = ReplaceAnyString(command, "%copy-count%", "1");

    cmdOpt = XpGetOneAttribute(pContext, XPJobAttr, "job-name");
    if(cmdOpt != (char *)NULL && strlen(cmdOpt) != 0)
        command = ReplaceAnyString(command, "%job-name%", cmdOpt);
    else
        command = ReplaceAnyString(command, "%job-name%", "");

    cmdOpt = XpGetOneAttribute(pContext, XPJobAttr, "job-owner");
    if(cmdOpt != (char *)NULL && strlen(cmdOpt) != 0)
        command = ReplaceAnyString(command, "%job-owner%", cmdOpt);
    else
        command = ReplaceAnyString(command, "%job-owner%", "");

    cmdOpt = XpGetOneAttribute(pContext, XPJobAttr, 
			       "xp-spooler-command-options");
    if(cmdOpt != (char *)NULL && strlen(cmdOpt) != 0)
        command = ReplaceAnyString(command, "%options%", cmdOpt);
    else
        command = ReplaceAnyString(command, "%options%", "");

    return command;
}

#ifdef __QNX__
#define toascii( c ) ((unsigned)(c) & 0x007f)
#endif

#if defined(CSRG_BASED) || \
    defined(linux) || \
    defined(__CYGWIN__) || \
    (defined(sun) && !defined(SVR4)) || \
    (defined(SVR4) && !defined(sun) && !defined(USL)) || \
    defined(__UNIXOS2__) || \
    defined(ISC) || \
    defined(Lynx) || \
    defined(__QNX__) || \
    defined(__DARWIN__)
#define iswspace(c) (isascii(c) && isspace(toascii(c)))
#endif

/*
 * GetToken - takes in a string and returns a malloc'd copy of the
 * first non-white-space sequence of characters in the string.
 * It returns the number of _bytes_ (NOT characters) parsed through 
 * the inStr to get to the end of the returned token.
 */
static int
GetToken(
    char *inStr,
    char **outStr)
{
    size_t mbCurMax = MB_CUR_MAX;
    wchar_t curChar;
    int i, numBytes, byteLen = strlen(inStr);
    char *tok;

    /*
     * read through any leading white space.
     */
    for(i = 0, numBytes = 0; i < byteLen; i += numBytes)
    {
        numBytes = mbtowc(&curChar, &inStr[i], mbCurMax);
        if(!iswspace(curChar))
	    break;
    }
    tok = inStr + i;

    /*
     * find the end of the token.
     */
    byteLen = strlen(tok);
    for(i = 0, numBytes = 0; i < byteLen; i += numBytes)
    {
        numBytes = mbtowc(&curChar, &tok[i], mbCurMax);
        if(iswspace(curChar))
	    break;
    }

    if((*outStr = (char *)xalloc(i + 1)) == (char *)NULL)
	return 0;
    strncpy(*outStr, tok, i);
    (*outStr)[i] = (char)'\0';
    return (tok + i) - inStr;
}

static void
FreeVector(
    char **vector)
{
    int i;

    if(vector == (char **)NULL) return;

    for(i = 0; vector[i] != (char *)NULL; i++)
	xfree(vector[i]);
    xfree(vector);
}


/*
 * AddVector appends the pAddition arg vector to the pTarget arg vector.
 * If the pTarget cannot be realloc'd, then pTarget is set to NULL.
 */
static void
AddVector(
    char ***pTarget,
    char **pAddition)
{
    int numTarget, numAdd, i;

    for(numTarget = 0; (*pTarget)[numTarget] != (char *)NULL; numTarget++)
	;
    for(numAdd = 0; pAddition[numAdd] != (char *)NULL; numAdd++)
	;

    *pTarget = (char **)xrealloc((void *)*pTarget, (numTarget + numAdd + 1) * 
	       sizeof(char *));
    if(*pTarget == (char **)NULL)
	return;
    for(i = 0; i < numAdd; i++)
	(*pTarget)[numTarget + i] = pAddition[i];

    (*pTarget)[numTarget + numAdd] = (char *)NULL;
}

static char **
BuildArgVector(
    char *argString,
    XpContextPtr pContext)
{
    char **pVector;
    char *curTok;
    int numChars, i;
    static int beenHere = 0; /* prevent recursion on embedded %options%
			     */

    pVector = (char **)xalloc(sizeof(char *));
    pVector[0] = (char *)NULL;
    for(i = 0; (numChars = GetToken(argString, &curTok)) != 0; 
	i++, argString += numChars)
    {
	if(beenHere || strcmp(curTok, "%options%"))
	{
	    if(curTok[0] == (char)'\0')
	    {
		xfree(curTok);
	    }
	    else
	    {
	        pVector = (char **)xrealloc((void *)pVector,
					    (i + 2)*sizeof(char *));
	        if(pVector == (char **)NULL)
	            return (char **)NULL;
	        pVector[i] = curTok;
	        pVector[i + 1] = (char *)NULL;
	    }
	}
	else if(!beenHere)
	{
	    char **optionsVec;

	    curTok = ReplaceAllKeywords(pContext, curTok);
	    beenHere = 1;
	    optionsVec = BuildArgVector(curTok, pContext);
	    xfree(curTok);
	    beenHere = 0;
	    AddVector(&pVector, optionsVec);
	    xfree(optionsVec);
	}
    }
    if(numChars == 0 && curTok != (char *)NULL)
	xfree(curTok);
    return pVector;
}

/*
 * VectorizeCommand takes a string and breaks it into a command name and
 * an array of character pointers suitable for handing to execv.  The
 * array is NULL-terminated.
 * The returned char * is the command name, and should be freed when no
 * longer needed.  The array elements returned in the pVector parameter 
 * should be individually freed, and the array itself should also be
 * freed when no longer needed.
 */
static char *
VectorizeCommand(
    char *command,
    char ***pVector,
    XpContextPtr pContext)
{
    char *cmdName;

    if(command == (char *)NULL)
	return (char *)NULL;
    
    (void) GetToken(command, &cmdName);

    if(cmdName == (char *)NULL)
	return (char *)NULL;

    *pVector = BuildArgVector(command, pContext);

    return cmdName;
}

#ifdef hpux
static char DEFAULT_SPOOL_COMMAND[] = "/usr/bin/lp -d %printer-name% -o raw -n %copy-count% -t %job-name% %options%";
#else
static char DEFAULT_SPOOL_COMMAND[] = "/usr/bin/lp -d %printer-name% -n %copy-count% -t %job-name% %options%";
#endif

int
XpSubmitJob(
     char *fileName,
     XpContextPtr pContext)
{
    char **vector, *cmdNam, *command, *userName;
    int i;

    command = XpGetOneAttribute(pContext, XPPrinterAttr, "xp-spooler-command");
    if(command == (char *)NULL || strlen(command) == 0)
	command = strdup(DEFAULT_SPOOL_COMMAND);
    else
	command = strdup(command);
    if(command == (char *)NULL)
	return BadAlloc;

    cmdNam = VectorizeCommand(command, &vector, pContext);
    xfree(command);

    if(cmdNam == (char *)NULL)
	return BadAlloc;

    for(i = 0; vector[i] != (char *)NULL; i++)
    {
        vector[i] = ReplaceAllKeywords(pContext, vector[i]);
	if(vector[i] == (char *)NULL)
	{
	    xfree(cmdNam);
	    for(i = 0; vector[i] != (char *)NULL; i++)
		xfree(vector[i]);
	    xfree(vector);
	    return BadAlloc;
	}
    }

    userName = XpGetOneAttribute(pContext, XPJobAttr, "job-owner");
    if(userName != (char *)NULL && strlen(userName) == 0)
	userName = (char *)NULL;

    SendFileToCommand(fileName, cmdNam, vector, userName);

    FreeVector(vector);
    xfree(cmdNam);
    return Success;
}

/*
 * SearchInputTrays()
 *
 * Given a tray, return the medium in the tray.  Conversely, given a
 * medium, return a tray in which it can be found.  In either case,
 * return NULL if the given tray or medium cannot be found.
 */
#define TRAY 0
#define MEDIUM 1

static char *
SearchInputTrays(XpContextPtr pCon,
		 int which,
		 char *val)
{
    char *inputTraysMedium, tray[80], medium[80], *copy;
    char *pS, *pE, *pLast;
    
    inputTraysMedium = XpGetOneAttribute( pCon, XPPrinterAttr,
					 "input-trays-medium" );
    
    copy = strdup( inputTraysMedium );
    pS = copy;
    pLast = copy + strlen( copy );
    
    while( pS < pLast )
      {
	  while( *pS && *pS != '{' )
	    pS++;
	  
	  pE = ++pS;
	  while( *pE && *pE != '}' )
	    pE++;
	  *pE = '\0';

	  sscanf( pS, "%s %s", tray, medium );

	  if( which == MEDIUM && !strcmp( val, medium ) )
	    {
		xfree( copy );
		return strdup( tray );
	    }

	  if( which == TRAY && !strcmp( val, tray ) )
	    {
		xfree( copy );
		return strdup( medium );
	    }
	  
	  pS = pE + 1;
      }

    xfree( copy );
    return strdup( NULL_STRING );
}

/*
 * XpGetTrayMediumFromContext()
 *
 * Given a print context, hit the input-trays-medium,
 * default-input-tray and default-medium attributes to find the
 * appropriate tray to use, and the medium in that tray.
 */
void
XpGetTrayMediumFromContext(XpContextPtr pCon,
			   char **medium,
			   char **tray)
{
    char *defMedium, *defTray;
    char *t, *m;
    
    defMedium = XpGetOneAttribute( pCon, XPPageAttr, 
				  "default-medium" );
    if( *defMedium == '\0' )
      defMedium = XpGetOneAttribute( pCon, XPDocAttr,
				    "default-medium" );

    defTray = XpGetOneAttribute( pCon, XPPageAttr,
				"default-input-tray" );
    if( *defTray == '\0' )
      defTray = XpGetOneAttribute( pCon, XPDocAttr,
				  "default-input-tray" );

    /*
     * First, check to see if the default tray has the default medium
     * installed.  This is the ideal case.
     */
    m = SearchInputTrays( pCon, TRAY, defTray );
    if( !strcmp( m, defMedium ) )
      {
	  xfree( m );
	  *tray = strdup( defTray );
	  *medium = strdup( defMedium );
	  return;
      }

    /*
     * If the default tray doesn't have the default medium, search for
     * a tray which has the default medium.
     */
    t = SearchInputTrays( pCon, MEDIUM, defMedium );
    if( t )
      {
	  *tray = t;
	  *medium = strdup( defMedium );
	  return;
      }
    
    /*
     * If all else fails, just return the default tray, and whatever
     * medium happens to be there.  Note that we simply return
     * whatever is in the attribute store.  Any further correction is
     * left up to the DDX driver.
     */
    *tray = strdup( defTray );
    *medium = m;
    xfree( t );
}
