/*
 * Copyright 2000, International Business Machines Corporation and others.
 * All Rights Reserved.
 * 
 * This software has been released under the terms of the IBM Public
 * License.  For details, see the LICENSE file in the top-level source
 * directory or online at http://www.openafs.org/dl/license10.html
 */

#include <afs/param.h>
#include <afs/stds.h>

#include <windows.h>
#include <winsock2.h>
#include <nb30.h>
#include <string.h>
#include <malloc.h>
#include "afsd.h"
#include <osi.h>
#include <rx/rx.h>

osi_rwlock_t cm_volumeLock;

long 
cm_ValidateVolume(void)
{
    cm_volume_t * volp;
    afs_uint32 count;

    for (volp = cm_data.allVolumesp, count = 0; volp; volp=volp->allNextp, count++) {
        if ( volp->magic != CM_VOLUME_MAGIC ) {
            afsi_log("cm_ValidateVolume failure: volp->magic != CM_VOLUME_MAGIC");
            fprintf(stderr, "cm_ValidateVolume failure: volp->magic != CM_VOLUME_MAGIC\n");
            return -1;
        }
        if ( volp->cellp && volp->cellp->magic != CM_CELL_MAGIC ) {
            afsi_log("cm_ValidateVolume failure: volp->cellp->magic != CM_CELL_MAGIC");
            fprintf(stderr, "cm_ValidateVolume failure: volp->cellp->magic != CM_CELL_MAGIC\n");
            return -2;
        }
        if ( volp->allNextp && volp->allNextp->magic != CM_VOLUME_MAGIC ) {
            afsi_log("cm_ValidateVolume failure: volp->allNextp->magic != CM_VOLUME_MAGIC");
            fprintf(stderr, "cm_ValidateVolume failure: volp->allNextp->magic != CM_VOLUME_MAGIC\n");
            return -3;
        }
        if ( count != 0 && volp == cm_data.allVolumesp || 
             count > cm_data.maxVolumes ) {
            afsi_log("cm_ValidateVolume failure: cm_data.allVolumep loop detected");
            fprintf(stderr, "cm_ValidateVolume failure: cm_data.allVolumep loop detected\n");
            return -4;
        }
    }

    if ( count != cm_data.currentVolumes ) {
        afsi_log("cm_ValidateVolume failure: count != cm_data.currentVolumes");
        fprintf(stderr, "cm_ValidateVolume failure: count != cm_data.currentVolumes\n");
        return -5;
    }
    
    return 0;
}

long
cm_ShutdownVolume(void)
{
    cm_volume_t * volp;

    for (volp = cm_data.allVolumesp; volp; volp=volp->allNextp) {
        afs_uint32 volType;
        for ( volType = RWVOL; volType < NUM_VOL_TYPES; volType++) {
            if (volp->vol[volType].ID)
                cm_VolumeStatusNotification(volp, volp->vol[volType].ID, volp->vol[volType].state, vl_alldown);
        }
        volp->cbExpiresRO = 0;
        volp->cbServerpRO = NULL;
        lock_FinalizeRWLock(&volp->rw);
    }

    return 0;
}

void cm_InitVolume(int newFile, long maxVols)
{
    static osi_once_t once;

    if (osi_Once(&once)) {
        lock_InitializeRWLock(&cm_volumeLock, "cm global volume lock", LOCK_HIERARCHY_VOLUME_GLOBAL);

        if ( newFile ) {
            cm_data.allVolumesp = NULL;
            cm_data.currentVolumes = 0;
            cm_data.maxVolumes = maxVols;
            memset(cm_data.volumeNameHashTablep, 0, sizeof(cm_volume_t *) * cm_data.volumeHashTableSize);
            memset(cm_data.volumeRWIDHashTablep, 0, sizeof(cm_volume_t *) * cm_data.volumeHashTableSize);
            memset(cm_data.volumeROIDHashTablep, 0, sizeof(cm_volume_t *) * cm_data.volumeHashTableSize);
            memset(cm_data.volumeBKIDHashTablep, 0, sizeof(cm_volume_t *) * cm_data.volumeHashTableSize);
            cm_data.volumeLRUFirstp = cm_data.volumeLRULastp = NULL;
        } else {
            cm_volume_t * volp;

            for (volp = cm_data.allVolumesp; volp; volp=volp->allNextp) {
                afs_uint32 volType;

                lock_InitializeRWLock(&volp->rw, "cm_volume_t rwlock", LOCK_HIERARCHY_VOLUME);
                volp->flags |= CM_VOLUMEFLAG_RESET;
                volp->flags &= ~CM_VOLUMEFLAG_UPDATING_VL;
                for (volType = RWVOL; volType < NUM_VOL_TYPES; volType++) {
                    volp->vol[volType].state = vl_unknown;
                    volp->vol[volType].serversp = NULL;
                    if (volp->vol[volType].ID)
                        cm_VolumeStatusNotification(volp, volp->vol[volType].ID, vl_unknown, volp->vol[volType].state);
                }
                volp->cbExpiresRO = 0;
                volp->cbServerpRO = NULL;
            }
        }
        osi_EndOnce(&once);
    }
}


/* returns true if the id is a decimal integer, in which case we interpret it
 * as an id.  make the cache manager much simpler.  
 * Stolen from src/volser/vlprocs.c */
int
cm_VolNameIsID(char *aname)
{
    int tc;
    while (tc = *aname++) {
        if (tc > '9' || tc < '0')
            return 0;
    }
    return 1;
}


/*
 * Update a volume.  Caller holds a write lock on the volume (volp->rw).
 *
 *
 *  shadow / openafs / jhutz@CS.CMU.EDU {ANDREW.CMU.EDU}  01:38    (JHutz)
 *    Yes, we support multihomed fileservers.
 *    Since before we got the code from IBM.
 *    But to find out about multiple addresses on a multihomed server, you need
 *    to use VL_GetEntryByNameU and VL_GetAddrsU.  If you use
 *    VL_GetEntryByNameO or VL_GetEntryByNameN, the vlserver just gives you one
 *    address per server.
 *  shadow / openafs / jhutz@CS.CMU.EDU {ANDREW.CMU.EDU}  01:39    (JHutz)
 *    see src/afs/afs_volume.c, paying particular attention to
 *    afs_NewVolumeByName, afs_SetupVolume, and InstallUVolumeEntry
 *  shadow / openafs / jaltman {ANDREW.CMU.EDU}  01:40    (Jeffrey Altman)
 *    thanks.  The windows client calls the 0 versions.
 *  shadow / openafs / jhutz@CS.CMU.EDU {ANDREW.CMU.EDU}  01:51    (JHutz)
 *    Oh.  Ew.
 *    By not using the N versions, you only get up to 8 sites instead of 13.
 *    By not using the U versions, you don't get to know about multihomed serve
 *  shadow / openafs / jhutz@CS.CMU.EDU {ANDREW.CMU.EDU}  01:52    (JHutz)
 *    Of course, you probably want to support the older versions for backward
 *    compatibility.  If you do that, you need to call the newest interface
 *    first, and fall back to successively older versions if you get
 *    RXGEN_OPCODE.
 */
#define MULTIHOMED 1
long cm_UpdateVolumeLocation(struct cm_cell *cellp, cm_user_t *userp, cm_req_t *reqp,
		     cm_volume_t *volp)
{
    cm_conn_t *connp;
    int i;
    afs_uint32 j, k;
    cm_serverRef_t *tsrp;
    cm_server_t *tsp;
    struct sockaddr_in tsockAddr;
    long tflags;
    u_long tempAddr;
    struct vldbentry vldbEntry;
    struct nvldbentry nvldbEntry;
#ifdef MULTIHOMED
    struct uvldbentry uvldbEntry;
#endif
    int method = -1;
    int ROcount = 0;
    long code;
    enum volstatus rwNewstate = vl_online;
    enum volstatus roNewstate = vl_online;
    enum volstatus bkNewstate = vl_online;
#ifdef AFS_FREELANCE_CLIENT
    int freelance = 0;
#endif
    afs_uint32 volType;

    lock_AssertWrite(&volp->rw);

#ifdef AFS_FREELANCE_CLIENT
    if ( cellp->cellID == AFS_FAKE_ROOT_CELL_ID && volp->vol[RWVOL].ID == AFS_FAKE_ROOT_VOL_ID ) 
    {
	freelance = 1;
        memset(&vldbEntry, 0, sizeof(vldbEntry));
        vldbEntry.flags |= VLF_RWEXISTS;
        vldbEntry.volumeId[0] = AFS_FAKE_ROOT_VOL_ID;
        code = 0;
        method = 0;
    } else
#endif
    {
        while (volp->flags & CM_VOLUMEFLAG_UPDATING_VL) {
            osi_Log3(afsd_logp, "cm_UpdateVolumeLocation sleeping name %s:%s flags 0x%x", 
                     volp->cellp->name, volp->namep, volp->flags);
            osi_SleepW((LONG_PTR) &volp->flags, &volp->rw);
            lock_ObtainWrite(&volp->rw);
            osi_Log3(afsd_logp, "cm_UpdateVolumeLocation awake name %s:%s flags 0x%x", 
                     volp->cellp->name, volp->namep, volp->flags);
            if (!(volp->flags & CM_VOLUMEFLAG_RESET)) {
                osi_Log3(afsd_logp, "cm_UpdateVolumeLocation nothing to do, waking others name %s:%s flags 0x%x", 
                         volp->cellp->name, volp->namep, volp->flags);
                osi_Wakeup((LONG_PTR) &volp->flags);
                return 0;
            }
        }

        /* clear out old bindings */
        for ( volType = RWVOL; volType < NUM_VOL_TYPES; volType++) {
            if (volp->vol[volType].serversp)
                cm_FreeServerList(&volp->vol[volType].serversp, CM_FREESERVERLIST_DELETE);
        }

        volp->flags |= CM_VOLUMEFLAG_UPDATING_VL;
        lock_ReleaseWrite(&volp->rw);

        if (cellp->flags & CM_CELLFLAG_VLSERVER_INVALID)
            cm_UpdateCell(cellp, 0);

        /* now we have volume structure locked and held; make RPC to fill it */
	osi_Log2(afsd_logp, "CALL VL_GetEntryByName{UNO} name %s:%s", 
                  osi_LogSaveString(afsd_logp,volp->cellp->name), 
                  osi_LogSaveString(afsd_logp,volp->namep));
        do {
            struct rx_connection * rxconnp;

            code = cm_ConnByMServers(cellp->vlServersp, userp, reqp, &connp);
            if (code) 
                continue;

            rxconnp = cm_GetRxConn(connp);
#ifdef MULTIHOMED
            code = VL_GetEntryByNameU(rxconnp, volp->namep, &uvldbEntry);
            method = 2;
            if ( code == RXGEN_OPCODE ) 
#endif
            {
                code = VL_GetEntryByNameN(rxconnp, volp->namep, &nvldbEntry);
                method = 1;
            }
            if ( code == RXGEN_OPCODE ) {
                code = VL_GetEntryByNameO(rxconnp, volp->namep, &vldbEntry);
                method = 0;
            }
            rx_PutConnection(rxconnp);
        } while (cm_Analyze(connp, userp, reqp, NULL, NULL, cellp->vlServersp, NULL, code));
        code = cm_MapVLRPCError(code, reqp);
	if ( code )
	    osi_Log3(afsd_logp, "CALL VL_GetEntryByName{UNO} name %s:%s FAILURE, code 0x%x", 
		      osi_LogSaveString(afsd_logp,volp->cellp->name), 
                      osi_LogSaveString(afsd_logp,volp->namep), code);
	else
	    osi_Log2(afsd_logp, "CALL VL_GetEntryByName{UNO} name %s:%s SUCCESS", 
		      osi_LogSaveString(afsd_logp,volp->cellp->name), 
                      osi_LogSaveString(afsd_logp,volp->namep));
    }

    /* We can end up here with code == CM_ERROR_NOSUCHVOLUME if the base volume name
     * does not exist but there might exist a .readonly volume.  If the base name 
     * doesn't exist we will not care about the .backup that might be left behind
     * since there should be no method to access it.  
     */
    if (code == CM_ERROR_NOSUCHVOLUME && volp->vol[RWVOL].ID == 0 && strlen(volp->namep) < (VL_MAXNAMELEN - 9)) {
        char name[VL_MAXNAMELEN];

        snprintf(name, VL_MAXNAMELEN, "%s.readonly", volp->namep);
                
        /* now we have volume structure locked and held; make RPC to fill it */
	osi_Log2(afsd_logp, "CALL VL_GetEntryByName{UNO} name %s:%s", 
                 osi_LogSaveString(afsd_logp,volp->cellp->name),
                 osi_LogSaveString(afsd_logp,name));
        do {
            struct rx_connection * rxconnp;

            code = cm_ConnByMServers(cellp->vlServersp, userp, reqp, &connp);
            if (code) 
                continue;

            rxconnp = cm_GetRxConn(connp);
#ifdef MULTIHOMED
            code = VL_GetEntryByNameU(connp->rxconnp, name, &uvldbEntry);
            method = 2;
            if ( code == RXGEN_OPCODE ) 
#endif
            {
                code = VL_GetEntryByNameN(connp->rxconnp, name, &nvldbEntry);
                method = 1;
            }
            if ( code == RXGEN_OPCODE ) {
                code = VL_GetEntryByNameO(connp->rxconnp, name, &vldbEntry);
                method = 0;
            }
            rx_PutConnection(rxconnp);
        } while (cm_Analyze(connp, userp, reqp, NULL, NULL, cellp->vlServersp, NULL, code));
        code = cm_MapVLRPCError(code, reqp);
	if ( code )
	    osi_Log3(afsd_logp, "CALL VL_GetEntryByName{UNO} name %s:%s FAILURE, code 0x%x", 
		     osi_LogSaveString(afsd_logp,volp->cellp->name), 
                     osi_LogSaveString(afsd_logp,name), code);
	else
	    osi_Log2(afsd_logp, "CALL VL_GetEntryByName{UNO} name %s:%s SUCCESS", 
		     osi_LogSaveString(afsd_logp,volp->cellp->name), 
                     osi_LogSaveString(afsd_logp,name));
    }
    
    lock_ObtainWrite(&volp->rw);
    if (code == 0) {
        afs_int32 flags;
        afs_int32 nServers;
        afs_int32 rwID;
        afs_int32 roID;
        afs_int32 bkID;
        afs_int32 serverNumber[NMAXNSERVERS];
        afs_int32 serverFlags[NMAXNSERVERS];
        afsUUID   serverUUID[NMAXNSERVERS];
        afs_int32 rwServers_alldown = 1;
        afs_int32 roServers_alldown = 1;
        afs_int32 bkServers_alldown = 1;
        char      name[VL_MAXNAMELEN];

#ifdef AFS_FREELANCE_CLIENT
	if (freelance)
	    rwServers_alldown = 0;
#endif

        memset(serverUUID, 0, sizeof(serverUUID));

        switch ( method ) {
        case 0:
            flags = vldbEntry.flags;
            nServers = vldbEntry.nServers;
            rwID = vldbEntry.volumeId[0];
            roID = vldbEntry.volumeId[1];
            bkID = vldbEntry.volumeId[2];
            for ( i=0; i<nServers; i++ ) {
                serverFlags[i] = vldbEntry.serverFlags[i];
                serverNumber[i] = vldbEntry.serverNumber[i];
            }
            strncpy(name, vldbEntry.name, VL_MAXNAMELEN);
            name[VL_MAXNAMELEN - 1] = '\0';
            break;
        case 1:
            flags = nvldbEntry.flags;
            nServers = nvldbEntry.nServers;
            rwID = nvldbEntry.volumeId[0];
            roID = nvldbEntry.volumeId[1];
            bkID = nvldbEntry.volumeId[2];
            for ( i=0; i<nServers; i++ ) {
                serverFlags[i] = nvldbEntry.serverFlags[i];
                serverNumber[i] = nvldbEntry.serverNumber[i];
            }
            strncpy(name, nvldbEntry.name, VL_MAXNAMELEN);
            name[VL_MAXNAMELEN - 1] = '\0';
            break;
#ifdef MULTIHOMED
        case 2:
            flags = uvldbEntry.flags;
            nServers = uvldbEntry.nServers;
            rwID = uvldbEntry.volumeId[0];
            roID = uvldbEntry.volumeId[1];
            bkID = uvldbEntry.volumeId[2];
            for ( i=0, j=0; code == 0 && i<nServers && j<NMAXNSERVERS; i++ ) {
                if ( !(uvldbEntry.serverFlags[i] & VLSERVER_FLAG_UUID) ) {
                    serverFlags[j] = uvldbEntry.serverFlags[i];
                    serverNumber[j] = uvldbEntry.serverNumber[i].time_low;
                    j++;
                } else {
                    afs_uint32 * addrp, nentries, code, unique;
                    bulkaddrs  addrs;
                    ListAddrByAttributes attrs;
                    afsUUID uuid;

                    memset((char *)&attrs, 0, sizeof(attrs));
                    attrs.Mask = VLADDR_UUID;
                    attrs.uuid = uvldbEntry.serverNumber[i];
                    memset((char *)&uuid, 0, sizeof(uuid));
                    memset((char *)&addrs, 0, sizeof(addrs));

                    do {
                        struct rx_connection *rxconnp;

                        code = cm_ConnByMServers(cellp->vlServersp, userp, reqp, &connp);
                        if (code) 
                            continue;
                   
                        rxconnp = cm_GetRxConn(connp);
                        code = VL_GetAddrsU(rxconnp, &attrs, &uuid, &unique, &nentries, &addrs);
                        rx_PutConnection(rxconnp);
                    } while (cm_Analyze(connp, userp, reqp, NULL, NULL, cellp->vlServersp, NULL, code));

                    if ( code ) {
                        code = cm_MapVLRPCError(code, reqp);
                        osi_Log2(afsd_logp, "CALL VL_GetAddrsU serverNumber %u FAILURE, code 0x%x", 
                                 i, code);
                        continue;
                    } 
                    osi_Log1(afsd_logp, "CALL VL_GetAddrsU serverNumber %u SUCCESS", i);

                    addrp = addrs.bulkaddrs_val;
                    for (k = 0; k < nentries && j < NMAXNSERVERS; j++, k++) {
                        serverFlags[j] = uvldbEntry.serverFlags[i];
                        serverNumber[j] = addrp[k];
                        serverUUID[j] = uuid;
                    }

                    xdr_free(addrs.bulkaddrs_val, addrs.bulkaddrs_len * sizeof(*addrs.bulkaddrs_val));

                    if (nentries == 0)
                        code = CM_ERROR_INVAL;
                }
            }
            nServers = j;					/* update the server count */
            strncpy(name, uvldbEntry.name, VL_MAXNAMELEN);
            name[VL_MAXNAMELEN - 1] = '\0';
            break;
#endif
        }

        /* decode the response */
        lock_ObtainWrite(&cm_volumeLock);
        if (cm_VolNameIsID(volp->namep)) {
            size_t    len;

            len = strlen(name);

            if (len >= 8 && strcmp(name + len - 7, ".backup") == 0) {
                name[len - 7] = '\0';
            } else if (len >= 10 && strcmp(name + len - 9, ".readonly") == 0) {
                name[len - 9] = '\0';
            }
            
            osi_Log2(afsd_logp, "cm_UpdateVolume name %s -> %s", 
                     osi_LogSaveString(afsd_logp,volp->namep), osi_LogSaveString(afsd_logp,name));

            if (volp->flags & CM_VOLUMEFLAG_IN_HASH)
                cm_RemoveVolumeFromNameHashTable(volp);

            strcpy(volp->namep, name);

            cm_AddVolumeToNameHashTable(volp);
        }

        if (flags & VLF_DFSFILESET) {
            volp->flags |= CM_VOLUMEFLAG_DFS_VOLUME;
            osi_Log1(afsd_logp, "cm_UpdateVolume Volume Group '%s' is a DFS File Set.  Correct behavior is not implemented.",
                     osi_LogSaveString(afsd_logp, volp->namep));
        }

        if (flags & VLF_RWEXISTS) {
            if (volp->vol[RWVOL].ID != rwID) {
                if (volp->vol[RWVOL].flags & CM_VOLUMEFLAG_IN_HASH)
                    cm_RemoveVolumeFromIDHashTable(volp, RWVOL);
                volp->vol[RWVOL].ID = rwID;
                cm_AddVolumeToIDHashTable(volp, RWVOL);
            }
        } else {
            if (volp->vol[RWVOL].flags & CM_VOLUMEFLAG_IN_HASH)
                cm_RemoveVolumeFromIDHashTable(volp, RWVOL);
            volp->vol[RWVOL].ID = 0;
        }
        if (flags & VLF_ROEXISTS) {
            if (volp->vol[ROVOL].ID != roID) {
                if (volp->vol[ROVOL].flags & CM_VOLUMEFLAG_IN_HASH)
                    cm_RemoveVolumeFromIDHashTable(volp, ROVOL);
                volp->vol[ROVOL].ID = roID;
                cm_AddVolumeToIDHashTable(volp, ROVOL);
            }
        } else {
            if (volp->vol[ROVOL].flags & CM_VOLUMEFLAG_IN_HASH)
                cm_RemoveVolumeFromIDHashTable(volp, ROVOL);
            volp->vol[ROVOL].ID = 0;
        }
        if (flags & VLF_BACKEXISTS) {
            if (volp->vol[BACKVOL].ID != bkID) {
                if (volp->vol[BACKVOL].flags & CM_VOLUMEFLAG_IN_HASH)
                    cm_RemoveVolumeFromIDHashTable(volp, BACKVOL);
                volp->vol[BACKVOL].ID = bkID;
                cm_AddVolumeToIDHashTable(volp, BACKVOL);
            }
        } else {
            if (volp->vol[BACKVOL].flags & CM_VOLUMEFLAG_IN_HASH)
                cm_RemoveVolumeFromIDHashTable(volp, BACKVOL);
            volp->vol[BACKVOL].ID = 0;
        }
        lock_ReleaseWrite(&cm_volumeLock);
        for (i=0; i<nServers; i++) {
            /* create a server entry */
            tflags = serverFlags[i];
            if (tflags & VLSF_DONTUSE) 
                continue;
            tsockAddr.sin_family = AF_INET;
            tempAddr = htonl(serverNumber[i]);
            tsockAddr.sin_addr.s_addr = tempAddr;
            tsp = cm_FindServer(&tsockAddr, CM_SERVER_FILE);
            if (tsp && (method == 2) && (tsp->flags & CM_SERVERFLAG_UUID)) {
                /* 
                 * Check to see if the uuid of the server we know at this address
                 * matches the uuid of the server we are being told about by the
                 * vlserver.  If not, ...?
                 */
                if (!afs_uuid_equal(&serverUUID[i], &tsp->uuid)) {
                    char uuid1[128], uuid2[128];
                    char hoststr[16];

                    afsUUID_to_string(&serverUUID[i], uuid1, sizeof(uuid1));
                    afsUUID_to_string(&tsp->uuid, uuid2, sizeof(uuid2));
                    afs_inet_ntoa_r(serverNumber[i], hoststr);

                    osi_Log3(afsd_logp, "cm_UpdateVolumeLocation UUIDs do not match! %s != %s (%s)",
                              osi_LogSaveString(afsd_logp, uuid1),
                              osi_LogSaveString(afsd_logp, uuid2),
                              osi_LogSaveString(afsd_logp, hoststr));
                }
            }
            if (!tsp) {
                /* cm_NewServer will probe the server which in turn will
                 * update the state on the volume group object */
                lock_ReleaseWrite(&volp->rw);
                tsp = cm_NewServer(&tsockAddr, CM_SERVER_FILE, cellp, &serverUUID[i], 0);
                lock_ObtainWrite(&volp->rw);
            }
            osi_assertx(tsp != NULL, "null cm_server_t");
                        
            /*
             * if this server was created by fs setserverprefs
             * then it won't have either a cell assignment or 
             * a server uuid.
             */
            if ( !tsp->cellp ) 
                tsp->cellp = cellp;
            if ( (method == 2) && !(tsp->flags & CM_SERVERFLAG_UUID) && 
                 !afs_uuid_is_nil(&serverUUID[i])) {
                tsp->uuid = serverUUID[i];
                tsp->flags |= CM_SERVERFLAG_UUID;
            }

            /* and add it to the list(s). */
            /*
             * Each call to cm_NewServerRef() increments the
             * ref count of tsp.  These reference will be dropped,
             * if and when the volume is reset; see reset code
             * earlier in this function.
             */
            if ((tflags & VLSF_RWVOL) && (flags & VLF_RWEXISTS)) {
                tsrp = cm_NewServerRef(tsp, rwID);
                cm_InsertServerList(&volp->vol[RWVOL].serversp, tsrp);

                lock_ObtainWrite(&cm_serverLock);
                tsrp->refCount--;       /* drop allocation reference */
                lock_ReleaseWrite(&cm_serverLock);

                if (!(tsp->flags & CM_SERVERFLAG_DOWN))
                    rwServers_alldown = 0;
            }
            if ((tflags & VLSF_ROVOL) && (flags & VLF_ROEXISTS)) {
                tsrp = cm_NewServerRef(tsp, roID);
                cm_InsertServerList(&volp->vol[ROVOL].serversp, tsrp);
                lock_ObtainWrite(&cm_serverLock);
                tsrp->refCount--;       /* drop allocation reference */
                lock_ReleaseWrite(&cm_serverLock);
                ROcount++;

                if (!(tsp->flags & CM_SERVERFLAG_DOWN))
                    roServers_alldown = 0;
            }
            /* We don't use VLSF_BACKVOL !?! */
            /* Because only the backup on the server holding the RW 
             * volume can be valid.  This check prevents errors if a
             * RW is moved but the old backup is not removed.
             */
            if ((tflags & VLSF_RWVOL) && (flags & VLF_BACKEXISTS)) {
                tsrp = cm_NewServerRef(tsp, bkID);
                cm_InsertServerList(&volp->vol[BACKVOL].serversp, tsrp);
                lock_ObtainWrite(&cm_serverLock);
                tsrp->refCount--;       /* drop allocation reference */
                lock_ReleaseWrite(&cm_serverLock);

                if (!(tsp->flags & CM_SERVERFLAG_DOWN))
                    bkServers_alldown = 0;
            }
            /* Drop the reference obtained by cm_FindServer() */
            cm_PutServer(tsp);
        }       

        /*
         * Randomize RO list
         *
         * If the first n servers have the same ipRank, then we 
         * randomly pick one among them and move it to the beginning.
         * We don't bother to re-order the whole list because
         * the rest of the list is used only if the first server is
         * down.  We only do this for the RO list; we assume the other
         * lists are length 1.
         */
        if (ROcount > 1) {
            cm_RandomizeServer(&volp->vol[ROVOL].serversp);
        }


        rwNewstate = rwServers_alldown ? vl_alldown : vl_online;
        roNewstate = roServers_alldown ? vl_alldown : vl_online;
        bkNewstate = bkServers_alldown ? vl_alldown : vl_online;
    } else if (code == CM_ERROR_NOSUCHVOLUME || code == VL_NOENT || code == VL_BADNAME) {
        /* this volume does not exist - we should discard it */
        if (volp->flags & CM_VOLUMEFLAG_IN_HASH)
            cm_RemoveVolumeFromNameHashTable(volp);
        for ( volType = RWVOL; volType < NUM_VOL_TYPES; volType++) {
            if (volp->vol[volType].flags & CM_VOLUMEFLAG_IN_HASH)
                cm_RemoveVolumeFromIDHashTable(volp, volType);
            if (volp->vol[volType].ID) {
                cm_VolumeStatusNotification(volp, volp->vol[volType].ID, volp->vol[volType].state, vl_alldown);
                volp->vol[volType].ID = 0;
            }
            cm_SetFid(&volp->vol[volType].dotdotFid, 0, 0, 0, 0);
        }

        /* Move to the end so it will be recycled first */
        cm_MoveVolumeToLRULast(volp);

        volp->namep[0] ='\0';
    } else {
        rwNewstate = roNewstate = bkNewstate = vl_alldown;
    }

    if (volp->vol[RWVOL].state != rwNewstate) {
        if (volp->vol[RWVOL].ID)
            cm_VolumeStatusNotification(volp, volp->vol[RWVOL].ID, volp->vol[RWVOL].state, rwNewstate);
        volp->vol[RWVOL].state = rwNewstate;
    }
    if (volp->vol[ROVOL].state != roNewstate) {
        if (volp->vol[ROVOL].ID)
            cm_VolumeStatusNotification(volp, volp->vol[ROVOL].ID, volp->vol[ROVOL].state, roNewstate);
        volp->vol[ROVOL].state = roNewstate;
    }
    if (volp->vol[BACKVOL].state != bkNewstate) {
        if (volp->vol[BACKVOL].ID)
            cm_VolumeStatusNotification(volp, volp->vol[BACKVOL].ID, volp->vol[BACKVOL].state, bkNewstate);
        volp->vol[BACKVOL].state = bkNewstate;
    }

    if (code == 0)
        volp->flags &= ~CM_VOLUMEFLAG_RESET;

    volp->flags &= ~CM_VOLUMEFLAG_UPDATING_VL;
    osi_Log4(afsd_logp, "cm_UpdateVolumeLocation done, waking others name %s:%s flags 0x%x code 0x%x", 
             osi_LogSaveString(afsd_logp,volp->cellp->name), 
             osi_LogSaveString(afsd_logp,volp->namep), volp->flags, code);
    osi_Wakeup((LONG_PTR) &volp->flags);

    return code;
}

/* Requires read or write lock on cm_volumeLock */
void cm_GetVolume(cm_volume_t *volp)
{
    InterlockedIncrement(&volp->refCount);
}

cm_volume_t *cm_GetVolumeByFID(cm_fid_t *fidp)
{
    cm_volume_t *volp;
    afs_uint32 hash;

    lock_ObtainRead(&cm_volumeLock);
    hash = CM_VOLUME_ID_HASH(fidp->volume);
    /* The volumeID can be any one of the three types.  So we must
     * search the hash table for all three types until we find it.
     * We will search in the order of RO, RW, BK.
     */
    for ( volp = cm_data.volumeROIDHashTablep[hash]; volp; volp = volp->vol[ROVOL].nextp) {
        if ( fidp->cell == volp->cellp->cellID && fidp->volume == volp->vol[ROVOL].ID )
            break;
    }
    if (!volp) {
        /* try RW volumes */
        for ( volp = cm_data.volumeRWIDHashTablep[hash]; volp; volp = volp->vol[RWVOL].nextp) {
            if ( fidp->cell == volp->cellp->cellID && fidp->volume == volp->vol[RWVOL].ID )
                break;
        }
    }
    if (!volp) {
        /* try BK volumes */
        for ( volp = cm_data.volumeBKIDHashTablep[hash]; volp; volp = volp->vol[BACKVOL].nextp) {
            if ( fidp->cell == volp->cellp->cellID && fidp->volume == volp->vol[BACKVOL].ID )
                break;
        }
    }

    /* hold the volume if we found it */
    if (volp) 
        cm_GetVolume(volp);
        
    lock_ReleaseRead(&cm_volumeLock);
    return volp;
}

long cm_FindVolumeByID(cm_cell_t *cellp, afs_uint32 volumeID, cm_user_t *userp,
                      cm_req_t *reqp, afs_uint32 flags, cm_volume_t **outVolpp)
{
    cm_volume_t *volp;
#ifdef SEARCH_ALL_VOLUMES
    cm_volume_t *volp2;
#endif
    char volNameString[VL_MAXNAMELEN];
    afs_uint32 hash;
    long code = 0;

    lock_ObtainRead(&cm_volumeLock);
#ifdef SEARCH_ALL_VOLUMES
    for(volp = cm_data.allVolumesp; volp; volp=volp->allNextp) {
	if (cellp == volp->cellp &&
	     ((unsigned) volumeID == volp->vol[RWVOL].ID ||
	       (unsigned) volumeID == volp->vol[ROVOL].ID ||
	       (unsigned) volumeID == volp->vol[BACKVOL].ID))
	    break;
    }	

    volp2 = volp;
#endif /* SEARCH_ALL_VOLUMES */

    hash = CM_VOLUME_ID_HASH(volumeID);
    /* The volumeID can be any one of the three types.  So we must
     * search the hash table for all three types until we find it.
     * We will search in the order of RO, RW, BK.
     */
    for ( volp = cm_data.volumeROIDHashTablep[hash]; volp; volp = volp->vol[ROVOL].nextp) {
        if ( cellp == volp->cellp && volumeID == volp->vol[ROVOL].ID )
            break;
    }
    if (!volp) {
        /* try RW volumes */
        for ( volp = cm_data.volumeRWIDHashTablep[hash]; volp; volp = volp->vol[RWVOL].nextp) {
            if ( cellp == volp->cellp && volumeID == volp->vol[RWVOL].ID )
                break;
        }
    }
    if (!volp) {
        /* try BK volumes */
        for ( volp = cm_data.volumeBKIDHashTablep[hash]; volp; volp = volp->vol[BACKVOL].nextp) {
            if ( cellp == volp->cellp && volumeID == volp->vol[BACKVOL].ID )
                break;
        }
    }

#ifdef SEARCH_ALL_VOLUMES
    osi_assertx(volp == volp2, "unexpected cm_vol_t");
#endif

    /* hold the volume if we found it */
    if (volp) 
        cm_GetVolume(volp);
        
    lock_ReleaseRead(&cm_volumeLock);

    /* return it held */
    if (volp) {
        lock_ObtainWrite(&volp->rw);
        
        code = 0;
        if ((volp->flags & CM_VOLUMEFLAG_RESET) && !(flags & CM_GETVOL_FLAG_NO_RESET)) {
            code = cm_UpdateVolumeLocation(cellp, userp, reqp, volp);
        }
        lock_ReleaseWrite(&volp->rw);
        if (code == 0) {
            *outVolpp = volp;

            if (!(flags & CM_GETVOL_FLAG_NO_LRU_UPDATE)) {
                lock_ObtainWrite(&cm_volumeLock);
                cm_AdjustVolumeLRU(volp);
                lock_ReleaseWrite(&cm_volumeLock);
            }
        } else {
            lock_ObtainRead(&cm_volumeLock);
            cm_PutVolume(volp);
            lock_ReleaseRead(&cm_volumeLock);
        }
        return code;
    }
        
    /* otherwise, we didn't find it so consult the VLDB */
    sprintf(volNameString, "%u", volumeID);
    code = cm_FindVolumeByName(cellp, volNameString, userp, reqp,
			      flags | CM_GETVOL_FLAG_IGNORE_LINKED_CELL, outVolpp);

    if (code == CM_ERROR_NOSUCHVOLUME && cellp->linkedName[0] && 
        !(flags & CM_GETVOL_FLAG_IGNORE_LINKED_CELL)) {
        cm_cell_t *linkedCellp = cm_GetCell(cellp->linkedName, flags);

        if (linkedCellp)
            code = cm_FindVolumeByID(linkedCellp, volumeID, userp, reqp, 
                                     flags | CM_GETVOL_FLAG_IGNORE_LINKED_CELL, 
                                     outVolpp);
    }
    return code;
}


long cm_FindVolumeByName(struct cm_cell *cellp, char *volumeNamep,
			struct cm_user *userp, struct cm_req *reqp,
			afs_uint32 flags, cm_volume_t **outVolpp)
{
    cm_volume_t *volp;
#ifdef SEARCH_ALL_VOLUMES
    cm_volume_t *volp2;
#endif
    long        code = 0;
    char        name[VL_MAXNAMELEN];
    size_t      len;
    int         type;
    afs_uint32  hash;

    strncpy(name, volumeNamep, VL_MAXNAMELEN);
    name[VL_MAXNAMELEN-1] = '\0';
    len = strlen(name);

    if (len >= 8 && strcmp(name + len - 7, ".backup") == 0) {
        type = BACKVOL;
        name[len - 7] = '\0';
    } else if (len >= 10 && strcmp(name + len - 9, ".readonly") == 0) {
        type = ROVOL;
        name[len - 9] = '\0';
    } else {
        type = RWVOL;
    }

    lock_ObtainRead(&cm_volumeLock);
#ifdef SEARCH_ALL_VOLUMES
    for (volp = cm_data.allVolumesp; volp; volp=volp->allNextp) {
	if (cellp == volp->cellp && strcmp(name, volp->namep) == 0) {
	    break;
	}
    }	
    volp2 = volp;
#endif /* SEARCH_ALL_VOLUMES */

    hash = CM_VOLUME_NAME_HASH(name);
    for (volp = cm_data.volumeNameHashTablep[hash]; volp; volp = volp->nameNextp) {
        if (cellp == volp->cellp && strcmp(name, volp->namep) == 0)
            break;
    }

#ifdef SEARCH_ALL_VOLUMES
    osi_assertx(volp2 == volp, "unexpected cm_vol_t");
#endif

    if (!volp && (flags & CM_GETVOL_FLAG_CREATE)) {
        afs_uint32 volType;
        /* otherwise, get from VLDB */

        /* 
         * Change to a write lock so that we have exclusive use of
         * the first cm_volume_t with a refCount of 0 so that we 
         * have time to increment it.
         */
        lock_ConvertRToW(&cm_volumeLock);

	if ( cm_data.currentVolumes >= cm_data.maxVolumes ) {
#ifdef RECYCLE_FROM_ALL_VOLUMES_LIST
	    for (volp = cm_data.allVolumesp; volp; volp=volp->allNextp) {
		if ( volp->refCount == 0 ) {
		    /* There is one we can re-use */
		    break;
		}
	    }
#else
            for ( volp = cm_data.volumeLRULastp;
                  volp;
                  volp = (cm_volume_t *) osi_QPrev(&volp->q)) 
            {
		if ( volp->refCount == 0 ) {
		    /* There is one we can re-use */
		    break;
		}
            }
#endif
	    if (!volp)
		osi_panic("Exceeded Max Volumes", __FILE__, __LINE__);

            InterlockedIncrement(&volp->refCount);
            lock_ReleaseWrite(&cm_volumeLock);
            lock_ObtainWrite(&volp->rw);
            lock_ObtainWrite(&cm_volumeLock);

            osi_Log2(afsd_logp, "Recycling Volume %s:%s",
                     volp->cellp->name, volp->namep);

            if (volp->flags & CM_VOLUMEFLAG_IN_LRU_QUEUE)
                cm_RemoveVolumeFromLRU(volp);
            if (volp->flags & CM_VOLUMEFLAG_IN_HASH)
                cm_RemoveVolumeFromNameHashTable(volp);

            for ( volType = RWVOL; volType < NUM_VOL_TYPES; volType++) {
                if (volp->vol[volType].flags & CM_VOLUMEFLAG_IN_HASH)
                    cm_RemoveVolumeFromIDHashTable(volp, volType);
                if (volp->vol[volType].ID)
                    cm_VolumeStatusNotification(volp, volp->vol[volType].ID, volp->vol[volType].state, vl_unknown);
                volp->vol[volType].ID = 0;
                cm_SetFid(&volp->vol[volType].dotdotFid, 0, 0, 0, 0);
                lock_ReleaseWrite(&cm_volumeLock);
                cm_FreeServerList(&volp->vol[volType].serversp, CM_FREESERVERLIST_DELETE);
                lock_ObtainWrite(&cm_volumeLock);
            }
	} else {
	    volp = &cm_data.volumeBaseAddress[cm_data.currentVolumes++];
	    memset(volp, 0, sizeof(cm_volume_t));
	    volp->magic = CM_VOLUME_MAGIC;
	    volp->allNextp = cm_data.allVolumesp;
	    cm_data.allVolumesp = volp;
	    lock_InitializeRWLock(&volp->rw, "cm_volume_t rwlock", LOCK_HIERARCHY_VOLUME);
            lock_ReleaseWrite(&cm_volumeLock);
            lock_ObtainWrite(&volp->rw);
            lock_ObtainWrite(&cm_volumeLock);
            volp->refCount = 1;	/* starts off held */
        }
	volp->cellp = cellp;
	strncpy(volp->namep, name, VL_MAXNAMELEN);
	volp->namep[VL_MAXNAMELEN-1] = '\0';
	volp->flags = CM_VOLUMEFLAG_RESET;
    
        for ( volType = RWVOL; volType < NUM_VOL_TYPES; volType++) {
            volp->vol[volType].state = vl_unknown;
            volp->vol[volType].nextp = NULL;
            volp->vol[volType].flags = 0;
        }
        volp->cbExpiresRO = 0;
        volp->cbServerpRO = NULL;
        cm_AddVolumeToNameHashTable(volp);
        lock_ReleaseWrite(&cm_volumeLock);
    }
    else {
        if (volp)
            cm_GetVolume(volp);
        lock_ReleaseRead(&cm_volumeLock);
        
        if (!volp)
            return CM_ERROR_NOSUCHVOLUME;

        lock_ObtainWrite(&volp->rw);
    }

    /* if we get here we are holding the mutex */
    if ((volp->flags & CM_VOLUMEFLAG_RESET) && !(flags & CM_GETVOL_FLAG_NO_RESET)) {
        code = cm_UpdateVolumeLocation(cellp, userp, reqp, volp);
    }	
    lock_ReleaseWrite(&volp->rw);

    if (code == 0 && (type == BACKVOL && volp->vol[BACKVOL].ID == 0 ||
                      type == ROVOL && volp->vol[ROVOL].ID == 0))
        code = CM_ERROR_NOSUCHVOLUME;

    if (code == 0) {
        *outVolpp = volp;
		
        if (!(flags & CM_GETVOL_FLAG_NO_LRU_UPDATE)) {
            lock_ObtainWrite(&cm_volumeLock);
            cm_AdjustVolumeLRU(volp);
            lock_ReleaseWrite(&cm_volumeLock);
        }
    } else {
        lock_ObtainRead(&cm_volumeLock);
        cm_PutVolume(volp);
        lock_ReleaseRead(&cm_volumeLock);
    }

    if (code == CM_ERROR_NOSUCHVOLUME && cellp->linkedName[0] && 
        !(flags & CM_GETVOL_FLAG_IGNORE_LINKED_CELL)) {
        cm_cell_t *linkedCellp = cm_GetCell(cellp->linkedName, flags);

        if (linkedCellp)
            code = cm_FindVolumeByName(linkedCellp, volumeNamep, userp, reqp, 
                                       flags | CM_GETVOL_FLAG_IGNORE_LINKED_CELL, 
                                       outVolpp);
    }
    return code;
}	

/* 
 * Only call this function in response to a VNOVOL or VMOVED error
 * from a file server.  Do not call it in response to CM_ERROR_NOSUCHVOLUME
 * as that can lead to recursive calls.
 */
long cm_ForceUpdateVolume(cm_fid_t *fidp, cm_user_t *userp, cm_req_t *reqp)
{
    cm_cell_t *cellp;
    cm_volume_t *volp;
#ifdef SEARCH_ALL_VOLUMES
    cm_volume_t *volp2;
#endif
    afs_uint32  hash;
    long code;

    if (!fidp) 
        return CM_ERROR_INVAL;

    cellp = cm_FindCellByID(fidp->cell, 0);
    if (!cellp) 
        return CM_ERROR_NOSUCHCELL;

    /* search for the volume */
    lock_ObtainRead(&cm_volumeLock);
#ifdef SEARCH_ALL_VOLUMES
    for(volp = cm_data.allVolumesp; volp; volp=volp->allNextp) {
	if (cellp == volp->cellp &&
	     (fidp->volume == volp->vol[RWVOL].ID ||
	       fidp->volume == volp->vol[ROVOL].ID ||
	       fidp->volume == volp->vol[BACKVOL].ID))
	    break;
    }	
#endif /* SEARCH_ALL_VOLUMES */

    hash = CM_VOLUME_ID_HASH(fidp->volume);
    /* The volumeID can be any one of the three types.  So we must
     * search the hash table for all three types until we find it.
     * We will search in the order of RO, RW, BK.
     */
    for ( volp = cm_data.volumeROIDHashTablep[hash]; volp; volp = volp->vol[ROVOL].nextp) {
        if ( cellp == volp->cellp && fidp->volume == volp->vol[ROVOL].ID )
            break;
    }
    if (!volp) {
        /* try RW volumes */
        for ( volp = cm_data.volumeRWIDHashTablep[hash]; volp; volp = volp->vol[RWVOL].nextp) {
            if ( cellp == volp->cellp && fidp->volume == volp->vol[RWVOL].ID )
                break;
        }
    }
    if (!volp) {
        /* try BK volumes */
        for ( volp = cm_data.volumeBKIDHashTablep[hash]; volp; volp = volp->vol[BACKVOL].nextp) {
            if ( cellp == volp->cellp && fidp->volume == volp->vol[BACKVOL].ID )
                break;
        }
    }

#ifdef SEARCH_ALL_VOLUMES
    osi_assertx(volp == volp2, "unexpected cm_vol_t");
#endif
    /* hold the volume if we found it */
    if (volp) 
	cm_GetVolume(volp);

    lock_ReleaseRead(&cm_volumeLock);

    if (!volp)
        return CM_ERROR_NOSUCHVOLUME;

    /* update it */
    cm_data.mountRootGen = time(NULL);
    lock_ObtainWrite(&volp->rw);
    volp->flags |= CM_VOLUMEFLAG_RESET;

    code = cm_UpdateVolumeLocation(cellp, userp, reqp, volp);
    lock_ReleaseWrite(&volp->rw);

    lock_ObtainRead(&cm_volumeLock);
    cm_PutVolume(volp);
    lock_ReleaseRead(&cm_volumeLock);

    return code;
}

/* find the appropriate servers from a volume */
cm_serverRef_t **cm_GetVolServers(cm_volume_t *volp, afs_uint32 volume, cm_user_t *userp, cm_req_t *reqp)
{
    cm_serverRef_t **serverspp;
    cm_serverRef_t *current;
    int firstTry = 1;

  start:
    lock_ObtainWrite(&cm_serverLock);

    if (volume == volp->vol[RWVOL].ID)
        serverspp = &volp->vol[RWVOL].serversp;
    else if (volume == volp->vol[ROVOL].ID)
        serverspp = &volp->vol[ROVOL].serversp;
    else if (volume == volp->vol[BACKVOL].ID)
        serverspp = &volp->vol[BACKVOL].serversp;
    else {
        lock_ReleaseWrite(&cm_serverLock);
        if (firstTry) {
            afs_int32 code;
            firstTry = 0;
            lock_ObtainWrite(&volp->rw);
            volp->flags |= CM_VOLUMEFLAG_RESET;
            code = cm_UpdateVolumeLocation(volp->cellp, userp, reqp, volp);
            lock_ReleaseWrite(&volp->rw);
            if (code == 0)
                goto start;
        }
        return NULL;
    }

    /* 
     * Increment the refCount on deleted items as well.
     * They will be freed by cm_FreeServerList when they get to zero 
     */
    for (current = *serverspp; current; current = current->next) 
        current->refCount++;

    lock_ReleaseWrite(&cm_serverLock);

    return serverspp;
}

void cm_PutVolume(cm_volume_t *volp)
{
    afs_int32 refCount = InterlockedDecrement(&volp->refCount);
    osi_assertx(refCount >= 0, "cm_volume_t refCount underflow has occurred");
}

/* return the read-only volume, if there is one, or the read-write volume if
 * not.
 */
long cm_GetROVolumeID(cm_volume_t *volp)
{
    long id;

    lock_ObtainRead(&volp->rw);
    if (volp->vol[ROVOL].ID && volp->vol[ROVOL].serversp)
	id = volp->vol[ROVOL].ID;
    else
	id = volp->vol[RWVOL].ID;
    lock_ReleaseRead(&volp->rw);

    return id;
}

void cm_RefreshVolumes(void)
{
    cm_volume_t *volp;
    cm_scache_t *scp;
    afs_int32 refCount;

    cm_data.mountRootGen = time(NULL);

    /* force a re-loading of volume data from the vldb */
    lock_ObtainRead(&cm_volumeLock);
    for (volp = cm_data.allVolumesp; volp; volp=volp->allNextp) {
	InterlockedIncrement(&volp->refCount);
	lock_ReleaseRead(&cm_volumeLock);

	lock_ObtainWrite(&volp->rw);
	volp->flags |= CM_VOLUMEFLAG_RESET;
	lock_ReleaseWrite(&volp->rw);
	
        lock_ObtainRead(&cm_volumeLock);
        refCount = InterlockedDecrement(&volp->refCount);
	osi_assertx(refCount >= 0, "cm_volume_t refCount underflow");
    }
    lock_ReleaseRead(&cm_volumeLock);

    /* force mount points to be re-evaluated so that 
     * if the volume location has changed we will pick 
     * that up
     */
    for ( scp = cm_data.scacheLRUFirstp; 
          scp;
          scp = (cm_scache_t *) osi_QNext(&scp->q)) {
        if ( scp->fileType == CM_SCACHETYPE_MOUNTPOINT 
#ifdef AFS_FREELANCE_CLIENT
             && !(scp->fid.cell == AFS_FAKE_ROOT_CELL_ID && scp->fid.volume == AFS_FAKE_ROOT_VOL_ID)
#endif
             ) {
            lock_ObtainWrite(&scp->rw);
            scp->mountPointStringp[0] = '\0';
            lock_ReleaseWrite(&scp->rw);
        }
    }

}

void
cm_CheckOfflineVolumeState(cm_volume_t *volp, cm_vol_state_t *statep, afs_uint32 volID,
                           afs_uint32 *onlinep, afs_uint32 *volumeUpdatedp)
{
    cm_conn_t *connp;
    long code;
    AFSFetchVolumeStatus volStat;
    char *Name;
    char *OfflineMsg;
    char *MOTD;
    cm_req_t req;
    struct rx_connection * rxconnp;
    char volName[32];
    char offLineMsg[256];
    char motd[256];
    long alldown, alldeleted;
    cm_serverRef_t *serversp;

    Name = volName;
    OfflineMsg = offLineMsg;
    MOTD = motd;

    if (statep->ID != 0 && (!volID || volID == statep->ID)) {
        if (!statep->serversp && !(*volumeUpdatedp)) {
            cm_InitReq(&req);
            code = cm_UpdateVolumeLocation(volp->cellp, cm_rootUserp, &req, volp);
            *volumeUpdatedp = 1;
        }

        if (statep->serversp) {
            alldown = 1;
            alldeleted = 1;
            for (serversp = statep->serversp; serversp; serversp = serversp->next) {
                if (serversp->status == srv_deleted)
                    continue;

                alldeleted = 0;
                *onlinep = 1;
                alldown = 0;
                
                if (serversp->status == srv_busy || serversp->status == srv_offline)
                    serversp->status = srv_not_busy;
            }

            if (alldeleted && !(*volumeUpdatedp)) {
                cm_InitReq(&req);
                code = cm_UpdateVolumeLocation(volp->cellp, cm_rootUserp, &req, volp);
                *volumeUpdatedp = 1;
            }

            if (statep->state == vl_busy || statep->state == vl_offline || statep->state == vl_unknown ||
                (!alldown && statep->state == vl_alldown)) {
                cm_InitReq(&req);

                lock_ReleaseWrite(&volp->rw);
                do {
                    code = cm_ConnFromVolume(volp, statep->ID, cm_rootUserp, &req, &connp);
                    if (code) 
                        continue;

                    rxconnp = cm_GetRxConn(connp);
                    code = RXAFS_GetVolumeStatus(rxconnp, statep->ID,
                                                 &volStat, &Name, &OfflineMsg, &MOTD);
                    rx_PutConnection(rxconnp);            

                } while (cm_Analyze(connp, cm_rootUserp, &req, NULL, NULL, NULL, NULL, code));
                code = cm_MapRPCError(code, &req);

                lock_ObtainWrite(&volp->rw);
                if (code == 0 && volStat.Online) {
                    cm_VolumeStatusNotification(volp, statep->ID, statep->state, vl_online);
                    statep->state = vl_online;
                    *onlinep = 1;
                } else if (code == CM_ERROR_NOACCESS) {
                    cm_VolumeStatusNotification(volp, statep->ID, statep->state, vl_unknown);
                    statep->state = vl_unknown;
                    *onlinep = 1;
                }
            } else if (alldown && statep->state != vl_alldown) {
                cm_VolumeStatusNotification(volp, statep->ID, statep->state, vl_alldown);
                statep->state = vl_alldown;
            }
        } else if (statep->state != vl_alldown) {
            cm_VolumeStatusNotification(volp, statep->ID, statep->state, vl_alldown);
            statep->state = vl_alldown;
        }
    }
}

/* The return code is 0 if the volume is not online and 
 * 1 if the volume is online
 */
long
cm_CheckOfflineVolume(cm_volume_t *volp, afs_uint32 volID)
{
    long code;
    cm_req_t req;
    afs_uint32 online = 0;
    afs_uint32 volumeUpdated = 0;

    lock_ObtainWrite(&volp->rw);

    if (volp->flags & CM_VOLUMEFLAG_RESET) {
        cm_InitReq(&req);
        code = cm_UpdateVolumeLocation(volp->cellp, cm_rootUserp, &req, volp);
        volumeUpdated = 1;
    }

    cm_CheckOfflineVolumeState(volp, &volp->vol[RWVOL], volID, &online, &volumeUpdated);
    cm_CheckOfflineVolumeState(volp, &volp->vol[ROVOL], volID, &online, &volumeUpdated);
    cm_CheckOfflineVolumeState(volp, &volp->vol[BACKVOL], volID, &online, &volumeUpdated);

    lock_ReleaseWrite(&volp->rw);
    return online;
}


/* 
 * called from the Daemon thread.
 * when checking the offline status, check those of the most recently used volumes first.
 */
void cm_CheckOfflineVolumes(void)
{
    cm_volume_t *volp;
    afs_int32 refCount;
    extern int daemon_ShutdownFlag;
    extern int powerStateSuspended;

    lock_ObtainRead(&cm_volumeLock);
    for (volp = cm_data.volumeLRULastp; 
         volp && !daemon_ShutdownFlag && !powerStateSuspended; 
         volp=(cm_volume_t *) osi_QPrev(&volp->q)) {
        if (volp->flags & CM_VOLUMEFLAG_IN_HASH) {
            InterlockedIncrement(&volp->refCount);
            lock_ReleaseRead(&cm_volumeLock);
            cm_CheckOfflineVolume(volp, 0);
            lock_ObtainRead(&cm_volumeLock);
            refCount = InterlockedDecrement(&volp->refCount);
            osi_assertx(refCount >= 0, "cm_volume_t refCount underflow");
        }
    }
    lock_ReleaseRead(&cm_volumeLock);
}


static void
cm_UpdateVolumeStatusInt(cm_volume_t *volp, struct cm_vol_state *statep)
{
    enum volstatus newStatus;
    cm_serverRef_t *tsrp;
    cm_server_t *tsp;
    int someBusy = 0, someOffline = 0, allOffline = 1, allBusy = 1, allDown = 1;
    char addr[16];

    if (!volp || !statep) {
#ifdef DEBUG
        DebugBreak();
#endif
        return;
    }

    lock_ObtainWrite(&cm_serverLock);
    for (tsrp = statep->serversp; tsrp; tsrp=tsrp->next) {
        tsp = tsrp->server;
        sprintf(addr, "%d.%d.%d.%d", 
                 ((tsp->addr.sin_addr.s_addr & 0xff)),
                 ((tsp->addr.sin_addr.s_addr & 0xff00)>> 8),
                 ((tsp->addr.sin_addr.s_addr & 0xff0000)>> 16),
                 ((tsp->addr.sin_addr.s_addr & 0xff000000)>> 24)); 

        if (tsrp->status == srv_deleted) {
            osi_Log2(afsd_logp, "cm_UpdateVolumeStatusInt volume %d server reference %s deleted", 
                     statep->ID, osi_LogSaveString(afsd_logp,addr));
            continue;
        }
        if (tsp) {
            cm_GetServerNoLock(tsp);
            if (!(tsp->flags & CM_SERVERFLAG_DOWN)) {
                allDown = 0;
                if (tsrp->status == srv_busy) {
                    osi_Log2(afsd_logp, "cm_UpdateVolumeStatusInt volume %d server reference %s busy", 
                              statep->ID, osi_LogSaveString(afsd_logp,addr));
                    allOffline = 0;
                    someBusy = 1;
                } else if (tsrp->status == srv_offline) {
                    osi_Log2(afsd_logp, "cm_UpdateVolumeStatusInt volume %d server reference %s offline", 
                              statep->ID, osi_LogSaveString(afsd_logp,addr));
                    allBusy = 0;
                    someOffline = 1;
                } else {
                    osi_Log2(afsd_logp, "cm_UpdateVolumeStatusInt volume %d server reference %s online", 
                              statep->ID, osi_LogSaveString(afsd_logp,addr));
                    allOffline = 0;
                    allBusy = 0;
                }
            } else {
                osi_Log2(afsd_logp, "cm_UpdateVolumeStatusInt volume %d server reference %s online", 
                          statep->ID, osi_LogSaveString(afsd_logp,addr));
            }
            cm_PutServerNoLock(tsp);
        }
    }   
    lock_ReleaseWrite(&cm_serverLock);

    osi_Log5(afsd_logp, "cm_UpdateVolumeStatusInt allDown %d allBusy %d someBusy %d someOffline %d allOffline %d", 
             allDown, allBusy, someBusy, someOffline, allOffline);

    if (allDown)
	newStatus = vl_alldown;
    else if (allBusy || (someBusy && someOffline)) 
	newStatus = vl_busy;
    else if (allOffline)
	newStatus = vl_offline;
    else
	newStatus = vl_online;

    if (statep->ID && statep->state != newStatus)
        cm_VolumeStatusNotification(volp, statep->ID, statep->state, newStatus);

    statep->state = newStatus;
}

void
cm_UpdateVolumeStatus(cm_volume_t *volp, afs_uint32 volID)
{

    if (volp->vol[RWVOL].ID == volID) {
        cm_UpdateVolumeStatusInt(volp, &volp->vol[RWVOL]);
    } else if (volp->vol[ROVOL].ID == volID) {
        cm_UpdateVolumeStatusInt(volp, &volp->vol[ROVOL]);
    } else if (volp->vol[BACKVOL].ID == volID) {
        cm_UpdateVolumeStatusInt(volp, &volp->vol[BACKVOL]);
    } else {
        /*
         * If we are called with volID == 0 then something has gone wrong.
         * Most likely a race occurred in the server volume list maintenance.
         * Since we don't know which volume's status should be updated, 
         * just update all of them that are known to exist.  Better to be 
         * correct than fast.
         */
        afs_uint32 volType;
        for ( volType = RWVOL; volType < NUM_VOL_TYPES; volType++) {
            if (volp->vol[volType].ID != 0)
                cm_UpdateVolumeStatusInt(volp, &volp->vol[volType]);
        }
    }
}

/*
** Finds all volumes that reside on this server and reorders their
** RO list according to the changed rank of server.
*/
void cm_ChangeRankVolume(cm_server_t *tsp)
{	
    int 		code;
    cm_volume_t*	volp;
    afs_int32 refCount;

    /* find volumes which might have RO copy on server*/
    lock_ObtainRead(&cm_volumeLock);
    for(volp = cm_data.allVolumesp; volp; volp=volp->allNextp)
    {
	code = 1 ;	/* assume that list is unchanged */
	InterlockedIncrement(&volp->refCount);
	lock_ReleaseRead(&cm_volumeLock);
	lock_ObtainWrite(&volp->rw);

	if ((tsp->cellp==volp->cellp) && (volp->vol[ROVOL].serversp))
	    code =cm_ChangeRankServer(&volp->vol[ROVOL].serversp, tsp);

	/* this volume list was changed */
	if ( !code )
	    cm_RandomizeServer(&volp->vol[ROVOL].serversp);

	lock_ReleaseWrite(&volp->rw);
	lock_ObtainRead(&cm_volumeLock);
        refCount = InterlockedDecrement(&volp->refCount);
	osi_assertx(refCount >= 0, "cm_volume_t refCount underflow");
    }
    lock_ReleaseRead(&cm_volumeLock);
}	

/* dump all volumes that have reference count > 0 to a file. 
 * cookie is used to identify this batch for easy parsing, 
 * and it a string provided by a caller 
 */
int cm_DumpVolumes(FILE *outputFile, char *cookie, int lock)
{
    int zilch;
    cm_volume_t *volp;
    char output[1024];
  
    if (lock) {
	lock_ObtainRead(&cm_scacheLock);
        lock_ObtainRead(&cm_volumeLock);
    }
  
    sprintf(output, "%s - dumping volumes - cm_data.currentVolumes=%d, cm_data.maxVolumes=%d\r\n", cookie, cm_data.currentVolumes, cm_data.maxVolumes);
    WriteFile(outputFile, output, (DWORD)strlen(output), &zilch, NULL);
  
    for (volp = cm_data.allVolumesp; volp; volp=volp->allNextp)
    {
        sprintf(output, "%s - volp=0x%p cell=%s name=%s rwID=%u roID=%u bkID=%u flags=0x%x refCount=%u\r\n", 
                 cookie, volp, volp->cellp->name, volp->namep, volp->vol[RWVOL].ID, volp->vol[ROVOL].ID, volp->vol[BACKVOL].ID, volp->flags, 
                 volp->refCount);
        WriteFile(outputFile, output, (DWORD)strlen(output), &zilch, NULL);
    }
    sprintf(output, "%s - Done dumping volumes.\r\n", cookie);
    WriteFile(outputFile, output, (DWORD)strlen(output), &zilch, NULL);
  
    if (lock) {
        lock_ReleaseRead(&cm_volumeLock);
	lock_ReleaseRead(&cm_scacheLock);
    }
    return (0);     
}


/* 
 * String hash function used by SDBM project.
 * It was chosen because it is fast and provides
 * decent coverage.
 */
afs_uint32 SDBMHash(const char * str)
{
    afs_uint32 hash = 0;
    size_t i, len;

    if (str == NULL)
        return 0;

    for(i = 0, len = strlen(str); i < len; i++)
    {
        hash = str[i] + (hash << 6) + (hash << 16) - hash;
    }

    return (hash & 0x7FFFFFFF);
}

/* call with volume write-locked and mutex held */
void cm_AddVolumeToNameHashTable(cm_volume_t *volp)
{
    int i;
    
    if (volp->flags & CM_VOLUMEFLAG_IN_HASH)
        return;

    i = CM_VOLUME_NAME_HASH(volp->namep);

    volp->nameNextp = cm_data.volumeNameHashTablep[i];
    cm_data.volumeNameHashTablep[i] = volp;
    volp->flags |= CM_VOLUMEFLAG_IN_HASH;
}

/* call with volume write-locked and mutex held */
void cm_RemoveVolumeFromNameHashTable(cm_volume_t *volp)
{
    cm_volume_t **lvolpp;
    cm_volume_t *tvolp;
    int i;
	
    if (volp->flags & CM_VOLUMEFLAG_IN_HASH) {
	/* hash it out first */
	i = CM_VOLUME_NAME_HASH(volp->namep);
	for (lvolpp = &cm_data.volumeNameHashTablep[i], tvolp = cm_data.volumeNameHashTablep[i];
	     tvolp;
	     lvolpp = &tvolp->nameNextp, tvolp = tvolp->nameNextp) {
	    if (tvolp == volp) {
		*lvolpp = volp->nameNextp;
		volp->flags &= ~CM_VOLUMEFLAG_IN_HASH;
                volp->nameNextp = NULL;
		break;
	    }
	}
    }
}

/* call with volume write-locked and mutex held */
void cm_AddVolumeToIDHashTable(cm_volume_t *volp, afs_uint32 volType)
{
    int i;
    struct cm_vol_state * statep;

    statep = cm_VolumeStateByType(volp, volType);

    if (statep->flags & CM_VOLUMEFLAG_IN_HASH)
        return;

    i = CM_VOLUME_ID_HASH(statep->ID);

    switch (volType) {
    case RWVOL:
        statep->nextp = cm_data.volumeRWIDHashTablep[i];
        cm_data.volumeRWIDHashTablep[i] = volp;
        break;
    case ROVOL:                                
        statep->nextp = cm_data.volumeROIDHashTablep[i];
        cm_data.volumeROIDHashTablep[i] = volp;
        break;
    case BACKVOL:
        statep->nextp = cm_data.volumeBKIDHashTablep[i];
        cm_data.volumeBKIDHashTablep[i] = volp;
        break;
    }
    statep->flags |= CM_VOLUMEFLAG_IN_HASH;
}


/* call with volume write-locked and mutex held */
void cm_RemoveVolumeFromIDHashTable(cm_volume_t *volp, afs_uint32 volType)
{
    cm_volume_t **lvolpp;
    cm_volume_t *tvolp;
    struct cm_vol_state * statep;
    int i;
	
    statep = cm_VolumeStateByType(volp, volType);

    if (statep->flags & CM_VOLUMEFLAG_IN_HASH) {
	/* hash it out first */
        i = CM_VOLUME_ID_HASH(statep->ID);

        switch (volType) {
        case RWVOL:
            lvolpp = &cm_data.volumeRWIDHashTablep[i];
            tvolp = cm_data.volumeRWIDHashTablep[i];
            break;
        case ROVOL:
            lvolpp = &cm_data.volumeROIDHashTablep[i];
            tvolp = cm_data.volumeROIDHashTablep[i];
            break;
        case BACKVOL:
            lvolpp = &cm_data.volumeBKIDHashTablep[i];
            tvolp = cm_data.volumeBKIDHashTablep[i];
            break;
        default:
            osi_assertx(0, "invalid volume type");
        }
	do {
	    if (tvolp == volp) {
		*lvolpp = statep->nextp;
                statep->flags &= ~CM_VOLUMEFLAG_IN_HASH;
                statep->nextp = NULL;
		break;
	    }

            lvolpp = &tvolp->vol[volType].nextp;
            tvolp = tvolp->vol[volType].nextp;
	} while(tvolp);
    }
}

/* must be called with cm_volumeLock write-locked! */
void cm_AdjustVolumeLRU(cm_volume_t *volp)
{
    if (volp == cm_data.volumeLRULastp)
        cm_data.volumeLRULastp = (cm_volume_t *) osi_QPrev(&volp->q);
    if (volp->flags & CM_VOLUMEFLAG_IN_LRU_QUEUE)
        osi_QRemoveHT((osi_queue_t **) &cm_data.volumeLRUFirstp, (osi_queue_t **) &cm_data.volumeLRULastp, &volp->q);
    osi_QAdd((osi_queue_t **) &cm_data.volumeLRUFirstp, &volp->q);
    volp->flags |= CM_VOLUMEFLAG_IN_LRU_QUEUE;
    if (!cm_data.volumeLRULastp) 
        cm_data.volumeLRULastp = volp;
}

/* must be called with cm_volumeLock write-locked! */
void cm_MoveVolumeToLRULast(cm_volume_t *volp)
{
    if (volp == cm_data.volumeLRULastp)
        return;

    if (volp == cm_data.volumeLRUFirstp)
        cm_data.volumeLRUFirstp = (cm_volume_t *) osi_QNext(&volp->q);
    if (volp->flags & CM_VOLUMEFLAG_IN_LRU_QUEUE)
        osi_QRemoveHT((osi_queue_t **) &cm_data.volumeLRUFirstp, (osi_queue_t **) &cm_data.volumeLRULastp, &volp->q);
    osi_QAddT((osi_queue_t **) &cm_data.volumeLRUFirstp, (osi_queue_t **) &cm_data.volumeLRULastp, &volp->q);
    volp->flags |= CM_VOLUMEFLAG_IN_LRU_QUEUE;
    if (!cm_data.volumeLRULastp) 
        cm_data.volumeLRULastp = volp;
}

/* must be called with cm_volumeLock write-locked! */
void cm_RemoveVolumeFromLRU(cm_volume_t *volp)
{
    if (volp->flags & CM_VOLUMEFLAG_IN_LRU_QUEUE) {
        if (volp == cm_data.volumeLRULastp)
            cm_data.volumeLRULastp = (cm_volume_t *) osi_QPrev(&volp->q);
        osi_QRemoveHT((osi_queue_t **) &cm_data.volumeLRUFirstp, (osi_queue_t **) &cm_data.volumeLRULastp, &volp->q);
        volp->flags &= ~CM_VOLUMEFLAG_IN_LRU_QUEUE;
    }
}

static char * volstatus_str(enum volstatus vs)
{
    switch (vs) {
    case vl_online:
        return "online";
    case vl_busy:
        return "busy";
    case vl_offline:
        return "offline";
    case vl_alldown:
        return "alldown";
    default:
        return "unknown";
    }
}

void cm_VolumeStatusNotification(cm_volume_t * volp, afs_uint32 volID, enum volstatus old, enum volstatus new)
{
    char volstr[CELL_MAXNAMELEN + VL_MAXNAMELEN]="";
    char *ext = "";

    if (volID == volp->vol[RWVOL].ID)
        ext = "";
    else if (volID == volp->vol[ROVOL].ID)
        ext = ".readonly";
    else if (volID == volp->vol[BACKVOL].ID)
        ext = ".backup";
    else
        ext = ".nomatch";
    snprintf(volstr, sizeof(volstr), "%s:%s%s", volp->cellp->name, volp->namep, ext);

    osi_Log4(afsd_logp, "VolumeStatusNotification: %-48s [%10u] (%s -> %s)",
             osi_LogSaveString(afsd_logp, volstr), volID, volstatus_str(old), volstatus_str(new));

    cm_VolStatus_Change_Notification(volp->cellp->cellID, volID, new);
}       

enum volstatus cm_GetVolumeStatus(cm_volume_t *volp, afs_uint32 volID)
{
    cm_vol_state_t * statep = cm_VolumeStateByID(volp, volID);
    if (statep)
        return statep->state;
    else
        return vl_unknown;
}

/* Renew .readonly volume callbacks that are more than
 * 30 minutes old.  (A volume callback is issued for 2 hours.)
 */
void 
cm_VolumeRenewROCallbacks(void)
{
    cm_volume_t * volp;
    time_t minexp = time(NULL) + 90 * 60;

    lock_ObtainRead(&cm_volumeLock);
    for (volp = cm_data.allVolumesp; volp; volp=volp->allNextp) {
        if ( volp->cbExpiresRO > 0 && volp->cbExpiresRO < minexp) {
            cm_req_t      req;
            cm_fid_t      fid;
            cm_scache_t * scp;

            cm_SetFid(&fid, volp->cellp->cellID, volp->vol[ROVOL].ID, 1, 1);

            cm_InitReq(&req);

            lock_ReleaseRead(&cm_volumeLock);
            if (cm_GetSCache(&fid, &scp, cm_rootUserp, &req) == 0) {
                lock_ObtainWrite(&scp->rw);
                cm_GetCallback(scp, cm_rootUserp, &req, 1);
                lock_ReleaseWrite(&scp->rw);
                cm_ReleaseSCache(scp);
            }
            lock_ObtainRead(&cm_volumeLock);
        }
    }
    lock_ReleaseRead(&cm_volumeLock);
}

cm_vol_state_t * 
cm_VolumeStateByType(cm_volume_t *volp, afs_uint32 volType)
{
    return &volp->vol[volType];
}

cm_vol_state_t * 
cm_VolumeStateByID(cm_volume_t *volp, afs_uint32 id)
{
    cm_vol_state_t * statep = NULL;

    if (id == volp->vol[RWVOL].ID)
        statep = &volp->vol[RWVOL];
    else if (id == volp->vol[ROVOL].ID)
        statep = &volp->vol[ROVOL];
    else if (id == volp->vol[BACKVOL].ID)
        statep = &volp->vol[BACKVOL];

    return(statep);
}

cm_vol_state_t * 
cm_VolumeStateByName(cm_volume_t *volp, char *volname)
{
    size_t len = strlen(volname);
    cm_vol_state_t *statep;

    if (cm_stricmp_utf8N(".readonly", &volname[len-9]) == 0)
        statep = &volp->vol[ROVOL];
    else if (cm_stricmp_utf8N(".backup", &volname[len-7]) == 0)
        statep = &volp->vol[BACKVOL];
    else 
        statep = &volp->vol[RWVOL];

    return statep;
}

