last コマンドを作る
現在は Linux のほとんどは glibc 対応になりました。
ということで、
「
last コマンドを作る Linux glibc version
」
をどうぞ。
Ruby で last コマンドを作成しました。
これは、TurboLinux 2.0J にシステムを移行したら、libc 5 と glibc(libc 6) の混在環境のため /var/log/wtmp ないの整合性がとれなくなっているためです。
そのため、本来の last コマンドでは、情報がちゃんと表示されません。
そこで、last.rb をに作成して使うことにしました。
ただ、残念なことにこれも整合性があっていないためと思われるのですが、X 関係のコマンド kterm などでは、使用開始時の情報はあるのですが、使用修了時の情報が残らず、本来の出力が得られない部分があります。
xdm を普段から使っている方の場合、この情報がないため十分な結果が得られない状態です。
残念。
でも、存在しない情報なのでどうすることもできないのです。
対応システムは、予定では? libc 5 と libc 6(glibc) と HP-UX 10.20 です。
使い方といっても特にオプションなどはなく、次のように実行してください。
$ last.rb | head tetsu ttyp1 :0.0 Sun Jul 19 01:10 still logged in tetsu ttyp0 :0.0 Sun Jul 19 01:10 still logged in tetsu tty1 Sun Jul 19 01:09 still logged in tetsu ttyS0 Sat Jul 18 22:59 - 23:10 (00:10) tetsu tty1 Sat Jul 18 19:11 - 19:12 (00:00) reboot system boot Sat Jul 18 19:11 root tty1 Sat Jul 18 10:58 - down (00:00) tetsu ttyp2 :0.0 Sat Jul 18 10:50 - down (00:00) tetsu ttyp1 :0.0 Sat Jul 18 08:36 - down (00:00) tetsu ttyp0 :0.0 Sat Jul 18 08:36 - down (00:00)表示形式は、Linux の
last コマンドにあわせました。
X のターミナルウィンドウでは、うまく終了を確認できません。
終了情報がないためにいまのところ対応できていません。
スクリプトをセーブして、使用する場合には「テキスト」でセーブしてください。 これで使えるようになるはずです。
#! /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.3 1998/07/18 00:57:17 tetsu Exp $
# usage:
$last = []
class Wtmp
def initialize
@status = ''
@wtmp = []
end
def w_start(wtmp)
@wtmp = wtmp
@status = 'start'
end
# 0: still logged in
# nil: down
# other: normal
def w_end(time = nil)
if @status == 'start'
@status = 'end'
@wtmp.push(time)
$last.push(@wtmp)
@wtmp = []
end
end
def status
@status
end
end
def reset_entry(w_hash)
for k, v in w_hash
if v.status == 'start'
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
LTWTMP_FMT = 'A44A32A*'
if File.exist? '/vmlinuz'
WTMP = '/var/log/wtmp'
if File.exist? '/lib/libc.so.6'
LNAME_IN = 5
LHOST_IN = 6
LTIME_IN = 10
LWTMP_FMT = 'ssiA32A4A32A256ssllliiiiA20'
LWTMP_LEN = 384
end
TYPE_IN = 0
PID_IN = 2
LINE_IN = 3
TIME_IN = 5
NAME_IN = 6
HOST_IN = 7
WTMP_FMT = 'ssiA12A4LA8A16L'
TWTMP_FMT = 'A24LA*'
WTMP_LEN = 56
elsif File.exist? '/stand/vmunix'
WTMP = '/var/adm/wtmp'
TYPE_IN = 4
PID_IN = 3
LINE_IN = 2
TIME_IN = 8
NAME_IN = 0
HOST_IN = 9
WTMP_FMT = 'A8A4A12issssLA16l'
WTMP_LEN = 60
else
fail 'unknown system'
end
wtmp = File.open(WTMP)
w_hash = {}
w_start_time = 0
while wtmp_line = wtmp.read(WTMP_LEN)
user = wtmp_line.unpack(LTWTMP_FMT).indexes(1)[0]
if /^\w+$/ =~ user
wtmp_line += wtmp.read(LWTMP_LEN - WTMP_LEN)
arr = wtmp_line.unpack(LWTMP_FMT).indexes(TYPE_IN, LNAME_IN, LINE_IN, LHOST_IN, LTIME_IN)
else
arr = wtmp_line.unpack(WTMP_FMT).indexes(TYPE_IN, NAME_IN, LINE_IN, HOST_IN, TIME_IN)
end
key = arr[2]
if w_start_time == 0
w_start_time = arr[4]
end
case arr.shift
when 7
w = Wtmp.new
w.w_start(arr)
w_hash[key] = w
when 8, 6
if w_hash.key?(key)
w = w_hash[key]
w.w_end(arr[3])
w_hash.delete(key)
end
when 2
reboot(w_hash, 'reboot', 'system boot', '', arr[3])
w_hash = {}
when 1
reset_entry(w_hash)
w_hash = {}
end
end
wtmp.close
for k, v in w_hash
if v.status == 'start'
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, Time.at(w_start_time).strftime('%c'))
exit
「解説」よりは、言い訳だったりします。
ちょっとグチャグチャしています。 まだ練り込みが足りないですね。 基本的には、一つのセッションを「オブジェクト」として扱っています。 セッションは「開始」「終了」で一つの情報になります。 オブジェクトは、この情報を表示するためのものです。 表示可能な条件ができたら配列に格納しています。
今回の方法でよかった点は、「オブジェクト」の対象を一つのセッションで扱うので、「開始」「終了」が単純に扱えること。 例外的にシステムがリブートするなどが起きた場合には、現在のステータスで「開始」の情報だけが入っているものを拾いだして(結局は残り全部だけれど)まとめ処理すればいいこと。 など、処理を単純化できました。 これがオブジェクト単位に考えない場合? データ構造をハッシュ(連想配列)にして、キーの処理を... などとよく分からない状態に陥りやすくなります。
よりよくするために、もうちょっと最後まで(つまり表示の部分まで)オブジェクトとしてセッションを扱うと、よかったかなとおもっています。 ちょっと単目的にしたので、作りやすかったということはあるのですが、扱うオブジェクトをちゃんと最後まで...
wtmp のフォーマットについてはオンラインマニュアル wtmp(5) を参照ください。
libc やシステムによりフォーマットが違うので注意してください。
また内容確認には hd.rb 「
Ruby で hex dump
」 が活躍します。
wtmp には、セッションの記録が残ります。
私は、わりとこの情報を重要視しているので、C でも Perl でも似たようなものを作ってきました。
最初は、wtmp の情報から、last コマンドと同じ出力を得るために悩みました。
いまとなっては、なれもあって作成するのに短時間で済みましたが、今回は Ruby のおかげでより単純に考えることができて、とても楽でした。
Ruby のような手軽に扱える OOPL(OOSL?) があるのは、とても助かります。