#!/usr/bin/ruby

# Purpose:
# Allows to remap specified fs-tree to different place and let it
# be accessed & modified by specified user (uid)
#
# Example:
# Extremely handy to let others modify cgroup filesystem which can't
# be mounted under different uid...
#
# cd /dev
# mkdir cgroup cgroup.wfw
# mount cgroup cgroup -t cgroup -o devices
# ./fuse-rights-proxy.rb
# (elsewhere:) setuidgid wejn sh -c 'echo $$ > /dev/cgroup.wfw/tasks'
#
# Author: Wejn (wejn at box dot cz)
# License: MIT

require 'fusefs'

class FUSERightsProxy
    # constructor {{{
    def initialize(target, uid)
        unless FileTest.directory?(target)
            raise ArgumentError, "target: #{target} doesn't exist"
        end

        if uid.to_i.zero?
            raise ArgumentError, "uid should be non-root"
        end

        @target, @uid = target, uid.to_i
    end
    # }}}

    # dirlist {{{

    def contents(path)
        out = nil
        Dir.chdir(File.join(@target, path)) do
            out = Dir['*']
        end
        out
    end

    # }}}

    # tests {{{
    
    def directory?(path)
        FileTest.directory?(File.join(@target, path))
    end

    def file?(path)
        FileTest.file?(File.join(@target, path))
    end

    # }}}

    # file read {{{

    def read_file(path)
        File.open(File.join(@target, path)).read
    rescue Object
        nil
    end

    # }}}

    # file write {{{

    def can_write?(path)
        (FuseFS.uid.zero? || FuseFS.uid == @uid) && !directory?(path)
    end

    def write_to(path, str)
        File.open(File.join(@target, path), 'w') { |f| f.write(str) }
        true
    rescue Object
        nil
    end

    # }}}

    # file deletion {{{

    def can_delete?(path)
        # also needed for open(path, 'w')
        (FuseFS.uid.zero? || FuseFS.uid == @uid) && file?(path)
    end

    def delete(path)
        file?(path) && File.unlink(File.join(@target, path))
    rescue Object
        nil
    end

    # }}}

    # dir manipulation {{{

    def can_mkdir?(path)
        (FuseFS.uid.zero? || FuseFS.uid == @uid) && !directory?(path) && !file?(path)
    end

    def mkdir(path)
        Dir.mkdir(File.join(@target, path))
        true
    rescue Object
        nil
    end

    def can_rmdir?(path)
        (FuseFS.uid.zero? || FuseFS.uid == @uid) && directory?(path)
    end

    def rmdir(path)
        Dir.rmdir(File.join(@target, path))
        true
    rescue Object
        nil
    end

    # }}}
end

if __FILE__ == $0
    target = '/dev/cgroup'
    mpoint = '/dev/cgroup.wfw'
    uid = 1000

    unless FileTest.directory?(mpoint)
        STDERR.puts "mount point: #{mpoint} doesn't exist"
        exit 1
    end

    unless Process.uid.zero?
        STDERR.puts "must be run as root"
        exit 1
    end

    if File.open('/proc/filesystems').grep(/\sfuse/).empty?
        STDERR.puts "/proc/filesystems indicates lack of FUSE support;" +
            " modprobe it perhaps?"
        exit 1
    end

    unless File.open('/proc/mounts').grep(Regexp.new(mpoint)).empty?
        STDERR.puts "/proc/mounts indicates #{mpoint} is already mounted"
        exit 1
    end

    d = FUSERightsProxy.new(target, uid)
    FuseFS.set_root(d)
    FuseFS.mount_to(mpoint, "allow_other")
    begin
        FuseFS.run
    rescue Interrupt
    end
end