/* * $Id: async.c,v 1.3 1996/08/04 02:24:26 ceder Exp $ * Copyright (C) 1991, 1993, 1996 Lysator Academic Computer Association. * * This file is part of the LysKOM server. * * LysKOM 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 1, or (at your option) * any later version. * * LysKOM 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 LysKOM; see the file COPYING. If not, write to * Lysator, c/o ISY, Linkoping University, S-581 83 Linkoping, SWEDEN, * or the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, * MA 02139, USA. * * Please mail bug reports to bug-lyskom@lysator.liu.se. */ /* * async.c -- Receive asynchronous messages and call a handler. * * Written by Per Cederqvist 1990-07-24 */ static char *rcsid = "$Id: async.c,v 1.3 1996/08/04 02:24:26 ceder Exp $"; #include #include #include #include /* #include is included from kom-types.h */ #include #if defined(__svr4__) && defined(__sparc__) /* Solaris */ #include #include #define POLLINGREAD I_NREAD #else #define POLLINGREAD FIONREAD #endif #include #include #include #include #include #include #include "async.h" #include "client-malloc.h" #include "parse.h" #include "client.h" #include "services.h" /* * The handlers. */ static void (*new_text_handler)(Text_no text_no, Text_stat text_s) = NULL; static void (*i_am_on_handler)(Who_info info) = NULL; static void (*i_am_off_handler)(Pers_no pers_no) = NULL; static void (*new_name_handler)(Conf_no conf_no, String old_name, String new_name) = NULL; static void (*directed_message_handler)(Conf_no to, Conf_no from, String message) = NULL; static void (*saving_handler)(void) = NULL; /* Forward declarators for this file. */ static Success skip_one_token(FILE *fp); /* * Functions that skip tokens. A token is: * a number, * a string or * an array * * An array is * the character '*' or * the character '{' followed by any number of tokens followed by '}' */ /* * skip_array is used when the first '{' of an array has been detected. * It skips all tokens to the closing '}'. */ static Success skip_array(FILE *fp) { int c; while ( skipwhite(fp), (c=getc(fp)) != '}' ) { ungetc(c, fp); if ( skip_one_token(fp) != OK ) return FAILURE; } return OK; } /* * Skips the next token. Returns FAILURE if it isn't a token. */ static Success skip_one_token(FILE *fp) { int c; u_long strlen; skipwhite(fp); switch(c=getc(fp)) { case '{': if ( skip_array(fp) != OK ) return FAILURE; break; case '*': break; default: if ( !isdigit(c) ) { kom_errno = KOM_SERVER_IS_CRAZY; return FAILURE; } ungetc(c, fp); strlen = parse_long(fp); if ( (c = getc(fp)) == 'H' ) { /* It was a string. Read it. */ while ( strlen-- > 0 ) getc(fp); } else { /* It was a number. Ignore it. */ if ( ! isspace(c) ) { kom_errno = KOM_SERVER_IS_CRAZY; return FAILURE; } } } return OK; } /* * Skip no_of_tokens token. */ static Success /* FIXME+++ - should this be exported? */ skip_token(FILE *fp, int no_of_tokens) { while ( no_of_tokens-- > 0 ) { if ( skip_one_token(fp) != OK ) return FAILURE; } return OK; } /* * Functions which gather the arguments for a handler and * calls that handler. * * A handler is always responsible for free:ing everything * which is given to it as a pointer. If no handler is registered * these functions of course free everything they have malloced. */ static Success call_new_text(FILE *fp) { Text_no text_no; Text_stat text = EMPTY_TEXT_STAT; text_no = parse_long(fp); if ( parse_text_stat(fp, &text) != OK ) return FAILURE; if ( new_text_handler == NULL ) { if ( text.misc_items != NULL ) isc_free(text.misc_items); } else new_text_handler(text_no, text); return OK; } static Success call_i_am_on(FILE *fp) { Who_info info = EMPTY_WHO_INFO; if ( parse_who_info(fp, &info) != OK ) return FAILURE; if ( i_am_on_handler == NULL ) { s_clear (&info.what_am_i_doing); s_clear (&info.username); } else i_am_on_handler(info); return OK; } static Success call_i_am_off(FILE *fp) { Pers_no pers_no; pers_no = parse_long(fp); if ( i_am_off_handler != NULL ) i_am_off_handler(pers_no); return OK; } static Success call_new_name(FILE *fp) { Conf_no conf_no; String old_name = EMPTY_STRING; String new_name = EMPTY_STRING; conf_no = parse_long(fp); if ( parse_string(fp, &old_name) != OK || parse_string(fp, &new_name) != OK ) { return FAILURE; } if ( new_name_handler == NULL ) { s_clear (&old_name); s_clear (&new_name); } else new_name_handler(conf_no, old_name, new_name); return OK; } static Success call_directed_message(FILE *fp) { Conf_no to, from; String message = EMPTY_STRING; to = parse_long(fp); from = parse_long(fp); if ( parse_string(fp, &message) != OK ) { return FAILURE; } if ( directed_message_handler == NULL ) { s_clear (&message); } else directed_message_handler(to, from, message); return OK; } /* * Call function telling that the server is syncing. */ static Success call_saving(FILE *fp) { if (saving_handler == NULL) { /* Do nothing */ } else { saving_handler(); } return OK; } /* * Parse an asynchronous message and call the appropriate function. */ Success async(FILE *fp) { int tokens_to_skip; Async fnc; tokens_to_skip = parse_long(fp); fnc = parse_long(fp); switch(fnc) { case ay_new_text: return call_new_text(fp); case ay_i_am_on: return call_i_am_on(fp); case ay_i_am_off: return call_i_am_off(fp); case ay_new_name: return call_new_name(fp); case ay_directed_message: return call_directed_message(fp); case ay_saving: return call_saving(fp); default: /* * Messages that are not implemented. Since the server * tells how long the message is it is possible to skip it. */ return skip_token(fp, tokens_to_skip); } } /* * Use the following functions to say that you want to catch a * certain type of message. The default action is to ignore all * messages. */ void register_new_text(void (*async_new_text)(Text_no text_no, Text_stat text_s)) { new_text_handler = async_new_text; } void register_i_am_on(void (*async_i_am_on)(Who_info info)) { i_am_on_handler = async_i_am_on; } void register_i_am_off (void (*async_i_am_off)(Pers_no pers_no)) { i_am_off_handler = async_i_am_off; } void register_new_name(void (*async_new_name)(Conf_no conf_no, String old_name, String new_name)) { new_name_handler = async_new_name; } void register_directed_message(void (*async_message)(Conf_no to, Conf_no from, String message)) { directed_message_handler = async_message; } void register_saving(void (*saving_fun)(void)) { saving_handler = saving_fun; } static enum { poll_err, poll_got_msg, poll_no_msg } poll_server(void) { int c; long nread; while ( 1 ) { if ( ioctl(kom_server->fd, POLLINGREAD, (caddr_t) &nread) != 0) { kom_errno = KOM_NO_CONNECT; return poll_err; } if ( nread == 0 ) return poll_no_msg; if ( (c=getc(kom_server->in)) == ':' ) return (async(kom_server->in) == OK) ? poll_got_msg : poll_err; if ( !isspace(c) ) { kom_errno = KOM_SERVER_IS_CRAZY; return poll_err; } } } /* * Wait for asynchronous message from client, or input on any of * the file-descriptors in opt_set. Returns FAILURE if nothing * happened in msec milliseconds or an error occurs. (kom_errno == * KOM_NO_ERR if the return was due to a timeout). Returns OK if * there was an asynchronous message or input on any of the fd's in * opt_set. * * Set opt_set to NULL if you don't want to wait on any other input * than a message. * * To wait for a message or input on stdin the following code can be used: * * fd_set read_set; * * FD_ZERO(&read_set); * FD_SET(fileno(stdin), &read_set); * if ( kom_wait(read_set, 1000) == OK ) * { * if ( FD_ISSET(fileno(stdio), &read_set) ) * handle_stdin_input(); * else * handle_asynchronous_message(); * } * else * time_out(); * * (The handler is called from kom_wait and not handle_asynchronous_message, * but there might be more to do afterwards since a handler cannot call * functions that communicate with the server.) */ extern Success kom_wait (fd_set *opt_set, int msec) { struct timeval wait; fd_set read_set; int nfds; kom_errno = KOM_NO_ERROR; switch(poll_server()) { case poll_err: FD_ZERO(opt_set); return FAILURE; case poll_got_msg: FD_ZERO(opt_set); return OK; case poll_no_msg: break; } /* No waiting message, so wait a while or two. */ /* Setup timeout structure */ wait.tv_sec = msec / 1000; wait.tv_usec = (msec % 1000) * 1000; /* Setup file descriptor set */ if ( opt_set == NULL ) { FD_ZERO(&read_set); opt_set = &read_set; } FD_SET(kom_server->fd, opt_set); nfds = select(FD_SETSIZE, opt_set, (fd_set *) NULL, (fd_set *) NULL, &wait); if ( nfds < 0 ) { kom_errno = KOM_NO_CONNECT; return FAILURE; } if ( FD_ISSET(kom_server->fd, opt_set) && poll_server() == poll_err ) { return FAILURE; } return nfds == 0 ? FAILURE : OK; }