警告: (愚かにも) 多くのシステムプログラムがその整合性に依存しているので、 utmp ファイルは "other" に分類されるユーザーに対して 書き込み可能にしてはならない。 ファイルの所有者とグループオーナー以外のユーザーに対して utmp ファイルを書き込み可能な状態にしておくと、 システムのログファイルを偽造されたり、システムファイルの 改ざんを受けるといったリスクを犯すことになる。
このファイルは utmp 構造体の繰り返しで構成される。 この構造体は <utmp.h> で以下のように定義されている (ここに記述してあるのは幾つかの大まかな定義のみで、 詳細は libc のバージョンにより変わることに注意が必要である)。
/* Values for ut_type field, below */
#define EMPTY 0 /* Record does not contain valid info
(formerly known as UT_UNKNOWN on Linux) */
#define RUN_LVL 1 /* Change in system run-level (see
init(1)) */
#define BOOT_TIME 2 /* Time of system boot (in ut_tv) */
#define NEW_TIME 3 /* Time after system clock change
(in ut_tv) */
#define OLD_TIME 4 /* Time before system clock change
(in ut_tv) */
#define INIT_PROCESS 5 /* Process spawned by init(1) */
#define LOGIN_PROCESS 6 /* Session leader process for user login */
#define USER_PROCESS 7 /* Normal process */
#define DEAD_PROCESS 8 /* Terminated process */
#define ACCOUNTING 9 /* Not implemented */
#define UT_LINESIZE 32 #define UT_NAMESIZE 32 #define UT_HOSTSIZE 256
struct exit_status { /* Type for ut_exit, below */
short e_termination; /* Process termination status */
short e_exit; /* Process exit status */
};
struct utmp {
short ut_type; /* Type of record */
pid_t ut_pid; /* PID of login process */
char ut_line[UT_LINESIZE]; /* Device name of tty - "/dev/" */
char ut_id[4]; /* Terminal name suffix,
or inittab(5) ID */
char ut_user[UT_NAMESIZE]; /* Username */
char ut_host[UT_HOSTSIZE]; /* Hostname for remote login, or
kernel version for run-level
messages */
struct exit_status ut_exit; /* Exit status of a process
marked as DEAD_PROCESS; not
used by Linux init(1) */
/* ut_session と ut_tv フィールドは、32ビットでコンパイルされた場合と
64ビットでコンパイルされた場合で同じサイズでなければならない。
こうすることで、32ビットと64ビットのアプリケーションで、
データファイルと共有メモリーを共有することができるようになる。 */
#if __WORDSIZE == 64 && defined __WORDSIZE_COMPAT32
int32_t ut_session; /* Session ID (getsid(2)),
used for windowing */
struct {
int32_t tv_sec; /* Seconds */
int32_t tv_usec; /* Microseconds */
} ut_tv; /* Time entry was made */
#else
long ut_session; /* Session ID */
struct timeval ut_tv; /* Time entry was made */
#endif
int32_t ut_addr_v6[4]; /* Internet address of remote
host; IPv4 address uses
just ut_addr_v6[0] */
char __unused[20]; /* Reserved for future use */
};
/* Backward compatibility hacks */ #define ut_name ut_user #ifndef _NO_UT_TIME #define ut_time ut_tv.tv_sec #endif #define ut_xtime ut_tv.tv_sec #define ut_addr ut_addr_v6[0]
この構造体からユーザーの使っている端末のスペシャルファイル名、 ユーザーのログイン名、 (time(2) 形式での)ログイン時刻がわかる。文字列フィールドは、 フィールドの長さより文字列が短い場合には、ヌルバイト ('\0') によって終端される。
最初のエントリーは init(1) コマンドが inittab(5) を処理することで作られる。 あるエントリーを処理する前に、 init(1) は ut_type を DEAD_PROCESS に初期化する。 レコードの ut_type が DEAD_PROCESS と RUN_LVL のいずれでもなく、 かつ PID が ut_pid であるプロセスがいない場合は、ut_user, ut_host, ut_time をヌルバイトでクリアして初期化を行う。 必要な ut_id を持つ空のレコードを見つけられなかった場合、 init(1) は新しいレコードを作る。inittab から ut_id を設定し、 ut_pid および ut_time を現在値に、 ut_type を INIT_PROCESS に設定する。
mingetty(8) (または agetty(8)) は pid でエントリーを特定し、 ut_type を LOGIN_PROCESS に変更し、 ut_time を更新し、ut_lineを設定した後、接続が確立されるのを待つ。 login(1) はユーザー認証が終了すると、 ut_type を USER_PROCESS に変更し、 ut_time を更新し、ut_host と ut_addrを設定する。 mingetty(8) (または agetty(8)) と login(1) により異なるが、 ut_pid の代わりに ut_line を使ってレコードの特定が行われることもある (ut_pid を使う方が望ましい) 。
init(1) はプロセスの終了を検出した場合、 ut_pid をキーとして utmp のエントリーを特定し、 ut_type を DEAD_PROCESS に設定し、 ut_user, ut_host, ut_time をヌルバイトでクリアする。
xterm(1) コマンドや他の端末エミュレータは 直接 USER_PROCESS のレコードを作り、 端末名のサフィックス文字列 (/dev/[pt]ty に続く文字列) を使って ut_id の値を生成する。 この id を持つエントリーが DEAD_PROCESS であった場合には再利用し、 それ以外の場合には新しいエントリーが作られる。 可能な場合にはプロセス終了時に DEAD_PROCESS と設定し、 さらに ut_line, ut_time, ut_user, ut_host をヌルバイトでクリアすることが奨励されている。
telnetd(8) は LOGIN_PROCESS を設定するだけでよく、 残りの処理は通常通り login(1) に任せればよい。 telnet のセッションが終了した後、前述のように telnetd(8) が utmp のエントリーを初期化する。
wtmp ファイルには、すべてのログインとログアウトが記録される。 そのフォーマットは、ログアウト済の端末でユーザー名がヌルとなること以外は utmp とまったく同じである。 ユーザー名が shutdown もしくは reboot の端末名 ~ は、システムの停止 (shutdown) または再起動 (reboot) を意味する。またその端末名が | と } の対は date(1) コマンドで変更した新/旧のシステム時刻を記録している。 wtmp ファイルは login(1), init(1) やいくつかのバージョンの getty(8) (mingetty(8) または agetty(8)) により管理されている。 これらのプログラムはどれもファイルを新たに作成しないので、 ファイルを削除することで情報の記録 (record-keeping) を止めることができる。
POSIX.1 では、 utmp 構造体ではなく、 utmpx 構造体を規定している。 utmpx 構造体で規定されているのは、フィールド ut_type, ut_pid, ut_line, ut_id, ut_user, ut_tv である。 POSIX.1 では、フィールド ut_line と ut_user の長さは規定されていない。
Linux では、 utmpx 構造体の定義は utmp 構造体と同じである。
v7/BSD ではより少しの項目しかない; もっとも重要なことは、ut_type が無いことである。 そのため v7/BSD 系のプログラムでは (たとえば) 死んだ状態のエントリーや ログイン状態のエントリーまで表示されてしまうことになった。 さらにセッション用のスロットを割り当てるための設定ファイルがない。 BSD に設定ファイルがあるのは ut_id がないからである。
Linux (System V 系)では、設定ファイルを必要とせず セッション用のスロットを割り当てるので、一旦設定 されてしまうとレコードの ut_id は決して変更されない。 ut_id をクリアすると競合状態におちいり、 utmp のエントリーを壊したり、潜在的なセキュリティホールになる可能性がある。 上述のフィールドをヌルバイトで埋めてクリアしておくのは、 System V での取り決めでは必要とはされていないが、 BSD での取り決めを前提としていて、かつ utmp を更新しない多くのプログラムが 動作するようにするためである。 Linux ではここまで記述してきたように、行内容の表示は BSD の慣例に従っている。
UT_UNKNOWN は Linux で作られたもののようである。 System V には ut_host も ut_addr_v6 も存在しない。
ファイルを削除することで utmp への記録を止められる 他の様々なシステムとは違い、Linux では utmp ファイルを必ずおいて おく必要がある。 who(1) コマンドが機能しないようにしたい場合には、 utmp ファイルの全ユーザーに対する読み取り許可を設定しないようにする。
ファイルのフォーマットはマシンに依存するので、ファイルが作られた マシンもしくは同一アーキテクチャーのマシン上でのみ処理することを推奨する。
注意すべき点としては、 biarch なプラットフォーム、つまり 32ビットと 64ビットの両方の アプリケーションを実行できるシステム (x86-64, ppc64, s390x など) では、 ut_tv のサイズは 32ビットモードと 64ビットモードで同じである。 ut_session と ut_time も、存在する場合には同様に同じサイズ である。これにより、32ビットアプリケーションと 64ビットアプリケーション の間でデータファイルと共有メモリーを共有することが可能になる。 そのためには、 ut_session を int32_t 型に、 ut_tv を 2つの int32_t 型のフィールド tv_sec, tv_usec を持つ構造体に変更すればよい ut_tv は struct timeval と同じサイズとは限らないので、
gettimeofday((struct timeval *) &ut.ut_tv, NULL);
のような呼び出しをするのではなく、 以下のように各フィールドを設定する方法が推奨される:
struct utmp ut; struct timeval tv;
gettimeofday(&tv, NULL); ut.ut_tv.tv_sec = tv.tv_sec; ut.ut_tv.tv_usec = tv.tv_usec;