Sindbad~EG File Manager

Current Path : /usr/home/beeson/ThreeBody/ThreeBodyProblem/
Upload File :
Current File : /usr/home/beeson/ThreeBody/ThreeBodyProblem/main3Body.c

// Server for the ThreeBody Engine
// Handles multiple socket connections with select and fd_set
// Author:  Michael Beeson
// Original date: 3.13.25, modeled after the MathXpert Engine

#include <stdio.h>
#include <string.h> //strlen
#include <stdlib.h>
#include <time.h>
#include <assert.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h> //close
#include <arpa/inet.h> //close
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/time.h> //FD_SET, FD_ISSET, FD_ZERO macros
#include <setjmp.h>
#include "svgGraph3.h"       // getDevice
#include "svgSymbolText.h"  // svgSymbolTextElement
#include "uthash.h"
#include "ThreeBodyDoc.h"
#include "ThreeBody.h"
#include "svgGraph3.h"


#define TRUE 1
#define FALSE 0
#define PORT 12350  // different than MathXpert's PORT!
#define INBUFFER_SIZE (1 << 12)  // incoming messages are short!
#define OUTBUFFER_SIZE 9990000  // must fit in seven digits, as message lengths are 7 digits.
static char outbuffer[OUTBUFFER_SIZE];
#define MAXCLIENTS 10

static char lastSessionId[32]; // to assist in error recovery
static int master_socket;   // global so available to SignalHandler
static int client_socket[MAXCLIENTS];

static ssize_t my_send(int newsockfd, char *outbuffer, ssize_t messagelength);
static int respond(int newsockfd);
static void performCleanup(void);
static int shouldPerformCleanup(void);
static PDOCDATA3 AllDocuments3;

/*___________________________________________________________*/

// See assertRecovery5.c  for an  example of setjmp/longjmp
// But "that will never work"  in this program.

static sigjmp_buf jumpBuffer;


static void signalHandler(int signum) {
    printf("Caught signal \n");
    siglongjmp(jumpBuffer, 1); // Attempt to jump back
}

static void logEngineSession(const char *sessionID) {
    FILE *logFile = fopen("/home/beeson/ThreeBody/logs/EngineLog3.csv", "a");
    if (!logFile) return;  // Fail silently

    time_t now = time(NULL);
    struct tm *t = localtime(&now);

    fprintf(logFile, "%04d-%02d-%02d,%02d:%02d:%02d,%s\n",
            t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
            t->tm_hour, t->tm_min, t->tm_sec,
            sessionID);

    fclose(logFile);
}

/*________________________________________________________*/
void destroyDocument(PDOCDATA3 pDocData)
//  free the memory allocated by createDocument3 and
//  remove the corresponding entry from the hash table.
//  This will be called when the document has been apparently
//  abandoned by nobody sending messages to it for some time.
{  char sessionID[MAX_SESSIONID];
   strncpy(sessionID,pDocData->sessionId,32);
   //destroy_document_data3(pDocData);
    //  Frees memory allocated by allocate_doc_data including
    //  the document's heap.
  // free must come after HASH_DEL, as HASH_DEL still needs
  // to access the data.
   HASH_DEL(AllDocuments3, pDocData);  // remove it from the hash table
   free(pDocData);  // allocated by createDocument
   printf("document for session %s destroyed\n",sessionID);
}

/*_________________________________________________*/


int main(int argc , char *argv[])
{
	int opt = TRUE;
	int  addrlen , newsockfd;
   int activity, i, sd, max_sd;
	struct sockaddr_in address;
	fd_set readfds;  //set of socket descriptors
   signal(SIGABRT,signalHandler);
	
	//initialise all client_socket[] to 0 so not checked
	for (i = 0; i < MAXCLIENTS; i++)
	{
		client_socket[i] = 0;
	}
		
	//create a master socket
	if( (master_socket = socket(AF_INET , SOCK_STREAM , 0)) == 0)
	{
		perror("socket failed");
		exit(EXIT_FAILURE);
	}
	
	//set master socket to allow multiple connections ,
	// it will work without this
	if( setsockopt(master_socket, SOL_SOCKET, SO_REUSEADDR, (char *)&opt,
		sizeof(opt)) < 0 )
	{
		perror("setsockopt");
		exit(EXIT_FAILURE);
	}
	
	//type of socket created
	address.sin_family = AF_INET;
	address.sin_addr.s_addr = INADDR_ANY;
	address.sin_port = htons( PORT );
		
	//bind the socket to localhost port given by PORT
	if (bind(master_socket, (struct sockaddr *)&address, sizeof(address))<0)
	{
		perror("bind failed");
		exit(EXIT_FAILURE);
	}
	printf("Listener on port %d \n", PORT);
		
	if (listen(master_socket, MAXCLIENTS) < 0)
	{
		perror("listen");
		exit(EXIT_FAILURE);
	}
	printf("Listening on port %d...\n", PORT);
	puts("Waiting for connections ...");
   
	addrlen = sizeof(address);
	while(TRUE)
	{
		//clear the socket set
		FD_ZERO(&readfds);
	
		//add master socket to set
		FD_SET(master_socket, &readfds);
		max_sd = master_socket;
			
		//add child sockets to set
		for ( i = 0 ; i < MAXCLIENTS ; i++)
		{
			//socket descriptor
			sd = client_socket[i];
				
			//if valid socket descriptor then add to read list
			if(sd > 0)
				FD_SET( sd , &readfds);
				
			//highest file descriptor number, need it for the select function
			if(sd > max_sd)
				max_sd = sd;
		}
		//wait for an activity on one of the sockets , timeout is NULL ,
		//so wait indefinitely
      printf("\nWaiting for activity, ");
      time_t now;
      time(&now);
      struct tm *local = localtime(&now);
      printf("%02d-%02d-%04d %02d:%02d:%02d\n",
      local->tm_mday, local->tm_mon + 1, local->tm_year + 1900,
      local->tm_hour, local->tm_min, local->tm_sec);

      if (shouldPerformCleanup()) {
          performCleanup();
      }
      activity = select( max_sd + 1 , &readfds , NULL , NULL , NULL);
	
		if ((activity < 0) && (errno!=EINTR))
		{
         perror("select");
		}
			
		//If something happened on the master socket ,
		//then it's an incoming connection
		if (FD_ISSET(master_socket, &readfds))
		{
			if ((newsockfd = accept(master_socket,
					(struct sockaddr *)&address, (socklen_t*)&addrlen))<0)
			{
				perror("accept");
				continue;
			}
         printf("New connection, socket fd is %d, ip is: %s, port: %d\n",
             newsockfd, inet_ntoa(address.sin_addr), ntohs(address.sin_port));
			
			//add new socket to array of sockets
			for (i = 0; i < MAXCLIENTS; i++)
			{
				//if position is empty
				if( client_socket[i] == 0 )
				{
					client_socket[i] = newsockfd;
					// printf("Adding to list of sockets at position %d\n" , i);
					break;
				}
			}
			if(i==MAXCLIENTS)
				{
					printf("No more clients allowed\n");
					close(newsockfd);
					continue;
				}
         continue;  // with the next iteration of the loop
		}
			
		//else it's a client socket
		for (i = 0; i < MAXCLIENTS; i++)
		{
			sd = client_socket[i];
			if (FD_ISSET( sd , &readfds))
			{
				// Check if it was for closing , and if not, read the
				// incoming message
            
                  int rval;
                  printf("Trying to respond to client %d\n", sd);

                  // try to recover back to here from a crash in respond()
                  if (sigsetjmp(jumpBuffer,1) == 0)
                     {
                       rval = respond(sd);
                     }
                  else
                     { // there was a SIGABRT  in processing some message
                       rval = 0;
                       rect lastRect;
                       printf("Recovered from SIGABRT, continuing\n");
                       PDOCDATA3 storedDoc;
                       HASH_FIND_STR(AllDocuments3, lastSessionId, storedDoc);
                       if(storedDoc == NULL)
                          { // there is no document yet, we crashed on
                            // the first message!
                            char *message = "Goodbye, cruel world. The ThreeBody Engine cannot find or create your document. Please try again.";
                            strcpy(outbuffer,message);
                            my_send(sd, outbuffer, strlen(outbuffer));
                          }
                       else
                          {  char *message = "ThreeBody crashed trying to obey your command.  Our apologies. We attempted to restore your work; if we succeeded you can continue, but don't just do the same thing again!";
                             svgSymbolTextElement(message,outbuffer,"crash","crash",
                               OUTBUFFER_SIZE,0,0,0);  // black, x, y
                             int byteswritten = (int) strlen(outbuffer);
                             svgDevice *theDevice = get_device();
                             lastRect.left = theDevice->pxmin;
                             lastRect.right = theDevice->pxmax;
                             lastRect.top = theDevice->pymin;
                             lastRect.bottom = theDevice->pymax;
                             set_svgDevice(outbuffer + byteswritten,
                                           OUTBUFFER_SIZE - byteswritten,
                                           &lastRect);
                             sendDocument3(storedDoc);
                             my_send(sd, outbuffer, strlen(outbuffer));
                             rval = 1;
                          }
                     }
                  if(rval == 0)
                     {	//Somebody disconnected  or we disconnected them,
                        /*  Commented this out 1.22.25--the output is cluttering the console.
                         // get details and print
                        getpeername(sd , (struct sockaddr*)&address , (socklen_t*)&addrlen);
                        printf("Host disconnected , ip %s , port %d \n" ,
                        inet_ntoa(address.sin_addr) , ntohs(address.sin_port));
                        */
                        close(sd);
                        client_socket[i] = 0;
                     }
                  else
                     {
                        printf("Finished trying to respond\n");
                     }
               
          
            break;
			}
      }
	}
	return 0;
}
/*______________________________________________________________*/
#define MAXDIGITS 7   // maximum length of a message in decimal digits
static ssize_t my_send(int newsockfd, char *outbuffer, ssize_t messagelength)
/* send messagelength bytes from outbuffer reliably.
First, send MAXDIGITS digits with the messagelength, using a loop,
because (with high network traffic) it is not certain that even
six characters can be sent in one go.  Then,  another loop
sends the actual message. */

{ ssize_t sent = 0, more;
  char buf[32];
  char format[10];
  snprintf(format, sizeof(format), "%%%dlld", MAXDIGITS);
  sprintf(buf, format, messagelength); // MAXDIGITS characters, left-padded with blanks if necessary
  assert(strlen(buf) <= MAXDIGITS);
  sent = 0;
  while( sent < MAXDIGITS)
    { more = send(newsockfd,buf+sent,MAXDIGITS-sent,0);
      if(more <= 0)
         { // connection was broken or some other error
           perror("Socket send error");
           // send can return -1; hence messagelength is (signed) long
           // send returns ssizet but if I use that type, it's not defined.
           break;
         }
      sent += more;
    }
  // OK, now we've sent the message length
  sent = 0;
  while(sent < messagelength)
      { ssize_t more = send(newsockfd,outbuffer+sent, messagelength-sent,0);
        if(more <= 0)
            { // connection was broken or some other error
              perror("Socket send error");
              // send can return -1; hence messagelength is (signed) long
              // send returns ssizet but if I use that type, it's not defined.
              return more;
            }
         sent += more;
      }
   assert(sent == messagelength);
   return messagelength;
}

/*___________________________________________________________________________*/
static int respond(int newsockfd)
				// read and respond to a message
            // It will block until a message is received
            // return 0 if newsockfd has disconnected on the other end
            // or it is necessary to close on this end because of an error
            // otherwise return anything nonzero
{
   // first the client should send 6 bytes with the length of the data to follow
   // client-to-server messages have 6-digit lengths.
   // server-to-client messages have MAXDIGIT lengths, presently 7
   ssize_t receivedbytes = 0;
   ssize_t expectedbytes;
   char sizebuf[32];
   char buffer[INBUFFER_SIZE];
   // printf("Entering respond\n");  // debug
   while (receivedbytes < 6)
      { ssize_t newbytes = recv(newsockfd,sizebuf,6-receivedbytes,0);
        if(newbytes == 0)
          { // client has disconnected
            // printf("Client disconnected\n");
            return 0;
          }
        receivedbytes += newbytes;
        // printf("Received %ld bytes\n",newbytes);
      }
   sscanf(sizebuf,"%ld",&expectedbytes);
   // printf("Received notice to expect  %ld bytes\n",expectedbytes);
   receivedbytes = 0;
   while(receivedbytes < expectedbytes)
      { ssize_t newbytes = recv(newsockfd,buffer + receivedbytes,expectedbytes-receivedbytes,0);
        if(newbytes == 0)
           { printf ("Client disconnected\n");
             close(newsockfd);
             return 0;
           }
        if(newbytes < 0)
           { perror("Socket receive error");
             close(newsockfd);
             return 0;
           }
        // printf("Got here in respond: received %ld bytes\n",newbytes);
        receivedbytes += newbytes;
      }

    // process data
   assert(receivedbytes == expectedbytes);
   buffer[receivedbytes] = '\0';
   // printf("Received data: %s\n", buffer);
   char *sessionId = strtok(buffer, "|");  // Get the session ID
   strncpy(lastSessionId, sessionId,32);
   char *message = strtok(NULL, "|");
   char *param = message + strlen(message)+1;
   if (sessionId == NULL || message == NULL || param[0] == '\0') // param can't be empty
   {
       perror("Invalid message format");
       close(newsockfd);
       return 0;
   }

   //  Is this a new sessionId or not?
   PDOCDATA3 storedDoc;
   printf("Hashing sessionID %s\n", sessionId);
   HASH_FIND_STR(AllDocuments3, sessionId, storedDoc);

   if (storedDoc == NULL)
       { // new sessionID
        
         if ( !strcmp(message, "newDocument"))
            {  logEngineSession(sessionId);
               storedDoc = calloc(1, sizeof(DOCDATA3));
               printf("Created document for %s\n", sessionId);
               printf("message is %s\n", message);
               printf("param is %s\n",param);
               init_docData3(storedDoc,sessionId,param);
               HASH_ADD_STR(AllDocuments3, sessionId, storedDoc);
               assert(storedDoc != NULL);
            }
         else
            {
               perror("Invalid message format for a new document");
               close(newsockfd);  // without a response.
               return 0;
            }
       }
   else
       {
         printf("Found existing document for %s\n", sessionId);
         // but it might be a new problem, which would come with "newDocument"
         if ( !strcmp(message, "newDocument"))
            {  logEngineSession(sessionId);
               storedDoc = calloc(1, sizeof(DOCDATA3));
               printf("Created document for %s\n", sessionId);
               printf("message is %s\n", message);
               printf("param is %s\n",param);
               init_docData3(storedDoc,sessionId,param);
               HASH_ADD_STR(AllDocuments3, sessionId, storedDoc);
               assert(storedDoc != NULL);
            }
         else
            {  // as yet there is only one more, "saveSVG"
               printf("Received message %s\n", message);
               printf("with parameter %s\n", param);
            }
       }
   int err = process_message3(storedDoc, message, param, outbuffer, OUTBUFFER_SIZE);
   if(!err && !strcmp(message,"saveSVG"))
      { // this message is exceptional as we are going to save the document,
        // which is now in outbuffer, rather than send it to the browser.
        char errmsg[128];
        int err = saveAsSVG(outbuffer, param, errmsg); // returns 0 for success
        if(!err)
           { strcpy(outbuffer,"ok");
             printf("SVG file saved as %s\n", param);
           }
        else
           { strcpy(outbuffer, errmsg);
             printf("SVG file not saved, %s\n",errmsg);
           }
        err = 0;  // and continue to the next section where outbuffer
                  // will be sent to the browser.
      }
   long messagelength =  (long) strlen(outbuffer);
   if(!err)
      {    // printf("About to send %lu bytes\n", messagelength);
         ssize_t sent = my_send(newsockfd,outbuffer,messagelength);
         assert(sent <= messagelength);
         if (sent == messagelength)
            { printf("Sent a correct result of %ld bytes back to the caller\n", messagelength);
              if(messagelength < 80)
                 printf("Namely, %s\n",outbuffer);
              return 1;
            }
         else
            { perror( "Send error: ");
              close(newsockfd);
              return 0;
            }
      }
   else
      {  if (strlen(outbuffer) != 0 && strstr(outbuffer,"ERROR"))
            { // it's an error message
              ssize_t sent = my_send(newsockfd,outbuffer,messagelength);
              assert(sent <= messagelength);
              if (sent == messagelength)
                 { printf("Sent error message of %ld bytes back to the caller\n", messagelength);
                   close(newsockfd);
                   return 0;
                 }
            }
         else
            { perror( "Send error: ");
              close(newsockfd);
              return 0;
            }
         printf("Not processed: %s", outbuffer);
         close(newsockfd);
         return 0;
      }
}

#define CLEANUP_INTERVAL   900      //  15 minutes,  one-quarter of ABANDONMENT_THRESHHOLD
#define ABANDONMENT_THRESHOLD   3600   // one hour

// Function to decide if cleanup should be performed
static int shouldPerformCleanup(void) {
    static time_t lastCleanupTime = 0;
    time_t currentTime = time(NULL);
    if (difftime(currentTime, lastCleanupTime) >= CLEANUP_INTERVAL) {
        lastCleanupTime = currentTime;
        return 1;
    }
    return 0;
}

// Cleanup function
static void performCleanup(void) {
    time_t currentTime = time(NULL);
    PDOCDATA3 s, tmp;
    int counter = 0;
    HASH_ITER(hh, AllDocuments3, s, tmp)
      {
        if (difftime(currentTime, s->lastMessageTime) >= ABANDONMENT_THRESHOLD)
           {
             destroyDocument(s);
             ++counter;
           }
      }
    time_t now = time(NULL);
    struct tm *local_time = localtime(&now);
    char time_str[100];
    strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", local_time);
    printf("Cleanup performed at %s\n", time_str);
    printf("%d documents were destroyed\n", counter);
}


Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists