/* The GIMP -- an image manipulation program * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #ifdef HAVE_SYS_SELECT_H #include #endif /* HAVE_SYS_SELECT_H */ #include "libgimp/gimp.h" #include "libgimp/gimpui.h" #include "script-fu-intl.h" #include "gtk/gtk.h" #include "siod.h" #include "script-fu-server.h" #define COMMAND_HEADER 3 #define RESPONSE_HEADER 4 #define MAGIC 'G' #ifdef NO_DIFFTIME #define difftime(a,b) (((gdouble)(a)) - ((gdouble)(b))) #endif #ifndef NO_FD_SET # define SELECT_MASK fd_set #else # ifndef _AIX typedef long fd_mask; # endif # if defined(_IBMR2) # define SELECT_MASK void # else # define SELECT_MASK int # endif #endif /* image information */ /* Header format for incoming commands... * bytes: 1 2 3 * MAGIC CMD_LEN_H CMD_LEN_L */ /* Header format for outgoing responses... * bytes: 1 2 3 4 * MAGIC ERROR? RSP_LEN_H RSP_LEN_L */ #define MAGIC_BYTE 0 #define CMD_LEN_H_BYTE 1 #define CMD_LEN_L_BYTE 2 #define ERROR 1 #define RSP_LEN_H_BYTE 2 #define RSP_LEN_L_BYTE 3 /* * Local Structures */ typedef struct { gchar *command; gint filedes; gint request_no; } SFCommand; typedef struct { GtkWidget *port_entry; GtkWidget *log_entry; gint port; gchar *logfile; gboolean run; } ServerInterface; /* * Local Functions */ static void server_start (gint port, gchar *logfile); static gboolean execute_command (SFCommand *cmd); static gint read_from_client (gint filedes); static gint make_socket (guint port); static void server_log (gchar *format, ...); static void server_quit (void); static gboolean server_interface (void); static void ok_callback (GtkWidget *widget, gpointer data); /* * Global variables */ gint server_mode = FALSE; /* * Local variables */ static gint server_sock; static GList *command_queue = NULL; static gint queue_length = 0; static gint request_no = 0; static FILE *server_log_file = NULL; static GHashTable *clientname_ht = NULL; static SELECT_MASK server_active; static SELECT_MASK server_read; static ServerInterface sint = { NULL, /* port entry widget */ NULL, /* log entry widget */ 10008, /* default port number */ NULL, /* use stdout */ FALSE /* run */ }; extern gboolean script_fu_done; extern gchar siod_err_msg[]; extern LISP repl_return_val; /* * Server interface functions */ void script_fu_server_run (gchar *name, gint nparams, GimpParam *params, gint *nreturn_vals, GimpParam **return_vals) { static GimpParam values[1]; GimpPDBStatusType status = GIMP_PDB_SUCCESS; GimpRunModeType run_mode; run_mode = params[0].data.d_int32; switch (run_mode) { case GIMP_RUN_INTERACTIVE: if (server_interface ()) { server_mode = TRUE; /* Start the server */ server_start (sint.port, sint.logfile); } break; case GIMP_RUN_NONINTERACTIVE: /* Set server_mode to TRUE */ server_mode = TRUE; /* Start the server */ server_start (params[1].data.d_int32, params[2].data.d_string); break; case GIMP_RUN_WITH_LAST_VALS: status = GIMP_PDB_CALLING_ERROR; g_warning ("Script-Fu server does handle \"GIMP_RUN_WITH_LAST_VALS\""); default: break; } *nreturn_vals = 1; *return_vals = values; values[0].type = GIMP_PDB_STATUS; values[0].data.d_status = status; } void script_fu_server_listen (gint timeout) { struct sockaddr_in clientname; struct timeval tv; struct timeval *tvp; gint i; gint size; /* Set time struct */ if (timeout) { tv.tv_sec = timeout / 1000; tv.tv_usec = timeout % 1000; tvp = &tv; } else tvp = NULL; /* Block until input arrives on one or more active sockets or timeout occurs. */ server_read = server_active; if (select (FD_SETSIZE, &server_read, NULL, NULL, tvp) < 0) { perror ("select"); return; } /* Service all the sockets with input pending. */ for (i = 0; i < FD_SETSIZE; ++i) if (FD_ISSET (i, &server_read)) { if (i == server_sock) { /* Connection request on original socket. */ gint new; size = sizeof (clientname); new = accept (server_sock, (struct sockaddr *) &clientname, &size); if (new < 0) { perror ("accept"); return; } /* Associate the client address with the socket */ g_hash_table_insert (clientname_ht, (gpointer) new, g_strdup (inet_ntoa (clientname.sin_addr))); /* server_log ("Server: connect from host %s, port %d.\n", inet_ntoa (clientname.sin_addr), (unsigned int) ntohs (clientname.sin_port)); */ FD_SET (new, &server_active); } else { if (read_from_client (i) < 0) { /* Disassociate the client address with the socket */ g_hash_table_remove (clientname_ht, (gpointer) i); /* server_log ("Server: disconnect from host %s, port %d.\n", inet_ntoa (clientname.sin_addr), (unsigned int) ntohs (clientname.sin_port)); */ close (i); FD_CLR (i, &server_active); } } } } static void server_start (gint port, gchar *logfile) { SFCommand *cmd; /* Set up the clientname hash table */ clientname_ht = g_hash_table_new (g_direct_hash, NULL); /* Setup up the server log file */ if (logfile) server_log_file = fopen (logfile, "a"); else server_log_file = NULL; if (server_log_file == NULL) server_log_file = stdout; /* Create the socket and set it up to accept connections. */ server_sock = make_socket (port); if (listen (server_sock, 5) < 0) { perror ("listen"); return; } server_log ("Script-fu initialized and listening...\n"); /* Initialize the set of active sockets. */ FD_ZERO (&server_active); FD_SET (server_sock, &server_active); /* Loop until the server is finished */ while (! script_fu_done) { script_fu_server_listen (0); while (command_queue) { /* Get the current command */ cmd = (SFCommand *) command_queue->data; /* Process the command */ execute_command (cmd); /* Remove the command from the list */ command_queue = g_list_remove (command_queue, cmd); queue_length--; /* Free the request */ g_free (cmd->command); g_free (cmd); } } server_quit (); /* Close the server log file */ if (server_log_file != stdout) fclose (server_log_file); } static gboolean execute_command (SFCommand *cmd) { guchar buffer[RESPONSE_HEADER]; gchar *response; time_t clock1; time_t clock2; gint response_len; gboolean error; gint i; /* Get the client address from the address/socket table */ server_log ("Processing request #%d\n", cmd->request_no); time (&clock1); /* run the command */ if (repl_c_string (cmd->command, 0, 0, 1) != 0) { error = TRUE; response_len = strlen (siod_err_msg); response = siod_err_msg; server_log ("%s\n", siod_err_msg); } else { error = FALSE; if (TYPEP (repl_return_val, tc_string)) response = get_c_string (repl_return_val); else response = "Success"; response_len = strlen (response); time (&clock2); server_log ("Request #%d processed in %f seconds, finishing on %s", cmd->request_no, difftime (clock2, clock1), ctime (&clock2)); } buffer[MAGIC_BYTE] = MAGIC; buffer[ERROR] = error ? TRUE : FALSE; buffer[RSP_LEN_H_BYTE] = (guchar) (response_len >> 8); buffer[RSP_LEN_L_BYTE] = (guchar) (response_len & 0xFF); /* Write the response to the client */ for (i = 0; i < RESPONSE_HEADER; i++) if (write (cmd->filedes, buffer + i, 1) < 0) { /* Write error */ perror ("write"); return FALSE; } for (i = 0; i < response_len; i++) if (write (cmd->filedes, response + i, 1) < 0) { /* Write error */ perror ("write"); return FALSE; } return FALSE; } static gint read_from_client (gint filedes) { SFCommand *cmd; guchar buffer[COMMAND_HEADER]; gchar *command; gchar *clientaddr; time_t clock; gint command_len; gint nbytes; gint i; for (i = 0; i < COMMAND_HEADER; i++) { if ((nbytes = read (filedes, buffer + i, 1)) < 0) { /* Read error. */ perror ("read"); return 0; } else if (nbytes == 0) /* End-of-file. */ return -1; } if (buffer[MAGIC_BYTE] != MAGIC) { server_log ("Error in script-fu command transmission.\n"); return -1; } command_len = (buffer [CMD_LEN_H_BYTE] << 8) | buffer [CMD_LEN_L_BYTE]; command = g_new (gchar, command_len + 1); for (i = 0; i < command_len; i++) if (read (filedes, command + i, 1) == 0) { server_log ("Error reading command. Read %d out of %d bytes.\n", i, command_len); return -1; } command[command_len] = '\0'; cmd = g_new (SFCommand, 1); cmd->filedes = filedes; cmd->command = command; cmd->request_no = request_no ++; /* Add the command to the queue */ command_queue = g_list_append (command_queue, cmd); queue_length ++; /* Get the client address from the address/socket table */ clientaddr = g_hash_table_lookup (clientname_ht, (gpointer) cmd->filedes); time (&clock); server_log ("Received request #%d from IP address %s: %s on %s," "[Request queue length: %d]", cmd->request_no, clientaddr, cmd->command, ctime (&clock), queue_length); return 0; } static gint make_socket (guint port) { struct sockaddr_in name; gint sock; gint v = 1; /* Create the socket. */ sock = socket (PF_INET, SOCK_STREAM, 0); if (sock < 0) { perror ("socket"); gimp_quit (); } setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &v, sizeof(v)); /* Give the socket a name. */ name.sin_family = AF_INET; name.sin_port = htons (port); name.sin_addr.s_addr = htonl (INADDR_ANY); if (bind (sock, (struct sockaddr *) &name, sizeof (name)) < 0) { perror ("bind"); gimp_quit (); } return sock; } static void server_log (gchar *format, ...) { va_list args; gchar *buf; va_start (args, format); buf = g_strdup_vprintf (format, args); va_end (args); fputs (buf, server_log_file); if (server_log_file != stdout) fflush (server_log_file); } static void server_quit (void) { gint i; for (i = 0; i < FD_SETSIZE; ++i) if (FD_ISSET (i, &server_active)) shutdown (i, 2); } static gboolean server_interface (void) { GtkWidget *dlg; GtkWidget *table; INIT_I18N_UI(); gimp_ui_init ("script-fu", FALSE); dlg = gimp_dialog_new (_("Script-Fu Server Options"), "script-fu", gimp_standard_help_func, "filters/script-fu.html", GTK_WIN_POS_MOUSE, FALSE, TRUE, FALSE, _("OK"), ok_callback, NULL, NULL, NULL, TRUE, FALSE, _("Cancel"), gtk_widget_destroy, NULL, 1, NULL, FALSE, TRUE, NULL); gtk_signal_connect (GTK_OBJECT (dlg), "destroy", GTK_SIGNAL_FUNC (gtk_main_quit), NULL); /* The table to hold port & logfile entries */ table = gtk_table_new (2, 2, FALSE); gtk_table_set_col_spacings (GTK_TABLE (table), 4); gtk_table_set_row_spacings (GTK_TABLE (table), 2); gtk_container_set_border_width (GTK_CONTAINER (table), 4); gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->vbox), table, TRUE, TRUE, 0); /* The server port */ sint.port_entry = gtk_entry_new (); gtk_entry_set_text (GTK_ENTRY (sint.port_entry), "10008"); gimp_table_attach_aligned (GTK_TABLE (table), 0, 0, _("Server Port:"), 1.0, 0.5, sint.port_entry, 1, TRUE); /* The server logfile */ sint.log_entry = gtk_entry_new (); gimp_table_attach_aligned (GTK_TABLE (table), 0, 1, _("Server Logfile:"), 1.0, 0.5, sint.log_entry, 1, TRUE); gtk_widget_show (table); gtk_widget_show (dlg); gtk_main (); gdk_flush (); return sint.run; } static void ok_callback (GtkWidget *widget, gpointer data) { sint.port = atoi (gtk_entry_get_text (GTK_ENTRY (sint.port_entry))); sint.logfile = g_strdup (gtk_entry_get_text (GTK_ENTRY (sint.log_entry))); sint.run = TRUE; gtk_widget_destroy (GTK_WIDGET (data)); }