diff options
author | Steve Slaven <bpk@hoopajoo.net> | 2005-03-12 00:27:55 (GMT) |
---|---|---|
committer | Steve Slaven <bpk@hoopajoo.net> | 2005-03-12 00:27:55 (GMT) |
commit | 77b250bfb63a28a8fe8a8da67de7354bce6e61ff (patch) | |
tree | e28f72c18305016c19c608f5fce4432529e7673d /beam.c | |
parent | 5dfb1906b299bf7c8a1ee3ba5cd1c9ea40648d89 (diff) | |
download | powwow-1.2.7.zip powwow-1.2.7.tar.gz powwow-1.2.7.tar.bz2 |
Initial revisionv1.2.7
Diffstat (limited to 'beam.c')
-rw-r--r-- | beam.c | 438 |
1 files changed, 438 insertions, 0 deletions
@@ -0,0 +1,438 @@ +/* + * beam.c -- code to beam texts across the TCP connection following a + * special protocol + * + * Copyright (C) 1998 by Massimiliano Ghilardi + * + * 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. + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <time.h> +#include <unistd.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> + +#include "defines.h" +#include "main.h" +#include "utils.h" +#include "beam.h" +#include "map.h" +#include "tcp.h" +#include "tty.h" +#include "edit.h" +#include "eval.h" + +editsess *edit_sess; /* head of session list */ + +char edit_start[BUFSIZE]; /* messages to send to host when starting */ +char edit_end[BUFSIZE]; /* or leaving editing sessions */ + +static void write_message __P1 (char *,s) +{ + clear_input_line(opt_compact); + if (!opt_compact) { + tty_putc('\n'); + status(1); + } + tty_puts(s); +} + +/* + * Process editing protocol message from buf with len remaining chars. + * Return number of characters used in the message. + */ +int process_message __P2 (char *,buf, int,len) +{ + int msglen, i, l, used; + char *text; + char msg[BUFSIZE]; + + status(1); + + msglen = atoi(buf + 1); + for (i = 1; i < len && isdigit(buf[i]); i++) + ; + + if (i < len && buf[i] == '\r') i++; + if (i >= len || buf[i] != '\n') { + write_message ("#warning: MPI protocol error\n"); + return 0; + } + + l = len - ++i; + if (msglen < l) { + l = msglen; + used = msglen + i; + } else + used = len; + + text = (char *)malloc(msglen); + if (!text) { + errmsg("malloc"); + return used > len ? len : used; + } + + memmove(text, buf + i, l); + i = l; + while(i < msglen) { + /* + * read() might block here, but it should't be long. I should also + * process any telnet protocol commands, but I don't care right now. + */ + while ((l = read(tcp_fd, text + i, msglen - i)) == -1 && errno == EINTR) + ; + if (l == -1) { + errmsg("read message from socket"); + return used > len ? len : used; + } + tty_printf("\rgot %d chars out of %d", i, msglen); + tty_flush(); + i += l; + } + tty_printf("\rread all %d chars.%s\n", msglen, tty_clreoln); + + if ((msglen = tcp_unIAC(text, msglen))) + switch(*buf) { + case 'E': + message_edit(text, msglen, 0, 0); + break; + case 'V': + message_edit(text, msglen, 1, 0); + break; + default: + sprintf(msg, "#warning: received a funny message (0x%x)\n", *buf); + write_message(msg); + free(text); + break; + } + + return used; +} + +/* + * abort an editing session when + * the MUD socket it comes from gets closed + */ +void abort_edit_fd __P1 (int,fd) +{ + editsess **sp, *p; + + if (fd < 0) + return; + + for (sp = &edit_sess; *sp; sp = &(*sp)->next) { + p = *sp; + if (p->fd != fd) + continue; + + if (kill(p->pid, SIGKILL) < 0) { /* Editicide */ + errmsg("kill editor child"); + continue; + } + + PRINTF("#aborted `%s' (%u)\n", p->descr, p->key); + p->cancel = 1; + } +} + +/* + * cancel an editing session; does not free anything + * (the child death signal handler will remove the session from the list) + */ +void cancel_edit __P1 (editsess *,sp) +{ + char buf[BUFSIZE]; + char keystr[16]; + + if (kill(sp->pid, SIGKILL) < 0) { /* Editicide */ + errmsg("kill editor child"); + return; + } + PRINTF("#killed `%s' (%u)\n", sp->descr, sp->key); + sprintf(keystr, "C%u\n", sp->key); + sprintf(buf, "%sE%d\n%s", MPI, (int)strlen(keystr), keystr); + tcp_write(sp->fd, buf); + sp->cancel = 1; +} + +/* + * send back edited text to server, or cancel the editing session if the + * file was not changed. + */ +static void finish_edit __P1 (editsess *,sp) +{ + char *realtext = NULL, *text; + int fd, txtlen, hdrlen; + struct stat sbuf; + char keystr[16], buf[256], hdr[65]; + + if (sp->fd == -1) + goto cleanup_file; + + fd = open(sp->file, O_RDONLY); + if (fd == -1) { + errmsg("open edit file"); + goto cleanup_file; + } + if (fstat(fd, &sbuf) == -1) { + errmsg("fstat edit file"); + goto cleanup_fd; + } + + txtlen = sbuf.st_size; + + if (!sp->cancel && (sbuf.st_mtime > sp->ctime || txtlen != sp->oldsize)) { + /* file was changed by editor: send back result to server */ + + realtext = (char *)malloc(2*txtlen + 65); + /* *2 to protect IACs, +1 is for possible LF, +64 for header */ + if (!realtext) { + errmsg("malloc"); + goto cleanup_fd; + } + + text = realtext + 64; + if ((txtlen = tcp_read_addIAC(fd, text, txtlen)) == -1) + goto cleanup_text; + + if (txtlen && text[txtlen - 1] != '\n') { + /* If the last line isn't LF-terminated, add an LF; + * however, empty files must remain empty */ + text[txtlen] = '\n'; + txtlen++; + } + + sprintf(keystr, "E%u\n", sp->key); + + sprintf(hdr, "%sE%d\n%s", MPI, (int)(txtlen + strlen(keystr)), keystr); + + text -= (hdrlen = strlen(hdr)); + memcpy(text, hdr, hdrlen); + + /* text[hdrlen + txtlen] = '\0'; */ + tcp_raw_write(sp->fd, text, hdrlen + txtlen); + + sprintf(buf, "#completed session %s (%u)\n", sp->descr, sp->key); + write_message(buf); + } else { + /* file wasn't changed, cancel editing session */ + sprintf(keystr, "C%u\n", sp->key); + sprintf(hdr, "%sE%d\n%s", MPI, (int) strlen(keystr), keystr); + + tcp_raw_write(sp->fd, hdr, strlen(hdr)); + + sprintf(buf, "#cancelled session %s (%u)\n", sp->descr, sp->key); + write_message(buf); + } + +cleanup_text: if (realtext) free(realtext); +cleanup_fd: close(fd); +cleanup_file: if (unlink(sp->file) < 0) + errmsg("unlink edit file"); +} + +/* + * start an editing session: process the EDIT/VIEW message + * if view == 1, text will be viewed, else edited + */ +void message_edit __P4 (char *,text, int,msglen, char,view, char,builtin) +{ + char tmpname[BUFSIZE], command_str[BUFSIZE], buf[BUFSIZE]; + char *errdesc = "#warning: protocol error (message_edit, no %s)\n"; + int tmpfd, i, childpid; + unsigned int key; + editsess *s; + char *editor, *descr; + char *args[4]; + int waitforeditor; + + status(1); + + args[0] = "/bin/sh"; args[1] = "-c"; + args[2] = command_str; args[3] = 0; + + if (view) { + key = (unsigned int)-1; + i = 0; + } else { + if (text[0] != 'M') { + tty_printf(errdesc, "M"); + free(text); + return; + } + for (i = 1; i < msglen && isdigit(text[i]); i++) + ; + if (text[i++] != '\n' || i >= msglen) { + tty_printf(errdesc, "\\n"); + free(text); + return; + } + key = strtoul(text + 1, NULL, 10); + } + descr = text + i; + while (i < msglen && text[i] != '\n') i++; + if (i >= msglen) { + tty_printf(errdesc, "desc"); + free(text); + return; + } + text[i++] = '\0'; + + sprintf(tmpname, "/tmp/powwow.%u.%d%d", key, getpid(), abs(rand()) >> 8); + if ((tmpfd = open(tmpname, O_WRONLY | O_CREAT, 0600)) < 0) { + errmsg("create temp edit file"); + free(text); + return; + } + if (write(tmpfd, text + i, msglen - i) < msglen - i) { + errmsg("write to temp edit file"); + free(text); + close(tmpfd); + return; + } + close(tmpfd); + + s = (editsess*)malloc(sizeof(editsess)); + if (!s) { + errmsg("malloc"); + return; + } + s->ctime = time((time_t*)NULL); + s->oldsize = msglen - i; + s->key = key; + s->fd = (view || builtin) ? -1 : tcp_fd; /* MUME doesn't expect a reply. */ + s->cancel = 0; + s->descr = my_strdup(descr); + s->file = my_strdup(tmpname); + free(text); + + /* send a edit_start message (if wanted) */ + if ((!edit_sess) && (*edit_start)) { + error = 0; + parse_instruction(edit_start, 0, 0, 1); + history_done = 0; + } + + if (view) { + if (!(editor = getenv("POWWOWPAGER")) && !(editor = getenv("PAGER"))) + editor = "more"; + } else { + if (!(editor = getenv("POWWOWEDITOR")) && !(editor = getenv("EDITOR"))) + editor = "emacs"; + } + + if (editor[0] == '&') { + waitforeditor = 0; + editor++; + } else + waitforeditor = 1; + + if (waitforeditor) { + tty_quit(); + /* ignore SIGINT since interrupting the child would interrupt us too, + if we are in the same tty group */ + sig_permanent(SIGINT, SIG_IGN); + sig_permanent(SIGCHLD, SIG_DFL); + } + + switch(childpid = fork()) { /* let's get schizophrenic */ + case 0: + sprintf(command_str, "%s %s", editor, s->file); + sprintf(buf, "TITLE=%s", s->descr); + putenv(buf); + /* setenv("TITLE", s->descr, 1);*/ + execvp((char *)args[0], (char **)args); + syserr("execve"); + break; + case -1: + errmsg("fork"); + free(s->descr); + free(s->file); + free(s); + return; + } + s->pid = childpid; + if (waitforeditor) { + while ((i = waitpid(childpid, (int*)NULL, 0)) == -1 && errno == EINTR) + ; + + signal_start(); /* reset SIGINT and SIGCHLD handlers */ + tty_start(); + + if (s->fd != -1) { + tty_gotoxy(0, lines - 1); + tty_putc('\n'); + } + + if (i == -1) + errmsg("waitpid"); + else + finish_edit(s); + + free(s->descr); + free(s->file); + if (i != -1 && !edit_sess && *edit_end) { + error = 0; + parse_instruction(edit_end, 0, 0, 1); + history_done = 0; + } + + free(s); + + } else { + s->next = edit_sess; + edit_sess = s; + } +} + +/* + * Our child has snuffed it. check if it was an editor, and update the + * session list if that is the case. + */ +void sig_chld_bottomhalf __P0 (void) +{ + int fd, pid, ret; + editsess **sp, *p; + + /* GH: while() instead of just one check */ + while ((pid = waitpid(-1, &ret, WNOHANG)) > 0) { + + /* GH: check for WIFSTOPPED unnecessary since no */ + /* WUNTRACED to waitpid() */ + for (sp = &edit_sess; *sp && (*sp)->pid != pid; sp = &(*sp)->next) + ; + if (*sp) { + finish_edit(*sp); + p = *sp; *sp = p->next; + fd = p->fd; + free(p->descr); + free(p->file); + free(p); + + /* GH: only send message if found matching session */ + + /* send the edit_end message if this is the last editor... */ + if ((!edit_sess) && (*edit_end)) { + int otcp_fd = tcp_fd; /* backup current socket fd */ + tcp_fd = fd; + error = 0; + parse_instruction(edit_end, 0, 0, 1); + history_done = 0; + tcp_fd = otcp_fd; + } + } + } +} + |