require 'dl'

module LibC
  require 'dl/import'
  extend DL::Importable
  dlload "/lib/libc.so.6"

  extern "int statvfs(const char *, struct statvfs *)"
  alias :sym_statvfs :statvfs
  module_function :sym_statvfs

  FLAGS_BITS = [
    [ :st_rdonly,        1 ], # Mount read-only.
    [ :st_nosuid,        2 ], # Ignore suid and sgid bits.
    [ :st_nodev,         4 ], # Disallow access to device special files.
    [ :st_noexec,        8 ], # Disallow program execution.
    [ :st_synchronous,  16 ], # Writes are synced at once.
    [ :st_mandlock,     64 ], # Allow mandatory locks on an FS.
    [ :st_write,       128 ], # Write on file/directory/symlink.
    [ :st_append,      256 ], # Append-only file.
    [ :st_immutable,   512 ], # Immutable file.
    [ :st_noatime,    1024 ], # Do not update access times.
    [ :st_nodiratime, 2048 ], # Do not update directory access times.
    [ :st_relatime,   4096 ], # Update atime relative to mtime/ctime.
  ].map {|name, value| eval "#{name.to_s.upcase} = #{value}"; [ name, value ]}

  def pretty_flags(bitfield, flagdef = [])
    flags = []
    flagdef.each {|flag, value|
      flags << flag if (bitfield & value) == value
    }
    flags
  end
  module_function :pretty_flags

  def statvfs(path)
    path = path.to_s
    raise "path not found" unless File.exists?(path)

    # Proto: [ :name, :sizecode, :ignore ]
    proto = [
      [ :f_bsize,    :L ],
      [ :f_frsize,   :L ],
      [ :f_blocks,   :L ],
      [ :f_bfree,    :L ],
      [ :f_bavail,   :L ],
      [ :f_files,    :L ],
      [ :f_ffree,    :L ],
      [ :f_favail,   :L ],
      [ :f_fsid,     :L ],
      [ :__f_unused, :L, true ],
      [ :f_flag,     :L ],
      [ :f_namemax,  :L ],
      [ :__f_spare0, :L, true ],
      [ :__f_spare1, :L, true ],
      [ :__f_spare2, :L, true ],
      [ :__f_spare3, :L, true ],
      [ :__f_spare4, :L, true ],
      [ :__f_spare5, :L, true ],
    ]
    names = proto.map {|thing| thing[0] }
    descr = proto.map {|thing| thing[1] }.join

    statvfs = DL.malloc(DL.sizeof(descr))
    statvfs.struct!(descr, *names)

    res = sym_statvfs(path, statvfs)

    raise "extern statvfs returned %u" % res unless res.zero?
    res = Hash[*names.map {|name| [ name, statvfs[name] ] if proto.select {|p| p[0] == name && p[2] }.empty? }.compact.flatten]
    res[:flags] = self.pretty_flags(res[:f_flag], FLAGS_BITS)
    return res
  end
  module_function :statvfs
end