Ruby: How to implement statfs syscall with Ruby Fiddle FFI
Ruby will retire its broken Kernel#syscall method in the future. In the current Ruby releases you cannot provide NUL octets to syscalls which breaks almost all syscalls using a struct to provide parameters to it (Ruby bug 1472). Luckily starting Ruby 1.9.2 there is Fiddle standard library that can be used to call foreign functions like C-library’s functions, including the wrappers for syscalls. Unfortunate part is that Fiddle is still (2.1.2) poorly documented and there are very few sites with examples to implement more complex cases than calling sin(), strcpy(), or strdup(). Now I have implemented statfs() syscall using Fiddle:
#!/usr/bin/ruby
# gcc -x c -E <(echo "#include <sys/vfs.h>") | less
# struct statfs {
# long int f_type
# long int f_bsize
# unsigned long int f_blocks
# unsigned long int f_bfree
# unsigned long int f_bavail
# unsigned long int f_files
# unsigned long int f_ffree
# struct { int __val[2] } f_fsid
# long int f_namelen
# long int f_frsize
# long int f_flags
# long int f_spare[4]
# }
# extern int statfs (const char *__file, struct statfs *__buf)
require 'fiddle'
require 'fiddle/import'
module Sys
module Int
extend Fiddle::Importer
dlload Fiddle.dlopen(nil) # open myself, including libc
Struct_statfs = struct <<-EOS.gsub %r{^\s+}, ''
long type,
long bsize,
unsigned long blocks,
unsigned long bfree,
unsigned long bavail,
unsigned long files,
unsigned long ffree,
int fsid[2],
long namelen,
long frsize,
long flags,
long spare[4]
EOS
extern 'int statfs (const char *__file, struct statfs *__buf)'
end
def self.statfs(file)
buf = Int::Struct_statfs.malloc
val = Int::statfs(file, buf)
raise SystemCallError.new(Fiddle.last_error) unless val == 0
buf
end
end
buf = Sys.statfs('/tmp')
puts "type of file system: #{buf.type}"
puts "optimal transfer block size: #{buf.bsize}"
puts "total data blocks in file system: #{buf.blocks}"
puts "free blocks in fs: #{buf.bfree}"
puts "free blocks available to unprivileged user: #{buf.bavail}"
puts "total file nodes in file system: #{buf.files}"
puts "free file nodes in fs: #{buf.ffree}"
puts "file system id: #{buf.fsid}"
puts "maximum length of filenames: #{buf.namelen}"
puts "fragment size: #{buf.frsize}"
puts "flags: #{buf.flags}"
puts "spare: #{buf.spare}"
Output:
type of file system: 16914836
optimal transfer block size: 4096
total data blocks in file system: 983850
free blocks in fs: 972634
free blocks available to unprivileged user: 972634
total file nodes in file system: 983850
free file nodes in fs: 980728
file system id: [0, 0]
maximum length of filenames: 255
fragment size: 4096
flags: 32
spare: [0, 0, 0, 0]
Compared to the output of df command:
% df --block-size=4k /tmp
Filesystem 4K-blocks Used Available Use% Mounted on
tmpfs 983850 11216 972634 2% /tmp
% df -i /tmp
Filesystem Inodes IUsed IFree IUse% Mounted on
tmpfs 983850 3122 980728 1% /tmp
So this is fast way to get file system statistics like disk usage in Ruby. You don’t anymore need to use df command with ticks and regexps to get the stats.
It seems that in the future (2.2?) Ruby core will have statfs() as built-in: File::Statfs. In the meantime Fiddle version can be handy.