/* * 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 #include #ifndef DJGPP #include #endif /* !DJGPP */ #include #include #include #include #ifndef DJGPP #include #else #include #endif #include "afsd.h" osi_rwlock_t cm_connLock; long RDRtimeout = CM_CONN_DEFAULTRDRTIMEOUT; #define LANMAN_WKS_PARAM_KEY "SYSTEM\\CurrentControlSet\\Services\\lanmanworkstation\\parameters" #define LANMAN_WKS_SESSION_TIMEOUT "SessTimeout" afs_int32 cryptall = 0; void cm_PutConn(cm_conn_t *connp) { lock_ObtainWrite(&cm_connLock); osi_assert(connp->refCount-- > 0); lock_ReleaseWrite(&cm_connLock); } void cm_InitConn(void) { static osi_once_t once; long code; DWORD sessTimeout; HKEY parmKey; if (osi_Once(&once)) { lock_InitializeRWLock(&cm_connLock, "connection global lock"); /* keisa - read timeout value for lanmanworkstation service. * It is used as hardtimeout for connections. * Default value is 45 */ code = RegOpenKeyEx(HKEY_LOCAL_MACHINE, LANMAN_WKS_PARAM_KEY, 0, KEY_QUERY_VALUE, &parmKey); if (code == ERROR_SUCCESS) { DWORD dummyLen = sizeof(sessTimeout); code = RegQueryValueEx(parmKey, LANMAN_WKS_SESSION_TIMEOUT, NULL, NULL, (BYTE *) &sessTimeout, &dummyLen); if (code == ERROR_SUCCESS) { afsi_log("lanmanworkstation : SessTimeout %d", sessTimeout); RDRtimeout = sessTimeout; } else { RDRtimeout = CM_CONN_DEFAULTRDRTIMEOUT; } } osi_EndOnce(&once); } } void cm_InitReq(cm_req_t *reqp) { memset((char *)reqp, 0, sizeof(cm_req_t)); #ifndef DJGPP reqp->startTime = GetCurrentTime(); #else gettimeofday(&reqp->startTime, NULL); #endif } long cm_GetServerList(struct cm_fid *fidp, struct cm_user *userp, struct cm_req *reqp, cm_serverRef_t **serverspp) { long code; cm_volume_t *volp = NULL; cm_serverRef_t *serversp = NULL; cm_cell_t *cellp = NULL; if (!fidp) { *serverspp = NULL; return 0; } cellp = cm_FindCellByID(fidp->cell); if (!cellp) return CM_ERROR_NOSUCHCELL; code = cm_GetVolumeByID(cellp, fidp->volume, userp, reqp, &volp); if (code) return code; if (fidp->volume == volp->rwID) serversp = volp->rwServersp; else if (fidp->volume == volp->roID) serversp = volp->roServersp; else if (fidp->volume == volp->bkID) serversp = volp->bkServersp; else serversp = NULL; cm_PutVolume(volp); *serverspp = serversp; return 0; } /* * Analyze the error return from an RPC. Determine whether or not to retry, * and if we're going to retry, determine whether failover is appropriate, * and whether timed backoff is appropriate. * * If the error code is from cm_Conn() or friends, it will be a CM_ERROR code. * Otherwise it will be an RPC code. This may be a UNIX code (e.g. EDQUOT), or * it may be an RX code, or it may be a special code (e.g. VNOVOL), or it may * be a security code (e.g. RXKADEXPIRED). * * If the error code is from cm_Conn() or friends, connp will be NULL. * * For VLDB calls, fidp will be NULL. * * volSyncp and/or cbrp may also be NULL. */ int cm_Analyze(cm_conn_t *connp, cm_user_t *userp, cm_req_t *reqp, struct cm_fid *fidp, AFSVolSync *volSyncp, cm_callbackRequest_t *cbrp, long errorCode) { cm_server_t *serverp; cm_serverRef_t *serversp, *tsrp; cm_ucell_t *ucellp; int retry = 0; int dead_session; osi_Log2(afsd_logp, "cm_Analyze connp 0x%x, code %d", (long) connp, errorCode); /* no locking required, since connp->serverp never changes after * creation */ dead_session = (userp->cellInfop == NULL); if (connp) serverp = connp->serverp; /* Update callback pointer */ if (cbrp && errorCode == 0) cbrp->serverp = connp->serverp; /* If not allowed to retry, don't */ if (reqp->flags & CM_REQ_NORETRY) goto out; /* if timeout - check that is did not exceed the SMB timeout and retry */ if (errorCode == CM_ERROR_TIMEDOUT) { long timeUsed, timeLeft; /* timeleft - get if from reqp the same way as cmXonnByMServers does */ #ifndef DJGPP timeUsed = (GetCurrentTime() - reqp->startTime) / 1000; #else gettimeofday(&now, NULL); timeUsed = sub_time(now, reqp->startTime) / 1000; #endif /* leave 5 seconds margin for sleep */ timeLeft = RDRtimeout - timeUsed; if (timeLeft > 5) { thrd_Sleep(3000); cm_CheckServers(CM_FLAG_CHECKDOWNSERVERS, NULL); retry = 1; } } /* if all servers are offline, mark them non-busy and start over */ if (errorCode == CM_ERROR_ALLOFFLINE) { osi_Log0(afsd_logp, "cm_Analyze passed CM_ERROR_ALLOFFLINE."); thrd_Sleep(5000); /* cm_ForceUpdateVolume marks all servers as non_busy */ cm_ForceUpdateVolume(fidp, userp, reqp); retry = 1; } /* if all servers are busy, mark them non-busy and start over */ if (errorCode == CM_ERROR_ALLBUSY) { cm_GetServerList(fidp, userp, reqp, &serversp); for (tsrp = serversp; tsrp; tsrp=tsrp->next) { if (tsrp->status == busy) tsrp->status = not_busy; } thrd_Sleep(5000); retry = 1; } /* special codes: VBUSY and VRESTARTING */ if (errorCode == VBUSY || errorCode == VRESTARTING) { cm_GetServerList(fidp, userp, reqp, &serversp); for (tsrp = serversp; tsrp; tsrp=tsrp->next) { if (tsrp->server == serverp && tsrp->status == not_busy) { tsrp->status = busy; break; } } retry = 1; } /* special codes: missing volumes */ if (errorCode == VNOVOL || errorCode == VMOVED || errorCode == VOFFLINE || errorCode == VSALVAGE || errorCode == VNOSERVICE) { /* Log server being offline for this volume */ osi_Log4(afsd_logp, "cm_Analyze found server %d.%d.%d.%d marked offline for a volume", ((serverp->addr.sin_addr.s_addr & 0xff)), ((serverp->addr.sin_addr.s_addr & 0xff00)>> 8), ((serverp->addr.sin_addr.s_addr & 0xff0000)>> 16), ((serverp->addr.sin_addr.s_addr & 0xff000000)>> 24)); /* Create Event Log message */ { HANDLE h; char *ptbuf[1]; char s[100]; h = RegisterEventSource(NULL, AFS_DAEMON_EVENT_NAME); sprintf(s, "cm_Analyze: Server %d.%d.%d.%d reported volume %d as missing.", ((serverp->addr.sin_addr.s_addr & 0xff)), ((serverp->addr.sin_addr.s_addr & 0xff00)>> 8), ((serverp->addr.sin_addr.s_addr & 0xff0000)>> 16), ((serverp->addr.sin_addr.s_addr & 0xff000000)>> 24), fidp->volume); ptbuf[0] = s; ReportEvent(h, EVENTLOG_WARNING_TYPE, 0, 1009, NULL, 1, 0, ptbuf, NULL); DeregisterEventSource(h); } /* Mark server offline for this volume */ cm_GetServerList(fidp, userp, reqp, &serversp); for (tsrp = serversp; tsrp; tsrp=tsrp->next) { if (tsrp->server == serverp) tsrp->status = offline; } retry = 1; } /* RX codes */ if (errorCode == RX_CALL_TIMEOUT) { /* server took longer than hardDeadTime * don't mark server as down but don't retry * this is to prevent the SMB session from timing out * In addition, we log an event to the event log */ #ifndef DJGPP HANDLE h; char *ptbuf[1]; char s[100]; h = RegisterEventSource(NULL, AFS_DAEMON_EVENT_NAME); sprintf(s, "cm_Analyze: HardDeadTime exceeded."); ptbuf[0] = s; ReportEvent(h, EVENTLOG_WARNING_TYPE, 0, 1009, NULL, 1, 0, ptbuf, NULL); DeregisterEventSource(h); #endif /* !DJGPP */ retry = 0; osi_Log0(afsd_logp, "cm_Analyze: hardDeadTime exceeded"); } else if (errorCode >= -64 && errorCode < 0) { /* mark server as down */ lock_ObtainMutex(&serverp->mx); serverp->flags |= CM_SERVERFLAG_DOWN; lock_ReleaseMutex(&serverp->mx); retry = 1; } if (errorCode == RXKADEXPIRED && !dead_session) { lock_ObtainMutex(&userp->mx); ucellp = cm_GetUCell(userp, serverp->cellp); if (ucellp->ticketp) { free(ucellp->ticketp); ucellp->ticketp = NULL; } ucellp->flags &= ~CM_UCELLFLAG_RXKAD; ucellp->gen++; lock_ReleaseMutex(&userp->mx); retry = 1; } if (retry && dead_session) retry = 0; out: /* drop this on the way out */ if (connp) cm_PutConn(connp); /* retry until we fail to find a connection */ return retry; } long cm_ConnByMServers(cm_serverRef_t *serversp, cm_user_t *usersp, cm_req_t *reqp, cm_conn_t **connpp) { long code; cm_serverRef_t *tsrp; cm_server_t *tsp; long firstError = 0; int someBusy = 0, someOffline = 0, allDown = 1; long timeUsed, timeLeft, hardTimeLeft; #ifdef DJGPP struct timeval now; #endif /* DJGPP */ *connpp = NULL; #ifndef DJGPP timeUsed = (GetCurrentTime() - reqp->startTime) / 1000; #else gettimeofday(&now, NULL); timeUsed = sub_time(now, reqp->startTime) / 1000; #endif /* leave 5 seconds margin of safety */ timeLeft = RDRtimeout - timeUsed - 5; hardTimeLeft = timeLeft; /* Time enough to do an RPC? */ if (timeLeft < 1) { return CM_ERROR_TIMEDOUT; } lock_ObtainWrite(&cm_serverLock); for(tsrp = serversp; tsrp; tsrp=tsrp->next) { tsp = tsrp->server; tsp->refCount++; lock_ReleaseWrite(&cm_serverLock); if (!(tsp->flags & CM_SERVERFLAG_DOWN)) { allDown = 0; if (tsrp->status == busy) someBusy = 1; else if (tsrp->status == offline) someOffline = 1; else { code = cm_ConnByServer(tsp, usersp, connpp); if (code == 0) { cm_PutServer(tsp); /* Set RPC timeout */ if (timeLeft > CM_CONN_CONNDEADTIME) timeLeft = CM_CONN_CONNDEADTIME; if (hardTimeLeft > CM_CONN_HARDDEADTIME) hardTimeLeft = CM_CONN_HARDDEADTIME; lock_ObtainMutex(&(*connpp)->mx); rx_SetConnDeadTime((*connpp)->callp, timeLeft); rx_SetConnHardDeadTime((*connpp)->callp, (u_short) hardTimeLeft); lock_ReleaseMutex(&(*connpp)->mx); return 0; } if (firstError == 0) firstError = code; } } lock_ObtainWrite(&cm_serverLock); osi_assert(tsp->refCount-- > 0); } lock_ReleaseWrite(&cm_serverLock); if (firstError == 0) { if (someBusy) firstError = CM_ERROR_ALLBUSY; else if (someOffline) firstError = CM_ERROR_ALLOFFLINE; else if (!allDown && serversp) firstError = CM_ERROR_TIMEDOUT; /* Only return CM_ERROR_NOSUCHVOLUME if there are no servers for this volume */ else firstError = CM_ERROR_NOSUCHVOLUME; } osi_Log1(afsd_logp, "cm_ConnByMServers returning %x", firstError); return firstError; } /* called with a held server to GC all bad connections hanging off of the server */ void cm_GCConnections(cm_server_t *serverp) { cm_conn_t *tcp; cm_conn_t **lcpp; cm_user_t *userp; lock_ObtainWrite(&cm_connLock); lcpp = &serverp->connsp; for(tcp = *lcpp; tcp; tcp = *lcpp) { userp = tcp->userp; if (userp && tcp->refCount == 0 && (userp->vcRefs == 0)) { /* do the deletion of this guy */ cm_ReleaseUser(userp); *lcpp = tcp->nextp; rx_DestroyConnection(tcp->callp); lock_FinalizeMutex(&tcp->mx); free(tcp); } else { /* just advance to the next */ lcpp = &tcp->nextp; } } lock_ReleaseWrite(&cm_connLock); } static void cm_NewRXConnection(cm_conn_t *tcp, cm_ucell_t *ucellp, cm_server_t *serverp) { unsigned short port; int serviceID; int secIndex; struct rx_securityClass *secObjp; afs_int32 level; if (serverp->type == CM_SERVER_VLDB) { port = htons(7003); serviceID = 52; } else { osi_assert(serverp->type == CM_SERVER_FILE); port = htons(7000); serviceID = 1; } if (ucellp->flags & CM_UCELLFLAG_RXKAD) { secIndex = 2; if (cryptall) { level = rxkad_crypt; tcp->cryptlevel = rxkad_crypt; } else { level = rxkad_clear; } secObjp = rxkad_NewClientSecurityObject(level, &ucellp->sessionKey, ucellp->kvno, ucellp->ticketLen, ucellp->ticketp); } else { /* normal auth */ secIndex = 0; secObjp = rxnull_NewClientSecurityObject(); } osi_assert(secObjp != NULL); tcp->callp = rx_NewConnection(serverp->addr.sin_addr.s_addr, port, serviceID, secObjp, secIndex); rx_SetConnDeadTime(tcp->callp, CM_CONN_CONNDEADTIME); rx_SetConnHardDeadTime(tcp->callp, CM_CONN_HARDDEADTIME); tcp->ucgen = ucellp->gen; if (secObjp) rxs_Release(secObjp); /* Decrement the initial refCount */ } long cm_ConnByServer(cm_server_t *serverp, cm_user_t *userp, cm_conn_t **connpp) { cm_conn_t *tcp; cm_ucell_t *ucellp; lock_ObtainMutex(&userp->mx); lock_ObtainWrite(&cm_connLock); for(tcp = serverp->connsp; tcp; tcp=tcp->nextp) { if (tcp->userp == userp) break; } /* find ucell structure */ ucellp = cm_GetUCell(userp, serverp->cellp); if (!tcp) { tcp = malloc(sizeof(*tcp)); memset(tcp, 0, sizeof(*tcp)); tcp->nextp = serverp->connsp; serverp->connsp = tcp; tcp->userp = userp; cm_HoldUser(userp); lock_InitializeMutex(&tcp->mx, "cm_conn_t mutex"); tcp->serverp = serverp; tcp->cryptlevel = rxkad_clear; cm_NewRXConnection(tcp, ucellp, serverp); tcp->refCount = 1; } else { if ((tcp->ucgen < ucellp->gen) || (tcp->cryptlevel != cryptall)) { rx_DestroyConnection(tcp->callp); cm_NewRXConnection(tcp, ucellp, serverp); } tcp->refCount++; } lock_ReleaseWrite(&cm_connLock); lock_ReleaseMutex(&userp->mx); /* return this pointer to our caller */ osi_Log1(afsd_logp, "cm_ConnByServer returning conn 0x%x", (long) tcp); *connpp = tcp; return 0; } long cm_Conn(struct cm_fid *fidp, struct cm_user *userp, cm_req_t *reqp, cm_conn_t **connpp) { long code; cm_serverRef_t *serversp; code = cm_GetServerList(fidp, userp, reqp, &serversp); if (code) { *connpp = NULL; return code; } code = cm_ConnByMServers(serversp, userp, reqp, connpp); return code; }