last コマンドを作る Linux glibc version
Ruby で last コマンドを作りました。
以前、TurboLinux 2.0J で last コマンドに不具合があったのがきっかけですが、使いなれてしまったのでいまでも使い続けています。
今回のものは、最近 Linux では glibc が標準的になってきたので glibc-2.0.x 用の last コマンドです。
基本的な部分をうまく活用すれば、他のシステムへの移植も可能と思います。
普段(2007/4/28 現在)からこのコマンドを使っています。
使い方といっても特にオプションなどはなく、次のように実行してください。
$ last.rb tetsu ttyp2 :0.0 Mon May 10 20:40 still logged in tetsu ttyp1 :0.0 Mon May 10 20:40 still logged in tetsu ttyp0 :0.0 Mon May 10 20:35 still logged in tetsu tty1 Mon May 10 20:35 still logged in reboot system boot Mon May 10 20:14 root tty1 Mon May 10 08:31 - down (00:00) tetsu ttyp2 :0.0 Fri May 07 19:32 - 08:29 (2+12:56) tetsu ttyp1 :0.0 Fri May 07 19:32 - 08:29 (2+12:56) tetsu ttyp0 :0.0 Fri May 07 19:31 - 08:31 (2+12:59) tetsu tty1 Fri May 07 19:31 - 08:31 (2+12:59) reboot system boot Fri May 07 19:31 root tty1 Fri May 07 06:11 - down (00:00) tetsu ttyp2 :0.0 Thu May 06 19:34 - 06:10 (10:36) tetsu ttyp1 :0.0 Thu May 06 19:34 - 06:10 (10:36) tetsu ttyp0 :0.0 Thu May 06 19:34 - 06:11 (10:36) tetsu tty1 Thu May 06 19:33 - 06:11 (10:37) reboot system boot Thu May 06 19:33 root tty1 Thu May 06 07:47 - down (00:00) tetsu ttyp3 :0.0 Wed May 05 22:36 - 22:46 (00:09) /var/log/wtmp begins Wed May 5 22:36:21 1999
Web ブラウザでの表示上は Ruby のスクリプトですが、HTML の特殊文字をエスケープしています。 テキストとしてセーブしてご利用ください。
#! /usr/local/bin/ruby
# /home/tetsu/src/ruby/toolbox/last.rb
# Created: July 13,1998 Monday 00:26:49
# Author: tetsu(WATANABE Tetsuya)
# $Id: last.rb,v 1.9 2003/05/08 03:00:53 tetsu Exp $
# usage:
class Wtmp
def initialize
@status = ''
@wtmp = []
end
attr :status
def w_start(wtmp)
@wtmp = wtmp
@status = 'start'
end
# 0: still logged in
# nil: down
# other: normal
def w_end(time = nil)
retval = []
if @status == 'start'
@status = 'end'
@wtmp.push(time)
retval = @wtmp
@wtmp = []
end
retval
end
end
def reset_entry(w_hash)
for k, v in w_hash
if v.status == 'start'
$last.push(v.w_end)
end
end
end
def reboot(w_hash, *wtmp)
$last.push(wtmp)
reset_entry(w_hash)
end
def delta_time(t)
d = 0
if t > 24 * 3600
d = t / (24 * 3600)
t %= 24 * 3600
end
h = t / 3600
m = t % 3600 / 60
if d == 0
return format("%02d:%02d", h, m)
else
return format("%d+%02d:%02d", d, h, m)
end
end
WTMP = '/var/log/wtmp'
TYPE_IN = 0
PID_IN = 2
LINE_IN = 3
NAME_IN = 5
HOST_IN = 6
TIME_IN = 10
WTMP_FMT = 'ssiA32A4A32A256ssllliiiiA20'
WTMP_LEN = 384
$last = []
w_hash = {}
w_start_time = 0
wtmp_file =
if ARGV.size > 0
ARGV[0]
else
WTMP
end
wtmp = File.open(wtmp_file)
while wtmp_line = wtmp.read(WTMP_LEN)
arr = wtmp_line.unpack(WTMP_FMT).values_at(TYPE_IN, NAME_IN, LINE_IN, HOST_IN, TIME_IN)
key = arr[2]
if w_start_time == 0
w_start_time = arr[4]
end
case arr.shift
when 7 # USER_PROCESS
w = Wtmp.new
w.w_start(arr)
w_hash[key] = w
when 8, 6 # DEAD_PROCESS LOGIN_PROCESS
if w_hash.key?(key)
w = w_hash[key]
$last.push(w.w_end(arr[3]))
w_hash.delete(key)
end
when 2 # BOOT_TIME
reboot(w_hash, 'reboot', 'system boot', '', arr[3])
w_hash = {}
when 1 # RUN_LVL
reset_entry(w_hash)
w_hash = {}
end
end
wtmp.close
for k, v in w_hash
if v.status == 'start'
$last.push(v.w_end(0)) # 0 is "still logged in"
end
end
$last.sort {|a, b|
(b[3] <=> a[3]).nonzero? or (a[1] <=> b[1]).nonzero? or a[0] <=> b[0]
}.each {|v|
printf("%-8s %-12s %-16s %s",
v[0],
v[1],
v[2],
Time.at(v[3]).strftime('%a %b %d %H:%M'))
if v[0] == 'reboot'
print "\n"
elsif v[4] == 0
print " still logged in\n"
elsif v[4] == nil
print " - down (00:00)\n"
else
printf(" - %s (%s)\n",
Time.at(v[4]).strftime('%H:%M'),
delta_time(v[4] - v[3])
)
end
}
printf("\n%s begins %s\n", wtmp_file, Time.at(w_start_time).strftime('%c'))
exit
last コマンドは、データファイルになる /var/log/wtmp の形式に依存しています。
この形式の定義は /usr/include/utmp.h にあります。
この定義をもとにデータをとりだすための unpack の定義を決めます。
これを手作業で実施すると大変なので、pstruct というコマンドを利用します。
これは、Perl システムが提供するコマンド(Perl スクリプト)ですが、wtmp ファイルの「utmp 構造体」の形式を確認しやすくなります。
Ruby スクリプト中で使用している、構造体の大きさ(WTMP_LEN)や unpack のフォーマット(WTMP_FMT)などを、この情報をもとに決めています。
この pstruct というツールは、便利ですね。
ファイルフォーマットの情報がわかり、データを扱えるようになったらログインしている状況を last コマンドと同じように扱えるようにします。
この処理は少々ややこしいです。
データの蓄積は、ログイン、ログアウトのタイミングでそれぞれ独立に、wtmp ファイルに追加されていきます。
このため、ログインの状況を示すのには、「ログイン」と「ログアウト」の関連を見つけなければなりません。
これを「ターミナルデバイス」名から判断して、処理を行っています。
途中リブートなどが入ったら、そのための整合性を合わせるなどの処理も行います。
同じような内容を扱っている
「
last コマンドを作る
」
も参考にしてください。
Array#values_at へ修正。