summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--include/proto.h11
-rw-r--r--mbbsd/Makefile3
-rw-r--r--mbbsd/more.c1
-rw-r--r--mbbsd/pmore.c778
-rw-r--r--sample/pttbbs.conf3
5 files changed, 794 insertions, 2 deletions
diff --git a/include/proto.h b/include/proto.h
index 1e031e9a..558a6b05 100644
--- a/include/proto.h
+++ b/include/proto.h
@@ -384,8 +384,17 @@ int m_sob(void);
void m_sob_brd(char *bname,char *fromdir);
#endif
-/* more */
+#ifdef USE_PIAIP_MORE
+#ifndef REAL_MORE
+#define more(a,b) pmore(a,b)
+#endif
+#else
+/* old more */
int more(char *fpath, int promptend);
+#endif
+/* piaip's new pager */
+int pmore(char *fpath, int promptend);
+
/* name */
typedef int (*gnc_comp_func)(int, const char*, int);
diff --git a/mbbsd/Makefile b/mbbsd/Makefile
index 18458e65..8f2abc22 100644
--- a/mbbsd/Makefile
+++ b/mbbsd/Makefile
@@ -14,7 +14,8 @@ OBJS= admin.o announce.o args.o assess.o bbs.o board.o cache.o cal.o card.o\
gomo.o guess.o indict.o io.o kaede.o lovepaper.o mail.o mbbsd.o menu.o\
more.o name.o osdep.o othello.o page.o read.o record.o register.o\
screen.o stuff.o talk.o term.o topsong.o user.o brc.o vice.o vote.o\
- xyz.o voteboard.o syspost.o var.o passwd.o calendar.o go.o file.o
+ xyz.o voteboard.o syspost.o var.o passwd.o calendar.o go.o file.o \
+ pmore.o
.if defined(DIET)
OBJS+= random.o time.o
DIETCC= diet -Os
diff --git a/mbbsd/more.c b/mbbsd/more.c
index 10ca8a83..33af9d74 100644
--- a/mbbsd/more.c
+++ b/mbbsd/more.c
@@ -1,4 +1,5 @@
/* $Id$ */
+#define REAL_MORE
#include "bbs.h"
/* 把這兩個 size 調到一頁的範圍是不是能降低不必要的 IO ? */
diff --git a/mbbsd/pmore.c b/mbbsd/pmore.c
new file mode 100644
index 00000000..2ee82a3a
--- /dev/null
+++ b/mbbsd/pmore.c
@@ -0,0 +1,778 @@
+#include "bbs.h"
+
+/*
+ * piaip's new implementation of pager(more) with mmap,
+ * designed for unlimilited length(lines).
+ *
+ * Author: Hung-Te Lin (piaip), 2005
+ * <piaip@csie.ntu.edu.tw>
+ */
+
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <ctype.h>
+#include <string.h>
+
+// Platform Related
+#ifndef MAP_NOSYNC
+#define MAP_NOSYNC MAP_SHARED
+#endif
+
+//#define DEBUG
+
+typedef struct
+{
+ unsigned char
+ *start , *end, // file buffer
+ *disps, *dispe, // disply start/end
+ *maxdisps; // a very special pointer,
+ // consider as "disps of last page"
+ off_t len, // file total length
+ lineno; // lineno of disps
+
+} MmappedFile;
+
+MmappedFile mf = { 0, 0, 0, 0, 0 }; // current file
+
+/* mf_* navigation commands and return value meanings */
+enum {
+ MFNAV_OK, // navigation ok
+ MFNAV_EXCEED, // request exceeds buffer
+} MF_NAV_COMMANDS;
+
+#define MFNAV_PAGE (t_lines-2)
+#define RESETMF() memset(&mf, 0, sizeof(mf));
+#define ANSI_ESC (0x1b)
+
+int mf_backward(int);
+
+/* search records */
+enum {
+ MFSEARCH_FORWARD,
+ MFSEARCH_BACKWARD,
+} MFSEARCH_DIRECTION;
+
+typedef struct
+{
+ int len;
+ int (*cmpfunc) (const char *, const char *, size_t);
+ char search_str[81];
+} SearchRecord;
+
+SearchRecord sr = { 0, strncmp, "" };
+
+/*
+ * mmap basic operations
+ */
+int mf_attach(unsigned char *fn)
+{
+ struct stat st;
+ int fd = open(fn, O_RDONLY, 0600);
+
+ if(fd < 0)
+ return 0;
+
+ if (fstat(fd, &st) || ((mf.len = st.st_size) <= 0) || S_ISDIR(st.st_mode))
+ {
+ mf.len = 0;
+ close(fd);
+ return 0;
+ }
+
+// mf.len = lseek(fd, 0L, SEEK_END);
+// lseek(fd, 0, SEEK_SET);
+
+ mf.start = mmap(NULL, mf.len, PROT_READ,
+ MAP_NOSYNC, fd, 0);
+ close(fd);
+
+ if(mf.start == MAP_FAILED)
+ {
+ RESETMF();
+ return 0;
+ }
+
+ mf.end = mf.start + mf.len;
+ mf.disps = mf.dispe = mf.start;
+ mf.lineno = 0;
+ // build maxdisps
+ mf.disps = mf.end - 1;
+ mf_backward(MFNAV_PAGE);
+ mf.maxdisps = mf.disps;
+ mf.disps = mf.dispe = mf.start;
+ mf.lineno = 0;
+ return 1;
+}
+
+void mf_detach()
+{
+ if(mf.start) {
+ munmap(mf.start, mf.len);
+ RESETMF();
+ }
+}
+
+/*
+ * lineno calculation, and moving
+ */
+void mf_sync_lineno()
+{
+ unsigned char *p;
+ mf.lineno = 0;
+ for (p = mf.start; p < mf.disps; p++)
+ if(*p == '\n')
+ mf.lineno ++;
+}
+
+int mf_backward(int lines)
+{
+ int flFirstLine = 1;
+ // first, because we have to trace back to line beginning,
+ // add one line.
+ lines ++;
+
+ // now try to rollback for lines
+ if(lines == 1)
+ {
+ /* special case! just rollback to start */
+ while ( mf.disps > mf.start &&
+ *(mf.disps-1) != '\n')
+ mf.disps --;
+ mf.disps --;
+ lines --;
+ }
+ else while(mf.disps > mf.start && lines > 0)
+ {
+ while (mf.disps > mf.start && *--mf.disps != '\n');
+ if(flFirstLine)
+ {
+ flFirstLine = 0; lines--;
+ continue;
+ }
+
+ if(mf.disps >= mf.start)
+ mf.lineno--, lines--;
+ }
+
+ if(mf.disps == mf.start)
+ mf.lineno = 0;
+ else
+ mf.disps ++;
+
+ if(lines > 0)
+ return MFNAV_OK;
+ else
+ return MFNAV_EXCEED;
+}
+
+int mf_forward(int lines)
+{
+ while(mf.disps <= mf.maxdisps && lines > 0)
+ {
+ while (mf.disps <= mf.maxdisps && *mf.disps++ != '\n');
+
+ if(mf.disps <= mf.maxdisps)
+ mf.lineno++, lines--;
+ }
+
+ if(mf.disps > mf.maxdisps)
+ mf.disps = mf.maxdisps;
+
+ if(lines > 0)
+ return MFNAV_OK;
+ else
+ return MFNAV_EXCEED;
+}
+
+int mf_goTop()
+{
+ mf.disps = mf.start;
+ mf.lineno = 0;
+ return MFNAV_OK;
+}
+
+int mf_goBottom()
+{
+ mf.disps = mf.end-1;
+ mf_backward(MFNAV_PAGE);
+ // lineno?
+ mf_sync_lineno();
+ return MFNAV_OK;
+}
+
+int mf_goto(int lineno)
+{
+ mf.disps = mf.start;
+ mf.lineno = 0;
+ return mf_forward(lineno);
+}
+
+int mf_viewedNone()
+{
+ return (mf.disps <= mf.start);
+}
+
+int mf_viewedAll()
+{
+ return (mf.dispe >= mf.end-1);
+}
+/*
+ * search!
+ */
+int mf_search(int direction)
+{
+ unsigned char *s = sr.search_str;
+ int l = sr.len;
+ int flFound = 0;
+
+ if(!*s)
+ return 0;
+
+ if(direction == MFSEARCH_FORWARD)
+ {
+ mf_forward(1);
+ while(mf.disps < mf.end - l)
+ {
+ if(sr.cmpfunc(mf.disps, s, l) == 0)
+ {
+ flFound = 1;
+ break;
+ } else
+ mf.disps ++;
+ }
+ mf_backward(0);
+ if(mf.disps > mf.maxdisps)
+ mf.disps = mf.maxdisps;
+ mf_sync_lineno();
+ }
+ else if(direction == MFSEARCH_BACKWARD)
+ {
+ mf_backward(1);
+ while (!flFound && mf.disps > mf.start)
+ {
+ while(!flFound && mf.disps < mf.end-l && *mf.disps != '\n')
+ {
+ if(sr.cmpfunc(mf.disps, s, l) == 0)
+ {
+ flFound = 1;
+ } else
+ mf.disps ++;
+ }
+ if(!flFound)
+ mf_backward(1);
+ }
+ mf_backward(0);
+ if(mf.disps < mf.start)
+ mf.disps = mf.start;
+ mf_sync_lineno();
+ }
+ return flFound;
+}
+
+/*
+ * Format Related
+ */
+typedef struct
+{
+ int lines; // header lines
+ int authorlen;
+ int boardlen;
+} ArticleHeader;
+
+ArticleHeader ah;
+
+void mf_parseHeader()
+{
+ /* format:
+ * AUTHOR: author BOARD: blah
+ * XXX: xxx
+ * XXX: xxx
+ * [blank, fill with seperator]
+ *
+ * #define STR_AUTHOR1 "作者:"
+ * #define STR_AUTHOR2 "發信人:"
+ * #define STR_POST1 "看板:"
+ * #define STR_POST2 "站內:"
+ */
+ ah.lines = -1;
+ ah.authorlen= -1;
+ ah.boardlen = -1;
+ if(mf.len > LEN_AUTHOR2)
+ {
+ if (strncmp(mf.start, STR_AUTHOR1, LEN_AUTHOR1) == 0)
+ {
+ ah.lines = 3; // local
+ ah.authorlen = LEN_AUTHOR1;
+ }
+ else if (strncmp(mf.start, STR_AUTHOR2, LEN_AUTHOR2) == 0)
+ {
+ ah.lines = 4;
+ ah.authorlen = LEN_AUTHOR2;
+ }
+ /* traverse for author length */
+ {
+ unsigned char *p = mf.start;
+ unsigned char *pb = p;
+
+ /* first, go to line-end */
+ while(p < mf.end && *p != '\n')
+ p++;
+ pb = p;
+ /* next, rollback for ':' */
+ while(p > mf.start && *p != ':')
+ p--;
+ if(p > mf.start && *p == ':')
+ {
+ ah.boardlen = pb - p;
+ while (p > mf.start && *p != ' ')
+ p--;
+ if( *p == ' ')
+ {
+ ah.authorlen = p - mf.start - ah.authorlen;
+ } else
+ ah.boardlen = -1, ah.authorlen = -1;
+ } else
+ ah.authorlen = -1;
+ }
+ }
+}
+
+/*
+ * display mf content from disps for MFNAV_PAGE+1
+ */
+#define STR_ANSICODE "[0123456789;,"
+#define DISP_HEADS_LEN (4) // strlen of each heads
+static char *disp_heads[] = {"作者", "標題", "時間", "轉信"};
+
+void mf_disp()
+{
+ int lines = 0, col = 0, currline = 0;
+
+ mf.dispe = mf.disps;
+ while (lines < MFNAV_PAGE+1)
+ {
+ int inAnsi = 0;
+
+ currline = mf.lineno + lines;
+ col = 0;
+
+ if (currline < ah.lines)
+ {
+ int w = t_columns - 2, i_author = 0;
+ int flDrawBoard = 0, flDrawAuthor = 0;
+ char *ph = disp_heads[currline];
+
+ /* case 1, we're printing headers */
+
+ if (currline == 0 && ah.boardlen > 0)
+ flDrawAuthor = 1;
+draw_header:
+ if(flDrawAuthor)
+ w = t_columns - 2 - ah.boardlen - 6;
+ else
+ w = t_columns - 2;
+
+ outs("\033[47;34m "); col++;
+
+ /* special case for STR_AUTHOR2 */
+ if(!flDrawBoard)
+ {
+ outs(ph);
+ col += DISP_HEADS_LEN; // strlen(disp_heads[currline])
+ } else {
+ /* display as-is */
+ while (*mf.dispe != ':' && *mf.dispe != '\n')
+ if(col++ < t_columns)
+ outc(*mf.dispe++);
+ }
+
+ while (*mf.dispe != ':' && *mf.dispe != '\n')
+ mf.dispe ++;
+
+ if(*mf.dispe == ':') {
+ outs(" \033[44;37m"); col++;
+ mf.dispe ++;
+ }
+
+ while (col < w) { // -2 to match seperator
+ int flCanDraw = (*mf.dispe != '\n');
+
+ if(flDrawAuthor)
+ flCanDraw = i_author < ah.authorlen;
+ if(flCanDraw)
+ {
+ /* strip ansi in headers */
+ unsigned char c = *mf.dispe++;
+ if(inAnsi)
+ {
+ if (!strchr(STR_ANSICODE, c))
+ inAnsi = 0;
+ } else {
+ if(c == ANSI_ESC)
+ inAnsi = 1;
+ else
+ outc(c), col++, i_author++;
+ }
+ } else {
+ outc(' '), col++;
+ }
+ }
+
+ if (flDrawAuthor)
+ {
+ flDrawBoard = 1;
+ flDrawAuthor = 0;
+ while(*mf.dispe == ' ')
+ mf.dispe ++;
+ goto draw_header;
+ }
+
+ outs("\033[m");
+ // skip to end of line
+ while(mf.dispe < mf.end && *mf.dispe != '\n')
+ mf.dispe++;
+ }
+ else if (currline == ah.lines)
+ {
+ /* case 2, header seperator line */
+ outs("\033[36m");
+ for(col = 0; col < t_columns -2; col+=2)
+ {
+ outs("─");
+ }
+ outs("\033[m");
+ while(mf.dispe < mf.end && *mf.dispe != '\n')
+ mf.dispe++;
+ }
+ else if(mf.dispe < mf.end)
+ {
+ /* case 3, normal text */
+ long dist = mf.end - mf.dispe;
+ long flResetColor = 0;
+ int srlen = -1;
+
+ // first check quote
+ if(dist > 1 &&
+ (*mf.dispe == ':' || *mf.dispe == '>') &&
+ *(mf.dispe+1) == ' ')
+ {
+ outs("\033[36m");
+ flResetColor = 1;
+ } else if (dist > 2 &&
+ (!strncmp(mf.dispe, "※", 2) ||
+ !strncmp(mf.dispe, "==>", 3)))
+ {
+ outs("\033[32m");
+ flResetColor = 1;
+ }
+
+ while(mf.dispe < mf.end && *mf.dispe != '\n')
+ {
+ if(inAnsi)
+ {
+ if (!strchr(STR_ANSICODE, *mf.dispe))
+ inAnsi = 0;
+ if(col < t_columns)
+ outc(*mf.dispe);
+ } else {
+ if(*mf.dispe == ANSI_ESC)
+ inAnsi = 1;
+ else if(srlen < 0 && sr.search_str[0] && // support search
+ //tolower(sr.search_str[0]) == tolower(*mf.dispe) &&
+ mf.end - mf.dispe > sr.len &&
+ sr.cmpfunc(mf.dispe, sr.search_str, sr.len) == 0)
+ {
+ outs("\033[7m");
+ srlen = sr.len-1;
+ flResetColor = 1;
+ }
+
+ if(col < t_columns)
+ outc(*mf.dispe);
+ if(!inAnsi)
+ {
+ col++;
+ if (srlen == 0)
+ outs("\033[m");
+ if(srlen >= 0)
+ srlen --;
+ }
+ }
+ mf.dispe ++;
+ }
+ if(flResetColor)
+ outs("\033[m");
+ }
+
+ if(mf.dispe < mf.end)
+ mf.dispe ++;
+ if(col < t_columns)
+ outc('\n');
+ lines ++;
+ }
+}
+
+/* --------------------- MAIN PROCEDURE ------------------------- */
+
+static const char * const pmore_help[] = {
+ "\0閱\讀文章功\能鍵使用說明",
+ "\01游標移動功\能鍵",
+ "(↑) 上捲一行",
+ "(↓)(Enter) 下捲一行",
+ "(^B)(PgUp)(BackSpace) 上捲一頁",
+ "(→)(PgDn)(Space) 下捲一頁",
+ "(0)(g)(Home) 檔案開頭",
+ "($)(G) (End) 檔案結尾",
+ "\01其他功\能鍵",
+ "(/) 搜尋字串",
+ "(n/N) 重複正/反向搜尋",
+// "(TAB) URL連結",
+ "(Ctrl-T) 存到暫存檔",
+ "(:/f/b) 跳至某頁/下/上篇",
+ "(a/A) 跳至同一作者下/上篇",
+ "([-/]+) 主題式閱\讀 上/下",
+ "(t) 主題式循序閱\讀",
+ "(q)(←) 結束",
+ "(h)(H)(?) 輔助說明畫面",
+ "\01本系統使用 piaip 的新式瀏覽程式",
+ NULL
+};
+
+int pmore(char *fpath, int promptend)
+{
+ int flExit = 0, retval = 0;
+ int ch = 0;
+
+ STATINC(STAT_MORE);
+ if(!mf_attach(fpath))
+ return -1;
+
+ /* reset and parse article header */
+ memset(&ah, 0, sizeof(ah));
+ mf_parseHeader();
+
+ clear();
+ while(!flExit)
+ {
+ clear();
+ move(0, 0);
+ mf_disp();
+
+ if(promptend == NA && mf_viewedAll())
+ break;
+
+ move(b_lines, 0);
+
+#ifdef DEBUG
+ prints("L#%d prmpt=%d Disp:%08X/%08X/%08X, File:%08X/%08X(%d)",
+ (int)mf.lineno,
+ promptend,
+ (unsigned int)mf.disps,
+ (unsigned int)mf.maxdisps,
+ (unsigned int)mf.dispe,
+ (unsigned int)mf.start, (unsigned int)mf.end,
+ mf.len);
+#else
+ if(mf.len)
+ prints("\033[m\033[%sm 瀏覽 P.%d(%d%%) %s %-30.30s%s",
+ "44", //printcolor[(int)color],
+ (int)(mf.lineno / MFNAV_PAGE)+1,
+ (int)((unsigned long)(mf.dispe-mf.start) * 100 / mf.len),
+ "\033[31;47m",
+ "(h)\033[30m求助 \033[31m→↓[PgUp][",
+ "PgDn][Home][End]\033[30m游標移動 \033[31m←[q]\033[30m結束 \033[m");
+#endif
+
+ ch = igetch();
+ switch (ch) {
+ /* ------------------ EXITING KEYS ------------------ */
+ case 'r': // Ptt: put all reply/recommend function here
+ case 'R':
+ case 'Y':
+ case 'y':
+ flExit = 1, retval = 999;
+ break;
+ case 'X':
+ flExit = 1, retval = 998;
+ break;
+ case 'A':
+ flExit = 1, retval = AUTHOR_PREV;
+ break;
+ case 'a':
+ flExit = 1, retval = AUTHOR_NEXT;
+ break;
+ case 'F':
+ case 'f':
+ flExit = 1, retval = READ_NEXT;
+ break;
+ case 'B':
+ case 'b':
+ flExit = 1, retval = READ_PREV;
+ break;
+ case KEY_LEFT:
+ case 'q':
+ flExit = 1, retval = FULLUPDATE;
+ break;
+
+ /* from Kaede, thread reading */
+ case ']':
+ case '+':
+ flExit = 1, retval = RELATE_NEXT;
+ break;
+ case '[':
+ case '-':
+ flExit = 1, retval = RELATE_PREV;
+ break;
+ case '=':
+ flExit = 1, retval = RELATE_FIRST;
+ break;
+ case 't':
+ if (mf_viewedAll())
+ flExit = 1, retval = RELATE_NEXT;
+ else
+ mf_forward(MFNAV_PAGE);
+ break;
+ /* ------------------ NAVIGATION KEYS ------------------ */
+ /* Simple Navigation */
+ case Ctrl('F'):
+ case KEY_PGDN:
+ mf_forward(MFNAV_PAGE);
+ break;
+ case Ctrl('B'):
+ case KEY_PGUP:
+ mf_backward(MFNAV_PAGE);
+ break;
+
+ case '0':
+ case 'g':
+ case KEY_HOME:
+ mf_goTop();
+ break;
+ case '$':
+ case 'G':
+ case KEY_END:
+ mf_goBottom();
+ break;
+
+ /* Compound Navigation */
+ case '\r':
+ case '\n':
+ case KEY_DOWN:
+ if (mf_viewedAll() ||
+ (promptend == 2 && (ch == '\r' || ch == '\n')))
+ flExit = 1, retval = READ_NEXT;
+ else
+ mf_forward(1);
+ break;
+
+ case ' ':
+ if (mf_viewedAll())
+ flExit = 1, retval = READ_NEXT;
+ else
+ mf_forward(MFNAV_PAGE);
+ break;
+ case KEY_RIGHT:
+ if(mf_viewedAll())
+ promptend = 0, flExit = 1, retval = 0;
+ else
+ mf_forward(MFNAV_PAGE);
+ break;
+
+ case KEY_UP:
+ if(mf_viewedNone())
+ flExit = 1, retval = READ_PREV;
+ else
+ mf_backward(1);
+ break;
+ case Ctrl('H'):
+ if(mf_viewedNone())
+ flExit = 1, retval = READ_PREV;
+ else
+ mf_backward(MFNAV_PAGE);
+ break;
+
+ /* ------------------ SEARCH KEYS ------------------ */
+ case '/':
+ {
+ char ans[4] = "n";
+
+ sr.search_str[0] = 0;
+ getdata_buf(b_lines - 1, 0, "[搜尋]關鍵字:", sr.search_str,
+ 40, DOECHO);
+ if (sr.search_str[0]) {
+ if (getdata(b_lines - 1, 0, "區分大小寫(Y/N/Q)? [N] ",
+ ans, sizeof(ans), LCECHO) && *ans == 'y')
+ sr.cmpfunc = strncmp;
+ else
+ sr.cmpfunc = strncasecmp;
+ if (*ans == 'q')
+ sr.search_str[0] = 0;
+ }
+ sr.len = strlen(sr.search_str);
+ mf_search(MFSEARCH_FORWARD);
+ }
+ break;
+ case 'n':
+ mf_search(MFSEARCH_FORWARD);
+ break;
+ case 'N':
+ mf_search(MFSEARCH_BACKWARD);
+ break;
+ /* ------------------ SPECIAL KEYS ------------------ */
+ case ':':
+ {
+ char buf[10];
+ int i = 0;
+
+ getdata(t_lines, 0, "Goto Page: ", buf, 5, DOECHO);
+ sscanf(buf, "%d", &i);
+ if(--i < 0) i = 0;
+ mf_goto(i * MFNAV_PAGE);
+ break;
+ }
+ break;
+
+ case Ctrl('T'):
+ {
+ char buf[10];
+ getdata(b_lines - 2, 0, "把這篇文章收入到暫存檔?[y/N] ",
+ buf, 4, LCECHO);
+ if (buf[0] == 'y') {
+ setuserfile(buf, ask_tmpbuf(b_lines - 1));
+ Copy(fpath, buf);
+ }
+ }
+ break;
+
+ case 'h':
+ case 'H':
+ case '?':
+ // help
+ show_help(pmore_help);
+ break;
+
+ case 'E':
+ // admin edit ve help file
+ if (HAS_PERM(PERM_SYSOP) && strcmp(fpath, "etc/ve.hlp")) {
+ mf_detach();
+ vedit(fpath, NA, NULL);
+ return 0;
+ }
+ break;
+ }
+ }
+
+ mf_detach();
+ if (retval == 0 && promptend) {
+ pressanykey();
+ clear();
+ } else
+ outs(reset_color);
+
+ return retval;
+}
+
+/* vim:sw=4
+ */
diff --git a/sample/pttbbs.conf b/sample/pttbbs.conf
index ac65fbbc..19c06f36 100644
--- a/sample/pttbbs.conf
+++ b/sample/pttbbs.conf
@@ -118,6 +118,9 @@
/* 若定義, 在使用者註冊之前, 會先顯示出該檔案, 經使用者確認後才能註冊 */
//#define HAVE_USERAGREEMENT "etc/UserAgreement"
+/* 使用新式的 pmore (piaip's more) 代替舊式 bug 抓不完的 more */
+//#define USE_PIAIP_MORE
+
/* 使用 rfork()取代 fork() . 目前只在 FreeBSD上有效 */
//#define USE_RFORK