/*
 * Copyright (c) 1995, 1996, 1997, 1998 Kungliga Tekniska Högskolan
 * (Royal Institute of Technology, Stockholm, Sweden).
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by the Kungliga Tekniska
 *      Högskolan and its contributors.
 * 
 * 4. Neither the name of the Institute nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "milko_locl.h"
#include "vldb.h"

RCSID("$Id: vldbserver.c,v 1.6 1998/11/07 00:04:16 map Exp $");

#define N_SECURITY_OBJECTS 3

static struct rx_service *vldbservice;
static struct rx_securityClass *(securityObjects[N_SECURITY_OBJECTS]);

static char *srvtab_filename = "/etc/srvtab";
static int use_kerberos = 1;

#define IPADDRESS "127.0.0.1"

int vl_database;
vlheader vl_header;
off_t file_length;

char vl_header_ydr[VLHEADER_SIZE];

void
write_header()
{
    off_t pos;
    int length = VLHEADER_SIZE;

    if (ydr_encode_vlheader(&vl_header, vl_header_ydr, &length) == NULL)
        err(1, "write_header");

    pos = lseek(vl_database, 0, SEEK_SET);
    assert(pos == 0);

    length = write(vl_database, vl_header_ydr, VLHEADER_SIZE);
    assert (length == VLHEADER_SIZE);
}

void
read_header()
{
    char vl_header_ydr[VLHEADER_SIZE];
    int length = VLHEADER_SIZE;

    if (lseek(vl_database, 0, SEEK_SET) == -1)
        err(1, "lseek");

    length = read(vl_database, vl_header_ydr, VLHEADER_SIZE);
    if (length == -1)
        err(1, "read");
    if (length != VLHEADER_SIZE)
        errx(1, "read_header read failed");

    if (ydr_decode_vlheader(&vl_header, vl_header_ydr, &length) == NULL)
        err(1, "read_header");
}

void
get_file_length()
{
    file_length = lseek(vl_database, 0, SEEK_END);
    if (file_length == -1) {
        err(1, "lseek");
    }
}

off_t
find_first_free()
{
    off_t pos;

    if (vl_header.vital_header.freePtr == 0) { /* if there are no free entries */
	pos = lseek(vl_database, 0, SEEK_END);
	if (pos == -1)
	    err(1, "lseek");
	if (ftruncate(vl_database, pos + DISK_VLENTRY_SIZE) == -1)
	    err(1, "ftruncate");
	return pos;
    } else { /* there are free entries */
	/* Not implemented yet */
	assert(0);
    }
    return 0;
}

int
write_entry(off_t offset, disk_vlentry *vl_entry)
{
    off_t pos;
    char vl_entry_ydr[DISK_VLENTRY_SIZE];
    int length = DISK_VLENTRY_SIZE;

    if (ydr_encode_disk_vlentry((disk_vlentry *) vl_entry, vl_entry_ydr, &length) == NULL)
	err(1, "write_entry");

    pos = lseek(vl_database, offset, SEEK_SET);
    assert(pos == offset);

    length = write(vl_database, vl_entry_ydr, DISK_VLENTRY_SIZE);
    assert (length == DISK_VLENTRY_SIZE);

    return 0;
}

int
read_entry(off_t offset, disk_vlentry *vl_entry)
{
    off_t pos;
    char vl_entry_ydr[DISK_VLENTRY_SIZE];
    int length = DISK_VLENTRY_SIZE;

    pos = lseek(vl_database, offset, SEEK_SET);
    assert(pos == offset);

    length = read(vl_database, vl_entry_ydr, DISK_VLENTRY_SIZE);
    assert (length == DISK_VLENTRY_SIZE);

    if (ydr_decode_disk_vlentry((disk_vlentry *) vl_entry, vl_entry_ydr, &length) == NULL)
	err(1, "write_entry");

    return 0;
}

void
create_database()
{
    int i;

    vl_header.vital_header.vldbversion = 0;
    vl_header.vital_header.headersize = VLHEADER_SIZE;
    vl_header.vital_header.freePtr = 0;
    vl_header.vital_header.eofPtr = VLHEADER_SIZE;
    vl_header.vital_header.allocs = 0;
    vl_header.vital_header.frees = 0;
    vl_header.vital_header.MaxVolumeId = 0xA0000000 - 1;
    for (i = 0; i < MAXTYPES; i++)
	vl_header.vital_header.totalEntries[i] = 0;

    for (i = 0; i < MAXSERVERID+1; i++)
	vl_header.IpMappedAddr[i] = 0;

    memset(vl_header.VolnameHash, 0, HASHSIZE * sizeof(int32_t));
    memset(vl_header.VolidHash, 0, HASHSIZE * MAXTYPES * sizeof(int32_t));
    write_header();
    get_file_length();
}

unsigned long
get_id_hash(long id)
{
    return ((unsigned long) id) % HASHSIZE;
}

unsigned long
get_name_hash(const char *name)
{
    int i;
    unsigned long hash = 0x47114711;

    for (i = 0; name[i] && i < 32; i++)
	hash *= name[i];

    return hash % HASHSIZE;
}

int
get_first_id_entry(unsigned long hash_id, long type, disk_vlentry *vl_entry)
{
    off_t offset = vl_header.VolidHash[type][hash_id];
    int status;

    fprintf(stderr, "  get_first_id_entry hash_id: %lu type: %ld offset: %ld\n", hash_id, type, offset);
    if (offset == 0)
	return VL_NOENT;

    status = read_entry(offset, vl_entry);

    return status;
}

int
get_first_name_entry(unsigned long hash_name, disk_vlentry *vl_entry)
{
    off_t offset = vl_header.VolnameHash[hash_name];
    int status;

    fprintf(stderr, "  get_first_name_entry hash_name: %lu offset: %ld\n", hash_name, offset);
    if (offset == 0)
	return VL_NOENT;

    status = read_entry(offset, vl_entry);

    return status;
}

int
insert_entry(disk_vlentry *vl_entry)
{
    off_t offset;
    int status;
    unsigned long hash_id, hash_name;
    disk_vlentry first_id_entry;
    disk_vlentry first_name_entry;
    
    /* Allokera plats i filen */
    offset = find_first_free();

    /* Allocate new volume id? */
    /*id = vl_header.vital_header.MaxVolumeId++;*/

    /* Hitta plats i hashtabellerna */
    /* XXX At present, only RW is handled */
    hash_id = get_id_hash(vl_entry->volumeId[RWVOL]);
    hash_name = get_name_hash(vl_entry->name);

    status = get_first_id_entry(hash_id, vl_entry->volumeType, &first_id_entry);

/* XXX    vl_entry->nextIDHash[vldb_entry->type] = status ? 0 : first_id_entry.nextID;*/
    vl_entry->nextIdHash[vl_entry->volumeType] = status ? 0 : vl_header.VolidHash[vl_entry->volumeType][hash_id];

    status = get_first_name_entry(hash_name, &first_name_entry);
/* XXX    pr_entry->nextName = status ? 0 : first_name_entry.nextName;*/
    vl_entry->nextNameHash = status ? 0 : vl_header.VolnameHash[hash_name];

    /* XXX: uppdatera owned och nextOwned */

    /* Lägg in entryt i filen */
    status = write_entry(offset, vl_entry);
    if (status)
	return status;
    
    /* Uppdatera hashtabell */
    vl_header.VolidHash[vl_entry->volumeType][hash_id] = offset;
    vl_header.VolnameHash[hash_name] = offset;
    write_header();
    return 0;
}

void
make_vldb_from_vl(struct vldbentry *vldb_entry, struct disk_vlentry *vl_entry)
{
    int i;

    strncpy(vldb_entry->name, vl_entry->name, VLDB_MAXNAMELEN);
    vldb_entry->volumeType = vl_entry->volumeType;
    vldb_entry->nServers = 1; /* XXX What is this supposed to be? */

    for (i = 0; i < MAXNSERVERS; i++) {
	vldb_entry->serverNumber[i] = vl_entry->serverNumber[i];
	vldb_entry->serverPartition[i] = vl_entry->serverPartition[i];
	vldb_entry->serverFlags[i] = vl_entry->serverFlags[i];
    }

    for (i = 0; i < MAXTYPES; i++)
	vldb_entry->volumeId[i] = vl_entry->volumeId[i];

    vldb_entry->cloneId = vl_entry->cloneId;
    vldb_entry->flags = vl_entry->flags;
}

/*
 * The rpc - calls
 */

int
VL_CreateEntry(struct rx_call *call, 
	       const vldbentry *newentry) 
{
    struct disk_vlentry vl_entry;
    int i;

    printf("VL_CreateEntry (name=%s, type=%d, ids=%d,%d,%d flags=%d)\n",
	   newentry->name, newentry->volumeType,
	   newentry->volumeId[RWVOL],
	   newentry->volumeId[ROVOL],
	   newentry->volumeId[BACKVOL],
	   newentry->flags);

    /* XXX Should see to it that name is null terminated too */
    strncpy(vl_entry.name, newentry->name, VLDB_MAXNAMELEN);
    vl_entry.volumeType = newentry->volumeType;

    /* XXX All fields mustn't be set */
    for (i = 0; i < MAXNSERVERS; i++) {
	vl_entry.serverNumber[i] = newentry->serverNumber[i];
	vl_entry.serverPartition[i] = newentry->serverPartition[i];
	vl_entry.serverFlags[i] = newentry->serverFlags[i];
    }

    for (i = 0; i < MAXTYPES; i++)
	vl_entry.volumeId[i] = newentry->volumeId[i];

    vl_entry.cloneId = newentry->cloneId;
    vl_entry.flags = newentry->flags;

    insert_entry(&vl_entry);
    return 0;
}

int 
VL_DeleteEntry(struct rx_call *call, 
	       const int32_t Volid, 
	       const int32_t voltype)
{
    printf("VL_DeleteEntry\n") ;
    return VL_PERM ;
}

int
VL_GetEntryByID(struct rx_call *call,
		const int32_t Volid, 
		const int32_t voltype, 
		vldbentry *entry) 
{
    struct disk_vlentry vl_entry;

    printf("VL_GetEntryByID (Volid=%d,Voltype=%d)\n", Volid, voltype);
    
    if (get_first_id_entry(get_id_hash(Volid), voltype, &vl_entry) != 0)
	return VL_NOENT;

    while (1) {
	/* Return entry if match found */
	if (vl_entry.volumeId[voltype] == Volid) {
	    make_vldb_from_vl(entry, &vl_entry);
	    return 0;
	}
	
	if (vl_entry.nextIdHash[voltype] == 0)
	    break;
	
	read_entry(vl_entry.nextIdHash[voltype], &vl_entry);
    }
    return VL_NOENT;
}

int
VL_GetEntryByName(struct rx_call *call, 
		  const char *volumename, 
		  vldbentry *entry) 
{
    struct disk_vlentry vl_entry;

    printf("VL_GetEntryByName %s\n", volumename) ;

    if (get_first_name_entry(get_name_hash(volumename), &vl_entry) == 0)
	while (1) {
	    /* Return entry if match found */
	    if (strcmp(vl_entry.name, volumename) == 0) {
		make_vldb_from_vl(entry, &vl_entry);
		return 0;
	    }
	    
	    if (vl_entry.nextNameHash == 0)
		break;
	    
	    read_entry(vl_entry.nextNameHash, &vl_entry);
	}
    else if (strcmp(volumename, "root.afs") == 0) {
	
	/* XXX remove when we have a real vlserver */

	strcpy(entry->name, "root.afs" );
	entry->volumeType = 0; // XXX
	entry->flags = VLF_RWEXISTS;
	entry->volumeId[RWVOL] = 1;

	entry->nServers = 1 ;
	entry->serverNumber[0] = ntohl(inet_addr(IPADDRESS)); // XXX
	entry->serverFlags[0] = VLSF_RWVOL;
	return 0;
    } 

    return VL_NOENT;
}

int 
VL_GetNewVolumeId (struct rx_call *call, 
		   const int32_t bumpcount,
		   int32_t *newvolumid)
{
    printf("VL_GetNewVolumeId(bumpcount=%d)\n", bumpcount) ;
    *newvolumid = vl_header.vital_header.MaxVolumeId;
    printf("   returning low volume id = %d\n", *newvolumid);
    vl_header.vital_header.MaxVolumeId += bumpcount;

    return 0;
}

int
VL_ReplaceEntry (struct rx_call *call, 
		 const int32_t Volid, 
		 const int32_t voltype,
		 const vldbentry *newentry,
		 const int32_t ReleaseType) 
{
    printf("VL_ReplaceEntry\n") ;
    return VL_PERM ;
}

int
VL_UpdateEntry (struct rx_call *call, 
		const int32_t Volid, 
		const int32_t voltype, 
		const VldbUpdateEntry *UpdateEntry,
		const int32_t ReleaseType)
{
    printf("VL_UpdateEntry\n") ;
    return VL_PERM ;
}

int 
VL_SetLock (struct rx_call *call, 
	    const int32_t Volid, 
	    const int32_t voltype,
	    const int32_t voloper) 
{
    printf("VL_SetLock\n") ;
    return 0;
}

int
VL_ReleaseLock (struct rx_call *call, 
		const int32_t volid,
		const int32_t voltype, 
		const int32_t ReleaseType) 
{
    printf("VL_ReleaseLock\n") ;
    return 0;
}

int
VL_ListEntry (struct rx_call *call, 
	      const int32_t previous_index, 
	      int32_t *count,
	      int32_t *next_index, 
	      vldbentry *entry) 
{
    printf("VL_ListEntry\n") ;
    return VL_PERM ;
}

int
VL_ListAttributes (struct rx_call *call, 
		   const VldbListByAttributes *attributes,
		   int32_t *nentries,
		   bulkentries *blkentries) 
{
    printf("VL_ListAttributes\n") ;
    return VL_PERM ;
}

int
VL_GetStats (struct rx_call *call, 
	     vldstats *stats,
	     vital_vlheader *vital_header) 
{
    printf("VL_GetStats") ;
    return VL_PERM ;
}


int 
VL_Probe(struct rx_call *call)
{
    printf("VL_Probe\n") ;
    return 0;
}

int
VL_CreateEntryN(struct rx_call *call,
		const nvldbentry *entry)
{
    int i;
    struct vldbentry vldb_entry;

    strncpy(vldb_entry.name, entry->name, VLDB_MAXNAMELEN);
    vldb_entry.volumeType = RWVOL;
    vldb_entry.nServers = entry->nServers;

    for (i = 0; i < MAXNSERVERS; i++) {
	vldb_entry.serverNumber[i] = entry->serverNumber[i];
	vldb_entry.serverPartition[i] = entry->serverPartition[i];
	vldb_entry.serverFlags[i] = entry->serverFlags[i];
    }

    for (i = 0; i < MAXTYPES; i++)
	vldb_entry.volumeId[i] = entry->volumeId[i];

    vldb_entry.cloneId = entry->cloneId;
    vldb_entry.flags = entry->flags;

    return VL_CreateEntry(call, &vldb_entry);
}


int
VL_GetEntryByIDN(struct rx_call *call,
		 int32_t Volid,
		 int32_t voltype,
		 nvldbentry *entry)
{
    struct vldbentry vldb_entry;
    int status, i;

    printf("VL_GetEntryByIDN (Volid=%d,Voltype=%d)\n", Volid, voltype);

    if (voltype == -1)
	voltype = RWVOL;

    status = VL_GetEntryByID(call, Volid, voltype, &vldb_entry);

    if (status)
	return status;

    strncpy(entry->name, vldb_entry.name, VLDB_MAXNAMELEN);
    entry->nServers = vldb_entry.nServers;
    for (i = 0; i < MAXNSERVERS; i++) {
	entry->serverNumber[i] = vldb_entry.serverNumber[i];
	entry->serverPartition[i] = vldb_entry.serverPartition[i];
	entry->serverFlags[i] = vldb_entry.serverFlags[i];
    }

    for (i = 0; i < MAXTYPES; i++)
	entry->volumeId[i] = vldb_entry.volumeId[i];

    entry->cloneId = vldb_entry.cloneId;
    entry->flags = vldb_entry.flags;

    return 0;
/*
    memset(entry, 0, sizeof(*entry));

    if (Volid == 1)
	strcpy(entry->name, "home");
    else
	strcpy(entry->name, "home.readonly");

    entry->flags = VLF_RWEXISTS | VLF_ROEXISTS;
    entry->volumeId[RWVOL] = 1;
    entry->volumeId[ROVOL] = 2;

    entry->nServers = 2;
    entry->serverNumber[0] = ntohl(inet_addr(IPADDRESS)); // XXX
    entry->serverNumber[1] = ntohl(inet_addr(IPADDRESS)); // XXX
    entry->serverFlags[0] = VLSF_RWVOL | VLSF_ROVOL;
    entry->serverPartition[0] = 0;
    entry->serverPartition[1] = 1;
    entry->cloneId = 0;
    return 0;
*/
}

int
VL_GetEntryByNameN(struct rx_call *call, 
		   const char *volumename, 
		   nvldbentry *entry) 
{
    struct vldbentry vldb_entry;
    int status, i;

    printf("VL_GetEntryByNameN(volumename=%s)\n", volumename) ;
    status = VL_GetEntryByName(call, volumename, &vldb_entry);

    if (status)
	return status;

    strncpy(entry->name, vldb_entry.name, VLDB_MAXNAMELEN);
    entry->nServers = vldb_entry.nServers;
    for (i = 0; i < MAXNSERVERS; i++) {
	entry->serverNumber[i] = vldb_entry.serverNumber[i];
	entry->serverPartition[i] = vldb_entry.serverPartition[i];
	entry->serverFlags[i] = vldb_entry.serverFlags[i];
    }

    for (i = 0; i < MAXTYPES; i++)
	entry->volumeId[i] = vldb_entry.volumeId[i];

    entry->cloneId = vldb_entry.cloneId;
    entry->flags = vldb_entry.flags;

    return 0;
}

int
VL_GetEntryByNameU(struct rx_call *call, 
		   const char *volumename, 
		   uvldbentry *entry) 
{
    printf("VL_GetEntryByNameU %s\n", volumename);
    memset(entry, 0, sizeof(*entry));

    strcpy(entry->name, "root.afs" );
    entry->flags = VLF_RWEXISTS;
    entry->volumeId[RWVOL] = 1;

    entry->nServers = 1;
    entry->serverUnique[0] = 0;
    entry->serverNumber[0].time_low = ntohl(inet_addr(IPADDRESS));
    entry->serverFlags[0] = VLSF_RWVOL;
    entry->serverPartition[0] = 0;
    entry->cloneId = 0;

    return 0 ;
}

int
VL_ListAttributesN (struct rx_call *call, 
		   const VldbListByAttributes *attributes,
		   int32_t *nentries,
		   nbulkentries *blkentries) 
{
    struct nvldbentry  *entry;

    printf("VL_ListAttributesN\n");
    printf("  attributes: Mask=(%d=", attributes->Mask);

    if (attributes->Mask & VLLIST_SERVER)
	printf ("SERVER ");
    if (attributes->Mask & VLLIST_PARTITION)
	printf ("PARTITION ");
    if (attributes->Mask & VLLIST_VOLUMETYPE)
	printf ("VOLUMETYPE ");
    if (attributes->Mask & VLLIST_VOLUMEID)
	printf ("VOLUMEID ");
    if (attributes->Mask & VLLIST_FLAG)
	printf ("FLAG");

    printf(") server=%d partition=%d volumetype=%d volumeid=%d flag=%d\n",
	   attributes->server,
	   attributes->partition,
	   attributes->volumetype,
	   attributes->volumeid,
	   attributes->flag);

    *nentries = 1;
    blkentries->len = 1;
    entry = blkentries->val = malloc(sizeof(*entry));

    memset(entry, 0, sizeof(*entry));

    strcpy(entry->name, "home");
    entry->flags = VLF_RWEXISTS | VLF_ROEXISTS;
    entry->volumeId[RWVOL] = 1;
    entry->volumeId[ROVOL] = 2;
    entry->nServers = 2;
    entry->serverNumber[0] = ntohl(inet_addr(IPADDRESS)); /* XXX */
    entry->serverNumber[1] = ntohl(inet_addr(IPADDRESS)); /* XXX */
    entry->serverFlags[0] = VLSF_RWVOL | VLSF_ROVOL;
    entry->serverPartition[0] = 0;
    entry->serverPartition[1] = 1;
    entry->cloneId = 0;
    return 0;
}

int
VL_ListAttributesU (struct rx_call *call, 
		   const VldbListByAttributes *attributes,
		   int32_t *nentries,
		   ubulkentries *blkentries) 
{
    printf("VL_ListAttributesU\n") ;
    *nentries = 0;
    blkentries->len = 0;
    blkentries->val = malloc(1);
    return 0;
}

int
vldbserver_init()
{
    int status;

    vl_database = open("vl_database",
                       O_RDWR | O_CREAT,
                       S_IRWXU);

    if (vl_database == -1)
        err(1, "open");

    get_file_length();
    if (file_length == 0) {
        fprintf(stderr, "Database not found, creating a new.\n");
	create_database();
	/* XXX <insert code for making new root.afs or whatever here> */

	status = 0;
        if (status)
            return status;
    } else {
        fprintf(stderr, "Loading database.\n");
        read_header();
        get_file_length();
    }

    return 0;
}



/*
 * Network function, this should be broken out
 */


#ifdef KERBEROS
static int
milko_get_key(void *appl, int kvno, des_cblock *key)
{
    /* 
     * XXX this is not really good, since I think read_service_key
     * does not exist in MIT-kerberos
     */
    return read_service_key("afs", "", "STACKEN.KTH.SE", 
			    0, srvtab_filename, (char *)key);
}
#endif

static void
network_init(void)
{
    int maxsec = 1;

    if (rx_Init(0) != 0) 
	errx(1, "Cant open serverport port\n") ;
    
    securityObjects[0] = rxnull_NewServerSecurityObject();   /* XXX 0 */
    if (securityObjects[0] == NULL ) 
	errx(1, "cant create security object") ;

#ifdef KERBEROS
    if (use_kerberos) {
	maxsec = 2;
	securityObjects[2] = rxkad_NewServerSecurityObject(rxkad_auth,
							   NULL,
							   milko_get_key,
							   NULL);
	if (securityObjects[2] == NULL)
	    errx(1, "init_network: can't init rxkad server-security-object");
    }
#endif

    vldbservice = rx_NewService(VLDBSERVERPORT, VLDB_SERVICE_ID, "vl", 
				securityObjects, maxsec, 
				VL_ExecuteRequest);
    
    if (vldbservice == NULL) 
	errx(1, "Cant create VLDB service");

}

int main(void) 
{
    int status;
    
    status = vldbserver_init();
    if (status) {
	printf("vldbserver_init: error %d\n", status);
	exit(1);
    }

    network_init();
    printf("Milko vldbserver %s started\n", VERSION);

    rx_SetMaxProcs(vldbservice,5) ;
    rx_StartServer(1) ;

    abort() ; /* should not get here */
    return 0;
}
