home ホーム search 検索 -  login ログイン  | reload edit datainfo version cmd icon diff delete  | help ヘルプ

C言語系/「デーモン君のソース探検」読書メモ/A03, ttyname(3)

C言語系/「デーモン君のソース探検」読書メモ/A03, ttyname(3)

C言語系 / 「デーモン君のソース探検」読書メモ / A03, ttyname(3)
id: 575 所有者: msakamoto-sf    作成日: 2010-02-03 19:32:51
カテゴリ: BSD C言語 

お題:ttyname(3)が端末のデバイスファイル名を取得する仕組みを調査せよ

※この章は「デーモン君のソース探検」に載っていませんが、msakamoto-sf自身が個人的に興味を持って調べ、"Appendix"として読書メモシリーズに入れてありますのでご注意下さい。


ttyname(3), ttyname.c

まずソースファイルの場所を調べる。

$ locate ttyname
/usr/share/man/cat3/ttyname.0
/usr/share/man/man3/ttyname.3
/usr/src/lib/libc/gen/ttyname.3
/usr/src/lib/libc/gen/ttyname.c

ソースコードはコンパクトになっている。oldttyname()については本筋と離れるので省略する。

static char buf[sizeof(_PATH_DEV) + MAXNAMLEN] = _PATH_DEV;

char *
ttyname(fd)
  int fd;
{
  struct stat sb;
  struct termios ttyb;
  DB *db;
  DBT data, key;
  struct {
    mode_t type;
    dev_t dev;
  } bkey;

  _DIAGASSERT(fd != -1);

  /* Must be a terminal. */
  if (tcgetattr(fd, &ttyb) < 0)
    return (NULL);
  /* Must be a character device. */
  if (fstat(fd, &sb) || !S_ISCHR(sb.st_mode))
    return (NULL);

  if ((db = dbopen(_PATH_DEVDB, O_RDONLY, 0, DB_HASH, NULL)) != NULL) {
    memset(&bkey, 0, sizeof(bkey));
    bkey.type = S_IFCHR;
    bkey.dev = sb.st_rdev;
    key.data = &bkey;
    key.size = sizeof(bkey);
    if (!(db->get)(db, &key, &data, 0)) {
      memmove(buf + sizeof(_PATH_DEV) - 1, data.data,
          data.size);
      (void)(db->close)(db);
      return (buf);
    }
    (void)(db->close)(db);
  }
  return (oldttyname(&sb));
}

変数宣言の後tcgetattr()とfstat(2)により、引数で渡されたファイル記述子が端末に結びついていて、キャラクタデバイスであることをチェックしている。
その後 dbopen(3) により"_PATH_DEVDB" で指定されたデータベースファイルをオープンし、S_IFCHRとfstat(2)で取得したstat構造体のst_rdevメンバ(=デバイスファイルのデバイスタイプ)をキーとしてエントリを取得、bufにdataをコピーする。

devname(3)

ここまででttyname(3)の調査としては問題ないが、いきなり_PATH_DEVDBをdbopen(3)で操作しているのが気になる。続いて"_PATH_DEVDB"について調べてみる。

"_PATH_DEVDB"は paths.h で定義されている。
paths.h:

#define _PATH_DEVDB     "/var/run/dev.db"
...
#define _PATH_DEV       "/dev/"

たまたまttyname.cと同じディレクトリにdevname.cがあるのを見つけ、devname.3というmanページもあった。devname.cはdevname(3)のソースコードになっていて、デバイス名を取得する関数としてより汎用的なインターフェイスになっている。

DEVNAME(3)                NetBSD Programmer's Manual                DEVNAME(3)

NAME
     devname - get device name

LIBRARY
     Standard C Library (libc, -lc)

SYNOPSIS
     #include <stdlib.h>
     #include <sys/stat.h>

     char *
     devname(dev_t dev, mode_t type);

(...)

FILES
     /var/run/dev.db  Device database file.

SEE ALSO
     stat(2), dev_mkdb(8)

manページを見てみると、"SEE ALSO"に "dev_mkdb(8)" というのがある。manページを確認してみると、ズバリ、"/dev" データベースを作成するコマンドになっていた。

DEV_MKDB(8)             NetBSD System Manager's Manual             DEV_MKDB(8)

NAME
     dev_mkdb - create /dev database

SYNOPSIS
     dev_mkdb [-o database] [directory]

(...)

SEE ALSO
     ps(1), stat(2), db(3), devname(3), kvm_nlist(3), ttyname(3), kvm_mkdb(8)

NetBSD1.6の場合、システム起動時に次のrcスクリプトで実行されるようになっている。

/etc/rc.d/sysdb

このコマンドはdb(3)で操作可能なhashタイプのデータベースを "/var/run/dev.db" (="_PATH_DEVDB")に作成する。ファイルタイプとst_rdevをキーとして全てのキャラクタデバイスとブロックデバイス名を格納する。

dev_mkdb(8), dev_mkdb.c

dev_mkdbの実行ファイル・ソースファイルの場所は次の通り:

$ locate dev_mkdb
/usr/sbin/dev_mkdb
...
/usr/src/usr.sbin/dev_mkdb/dev_mkdb.c

dev_mkdb.cのソースのポイントをざっくりと見ていく。

まず"_PATH_DEV" = "/dev" ディレクトリをfts_open(3)で開く。以降、fts(3)関数を使ってファイル階層をトラバースしていく。

char path_dev[MAXPATHLEN + 1] = _PATH_DEV;

/* (...) */

pathv[0] = path_dev;
pathv[1] = NULL;
ftsp = fts_open(pathv, FTS_PHYSICAL, NULL);

次のポイントはdbopen(3)で、DB_HASHを指定してオープンしている。dbtmpはこの時点で適切なファイル名(=_PATH_DEVDB)に調整されている。

db = dbopen(dbtmp, O_CREAT|O_EXCL|O_EXLOCK|O_RDWR|O_TRUNC,
                   S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH, DB_HASH, NULL);

そしてfts_read()で各ファイルに対してファイルタイプとst_rdevをキーに、ファイル名をDBに追加していく:

/*
 * Keys are a mode_t followed by a dev_t.  The former is the type of
 * the file (mode & S_IFMT), the latter is the st_rdev field.  Note
 * that the structure may contain padding, so we have to clear it
 * out here.
 */
memset(&bkey, 0, sizeof(bkey));
key.data = &bkey;
key.size = sizeof(bkey);
data.data = buf;
while ((p = fts_read(ftsp)) != NULL) {
  switch (p->fts_info) {
  case FTS_DEFAULT:
    st = p->fts_statp;
    break;
  default:
    continue;
  }

  /* Create the key. */
  if (S_ISCHR(st->st_mode))
    bkey.type = S_IFCHR;
  else if (S_ISBLK(st->st_mode))
    bkey.type = S_IFBLK;
  else
    continue;
  bkey.dev = st->st_rdev;

  /*
   * Create the data; nul terminate the name so caller doesn't
   * have to.  Skip path_dev and slash.
   */
  strlcpy(buf, p->fts_path + (strlen(path_dev) + 1), sizeof(buf));
  data.size = p->fts_pathlen - (strlen(path_dev) + 1) + 1;
  if ((*db->put)(db, &key, &data, 0)) {
    err(1, "dbput %s", dbtmp);
  }
}
(void)(*db->close)(db);
fts_close(ftsp);

ところで細かい部分になってしまうが、DBを作成する時はファイル名の"_PATH_DEV"部分は削り落として登録するようになっている。

strlcpy(buf, p->fts_path + (strlen(path_dev) + 1), sizeof(buf));

path_devは_PATH_DEVで初期化されているので、例えばfts_pathが

/dev/abc01

なら、実際にbufにコピーされるのは

abc01

になる。また登録時のデータ長も、丁度path_dev(="_PATH_DEV")分短くなるよう調整されている:

data.size = p->fts_pathlen - (strlen(path_dev) + 1) + 1;

少しでも実行時のDBファイルサイズを減らそうという努力が伺える。
ttyname.cの方では、値を取り出すところで "_PATH_DEV" がうまく補完されるようになっている。まず取り出す領域を"_PATH_DEV"で初期化しておく。

static char buf[sizeof(_PATH_DEV) + MAXNAMLEN] = _PATH_DEV;

そして取り出す時にmemmove()を使い、オフセットを"_PATH_DEV"分だけずらしている:

memmove(buf + sizeof(_PATH_DEV) - 1, data.data, data.size);

これにより、丁度

"/dev/" + "abc01"

という形でデバイス名が復元される。

以上でttyname(3)がデバイス名を取得する仕組みが判明した。システム起動時にdev_mkdb(8)が "/dev/" ディレクトリをもとにデバイス情報のDBを生成するようになっており、ttyname(3)およびdevname(3)はそのDBからデバイス名を取得するようになっている。

今回のお題については、ここまで。



プレーンテキスト形式でダウンロード
現在のバージョン : 1
更新者: msakamoto-sf
更新日: 2010-02-03 19:35:05
md5:bcb0df474fd6148c57086d752f849467
sha1:128fb0269719515c86c70f0457621b33c3e17327
コメント
コメントを投稿するにはログインして下さい。