/*++ Copyright (c) 1994 Microsoft Corporation Module Name: session.cxx Abstract: Contains functions for maintaining the global session list and SESSION_INFO specific functions Contents: AcquireSessionLock ReleaseSessionLock CleanupSessions (CleanupViewList) FindOrCreateSession (CreateSession) (DestroySession) ReferenceSession DereferenceSession AcquireViewLock ReleaseViewLock GopherTransaction IsServerGopherPlus IsGopherPlusSession SearchSessionsForAttribute Author: Richard L Firth (rfirth) 19-Oct-1994 Environment: Win32 DLL Revision History: 19-Oct-1994 rfirth Created --*/ #include #include "gfrapih.h" // // manifests // #define NULL_HANDLE ((HANDLE)0) // // private prototypes // PRIVATE VOID CleanupViewList( IN LPSESSION_INFO SessionInfo, IN VIEW_TYPE ViewType ); PRIVATE LPSESSION_INFO CreateSession( IN LPSTR Host, IN DWORD Port, OUT LPDWORD Error ); PRIVATE VOID DestroySession( IN LPSESSION_INFO SessionInfo ); // // data // PUBLIC SERIALIZED_LIST SessionList; DEBUG_DATA(LONG, NumberOfSessions, 0); // // functions // VOID AcquireSessionLock( VOID ) /*++ Routine Description: Acquires the SESSION_INFO list lock Arguments: None. Return Value: None. --*/ { LockSerializedList(&SessionList); } VOID ReleaseSessionLock( VOID ) /*++ Routine Description: Releases the SESSION_INFO list lock Arguments: None. Return Value: None. --*/ { UnlockSerializedList(&SessionList); } VOID CleanupSessions( VOID ) /*++ Routine Description: Tries to Remove all SESSION_INFOs from the session list, and terminate all active operations Arguments: None. Return Value: None. --*/ { DEBUG_ENTER((DBG_GOPHER, None, "CleanupSessions", NULL )); while (1) { LPSESSION_INFO sessionInfo; // // find the next SESSION_INFO to delete. Because we may cause the entry // currently at the head of the list to be deleted during this loop, we // must walk the list each time. We may also end up with a list of items // that are marked for delete, but cannot be deleted until the threads // that own them complete their current operations // AcquireSessionLock(); for (sessionInfo = (LPSESSION_INFO)HeadOfSerializedList(&SessionList); (sessionInfo != (LPSESSION_INFO)&SessionList.List.Flink) && !(sessionInfo->Flags & SI_CLEANUP); sessionInfo = (LPSESSION_INFO)sessionInfo->List.Flink) { // // empty loop // } if (sessionInfo == (LPSESSION_INFO)&SessionList.List.Flink) { DEBUG_PRINT(SESSION, INFO, ("end of list\n" )); ReleaseSessionLock(); break; } // // mark this SESSION_INFO as being cleaned up, in case it does not get // removed from the list (some other thread is accessing it) // sessionInfo->Flags |= SI_CLEANUP; // // increment the reference count so that we can release the list lock // ReferenceSession(sessionInfo); ReleaseSessionLock(); // // now we have a pointer to a SESSION_INFO that cannot be deleted until // after we have dereferenced it. Dereference any items in the Find and // File lists. Note that had we not referenced the SESSION_INFO above, // it might have gotten deleted after cleaning up the Find list, and we // would be in danger of passing a bogus pointer to the second cleanup // view list call below // CleanupViewList(sessionInfo, ViewTypeFind); CleanupViewList(sessionInfo, ViewTypeFile); // // finally, dereference the session. This may cause it to be deleted, // and for the list to be changed // DereferenceSession(sessionInfo); } DEBUG_LEAVE(0); } PRIVATE VOID CleanupViewList( IN LPSESSION_INFO SessionInfo, IN VIEW_TYPE ViewType ) /*++ Routine Description: Cleans up a VIEW_INFO list on a SESSION_INFO Arguments: SessionInfo - pointer to SESSION_INFO we are cleaning up ViewType - identifies which VIEW_INFO list to clean up Return Value: None. --*/ { DEBUG_ENTER((DBG_GOPHER, None, "CleanupViewList", "%x, %x", SessionInfo, ViewType )); // // walk this VIEW_INFO list trying to delete everything by dereferencing // while (1) { LPSERIALIZED_LIST viewList; LPVIEW_INFO viewInfo; HINTERNET handle; AcquireViewLock(SessionInfo, ViewType); viewList = &SessionInfo->FindList; for (viewInfo = (LPVIEW_INFO)HeadOfSerializedList(viewList); (viewInfo != (LPVIEW_INFO)&viewList->List.Flink) && !(viewInfo->Flags & VI_CLEANUP); viewInfo = (LPVIEW_INFO)viewInfo->List.Flink) { // // empty loop // } if (viewInfo == (LPVIEW_INFO)&viewList->List.Flink) { DEBUG_PRINT(SESSION, INFO, ("end of list\n" )); ReleaseViewLock(SessionInfo, ViewType); break; } // // mark this VIEW_INFO as being cleaned-up, so we don't hit it again if // we don't delete it from the list this time // viewInfo->Flags |= VI_CLEANUP; // // safe to release the view lock // ReleaseViewLock(SessionInfo, ViewType); // // now dereference the VIEW_INFO. This may destroy it, but cannot // destroy the SESSION_INFO, since we added an extra reference in // CleanupSessions() // DereferenceView(viewInfo); } DEBUG_LEAVE(0); } LPSESSION_INFO FindOrCreateSession( IN LPSTR Host, IN DWORD Port, OUT LPDWORD Error ) /*++ Routine Description: Locates a SESSION_INFO that contains (Host, Port), or creates a new SESSION_INFO BUGBUG - need to do the following: resolve the host name find host by name/port or address/port Arguments: Host - pointer to host name where gopher server lives Port - port at which gopher server listens Error - place to return error Return Value: LPSESSION_INFO Success - pointer to session info. Created contains TRUE if we created the SESSION_INFO, else FALSE Failure - NULL Error contains reason for failure --*/ { LPSESSION_INFO sessionInfo; BOOL found; AcquireSessionLock(); found = FALSE; for (sessionInfo = (LPSESSION_INFO)SessionList.List.Flink; sessionInfo != (LPSESSION_INFO)&SessionList.List; sessionInfo = (LPSESSION_INFO)(sessionInfo->List.Flink)) { if (!stricmp(sessionInfo->Host, Host) && (sessionInfo->Port == Port)) { found = TRUE; break; } } if (!found) { sessionInfo = CreateSession(Host, Port, Error); if (sessionInfo != NULL) { InsertAtHeadOfSerializedList(&SessionList, &sessionInfo->List); } } if (sessionInfo != NULL) { // // the reference count will be at least 2 // ReferenceSession(sessionInfo); } ReleaseSessionLock(); return sessionInfo; } PRIVATE LPSESSION_INFO CreateSession( IN LPSTR Host, IN DWORD Port, OUT LPDWORD Error ) /*++ Routine Description: Creates and initializes a SESSION_INFO 'object' Arguments: Host - pointer to host name/ip address Port - host port Error - place to return reason for failure Return Value: LPSESSION_INFO Success - pointer to initialized session info Failure - NULL Error contains reason for failure --*/ { LPSESSION_INFO sessionInfo; LPSTR hostName = NULL; DWORD error; DEBUG_ENTER((DBG_GOPHER, Pointer, "CreateSession", "%q, %d, %x", Host, Port, Error )); sessionInfo = NEW(SESSION_INFO); if (sessionInfo != NULL) { hostName = NEW_STRING(Host); if (hostName != NULL) { error = AllocateHandle((LPVOID)sessionInfo, &sessionInfo->Handle); if (error == ERROR_SUCCESS) { InitializeListHead(&sessionInfo->List); sessionInfo->Host = hostName; sessionInfo->Port = Port; InitializeSerializedList(&sessionInfo->FindList); InitializeSerializedList(&sessionInfo->FileList); SESSION_CREATED(); } } else { error = ERROR_NOT_ENOUGH_MEMORY; } } else { error = ERROR_NOT_ENOUGH_MEMORY; } if (error != ERROR_SUCCESS) { if (hostName != NULL) { DEL_STRING(hostName); } if (sessionInfo != NULL) { DEL(sessionInfo); } sessionInfo = NULL; } DEBUG_ERROR(SESSION, error); *Error = error; DEBUG_LEAVE(sessionInfo); return sessionInfo; } PRIVATE VOID DestroySession( IN LPSESSION_INFO SessionInfo ) /*++ Routine Description: Opposite of CreateSession - removes a SESSION_INFO from SessionList and frees all resources owned by the SESSION_INFO and finally frees the memory Assumes: 1. SessionListLock is held 2. SessionInfo is not on any lists 3. The SERIALIZED_LISTs have already been created Arguments: SessionInfo - pointer to SESSION_INFO to delete Return Value: None. --*/ { DEBUG_ENTER((DBG_GOPHER, None, "DestroySession", "%x", SessionInfo )); INET_DEBUG_ASSERT(SessionInfo->List.Flink == NULL); INET_DEBUG_ASSERT(SessionInfo->List.Blink == NULL); INET_ASSERT(SessionInfo->ReferenceCount == 0); INET_ASSERT(IsSerializedListEmpty(&SessionInfo->FindList)); INET_DEBUG_ASSERT(!IsLockHeld(&SessionInfo->FindList)); INET_ASSERT(IsSerializedListEmpty(&SessionInfo->FileList)); INET_DEBUG_ASSERT(!IsLockHeld(&SessionInfo->FileList)); if (SessionInfo->Handle) { FreeHandle(SessionInfo->Handle); } if (SessionInfo->Host != NULL) { DEL(SessionInfo->Host); } TerminateSerializedList(&SessionInfo->FindList); TerminateSerializedList(&SessionInfo->FileList); DEL(SessionInfo); SESSION_DESTROYED(); DEBUG_LEAVE(0); } VOID ReferenceSession( IN LPSESSION_INFO SessionInfo ) /*++ Routine Description: Increases the reference count of a SESSION_INFO Arguments: Session - pointer to SESSION_INFO to reference Return Value: None. --*/ { INET_ASSERT(SessionInfo != NULL); InterlockedIncrement(&SessionInfo->ReferenceCount); } LPSESSION_INFO DereferenceSession( IN LPSESSION_INFO SessionInfo ) /*++ Routine Description: Reduces the reference count of a SESSION_INFO. If it goes to zero, the SESSION_INFO is removed from the SessionList and is deallocated Arguments: SessionInfo - pointer to SESSION_INFO to dereference Return Value: LPSESSION_INFO NULL - SessionInfo was deleted !NULL - Reference count still >0 --*/ { INET_ASSERT(SessionInfo); INET_ASSERT(SessionInfo->ReferenceCount >= 1); // // use InterlockedDecrement to dereference the session. If it goes to zero // acquire the session lock, check if the reference count is still zero, // and if so, remove the session from the session list // if (InterlockedDecrement(&SessionInfo->ReferenceCount) == 0) { AcquireSessionLock(); if (SessionInfo->ReferenceCount == 0) { RemoveFromSerializedList(&SessionList, (PLIST_ENTRY)SessionInfo); DestroySession(SessionInfo); SessionInfo = NULL; } ReleaseSessionLock(); } return SessionInfo; } VOID AcquireViewLock( IN LPSESSION_INFO SessionInfo, IN VIEW_TYPE ViewType ) /*++ Routine Description: Acquires one of the SessionInfo View locks Arguments: SessionInfo - pointer to SESSION_INFO ViewType - identifies which list to lock Return Value: None. --*/ { LPSERIALIZED_LIST list; INET_ASSERT(SessionInfo != NULL); INET_ASSERT((ViewType == ViewTypeFile) || (ViewType == ViewTypeFind)); list = (ViewType == ViewTypeFile) ? &SessionInfo->FileList : &SessionInfo->FindList ; LockSerializedList(list); } VOID ReleaseViewLock( IN LPSESSION_INFO SessionInfo, IN VIEW_TYPE ViewType ) /*++ Routine Description: Releases the SessionInfo View lock Arguments: SessionInfo - pointer to SESSION_INFO ViewType - identifies which list to lock Return Value: None. --*/ { LPSERIALIZED_LIST list; INET_ASSERT(SessionInfo != NULL); INET_ASSERT((ViewType == ViewTypeFile) || (ViewType == ViewTypeFind)); list = (ViewType == ViewTypeFile) ? &SessionInfo->FileList : &SessionInfo->FindList ; UnlockSerializedList(list); } DWORD GopherTransaction( IN LPVIEW_INFO ViewInfo ) /*++ Routine Description: Performs an 'atomic' gopher operation. Connects to a server (if it isn't already connected (in the future?)), sends a request and receives the entire response message. The connection is terminated Arguments: ViewInfo - pointer to VIEW_INFO describing gopher server to talk to, request and buffer for response Return Value: DWORD Success - ERROR_SUCCESS Failure - Winsock error --*/ { DWORD error; INET_ASSERT(ViewInfo != NULL); error = GopherConnect(ViewInfo); if (error == ERROR_SUCCESS) { error = GopherSendRequest(ViewInfo); if (error == ERROR_SUCCESS) { DWORD bytesReceived; // // receive the first part of the response. We don't care about the // number of bytes received at this point // // If the response is completed and the connection is not persistent // or an error occurs, the connection will be closed // ViewInfo->BufferInfo->Flags |= BI_FIRST_RECEIVE; error = GopherReceiveResponse(ViewInfo, &bytesReceived); } } return error; } DWORD IsServerGopherPlus( IN LPSESSION_INFO SessionInfo, OUT LPBOOL Answer ) /*++ Routine Description: Tries to determine whether a gopher server identified by Session is gopher+. The caller should already have determined that we don't know the type of gopher server described by Session and should modify the flags based on a successful return from this function Arguments: SessionInfo - pointer to SESSION_INFO describing the (unknown) gopher server Answer - pointer to place to put the answer Return Value: DWORD Success - ERROR_SUCCESS Failure - WSA error --*/ { /* DWORD error; BYTE buffer[GOPHER_PLUS_INFO_TOKEN_LENGTH + 2]; // "+INFO" // + 1 for possible ':' // + 1 for ' ' BOOL receiveComplete; // // in order to find out the type of gopher server, we send a request for // gopher+ info of the root directory. We will get back either the gopher+ // information or a gopher0 server will return the directory list (or we // will get an error). Therefore, if the transaction doesn't result in an // error, we can say that if the buffer starts with "+INFO" then the // server is gopher+ else plain gopher // error = GopherTransaction(SessionInfo, GOPHER_PLUS_INFO_REQUEST, TRUE, sizeof(buffer), buffer, NULL, &receiveComplete ); // // in both gopher+ and gopher server cases, the server should want to // return more data than we've supplied buffer for (7 bytes!). In the // case of gopher+, it will be trying to return the +INFO block for // the directory entry. In the case of gopher, it will be trying to // return the entire default directory list. Either way, we don't care // to take the data: "+INFO[:] " being present or not at the start of // the buffer is good enough for us, so just close the session and // examine what we have // DisconnectFromServer(SessionInfo, ); if (error == ERROR_SUCCESS) { register DWORD matchLength; matchLength = IsGopherPlusToken(GOPHER_PLUS_INFO_TOKEN, GOPHER_PLUS_INFO_TOKEN_LENGTH, buffer, sizeof(buffer) ); *Answer = (BOOL)(matchLength != 0); IF_DEBUG(SESSION) { DBGPRINT(DBG_INFO, "IsServerGopherPlus", ("Server \"%s\" %s gopher+\n", SessionInfo->Host, (matchLength == 0) ? "NOT" : "IS" )); } } else { IF_DEBUG(SESSION) { DBGPRINT(DBG_ERROR, "IsServerGopherPlus", ("GopherTransaction() returns %d\n", error )); } } return error; */ *Answer = FALSE; return ERROR_SUCCESS; } BOOL IsGopherPlusSession( IN LPSESSION_INFO SessionInfo ) /*++ Routine Description: Returns TRUE if Session is a session with a gopher+ server Arguments: SessionInfo - pointer to SESSION_INFO describing gopher[+] server Return Value: BOOL --*/ { return (BOOL)SessionInfo->Flags & SI_GOPHER_PLUS; } DWORD SearchSessionsForAttribute( IN LPSTR Locator, IN LPSTR Attribute, IN LPBYTE Buffer, IN OUT LPDWORD BufferLength ) /*++ Routine Description: Searches all VIEW_INFO buffers for a requested Locator and extracts the attributes if found Arguments: Locator - pointer to locator describing item to get attributes for Attribute - pointer to string describing attribute(s) to get Buffer - pointer to buffer in which to return attribute strings BufferLength - IN: length of Buffer in bytes OUT: length of returned attribute strings in bytes, excluding any terminating NUL Return Value: DWORD Success - ERROR_SUCCESS Failure - ERROR_GOPHER_ATTRIBUTE_NOT_FOUND ERROR_INSUFFICIENT_BUFFER --*/ { LPSESSION_INFO session; BOOL found = FALSE; DWORD error = ERROR_SUCCESS; AcquireSessionLock(); /* for (session = (LPSESSION_INFO)SessionList.Flink; session != (LPSESSION_INFO)&SessionList.Flink; session = (LPSESSION_INFO)session->List.Flink) { LPVIEW_INFO viewInfo; AcquireFindLock(session); for (findInfo = (LPVIEW_INFO)session->FindList.Flink; findInfo != (LPVIEW_INFO)&session->FindList; findInfo = (LPVIEW_INFO)findInfo->List.Flink) { ReferenceFind(findInfo); if (findInfo->Handle) { found = TRUE; break; // out of for() } } if (found) { break; // out of while() } ReleaseFindLock(session); } if (found) { error = ERROR_SUCCESS; } else { error = ERROR_GOPHER_ATTRIBUTE_NOT_FOUND; } */ ReleaseSessionLock(); error = ERROR_GOPHER_ATTRIBUTE_NOT_FOUND; return error; }