スーパー・カッコ

============= 怪 =============

暫く火を入れていなかったdebian機をupdateしようとしたんだ。そしたら、ネットに繋がっていなかったよ。

WIFIのインジケーターを見たら、消灯してる。ハードの認識に失敗したのかも知れん。一度火を落として、もう一度やってみるか。やっぱりインジケーターは点灯していない。コマンド叩いて、NICが生きているか調べる。ノーキャリアだって。オイラーと同じだな。

長年頑張ってきたけど、とうとういかれたか。

まて、わけわかめなリナ止めて、最後にUSBスティックに入れてる、OpenBSDで確認してみるか。

ブート画面にこんなメッセージが現れた。

:
starting network
iwn0: radio is disabled by hardware switch
iwn0: SIOCSIFFLAGS: Operation not parmitted
iwn0: no link ......... sleeping
:

一目瞭然、スイッチが切れてますって。そんなスイッチ何処にある? 探し回って、右サイドに こっそり隠れてるやつを見つけた。そしてそれが、確かにOFFになってた。スイッチが中途半端な位置にあるわけじゃなくて、きちんとOFFのポジション。

大体、使ってる本人がスイッチを失念してるぐらいだから、触れた記憶なんて全くない。 女房に聞いても、恐い物には触らないと言う。(最近はすっかりipad利用者)

はて、座敷わらしが夜中に出て来て、いたずらしてった?(by柳田邦男先生の遠野物語説) オイラーが霊に操られて、操作しちゃった?(ゾンビ症候群説)この所、リナをいじっていないので、リナの守り神が嫉妬した? 物の怪である。

いずれにしろ、OpenBSDの守護神であるダエモン君のおかげで、事なきを得た。感謝しろよ。

リナのマスコットはペンギン野郎、OpenBSDはふぐ、ふぐは不具に通じるから、ふくで福です。福の神。ダエモン君はBSD系の守護神。

そりゃ、 ダエモン君からのソース読めって言うメッセージだな。

sys/dev の中にあるデバドラを見るしかないでしょう。しかもiwn0ってんだから、ピンポイントです。/usr/src/sys/dev/if_iwn.c

iwn_init(struct ifnet *ifp)
{
        :
        /* Check that the radio is not disabled by hardware switch. */
        if (!(IWN_READ(sc, IWN_GP_CNTRL) & IWN_GP_CNTRL_RFKILL)) {
                printf("%s: radio is disabled by hardware switch\n",
                    sc->sc_dev.dv_xname);
                error = EPERM;  /* :-) */
                goto fail;
        }

リナでOFFの時に採取しておいたdmesgを覗いてみるか。リナのそれは、無駄に長いんだよな。BSD系みたいに簡潔にいかないものかね。ぶつくさ言いながら探してみる。

[   10.794876] thinkpad_acpi: Lenovo ThinkPad SL510, model 2875CTO
[   10.797502] thinkpad_acpi: radio switch found; radios are disabled

850行もあるメッセージから、こういうのを探すのは至難の技ですよ。しかも不親切なメッセージ。これだから、、、、(以下自粛)

この奇怪な現象、上に挙げた説の他にも色々考えられるぞ。例えば、時空の歪みにより、別のパラレルワールドへ遷移した。とか、CIA若しくはハム(ああ、これ公安部ってのの警察の隠語ね)の連中が、オイラーをリクルートするための最終試験とかね。

留守の間に潜入して、パソコンのスイッチをちょいと操作する。それを被試験者(オイラーの事ね)が、いかにして発見修復するか? その時間と過程を観察する。

どうでっしゃろ? オイラーの問題解決能力は?

付け加えておくと、OpenBSDでも一目瞭然の他dmesgを確認してます。最後の行に、ちょろと、スイッチがOFFになってますよーってのが出てた事を確認済です。なんの迷いもなく、最後の数行を見るって事だけで、用が足りてますよ。

と、自己アッピールしておこう。

前回の疑問

shで 鍵カッコを使ったテスト句の、閉じカッコ問題。

ob64$ touch foo
ob64$ [ -s foo ]; echo $?
1
ob64$ [ -s foo ; echo $?
ksh: [: missing ]
2

どうも、kshが番人になってるっぽい。バランスを見てるの?

この鍵カッコ、某Lisp方面では、スーパー・カッコと呼ばれる事がある。S式を閉じるコッカが連続すると、コッカが足りなかったり、余分に入力しちゃう事がある。そんな場合、このスーパー・カッコを使うと、程よくバランスしてくれる。

人気が出て常用されるかと思ったら、余り見かけない。頑張って使ってるのは、clojureぐらいなものかな。

ksh 探検

また、gdbにかけられるように整えてみる。

ob64$ pwd
/usr/src/bin
ob64$ cp -R ksh /tmp

/tmpの下はRAM-DISKなんで、SSDの寿命を縮めないし、ユーザー権限でコンパイル出来るから、常用の作業場所だ。

ob64$ make -j4
 :
cc -O2 -pipe  -DEMACS -DVI -I. -I/tmp/ksh -I/tmp/ksh/../../lib/libc/gen -Wall -Wpointer-arith -Wuninitialized -Wstrict-prototypes -Wmissing-prototypes -Wunused -Wsign-compare -Wshadow  -MD -MP  -c misc.c
misc.c:16:10: fatal error: 'charclass.h' file not found
#include "charclass.h"
         ^~~~~~~~~~~~~
1 error generated.
*** Error 1 in /tmp/ksh (<sys.mk>:87 'misc.o')

何と、ヘッダーファイルが見つからんエラー。正規の場所から移動しちゃったので、相対参照してるヘッダーファイルが見つからんのだな。よくある事。

CFLAGS=-ggdb3 -O0
CFLAGS+=${DEFS} -I. -I${.CURDIR} -I${.CURDIR}/../../lib/libc/gen

Makefileを開いて、ヘッダーファイルの在処を確認。場所が分かったら、コピーしてきちゃえ。 ついでに、-O2 -pipeを(デフォで)指定してる、CFLAGSを、gdbに適合するように書き換え。 これで、make clean してから、再コンパイル。

取り合えずemacs上のgdbからkshを起動して、どこらで待ちになってるか確認。

(gdb) r
Starting program: /tmp/ksh/ksh
  C-c C-c
Program received signal SIGINT, Interrupt.
_thread_sys_read () at -:3
3       -: No such file or directory.
(gdb) bt
#0  _thread_sys_read () at -:3
#1  0x012793a2 in _libc_read_cancel (fd=0, buf=0xcf7d7ac3, nbytes=1) at /usr/src/lib/libc/sys/w_read.c:27
#2  0x16484bec in blocking_read (fd=0, buf=0xcf7d7ac3 "\026J<\344lH", nbytes=1) at misc.c:1081
#3  0x1645653b in x_getc () at edit.c:124
#4  0x164606ea in x_e_getc () at emacs.c:1837
#5  0x16459c8e in x_e_getu8 (buf=0xcf7d7b93 "", off=0) at emacs.c:1847
#6  0x16459554 in x_emacs (buf=0x55081008 "", len=4096) at emacs.c:321
#7  0x16456131 in x_read (buf=0x55081008 "", len=4096) at edit.c:102
#8  0x1647e78c in getsc_line (s=0x51632388) at lex.c:1099
#9  0x1647e314 in getsc__ () at lex.c:988
#10 0x1647b8bb in getsc_bn () at lex.c:1633
#11 0x16477941 in yylex (cf=44) at lex.c:167
#12 0x1648a174 in get_command (cf=0) at syn.c:206
#13 0x16489e25 in pipeline (cf=0) at syn.c:77
#14 0x16489bbe in andor () at syn.c:98
#15 0x16489643 in c_list (multi=0) at syn.c:118
#16 0x16489543 in yyparse () at syn.c:64
#17 0x164894e6 in compile (s=0x51632388) at syn.c:767
#18 0x16481232 in shell (s=0x51632388, toplevel=1) at main.c:604
#19 0x164808dd in main (argc=1, argv=0xcf7d9934) at main.c:431

フレームを上ったり下りたりしながら、ソースをつまみ読みするのが乙。 syn.cあたりが核になりそうな予感がするぞ。

今日の所はここまで。gdbから扱えるようにしたkshを含む一式は、tarで固めてsaveしておこう。 再開する時は、/tmpに展開するだけ。

騒ぎを起こせ

ob64$ cat z.sh
[ -s NOTES ; echo $?

バランスの取れていないスクリプトを喰わせて、エラーを起こそう。エラーを報告してる場所は、下記の様に3か所だったので、そこにBPを貼っておく。

ob64$ cd /usr/src/bin/ksh
ob64$ fgrep missing *.[ch]
 :                                ;; 色々出てくるので、絞りこみした
ob64$ fgrep 'missing ]' *.[ch]
c_test.c:                       bi_errorf("missing ]");
expr.c:                         evalerr(es, ET_STR, "missing ]");
lex.c:                                          yyerror("missing ]\n");
(gdb) b bi_errorf
Breakpoint 1 at 0x272fd: file io.c, line 65.
(gdb) b evalerr
Breakpoint 2 at 0x2355e: file expr.c, line 227.
(gdb) b yyerror
Breakpoint 3 at 0x3238f: file lex.c, line 934.
(gdb) r z.sh
Starting program: /tmp/ksh/ksh z.sh

Breakpoint 1, bi_errorf (fmt=0x370c7bc0 "missing ]") at io.c:65
65              shl_stdout_ok = 0;      /* debugging: note that stdout not valid */

早速、引っかかってくれた。して、そこに至る道は?

(gdb) bt
#0  bi_errorf (fmt=0x370c7bc0 "missing ]") at io.c:65
#1  0x170cd64d in c_test (wp=0x6d2614ec) at c_test.c:121
#2  0x170e49da in call_builtin (tp=0x5f38de48, wp=0x6d2614ec) at exec.c:1032
#3  0x170e35c2 in comexec (t=0x4aa97d88, tp=0x5f38de48, ap=0x6d2614ec, flags=0, xerrok=0xcf7cd484) at exec.c:541
#4  0x170e0a5d in execute (t=0x4aa97d88, flags=0, xerrok=0xcf7cd484) at exec.c:130
#5  0x170e0d20 in execute (t=0x5f38d688, flags=0, xerrok=0xcf7cd674) at exec.c:166
#6  0x170fb36f in shell (s=0x61a37188, toplevel=1) at main.c:626
#7  0x170fa8dd in main (argc=2, argv=0xcf7cdaa4) at main.c:431

frame #1 の該当箇所。c_test関数の中

        if (strcmp(wp[0], "[") == 0) {
                if (strcmp(wp[--argc], "]") != 0) {
=>                      bi_errorf("missing ]");
                        return T_ERR_EXIT;
                }

少しwpを確認

(gdb) p argc
$2 = 2
(gdb) p wp[1]
$3 = 0x4a422e78 "-s"
(gdb) p wp[2]
$4 = 0x71d13c48 "NOTES"

今まで鍵カッコ [ は、/bin/[ を呼んでいると思ってたけど、c_test.c にそっくりそのまま取り込んで、shの内部コマンドになってるよ。

shellのbuilt-inにしておけば、外部コマンドを呼び出すオーバーヘッドを大幅に低減出来て、スクリプトの実行スピード向上に寄与出来るとな。

kshが返すエラーコードは

#define T_ERR_EXIT      2       /* POSIX says > 1 for errors */

ここに定義されてた。

ちなみに、テストで使った演算子、-s は、test_eval関数の中で

       case TO_FILGZ: /* -s */
                return stat(opnd1, &b1) == 0 && b1.st_size > 0L;

こんな風に定義されてた。

from sh/USD.doc

S. R. Bourne さんは、An Introduction to the UNIX Shell の中で、

The test command is known as `[' and may be invoked as such.
For aesthetic reasons, the command ignores a  close  bracket
`]' given at the end of a command so

          [ -f filename ]

and

          test -f filename

are  completely equivalent.  Typically, the bracket notation
is used when test  is  invoked  inside  shell  control  con-
structs.

こんな事を書いておられる。閉じ括弧は、美学だそうです。

この資料、NetBSDのshに付属していた(shをコンパイルするsh.txtが作成される)。

exec /bin/[

そんじゃ、内蔵コマンドじゃなくて、外部コマンドはどうよ?

ob64$ cat z.sh
/bin/[ -s z.sh

こんな外部コマンドを使ったやつを実行。BPを何処に置くかは試行錯誤で見つけておいた。

(gdb) bt
#0  exchild (t=0xcf7eb6c0, flags=0, xerrok=0xcf7eb8c4, close_fd=-1)
    at jobs.c:447
#1  0x16c74ecb in comexec (t=0x427657c8, tp=0x36c60288 <findcom.temp>,
    ap=0x42d1aa2c, flags=0, xerrok=0xcf7eb8c4) at exec.c:692
#2  0x16c71a5d in execute (t=0x427657c8, flags=0, xerrok=0xcf7eb8c4)
    at exec.c:130
#3  0x16c8c36f in shell (s=0x47175508, toplevel=1) at main.c:626
#4  0x16c8b8dd in main (argc=2, argv=0xcf7ebcf4) at main.c:431
(gdb) 
441             while ((i = fork()) < 0 && errno == EAGAIN && forksleep < 32) {
(gdb)
[New process 69351]
[: missing ]
(gdb) c
Continuing.
[Inferior 1 (process 69351) exited with code 02]

forkで新らしいプロセスが生れて、エラーで終了。そこで、やおら /bin/[ のソースを参照してみる。

int
main(int argc, char *argv[])
{
        extern char *__progname;
        int     res;
         :
        if (strcmp(__progname, "[") == 0) {
                if (strcmp(argv[--argc], "]"))
                        errx(2, "missing ]");
                argv[argc] = NULL;
        }

内蔵のtestコマンドと同様なエラー検出を行なっていた。

glob

ls *.c みたいなワイルドカードによる、ファイル展開がどう行われているか、調べてみるか。 (リクエストが有ったので)こういう展開は一般的に、globなんて名前で呼ばれているんで、定義してる関数が無いか探してみた。どんピシャな奴が存在してた。

ob64$ cat z.sh
ls ????

お題は、こんなの。* を期待してた人、御免。今回は趣向を変えて、ピッタリと4文字の名前のファイルをリストアップしようと言う魂胆です。

(gdb) b glob
Breakpoint 1 at 0x1b28e: file eval.c, line 956.
(gdb) r z.sh
Starting program: /tmp/ksh/ksh z.sh

Breakpoint 1, glob (cp=0x6b0f7228 "\a?\a?\a?\a?", wp=0xcf7d1020, markdirs=0) at eval.c:956
956             int oldsize = XPsize(*wp);
(gdb) bt
#0  glob (cp=0x6b0f7228 "\a?\a?\a?\a?", wp=0xcf7d1020, markdirs=0) at eval.c:956
#1  0x18ab7cf5 in expand (cp=0x6b0f7e08 "\001?\001?\001?\001?", wp=0xcf7d1020, f=75) at eval.c:571
#2  0x18ab6457 in eval (ap=0x441011d0, f=11) at eval.c:93
#3  0x18abb5f7 in execute (t=0x570e7a88, flags=0, xerrok=0xcf7d1214) at exec.c:89
#4  0x18ad636f in shell (s=0x65ff3708, toplevel=1) at main.c:626
#5  0x18ad58dd in main (argc=2, argv=0xcf7d1644) at main.c:431

継続すると

(gdb) c
Continuing.

Breakpoint 2, exchild (t=0xcf7d7d90, flags=0, xerrok=0xcf7d7f94, close_fd=-1)
    at jobs.c:390
390             int             rv = 0;
(gdb) c
Continuing.
[New process 63223]
io.c sh.1 sh.h vi.c z.sh
[Inferior 1 (process 63223) exited normally]

glob関数は、eval.cと言う、いかにもって場所に置いてある。ソースを覗くと

/*
 * glob
 * Name derived from V6's /etc/glob, the program that expanded filenames.
 */

/* XXX cp not const 'cause slashes are temporarily replaced with nulls... */
static void
glob(char *cp, XPtrV *wp, int markdirs)
 :

こんな事がコメントに書いてあった。unix v6の時代にはファイル名の拡張に/etc/globってのを使っていたとな。深入りすると大変な事になりそう。

ちょいと趣を変えて、 glob(3)

EXAMPLES
     A rough equivalent of `ls -l *.c *.h' can be obtained with the following
     code:

           glob_t g;

           g.gl_offs = 2;
           glob("*.c", GLOB_DOOFFS, NULL, &g);
           glob("*.h", GLOB_DOOFFS | GLOB_APPEND, NULL, &g);
           g.gl_pathv[0] = "ls";
           g.gl_pathv[1] = "-l";
           execvp("ls", g.gl_pathv);

無理にC語に拘らなくても、 /usr/local/lib/python2.7/glob.py こういうのがあるな。

ob64$ /usr/bin/env python
Python 2.7.15 (default, Oct 11 2018, 16:48:40)
[GCC 4.2.1 Compatible OpenBSD Clang 6.0.0 (tags/RELEASE_600/final)] on openbsd6
Type "help", "copyright", "credits" or "license" for more information.
>>> import glob
>>> glob.glob('????')
['io.c', 'sh.1', 'sh.h', 'vi.c', 'z.sh']
>>> glob.glob('*.h')
['c_test.h', 'config.h', 'edit.h', 'expand.h', 'lex.h', 'sh.h', 'shf.h', 'table.h', 'tree.h', 'tty.h', 'charclass.h']

ちょいと、シェバングで見掛ける方法でpythonを起動してみた(他意は無い)。

中身はどうなっている?

これからやってみるか。まて、余白が無いので、、、 と 、フェルマーさんみたいな事を言ってみる。

a^n + b^n = c^n

3以上の自然数nについて、上式を満たす自然数の組 (a, b, c) は存在しない。フェルマーの最終定理の話ね。

割と最近になってワイルズさんが、証明した。

rksh

余白が無いとか言っておきながら、kshのMakefileを見てたら

LINKS=  ${BINDIR}/ksh ${BINDIR}/rksh
LINKS+= ${BINDIR}/ksh ${BINDIR}/sh

shは理解できるけど、rkshって初めてお目にかなるな。

     -r      Restricted shell.  A shell is "restricted" if this option is
             used; if the basename the shell was invoked with was "rksh"; or
             if the SHELL parameter is set to "rksh".  The following
             restrictions come into effect after the shell processes any
             profile and ENV files:

             o   The cd command is disabled.
             o   The SHELL, ENV, and PATH parameters cannot be changed.
             o   Command names can't be specified with absolute or relative
                 paths.
             o   The -p option of the built-in command command can't be used.
             o   Redirections that create files can't be used (i.e. `>', `>|',
                 `>>', `<>').
ob64$ rksh
ob64$ pwd
/tmp/ksh
ob64$ cd ~
rksh: cd: restricted shell - can't cd

一番分かり易いのが、上の例だな。某会社の会長だった人みたいに、出国禁止という制限を課されるとな。PATHも変更禁止のみならず。勝手なコマンドも指定できない。

こういう環境から抜け出るウルトラCな技はあるのかな? チャレンジして脱獄のプロになろう。

text to pdf

プレーンなテキストファイルをPDFに変換するには、LibreOffice / OpenOffice に付属してるコマンドを使うらしい。

 unoconv -f pdf hoge.txt

これで、hoge.pdf が出来上がる。日本語は豆腐になるのは、ご愛嬌。笑って許せ!

C-u M-x ps-print-buffer して hoge.txt.ps を作り、それを ps2pdf にかける。これも豆腐になる。

黙ってM$の軍門に下り、PDFに出力するプリンターを使っとけ。