以下、Linux kernel 2.6 でのお話です。
ぼくが改めて言うまでもなく、プログラム(mysqld)が異常終了したときなどに吐かれるcoreファイルは、その原因を探る上で非常に有益です。
が、なぜか、
- setrlimitのRLIMIT_COREはunlimitedになっている
- my.cnfの[mysqld]セクションに「core-file」を指定している
にも関わらず、自分の環境のmysqldはcoreを吐いてくれませんでした。
結論から言うと、
# MYSQL_DIR=/usr/local/app/mysql # ( sleep 10 && pkill -ABRT mysqld ) & # $MYSQL_DIR/bin/mysqld \ --basedir=$MYSQL_DIR \ --datadir=/db/mysql \ --pid-file=/db/var/mysql.pid \ --user mysql \ ; Aborted # ls ~mysql/core* ls: cannot access /db/mysql/core*: No such file or directory # ( sleep 10 && pkill -ABRT mysqld ) & # sudo -u mysql $MYSQL_DIR/bin/mysqld \ --basedir=$MYSQL_DIR \ --datadir=/db/mysql \ --pid-file=/db/var/mysql.pid \ ; Aborted (core dumped) # ls ~mysql/core* /db/mysql/core.1254295158_mysqld_3367
の様に、実効ユーザの指定にmysqldの--userオプションを使った場合はcoreができませんが、sudo (やdaemontoolsに含まれるsetuidgidでもOK) を使った場合は期待通りcoreができます。
さて、--userの場合にcoreができない原因はなんでしょうか。
MySQLのドキュメントに依れば、
On some systems, such as Solaris, you do not get a core file if you are also using the --user option.
MySQL :: MySQL 8.0 Reference Manual :: 5.1.7 Server Command Options
だそうなのですが、「Linux」は明言されていないのでmysqlのソースを覗いてみます。
- http://bazaar.launchpad.net/~mysql/mysql-server/mysql-5.1/annotate/head%3A/sql/mysqld.cc (なんでかよくPlease try againと言われます。。。)
ながーいmain関数の中にこのようなコードがあり、check_user()の後でset_user()を呼んでいます。
if ((user_info= check_user(mysqld_user))) { #if defined(HAVE_MLOCKALL) && defined(MCL_CURRENT) if (locked_in_memory) // getuid() == 0 here set_effective_user(user_info); else #endif set_user(mysqld_user, user_info); }
続いて、check_user()とset_user()を引用します。
static struct passwd *check_user(const char *user) { (snip) if (*pos) // Not numeric id goto err; if (!(tmp_user_info= getpwuid(atoi(user)))) goto err; } return tmp_user_info; /* purecov: end */ err: sql_print_error("Fatal error: Can't change to run as user '%s' ; Please check that the user exists!\n",user); unireg_abort(1); #ifdef PR_SET_DUMPABLE if (test_flags & TEST_CORE_ON_SIGNAL) { /* inform kernel that process is dumpable */ (void) prctl(PR_SET_DUMPABLE, 1); } #endif #endif return NULL; }
static void set_user(const char *user, struct passwd *user_info_arg) { (snip) if (setgid(user_info_arg->pw_gid) == -1) (snip) if (setuid(user_info_arg->pw_uid) == -1) (snip) }
Linux の場合、setuidやsetgidなどで実効uidやgidが操作された場合、prctl(2)でPR_SET_DUMPABLEしないとcoreは吐かれません。
- http://linuxjm.sourceforge.jp/html/LDP_man-pages/man2/prctl.2.html
- http://linuxjm.sourceforge.jp/html/LDP_man-pages/man5/proc.5.html
ここで前掲のコードを見ると、prctlを呼んでいるcheck_user()を実行した後、setgid,setuidを呼んでいるset_user()を実行する流れになっています。
prctlは、setuidした後で呼ばないと意味がないので、これではcoreは吐かれませんね。
もうひとつ興味深いのは、check_userでprctlが呼ばれるのは、何かしらの処理が失敗してerrラベルへ飛ばされたときのみ、という点です。つまり、正常処理ではそもそもprctlが呼ばれないように読めます。うひ。
この症状がバグレポートされてないかと探してみるとありました。
Bug#21723の報告者のパッチではきちんとsetuidの後にprctlを呼ぶようになっているのですが、なぜか(5.1.20で)なされた修正では、前述のように変なところでprctlが呼ばれるようになってしまっていました。。。
というわけで、いざというときにcoreを吐かせるために、mysqldを起動するときは--userオプションではなくて、sudoやsetuidgidコマンドで実効ユーザを指定するといいんじゃないかと思います。
以上、さも自分が調べたかのように書いてますが、(7月頃に)実際に調べたのは id:kazuhooku さんです>< ありがとうございました!
あと、
echo 2 > /proc/sys/fs/suid_dumpable
したらsetuid後prctlしないでもcoreが生まれるかなーと思ったんですが生まれませんでした。なんか勘違いしてるのかしら。。。
http://linuxjm.osdn.jp/html/LDP_man-pages/man5/proc.5.html
↑生まれました。
Linux 3.6 以降では、/proc/sys/fs/suid_dumpable が 2 ("suidsafe") に設定されている場合、テンプレートは、絶対パス名 (先頭に '/' 文字があるパス名) かパイプ (以下で説明) のどちらかでなければならない。
http://linuxjm.osdn.jp/html/LDP_man-pages/man5/core.5.html
CentOSのkernel 2.6.32-431.el6ですが、この制限が効いてるらしく、
echo '/var/tmp/core.%t_%e_%p' > /proc/sys/kernel/core_pattern
とかしたらちゃんと生まれました!
あと、suid_dumpableの変更はその後で起動したプロセスにだけ適用されるので、変更後にmysqldの再起動が必要な点に注意す。