わらばんし仄聞記

南の国で引きこもってるWeb屋さん

OSvのCLI周辺コードをみてみる

※この記事は OSv Advent Calendar 2014 9日目の記事です

最近、ちょこちょことOSvを触らせてもらってるので、OS関連周りにはあまり明るくないながらも書かせてもらう事にしました。 こっち方面を専門としている訳ではない為、間違っている箇所などありましたらご指摘ください。

さて、CLI周りについてなのですが、前回のOSvもくもく会#4の時にこの辺りの構造について見ていたのでそこら辺触れてみようかと。adventcalendar始まってみたら @syuu1228 さんとかぶり気味だったので変えようかと思ったけど、そんなにホイホイとネタが出てくるわけでもないので突き進みます。

OSvのCLIについて

元々、OSvがjavaアプリを動かすという想定で始まっている事もあり、しばらくはCLIのシェルとしてCRaSHってのを使ってました。それをリプレースする物として、Luaで書かれたCLIが作られたそうです。後者については既に三日目のcalendarで軽く触れられてますね。

このCLI周りについて、チラッと見てみた事を徒然と書いていきます。 そうそう、OSvは凄い勢いで機能追加やらされてるので、今回書いている事もすぐに変わってしまうかもしれません。この記事は v0.16-12-gbc3b9f6 を元に書いています。

CLIのコードを見てみる

最初の一歩

場所については先ほど触れた三日目の記事で紹介されている通り、こちらにあります。pathを見ての通り、このCLIもmoduleの1つとして実装されていますね。ディレクトリ内はこんな感じ

$ ls -1
cli.c
cli.lua
cli.so
commands
doc
lib
Makefile
module.py
rpmbuild
test

module.pyはこのmoduleの起動処理辺りが書いてある様で、内容は下記の通り

from osv.modules.api import *
from osv.modules.filemap import FileMap
from osv.modules import api

require('lua')
require('ncurses')
require('libedit')
require_running('httpserver')

usr_files = FileMap()
usr_files.add('${OSV_BASE}/modules/cli').to('/cli') \
        .include('cli.so') \
        .include('cli.lua') \
        .include('lib/**') \
        .include('commands/**')

full = api.run('/cli/cli.so')
default = full

cli.soをrunしてますね。この段階で、cli.cがcliコマンド(これも例の三日目の記事で使ってましたね)の実装となり、これがcli.luaに処理を渡してcommandディレクトリ配下に記述のあるluaで書かれたコマンドを実行してるのかなーって予想が立ちますね。

cli.c

そんでは、cli.cを見てみましょうか。全部トレースしていくとこの記事だけでは終わらなそうなので、要所だけ。 main()内では大筋として、テストコマンド、単一コマンド実行、シェルの起動に分かれている様です。

int main (int argc, char* argv[]) {
    ...
    if (test_command != NULL) {
        ...
        lua_getglobal(L, "cli_command_test");
        ...
    else if (optind < argc) {
        /* If we have more arguments, the user is running a single command */
        ...
        lua_getglobal(L, "cli_command_single");
        ...
    } else {
        /* Start a shell */
        ...
           lua_getglobal(L, "cli_command");
        ...
    }
    ...
}

こんな感じで各々のパターンに応じてluaでglobalとして作られているメソッドを読んでいる様です。

cli.lua

さて、簡単な所から見てみる事にして、cli_command_singleから見てみましょう。

function cli_command_single(args, optind)  local t = {}
  for i = optind, #args do
    table.insert(t, args[i])
  end
  cli_command(t)
end

用はアレですね。例えば

 $ cli -a http://192.168.122.72:8000 ls -lh

とかって記述した場合、ls -lh の部分だけ抜き取ってcli_commandに投げる感じですかね。 cli_command_testは紙面の都合で飛ばさせてもらい、cli_commandに進んでみます。要所をピックアップすると

function cli_command(args)
  ...
  if #arguments > 0 then
    command = arguments[1]
    ...
    filename = command_filename(command)
    if file_exists(filename) then
      local cmd = dofile(filename)
      local cmd_run = true

      ...
      if cmd_run then
        local status, err = pcall(function() cmd.main(arguments) end)
        if not status then
          print_lua_error(command, err)
        end
      end
    else
      print_cmd_err(command, "command not found")
    end
  end
end

ざっと読むと、ここに渡された引数の最初の文字列と一致するファイルを探し、そのファイルに実装されているものがコマンドの実態として扱われ、実行されるということですね。ちなみにcommand_filename()はlib/util.lua内にあり、

function command_filename(name)
  return string.format('%s/%s.lua', context.commands_path, name)
end

となっています。commands_pathは巡り巡ってcli.c内にて #define CLI_COMMANDS_PATH "/cli/commands" として定義されていますね。以上より、CLI実行時、各コマンドの処理は/cli/commands内の当該luaファイルが実行されている事まで追えました。lsコマンドなら/cli/commands/ls.luaですね。

コマンドの実装

ということで、コマンドを実装してやるには先述のディレクトリへluaプログラムを置いてやればいいんだろうという判断に至ったわけですが、これ、散々述べている様にLuaで書かれてるんですよ・・・。なんか足りないコマンドを実装してやろうとは思ったんですが、Luaはneovimで使うぞー!って聞いて本買ってみた程度なので、実装能力はほぼ絶無な為、今のところ断念中。

これはOSv全体にわたって言える事なんですが、使ってる言語多すぎでは・・・。ぱっと見た限りでもC++11、python、go、luaが飛び交っております。でもLuaはちょっと興味あるので、これを機に弄ってみるとおもしろいんじゃないかとは目論んでます。

というわけで

何はともあれ、現在private betaが行われているOSv。まだまだとりつく島もないような大規模ではないので、興味あったら読んでみる(できれば何か実装してみたりする)と面白いんじゃないかと思います。パッチの送り方等については四日目の記事を参照ください。