ls 読めよ

家の近くで交通事故が有ったそうだ。90歳代のおじいちゃんが、認知症のおばあちゃんを乗せていての事故らしい。事故と言っても、自損事故。

車庫のシャッターに突っ込んで、シャッターを道路にまき散らし、車庫の柱をひん曲げて、軽自動車はようやく停止したそうだ。

巻き添えが無くて幸い。一時は救急車が2台も来て、大騒ぎになったとか。患者さんと言うか、被救助者は、1名/台しか乗れないから、2台来るのは当然か。

この暑さで、熱中症になりかけていたんじゃないとか、認知症のおばあちゃんが、もっとスピード出せと煽ったからとか、脇道から赤い車が一時停車もせずに出てきたんで、それを避けようとしてハンドルを切ったんだろうとか、噂しきり。真相はいかに?

いずれにしろ、この老人達(多分ご夫婦でしょうね)が、なんだか哀れ。老老介護の現実を見せられたようで、オイラー達の将来を暗示してるようで。

ぼけぼけにならないよう、頑張ろうって女房と話をする今日この頃でした。

rlwrap for gauche

前回は久しぶりにupdateしてたgaucheに気が付いてしまったものだから、思わず入れてしまった。何を隠そう、gauche本を持ってる身としては、入れるのが義務。

それはいいんだけど、何回もOSを入れて、その間に init.el を使い回している身としては、 schemeと言うかgaucheを便利に使うコードが離散してしまってた。emacsよりは、取り合えず素のgoshを使った時に、補完ぐらいは効いて欲しいな。

そんな訳で、

改めてGaucheとrlwrapの連携について

rlwrap による readline 支援 for Gauche

を参考に、設定してみた。早速使ってみるか。お題は? 前回だかにやった、似てる度数値を 出すやつぐらいがいいかな。

それより、前々回に調べて気になってたのが有ったな。

ls 読めよ

lsを読まずにプログラマを名乗るな!

そう、この本、リナでやってるんだろうけど、それじゃ、オイラーはBSD系でやってみる。 BSD3兄弟居るけど、何が良い?

gdbserverが居るって理由で、FreeBSDを指名します。だって、emacs + gdb の組み合わせで コードを追っている時、コンソール出力が有ると、フレームが乱れて面倒だから。

gdbserverを使って、対象lsを動かしておき、そこにアタッチする形で使えば、lsの出力がemacs側に混じってくる事は無い。便利なgdbserverなんだけど、NetBSDにもOpenBSDにも付いていない。誰もこういう事では苦労してないの?

外観検査と組み立て

この間出て来た、ロボットみたいだな。一から設計製作するのは面倒なので、今有るものを組み立てます。勿論、gdbから操れるようにね。

材料手配だな。

sakae@fb:/tmp % cp -r /usr/src/bin/ls .
sakae@fb:/tmp % ls -F ls
Makefile                ls.1                    tests/
Makefile.depend         ls.c                    util.c
cmp.c                   ls.h
extern.h                print.c

手順書のMakefileとマニュアルと本体部品と試験設備とおぼしき物がセットになってます。 リナみたいにぐちゃぐちゃしてて、何が何やら分からん風ではないので、学習向けかな。

次は、製作だな。gdbを使えるように改造しておこう。MakefileとそのサブのMakefile.dependを見て、改造。

FreeBSDのlsもカラー表示を許しているようで、その場合は、 termcapw っていうライブラリィーを追加する事になってる。色は取り合えずいらないな。それから、テスト関係と要らないな。 改造したやつを走らせる。

sakae@fb:/tmp/ls % make
make: "/tmp/ls/Makefile" line 4: Could not find src.opts.mk
make: Fatal errors encountered -- cannot continue
make: stopped in /tmp/ls

Makefileがインクルードしてる、src.opts.mk ってのが無いと言われてエラー。じゃ、手元に持ってきておくか。

sakae@fb:/tmp/ls % locate src.opts.mk
/usr/src/share/mk/src.opts.mk
sakae@fb:/tmp/ls % cp /usr/src/share/mk/src.opts.mk .

どうもこのファイルは、有るべき場所が決まっていて、相対で探しているっぽいな。

sakae@fb:/tmp/ls % make
echo ls.full: /usr/lib/libc.a  >> .depend
Warning: Object directory not changed from original /tmp/ls
cc -O2 -pipe   -g -O0 -g -MD  -MF.depend.cmp.o -MTcmp.o -std=gnu99 -fstack-protector-strong    -Qunused-arguments  -c cmp.c -o cmp.o
  :
cc -O2 -pipe -g -O0 -g -std=gnu99 -fstack-protector-strong -Qunused-arguments  -o ls.full cmp.o ls.o print.o util.o
ls.o: In function `main':
/tmp/ls/ls.c:194: undefined reference to `xo_parse_args'
  :
/tmp/ls/util.c:240: undefined reference to `xo_error'
cc: error: linker command failed with exit code 1 (use -v to see invocation)
*** Error code 1

Stop.

リンクの所で失敗してる。xoって言われると、 XO醤 を連想しちゃうんだけど、それはオイラーが食い意地が張っているから?

オブジェクトファイルは出来ているんで、強引にリンクしちゃえ!!

sakae@fb:/tmp/ls % cc -g -O0  cmp.o ls.o print.o util.o -lxo -lutil 

一応、バイナリーが出来上がったので、確認してみる。

sakae@fb:/tmp/ls % ./a.out -F
Makefile                ls.1                    src.opts.mk
Makefile.depend         ls.c                    tests/
a.out*                  ls.h                    util.c
cmp.c                   ls.o                    util.o
cmp.o                   print.c
extern.h                print.o

どうやら、問題なさそう。記念に変更したレシピを残しておく。

sakae@fb:/tmp/ls % diff -u /usr/src/bin/ls/Makefile Makefile
--- /usr/src/bin/ls/Makefile    2018-06-22 08:04:07.000000000 +0900
+++ Makefile    2018-07-31 14:02:57.164451000 +0900
@@ -8,14 +8,6 @@
 SRCS=  cmp.c ls.c print.c util.c
 LIBADD=        xo util

-.if !defined(RELEASE_CRUNCH) && \
-       ${MK_LS_COLORS} != no
-CFLAGS+= -DCOLORLS
-LIBADD+=       termcapw
-.endif
-
-.if ${MK_TESTS} != "no"
-SUBDIR+=       tests
-.endif
+CFLAGS+= -g -O0

 .include <bsd.prog.mk>

備え付けのlsとオイラー製lsの内部部品比べ。オイラーのは色扱い無し。

sakae@fb:/tmp/ls % ldd /bin/ls   ./a.out
/bin/ls:
        libxo.so.0 => /lib/libxo.so.0 (0x800828000)
        libutil.so.9 => /lib/libutil.so.9 (0x800a45000)
        libncursesw.so.8 => /lib/libncursesw.so.8 (0x800c59000)
        libc.so.7 => /lib/libc.so.7 (0x800eb8000)
./a.out:
        libxo.so.0 => /lib/libxo.so.0 (0x800829000)
        libutil.so.9 => /lib/libutil.so.9 (0x800a46000)
        libc.so.7 => /lib/libc.so.7 (0x800c5a000)

色付きにするには、-Gを付ける。備え付けだと、a.outが赤色になるけど、オイラーのは、色無いだったよ。じゃ、じっくり仕様を確認。

仕様書確認

そりゃ、manするって事。いやほどオプションが有るな。毎日使っているけど、知らないのが有ったぞ。 -Sだと、サイズ順にリストするとか。これを知っていたら、awkで加工してsortするなんて無駄が省けたな。知ってると思っても、暇に任せて眺めると、思わぬ拾い物があるぞ。

で、今回 特筆すべきは、

     --libxo
             Generate output via libxo(3) in a selection of different human
             and machine readable formats.  See xo_parse_args(3) for details
             on command line arguments.

FreeBSDオリジナルの機能。xoジャン、おいしいじゃんってやつらしい。機械も読める形式でlsするんですって。manを再帰すると、

LIBXO(3)               FreeBSD Library Functions Manual               LIBXO(3)

NAME
     xo_parse_args, xo_set_program - detect, parse, and remove arguments for
     libxo

LIBRARY
     Text, XML, JSON, and HTML Output Emission Library (libxo, -lxo)
      :
HISTORY
     The libxo library first appeared in FreeBSD 11.0.

AUTHORS
     libxo was written by Phil Shafer <phil@freebsd.org>.

どうやら、出来て間がないから、世間のほとんどの人が知らないんだな。xoなんてふざけた(素敵な)名前を思いつくあたり、香港出身の人に違いない。美味しそうだらかみんなで賞味してね。オイラーも、軽く試食してみる。

div>sakae@fb:/tmp/ls % ls /tmp
ls              tmux-1001
sakae@fb:/tmp/ls % ls --libxo html /tmp
<div class="line"><div class="data" data-tag="name">ls</div><div class="text">  </div><div class="text"> </div><div class="data" data-tag="name">tmux-1001</div></div>
sakae@fb:/tmp/ls % ls --libxo json /tmp
{"__version": "1", "file-information": {"directory": [{"entry": [{"name":"ls"}, {"name":"tmux-1001"}]}]}
}

なんだか、オイラーにはピンとこないな。ええ、本物のxoジャンを使った料理は大好きですよ。

switch to OpenBSD

emacs上からmanを引けたら便利。いちいち M-x man もかったるいので、下記を設定。

(define-key global-map (kbd "C-c m") 'man)

このキーバインド、C-h k で、バインド入力モードにして、C-c m したら、空いていたので、採用した。

手軽にmanを引けるようになったのは勿論の事、see also に出て来る関連のmanにも、リターンキー一発で飛べて嬉しい。manがネストされても、qで戻ってこれるので楽珍だ。

で、いよいよ本体を見て行こうと思うんだけど、FreeBSDのlsって、xoジャンですっかり毒(違、味付け)されてて、追いかけるには難儀しないか。xoの中まで、入って行く予感がする。

だったら、OpenBSDなりで試せばいいジャン。gdb問題どうすんねん? 当初の目論見を放棄するのか?

cgdb

んな、わけで、portsの中を漁ってみましたよ。そしたら、

cgdb

なんてのを発見。これって、gdbのlayoutを使った時と同じ雰囲気。OpenBSDでlayoutを使うと、画面が崩れる事があるけど、cgdbは、そうならない。

上下に画面が割れ、上部はソース表示。下部はgdbのコマンドバッファーになってる。起動時は、カーソルが下部にある。上に移動するにはEscを押す。ソース部は、viの操作風で、画面を移動出来る。任意の所でスペースを入力すると、BPを置ける。(もう一度スペースすると解除)下部に戻るには、i キーを押す。

その他、ファイルモードなんてのも有る。上部の窓に居る時に、o キーを押すと、ファイルリストが出て来る。開きたい所にカーソルを移動して(vi風にjとかkで)、リターンで決定。いやになったらqで抜ける。

また、分割された窓の大きさは、カーソルが上部窓に居る時に +,-キーで、増減出来るよ。

面白いので、少し使ってみるか。

なお、 OpenBSDに備え付けの gdb 6.3 だと、画面コントロールが上手くいかないので、portsから入れたegdbをgdbにして使う事。

いたずら

OpenBSDのMakefileを見たら、リンクするライブラリィーとして、utilが指定されてた。これって、FreeBSDでも使われていた。何のためにつかってるのだろう?

こういう科学の設問はよくあるな。この遺伝子は何に作用を及ぼすか? そんな時、科学者は、その遺伝子を破壊、もしくは除去して、どんな事が起こるか観測する。

オイラーも同じ手法を取ろう。リンクを指示してる行をコメントアウト。そして、コンパイル。

ob6$ make
cc   -o ls cmp.o ls.o main.o print.o util.o utf8.o
print.o: In function `printsize':
/tmp/ls/print.c:374: undefined reference to `fmt_scaled'
cc: error: linker command failed with exit code 1 (use -v to see invocation)
*** Error 1 in /tmp/ls (<bsd.prog.mk>:121 'ls')

該当箇所

        if ((f_humanval) && (fmt_scaled(bytes, ret) != -1)) {
                (void)printf("%*s ", width, ret);
                return;
        }

fmt_scaledにカーソルを合わせて、manを呼ぶ。人様が見やすいように数値を変換してくれるっぽい。本当か試してみる。

printsizeにBPを置いて、ls -l する。この関数は呼ばれているけど、該当するifは条件不成立でthen部は実行されず。ls -lh すると、

374│         if ((f_humanval) && (fmt_scaled(bytes, ret) != -1)) {
375│                 (void)printf("%*s ", width, ret);
376├───────────────> return;
377│         }

これでもかってぐらいに、BPの位置を主張してる。(cgdbと言う、上部のソース窓)

Breakpoint 1, printsize (width=5, bytes=59269) at print.c:374
(gdb) n
(gdb) n
(gdb) p ret
$1 = "57.9K\000"

そして、こちらは、gdbの窓。数値の59269が、人間(Kを1024と数える人)が分かり易いように変換してくれた。後は、回り回って、

(gdb) c
Continuing.
-rwxr-xr-x  1 sakae  wheel  57.9K Jul 31 14:57 a.out

こんなのが、出て来た。以上、つまみ喰いモードでした。

man of NetBSD

NetBSDのmanって、どのライブラリィーをリンクしたらよいか教えてくれるので、嬉しいな。 困った時は、NetBSDを頼ればいいな。さすが、先輩挌、伊達に歳喰ってない。

リンクしてるlutilを殺してコンパイルすると、flags_to_string ってのが、解決しないと 言われた。そいつをmanしてみたら、

STAT_FLAGS(3)              Library Functions Manual              STAT_FLAGS(3)

NAME
     string_to_flags, flags_to_string -- Stat flags parsing and printing
     functions

LIBRARY
     System Utilities Library (libutil, -lutil)

SYNOPSIS
     #include <util.h>

     char *
     flags_to_string(u_long flags, const char *def);

     int
     string_to_flags(char **stringp, u_long *setp, u_long *clrp);
       :

こんな風に出て来た。どのライブラリィーに属しているか、ちゃんと知れる。

ついでに、FreeBSDで同じ事をすると、

sakae@fb:/tmp/ls % cc -g -O0 cmp.c ls.c print.c util.c -lxo
/usr/bin/ld: undefined reference to symbol `humanize_number' (try adding -lutil)
//lib/libutil.so.9: could not read symbols: Bad value
cc: error: linker command failed with exit code 1 (use -v to see invocation)

(gccに比べて)親切なclangさんが、アマゾン宜しくお節介を焼いてきた。答えは出ちゃったけど、一応manする。

HUMANIZE_NUMBER(3)     FreeBSD Library Functions Manual     HUMANIZE_NUMBER(3)

NAME
     humanize_number - format a number into a human readable form

LIBRARY
     System Utilities Library (libutil, -lutil)

SYNOPSIS
     #include <libutil.h>

     int
     humanize_number(char *buf, size_t len, int64_t number,
         const char *suffix, int scale, int flags);

includeしてるヘッダーと同じ名前でライブラリィーが提供されてるって事でいいのかな。 ああ、違った。ライブラリィーを使うには、ヘッダーをインクルードして、ライブラリィーの詳細をコンパイラーに教えてあげなさい、が正しい理解。

同マニュアルからの豆知識

     The traditional (default) prefixes are:

           Prefix    Description    Multiplier             Multiplier 1000x
           (note)    kilo           1024                   1000
           M         mega           1048576                1000000
           G         giga           1073741824             1000000000
           T         tera           1099511627776          1000000000000
           P         peta           1125899906842624       1000000000000000
           E         exa            1152921504606846976    1000000000000000000

     Note: An uppercase K indicates a power of two, a lowercase k a power of
     ten.

     The IEE/IEC (and now also SI) power of two prefixes are:

           Prefix    Description    Multiplier
           Ki        kibi           1024
           Mi        mebi           1048576
           Gi        gibi           1073741824
           Ti        tebi           1099511627776
           Pi        pebi           1125899906842624
           Ei        exbi           1152921504606846976

大文字のKは、その筋の人用。小文字を使えば一般人用らしい。大きい数値については、こうして関数が提供されてるけど、小さい方(ミリ、マイクロ、ナノ、ピコ、フェムト)とかは、無いのか?

ls本体

を見るなら、スッキリ・サッパリのOpenBSDですな。(今回もあちこち飛び回ってしまったけれど、戻ってくる所は同じ)

Makefile  extern.h  ls.c      main.c    utf8.c
cmp.c     ls.1      ls.h      print.c   util.c

6本のCファイルと2本のヘッダー。matzさん風に、名前が機能を表しているっぽいので、想像出来るね。今回は、ヘッダーから喰ってみるかな。(ざっと見の試食って事だな)

ls.hに、大事そうな構造体DISPLAYってのが定義されてた。extern.hには、比較に使われぞうな関数の型が定義されてた。それと、各種print関数も列挙されてた。ls.hで定義されてた構造体が引数になってる。

cmp.cは、sortに使う比較関数。大きい、同じ、小さいってのを、1, 0, -1 に変換するやつね。utf8.cは文字通り、utf8のファイル名を印字するんだな。util.cはusageの表示。main.cは無くてもいいんだろうけど、一応型だけ整えるって目的?

そんな訳で、本腰を入れてみるのは、ls.cだけか。

static void      display(FTSENT *, FTSENT *);
static int       mastercmp(const FTSENT **, const FTSENT **);
static void      traverse(int, char **, int);

static void (*printfcn)(DISPLAY *);
static int (*sortfcn)(const FTSENT *, const FTSENT *);

関数の先行宣言。この後ろにmain.cから受け継いでいる、ls_mainってのが有る。 動きとしては、引数を解析した後、traverseで、ファイル情報をかき集め、displayで、整形して、printfcnで印字って事だろうね。

        /* Select a print function. */
        if (f_singlecol)
                printfcn = printscol;
        else if (f_columnacross)
                printfcn = printacol;
        else if (f_longform)
                printfcn = printlong;
        else if (f_stream)
                printfcn = printstream;
        else
                printfcn = printcol;

これ、ls_mainの中で定義されてた。オプションによって、色々な形式で印字しなければならないので、選択された関数名を保持。それをdisplayの最後の方(571行目)で使ってる。

(gdb) b ls.c:571
Breakpoint 1 at 0x238d: file ls.c, line 571.
(gdb) r
Starting program: /tmp/ls/a.out

Breakpoint 1, display (p=0xfe67d271500, list=0xfe6e62d6e00) at ls.c:571
(gdb) p d
$1 = {list = 0xfe6e62d6e00, btotal = 140187732482448, bcfile = 599841613, entrie
s = 17, maxlen = 8, s_block = 0, s_flags = 31320122, s_group = 4071, s_inode = 3
5350016, s_nlink = 4071, s_size = 15, s_user = 0}
(gdb) s
printcol (dp=0x7f7fffff18a0) at print.c:171

少し進めると、一行分印字された。

(gdb) n
Makefile cmp.c    extern.h ls.c     ls.h     main.d   print.d  utf8.d   util.d
(gdb) bt
#0  printcol (dp=0x7f7fffff18a0) at print.c:203
#1  0x00000fe44cf02393 in display (p=0xfe67d271500, list=0xfe6e62d6e00) at ls.c:
571
#2  0x00000fe44cf018f2 in traverse (argc=1, argv=0xfe44d107010 <ls_main.dotav>,
options=25) at ls.c:389
#3  0x00000fe44cf016fc in ls_main (argc=0, argv=0x7f7fffff1a90) at ls.c:335
#4  0x00000fe44cf02442 in main (argc=1, argv=0x7f7fffff1a88) at main.c:12

ファイル情報を引いて来るのは、fts_open して、読んで、fts_closeって流れで行われている。 このあたりの動きは、traverseを追いかければいいんだな。もっと上手い、悪魔のような方法を思い付いた。

ob6$ gdb -q ./a.out
Reading symbols from ./a.out...done.
(gdb) b qsort
Function "qsort" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (qsort) pending.

多分、ファイル名の並び変えに qsort を使っていると思うので、そこにBPを置く。そんなの見当たらないんで、保留するけどいいって聞いてきた。もちの論である。

(gdb) r
Starting program: /tmp/ls/a.out

Breakpoint 1, _libc_qsort (a=0x190f827f5a00, n=23, es=8,
    cmp=0x190d27a019f0 <mastercmp>) at /usr/src/lib/libc/stdlib/qsort.c:223
223             for (i = n; i > 0; i >>= 1)
(gdb) bt
#0  _libc_qsort (a=0x190f827f5a00, n=23, es=8, cmp=0x190d27a019f0 <mastercmp>)
    at /usr/src/lib/libc/stdlib/qsort.c:223
#1  0x000019100515dcf7 in fts_sort (sp=0x190fd8573c80, head=<optimized out>,
    nitems=23) at /usr/src/lib/libc/gen/fts.c:896
#2  0x000019100515ecef in _libc_fts_children (sp=0x190fd8573c80, instr=2)
    at /usr/src/lib/libc/gen/fts.c:522
#3  0x0000190d27a018d7 in traverse (argc=1,
    argv=0x190d27c07010 <ls_main.dotav>, options=25) at ls.c:387
#4  0x0000190d27a016fc in ls_main (argc=0, argv=0x7f7ffffe9c90) at ls.c:335
#5  0x0000190d27a02442 in main (argc=1, argv=0x7f7ffffe9c88) at main.c:12

走らせたら、予想的中。お馬さんより打率がいいな。冴えてるね。これぞOpenBSDの魔術。

(gdb) c
Continuing.
Makefile cmp.d    ls.1     ls.h     main.d   print.d  utf8.d   util.d
a.out    cmp.o    ls.c     ls.o     main.o   print.o  utf8.o   util.o
cmp.c    extern.h ls.d     main.c   print.c  utf8.c   util.c
[Inferior 1 (process 25159) exited normally]

ls of linux

恐いもの見たさで、リナのそれを。

/* If ls_mode is LS_MULTI_COL,
   the multi-column format is the default regardless
   of the type of output device.
   This is for the 'dir' program.

   If ls_mode is LS_LONG_FORMAT,
   the long format is the default regardless of the
   type of output device.
   This is for the 'vdir' program.

   If ls_mode is LS_LS,
   the output format depends on whether the output
   device is a terminal.
   This is for the 'ls' program.  */

/* Written by Richard Stallman and David MacKenzie.  */

ええ、ファイルの冒頭部分だけね。だって、5000行を超えてて、何処から読んだらいいかわからんもん。

こういうのを一本糞ファイルと言います。対して、BSDみたいに複数のファイルに分割する、ころころファイル風も有ります。どちらがかわゆいか、分かりますね。

とか言ってたら、あのemacsを書いた大将が書いてた。lisp的なのかなあ。確かに、使ってる変数や構造体の説明が詳しくなされている。プログラムの構造はデータの持ち方によって決定されますって事かな。

emacsのソースと見比べてみたいな。それは無謀の極みだな。ヤメとけ。