Sindbad~EG File Manager
| Current Path : /usr/home/beeson/ |
|
|
| Current File : //usr/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