#navi_header|C言語系| お題:ttyname(3)が端末のデバイスファイル名を取得する仕組みを調査せよ ※この章は「デーモン君のソース探検」に載っていませんが、msakamoto-sf自身が個人的に興味を持って調べ、"Appendix"として読書メモシリーズに入れてありますのでご注意下さい。 #more|| #outline|| ---- * 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()については本筋と離れるので省略する。 #pre||> 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)のソースコードになっていて、デバイス名を取得する関数としてより汎用的なインターフェイスになっている。 #pre||> DEVNAME(3) NetBSD Programmer's Manual DEVNAME(3) NAME devname - get device name LIBRARY Standard C Library (libc, -lc) SYNOPSIS #include #include 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" データベースを作成するコマンドになっていた。 #pre||> 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)関数を使ってファイル階層をトラバースしていく。 #pre||> 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に追加していく: #pre||> /* * 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からデバイス名を取得するようになっている。 今回のお題については、ここまで。 #navi_footer|C言語系|