curses

Table of Contents

patch bochs

OpenBSD用のbochsのコードを見ていたら、/dev/ptmxと言うデバイスの代わり をするデバイスが有ればコードを共通化できる事に気がついた。そんなデバイ スが/dev/ptmって名前で用意されてる事を知った。んで、下記のパッチを用意。

ob$ diff term.cc.org term.cc
36c36
< #define BX_DEBUGGER_TERM (BX_DEBUGGER && !defined(__OpenBSD__))
---
> #define BX_DEBUGGER_TERM (BX_DEBUGGER && 1)
192c192
<   scr_fd = open("/dev/ptmx",O_RDWR);
---
>   scr_fd = open("/dev/ptm",O_RDWR);

これで、良いはず。

ob$ gmake
c++ -c  -I.. -I./.. -I../iodev -I./../iodev -I../instrument/stubs
 -I./../instrument/stubs -g -O2 -D_FILE_OFFSET_BITS=64 -D_LARGE_FILES
 -pthread    term.cc -o term.o
term.cc:194:20: error: expression is not assignable
    stdin = stdout = fdopen(scr_fd,"wr");
            ~~~~~~ ^
term.cc:202:9: error: expression is not assignable
  stdin = old_stdin;
  ~~~~~ ^
term.cc:203:10: error: expression is not assignable
  stdout = old_stdout;
  ~~~~~~ ^
3 errors generated.

が、あえなく撃沈。 c++語は、本当にcフラフラ語と言っておこう。今からフラフラ語をやるって筋 が悪いぞ。 ホワイトハウスが開発者に対しC++やC言語からRustやJavaなどのメモリ安全性に優れたプログラミング言語への移行を勧める

[sakae@arch bochs-2.7]$ make
g++ -c  -I.. -I./.. -I../iodev -I./../iodev -I../instrument/stubs -I./../instrum
  :
sr/include/blkid -I/usr/include/sysprof-6 -pthread term.cc -o term.o

念の為リナでやってみると、問題の箇所もスンナリと通過した。GNUの勝手な 解釈がバグの温床になってます、ってか。

#include <ncurses.h>
#include <fcntl.h>
#lincude <atdlib.h>
#include <string.h>

static int scr_fd = -1;
int main(){
  int  x, y, w, h;
  char *str="Hello World";
  int  key;

  scr_fd = posix_openpty(O_RDWR);
  stdin = stdout = fdopen(scr_fd,"wr");

  grantpt(scr_fd);
  unlockpt(scr_fd);
  fprintf(stderr, "cu -l %s .. and Hit key\n",ptsname(scr_fd));
  key = getchar();

  initscr();
  noecho();
  cbreak();
  keypad(stdscr, TRUE);

  getmaxyx(stdscr, h, w);
  y = h/2;
  x = (w-strlen(str))/2;

  while (1) {
    erase();
    move(y, x);
    addstr(str);
    refresh();

    key = getch();
    if (key == 'q') break;
    switch (key) {
    case KEY_UP:y--; break;
    case KEY_DOWN:y++; break;
    case KEY_LEFT:x--; break;
    case KEY_RIGHT:x++; break;
    }
  }
  endwin();
  return (0);
}

cursesを組み込み。

ob$ cc d.c -lcurses
d.c:13:18: error: expression is not assignable
  stdin = stdout = fdopen(scr_fd,"wr");
          ~~~~~~ ^
1 error generated.
ob$ egcc d.c -lcurses
d.c: In function 'main':
d.c:13:18: error: lvalue required as left operand of assignment
   stdin = stdout = fdopen(scr_fd,"wr");
                  ^

が、エラーですよ。リナのgccは、自分に都合よく改造してあるのか。

rubyでcurses

cursesって、呪いって意味が有るのね。bochsの呪縛から逃れて、cursesして やるぞ。

Rubyでcursesを使う

Terminalの基礎とRuby、そしてcursesについて

これをやってみたいだけに、ruby 3.3.0をOpenBSDに入れた。欲求駆動型のス タイルだ。例によって、libyamlが無いからって、最後の最後で文句垂れるの やめれ。こういう不親切は、matzさんに通報すればいいのかな。tar玉から入れ るガチの人には常識ですって回答だったら、どうしよう。

ob$ ri Curses
= Curses

(from gem curses-1.4.4)
------------------------------------------------------------------------
== Description
An implementation of the CRT screen handling and optimization library.
 :
== Examples

 * hello.rb
 * rain.rb
ob$ locate rain.rb
/var/OPT/lib/ruby/gems/3.3.0/gems/curses-1.4.4/sample/rain.rb

雪国では、雨よりまだ雪なんだけどな。

i = cycle_index(i)
place_string(ypos[i] - 1, xpos[i],      "-")
place_string(ypos[i],     xpos[i] - 1, "|.|")
place_string(ypos[i] + 1, xpos[i],      "-")

こんな雨マークが数個登録されてた。これを雪マークに変更するには、美大を 出る素養が必要か。

ユーザーランド上のcurses

軽くcursesを利用してるコマンドを抽出してみる。

ob$ cd /usr/src
ob$ find usr.bin -name Makefile | xargs grep -l curses
usr.bin/bc/Makefile
usr.bin/ftp/Makefile
usr.bin/infocmp/Makefile
usr.bin/less/less/Makefile
usr.bin/mg/Makefile
usr.bin/systat/Makefile
usr.bin/talk/Makefile
usr.bin/telnet/Makefile
usr.bin/tic/Makefile
usr.bin/tmux/Makefile
usr.bin/top/Makefile
usr.bin/tput/Makefile
usr.bin/tset/Makefile
usr.bin/ul/Makefile
usr.bin/vi/build/Makefile

systat

代表例として、systatを調査。複数のファイルから構成されているので、利用 してるであろうファイルを抽出。

ob$ grep curses *.[ch]
engine.c:#include <curses.h>
engine.c:       GCC_PRINTFLIKE(1,2)       /* defined in curses.h */
engine.c:       GCC_PRINTFLIKE(1,2)       /* defined in curses.h */
engine.h:#include <curses.h>
main.c:#include <curses.h>
pftop.c:#include <curses.h>
pigs.c:#include <curses.h>

ふむ、エンジンのあたりが、主戦場だろう(と当たりをつける)。 ならば、きっと initscr(),endwin()の組が有るはず。

ob$ grep initscr *.c
ob$ grep newterm *.c
engine.c:               screen = newterm(NULL, stdout, stdin);

initscrの代わりに、newtermが使われているとな。この関数は、 setup_term って、いかにもって所で使われている。そして、端末のサイズ が変更されても、追従できるように do_resize_term なんてのも定義されて た。

do_resize_term(void)
{
        struct winsize ws;
          :
        resizeterm(ws.ws_row, ws.ws_col);

        columns = COLS;
        lines = LINES;

サイズが変更されると、cursesの変数である画面サイズが更新されるんで、そ れをsystatの変数にコピーしてるんでな。

その他にcursesゆかりの関数は、どんなのが利用されてる? 調べるのは簡単。 リンクを外して、リンカーが怒るのを眺めるだけ。

ld: error: undefined symbol: noecho
>>> referenced by engine.c
>>>               engine.o:(setup_term)

ld: error: undefined symbol: resizeterm
ld: error: undefined symbol: wclear
ld: error: undefined symbol: beep
ld: error: too many errors emitted, stopping now (use -error-limit=0 to see all errors)

リンカーもエラーが多すぎると、呆れて黙りこんじゃうのがデフォの挙動なの か。しつこくネチネチしないのは大人の対応だな。

systatでは、左右の矢印キーで、項目を切り替えられる。もちろんkeypad関数 が利用されてる。して、画面毎に、タイトルや配置が変る。それをどうやって サポートしてるか?

   2 users Load 0.45 0.49 0.47                            ob.my.domain 07:59:56

IFACE     STATE DESC             IPKTS IBYTES IFAILS  OPKTS OBYTES OFAILS  COLLS
iwn0      up:U                       1    125      0      0     36      0      0
re0       up:D                       0      0      0      0      0      0      0
enc0      dn:U                       0      0      0      0      0      0      0
lo0       up                         0      0      0      0      0      0      0
pflog0    up                         0      0      0      0      0      0      0
Totals                               1    125      0      0     36      0      0

NICの状態表示ね。if.cにてハンドリングされてる。その大事な定義部分。

/* Define fields */
field_def fields_if[] = {
        {"IFACE", 8, 16, 1, FLD_ALIGN_LEFT, -1, 0, 0, 0},
        {"STATE", 4, 6, 1, FLD_ALIGN_LEFT, -1, 0, 0, 0},
        {"IPKTS", 5, 8, 1, FLD_ALIGN_RIGHT, -1, 0, 0, 0},
        {"IBYTES", 5, 8, 1, FLD_ALIGN_RIGHT, -1, 0, 0, 0},
        {ifails, 5, 8, 1, FLD_ALIGN_RIGHT, -1, 0, 0, 0},
        {"OPKTS", 5, 8, 1, FLD_ALIGN_RIGHT, -1, 0, 0, 0},
        {"OBYTES", 5, 8, 1, FLD_ALIGN_RIGHT, -1, 0, 0, 0},
        {ofails, 5, 8, 1, FLD_ALIGN_RIGHT, -1, 0, 0, 0},
        {"COLLS", 5, 8, 1, FLD_ALIGN_RIGHT, -1, 0, 0, 0},
        {"DESC", 14, 64, 1, FLD_ALIGN_LEFT, -1, 0, 0, 0},
};

これの意味付けは、engine.hに定義されてた。

typedef struct {
        const char *title;
        int norm_width;
        int max_width;
        int increment;
        int align;
        int start;
        int width;
        unsigned flags;
        int arg;
} field_def;

後は、自在にカーソルを移動させてデータを更新していくんだな。Webで、任 意のフィールドを更新するのがあるけど、きっとcursesあたりを参考にしたに 違いない。

openpty

こちらは、仮想端末の関係。cursesと関連有る?無い?

ob$ find . -name '*.c' | xargs grep openpty -l
./mg/main.c
./script/script.c
./ssh/sshpty.c
./vi/ex/ex_script.c

fdforkpty

これもptyがらみ。

ob$ find . -name '*.c' | xargs grep ptm
  :
./tmux/job.c:           pid = fdforkpty(ptm_fd, &master, tty, NULL, &ws);
./tmux/spawn.c: new_wp->pid = fdforkpty(ptm_fd, &new_wp->fd, new_wp->tty, NULL, &ws);

qemu

OpenBSDのqemuが割と小気味良く動くので、FreeBSD 14.0を入れる事にした。 これもcursesの動作確認ね。で、qemuが言うには、メモリーがアロケート出来 ませんですとの事。今回は観念してググるを偵察。

ob$ ulimit -d
524288
ob$ ulimit -Sd `ulimit -Hd`
ob$ ulimit -d
3145728

ulimitにハードリミットとソフトリミットが有るみたい。何時でも最大限に使 いたいなら、/etc/login.confに設定するのが定石との事。オイラーはそこま で、欲張りではないので、qemuの起動の前で実行する様にした。

インストール時に、debug関係もインストールできる様だったので、 base-dbg.txzも選択したら、何時間待っても先に進まなかったので、これは断 念した。

FreeBSD 13.3-RELEASE

qemeでは、負荷が重いんだなと看破。丁度13.3も出た事なんで、アップグレー ドがてらやってみるか。VMWareだとbootデバイスをCDにする時のBIOS画面に入 いるのが難しかったな。

bios.forceSetupOnce = "TRUE"    ## F2 key
bios.bootDelay = "15000"

こんな設定によかったはず。んが、install,LiveCDの機能しかサポートしてな かった。急遽インストールって事で、base-dbg.txzも選択したよ。素直に完了。

いきなり、gdbします。

sakae@fb:~ $ gdb -q pwd
Reading symbols from pwd...
Reading symbols from /usr/lib/debug//bin/pwd.debug...
(gdb) b main
Breakpoint 1 at 0x4018e8: file /usr/src/bin/pwd/pwd.c, line 65.
(gdb) r
Starting program: /bin/pwd

Breakpoint 1, main (argc=1, argv=0xffbfec20) at /usr/src/bin/pwd/pwd.c:65
65              while ((ch = getopt(argc, argv, "LP")) != -1)
(gdb) l
60              int physical;
61              int ch;
62              char *p;
63
64              physical = 1;
65              while ((ch = getopt(argc, argv, "LP")) != -1)
66                      switch (ch) {
67                      case 'L':
68                              physical = 0;
69                              break;

どんな舞台が設えてあるかと言うと

sakae@fb:~ $ cd /usr/lib/debug/
sakae@fb:/usr/lib/debug $ ls
bin/            lib/            sbin/
boot/           libexec/        usr/
sakae@fb:/usr/lib/debug $ ls bin/
cat.debug               getfacl.debug           pwd.debug
chflags.debug           hostname.debug          realpath.debug
 :
sakae@fb:/usr/lib/debug $ file bin/cat.debug
bin/cat.debug: ELF 32-bit LSB executable, Intel 80386, version 1 (FreeBSD),
no program header, for FreeBSD 13.3, FreeBSD-style, with debug_info, not stripped

ちゃんとgdb用のバイナリーが置いてありました。

sakae@fb:/usr/lib/debug $ du -sh *
2.6M    bin
222M    boot
 31M    lib
320K    libexec
 16M    sbin
1.2G    usr

都合、1.6Gぐらい容量が増加してしまったけど、何時でもgdbってのは、リナ とは違うんですって優越感に浸れて嬉しい限りです。


This year's Index

Home