Haskell and FreeBSD 11.1 (2)

前回は、FreeBSD11.1という新しいOSにghcを突っ込もうとして、ごちゃごちゃやった。 普通なら、新しいOSが出ても、アップデータを使って、最新式OSに移行出来るんだけど、 あえてそれをやらないのは、ゴミ掃除のためだ。

一家を構えると、引っ越しが無い為、次第に使わないごみが溜まっていく。定期的にマイナー ガベコレでも発動させてれば、大事にはならないはずなんだけど、さぼり癖がつくと、もう 手が付けられなくなる。

オイラーは転勤が付きもののサラリーマン生活だったので、転勤の度にメジャーガベコレを 余儀なくされる運命だった。この習慣が根付いているせいか、自分で言うのはなんだけど、 身辺整理はお手の物。

新しいOSに乗り換える度に、ガベコレですよ。HOMEDIR下の必要品だけ、新しい所に持って いく。マークスイープじゃなくて、コピー方式ね。

で古くなったOS(今回なら、FreeBSD 11.0)はどうする? だれも(ってオイラーしかいないけど)使わなくなった空き家。誰かに貸すのもままならないので、火を付けて焼却して、更地に してからOS(今はWindows10)に返却するのが筋。土地(Disk)は、貴重な資源ですからねぇ。

今回はどうやって壊そう。rm -rf / じゃ、最近のやつは耐火性能が上がっていてどうにも ならない。上手い壊し方を誰か教えてくれませんか。処方箋を絶賛募集しています。

[fb11: sakae]$ cat /boot/loader.conf
tmpfs_load="YES"
autoboot_delay=1

ああ、忘れる所だった、辺鄙な所に置いてある設定知。manから導出出来るけど、未来のために。

前回の訂正記事

clangがらみで、/usr/local/libをライブラーのサーチパスに加えるように、/etc/rc.confに 手を加えるようにしてた。これ、真っ赤な大嘘だった。/etc/defalut/r.conf を覗いてみたんだ。そしたら

ldconfig_insecure="NO"  # Set to YES to disable ldconfig security checks
ldconfig_paths="/usr/lib/compat /usr/local/lib /usr/local/lib/compat/pkg"
                        # shared library search paths
ldconfig32_paths="/usr/lib32 /usr/lib32/compat"
                        # 32-bit compatibility shared library search paths
ldconfigsoft_paths="/usr/libsoft /usr/libsoft/compat /usr/local/libsoft"
                        # soft float compatibility shared library search paths
                        # Note: temporarily with extra stuff for transition
ldconfig_paths_aout="/usr/lib/compat/aout /usr/local/lib/aout"
                        # a.out shared library search paths
ldconfig_local_dirs="/usr/local/libdata/ldconfig"
                        # Local directories with ldconfig configuration files.
ldconfig_local32_dirs="/usr/local/libdata/ldconfig32"
                        # Local directories with 32-bit compatibility ldconfig
                        # configuration files.
ldconfig_localsoft_dirs="/usr/local/libdata/ldconfigsoft"
                        # Local directories with soft float compatibility ldconfig

こんな記述を発見したぞ。全く見当違いな事をしてたわい。この設定知は、/etc/rc.d/ldocnfig内で使用されるんだな。

ついでなんで、OpenBSDでは、このあたりをどう処理してるか調べてみる。man rc.confすると こんな説明が出てきた。なる程、規定の所意外は、shlib_dirsにスペース区切りで列挙して おけとな。すっきりした仕様が安全性を高めますって見本だな。

     shlib_dirs  Extra shared library search path entries.  rc calls:
                 ldconfig(8) /usr/X11R6/lib /usr/local/lib ${shlib_dirs}

昔のOpenBSDは、linuxとかのエミュレーションとかをサポートする為、それ用のシステムコールを用意してたけど、最近それらはバサーと削られて、我が道を行くって態度だそうだ。

Linuxみたいに便利が一番よの対極にあるんだな。折角なので、/etc/rcのコードもみておく。

if [[ -x /sbin/ldconfig ]]; then
        echo 'creating runtime link editor directory cache.'
        [[ -d /usr/local/lib ]] && shlib_dirs="/usr/local/lib $shlib_dirs"
        [[ -d /usr/X11R6/lib ]] && shlib_dirs="/usr/X11R6/lib $shlib_dirs"
        ldconfig $shlib_dirs
fi

それから案の定、clangを使った コンパイルでは、ld.lldには外部からどうこうする手段は無いみたいだ。 しょうがないので、禁断な手を使ったよ。

[fb11: ~]$ ls -l /usr/lib/libiconv.so
lrwxr-xr-x  1 root  wheel  32 Aug 22 08:16 /usr/lib/libiconv.so@ -> /usr/local/lib/libiconv.so.2.5.1

cat

FreeBSDの住み替えをやった時に、荷物の点検をしてたんだ。そしたら昔の記録が出て来た。 あの分厚いReal World Haskellの著者の方が掲示板に投稿した記録。

Haskellでグローバル変数が欲しい理由って山本先生の記事にインスパイアされて、自分ならどうするって問題意識で、書き下ろされたもの。 今から8年も前の記録が再びよみがえってきた。引っ越しって、こういう事があるから、たまにはやるものだ。あのshiroさんも引っ越し推奨派だ。引越し

グローバル変数が欲しい理由?が、オイラーの所に残っていた記事だ。残っててよかったぞ。

そしてその返歌と言うか返礼を先生が返されている。 グローバル変数が欲しい理由の考察2度読みされたと告白されてるけど、オイラーも3度読みぐらいして、ちんぷんかんぷんだったので、永久保存したんだな。そうか、これが理解出来るようになれば、仙人に近づける訳だな。

8年ぶりの正直で、再挑戦する。

まずオイラーが違和感を覚えたのが、mainが頭に来ている事。C語にしろPythonやlispにしろ、 使うものを組み立てておいて、最後にそれを使うってのが定番。

mainからいきなり書いていいのか。コピペして自分でも実行したら、何の 文句も言われずに動いた。(ああ、原文では、getArgsを使うんで、import System するって なってるけど、近代のghcだと、System.Environmentね。)

裁判の判決文みたいに、主文 なんとかかんとか、続いてその理由って述べ方。主関数 main の書いた後、そこで使ってるのを書き下していくって方式。

今までの言語は、小説の書き方だった。いわゆる、起承転結方式ね。こちらに慣れてしまって いるので、主文を述べてからそれを補強してく方式には強い違和感を覚えるのだ。

でも、相手はHaskell。Haskellと言えば遅延評価。必要になった時に呼ぶからねが、コンパイルの段階でも機能してる? それとも、

main = ....
   where
     ....

みたいなのが、適用される? それとも、型にしっかり守られているんで、型チェックの 段階で、芋づる式にチェックされる? どうも、型を見てくんで、定義の順番はどうでもいいって事なんだろうね。

main = getArgs >>=
       \argv -> let f = genProg opts
                    opts = parseOpt argv
                    inputs = getInputs opts
                in (runProg f) . concat =<< inputs

ここのlet内の一番最初に現れる引数、optsも、字面上では、まだ未定義。lispでこんな事を したら、怒られるはず。でも、haskellは芋蔓式だから、何食わぬ顔をして、旨く丸め込んで くれるのか。

この前に買った実践本でも述べられていたんだけど、Haskellでの開発はトップダウンが似合うと。これを可能してるのは、型が有るから。設計は型が合えば80%は完成してると述べている。

ハードの設計も、haskellと似てるな。例えば、ラジオを設計製作する場合。大まかな構成を 決める。シングルコンバージョン方式にするのか、ダブルコンバージョン方式にするのか、 それとも大胆にダイレクトコンバージョン方式にするのか。

それとも、もっともっと大胆に、SDR方式とかね。この方式ならハードでフロントエンドを用意し、そこから出てくるIQ信号をSDRっていうソフトの塊に任せる事になる。こうなるとほとんど ソフトの設計になるな。

まさか、Haskellでそんな事をやってる人は居ないだろう。一応探してみるか。 すると酔狂な人が居たよ。 The sdr package。これを組み立てたら、 添削君(hlint)や、ghc-modみたいにぶくぶくに膨れ上がったアプリになるんだろうか? それにしても、ghc-modは、90Mの太っちょアプリでびっくりしたぞ。ああ、余計な事を 書いてしまったわい。

余計ついでに、sdrのアプリの中で、使えそうなモジュールを見つけた。

import Options.Applicative
import Data.Decimal

{-| Parse a number that may have a decimal point and a suffix, e.g. 2.56M -}
parseSize :: ReadM Integer
parseSize = eitherReader $ \arg -> case reads arg of
    [(r, suffix)] -> case suffix of
        []  -> return $ round (r :: Decimal)
        "K" -> return $ round $ r * 1000
        "M" -> return $ round $ r * 1000000
        "G" -> return $ round $ r * 1000000000
        x   -> Left  $ "Cannot parse suffix: `" ++ x ++ "'"
    _             -> Left $ "Cannot parse value: `" ++ arg ++ "'"

次の段階は、各段のレベル設計。コンバージョンロスが-16dBあるから、前段にAmpを入れて、補償してやろうとか。この段階まで来れば、何処から手をつけてもOK。手戻りは無い(はず)だ。

そして、ハードなら、ここはこんなスペックの石(トランジスターの事ね)が欲しいなと、石の 規格表からチョイスしてく事になる。

Haskellでの開発だと、hoogleを型で検索して、ぴったりな関数を見つけ出す。希望するものが ないと、その部分は小さい関数を使って合成するかとなる。

ハードでも同じ、Hfeの高いコレクタ損失が大きい石なんて、なかなか無い。ならば、石2個を ダーリントン接続して、要求も満たす石にしようってなるな。

決して、部品からボトムアップで作るなんて事は無いはず。(東芝とかの石が得意なメーカーでも、ダーリントン接続した、ハイブリッドな石は、需要が多ければ作るだろうけど。かのメーカーが消滅しない ように祈ります)

こうしてみてくると、Haskellの開発方式は、ハード屋さんの開発方式とも相性がいいことが 分かるな。

昔の記事から、思わぬ事を考えてしまったわい。まあ、のんびりと行きましょ。

ghc-mod

前週でやったように、ghc8.2.1は今の所、FreeBSD11.1ではcabalの関係でまともに使えない。 ならばしょうがない、stackで我慢するか。入れる途中で、必要品を取り寄せるんだけど、 下記が追加されたよ。

compat8x-amd64-8.4.804000.20151116 Convenience package to install the compat8x libraries
compat9x-amd64-9.3.903000.20170608 Convenience package to install the compat9x libraries

この際だから、stackのインストールスクリプトを見ておくか。FreeBSDの部は、下記のように なってた。(普通、先に見ておくものだろう)

# Attempts to insall on FreeBSD.  Installs dependencies with
# 'pkg install' and then downloads bindist.
do_freebsd_install() {
  install_dependencies() {
    pkg_install_pkgs devel/gmake perl5 lang/gcc misc/compat8x misc/compat9x con\
verters/libiconv ca_root_nss
  }
  if is_64_bit ; then
    install_dependencies
    install_64bit_freebsd_binary
  else
    die "Sorry, there is currently no 32-bit FreeBSD binary available."
  fi
}

32Bitは、ここでも切り捨て御免なのね。折角Windows7機のFreeBSDも11.1にしたのに、残念至極であります。debianとかfedoraだと32Bit系もサポートしてた。Linuxのメジャー級は、手厚くサポートされてるな。ああ、alpineもサポートされてるけど、64Bitだけか。何となくdocker目当てなんだなと思う。世相を反映してて面白いな。

stackが入ったので、ghc-modを入れた。.local/binに、ghc-mod、ghc-modi、hlintがインストールされたよ。hlintって前回取り上げたけど、有名な添削君なのね。あちこちに顔を出して いるよ。

で、emacsの設定を済ませて、.local/binにもPATHを通して、使おうとしたら、ghcが無いと 言われた。しょうがないので、stack内のghcにもPATHを通したよ。

PATH=~/.local/bin:~/.stack/programs/x86_64-freebsd/ghc-8.0.2/bin:$PATH

それでも、思うようにghc-modが動かないので(先生はdebianが用意した一式)調査したら、 elpaからghcも必要なんだけど、何だか導入されていなかった。入れたら動き出した。 これで、debianと同じ使い心地になった。パッケージに頼るんぢゃなく、一度は手動で 入れておくと、トラブった時に、何かの知見になるな。

/usr/local/GHC821に入れた、ghc 8.2.1の方は、PATHから外しておいた。stackが使ってる やつと混じると危険だからね。まあ、8.2.1は、どこからも指されていない独立系になってるんで、使いたかったら都度PATHの頭にconsして使うか、絶対PATHで指定するかだな。

ああ、ふとPATHの設定区切りが、コロンである事に面白みを覚えた。上記のrc.confに設定する ldconfig_pathsは区切りがスペース。何となく、コロン区切りって、HaskellのList構築子に 思えるのは、Haskell脳になってきた証拠かなあ。

で、調子よく使っていたら、Debianのパッケージから入れたghc-modと違う挙動になってる事に 気がついた。import行の所で、C-M-d して、パッケージの説明をewwで表示しようとすると、 Debainのそれは、haskell.orgにアクセスしに行く。それに対してstackにghcを用意したFreeBSDでは、ローカルにあるファイルを参照しに行く。なんで、こんな違いが生まれるの?

山本先生作の、ghc-doc.el に答えが有った。

(defconst ghc-doc-local-format "file://%s/%s.html")
(defconst ghc-doc-hackage-format
  "http://hackage.haskell.org/packages/archive/%s/%s/doc/html/%s.html")

(defun ghc-display-document (pkg-ver-path mod haskell-org &optional symbol)
  (let* ((pkg  (ghc-pkg-ver-path-get-pkg pkg-ver-path))
         (mod- (ghc-replace-character mod ?. ?-))
         (ver  (ghc-pkg-ver-path-get-ver pkg-ver-path))
         (path (ghc-pkg-ver-path-get-path pkg-ver-path))
         (local (format ghc-doc-local-format path mod-))
         (remote (format ghc-doc-hackage-format pkg ver mod-))
         (file (format "%s/%s.html" path mod-))
         (url0 (if (or haskell-org (not (file-exists-p file))) remote local))
         (url (if symbol (ghc-add-anchor url0 symbol) url0)))
    (funcall ghc-doc-browser-function url)))

指定したものがローカルファイルとして存在してるか確認して、無ければhackageの方を見に行く、有ればファイルを参照する。そうか、Debianでも、ghc-doc あたりを入れると良いんだな。

どうでもいいけど、lispでは、letで変数類を用意しておいて使うってのが定番になってるけど、 haskellぽく、where構文って無いかなあ。こんな雰囲気で書けると、haskellを収めた人も lispに取っ付き易くなると思うんだけどね。(haskell屋はlispなんて言う不純な言語には、鼻から興味は在りません。逆は許せる、山本先生みたいにね)

(funcall ghc-doc-browser-function url
     (where
         :
         (url0 (if (or haskell-org (not (file-exists-p file))) remote local))
         (url (if symbol (ghc-add-anchor url0 symbol) url0))))

S式の最後にwhereが有るっていう構文。どうしてもやりたかったらマクロを書け。ああ、マクロ って、DSLの事。haskellでも、template-haskell ってのが有るじゃん。何故かLisp以外の言語って、マクロって語句は禁句になってるっぽい。ほんわかとテンプレートとかDSLって言ってるな。マクロは難しいってのを払拭したいんだろう。

FreeBSD 11.0 を壊す

使わなくなったFreeBSD。空き家バンクに登録して入居者を募ってもいいんだけど、この地では 希望者は絶望的。ならば、さっさと取り壊して更地にし、Windows10へ土地を返却しよう。

手順は簡単で、VMWAREが管理してるFreeBSDのdirを削除。VMWAREを起動して、FreeBSDを選ぶと、見つからないけど場所を移動した? それとも削除したって言われるので、削除したって すれば、跡形もなく昔の痕跡は消えてしまう。諸行無常。

でも、こういう消し方では味気ないので、FreeBSDの家屋を手動で壊して、建屋に入れないようにしてみる。Sの楽しみとでも申しましょうかね。

[fb11: sakae]$ sudo rm /etc/rc.d/ldconfig

これをやるとリモートからremote login出来なくなった。諦めてコンソールからloginすると

Shared object "libintl.so8 not found, required by bash

と言われた。そう、bashは/usr/localに入っている。ライブラリィーは、/usr/local/libを 見るんだけど、そんなのldconfigが参照出来るようにしてくれていないので、見つからない エラーに落ちるんですなあ。

こうなってしまっても、rootではlogin出来るので、ユーザーがbashを使わないようにすればよい。さしずめ、/bin/shあたりかな。

chsh sakae

shellを変更したら、ログインできた。まあ、分かっててやってるので、さっぱり訓練にならない。

ならば、もっと大胆に、/etc/rc.d下を空にしてみるか。 (近頃はリナしか触った事が無い人が多数なんで、説明しとくと、/etc/rc.dって、リナで昔は 使われていた、/etc/init.dね。何? そういうのも知らない? 全く新人類め、systemdの 大事な所だよ。仕様書が入っている場所さ。そんな細かい事は知らないだろーな。)

まだ、ユーザーでログイン出来た。けど、既にソケットが作れない。なぜならRead-only な ファイルシステムですってさ。 勿論、普通のファイルも作れない。DHCPでアドレスも取得出来ないので、リモートログインなんて、もってのほかになりましたよ。

rootで、mount / したら、書き込み可になった。dhclient em0で、アドレスが取れた。 けど、リモートで接続不可。sshdが起動出来なかったのだろうな? /usr/sbin/sshdしたら、 リモートから接続出来た。 これって、要介護度3ぐらいですか?

最後は致命的な事をやろう!!

root@:~ # rm /sbin/init
override r-xr-xr-x  root/wheel schg for /sbin/init? y
rm: /sbin/init: Operation not permitted
root@:~ # chmod 755 /sbin/init
chmod: /sbin/init: Operation not permitted

強い抵抗に遭いましたね。この抵抗を無力化します。

root@:~ # ls -lo /sbin/init
-r-xr-xr-x  1 root  wheel  schg 1061880 Sep 29  2016 /sbin/init
root@:~ # chflags noschg /sbin/init
root@:~ # ls -lo /sbin/init
-r-xr-xr-x  1 root  wheel  - 1061880 Sep 29  2016 /sbin/init
root@:~ # rm /sbin/init
override r-xr-xr-x  root/wheel for /sbin/init? y
root@:~ # ls /sbin/init
ls: /sbin/init: No such file or directory

削除抵抗フラグが付いていたので、フラグを落としてから削除した。これで、カーネルは動けど、ユーザーランド側の世話をしてくれるプロセスは生まれない状態になったはず。

[: sakae]$ ps awx
PID TT  STAT    TIME COMMAND
  0  -  DLs  0:00.01 [kernel]
  1  -  ILs  0:00.02 /rescue/init --
  :

緊急避難用のinitが動き出したぞ。(普通は、プロセスの1番に、/sbin/initが居るはずなんですけどね)

[: rescue]$ ls -l i*
-r-xr-xr-x  142 root  wheel  9578456 Feb 24 05:28 id*
-r-xr-xr-x  142 root  wheel  9578456 Feb 24 05:28 ifconfig*
-r-xr-xr-x  142 root  wheel  9578456 Feb 24 05:28 init*
-r-xr-xr-x  142 root  wheel  9578456 Feb 24 05:28 ipf*
-r-xr-xr-x  142 root  wheel  9578456 Feb 24 05:28 iscsictl*
-r-xr-xr-x  142 root  wheel  9578456 Feb 24 05:28 iscsid*

これって、FreeBSD業界のbusyboxなんですね。初めて動く所を見ましたよ。ファイルの実態は 同じでも、違う名前で呼び出すと、それ様の働きをする。なんと、142変化の多能ぶり。

次は、元ハード屋さんの矜持で、ドライバーを取り出し(ああ、仮想世界だからマウスでいいけど)て、VMWAREから、cd/ideとsoundのボードを取り外した。これでコンピュータの基本要素だけになったな。CPU、MEMORY、DISPLAY、PCI接続のHDDという構成。キーボードは、PCIバスにISA経由でぶら下がる事になるのかな。

これでも、ちゃんと起動してきたぞ。dmesgを見ると、このあたりだな。

atkbdc0: <Keyboard controller (i8042)> port 0x60,0x64 irq 1 on acpi0
atkbd0: <AT Keyboard> irq 1 on atkbdc0
kbd0 at atkbd0
atkbd0: [GIANT-LOCKED]
psm0: <PS/2 Mouse> irq 12 on atkbdc0
psm0: [GIANT-LOCKED]
psm0: model IntelliMouse, device ID 3
orm0: <ISA Option ROMs> at iomem 0xc0000-0xc7fff,0xc8000-0xc9fff,0xca000-0xcafff
,0xdc000-0xdffff,0xe0000-0xe7fff on isa0
vga0: <Generic ISA VGA> at port 0x3c0-0x3df iomem 0xa0000-0xbffff on isa0

次は、ユーザーランドの背骨を抜いてみよう。

root@:~ # ls -l /lib/libc.*
-r--r--r--  1 root  wheel  1744432 Dec 14  2016 /lib/libc.so.7
root@:~ # rm /lib/libc.so.7
override r--r--r--  root/wheel schg for /lib/libc.so.7? y
rm: /lib/libc.so.7: Operation not permitted

ああ、やっぱり保護されてる。上でやったように、LinuxのSELinuxもどきの保護フラグを落としてから、削除した。

そしたら、このオペレーションの効果が即現れましたよ。

root@:~ # sync
Shared object "libc.so.7" not found, required by "sync"
root@:~ # shutdown -p now
Shared object "libc.so.7" not found, required by "shutdown"

マシンが落とせなくなりました。そればかりか lsやその裏技echo * も出来なくなりました。 しょうがないので、電源を強制的に切りましたよ。さて、電源を入れたら、rootでlogin出来るかな?

起動中に、マウントしようとして、libcが無いと言われ諦めた風。それでも先に進んで、/dev/ttyv* を開こうとしてます。けど、いくらinitが頑張っても、もう息を吹き返す見込みは無し ですな。もう暫くまって、脳死判定ですかね。

最後の頼み、boot -sして、シングルユーザーモードで起動。 途中、

Enter full pathname or shell or RETURN for /rescue/sh:

となるので、RETを叩き、空しく緊急のshに入る。ここでもlsは出来ず。echo * は、出来て / 下にあるdir構成は確認出来たよ。でも、出来る事はこれまでだな。 (/rescue/ls とか、緊急キットは、きっと役立つな。このためのセットですから。もしやのために、非常訓練をやって、点検しておこう。今年の防災の日は終わってしまったけど、何時 北の方からミサイルが飛んで来るとも限りませんからね。

J-アラートで、頑丈な建物に避難 しろって言ってたけど、そんな建物有りませんです。自治体は直ぐに、火山シェルターを各戸に 配布すべきです。これで、もしもの時も、安心、安全、かな? 違うってば、御利益のありそうな神社の護符が一番頼りになりそう。原価10円ぐらいでしょうから、疲弊した自治体でも、容易に配布出来るぞ。ああ、また筆が滑ったわい。

援軍のレスキューCDでもあれば、そこから必要なものを分けてもらうとかの処置が出来る。

援軍もないので、ここで幕をひきますよ。