Magnolia Tech

いつもコードのことばかり考えている人のために。

sambaの「--foreground」「--no-process-group」について

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という関数の引数に使われていることがわかった。

github.com

void become_daemon(bool do_fork, bool no_session, bool log_stdout)

foregroundオプションが設定されるとdo_forkfalseが設定され、no-process-groupオプションが設定されるとno_sessiontrueが設定される。


どうでもいいけど、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_forktrueが渡されるので、さらにforkする。 (あと、systemd経由で起動する場合は、forkしないけど、通知は出す......なるほど)

no_sessionfalseなのでsetsid関数が呼ばれ、新しいセッションが生成され、生成された子プロセスがプロセスグループのリーダとなる。

最後にioctlの引数にTIOCNOTTYが渡されて元のターミナルから切り離され、デーモン化が完了する。


foregroundオプションだけを指定してsambaを起動すると、setsidが失敗し(manに、呼び出したプロセスが既にプロセスリーダーの場合、失敗すると書かれている)、起動に失敗する(sambaのログに、 exit_daemon: daemon failed to start: Failed to create session, error code 1と出力される)。

これは、sambaが自身がプロセスリーダーになっているためだ。forkされた子プロセスだったら、プロセスリーダーにはなっていない。

なお、このあたりの仕組みは、『試して理解 Linuxのしくみ」に分かりやすくかかれていた。

blog.magnolia.tech


以上のことから、foregroundと、no-process-groupはセットで指定する必要があることがわかった。


つまり、Docker上で起動してうまくいかなかったのは、自分がオプション無しで起動した結果、起動した元のプロセスはforkして抜けてしまって、プロセスが終了になりコンテナが終了してしまったためだった。

以下のコマンドでうまく起動することができた。

smbd --foreground --no-process-group

現代、そんなに自前でデーモン化する機能を持たなくてもいいのに……デーモン化せずにログも全部標準出力に出すだけでいいのに......と思ってしまった。


ちなみに、デーモン化に関して、そういえば2回forkするって話が有ったよなと疑問に思ったら、下記のブログエントリを見つけた。

sleepy-yoshi.hatenablog.com

抜き出すと、こうだ。

SVR4では,制御端末が割り振られていないセッションリーダが端末をオープンしようとすると,自動的に制御端末が付与されてしまう

これはマズイ!

O_NOCTTYフラグを指定すれば防げるけど,忘れたらマズイよね

SVR4の特性を発動させないためには,孫プロセス (非セッションリーダ) つくればよい.終.

なるほど。

sambaでは2回forkをやっていないのは、特にsambaの中ではターミナルデバイスをopenすることが無いから?なのか。