ふるつき

私は素直に思ったことを書いてるけど、上から目線だって言われる

ふるつきにいつでも質問できるサービスができました

この記事は、 kosen14s Advent Calendar 2017(本ページをリンク)のn日目の記事です。

昨日はちげくんが、 俺的スライドデザイン変遷2017 - 鍋はすき焼きがすき をやってくれました。ちげくんはn方向に手が伸びてるのにそれぞれがちゃんとハイレベルですごいですね。でも中学生なんだって。

今日は穴埋め的にここ数日で私が作っていた theoldmoon0602BOX ができたのでご報告を致します。

theoldmoon0602BOX はこんなサイトです

f:id:Furutsuki:20171205182134p:plain

殺風景なサイトです。見た目は Cakephp3 のものをそのまま使いました。だってかっこよかったので。というかこのデザインに合わせて作り始めてしまったのであとから変更とか考えてなかったですね。

で、これは何をするかと言って、「ふるつきに対して質問を投げかけるサイト」です。そしてそれに私が答える。

質問をする

f:id:Furutsuki:20171205182345p:plain

と、ふるつきにメールが届きます

f:id:Furutsuki:20171205182413p:plain

のでふるつきが答えて、

f:id:Furutsuki:20171205182434p:plain

質問した人にもメールが届きます

f:id:Furutsuki:20171205182456p:plain

やったね。

という感じのくだらないサイトです

Webアプリケーションみたいなのをフットワーク軽く作れるようになりたいな、とか。

くだらないサービス作って自己満足したいな、とか。

そういう気持ちが集まってできました。最初はLaravelで作りたかったのですが変に大きくて泣いちゃったので、Cakephp3に手を出してみたのですが、チュートリアルやったらだいたいわかって幸せになりました。ソースコードも一応公開していて、 https://github.com/theoldmoon0602/box です。

気が向いたら、適当に質問をしてくれると嬉しいです。


おまけ

theoldmoon0602BOX は さくらのVPSの上のUbuntu 16.04 上で動いています。 h2o + php-fpm + Cakephp3 という感じなので、軽くデプロイの感じを紹介しておきます。

h2o のビルド

https://github.com/h2o/h2o/ を clone してきて、 https://h2o.examp1e.net/install.html に従うとすんなりできました。依存で bison とか ruby とか、あと暗号周りを apt-get しました。 mruby 拡張使うかな、と思って ruby も入れたのですが、結局使っていません。

php-fpm の導入

最初は apt-get install php7.0-fpm していたのですが、あとで都合が悪くなって、 ppa から拾ってきた php7.1-fpm を使っています。開発環境は Ubuntu 17.10 だったので、 7.1 の環境で composerが lock lock しちゃったからのような気がします。

h2o の設定

/etc/h2o/ を作って /etc/h2o/h2o.conf.yml にはこんな感じで書いてます(ママ)

hosts:
  "theoldmoon0602.tk:80":
    listen:
      port: 80
    paths:
      "/":
        redirect: https://theoldmoon0602.tk/
  "theoldmoon0602.tk":
    listen: &default_ssl
      port: 443
      ssl:
        key-file: /etc/letsencrypt/live/theoldmoon0602.tk/privkey.pem
        certificate-file: /etc/letsencrypt/live/theoldmoon0602.tk/fullchain.pem
        min-version: TLSv1.2
        cipher-suite: ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:DH-DSS-AES256-GCM-SHA384:DHE-DSS-AES256-GCM-SHA384:DH-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA256:DH-RSA-AES256-SHA256:DH-DSS-AES256-SHA256:ECDH-RSA-AES256-GCM-SHA384:ECDH-ECDSA-AES256-GCM-SHA384:ECDH-RSA-AES256-SHA384:ECDH-ECDSA-AES256-SHA384:AES256-GCM-SHA384:AES256-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:DH-DSS-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:DH-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-SHA256:DHE-DSS-AES128-SHA256:DH-RSA-AES128-SHA256:DH-DSS-AES128-SHA256:ECDH-RSA-AES128-GCM-SHA256:ECDH-ECDSA-AES128-GCM-SHA256:ECDH-RSA-AES128-SHA256:ECDH-ECDSA-AES128-SHA256:AES128-GCM-SHA256:AES128-SHA256

    compress: [ gzip, br ]
    file.index: [ index.html, index.php ]
    paths:
      /:
        file.dir: /var/www/html
        header.add: "MI-ESTAS: THEOLDMOON0602"
      /bin:
        file.dir: /var/www/html/bin
        file.dirlisting: ON
      /kujokaren:
        file.dir: /var/www/kujokaren/public
      /box:
        file.dir: /var/www/box/webroot/
        redirect:
          url: /box/index.php
          internal: YES
          status: 307

  "box.theoldmoon0602.tk:80":
    listen:
      port: 80
    paths:
      /:
        redirect: https://theoldmoon0602.tk/box/
  "box.theoldmoon0602.tk":
    listen: *default_ssl
    paths:
      /:
        redirect: https://theoldmoon0602.tk/box/

user: www-data

access-log: /var/log/h2o/access-log
error-log: /var/log/h2o/error-log
pid-file: /var/run/h2o/h2o.pid

file.custom-handler:
  extension: .php
  fastcgi.connect:
    port: /var/run/php/php7.1-fpm.sock
    type: unix

cipher-suite くそ長くてじゃま。ファイル分けたいですね。まあこんな感じです。ホントは theoldmoon0602BOX を box.theoldmoon0602.tk で動かしたかったのですが、なんか url が /index.php/user/user/login とか /index.php/user/css/bake.css になって普通に辛くて泣きました。

h2o 自体は、昔は systemd で管理していたのですがうまく動いてくれなくて泣いたので、 /etc/h2o/runh2o.sh みたいなファイルを作ってます。

#!/bin/bash

start() {
    h2o -c /etc/h2o/h2o.conf.yml -m daemon
}

stop() {
    echo "STOP"
    kill $(cat /var/run/h2o/h2o.pid)
}

case $1 in
    "stop" ) stop ;;
    "restart" ) stop && start ;;
    "start" ) start ;;
    * ) start ;;
esac

php-fpm の設定

ほとんど設定する必要はないんですが、 h2o に合わせて、 /etc/php/7.1/fpm/pool.d を少しだけいじって、 走るユーザを www-data にしたり、 sock ファイルを /var/run... に動かしたりしています。

theoldmoon0602BOX のデプロイ

cd /var/www; git clone https://github.com/theoldmoon0602/box して、 composer install の後、 config/app.php を少し書き換えました。 debugfalse にしたり、 App.base' を/box` にしたり、SQLの設定をしたり、メールの設定をしたりしました。

そいから、 bin/cake migrations migrate とかするとだいたい設定完了でだいたい動いている気がします。

読書感想文「俺たちは異世界に行ったらまず真っ先に物理法則を確認する 3」

この記事は kosen14s Advent Calendar の二日目の記事です。一日目から誰もいなくてどうなることかと思っていたけど yamasy が一日目にふさわしい感じの記事を書いてくれました。ありがとう。

その一日目の記事の補遺のような感じですが、 kosen14s slack のchannelsには book なんてのもあります。 book はかつて行われた kosen14s読書会で利用されたり、おすすめの本を紹介したり、あとはkosen14sで薄い本かなにか作ろうぜという話がありましたが、あれはどこに行ったんでしょうね。やりたくなったからそのうち再燃すると思います。

さて、去る11月の30日に、「俺達は異世界に行ったらまず真っ先に物理法則を確認する」の3巻が発売されました。2巻が出たのが5月末だったと思うので、なかなか順調に刊行されているという感じですね。同日、小山高専で、作者の藍月要さんの講演があったらしいのですが、めっちゃ遠かったのでいけませんでした。とっても悲しい。

それはそれとして、3巻が出たし1巻2巻も読み返したので、全国の高専生やkosen14sの面々に届ける読書感想文を書きたいと思います。なお、文中で「高専生」を用いるとき、その代表として私が選ばれていることがありますのでそいう感じで。

タイトルで語るに落ちていますが、この小説は「高専生が異世界に行ってしまったら」という夢の作品です。大山高専のロボコペチームは、迫る全国大会に向けて夜を徹して作業に打ち込み、寝落ちて気がついたら異世界にいます。もちろん彼らが何をするかと言って、「まず真っ先に物理法則を確認する」のですが。

彼らは紛うかたなき高専生です。それもすこぶる優秀な。一体どこの高専生が異世界に飛ばされて重力加速度を調べるっていうんだ。俺もそういう高専生になりたかったかもしれない。

話の主軸には、高専生なのにコミュ力お化けの雨ケ谷幹人、その妹でお化け魔力持ちの雨ケ谷咲、ロボコペチームのリーダで雨ケ谷兄妹と仲良しの中久喜照治、コミュ障を極めた美少女天才プログラマ&ハッカ三峯魅衣、異世界で出会った美少女特級冒険者ザザ・ビラレッリなどがいますが、(咲とザザを除いて)みんな高専生です。おかしいっていう自覚がありながらおかしいに振り切ってるとことか、無限に効率もとめちゃったり運動がからきしだったりとか、もうめっちゃ高専生です。他のチームメイトももちろん高専生で、うーん最高に馬鹿ですね。

最高にわかりやすい例が幹人の「それぞれ独立でしょ」っていう台詞かなと思います。例えば言動とかですが、そういうこと思ったりしますよね? あいつは気に食わないけど言ってることは正しい、みたいな。

そういう高専生が異世界に行ったら……。もちろんモノを作って、プログラムして無双するわけですが、それを追いかけるのが、すっごい楽しいですね。俺でもこうする、俺ここまでの地力ないや……って自分と重ねてしまえるので、楽しい。一方で、「けーっ、天才プログラマ様は未知の言語もリバースエンジニアリングできるんですね」みたいな、高専生お得意のめっちゃこき下ろすみたいなのもしますが。とにかく、奇特な「高専生」という生き物が異世界で蠢いてるのはそれだけでも結構なものですよ。

それを書いてる、作者の藍月要さんはすごいですね。高専って機械とか電気とか情報とか科学とか建築とかいろいろあるのに、そして学科間の交流ってそんなにないのに、結構幅広い知識を扱ってる。なによりも高専を卒業して小説家になるのがすごいし、高専を卒業してなお高専と関わっていくの本当にすごいですね。尊敬の一言しかありません。

しかも3巻までちゃんとでている……。3巻では大山高専の面々は自動生成ダンジョン、n層からなるダンジョンで24時間(こちらの時間で言えば21時間と57分ですが)毎に各階が再生成されるようなダンジョンの攻略をするんですが、高専生のあなたならどうやって取り組みますか? 私はどうだろうなぁ……諦めそうだなぁ……。攻略諦めたら落単ですね。

結局3巻の感想文にならなかったし、文章読み返しても繋がってないし、短いし、だから私は小説家にはなれないんですが、とにかく3巻もちゃんと高専生が高専生していてよかったです。しかし高専生なのに恋愛をしていて許されない。

個人的には今の所2巻が好きです。終盤めっちゃかっこいいしずびすびなくよね……。

OCaml入門 ひとりAdvent Calendar

この記事は、OCaml入門 ひとりAdventCalendar 1日目の記事ですが、そんなAdventCalendarはなくって、AdventCalendarの季節にかこつけてOCamlの勉強をしてみるだけです。こんな記事がn個もブログにはびこるのもどうかと思うけど公開しないのも勿体無いみたいな気持ちの折衷案としてこの記事に全部収める。

思い立って OCaml に触れてみることにしました。当方C言語からプログラミングの世界に入って未だに初心者をやっています。

この Advent Calendar は次のような環境でお送りします。

~> cat /etc/os-release
NAME="Ubuntu"
VERSION="17.10 (Artful Aardvark)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 17.10"
VERSION_ID="17.10"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=artful
UBUNTU_CODENAME=artful

今日の目標

今日は OCaml を取り巻くエコシステムを構築していきたいです。最後にHello worldを書いて締めます。ちなみにいまのところ7日目まで書きましたが、すでにエディタ周りの環境はVimになりました……。

opam のインストール

opamOCaml のパッケージマネージャであり、Environment Managerっぽいです。Ubuntu 17.10 では apt を使って入れられるので楽そうです。

~> sudo apt install opam -y

よくわからないけど、 opam init しておいたほうが良さそうな風潮があるので、 opam init します。

~> opam init

すると $HOME の下に .opam ディレクトリが作成されそうです。 Do you want OPAM to modify ~/.zshrc and ~/.ocamlinit? と訪ねてくるので快く y と答えておきます(n と答えても opam config setup -a で同様のことができそうというきもちになりました)。

eval `opam config env` してね。と言われるのでしておきます。

環境の準備

opam が依存しているので ocaml コンパイラは既にインストールされていそうですが、なんか分けといたほうがためになりそうなので分けます。これには opam switch を使いそうなんで opam switch list で利用可能なコンパイラの一覧を見て後、一番新しそうなのを入れます。

ここで普通にバージョン番号を指定するとそのバージョン番号の環境が作成されると思ってるんですが、 名前を指定して環境をつくることも当然できるっぽいです。

ここでは adc という名前で環境を作ることにしました。

~> opam switch install adc --alias-of 4.06.0 

まあこれはそれなりに時間がかかります。

終わると 例によって例のごとく eval `opam config env` してくれといわれるのですると、環境は adc になってます。

utop の導入

今でも ocaml と打てばREPLが起動しますが、もう少しリッチな utop を導入しておきます。 opam install utop でインストールできて慣習が生きてます。

~> opam install utop

emacsのインストール

血迷ってしまったので emacs の環境をつくっていきます。まずはインストールからやね。 apt install emacs -y です。

~> sudo apt install emacs -y

tuaregの導入

tuareg は多分、emacsocaml-mode をもたらしてくれる偉い子だとおもいます。私は「つあれぐ」って読んでますけど読み方わからん。インストールは opam を使ってやっていきます。 melpa と marmalade にもあるけど古いかもねって github が言ってます。

~> opam install tuareg

インストール後のメッセージに、「これこれこうしてくれ」という指示がありますが無視します。なぜなら、 opam user-setup install という必殺技があるからです。 これをやると、初回は必要なパッケージのインストールが行われ、後にエディタの設定ファイルをいじってくれます。

merlinの導入

merlin (私は親しみを込めて「めるりん」と呼ぶことにしました)は、 tuareg と一緒に使って、「補完」「型の表示」をしてくれそうな感じがします。

これも opam で入れましょう。

~> opam install merlin

ついでに opam user-setup install もしておきます。

company-mode の導入のための el-get の導入

company-modeemacs の補完ライブラリです。 auto-complete というのもあるみたいですが時勢がこっちに傾いているっぽさがある。

company-mode は ELPA で提供されていて、 emacs では M-xpackage-install <RET> company-mode <RET> とかでインストールできそうなのですが、ちょっとこれはあんまり好きくない感じなので、 ついでということでemacsのパッケージマネージャの一つ el-get を使ってみることにしました。 el-get を選んだ理由はなんとなくです。

というわけで、 el-get をインストールしていきます。

el-get のインストールをするために emacs の設定ファイルであるところの ~/.emacs を編集していきます。user-setup が作ってくれた ~/.emacs が転がっていたので、これをいじります。いろいろ既に書いてあって触りたくない感じなので、これの上の方に必要な設定を書いていきます。

とはいえ、 el-get の README から引っ張ってくるだけなのでかんたんです。

次の行を足しておきました。

(add-to-list 'load-path "~/.emacs.d/el-get/el-get")

(unless (require 'el-get nil 'noerror)
  (with-current-buffer
      (url-retrieve-synchronously
       "https://raw.githubusercontent.com/dimitri/el-get/master/el-get-install.el")
    (goto-char (point-max))
    (eval-print-last-sexp)))

(add-to-list 'el-get-recipe-path "~/.emacs.d/el-get-user/recipes")
(el-get 'sync)

この状態で、 C-x C-s で保存して、 M-x eval-buffer としてあげると、勝手に el-get をインストールしてくれます。そんなきがする。

これが終わったら↑の下にもう一行追加します。 company-mode のインストールです。

(el-get-bundle company-mode/company-mode)

やはりこれも保存して、 eval-buffer します。さらに、もう一文、company-modeを自動で有効化する設定を追加します。

(add-hook 'after-init-hook 'global-company-mode)

で、 実は company-mode を入れたのは merlin のためなので、 merlin と連携させます。 ここ にあったコードを ~/.emacs に足します。

; Make company aware of merlin
(with-eval-after-load 'company
 (add-to-list 'company-backends 'merlin-company-backend))
; Enable company on merlin managed buffers
; (add-hook 'merlin-mode-hook 'company-mode)
; Or enable it globally:
; (add-hook 'after-init-hook 'global-company-mode)

hook は既に行っているのでコメントアウトしておきました。

これで多分設定が完了しました。

Hello worldを書く

C-x f して適当な hello.ml ファイルを開きます。モード表示が (Tuareg utop Merlin company) になっているといい感じです。

はじめは Printf.printfHello Worldしていたのですが、 kosen14s の面々に、「標準は print_string じゃないかなあ」という指摘を受けたので、 print_string で書きます。

let () =
  print_string "Hello World\n"

print_string だと恩恵が感じられなくて寂しいですが、 Printf. まで打つと、補完の候補が表示されると思います。うれしいね。

実行する

実は main を実行するようなコマンドはないので、二行目を選択して、 C-x C-r などしましょう。これは utop-eval-region のショートカットで、 utop repl が開いて、選択した部分を実行して結果を返してくれます。

utop[0]>   print_string "Hello World\n";;
Hello World
- : unit = ()

Hello World ができました。

やる気次第で、 2日目につづきます。

OCaml入門 ひとりAdventCalendar 2日目

昨日が楽しかったので今日もやります。環境構築って一番楽しい時間だと思うのね。

今日の目標

FizzBuzz を書きたい

自然な感情が生まれました。

試しにFizzBuzzを書いてみると、こうなりそうです。

let fizzbuzz = function
  |x when x mod 15 == 0 -> "FizzBuzz"
  |x when x mod 3 == 0 -> "Fizz"
  |x when x mod 5 == 0 -> "Buzz"
  |x -> string_of_int x

let main =
  for i=1 to 100 do
    print_string ((fizzbuzz i) ^ "\n")
  done

雰囲気で読んでください。 ^ は文字列の結合を行う(たぶん)関数です。 Pervasives といういつでも読み込まれている標準のモジュールで定義されてたはず。ぺるばしぶす。

けどまあ、forを使ってるのがOCamlっぽくない気がするので、forみたいな感じでリストを返す関数 range を作ってみます。

let fizzbuzz = function
  |x when x mod 15 == 0 -> "FizzBuzz"
  |x when x mod 3 == 0 -> "Fizz"
  |x when x mod 5 == 0 -> "Buzz"
  |x -> string_of_int x

let rec range a b =
  if a > b then []
  else a :: range (a+1) b


let main =
  List.iter (fun x -> (print_string ((fizzbuzz x)^"\n"))) (range 1 100)

書きましたけど、 List.iter に渡してる関数が汚いですね……。

こういうときは関数合成を使ってあげるのがいい気がするんですが、OCamlって関数合成の演算子ないんですね。なんでだ。

ということで定義します。F# にならって、記号 >> を使います。

let (>>) f g x = g (f x)

中置記号は () でくくって let してあげて、これを使うとFizzBuzzがもうちょっときれいになるはず。

let fizzbuzz = function
  |x when x mod 15 == 0 -> "FizzBuzz"
  |x when x mod 3 == 0 -> "Fizz"
  |x when x mod 5 == 0 -> "Buzz"
  |x -> string_of_int x

let rec range a b =
  if a > b then []
  else a :: range (a+1) b

let (>>) f g x =
  g (f x)

let print_string_line s = print_string (s ^ "\n")

let main =
  List.iter (fizzbuzz >> print_string_line) (range 1 100)

print_string して改行もする print_string_line つくっちゃった。まあいいよね。 (fizzbuzz >> (fun x -> x ^ "\n") >> print_string) って書いても同じことになりそう。あとで見たら、 print_endline という関数がありましたね……。

ちなみに >> が三引数取りそうなのに二引数でもエラーになってないのは、カリー化というやつです。僕はなんとなく知ってた。

OCaml入門 ひとりAdventCalendar 3日目

飽きてきた。ちなみに 7日目までしかやってないので。

今日の目標

cat を書きたい。にゃーん。

出来心で man cat したら普通にオプションとかあってびっくりしてる、ます。

オプションの実装はちょっと面倒なのでやりません。というか cat の実装くらい知っててもいいかもなぁという気持ちが強くなってきたぞ。

適当に読んだ一行を吐く

こういうのは調べるだけでできます。

let main =
  print_endline @@ read_line()

emacs 上の utop で入力する方法がわからなかったので ocaml cat.ml とかします。 @@ は 開き括弧のようなものです。 read_line は引数を持たないので、呼び出すときには () をつけるらしいです。

いつまでも読んで吐く

let main =
  while true do
    print_endline @@ read_line()
  done

なんだか当然のように while があって、これで無限に読んでは吐けます。ただ、 EOF に達すると End_of_file 例外を吐きます。

ので、例外処理をする

let main =
  try
    while true do
      print_endline @@ read_line()
    done
  with End_of_file -> ()

try-with で例外を捕まえられそうです。捕まえたあとは何もしないので ()

ファイルを読む

次はファイルを読みたいので読みます。ファイルを開くのは open_in で、これは当然のように読み込み用途でファイルを開きます。

これを let で束縛して、 input_line やなんかの引数として使えそうです。例えばこう。

let main =
  let chan = open_in "cat.ml" in
  try
    while true do
      print_endline @@ (input_line chan)
    done
  with End_of_file -> ()

関数にまとめる

あんまりきれいじゃない気がするけど echo_all とか適当な名前をつけてまとめます。

let echo_all chan =
  try
    while true do
      print_endline @@ (input_line chan)
    done
  with End_of_file -> ()

let main =
  let chan = open_in "cat.ml" in
  echo_all chan

こうすると echo_allstdinを渡すのもできるようになってくれる。

引数をとってcatする

コマンドライン引数は Sys.argv で取れそう。これを for で回します。 iter できれいにかけそうなの思いつかなかったので。

let echo_all chan =
  try
    while true do
      print_endline @@ (input_line chan)
    done
  with End_of_file -> ()

let main =
  for i = 1 to Array.length Sys.argv - 1 do
    let chan = open_in Sys.argv.(i) in
    echo_all chan;
    close_in chan
  done

二文以上書くときは ; でつなげるとか普通は知らないですよね。怒られた。配列のランダムアクセスが .(n) とか知らないですよね。困った。

完成させる

はい。

let echo_all chan =
  try
    while true do
      print_endline @@ (input_line chan)
    done
  with End_of_file -> ()

let main =
  if Array.length Sys.argv == 1
  then
    echo_all stdin
  else
    for i = 1 to Array.length Sys.argv - 1 do
      if Sys.argv.(i) = "-" then
        echo_all stdin
      else
        let chan = open_in Sys.argv.(i) in
        echo_all chan;
        close_in chan
    done

こう、

let chan = match Sys.argv.(i) with
    |"-" -> stdin
    |f -> open_in f

みたいに書いて echo_all chan 一個にまとめたかったんですが、 match 式と let in をうまくまとめる方法がわからなかったのと stdinclose_in すると怒られるのとで諦めました。

実行可能な形式にする

ocamlcバイトコードへのコンパイラで、 ocamlopt はネイティブコードへのコンパイラっぽいです。

ocamlopt cat.ml -o cat とかすると、 cat が生成されて、いい感じに動いてくれそうですね。

OCaml入門 ひとりAdventCalendar 4日目

惰性がもーちょい続いてほしい。

今日はライフゲームを実装した

やりたくなったからやったけど、時間の都合でちまちま記事を書いてないので、成果物だけ貼ることになる。

type lifegame_cell = Live | Dead
type lifegame_type = lifegame_cell array array

let lifegame_construct size =
    Array.make_matrix size size Dead

let random_choice xs =
    let len = Array.length xs in
    xs.(Random.int len)

let lifegame_initialize size =
    let field = lifegame_construct size in
    begin
        for y=0 to size-1 do
            for x=0 to size-1 do
                field.(y).(x) <- random_choice [|Live; Dead|]
            done
        done;
        field
    end

let lifegame_cell_string = function
    | Dead -> "□"
    | Live -> "■"

let lifegame_at field x y =
    let len = Array.length field in
    field.((y+len) mod len).((x + len) mod len)


let lifegame_cell_update field x y =
    let envcount = ref 0 in
    begin
        for i = -1 to 1 do
            for j = -1 to 1 do
                if not (i == 0 && j == 0) then
                    if lifegame_at field (y+i) (x+j) == Live then
                        envcount := !envcount + 1
            done
        done;
        match (lifegame_at field x y, !envcount) with
        |(Dead, 3) | (Live, 2) | (Live, 3) -> Live
        |_ -> Dead
    end

let lifegame_update field =
    let size = Array.length field in
    let new_field = lifegame_construct size in
    begin
        for y=0 to size-1 do
            for x=0 to size-1 do
                new_field.(y).(x) <- lifegame_cell_update field x y
            done
        done;
        new_field
    end


let lifegame_print field =
    Array.iter (fun row ->
        Array.map lifegame_cell_string row
        |> Array.to_list
        |> String.concat ""
        |> print_endline) field

let main =
    begin
        Random.self_init();
        let turn = ref 1 in
        let field = ref (lifegame_initialize 10) in
        while true do
            print_string "\027c";
            lifegame_print !field;
            print_endline ("turn:"^string_of_int !turn);
            turn := !turn + 1;
            field := lifegame_update !field;
            Unix.sleep 1;
        done
    end

いろいろ新しく触った部分があるので紹介していく、ます。

|>

F# で大人気のパイプライン演算子です。 f a って言う関数適用を a |> f って書けるようにする演算子で、実装は let (|>) a f = f a とかだと思う。中置記号強いね。

これがあると流れるプログラムが書ける。UFCSと似たりよったり。

type

type を使うと独自型を定義できる。特定のデータ構造に依るプログラムを書くときは定義したほうがバグを生みにくそうだし読みやすそう。 0, 1って書くよりも Live, Dead って書いたほうがわかるという enum みたいな type と、 type lifegame_type = lifegame_cell array array っていう typedef みたいな type とがあった。

ref

OCamlは純粋じゃないから、 mutable な変数を作れて、それを作るときには let x = ref v ってする。値を参照するときは !x で、何かを代入するときは := を使ってあげる。

begin ... end

(...) の構文糖衣。こっちのほうがみためがよさそうなので使ってます。基本的にOcamlLispというか一つの式しかかけないので、() でくくって ; でつなげて、無理くり一つの式にしてあげる。 Lisp 系の do とか begin と同じだね。ということは do ... done は暗黙の begin のような気がしてきた。

<-

Array は実は mutable なデータ構造で、任意のデータを書き換えられるんだけど xs.set i v ってする代わりに xs.(i) <- v って書いてもいいそうです。

print_string "\027c"

画面のクリア。 printf "\x1bc" みたいなことをします。OCamlでは '\xxx で任意の文字コードを持つ文字を書ける(必ず三桁。そして10進)。


だいたいこんな感じです。スリープのために Unix.sleep を使ったから、実行とかコンパイルのときは ocaml unix.cma lifegame.ml ってする。順番を遵守しないといけない……。

ところで lifegame_cell_update の match 式の部分、最初は

match (lifegame_at field x y, !envcount) with
|(a,b) when a == Dead && b == 3 -> Live
|(a,b) when a == Live && (b == 2 || b == 3) -> Live
|(a,b) when a == Live && b <= 1 -> Dead
|(a,b) when a == Live && b >= 4 -> Dead
|_ -> Dead

って超絶まどろっこしく書いてたけど、 shinkwhek くんのおかげで今のように短く読みやすくなりました。感謝 :pray:

longとboolのoverload

こういうはまり方をした。

https://run.dlang.io/is/LpLTja

longを引数に取る f が呼ばれると思ってたんだけど、その実 bool のほうが呼ばれてた。0と1に限っては、こういうことになるらしい。 回避策としては 0L、1Lと書いてlongを明示するか、 int も overload して、 long を呼ぶか。

高専セキュリティコンテスト #SCKOSEN で優勝した

奈良高専は insecure というチームで、 theoldmoon0602、 thrust2799、 kyumina、 miwpayou の四人で、高専セキュリティコンテストに参加しました。現地行きたかったけど申し込み失敗しました。

あと師匠がいねぇ。

これは二日間(実質24時間程度)の Jeopardy + King of the Hill なCTFで、 Jeopardy が 22問くらい、 KoH が 2問ありました。 KoH では 30点が 10分ごとに与えられます。

私チーム insecure はチームワークを遺憾なく発揮して、 3930 点で 1位(35チーム中)でした。私はそのうち、 1400 + 630 点を入れましたので、解いたものについて 簡単な Write up をします。

f:id:Furutsuki:20171022123605p:plain

jeopardy Binary[100] フラグを答えろ

バイナリが降ってくるので strings する

jeopardy Binary[500] OreNoFS

途中で追加された問題。31M くらいのバイナリが渡されて、これが独自FSらしい。めっちゃよなべしたけど解けなかった。最終的には解いた。

バイナリは GPT でパーティショニングされているということ。1クラスタ4096byte 単位で管理されていること。AllocationTableのサイズ、DirectoryEntry 構造体が示されていた。

最初は binwalk などしていたが、 何かしらの zip ファイルが存在していて、 flag.png_MAXOSX/.flag.png が入ってることしかわからず、 unzip はできなかった。

二日目になってやっと参考になるサイトを見つけて ( http://memes.sakura.ne.jp/memes/?page_id=2303 ) 、AllocationTable がどのような存在かを理解したので、ごちゃっとパーサを書いたらunzippable な zip を取り出せました。

方針としては、

  1. DirectoryEntry をみつける
  2. これは ほしいzipのEntryだって知ってるので、 offset を見て、バイナリを読み、AllocationTable から次のクラスタのオフセットを手に入れる
  3. これ以上なくなったらそれを吐く

です。

import struct
import os
import codecs
import sys
from io import SEEK_CUR,SEEK_SET

LBA_SIZE = 512


f = open("raw.dmg", "rb")

f.seek(512, SEEK_CUR) # skip mbr
f.seek(512, SEEK_CUR) # gpt header (primary)

f.seek(32, SEEK_CUR) # partition entry

first_sector = struct.unpack('<Q', f.read(8))[0]
last_sector = struct.unpack('<Q', f.read(8))[0]
f.seek(8, SEEK_CUR)

partition_name = f.read(72)
partition_name_str = codecs.decode(partition_name, encoding='utf_16_le')
if len(partition_name_str)>0:
    print("------------")
    print("first sector: {}".format(first_sector))
    print("last sector:  {}".format(last_sector))
    print("name: {}".format(partition_name_str))
    print()

f.seek( first_sector*LBA_SIZE, SEEK_SET)



AT = f.tell()
print("ALLOCATION TABLE IS {:0X}".format(AT))

allocationTable = []
for _ in range(8192):
    v = f.read(2)
    v = struct.unpack("<H", v)[0]
    allocationTable.append(v)

print("----------------")
print("Allocation Table");
for i, v in enumerate(allocationTable):
    if v == 0:
        continue
    # print("[{0}]   {1:0X}( {1} )".format(i, v));
    pass

DIRENTS = f.tell()
print("DIRECTORY ENTRIES AT: {:0X}".format(DIRENTS))

binvalue = []

while f.tell() < (last_sector+1)*LBA_SIZE:
# read directory entry
    tell = f.tell()
    magic = f.read(1)
    if magic != b'\x0f':
        f.seek(31, SEEK_CUR)
        continue

    name = f.read(8)
    ext = f.read(3)
    size = struct.unpack("<I", f.read(4))[0]
    offset = struct.unpack("<H", f.read(2))[0]
    attr = f.read(12)
    reserved = f.read(2)


    print("-----")
    print("@ {:0X}".format(tell))
    print("magic: {}".format(magic))
    print("name: {}".format(name))
    print("ext: {}".format(ext))
    print("size: {0}({0:0X})".format(size))
    print("offset: {0}({0:0X})".format(offset))
    print("attr: {}".format(attr))
    print("resv: {}".format(reserved))

    nextoffset = offset
    while nextoffset != 0xffff:
        f.seek(0x1000 * nextoffset + AT, SEEK_SET)
        b = f.read(0x1000)
        binvalue.append(b)

        print("atvalue: ", end="")
        print(allocationTable[nextoffset])
        nextoffset = allocationTable[nextoffset]
        if nextoffset >= len(allocationTable):
            break

        
    print()

    break
o = open("kore", "wb")
for b in binvalue:
    o.write(b)

きったねぇ

jeopardy Crypto[100] かんたんな符号化2

これはほとんど thrust2799 が解いた。

わけのわからん文字列が与えられるけど、 base64 -d すると16進値っぽい数字列になる。これを decode('hex') してやると KP から始まる文字列になるので、エンディアンをひっくり返してあげて、保存する。 file をかけると Word 2007 と出るので、 libre Office Writer で開くと 縦にフラグが書いてある。コピーして適当なファイルに保存して cat | tr -d '\n' した。

jeopardy Network[100] 寝坊気味のコンピュータ

pcap が降ってくるので strings してうまいことつなぎ合わせる

jeopardy Network[100] ログインしたいんだ!

pcap が降ってくるので strings してBase64っぽい文字列を見かけたら base64 -d する

jeopardy Web[100] ログインせよ

ログインフォームがあるのでユーザ名 admin、パスワード 'or'a'='a と入力する

jeopardy Web[100] 灯台下暗し

ログインフォームと、ログインのロジックをPHPで書いたものが表示されるので ざっと眺めて login.db みたいな名前がひっかかる。アクセスすると sqlite dbファイルが降ってくるので apt install sqlite3 して sqlite3 login.db する。 .schema で users というテーブルだけがあることがわかるので select * from usersすると admin のパスワードがフラグになっている。

jeopardy Web[200] Web1

難読化された javascript がある。適当にBeautifyして処理を一つずつ追ってやる。すると DOM を書き換えるしょりと、別のことをしている処理が存在することがわかる。 別のことをしている処理が怪しいので単体で実行するように書き換え、余計な分岐をはずしてやるとフラグが手に入る。

jeopardy Web[200] Web2

いわゆる Javascript puzzle。解き方が何処かに乗ってないかと思って調べたら burningCTF でまったく同じ問題が出題されていることがわかったのでWriteUpをみながら真似をした。

KoH 通信を解析しろ

pcap ファイルが降ってくる。サイズはそこそこ。みると、幾つかの ssh ログインとたくさんの TCP SYN がある。ssh はよくわからないIP同士で通信していることが多い。TCP SYNは 10.201.1.101から10.201.1.102 へのもの。最初はポートスキャンかなと思っていた。

ちょっとだけある HTTP パケットにフラグが書いてあり、さらにバイナリが存在することが示唆される。フラグに port knocking とあるので TCP SYN のうちACKが帰ってきたものを真似て port knocking してやると a.out が手に入る(port knocking しなくてもいいという説がある→作問者がいるって言ってた)。

フラグは thrust2799 が手に入れ、私が port knocking してみれば、と言った。 thrust2799 はバイナリ読めないと開始前から何度も言っていたので、私が解析することにしてファイルを貰った。

radare2 で見ていたがわかりにくかったので Bokken を入れてる miwpayou にも解析してもらった。どうやら a.out は 8081番で待ち受ける fork型のサーバで、fork した先でstrncmp や strlen を使い、一部で popen していることがわかった。

gdb が存在することを思い出したので set follow-fork-mode child して b do_workして、fork後の処理を追う。 strncmp では recv した文字列を SCK と比較していることがわかった。さらに次では recvした文字列+3を popenに突っ込んでいることがわかった。

そこで試しに SCKwhoami と送ってみたところ ubuntu というレスポンスが来た。 SCKls → SCKcat README → SCKecho insecure >> flag してKoHになった。

余談だけど SCKkillall a.out したらサーバからのれすぽんすがなくなった。これを運営に報告したらしばらく経って復活した。復活して SCKwhoami したら root だった。


SECCON まわりのコンテストって教育的な問題が多いですよね。高専生のレベルが上ってもっとヤバゲな問題も出てきてほしいです。

それにしても楽しいコンテストでした。来年も参加したいものです。

あと #procon28 で悔しい思いをしたぶんこっちで晴らせてよかった。

最後ですけど

  運営の皆さんお疲れ様です。ありがとうございました。

  弊学のお方、休日出勤させてしまい申し訳ありません。優勝したので許して。

  ちーむめいとの人々。さいこう

Wayland は ~/.xprofile を読んでくれない

~/.profile~/.xinitrc もよまない。

おいおい、じゃあ環境変数どこに書いてやればいいんだよ。 ~/.bashrc に書いてもいい。これが一番楽でしょ。

あとは /etc/environment 。ここは読まれる。

最近 Ubuntu 17.10 にしたらデフォルトが Gnome3 on Wayland で fcitx が chrome 上でうまく動かなかったので、 /etc/environment

XMODIFIERS="@im=fcitx"
GTK_IM_MODULE=fcitx
QT_IM_MODULE=fcitx

って書いたらうまく行った。

Fcitx - ArchWiki

に書いてあったよ。普通に ~/.xprofile 読んでくれてもいいと思うし、個人のスタートアップスクリプトなんでもいいから一個読めるようにしといてくれりゃあいいのに

#procon28 にいってきた 

procon28 に行ってきた

この記事は、高専プロコンが嫌いな私が、いろいろ難癖をつける記事です。

「俺たちはプロコンに行ったらまず真っ先にバグを確認する」

本年で一番推してるラノベであるところの、「俺たちは異世界に行ったらまず真っ先に物理法則を確認する」へのリスペクトを最大限に発揮したチーム名で、全国高等専門学校プログラミングコンテストの競技部門に参加してきました。チームメイトはスーパーコンで一緒に組んでたクラスメイトと、一つ下のかわいい男の子です。

結果を端的にお伝えすると、「準決勝12位(くらい)で敗退」でした。

おいおまえパズルやめろ

昨年の競技部門の悪夢を憶えていますか。俺は憶えてるぞ。なんだあの人力は。なーにがプログラミングコンテストじゃ。(きくところによると高専プロコンは過去にもいろいろな悪夢を産んでるらしい。サイコロとかね)。

昨年、私たち奈良高専は「複合機」を引っさげて競技部門に参加し、人力で予選を突破したのち(バッテリーがうまく複合機につながらなかったんよ。焦った)、準決勝で適当な解を印刷、一瞬だけ場を沸かせた後退場しました。あの大会では、n-1解法(ガチャガチャとやってピースが一個以外すべて枠内に収まるような配置を探す)が横行し、出場者、聴衆、運営に対して、大きな傷跡を残していきました。

……なんで翌年、おんなじようなルールの競技をやらせるんだよ。

やりたくねえんだよ俺はあれが嫌いなんだ。パズルは解きたくねぇんだよ。

自分たちが解けない程度の雑魚であることを棚に上げてるのはわかってますが……。

とにかく、今年のルール発表を見た瞬間に私に押し寄せた絶望がおわかりか。またパズルだよ……。いやだ……。

問題作成側もいろいろ配慮はしてくれて、「すべての頂点がグリッド上に配置される」「ピースの形状はQRコードで配布される」という、コンピュータで解きやすい問題になっていましたが、だけど僕はこのパズルを解くための何を作る気持ちもうまれなかった。

別に競技で完全回答できたから楽しかったとか思ってないんだからね。成功体験(?)に負けたりしないんだから。

競技の成績

予行はよく見知った問題だし楽勝でしょw! と思ってましたが、実際にパズルを組み立てる練習をあんまりしていなかったのでいろいろ苦戦して、あと、形状情報のQRコードが分かれている想定を指定なかったのと、x座標y座標を逆に読んでいたのとでぎりぎりに解き終わりました。このときは入力をVimでがががと編集して使いましたけど、席に戻ってから急いでまとめて読めるように修正をかけました。

予選はどうだったんだっけ……。形状情報をとりあえず読んでみたけど、ソルバが行き詰まって、しかたなく配置情報を開けてみたらソルバが解いたところがあっているということがわかった気がします。それでもいっこ開けて、ソルバに正しい情報を入力してたけどばぐばぐし始めたので諦めて配置情報を全部使って人力したら、予選通過ぎりぎりだったはず。八位とかかな。

この日の夜にこのままじゃ負けると思ってソルバの修正をやろうとしてた(ピースの結合条件を緩めてたはず)んだけど眠気に負けて二時間くらいねたはず。

準決勝ではとにかく形状情報と配置情報1を読んだ。ソルバでやってみようかと思ったら反応しなくて、よく見たらNullPointerExceptionしてた。なんだおいおい、と思ってちょっと詳しく見たら、昨晩変更した箇所から未確認のバグが湧いて出てきていた。何が「俺たちはプロコンに行ったらまず真っ先にバグを確認する」だよ。ソルバがUndoできなくなったのでやむなく配置情報を3まであけて人力に方針変更。チームメイトが人力してる間に、わたしはソルバを何度も起動し直していろいろなパターンを探していた。

いくつかソルバが新情報をもたらしてくれたのでなんとかACして、ふえぇって言いながらソルバを適当に回してたら、形状情報だけで解答がでた。おいおい読めねぇよ。このなんかぐちゃあってなるのは既知の修正できてないバグで、回避策としてUndoがあったんだけど、Undoを封じられた結果コレですよ。

f:id:Furutsuki:20171009174617p:plain

チーム紹介動画

運営としてはこれは成功だったんですか。私ら一個も見れてなくてクソつまんないんだけど。ところで弊チームの紹介動画見てくれ。ウチの動画リーダーまじすげぇから。

学生交流会

去年も思ったけど出場するチーム毎でチーム組んでも交流につながらねぇよなぁ。上位陣にはやたら豪華な商品があることを学んでいたのでがんばりました。一位だよ。バグってなんですか。俺も知らねぇよ。確認なんかしません。2万円もらったけどバグがでました。

競技部門公募

その場にいなかったのでTwitterの聞きかじりですが、競技部門の内容、次回は公募するんですか? 個人的にはがっかりですね、運営頑張ってくれよ。というかこれ、解法を考えてから問題を投稿すればいいのでは(?)

次回

来年は順調にいくと五年生なので(最近学科主任に楯突いてるのでちょっと進級不安になってきた)、プロコンしてる余裕なさそうです。というか私はあんまり高専プロコン好きじゃないんだ。俺が勝てないから。あと競技が不遇なのと審査員が匿名なのが嫌。

でも、いざ身を引くとなると、少しさみしいものですね。高専プロコン、3位までに入って泊をつけるくらいしか参加意義が無いと思ってたのに。あとさくらのVPSのクーポンな。さくらさんどうしたんですか、高専プロコンは魅力的ではなくなってしまいましたか。

なんか書き忘れてたら書き足します。コレ書いてる途中にめっちゃ喉痛くなってきたし明日は寝こむ。やっと高専プロコンが終わって自分のやりたいことしたりアニメ見たりできるんだよな。やったー。