SambaをDocker上で起動しようとして、smbd
コマンドを実行してみると、上手くいかない......プロセスが終わってしまい、コンテナを抜けてしまう。
なぜだろうと思って調べた記録
参考に、Ubuntuのsambaのserviceファイルを見てみると以下のような記述が有った。
ExecStart=/usr/sbin/smbd --foreground --no-process-group $SMBDOPTIONS
sambaの公式ドキュメントを、公式ドキュメントで見てみる(それにしてもsambaの公式サイトは、古き良き時代のデザインなので色使いが目に痛いし、フォントが読みづらい......)。
-F|--foreground If specified, this parameter causes the main smbd process to not daemonize, i.e. double-fork and disassociate with the terminal. Child processes are still created as normal to service each connection request, but the main process does not exit. This operation mode is suitable for running smbd under process supervisors such as supervise and svscan from Daniel J. Bernstein's daemontools package, or the AIX process monitor. --no-process-group Do not create a new process group for smbd.
foreground
オプションはデーモン化しないためのオプション、--no-process-group
はプロセスグループを作らないためのオプションとなっている。
辿っていくと、この二つのオプションは、sambaのソースコードではbecome_daemon.c
という関数の引数に使われていることがわかった。
void become_daemon(bool do_fork, bool no_session, bool log_stdout)
foreground
オプションが設定されるとdo_fork
にfalse
が設定され、no-process-group
オプションが設定されるとno_session
にtrue
が設定される。
どうでもいいけど、Becoming a daemon
とメッセージを出しておいて、オプション引数の内容によってデーモン化を制御するって、デバッグメッセージと矛盾しているような......
if (cmdline_daemon_cfg->daemon) { DBG_NOTICE("Becoming a daemon.\n"); become_daemon(cmdline_daemon_cfg->fork, cmdline_daemon_cfg->no_process_group, false); } else if (!cmdline_daemon_cfg->interactive) { daemon_status("samba", "Starting process..."); }
あと、interactive
モードというのが有るので、最初はこちらを使えば良いのかと思ったら......
-i|--interactive If this parameter is specified it causes the server to run "interactively", not as a daemon, even if the server is executed on the command line of a shell. Setting this parameter negates the implicit daemon mode when run from the command line. smbd will only accept one connection and terminate. It will also log to standard output, as if the -S parameter had been given.
smbd will only accept one connection and terminate.と書かれている通りに、接続を試みたら、terminateされてしまって意味が無かった。最初なぜterminateのメッセージが出て終了してしまうのかと思ったら、そうドキュメントに書いてあった。
さて、become_daemon
関数の中を見てみる。短いので、全部引用してみると、こんな関数になっている。
void become_daemon(bool do_fork, bool no_session, bool log_stdout) { pid_t newpid; if (do_fork) { newpid = fork(); if (newpid == -1) { exit_daemon("Fork failed", errno); } if (newpid) { _exit(0); } #if defined(HAVE_LIBSYSTEMD_DAEMON) || defined(HAVE_LIBSYSTEMD) } else if (sd_notifications) { sd_notify(0, "STATUS=Starting process..."); #endif } /* detach from the terminal */ #ifdef HAVE_SETSID if (!no_session) { int ret = setsid(); if (ret == -1) { exit_daemon("Failed to create session", errno); } } #elif defined(TIOCNOTTY) if (!no_session) { int i = open("/dev/tty", O_RDWR, 0); if (i != -1) { ioctl(i, (int) TIOCNOTTY, (char *)0); close(i); } } #endif /* HAVE_SETSID */ /* Close fd's 0,1,2 as appropriate. Needed if started by rsh. */ /* stdin must be open if we do not fork, for monitoring for * close. stdout must be open if we are logging there, and we * never close stderr (but debug might dup it onto a log file) */ if (do_fork) { int ret = close_low_fd(0); if (ret != 0) { exit_daemon("close_low_fd(0) failed: %s\n", errno); } } if (!log_stdout) { int ret = close_low_fd(1); if (ret != 0) { exit_daemon("close_low_fd(1) failed: %s\n", errno); } } }
デフォルトではdo_fork
にtrue
が渡されるので、さらにfork
する。
(あと、systemd経由で起動する場合は、forkしないけど、通知は出す......なるほど)
no_session
がfalse
なのでsetsid
関数が呼ばれ、新しいセッションが生成され、生成された子プロセスがプロセスグループのリーダとなる。
最後にioctl
の引数にTIOCNOTTY
が渡されて元のターミナルから切り離され、デーモン化が完了する。
foreground
オプションだけを指定してsambaを起動すると、setsid
が失敗し(manに、呼び出したプロセスが既にプロセスリーダーの場合、失敗すると書かれている)、起動に失敗する(sambaのログに、 exit_daemon: daemon failed to start: Failed to create session, error code 1
と出力される)。
これは、sambaが自身がプロセスリーダーになっているためだ。forkされた子プロセスだったら、プロセスリーダーにはなっていない。
なお、このあたりの仕組みは、『試して理解 Linuxのしくみ」に分かりやすくかかれていた。
以上のことから、foreground
と、no-process-group
はセットで指定する必要があることがわかった。
つまり、Docker上で起動してうまくいかなかったのは、自分がオプション無しで起動した結果、起動した元のプロセスはforkして抜けてしまって、プロセスが終了になりコンテナが終了してしまったためだった。
以下のコマンドでうまく起動することができた。
smbd --foreground --no-process-group
現代、そんなに自前でデーモン化する機能を持たなくてもいいのに……デーモン化せずにログも全部標準出力に出すだけでいいのに......と思ってしまった。
ちなみに、デーモン化に関して、そういえば2回forkするって話が有ったよなと疑問に思ったら、下記のブログエントリを見つけた。
抜き出すと、こうだ。
SVR4では,制御端末が割り振られていないセッションリーダが端末をオープンしようとすると,自動的に制御端末が付与されてしまう
これはマズイ!
O_NOCTTYフラグを指定すれば防げるけど,忘れたらマズイよね
SVR4の特性を発動させないためには,孫プロセス (非セッションリーダ) つくればよい.終.
なるほど。
sambaでは2回forkをやっていないのは、特にsambaの中ではターミナルデバイスをopenすることが無いから?なのか。