Sindbad~EG File Manager

Current Path : /home/beeson/
Upload File :
Current File : //home/beeson/main.c

// Server for the MathXpert Engine
// Handles multiple socket connections with select and fd_set
// Author:  Michael Beeson
// Original date: 12.12.23
// 3.23.24  added close(sd)
// 4.1.24  added signal_handler
// 4.3.24 modified respond to send back error messages.
// 5.8.24  introduced MAXDIGITS and made it 7, as 6 turned out to be too small.
// 6.18.24  added call to preDocument()
// 6.19.24  added performCleanup and shouldPerformCleanup
// 6.29.24  made signal_handler work on SIGABRT,  sleep, and reset to default handler  [later removed]
// 7.22.24  signalHandler (different spelling and code) is working well,
//          but only when compiled outside Xcode.
// 7.22.24  made respond() reject NULL parameters.

#include <stdio.h>
#include <string.h> //strlen
#include <stdlib.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 "uthash.h"

#include "terms.h"
#include "parser.h"
#include "defns.h"
#include "vaux.h"
#include "proverdl.h"
#include "model.h"
#include "arith.h"
#include "polyval.h"
#include "graphstr.h"
#include "mpdoc.h"
#include "heap.h"
#include "heaps.h"
#include "ProcessMessage.h"
#include "lang.h"   // ENGLISH
#include "natlang.h"  // get_language_number
#include "verify.h"   // preDocument()
#include "svgSymbolText.h"  // svgSymbolTextElement
#include "sendDocument.h"
#include "svgGraph.h"       // getDevice

#define TRUE 1
#define FALSE 0
#define PORT 12349
#define INBUFFER_SIZE (1 << 10)
#define OUTBUFFER_SIZE 3000000  // must fit in seven 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 int  createDocument( char *sessionId,int languageNumber);
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);

/*___________________________________________________________*/

// 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 PDOCDATA AllDocuments;  // it's automatically NULL

static int createDocument(char *sessionId, int languageNumber)
// make a new Document (on the C-language heap) and add it to the hash table
// includes allocating space for the document's heap and calling init_heap
{
  PDOCDATA pDocData = (PDOCDATA)  calloc(1,sizeof(DOCDATA));
  pDocData->language = languageNumber;
  int err = init_doc_data(SYMBOLDOC, pDocData);  // includes create_heap
  if(err)
      return err;
  strncpy(pDocData->sessionId,sessionId, MAX_SESSIONID);
  printf("Creating document for %s\n", sessionId);
     // Add the document to the hash table
  HASH_ADD_STR(AllDocuments, sessionId, pDocData);
  printf("hashed sessionID successfully\n");
  return 0;
}

/*________________________________________________________*/
void destroyDocument(PDOCDATA pDocData)
//  free the memory allocated by createDocument 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[32];
   strncpy(sessionID,pDocData->sessionId,32);
   destroy_document_data(pDocData);
    //  Frees memory allocated by allocate_doc_data including
    //  the document's heap.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wuse-after-free"
   free(pDocData);  // allocated by createDocument
   // free must come before HASH_DEL, as HASH_DEL corrupts the hashed pointer. 
   HASH_DEL(AllDocuments, pDocData);  // remove it from the hash table
#pragma GCC diagnostic pop
   printf("document for session %s destroyed\n",sessionID);
}

/*________________________________________________________*/
#if 0
static void signal_handler(int sig)
// Make sure the Engine will respond to a kill signal
// Define a signal handler to close sockets
{
    printf("Received signal %d, cleaning up...\n", sig);
    
    // Close all client sockets
    for (int i = 0; i < MAXCLIENTS; i++) {
        if (client_socket[i] > 0) {
            close(client_socket[i]);
            client_socket[i] = 0;
        }
    }

    // Close the master socket
    if (master_socket > 0) {
        close(master_socket);
    }
     
    sleep(1);  // Wait for a moment to ensure ports are released
    signal(sig, SIG_DFL); // reset to default handler
    raise(sig);           // and re-raise the signal
    exit(EXIT_SUCCESS);
}
#endif 
/*_________________________________________________*/
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;
			}
			
			//write on server console information about the  socket number - used in send and receive commands
			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");
                       PDOCDATA storedDoc;
                       HASH_FIND_STR(AllDocuments, lastSessionId, storedDoc);
                       if(storedDoc == NULL)
                          { // there is no document yet, we crashed on
                            // the first message!
                            char *message = "Goodbye, cruel world. The MathXpert Engine cannot find or create your document. Please try again.";
                            strcpy(outbuffer,message);
                            my_send(sd, outbuffer, strlen(outbuffer));
                          }
                       else if(storedDoc->kind == GRAPHDOC)
                          {  char *message = "MathXpert 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);
                             sendGraphDocument(storedDoc);
                             my_send(sd, outbuffer, strlen(outbuffer));
                             rval = 1;
                          }
                       else if(storedDoc->kind == SYMBOLDOC)
                          {  char *message = "MathXpert 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);
                             lastRect.left = 0;
                             lastRect.top = 0;
                             lastRect.right = storedDoc->papyrus->right;
                             lastRect.bottom = storedDoc->papyrus->bottom;
                             set_svgDevice(outbuffer + byteswritten,
                                           OUTBUFFER_SIZE - byteswritten,
                                           &lastRect);
                             sendSymbolDocument(storedDoc,
                                                outbuffer+byteswritten,
                                                OUTBUFFER_SIZE-byteswritten
                                               );
                             my_send(sd, outbuffer, strlen(outbuffer));
                             rval = 1;
                          }
                     }
                  if(rval == 0)
                     {	//Somebody disconnected  or we disconnected them,
                        // 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[OUTBUFFER_SIZE];   // for incoming data despite the allocation
   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 = strtok(NULL, "|");  // can be "dummy" but can't be NULL

   if (sessionId == NULL || message == NULL || param == NULL)
   {
       perror("Invalid message format");
       close(newsockfd);
       return 0;
   }

   //  Is this a new sessionId or not?
   PDOCDATA storedDoc;
   HASH_FIND_STR(AllDocuments, sessionId, storedDoc);

   if (storedDoc == NULL)
       { // new sessionID
         int languageNumber = ENGLISH;
         if(param && strlen(param)< 32)
            { languageNumber = get_language_number(param);
               // if it doesn't match a supported language, ENGLISH is returned;
            }
         if ( preDocument(message))
            {  createDocument(sessionId,languageNumber);
               printf("Created document for %s\n", sessionId);
               printf("message is %s\n", message);
               printf("param is %s\n",param);
               printf("languageNumber is %d\n",languageNumber);
               HASH_FIND_STR(AllDocuments, 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);
         activate(storedDoc);
         printf("Activating document\n");
       }
   int err = process_message(storedDoc, message, param, outbuffer, OUTBUFFER_SIZE);
   long messagelength =  (long) strlen(outbuffer);
   // printf("About to send %lu bytes\n", messagelength);
   if(!err)
      {  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() {
    time_t currentTime = time(NULL);
    PDOCDATA s, tmp;
    int counter = 0;
    HASH_ITER(hh, AllDocuments, 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