wall

義姉が連休を利用して、香港とマカオに旅行に行く予定だったらしい。所が超巨大な台風22号だったかが押し寄せてきて、旅行業者からキャンセルの通知を受けたとか。

運が悪かったねぇ。NHKのチコちゃんに出てた岡村君も、ハワイとかセブ島だかグアムに行った らしいけど、何処も全部台風で、飛行機ガタガタ、死ぬ思いで帰ってきたなんて言ってたな。

猛暑、大雨、台風、地震と、日本列島はこの所、ご難続き。被害に遭われた方には、お見舞い申し上げます。

この間、某行列に並んでいた時暇なんで、隣のおばーちゃんと話をしたよ。そういう時の話題は、天気にしておくのが無難。(若いおねーちゃんの場合なら、安室奈美恵ちゃん、引退しちゃうんだね、惜しいねとかって、話題を出したはず)

そのおばあちゃんが言う事には、今年は戌年だからねぇ。戌の時は、いつも自然災害が多いのよとおっしゃる。

【干支】戌年には災害が多い?ウェザーニュースの記事を検討する

【災害】丁酉(ひのととり)の年には大地震・火山噴火など天災・大災害が起きやすい?

2018年が大地震・大噴火・大津波連発の年になる証拠11連発! 東京オリンピック中止どころじゃない、もう日本はおしまいだ!

先人の教えは当たる? 株式相場と干支のジンクス

gdb call

ネットをウロウロしてたら、gdbの面白い使い方(call)を見つけたので試行してみる。お題は、日頃お世話になるpwdコマンドを簡略化したもの。

#include <stdio.h>
#include <unistd.h>

int main(){
        char *p;

        p = getcwd(NULL, 0);
        __asm__  __volatile__("int3");  // drop gdb
        return puts(p);
}

getcwdの第一引数は、結果が入るbuf。第二引数は、そのサイズ。bufにNULLを渡すと、よしなにbufを確保して、結果のbufをpに返してくれる。

ob6$ gdb -q a.out
Reading symbols from a.out...done.
(gdb) r
Starting program: /tmp/a.out

Program received signal SIGTRAP, Trace/breakpoint trap.
main () at test.c:9
9               return puts(p);
(gdb) call/x malloc(128)
$1 = 0xb49b498b100
(gdb) call getcwd($1, 64)
$2 = -1265061632
(gdb) p (char *)$1
$3 = 0xb49b498b100 "/tmp"

gdb内から、自前でgetcwdを呼び出したい。それには、結果のbufを自前で用意する必要がある。

そこで、callの出番。メモリーをgdb上から確保したいので、お馴染みのmallocを呼び出す。 その結果(のポインター)を、16進で表示。

自前で確保したbufを指定して、getcwdを呼び出す。結果はbufに入っているので、文字列ポインターにキャストした上で、表示。ちゃんと、答えが返ってきた。万歳。

getcwd for (at|cron)

上で覚えたgdb callの技を、早速職業人的(適)OSでCentOSで試してみたい。前回の最後で、atがダエモン君に変身した時、作法の一つ / へ移動するのをさぼっている節があるからだ。

そんな訳で、動いているatにgdbで乗り込んで行って、getcwdで、atが何処にいるか調べたい訳。root権限で動いているはずだから、rootになっておいて互角の勝負を挑む。

[sakae@cent ~]$ ps awx | grep atd
  851 ?        Ss     0:00 /usr/sbin/atd -f
 1922 pts/1    R+     0:00 grep --color=auto atd
[sakae@cent ~]$ sudo -s
[root@cent sakae]# gdb -q -p 851
  :
(gdb) call/x malloc(128)
$1 = 0xc82735e0
(gdb) call getcwd($1, 64)
$2 = 0
(gdb) p (char *)$1
$3 = 0xffffffffc82735e0 <Address 0xffffffffc82735e0 out of bounds>
(gdb) p/x $1
$5 = 0xc82735e0
(gdb) x/s $1
0xffffffffc82735e0:     <Address 0xffffffffc82735e0 out of bounds>

なんで、こうなるの? mallocで手に入れたエリアが、勝手に符号拡張された風で、神聖かつ侵すべからずのカーネルエリアに侵入してる。この手は使えんって事か。ええい、こうなったらウブで試してやる。

(gdb) call/x malloc(128)
$1 = 0x7f3fe5b512c0
(gdb) call getcwd($1, 64)
$2 = 0x7f3fe5b512c0 "/var/spool/cron/atjobs"
(gdb) p (char *)$1
$3 = 0x7f3fe5b512c0 "/var/spool/cron/atjobs"
(gdb) x/s $1
0x7f3fe5b512c0: "/var/spool/cron/atjobs"
(gdb) detach
Detaching from program: /usr/sbin/atd, process 900
(gdb) q
root@usvr:~# gdb -v
GNU gdb (Ubuntu 8.1-0ubuntu3) 8.1.0.20180409-git

ウブの方では上手くいった。mallocで得たエリアが素直に使われている。ひょっとして、gdbが 古くて保守的なのかな? もう一度CentOSを立ち上げて確認してみるか。先ほどCentOSを落とす時、/etc/selinux/configを変更して、selinuxを殺しておいたからね。

[sakae@cent ~]$ gdb -v
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-110.el7

微妙に古いな。再び試すも、やはり符号拡張されちゃってエラーだった。gdbの挙動がselinuxによって左右されない事がこれで判明(まあ、そうだわな)。

(gdb) catch syscall
Catchpoint 1 (any syscall)
(gdb) c
Continuing.

Catchpoint 1 (call to syscall restart_syscall), 0x00007f7c05e37550 in __nanosleep_nocancel () from /lib64/libc.so.6
(gdb) bt
#0  0x00007f7c05e37550 in __nanosleep_nocancel () from /lib64/libc.so.6
#1  0x00007f7c05e37404 in sleep () from /lib64/libc.so.6
#2  0x0000562e8388b64e in main ()

これ、なかなか便利そう。それじゃ、調子に乗ってBSD業界も調べてみる。ターゲットはリナに一番近い所に居るFreeBSDです。なんたって、debian/FreeBSD なんてのもありました(ありますの進行形かしら)。

root@fb:~ # gdb -v
GNU gdb (GDB) 8.1 [GDB v8.1 for FreeBSD]

このgdb、新しい機能が欲しくて、portsで入れたんだったな。システム付属のやつは、とっても古いやつです。

(gdb) call/x malloc(128)
'malloc' has unknown return type; cast the call to its declared return type
(gdb) call/x (char *)malloc(128)
$1 = 0x80146f600
(gdb) call getcwd($1, 64)
'getcwd' has unknown return type; cast the call to its declared return type
(gdb) call (char *)getcwd($1,64)
$2 = 0x80146f600 "/var/cron"
(gdb) p (char *)$1
$3 = 0x80146f600 "/var/cron"

むき出しのatは動いていないので、cronで確認。ちゃんとキャストしろって言うのは、その通りだよな。リナはあくまで便利さ追及なんだな。

(gdb) catch syscall
Catchpoint 1 (any syscall)
(gdb) c
Continuing.

Catchpoint 1 (call to syscall nanosleep), 0x0000000800d36bfa in _nanosleep ()
   from /lib/libc.so.7
(gdb) bt
#0  0x0000000800d36bfa in _nanosleep () from /lib/libc.so.7
#1  0x00000000004032d7 in ?? ()
#2  0x0000000000402b85 in ?? ()
#3  0x000000080062d000 in ?? ()
#4  0x0000000000000000 in ?? ()
(gdb) detach
Detaching from program: /usr/sbin/cron, process 640

catchも使えた。けど、libcあたりが最適化されてて、追跡されない。そういう時は、OpenBSDだな。

ob6# gdb -q -p 18417
Attaching to process 18417

warning: No executable has been specified and target does not support
determining executable automatically.  Try using the "file" command.
[Switching to thread 529536]
0xe040e34a in ?? ()

どうもPIDだけじゃ、お気に召さないようなので。

ob6# gdb -q /usr/sbin/cron 18417
Reading symbols from /usr/sbin/cron...(no debugging symbols found)...done.
Attaching to program: /usr/sbin/cron, process 18417
Reading symbols from /usr/lib/libc.so.92.3...done.
Reading symbols from /usr/libexec/ld.so...done.
[Switching to thread 529536]
_thread_sys_ppoll () at -:3
3       -: No such file or directory.
(gdb) bt
#0  _thread_sys_ppoll () at -:3
#1  0x00001744e0405f08 in _libc_ppoll_cancel (fds=0x17426aa0a180, nfds=1,
    timeout=0x7f7ffffd2700, sigmask=<optimized out>)
    at /usr/src/lib/libc/sys/w_ppoll.c:36
#2  0x000017426a8011e4 in ?? ()
#3  0x000017426a800b26 in ?? ()
#4  0x0000000000000000 in ?? ()

ちゃんと、バイナリーも指定したら、まともに応答。待ってる所が分かった。

(gdb) call/x malloc(128)
$1 = 0x174510c1e900
(gdb) call getcwd($1, 64)
$2 = 281143552
(gdb) p (char *)$1
$3 = 0x174510c1e900 "/"
(gdb) detach
Detaching from program: /usr/sbin/cron, process 18417

そしてお目当ての、何処にいるかの確認。daemonを呼び出しているので、ちゃんとtopDirに居ましたよ。

ob6# gdb -v
GNU gdb (GDB) 7.12.1

使ったgdbは、こういうやつなんだけど、思う通りに動いている。CentOSのそれは、何か細工でもされてるのか? それとも、同じ7系でも、大幅に機能が違うとか?

/procはどうよ?

困った時の /proc頼り。思い出したね。

[sakae@cent ~]$ ps awx | grep atd
  829 ?        Ss     0:00 /usr/sbin/atd -f
 1675 pts/0    S+     0:00 grep --color=auto atd
[sakae@cent ~]$ cd /proc/829
[sakae@cent 829]$ ls
attr/            cwd@      map_files/  oom_adj        schedstat  task/
autogroup        environ   maps        oom_score      sessionid  timers
auxv             exe@      mem         oom_score_adj  setgroups  uid_map
cgroup           fd/       mountinfo   pagemap        smaps      wchan
clear_refs       fdinfo/   mounts      patch_state    stack
cmdline          gid_map   mountstats  personality    stat
comm             io        net/        projid_map     statm
coredump_filter  limits    ns/         root@          status
cpuset           loginuid  numa_maps   sched          syscall
[sakae@cent 829]$ ls -l cwd
ls: シンボリックリンク cwd を読み込めません: 許可がありません
lrwxrwxrwx 1 root root 0  9月 17 07:14 cwd
[sakae@cent 829]$ sudo ls -l cwd
lrwxrwxrwx 1 root root 0  9月 17 07:14 cwd -> /var/spool/at

信じていいのかな? ちょいといたずらする。

[root@cent 829]# kill -TERM 829
[root@cent 829]# cd /home/sakae
[root@cent sakae]# /usr/sbin/atd -f
^Z
[1]+  停止                  /usr/sbin/atd -f
[root@cent sakae]# bg
[root@cent sakae]# ps awx|grep atd
 1748 pts/0    S      0:00 /usr/sbin/atd -f
 1765 pts/0    S+     0:00 grep --color=auto atd
[root@cent sakae]# ls -l /proc/1748/cwd
lrwxrwxrwx 1 root root 0  9月 17 07:20 /proc/1748/cwd -> /var/spool/at

ちゃんと自発的に、場所を移しているね。ダエモン君の作法は、守られていました。

wall

以前、WSLでまともに動かなかったwallコマンド。MSがどのあたりの実装でサボっているか、考察すると言う、不純な動機で、OpenBSD側の実装を眺めてみる事にする。

何でも、MSは秋の大幅改修を実施するようなんで、果たして良くなっているか、ハラハラドキドキの注目です。(本音は、もう何もしないでほっといてくれですが。。。) きっと、雑誌に提灯記事が氾濫するんでしょうな。

ちょいと予習しとく。関連がありそうだからね。

ttyとかptsとかについて確認してみる

PTY を使ってシェルの入出力を好きなようにする

TTY/PTYに関するクイズ

wall.c をざっと見、mainのオプション解析で

                case 'n':
                        /* undoc option for shutdown: suppress banner */
                        pw = getpwnam("nobody");
                        if (geteuid() == 0 || (pw && getuid() == pw->pw_uid))
                                nobanner = 1;
                        break;

こんな楽しいコメントを発見。確かにmanしても説明が無い。ずるいぞOpenBSD。いや違うよ。 Sourceを開いてみた人へのご褒美です。

オプションの解析が終わると、makemesg(*argv)で、多分、出力すべきメッセージを作って いるんでしょう。何処に作るんでしょうね。返り値も無いですし。きっと、一時的ファイルに でも書き出すのかな、と想像しておく。

        if (!(fp = fopen(_PATH_UTMP, "r")))
                err(1, "cannot read %s", _PATH_UTMP);

この _PATH_UTMPが大事そうなんだけど、探しても出てこない。こういう時は、cc -E で、cppに一働きさせるのが(オイラーの中では)定番になりつつあります。 展開してもらった結果は、

 if (!(fp = fopen("/var/run/utmp", "r")))
  err(1, "cannot read %s", "/var/run/utmp");

こういう結果になりました。綺麗な書式なんて知った事ではない。ccが読めれば(機械可読)いいんですから。diffで閲覧する時は、-b を付けて、スペース数の違いを無視するようにすると楽。また、前半はヘッダーファイルの展開結果がわんさか出てくるので、尻尾からさかのぼってみるのが効率的。こういうのをバッドノウハウって言うのかな。

-       if (pledge("stdio rpath wpath getpw proc", NULL) == -1)
+ if (pledge("stdio rpath wpath getpw proc", ((void *)0)) == -1)

ちょっと今回の事例とは直接関係ないけど、NULLの正しい正体が晒されている。こういうのは、まともにヘッダーを見る事ないから、参考になりますだ。いや、重箱の隅を突くためのネタですな。

-       char *p, *whom, hostname[HOST_NAME_MAX+1], lbuf[100], tmpname[PATH_MAX];
+ char *p, *whom, hostname[255 +1], lbuf[100], tmpname[1024];

こういうの集めてくるの苦労するけど、cppにやらせると一望出来て大変宜しい。いやね、具体的な値が分かると、実感がこもるってもんですよ。

話を戻して、/var/run/utmp なんてファイルをオープンしてたので、man utmpしたら ファイルフォーマットのセクションが出て来た。

     The <utmp.h> file declares the structures used to record information
     about current users in the utmp file, logins and logouts in the wtmp
     file, and last logins in the lastlog file.  The timestamps of date
     changes, shutdowns, and reboots are also logged in the wtmp file.

           #define _PATH_UTMP      "/var/run/utmp"
           #define _PATH_WTMP      "/var/log/wtmp"
           #define _PATH_LASTLOG   "/var/log/lastlog"

なるほど、オイラーのつたないAIによって、一般化すると、_PATHは、ファイルパスだよ。それに続くやつは、個別ファイルだよ。大事なファイルなんで、セクション5に説明が有るはずってのが、学習されました。

で、このファイルの内容をダンプするコマンドは、who(1)なんて事が書かれていた。簡単にログインしてるユーザーを知る事が出来るんだな。

後は、ログインユーザーとそのグループに関して情報を集め(どの端末から入ってきてる)て、 リストを作って行く。そして、それを元にttymsgを呼んで、端末にメッセージを送る。

WSLに足りない所

ここまで、wallの動きが分かれば、WSLでwallが動かなかった理由の説明が出来そう。

そう、メッセージを送るべきユーザーが検出出来なかったのだろう。loginしてるユーザー情報は、/var/run/utmpに記録されるはず。ご丁寧に、このファイルを使ってる、別なコマンドが列挙されてた。

sakae@atom:~$ w
 05:51:48 up 0 min,  0 users,  load average: 0.52, 0.58, 0.59
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
sakae@atom:~$ who
sakae@atom:~$ users

少なくとも、sakaeはログインしてるんで、ここに載ってくるはずだけど、誰もいない。だったら、wallで何処にメッセージを出すとな。そう言えばば、かの昔lastが機能してなかった事に気が付いていたな。

ようするに、WSLは、ユーザーのログイン管理をすっかり放棄してるって事だ。aptとかする時に sudoするけど、それがかろうじてのユーザー管理になるかな。

ちなみに、

sakae@atom:~$ ls -l /var/run/utmp
ls: cannot access '/var/run/utmp': No such file or directory
sakae@atom:~$ ls -l /var/log/wtmp
-rw-rw-r-- 1 root utmp 0 Mar  1  2018 /var/log/wtmp

ちゃんとしたdebian様と比べてみるかな。

debian:~$ who
sakae    pts/0        2018-09-17 05:29 (xxx.xxx.xxx.xxx)
sakae    pts/1        2018-09-17 05:29 (tmux(1715).%0)
sakae    tty7         2018-09-17 05:41 (:0)
debian:~$ w
 06:06:45 up 46 min,  3 users,  load average: 0.01, 0.03, 0.06
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
sakae    pts/0    xxx.xxx.xxx.xxx  05:29    5.00s  0.00s  0.00s tmux -u
sakae    pts/1    tmux(1715).%0    05:29    5.00s  0.01s  0.00s w
sakae    tty7     :0               05:41   46:22   2.89s  0.08s /usr/bin/lxsess
debian:~$ users
sakae sakae sakae
debian:~$ ls -l /var/run/utmp
-rw-rw-r-- 1 root utmp 2304 Sep 17 05:41 /var/run/utmp
debian:~$ ls -l /var/log/wtmp
-rw-rw-r-- 1 root utmp 49920 Sep 17 05:41 /var/log/wtmp

ひょっとしてutmpをWSL側に移植したら、これらのコマンドが曲りなりにも動くそぶりを見せてくれたりして。(unix同士なら、平気でこういう馬鹿やっちゃうけど、相手先がWSLなんで、自粛しておきます)

WSLの/var/run内(実体は、/run だけど)は、debianのそれに比べて、内容が乏しいな。この乏しさが、手抜き具合に直結してる訳ね。

余りMSを貶すとしっぺ返しを食らいそうなので、少し よいしょ しておくか。

WSLはオイラーみたいな人の為にあります。前回Windows golang をやった時、それ様のMakefileを書いた。直接の理由は、出来上がったバイナリーをWindows上で削除しようとして、rm と叩いちゃったんだ。そして、そんなコマンドは無いと怒られた。

つなたい脳内変換をしてdelかなと推測。それじゃ、a.exeを別の名前に変更するには? rename。脳内変換の負荷が高すぎる。sleepに至っては、timeoutなんて、もうカオスですよ。

そこで、勝手知ったるunix系。WSLがそこに有った。便利じゃん。いちいちログインして使うなんて、二度手間じゃん。だって、Windowsを使い始めるのにログインしてますからね。

きっと、MSもこういう思考をしたんだろうね。だから、あえてloginの機構を外した。

ポジテブ思考をすれば、人生楽しく生きられるよ。

上手くまとまったので、この辺で切り上げる。次回に続く。

追記

上記の原稿を書いた後、散歩に出かけた。ふと思ったね。WSLの件。/var/run/utmpが無い状態で、whoとかがよくエラーにならなかったなと。

思うに、debian側とWindows側の間に立ってるあぷりが、utmp,wtmp,lastlogファイルのopenを検出して、exitを実施しちゃってるんではなかろうかと。機微に触れる事は、無かったことに しちゃうという、どこかの誰かさんが好きな戦法。

これを確かめるには、間に立つアプリ lxrunか LxssManagerをリバースしてみろ。何、難しい事は無い。

これらのファイルをstringsして、/var/run/utmpって文字列が含まれていないか調べる。もし含まれていれば、オイラーの推測の確度が俄然UPするな。

これなら、逆アセしてないんで、禁に触れる事はなかろう。MSさんに朗報です。情けない事に、上記のファイルが何処にあるか分かりません。(だからWindowsは嫌いなのよ)