AsciiDocのワイド文字対応手抜きパッチ

主流は、reStructuredTextのようですね。
AsciiDocいいのになあ。

  • DocBookに変換できる。
  • お手軽なわりにOutputの完成度高い。
  • (個人的に)ソースがreStructuredTextより見やすい。

いまいちなのは、ワイド文字だと見栄えがよろしくないこと。タイトルや見出しは、まあ、我慢するとしても、表だけはさすがに激しく面倒。

`-----------`---
くだもの    数
----------------
りんご      3個
みかん      10個
さくらんぼ  20個
梨          2個
----------------

とやっても、うまく判定してくれません。
AsciiDocでは、文字の見栄えの幅ではなく、数であわせるため、下のように空白を調整する必要があります。

`------`---
くだもの   数
-----------
りんご    3個
みかん    10個
さくらんぼ  20個
梨      2個
-----------

はっきりいって、見にくい。いや、醜い。
そこで、適当なパッチを作ってみました。ずいぶん昔に作ったものです。
見た目の幅ではなく、バイト数でアンダーラインの文字数を決めるようにしています。手抜き。

以下、そのパッチ。

--- org_asciidoc.py	2006-03-07 10:25:38.000000000 +0900
+++ asciidoc.py	2006-06-15 19:08:27.760867200 +0900
@@ -1466,8 +1466,8 @@
             if not Title.pattern: return False  # Single-line titles only.
             if len(lines) < 2: return False
             title,ul = lines[:2]
-            title_len = char_len(title)
-            ul_len = char_len(ul)
+            title_len = len(title)
+            ul_len = len(ul)
             if ul_len < 2: return False
             # Fast elimination check.
             if ul[:2] not in Title.underlines: return False
@@ -2528,17 +2528,15 @@
         for row in rows:
             data = []
             start = 0
-            # build an encoded representation
-            row = char_decode(row)
             for c in self.columns:
                 end = start + c.rulerwidth
                 if c is self.columns[-1]:
                     # Text in last column can continue forever.
                     # Use the encoded string to slice, but convert back
                     # to plain string before further processing
-                    data.append(string.strip(char_encode(row[start:])))
+                    data.append(string.strip(row[start:]))
                 else:
-                    data.append(string.strip(char_encode(row[start:end])))
+                    data.append(string.strip(row[start:end]))
                 start = end
             result.append(data)
         return result

/dev/bpfをreadした場合、データグラム単位になっているか?

NetBSD 1.6.2 Releaseのソースしか見てませんが、答えを先に言うと、データグラム単位です。 はっきりした仕様なのか、実装の都合なのかはわかりません。

man bpfを見ても、いまいち載ってなかったので、 bpfドライバの実装を見ることに。

ソースはこの2つを見ました。

src/sys/net/bpf.c r1.64
src/sys/net/bpfdesc.h r1.15

まずは、read処理。

 403  /*
 404   *  bpfread - read next chunk of packets from buffers
 405   */
 406  int
 407  bpfread(dev, uio, ioflag)
 408          dev_t dev;
 409          struct uio *uio;
 410          int ioflag;
 411  {
 412          struct bpf_d *d = &bpf_dtab[minor(dev)];
 413          int error;
 414          int s;
 415
 416          /*
 417           * Restrict application to use a buffer the same size as
 418           * as kernel buffers.
 419           */
 420          if (uio->uio_resid != d->bd_bufsize)
 421                  return (EINVAL);

これといって変わったところはないのですが、 ユーザから指定するサイズが d->bd_bufsize でないとエラーになる仕様のようです。

どこでユーザランド側にコピーしているかというと、 bpfread の最後の方にありました。

 486          /*
 487           * Move data from hold buffer into user space.
 488           * We know the entire buffer is transferred since
 489           * we checked above that the read buffer is bpf_bufsize bytes.
 490           */
 491          error = uiomove(d->bd_hbuf, d->bd_hlen, uio);
 492
 493          s = splnet();
 494          d->bd_fbuf = d->bd_hbuf;
 495          d->bd_hbuf = 0;
 496          d->bd_hlen = 0;
 497  done:
 498          splx(s);
 499          return (error);
 500  }

どうやら、d->bd_hbufそのものを返しているようです。 というわけで、d->bd_hbufを追ってみることにします。

まずは、定義から。 struct bpf_dという構造体の定義がどうなっているのか見てみます。

  50  /*
  51   * Descriptor associated with each open bpf file.
  52   */
  53  struct bpf_d {
  54          struct bpf_d    *bd_next;       /* Linked list of descriptors */
  55          /*
  56           * Buffer slots: two mbuf clusters buffer the incoming packets.
  57           *   The model has three slots.  Sbuf is always occupied.
  58           *   sbuf (store) - Receive interrupt puts packets here.
  59           *   hbuf (hold) - When sbuf is full, put cluster here and
  60           *                 wakeup read (replace sbuf with fbuf).
  61           *   fbuf (free) - When read is done, put cluster here.
  62           * On receiving, if sbuf is full and fbuf is 0, packet is dropped.
  63           */
  64          caddr_t         bd_sbuf;        /* store slot */
  65          caddr_t         bd_hbuf;        /* hold slot */
  66          caddr_t         bd_fbuf;        /* free slot */
  67          int             bd_slen;        /* current length of store buffer */
  68          int             bd_hlen;        /* current length of hold buffer */
  69
  70          int             bd_bufsize;     /* absolute length of buffers */
  71

定義の中に興味深いコメントを見つけました。 3っつのスロットがあり、そのうち2つはバッファを指しているようです。 バッファをコピーするのではなく、ポインタの付け替えを行うことで、 効率をUPさせているのでしょう。

定義を理解したところで、次にこのバッファの領域を確保しているところを見てみます。

1176  /*
1177   * Initialize all nonzero fields of a descriptor.
1178   */
1179  static int
1180  bpf_allocbufs(d)
1181          struct bpf_d *d;
1182  {
1183
1184          d->bd_fbuf = (caddr_t)malloc(d->bd_bufsize, M_DEVBUF, M_WAITOK);
1185          d->bd_sbuf = (caddr_t)malloc(d->bd_bufsize, M_DEVBUF, M_WAITOK);
1186          d->bd_slen = 0;
1187          d->bd_hlen = 0;
1188          return (0);
1189  }
1190

ここ以外に malloc, realloc しているところを探してみましたが、みつかりませんでした。 どうやら、バッファのサイズは、必ず d->bd_bufsize であるようです。

ここ以外に領域を確保しているところがないということは、つまり、 2つのバッファを使いまわすだけということになります。

これまでに調べた内容だけで、このバッファがデータグラム単位であることがわかるのですが、 せっかくなのでもう少し調べてみます。

受信したデータをこのバッファに格納する処理を探してみましょう。 bpf.c のソースを眺めていくと、catchpacket という、いかにもな関数を見つけました。

1105  /*
1106   * Move the packet data from interface memory (pkt) into the
1107   * store buffer.  Return 1 if it's time to wakeup a listener (buffer full),
1108   * otherwise 0.  "copy" is the routine called to do the actual data
1109   * transfer.  memcpy is passed in to copy contiguous chunks, while
1110   * bpf_mcpy is passed in to copy mbuf chains.  In the latter case,
1111   * pkt is really an mbuf.
1112   */
1113  static void
1114  catchpacket(d, pkt, pktlen, snaplen, cpfn)
1115          struct bpf_d *d;
1116          u_char *pkt;
1117          u_int pktlen, snaplen;
1118          void *(*cpfn) __P((void *, const void *, size_t));
1119  {
1120          struct bpf_hdr *hp;
1121          int totlen, curlen;
1122          int hdrlen = d->bd_bif->bif_hdrlen;
1123          /*
1124           * Figure out how many bytes to move.  If the packet is
1125           * greater or equal to the snapshot length, transfer that
1126           * much.  Otherwise, transfer the whole packet (unless
1127           * we hit the buffer size limit).
1128           */
1129          totlen = hdrlen + min(snaplen, pktlen);
1130          if (totlen > d->bd_bufsize)
1131                  totlen = d->bd_bufsize;
1132
1133          /*
1134           * Round up the end of the previous packet to the next longword.
1135           */
1136          curlen = BPF_WORDALIGN(d->bd_slen);
1137          if (curlen + totlen > d->bd_bufsize) {
1138                  /*
1139                   * This packet will overflow the storage buffer.
1140                   * Rotate the buffers if we can, then wakeup any
1141                   * pending reads.
1142                   */
1143                  if (d->bd_fbuf == 0) {
1144                          /*
1145                           * We haven't completed the previous read yet,
1146                           * so drop the packet.
1147                           */
1148                          ++d->bd_dcount;
1149                          return;
1150                  }
1151                  ROTATE_BUFFERS(d);
1152                  bpf_wakeup(d);
1153                  curlen = 0;
1154          }

              (略)

1162          /*
1163           * Append the bpf header.
1164           */
1165          hp = (struct bpf_hdr *)(d->bd_sbuf + curlen);
1166          microtime(&hp->bh_tstamp);
1167          hp->bh_datalen = pktlen;
1168          hp->bh_hdrlen = hdrlen;
1169          /*
1170           * Copy the packet data into the store buffer and update its length.
1171           */
1172          (*cpfn)((u_char *)hp + hdrlen, pkt, (hp->bh_caplen = totlen - hdrlen));
1173          d->bd_slen = curlen + totlen;
1174  }

1162 行目からは、bpf ヘッダを編集し、cpfn にコピー処理を委託する処理になります。 cpfn と、わざわざコールバック関数になっているのは、 pkt が単純な OctetString であるか、mbuf であるかによって、コピーの処理が違うためです。 単純な OctetString であれば、cpfn は memcpy になります。

catchpacket でやっていることは、至って単純です。 パケットがバッファに収まるかどうか判定し、バッファにコピーするだけです。

受信したパケットを格納している先は、逆からたどっていくと、d->bd_sbuf のようです。 もし、d->bd_sbufに収まらないパケットを受信した場合は、ROTATE_BUFFERS(d) によって、 バッファをローテートします。 ローテートの前に free のバッファがあるかチェックし、 なければ、discard カウンタをインクリメントし、パケットを破棄しています。

受信したパケットをまるごとコピーできるか、 できないかという判断であることがわかりました。 受信したパケットの何バイトまでコピーは完了して、残りはこれこれという考えはありません。 よって、bpfドライバで管理しているバッファはデータグラム単位であるといえます。

ここで、ちょっと気になる点があるかもしれません。 『catchpacket の引数である pkt って、データグラムなのか?』です。 答えは、データグラムです。 なぜかというと、pkt は catchpacket の前にフィルター条件のロジックを通っています。 もし、ここで中途半端なサイズのパケットなのだとしたら、 フィルタリングの判定なんてできないはずだからです。 それ以前に、pkt という名前でデータグラムじゃないなんてヤクザです。

セミコルーチンの実装(x86 only)

手っ取り早く、手ぬきで実装するとこんな感じになります。
他のマシンで実行したい場合は、jmp_bufの中身をよく観察して、適切なインデックスに突っ込めば、大抵OKです。

#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>

typedef struct co {
    void (*proc)(void);
    jmp_buf jb;
} co_t;

static jmp_buf org;
static co_t *cur = NULL;
static void *del = NULL;

static void
co_run(void)
{
    (*cur->proc)();
    del = cur;
    longjmp(org, 1);
}

static void *
co_create(void (*proc)(void), size_t size)
{
    co_t *co = malloc(size + sizeof(co_t));
    setjmp(co->jb);
    co->jb[0] = (typeof(co->jb[0])) co_run;
    co->jb[2] = (typeof(co->jb[0])) (char *)(co + 1) + size;
    co->proc = proc;
    cur = co;
    return co;
}

static void
co_suspend(void)
{
    if (setjmp(cur->jb) == 0)
        longjmp(org, 1);
}

static int
co_resume(void *obj)
{
    co_t *co = (co_t *)obj;
    if (setjmp(org) == 0) {
        longjmp(co->jb, 1);
    } else if (del) {
        free(del);
        del = NULL;
        return 0;
    }
    return 1;
}

static void
co(void)
{
    int i;

    for (i = 0; i < 20; i++) {
        printf("%d\n", i);
        co_suspend();
    }
}

int
main(int argc, char **argv)
{
    void *obj;

    obj = co_create(co, 8000);

    while (co_resume(obj))
        ;

    return EXIT_SUCCESS;
}