(n)curses

Don't use ZFS ―Linus,ZFSをマージしない姿勢をあらためて強調こんな記事が出てた。

賢明な判断だと思うよ。記事でも言及してたけど、Javaの豹変が有ったしね。更にはMySQLみたいな事例もあるし、信用ならんのは頷ける。

便利なら貪欲に取り込んじゃえが、デフォの世界にあって、こういうのは、重い判断だよな。 みんなで、爪の垢を煎じて飲むようにしよう。

gaucheのファイル時差

ふと、リリース版の0.9.9のsrcを見ていたんだ。そしたら、面白い事を発見。

 :
-rw-r--r-- 1 sakae sakae    8996 Dec 14 06:50 boolean.c
-rw-r--r-- 1 sakae sakae   10445 Dec 14 06:50 bits.c
-rw-r--r-- 1 sakae sakae   42146 Dec 14 06:50 bignum.c
-rw------- 1 sakae sakae  106455 Dec 14 15:44 vminsn.c
-rw------- 1 sakae sakae 4486401 Dec 14 15:45 compile.c
-rw------- 1 sakae sakae   17977 Dec 14 15:45 builtin-syms.c
-rw------- 1 sakae sakae   69549 Dec 14 15:45 autoloads.c
 :

何が面白いって? 明らかに、ファイルの時刻刻印に時差が有るじゃん。その差は大雑把に7時間と言った所。何故かな。デカと言うか探偵はこだわるのだ。

推理する、散歩する、夜も眠らないで考える。閃いた。先端ではどうなってる? 先端ってのは、gitでpullした最新のやつね。差分を取って比べてみるってのが、科学の鉄則だから。

 :
-rw-r--r-- 1 sakae sakae  14947 Dec 22 07:35 weak.c
-rw-r--r-- 1 sakae sakae   4797 Dec 22 07:35 vmstat.c
-rw-r--r-- 1 sakae sakae  16136 Dec 22 07:35 vmcall.c
-rw-r--r-- 1 sakae sakae 124940 Jan  3 05:44 class.c
-rw-r--r-- 1 sakae sakae  18772 Jan  3 05:44 lazy.c
-rw-r--r-- 1 sakae sakae 150316 Jan  3 05:44 number.c
 :

こちらは、時差とは言えない、時間の差が有る。他の差として、じっと眺めると、ファイルの個数が違うぞ。

debian:src$ ls Gauche/src/*.c | wc
     58      58    1184
debian:src$ ls Gauche-0.9.9/src/*.c | wc
     96      96    2589

この差を体感的に気が付いた自分を誉めてあげたい。デカの見習いぐらいは出来るでしょうか? >左京さん

そうと分かれば詳細分析。

debian:src$ diff <(cd Gauche/src; ls *.c) <(cd Gauche-0.9.9/src; ls *.c)
0a1
> autoloads.c
4a6,7
> builtin-syms.c
> char_attr.c
10a14
> compile.c
20a25,26
> features.c
> gauche-config.c
26a33,59
> libalpha.c
> libbool.c
> libchar.c
 :

bashのプロセス置換とか言う機能を使って、中間ファイルを作らずに、差分を取ってみた。右三角括弧は追加されてますって記号だ。要するに、0.9.9版の方が増えていますよって事。

これはきっと、リリース時にファイルを追加してるに違いない。 そう言えば、先端Gaucheをコンパイルする時、./DIST gen ってやったな。参考までに、HACKING.adocを見るか。(こういうのは、最初に見るのが、先端を追う人の勤めです)

きっちり答えが書いてあった。DISTってのは出荷の意味なんだね。

usage () {
    echo "Maintenance script for Gauche."
    echo "Usage: DIST command"
    echo "Commands:"
    echo "  gen    Generate configure scripts"
    echo "  tgz    Generate distribution tarball"
    echo "  clean-build-test   Run make maintainer-clean, then fully build and"
    echo "                     run tests.  Requires newest release of gosh in"
    echo "                     PATH."
    echo "  self-host-test     In a separate directly, first build the current"
    echo "                     source tree with installed gosh, then build the"
    echo "                     source tree again using the freshly compiled"
    echo "                     gosh, and run tests."
    exit 0

出荷用のtgzを作れるよとな。試してみる。

debian:Gauche$ ./DIST tgz                                                       libtoolize: putting macros in AC_CONFIG_MACRO_DIRS, 'm4'.
  :
Makefile.am: installing '../depcomp'

Ready to run './configure'.
checking build system type... i686-pc-linux-gnu
 :
cd src; make GOSH=gosh BUILD_GOSH_FLAGS= pre-package
make[1]: Entering directory '/tmp/tmp.EFbapIz8F8/Gauche-0.9.9/src'
GAUCHE_LOAD_PATH="" GAUCHE_DYNLOAD_PATH="" "/usr/local/bin/gosh"  -l./preload -I
../src -I../lib -I../lib `cat ./features.flags` geninsn ./vminsn.scm
GAUCHE_LOAD_PATH="" GAUCHE_DYNLOAD_PATH="" "/usr/local/bin/gosh"  -l./preload -I
../src -I../lib -I../lib `cat ./features.flags` ./precomp -D LIBGAUCHE_BODY compile.scm
 :

ってな具合に、scmファイルを変換してました。

 :
-rw-r--r-- 1 sakae sakae    8996 Jan 12 21:03 boolean.c
-rw-r--r-- 1 sakae sakae   10445 Jan 12 21:03 bits.c
-rw-r--r-- 1 sakae sakae   42146 Jan 12 21:03 bignum.c
-rw-r--r-- 1 sakae sakae    7737 Jan 12 21:03 autoloads.scm
-rw-r--r-- 1 sakae sakae   22525 Jan 13 07:06 Makefile
-rw-r--r-- 1 sakae sakae   12956 Jan 13 07:06 genconfig
-rw-r--r-- 1 sakae sakae    1779 Jan 13 07:06 makeverslink
-rw-r--r-- 1 sakae sakae     444 Jan 13 07:06 features.flags
-rw-r--r-- 1 sakae sakae     748 Jan 13 07:06 features.c
drwxr-xr-x 3 sakae sakae     980 Jan 13 07:06 gauche/
-rw------- 1 sakae sakae  106455 Jan 13 07:06 vminsn.c
 :

これが結果の一部。13日の7時が、DIST tgzをした時間ね。これで、疑問が氷解。 なお、以前、CentOSにcurrent-trace-portを組み込み失敗したけど、今回作成したtar玉(Gauche/のdirと同じ階層に出来るんで、焦らない事)で試したら、無事に動いた。

who use curces

前回、思わぬ事から懐かしいcurcesに出会った。(懐かしいってのは、昔のパソコンFM-11の画面制御用裏CPUね)現代では、CPUがやってた事をcurcesってライブラリィーが肩代わりしてる。その代表例がexなりviだった。他にも使ってるのがありそう。ぱっと思い付く所ではtmuxあたりだろう。他にどんなアプリ(コマンド)が有るの? 興味が有るので、調べてみる。

ob$ cat wuc.sh
#!/bin/sh
# Usage: wuc.sh dir

DIR=$1
#DIR=/usr/local/bin

for f in $DIR/*
do
    file $f | grep 'ELF 64-bit' > /dev/null && \
    ldd $f  | grep 'curses' > /dev/null && \
    echo $f
done
# C-c C-x to exec shell script on emacs

最初、grepの実行ステータスを見て(指定した文字が存在すれば、$? は0になる。if [ ] の判定子でtrueとして使える)いたんだけど、スクリプトが不格好になったので、上記に落ち着いた。なんだかlisperが発想しそうなコードだ。

fileコマンドで指定したファイルの素性を洗う。結果にELF...が含まれていたら、次のコマンドを実行。次のコマンドはlddで、リンクしてるライブラリィーを教えてくれる。それにcursesが含まれていれば、お目当てのものに辿りついた事になる。最後にそのファイル名を印字(ああ、これもlispっぽい表現だな)。

流れるようなコード、頑張れば一行野郎になりそう。そんじゃ実行。完全自動化したいなら、PATHを分解して、それをこの部品スクリプトに喰わせればよい。こんな書き方をすると、SDのあの連載みたいになるな。ファイルの最後にあるコメントは、見ての通りのもの。後で検索キーワードになるっておまけ付き。(単にお前の記憶能力低下を補うメモってのは、公然の秘密だ。何でわざわざ書いておく? そんなのemacsの C-h m で、出て来るだろうに。ブブー、秘密のやつみたいで、出てこないのよ)

ob$ ./wuc.sh /usr/bin
/usr/bin/bc
/usr/bin/bgplgsh
 :
/usr/bin/vi
/usr/bin/view

原稿を水増ししないので、途中は省略した。以外なコマンドがcursesを使っていて、目から鱗ですよ。

ob$ ./wuc.sh /usr/local/bin
/usr/local/bin/bash
/usr/local/bin/egdb
/usr/local/bin/emacs-26.3
 :
/usr/local/bin/rlwrap
/usr/local/bin/sqlite3
/usr/local/bin/w3m

今度は、自己責任で入れたパッケージ群が入っているエリア。誰がsqlite3が利用者なんて想像出来ただろうか?(そりゃ、お前の想像力の欠如だろうに)

さて、また某OSをdisっておく。後から入れたパッケージも/usr/binに突っ込んで、責任の所在をうやむやにするのはヤメレ。OpenBSDってかBSD系は、きっちり、公式コマンドと自己責任のエリアを分離してるぞ。まあ、某からすれば、くそみそ一緒で全体責任ですって反応が返って来るだろうけど。

違うって、某OSの場合は、自己責任のエリアを/usr/localにしてるでしょ。野良ビルドする時、普通にconfigureすると、インストール場所に選ばれるでしょ。そうか、そうすると追加パッケージは、各ディストリビューターが責任を持ちますって事なんだな。でも、オイラー的には、釈然としないな。

ちょいと、上記コマンドに落ち着くまでの右往左往を書いておく。

fileコマンドの出力文字から何を拾うか?

ob$ file /usr/bin/vi
/usr/bin/vi: ELF 64-bit LSB shared object, x86-64, version 1

これを見て、'shared object'ぐらいかなあってんで、採用したんだ。だって、そのものずばりと思ったから。所が、既知のemacs-26.3が対象に表われない。(emacsって名前は、リンクなんで、除外されるのは理解出来る)

ob$ file /usr/local/bin/emacs-26.3
/usr/local/bin/emacs-26.3: ELF 64-bit LSB executable, x86-64, version 1

お目当ての文字列が無いので、候補から外れていたんだ。なんで、こんな気まぐれが起きるかは、深遠な(以前、調べて深みにはまって、諦めた)fileコマンドに聞くしかない。だけど、hacker(見習い)は、些細な事は無かった事にしたのさ。

X11のアプリ、xtermのcursesのユーザーなのね。って、挙動を見て想像つかないかねぇ。全く困ったものだ。

ああ、cursesを対象に調べたけど、cryptとかにすれば、責任重大なアプリも検出出来るぞ。コマンドは、簡潔に書いてこそ応用が利くってもんだ。

curses vs. ncurses

ものの本によっては、cursesであったりncursesであったりする。どちらも同じ物? 普通に想像するに、頭のnはnewなんだろうな。

OpenBSDでヘッダーファイルを調べてみると、両方共登録されている。じゃ中身は?

ob$ ls -l /usr/include/*curses.h
-r--r--r--  1 root  bin    251 Oct 13 01:34 /usr/include/curses.h
-r--r--r--  1 root  bin  80255 Oct 13 01:34 /usr/include/ncurses.h

リンクでも無いし、curses.hは小さいながら内容が詰まってる風。

ob$ cat /usr/include/curses.h
/*      $OpenBSD: curses.h,v 1.5 2019/01/25 00:19:25 millert Exp $      */

/*
 * Placed in the public domain by Todd C. Miller <millert@openbsd.org>
 * on June 17, 2003.
 */

#ifndef _CURSES_H_
#define _CURSES_H_

#include <ncurses.h>

#endif /* _CURSES_H_ */

互換性のために準備されたんだな。実体はncursesね。libの方も同様(ハードリンクになってた)。歴史の所産な訳ね。

ncurseは、現在GNUでメンテナンスされてるそうな。 それから、 cursesって変な名前の由来は、cursor optimizationとからしい。

昔、某プロバイダーの見学に行った事がある。その時に見たCRT画面の表示の凄まじさにびっくりした記憶が有る。

画面一杯に、何やら数字の羅列。よく見ると、画面が分割されてた。その小画面で、それぞれコンピュータの負荷とか、ネットワークのトラフィック状態やら、DISK関係の状態やら、もろもろな情報が表示されてた。

自前で構築した総合監視盤と自慢してたぞ。今にして思えば、この画面分割にcursesを使っていたに違いない。こういう用途には枯れた技術が必要だからね。X11R6なグラフィックなんて、ちゃんちゃらおかしくて、頭の隅にも浮かんで来なかったんでしょう(多分)。

そういう見方をすれば、MSが太鼓判を押したWindows Serverなんて、論外なんでしょうな。そんなのを採用した暁には、49日毎に、メンテナンス停止とやらを実行する羽目になりますから。

そう言えば、昔勤めていた会社の情シスにもWindows派が居て、毎朝5時にrebootを仕込んでいたな。オイラーが管理してたFreeBSD(のノーパソ)は、無停止で1000日は動いていたな。まだまだ記録は伸びるはずだったんだけど、やむなき理由により、shutdownしたよ。確か、FreeBSD 2.2Xの時代だった。(年寄りは、ときたま走馬燈のように昔を思い出すものだ)

curses

ncurses

ncursesライブラリの関数

curses による端末制御

ncursesでブロック崩し

#include <ncurses.h>

int
main(void) {
  initscr();
  mvprintw(12, 30, "Hello World!");
  while (true) {
    int ch = getch();
    if (ch == 'q') break;
  }
  endwin();
}
ob$ cc -g hw.c -lncurses
ob$ ./a.out

折角OpenBSDなんだから、久しぶりにgdbにかけてみるか。

Breakpoint 1, main () at hw.c:4
4       main(void) {
(gdb) n
5         initscr();
(gdb) s
initscr () at /usr/src/lib/libcurses/base/lib_initscr.c:54
54      {

ふーん、ソースの在処が出て来た。そのソースの中では、termio.hなんてのを読み込んでいる。端末制御なんだな。

この関数からの戻り構造体(の一部)

(gdb) p *result
$3 = {
  _cury = 0,
  _curx = 0,
  _maxy = 23,
  _maxx = 79,
    :

何となく、80桁24行の端末です。カーソルは左上に有りますって、心眼で読めるな。これが正しいUnixerの端末ですよ。それって、単にvt100の仕様じゃなかろうか? 初期値なんで、どうでもいいけどね。

Ncurses 入門

本格的な例が、解説付きで載ってた、よさげなページ。

(gdb) n
main () at hw.c:6
6         mvprintw(12, 30, "Hello World!");
(gdb) s
mvprintw (y=12, x=30, fmt=0x8882be215e0 "Hello World!") at /usr/src/lib/libcurses/base/lib_printw.c:88
88      {

最初に行が来て、次に桁ってのは、慣れないとつらいな。PDFとかで任意の場所に文字列を書く時、最初はXのポイントだったような。似てるようで微妙に違う落とし穴。

    if ((code = move(y, x)) != ERR) {
        va_start(argp, fmt);
        code = vwprintw(stdscr, fmt, argp);
        va_end(argp);
    }

mvprintwの中身はこんなのだった。移動してから印字って、複合命令になってた。move関数も、yが先に来てるね。これを開発してた人は慣れてしまっているだろうけど、普通に人には辛いと思うぞ。

こんなのは、マクロで吸収しちゃうのが正しいと思うんだけど、どうよ?

実例を調べてみる。systatって奴。某OSには無いかも知れんな。簡単に言うとtopコマンドを、テキスト版グラフィック表示にしたようなものだ。 表示画面の一部を張り付けておく。

   1 users Load 0.58 0.23 0.10                         o32.localdomain 05:45:44

            memory totals (in KB)            PAGING   SWAPPING     Interrupts
           real   virtual     free           in  out   in  out      251 total
Active    59788     59788  1295264   ops                            100 clock
All      748452    748452  1295264   pages                          113 ipi
                                                                     38 pciide0
Proc:r  d  s  w    Csw   Trp   Sys   Int   Sof  Flt    10 forks         vic0
     2    39       246  3105  1440    38    62 3105       fkppw
                                                          fksvm
   0.2%Int  43.2%Spn  18.1%Sys   0.2%Usr  38.3%Idle       pwait
|    |    |    |    |    |    |    |    |    |    |     1 relck
@@@@@@@@@@@@@@@@@@@@@@=========                         1 rlkok
  :                                                       noram

vmstat.c

void
labelkre(void)
{
        int i, j, l;

        mvprintw(MEMROW, MEMCOL,     "            memory totals (in KB)");
        mvprintw(MEMROW + 1, MEMCOL, "           real   virtual     free");
        mvprintw(MEMROW + 2, MEMCOL, "Active");
        mvprintw(MEMROW + 3, MEMCOL, "All");

        mvprintw(PAGEROW, PAGECOL, "        PAGING   SWAPPING ");
        mvprintw(PAGEROW + 1, PAGECOL, "        in  out   in  out ");
        mvprintw(PAGEROW + 2, PAGECOL, "ops");
        mvprintw(PAGEROW + 3, PAGECOL, "pages");
           :

こんな具合に、間違えないように、位置を名前で定義してた。左上を起点にして、そこからの差で、位置を決定。なるほどね。

#define MEMROW           2      /* uses 4 rows and 34 cols */
#define MEMCOL           0
#define PAGEROW          2      /* uses 4 rows and 26 cols */
#define PAGECOL         37

そして、各起点は、こんな風になってた。注意のコメントに、エリアサイズも載せてる。画面配置は、手書きの設計なんですかね。それとも専用ツールが有った?

binding curses

/home/sakae/.roswell/lisp/quicklisp/dists/quicklisp/software/cl-charms-20181210-git/examples ここに、paint.lispなんてのが有るけど、どうやって動かすのかな? どうもCommonLispの作法には馴染めないな。

debian:~$ ros run
* (ql:quickload :cl-charms-paint)
To load "cl-charms-paint":
  Load 1 ASDF system:
    cl-charms-paint
; Loading "cl-charms-paint"
..................................................
[package charms-paint]
(:CL-CHARMS-PAINT)
* (charms-paint:main)
NIL

懇切丁寧な説明が有った。quickloadで、スクリプトをロードして実行せいとな。後は、コード嫁か。w/s で縦方向にカーソル移動。a/dで横方向に移動。スペースキーで、★印の投下/回収。qで終了。

wsadって、キーボード上で、何となく矢印キーの配置を彷彿させるな。オイラーなら迷わずにhjklを採用したい所。指に馴染んでいますから。(分からない人へのヒント: viのとっても大事なコマンドね)ああ、lisperはviより、emacsを好む傾向にあるな。

何となく使えてしまったのだけど、どうも居心地が悪い。ならば、CLよりは縁が濃いschemeではどうよ。ぐぐったら、 checken:ncursesを紹介された。復活してるじゃん。おめでとうございます。

debian:tmp$ csi life.scm
   :
; loading /var/lib//chicken/8/ncurses.import.so ...
; loading /var/lib//chicken/8/chicken.import.so ...
; loading /var/lib//chicken/8/foreign.import.so ...
; loading /var/lib//chicken/8/extras.import.so ...
; loading /var/lib//chicken/8/easyffi.import.so ...

Warning: excluded identifier doesn't exist in module foreign: foreign-declare
; loading /var/lib//chicken/8/srfi-25.import.so ...

Error: unbound variable: ncurses#initscr
         :
        <eval>    (main)
        <eval>    [main] (initscr)      <--

古いニワトリ(4.13.0)だとふ化しないのかな。それとも環境のせい? 最近、晩婚化で色々な問題が出てきてるみたいだからね。

OpenBSDに入れているVersion 5.1.0で確かめたら、ちゃんと動いた。 さて、どんな事をやってるか、読んでみるか。