df コマンド

UNIX 系のシステムでの基本コマンド df の模造品です。 ファイルシステムの使用状態を示すコマンドです。 システムコールの statfs(2) を使用し、ファイルシステムの情報をとりだします。

ここで紹介しているスクリプトは、Linux 用です。 syscall で statfs(2) を呼びだすことで他の Unix 系システムでも応用ができると思います。

■ 使い方

デフォルトで、ディスクの使用状態を示します。 オプション「-i」で i-node の使用状態(ファイルの数)を示します。 また、引数として「ファイル名」「ディレクトリ名」を渡すことで、そのファイルシステムの情報を表示します。

オプション意味
-ii-node 情報の表示
-aサイズ 0 のファイルシステムも表示
-kブロックサイズを KB で表示
-m デフォルトブロックサイズを MB で表示
$ df
Filesystem          1m-blocks     Used Available Use% Mounted on
/dev/hda2                2957     1955      851   69%  /
/dev/hda5               15488    11777     2924   80%  /t
$ df -k
Filesystem          1k-blocks     Used Available Use% Mounted on
/dev/hda2             3028108  2002316   871972   69%  /
/dev/hda5            15860192 12059732  2994804   80%  /t
$ df -i
Filesystem           Inodes   IUsed   IFree  %IUsed Mounted on
/dev/hda2             384768   85229  299539   22%  /
/dev/hda5            2015232  166788 1848444    8%  /t

■ ソースコード

Web ブラウザでの表示上は Ruby のスクリプトですが、HTML の特殊文字をエスケープしています。 テキストとしてセーブしてご利用ください。

Linux 用です。

#! /usr/local/bin/ruby
# /home/tetsu/src/ruby/linux/df.rb
# Created: May 12,1999 Wednesday 05:05:31
# Author: tetsu(WATANABE Tetsuya)
RCS_ID = '$Id: df.rb,v 1.16 2004/09/15 06:28:08 tetsu Exp $'
# usage:

class StatFS
  def initialize(path)
    @path = path
    size = 64
    unpack_fmt = 'iiiiiiiiiiiiiiii'
    foo = ' ' * size
    # asm/unistd.h __NR_statfs 99
    syscall(99, @path, foo)
    type, @bsize, @blocks, @bfree, @bavail, @files, @ffree, @namelen =
      foo.unpack(unpack_fmt).values_at(0, 1, 2, 3, 4, 5, 6, 9)
    @type = 
      case type
      when 0xADFF then 'AFFS_SUPER_MAGIC'
      when 0x137D then 'EXT_SUPER_MAGIC'
      when 0xEF51 then 'EXT2_OLD_SUPER_MAGIC'
      when 0xEF53 then 'EXT2_SUPER_MAGIC'
      when 0xF995E849 then 'HPFS_SUPER_MAGIC'
      when 0x9660 then 'ISOFS_SUPER_MAGIC'
      when 0x137F then 'MINIX_SUPER_MAGIC'
      when 0x138F then 'MINIX_SUPER_MAGIC2'
      when 0x2468 then 'MINIX2_SUPER_MAGIC'
      when 0x2478 then 'MINIX2_SUPER_MAGIC2'
      when 0x4d44 then 'MSDOS_SUPER_MAGIC'
      when 0x564c then 'NCP_SUPER_MAGIC'
      when 0x6969 then 'NFS_SUPER_MAGIC'
      when 0x9fa0 then 'PROC_SUPER_MAGIC'
      when 0x517B then 'SMB_SUPER_MAGIC'
      when 0x012FF7B4 then 'XENIX_SUPER_MAGIC'
      when 0x012FF7B5 then 'SYSV4_SUPER_MAGIC'
      when 0x012FF7B6 then 'SYSV2_SUPER_MAGIC'
      when 0x012FF7B7 then 'COH_SUPER_MAGIC'
      when 0x00011954 then 'UFS_MAGIC'
      when 0x012FD16D then '_XIAFS_SUPER_MAGIC'
      end
  end
  attr :path
  attr :type
  attr :bsize
  attr :blocks
  attr :bfree
  attr :bavail
  attr :files
  attr :ffree
  attr :namelen
end

def pr_i(dev, foo)
  inodes_used = foo.files - foo.ffree
  printf("%-20s %7d %7d %7d  %3d%%  %s\n",
         dev,
         foo.files,
         inodes_used,
         foo.ffree,
         if foo.files == 0 then 0
         else (inodes_used.to_f * 100.0 / foo.files.to_f).round end,
         foo.path)
end

def pr_d(dev, foo, bs)
  blocks_used = foo.blocks - foo.bfree
  printf("%-20s %8d %8d %8d  %3d%%  %s\n",
         dev,
         foo.blocks * foo.bsize / bs,
         blocks_used * foo.bsize / bs,
         foo.bavail * foo.bsize / bs,
         if blocks_used + foo.bavail == 0 then 0 
         else (blocks_used.to_f * 100.0 / (blocks_used.to_f + foo.bavail.to_f)).round end,
         foo.path)
end

class Mtab
  def initialize
    @file = '/etc/mtab'
    @info = []
    f = File.open('/etc/mtab')
    while f.gets
      @info.push($_.split)
    end
    f.close
  end
  attr :file
  attr :info
end

def usage
  STDERR.print <<EOF
usage: #{$0} [-a] [-i] [file or dir]
   -a: all
   -i: i-node
   -m: MB blocks (default)
   -k: KB blocks
   -v: version
#{RCS_ID}
EOF
  exit 1
end

opt_i = false                   # inode
opt_a = false                   # all
opt_block_size = 1024 * 1024    # MB

while ARGV[0] =~ /^-/
  $_ = ARGV.shift
  if ~/i/ then opt_i = true end
  if ~/a/ then opt_a = true end
  if ~/k/ then opt_block_size = 1024 end
  if ~/m/ then opt_block_size = 1024 * 1024 end
  if ~/v/
    puts RCS_ID
    exit
  end
  usage if ~/[^\-aikmv]/
end

if opt_i
  puts 'Filesystem           Inodes   IUsed   IFree  %IUsed Mounted on'
else
  puts "Filesystem          #{if opt_block_size == 1024 then '1k' else '1m' end}-blocks     Used Available Use% Mounted on"
end

mt = Mtab.new

if ARGV.size == 0
  mt.info.each do |arr|
    dev, dir, type, opt = arr
    next if (dev == 'none' or dev == 'usbdevfs') and opt_a == false
    foo = StatFS.new(dir)
    if opt_i then pr_i(dev, foo) else pr_d(dev, foo, opt_block_size) end
  end
else
  dev_arr = []
  while path = ARGV.shift       # stat ARGV
    st = File.stat(path)
    dev_arr.push(st.dev)
  end

  dev2mtab = {}
  mt.info.each do |arr|         # stat FS
    dev, dir, type, opt = arr
    st = File.stat(dir)
    dev2mtab[st.dev] = arr
  end

  dev_arr.each do |id|          # print
    dev, dir, type, opt = dev2mtab[id]
    foo = StatFS.new(dir)
    if opt_i then pr_i(dev, foo) else pr_d(dev, foo, opt_block_size) end
  end
end

■ 解説

syscall を使用し、statfs(2) システムコールを呼びだしています。 このとき、statfs(2) が要求する引数を渡すことになりますが、その「場所」「入れもの」を用意します。 ここには構造体と同じメモリイメージが入っていますので、unpack で値をとりだします。 必要な値をインスタンス変数に入れています。 あとは、この値を使用してディスクの使用量を表示します。

使用しているアルゴリズムは、GNU fileutils に含まれる df.c とほぼ同じです。 一度作成してから確認しました。 使用率を表示する部分は、整数演算で行っています。 このため、小数点が切り捨てになっていて、本来の df コマンドと違っています。 df コマンドは、四捨五入しています。

本物の df コマンドは、もっとたくさんのオプションを持っています。 必要なものがあれば、追加してみてはいかがでしょうか? 表示も、最近なら MB 単位がよかったかな?

クラスの中に、メッセージ出力も含めてしまった方がよかったかな。 でも、「ファイルシステム名」をどこから引っ張りだす?

■ 履歴

1.16 2004/9/15

小数点の扱いを変更。

1.15 2004/9/15

小数点の扱いを変更。

1.14 2003/5/8

Ruby 1.9 系対応。 Array#select から Array#values_at へ。

1.13 2003/3/24

正規表現の書き直し

1.12 2002/8/13

usbdevfs 対応。

1.11 2002/4/11

いままで KB 単位のレポートでしたが、MB 単位をサポート。 最近のディスクは大きいので。

1.10 2002/3/11

コメントの追加。 syscall の 99 番がどのファイルに定義されているか。


渡辺哲也(WATANABE Tetsuya): Tetsuya.WATANABE atmark nifty.com