summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorpiaip <piaip@63ad8ddf-47c3-0310-b6dd-a9e9d9715204>2009-11-08 11:49:50 +0800
committerpiaip <piaip@63ad8ddf-47c3-0310-b6dd-a9e9d9715204>2009-11-08 11:49:50 +0800
commita29ff2de33116af3a1abd64913ec4b84a08fcf6a (patch)
tree2b1d0e37e38d7899d8af34c9b899e45a88b9ea84
parent60cb92c89ecfe44d52f6503b36cd2ea3fb9f5b5f (diff)
downloadpttbbs-a29ff2de33116af3a1abd64913ec4b84a08fcf6a.tar
pttbbs-a29ff2de33116af3a1abd64913ec4b84a08fcf6a.tar.gz
pttbbs-a29ff2de33116af3a1abd64913ec4b84a08fcf6a.tar.bz2
pttbbs-a29ff2de33116af3a1abd64913ec4b84a08fcf6a.tar.lz
pttbbs-a29ff2de33116af3a1abd64913ec4b84a08fcf6a.tar.xz
pttbbs-a29ff2de33116af3a1abd64913ec4b84a08fcf6a.tar.zst
pttbbs-a29ff2de33116af3a1abd64913ec4b84a08fcf6a.zip
* nios: the new network i/o framework
git-svn-id: http://opensvn.csie.org/pttbbs/trunk@5021 63ad8ddf-47c3-0310-b6dd-a9e9d9715204
-rw-r--r--pttbbs/mbbsd/nios.c667
1 files changed, 667 insertions, 0 deletions
diff --git a/pttbbs/mbbsd/nios.c b/pttbbs/mbbsd/nios.c
new file mode 100644
index 00000000..93afe6c7
--- /dev/null
+++ b/pttbbs/mbbsd/nios.c
@@ -0,0 +1,667 @@
+/* $Id$ */
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <poll.h>
+#include <errno.h>
+#include "vtkbd.h"
+#include "bbs.h"
+
+#ifdef EXP_NIOS
+
+// nios: piaip's Network I/O Stream
+// piaip's New implementation for Input and Output System
+// Author: Hung-Te Lin (piaip)
+// Create: Mon Oct 26 01:56:27 CST 2009
+// --------------------------------------------------------------------------
+// Copyright (c) 2009 Hung-Te Lin <piaip@csie.ntu.edu.tw>
+// All rights reserved.
+// Distributed under BSD license (GPL compatible).
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// --------------------------------------------------------------------------
+// The input system has several layers:
+// 1. OS socket file descriptor (read/write): network -> fd
+// 2. console stream (nios.c, cin_*): fd -> buffer
+// 3. (optional) Telnet Protocol (common/sys/telnet.c): buffer -> term
+// 4. (optional) Encoding Conversion (convert.c): term(enc1) -> term(enc2)
+// 5. Virtual Terminal Keyboard (vtkbd.c): term -> key
+// 6. Virtual Key (nios.c, vkey_*) key -> preprocess and management
+// --------------------------------------------------------------------------
+
+// features
+#define VKEY_USE_CIN
+
+#ifdef DEBUG
+#define CIN_DEBUG
+#define VKEY_DEBUG
+#endif
+
+#ifndef DEBUG
+#define CIN_PROTO static inline
+#define VKEY_PROTO inline
+#else
+#define CIN_PROTO
+#define VKEY_PROTO
+#endif
+
+// XXX use this temporary...
+int process_pager_keys(int ch);
+
+// debug helpers
+#if defined(CIN_DEBUG) || defined(VKEY_DEBUG)
+#include <stdarg.h>
+static void
+nios_dbgf(const char *fmt, ...)
+{
+ char msg[256];
+ time4_t now = time(0);
+ const char *logfn = BBSHOME "/log/niosdbg.log";
+ va_list ap;
+
+ va_start(ap, fmt);
+ snprintf (msg, sizeof(msg), "%s %*s ", Cdate_mdHMS(&now), IDLEN, cuser.userid);
+ vsnprintf(msg + strlen(msg), sizeof(msg)-strlen(msg), fmt, ap);
+ va_end(ap);
+ strlcat(msg, "\n", sizeof(msg));
+
+ log_file(logfn, LOG_CREAT, msg);
+}
+#endif
+
+#ifdef CIN_DEBUG
+# define CINDBGLOG(...) nios_dbgf( __VA_ARGS__)
+# else
+# define CINDBGLOG(...)
+#endif
+#ifdef VKEY_DEBUG
+# define VKEYDBGLOG(...) nios_dbgf( __VA_ARGS__)
+# else
+# define VKEYDBGLOG(...)
+#endif
+
+/////////////////////////////////////////////////////////////////////////////
+// console stream input (nios:cin): fd -> buffer
+
+// configuration
+#define CIN_BUFFER_SIZE (4096)
+#define CIN_DEFAULT_FD (cin_fd)
+
+// API prototypes
+
+// buffer management
+CIN_PROTO int cin_is_buffer_empty(void); // check if input buffer is empty
+CIN_PROTO int cin_is_buffer_full(void); // check if input buffer is full
+CIN_PROTO void cin_clear_buffer(void); // drop everying in buffer
+CIN_PROTO int cin_scan_buffer(char c); // return if buffer has given character
+// fd management
+CIN_PROTO int cin_is_fd_empty(int fd); // quick determine if a fd is empty
+CIN_PROTO int cin_poll_fds(int fd1, int fd2, int ms); // poll fd1 and fd2 for ms milliseconds
+CIN_PROTO void cin_clear_fd(int fd); // drop everying in fd
+CIN_PROTO void cin_fetch_fd(int fd); // read more data from fd to buffer
+// virtual combination
+CIN_PROTO int cin_read(void); // read one byte from cin (buffer then cin_fd)
+
+// virtual buffer for cin
+static const int cin_fd = STDIN_FILENO;
+static char cin_buf[CIN_BUFFER_SIZE];
+static VBUF vcin = {
+ .head = cin_buf,
+ .tail = cin_buf,
+ .capacity= sizeof(cin_buf)-1,
+ .buf = cin_buf,
+ .buf_end = cin_buf + sizeof(cin_buf),
+}, *cin = &vcin;
+
+#ifdef CIN_DEBUG
+static int cin_debugging = 0;
+CIN_PROTO void cin_debug_print_content();
+#endif
+
+/**
+ * cin_init(): initialize cin context
+ */
+CIN_PROTO void
+cin_init()
+{
+ vbuf_attach(cin, cin_buf, sizeof(cin_buf));
+}
+
+/**
+ * cin_is_buffer_empty(): quick check if input buffer is empty
+ */
+CIN_PROTO int
+cin_is_buffer_empty()
+{
+ return vbuf_is_empty(cin);
+}
+
+/**
+ * cin_is_buffer_full(): check if input buffer is full
+ */
+CIN_PROTO int
+cin_is_buffer_full()
+{
+ return vbuf_is_full(cin);
+}
+
+/**
+ * cin_scan_buffer(c): return if buffer has given character
+ */
+CIN_PROTO int
+cin_scan_buffer(char c)
+{
+ return vbuf_strchr(cin, c) >= 0;
+}
+
+/**
+ * cin_is_fd_empty(fd): query if cin_fd is empty
+ * @return: 1 for empty, 0 for error / data available.
+ */
+CIN_PROTO int
+cin_is_fd_empty(int fd)
+{
+ CINDBGLOG("cin_is_fd_empty(%d)", fd);
+
+#ifdef FIONREAD
+// #warning nios:cin_is_fd_empty(): using ioctl(FIONREAD) method.
+ // try ioctl
+ int r;
+ if (ioctl(fd, FIONREAD, &r) == 0)
+ return r == 0;
+ assert(!"sorry, your system failed for FIONREAD...");
+ return 0; // error
+
+#elif defined(MSG_DONTWAIT) && defined(MSG_PEEK)
+# warning nios:cin_is_fd_empty(): changed to recv(MSG_DONTWAIT) method.
+ // try recv
+ int r;
+ char c = 0;
+ r = recv(fd, &c, sizeof(c), MSG_PEEK | MSG_DONTWAIT);
+ if (errno == EAGAIN)
+ return 1;
+ // XXX errno should not be EINTR...
+ assert(errno != EINTR);
+ return r > 0 ? 0 : 1;
+
+#else
+# warning nios:cin_is_fd_empty(): changed to polling method.
+ // we can only use poll.
+ return cin_poll_fds(-1, 0) == 0;
+
+#endif
+}
+
+/**
+ * cin_poll_fds(fd1, fd2, ms): query the input status of given fds.
+ * @param fd1: primary fd to check
+ * @param fd2: skip if <= 0
+ * @param ms: 0 for non-blocking, INFTIM(-1) for infinite, otherwise timeout milliseconds
+ * @return: bitmask of fds having data/error, 0 for timeout, and -1 if error.
+ */
+#define CIN_POLL_FDS_MASK (POLLIN|POLLERR|POLLHUP|POLLNVAL)
+// mask to check the return value of cin_poll_fds
+#define CIN_POLL_CINFD (1)
+#define CIN_POLL_FD2 (2)
+
+#define CIN_IS_VALID_FD2(fd) ((fd) > 0)
+
+CIN_PROTO int
+cin_poll_fds(int fd1, int fd2, int timeout)
+{
+ CINDBGLOG("cin_poll_fds(fd1=%d, fd2=%d, timeout=%d)", fd1, fd2, timeout);
+
+ int r;
+ struct pollfd fds[2] ={
+ { .fd = fd1, .events = POLLIN, .revents = 0, },
+ { .fd = fd2, .events = POLLIN, .revents = 0, },
+ };
+
+#ifdef STAT_SYSSELECT
+ STATINC(STAT_SYSSELECT);
+#endif
+ while ( 0 > (r = poll(fds, CIN_IS_VALID_FD2(fd2) ? 2 : 1, timeout)) &&
+ errno == EINTR);
+ assert(r >= 0);
+ if (r <= 0)
+ return r;
+
+ r = ((fds[0].revents & CIN_POLL_FDS_MASK) ? CIN_POLL_CINFD : 0) |
+ ((fds[1].revents & CIN_POLL_FDS_MASK) ? CIN_POLL_FD2 : 0);
+ return r;
+}
+
+/**
+ * cin_clear_buffer(): drop everying in buffer
+ */
+CIN_PROTO void
+cin_clear_buffer()
+{
+ CINDBGLOG("cin_clear_buffer()");
+ vbuf_clear(cin);
+}
+
+/**
+ * cin_clear_fd(int fd): quick discard all data in fd
+ */
+CIN_PROTO void
+cin_clear_fd(int fd)
+{
+ CINDBGLOG("cin_clear_fd(%d)", fd);
+ // method 1, use setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (char *)(&optval), sizeof(optval));
+ // method 2, fetch and discard
+ VBUF vgarbage, *v = &vgarbage;
+ char garbage[4096]; // any magic number works here
+
+ if (cin_is_fd_empty(fd))
+ return;
+
+ vbuf_attach(v, garbage, sizeof(garbage));
+ do {
+ vbuf_read(v, fd, VBUF_RWSZ_MIN);
+ if (vbuf_is_empty(v)) // maybe read error
+ break;
+ vbuf_clear(v);
+ } while (!cin_is_fd_empty(fd));
+}
+
+/**
+ * cin_fetch_fd(fd): read more data from fd to buffer
+ */
+CIN_PROTO void
+cin_fetch_fd(int fd)
+{
+#ifdef STAT_SYSREADSOCKET
+ STATINC(STAT_SYSREADSOCKET);
+#endif
+ assert(fd == cin_fd);
+#if 1
+ // XXX we don't know how to deal with telnetctx/convert yet...
+ char buf[sizeof(cin_buf)];
+ ssize_t sz;
+ do {
+ sz = tty_read((unsigned char*)buf, vbuf_space(cin));
+ // for tty_read: sz<0 = EAGAIN
+ if (sz > 0)
+ {
+ vbuf_putblk(cin, buf, sz);
+ }
+ } while (sz < 0);
+#else
+ // try to read data from stdin
+ vbuf_read(cin, fd, VBUF_RWSZ_MIN);
+#endif
+#ifdef CIN_DEBUG
+ cin_debug_print_content();
+#endif
+}
+
+/**
+ * cin_read(): read one byte from cin (buffer then fd)
+ * @return: EOF if error, otherwise the first byte from cin.
+ */
+CIN_PROTO int
+cin_read()
+{
+ if (!cin_is_buffer_empty())
+ return vbuf_pop(cin);
+
+ CINDBGLOG("cin_read()");
+
+ cin_fetch_fd(cin_fd);
+ if (cin_is_buffer_empty())
+ return EOF;
+
+ return vbuf_pop(cin);
+}
+
+#ifdef CIN_DEBUG
+void debug_print_input_buffer(char *s, size_t len);
+
+CIN_PROTO void
+cin_debug_print_content()
+{
+ if (!cin_debugging || cin_is_buffer_empty())
+ return;
+ debug_print_input_buffer(vbuf_cstr(cin), vbuf_size(cin));
+}
+#endif
+
+/////////////////////////////////////////////////////////////////////////////
+// virtual key (nios:vkey): key -> preprocess and management
+
+// NOTE for every vkey* API, you must consider:
+// 1. PEEKed character in vkctx
+// 2. cin_buffer and cin_fd
+// 3. vkctx.attached_fd()
+// using the vkey_process() will handle all these cases. otherwise, you
+// have to take care of all thse input sources.
+
+// vkey context
+typedef struct VKEY_CTX {
+ int peek_ch;
+ int attached_fd;
+ VtkbdCtx vtkbd;
+ // TelnetCtx telnet;
+} VKEY_CTX;
+
+static VKEY_CTX vkctx = {
+ .peek_ch = KEY_INCOMPLETE,
+};
+
+#define VKEY_HAS_PEEK() (vkctx.peek_ch != KEY_INCOMPLETE)
+#define VKEY_GET_PEEK() (vkctx.peek_ch)
+#define VKEY_SET_PEEK(c) (vkctx.peek_ch = (c))
+#define VKEY_RESET_PEEK() (VKEY_SET_PEEK(KEY_INCOMPLETE))
+
+VKEY_PROTO void
+vkey_init()
+{
+ VKEYDBGLOG("vkey_init()");
+
+ memset(&vkctx, 0, sizeof(vkctx));
+ cin_init();
+ VKEY_RESET_PEEK();
+ // XXX initialize telnet, convert, ...?
+}
+
+/**
+ * vkey_attach(fd): attach and replace additional fd to vkey*
+ * @param fd: the file descriptor to attach
+ * @return: previously attached fd
+ */
+VKEY_PROTO int
+vkey_attach(int fd)
+{
+ VKEYDBGLOG("vkey_attach(%d)", fd);
+
+ int r = vkctx.attached_fd;
+ vkctx.attached_fd = fd;
+ return r;
+}
+
+/**
+ * vkey_detach(): detach any additional fd in vkey*
+ */
+VKEY_PROTO int
+vkey_detach()
+{
+ CINDBGLOG("vkey_detach()");
+
+ int r = vkctx.attached_fd;
+ vkctx.attached_fd = 0;
+ return r;
+}
+
+/**
+ * vkey_is_full(): return if key buffer is full.
+ */
+VKEY_PROTO int
+vkey_is_full()
+{
+ VKEYDBGLOG("vkey_is_full()");
+ return cin_is_buffer_full();
+}
+
+/**
+ * vkey_process_cin(): read data from cin and process.
+ * @return first byte from cin, or EOF for error
+ */
+static VKEY_PROTO int
+vkey_process_cin()
+{
+ VKEYDBGLOG("vkey_process_cin()");
+
+ int ch;
+
+ ch = cin_read();
+ if (ch == EOF) // quick abort
+ return EOF;
+
+ // TODO process telnet protocol?
+ // (it's now temporary done in cin_read -> tty_read... )
+
+ // convert virtual terminal keys
+ ch = vtkbd_process(ch, &vkctx.vtkbd);
+
+ // process transparent keys
+ switch(ch)
+ {
+ case KEY_ESC:
+ // XXX change this to (META|key) someday...
+ KEY_ESC_arg = vkctx.vtkbd.esc_arg;
+ break;
+
+ // TODO move this to somewhere else?
+ case Ctrl('L'):
+ ch = KEY_INCOMPLETE;
+ redrawwin();
+ refresh();
+ // doupdate();
+ break;
+
+#ifdef DEBUG
+ case Ctrl('Q'):
+ ch = KEY_INCOMPLETE;
+ {
+ struct rusage ru;
+ getrusage(RUSAGE_SELF, &ru);
+ vmsgf("sbrk: %ld KB, idrss: %d KB, isrss: %d KB",
+ (sbrk(0) - (void*)0x8048000) / 1024,
+ (int)ru.ru_idrss, (int)ru.ru_isrss);
+#ifdef CIN_DEBUG
+ cin_debugging = !cin_debugging;
+#endif
+ }
+ break;
+#endif
+ }
+ if (currutmp) ch = process_pager_keys(ch);
+ return ch;
+}
+
+/**
+ * vkey_process(timeout, peek): receive and block (max to timeout milliseconds) next key
+ * @param timeout: 0 for non-block, INFTIM(-1) for infinite, otherwise milliseconds
+ * @param peek: to keep the data in buffer
+ * @return: virtual key code, or KEY_INCOMPLETE for timeout
+ */
+static VKEY_PROTO int
+vkey_process(int timeout, int peek)
+{
+ VKEYDBGLOG("vkey_process(%d, %d)", timeout, peek);
+
+ int r;
+
+ // process lask peeked data
+ if (VKEY_HAS_PEEK())
+ {
+ // cached
+ r = VKEY_GET_PEEK();
+ if (!peek)
+ VKEY_RESET_PEEK();
+ return r;
+ }
+
+ do {
+ if (!cin_is_buffer_empty())
+ {
+ r = vkey_process_cin();
+ continue;
+ }
+
+ // XXX should we let cin_poll select from fds of fd_empty?
+
+ // now, try to read from fd.
+ assert(1 == CIN_POLL_CINFD); // cin_is_fd_empty() will return 1.
+ if (timeout > 0 || CIN_IS_VALID_FD2(vkctx.attached_fd))
+ {
+ r = cin_poll_fds(CIN_DEFAULT_FD, vkctx.attached_fd, timeout);
+ }
+ else if (timeout == 0)
+ {
+ r = cin_is_fd_empty(CIN_DEFAULT_FD);
+ }
+ else {
+ assert(timeout == INFTIM); // let's simply read it.
+ r = CIN_POLL_CINFD;
+ }
+
+ if (r == 0) // timeout
+ {
+ if (KEY_INCOMPLETE != I_TIMEOUT && CIN_IS_VALID_FD2(vkctx.attached_fd))
+ r = I_TIMEOUT;
+ else
+ return KEY_INCOMPLETE; // must directly return here.
+ }
+ if (r < 0) // error
+ r = KEY_UNKNOWN; // or EOF?
+ else if (r & CIN_POLL_FD2) // the second fd
+ r = I_OTHERDATA;
+ else {
+ assert(r & CIN_POLL_CINFD);
+ r = vkey_process_cin();
+ }
+ } while (r == KEY_INCOMPLETE);
+
+ // process peek
+ if (peek && r != KEY_INCOMPLETE)
+ VKEY_SET_PEEK(r);
+
+ return r;
+}
+
+/**
+ * vkey_is_ready(): determine if input buffer is complete for a key input
+ * @return: 1 for something to read/error, 0 for incomplete
+ */
+VKEY_PROTO int
+vkey_is_ready()
+{
+ VKEYDBGLOG("vkey_is_ready()");
+ return vkey_process(0, 1) != KEY_INCOMPLETE;
+}
+
+/**
+ * vkey_poll(timeout): poll for timeout milliseconds and return if key is ready
+ * @param timeout: 0 for non-block, INFTIM(-1) for infinite, otherwise milliseconds
+ * @return: 1 for ready, otherwise 0
+ */
+VKEY_PROTO int
+vkey_poll(int timeout)
+{
+ if (timeout) refresh();
+ VKEYDBGLOG("vkey_poll(%d)", timeout);
+ return vkey_process(timeout, 1) != KEY_INCOMPLETE;
+}
+
+/**
+ * vkey_is_typeahead(): quick check if input buffer has data arrived (maybe not ready yet)
+ * @return: 0 for empty, otherwise 1
+ */
+VKEY_PROTO int
+vkey_is_typeahead()
+{
+ VKEYDBGLOG("vkey_is_typeahead(): %d|%d",
+ VKEY_HAS_PEEK() || !cin_is_buffer_empty());
+
+ return VKEY_HAS_PEEK() || !cin_is_buffer_empty();
+}
+
+/**
+ * vkey_prefetch(timeout): try to prefecth as more data as possible from fd to buffer for up to timeout milliseconds
+ */
+VKEY_PROTO int
+vkey_prefetch(int timeout)
+{
+ VKEYDBGLOG("vkey_prefetch(%d)", timeout);
+
+ // always only prefetch cin_fd
+ if (cin_poll_fds(CIN_DEFAULT_FD, -1, timeout) == 0) // timeout
+ return 0;
+
+ // error or valid date
+ cin_fetch_fd(CIN_DEFAULT_FD);
+ return 1;
+}
+
+/**
+ * vkey_is_prefetched(c): check if c (in raw data form) is already in prefetched buffer
+ */
+VKEY_PROTO int
+vkey_is_prefetched(char c)
+{
+ // VKEYDBGLOG("vkey_is_prefetched(0x%02X)", c);
+
+ if ((int)c == VKEY_GET_PEEK())
+ return 1;
+
+ if (cin_scan_buffer(c))
+ return 1;
+
+ return 0;
+}
+
+/**
+ * vkey(): receive and block for next key
+ * @return: virtual key code
+ */
+VKEY_PROTO int
+vkey()
+{
+ VKEYDBGLOG("vkey()");
+
+ int c;
+ refresh();
+
+ while ((c = vkey_process(INFTIM, 1)) == KEY_INCOMPLETE);
+ return c;
+}
+
+/**
+ * vkey_purge(): clear and discard all data in current input buffer
+ */
+VKEY_PROTO void
+vkey_purge()
+{
+ VKEYDBGLOG("vkey_purge()");
+
+ // a magic number to set how much data we're going to process
+ // before a real purge (may contain telnet protocol)
+ int bytes_before_purge = 32;
+
+ // let's still try to process some remaining bytes
+ vkey_prefetch(0);
+ while (vkey_is_ready() && bytes_before_purge-- > 0)
+ {
+ // we can't deal with I_OTHERDATA...
+ if (vkey() == I_OTHERDATA)
+ break;
+ }
+
+ // ok, now let's try our best to purge all remaining data
+ cin_clear_fd(CIN_DEFAULT_FD);
+
+ // XXX in current usage, we don't expect vkey_purge to clean
+ // remote FDs.
+#if 0
+ if (CIN_IS_VALID_FD2(vkctx.attached_fd))
+ cin_clear_fd(vkctx.attached_fd);
+#endif
+
+ cin_clear_buffer();
+ VKEY_RESET_PEEK();
+
+ // TODO reset telnet/vtkbd/conver?
+}
+
+#endif // EXP_NIOS
+
+// vim:ts=4:sw=4:et