2006年9月22日

ファイル記述子をUnixドメインソケット経由で渡す

Unix 系の多くの OSには、ファイル記述子を別のプロセスに Unix ドメインソケット経由で渡す機能があります。一見、何のために使うのかよくわからない機能ですが、 glibc の nscd はこれをうまく使っています。

 

nscd (name service caching daemon) は glibc 内で行われる名前関連の問い合わせをキャッシュするサーバです。NIS や LDAP などを用いてネットワークベースでユーザ管理を行っている場合、 getpwuid() などの関数はユーザ名の取得にネットワークアクセスを必要としますが、 nscd を立ち上げておけば、二度目からの同じ問い合わせはキャッシュから得られます。

nscd を立ち上げている GNU/Linux システムでは、キャッシュファイルが /var/db/nscd 以下に作成されます。そして、 getpwuid() を呼び出したプログラムではこのファイルが mmap(2) されて使われます。mmap(2) されたファイルは /proc/PID/maps に現れるので、 /proc/PID/maps を覗いてみると、次のような行が見つかります。

f7371000-f7d41000 r--s 00000000 08:01 3942426  /var/db/nscd/passwd

このファイルを見てみると、所有者は root でパーミッションは 600、つまり、 root しか読み書きできないファイルとなっています。mmap(2) するにはファイル記述子が必要ですが、通常、ファイル記述子はファイルを open(2) しないと得られません。ではどうやって一般ユーザのプロセスはこのファイルを mmap(2) しているのでしょうか。

そこで、ファイル記述子の受け渡し機能の出番です。glibc 内のクライアントコードは /var/run/nscd/socket にある Unix ドメインソケットを通じて、 nscd から /var/db/nscd/passwd のファイル記述子を受け取っています。ファイル記述子を受け取ってしまえば、ファイルのサイズなどの情報は fstat(2) で調べることができ、 mmap(2) も問題なくできます。

ファイル記述子を送るコードは glibc-2.4/nscd/connections.c にあります。sendmsg(2) を使ってファイル記述子を送っています。

static void
send_ro_fd (struct database_dyn *db, char *key, int fd)
{
  /* If we do not have an read-only file descriptor do nothing.  */
  if (db->ro_fd == -1)
    return;

  /* We need to send some data along with the descriptor.  */
  struct iovec iov[1];
  iov[0].iov_base = key;
  iov[0].iov_len = strlen (key) + 1;

  /* Prepare the control message to transfer the descriptor.  */
  union
  {
    struct cmsghdr hdr;
    char bytes[CMSG_SPACE (sizeof (int))];
  } buf;
  struct msghdr msg = { .msg_iov = iov, .msg_iovlen = 1,
                        .msg_control = buf.bytes,
                        .msg_controllen = sizeof (buf) };
  struct cmsghdr *cmsg = CMSG_FIRSTHDR (&msg);

  cmsg->cmsg_level = SOL_SOCKET;
  cmsg->cmsg_type = SCM_RIGHTS;
  cmsg->cmsg_len = CMSG_LEN (sizeof (int));

  *(int *) CMSG_DATA (cmsg) = db->ro_fd;

  msg.msg_controllen = cmsg->cmsg_len;

  /* Send the control message.  We repeat when we are interrupted but
     everything else is ignored.  */
#ifndef MSG_NOSIGNAL
# define MSG_NOSIGNAL 0
#endif
  (void) TEMP_FAILURE_RETRY (sendmsg (fd, &msg, MSG_NOSIGNAL));

  if (__builtin_expect (debug_level > 0, 0))
    dbg_log (_("provide access to FD %d, for %s"), db->ro_fd, key);
}

クライアント側のコードは glibc-2.4/nscd/nscd_helper.c の get_mapping() にあります。こちらでは recvmsg(2) が使われています。

まとめ

glibc の nscd における、ファイル記述子の受け渡し機能の利用法を紹介しました。 root しか読み書きできないファイルを一般ユーザのクライアントプロセスに渡したいときに役立つテクニックだと思います。

プロセス間のファイル記述子の受け渡しについては 「詳解UNIXプログラミング」の15章に詳しく述べられています。18章では応用例として、モデムのデバイスファイル (root しか読み書きできない) のファイル記述子を、モデムサーバからクライアントへ渡す例が紹介されています。

ちなみに、 nscd のこの仕組みは、 /var/db/nscd/passwd をどうやって mmap(2) しているのだろうと疑問に思って調べた結果、わかりました。ファイル記述子を nscd からもらっているのだろうと予想して、プロセスを strace したところ、案の定 recvmsg(2) が呼ばれていました。 strace のおかげで、 glibc のソースコードを見る前におおよその仕組みがわかりました。