/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #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(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(char *buf, int len) { int msglen, i, l, used; char *text, *from, *to; char msg[BUFSIZE]; int got_iac; 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; text = (char *)malloc(msglen); if (!text) { errmsg("malloc"); return i; } /* only doing trivial (IAC IAC) processing; this should all be * rewritten */ got_iac = 0; from = buf + i; to = text; for (i = 0; i < l && (to - text) < msglen; ++i) { if (got_iac) { if (*from != (char)IAC) errmsg("got IAC in MPI message; treating as IAC IAC"); else ++from; got_iac = 0; } else { got_iac = (*to++ = *from++) == (char)IAC; } } i = to - text; used = from - buf; while (i < msglen) { /* read() might block here, but it should't be long */ 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; } from = to = text + i; for (i = 0; i < l; ++i) { if (got_iac) { if (*from != (char)IAC) errmsg("got IAC in MPI message; treating as IAC IAC"); else ++from; got_iac = 0; } else { got_iac = (*to++ = *from++) == (char)IAC; } } i = to - text; tty_printf("\rgot %d chars out of %d", i, msglen); tty_flush(); } tty_printf("\rread all %d chars.%s\n", msglen, tty_clreoln); 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(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(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; } static ssize_t read_file(int fd, void *buf, size_t count) { size_t result = 0; while (count > 0) { int r; do { r = read(fd, buf, count); } while (r < 0 && errno == EINTR); if (r < 0) return -1; if (r == 0) break; result += r; buf += r; count -= r; } return result; } /* * send back edited text to server, or cancel the editing session if the * file was not changed. */ static void finish_edit(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(txtlen + 65); /* +1 is for possible LF, +64 for header */ if (!realtext) { errmsg("malloc"); goto cleanup_fd; } text = realtext + 64; txtlen = read_file(fd, text, txtlen); if (txtlen < 0) 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_write_escape_iac(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(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 (msglen < 1 || 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); setenv("TITLE", s->descr, 1); execvp(args[0], 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(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; } } } }